chore: normalize line endings (CRLF → LF)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
M09IcandClaude Opus 4.6 committed 2026-05-08 10:12:56 -07:00
1 parent b4e9b10c84
commit 0a6132ca02
20 files changed
+2643 -2643

No files matched your search

+34 -34
View File
@@ -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
+40 -40
View File
@@ -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
View File
@@ -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
View File
@@ -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
+674 -674
View File
File diff suppressed because it is too large. Load diff
+127 -127
View File
@@ -1,128 +1,128 @@
# SPRAY
![](https://socialify.git.ci/chainreactors/spray/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light)
<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
![](https://socialify.git.ci/chainreactors/spray/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Light)
<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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+73 -73
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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("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"))
}
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("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{}
}
+122 -122
View File
@@ -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)
}
})
}
+27 -27
View File
@@ -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()
}