mirror of
https://github.com/chainreactors/spray.git
synced 2026-10-24 08:54:30 +00:00
chore: normalize line endings (CRLF → LF)
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
1 parent
b4e9b10c84
commit
0a6132ca02
20 files changed
+2643
-2643
No files matched your search
+34
-34
@@ -1,34 +1,34 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Build
|
||||
run: go build ./...
|
||||
|
||||
- name: Test (race)
|
||||
run: go test ./core/pool/ ./core/baseline/ ./core/ihttp/ ./pkg/ -v -race -count=1 -timeout=120s
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./core/... ./pkg/... ./cmd/...
|
||||
continue-on-error: true
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Build
|
||||
run: go build ./...
|
||||
|
||||
- name: Test (race)
|
||||
run: go test ./core/pool/ ./core/baseline/ ./core/ihttp/ ./pkg/ -v -race -count=1 -timeout=120s
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./core/... ./pkg/... ./cmd/...
|
||||
continue-on-error: true
|
||||
@@ -1,40 +1,40 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Install upx
|
||||
run: sudo apt install upx -y
|
||||
continue-on-error: true
|
||||
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.20"
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GOPATH: "/home/runner/go"
|
||||
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Install upx
|
||||
run: sudo apt install upx -y
|
||||
continue-on-error: true
|
||||
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: "1.20"
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GOPATH: "/home/runner/go"
|
||||
|
||||
+19
-19
@@ -1,19 +1,19 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea/
|
||||
bin/
|
||||
*.stat
|
||||
spray
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
.idea/
|
||||
bin/
|
||||
*.stat
|
||||
spray
|
||||
+68
-68
@@ -1,68 +1,68 @@
|
||||
project_name: spray
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate
|
||||
|
||||
builds:
|
||||
-
|
||||
main: .
|
||||
binary: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
goos:
|
||||
- windows
|
||||
- linux
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
ldflags: "-s -w -X 'github.com/chainreactors/spray/cmd.ver=={{ .Tag }}'"
|
||||
flags:
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
no_unique_dist_dir: true
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
tags:
|
||||
- forceposix
|
||||
- osusergo
|
||||
- netgo
|
||||
|
||||
upx:
|
||||
-
|
||||
enabled: true
|
||||
goos: [linux, windows]
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
|
||||
archives:
|
||||
-
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
format: binary
|
||||
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_checksums.txt"
|
||||
|
||||
changelog:
|
||||
sort: desc
|
||||
filters:
|
||||
exclude:
|
||||
- '^MERGE'
|
||||
- "{{ .Tag }}"
|
||||
- "^docs"
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: chainreactors
|
||||
name: spray
|
||||
draft: true
|
||||
project_name: spray
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate
|
||||
|
||||
builds:
|
||||
-
|
||||
main: .
|
||||
binary: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
goos:
|
||||
- windows
|
||||
- linux
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
- arm64
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
- goos: darwin
|
||||
goarch: "386"
|
||||
ldflags: "-s -w -X 'github.com/chainreactors/spray/cmd.ver=={{ .Tag }}'"
|
||||
flags:
|
||||
- -trimpath
|
||||
asmflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
no_unique_dist_dir: true
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
tags:
|
||||
- forceposix
|
||||
- osusergo
|
||||
- netgo
|
||||
|
||||
upx:
|
||||
-
|
||||
enabled: true
|
||||
goos: [linux, windows]
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
|
||||
archives:
|
||||
-
|
||||
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
|
||||
format: binary
|
||||
|
||||
checksum:
|
||||
name_template: "{{ .ProjectName }}_checksums.txt"
|
||||
|
||||
changelog:
|
||||
sort: desc
|
||||
filters:
|
||||
exclude:
|
||||
- '^MERGE'
|
||||
- "{{ .Tag }}"
|
||||
- "^docs"
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: chainreactors
|
||||
name: spray
|
||||
draft: true
|
||||
@@ -1,128 +1,128 @@
|
||||
# SPRAY
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="#features">Features</a> •
|
||||
<a href="#quickstart">QuickStart</a> •
|
||||
<a href="#make">Make</a> •
|
||||
<a href="https://chainreactors.github.io/wiki/spray/">Wiki</a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
* 超强的性能, 在本地测试极限性能的场景下, 能超过ffuf与feroxbruster的性能50%以上. 实际情况受到网络的影响, 感受没有这么明确. 但在多目标下可以感受到明显的区别.
|
||||
* 基于掩码的字典生成
|
||||
* 基于规则的字典生成
|
||||
* 动态智能过滤, 自定义过滤策略
|
||||
* 全量[gogo](https://github.com/chainreactors/gogo)的指纹识别, 全量的[fingerprinthub](https://github.com/0x727/FingerprintHub),[wappalyzer](https://github.com/projectdiscovery/wappalyzergo)指纹
|
||||
* 自定义信息提取, 内置敏感信息提取规则
|
||||
* 自定义输出格式与内容
|
||||
* *nix的命令行设计, 轻松与其他工具联动
|
||||
* 多角度的自动被ban,被waf判断
|
||||
* 断点续传
|
||||
|
||||
## QuickStart
|
||||
|
||||
[**Document**](https://chainreactors.github.io/wiki/spray/start)
|
||||
|
||||
### 基本使用
|
||||
|
||||
**从字典中读取目录进行爆破**
|
||||
|
||||
`spray -u http://example.com -d wordlist1.txt -d wordlist2.txt`
|
||||
|
||||
**通过掩码生成字典进行爆破**
|
||||
|
||||
`spray -u http://example.com -w "/aaa/bbb{?l#4}/ccc"`
|
||||
|
||||
**通过规则生成字典爆破**
|
||||
|
||||
规则文件格式参考hashcat的字典生成规则
|
||||
|
||||
`spray -u http://example.com -r rule.txt -d 1.txt`
|
||||
|
||||
**批量爆破多个目标**
|
||||
|
||||
`spray -l url.txt -r rule.txt -d 1.txt`
|
||||
|
||||
**断点续传**
|
||||
|
||||
`spray --resume stat.json`
|
||||
|
||||
### 高级用法
|
||||
|
||||
**check-only 模式**
|
||||
|
||||
类似ehole/httpx这类对单页面信息收集的模式. 会有针对性的性能优化. 默认使用[templates](https://github.com/chainreactors/templates/tree/master/fingers)指纹库. 可以使用`--finger`打开第三方指纹库的匹配
|
||||
|
||||
`spray -l url.txt --check-only`
|
||||
|
||||
**启用拓展指纹识别**
|
||||
|
||||
会进行主动探测常见的指纹目录, 并额外启用fingerprinthub与wappalyzer拓展指纹库
|
||||
|
||||
`spray -u http://example.com --finger `
|
||||
|
||||
**启用爬虫**
|
||||
|
||||
`spray -u http://example.com --crawl`
|
||||
|
||||
**扫描备份文件与常见通用文件**
|
||||
|
||||
`spray -u http://example.com --bak --common`
|
||||
|
||||
**启用所有插件**
|
||||
|
||||
`spray -u http://example.com -a`
|
||||
|
||||
**被动url收集**
|
||||
|
||||
参见: https://github.com/chainreactors/urlfounder
|
||||
|
||||
## Wiki
|
||||
|
||||
详细用法请见[wiki](https://chainreactors.github.io/wiki/spray/)
|
||||
|
||||
https://chainreactors.github.io/wiki/spray/
|
||||
|
||||
## Make
|
||||
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/chainreactors/spray
|
||||
|
||||
cd spray
|
||||
|
||||
go mod tidy
|
||||
|
||||
go generate
|
||||
|
||||
go build .
|
||||
```
|
||||
|
||||
## Similar or related works
|
||||
|
||||
* [ffuf](https://github.com/ffuf/ffuf) 一款优秀的http fuzz工具, 与spray的功能有一定重合但并不完全相同
|
||||
* [feroxbuster](https://github.com/epi052/feroxbuster) 在编写spray之前我最常使用的目录爆破工具, 但因为批量扫描与过滤配置不便的原因选择自行编写
|
||||
* [dirsearch](https://github.com/maurosoria/dirsearch) 较早的目录爆破工具, 参考了部分字典生成与配色
|
||||
* [httpx](https://github.com/projectdiscovery/httpx) http信息收集功能, 参考了通过脚本语言编写任意过滤条件的功能
|
||||
* [gobuster](https://github.com/OJ/gobuster) 一款统一是go编写的爆破工具, 但不仅限于目录爆破
|
||||
|
||||
## TODO
|
||||
|
||||
1. [x] 模糊对比
|
||||
2. [x] 断点续传
|
||||
3. [x] 简易爬虫
|
||||
4. [x] 支持http2
|
||||
5. [ ] auto-tune, 自动调整并发数量
|
||||
6. [x] 可自定义的递归配置
|
||||
7. [x] 参考[feroxbuster](https://github.com/epi052/feroxbuster)的`--collect-backups`, 自动爆破有效目录的备份
|
||||
8. [x] 支持socks/http代理, 不建议使用, 优先级较低. 代理的keep-alive会带来严重的性能下降
|
||||
9. [ ] 云函数化, chainreactors工具链的通用分布式解决方案.
|
||||
|
||||
## Thanks
|
||||
|
||||
* [fuzzuli](https://github.com/musana/fuzzuli) 提供了一个备份文件字典生成思路
|
||||
* [fingerprinthub](https://github.com/0x727/FingerprintHub) 作为指纹库的补充
|
||||
* [wappalyzer](https://github.com/projectdiscovery/wappalyzergo) 作为指纹库补充
|
||||
# SPRAY
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="#features">Features</a> •
|
||||
<a href="#quickstart">QuickStart</a> •
|
||||
<a href="#make">Make</a> •
|
||||
<a href="https://chainreactors.github.io/wiki/spray/">Wiki</a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
|
||||
* 超强的性能, 在本地测试极限性能的场景下, 能超过ffuf与feroxbruster的性能50%以上. 实际情况受到网络的影响, 感受没有这么明确. 但在多目标下可以感受到明显的区别.
|
||||
* 基于掩码的字典生成
|
||||
* 基于规则的字典生成
|
||||
* 动态智能过滤, 自定义过滤策略
|
||||
* 全量[gogo](https://github.com/chainreactors/gogo)的指纹识别, 全量的[fingerprinthub](https://github.com/0x727/FingerprintHub),[wappalyzer](https://github.com/projectdiscovery/wappalyzergo)指纹
|
||||
* 自定义信息提取, 内置敏感信息提取规则
|
||||
* 自定义输出格式与内容
|
||||
* *nix的命令行设计, 轻松与其他工具联动
|
||||
* 多角度的自动被ban,被waf判断
|
||||
* 断点续传
|
||||
|
||||
## QuickStart
|
||||
|
||||
[**Document**](https://chainreactors.github.io/wiki/spray/start)
|
||||
|
||||
### 基本使用
|
||||
|
||||
**从字典中读取目录进行爆破**
|
||||
|
||||
`spray -u http://example.com -d wordlist1.txt -d wordlist2.txt`
|
||||
|
||||
**通过掩码生成字典进行爆破**
|
||||
|
||||
`spray -u http://example.com -w "/aaa/bbb{?l#4}/ccc"`
|
||||
|
||||
**通过规则生成字典爆破**
|
||||
|
||||
规则文件格式参考hashcat的字典生成规则
|
||||
|
||||
`spray -u http://example.com -r rule.txt -d 1.txt`
|
||||
|
||||
**批量爆破多个目标**
|
||||
|
||||
`spray -l url.txt -r rule.txt -d 1.txt`
|
||||
|
||||
**断点续传**
|
||||
|
||||
`spray --resume stat.json`
|
||||
|
||||
### 高级用法
|
||||
|
||||
**check-only 模式**
|
||||
|
||||
类似ehole/httpx这类对单页面信息收集的模式. 会有针对性的性能优化. 默认使用[templates](https://github.com/chainreactors/templates/tree/master/fingers)指纹库. 可以使用`--finger`打开第三方指纹库的匹配
|
||||
|
||||
`spray -l url.txt --check-only`
|
||||
|
||||
**启用拓展指纹识别**
|
||||
|
||||
会进行主动探测常见的指纹目录, 并额外启用fingerprinthub与wappalyzer拓展指纹库
|
||||
|
||||
`spray -u http://example.com --finger `
|
||||
|
||||
**启用爬虫**
|
||||
|
||||
`spray -u http://example.com --crawl`
|
||||
|
||||
**扫描备份文件与常见通用文件**
|
||||
|
||||
`spray -u http://example.com --bak --common`
|
||||
|
||||
**启用所有插件**
|
||||
|
||||
`spray -u http://example.com -a`
|
||||
|
||||
**被动url收集**
|
||||
|
||||
参见: https://github.com/chainreactors/urlfounder
|
||||
|
||||
## Wiki
|
||||
|
||||
详细用法请见[wiki](https://chainreactors.github.io/wiki/spray/)
|
||||
|
||||
https://chainreactors.github.io/wiki/spray/
|
||||
|
||||
## Make
|
||||
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/chainreactors/spray
|
||||
|
||||
cd spray
|
||||
|
||||
go mod tidy
|
||||
|
||||
go generate
|
||||
|
||||
go build .
|
||||
```
|
||||
|
||||
## Similar or related works
|
||||
|
||||
* [ffuf](https://github.com/ffuf/ffuf) 一款优秀的http fuzz工具, 与spray的功能有一定重合但并不完全相同
|
||||
* [feroxbuster](https://github.com/epi052/feroxbuster) 在编写spray之前我最常使用的目录爆破工具, 但因为批量扫描与过滤配置不便的原因选择自行编写
|
||||
* [dirsearch](https://github.com/maurosoria/dirsearch) 较早的目录爆破工具, 参考了部分字典生成与配色
|
||||
* [httpx](https://github.com/projectdiscovery/httpx) http信息收集功能, 参考了通过脚本语言编写任意过滤条件的功能
|
||||
* [gobuster](https://github.com/OJ/gobuster) 一款统一是go编写的爆破工具, 但不仅限于目录爆破
|
||||
|
||||
## TODO
|
||||
|
||||
1. [x] 模糊对比
|
||||
2. [x] 断点续传
|
||||
3. [x] 简易爬虫
|
||||
4. [x] 支持http2
|
||||
5. [ ] auto-tune, 自动调整并发数量
|
||||
6. [x] 可自定义的递归配置
|
||||
7. [x] 参考[feroxbuster](https://github.com/epi052/feroxbuster)的`--collect-backups`, 自动爆破有效目录的备份
|
||||
8. [x] 支持socks/http代理, 不建议使用, 优先级较低. 代理的keep-alive会带来严重的性能下降
|
||||
9. [ ] 云函数化, chainreactors工具链的通用分布式解决方案.
|
||||
|
||||
## Thanks
|
||||
|
||||
* [fuzzuli](https://github.com/musana/fuzzuli) 提供了一个备份文件字典生成思路
|
||||
* [fingerprinthub](https://github.com/0x727/FingerprintHub) 作为指纹库的补充
|
||||
* [wappalyzer](https://github.com/projectdiscovery/wappalyzergo) 作为指纹库补充
|
||||
* [dirsearch](https://github.com/maurosoria/dirsearch) 提供了默认字典
|
||||
+154
-154
@@ -1,156 +1,156 @@
|
||||
input:
|
||||
# Files, Multi,dict files, e.g.: -d 1.txt -d 2.txt
|
||||
dictionaries: []
|
||||
# Bool, no dictionary
|
||||
no-dict: false
|
||||
# String, word generate dsl, e.g.: -w test{?ld#4}
|
||||
word: ""
|
||||
# Files, rule files, e.g.: -r rule1.txt -r rule2.txt
|
||||
rules: []
|
||||
# Files, when found valid path , use append rule generator new word with current path
|
||||
append-rules: []
|
||||
# String, filter rule, e.g.: --rule-filter '>8 <4'
|
||||
filter-rule: ""
|
||||
# Files, when found valid path , use append file new word with current path
|
||||
append-files: []
|
||||
functions:
|
||||
# String, add extensions (separated by commas), e.g.: -e jsp,jspx
|
||||
extension: ""
|
||||
# Bool, force add extensions
|
||||
force-extension: false
|
||||
# String, exclude extensions (separated by commas), e.g.: --exclude-extension jsp,jspx
|
||||
exclude-extension: ""
|
||||
# String, remove extensions (separated by commas), e.g.: --remove-extension jsp,jspx
|
||||
remove-extension: ""
|
||||
# Bool, upper wordlist, e.g.: --uppercase
|
||||
upper: false
|
||||
# Bool, lower wordlist, e.g.: --lowercase
|
||||
lower: false
|
||||
# Strings, add prefix, e.g.: --prefix aaa --prefix bbb
|
||||
prefix: []
|
||||
# Strings, add suffix, e.g.: --suffix aaa --suffix bbb
|
||||
suffix: []
|
||||
# Strings, replace string, e.g.: --replace aaa:bbb --replace ccc:ddd
|
||||
replace: {}
|
||||
# String, skip word when generate. rule, e.g.: --skip aaa
|
||||
skip: []
|
||||
output:
|
||||
# String, custom match function, e.g.: --match 'current.Status != 200''
|
||||
match: ""
|
||||
# String, custom filter function, e.g.: --filter 'current.Body contains "hello"'
|
||||
filter: ""
|
||||
# String, open fuzzy output
|
||||
fuzzy: false
|
||||
# String, output filename
|
||||
output-file: ""
|
||||
# String, fuzzy output filename
|
||||
fuzzy-file: ""
|
||||
# String, dump all request, and write to filename
|
||||
dump-file: ""
|
||||
# Bool, dump all request
|
||||
dump: false
|
||||
# Bool, auto generator output and fuzzy filename
|
||||
auto-file: false
|
||||
# String, output format, e.g.: --format 1.json
|
||||
format: ""
|
||||
# String, output format
|
||||
output_probe: ""
|
||||
# Bool, Quiet
|
||||
quiet: false
|
||||
# Bool, no color
|
||||
no-color: false
|
||||
input:
|
||||
# Files, Multi,dict files, e.g.: -d 1.txt -d 2.txt
|
||||
dictionaries: []
|
||||
# Bool, no dictionary
|
||||
no-dict: false
|
||||
# String, word generate dsl, e.g.: -w test{?ld#4}
|
||||
word: ""
|
||||
# Files, rule files, e.g.: -r rule1.txt -r rule2.txt
|
||||
rules: []
|
||||
# Files, when found valid path , use append rule generator new word with current path
|
||||
append-rules: []
|
||||
# String, filter rule, e.g.: --rule-filter '>8 <4'
|
||||
filter-rule: ""
|
||||
# Files, when found valid path , use append file new word with current path
|
||||
append-files: []
|
||||
functions:
|
||||
# String, add extensions (separated by commas), e.g.: -e jsp,jspx
|
||||
extension: ""
|
||||
# Bool, force add extensions
|
||||
force-extension: false
|
||||
# String, exclude extensions (separated by commas), e.g.: --exclude-extension jsp,jspx
|
||||
exclude-extension: ""
|
||||
# String, remove extensions (separated by commas), e.g.: --remove-extension jsp,jspx
|
||||
remove-extension: ""
|
||||
# Bool, upper wordlist, e.g.: --uppercase
|
||||
upper: false
|
||||
# Bool, lower wordlist, e.g.: --lowercase
|
||||
lower: false
|
||||
# Strings, add prefix, e.g.: --prefix aaa --prefix bbb
|
||||
prefix: []
|
||||
# Strings, add suffix, e.g.: --suffix aaa --suffix bbb
|
||||
suffix: []
|
||||
# Strings, replace string, e.g.: --replace aaa:bbb --replace ccc:ddd
|
||||
replace: {}
|
||||
# String, skip word when generate. rule, e.g.: --skip aaa
|
||||
skip: []
|
||||
output:
|
||||
# String, custom match function, e.g.: --match 'current.Status != 200''
|
||||
match: ""
|
||||
# String, custom filter function, e.g.: --filter 'current.Body contains "hello"'
|
||||
filter: ""
|
||||
# String, open fuzzy output
|
||||
fuzzy: false
|
||||
# String, output filename
|
||||
output-file: ""
|
||||
# String, fuzzy output filename
|
||||
fuzzy-file: ""
|
||||
# String, dump all request, and write to filename
|
||||
dump-file: ""
|
||||
# Bool, dump all request
|
||||
dump: false
|
||||
# Bool, auto generator output and fuzzy filename
|
||||
auto-file: false
|
||||
# String, output format, e.g.: --format 1.json
|
||||
format: ""
|
||||
# String, output format
|
||||
output_probe: ""
|
||||
# Bool, Quiet
|
||||
quiet: false
|
||||
# Bool, no color
|
||||
no-color: false
|
||||
# Bool, No progress bar
|
||||
no-bar: false
|
||||
# Bool, No stat
|
||||
no-stat: false
|
||||
plugins:
|
||||
# Bool, enable all plugin
|
||||
all: false
|
||||
# Strings, extract response, e.g.: --extract js --extract ip --extract version:(.*?)
|
||||
extract: []
|
||||
# String, extract config filename
|
||||
extract-config: ""
|
||||
# Bool, enable recon
|
||||
recon: false
|
||||
# Bool, enable active finger detect
|
||||
finger: false
|
||||
# Bool, enable bak found
|
||||
bak: false
|
||||
# Bool, enable valid result bak found, equal --append-rule rule/filebak.txt
|
||||
file-bak: false
|
||||
# Bool, enable common file found
|
||||
common: false
|
||||
# Bool, enable crawl
|
||||
crawl: false
|
||||
# Int, crawl depth
|
||||
crawl-depth: 3
|
||||
request:
|
||||
# Strings, custom headers, e.g.: --headers 'Auth: example_auth'
|
||||
headers: []
|
||||
# String, custom user-agent, e.g.: --user-agent Custom
|
||||
useragent: ""
|
||||
# Bool, use random with default user-agent
|
||||
random-useragent: false
|
||||
# Strings, custom cookie
|
||||
cookies: []
|
||||
# Bool, read all response body
|
||||
read-all: false
|
||||
# Int, max response body length (kb), -1 read-all, 0 not read body, default 100k, e.g. --max-length 1000
|
||||
max-length: 100
|
||||
mode:
|
||||
# Int, request rate limit (rate/s), e.g.: --rate-limit 100
|
||||
rate-limit: 0
|
||||
# Bool, skip error break
|
||||
force: false
|
||||
# Bool, check only
|
||||
default: false
|
||||
# Bool, no scope
|
||||
no-scope: false
|
||||
# String, custom scope, e.g.: --scope *.example.com
|
||||
scope: []
|
||||
# String,custom recursive rule, e.g.: --recursive current.IsDir()
|
||||
recursive: current.IsDir()
|
||||
# Int, recursive depth
|
||||
depth: 0
|
||||
# String, custom index path
|
||||
index: /
|
||||
# String, custom random path
|
||||
random: ""
|
||||
# Int, check period when request
|
||||
check-period: 200
|
||||
# Int, check period when error
|
||||
error-period: 10
|
||||
# Int, break when the error exceeds the threshold
|
||||
error-threshold: 20
|
||||
# Strings (comma split),custom black status
|
||||
black-status: 400,410
|
||||
# Strings (comma split), custom white status
|
||||
white-status: 200
|
||||
# Strings (comma split), custom fuzzy status
|
||||
fuzzy-status: 500,501,502,503
|
||||
# Strings (comma split), custom unique status
|
||||
unique-status: 403,200,404
|
||||
# Bool, unique response
|
||||
unique: false
|
||||
# Int, retry count
|
||||
retry: 0
|
||||
sim-distance: 5
|
||||
misc:
|
||||
# String, path/host spray
|
||||
mod: path
|
||||
# String, Client type
|
||||
client: auto
|
||||
# Int, deadline (seconds)
|
||||
deadline: 999999
|
||||
# Int, timeout with request (seconds)
|
||||
timeout: 5
|
||||
# Int, Pool size
|
||||
pool: 5
|
||||
# Int, number of threads per pool
|
||||
thread: 20
|
||||
# Bool, output debug info
|
||||
debug: false
|
||||
# Bool, log verbose level ,default 0, level1: -v level2 -vv
|
||||
verbose: []
|
||||
# String, proxy address, e.g.: --proxy socks5://127.0.0.1:1080
|
||||
proxy: ""
|
||||
# Bool, No stat
|
||||
no-stat: false
|
||||
plugins:
|
||||
# Bool, enable all plugin
|
||||
all: false
|
||||
# Strings, extract response, e.g.: --extract js --extract ip --extract version:(.*?)
|
||||
extract: []
|
||||
# String, extract config filename
|
||||
extract-config: ""
|
||||
# Bool, enable recon
|
||||
recon: false
|
||||
# Bool, enable active finger detect
|
||||
finger: false
|
||||
# Bool, enable bak found
|
||||
bak: false
|
||||
# Bool, enable valid result bak found, equal --append-rule rule/filebak.txt
|
||||
file-bak: false
|
||||
# Bool, enable common file found
|
||||
common: false
|
||||
# Bool, enable crawl
|
||||
crawl: false
|
||||
# Int, crawl depth
|
||||
crawl-depth: 3
|
||||
request:
|
||||
# Strings, custom headers, e.g.: --headers 'Auth: example_auth'
|
||||
headers: []
|
||||
# String, custom user-agent, e.g.: --user-agent Custom
|
||||
useragent: ""
|
||||
# Bool, use random with default user-agent
|
||||
random-useragent: false
|
||||
# Strings, custom cookie
|
||||
cookies: []
|
||||
# Bool, read all response body
|
||||
read-all: false
|
||||
# Int, max response body length (kb), -1 read-all, 0 not read body, default 100k, e.g. --max-length 1000
|
||||
max-length: 100
|
||||
mode:
|
||||
# Int, request rate limit (rate/s), e.g.: --rate-limit 100
|
||||
rate-limit: 0
|
||||
# Bool, skip error break
|
||||
force: false
|
||||
# Bool, check only
|
||||
default: false
|
||||
# Bool, no scope
|
||||
no-scope: false
|
||||
# String, custom scope, e.g.: --scope *.example.com
|
||||
scope: []
|
||||
# String,custom recursive rule, e.g.: --recursive current.IsDir()
|
||||
recursive: current.IsDir()
|
||||
# Int, recursive depth
|
||||
depth: 0
|
||||
# String, custom index path
|
||||
index: /
|
||||
# String, custom random path
|
||||
random: ""
|
||||
# Int, check period when request
|
||||
check-period: 200
|
||||
# Int, check period when error
|
||||
error-period: 10
|
||||
# Int, break when the error exceeds the threshold
|
||||
error-threshold: 20
|
||||
# Strings (comma split),custom black status
|
||||
black-status: 400,410
|
||||
# Strings (comma split), custom white status
|
||||
white-status: 200
|
||||
# Strings (comma split), custom fuzzy status
|
||||
fuzzy-status: 500,501,502,503
|
||||
# Strings (comma split), custom unique status
|
||||
unique-status: 403,200,404
|
||||
# Bool, unique response
|
||||
unique: false
|
||||
# Int, retry count
|
||||
retry: 0
|
||||
sim-distance: 5
|
||||
misc:
|
||||
# String, path/host spray
|
||||
mod: path
|
||||
# String, Client type
|
||||
client: auto
|
||||
# Int, deadline (seconds)
|
||||
deadline: 999999
|
||||
# Int, timeout with request (seconds)
|
||||
timeout: 5
|
||||
# Int, Pool size
|
||||
pool: 5
|
||||
# Int, number of threads per pool
|
||||
thread: 20
|
||||
# Bool, output debug info
|
||||
debug: false
|
||||
# Bool, log verbose level ,default 0, level1: -v level2 -vv
|
||||
verbose: []
|
||||
# String, proxy address, e.g.: --proxy socks5://127.0.0.1:1080
|
||||
proxy: ""
|
||||
+278
-278
@@ -1,278 +1,278 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/spray/core/baseline"
|
||||
"github.com/chainreactors/spray/pkg"
|
||||
"github.com/chainreactors/words/mask"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TreeNode represents a node in the path tree
|
||||
type TreeNode struct {
|
||||
Name string
|
||||
Children map[string]*TreeNode
|
||||
Result *baseline.Baseline // nil for directory nodes
|
||||
IsLeaf bool
|
||||
}
|
||||
|
||||
// NewTreeNode creates a new tree node
|
||||
func NewTreeNode(name string) *TreeNode {
|
||||
return &TreeNode{
|
||||
Name: name,
|
||||
Children: make(map[string]*TreeNode),
|
||||
IsLeaf: false,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPath adds a path to the tree
|
||||
func (tn *TreeNode) AddPath(parts []string, result *baseline.Baseline) {
|
||||
if len(parts) == 0 {
|
||||
tn.Result = result
|
||||
tn.IsLeaf = true
|
||||
return
|
||||
}
|
||||
|
||||
part := parts[0]
|
||||
if part == "" {
|
||||
if len(parts) > 1 {
|
||||
tn.AddPath(parts[1:], result)
|
||||
} else {
|
||||
tn.Result = result
|
||||
tn.IsLeaf = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, exists := tn.Children[part]; !exists {
|
||||
tn.Children[part] = NewTreeNode(part)
|
||||
}
|
||||
|
||||
tn.Children[part].AddPath(parts[1:], result)
|
||||
}
|
||||
|
||||
// RenderTree renders the tree structure
|
||||
func (tn *TreeNode) RenderTree(prefix string, isLast bool, color bool) string {
|
||||
var sb strings.Builder
|
||||
|
||||
if tn.Name != "" {
|
||||
connector := "├── "
|
||||
if isLast {
|
||||
connector = "└── "
|
||||
}
|
||||
|
||||
sb.WriteString(prefix)
|
||||
sb.WriteString(connector)
|
||||
|
||||
if tn.IsLeaf && tn.Result != nil {
|
||||
// Leaf node with result
|
||||
if color {
|
||||
sb.WriteString(tn.Result.ColorString())
|
||||
} else {
|
||||
sb.WriteString(tn.Result.String())
|
||||
}
|
||||
} else {
|
||||
// Directory node
|
||||
sb.WriteString(tn.Name)
|
||||
if !strings.HasSuffix(tn.Name, "/") {
|
||||
sb.WriteString("/")
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
// Sort children for consistent output
|
||||
var keys []string
|
||||
for k := range tn.Children {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
child := tn.Children[key]
|
||||
isLastChild := i == len(keys)-1
|
||||
|
||||
newPrefix := prefix
|
||||
if tn.Name != "" {
|
||||
if isLast {
|
||||
newPrefix += " "
|
||||
} else {
|
||||
newPrefix += "│ "
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(child.RenderTree(newPrefix, isLastChild, color))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func Format(opts Option) {
|
||||
var content []byte
|
||||
var err error
|
||||
if opts.Format == "stdin" {
|
||||
content, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
content, err = os.ReadFile(opts.Format)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Group by host+path for deduplication.
|
||||
// Prefer the HTTP Host header when present so that
|
||||
// different virtual hosts on the same IP:port do not
|
||||
// get merged together.
|
||||
group := make(map[string]map[string]*baseline.Baseline)
|
||||
for _, line := range bytes.Split(bytes.TrimSpace(content), []byte("\n")) {
|
||||
var result baseline.Baseline
|
||||
err := json.Unmarshal(line, &result)
|
||||
if err != nil {
|
||||
logs.Log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
result.Url, err = url.Parse(result.UrlString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine host key: prefer Host header when available.
|
||||
hostKey := result.Host
|
||||
if hostKey == "" {
|
||||
hostKey = result.Url.Host
|
||||
// Normalize default ports when URL host does not include one.
|
||||
if result.Url.Port() == "" {
|
||||
if result.Url.Scheme == "https" {
|
||||
hostKey += ":443"
|
||||
} else if result.Url.Scheme == "http" {
|
||||
hostKey += ":80"
|
||||
}
|
||||
}
|
||||
}
|
||||
if hostKey == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := group[hostKey]; !exists {
|
||||
group[hostKey] = make(map[string]*baseline.Baseline)
|
||||
}
|
||||
group[hostKey][result.Path] = &result
|
||||
}
|
||||
|
||||
// Default to tree mode if not specified
|
||||
outputProbe := opts.OutputProbe
|
||||
if outputProbe == "" {
|
||||
outputProbe = "full"
|
||||
}
|
||||
|
||||
if outputProbe == "tree" {
|
||||
formatTree(group, opts)
|
||||
} else if outputProbe == "full" {
|
||||
formatFull(group, opts)
|
||||
} else {
|
||||
// Custom probe output
|
||||
formatProbe(group, opts, outputProbe)
|
||||
}
|
||||
}
|
||||
|
||||
// formatTree renders results in tree structure
|
||||
func formatTree(group map[string]map[string]*baseline.Baseline, opts Option) {
|
||||
// Sort host:port for consistent output
|
||||
var hosts []string
|
||||
for host := range group {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
sort.Strings(hosts)
|
||||
|
||||
for _, host := range hosts {
|
||||
results := group[host]
|
||||
|
||||
// Build tree for this host
|
||||
root := NewTreeNode("")
|
||||
for path, result := range results {
|
||||
if !opts.Fuzzy && result.IsFuzzy {
|
||||
continue
|
||||
}
|
||||
|
||||
// Split path into parts
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
if path == "/" {
|
||||
parts = []string{"/"}
|
||||
}
|
||||
|
||||
root.AddPath(parts, result)
|
||||
}
|
||||
|
||||
// Print host header
|
||||
logs.Log.Console(fmt.Sprintf("\n%s\n", host))
|
||||
|
||||
// Render tree
|
||||
logs.Log.Console(root.RenderTree("", true, !opts.NoColor))
|
||||
}
|
||||
}
|
||||
|
||||
// formatFull renders results in full/original format
|
||||
func formatFull(group map[string]map[string]*baseline.Baseline, opts Option) {
|
||||
for _, results := range group {
|
||||
for _, result := range results {
|
||||
if !opts.Fuzzy && result.IsFuzzy {
|
||||
continue
|
||||
}
|
||||
if !opts.NoColor {
|
||||
logs.Log.Console(result.ColorString() + "\n")
|
||||
} else {
|
||||
logs.Log.Console(result.String() + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatProbe renders results with custom probe fields
|
||||
func formatProbe(group map[string]map[string]*baseline.Baseline, opts Option, probeFields string) {
|
||||
probes := strings.Split(probeFields, ",")
|
||||
for _, results := range group {
|
||||
for _, result := range results {
|
||||
if !opts.Fuzzy && result.IsFuzzy {
|
||||
continue
|
||||
}
|
||||
logs.Log.Console(result.ProbeOutput(probes) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintPreset() {
|
||||
logs.Log.Console("internal rules:\n")
|
||||
for name, rule := range pkg.Rules {
|
||||
logs.Log.Consolef("\t%s\t%d rules\n", name, len(strings.Split(rule, "\n")))
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal dicts:\n")
|
||||
for name, dict := range pkg.Dicts {
|
||||
logs.Log.Consolef("\t%s\t%d items\n", name, len(dict))
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal words keyword:\n")
|
||||
for name, words := range mask.SpecialWords {
|
||||
logs.Log.Consolef("\t%s\t%d words\n", name, len(words))
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal extractor:\n")
|
||||
for name, _ := range pkg.ExtractRegexps {
|
||||
logs.Log.Consolef("\t%s\n", name)
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal fingers:\n")
|
||||
for name, engine := range pkg.FingerEngine.EnginesImpl {
|
||||
logs.Log.Consolef("\t%s\t%d fingerprints \n", name, engine.Len())
|
||||
}
|
||||
|
||||
logs.Log.Consolef("\nload %d active path\n", len(pkg.ActivePath))
|
||||
}
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/spray/core/baseline"
|
||||
"github.com/chainreactors/spray/pkg"
|
||||
"github.com/chainreactors/words/mask"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TreeNode represents a node in the path tree
|
||||
type TreeNode struct {
|
||||
Name string
|
||||
Children map[string]*TreeNode
|
||||
Result *baseline.Baseline // nil for directory nodes
|
||||
IsLeaf bool
|
||||
}
|
||||
|
||||
// NewTreeNode creates a new tree node
|
||||
func NewTreeNode(name string) *TreeNode {
|
||||
return &TreeNode{
|
||||
Name: name,
|
||||
Children: make(map[string]*TreeNode),
|
||||
IsLeaf: false,
|
||||
}
|
||||
}
|
||||
|
||||
// AddPath adds a path to the tree
|
||||
func (tn *TreeNode) AddPath(parts []string, result *baseline.Baseline) {
|
||||
if len(parts) == 0 {
|
||||
tn.Result = result
|
||||
tn.IsLeaf = true
|
||||
return
|
||||
}
|
||||
|
||||
part := parts[0]
|
||||
if part == "" {
|
||||
if len(parts) > 1 {
|
||||
tn.AddPath(parts[1:], result)
|
||||
} else {
|
||||
tn.Result = result
|
||||
tn.IsLeaf = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if _, exists := tn.Children[part]; !exists {
|
||||
tn.Children[part] = NewTreeNode(part)
|
||||
}
|
||||
|
||||
tn.Children[part].AddPath(parts[1:], result)
|
||||
}
|
||||
|
||||
// RenderTree renders the tree structure
|
||||
func (tn *TreeNode) RenderTree(prefix string, isLast bool, color bool) string {
|
||||
var sb strings.Builder
|
||||
|
||||
if tn.Name != "" {
|
||||
connector := "├── "
|
||||
if isLast {
|
||||
connector = "└── "
|
||||
}
|
||||
|
||||
sb.WriteString(prefix)
|
||||
sb.WriteString(connector)
|
||||
|
||||
if tn.IsLeaf && tn.Result != nil {
|
||||
// Leaf node with result
|
||||
if color {
|
||||
sb.WriteString(tn.Result.ColorString())
|
||||
} else {
|
||||
sb.WriteString(tn.Result.String())
|
||||
}
|
||||
} else {
|
||||
// Directory node
|
||||
sb.WriteString(tn.Name)
|
||||
if !strings.HasSuffix(tn.Name, "/") {
|
||||
sb.WriteString("/")
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
// Sort children for consistent output
|
||||
var keys []string
|
||||
for k := range tn.Children {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i, key := range keys {
|
||||
child := tn.Children[key]
|
||||
isLastChild := i == len(keys)-1
|
||||
|
||||
newPrefix := prefix
|
||||
if tn.Name != "" {
|
||||
if isLast {
|
||||
newPrefix += " "
|
||||
} else {
|
||||
newPrefix += "│ "
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(child.RenderTree(newPrefix, isLastChild, color))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func Format(opts Option) {
|
||||
var content []byte
|
||||
var err error
|
||||
if opts.Format == "stdin" {
|
||||
content, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
content, err = os.ReadFile(opts.Format)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Group by host+path for deduplication.
|
||||
// Prefer the HTTP Host header when present so that
|
||||
// different virtual hosts on the same IP:port do not
|
||||
// get merged together.
|
||||
group := make(map[string]map[string]*baseline.Baseline)
|
||||
for _, line := range bytes.Split(bytes.TrimSpace(content), []byte("\n")) {
|
||||
var result baseline.Baseline
|
||||
err := json.Unmarshal(line, &result)
|
||||
if err != nil {
|
||||
logs.Log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
result.Url, err = url.Parse(result.UrlString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine host key: prefer Host header when available.
|
||||
hostKey := result.Host
|
||||
if hostKey == "" {
|
||||
hostKey = result.Url.Host
|
||||
// Normalize default ports when URL host does not include one.
|
||||
if result.Url.Port() == "" {
|
||||
if result.Url.Scheme == "https" {
|
||||
hostKey += ":443"
|
||||
} else if result.Url.Scheme == "http" {
|
||||
hostKey += ":80"
|
||||
}
|
||||
}
|
||||
}
|
||||
if hostKey == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := group[hostKey]; !exists {
|
||||
group[hostKey] = make(map[string]*baseline.Baseline)
|
||||
}
|
||||
group[hostKey][result.Path] = &result
|
||||
}
|
||||
|
||||
// Default to tree mode if not specified
|
||||
outputProbe := opts.OutputProbe
|
||||
if outputProbe == "" {
|
||||
outputProbe = "full"
|
||||
}
|
||||
|
||||
if outputProbe == "tree" {
|
||||
formatTree(group, opts)
|
||||
} else if outputProbe == "full" {
|
||||
formatFull(group, opts)
|
||||
} else {
|
||||
// Custom probe output
|
||||
formatProbe(group, opts, outputProbe)
|
||||
}
|
||||
}
|
||||
|
||||
// formatTree renders results in tree structure
|
||||
func formatTree(group map[string]map[string]*baseline.Baseline, opts Option) {
|
||||
// Sort host:port for consistent output
|
||||
var hosts []string
|
||||
for host := range group {
|
||||
hosts = append(hosts, host)
|
||||
}
|
||||
sort.Strings(hosts)
|
||||
|
||||
for _, host := range hosts {
|
||||
results := group[host]
|
||||
|
||||
// Build tree for this host
|
||||
root := NewTreeNode("")
|
||||
for path, result := range results {
|
||||
if !opts.Fuzzy && result.IsFuzzy {
|
||||
continue
|
||||
}
|
||||
|
||||
// Split path into parts
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
if path == "/" {
|
||||
parts = []string{"/"}
|
||||
}
|
||||
|
||||
root.AddPath(parts, result)
|
||||
}
|
||||
|
||||
// Print host header
|
||||
logs.Log.Console(fmt.Sprintf("\n%s\n", host))
|
||||
|
||||
// Render tree
|
||||
logs.Log.Console(root.RenderTree("", true, !opts.NoColor))
|
||||
}
|
||||
}
|
||||
|
||||
// formatFull renders results in full/original format
|
||||
func formatFull(group map[string]map[string]*baseline.Baseline, opts Option) {
|
||||
for _, results := range group {
|
||||
for _, result := range results {
|
||||
if !opts.Fuzzy && result.IsFuzzy {
|
||||
continue
|
||||
}
|
||||
if !opts.NoColor {
|
||||
logs.Log.Console(result.ColorString() + "\n")
|
||||
} else {
|
||||
logs.Log.Console(result.String() + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// formatProbe renders results with custom probe fields
|
||||
func formatProbe(group map[string]map[string]*baseline.Baseline, opts Option, probeFields string) {
|
||||
probes := strings.Split(probeFields, ",")
|
||||
for _, results := range group {
|
||||
for _, result := range results {
|
||||
if !opts.Fuzzy && result.IsFuzzy {
|
||||
continue
|
||||
}
|
||||
logs.Log.Console(result.ProbeOutput(probes) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrintPreset() {
|
||||
logs.Log.Console("internal rules:\n")
|
||||
for name, rule := range pkg.Rules {
|
||||
logs.Log.Consolef("\t%s\t%d rules\n", name, len(strings.Split(rule, "\n")))
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal dicts:\n")
|
||||
for name, dict := range pkg.Dicts {
|
||||
logs.Log.Consolef("\t%s\t%d items\n", name, len(dict))
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal words keyword:\n")
|
||||
for name, words := range mask.SpecialWords {
|
||||
logs.Log.Consolef("\t%s\t%d words\n", name, len(words))
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal extractor:\n")
|
||||
for name, _ := range pkg.ExtractRegexps {
|
||||
logs.Log.Consolef("\t%s\n", name)
|
||||
}
|
||||
|
||||
logs.Log.Console("\ninternal fingers:\n")
|
||||
for name, engine := range pkg.FingerEngine.EnginesImpl {
|
||||
logs.Log.Consolef("\t%s\t%d fingerprints \n", name, engine.Len())
|
||||
}
|
||||
|
||||
logs.Log.Consolef("\nload %d active path\n", len(pkg.ActivePath))
|
||||
}
|
||||
+136
-136
@@ -1,136 +1,136 @@
|
||||
package ihttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/chainreactors/proxyclient"
|
||||
"github.com/valyala/fasthttp"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultMaxBodySize int64 = 1024 * 100 // 100k
|
||||
)
|
||||
|
||||
func CheckBodySize(size int64) bool {
|
||||
if DefaultMaxBodySize == -1 {
|
||||
return true
|
||||
}
|
||||
if DefaultMaxBodySize == 0 {
|
||||
return false
|
||||
}
|
||||
return size < DefaultMaxBodySize
|
||||
}
|
||||
|
||||
const (
|
||||
Auto = iota
|
||||
FAST
|
||||
STANDARD
|
||||
)
|
||||
|
||||
func NewClient(config *ClientConfig) *Client {
|
||||
var client *Client
|
||||
|
||||
if config.Type == FAST {
|
||||
client = &Client{
|
||||
fastClient: &fasthttp.Client{
|
||||
TLSConfig: &tls.Config{
|
||||
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Dial: customDialFunc(config.ProxyClient, config.Timeout),
|
||||
MaxConnsPerHost: config.Thread * 3 / 2,
|
||||
MaxIdleConnDuration: config.Timeout,
|
||||
//MaxConnWaitTimeout: time.Duration(timeout) * time.Second,
|
||||
ReadTimeout: config.Timeout,
|
||||
WriteTimeout: config.Timeout,
|
||||
ReadBufferSize: 16384, // 16k
|
||||
MaxResponseBodySize: int(DefaultMaxBodySize),
|
||||
NoDefaultUserAgentHeader: true,
|
||||
DisablePathNormalizing: true,
|
||||
DisableHeaderNamesNormalizing: true,
|
||||
},
|
||||
ClientConfig: config,
|
||||
}
|
||||
} else {
|
||||
client = &Client{
|
||||
standardClient: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: config.ProxyClient,
|
||||
TLSClientConfig: &tls.Config{
|
||||
Renegotiation: tls.RenegotiateNever,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TLSHandshakeTimeout: config.Timeout,
|
||||
MaxConnsPerHost: config.Thread * 3 / 2,
|
||||
IdleConnTimeout: config.Timeout,
|
||||
ReadBufferSize: 16384, // 16k
|
||||
},
|
||||
Timeout: config.Timeout,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
},
|
||||
ClientConfig: config,
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
Type int
|
||||
Timeout time.Duration
|
||||
Thread int
|
||||
ProxyClient proxyclient.Dial
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
fastClient *fasthttp.Client
|
||||
standardClient *http.Client
|
||||
*ClientConfig
|
||||
}
|
||||
|
||||
func (c *Client) TransToCheck() {
|
||||
if c.fastClient != nil {
|
||||
c.fastClient.MaxConnsPerHost = -1 // disable keepalive
|
||||
} else if c.standardClient != nil {
|
||||
c.standardClient.Transport.(*http.Transport).DisableKeepAlives = true // disable keepalive
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) FastDo(req *fasthttp.Request) (*fasthttp.Response, error) {
|
||||
resp := fasthttp.AcquireResponse()
|
||||
err := c.fastClient.DoTimeout(req, resp, c.Timeout)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) StandardDo(req *http.Request) (*http.Response, error) {
|
||||
return c.standardClient.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) Do(req *Request) (*Response, error) {
|
||||
if c.fastClient != nil {
|
||||
resp, err := c.FastDo(req.FastRequest)
|
||||
return &Response{FastResponse: resp, ClientType: FAST}, err
|
||||
} else if c.standardClient != nil {
|
||||
resp, err := c.StandardDo(req.StandardRequest)
|
||||
return &Response{StandardResponse: resp, ClientType: STANDARD}, err
|
||||
} else {
|
||||
return nil, fmt.Errorf("not found client")
|
||||
}
|
||||
}
|
||||
|
||||
func customDialFunc(dialer proxyclient.Dial, timeout time.Duration) fasthttp.DialFunc {
|
||||
if dialer == nil {
|
||||
return func(addr string) (net.Conn, error) {
|
||||
return fasthttp.DialTimeout(addr, timeout)
|
||||
}
|
||||
}
|
||||
return func(addr string) (net.Conn, error) {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
return dialer.DialContext(ctx, "tcp", addr)
|
||||
}
|
||||
}
|
||||
package ihttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/chainreactors/proxyclient"
|
||||
"github.com/valyala/fasthttp"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultMaxBodySize int64 = 1024 * 100 // 100k
|
||||
)
|
||||
|
||||
func CheckBodySize(size int64) bool {
|
||||
if DefaultMaxBodySize == -1 {
|
||||
return true
|
||||
}
|
||||
if DefaultMaxBodySize == 0 {
|
||||
return false
|
||||
}
|
||||
return size < DefaultMaxBodySize
|
||||
}
|
||||
|
||||
const (
|
||||
Auto = iota
|
||||
FAST
|
||||
STANDARD
|
||||
)
|
||||
|
||||
func NewClient(config *ClientConfig) *Client {
|
||||
var client *Client
|
||||
|
||||
if config.Type == FAST {
|
||||
client = &Client{
|
||||
fastClient: &fasthttp.Client{
|
||||
TLSConfig: &tls.Config{
|
||||
Renegotiation: tls.RenegotiateOnceAsClient,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
Dial: customDialFunc(config.ProxyClient, config.Timeout),
|
||||
MaxConnsPerHost: config.Thread * 3 / 2,
|
||||
MaxIdleConnDuration: config.Timeout,
|
||||
//MaxConnWaitTimeout: time.Duration(timeout) * time.Second,
|
||||
ReadTimeout: config.Timeout,
|
||||
WriteTimeout: config.Timeout,
|
||||
ReadBufferSize: 16384, // 16k
|
||||
MaxResponseBodySize: int(DefaultMaxBodySize),
|
||||
NoDefaultUserAgentHeader: true,
|
||||
DisablePathNormalizing: true,
|
||||
DisableHeaderNamesNormalizing: true,
|
||||
},
|
||||
ClientConfig: config,
|
||||
}
|
||||
} else {
|
||||
client = &Client{
|
||||
standardClient: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: config.ProxyClient,
|
||||
TLSClientConfig: &tls.Config{
|
||||
Renegotiation: tls.RenegotiateNever,
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TLSHandshakeTimeout: config.Timeout,
|
||||
MaxConnsPerHost: config.Thread * 3 / 2,
|
||||
IdleConnTimeout: config.Timeout,
|
||||
ReadBufferSize: 16384, // 16k
|
||||
},
|
||||
Timeout: config.Timeout,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
},
|
||||
ClientConfig: config,
|
||||
}
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
Type int
|
||||
Timeout time.Duration
|
||||
Thread int
|
||||
ProxyClient proxyclient.Dial
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
fastClient *fasthttp.Client
|
||||
standardClient *http.Client
|
||||
*ClientConfig
|
||||
}
|
||||
|
||||
func (c *Client) TransToCheck() {
|
||||
if c.fastClient != nil {
|
||||
c.fastClient.MaxConnsPerHost = -1 // disable keepalive
|
||||
} else if c.standardClient != nil {
|
||||
c.standardClient.Transport.(*http.Transport).DisableKeepAlives = true // disable keepalive
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) FastDo(req *fasthttp.Request) (*fasthttp.Response, error) {
|
||||
resp := fasthttp.AcquireResponse()
|
||||
err := c.fastClient.DoTimeout(req, resp, c.Timeout)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *Client) StandardDo(req *http.Request) (*http.Response, error) {
|
||||
return c.standardClient.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) Do(req *Request) (*Response, error) {
|
||||
if c.fastClient != nil {
|
||||
resp, err := c.FastDo(req.FastRequest)
|
||||
return &Response{FastResponse: resp, ClientType: FAST}, err
|
||||
} else if c.standardClient != nil {
|
||||
resp, err := c.StandardDo(req.StandardRequest)
|
||||
return &Response{StandardResponse: resp, ClientType: STANDARD}, err
|
||||
} else {
|
||||
return nil, fmt.Errorf("not found client")
|
||||
}
|
||||
}
|
||||
|
||||
func customDialFunc(dialer proxyclient.Dial, timeout time.Duration) fasthttp.DialFunc {
|
||||
if dialer == nil {
|
||||
return func(addr string) (net.Conn, error) {
|
||||
return fasthttp.DialTimeout(addr, timeout)
|
||||
}
|
||||
}
|
||||
return func(addr string) (net.Conn, error) {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
return dialer.DialContext(ctx, "tcp", addr)
|
||||
}
|
||||
}
|
||||
+110
-110
@@ -1,110 +1,110 @@
|
||||
package ihttp
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/utils/httputils"
|
||||
"github.com/valyala/fasthttp"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
StandardResponse *http.Response
|
||||
FastResponse *fasthttp.Response
|
||||
ClientType int
|
||||
}
|
||||
|
||||
func (r *Response) StatusCode() int {
|
||||
if r.FastResponse != nil {
|
||||
return r.FastResponse.StatusCode()
|
||||
} else if r.StandardResponse != nil {
|
||||
return r.StandardResponse.StatusCode
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Body() []byte {
|
||||
if r.FastResponse != nil {
|
||||
return r.FastResponse.Body()
|
||||
} else if r.StandardResponse != nil {
|
||||
if r.StandardResponse.ContentLength == -1 {
|
||||
body, err := io.ReadAll(r.StandardResponse.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return body
|
||||
} else {
|
||||
var body []byte
|
||||
if r.StandardResponse.ContentLength > 0 && CheckBodySize(r.StandardResponse.ContentLength) {
|
||||
body = make([]byte, r.StandardResponse.ContentLength)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(r.StandardResponse.Body, body)
|
||||
_ = r.StandardResponse.Body.Close()
|
||||
if err == nil {
|
||||
return body
|
||||
} else if err == io.ErrUnexpectedEOF {
|
||||
return body[:n]
|
||||
} else if err == io.EOF {
|
||||
return nil
|
||||
} else {
|
||||
logs.Log.Error("readfull failed, " + err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) ContentLength() int64 {
|
||||
if r.FastResponse != nil {
|
||||
return int64(r.FastResponse.Header.ContentLength())
|
||||
} else if r.StandardResponse != nil {
|
||||
return r.StandardResponse.ContentLength
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) ContentType() string {
|
||||
var t string
|
||||
if r.FastResponse != nil {
|
||||
t = string(r.FastResponse.Header.ContentType())
|
||||
} else if r.StandardResponse != nil {
|
||||
t = r.StandardResponse.Header.Get("Content-Type")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
if i := strings.Index(t, ";"); i > 0 {
|
||||
return t[:i]
|
||||
} else {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Header() []byte {
|
||||
if r.FastResponse != nil {
|
||||
return r.FastResponse.Header.Header()
|
||||
} else if r.StandardResponse != nil {
|
||||
return append(httputils.ReadRawHeader(r.StandardResponse), []byte("\r\n")...)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) GetHeader(key string) string {
|
||||
if r.FastResponse != nil {
|
||||
return string(r.FastResponse.Header.Peek(key))
|
||||
} else if r.StandardResponse != nil {
|
||||
return r.StandardResponse.Header.Get(key)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
package ihttp
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/utils/httputils"
|
||||
"github.com/valyala/fasthttp"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
StandardResponse *http.Response
|
||||
FastResponse *fasthttp.Response
|
||||
ClientType int
|
||||
}
|
||||
|
||||
func (r *Response) StatusCode() int {
|
||||
if r.FastResponse != nil {
|
||||
return r.FastResponse.StatusCode()
|
||||
} else if r.StandardResponse != nil {
|
||||
return r.StandardResponse.StatusCode
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Body() []byte {
|
||||
if r.FastResponse != nil {
|
||||
return r.FastResponse.Body()
|
||||
} else if r.StandardResponse != nil {
|
||||
if r.StandardResponse.ContentLength == -1 {
|
||||
body, err := io.ReadAll(r.StandardResponse.Body)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return body
|
||||
} else {
|
||||
var body []byte
|
||||
if r.StandardResponse.ContentLength > 0 && CheckBodySize(r.StandardResponse.ContentLength) {
|
||||
body = make([]byte, r.StandardResponse.ContentLength)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(r.StandardResponse.Body, body)
|
||||
_ = r.StandardResponse.Body.Close()
|
||||
if err == nil {
|
||||
return body
|
||||
} else if err == io.ErrUnexpectedEOF {
|
||||
return body[:n]
|
||||
} else if err == io.EOF {
|
||||
return nil
|
||||
} else {
|
||||
logs.Log.Error("readfull failed, " + err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) ContentLength() int64 {
|
||||
if r.FastResponse != nil {
|
||||
return int64(r.FastResponse.Header.ContentLength())
|
||||
} else if r.StandardResponse != nil {
|
||||
return r.StandardResponse.ContentLength
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) ContentType() string {
|
||||
var t string
|
||||
if r.FastResponse != nil {
|
||||
t = string(r.FastResponse.Header.ContentType())
|
||||
} else if r.StandardResponse != nil {
|
||||
t = r.StandardResponse.Header.Get("Content-Type")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
if i := strings.Index(t, ";"); i > 0 {
|
||||
return t[:i]
|
||||
} else {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) Header() []byte {
|
||||
if r.FastResponse != nil {
|
||||
return r.FastResponse.Header.Header()
|
||||
} else if r.StandardResponse != nil {
|
||||
return append(httputils.ReadRawHeader(r.StandardResponse), []byte("\r\n")...)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Response) GetHeader(key string) string {
|
||||
if r.FastResponse != nil {
|
||||
return string(r.FastResponse.Header.Peek(key))
|
||||
} else if r.StandardResponse != nil {
|
||||
return r.StandardResponse.Header.Get(key)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
+70
-70
@@ -1,70 +1,70 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/proxyclient"
|
||||
"github.com/chainreactors/spray/core/baseline"
|
||||
"github.com/chainreactors/spray/core/ihttp"
|
||||
"github.com/chainreactors/words"
|
||||
"github.com/chainreactors/words/rule"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
ProxyClient proxyclient.Dial
|
||||
Thread int
|
||||
Wordlist []string
|
||||
Timeout time.Duration
|
||||
ProcessCh chan *baseline.Baseline
|
||||
OutputCh chan *baseline.Baseline
|
||||
FuzzyCh chan *baseline.Baseline
|
||||
Outwg *sync.WaitGroup
|
||||
RateLimit int
|
||||
CheckPeriod int
|
||||
ErrPeriod int32
|
||||
BreakThreshold int32
|
||||
Mod SprayMod
|
||||
Request *ihttp.RequestConfig
|
||||
ClientType int
|
||||
MatchExpr *vm.Program
|
||||
FilterExpr *vm.Program
|
||||
RecuExpr *vm.Program
|
||||
AppendRule *rule.Program
|
||||
Fns []words.WordFunc
|
||||
AppendWords []string
|
||||
Fuzzy bool
|
||||
IgnoreWaf bool
|
||||
Crawl bool
|
||||
Scope []string
|
||||
Active bool
|
||||
Bak bool
|
||||
Fuzzuli bool
|
||||
Common bool
|
||||
RetryLimit int
|
||||
Random string
|
||||
Index string
|
||||
MaxRedirect int
|
||||
MaxCrawlDepth int
|
||||
MaxRecursionDepth int
|
||||
MaxAppendDepth int
|
||||
}
|
||||
|
||||
func NewBruteWords(config *Config, list []string) *words.Worder {
|
||||
word := words.NewWorderWithList(list)
|
||||
word.Fns = config.Fns
|
||||
word.Run()
|
||||
return word
|
||||
}
|
||||
|
||||
func NewBruteDSL(config *Config, dsl string, params [][]string) *words.Worder {
|
||||
word, err := words.NewWorderWithDsl(dsl, params, nil)
|
||||
if err != nil {
|
||||
logs.Log.Error(err.Error())
|
||||
}
|
||||
word.Fns = config.Fns
|
||||
word.Run()
|
||||
return word
|
||||
}
|
||||
package pool
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/proxyclient"
|
||||
"github.com/chainreactors/spray/core/baseline"
|
||||
"github.com/chainreactors/spray/core/ihttp"
|
||||
"github.com/chainreactors/words"
|
||||
"github.com/chainreactors/words/rule"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
ProxyClient proxyclient.Dial
|
||||
Thread int
|
||||
Wordlist []string
|
||||
Timeout time.Duration
|
||||
ProcessCh chan *baseline.Baseline
|
||||
OutputCh chan *baseline.Baseline
|
||||
FuzzyCh chan *baseline.Baseline
|
||||
Outwg *sync.WaitGroup
|
||||
RateLimit int
|
||||
CheckPeriod int
|
||||
ErrPeriod int32
|
||||
BreakThreshold int32
|
||||
Mod SprayMod
|
||||
Request *ihttp.RequestConfig
|
||||
ClientType int
|
||||
MatchExpr *vm.Program
|
||||
FilterExpr *vm.Program
|
||||
RecuExpr *vm.Program
|
||||
AppendRule *rule.Program
|
||||
Fns []words.WordFunc
|
||||
AppendWords []string
|
||||
Fuzzy bool
|
||||
IgnoreWaf bool
|
||||
Crawl bool
|
||||
Scope []string
|
||||
Active bool
|
||||
Bak bool
|
||||
Fuzzuli bool
|
||||
Common bool
|
||||
RetryLimit int
|
||||
Random string
|
||||
Index string
|
||||
MaxRedirect int
|
||||
MaxCrawlDepth int
|
||||
MaxRecursionDepth int
|
||||
MaxAppendDepth int
|
||||
}
|
||||
|
||||
func NewBruteWords(config *Config, list []string) *words.Worder {
|
||||
word := words.NewWorderWithList(list)
|
||||
word.Fns = config.Fns
|
||||
word.Run()
|
||||
return word
|
||||
}
|
||||
|
||||
func NewBruteDSL(config *Config, dsl string, params [][]string) *words.Worder {
|
||||
word, err := words.NewWorderWithDsl(dsl, params, nil)
|
||||
if err != nil {
|
||||
logs.Log.Error(err.Error())
|
||||
}
|
||||
word.Fns = config.Fns
|
||||
word.Run()
|
||||
return word
|
||||
}
|
||||
+526
-526
File diff suppressed because it is too large.
Load diff
+73
-73
@@ -1,73 +1,73 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/utils"
|
||||
"github.com/chainreactors/words/rule"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
baseUrl string
|
||||
depth int
|
||||
rule []rule.Expression
|
||||
origin *Origin
|
||||
}
|
||||
|
||||
func NewTaskGenerator(port string) *TaskGenerator {
|
||||
gen := &TaskGenerator{
|
||||
ports: utils.ParsePortsString(port),
|
||||
tasks: make(chan *Task),
|
||||
In: make(chan *Task),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for task := range gen.In {
|
||||
gen.tasks <- task
|
||||
}
|
||||
close(gen.tasks)
|
||||
}()
|
||||
return gen
|
||||
|
||||
}
|
||||
|
||||
type TaskGenerator struct {
|
||||
Name string
|
||||
ports []string
|
||||
tasks chan *Task
|
||||
In chan *Task
|
||||
}
|
||||
|
||||
func (gen *TaskGenerator) Run(baseurl string) {
|
||||
parsed, err := url.Parse(baseurl)
|
||||
if err != nil {
|
||||
logs.Log.Warnf("parse %s, %s ", baseurl, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if parsed.Scheme == "" {
|
||||
if parsed.Port() == "443" {
|
||||
parsed.Scheme = "https"
|
||||
} else {
|
||||
parsed.Scheme = "http"
|
||||
}
|
||||
}
|
||||
|
||||
if len(gen.ports) == 0 {
|
||||
gen.In <- &Task{baseUrl: parsed.String()}
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range gen.ports {
|
||||
if parsed.Host == "" {
|
||||
gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s", parsed.Scheme, parsed.Path, p)}
|
||||
} else {
|
||||
gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s/%s", parsed.Scheme, parsed.Host, p, parsed.Path)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gen *TaskGenerator) Close() {
|
||||
close(gen.tasks)
|
||||
}
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chainreactors/logs"
|
||||
"github.com/chainreactors/utils"
|
||||
"github.com/chainreactors/words/rule"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
baseUrl string
|
||||
depth int
|
||||
rule []rule.Expression
|
||||
origin *Origin
|
||||
}
|
||||
|
||||
func NewTaskGenerator(port string) *TaskGenerator {
|
||||
gen := &TaskGenerator{
|
||||
ports: utils.ParsePortsString(port),
|
||||
tasks: make(chan *Task),
|
||||
In: make(chan *Task),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for task := range gen.In {
|
||||
gen.tasks <- task
|
||||
}
|
||||
close(gen.tasks)
|
||||
}()
|
||||
return gen
|
||||
|
||||
}
|
||||
|
||||
type TaskGenerator struct {
|
||||
Name string
|
||||
ports []string
|
||||
tasks chan *Task
|
||||
In chan *Task
|
||||
}
|
||||
|
||||
func (gen *TaskGenerator) Run(baseurl string) {
|
||||
parsed, err := url.Parse(baseurl)
|
||||
if err != nil {
|
||||
logs.Log.Warnf("parse %s, %s ", baseurl, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if parsed.Scheme == "" {
|
||||
if parsed.Port() == "443" {
|
||||
parsed.Scheme = "https"
|
||||
} else {
|
||||
parsed.Scheme = "http"
|
||||
}
|
||||
}
|
||||
|
||||
if len(gen.ports) == 0 {
|
||||
gen.In <- &Task{baseUrl: parsed.String()}
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range gen.ports {
|
||||
if parsed.Host == "" {
|
||||
gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s", parsed.Scheme, parsed.Path, p)}
|
||||
} else {
|
||||
gen.In <- &Task{baseUrl: fmt.Sprintf("%s://%s:%s/%s", parsed.Scheme, parsed.Host, p, parsed.Path)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gen *TaskGenerator) Close() {
|
||||
close(gen.tasks)
|
||||
}
|
||||
+37
-37
@@ -1,37 +1,37 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/spray/pkg"
|
||||
"github.com/chainreactors/words"
|
||||
)
|
||||
|
||||
func NewOrigin(stat *pkg.Statistor) *Origin {
|
||||
return &Origin{Statistor: stat}
|
||||
}
|
||||
|
||||
type Origin struct {
|
||||
*pkg.Statistor
|
||||
sum int
|
||||
}
|
||||
|
||||
func (o *Origin) InitWorder(fns []words.WordFunc) (*words.Worder, error) {
|
||||
var worder *words.Worder
|
||||
wl, err := pkg.LoadWordlist(o.Word, o.Dictionaries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
worder = words.NewWorderWithList(wl)
|
||||
worder.Fns = fns
|
||||
rules, err := pkg.LoadRuleWithFiles(o.RuleFiles, o.RuleFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
worder.Rules = rules
|
||||
if len(rules) > 0 {
|
||||
o.sum = len(rules) * len(wl)
|
||||
} else {
|
||||
o.sum = len(wl)
|
||||
}
|
||||
|
||||
return worder, nil
|
||||
}
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/spray/pkg"
|
||||
"github.com/chainreactors/words"
|
||||
)
|
||||
|
||||
func NewOrigin(stat *pkg.Statistor) *Origin {
|
||||
return &Origin{Statistor: stat}
|
||||
}
|
||||
|
||||
type Origin struct {
|
||||
*pkg.Statistor
|
||||
sum int
|
||||
}
|
||||
|
||||
func (o *Origin) InitWorder(fns []words.WordFunc) (*words.Worder, error) {
|
||||
var worder *words.Worder
|
||||
wl, err := pkg.LoadWordlist(o.Word, o.Dictionaries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
worder = words.NewWorderWithList(wl)
|
||||
worder.Fns = fns
|
||||
rules, err := pkg.LoadRuleWithFiles(o.RuleFiles, o.RuleFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
worder.Rules = rules
|
||||
if len(rules) > 0 {
|
||||
o.sum = len(rules) * len(wl)
|
||||
} else {
|
||||
o.sum = len(wl)
|
||||
}
|
||||
|
||||
return worder, nil
|
||||
}
|
||||
+61
-61
@@ -1,61 +1,61 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/vbauerster/mpb/v8"
|
||||
"github.com/vbauerster/mpb/v8/decor"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewBar(u string, total int, stat *Statistor, p *mpb.Progress) *Bar {
|
||||
if p == nil {
|
||||
return &Bar{
|
||||
url: u,
|
||||
}
|
||||
}
|
||||
bar := p.AddBar(int64(total),
|
||||
mpb.BarFillerClearOnComplete(),
|
||||
mpb.BarRemoveOnComplete(),
|
||||
mpb.PrependDecorators(
|
||||
decor.Name(u, decor.WC{W: len(u) + 1, C: decor.DindentRight}), // 这里调整了装饰器的参数
|
||||
decor.NewAverageSpeed(0, "% .0f/s ", time.Now()),
|
||||
decor.Counters(0, "%d/%d"),
|
||||
decor.Any(func(s decor.Statistics) string {
|
||||
return fmt.Sprintf(" found: %d", stat.FoundNumber)
|
||||
}),
|
||||
),
|
||||
mpb.AppendDecorators(
|
||||
decor.Percentage(),
|
||||
decor.Elapsed(decor.ET_STYLE_GO, decor.WC{W: 4}),
|
||||
),
|
||||
)
|
||||
|
||||
return &Bar{
|
||||
url: u,
|
||||
bar: bar,
|
||||
//m: m,
|
||||
}
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
url string
|
||||
bar *mpb.Bar
|
||||
//m metrics.Meter
|
||||
}
|
||||
|
||||
func (bar *Bar) Done() {
|
||||
//bar.m.Mark(1)
|
||||
if bar.bar == nil {
|
||||
return
|
||||
}
|
||||
bar.bar.Increment()
|
||||
}
|
||||
|
||||
func (bar *Bar) Close() {
|
||||
//metrics.Unregister(bar.url)
|
||||
// 标记进度条为完成状态
|
||||
if bar.bar == nil {
|
||||
return
|
||||
}
|
||||
bar.bar.Abort(true)
|
||||
}
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/vbauerster/mpb/v8"
|
||||
"github.com/vbauerster/mpb/v8/decor"
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewBar(u string, total int, stat *Statistor, p *mpb.Progress) *Bar {
|
||||
if p == nil {
|
||||
return &Bar{
|
||||
url: u,
|
||||
}
|
||||
}
|
||||
bar := p.AddBar(int64(total),
|
||||
mpb.BarFillerClearOnComplete(),
|
||||
mpb.BarRemoveOnComplete(),
|
||||
mpb.PrependDecorators(
|
||||
decor.Name(u, decor.WC{W: len(u) + 1, C: decor.DindentRight}), // 这里调整了装饰器的参数
|
||||
decor.NewAverageSpeed(0, "% .0f/s ", time.Now()),
|
||||
decor.Counters(0, "%d/%d"),
|
||||
decor.Any(func(s decor.Statistics) string {
|
||||
return fmt.Sprintf(" found: %d", stat.FoundNumber)
|
||||
}),
|
||||
),
|
||||
mpb.AppendDecorators(
|
||||
decor.Percentage(),
|
||||
decor.Elapsed(decor.ET_STYLE_GO, decor.WC{W: 4}),
|
||||
),
|
||||
)
|
||||
|
||||
return &Bar{
|
||||
url: u,
|
||||
bar: bar,
|
||||
//m: m,
|
||||
}
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
url string
|
||||
bar *mpb.Bar
|
||||
//m metrics.Meter
|
||||
}
|
||||
|
||||
func (bar *Bar) Done() {
|
||||
//bar.m.Mark(1)
|
||||
if bar.bar == nil {
|
||||
return
|
||||
}
|
||||
bar.bar.Increment()
|
||||
}
|
||||
|
||||
func (bar *Bar) Close() {
|
||||
//metrics.Unregister(bar.url)
|
||||
// 标记进度条为完成状态
|
||||
if bar.bar == nil {
|
||||
return
|
||||
}
|
||||
bar.bar.Abort(true)
|
||||
}
|
||||
+41
-41
@@ -1,41 +1,41 @@
|
||||
package pkg
|
||||
|
||||
type ErrorType uint
|
||||
|
||||
const (
|
||||
NoErr ErrorType = iota
|
||||
ErrBadStatus
|
||||
ErrSameStatus
|
||||
ErrRequestFailed
|
||||
ErrWaf
|
||||
ErrRedirect
|
||||
ErrCompareFailed
|
||||
ErrCustomCompareFailed
|
||||
ErrCustomFilter
|
||||
ErrFuzzyCompareFailed
|
||||
ErrFuzzyRedirect
|
||||
ErrFuzzyNotUnique
|
||||
ErrUrlError
|
||||
ErrResponseError
|
||||
)
|
||||
|
||||
var ErrMap = map[ErrorType]string{
|
||||
NoErr: "",
|
||||
ErrBadStatus: "blacklist status",
|
||||
ErrSameStatus: "same status with random baseline",
|
||||
ErrRequestFailed: "request failed",
|
||||
ErrWaf: "maybe banned by waf",
|
||||
ErrRedirect: "duplicate redirect url",
|
||||
ErrCompareFailed: "compare failed",
|
||||
ErrCustomCompareFailed: "custom compare failed",
|
||||
ErrCustomFilter: "custom filtered",
|
||||
ErrFuzzyCompareFailed: "fuzzy compare failed",
|
||||
ErrFuzzyRedirect: "fuzzy redirect",
|
||||
ErrFuzzyNotUnique: "not unique",
|
||||
ErrUrlError: "url parse error",
|
||||
ErrResponseError: "response parse error",
|
||||
}
|
||||
|
||||
func (e ErrorType) Error() string {
|
||||
return ErrMap[e]
|
||||
}
|
||||
package pkg
|
||||
|
||||
type ErrorType uint
|
||||
|
||||
const (
|
||||
NoErr ErrorType = iota
|
||||
ErrBadStatus
|
||||
ErrSameStatus
|
||||
ErrRequestFailed
|
||||
ErrWaf
|
||||
ErrRedirect
|
||||
ErrCompareFailed
|
||||
ErrCustomCompareFailed
|
||||
ErrCustomFilter
|
||||
ErrFuzzyCompareFailed
|
||||
ErrFuzzyRedirect
|
||||
ErrFuzzyNotUnique
|
||||
ErrUrlError
|
||||
ErrResponseError
|
||||
)
|
||||
|
||||
var ErrMap = map[ErrorType]string{
|
||||
NoErr: "",
|
||||
ErrBadStatus: "blacklist status",
|
||||
ErrSameStatus: "same status with random baseline",
|
||||
ErrRequestFailed: "request failed",
|
||||
ErrWaf: "maybe banned by waf",
|
||||
ErrRedirect: "duplicate redirect url",
|
||||
ErrCompareFailed: "compare failed",
|
||||
ErrCustomCompareFailed: "custom compare failed",
|
||||
ErrCustomFilter: "custom filtered",
|
||||
ErrFuzzyCompareFailed: "fuzzy compare failed",
|
||||
ErrFuzzyRedirect: "fuzzy redirect",
|
||||
ErrFuzzyNotUnique: "not unique",
|
||||
ErrUrlError: "url parse error",
|
||||
ErrResponseError: "response parse error",
|
||||
}
|
||||
|
||||
func (e ErrorType) Error() string {
|
||||
return ErrMap[e]
|
||||
}
|
||||
+26
-26
@@ -1,26 +1,26 @@
|
||||
package pkg
|
||||
|
||||
import "strings"
|
||||
|
||||
var (
|
||||
SkipChar = "%SKIP%"
|
||||
EXTChar = "%EXT%"
|
||||
)
|
||||
|
||||
func ParseEXTPlaceholderFunc(exts []string) func(string) []string {
|
||||
return func(s string) []string {
|
||||
ss := make([]string, len(exts))
|
||||
var n int
|
||||
for i, e := range exts {
|
||||
if strings.Contains(s, EXTChar) {
|
||||
n++
|
||||
ss[i] = strings.Replace(s, EXTChar, e, -1)
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return []string{s}
|
||||
} else {
|
||||
return ss
|
||||
}
|
||||
}
|
||||
}
|
||||
package pkg
|
||||
|
||||
import "strings"
|
||||
|
||||
var (
|
||||
SkipChar = "%SKIP%"
|
||||
EXTChar = "%EXT%"
|
||||
)
|
||||
|
||||
func ParseEXTPlaceholderFunc(exts []string) func(string) []string {
|
||||
return func(s string) []string {
|
||||
ss := make([]string, len(exts))
|
||||
var n int
|
||||
for i, e := range exts {
|
||||
if strings.Contains(s, EXTChar) {
|
||||
n++
|
||||
ss[i] = strings.Replace(s, EXTChar, e, -1)
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return []string{s}
|
||||
} else {
|
||||
return ss
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-20
@@ -2,24 +2,24 @@
|
||||
// +build !emptytemplates
|
||||
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/utils/encode"
|
||||
)
|
||||
|
||||
var RandomDir = "/g8kZMwp4oeKsL2in"
|
||||
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/utils/encode"
|
||||
)
|
||||
|
||||
var RandomDir = "/g8kZMwp4oeKsL2in"
|
||||
|
||||
func loadEmbeddedConfig(typ string) []byte {
|
||||
if typ == "spray_rule" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("hFPNbqs8EN3zFEfJsMQYU5rwsfpa9RXuKrLktKQlTUsUkvvTxX32K48xcQJRJQRzxsdnPMOxOR3f1n/2puv+w2yOtenq1WH1Sal971wI/p56wN/nHvD3sQdkXBABlNlwju6tObRwBWxGp9ACOsX88cdTomQmk3x5nwdLvKo76C0G0l0i5eLOkSJAV7eYSiZZWSy83IhH6RSzcm+rnEKnfrdhAYYUgxRo0/d1hgK0BXV+ADEoB609jScx5ELuFfEWR4Je0AfBcA36ExReXnFwb5+gwaxclqOjmZCRlzkPP0gtikyeS4lgvwLVVxDOKBeZW0wbWMnxJomL+oqP1GQGTQaJJouHdZXkqsiVXXcG25qfJgKe24+PllN2hMYH4b/5y8Gc31VwBBE0KXy+4t4vBVKvKjAIeNP6eEimZ/PYeANte9dxkKsuYxHAGlqdybOo/n0cLmlvB8nn2IM+Qa+BX/4f5VcrBqOsbfHktSS7amLnwBgMOsEo2Hu5VfyGM1knBj15M5egJQdL14vV/Jb7wM+VajVVJwc99P82WJlFm2ZXr827na8zCt8Uevf/fBqClM+0oB3oxcOdLdL6wsJfdBHs+AI1oL2HR5Y8ePgK+mI7OG+JfhQd6BdoP4v+AQAA//8="))
|
||||
} else if typ == "spray_common" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("nJOxbtRAFEV7S/4HKxUUIM84m93hZ9DYHuI49tp4hjhJR5UWmkh0FOloqJDIKn+Tzf4G2sy7FxSlAKp3ZFv3zN55e+Hs9CZNsuxVpswqJymSJhWkQ9KCdERaklYkAzJ0GDoMHYYOQ4ehw9Bh6DB0GHHoPM9JiqRJBWlBOiItSSsSkxWTFZMVkxWT1SGJDkWHokPRoejQdGg6NB2aDk2HpkPToenQdGiTJv2wDo3cONpBN2gml2T0g3bQDZpBL2gFnewbqe3F31r+I/1xYlewKdgTbAl2hBuCHmSiXXSLZtErWkWnaBR9Sl4heYVKk9KevnXnQX75ZKcIlydjhGCn18eX5Ah4sJTp33cRSnsaYd5/us9e295J+DzPAq5UJL6bhiFI3ElwYnQ+PEkubSVU22BL6+XTWpJCj5M7EK4zDhvHQXbwr/v7+z7++Bcsn/m3yD18GLvB1pG74fhJVw/X37eb24frq/vNj+3Nz2cfZi/Uy/hie3N1v7mTj24/7b5+jLy7+7z79iVN1g53aP0IsIRzviM1pJ5UCdWdHLJycuWVx2xC36VJa8+sCFsIW6a3fga84ztQDHhEeyZHrDrrfZqMzSihYzMSCtICxIixCWnSeh4lTgcIODYe9K3/BQAA//8="))
|
||||
} else if typ == "spray_dict" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("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 truncated
|
||||
} else if typ == "extract" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("nFbdbts2FL4fsHfw3LQmLVPWT5Layo+srUXhFeiKtii28tCBIjGNm9gyTBlqIwrI/a4HbMD2DLvaC+Vi2GMMlH8kx0oWD0isw8PzfeeXlEht7I+4U/skvv6qVpvyj/zzRDhKrtVIrY7O43giUqNlZg4dAIjGzfVvUL+5/uP4EGHZBKAstVrWnpG5dHDUXFgcHzoHEmHKAPRPAtdXfEcAim0/o1BvsJx3qUE5/0DBW9rx4cFt+jlVjivxPQxVAHL8HNRoFbnkOOdud8syzaaXlXVa5bVZMKhvlGojnnY5ICePqLdzO5fbuc+huT93zVdvowTV+ZaNVuSPyuxFbx61dNUuK2svPbWODw96Oxs8aw0uggX4bBnnU3623F+bhJvrX2+uf6+ah0br+HCnd1BRwNXkJrHyEvsfi35M+DjmIq5qFeJfvqce+eCTK4N0TwhLTaOVgV7o9KVSrptCW6u0LvS4iGrkDysnpYEKKMBslxsGAZh1z/y9E51hDXqo7JJArtSXSlWAToYbGxkPx2eRkgNf8EKritAzLVsPotFS9/dfP//z5y+FdhnxMAz8aXgn8UYidGCQLkMIwrSTIQNCaRrSNKVpYUQNYjEIpW1I28QQpna2g6Uy3c+Q2ZFmV1qG0lsZMqhJuux+MIJQ/ig/Y4znXhtF2JPzaMwfHjUCzTXczj4QF7smtUmXQZh2sxLjcHIn3X/NFrLQHjXIHpPUILsMQoyVZDIXwtRsWRlGoD/ACqd2VgpoHPMpqQjrnlBW7TGtp6Ab+Z+JJTIN0HMvdrYpqP2nlorQpPuky7BEFoRYIpsaJsO4GtG1QDf3OxWbuFTVSIitjin1pM+CgAtBX8oLxr/QvhwyGspnTG7uvZWC8WDK49IRFPZWDhFC1CdXHvmwuAM00IUtha16JoFg7da2vMMcdH/kX0VjPxGgjhiWSNgOtKFdAAjocMK0fEsPorGILrnuJ2IBVTBoCxvap7PggseiGuuPrsagj5QfapCuT85Y2snISt59gGxaqok8sHIVYZoehITO7598FY0ms5gXOS1Smgl6QpjLfRHngjlfXp7mz5V1vlKIUmP8RBD/4n9Ns58IpKv3kJFhF7lkiGmjAXU2T0cVCNoaS3eNbK7HEiHPfkfnlWPSe9n3pPfitSe9/jNPem9+UJJavsp/3nvSe9v3cFFtlpr7WcW9MxU+ueBftptpwrRvn7/ov6rRAQim1V6/6b/33j2vvXz+EyWsVKJkOA6jRJCz4SXfslCQsNUkMwcAuQ4kGoCLm1hWbeCmeuiQaGsHduILkUQVb4R7M5ww5Ashk1Aq+Pw3moYYUxBH9YbDtOL19k3v0c7jgas3n7DUVi2l9QYrfWsJPlXSgy93RGcMCT6VCiXV/4LhDu+P9aVfEGueEx5Eo+27G8jvWDSlE/maDUO5WE6okG837qZP4WmwVV8VwGm3FwcTdCAnzsERtHtu6wnTSm37OIzPZ6ckji74eLvwV19ETYeuvj+Y1psz5ldZEyvzfwEAAP//"))
|
||||
} else if typ == "port" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("pFlBbvPKDd4/4N0h8MavCwHkjGaG85ZFLyJLSqzElvxLcvynq16gFyiKAi3Q7rvq7vUyxd/2Fg9KbItMKMvxvzHMkYbkkB85JJUkyY8/JHd1ti1/vuubHf74w93drmn77ufh391dcrckWJ7/p6ldsgfHR332ML6/7vud4GkUnmHkSZBEYAJMkqbpKASByTcAkBgAPC3YYcGyhSUKFdN3lB15uWGr41udBzw/9sNjD2BPC2FYCGwhDguRLQz2SAgijgswLJjxeESGWdNLcwoqymeBwpJZj/uEgFlzGSQleYIHTrFnEV79QG60hnOOqeq9Z2xD4OoEIsY2xsj2kWfPiEhQQT5j+wJFpqrz/E0f2JsRIiQRAZgCQh0KntvRO265iIIa2S4oeLtgyqVcVfRiH7cqET8+gTgwCLehOPyZQBhggwAIIgoSBDI8MAQKELirFziAccFJkCSRIOP4dImvNh1+Rw0XDiC8vaNE+9v/vNlum1rEvlViXyLbi1OIiFjEk8ijo00SIfKAtEkARBaQNolsgXCIPxwXpOcIDCYE9mxmoiGAaUxHBCgDeDBUQgxthMMWJCYiEveDRWQwsp4HtcRDEKCiIN7k0UdR7DMcFSSijyIJMAouRnBBFJC2AqkpTMIdJQjFkQaY4dwN0TX5U6fA5Jj4T69VlfZSGgBmJTyumk7d7Bw3SJqmjLLCAU4V0+blKGXXdP1DW3ZfNoool1rzcX+xeh86b9Sq3feM87bTmWJqbYKpHTPa65t1v9neLOpFFWUt+MRaoNE8VsBgIP3NB2zaLN+U2gnRsfSJzjCMoiQNwvXyK5al8mZf5+u3N997LRKrRPyJ+iCCscrXq6wr1SoqCuUn9T0Dvr7f7L/+7rcqL/KXN6+L+07dyMuPAdQzSuSbKn9aN3v9SGjs5e1tWahh622I13vrNcxGx+nALbd5lq91EBnES9JGphdRWmx/+t8vv3z72z9+o0HFWD8n4wz3Ls/an/7797/8/w9/0ngZAHstr+5lAm9DectyG8KEp5kJm/qhUePABMCQDL/x5hifigv0QlH0J0weSQM4A9Gs67K6aDOt1IDU8NIYZuDeZqtV1W+/aHo6H4yoSK9K6CfOddmkj1ojlAaWYoI/lvnTgV12XZXVGqdTiTp9RT3WRfVxJ0KkBHFshPBUf54W0jRNh64ssMtZ1BkYgVezsgUgIF7KePKiiAWRFnkNfunWncvqj8Vhp0cFr/9TZ2/kfyhX3W5dtmpeJF4TGYJ4VQu2jCgiAQH88mLBLZ27/arpIsxNEHnLh0PLh4ZZx3ENPHr0N9pnt94lX4tytX9Qw1ILxHdp/hw5TVE+dskUL0e8RIjGxJkgeNhkXXdfdWutIpyPobbJn0o9RchWEyFyxyNEXmEPpLnRtlneV8+lqoLsTL/HgU/Z/ZOeUqOZMVFedXmjFuvk56y7aR4qJbs5NInD9PLmtRrxYozhnJtToezzQrsDTyXLkdJGXx+teVzfNPuCVXZdXzXqFSPvPgcCMQ40xFwps6yfmxddppZjr+T6mJUPZau2JobPfdB7zfVXSnkivT80ToSbcSBJd7vET4H/SqZ11qtdqDG8fz9Rt0jY7TddprnDe3c7XPOm7vZa/0nitiALkJCF7zFQ3qhdC6WUEHH3xgC3H6gYUrhiJmODS4wN9LmMqbuiafusqtXg+NT1lz3u1EYO4o2JvdxkXV/lXZm1uXYHRjEXivYKXd8Xv8fSX6+ED1VdNAfFz1W+3Y0Jlk862YRjiTbwkY3jMwg27DpUdbs9Ec3XqvhwhsNU28eN1ZebuuzV62B5G8dNkWkOPek+xfCaScZT2a7KdiKEblS3q7c7tWvDiwzfNX+7qn6YdvhHJqd9970mXI59DH7iaO8HT/1aTQSYGMvmPssP5z7Wz0kEIzpuJVaq+r5RLbvVD8erFf7RJ+VfLowzyu22zaoNT0Hq+B15jRHjLJtqqwIWRecS7Ryb3zfNU1nu1IRokLhLxUciSxp2uVGnwdt2L3WuRUOwl1mqCUyJ5abfq6WJdeZ6INTqqM5AGq++JN4GqMlUuAAfpHdf9pVW4Fo0c73PLs/ql8NE1+u8nTz0WbbadfFcb0Q1hADG3Or9Z9X3Tnw8cZGPHxxpATwDBq0RyYqm0a9tDu3XT2raOPZEGpgz6PP2kKneiBRYukhN0DhdGLee75RqldWZ6m6tb9A8c47GQr34+K29REkOFPsmawd6cXO2L/arldp3nefpr1IMEEEy/JqFwOVsZSss9+1f//zPv//67c9/7LpN8rzTRnYxso/cizS17BMtkfa5VanwzoWi/gnDaW2QwuZXAAAA//8="))
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
if typ == "spray_rule" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("hFPNbqs8EN3zFEfJsMQYU5rwsfpa9RXuKrLktKQlTUsUkvvTxX32K48xcQJRJQRzxsdnPMOxOR3f1n/2puv+w2yOtenq1WH1Sal971wI/p56wN/nHvD3sQdkXBABlNlwju6tObRwBWxGp9ACOsX88cdTomQmk3x5nwdLvKo76C0G0l0i5eLOkSJAV7eYSiZZWSy83IhH6RSzcm+rnEKnfrdhAYYUgxRo0/d1hgK0BXV+ADEoB609jScx5ELuFfEWR4Je0AfBcA36ExReXnFwb5+gwaxclqOjmZCRlzkPP0gtikyeS4lgvwLVVxDOKBeZW0wbWMnxJomL+oqP1GQGTQaJJouHdZXkqsiVXXcG25qfJgKe24+PllN2hMYH4b/5y8Gc31VwBBE0KXy+4t4vBVKvKjAIeNP6eEimZ/PYeANte9dxkKsuYxHAGlqdybOo/n0cLmlvB8nn2IM+Qa+BX/4f5VcrBqOsbfHktSS7amLnwBgMOsEo2Hu5VfyGM1knBj15M5egJQdL14vV/Jb7wM+VajVVJwc99P82WJlFm2ZXr827na8zCt8Uevf/fBqClM+0oB3oxcOdLdL6wsJfdBHs+AI1oL2HR5Y8ePgK+mI7OG+JfhQd6BdoP4v+AQAA//8="))
|
||||
} else if typ == "spray_common" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("nJOxbtRAFEV7S/4HKxUUIM84m93hZ9DYHuI49tp4hjhJR5UWmkh0FOloqJDIKn+Tzf4G2sy7FxSlAKp3ZFv3zN55e+Hs9CZNsuxVpswqJymSJhWkQ9KCdERaklYkAzJ0GDoMHYYOQ4ehw9Bh6DB0GHHoPM9JiqRJBWlBOiItSSsSkxWTFZMVkxWT1SGJDkWHokPRoejQdGg6NB2aDk2HpkPToenQdGiTJv2wDo3cONpBN2gml2T0g3bQDZpBL2gFnewbqe3F31r+I/1xYlewKdgTbAl2hBuCHmSiXXSLZtErWkWnaBR9Sl4heYVKk9KevnXnQX75ZKcIlydjhGCn18eX5Ah4sJTp33cRSnsaYd5/us9e295J+DzPAq5UJL6bhiFI3ElwYnQ+PEkubSVU22BL6+XTWpJCj5M7EK4zDhvHQXbwr/v7+z7++Bcsn/m3yD18GLvB1pG74fhJVw/X37eb24frq/vNj+3Nz2cfZi/Uy/hie3N1v7mTj24/7b5+jLy7+7z79iVN1g53aP0IsIRzviM1pJ5UCdWdHLJycuWVx2xC36VJa8+sCFsIW6a3fga84ztQDHhEeyZHrDrrfZqMzSihYzMSCtICxIixCWnSeh4lTgcIODYe9K3/BQAA//8="))
|
||||
} else if typ == "spray_dict" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("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 truncated
|
||||
} else if typ == "extract" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("nFbdbts2FL4fsHfw3LQmLVPWT5Layo+srUXhFeiKtii28tCBIjGNm9gyTBlqIwrI/a4HbMD2DLvaC+Vi2GMMlH8kx0oWD0isw8PzfeeXlEht7I+4U/skvv6qVpvyj/zzRDhKrtVIrY7O43giUqNlZg4dAIjGzfVvUL+5/uP4EGHZBKAstVrWnpG5dHDUXFgcHzoHEmHKAPRPAtdXfEcAim0/o1BvsJx3qUE5/0DBW9rx4cFt+jlVjivxPQxVAHL8HNRoFbnkOOdud8syzaaXlXVa5bVZMKhvlGojnnY5ICePqLdzO5fbuc+huT93zVdvowTV+ZaNVuSPyuxFbx61dNUuK2svPbWODw96Oxs8aw0uggX4bBnnU3623F+bhJvrX2+uf6+ah0br+HCnd1BRwNXkJrHyEvsfi35M+DjmIq5qFeJfvqce+eCTK4N0TwhLTaOVgV7o9KVSrptCW6u0LvS4iGrkDysnpYEKKMBslxsGAZh1z/y9E51hDXqo7JJArtSXSlWAToYbGxkPx2eRkgNf8EKritAzLVsPotFS9/dfP//z5y+FdhnxMAz8aXgn8UYidGCQLkMIwrSTIQNCaRrSNKVpYUQNYjEIpW1I28QQpna2g6Uy3c+Q2ZFmV1qG0lsZMqhJuux+MIJQ/ig/Y4znXhtF2JPzaMwfHjUCzTXczj4QF7smtUmXQZh2sxLjcHIn3X/NFrLQHjXIHpPUILsMQoyVZDIXwtRsWRlGoD/ACqd2VgpoHPMpqQjrnlBW7TGtp6Ab+Z+JJTIN0HMvdrYpqP2nlorQpPuky7BEFoRYIpsaJsO4GtG1QDf3OxWbuFTVSIitjin1pM+CgAtBX8oLxr/QvhwyGspnTG7uvZWC8WDK49IRFPZWDhFC1CdXHvmwuAM00IUtha16JoFg7da2vMMcdH/kX0VjPxGgjhiWSNgOtKFdAAjocMK0fEsPorGILrnuJ2IBVTBoCxvap7PggseiGuuPrsagj5QfapCuT85Y2snISt59gGxaqok8sHIVYZoehITO7598FY0ms5gXOS1Smgl6QpjLfRHngjlfXp7mz5V1vlKIUmP8RBD/4n9Ns58IpKv3kJFhF7lkiGmjAXU2T0cVCNoaS3eNbK7HEiHPfkfnlWPSe9n3pPfitSe9/jNPem9+UJJavsp/3nvSe9v3cFFtlpr7WcW9MxU+ueBftptpwrRvn7/ov6rRAQim1V6/6b/33j2vvXz+EyWsVKJkOA6jRJCz4SXfslCQsNUkMwcAuQ4kGoCLm1hWbeCmeuiQaGsHduILkUQVb4R7M5ww5Ashk1Aq+Pw3moYYUxBH9YbDtOL19k3v0c7jgas3n7DUVi2l9QYrfWsJPlXSgy93RGcMCT6VCiXV/4LhDu+P9aVfEGueEx5Eo+27G8jvWDSlE/maDUO5WE6okG837qZP4WmwVV8VwGm3FwcTdCAnzsERtHtu6wnTSm37OIzPZ6ckji74eLvwV19ETYeuvj+Y1psz5ldZEyvzfwEAAP//"))
|
||||
} else if typ == "port" {
|
||||
return encode.MustDeflateDeCompress(encode.Base64Decode("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"))
|
||||
}
|
||||
return []byte{}
|
||||
}
|
||||
+122
-122
@@ -1,122 +1,122 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseRawResponse(t *testing.T) {
|
||||
t.Run("valid complete response", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\nhello"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if ct := resp.Header.Get("Content-Type"); ct != "text/html" {
|
||||
t.Fatalf("expected Content-Type text/html, got %s", ct)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil input", func(t *testing.T) {
|
||||
_, err := ParseRawResponse(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nil input")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty input", func(t *testing.T) {
|
||||
_, err := ParseRawResponse([]byte{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty input")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("status line only no headers", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("truncated status line", func(t *testing.T) {
|
||||
raw := "HTTP/1."
|
||||
_, err := ParseRawResponse([]byte(raw))
|
||||
if err == nil {
|
||||
t.Fatal("expected error for truncated status line")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("redirect response no body", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 302 Found\r\nLocation: https://example.com/new\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 302 {
|
||||
t.Fatalf("expected status 302, got %d", resp.StatusCode)
|
||||
}
|
||||
if loc := resp.Header.Get("Location"); loc != "https://example.com/new" {
|
||||
t.Fatalf("expected Location https://example.com/new, got %s", loc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("chunked transfer encoding header", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if te := resp.Header.Get("Transfer-Encoding"); te == "" {
|
||||
// Transfer-Encoding may be consumed by http.ReadResponse, just verify no panic
|
||||
}
|
||||
_ = resp
|
||||
})
|
||||
|
||||
t.Run("large header value", func(t *testing.T) {
|
||||
largeValue := strings.Repeat("A", 8192)
|
||||
raw := "HTTP/1.1 200 OK\r\nX-Large: " + largeValue + "\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := resp.Header.Get("X-Large"); got != largeValue {
|
||||
t.Fatalf("large header value mismatch, got length %d", len(got))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid status code", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 xyz OK\r\n\r\n"
|
||||
_, err := ParseRawResponse([]byte(raw))
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid status code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("incomplete header no terminator", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html"
|
||||
_, err := ParseRawResponse([]byte(raw))
|
||||
// Should return error or at least not panic
|
||||
// http.ReadResponse may or may not error on missing \r\n\r\n,
|
||||
// the key requirement is no panic
|
||||
_ = err
|
||||
})
|
||||
|
||||
t.Run("binary body", func(t *testing.T) {
|
||||
body := []byte{0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd}
|
||||
raw := append([]byte("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n"), body...)
|
||||
resp, err := ParseRawResponse(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseRawResponse(t *testing.T) {
|
||||
t.Run("valid complete response", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\nhello"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
if ct := resp.Header.Get("Content-Type"); ct != "text/html" {
|
||||
t.Fatalf("expected Content-Type text/html, got %s", ct)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("nil input", func(t *testing.T) {
|
||||
_, err := ParseRawResponse(nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nil input")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty input", func(t *testing.T) {
|
||||
_, err := ParseRawResponse([]byte{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error for empty input")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("status line only no headers", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("truncated status line", func(t *testing.T) {
|
||||
raw := "HTTP/1."
|
||||
_, err := ParseRawResponse([]byte(raw))
|
||||
if err == nil {
|
||||
t.Fatal("expected error for truncated status line")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("redirect response no body", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 302 Found\r\nLocation: https://example.com/new\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 302 {
|
||||
t.Fatalf("expected status 302, got %d", resp.StatusCode)
|
||||
}
|
||||
if loc := resp.Header.Get("Location"); loc != "https://example.com/new" {
|
||||
t.Fatalf("expected Location https://example.com/new, got %s", loc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("chunked transfer encoding header", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if te := resp.Header.Get("Transfer-Encoding"); te == "" {
|
||||
// Transfer-Encoding may be consumed by http.ReadResponse, just verify no panic
|
||||
}
|
||||
_ = resp
|
||||
})
|
||||
|
||||
t.Run("large header value", func(t *testing.T) {
|
||||
largeValue := strings.Repeat("A", 8192)
|
||||
raw := "HTTP/1.1 200 OK\r\nX-Large: " + largeValue + "\r\n\r\n"
|
||||
resp, err := ParseRawResponse([]byte(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got := resp.Header.Get("X-Large"); got != largeValue {
|
||||
t.Fatalf("large header value mismatch, got length %d", len(got))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid status code", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 xyz OK\r\n\r\n"
|
||||
_, err := ParseRawResponse([]byte(raw))
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid status code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("incomplete header no terminator", func(t *testing.T) {
|
||||
raw := "HTTP/1.1 200 OK\r\nContent-Type: text/html"
|
||||
_, err := ParseRawResponse([]byte(raw))
|
||||
// Should return error or at least not panic
|
||||
// http.ReadResponse may or may not error on missing \r\n\r\n,
|
||||
// the key requirement is no panic
|
||||
_ = err
|
||||
})
|
||||
|
||||
t.Run("binary body", func(t *testing.T) {
|
||||
body := []byte{0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd}
|
||||
raw := append([]byte("HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\n"), body...)
|
||||
resp, err := ParseRawResponse(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("expected status 200, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,27 +1,27 @@
|
||||
//go:generate go run templates/templates_gen.go -t templates -o pkg/templates.go -need spray
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/spray/cmd"
|
||||
"github.com/gookit/config/v2"
|
||||
"github.com/gookit/config/v2/yaml"
|
||||
//_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.WithOptions(func(opt *config.Options) {
|
||||
opt.DecoderConfig.TagName = "config"
|
||||
opt.ParseDefault = true
|
||||
})
|
||||
config.AddDriver(yaml.Driver)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 启动 pprof HTTP 服务器,用于调试 goroutine 泄露等问题
|
||||
//go func() {
|
||||
// // 仅监听本地 6060 端口,避免对外暴露
|
||||
// _ = http.ListenAndServe("127.0.0.1:6060", nil)
|
||||
//}()
|
||||
|
||||
cmd.Spray()
|
||||
}
|
||||
//go:generate go run templates/templates_gen.go -t templates -o pkg/templates.go -need spray
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/chainreactors/spray/cmd"
|
||||
"github.com/gookit/config/v2"
|
||||
"github.com/gookit/config/v2/yaml"
|
||||
//_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.WithOptions(func(opt *config.Options) {
|
||||
opt.DecoderConfig.TagName = "config"
|
||||
opt.ParseDefault = true
|
||||
})
|
||||
config.AddDriver(yaml.Driver)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 启动 pprof HTTP 服务器,用于调试 goroutine 泄露等问题
|
||||
//go func() {
|
||||
// // 仅监听本地 6060 端口,避免对外暴露
|
||||
// _ = http.ListenAndServe("127.0.0.1:6060", nil)
|
||||
//}()
|
||||
|
||||
cmd.Spray()
|
||||
}
|
||||
Reference in new issue
Block a user