diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 7d33dd1..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request -about: Request feature to implement in this project -labels: 'Type: Enhancement' ---- - - - -### Please describe your feature request: - - -### Describe the use case of this feature: - diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md deleted file mode 100644 index d689b0f..0000000 --- a/.github/ISSUE_TEMPLATE/issue-report.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Issue report -about: Create a report to help us to improve the project -labels: 'Type: Bug' - ---- - - - - - -### simplehttpserver version: - - - - -### Current Behavior: - - -### Expected Behavior: - - -### Steps To Reproduce: - - - -### Anything else: - \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/reference-templates/bug-report-reference.yml b/.github/ISSUE_TEMPLATE/reference-templates/bug-report-reference.yml new file mode 100644 index 0000000..901a1fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/reference-templates/bug-report-reference.yml @@ -0,0 +1,79 @@ +name: Bug Report +description: Create a report to help us to improve the simplehttpserver. +title: "[BUG] ..." +labels: ["Type: Bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + For support requests, FAQs or "How to" questions, please use the [GitHub Discussions](https://github.com/projectdiscovery/simplehttpserver/discussions) section instead or join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#simplehttpserver** channel. + + :warning: **Issues missing important information may be closed without further investigation.** + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues. + required: true + - type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true + - type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: true + - type: textarea + attributes: + label: Steps To Reproduce + description: | + Steps to reproduce the behavior, for example, commands to run simplehttpserver. + + πŸ“ For a more detailed output that could help in troubleshooting, you may want to run simplehttpserver with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood. + + :warning: **Please redact any literal target hosts/URLs or other sensitive information.** + placeholder: | + 1. Run `simplehttpserver -t ...` + validations: + required: true + - type: textarea + attributes: + label: Relevant log output + description: | + Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + + πŸ“ For a more detailed output that could help in troubleshooting, you may want to run simplehttpserver with the **`-verbose`** or **`-debug`** flags. This will provide additional insights into what's happening under the hood. + + :warning: **Please redact any literal target hosts/URLs or other sensitive information.** + render: shell + - type: textarea + attributes: + label: Environment + description: | + Examples: + - **OS**: Ubuntu 20.04 + - **simplehttpserver** (`simplehttpserver -version`): v3.3.1 + - **Go** (`go version`): go1.22.0 _(only if you've installed it via the `go install` command)_ + value: | + - OS: + - simplehttpserver: + - Go: + render: markdown + validations: + required: true + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Templates? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml b/.github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml new file mode 100644 index 0000000..18fc17a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/reference-templates/feature-request-reference.yml @@ -0,0 +1,37 @@ +name: Feature Request +description: Request feature to implement in the simplehttpserver. +title: "[FEATURE] ..." +labels: ["Type: Enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request! + + 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/simplehttpserver/discussions/categories/ideas) instead. + + Join our [Discord server](https://discord.gg/projectdiscovery) to discuss the idea on the **#simplehttpserver** channel. + - type: textarea + attributes: + label: Describe your feature request + description: A clear and concise description of feature to implement. + validations: + required: true + - type: textarea + attributes: + label: Describe the use case of the feature + description: A clear and concise description of the feature request's motivation and the use-cases in which it could be useful. + validations: + required: true + - type: textarea + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + - type: textarea + attributes: + label: Additional context + description: Add any other context about the feature request here. + validations: + required: false diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000..273b6ac --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,9 @@ +addReviewers: true +reviewers: + - dogancanbakir + - dwisiswant0 + - mzack9999 + +numberOfReviewers: 1 +skipKeywords: + - '@dependabot' \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 69d9543..e5e1f5f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,13 +1,6 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - version: 2 updates: - - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" + - package-ecosystem: "gomod" directory: "/" schedule: interval: "weekly" @@ -15,9 +8,19 @@ updates: commit-message: prefix: "chore" include: "scope" + allow: + - dependency-name: "github.com/projectdiscovery/*" + groups: + modules: + patterns: ["github.com/projectdiscovery/*"] + security: + applies-to: "security-updates" + patterns: ["*"] + exclude-patterns: ["github.com/projectdiscovery/*"] + labels: + - "Type: Maintenance" - # Maintain dependencies for go modules - - package-ecosystem: "gomod" + - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" @@ -25,13 +28,21 @@ updates: commit-message: prefix: "chore" include: "scope" + groups: + workflows: + patterns: ["*"] + exclude-patterns: ["projectdiscovery/actions/*"] + labels: + - "Type: Maintenance" - # Maintain dependencies for docker - - package-ecosystem: "docker" - directory: "/" - schedule: - interval: "weekly" - target-branch: "dev" - commit-message: - prefix: "chore" - include: "scope" \ No newline at end of file +# # Maintain dependencies for docker +# - package-ecosystem: "docker" +# directory: "/" +# schedule: +# interval: "weekly" +# target-branch: "dev" +# commit-message: +# prefix: "chore" +# include: "scope" +# labels: +# - "Type: Maintenance" diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml new file mode 100644 index 0000000..302b94a --- /dev/null +++ b/.github/workflows/auto-merge.yaml @@ -0,0 +1,28 @@ +name: πŸ€– Auto Merge + +on: + pull_request_review: + types: [submitted] + workflow_run: + workflows: ["♾️ Compatibility Check"] + types: + - completed + +permissions: + pull-requests: write + issues: write + repository-projects: write + +jobs: + auto-merge: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - uses: actions/checkout@v7 + with: + token: ${{ secrets.DEPENDABOT_PAT }} + + - uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + github-token: ${{ secrets.DEPENDABOT_PAT }} + target: all \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index c9baa4e..687255d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -13,16 +13,11 @@ jobs: name: Test Builds runs-on: ubuntu-latest steps: - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.19 - - - name: Check out code - uses: actions/checkout@v3 + - uses: actions/checkout@v7 + - uses: projectdiscovery/actions/setup/go@v1 - name: Test - run: go test . + run: go test ./... working-directory: cmd/simplehttpserver/ - name: Build diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2863865..7f20180 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,16 +26,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 \ No newline at end of file + uses: github/codeql-action/analyze@v4 \ No newline at end of file diff --git a/.github/workflows/dockerhub-push.yml b/.github/workflows/dockerhub-push.yml index d141bff..a0e6411 100644 --- a/.github/workflows/dockerhub-push.yml +++ b/.github/workflows/dockerhub-push.yml @@ -13,22 +13,22 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v7 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v4 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v7 with: context: . platforms: linux/amd64,linux/arm64,linux/arm diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index 7f4c078..fe56a0d 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -12,15 +12,6 @@ jobs: name: Lint Test runs-on: ubuntu-latest steps: - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: 1.19 - - name: Checkout code - uses: actions/checkout@v3 - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v3.4.0 - with: - version: latest - args: --timeout 5m - working-directory: . \ No newline at end of file + - uses: actions/checkout@v7 + - uses: projectdiscovery/actions/setup/go@v1 + - uses: projectdiscovery/actions/golangci-lint/v2@v1 \ No newline at end of file diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml index 8581b0b..2e774e0 100644 --- a/.github/workflows/release-binary.yml +++ b/.github/workflows/release-binary.yml @@ -10,22 +10,20 @@ jobs: release: runs-on: ubuntu-latest steps: - - name: "Set up Go" - uses: actions/setup-go@v4 - with: - go-version: 1.19 - name: "Check out code" - uses: actions/checkout@v3 + uses: actions/checkout@v7 with: fetch-depth: 0 + - name: "Set up Go" + uses: projectdiscovery/actions/setup/go@v1 - name: "Create release on GitHub" - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v7 env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" with: - args: "release --rm-dist" + args: "release --clean" version: latest workdir: . \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4f9c0f3..0a0b7ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.exe cmd/simplehttpserver/simplehttpserver +simplehttpserver diff --git a/.goreleaser.yml b/.goreleaser.yml index e0a4795..2df503f 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + before: hooks: - go mod tidy @@ -16,9 +18,8 @@ builds: - arm64 archives: -- format: zip - replacements: - darwin: macOS +- formats: [zip] + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}' checksum: algorithm: sha256 diff --git a/Dockerfile b/Dockerfile index cbcb4d2..c2952b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,12 @@ -FROM golang:1.20.2-alpine as build-env -RUN go install -v github.com/projectdiscovery/simplehttpserver/cmd/simplehttpserver@latest +# Pinning apk package versions breaks the build once Alpine drops them; the base image tag is the pin. +# hadolint global ignore=DL3018 +FROM golang:1.27.3-alpine AS build-env +RUN apk add --no-cache git +WORKDIR /app +COPY . /app +RUN go mod download && go build ./cmd/simplehttpserver -FROM alpine:latest +FROM alpine:3.24 RUN apk add --no-cache bind-tools ca-certificates -COPY --from=build-env /go/bin/simplehttpserver /usr/local/bin/simplehttpserver -ENTRYPOINT ["simplehttpserver"] \ No newline at end of file +COPY --from=build-env /app/simplehttpserver /usr/local/bin/simplehttpserver +ENTRYPOINT ["simplehttpserver"] diff --git a/README.md b/README.md index 8d087ae..a95f253 100644 --- a/README.md +++ b/README.md @@ -29,18 +29,27 @@ SimpleHTTPserver is a go enhanced version of the well known python simplehttpser - HTTP/S Web Server - File Server with arbitrary directory support - HTTP request/response dump +- JSON request logging to file - Configurable ip address and listening port - Configurable HTTP/TCP server with customizable response via YAML template # Installing SimpleHTTPserver -SimpleHTTPserver requires **go1.17+** to install successfully. Run the following command to get the repo - +SimpleHTTPserver requires **go1.26.10+** to install successfully. Run the following command to get the repo - ```sh go install -v github.com/projectdiscovery/simplehttpserver/cmd/simplehttpserver@latest ``` +To update an installed binary to the latest release, run: + +```sh +simplehttpserver -update +``` + +The `-up` alias is also supported. The update command exits when finished without starting a server. + # Usage ```sh @@ -69,9 +78,11 @@ This will display help for the tool. Here are all the switches it supports. | `-basic-auth` | Basic auth (username:password) | `simplehttpserver -basic-auth user:password` | | `-realm` | Basic auth message | `simplehttpserver -realm "insert the credentials"` | | `-version` | Show version | `simplehttpserver -version` | +| `-up`, `-update` | Update to the latest release | `simplehttpserver -update` | | `-silent` | Show only results | `simplehttpserver -silent` | | `-py` | Emulate Python Style | `simplehttpserver -py` | | `-header` | HTTP response header (can be used multiple times) | `simplehttpserver -header 'X-Powered-By: Go'` | +| `-json-log` | JSON log file path for request logging | `simplehttpserver -json-log /tmp/requests.json` | ### Running simplehttpserver in the current folder @@ -121,6 +132,39 @@ To upload files use the following curl request with basic auth header: curl -v --user 'root:root' --upload-file file.txt http://localhost:8000/file.txt ``` +### Running simplehttpserver with JSON request logging + +This will run the tool and log all HTTP requests to a JSON file: + +```sh +simplehttpserver -json-log /tmp/requests.json + +2021/01/11 21:40:48 Serving . on http://0.0.0.0:8000/... +``` + +The JSON log file will contain structured request data including: +- Timestamp, remote address, HTTP method, URL, protocol +- Status code, response size, user agent +- Request headers, request body, response body + +Example JSON log entry: +```json +{ + "timestamp": "2021-01-11T21:41:15Z", + "remote_addr": "127.0.0.1:50181", + "method": "GET", + "url": "/", + "proto": "HTTP/1.1", + "status_code": 200, + "size": 383, + "user_agent": "curl/7.68.0", + "headers": { + "Accept": "*/*", + "User-Agent": "curl/7.68.0" + } +} +``` + ### Running TCP server with custom responses This will run the tool as TLS TCP server and enable custom responses based on YAML templates: diff --git a/go.mod b/go.mod index 01a2ea1..f8d334c 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,76 @@ module github.com/projectdiscovery/simplehttpserver -go 1.19 +go 1.26.10 require ( - github.com/fsnotify/fsnotify v1.6.0 + github.com/fsnotify/fsnotify v1.10.1 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 - github.com/projectdiscovery/gologger v1.1.8 + github.com/projectdiscovery/goflags v0.2.1 + github.com/projectdiscovery/gologger v1.1.73 github.com/projectdiscovery/sslcert v0.0.0-20210416140253-8f56bec1bb5e + github.com/projectdiscovery/utils v0.11.10 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/dsnet/compress v0.0.1 // indirect - github.com/golang/snappy v0.0.1 // indirect + aead.dev/minisign v0.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/alecthomas/chroma/v2 v2.14.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/charmbracelet/glamour v0.8.0 // indirect + github.com/charmbracelet/lipgloss v0.13.0 // indirect + github.com/charmbracelet/x/ansi v0.3.2 // indirect + github.com/cheggaaa/pb/v3 v3.1.4 // indirect + github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect + github.com/djherbis/times v1.6.0 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/go-github/v92 v92.0.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/mholt/archiver v3.1.1+incompatible // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/logrusorgru/aurora/v4 v4.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/nwaples/rardecode v1.1.0 // indirect - github.com/pierrec/lz4 v2.6.0+incompatible // indirect - github.com/ulikunitz/xz v0.5.7 // indirect - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect - gopkg.in/djherbis/times.v1 v1.3.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/projectdiscovery/blackrock v0.0.2 // indirect + github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect + github.com/shirou/gopsutil/v4 v4.26.3 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/yuin/goldmark v1.7.4 // indirect + github.com/yuin/goldmark-emoji v1.0.3 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zcalusic/sysinfo v1.0.2 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/net v0.58.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/term v0.45.0 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 866d4e0..e228bd5 100644 --- a/go.sum +++ b/go.sum @@ -1,70 +1,194 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs= +github.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw= +github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= +github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= +github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= +github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= +github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= +github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a h1:Ohw57yVY2dBTt+gsC6aZdteyxwlxfbtgkFEMTEkwgSw= +github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= -github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= +github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-github/v92 v92.0.0 h1:4vW4RVffwIvoEfIA4RX09mRSB47qQX557/2+HgeWRRg= +github.com/google/go-github/v92 v92.0.0/go.mod h1:w3CH62ZcmRfvW1cdXpyTztSOVMtdjxtKpgo0GouLmjY= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= -github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= +github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= +github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc= +github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= -github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg= +github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4 v2.6.0+incompatible h1:Ix9yFKn1nSPBLFl/yZknTp8TU5G4Ps0JDmguYK6iH1A= -github.com/pierrec/lz4 v2.6.0+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/projectdiscovery/gologger v1.1.8 h1:CFlCzGlqAhPqWIrAXBt1OVh5jkMs1qgoR/z4xhdzLNE= -github.com/projectdiscovery/gologger v1.1.8/go.mod h1:bNyVaC1U/NpJtFkJltcesn01NR3K8Hg6RsLVce6yvrw= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/projectdiscovery/blackrock v0.0.2 h1:mxXdu0uM8P2L2Qi210COlU8QiICPFW/Rxk5QUhlPO2k= +github.com/projectdiscovery/blackrock v0.0.2/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= +github.com/projectdiscovery/goflags v0.2.1 h1:XnSYvsCjUwHbZaaJUZ3YznCJPoXRk/IhTFP9Gecmx6M= +github.com/projectdiscovery/goflags v0.2.1/go.mod h1:7nAP1r2Dqgn/rwmOE3EWbZWUCEJKNIhVSBGpuzJAIns= +github.com/projectdiscovery/gologger v1.1.73 h1:WX2nzq29cL7HdvOHqIttacoid/5lMwapyM7ukGU1jFY= +github.com/projectdiscovery/gologger v1.1.73/go.mod h1:mJwODZcFDg70ihINpOvZevmBtgvpP8H9/l8Y+OPhZPY= +github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q= +github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI= github.com/projectdiscovery/sslcert v0.0.0-20210416140253-8f56bec1bb5e h1:IZa08TUGbU7I0HUb9QQt/8wuu2fPZqfnMXwWhtMxei8= github.com/projectdiscovery/sslcert v0.0.0-20210416140253-8f56bec1bb5e/go.mod h1:jSp8W5zIkNPxAqVdcoFlfv0K5cqogTe65fMinR0Fvuk= +github.com/projectdiscovery/utils v0.11.10 h1:6mtjWZifPT+UFXCeWSSzwb10KadrlkhYTm03jfhJ2EY= +github.com/projectdiscovery/utils v0.11.10/go.mod h1:T7KzyElivWJiPP+B4sXqkp1CcFe6O/P0qBn/MjDNa74= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= -github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= -github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4= +github.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc= +github.com/zcalusic/sysinfo v1.0.2/go.mod h1:kluzTYflRWo6/tXVMJPdEjShsbPpsFRyy+p1mBQPC30= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= -gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/runner/banner.go b/internal/runner/banner.go index ed4193f..f782d84 100644 --- a/internal/runner/banner.go +++ b/internal/runner/banner.go @@ -1,6 +1,9 @@ package runner -import "github.com/projectdiscovery/gologger" +import ( + "github.com/projectdiscovery/gologger" + updateutils "github.com/projectdiscovery/utils/update" +) const banner = ` _____ _ __ __ __________________ @@ -19,3 +22,11 @@ func showBanner() { gologger.Print().Msgf("%s\n", banner) gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") } + +// GetUpdateCallback returns a callback function that updates simplehttpserver. +func GetUpdateCallback() func() { + return func() { + showBanner() + updateutils.GetUpdateToolCallback("simplehttpserver", Version)() + } +} diff --git a/internal/runner/options.go b/internal/runner/options.go index 2890a04..6128d8c 100644 --- a/internal/runner/options.go +++ b/internal/runner/options.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "github.com/projectdiscovery/goflags" "github.com/projectdiscovery/gologger" "github.com/projectdiscovery/gologger/levels" "github.com/projectdiscovery/simplehttpserver/pkg/httpserver" @@ -27,6 +28,7 @@ type Options struct { Verbose bool EnableUpload bool EnableTCP bool + LogUA bool RulesFile string TCPWithTLS bool Version bool @@ -38,38 +40,60 @@ type Options struct { Python bool CORS bool HTTPHeaders HTTPHeaders + JSONLogFile string } // ParseOptions parses the command line options for application func ParseOptions() *Options { options := &Options{} - flag.StringVar(&options.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port") - flag.BoolVar(&options.EnableTCP, "tcp", false, "TCP Server") - flag.BoolVar(&options.TCPWithTLS, "tls", false, "Enable TCP TLS") - flag.StringVar(&options.RulesFile, "rules", "", "Rules yaml file") + flagSet := goflags.NewFlagSet() + flagSet.SetDescription(`SimpleHTTPserver is a go enhanced version of the well known python simplehttpserver with in addition a fully customizable TCP server, both supporting TLS`) + currentPath := "." if p, err := os.Getwd(); err == nil { currentPath = p } - flag.StringVar(&options.Folder, "path", currentPath, "Folder") - flag.BoolVar(&options.EnableUpload, "upload", false, "Enable upload via PUT") - flag.BoolVar(&options.HTTPS, "https", false, "HTTPS") - flag.StringVar(&options.TLSCertificate, "cert", "", "HTTPS Certificate") - flag.StringVar(&options.TLSKey, "key", "", "HTTPS Certificate Key") - flag.StringVar(&options.TLSDomain, "domain", "local.host", "Domain") - flag.BoolVar(&options.Verbose, "verbose", false, "Verbose") - flag.StringVar(&options.BasicAuth, "basic-auth", "", "Basic auth (username:password)") - flag.StringVar(&options.Realm, "realm", "Please enter username and password", "Realm") - flag.BoolVar(&options.Version, "version", false, "Show version of the software") - flag.BoolVar(&options.Silent, "silent", false, "Show only results in the output") - flag.BoolVar(&options.Sandbox, "sandbox", false, "Enable sandbox mode") - flag.BoolVar(&options.HTTP1Only, "http1", false, "Enable only HTTP1") - flag.IntVar(&options.MaxFileSize, "max-file-size", 50, "Max Upload File Size") - flag.IntVar(&options.MaxDumpBodySize, "max-dump-body-size", -1, "Max Dump Body Size") - flag.BoolVar(&options.Python, "py", false, "Emulate Python Style") - flag.BoolVar(&options.CORS, "cors", false, "Enable Cross-Origin Resource Sharing (CORS)") - flag.Var(&options.HTTPHeaders, "header", "Add HTTP Response Header (name: value), can be used multiple times") - flag.Parse() + + flagSet.CreateGroup("input", "Input", + flagSet.StringVar(&options.ListenAddress, "listen", "0.0.0.0:8000", "Address:Port"), + ) + + flagSet.CreateGroup("config", "Config", + flagSet.BoolVar(&options.EnableTCP, "tcp", false, "TCP Server"), + flagSet.BoolVar(&options.TCPWithTLS, "tls", false, "Enable TCP TLS"), + flagSet.StringVar(&options.RulesFile, "rules", "", "Rules yaml file"), + flagSet.StringVar(&options.Folder, "path", currentPath, "Folder"), + flagSet.BoolVar(&options.EnableUpload, "upload", false, "Enable upload via PUT"), + flagSet.BoolVar(&options.HTTPS, "https", false, "HTTPS"), + flagSet.StringVar(&options.TLSCertificate, "cert", "", "HTTPS Certificate"), + flagSet.StringVar(&options.TLSKey, "key", "", "HTTPS Certificate Key"), + flagSet.StringVar(&options.TLSDomain, "domain", "local.host", "Domain"), + flagSet.StringVar(&options.BasicAuth, "basic-auth", "", "Basic auth (username:password),"), + flagSet.StringVar(&options.Realm, "realm", "Please enter username and password", "Realm"), + flagSet.BoolVar(&options.Silent, "silent", false, "Show only results in the output"), + flagSet.BoolVar(&options.Sandbox, "sandbox", false, "Enable sandbox mode"), + flagSet.BoolVar(&options.HTTP1Only, "http1", false, "Enable only HTTP1"), + flagSet.IntVar(&options.MaxFileSize, "max-file-size", 50, "Max Upload File Size"), + flagSet.IntVar(&options.MaxDumpBodySize, "max-dump-body-size", -1, "Max Dump Body Size"), + flagSet.BoolVar(&options.Python, "py", false, "Emulate Python Style"), + flagSet.BoolVar(&options.CORS, "cors", false, "Enable Cross-Origin Resource Sharing (CORS)"), + flagSet.Var(&options.HTTPHeaders, "header", "Add HTTP Response Header (name: value), can be used multiple times"), + flagSet.StringVar(&options.JSONLogFile, "json-log", "", "JSON log file path for request logging"), + ) + + flagSet.CreateGroup("debug", "Debug", + flagSet.BoolVar(&options.Version, "version", false, "Show version of the software"), + flagSet.BoolVar(&options.Verbose, "verbose", false, "Verbose"), + flagSet.BoolVar(&options.LogUA, "log-ua", false, "Log User Agent"), + ) + + flagSet.CreateGroup("update", "Update", + flagSet.CallbackVarP(GetUpdateCallback(), "update", "up", "update simplehttpserver to latest version"), + ) + + if err := flagSet.Parse(); err != nil { + gologger.Fatal().Msgf("%v: parse error", err.Error()) + } // Read the inputs and configure the logging options.configureOutput() diff --git a/internal/runner/runner.go b/internal/runner/runner.go index dc63940..eecf8b8 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -46,7 +46,11 @@ func New(options *Options) (*Runner, error) { if err != nil { return nil, err } - defer watcher.Close() + defer func() { + if watcher != nil { + _ = watcher.Close() + } + }() r.serverTCP = serverTCP return &r, nil @@ -55,6 +59,7 @@ func New(options *Options) (*Runner, error) { httpServer, err := httpserver.New(&httpserver.Options{ Folder: r.options.Folder, EnableUpload: r.options.EnableUpload, + LogUA: r.options.LogUA, ListenAddress: r.options.ListenAddress, TLS: r.options.HTTPS, Certificate: r.options.TLSCertificate, @@ -71,6 +76,7 @@ func New(options *Options) (*Runner, error) { Python: r.options.Python, CORS: r.options.CORS, HTTPHeaders: r.options.HTTPHeaders, + JSONLogFile: r.options.JSONLogFile, }) if err != nil { return nil, err diff --git a/internal/runner/update_test.go b/internal/runner/update_test.go new file mode 100644 index 0000000..43e17c8 --- /dev/null +++ b/internal/runner/update_test.go @@ -0,0 +1,80 @@ +package runner + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +type updateTransport func(*http.Request) (*http.Response, error) + +func (f updateTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + +func TestUpdateFlags(t *testing.T) { + if updateFlag := os.Getenv("SIMPLEHTTPSERVER_TEST_UPDATE_FLAG"); updateFlag != "" { + // The shared updater exits the process, so run it in a child and + // intercept HTTP to avoid real downloads or replacing the executable. + t.Setenv("GITHUB_TOKEN", "") + http.DefaultTransport = updateTransport(func(req *http.Request) (*http.Response, error) { + if req.Method != http.MethodGet || req.URL.String() != "https://api.github.com/repos/projectdiscovery/simplehttpserver/releases/latest" { + t.Fatalf("unexpected update request: %s %s", req.Method, req.URL) + } + fmt.Println("requested simplehttpserver release") + status := http.StatusOK + body := fmt.Sprintf(`{"tag_name":"v%s"}`, Version) + if os.Getenv("SIMPLEHTTPSERVER_TEST_UPDATE_ERROR") == "1" { + status = http.StatusServiceUnavailable + body = `{"message":"service unavailable"}` + } + return &http.Response{ + StatusCode: status, + Header: http.Header{"Content-Type": []string{"application/json"}}, + Body: io.NopCloser(strings.NewReader(body)), + Request: req, + }, nil + }) + os.Args = []string{"simplehttpserver", updateFlag} + ParseOptions() + t.Fatal("update flag returned to server startup") + } + + for _, updateFlag := range []string{"-up", "-update"} { + for _, fails := range []bool{false, true} { + t.Run(fmt.Sprintf("%s/error=%t", updateFlag, fails), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestUpdateFlags$") + errorMode := "0" + if fails { + errorMode = "1" + } + cmd.Env = append(os.Environ(), + "SIMPLEHTTPSERVER_TEST_UPDATE_FLAG="+updateFlag, + "SIMPLEHTTPSERVER_TEST_UPDATE_ERROR="+errorMode, + ) + output, err := cmd.CombinedOutput() + if ctx.Err() != nil { + t.Fatalf("update command did not exit: %s", output) + } + if fails && err == nil || !fails && err != nil { + t.Fatalf("unexpected update exit: %v\n%s", err, output) + } + want := "simplehttpserver is already updated to latest version" + if fails { + want = "failed to download latest release" + } + if !strings.Contains(string(output), "requested simplehttpserver release") || !strings.Contains(string(output), want) { + t.Fatalf("missing expected updater output %q:\n%s", want, output) + } + }) + } + } +} diff --git a/internal/runner/watchdog.go b/internal/runner/watchdog.go index 2cdde4c..3686724 100644 --- a/internal/runner/watchdog.go +++ b/internal/runner/watchdog.go @@ -25,8 +25,8 @@ func watchFile(fname string, callback WatchEvent) (watcher *fsnotify.Watcher, er log.Println("err", err) } } - case <-watcher.Errors: - // ignore errors for now + case err := <-watcher.Errors: + log.Println("err", err) } } }() diff --git a/pkg/httpserver/httpserver.go b/pkg/httpserver/httpserver.go index 5dd65a6..27576de 100644 --- a/pkg/httpserver/httpserver.go +++ b/pkg/httpserver/httpserver.go @@ -14,6 +14,7 @@ import ( type Options struct { Folder string EnableUpload bool + LogUA bool // enable logging user agent ListenAddress string TLS bool Certificate string @@ -30,12 +31,14 @@ type Options struct { Python bool CORS bool HTTPHeaders []HTTPHeader + JSONLogFile string } // HTTPServer instance type HTTPServer struct { - options *Options - layers http.Handler + options *Options + layers http.Handler + jsonLogger *JSONLogger } // LayerHandler is the interface of all layer funcs @@ -46,6 +49,16 @@ func New(options *Options) (*HTTPServer, error) { var h HTTPServer EnableUpload = options.EnableUpload EnableVerbose = options.Verbose + EnableLogUA = options.LogUA + + // Initialize JSON logger if specified + if options.JSONLogFile != "" { + jsonLogger, err := NewJSONLogger(options.JSONLogFile) + if err != nil { + return nil, err + } + h.jsonLogger = jsonLogger + } folder, err := filepath.Abs(options.Folder) if err != nil { return nil, err @@ -60,6 +73,12 @@ func New(options *Options) (*HTTPServer, error) { dir = SandboxFileSystem{fs: http.Dir(options.Folder), RootFolder: options.Folder} } + // the python style handler reads the folder directly, without the + // sandbox file system + if options.Python && options.Sandbox { + return nil, errors.New("python style (-py) cannot be used with -sandbox") + } + var httpHandler http.Handler if options.Python { httpHandler = PythonStyle(dir.(http.Dir)) @@ -127,5 +146,8 @@ func (t *HTTPServer) ListenAndServeTLS() error { // Close the service func (t *HTTPServer) Close() error { + if t.jsonLogger != nil { + return t.jsonLogger.Close() + } return nil } diff --git a/pkg/httpserver/jsonlogger.go b/pkg/httpserver/jsonlogger.go new file mode 100644 index 0000000..960ad83 --- /dev/null +++ b/pkg/httpserver/jsonlogger.go @@ -0,0 +1,93 @@ +package httpserver + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "sync" + "time" +) + +// JSONLogEntry represents a single HTTP request log entry in JSON format +type JSONLogEntry struct { + Timestamp string `json:"timestamp"` + RemoteAddr string `json:"remote_addr"` + Method string `json:"method"` + URL string `json:"url"` + Proto string `json:"proto"` + StatusCode int `json:"status_code"` + Size int `json:"size"` + UserAgent string `json:"user_agent,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + RequestBody string `json:"request_body,omitempty"` + ResponseBody string `json:"response_body,omitempty"` +} + +// JSONLogger handles writing JSON log entries to a file +type JSONLogger struct { + file *os.File + mutex sync.Mutex + enable bool +} + +// NewJSONLogger creates a new JSON logger instance +func NewJSONLogger(filePath string) (*JSONLogger, error) { + if filePath == "" { + return &JSONLogger{enable: false}, nil + } + + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + if err != nil { + return nil, fmt.Errorf("failed to open JSON log file: %w", err) + } + + return &JSONLogger{ + file: file, + enable: true, + }, nil +} + +// LogRequest logs an HTTP request to the JSON file +func (jl *JSONLogger) LogRequest(r *http.Request, statusCode, size int, userAgent string, headers map[string]string, requestBody, responseBody string) error { + if !jl.enable { + return nil + } + + entry := JSONLogEntry{ + Timestamp: time.Now().Format(time.RFC3339), + RemoteAddr: r.RemoteAddr, + Method: r.Method, + URL: r.URL.String(), + Proto: r.Proto, + StatusCode: statusCode, + Size: size, + UserAgent: userAgent, + Headers: headers, + RequestBody: requestBody, + ResponseBody: responseBody, + } + + jl.mutex.Lock() + defer jl.mutex.Unlock() + + jsonData, err := json.Marshal(entry) + if err != nil { + return fmt.Errorf("failed to marshal JSON log entry: %w", err) + } + + _, err = jl.file.Write(append(jsonData, '\n')) + if err != nil { + return fmt.Errorf("failed to write JSON log entry: %w", err) + } + + return nil +} + +// Close closes the JSON log file +func (jl *JSONLogger) Close() error { + if jl.file != nil { + return jl.file.Close() + } + return nil +} diff --git a/pkg/httpserver/loglayer.go b/pkg/httpserver/loglayer.go index f3fb4f7..c4fb78d 100644 --- a/pkg/httpserver/loglayer.go +++ b/pkg/httpserver/loglayer.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httputil" "time" + "github.com/projectdiscovery/gologger" ) @@ -12,6 +13,7 @@ import ( var ( EnableUpload bool EnableVerbose bool + EnableLogUA bool ) func (t *HTTPServer) shouldDumpBody(bodysize int64) bool { @@ -29,12 +31,41 @@ func (t *HTTPServer) loglayer(handler http.Handler) http.Handler { lrw := newLoggingResponseWriter(w, t.options.MaxDumpBodySize) handler.ServeHTTP(lrw, r) + // Log to JSON file if JSON logger is enabled + if t.jsonLogger != nil { + // Extract headers + headers := make(map[string]string) + for name, values := range r.Header { + if len(values) > 0 { + headers[name] = values[0] + } + } + + // Extract request body from fullRequest + requestBody := "" + if len(fullRequest) > 0 { + // Find the double CRLF that separates headers from body + bodyStart := bytes.Index(fullRequest, []byte("\r\n\r\n")) + if bodyStart != -1 && bodyStart+4 < len(fullRequest) { + requestBody = string(fullRequest[bodyStart+4:]) + } + } + + // Log to JSON file + _ = t.jsonLogger.LogRequest(r, lrw.statusCode, lrw.Size, r.UserAgent(), headers, requestBody, string(lrw.Data)) + } + + // Continue with existing console logging if EnableVerbose { headers := new(bytes.Buffer) lrw.Header().Write(headers) //nolint gologger.Print().Msgf("\n[%s]\nRemote Address: %s\n%s\n%s %d %s\n%s\n%s\n", time.Now().Format("2006-01-02 15:04:05"), r.RemoteAddr, string(fullRequest), r.Proto, lrw.statusCode, http.StatusText(lrw.statusCode), headers.String(), string(lrw.Data)) } else { - gologger.Print().Msgf("[%s] %s \"%s %s %s\" %d %d", time.Now().Format("2006-01-02 15:04:05"), r.RemoteAddr, r.Method, r.URL, r.Proto, lrw.statusCode, lrw.Size) + if EnableLogUA { + gologger.Print().Msgf("[%s] %s \"%s %s %s\" %d %d - %s", time.Now().Format("2006-01-02 15:04:05"), r.RemoteAddr, r.Method, r.URL, r.Proto, lrw.statusCode, lrw.Size, r.UserAgent()) + } else { + gologger.Print().Msgf("[%s] %s \"%s %s %s\" %d %d", time.Now().Format("2006-01-02 15:04:05"), r.RemoteAddr, r.Method, r.URL, r.Proto, lrw.statusCode, lrw.Size) + } } }) } diff --git a/pkg/httpserver/pythonliststyle.go b/pkg/httpserver/pythonliststyle.go index ee6342f..1bda78b 100644 --- a/pkg/httpserver/pythonliststyle.go +++ b/pkg/httpserver/pythonliststyle.go @@ -3,6 +3,7 @@ package httpserver import ( "bytes" "fmt" + "html" "io" "net/http" "os" @@ -82,8 +83,10 @@ func (h *pythonStyleHandler) ServeHTTP(writer http.ResponseWriter, request *http http.ServeFile(writer, request, target) return } else { - _, _ = fmt.Fprintf(writer, htmlHeader, request.URL.Path) - _, _ = fmt.Fprintf(writer, "

Directory listing for %s

\n
\n", request.URL.Path) + // the path comes from the request: escape it before writing HTML + escapedPath := html.EscapeString(request.URL.Path) + _, _ = fmt.Fprintf(writer, htmlHeader, escapedPath) + _, _ = fmt.Fprintf(writer, "

Directory listing for %s

\n
\n", escapedPath) h.origWriter = writer http.ServeFile(h, request, target) _, _ = fmt.Fprint(writer, htmlFooter) diff --git a/test/pythonliststyle_test.go b/test/pythonliststyle_test.go index 0ee449a..f4c1eb9 100644 --- a/test/pythonliststyle_test.go +++ b/test/pythonliststyle_test.go @@ -74,3 +74,26 @@ func TestResponseNotFound(t *testing.T) { t.Errorf("want:\n%s\ngot:\n%s", want, got) } } + +func TestDirectoryListingEscapesPath(t *testing.T) { + py := httpserver.PythonStyle("./fixture/pythonliststyle") + + w := httptest.NewRecorder() + py.ServeHTTP(w, httptest.NewRequest("GET", "http://example.com/%3Cimg%20src=x%20onerror=alert(document.domain)%3E/..%2F", nil)) + b, _ := io.ReadAll(w.Result().Body) + + body := string(b) + if strings.Contains(body, "