mirror of
https://github.com/bettercap/bettercap
synced 2025-08-19 13:09:49 -07:00
Compare commits
No commits in common. "master" and "v2.40.0" have entirely different histories.
1776 changed files with 886 additions and 286401 deletions
4
.gitattributes
vendored
4
.gitattributes
vendored
|
|
@ -1,4 +0,0 @@
|
|||
*.js linguist-vendored
|
||||
/Dockerfile linguist-vendored
|
||||
/release.py linguist-vendored
|
||||
/**/*.js linguist-vendored
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1,5 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Bettercap Documentation
|
||||
url: https://www.bettercap.org/
|
||||
about: Please read the instructions before asking for help.
|
||||
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
|
|
@ -1,7 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
# GitHub Actions
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
96
.github/workflows/build-and-deploy.yml
vendored
96
.github/workflows/build-and-deploy.yml
vendored
|
|
@ -8,57 +8,58 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.os.pretty }} ${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.os.runs-on }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- name: darwin
|
||||
runs-on: [macos-latest]
|
||||
pretty: 🍎 macOS
|
||||
- name: linux
|
||||
runs-on: [ubuntu-latest]
|
||||
pretty: 🐧 Linux
|
||||
- name: windows
|
||||
runs-on: [windows-latest]
|
||||
pretty: 🪟 Windows
|
||||
output: bettercap.exe
|
||||
arch: [amd64, arm64]
|
||||
go: [1.24.x]
|
||||
exclude:
|
||||
- os:
|
||||
name: darwin
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go-version: ['1.22.x']
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
arch: amd64
|
||||
# Linux ARM64 images are not yet publicly available (https://github.com/actions/runner-images)
|
||||
- os:
|
||||
name: linux
|
||||
target_os: linux
|
||||
target_arch: amd64
|
||||
- os: ubuntu-latest
|
||||
arch: arm64
|
||||
- os:
|
||||
name: windows
|
||||
target_os: linux
|
||||
target_arch: aarch64
|
||||
- os: macos-latest
|
||||
arch: arm64
|
||||
target_os: darwin
|
||||
target_arch: arm64
|
||||
- os: windows-latest
|
||||
arch: amd64
|
||||
target_os: windows
|
||||
target_arch: amd64
|
||||
output: bettercap.exe
|
||||
|
||||
env:
|
||||
OUTPUT: ${{ matrix.os.output || 'bettercap' }}
|
||||
TARGET_OS: ${{ matrix.target_os }}
|
||||
TARGET_ARCH: ${{ matrix.target_arch }}
|
||||
GO_VERSION: ${{ matrix.go-version }}
|
||||
OUTPUT: ${{ matrix.output || 'bettercap' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Install Dependencies
|
||||
if: ${{ matrix.os.name == 'linux' }}
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: sudo apt-get update && sudo apt-get install -y p7zip-full libpcap-dev libnetfilter-queue-dev libusb-1.0-0-dev
|
||||
|
||||
- name: Install Dependencies (macOS)
|
||||
if: ${{ matrix.os.name == 'macos' }}
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: brew install libpcap libusb p7zip
|
||||
|
||||
|
||||
- name: Install libusb via mingw (Windows)
|
||||
if: ${{ matrix.os.name == 'windows' }}
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
install: |-
|
||||
|
|
@ -66,7 +67,7 @@ jobs:
|
|||
mingw64/mingw-w64-x86_64-pkg-config
|
||||
|
||||
- name: Install other Dependencies (Windows)
|
||||
if: ${{ matrix.os.name == 'windows' }}
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
choco install openssl.light -y
|
||||
choco install make -y
|
||||
|
|
@ -82,36 +83,25 @@ jobs:
|
|||
- name: Verify Build
|
||||
run: |
|
||||
file "${{ env.OUTPUT }}"
|
||||
openssl dgst -sha256 "${{ env.OUTPUT }}" | tee bettercap_${{ matrix.os.name }}_${{ matrix.arch }}.sha256
|
||||
7z a "bettercap_${{ matrix.os.name }}_${{ matrix.arch }}.zip" "${{ env.OUTPUT }}" "bettercap_${{ matrix.os.name }}_${{ matrix.arch }}.sha256"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-artifacts-${{ matrix.os.name }}-${{ matrix.arch }}
|
||||
path: |
|
||||
bettercap_*.zip
|
||||
bettercap_*.sha256
|
||||
openssl dgst -sha256 "${{ env.OUTPUT }}" | tee bettercap_${{ matrix.target_os }}_${{ matrix.target_arch }}_${{ env.VERSION }}.sha256
|
||||
7z a "bettercap_${{ matrix.target_os }}_${{ matrix.target_arch }}_${{ env.VERSION }}.zip" "${{ env.OUTPUT }}" "bettercap_${{ matrix.target_os }}_${{ matrix.target_arch }}_${{ env.VERSION }}.sha256"
|
||||
|
||||
deploy:
|
||||
needs: [build]
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
pattern: release-artifacts-*
|
||||
merge-multiple: true
|
||||
path: dist/
|
||||
|
||||
- name: Release Assets
|
||||
run: ls -l dist
|
||||
submodules: true
|
||||
|
||||
- name: Upload Release Assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: dist/bettercap_*
|
||||
files: |
|
||||
bettercap_*.zip
|
||||
bettercap_*.sha256
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
5
.github/workflows/build-and-push-docker.yml
vendored
5
.github/workflows/build-and-push-docker.yml
vendored
|
|
@ -12,7 +12,8 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
with:
|
||||
submodules: true
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
|
|
@ -23,7 +24,7 @@ jobs:
|
|||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
|
|
|
|||
8
.github/workflows/test-on-linux.yml
vendored
8
.github/workflows/test-on-linux.yml
vendored
|
|
@ -13,14 +13,16 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
go-version: ['1.24.x']
|
||||
go-version: ['1.22.x']
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
|
|
|||
8
.github/workflows/test-on-macos.yml
vendored
8
.github/workflows/test-on-macos.yml
vendored
|
|
@ -13,14 +13,16 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest]
|
||||
go-version: ['1.24.x']
|
||||
go-version: ['1.22.x']
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
|
|
|||
8
.github/workflows/test-on-windows.yml
vendored
8
.github/workflows/test-on-windows.yml
vendored
|
|
@ -13,14 +13,16 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
go-version: ['1.24.x']
|
||||
go-version: ['1.22.x']
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -15,6 +15,4 @@ stage/
|
|||
/snap
|
||||
.idea
|
||||
/cover.out
|
||||
/can-data
|
||||
/test*.yml
|
||||
/zerochaos.js
|
||||
/can-data
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "ui"]
|
||||
path = modules/ui/ui
|
||||
url = https://github.com/bettercap/ui.git
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# build stage
|
||||
FROM golang:1.24-alpine AS build-env
|
||||
FROM golang:1.22-alpine3.20 AS build-env
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN apk add --no-cache bash gcc g++ binutils-gold iptables wireless-tools build-base libpcap-dev libusb-dev linux-headers libnetfilter_queue-dev git
|
||||
|
|
@ -13,9 +13,9 @@ RUN mkdir -p /usr/local/share/bettercap
|
|||
RUN git clone https://github.com/bettercap/caplets /usr/local/share/bettercap/caplets
|
||||
|
||||
# final stage
|
||||
FROM alpine
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache ca-certificates
|
||||
RUN apk add --no-cache bash iproute2 libpcap libusb-dev libnetfilter_queue wireless-tools iw
|
||||
RUN apk add --no-cache bash iproute2 libpcap libusb-dev libnetfilter_queue wireless-tools
|
||||
COPY --from=build-env /go/src/github.com/bettercap/bettercap/bettercap /app/
|
||||
COPY --from=build-env /usr/local/share/bettercap/caplets /app/
|
||||
WORKDIR /app
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
---
|
||||
name: General Issue
|
||||
about: Write a general issue or bug report.
|
||||
---
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Please, before creating this issue make sure that you read the [README](https://github.com/bettercap/bettercap/blob/master/README.md), that you are running the [latest stable version](https://github.com/bettercap/bettercap/releases) and that you already searched [other issues](https://github.com/bettercap/bettercap/issues?q=is%3Aopen+is%3Aissue+label%3Abug) to see if your problem or request was already reported.
|
||||
17
Makefile
17
Makefile
|
|
@ -5,14 +5,19 @@ GO ?= go
|
|||
|
||||
all: build
|
||||
|
||||
build: resources
|
||||
$(GO) build $(GOFLAGS) -o $(TARGET) .
|
||||
build: resources ui
|
||||
$(GOFLAGS) $(GO) build -o $(TARGET) .
|
||||
|
||||
build_with_race_detector: resources
|
||||
$(GO) build $(GOFLAGS) -race -o $(TARGET) .
|
||||
$(GOFLAGS) $(GO) build -race -o $(TARGET) .
|
||||
|
||||
resources: network/manuf.go
|
||||
|
||||
ui: ./modules/ui/ui/dist/ui/index.html
|
||||
|
||||
./modules/ui/ui/dist/ui/index.html:
|
||||
@cd modules/ui/ui && make build
|
||||
|
||||
network/manuf.go:
|
||||
@python3 ./network/make_manuf.py
|
||||
|
||||
|
|
@ -24,13 +29,13 @@ docker:
|
|||
@docker build -t bettercap:latest .
|
||||
|
||||
test:
|
||||
$(GO) test -covermode=atomic -coverprofile=cover.out ./...
|
||||
$(GOFLAGS) $(GO) test -covermode=atomic -coverprofile=cover.out ./...
|
||||
|
||||
html_coverage: test
|
||||
$(GO) tool cover -html=cover.out -o cover.out.html
|
||||
$(GOFLAGS) $(GO) tool cover -html=cover.out -o cover.out.html
|
||||
|
||||
benchmark: server_deps
|
||||
$(GO) test -v -run=doNotRunTests -bench=. -benchmem ./...
|
||||
$(GOFLAGS) $(GO) test -v -run=doNotRunTests -bench=. -benchmem ./...
|
||||
|
||||
fmt:
|
||||
$(GO) fmt -s -w $(PACKAGES)
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -19,7 +19,7 @@
|
|||
</p>
|
||||
</p>
|
||||
|
||||
bettercap is a powerful, easily extensible and portable framework written in Go which aims to offer to security researchers, red teamers and reverse engineers an **easy to use**, **all-in-one solution** with all the features they might possibly need for performing reconnaissance and attacking [WiFi](https://www.bettercap.org/modules/wifi/) networks, [Bluetooth Low Energy](https://www.bettercap.org/modules/ble/) devices, [CAN-bus](https://www.bettercap.org/modules/canbus/), wireless [HID](https://www.bettercap.org/modules/hid/) devices and [Ethernet](https://www.bettercap.org/modules/ethernet) networks.
|
||||
bettercap is a powerful, easily extensible and portable framework written in Go which aims to offer to security researchers, red teamers and reverse engineers an **easy to use**, **all-in-one solution** with all the features they might possibly need for performing reconnaissance and attacking [WiFi](https://www.bettercap.org/modules/wifi/) networks, [Bluetooth Low Energy](https://www.bettercap.org/modules/ble/) devices, [CAN-bus](https://www.bettercap.org/modules/can/), wireless [HID](https://www.bettercap.org/modules/hid/) devices and [Ethernet](https://www.bettercap.org/modules/ethernet) networks.
|
||||
|
||||

|
||||
|
||||
|
|
@ -38,15 +38,9 @@ bettercap is a powerful, easily extensible and portable framework written in Go
|
|||
* **A very convenient [web UI](https://www.bettercap.org/usage/#web-ui).**
|
||||
* [More!](https://www.bettercap.org/modules/)
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/bettercap/bettercap/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=bettercap/bettercap" alt="bettercap project contributors" />
|
||||
</a>
|
||||
|
||||
## License
|
||||
|
||||
`bettercap` is made with ♥ and released under the GPL 3 license.
|
||||
`bettercap` is made with ♥ by [the dev team](https://github.com/orgs/bettercap/people) and it's released under the GPL 3 license.
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
|
|
|
|||
|
|
@ -1,378 +0,0 @@
|
|||
package caplets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewCaplet(t *testing.T) {
|
||||
name := "test-caplet"
|
||||
path := "/path/to/caplet.cap"
|
||||
size := int64(1024)
|
||||
|
||||
cap := NewCaplet(name, path, size)
|
||||
|
||||
if cap.Name != name {
|
||||
t.Errorf("expected name %s, got %s", name, cap.Name)
|
||||
}
|
||||
if cap.Path != path {
|
||||
t.Errorf("expected path %s, got %s", path, cap.Path)
|
||||
}
|
||||
if cap.Size != size {
|
||||
t.Errorf("expected size %d, got %d", size, cap.Size)
|
||||
}
|
||||
if cap.Code == nil {
|
||||
t.Error("Code should not be nil")
|
||||
}
|
||||
if cap.Scripts == nil {
|
||||
t.Error("Scripts should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapletEval(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code []string
|
||||
argv []string
|
||||
wantLines []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty code",
|
||||
code: []string{},
|
||||
argv: nil,
|
||||
wantLines: []string{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "skip comments and empty lines",
|
||||
code: []string{
|
||||
"# this is a comment",
|
||||
"",
|
||||
"set test value",
|
||||
"# another comment",
|
||||
"set another value",
|
||||
},
|
||||
argv: nil,
|
||||
wantLines: []string{
|
||||
"set test value",
|
||||
"set another value",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "variable substitution",
|
||||
code: []string{
|
||||
"set param $0",
|
||||
"set value $1",
|
||||
"run $0 $1 $2",
|
||||
},
|
||||
argv: []string{"arg0", "arg1", "arg2"},
|
||||
wantLines: []string{
|
||||
"set param arg0",
|
||||
"set value arg1",
|
||||
"run arg0 arg1 arg2",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "multiple occurrences of same variable",
|
||||
code: []string{
|
||||
"$0 $0 $1 $0",
|
||||
},
|
||||
argv: []string{"foo", "bar"},
|
||||
wantLines: []string{
|
||||
"foo foo bar foo",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "missing argv values",
|
||||
code: []string{
|
||||
"set $0 $1 $2",
|
||||
},
|
||||
argv: []string{"only_one"},
|
||||
wantLines: []string{
|
||||
"set only_one $1 $2",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tempFile, err := ioutil.TempFile("", "test-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
cap := NewCaplet("test", tempFile.Name(), 100)
|
||||
cap.Code = tt.code
|
||||
|
||||
var gotLines []string
|
||||
err = cap.Eval(tt.argv, func(line string) error {
|
||||
gotLines = append(gotLines, line)
|
||||
return nil
|
||||
})
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Eval() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if len(gotLines) != len(tt.wantLines) {
|
||||
t.Errorf("got %d lines, want %d", len(gotLines), len(tt.wantLines))
|
||||
return
|
||||
}
|
||||
|
||||
for i, line := range gotLines {
|
||||
if line != tt.wantLines[i] {
|
||||
t.Errorf("line %d: got %q, want %q", i, line, tt.wantLines[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapletEvalError(t *testing.T) {
|
||||
tempFile, err := ioutil.TempFile("", "test-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
cap := NewCaplet("test", tempFile.Name(), 100)
|
||||
cap.Code = []string{
|
||||
"first line",
|
||||
"error line",
|
||||
"third line",
|
||||
}
|
||||
|
||||
expectedErr := errors.New("test error")
|
||||
var executedLines []string
|
||||
|
||||
err = cap.Eval(nil, func(line string) error {
|
||||
executedLines = append(executedLines, line)
|
||||
if line == "error line" {
|
||||
return expectedErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != expectedErr {
|
||||
t.Errorf("expected error %v, got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
// Should have executed first two lines before error
|
||||
if len(executedLines) != 2 {
|
||||
t.Errorf("expected 2 executed lines, got %d", len(executedLines))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapletEvalWithChdirPath(t *testing.T) {
|
||||
// Create a temporary caplet file to test with
|
||||
tempFile, err := ioutil.TempFile("", "test-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
cap := NewCaplet("test", tempFile.Name(), 100)
|
||||
cap.Code = []string{"test command"}
|
||||
|
||||
executed := false
|
||||
err = cap.Eval(nil, func(line string) error {
|
||||
executed = true
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !executed {
|
||||
t.Error("callback was not executed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewScript(t *testing.T) {
|
||||
path := "/path/to/script.js"
|
||||
size := int64(2048)
|
||||
|
||||
script := newScript(path, size)
|
||||
|
||||
if script.Path != path {
|
||||
t.Errorf("expected path %s, got %s", path, script.Path)
|
||||
}
|
||||
if script.Size != size {
|
||||
t.Errorf("expected size %d, got %d", size, script.Size)
|
||||
}
|
||||
if script.Code == nil {
|
||||
t.Error("Code should not be nil")
|
||||
}
|
||||
if len(script.Code) != 0 {
|
||||
t.Errorf("expected empty Code slice, got %d elements", len(script.Code))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapletEvalCommentAtStartOfLine(t *testing.T) {
|
||||
tempFile, err := ioutil.TempFile("", "test-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
cap := NewCaplet("test", tempFile.Name(), 100)
|
||||
cap.Code = []string{
|
||||
"# comment",
|
||||
" # not a comment (has space before #)",
|
||||
" # not a comment (has tab before #)",
|
||||
"command # inline comment",
|
||||
}
|
||||
|
||||
var gotLines []string
|
||||
err = cap.Eval(nil, func(line string) error {
|
||||
gotLines = append(gotLines, line)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedLines := []string{
|
||||
" # not a comment (has space before #)",
|
||||
" # not a comment (has tab before #)",
|
||||
"command # inline comment",
|
||||
}
|
||||
|
||||
if len(gotLines) != len(expectedLines) {
|
||||
t.Errorf("got %d lines, want %d", len(gotLines), len(expectedLines))
|
||||
return
|
||||
}
|
||||
|
||||
for i, line := range gotLines {
|
||||
if line != expectedLines[i] {
|
||||
t.Errorf("line %d: got %q, want %q", i, line, expectedLines[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapletEvalArgvSubstitutionEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
code string
|
||||
argv []string
|
||||
wantLine string
|
||||
}{
|
||||
{
|
||||
name: "double digit substitution $10",
|
||||
code: "$1$0",
|
||||
argv: []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"},
|
||||
wantLine: "10",
|
||||
},
|
||||
{
|
||||
name: "no space between variables",
|
||||
code: "$0$1$2",
|
||||
argv: []string{"a", "b", "c"},
|
||||
wantLine: "abc",
|
||||
},
|
||||
{
|
||||
name: "variables in quotes",
|
||||
code: `"$0" '$1'`,
|
||||
argv: []string{"foo", "bar"},
|
||||
wantLine: `"foo" 'bar'`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tempFile, err := ioutil.TempFile("", "test-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
cap := NewCaplet("test", tempFile.Name(), 100)
|
||||
cap.Code = []string{tt.code}
|
||||
|
||||
var gotLine string
|
||||
err = cap.Eval(tt.argv, func(line string) error {
|
||||
gotLine = line
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if gotLine != tt.wantLine {
|
||||
t.Errorf("got line %q, want %q", gotLine, tt.wantLine)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapletStructFields(t *testing.T) {
|
||||
// Test that Caplet properly embeds Script
|
||||
tempFile, err := ioutil.TempFile("", "test-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
tempFile.Close()
|
||||
|
||||
cap := NewCaplet("test", tempFile.Name(), 100)
|
||||
|
||||
// These fields should be accessible due to embedding
|
||||
_ = cap.Path
|
||||
_ = cap.Size
|
||||
_ = cap.Code
|
||||
|
||||
// And these are Caplet's own fields
|
||||
_ = cap.Name
|
||||
_ = cap.Scripts
|
||||
}
|
||||
|
||||
func BenchmarkCapletEval(b *testing.B) {
|
||||
cap := NewCaplet("bench", "/tmp/bench.cap", 100)
|
||||
cap.Code = []string{
|
||||
"set param1 $0",
|
||||
"set param2 $1",
|
||||
"# comment line",
|
||||
"",
|
||||
"run command $0 $1 $2",
|
||||
"another command",
|
||||
}
|
||||
argv := []string{"arg0", "arg1", "arg2"}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cap.Eval(argv, func(line string) error {
|
||||
// Do nothing, just measure evaluation overhead
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVariableSubstitution(b *testing.B) {
|
||||
line := "command $0 $1 $2 $3 $4 $5 $6 $7 $8 $9"
|
||||
argv := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
result := line
|
||||
for j, arg := range argv {
|
||||
what := "$" + string(rune('0'+j))
|
||||
result = strings.Replace(result, what, arg, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,308 +0,0 @@
|
|||
package caplets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetDefaultInstallBase(t *testing.T) {
|
||||
base := getDefaultInstallBase()
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected := filepath.Join(os.Getenv("ALLUSERSPROFILE"), "bettercap")
|
||||
if base != expected {
|
||||
t.Errorf("on windows, expected %s, got %s", expected, base)
|
||||
}
|
||||
} else {
|
||||
expected := "/usr/local/share/bettercap/"
|
||||
if base != expected {
|
||||
t.Errorf("on non-windows, expected %s, got %s", expected, base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserHomeDir(t *testing.T) {
|
||||
home := getUserHomeDir()
|
||||
|
||||
// Should return a non-empty string
|
||||
if home == "" {
|
||||
t.Error("getUserHomeDir returned empty string")
|
||||
}
|
||||
|
||||
// Should be an absolute path
|
||||
if !filepath.IsAbs(home) {
|
||||
t.Errorf("expected absolute path, got %s", home)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
// Save original values
|
||||
origInstallBase := InstallBase
|
||||
origInstallPathArchive := InstallPathArchive
|
||||
origInstallPath := InstallPath
|
||||
origArchivePath := ArchivePath
|
||||
origLoadPaths := LoadPaths
|
||||
|
||||
// Test with custom base
|
||||
testBase := "/custom/base"
|
||||
err := Setup(testBase)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Setup returned error: %v", err)
|
||||
}
|
||||
|
||||
// Check that paths are set correctly
|
||||
if InstallBase != testBase {
|
||||
t.Errorf("expected InstallBase %s, got %s", testBase, InstallBase)
|
||||
}
|
||||
|
||||
expectedArchivePath := filepath.Join(testBase, "caplets-master")
|
||||
if InstallPathArchive != expectedArchivePath {
|
||||
t.Errorf("expected InstallPathArchive %s, got %s", expectedArchivePath, InstallPathArchive)
|
||||
}
|
||||
|
||||
expectedInstallPath := filepath.Join(testBase, "caplets")
|
||||
if InstallPath != expectedInstallPath {
|
||||
t.Errorf("expected InstallPath %s, got %s", expectedInstallPath, InstallPath)
|
||||
}
|
||||
|
||||
expectedTempPath := filepath.Join(os.TempDir(), "caplets.zip")
|
||||
if ArchivePath != expectedTempPath {
|
||||
t.Errorf("expected ArchivePath %s, got %s", expectedTempPath, ArchivePath)
|
||||
}
|
||||
|
||||
// Check LoadPaths contains expected paths
|
||||
expectedInLoadPaths := []string{
|
||||
"./",
|
||||
"./caplets/",
|
||||
InstallPath,
|
||||
filepath.Join(getUserHomeDir(), "caplets"),
|
||||
}
|
||||
|
||||
for _, expected := range expectedInLoadPaths {
|
||||
absExpected, _ := filepath.Abs(expected)
|
||||
found := false
|
||||
for _, path := range LoadPaths {
|
||||
if path == absExpected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected path %s not found in LoadPaths", absExpected)
|
||||
}
|
||||
}
|
||||
|
||||
// All paths should be absolute
|
||||
for _, path := range LoadPaths {
|
||||
if !filepath.IsAbs(path) {
|
||||
t.Errorf("LoadPath %s is not absolute", path)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
InstallBase = origInstallBase
|
||||
InstallPathArchive = origInstallPathArchive
|
||||
InstallPath = origInstallPath
|
||||
ArchivePath = origArchivePath
|
||||
LoadPaths = origLoadPaths
|
||||
}
|
||||
|
||||
func TestSetupWithEnvironmentVariable(t *testing.T) {
|
||||
// Save original values
|
||||
origEnv := os.Getenv(EnvVarName)
|
||||
origLoadPaths := LoadPaths
|
||||
|
||||
// Set environment variable with multiple paths
|
||||
testPaths := []string{"/path1", "/path2", "/path3"}
|
||||
os.Setenv(EnvVarName, strings.Join(testPaths, string(os.PathListSeparator)))
|
||||
|
||||
// Run setup
|
||||
err := Setup("/test/base")
|
||||
if err != nil {
|
||||
t.Errorf("Setup returned error: %v", err)
|
||||
}
|
||||
|
||||
// Check that custom paths from env var are in LoadPaths
|
||||
for _, testPath := range testPaths {
|
||||
absTestPath, _ := filepath.Abs(testPath)
|
||||
found := false
|
||||
for _, path := range LoadPaths {
|
||||
if path == absTestPath {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected env path %s not found in LoadPaths", absTestPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
if origEnv == "" {
|
||||
os.Unsetenv(EnvVarName)
|
||||
} else {
|
||||
os.Setenv(EnvVarName, origEnv)
|
||||
}
|
||||
LoadPaths = origLoadPaths
|
||||
}
|
||||
|
||||
func TestSetupWithEmptyEnvironmentVariable(t *testing.T) {
|
||||
// Save original values
|
||||
origEnv := os.Getenv(EnvVarName)
|
||||
origLoadPaths := LoadPaths
|
||||
|
||||
// Set empty environment variable
|
||||
os.Setenv(EnvVarName, "")
|
||||
|
||||
// Count LoadPaths before setup
|
||||
err := Setup("/test/base")
|
||||
if err != nil {
|
||||
t.Errorf("Setup returned error: %v", err)
|
||||
}
|
||||
|
||||
// Should have only the default paths (4)
|
||||
if len(LoadPaths) != 4 {
|
||||
t.Errorf("expected 4 default LoadPaths, got %d", len(LoadPaths))
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
if origEnv == "" {
|
||||
os.Unsetenv(EnvVarName)
|
||||
} else {
|
||||
os.Setenv(EnvVarName, origEnv)
|
||||
}
|
||||
LoadPaths = origLoadPaths
|
||||
}
|
||||
|
||||
func TestSetupWithWhitespaceInEnvironmentVariable(t *testing.T) {
|
||||
// Save original values
|
||||
origEnv := os.Getenv(EnvVarName)
|
||||
origLoadPaths := LoadPaths
|
||||
|
||||
// Set environment variable with whitespace
|
||||
testPaths := []string{" /path1 ", " ", "/path2 "}
|
||||
os.Setenv(EnvVarName, strings.Join(testPaths, string(os.PathListSeparator)))
|
||||
|
||||
// Run setup
|
||||
err := Setup("/test/base")
|
||||
if err != nil {
|
||||
t.Errorf("Setup returned error: %v", err)
|
||||
}
|
||||
|
||||
// Should have added only non-empty paths after trimming
|
||||
expectedPaths := []string{"/path1", "/path2"}
|
||||
foundCount := 0
|
||||
for _, expectedPath := range expectedPaths {
|
||||
absExpected, _ := filepath.Abs(expectedPath)
|
||||
for _, path := range LoadPaths {
|
||||
if path == absExpected {
|
||||
foundCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if foundCount != len(expectedPaths) {
|
||||
t.Errorf("expected to find %d paths from env, found %d", len(expectedPaths), foundCount)
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
if origEnv == "" {
|
||||
os.Unsetenv(EnvVarName)
|
||||
} else {
|
||||
os.Setenv(EnvVarName, origEnv)
|
||||
}
|
||||
LoadPaths = origLoadPaths
|
||||
}
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
// Test that constants have expected values
|
||||
if EnvVarName != "CAPSPATH" {
|
||||
t.Errorf("expected EnvVarName to be 'CAPSPATH', got %s", EnvVarName)
|
||||
}
|
||||
|
||||
if Suffix != ".cap" {
|
||||
t.Errorf("expected Suffix to be '.cap', got %s", Suffix)
|
||||
}
|
||||
|
||||
if InstallArchive != "https://github.com/bettercap/caplets/archive/master.zip" {
|
||||
t.Errorf("unexpected InstallArchive value: %s", InstallArchive)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit(t *testing.T) {
|
||||
// The init function should have been called already
|
||||
// Check that paths are initialized
|
||||
if InstallBase == "" {
|
||||
t.Error("InstallBase not initialized")
|
||||
}
|
||||
|
||||
if InstallPath == "" {
|
||||
t.Error("InstallPath not initialized")
|
||||
}
|
||||
|
||||
if InstallPathArchive == "" {
|
||||
t.Error("InstallPathArchive not initialized")
|
||||
}
|
||||
|
||||
if ArchivePath == "" {
|
||||
t.Error("ArchivePath not initialized")
|
||||
}
|
||||
|
||||
if LoadPaths == nil || len(LoadPaths) == 0 {
|
||||
t.Error("LoadPaths not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupMultipleTimes(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
|
||||
// Setup multiple times with different bases
|
||||
bases := []string{"/base1", "/base2", "/base3"}
|
||||
|
||||
for _, base := range bases {
|
||||
err := Setup(base)
|
||||
if err != nil {
|
||||
t.Errorf("Setup(%s) returned error: %v", base, err)
|
||||
}
|
||||
|
||||
// Check that InstallBase is updated
|
||||
if InstallBase != base {
|
||||
t.Errorf("expected InstallBase %s, got %s", base, InstallBase)
|
||||
}
|
||||
|
||||
// LoadPaths should be recreated each time
|
||||
if len(LoadPaths) < 4 {
|
||||
t.Errorf("LoadPaths should have at least 4 entries, got %d", len(LoadPaths))
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
}
|
||||
|
||||
func BenchmarkSetup(b *testing.B) {
|
||||
// Save original values
|
||||
origEnv := os.Getenv(EnvVarName)
|
||||
|
||||
// Set a complex environment
|
||||
paths := []string{"/p1", "/p2", "/p3", "/p4", "/p5"}
|
||||
os.Setenv(EnvVarName, strings.Join(paths, string(os.PathListSeparator)))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Setup("/benchmark/base")
|
||||
}
|
||||
|
||||
// Restore
|
||||
if origEnv == "" {
|
||||
os.Unsetenv(EnvVarName)
|
||||
} else {
|
||||
os.Setenv(EnvVarName, origEnv)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,511 +0,0 @@
|
|||
package caplets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createTestCaplet(t testing.TB, dir string, name string, content []string) string {
|
||||
filename := filepath.Join(dir, name)
|
||||
data := strings.Join(content, "\n")
|
||||
err := ioutil.WriteFile(filename, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test caplet: %v", err)
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directories
|
||||
tempDir, err := ioutil.TempDir("", "caplets-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create subdirectories
|
||||
dir1 := filepath.Join(tempDir, "dir1")
|
||||
dir2 := filepath.Join(tempDir, "dir2")
|
||||
subdir := filepath.Join(dir1, "subdir")
|
||||
|
||||
os.Mkdir(dir1, 0755)
|
||||
os.Mkdir(dir2, 0755)
|
||||
os.Mkdir(subdir, 0755)
|
||||
|
||||
// Create test caplets
|
||||
createTestCaplet(t, dir1, "test1.cap", []string{"# Test caplet 1", "set test 1"})
|
||||
createTestCaplet(t, dir1, "test2.cap", []string{"# Test caplet 2", "set test 2"})
|
||||
createTestCaplet(t, dir2, "test3.cap", []string{"# Test caplet 3", "set test 3"})
|
||||
createTestCaplet(t, subdir, "nested.cap", []string{"# Nested caplet", "set nested test"})
|
||||
|
||||
// Also create a non-caplet file
|
||||
ioutil.WriteFile(filepath.Join(dir1, "notacaplet.txt"), []byte("not a caplet"), 0644)
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{dir1, dir2}
|
||||
|
||||
// Call List()
|
||||
caplets := List()
|
||||
|
||||
// Check results
|
||||
if len(caplets) != 4 {
|
||||
t.Errorf("expected 4 caplets, got %d", len(caplets))
|
||||
}
|
||||
|
||||
// Check names (should be sorted)
|
||||
expectedNames := []string{filepath.Join("subdir", "nested"), "test1", "test2", "test3"}
|
||||
sort.Strings(expectedNames)
|
||||
|
||||
gotNames := make([]string, len(caplets))
|
||||
for i, cap := range caplets {
|
||||
gotNames[i] = cap.Name
|
||||
}
|
||||
|
||||
for i, expected := range expectedNames {
|
||||
if i >= len(gotNames) || gotNames[i] != expected {
|
||||
t.Errorf("expected caplet %d to be %s, got %s", i, expected, gotNames[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestListEmptyDirectories(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directory
|
||||
tempDir, err := ioutil.TempDir("", "caplets-empty-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Set LoadPaths to empty directory
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
// Call List()
|
||||
caplets := List()
|
||||
|
||||
// Should return empty list
|
||||
if len(caplets) != 0 {
|
||||
t.Errorf("expected 0 caplets, got %d", len(caplets))
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directory
|
||||
tempDir, err := ioutil.TempDir("", "caplets-load-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create test caplet
|
||||
capletContent := []string{
|
||||
"# Test caplet",
|
||||
"set param value",
|
||||
"",
|
||||
"# Another comment",
|
||||
"run command",
|
||||
}
|
||||
createTestCaplet(t, tempDir, "test.cap", capletContent)
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
// Test loading without .cap extension
|
||||
cap, err := Load("test")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if cap == nil {
|
||||
t.Error("caplet is nil")
|
||||
} else {
|
||||
if cap.Name != "test" {
|
||||
t.Errorf("expected name 'test', got %s", cap.Name)
|
||||
}
|
||||
if len(cap.Code) != len(capletContent) {
|
||||
t.Errorf("expected %d lines, got %d", len(capletContent), len(cap.Code))
|
||||
}
|
||||
}
|
||||
|
||||
// Test loading from cache
|
||||
// Note: The Load function caches with the suffix, so we need to use the same name with suffix
|
||||
cap2, err := Load("test.cap")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error on cache hit: %v", err)
|
||||
}
|
||||
if cap2 == nil {
|
||||
t.Error("caplet is nil on cache hit")
|
||||
}
|
||||
|
||||
// Test loading with .cap extension
|
||||
// Note: Load caches by the name parameter, so "test.cap" is a different cache key
|
||||
cap3, err := Load("test.cap")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error with .cap extension: %v", err)
|
||||
}
|
||||
if cap3 == nil {
|
||||
t.Error("caplet is nil")
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestLoadAbsolutePath(t *testing.T) {
|
||||
// Save original values
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp file
|
||||
tempFile, err := ioutil.TempFile("", "test-absolute-*.cap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// Write content
|
||||
content := "# Absolute path test\nset test absolute"
|
||||
tempFile.WriteString(content)
|
||||
tempFile.Close()
|
||||
|
||||
// Load with absolute path
|
||||
cap, err := Load(tempFile.Name())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if cap == nil {
|
||||
t.Error("caplet is nil")
|
||||
} else {
|
||||
if cap.Path != tempFile.Name() {
|
||||
t.Errorf("expected path %s, got %s", tempFile.Name(), cap.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestLoadNotFound(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Set empty LoadPaths
|
||||
LoadPaths = []string{}
|
||||
|
||||
// Try to load non-existent caplet
|
||||
cap, err := Load("nonexistent")
|
||||
if err == nil {
|
||||
t.Error("expected error for non-existent caplet")
|
||||
}
|
||||
if cap != nil {
|
||||
t.Error("expected nil caplet for non-existent file")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not found") {
|
||||
t.Errorf("expected 'not found' error, got: %v", err)
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestLoadWithFolder(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directory structure
|
||||
tempDir, err := ioutil.TempDir("", "caplets-folder-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a caplet folder
|
||||
capletDir := filepath.Join(tempDir, "mycaplet")
|
||||
os.Mkdir(capletDir, 0755)
|
||||
|
||||
// Create main caplet file
|
||||
mainContent := []string{"# Main caplet", "set main test"}
|
||||
createTestCaplet(t, capletDir, "mycaplet.cap", mainContent)
|
||||
|
||||
// Create additional files
|
||||
jsContent := []string{"// JavaScript file", "console.log('test');"}
|
||||
createTestCaplet(t, capletDir, "script.js", jsContent)
|
||||
|
||||
capContent := []string{"# Sub caplet", "set sub test"}
|
||||
createTestCaplet(t, capletDir, "sub.cap", capContent)
|
||||
|
||||
// Create a file that should be ignored
|
||||
ioutil.WriteFile(filepath.Join(capletDir, "readme.txt"), []byte("readme"), 0644)
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
// Load the caplet
|
||||
cap, err := Load("mycaplet/mycaplet")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if cap == nil {
|
||||
t.Fatal("caplet is nil")
|
||||
}
|
||||
|
||||
// Check main caplet
|
||||
if cap.Name != "mycaplet/mycaplet" {
|
||||
t.Errorf("expected name 'mycaplet/mycaplet', got %s", cap.Name)
|
||||
}
|
||||
if len(cap.Code) != len(mainContent) {
|
||||
t.Errorf("expected %d lines in main, got %d", len(mainContent), len(cap.Code))
|
||||
}
|
||||
|
||||
// Check additional scripts
|
||||
if len(cap.Scripts) != 2 {
|
||||
t.Errorf("expected 2 additional scripts, got %d", len(cap.Scripts))
|
||||
}
|
||||
|
||||
// Find and check the .js file
|
||||
foundJS := false
|
||||
foundCap := false
|
||||
for _, script := range cap.Scripts {
|
||||
if strings.HasSuffix(script.Path, "script.js") {
|
||||
foundJS = true
|
||||
if len(script.Code) != len(jsContent) {
|
||||
t.Errorf("expected %d lines in JS, got %d", len(jsContent), len(script.Code))
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(script.Path, "sub.cap") {
|
||||
foundCap = true
|
||||
if len(script.Code) != len(capContent) {
|
||||
t.Errorf("expected %d lines in sub.cap, got %d", len(capContent), len(script.Code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundJS {
|
||||
t.Error("script.js not found in Scripts")
|
||||
}
|
||||
if !foundCap {
|
||||
t.Error("sub.cap not found in Scripts")
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestCacheConcurrency(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directory
|
||||
tempDir, err := ioutil.TempDir("", "caplets-concurrent-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create test caplets
|
||||
for i := 0; i < 5; i++ {
|
||||
name := fmt.Sprintf("test%d.cap", i)
|
||||
content := []string{fmt.Sprintf("# Test %d", i)}
|
||||
createTestCaplet(t, tempDir, name, content)
|
||||
}
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
// Run concurrent loads
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 50)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for j := 0; j < 5; j++ {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
name := fmt.Sprintf("test%d", idx)
|
||||
_, err := Load(name)
|
||||
if err != nil {
|
||||
errors <- err
|
||||
}
|
||||
}(j)
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Errorf("concurrent load error: %v", err)
|
||||
}
|
||||
|
||||
// Verify cache has all entries
|
||||
if len(cache) != 5 {
|
||||
t.Errorf("expected 5 cached entries, got %d", len(cache))
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func TestLoadPathPriority(t *testing.T) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directories
|
||||
tempDir1, _ := ioutil.TempDir("", "caplets-priority1-")
|
||||
tempDir2, _ := ioutil.TempDir("", "caplets-priority2-")
|
||||
defer os.RemoveAll(tempDir1)
|
||||
defer os.RemoveAll(tempDir2)
|
||||
|
||||
// Create same-named caplet in both directories
|
||||
createTestCaplet(t, tempDir1, "test.cap", []string{"# From dir1"})
|
||||
createTestCaplet(t, tempDir2, "test.cap", []string{"# From dir2"})
|
||||
|
||||
// Set LoadPaths with tempDir1 first
|
||||
LoadPaths = []string{tempDir1, tempDir2}
|
||||
|
||||
// Load caplet
|
||||
cap, err := Load("test")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Should load from first directory
|
||||
if cap != nil && len(cap.Code) > 0 {
|
||||
if cap.Code[0] != "# From dir1" {
|
||||
t.Error("caplet not loaded from first directory in LoadPaths")
|
||||
}
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func BenchmarkLoad(b *testing.B) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
|
||||
// Create temp directory
|
||||
tempDir, _ := ioutil.TempDir("", "caplets-bench-")
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create test caplet
|
||||
content := make([]string, 100)
|
||||
for i := range content {
|
||||
content[i] = fmt.Sprintf("command %d", i)
|
||||
}
|
||||
createTestCaplet(b, tempDir, "bench.cap", content)
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Clear cache to measure loading time
|
||||
cache = make(map[string]*Caplet)
|
||||
Load("bench")
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func BenchmarkLoadFromCache(b *testing.B) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
cache = make(map[string]*Caplet)
|
||||
|
||||
// Create temp directory
|
||||
tempDir, _ := ioutil.TempDir("", "caplets-bench-cache-")
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create test caplet
|
||||
createTestCaplet(b, tempDir, "bench.cap", []string{"# Benchmark"})
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
// Pre-load into cache
|
||||
Load("bench")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Load("bench")
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
||||
func BenchmarkList(b *testing.B) {
|
||||
// Save original values
|
||||
origLoadPaths := LoadPaths
|
||||
origCache := cache
|
||||
|
||||
// Create temp directory
|
||||
tempDir, _ := ioutil.TempDir("", "caplets-bench-list-")
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create multiple caplets
|
||||
for i := 0; i < 20; i++ {
|
||||
name := fmt.Sprintf("test%d.cap", i)
|
||||
createTestCaplet(b, tempDir, name, []string{fmt.Sprintf("# Test %d", i)})
|
||||
}
|
||||
|
||||
// Set LoadPaths
|
||||
LoadPaths = []string{tempDir}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
cache = make(map[string]*Caplet)
|
||||
List()
|
||||
}
|
||||
|
||||
// Restore original values
|
||||
LoadPaths = origLoadPaths
|
||||
cache = origCache
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package core
|
|||
|
||||
const (
|
||||
Name = "bettercap"
|
||||
Version = "2.41.4"
|
||||
Version = "2.40.0"
|
||||
Author = "Simone 'evilsocket' Margaritelli"
|
||||
Website = "https://bettercap.org/"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -97,144 +97,3 @@ func TestCoreExists(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasBinary(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
executable string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "common shell",
|
||||
executable: "sh",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "echo command",
|
||||
executable: "echo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "non-existent binary",
|
||||
executable: "this-binary-definitely-does-not-exist-12345",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
executable: "",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := HasBinary(tt.executable)
|
||||
if got != tt.expected {
|
||||
t.Errorf("HasBinary(%q) = %v, want %v", tt.executable, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
executable string
|
||||
args []string
|
||||
wantError bool
|
||||
contains string
|
||||
}{
|
||||
{
|
||||
name: "echo with args",
|
||||
executable: "echo",
|
||||
args: []string{"hello", "world"},
|
||||
wantError: false,
|
||||
contains: "hello world",
|
||||
},
|
||||
{
|
||||
name: "echo empty",
|
||||
executable: "echo",
|
||||
args: []string{},
|
||||
wantError: false,
|
||||
contains: "",
|
||||
},
|
||||
{
|
||||
name: "non-existent command",
|
||||
executable: "this-command-does-not-exist-12345",
|
||||
args: []string{},
|
||||
wantError: true,
|
||||
contains: "",
|
||||
},
|
||||
{
|
||||
name: "true command",
|
||||
executable: "true",
|
||||
args: []string{},
|
||||
wantError: false,
|
||||
contains: "",
|
||||
},
|
||||
{
|
||||
name: "false command",
|
||||
executable: "false",
|
||||
args: []string{},
|
||||
wantError: true,
|
||||
contains: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip platform-specific commands if not available
|
||||
if !HasBinary(tt.executable) && !tt.wantError {
|
||||
t.Skipf("%s not found in PATH", tt.executable)
|
||||
}
|
||||
|
||||
output, err := Exec(tt.executable, tt.args)
|
||||
|
||||
if tt.wantError {
|
||||
if err == nil {
|
||||
t.Errorf("Exec(%q, %v) expected error but got none", tt.executable, tt.args)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Exec(%q, %v) unexpected error: %v", tt.executable, tt.args, err)
|
||||
}
|
||||
if tt.contains != "" && output != tt.contains {
|
||||
t.Errorf("Exec(%q, %v) = %q, want %q", tt.executable, tt.args, output, tt.contains)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecWithOutput(t *testing.T) {
|
||||
// Test that Exec properly captures and trims output
|
||||
if HasBinary("printf") {
|
||||
output, err := Exec("printf", []string{" hello world \n"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if output != "hello world" {
|
||||
t.Errorf("expected trimmed output 'hello world', got %q", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUniqueInts(b *testing.B) {
|
||||
// Create a slice with duplicates
|
||||
input := make([]int, 1000)
|
||||
for i := 0; i < 1000; i++ {
|
||||
input[i] = i % 100 // This creates 10 duplicates of each number 0-99
|
||||
}
|
||||
|
||||
b.Run("unsorted", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = UniqueInts(input, false)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("sorted", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = UniqueInts(input, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,268 +0,0 @@
|
|||
package firewall
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewRedirection(t *testing.T) {
|
||||
iface := "eth0"
|
||||
proto := "tcp"
|
||||
portFrom := 8080
|
||||
addrTo := "192.168.1.100"
|
||||
portTo := 9090
|
||||
|
||||
r := NewRedirection(iface, proto, portFrom, addrTo, portTo)
|
||||
|
||||
if r == nil {
|
||||
t.Fatal("NewRedirection returned nil")
|
||||
}
|
||||
|
||||
if r.Interface != iface {
|
||||
t.Errorf("expected Interface %s, got %s", iface, r.Interface)
|
||||
}
|
||||
|
||||
if r.Protocol != proto {
|
||||
t.Errorf("expected Protocol %s, got %s", proto, r.Protocol)
|
||||
}
|
||||
|
||||
if r.SrcAddress != "" {
|
||||
t.Errorf("expected empty SrcAddress, got %s", r.SrcAddress)
|
||||
}
|
||||
|
||||
if r.SrcPort != portFrom {
|
||||
t.Errorf("expected SrcPort %d, got %d", portFrom, r.SrcPort)
|
||||
}
|
||||
|
||||
if r.DstAddress != addrTo {
|
||||
t.Errorf("expected DstAddress %s, got %s", addrTo, r.DstAddress)
|
||||
}
|
||||
|
||||
if r.DstPort != portTo {
|
||||
t.Errorf("expected DstPort %d, got %d", portTo, r.DstPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectionString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
r Redirection
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "basic redirection",
|
||||
r: Redirection{
|
||||
Interface: "eth0",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "",
|
||||
SrcPort: 8080,
|
||||
DstAddress: "192.168.1.100",
|
||||
DstPort: 9090,
|
||||
},
|
||||
want: "[eth0] (tcp) :8080 -> 192.168.1.100:9090",
|
||||
},
|
||||
{
|
||||
name: "with source address",
|
||||
r: Redirection{
|
||||
Interface: "wlan0",
|
||||
Protocol: "udp",
|
||||
SrcAddress: "192.168.1.50",
|
||||
SrcPort: 53,
|
||||
DstAddress: "8.8.8.8",
|
||||
DstPort: 53,
|
||||
},
|
||||
want: "[wlan0] (udp) 192.168.1.50:53 -> 8.8.8.8:53",
|
||||
},
|
||||
{
|
||||
name: "localhost redirection",
|
||||
r: Redirection{
|
||||
Interface: "lo",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "127.0.0.1",
|
||||
SrcPort: 80,
|
||||
DstAddress: "127.0.0.1",
|
||||
DstPort: 8080,
|
||||
},
|
||||
want: "[lo] (tcp) 127.0.0.1:80 -> 127.0.0.1:8080",
|
||||
},
|
||||
{
|
||||
name: "high port numbers",
|
||||
r: Redirection{
|
||||
Interface: "eth1",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "",
|
||||
SrcPort: 65535,
|
||||
DstAddress: "10.0.0.1",
|
||||
DstPort: 65534,
|
||||
},
|
||||
want: "[eth1] (tcp) :65535 -> 10.0.0.1:65534",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.r.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("String() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRedirectionVariousProtocols(t *testing.T) {
|
||||
protocols := []string{"tcp", "udp", "icmp", "any"}
|
||||
|
||||
for _, proto := range protocols {
|
||||
t.Run(proto, func(t *testing.T) {
|
||||
r := NewRedirection("eth0", proto, 1234, "10.0.0.1", 5678)
|
||||
if r.Protocol != proto {
|
||||
t.Errorf("expected protocol %s, got %s", proto, r.Protocol)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRedirectionVariousInterfaces(t *testing.T) {
|
||||
interfaces := []string{"eth0", "wlan0", "lo", "docker0", "br0", "tun0"}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
t.Run(iface, func(t *testing.T) {
|
||||
r := NewRedirection(iface, "tcp", 80, "192.168.1.1", 8080)
|
||||
if r.Interface != iface {
|
||||
t.Errorf("expected interface %s, got %s", iface, r.Interface)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectionStringEmptyFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
r Redirection
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty interface",
|
||||
r: Redirection{
|
||||
Interface: "",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "",
|
||||
SrcPort: 80,
|
||||
DstAddress: "192.168.1.1",
|
||||
DstPort: 8080,
|
||||
},
|
||||
want: "[] (tcp) :80 -> 192.168.1.1:8080",
|
||||
},
|
||||
{
|
||||
name: "empty protocol",
|
||||
r: Redirection{
|
||||
Interface: "eth0",
|
||||
Protocol: "",
|
||||
SrcAddress: "",
|
||||
SrcPort: 80,
|
||||
DstAddress: "192.168.1.1",
|
||||
DstPort: 8080,
|
||||
},
|
||||
want: "[eth0] () :80 -> 192.168.1.1:8080",
|
||||
},
|
||||
{
|
||||
name: "empty destination",
|
||||
r: Redirection{
|
||||
Interface: "eth0",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "",
|
||||
SrcPort: 80,
|
||||
DstAddress: "",
|
||||
DstPort: 8080,
|
||||
},
|
||||
want: "[eth0] (tcp) :80 -> :8080",
|
||||
},
|
||||
{
|
||||
name: "all empty strings",
|
||||
r: Redirection{
|
||||
Interface: "",
|
||||
Protocol: "",
|
||||
SrcAddress: "",
|
||||
SrcPort: 0,
|
||||
DstAddress: "",
|
||||
DstPort: 0,
|
||||
},
|
||||
want: "[] () :0 -> :0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.r.String()
|
||||
if got != tt.want {
|
||||
t.Errorf("String() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectionStructCopy(t *testing.T) {
|
||||
// Test that Redirection can be safely copied
|
||||
original := NewRedirection("eth0", "tcp", 80, "192.168.1.1", 8080)
|
||||
original.SrcAddress = "10.0.0.1"
|
||||
|
||||
// Create a copy
|
||||
copy := *original
|
||||
|
||||
// Modify the copy
|
||||
copy.Interface = "wlan0"
|
||||
copy.SrcPort = 443
|
||||
|
||||
// Verify original is unchanged
|
||||
if original.Interface != "eth0" {
|
||||
t.Error("original Interface was modified")
|
||||
}
|
||||
if original.SrcPort != 80 {
|
||||
t.Error("original SrcPort was modified")
|
||||
}
|
||||
|
||||
// Verify copy has new values
|
||||
if copy.Interface != "wlan0" {
|
||||
t.Error("copy Interface was not set correctly")
|
||||
}
|
||||
if copy.SrcPort != 443 {
|
||||
t.Error("copy SrcPort was not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewRedirection(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewRedirection("eth0", "tcp", 80, "192.168.1.1", 8080)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRedirectionString(b *testing.B) {
|
||||
r := Redirection{
|
||||
Interface: "eth0",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "192.168.1.50",
|
||||
SrcPort: 8080,
|
||||
DstAddress: "192.168.1.100",
|
||||
DstPort: 9090,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.String()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRedirectionStringEmpty(b *testing.B) {
|
||||
r := Redirection{
|
||||
Interface: "eth0",
|
||||
Protocol: "tcp",
|
||||
SrcAddress: "",
|
||||
SrcPort: 8080,
|
||||
DstAddress: "192.168.1.100",
|
||||
DstPort: 9090,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.String()
|
||||
}
|
||||
}
|
||||
45
go.mod
45
go.mod
|
|
@ -1,20 +1,19 @@
|
|||
module github.com/bettercap/bettercap/v2
|
||||
|
||||
go 1.23.0
|
||||
go 1.21
|
||||
|
||||
toolchain go1.24.4
|
||||
toolchain go1.22.6
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/adrianmo/go-nmea v1.10.0
|
||||
github.com/antchfx/jsonquery v1.3.6
|
||||
github.com/adrianmo/go-nmea v1.9.0
|
||||
github.com/antchfx/jsonquery v1.3.5
|
||||
github.com/bettercap/gatt v0.0.0-20240808115956-ec4935e8c4a0
|
||||
github.com/bettercap/nrf24 v0.0.0-20190219153547-aa37e6d0e0eb
|
||||
github.com/bettercap/readline v0.0.0-20210228151553-655e48bcb7bf
|
||||
github.com/bettercap/recording v0.0.0-20190408083647-3ce1dcf032e3
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/elazarl/goproxy v1.7.2
|
||||
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380
|
||||
github.com/evilsocket/islazy v1.11.0
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0
|
||||
github.com/gobwas/glob v0.0.0-20181002190808-e7a84e9525fe
|
||||
|
|
@ -24,44 +23,44 @@ require (
|
|||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hashicorp/go-bexpr v0.1.14
|
||||
github.com/hashicorp/mdns v1.0.5
|
||||
github.com/inconshreveable/go-vhost v1.0.0
|
||||
github.com/jpillora/go-tld v1.2.1
|
||||
github.com/malfunkt/iprange v0.9.0
|
||||
github.com/mdlayher/dhcp6 v0.0.0-20190311162359-2a67805d7d0b
|
||||
github.com/miekg/dns v1.1.67
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/phin1x/go-ipp v1.6.1
|
||||
github.com/robertkrimen/otto v0.5.1
|
||||
github.com/robertkrimen/otto v0.4.0
|
||||
github.com/stratoberry/go-gpsd v1.3.0
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
|
||||
go.einride.tech/can v0.14.0
|
||||
golang.org/x/net v0.42.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
go.einride.tech/can v0.12.0
|
||||
golang.org/x/net v0.28.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/antchfx/xpath v1.3.4 // indirect
|
||||
github.com/antchfx/xpath v1.3.1 // indirect
|
||||
github.com/chzyer/logex v1.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/kr/binarydist v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/mitchellh/pointerstructure v1.2.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
gopkg.in/sourcemap.v1 v1.0.5 // indirect
|
||||
)
|
||||
|
|
|
|||
92
go.sum
92
go.sum
|
|
@ -1,12 +1,11 @@
|
|||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/adrianmo/go-nmea v1.10.0 h1:L1aYaebZ4cXFCoXNSeDeQa0tApvSKvIbqMsK+iaRiCo=
|
||||
github.com/adrianmo/go-nmea v1.10.0/go.mod h1:u8bPnpKt/D/5rll/5l9f6iDfeq5WZW0+/SXdkwix6Tg=
|
||||
github.com/antchfx/jsonquery v1.3.6 h1:TaSfeAh7n6T11I74bsZ1FswreIfrbJ0X+OyLflx6mx4=
|
||||
github.com/antchfx/jsonquery v1.3.6/go.mod h1:fGzSGJn9Y826Qd3pC8Wx45avuUwpkePsACQJYy+58BU=
|
||||
github.com/antchfx/xpath v1.3.2/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/antchfx/xpath v1.3.4 h1:1ixrW1VnXd4HurCj7qnqnR0jo14g8JMe20Fshg1Vgz4=
|
||||
github.com/antchfx/xpath v1.3.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/adrianmo/go-nmea v1.9.0 h1:kCuerWLDIppltHNZ2HGdCGkqbmupYJYfE6indcGkcp8=
|
||||
github.com/adrianmo/go-nmea v1.9.0/go.mod h1:u8bPnpKt/D/5rll/5l9f6iDfeq5WZW0+/SXdkwix6Tg=
|
||||
github.com/antchfx/jsonquery v1.3.5 h1:243OSaQh02EfmASa3w3weKC9UaiD8RRzJhgfvq3q408=
|
||||
github.com/antchfx/jsonquery v1.3.5/go.mod h1:qH23yX2Jsj1/k378Yu/EOgPCNgJ35P9tiGOeQdt/GWc=
|
||||
github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk=
|
||||
github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
|
||||
github.com/bettercap/gatt v0.0.0-20240808115956-ec4935e8c4a0 h1:HiFUGV/7eGWG/YJAf9HcKOUmxIj+7LVzC8zD57VX1qo=
|
||||
github.com/bettercap/gatt v0.0.0-20240808115956-ec4935e8c4a0/go.mod h1:oafnPgaBI4gqJiYkueCyR4dqygiWGXTGOE0gmmAVeeQ=
|
||||
github.com/bettercap/nrf24 v0.0.0-20190219153547-aa37e6d0e0eb h1:JWAAJk4ny+bT3VrtcX+e7mcmWtWUeUM0xVcocSAUuWc=
|
||||
|
|
@ -15,8 +14,6 @@ github.com/bettercap/readline v0.0.0-20210228151553-655e48bcb7bf h1:pwGPRc5PIp4K
|
|||
github.com/bettercap/readline v0.0.0-20210228151553-655e48bcb7bf/go.mod h1:03rWiUf60r1miMVzMEtgtkq7RdZniecZFw3/Zgvyxcs=
|
||||
github.com/bettercap/recording v0.0.0-20190408083647-3ce1dcf032e3 h1:pC4ZAk7UtDIbrRKzMMiIL1TVkiKlgtgcJodqKB53Rl4=
|
||||
github.com/bettercap/recording v0.0.0-20190408083647-3ce1dcf032e3/go.mod h1:kqVwnx6DKuOHMZcBnzsgp2Lq2JZHDtFtm92b5hxdRaM=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
|
|
@ -27,22 +24,23 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 h1:1NyRx2f4W4WBRyg0Kys0ZbaNmDDzZ2R/C7DTi+bbsJ0=
|
||||
github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380/go.mod h1:thX175TtLTzLj3p7N/Q9IiKZ7NF+p72cvL91emV0hzo=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e h1:CQn2/8fi3kmpT9BTiHEELgdxAOQNVZc9GoPA4qnQzrs=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/evilsocket/islazy v1.11.0 h1:B5w6uuS6ki6iDG+aH/RFeoMb8ijQh/pGabewqp2UeJ0=
|
||||
github.com/evilsocket/islazy v1.11.0/go.mod h1:muYH4x5MB5YRdkxnrOtrXLIBX6LySj1uFIqys94LKdo=
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0 h1:NTCxS9b0GSbHkWv1a7oOvZn679fsyDkaSkRvOYpQ9Oo=
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0/go.mod h1:M2tBLIj62QpwqjwV0qfcjqGOqP3qiTuXr2uSRBXH9Qk=
|
||||
github.com/gobwas/glob v0.0.0-20181002190808-e7a84e9525fe h1:8P+/htb3mwwpeGdJg69yBF/RofK7c6Fjz5Ypa/bTqbY=
|
||||
github.com/gobwas/glob v0.0.0-20181002190808-e7a84e9525fe/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
|
|
@ -57,6 +55,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
|||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/go-bexpr v0.1.14 h1:uKDeyuOhWhT1r5CiMTjdVY4Aoxdxs6EtwgTGnlosyp4=
|
||||
github.com/hashicorp/go-bexpr v0.1.14/go.mod h1:gN7hRKB3s7yT+YvTdnhZVLTENejvhlkZ8UE4YVBS+Q8=
|
||||
github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE=
|
||||
github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/inconshreveable/go-vhost v1.0.0 h1:IK4VZTlXL4l9vz2IZoiSFbYaaqUW7dXJAiPriUN5Ur8=
|
||||
github.com/inconshreveable/go-vhost v1.0.0/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
|
|
@ -65,47 +65,45 @@ github.com/jpillora/go-tld v1.2.1 h1:kDKOkmXLlskqjcvNs7w5XHLep7c8WM7Xd4HQjxllVMk
|
|||
github.com/jpillora/go-tld v1.2.1/go.mod h1:plzIl7xr5UWKGy7R+giuv+L/nOjrPjsoWxy/ST9OBUk=
|
||||
github.com/kr/binarydist v0.1.0 h1:6kAoLA9FMMnNGSehX0s1PdjbEaACznAv/W219j2uvyo=
|
||||
github.com/kr/binarydist v0.1.0/go.mod h1:DY7S//GCoz1BCd0B0EVrinCKAZN3pXe+MDaIZbXQVgM=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/malfunkt/iprange v0.9.0 h1:VCs0PKLUPotNVQTpVNszsut4lP7OCGNBwX+lOYBrnVQ=
|
||||
github.com/malfunkt/iprange v0.9.0/go.mod h1:TRGqO/f95gh3LOndUGTL46+W0GXA91WTqyZ0Quwvt4U=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mdlayher/dhcp6 v0.0.0-20190311162359-2a67805d7d0b h1:r12blE3QRYlW1WBiBEe007O6NrTb/P54OjR5d4WLEGk=
|
||||
github.com/mdlayher/dhcp6 v0.0.0-20190311162359-2a67805d7d0b/go.mod h1:p4K2+UAoap8Jzsadsxc0KG0OZjmmCthTPUyZqAVkjBY=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab h1:n8cgpHzJ5+EDyDri2s/GC7a9+qK3/YEGnBsd0uS/8PY=
|
||||
github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab/go.mod h1:y1pL58r5z2VvAjeG1VLGc8zOQgSOzbKN7kMHPvFXJ+8=
|
||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw=
|
||||
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/phin1x/go-ipp v1.6.1 h1:oxJXi92BO2FZhNcG3twjnxKFH1liTQ46vbbZx+IN/80=
|
||||
github.com/phin1x/go-ipp v1.6.1/go.mod h1:GZwyNds6grdLi2xRBX22Cvt7Dh7ITWsML0bjrqBF5uo=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0=
|
||||
github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8=
|
||||
github.com/robertkrimen/otto v0.4.0 h1:/c0GRrK1XDPcgIasAsnlpBT5DelIeB9U/Z/JCQsgr7E=
|
||||
github.com/robertkrimen/otto v0.4.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/stratoberry/go-gpsd v1.3.0 h1:JxJOEC4SgD0QY65AE7B1CtJtweP73nqJghZeLNU9J+c=
|
||||
github.com/stratoberry/go-gpsd v1.3.0/go.mod h1:nVf/vTgfYxOMxiQdy9BtJjojbFRtG8H3wNula++VgkU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -115,16 +113,15 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 h1:l/T7dYuJEQZOwVOpjIXr1180aM9PZL/d1MnMVIxefX4=
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64/go.mod h1:Q1NAJOuRdQCqN/VIWdnaaEhV8LpeO2rtlBP7/iDJNII=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.einride.tech/can v0.14.0 h1:OkQ0jsjCk4ijgTMjD43V1NKQyDztpX7Vo/NrvmnsAXE=
|
||||
go.einride.tech/can v0.14.0/go.mod h1:615YuRGnWfndMGD+f3Ud1sp1xJLP1oj14dKRtb2CXDQ=
|
||||
go.einride.tech/can v0.12.0 h1:6MW9TKycSovWqJxcYHpZEiuFCGuAfpqApCzTS15KrPk=
|
||||
go.einride.tech/can v0.12.0/go.mod h1:5n3+AonCfUso6PfjD9l2d0W2LxTFjjHOnHAm+UMS9Ws=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
|
@ -132,54 +129,57 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190310074541-c10a0554eabf/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
|
||||
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
|
||||
|
|
|
|||
4
hooks/post_checkout
Normal file
4
hooks/post_checkout
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
# Docker hub does a recursive clone, then checks the branch out,
|
||||
# so when a PR adds a submodule (or updates it), it fails.
|
||||
git submodule update --init
|
||||
29
js/crypto.go
29
js/crypto.go
|
|
@ -1,29 +0,0 @@
|
|||
package js
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
func cryptoSha1(call otto.FunctionCall) otto.Value {
|
||||
argv := call.ArgumentList
|
||||
argc := len(argv)
|
||||
if argc != 1 {
|
||||
return ReportError("Crypto.sha1: expected 1 argument, %d given instead.", argc)
|
||||
}
|
||||
|
||||
arg := argv[0]
|
||||
if (!arg.IsString()) {
|
||||
return ReportError("Crypto.sha1: single argument must be a string.")
|
||||
}
|
||||
|
||||
hasher := sha1.New()
|
||||
hasher.Write([]byte(arg.String()))
|
||||
v, err := otto.ToValue(string(hasher.Sum(nil)))
|
||||
if err != nil {
|
||||
return ReportError("Crypto.sha1: could not convert to string: %s", err)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
94
js/data.go
94
js/data.go
|
|
@ -8,94 +8,25 @@ import (
|
|||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
func textEncode(call otto.FunctionCall) otto.Value {
|
||||
argv := call.ArgumentList
|
||||
argc := len(argv)
|
||||
if argc != 1 {
|
||||
return ReportError("textEncode: expected 1 argument, %d given instead.", argc)
|
||||
}
|
||||
|
||||
arg := argv[0]
|
||||
if (!arg.IsString()) {
|
||||
return ReportError("textEncode: single argument must be a string.")
|
||||
}
|
||||
|
||||
encoded := []byte(arg.String())
|
||||
vm := otto.New()
|
||||
v, err := vm.ToValue(encoded)
|
||||
if err != nil {
|
||||
return ReportError("textEncode: could not convert to []uint8: %s", err.Error())
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func textDecode(call otto.FunctionCall) otto.Value {
|
||||
argv := call.ArgumentList
|
||||
argc := len(argv)
|
||||
if argc != 1 {
|
||||
return ReportError("textDecode: expected 1 argument, %d given instead.", argc)
|
||||
}
|
||||
|
||||
arg, err := argv[0].Export()
|
||||
if err != nil {
|
||||
return ReportError("textDecode: could not export argument value: %s", err.Error())
|
||||
}
|
||||
byteArr, ok := arg.([]uint8)
|
||||
if !ok {
|
||||
return ReportError("textDecode: single argument must be of type []uint8.")
|
||||
}
|
||||
|
||||
decoded := string(byteArr)
|
||||
v, err := otto.ToValue(decoded)
|
||||
if err != nil {
|
||||
return ReportError("textDecode: could not convert to string: %s", err.Error())
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func btoa(call otto.FunctionCall) otto.Value {
|
||||
argv := call.ArgumentList
|
||||
argc := len(argv)
|
||||
if argc != 1 {
|
||||
return ReportError("btoa: expected 1 argument, %d given instead.", argc)
|
||||
}
|
||||
|
||||
arg := argv[0]
|
||||
if (!arg.IsString()) {
|
||||
return ReportError("btoa: single argument must be a string.")
|
||||
}
|
||||
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(arg.String()))
|
||||
v, err := otto.ToValue(encoded)
|
||||
varValue := base64.StdEncoding.EncodeToString([]byte(call.Argument(0).String()))
|
||||
v, err := otto.ToValue(varValue)
|
||||
if err != nil {
|
||||
return ReportError("btoa: could not convert to string: %s", err.Error())
|
||||
return ReportError("Could not convert to string: %s", varValue)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func atob(call otto.FunctionCall) otto.Value {
|
||||
argv := call.ArgumentList
|
||||
argc := len(argv)
|
||||
if argc != 1 {
|
||||
return ReportError("atob: expected 1 argument, %d given instead.", argc)
|
||||
}
|
||||
|
||||
arg := argv[0]
|
||||
if (!arg.IsString()) {
|
||||
return ReportError("atob: single argument must be a string.")
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(arg.String())
|
||||
varValue, err := base64.StdEncoding.DecodeString(call.Argument(0).String())
|
||||
if err != nil {
|
||||
return ReportError("atob: could not decode string: %s", err.Error())
|
||||
return ReportError("Could not decode string: %s", call.Argument(0).String())
|
||||
}
|
||||
|
||||
v, err := otto.ToValue(string(decoded))
|
||||
v, err := otto.ToValue(string(varValue))
|
||||
if err != nil {
|
||||
return ReportError("atob: could not convert to string: %s", err.Error())
|
||||
return ReportError("Could not convert to string: %s", varValue)
|
||||
}
|
||||
|
||||
return v
|
||||
|
|
@ -108,12 +39,7 @@ func gzipCompress(call otto.FunctionCall) otto.Value {
|
|||
return ReportError("gzipCompress: expected 1 argument, %d given instead.", argc)
|
||||
}
|
||||
|
||||
arg := argv[0]
|
||||
if (!arg.IsString()) {
|
||||
return ReportError("gzipCompress: single argument must be a string.")
|
||||
}
|
||||
|
||||
uncompressedBytes := []byte(arg.String())
|
||||
uncompressedBytes := []byte(argv[0].String())
|
||||
|
||||
var writerBuffer bytes.Buffer
|
||||
gzipWriter := gzip.NewWriter(&writerBuffer)
|
||||
|
|
@ -127,7 +53,7 @@ func gzipCompress(call otto.FunctionCall) otto.Value {
|
|||
|
||||
v, err := otto.ToValue(string(compressedBytes))
|
||||
if err != nil {
|
||||
return ReportError("gzipCompress: could not convert to string: %s", err.Error())
|
||||
return ReportError("Could not convert to string: %s", err.Error())
|
||||
}
|
||||
|
||||
return v
|
||||
|
|
@ -157,7 +83,7 @@ func gzipDecompress(call otto.FunctionCall) otto.Value {
|
|||
decompressedBytes := decompressedBuffer.Bytes()
|
||||
v, err := otto.ToValue(string(decompressedBytes))
|
||||
if err != nil {
|
||||
return ReportError("gzipDecompress: could not convert to string: %s", err.Error())
|
||||
return ReportError("Could not convert to string: %s", err.Error())
|
||||
}
|
||||
|
||||
return v
|
||||
|
|
|
|||
514
js/data_test.go
514
js/data_test.go
|
|
@ -1,514 +0,0 @@
|
|||
package js
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
func TestBtoa(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "simple string",
|
||||
input: "hello world",
|
||||
expected: base64.StdEncoding.EncodeToString([]byte("hello world")),
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: base64.StdEncoding.EncodeToString([]byte("")),
|
||||
},
|
||||
{
|
||||
name: "special characters",
|
||||
input: "!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
expected: base64.StdEncoding.EncodeToString([]byte("!@#$%^&*()_+-=[]{}|;:,.<>?")),
|
||||
},
|
||||
{
|
||||
name: "unicode string",
|
||||
input: "Hello 世界 🌍",
|
||||
expected: base64.StdEncoding.EncodeToString([]byte("Hello 世界 🌍")),
|
||||
},
|
||||
{
|
||||
name: "newlines and tabs",
|
||||
input: "line1\nline2\ttab",
|
||||
expected: base64.StdEncoding.EncodeToString([]byte("line1\nline2\ttab")),
|
||||
},
|
||||
{
|
||||
name: "long string",
|
||||
input: strings.Repeat("a", 1000),
|
||||
expected: base64.StdEncoding.EncodeToString([]byte(strings.Repeat("a", 1000))),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create call with argument
|
||||
arg, _ := vm.ToValue(tt.input)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := btoa(call)
|
||||
|
||||
// Check if result is an error
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("btoa returned undefined")
|
||||
}
|
||||
|
||||
// Get string value
|
||||
resultStr, err := result.ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert result to string: %v", err)
|
||||
}
|
||||
|
||||
if resultStr != tt.expected {
|
||||
t.Errorf("btoa(%q) = %q, want %q", tt.input, resultStr, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtob(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
name: "simple base64",
|
||||
input: base64.StdEncoding.EncodeToString([]byte("hello world")),
|
||||
expected: "hello world",
|
||||
},
|
||||
{
|
||||
name: "empty base64",
|
||||
input: base64.StdEncoding.EncodeToString([]byte("")),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "special characters base64",
|
||||
input: base64.StdEncoding.EncodeToString([]byte("!@#$%^&*()_+-=[]{}|;:,.<>?")),
|
||||
expected: "!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
},
|
||||
{
|
||||
name: "unicode base64",
|
||||
input: base64.StdEncoding.EncodeToString([]byte("Hello 世界 🌍")),
|
||||
expected: "Hello 世界 🌍",
|
||||
},
|
||||
{
|
||||
name: "invalid base64",
|
||||
input: "not valid base64!",
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid padding",
|
||||
input: "SGVsbG8gV29ybGQ", // Missing padding
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "long base64",
|
||||
input: base64.StdEncoding.EncodeToString([]byte(strings.Repeat("a", 1000))),
|
||||
expected: strings.Repeat("a", 1000),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create call with argument
|
||||
arg, _ := vm.ToValue(tt.input)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := atob(call)
|
||||
|
||||
// Get string value
|
||||
resultStr, err := result.ToString()
|
||||
if err != nil && !tt.wantError {
|
||||
t.Fatalf("failed to convert result to string: %v", err)
|
||||
}
|
||||
|
||||
if tt.wantError {
|
||||
// Should return undefined (NullValue) on error
|
||||
if !result.IsUndefined() {
|
||||
t.Errorf("expected undefined for error case, got %q", resultStr)
|
||||
}
|
||||
} else {
|
||||
if resultStr != tt.expected {
|
||||
t.Errorf("atob(%q) = %q, want %q", tt.input, resultStr, tt.expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGzipCompress(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "simple string",
|
||||
input: "hello world",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
},
|
||||
{
|
||||
name: "repeated pattern",
|
||||
input: strings.Repeat("abcd", 100),
|
||||
},
|
||||
{
|
||||
name: "random text",
|
||||
input: "The quick brown fox jumps over the lazy dog. " + strings.Repeat("Lorem ipsum dolor sit amet. ", 10),
|
||||
},
|
||||
{
|
||||
name: "unicode text",
|
||||
input: "Hello 世界 🌍 " + strings.Repeat("测试数据 ", 50),
|
||||
},
|
||||
{
|
||||
name: "binary-like data",
|
||||
input: string([]byte{0, 1, 2, 3, 255, 254, 253, 252}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create call with argument
|
||||
arg, _ := vm.ToValue(tt.input)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := gzipCompress(call)
|
||||
|
||||
// Get compressed data
|
||||
compressed, err := result.ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert result to string: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's actually compressed (for non-empty strings, compressed should be different)
|
||||
if tt.input != "" && compressed == tt.input {
|
||||
t.Error("compressed data is same as input")
|
||||
}
|
||||
|
||||
// Verify gzip header (should start with 0x1f, 0x8b)
|
||||
if len(compressed) >= 2 {
|
||||
if compressed[0] != 0x1f || compressed[1] != 0x8b {
|
||||
t.Error("compressed data doesn't have valid gzip header")
|
||||
}
|
||||
}
|
||||
|
||||
// Now decompress to verify
|
||||
argCompressed, _ := vm.ToValue(compressed)
|
||||
callDecompress := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argCompressed},
|
||||
}
|
||||
|
||||
resultDecompressed := gzipDecompress(callDecompress)
|
||||
decompressed, err := resultDecompressed.ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decompress: %v", err)
|
||||
}
|
||||
|
||||
if decompressed != tt.input {
|
||||
t.Errorf("round-trip failed: got %q, want %q", decompressed, tt.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGzipCompressInvalidArgs(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []otto.Value
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
args: []otto.Value{},
|
||||
},
|
||||
{
|
||||
name: "too many arguments",
|
||||
args: func() []otto.Value {
|
||||
arg1, _ := vm.ToValue("test")
|
||||
arg2, _ := vm.ToValue("extra")
|
||||
return []otto.Value{arg1, arg2}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: tt.args,
|
||||
}
|
||||
|
||||
result := gzipCompress(call)
|
||||
|
||||
// Should return undefined (NullValue) on error
|
||||
if !result.IsUndefined() {
|
||||
resultStr, _ := result.ToString()
|
||||
t.Errorf("expected undefined for error case, got %q", resultStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGzipDecompress(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
// First compress some data
|
||||
originalData := "This is test data for decompression"
|
||||
arg, _ := vm.ToValue(originalData)
|
||||
compressCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
compressedResult := gzipCompress(compressCall)
|
||||
compressedData, _ := compressedResult.ToString()
|
||||
|
||||
t.Run("valid decompression", func(t *testing.T) {
|
||||
argCompressed, _ := vm.ToValue(compressedData)
|
||||
decompressCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argCompressed},
|
||||
}
|
||||
|
||||
result := gzipDecompress(decompressCall)
|
||||
decompressed, err := result.ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert result to string: %v", err)
|
||||
}
|
||||
|
||||
if decompressed != originalData {
|
||||
t.Errorf("decompressed data doesn't match original: got %q, want %q", decompressed, originalData)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid gzip data", func(t *testing.T) {
|
||||
argInvalid, _ := vm.ToValue("not gzip data")
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argInvalid},
|
||||
}
|
||||
|
||||
result := gzipDecompress(call)
|
||||
|
||||
// Should return undefined (NullValue) on error
|
||||
if !result.IsUndefined() {
|
||||
resultStr, _ := result.ToString()
|
||||
t.Errorf("expected undefined for error case, got %q", resultStr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("corrupted gzip data", func(t *testing.T) {
|
||||
// Create corrupted gzip by taking valid gzip and modifying it
|
||||
corruptedData := compressedData[:len(compressedData)/2] + "corrupted"
|
||||
|
||||
argCorrupted, _ := vm.ToValue(corruptedData)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argCorrupted},
|
||||
}
|
||||
|
||||
result := gzipDecompress(call)
|
||||
|
||||
// Should return undefined (NullValue) on error
|
||||
if !result.IsUndefined() {
|
||||
resultStr, _ := result.ToString()
|
||||
t.Errorf("expected undefined for error case, got %q", resultStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGzipDecompressInvalidArgs(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []otto.Value
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
args: []otto.Value{},
|
||||
},
|
||||
{
|
||||
name: "too many arguments",
|
||||
args: func() []otto.Value {
|
||||
arg1, _ := vm.ToValue("test")
|
||||
arg2, _ := vm.ToValue("extra")
|
||||
return []otto.Value{arg1, arg2}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: tt.args,
|
||||
}
|
||||
|
||||
result := gzipDecompress(call)
|
||||
|
||||
// Should return undefined (NullValue) on error
|
||||
if !result.IsUndefined() {
|
||||
resultStr, _ := result.ToString()
|
||||
t.Errorf("expected undefined for error case, got %q", resultStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBtoaAtobRoundTrip(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
testStrings := []string{
|
||||
"simple",
|
||||
"",
|
||||
"with spaces and\nnewlines\ttabs",
|
||||
"special!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
"unicode 世界 🌍",
|
||||
strings.Repeat("long string ", 100),
|
||||
}
|
||||
|
||||
for _, original := range testStrings {
|
||||
t.Run(original, func(t *testing.T) {
|
||||
// Encode with btoa
|
||||
argOriginal, _ := vm.ToValue(original)
|
||||
encodeCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argOriginal},
|
||||
}
|
||||
|
||||
encoded := btoa(encodeCall)
|
||||
encodedStr, _ := encoded.ToString()
|
||||
|
||||
// Decode with atob
|
||||
argEncoded, _ := vm.ToValue(encodedStr)
|
||||
decodeCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argEncoded},
|
||||
}
|
||||
|
||||
decoded := atob(decodeCall)
|
||||
decodedStr, _ := decoded.ToString()
|
||||
|
||||
if decodedStr != original {
|
||||
t.Errorf("round-trip failed: got %q, want %q", decodedStr, original)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGzipCompressDecompressRoundTrip(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
testData := []string{
|
||||
"simple",
|
||||
"",
|
||||
strings.Repeat("repetitive data ", 100),
|
||||
"unicode 世界 🌍 " + strings.Repeat("测试 ", 50),
|
||||
string([]byte{0, 1, 2, 3, 255, 254, 253, 252}),
|
||||
}
|
||||
|
||||
for _, original := range testData {
|
||||
t.Run(original, func(t *testing.T) {
|
||||
// Compress
|
||||
argOriginal, _ := vm.ToValue(original)
|
||||
compressCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argOriginal},
|
||||
}
|
||||
|
||||
compressed := gzipCompress(compressCall)
|
||||
compressedStr, _ := compressed.ToString()
|
||||
|
||||
// Decompress
|
||||
argCompressed, _ := vm.ToValue(compressedStr)
|
||||
decompressCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argCompressed},
|
||||
}
|
||||
|
||||
decompressed := gzipDecompress(decompressCall)
|
||||
decompressedStr, _ := decompressed.ToString()
|
||||
|
||||
if decompressedStr != original {
|
||||
t.Errorf("round-trip failed: got %q, want %q", decompressedStr, original)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBtoa(b *testing.B) {
|
||||
vm := otto.New()
|
||||
arg, _ := vm.ToValue("The quick brown fox jumps over the lazy dog")
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = btoa(call)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAtob(b *testing.B) {
|
||||
vm := otto.New()
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte("The quick brown fox jumps over the lazy dog"))
|
||||
arg, _ := vm.ToValue(encoded)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = atob(call)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGzipCompress(b *testing.B) {
|
||||
vm := otto.New()
|
||||
data := strings.Repeat("The quick brown fox jumps over the lazy dog. ", 10)
|
||||
arg, _ := vm.ToValue(data)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = gzipCompress(call)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGzipDecompress(b *testing.B) {
|
||||
vm := otto.New()
|
||||
|
||||
// First compress some data
|
||||
data := strings.Repeat("The quick brown fox jumps over the lazy dog. ", 10)
|
||||
argData, _ := vm.ToValue(data)
|
||||
compressCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argData},
|
||||
}
|
||||
compressed := gzipCompress(compressCall)
|
||||
compressedStr, _ := compressed.ToString()
|
||||
|
||||
// Benchmark decompression
|
||||
argCompressed, _ := vm.ToValue(compressedStr)
|
||||
decompressCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argCompressed},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = gzipDecompress(decompressCall)
|
||||
}
|
||||
}
|
||||
684
js/fs_test.go
684
js/fs_test.go
|
|
@ -1,684 +0,0 @@
|
|||
package js
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
func TestReadDir(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
// Create a temporary directory for testing
|
||||
tmpDir, err := ioutil.TempDir("", "js_test_readdir_*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create some test files and subdirectories
|
||||
testFiles := []string{"file1.txt", "file2.log", ".hidden"}
|
||||
testDirs := []string{"subdir1", "subdir2"}
|
||||
|
||||
for _, name := range testFiles {
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpDir, name), []byte("test"), 0644); err != nil {
|
||||
t.Fatalf("failed to create test file %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range testDirs {
|
||||
if err := os.Mkdir(filepath.Join(tmpDir, name), 0755); err != nil {
|
||||
t.Fatalf("failed to create test dir %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("valid directory", func(t *testing.T) {
|
||||
arg, _ := vm.ToValue(tmpDir)
|
||||
call := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readDir(call)
|
||||
|
||||
// Check if result is not undefined
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("readDir returned undefined")
|
||||
}
|
||||
|
||||
// Convert to Go slice
|
||||
export, err := result.Export()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to export result: %v", err)
|
||||
}
|
||||
|
||||
entries, ok := export.([]string)
|
||||
if !ok {
|
||||
t.Fatalf("expected []string, got %T", export)
|
||||
}
|
||||
|
||||
// Check all expected entries are present
|
||||
expectedEntries := append(testFiles, testDirs...)
|
||||
if len(entries) != len(expectedEntries) {
|
||||
t.Errorf("expected %d entries, got %d", len(expectedEntries), len(entries))
|
||||
}
|
||||
|
||||
// Check each entry exists
|
||||
for _, expected := range expectedEntries {
|
||||
found := false
|
||||
for _, entry := range entries {
|
||||
if entry == expected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected entry %s not found", expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-existent directory", func(t *testing.T) {
|
||||
arg, _ := vm.ToValue("/path/that/does/not/exist")
|
||||
call := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readDir(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined for non-existent directory")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file instead of directory", func(t *testing.T) {
|
||||
// Create a file
|
||||
testFile := filepath.Join(tmpDir, "notadir.txt")
|
||||
ioutil.WriteFile(testFile, []byte("test"), 0644)
|
||||
|
||||
arg, _ := vm.ToValue(testFile)
|
||||
call := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readDir(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined when passing file instead of directory")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid arguments", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []otto.Value
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
args: []otto.Value{},
|
||||
},
|
||||
{
|
||||
name: "too many arguments",
|
||||
args: func() []otto.Value {
|
||||
arg1, _ := vm.ToValue(tmpDir)
|
||||
arg2, _ := vm.ToValue("extra")
|
||||
return []otto.Value{arg1, arg2}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
call := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: tt.args,
|
||||
}
|
||||
|
||||
result := readDir(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined for invalid arguments")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty directory", func(t *testing.T) {
|
||||
emptyDir := filepath.Join(tmpDir, "empty")
|
||||
os.Mkdir(emptyDir, 0755)
|
||||
|
||||
arg, _ := vm.ToValue(emptyDir)
|
||||
call := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readDir(call)
|
||||
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("readDir returned undefined for empty directory")
|
||||
}
|
||||
|
||||
export, _ := result.Export()
|
||||
entries, _ := export.([]string)
|
||||
|
||||
if len(entries) != 0 {
|
||||
t.Errorf("expected 0 entries for empty directory, got %d", len(entries))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
// Create a temporary directory for testing
|
||||
tmpDir, err := ioutil.TempDir("", "js_test_readfile_*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
t.Run("valid file", func(t *testing.T) {
|
||||
testContent := "Hello, World!\nThis is a test file.\n特殊字符测试 🌍"
|
||||
testFile := filepath.Join(tmpDir, "test.txt")
|
||||
ioutil.WriteFile(testFile, []byte(testContent), 0644)
|
||||
|
||||
arg, _ := vm.ToValue(testFile)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("readFile returned undefined")
|
||||
}
|
||||
|
||||
content, err := result.ToString()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to convert result to string: %v", err)
|
||||
}
|
||||
|
||||
if content != testContent {
|
||||
t.Errorf("expected content %q, got %q", testContent, content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-existent file", func(t *testing.T) {
|
||||
arg, _ := vm.ToValue("/path/that/does/not/exist.txt")
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined for non-existent file")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("directory instead of file", func(t *testing.T) {
|
||||
arg, _ := vm.ToValue(tmpDir)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined when passing directory instead of file")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty file", func(t *testing.T) {
|
||||
emptyFile := filepath.Join(tmpDir, "empty.txt")
|
||||
ioutil.WriteFile(emptyFile, []byte(""), 0644)
|
||||
|
||||
arg, _ := vm.ToValue(emptyFile)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("readFile returned undefined for empty file")
|
||||
}
|
||||
|
||||
content, _ := result.ToString()
|
||||
if content != "" {
|
||||
t.Errorf("expected empty string, got %q", content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("binary file", func(t *testing.T) {
|
||||
binaryContent := []byte{0, 1, 2, 3, 255, 254, 253, 252}
|
||||
binaryFile := filepath.Join(tmpDir, "binary.bin")
|
||||
ioutil.WriteFile(binaryFile, binaryContent, 0644)
|
||||
|
||||
arg, _ := vm.ToValue(binaryFile)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("readFile returned undefined for binary file")
|
||||
}
|
||||
|
||||
content, _ := result.ToString()
|
||||
if content != string(binaryContent) {
|
||||
t.Error("binary content mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid arguments", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []otto.Value
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
args: []otto.Value{},
|
||||
},
|
||||
{
|
||||
name: "too many arguments",
|
||||
args: func() []otto.Value {
|
||||
arg1, _ := vm.ToValue("file.txt")
|
||||
arg2, _ := vm.ToValue("extra")
|
||||
return []otto.Value{arg1, arg2}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: tt.args,
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined for invalid arguments")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("large file", func(t *testing.T) {
|
||||
// Create a 1MB file
|
||||
largeContent := strings.Repeat("A", 1024*1024)
|
||||
largeFile := filepath.Join(tmpDir, "large.txt")
|
||||
ioutil.WriteFile(largeFile, []byte(largeContent), 0644)
|
||||
|
||||
arg, _ := vm.ToValue(largeFile)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
result := readFile(call)
|
||||
|
||||
if result.IsUndefined() {
|
||||
t.Fatal("readFile returned undefined for large file")
|
||||
}
|
||||
|
||||
content, _ := result.ToString()
|
||||
if len(content) != len(largeContent) {
|
||||
t.Errorf("expected content length %d, got %d", len(largeContent), len(content))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
// Create a temporary directory for testing
|
||||
tmpDir, err := ioutil.TempDir("", "js_test_writefile_*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
t.Run("write new file", func(t *testing.T) {
|
||||
testFile := filepath.Join(tmpDir, "new_file.txt")
|
||||
testContent := "Hello, World!\nThis is a new file.\n特殊字符测试 🌍"
|
||||
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue(testContent)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
|
||||
result := writeFile(call)
|
||||
|
||||
// writeFile returns null on success
|
||||
if !result.IsNull() {
|
||||
t.Error("expected null return value for successful write")
|
||||
}
|
||||
|
||||
// Verify file was created with correct content
|
||||
content, err := ioutil.ReadFile(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read written file: %v", err)
|
||||
}
|
||||
|
||||
if string(content) != testContent {
|
||||
t.Errorf("expected content %q, got %q", testContent, string(content))
|
||||
}
|
||||
|
||||
// Check file permissions
|
||||
info, _ := os.Stat(testFile)
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows, permissions are different - just check that file exists and is readable
|
||||
if info.Mode()&0400 == 0 {
|
||||
t.Error("expected file to be readable on Windows")
|
||||
}
|
||||
} else {
|
||||
// On Unix-like systems, check exact permissions
|
||||
if info.Mode().Perm() != 0644 {
|
||||
t.Errorf("expected permissions 0644, got %v", info.Mode().Perm())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("overwrite existing file", func(t *testing.T) {
|
||||
testFile := filepath.Join(tmpDir, "existing.txt")
|
||||
oldContent := "Old content"
|
||||
newContent := "New content that is longer than the old content"
|
||||
|
||||
// Create initial file
|
||||
ioutil.WriteFile(testFile, []byte(oldContent), 0644)
|
||||
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue(newContent)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
|
||||
result := writeFile(call)
|
||||
|
||||
if !result.IsNull() {
|
||||
t.Error("expected null return value for successful write")
|
||||
}
|
||||
|
||||
// Verify file was overwritten
|
||||
content, _ := ioutil.ReadFile(testFile)
|
||||
if string(content) != newContent {
|
||||
t.Errorf("expected content %q, got %q", newContent, string(content))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("write to non-existent directory", func(t *testing.T) {
|
||||
testFile := filepath.Join(tmpDir, "nonexistent", "subdir", "file.txt")
|
||||
testContent := "test"
|
||||
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue(testContent)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
|
||||
result := writeFile(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined when writing to non-existent directory")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("write empty content", func(t *testing.T) {
|
||||
testFile := filepath.Join(tmpDir, "empty.txt")
|
||||
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue("")
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
|
||||
result := writeFile(call)
|
||||
|
||||
if !result.IsNull() {
|
||||
t.Error("expected null return value for successful write")
|
||||
}
|
||||
|
||||
// Verify empty file was created
|
||||
content, _ := ioutil.ReadFile(testFile)
|
||||
if len(content) != 0 {
|
||||
t.Errorf("expected empty file, got %d bytes", len(content))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid arguments", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []otto.Value
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
args: []otto.Value{},
|
||||
},
|
||||
{
|
||||
name: "one argument",
|
||||
args: func() []otto.Value {
|
||||
arg, _ := vm.ToValue("file.txt")
|
||||
return []otto.Value{arg}
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "too many arguments",
|
||||
args: func() []otto.Value {
|
||||
arg1, _ := vm.ToValue("file.txt")
|
||||
arg2, _ := vm.ToValue("content")
|
||||
arg3, _ := vm.ToValue("extra")
|
||||
return []otto.Value{arg1, arg2, arg3}
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: tt.args,
|
||||
}
|
||||
|
||||
result := writeFile(call)
|
||||
|
||||
// Should return undefined (error)
|
||||
if !result.IsUndefined() {
|
||||
t.Error("expected undefined for invalid arguments")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("write binary content", func(t *testing.T) {
|
||||
testFile := filepath.Join(tmpDir, "binary.bin")
|
||||
binaryContent := string([]byte{0, 1, 2, 3, 255, 254, 253, 252})
|
||||
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue(binaryContent)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
|
||||
result := writeFile(call)
|
||||
|
||||
if !result.IsNull() {
|
||||
t.Error("expected null return value for successful write")
|
||||
}
|
||||
|
||||
// Verify binary content
|
||||
content, _ := ioutil.ReadFile(testFile)
|
||||
if string(content) != binaryContent {
|
||||
t.Error("binary content mismatch")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileSystemIntegration(t *testing.T) {
|
||||
vm := otto.New()
|
||||
|
||||
// Create a temporary directory for testing
|
||||
tmpDir, err := ioutil.TempDir("", "js_test_integration_*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
t.Run("write then read file", func(t *testing.T) {
|
||||
testFile := filepath.Join(tmpDir, "roundtrip.txt")
|
||||
testContent := "Round-trip test content\nLine 2\nLine 3"
|
||||
|
||||
// Write file
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue(testContent)
|
||||
writeCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
|
||||
writeResult := writeFile(writeCall)
|
||||
if !writeResult.IsNull() {
|
||||
t.Fatal("write failed")
|
||||
}
|
||||
|
||||
// Read file back
|
||||
readCall := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile},
|
||||
}
|
||||
|
||||
readResult := readFile(readCall)
|
||||
if readResult.IsUndefined() {
|
||||
t.Fatal("read failed")
|
||||
}
|
||||
|
||||
readContent, _ := readResult.ToString()
|
||||
if readContent != testContent {
|
||||
t.Errorf("round-trip failed: expected %q, got %q", testContent, readContent)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("create files then list directory", func(t *testing.T) {
|
||||
// Create multiple files
|
||||
files := []string{"file1.txt", "file2.txt", "file3.txt"}
|
||||
for _, name := range files {
|
||||
path := filepath.Join(tmpDir, name)
|
||||
argFile, _ := vm.ToValue(path)
|
||||
argContent, _ := vm.ToValue("content of " + name)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
writeFile(call)
|
||||
}
|
||||
|
||||
// List directory
|
||||
argDir, _ := vm.ToValue(tmpDir)
|
||||
listCall := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: []otto.Value{argDir},
|
||||
}
|
||||
|
||||
listResult := readDir(listCall)
|
||||
if listResult.IsUndefined() {
|
||||
t.Fatal("readDir failed")
|
||||
}
|
||||
|
||||
export, _ := listResult.Export()
|
||||
entries, _ := export.([]string)
|
||||
|
||||
// Check all files are listed
|
||||
for _, expected := range files {
|
||||
found := false
|
||||
for _, entry := range entries {
|
||||
if entry == expected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected file %s not found in directory listing", expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkReadFile(b *testing.B) {
|
||||
vm := otto.New()
|
||||
|
||||
// Create test file
|
||||
tmpFile, _ := ioutil.TempFile("", "bench_readfile_*")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
content := strings.Repeat("Benchmark test content line\n", 100)
|
||||
ioutil.WriteFile(tmpFile.Name(), []byte(content), 0644)
|
||||
|
||||
arg, _ := vm.ToValue(tmpFile.Name())
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = readFile(call)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWriteFile(b *testing.B) {
|
||||
vm := otto.New()
|
||||
|
||||
tmpDir, _ := ioutil.TempDir("", "bench_writefile_*")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
content := strings.Repeat("Benchmark test content line\n", 100)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
testFile := filepath.Join(tmpDir, fmt.Sprintf("bench_%d.txt", i))
|
||||
argFile, _ := vm.ToValue(testFile)
|
||||
argContent, _ := vm.ToValue(content)
|
||||
call := otto.FunctionCall{
|
||||
ArgumentList: []otto.Value{argFile, argContent},
|
||||
}
|
||||
_ = writeFile(call)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReadDir(b *testing.B) {
|
||||
vm := otto.New()
|
||||
|
||||
// Create test directory with files
|
||||
tmpDir, _ := ioutil.TempDir("", "bench_readdir_*")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// Create 100 files
|
||||
for i := 0; i < 100; i++ {
|
||||
name := filepath.Join(tmpDir, fmt.Sprintf("file_%d.txt", i))
|
||||
ioutil.WriteFile(name, []byte("test"), 0644)
|
||||
}
|
||||
|
||||
arg, _ := vm.ToValue(tmpDir)
|
||||
call := otto.FunctionCall{
|
||||
Otto: vm,
|
||||
ArgumentList: []otto.Value{arg},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = readDir(call)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
|
@ -63,7 +64,7 @@ func (c httpPackage) Request(method string, uri string,
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return httpResponse{Error: err}
|
||||
}
|
||||
|
|
@ -132,7 +133,7 @@ func httpRequest(call otto.FunctionCall) otto.Value {
|
|||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ReportError("Could not read response: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,16 +27,10 @@ func init() {
|
|||
plugin.Defines["log_error"] = log_error
|
||||
plugin.Defines["log_fatal"] = log_fatal
|
||||
|
||||
plugin.Defines["Crypto"] = map[string]interface{}{
|
||||
"sha1": cryptoSha1,
|
||||
}
|
||||
|
||||
plugin.Defines["btoa"] = btoa
|
||||
plugin.Defines["atob"] = atob
|
||||
plugin.Defines["gzipCompress"] = gzipCompress
|
||||
plugin.Defines["gzipDecompress"] = gzipDecompress
|
||||
plugin.Defines["textEncode"] = textEncode
|
||||
plugin.Defines["textDecode"] = textDecode
|
||||
|
||||
plugin.Defines["httpRequest"] = httpRequest
|
||||
plugin.Defines["http"] = httpPackage{}
|
||||
|
|
|
|||
|
|
@ -1,307 +0,0 @@
|
|||
package js
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRandomString(t *testing.T) {
|
||||
r := randomPackage{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
charset string
|
||||
}{
|
||||
{
|
||||
name: "alphanumeric",
|
||||
size: 10,
|
||||
charset: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
||||
},
|
||||
{
|
||||
name: "numbers only",
|
||||
size: 20,
|
||||
charset: "0123456789",
|
||||
},
|
||||
{
|
||||
name: "lowercase letters",
|
||||
size: 15,
|
||||
charset: "abcdefghijklmnopqrstuvwxyz",
|
||||
},
|
||||
{
|
||||
name: "uppercase letters",
|
||||
size: 8,
|
||||
charset: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
},
|
||||
{
|
||||
name: "special characters",
|
||||
size: 12,
|
||||
charset: "!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
},
|
||||
{
|
||||
name: "unicode characters",
|
||||
size: 5,
|
||||
charset: "αβγδεζηθικλμνξοπρστυφχψω",
|
||||
},
|
||||
{
|
||||
name: "mixed unicode and ascii",
|
||||
size: 10,
|
||||
charset: "abc123αβγ",
|
||||
},
|
||||
{
|
||||
name: "single character",
|
||||
size: 100,
|
||||
charset: "a",
|
||||
},
|
||||
{
|
||||
name: "empty size",
|
||||
size: 0,
|
||||
charset: "abcdef",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := r.String(tt.size, tt.charset)
|
||||
|
||||
// Check length
|
||||
if len([]rune(result)) != tt.size {
|
||||
t.Errorf("expected length %d, got %d", tt.size, len([]rune(result)))
|
||||
}
|
||||
|
||||
// Check that all characters are from the charset
|
||||
for _, char := range result {
|
||||
if !strings.ContainsRune(tt.charset, char) {
|
||||
t.Errorf("character %c not in charset %s", char, tt.charset)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomStringDistribution(t *testing.T) {
|
||||
r := randomPackage{}
|
||||
charset := "ab"
|
||||
size := 1000
|
||||
|
||||
// Generate many single-character strings
|
||||
counts := make(map[rune]int)
|
||||
for i := 0; i < size; i++ {
|
||||
result := r.String(1, charset)
|
||||
if len(result) == 1 {
|
||||
counts[rune(result[0])]++
|
||||
}
|
||||
}
|
||||
|
||||
// Check that both characters appear (very high probability)
|
||||
if len(counts) != 2 {
|
||||
t.Errorf("expected both characters to appear, got %d unique characters", len(counts))
|
||||
}
|
||||
|
||||
// Check distribution is reasonable (not perfect due to randomness)
|
||||
for char, count := range counts {
|
||||
ratio := float64(count) / float64(size)
|
||||
if ratio < 0.3 || ratio > 0.7 {
|
||||
t.Errorf("character %c appeared %d times (%.2f%%), expected around 50%%",
|
||||
char, count, ratio*100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomMac(t *testing.T) {
|
||||
r := randomPackage{}
|
||||
macRegex := regexp.MustCompile(`^([0-9a-f]{2}:){5}[0-9a-f]{2}$`)
|
||||
|
||||
// Generate multiple MAC addresses
|
||||
macs := make(map[string]bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
mac := r.Mac()
|
||||
|
||||
// Check format
|
||||
if !macRegex.MatchString(mac) {
|
||||
t.Errorf("invalid MAC format: %s", mac)
|
||||
}
|
||||
|
||||
// Check it's a valid MAC
|
||||
_, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
t.Errorf("invalid MAC address: %s, error: %v", mac, err)
|
||||
}
|
||||
|
||||
// Store for uniqueness check
|
||||
macs[mac] = true
|
||||
}
|
||||
|
||||
// Check that we get different MACs (very high probability)
|
||||
if len(macs) < 95 {
|
||||
t.Errorf("expected at least 95 unique MACs out of 100, got %d", len(macs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomMacNormalization(t *testing.T) {
|
||||
r := randomPackage{}
|
||||
|
||||
// Generate several MACs and check they're normalized
|
||||
for i := 0; i < 10; i++ {
|
||||
mac := r.Mac()
|
||||
|
||||
// Check lowercase
|
||||
if mac != strings.ToLower(mac) {
|
||||
t.Errorf("MAC not normalized to lowercase: %s", mac)
|
||||
}
|
||||
|
||||
// Check separator is colon
|
||||
if strings.Contains(mac, "-") {
|
||||
t.Errorf("MAC contains hyphen instead of colon: %s", mac)
|
||||
}
|
||||
|
||||
// Check length
|
||||
if len(mac) != 17 { // 6 bytes * 2 chars + 5 colons
|
||||
t.Errorf("MAC has wrong length: %s (len=%d)", mac, len(mac))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomStringEdgeCases(t *testing.T) {
|
||||
r := randomPackage{}
|
||||
|
||||
// Test with various edge cases
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
charset string
|
||||
}{
|
||||
{
|
||||
name: "zero size",
|
||||
size: 0,
|
||||
charset: "abc",
|
||||
},
|
||||
{
|
||||
name: "very large size",
|
||||
size: 10000,
|
||||
charset: "abc",
|
||||
},
|
||||
{
|
||||
name: "size larger than charset",
|
||||
size: 10,
|
||||
charset: "ab",
|
||||
},
|
||||
{
|
||||
name: "single char charset with large size",
|
||||
size: 1000,
|
||||
charset: "x",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := r.String(tt.size, tt.charset)
|
||||
|
||||
if len([]rune(result)) != tt.size {
|
||||
t.Errorf("expected length %d, got %d", tt.size, len([]rune(result)))
|
||||
}
|
||||
|
||||
// Check all characters are from charset
|
||||
for _, c := range result {
|
||||
if !strings.ContainsRune(tt.charset, c) {
|
||||
t.Errorf("character %c not in charset %s", c, tt.charset)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomStringNegativeSize(t *testing.T) {
|
||||
r := randomPackage{}
|
||||
|
||||
// Test that negative size causes panic
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("expected panic for negative size but didn't get one")
|
||||
}
|
||||
}()
|
||||
|
||||
// This should panic
|
||||
_ = r.String(-1, "abc")
|
||||
}
|
||||
|
||||
func TestRandomPackageInstance(t *testing.T) {
|
||||
// Test that we can create multiple instances
|
||||
r1 := randomPackage{}
|
||||
r2 := randomPackage{}
|
||||
|
||||
// Both should work independently
|
||||
s1 := r1.String(5, "abc")
|
||||
s2 := r2.String(5, "xyz")
|
||||
|
||||
if len(s1) != 5 {
|
||||
t.Errorf("r1.String returned wrong length: %d", len(s1))
|
||||
}
|
||||
if len(s2) != 5 {
|
||||
t.Errorf("r2.String returned wrong length: %d", len(s2))
|
||||
}
|
||||
|
||||
// Check correct charset usage
|
||||
for _, c := range s1 {
|
||||
if !strings.ContainsRune("abc", c) {
|
||||
t.Errorf("r1 produced character outside charset: %c", c)
|
||||
}
|
||||
}
|
||||
for _, c := range s2 {
|
||||
if !strings.ContainsRune("xyz", c) {
|
||||
t.Errorf("r2 produced character outside charset: %c", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRandomString(b *testing.B) {
|
||||
r := randomPackage{}
|
||||
charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
b.Run("size-10", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.String(10, charset)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("size-100", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.String(100, charset)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("size-1000", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.String(1000, charset)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRandomMac(b *testing.B) {
|
||||
r := randomPackage{}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.Mac()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRandomStringCharsets(b *testing.B) {
|
||||
r := randomPackage{}
|
||||
|
||||
charsets := map[string]string{
|
||||
"small": "abc",
|
||||
"medium": "abcdefghijklmnopqrstuvwxyz",
|
||||
"large": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?",
|
||||
"unicode": "αβγδεζηθικλμνξοπρστυφχψωABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
}
|
||||
|
||||
for name, charset := range charsets {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = r.String(20, charset)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
106
log/log_test.go
106
log/log_test.go
|
|
@ -1,106 +0,0 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/evilsocket/islazy/log"
|
||||
)
|
||||
|
||||
var called bool
|
||||
var calledLevel log.Verbosity
|
||||
var calledFormat string
|
||||
var calledArgs []interface{}
|
||||
|
||||
func mockLogger(level log.Verbosity, format string, args ...interface{}) {
|
||||
called = true
|
||||
calledLevel = level
|
||||
calledFormat = format
|
||||
calledArgs = args
|
||||
}
|
||||
|
||||
func reset() {
|
||||
called = false
|
||||
calledLevel = log.DEBUG
|
||||
calledFormat = ""
|
||||
calledArgs = nil
|
||||
}
|
||||
|
||||
func TestLoggerNil(t *testing.T) {
|
||||
reset()
|
||||
Logger = nil
|
||||
|
||||
Debug("test")
|
||||
if called {
|
||||
t.Error("Debug should not call if Logger is nil")
|
||||
}
|
||||
|
||||
Info("test")
|
||||
if called {
|
||||
t.Error("Info should not call if Logger is nil")
|
||||
}
|
||||
|
||||
Warning("test")
|
||||
if called {
|
||||
t.Error("Warning should not call if Logger is nil")
|
||||
}
|
||||
|
||||
Error("test")
|
||||
if called {
|
||||
t.Error("Error should not call if Logger is nil")
|
||||
}
|
||||
|
||||
Fatal("test")
|
||||
if called {
|
||||
t.Error("Fatal should not call if Logger is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebug(t *testing.T) {
|
||||
reset()
|
||||
Logger = mockLogger
|
||||
|
||||
Debug("test %d", 42)
|
||||
if !called || calledLevel != log.DEBUG || calledFormat != "test %d" || len(calledArgs) != 1 || calledArgs[0] != 42 {
|
||||
t.Errorf("Debug not called correctly: level=%v format=%s args=%v", calledLevel, calledFormat, calledArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
reset()
|
||||
Logger = mockLogger
|
||||
|
||||
Info("test %s", "info")
|
||||
if !called || calledLevel != log.INFO || calledFormat != "test %s" || len(calledArgs) != 1 || calledArgs[0] != "info" {
|
||||
t.Errorf("Info not called correctly: level=%v format=%s args=%v", calledLevel, calledFormat, calledArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarning(t *testing.T) {
|
||||
reset()
|
||||
Logger = mockLogger
|
||||
|
||||
Warning("test %f", 3.14)
|
||||
if !called || calledLevel != log.WARNING || calledFormat != "test %f" || len(calledArgs) != 1 || calledArgs[0] != 3.14 {
|
||||
t.Errorf("Warning not called correctly: level=%v format=%s args=%v", calledLevel, calledFormat, calledArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
reset()
|
||||
Logger = mockLogger
|
||||
|
||||
Error("test error")
|
||||
if !called || calledLevel != log.ERROR || calledFormat != "test error" || len(calledArgs) != 0 {
|
||||
t.Errorf("Error not called correctly: level=%v format=%s args=%v", calledLevel, calledFormat, calledArgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFatal(t *testing.T) {
|
||||
reset()
|
||||
Logger = mockLogger
|
||||
|
||||
Fatal("test fatal")
|
||||
if !called || calledLevel != log.FATAL || calledFormat != "test fatal" || len(calledArgs) != 0 {
|
||||
t.Errorf("Fatal not called correctly: level=%v format=%s args=%v", calledLevel, calledFormat, calledArgs)
|
||||
}
|
||||
}
|
||||
88
main_test.go
88
main_test.go
|
|
@ -1,88 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExitPrompt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "yes lowercase",
|
||||
input: "y\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "yes uppercase",
|
||||
input: "Y\n",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "no lowercase",
|
||||
input: "n\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no uppercase",
|
||||
input: "N\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "invalid input",
|
||||
input: "maybe\n",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
input: "\n",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Redirect stdin
|
||||
oldStdin := strings.NewReader(tt.input)
|
||||
r := bytes.NewReader([]byte(tt.input))
|
||||
|
||||
// Mock stdin by reading from our buffer
|
||||
// This is a simplified test - in production you'd want to properly mock stdin
|
||||
_ = oldStdin
|
||||
_ = r
|
||||
|
||||
// For now, we'll test the string comparison logic directly
|
||||
input := strings.TrimSpace(strings.TrimSuffix(tt.input, "\n"))
|
||||
result := strings.ToLower(input) == "y"
|
||||
|
||||
if result != tt.expected {
|
||||
t.Errorf("exitPrompt() with input %q = %v, want %v", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test some utility functions that would be refactored from main
|
||||
func TestVersionString(t *testing.T) {
|
||||
// This tests the version string formatting logic
|
||||
version := "2.32.0"
|
||||
os := "darwin"
|
||||
arch := "amd64"
|
||||
goVersion := "go1.19"
|
||||
|
||||
expected := "bettercap v2.32.0 (built for darwin amd64 with go1.19)"
|
||||
result := formatVersion("bettercap", version, os, arch, goVersion)
|
||||
|
||||
if result != expected {
|
||||
t.Errorf("formatVersion() = %v, want %v", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function that would be refactored from main
|
||||
func formatVersion(name, version, os, arch, goVersion string) string {
|
||||
return name + " v" + version + " (built for " + os + " " + arch + " with " + goVersion + ")"
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
package any_proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
||||
var (
|
||||
testSession *session.Session
|
||||
sessionOnce sync.Once
|
||||
)
|
||||
|
||||
func createMockSession(t *testing.T) *session.Session {
|
||||
sessionOnce.Do(func() {
|
||||
var err error
|
||||
testSession, err = session.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
}
|
||||
})
|
||||
return testSession
|
||||
}
|
||||
|
||||
func TestNewAnyProxy(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewAnyProxy(s)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewAnyProxy returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "any.proxy" {
|
||||
t.Errorf("Expected name 'any.proxy', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("Unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.Description() == "" {
|
||||
t.Error("Empty description")
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
if len(handlers) != 2 {
|
||||
t.Errorf("Expected 2 handlers, got %d", len(handlers))
|
||||
}
|
||||
|
||||
handlerNames := make(map[string]bool)
|
||||
for _, h := range handlers {
|
||||
handlerNames[h.Name] = true
|
||||
}
|
||||
|
||||
if !handlerNames["any.proxy on"] {
|
||||
t.Error("Handler 'any.proxy on' not found")
|
||||
}
|
||||
if !handlerNames["any.proxy off"] {
|
||||
t.Error("Handler 'any.proxy off' not found")
|
||||
}
|
||||
|
||||
// Check that parameters were added (but don't try to get values as that requires session interface)
|
||||
expectedParams := 6 // iface, protocol, src_port, src_address, dst_address, dst_port
|
||||
// This is a simplified check - in a real test we'd mock the interface
|
||||
_ = expectedParams
|
||||
}
|
||||
|
||||
// Test port parsing logic directly
|
||||
func TestPortParsingLogic(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
portString string
|
||||
expectPorts []int
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "single port",
|
||||
portString: "80",
|
||||
expectPorts: []int{80},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "multiple ports",
|
||||
portString: "80,443,8080",
|
||||
expectPorts: []int{80, 443, 8080},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "port range",
|
||||
portString: "8000-8003",
|
||||
expectPorts: []int{8000, 8001, 8002, 8003},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid port",
|
||||
portString: "not-a-port",
|
||||
expectPorts: nil,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ports, err := parsePortsString(tt.portString)
|
||||
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Error("Expected error but got none")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
} else {
|
||||
if len(ports) != len(tt.expectPorts) {
|
||||
t.Errorf("Expected %d ports, got %d", len(tt.expectPorts), len(ports))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to test port parsing logic
|
||||
func parsePortsString(portsStr string) ([]int, error) {
|
||||
var ports []int
|
||||
tokens := strings.Split(strings.ReplaceAll(portsStr, " ", ""), ",")
|
||||
|
||||
for _, token := range tokens {
|
||||
if token == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if p, err := strconv.Atoi(token); err == nil {
|
||||
if p < 1 || p > 65535 {
|
||||
return nil, fmt.Errorf("port %d out of range", p)
|
||||
}
|
||||
ports = append(ports, p)
|
||||
} else if strings.Contains(token, "-") {
|
||||
parts := strings.Split(token, "-")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid range format")
|
||||
}
|
||||
|
||||
from, err1 := strconv.Atoi(parts[0])
|
||||
to, err2 := strconv.Atoi(parts[1])
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
return nil, fmt.Errorf("invalid range values")
|
||||
}
|
||||
|
||||
if from < 1 || from > 65535 || to < 1 || to > 65535 {
|
||||
return nil, fmt.Errorf("port range out of bounds")
|
||||
}
|
||||
|
||||
if from > to {
|
||||
return nil, fmt.Errorf("invalid range order")
|
||||
}
|
||||
|
||||
for p := from; p <= to; p++ {
|
||||
ports = append(ports, p)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid port format: %s", token)
|
||||
}
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
func TestStartStop(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewAnyProxy(s)
|
||||
|
||||
// Initially should not be running
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running initially")
|
||||
}
|
||||
|
||||
// Note: Start() will fail because it requires firewall operations
|
||||
// which need proper network setup and possibly root permissions
|
||||
// We're just testing that the methods exist and basic flow
|
||||
}
|
||||
|
||||
// Test error cases in port parsing
|
||||
func TestPortParsingErrors(t *testing.T) {
|
||||
errorCases := []string{
|
||||
"0", // out of range
|
||||
"65536", // out of range
|
||||
"abc", // not a number
|
||||
"80-", // incomplete range
|
||||
"-80", // incomplete range
|
||||
"100-50", // inverted range
|
||||
"80-abc", // invalid end
|
||||
"xyz-100", // invalid start
|
||||
"80--100", // malformed
|
||||
// Remove these as our parser handles empty tokens correctly
|
||||
}
|
||||
|
||||
for _, portStr := range errorCases {
|
||||
_, err := parsePortsString(portStr)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for port string '%s', but got none", portStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkPortParsing(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parsePortsString("80,443,8000-8010,9000")
|
||||
}
|
||||
}
|
||||
|
|
@ -90,12 +90,12 @@ func NewRestAPI(s *session.Session) *RestAPI {
|
|||
"Value of the Access-Control-Allow-Origin header of the API server."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("api.rest.username",
|
||||
"user",
|
||||
"",
|
||||
"",
|
||||
"API authentication username."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("api.rest.password",
|
||||
"pass",
|
||||
"",
|
||||
"",
|
||||
"API authentication password."))
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -17,10 +17,6 @@ import (
|
|||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
var (
|
||||
ansiEscapeRegex = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`)
|
||||
)
|
||||
|
||||
type CommandRequest struct {
|
||||
Command string `json:"cmd"`
|
||||
}
|
||||
|
|
@ -225,10 +221,6 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "Bad Request", 400)
|
||||
}
|
||||
|
||||
rescueStdout := os.Stdout
|
||||
stdoutReader, stdoutWriter, _ := os.Pipe()
|
||||
os.Stdout = stdoutWriter
|
||||
|
||||
for _, aCommand := range session.ParseCommands(cmd.Command) {
|
||||
if err = mod.Session.Run(aCommand); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
|
|
@ -236,12 +228,7 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
stdoutWriter.Close()
|
||||
out, _ := io.ReadAll(stdoutReader)
|
||||
os.Stdout = rescueStdout
|
||||
|
||||
// remove ANSI escape sequences (bash color codes) from output
|
||||
mod.toJSON(w, APIResponse{Success: true, Message: ansiEscapeRegex.ReplaceAllString(string(out), "")})
|
||||
mod.toJSON(w, APIResponse{Success: true})
|
||||
}
|
||||
|
||||
func (mod *RestAPI) getEvents(limit int) []session.Event {
|
||||
|
|
@ -393,7 +380,7 @@ func (mod *RestAPI) readFile(fileName string, w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
|
||||
func (mod *RestAPI) writeFile(fileName string, w http.ResponseWriter, r *http.Request) {
|
||||
data, err := io.ReadAll(r.Body)
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("invalid file upload: %s", err)
|
||||
mod.Warning(msg)
|
||||
|
|
@ -401,7 +388,7 @@ func (mod *RestAPI) writeFile(fileName string, w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(fileName, data, 0666)
|
||||
err = ioutil.WriteFile(fileName, data, 0666)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("can't write to %s: %s", fileName, err)
|
||||
mod.Warning(msg)
|
||||
|
|
|
|||
|
|
@ -1,671 +0,0 @@
|
|||
package api_rest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
||||
var (
|
||||
testSession *session.Session
|
||||
sessionOnce sync.Once
|
||||
)
|
||||
|
||||
func createMockSession(t *testing.T) *session.Session {
|
||||
sessionOnce.Do(func() {
|
||||
var err error
|
||||
testSession, err = session.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
}
|
||||
})
|
||||
return testSession
|
||||
}
|
||||
|
||||
func TestNewRestAPI(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewRestAPI returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "api.rest" {
|
||||
t.Errorf("Expected name 'api.rest', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("Unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.Description() == "" {
|
||||
t.Error("Empty description")
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
expectedHandlers := []string{
|
||||
"api.rest on",
|
||||
"api.rest off",
|
||||
"api.rest.record off",
|
||||
"api.rest.record FILENAME",
|
||||
"api.rest.replay off",
|
||||
"api.rest.replay FILENAME",
|
||||
}
|
||||
|
||||
if len(handlers) != len(expectedHandlers) {
|
||||
t.Errorf("Expected %d handlers, got %d", len(expectedHandlers), len(handlers))
|
||||
}
|
||||
|
||||
handlerNames := make(map[string]bool)
|
||||
for _, h := range handlers {
|
||||
handlerNames[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerNames[expected] {
|
||||
t.Errorf("Handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Check initial state
|
||||
if mod.recording {
|
||||
t.Error("Should not be recording initially")
|
||||
}
|
||||
if mod.replaying {
|
||||
t.Error("Should not be replaying initially")
|
||||
}
|
||||
if mod.useWebsocket {
|
||||
t.Error("Should not use websocket by default")
|
||||
}
|
||||
if mod.allowOrigin != "*" {
|
||||
t.Errorf("Expected default allowOrigin '*', got '%s'", mod.allowOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTLS(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Initially should not be TLS
|
||||
if mod.isTLS() {
|
||||
t.Error("Should not be TLS without cert and key")
|
||||
}
|
||||
|
||||
// Set cert and key
|
||||
mod.certFile = "cert.pem"
|
||||
mod.keyFile = "key.pem"
|
||||
|
||||
if !mod.isTLS() {
|
||||
t.Error("Should be TLS with cert and key")
|
||||
}
|
||||
|
||||
// Only cert
|
||||
mod.certFile = "cert.pem"
|
||||
mod.keyFile = ""
|
||||
|
||||
if mod.isTLS() {
|
||||
t.Error("Should not be TLS with only cert")
|
||||
}
|
||||
|
||||
// Only key
|
||||
mod.certFile = ""
|
||||
mod.keyFile = "key.pem"
|
||||
|
||||
if mod.isTLS() {
|
||||
t.Error("Should not be TLS with only key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateStore(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Check that state variables are properly stored
|
||||
stateKeys := []string{
|
||||
"recording",
|
||||
"rec_clock",
|
||||
"replaying",
|
||||
"loading",
|
||||
"load_progress",
|
||||
"rec_time",
|
||||
"rec_filename",
|
||||
"rec_frames",
|
||||
"rec_cur_frame",
|
||||
"rec_started",
|
||||
"rec_stopped",
|
||||
}
|
||||
|
||||
for _, key := range stateKeys {
|
||||
val, exists := mod.State.Load(key)
|
||||
if !exists || val == nil {
|
||||
t.Errorf("State key '%s' not found", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameters(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Check that all parameters are registered
|
||||
paramNames := []string{
|
||||
"api.rest.address",
|
||||
"api.rest.port",
|
||||
"api.rest.alloworigin",
|
||||
"api.rest.username",
|
||||
"api.rest.password",
|
||||
"api.rest.certificate",
|
||||
"api.rest.key",
|
||||
"api.rest.websocket",
|
||||
"api.rest.record.clock",
|
||||
}
|
||||
|
||||
// Parameters are stored in the session environment
|
||||
// We'll just check they can be accessed without error
|
||||
for _, param := range paramNames {
|
||||
// This is a simplified check
|
||||
_ = param
|
||||
}
|
||||
|
||||
// Ensure mod is used
|
||||
if mod == nil {
|
||||
t.Error("Module should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSSessionStructs(t *testing.T) {
|
||||
// Test struct creation
|
||||
req := JSSessionRequest{
|
||||
Command: "test command",
|
||||
}
|
||||
|
||||
if req.Command != "test command" {
|
||||
t.Errorf("Expected command 'test command', got '%s'", req.Command)
|
||||
}
|
||||
|
||||
resp := JSSessionResponse{
|
||||
Error: "test error",
|
||||
}
|
||||
|
||||
if resp.Error != "test error" {
|
||||
t.Errorf("Expected error 'test error', got '%s'", resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultValues(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Check default values
|
||||
if mod.recClock != 1 {
|
||||
t.Errorf("Expected default recClock 1, got %d", mod.recClock)
|
||||
}
|
||||
|
||||
if mod.recTime != 0 {
|
||||
t.Errorf("Expected default recTime 0, got %d", mod.recTime)
|
||||
}
|
||||
|
||||
if mod.recordFileName != "" {
|
||||
t.Errorf("Expected empty recordFileName, got '%s'", mod.recordFileName)
|
||||
}
|
||||
|
||||
if mod.upgrader.ReadBufferSize != 1024 {
|
||||
t.Errorf("Expected ReadBufferSize 1024, got %d", mod.upgrader.ReadBufferSize)
|
||||
}
|
||||
|
||||
if mod.upgrader.WriteBufferSize != 1024 {
|
||||
t.Errorf("Expected WriteBufferSize 1024, got %d", mod.upgrader.WriteBufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningState(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Initially should not be running
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running initially")
|
||||
}
|
||||
|
||||
// Note: Cannot test actual Start/Stop without proper server setup
|
||||
}
|
||||
|
||||
func TestRecordingState(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test recording state changes
|
||||
mod.recording = true
|
||||
if !mod.recording {
|
||||
t.Error("Recording flag should be true")
|
||||
}
|
||||
|
||||
mod.recording = false
|
||||
if mod.recording {
|
||||
t.Error("Recording flag should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplayingState(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test replaying state changes
|
||||
mod.replaying = true
|
||||
if !mod.replaying {
|
||||
t.Error("Replaying flag should be true")
|
||||
}
|
||||
|
||||
mod.replaying = false
|
||||
if mod.replaying {
|
||||
t.Error("Replaying flag should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureErrors(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test configuration validation
|
||||
testCases := []struct {
|
||||
name string
|
||||
setup func()
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "invalid address",
|
||||
setup: func() {
|
||||
s.Env.Set("api.rest.address", "999.999.999.999")
|
||||
},
|
||||
expected: "address",
|
||||
},
|
||||
{
|
||||
name: "invalid port",
|
||||
setup: func() {
|
||||
s.Env.Set("api.rest.address", "127.0.0.1")
|
||||
s.Env.Set("api.rest.port", "not-a-port")
|
||||
},
|
||||
expected: "port",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.setup()
|
||||
// Configure may fail due to parameter validation
|
||||
_ = mod.Configure()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfiguration(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Set valid parameters
|
||||
s.Env.Set("api.rest.address", "127.0.0.1")
|
||||
s.Env.Set("api.rest.port", "8081")
|
||||
s.Env.Set("api.rest.username", "testuser")
|
||||
s.Env.Set("api.rest.password", "testpass")
|
||||
s.Env.Set("api.rest.websocket", "true")
|
||||
s.Env.Set("api.rest.alloworigin", "http://localhost:3000")
|
||||
|
||||
// This might fail due to TLS cert generation, but we're testing the flow
|
||||
_ = mod.Configure()
|
||||
|
||||
// Check that values were set
|
||||
if mod.username != "" && mod.username != "testuser" {
|
||||
t.Logf("Username set to: %s", mod.username)
|
||||
}
|
||||
if mod.password != "" && mod.password != "testpass" {
|
||||
t.Logf("Password set to: %s", mod.password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuitChannel(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test quit channel is created
|
||||
if mod.quit == nil {
|
||||
t.Error("Quit channel should not be nil")
|
||||
}
|
||||
|
||||
// Test sending to quit channel doesn't block
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
select {
|
||||
case mod.quit <- true:
|
||||
done <- true
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
done <- false
|
||||
}
|
||||
}()
|
||||
|
||||
// Start reading from quit channel
|
||||
go func() {
|
||||
<-mod.quit
|
||||
}()
|
||||
|
||||
if !<-done {
|
||||
t.Error("Sending to quit channel timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordWaitGroup(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test wait group is initialized
|
||||
if mod.recordWait == nil {
|
||||
t.Error("Record wait group should not be nil")
|
||||
}
|
||||
|
||||
// Test wait group operations
|
||||
mod.recordWait.Add(1)
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
mod.recordWait.Done()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
go func() {
|
||||
mod.recordWait.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// Success
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Error("Wait group operation timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartErrors(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test start when replaying
|
||||
mod.replaying = true
|
||||
err := mod.Start()
|
||||
if err == nil {
|
||||
t.Error("Expected error when starting while replaying")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureAlreadyRunning(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Simulate running state
|
||||
mod.SetRunning(true, func() {})
|
||||
|
||||
err := mod.Configure()
|
||||
if err == nil {
|
||||
t.Error("Expected error when configuring while running")
|
||||
}
|
||||
|
||||
// Reset
|
||||
mod.SetRunning(false, func() {})
|
||||
}
|
||||
|
||||
func TestServerAddr(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Set parameters
|
||||
s.Env.Set("api.rest.address", "192.168.1.100")
|
||||
s.Env.Set("api.rest.port", "9090")
|
||||
|
||||
// Configure may fail but we can check server addr format
|
||||
_ = mod.Configure()
|
||||
|
||||
expectedAddr := "192.168.1.100:9090"
|
||||
if mod.server != nil && mod.server.Addr != "" && mod.server.Addr != expectedAddr {
|
||||
t.Logf("Server addr: %s", mod.server.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSConfiguration(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Test with TLS params
|
||||
s.Env.Set("api.rest.certificate", "/tmp/test.crt")
|
||||
s.Env.Set("api.rest.key", "/tmp/test.key")
|
||||
|
||||
// Configure will attempt to expand paths and check files
|
||||
_ = mod.Configure()
|
||||
|
||||
// Just verify the attempt was made
|
||||
t.Logf("Attempted TLS configuration")
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkNewRestAPI(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewRestAPI(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsTLS(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
mod := NewRestAPI(s)
|
||||
mod.certFile = "cert.pem"
|
||||
mod.keyFile = "key.pem"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mod.isTLS()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConfigure(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod := NewRestAPI(s)
|
||||
s.Env.Set("api.rest.address", "127.0.0.1")
|
||||
s.Env.Set("api.rest.port", "8081")
|
||||
_ = mod.Configure()
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for controller functionality
|
||||
func TestCommandRequest(t *testing.T) {
|
||||
cmd := CommandRequest{
|
||||
Command: "help",
|
||||
}
|
||||
|
||||
if cmd.Command != "help" {
|
||||
t.Errorf("Expected command 'help', got '%s'", cmd.Command)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIResponse(t *testing.T) {
|
||||
resp := APIResponse{
|
||||
Success: true,
|
||||
Message: "Operation completed",
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
t.Error("Expected success to be true")
|
||||
}
|
||||
|
||||
if resp.Message != "Operation completed" {
|
||||
t.Errorf("Expected message 'Operation completed', got '%s'", resp.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAuthNoCredentials(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// No username/password set - should allow access
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
|
||||
if !mod.checkAuth(req) {
|
||||
t.Error("Expected auth to pass with no credentials set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAuthWithCredentials(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Set credentials
|
||||
mod.username = "testuser"
|
||||
mod.password = "testpass"
|
||||
|
||||
// Test without auth header
|
||||
req1, _ := http.NewRequest("GET", "/test", nil)
|
||||
if mod.checkAuth(req1) {
|
||||
t.Error("Expected auth to fail without credentials")
|
||||
}
|
||||
|
||||
// Test with wrong credentials
|
||||
req2, _ := http.NewRequest("GET", "/test", nil)
|
||||
req2.SetBasicAuth("wronguser", "wrongpass")
|
||||
if mod.checkAuth(req2) {
|
||||
t.Error("Expected auth to fail with wrong credentials")
|
||||
}
|
||||
|
||||
// Test with correct credentials
|
||||
req3, _ := http.NewRequest("GET", "/test", nil)
|
||||
req3.SetBasicAuth("testuser", "testpass")
|
||||
if !mod.checkAuth(req3) {
|
||||
t.Error("Expected auth to pass with correct credentials")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEventsEmpty(t *testing.T) {
|
||||
// Skip this test if running with others due to shared session state
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping in short mode due to shared session state")
|
||||
}
|
||||
|
||||
// Create a fresh session using the singleton
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Record initial event count
|
||||
initialCount := len(mod.getEvents(0))
|
||||
|
||||
// Get events - we can't guarantee zero events due to session initialization
|
||||
events := mod.getEvents(0)
|
||||
if len(events) < initialCount {
|
||||
t.Errorf("Event count should not decrease, got %d", len(events))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEventsWithLimit(t *testing.T) {
|
||||
// Create session using the singleton
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
// Record initial state
|
||||
initialEvents := mod.getEvents(0)
|
||||
initialCount := len(initialEvents)
|
||||
|
||||
// Add some test events
|
||||
testEventCount := 10
|
||||
for i := 0; i < testEventCount; i++ {
|
||||
s.Events.Add(fmt.Sprintf("test.event.limit.%d", i), nil)
|
||||
}
|
||||
|
||||
// Get all events
|
||||
allEvents := mod.getEvents(0)
|
||||
expectedTotal := initialCount + testEventCount
|
||||
if len(allEvents) != expectedTotal {
|
||||
t.Errorf("Expected %d total events, got %d", expectedTotal, len(allEvents))
|
||||
}
|
||||
|
||||
// Test limit functionality - get last 5 events
|
||||
limitedEvents := mod.getEvents(5)
|
||||
if len(limitedEvents) != 5 {
|
||||
t.Errorf("Expected 5 events when limiting, got %d", len(limitedEvents))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSecurityHeaders(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
mod.allowOrigin = "http://localhost:3000"
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
mod.setSecurityHeaders(w)
|
||||
|
||||
headers := w.Header()
|
||||
|
||||
// Check security headers
|
||||
if headers.Get("X-Frame-Options") != "DENY" {
|
||||
t.Error("X-Frame-Options header not set correctly")
|
||||
}
|
||||
|
||||
if headers.Get("X-Content-Type-Options") != "nosniff" {
|
||||
t.Error("X-Content-Type-Options header not set correctly")
|
||||
}
|
||||
|
||||
if headers.Get("X-XSS-Protection") != "1; mode=block" {
|
||||
t.Error("X-XSS-Protection header not set correctly")
|
||||
}
|
||||
|
||||
if headers.Get("Access-Control-Allow-Origin") != "http://localhost:3000" {
|
||||
t.Error("Access-Control-Allow-Origin header not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCorsRoute(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
req, _ := http.NewRequest("OPTIONS", "/test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
mod.corsRoute(w, req)
|
||||
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusNoContent, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToJSON(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewRestAPI(s)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
testData := map[string]string{
|
||||
"key": "value",
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
mod.toJSON(w, testData)
|
||||
|
||||
// Check content type
|
||||
if w.Header().Get("Content-Type") != "application/json" {
|
||||
t.Error("Content-Type header not set to application/json")
|
||||
}
|
||||
|
||||
// Check JSON response
|
||||
var result map[string]string
|
||||
if err := json.NewDecoder(w.Body).Decode(&result); err != nil {
|
||||
t.Errorf("Failed to decode JSON response: %v", err)
|
||||
}
|
||||
|
||||
if result["key"] != "value" || result["foo"] != "bar" {
|
||||
t.Error("JSON response doesn't match expected data")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,785 +0,0 @@
|
|||
package arp_spoof
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/firewall"
|
||||
"github.com/bettercap/bettercap/v2/network"
|
||||
"github.com/bettercap/bettercap/v2/packets"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/evilsocket/islazy/data"
|
||||
)
|
||||
|
||||
// MockFirewall implements a mock firewall for testing
|
||||
type MockFirewall struct {
|
||||
forwardingEnabled bool
|
||||
redirections []firewall.Redirection
|
||||
}
|
||||
|
||||
func NewMockFirewall() *MockFirewall {
|
||||
return &MockFirewall{
|
||||
forwardingEnabled: false,
|
||||
redirections: make([]firewall.Redirection, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockFirewall) IsForwardingEnabled() bool {
|
||||
return m.forwardingEnabled
|
||||
}
|
||||
|
||||
func (m *MockFirewall) EnableForwarding(enabled bool) error {
|
||||
m.forwardingEnabled = enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockFirewall) EnableRedirection(r *firewall.Redirection, enabled bool) error {
|
||||
if enabled {
|
||||
m.redirections = append(m.redirections, *r)
|
||||
} else {
|
||||
for i, red := range m.redirections {
|
||||
if red.String() == r.String() {
|
||||
m.redirections = append(m.redirections[:i], m.redirections[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockFirewall) DisableRedirection(r *firewall.Redirection, enabled bool) error {
|
||||
return m.EnableRedirection(r, false)
|
||||
}
|
||||
|
||||
func (m *MockFirewall) Restore() {
|
||||
m.redirections = make([]firewall.Redirection, 0)
|
||||
m.forwardingEnabled = false
|
||||
}
|
||||
|
||||
// MockPacketQueue extends packets.Queue to capture sent packets
|
||||
type MockPacketQueue struct {
|
||||
*packets.Queue
|
||||
sync.Mutex
|
||||
sentPackets [][]byte
|
||||
}
|
||||
|
||||
func NewMockPacketQueue() *MockPacketQueue {
|
||||
q := &packets.Queue{
|
||||
Traffic: sync.Map{},
|
||||
Stats: packets.Stats{},
|
||||
}
|
||||
return &MockPacketQueue{
|
||||
Queue: q,
|
||||
sentPackets: make([][]byte, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockPacketQueue) Send(data []byte) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// Store a copy of the packet
|
||||
packet := make([]byte, len(data))
|
||||
copy(packet, data)
|
||||
m.sentPackets = append(m.sentPackets, packet)
|
||||
|
||||
// Also update stats like the real queue would
|
||||
m.TrackSent(uint64(len(data)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPacketQueue) GetSentPackets() [][]byte {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.sentPackets
|
||||
}
|
||||
|
||||
func (m *MockPacketQueue) ClearSentPackets() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.sentPackets = make([][]byte, 0)
|
||||
}
|
||||
|
||||
// MockSession for testing
|
||||
type MockSession struct {
|
||||
*session.Session
|
||||
findMACResults map[string]net.HardwareAddr
|
||||
skipIPs map[string]bool
|
||||
mockQueue *MockPacketQueue
|
||||
}
|
||||
|
||||
// Override session methods to use our mocks
|
||||
func setupMockSession(mockSess *MockSession) {
|
||||
// Replace the Session's FindMAC method behavior by manipulating the LAN
|
||||
// Since we can't override methods directly, we'll ensure the LAN has the data
|
||||
for ip, mac := range mockSess.findMACResults {
|
||||
mockSess.Lan.AddIfNew(ip, mac.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockSession) FindMAC(ip net.IP, probe bool) (net.HardwareAddr, error) {
|
||||
// First check our mock results
|
||||
if mac, ok := m.findMACResults[ip.String()]; ok {
|
||||
return mac, nil
|
||||
}
|
||||
// Then check the LAN
|
||||
if e, found := m.Lan.Get(ip.String()); found && e != nil {
|
||||
return e.HW, nil
|
||||
}
|
||||
return nil, fmt.Errorf("MAC not found for %s", ip.String())
|
||||
}
|
||||
|
||||
func (m *MockSession) Skip(ip net.IP) bool {
|
||||
if m.skipIPs == nil {
|
||||
return false
|
||||
}
|
||||
return m.skipIPs[ip.String()]
|
||||
}
|
||||
|
||||
// MockNetRecon implements a minimal net.recon module for testing
|
||||
type MockNetRecon struct {
|
||||
session.SessionModule
|
||||
}
|
||||
|
||||
func NewMockNetRecon(s *session.Session) *MockNetRecon {
|
||||
mod := &MockNetRecon{
|
||||
SessionModule: session.NewSessionModule("net.recon", s),
|
||||
}
|
||||
|
||||
// Add handlers
|
||||
mod.AddHandler(session.NewModuleHandler("net.recon on", "",
|
||||
"Start net.recon",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("net.recon off", "",
|
||||
"Stop net.recon",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Name() string {
|
||||
return "net.recon"
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Description() string {
|
||||
return "Mock net.recon module"
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Author() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Start() error {
|
||||
return m.SetRunning(true, nil)
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Stop() error {
|
||||
return m.SetRunning(false, nil)
|
||||
}
|
||||
|
||||
// Create a mock session for testing
|
||||
func createMockSession() (*MockSession, *MockPacketQueue, *MockFirewall) {
|
||||
// Create interface
|
||||
iface := &network.Endpoint{
|
||||
IpAddress: "192.168.1.100",
|
||||
HwAddress: "aa:bb:cc:dd:ee:ff",
|
||||
Hostname: "eth0",
|
||||
}
|
||||
iface.SetIP("192.168.1.100")
|
||||
iface.SetBits(24)
|
||||
|
||||
// Parse interface addresses
|
||||
ifaceIP := net.ParseIP("192.168.1.100")
|
||||
ifaceHW, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
||||
iface.IP = ifaceIP
|
||||
iface.HW = ifaceHW
|
||||
|
||||
// Create gateway
|
||||
gateway := &network.Endpoint{
|
||||
IpAddress: "192.168.1.1",
|
||||
HwAddress: "11:22:33:44:55:66",
|
||||
}
|
||||
gatewayIP := net.ParseIP("192.168.1.1")
|
||||
gatewayHW, _ := net.ParseMAC("11:22:33:44:55:66")
|
||||
gateway.IP = gatewayIP
|
||||
gateway.HW = gatewayHW
|
||||
|
||||
// Create mock queue and firewall
|
||||
mockQueue := NewMockPacketQueue()
|
||||
mockFirewall := NewMockFirewall()
|
||||
|
||||
// Create environment
|
||||
env, _ := session.NewEnvironment("")
|
||||
|
||||
// Create LAN
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
lan := network.NewLAN(iface, gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
// Create session
|
||||
sess := &session.Session{
|
||||
Interface: iface,
|
||||
Gateway: gateway,
|
||||
Lan: lan,
|
||||
StartedAt: time.Now(),
|
||||
Active: true,
|
||||
Env: env,
|
||||
Queue: mockQueue.Queue,
|
||||
Firewall: mockFirewall,
|
||||
Modules: make(session.ModuleList, 0),
|
||||
}
|
||||
|
||||
// Initialize events
|
||||
sess.Events = session.NewEventPool(false, false)
|
||||
|
||||
// Add mock net.recon module
|
||||
mockNetRecon := NewMockNetRecon(sess)
|
||||
sess.Modules = append(sess.Modules, mockNetRecon)
|
||||
|
||||
// Create mock session wrapper
|
||||
mockSess := &MockSession{
|
||||
Session: sess,
|
||||
findMACResults: make(map[string]net.HardwareAddr),
|
||||
skipIPs: make(map[string]bool),
|
||||
mockQueue: mockQueue,
|
||||
}
|
||||
|
||||
return mockSess, mockQueue, mockFirewall
|
||||
}
|
||||
|
||||
func TestNewArpSpoofer(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewArpSpoofer returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "arp.spoof" {
|
||||
t.Errorf("expected module name 'arp.spoof', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
params := []string{"arp.spoof.targets", "arp.spoof.whitelist", "arp.spoof.internal", "arp.spoof.fullduplex", "arp.spoof.skip_restore"}
|
||||
for _, param := range params {
|
||||
if !mod.Session.Env.Has(param) {
|
||||
t.Errorf("parameter %s not registered", param)
|
||||
}
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
expectedHandlers := []string{"arp.spoof on", "arp.ban on", "arp.spoof off", "arp.ban off"}
|
||||
handlerMap := make(map[string]bool)
|
||||
|
||||
for _, h := range handlers {
|
||||
handlerMap[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerMap[expected] {
|
||||
t.Errorf("Expected handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestArpSpooferConfigure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
params map[string]string
|
||||
setupMock func(*MockSession)
|
||||
expectErr bool
|
||||
validate func(*ArpSpoofer) error
|
||||
}{
|
||||
{
|
||||
name: "default configuration",
|
||||
params: map[string]string{
|
||||
"arp.spoof.targets": "192.168.1.10",
|
||||
"arp.spoof.whitelist": "",
|
||||
"arp.spoof.internal": "false",
|
||||
"arp.spoof.fullduplex": "false",
|
||||
"arp.spoof.skip_restore": "false",
|
||||
},
|
||||
setupMock: func(ms *MockSession) {
|
||||
ms.Lan.AddIfNew("192.168.1.10", "aa:aa:aa:aa:aa:aa")
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *ArpSpoofer) error {
|
||||
if mod.internal {
|
||||
return fmt.Errorf("expected internal to be false")
|
||||
}
|
||||
if mod.fullDuplex {
|
||||
return fmt.Errorf("expected fullDuplex to be false")
|
||||
}
|
||||
if mod.skipRestore {
|
||||
return fmt.Errorf("expected skipRestore to be false")
|
||||
}
|
||||
if len(mod.addresses) != 1 {
|
||||
return fmt.Errorf("expected 1 address, got %d", len(mod.addresses))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple targets and whitelist",
|
||||
params: map[string]string{
|
||||
"arp.spoof.targets": "192.168.1.10,192.168.1.20",
|
||||
"arp.spoof.whitelist": "192.168.1.30",
|
||||
"arp.spoof.internal": "true",
|
||||
"arp.spoof.fullduplex": "true",
|
||||
"arp.spoof.skip_restore": "true",
|
||||
},
|
||||
setupMock: func(ms *MockSession) {
|
||||
ms.Lan.AddIfNew("192.168.1.10", "aa:aa:aa:aa:aa:aa")
|
||||
ms.Lan.AddIfNew("192.168.1.20", "bb:bb:bb:bb:bb:bb")
|
||||
ms.Lan.AddIfNew("192.168.1.30", "cc:cc:cc:cc:cc:cc")
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *ArpSpoofer) error {
|
||||
if !mod.internal {
|
||||
return fmt.Errorf("expected internal to be true")
|
||||
}
|
||||
if !mod.fullDuplex {
|
||||
return fmt.Errorf("expected fullDuplex to be true")
|
||||
}
|
||||
if !mod.skipRestore {
|
||||
return fmt.Errorf("expected skipRestore to be true")
|
||||
}
|
||||
if len(mod.addresses) != 2 {
|
||||
return fmt.Errorf("expected 2 addresses, got %d", len(mod.addresses))
|
||||
}
|
||||
if len(mod.wAddresses) != 1 {
|
||||
return fmt.Errorf("expected 1 whitelisted address, got %d", len(mod.wAddresses))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "MAC address targets",
|
||||
params: map[string]string{
|
||||
"arp.spoof.targets": "aa:aa:aa:aa:aa:aa",
|
||||
"arp.spoof.whitelist": "",
|
||||
"arp.spoof.internal": "false",
|
||||
"arp.spoof.fullduplex": "false",
|
||||
"arp.spoof.skip_restore": "false",
|
||||
},
|
||||
setupMock: func(ms *MockSession) {
|
||||
ms.Lan.AddIfNew("192.168.1.10", "aa:aa:aa:aa:aa:aa")
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *ArpSpoofer) error {
|
||||
if len(mod.macs) != 1 {
|
||||
return fmt.Errorf("expected 1 MAC address, got %d", len(mod.macs))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid target",
|
||||
params: map[string]string{
|
||||
"arp.spoof.targets": "invalid-target",
|
||||
"arp.spoof.whitelist": "",
|
||||
"arp.spoof.internal": "false",
|
||||
"arp.spoof.fullduplex": "false",
|
||||
"arp.spoof.skip_restore": "false",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Set parameters
|
||||
for k, v := range tt.params {
|
||||
mockSess.Env.Set(k, v)
|
||||
}
|
||||
|
||||
// Setup mock
|
||||
if tt.setupMock != nil {
|
||||
tt.setupMock(mockSess)
|
||||
}
|
||||
|
||||
err := mod.Configure()
|
||||
|
||||
if tt.expectErr && err == nil {
|
||||
t.Error("expected error but got none")
|
||||
} else if !tt.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !tt.expectErr && tt.validate != nil {
|
||||
if err := tt.validate(mod); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestArpSpooferStartStop(t *testing.T) {
|
||||
mockSess, _, mockFirewall := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Setup targets
|
||||
targetIP := "192.168.1.10"
|
||||
targetMAC, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
mockSess.Lan.AddIfNew(targetIP, targetMAC.String())
|
||||
mockSess.findMACResults[targetIP] = targetMAC
|
||||
|
||||
// Configure
|
||||
mockSess.Env.Set("arp.spoof.targets", targetIP)
|
||||
mockSess.Env.Set("arp.spoof.fullduplex", "false")
|
||||
mockSess.Env.Set("arp.spoof.internal", "false")
|
||||
|
||||
// Start the spoofer
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start spoofer: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("Spoofer should be running after Start()")
|
||||
}
|
||||
|
||||
// Check that forwarding was enabled
|
||||
if !mockFirewall.IsForwardingEnabled() {
|
||||
t.Error("Forwarding should be enabled after starting spoofer")
|
||||
}
|
||||
|
||||
// Let it run for a bit
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Stop the spoofer
|
||||
err = mod.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stop spoofer: %v", err)
|
||||
}
|
||||
|
||||
if mod.Running() {
|
||||
t.Error("Spoofer should not be running after Stop()")
|
||||
}
|
||||
|
||||
// Note: We can't easily verify packet sending without modifying the actual module
|
||||
// to use an interface for the queue. The module behavior is verified through
|
||||
// state changes (running state, forwarding enabled, etc.)
|
||||
}
|
||||
|
||||
func TestArpSpooferBanMode(t *testing.T) {
|
||||
mockSess, _, mockFirewall := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Setup targets
|
||||
targetIP := "192.168.1.10"
|
||||
targetMAC, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
mockSess.Lan.AddIfNew(targetIP, targetMAC.String())
|
||||
mockSess.findMACResults[targetIP] = targetMAC
|
||||
|
||||
// Configure
|
||||
mockSess.Env.Set("arp.spoof.targets", targetIP)
|
||||
|
||||
// Find and execute the ban handler
|
||||
handlers := mod.Handlers()
|
||||
for _, h := range handlers {
|
||||
if h.Name == "arp.ban on" {
|
||||
err := h.Exec([]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start ban mode: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !mod.ban {
|
||||
t.Error("Ban mode should be enabled")
|
||||
}
|
||||
|
||||
// Check that forwarding was NOT enabled
|
||||
if mockFirewall.IsForwardingEnabled() {
|
||||
t.Error("Forwarding should NOT be enabled in ban mode")
|
||||
}
|
||||
|
||||
// Let it run for a bit
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Stop using ban off handler
|
||||
for _, h := range handlers {
|
||||
if h.Name == "arp.ban off" {
|
||||
err := h.Exec([]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stop ban mode: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if mod.ban {
|
||||
t.Error("Ban mode should be disabled after stop")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArpSpooferWhitelisting(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Add some IPs and MACs to whitelist
|
||||
whitelistIP := net.ParseIP("192.168.1.50")
|
||||
whitelistMAC, _ := net.ParseMAC("ff:ff:ff:ff:ff:ff")
|
||||
|
||||
mod.wAddresses = []net.IP{whitelistIP}
|
||||
mod.wMacs = []net.HardwareAddr{whitelistMAC}
|
||||
|
||||
// Test IP whitelisting
|
||||
if !mod.isWhitelisted("192.168.1.50", nil) {
|
||||
t.Error("IP should be whitelisted")
|
||||
}
|
||||
|
||||
if mod.isWhitelisted("192.168.1.60", nil) {
|
||||
t.Error("IP should not be whitelisted")
|
||||
}
|
||||
|
||||
// Test MAC whitelisting
|
||||
if !mod.isWhitelisted("", whitelistMAC) {
|
||||
t.Error("MAC should be whitelisted")
|
||||
}
|
||||
|
||||
otherMAC, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
if mod.isWhitelisted("", otherMAC) {
|
||||
t.Error("MAC should not be whitelisted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArpSpooferFullDuplex(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Setup targets
|
||||
targetIP := "192.168.1.10"
|
||||
targetMAC, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
mockSess.Lan.AddIfNew(targetIP, targetMAC.String())
|
||||
mockSess.findMACResults[targetIP] = targetMAC
|
||||
|
||||
// Configure with full duplex
|
||||
mockSess.Env.Set("arp.spoof.targets", targetIP)
|
||||
mockSess.Env.Set("arp.spoof.fullduplex", "true")
|
||||
|
||||
// Verify configuration
|
||||
err := mod.Configure()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to configure: %v", err)
|
||||
}
|
||||
|
||||
if !mod.fullDuplex {
|
||||
t.Error("Full duplex mode should be enabled")
|
||||
}
|
||||
|
||||
// Start the spoofer
|
||||
err = mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start spoofer: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("Module should be running")
|
||||
}
|
||||
|
||||
// Let it run for a bit
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
// Stop
|
||||
mod.Stop()
|
||||
}
|
||||
|
||||
func TestArpSpooferInternalMode(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Setup multiple targets
|
||||
targets := map[string]string{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
"192.168.1.20": "bb:bb:bb:bb:bb:bb",
|
||||
"192.168.1.30": "cc:cc:cc:cc:cc:cc",
|
||||
}
|
||||
|
||||
for ip, mac := range targets {
|
||||
mockSess.Lan.AddIfNew(ip, mac)
|
||||
hwAddr, _ := net.ParseMAC(mac)
|
||||
mockSess.findMACResults[ip] = hwAddr
|
||||
}
|
||||
|
||||
// Configure with internal mode
|
||||
mockSess.Env.Set("arp.spoof.targets", "192.168.1.10,192.168.1.20")
|
||||
mockSess.Env.Set("arp.spoof.internal", "true")
|
||||
|
||||
// Verify configuration
|
||||
err := mod.Configure()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to configure: %v", err)
|
||||
}
|
||||
|
||||
if !mod.internal {
|
||||
t.Error("Internal mode should be enabled")
|
||||
}
|
||||
|
||||
// Start the spoofer
|
||||
err = mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start spoofer: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("Module should be running")
|
||||
}
|
||||
|
||||
// Let it run briefly
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Stop
|
||||
mod.Stop()
|
||||
}
|
||||
|
||||
func TestArpSpooferGetTargets(t *testing.T) {
|
||||
// This test verifies the getTargets logic without actually calling it
|
||||
// since the method uses Session.FindMAC which can't be easily mocked
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Test address and MAC parsing
|
||||
targetIP := net.ParseIP("192.168.1.10")
|
||||
targetMAC, _ := net.ParseMAC("aa:aa:aa:aa:aa:aa")
|
||||
|
||||
// Add targets by IP
|
||||
mod.addresses = []net.IP{targetIP}
|
||||
|
||||
// Verify addresses were set correctly
|
||||
if len(mod.addresses) != 1 {
|
||||
t.Errorf("expected 1 address, got %d", len(mod.addresses))
|
||||
}
|
||||
|
||||
if !mod.addresses[0].Equal(targetIP) {
|
||||
t.Errorf("expected address %s, got %s", targetIP, mod.addresses[0])
|
||||
}
|
||||
|
||||
// Add targets by MAC
|
||||
mod.macs = []net.HardwareAddr{targetMAC}
|
||||
|
||||
// Verify MACs were set correctly
|
||||
if len(mod.macs) != 1 {
|
||||
t.Errorf("expected 1 MAC, got %d", len(mod.macs))
|
||||
}
|
||||
|
||||
if !bytes.Equal(mod.macs[0], targetMAC) {
|
||||
t.Errorf("expected MAC %s, got %s", targetMAC, mod.macs[0])
|
||||
}
|
||||
|
||||
// Note: The actual getTargets method would look up these addresses/MACs
|
||||
// in the network, but we can't easily test that without refactoring
|
||||
// the module to use dependency injection for network operations
|
||||
}
|
||||
|
||||
func TestArpSpooferSkipRestore(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// The skip_restore parameter is set up with an observer in NewArpSpoofer
|
||||
// We'll test it by changing the parameter value, which triggers the observer
|
||||
mockSess.Env.Set("arp.spoof.skip_restore", "true")
|
||||
|
||||
// Configure to trigger parameter reading
|
||||
mod.Configure()
|
||||
|
||||
// Check the observer worked by checking if skipRestore was set
|
||||
// Note: The actual observer is triggered during module creation
|
||||
// so we test the functionality indirectly through the module's behavior
|
||||
|
||||
// Start and stop to see if restoration is skipped
|
||||
mockSess.Env.Set("arp.spoof.targets", "192.168.1.10")
|
||||
mockSess.Lan.AddIfNew("192.168.1.10", "aa:aa:aa:aa:aa:aa")
|
||||
|
||||
mod.Start()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
mod.Stop()
|
||||
|
||||
// With skip_restore true, the module should have skipRestore set
|
||||
// We can't directly test the observer, but we verify the behavior
|
||||
}
|
||||
|
||||
func TestArpSpooferEmptyTargets(t *testing.T) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Configure with empty targets
|
||||
mockSess.Env.Set("arp.spoof.targets", "")
|
||||
|
||||
// Start should not error but should not actually start
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Start with empty targets should not error: %v", err)
|
||||
}
|
||||
|
||||
// Module should not be running
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running with empty targets")
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
func BenchmarkArpSpooferGetTargets(b *testing.B) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Setup targets
|
||||
for i := 0; i < 10; i++ {
|
||||
ip := fmt.Sprintf("192.168.1.%d", i+10)
|
||||
mac := fmt.Sprintf("aa:bb:cc:dd:ee:%02x", i)
|
||||
mockSess.Lan.AddIfNew(ip, mac)
|
||||
hwAddr, _ := net.ParseMAC(mac)
|
||||
mockSess.findMACResults[ip] = hwAddr
|
||||
mod.addresses = append(mod.addresses, net.ParseIP(ip))
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mod.getTargets(false)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArpSpooferWhitelisting(b *testing.B) {
|
||||
mockSess, _, _ := createMockSession()
|
||||
mod := NewArpSpoofer(mockSess.Session)
|
||||
|
||||
// Add many whitelist entries
|
||||
for i := 0; i < 100; i++ {
|
||||
ip := net.ParseIP(fmt.Sprintf("192.168.1.%d", i))
|
||||
mod.wAddresses = append(mod.wAddresses, ip)
|
||||
}
|
||||
|
||||
testMAC, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mod.isWhitelisted("192.168.1.50", testMAC)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,321 +0,0 @@
|
|||
//go:build !windows && !freebsd && !openbsd && !netbsd
|
||||
// +build !windows,!freebsd,!openbsd,!netbsd
|
||||
|
||||
package ble
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
||||
var (
|
||||
testSession *session.Session
|
||||
sessionOnce sync.Once
|
||||
)
|
||||
|
||||
func createMockSession(t *testing.T) *session.Session {
|
||||
sessionOnce.Do(func() {
|
||||
var err error
|
||||
testSession, err = session.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
}
|
||||
})
|
||||
return testSession
|
||||
}
|
||||
|
||||
func TestNewBLERecon(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewBLERecon returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "ble.recon" {
|
||||
t.Errorf("Expected name 'ble.recon', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("Unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.Description() == "" {
|
||||
t.Error("Empty description")
|
||||
}
|
||||
|
||||
// Check initial values
|
||||
if mod.deviceId != -1 {
|
||||
t.Errorf("Expected deviceId -1, got %d", mod.deviceId)
|
||||
}
|
||||
|
||||
if mod.connected {
|
||||
t.Error("Should not be connected initially")
|
||||
}
|
||||
|
||||
if mod.connTimeout != 5 {
|
||||
t.Errorf("Expected connection timeout 5, got %d", mod.connTimeout)
|
||||
}
|
||||
|
||||
if mod.devTTL != 30 {
|
||||
t.Errorf("Expected device TTL 30, got %d", mod.devTTL)
|
||||
}
|
||||
|
||||
// Check channels
|
||||
if mod.quit == nil {
|
||||
t.Error("Quit channel should not be nil")
|
||||
}
|
||||
|
||||
if mod.done == nil {
|
||||
t.Error("Done channel should not be nil")
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
expectedHandlers := []string{
|
||||
"ble.recon on",
|
||||
"ble.recon off",
|
||||
"ble.clear",
|
||||
"ble.show",
|
||||
"ble.enum MAC",
|
||||
"ble.write MAC UUID HEX_DATA",
|
||||
}
|
||||
|
||||
if len(handlers) != len(expectedHandlers) {
|
||||
t.Errorf("Expected %d handlers, got %d", len(expectedHandlers), len(handlers))
|
||||
}
|
||||
|
||||
handlerNames := make(map[string]bool)
|
||||
for _, h := range handlers {
|
||||
handlerNames[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerNames[expected] {
|
||||
t.Errorf("Handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEnumerating(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Initially should not be enumerating
|
||||
if mod.isEnumerating() {
|
||||
t.Error("Should not be enumerating initially")
|
||||
}
|
||||
|
||||
// When currDevice is set, should be enumerating
|
||||
// We can't create a real BLE device here, but we can test the logic
|
||||
}
|
||||
|
||||
func TestDummyWriter(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
writer := dummyWriter{mod}
|
||||
testData := []byte("test log message")
|
||||
|
||||
n, err := writer.Write(testData)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if n != len(testData) {
|
||||
t.Errorf("Expected to write %d bytes, wrote %d", len(testData), n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameters(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Check that parameters are registered
|
||||
paramNames := []string{
|
||||
"ble.device",
|
||||
"ble.timeout",
|
||||
"ble.ttl",
|
||||
}
|
||||
|
||||
// Parameters are stored in the session environment
|
||||
// We'll just ensure the module was created properly
|
||||
for _, param := range paramNames {
|
||||
// This is a simplified check
|
||||
_ = param
|
||||
}
|
||||
|
||||
if mod == nil {
|
||||
t.Error("Module should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningState(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Initially should not be running
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running initially")
|
||||
}
|
||||
|
||||
// Note: Cannot test actual Start/Stop without BLE hardware
|
||||
}
|
||||
|
||||
func TestChannels(t *testing.T) {
|
||||
// Skip this test as channel operations might hang in certain environments
|
||||
t.Skip("Skipping channel test to prevent potential hangs")
|
||||
}
|
||||
|
||||
func TestClearHandler(t *testing.T) {
|
||||
// Skip this test as it requires BLE to be initialized in the session
|
||||
t.Skip("Skipping clear handler test - requires initialized BLE in session")
|
||||
}
|
||||
|
||||
func TestBLEPrompt(t *testing.T) {
|
||||
expected := "{blb}{fw}BLE {fb}{reset} {bold}» {reset}"
|
||||
if blePrompt != expected {
|
||||
t.Errorf("Expected prompt '%s', got '%s'", expected, blePrompt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCurrentDevice(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Test setting nil device
|
||||
mod.setCurrentDevice(nil)
|
||||
if mod.currDevice != nil {
|
||||
t.Error("Current device should be nil")
|
||||
}
|
||||
if mod.connected {
|
||||
t.Error("Should not be connected after setting nil device")
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewSelector(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Check that view selector is initialized
|
||||
if mod.selector == nil {
|
||||
t.Error("View selector should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBLEAliveInterval(t *testing.T) {
|
||||
expected := time.Duration(5) * time.Second
|
||||
if bleAliveInterval != expected {
|
||||
t.Errorf("Expected alive interval %v, got %v", expected, bleAliveInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColNames(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Test without name
|
||||
cols := mod.colNames(false)
|
||||
expectedCols := []string{"RSSI", "MAC", "Vendor", "Flags", "Connect", "Seen"}
|
||||
if len(cols) != len(expectedCols) {
|
||||
t.Errorf("Expected %d columns, got %d", len(expectedCols), len(cols))
|
||||
}
|
||||
|
||||
// Test with name
|
||||
colsWithName := mod.colNames(true)
|
||||
expectedColsWithName := []string{"RSSI", "MAC", "Name", "Vendor", "Flags", "Connect", "Seen"}
|
||||
if len(colsWithName) != len(expectedColsWithName) {
|
||||
t.Errorf("Expected %d columns with name, got %d", len(expectedColsWithName), len(colsWithName))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoFilter(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// Without expression, should always return true
|
||||
result := mod.doFilter(nil)
|
||||
if !result {
|
||||
t.Error("doFilter should return true when no expression is set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShow(t *testing.T) {
|
||||
// Skip this test as it requires BLE to be initialized in the session
|
||||
t.Skip("Skipping show test - requires initialized BLE in session")
|
||||
}
|
||||
|
||||
func TestConfigure(t *testing.T) {
|
||||
// Skip this test as it may hang trying to access BLE hardware
|
||||
t.Skip("Skipping configure test - may hang accessing BLE hardware")
|
||||
}
|
||||
|
||||
func TestGetRow(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
// We can't create a real BLE device without hardware, but we can test the logic
|
||||
// by ensuring the method exists and would handle nil gracefully
|
||||
_ = mod
|
||||
}
|
||||
|
||||
func TestDoSelection(t *testing.T) {
|
||||
// Skip this test as it requires BLE to be initialized in the session
|
||||
t.Skip("Skipping doSelection test - requires initialized BLE in session")
|
||||
}
|
||||
|
||||
func TestWriteBuffer(t *testing.T) {
|
||||
// Skip this test as it may hang trying to access BLE hardware
|
||||
t.Skip("Skipping writeBuffer test - may hang accessing BLE hardware")
|
||||
}
|
||||
|
||||
func TestEnumAllTheThings(t *testing.T) {
|
||||
// Skip this test as it may hang trying to access BLE hardware
|
||||
t.Skip("Skipping enumAllTheThings test - may hang accessing BLE hardware")
|
||||
}
|
||||
|
||||
// Benchmark tests - using singleton session to avoid flag redefinition
|
||||
func BenchmarkNewBLERecon(b *testing.B) {
|
||||
// Use a test instance to get singleton session
|
||||
s := createMockSession(&testing.T{})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewBLERecon(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsEnumerating(b *testing.B) {
|
||||
s := createMockSession(&testing.T{})
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = mod.isEnumerating()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDummyWriter(b *testing.B) {
|
||||
s := createMockSession(&testing.T{})
|
||||
mod := NewBLERecon(s)
|
||||
writer := dummyWriter{mod}
|
||||
testData := []byte("benchmark log message")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
writer.Write(testData)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDoFilter(b *testing.B) {
|
||||
s := createMockSession(&testing.T{})
|
||||
mod := NewBLERecon(s)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod.doFilter(nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,356 +0,0 @@
|
|||
package c2
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"text/template"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
||||
var (
|
||||
testSession *session.Session
|
||||
sessionOnce sync.Once
|
||||
)
|
||||
|
||||
func createMockSession(t *testing.T) *session.Session {
|
||||
sessionOnce.Do(func() {
|
||||
var err error
|
||||
testSession, err = session.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
}
|
||||
})
|
||||
return testSession
|
||||
}
|
||||
|
||||
func TestNewC2(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewC2 returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "c2" {
|
||||
t.Errorf("Expected name 'c2', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("Unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.Description() == "" {
|
||||
t.Error("Empty description")
|
||||
}
|
||||
|
||||
// Check default settings
|
||||
if mod.settings.server != "localhost:6697" {
|
||||
t.Errorf("Expected default server 'localhost:6697', got '%s'", mod.settings.server)
|
||||
}
|
||||
|
||||
if !mod.settings.tls {
|
||||
t.Error("Expected TLS to be enabled by default")
|
||||
}
|
||||
|
||||
if mod.settings.tlsVerify {
|
||||
t.Error("Expected TLS verify to be disabled by default")
|
||||
}
|
||||
|
||||
if mod.settings.nick != "bettercap" {
|
||||
t.Errorf("Expected default nick 'bettercap', got '%s'", mod.settings.nick)
|
||||
}
|
||||
|
||||
if mod.settings.user != "bettercap" {
|
||||
t.Errorf("Expected default user 'bettercap', got '%s'", mod.settings.user)
|
||||
}
|
||||
|
||||
if mod.settings.operator != "admin" {
|
||||
t.Errorf("Expected default operator 'admin', got '%s'", mod.settings.operator)
|
||||
}
|
||||
|
||||
// Check channels
|
||||
if mod.quit == nil {
|
||||
t.Error("Quit channel should not be nil")
|
||||
}
|
||||
|
||||
// Check maps
|
||||
if mod.templates == nil {
|
||||
t.Error("Templates map should not be nil")
|
||||
}
|
||||
|
||||
if mod.channels == nil {
|
||||
t.Error("Channels map should not be nil")
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
expectedHandlers := []string{
|
||||
"c2 on",
|
||||
"c2 off",
|
||||
"c2.channel.set EVENT_TYPE CHANNEL",
|
||||
"c2.channel.clear EVENT_TYPE",
|
||||
"c2.template.set EVENT_TYPE TEMPLATE",
|
||||
"c2.template.clear EVENT_TYPE",
|
||||
}
|
||||
|
||||
if len(handlers) != len(expectedHandlers) {
|
||||
t.Errorf("Expected %d handlers, got %d", len(expectedHandlers), len(handlers))
|
||||
}
|
||||
|
||||
handlerNames := make(map[string]bool)
|
||||
for _, h := range handlers {
|
||||
handlerNames[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerNames[expected] {
|
||||
t.Errorf("Handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultSettings(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
// Check default channel settings
|
||||
if mod.settings.eventsChannel != "#events" {
|
||||
t.Errorf("Expected default events channel '#events', got '%s'", mod.settings.eventsChannel)
|
||||
}
|
||||
|
||||
if mod.settings.outputChannel != "#events" {
|
||||
t.Errorf("Expected default output channel '#events', got '%s'", mod.settings.outputChannel)
|
||||
}
|
||||
|
||||
if mod.settings.controlChannel != "#events" {
|
||||
t.Errorf("Expected default control channel '#events', got '%s'", mod.settings.controlChannel)
|
||||
}
|
||||
|
||||
if mod.settings.password != "password" {
|
||||
t.Errorf("Expected default password 'password', got '%s'", mod.settings.password)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningState(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
// Initially should not be running
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running initially")
|
||||
}
|
||||
|
||||
// Note: Cannot test actual Start/Stop without IRC server
|
||||
}
|
||||
|
||||
func TestEventContext(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
|
||||
ctx := eventContext{
|
||||
Session: s,
|
||||
Event: session.Event{Tag: "test.event"},
|
||||
}
|
||||
|
||||
if ctx.Session == nil {
|
||||
t.Error("Session should not be nil")
|
||||
}
|
||||
|
||||
if ctx.Event.Tag != "test.event" {
|
||||
t.Errorf("Expected event tag 'test.event', got '%s'", ctx.Event.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelHandlers(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
// Test channel.set handler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.channel.set EVENT_TYPE CHANNEL" {
|
||||
err := h.Exec([]string{"test.event", "#test"})
|
||||
if err != nil {
|
||||
t.Errorf("channel.set handler failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify channel was set
|
||||
if channel, found := mod.channels["test.event"]; !found {
|
||||
t.Error("Channel was not set")
|
||||
} else if channel != "#test" {
|
||||
t.Errorf("Expected channel '#test', got '%s'", channel)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Test channel.clear handler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.channel.clear EVENT_TYPE" {
|
||||
err := h.Exec([]string{"test.event"})
|
||||
if err != nil {
|
||||
t.Errorf("channel.clear handler failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify channel was cleared
|
||||
if _, found := mod.channels["test.event"]; found {
|
||||
t.Error("Channel was not cleared")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateHandlers(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
// Test template.set handler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.template.set EVENT_TYPE TEMPLATE" {
|
||||
err := h.Exec([]string{"test.event", "Event: {{.Event.Tag}}"})
|
||||
if err != nil {
|
||||
t.Errorf("template.set handler failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify template was set
|
||||
if tpl, found := mod.templates["test.event"]; !found {
|
||||
t.Error("Template was not set")
|
||||
} else if tpl == nil {
|
||||
t.Error("Template is nil")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Test template.clear handler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.template.clear EVENT_TYPE" {
|
||||
err := h.Exec([]string{"test.event"})
|
||||
if err != nil {
|
||||
t.Errorf("template.clear handler failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify template was cleared
|
||||
if _, found := mod.templates["test.event"]; found {
|
||||
t.Error("Template was not cleared")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearNonExistent(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
// Test clearing non-existent channel
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.channel.clear EVENT_TYPE" {
|
||||
err := h.Exec([]string{"non.existent"})
|
||||
if err == nil {
|
||||
t.Error("Expected error when clearing non-existent channel")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Test clearing non-existent template
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.template.clear EVENT_TYPE" {
|
||||
err := h.Exec([]string{"non.existent"})
|
||||
if err == nil {
|
||||
t.Error("Expected error when clearing non-existent template")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameters(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewC2(s)
|
||||
|
||||
// Check that all parameters are registered
|
||||
paramNames := []string{
|
||||
"c2.server",
|
||||
"c2.server.tls",
|
||||
"c2.server.tls.verify",
|
||||
"c2.operator",
|
||||
"c2.nick",
|
||||
"c2.username",
|
||||
"c2.password",
|
||||
"c2.sasl.username",
|
||||
"c2.sasl.password",
|
||||
"c2.channel.output",
|
||||
"c2.channel.events",
|
||||
"c2.channel.control",
|
||||
}
|
||||
|
||||
// Parameters are stored in the session environment
|
||||
for _, param := range paramNames {
|
||||
// This is a simplified check
|
||||
_ = param
|
||||
}
|
||||
|
||||
if mod == nil {
|
||||
t.Error("Module should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateExecution(t *testing.T) {
|
||||
// Test template parsing and execution
|
||||
tmpl, err := template.New("test").Parse("Event: {{.Event.Tag}}")
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse template: %v", err)
|
||||
}
|
||||
|
||||
if tmpl == nil {
|
||||
t.Error("Template should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkNewC2(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewC2(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChannelSet(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
mod := NewC2(s)
|
||||
|
||||
var handler *session.ModuleHandler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.channel.set EVENT_TYPE CHANNEL" {
|
||||
handler = &h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
handler.Exec([]string{"test.event", "#test"})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTemplateSet(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
mod := NewC2(s)
|
||||
|
||||
var handler *session.ModuleHandler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "c2.template.set EVENT_TYPE TEMPLATE" {
|
||||
handler = &h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
handler.Exec([]string{"test.event", "Event: {{.Event.Tag}}"})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
package can
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"go.einride.tech/can"
|
||||
)
|
||||
|
||||
var (
|
||||
testSession *session.Session
|
||||
sessionOnce sync.Once
|
||||
)
|
||||
|
||||
func createMockSession(t *testing.T) *session.Session {
|
||||
sessionOnce.Do(func() {
|
||||
var err error
|
||||
testSession, err = session.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
}
|
||||
})
|
||||
return testSession
|
||||
}
|
||||
|
||||
func TestNewCanModule(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewCanModule returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "can" {
|
||||
t.Errorf("Expected name 'can', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("Unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.Description() == "" {
|
||||
t.Error("Empty description")
|
||||
}
|
||||
|
||||
// Check default values
|
||||
if mod.transport != "can" {
|
||||
t.Errorf("Expected default transport 'can', got '%s'", mod.transport)
|
||||
}
|
||||
|
||||
if mod.deviceName != "can0" {
|
||||
t.Errorf("Expected default device 'can0', got '%s'", mod.deviceName)
|
||||
}
|
||||
|
||||
if mod.dumpName != "" {
|
||||
t.Errorf("Expected empty dumpName, got '%s'", mod.dumpName)
|
||||
}
|
||||
|
||||
if mod.dumpInject {
|
||||
t.Error("Expected dumpInject to be false by default")
|
||||
}
|
||||
|
||||
if mod.filter != "" {
|
||||
t.Errorf("Expected empty filter, got '%s'", mod.filter)
|
||||
}
|
||||
|
||||
// Check DBC and OBD2
|
||||
if mod.dbc == nil {
|
||||
t.Error("DBC should not be nil")
|
||||
}
|
||||
|
||||
if mod.obd2 == nil {
|
||||
t.Error("OBD2 should not be nil")
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
expectedHandlers := []string{
|
||||
"can.recon on",
|
||||
"can.recon off",
|
||||
"can.clear",
|
||||
"can.show",
|
||||
"can.dbc.load NAME",
|
||||
"can.inject FRAME_EXPRESSION",
|
||||
"can.fuzz ID_OR_NODE_NAME OPTIONAL_SIZE",
|
||||
}
|
||||
|
||||
if len(handlers) != len(expectedHandlers) {
|
||||
t.Errorf("Expected %d handlers, got %d", len(expectedHandlers), len(handlers))
|
||||
}
|
||||
|
||||
handlerNames := make(map[string]bool)
|
||||
for _, h := range handlers {
|
||||
handlerNames[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerNames[expected] {
|
||||
t.Errorf("Handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningState(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Initially should not be running
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running initially")
|
||||
}
|
||||
|
||||
// Note: Cannot test actual Start/Stop without CAN hardware
|
||||
}
|
||||
|
||||
func TestClearHandler(t *testing.T) {
|
||||
// Skip this test as it requires CAN to be initialized in the session
|
||||
t.Skip("Skipping clear handler test - requires initialized CAN in session")
|
||||
}
|
||||
|
||||
func TestInjectNotRunning(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Test inject when not running
|
||||
handlers := mod.Handlers()
|
||||
for _, h := range handlers {
|
||||
if h.Name == "can.inject FRAME_EXPRESSION" {
|
||||
err := h.Exec([]string{"123#deadbeef"})
|
||||
if err == nil {
|
||||
t.Error("Expected error when injecting while not running")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzNotRunning(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Test fuzz when not running
|
||||
handlers := mod.Handlers()
|
||||
for _, h := range handlers {
|
||||
if h.Name == "can.fuzz ID_OR_NODE_NAME OPTIONAL_SIZE" {
|
||||
err := h.Exec([]string{"123", ""})
|
||||
if err == nil {
|
||||
t.Error("Expected error when fuzzing while not running")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParameters(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Check that all parameters are registered
|
||||
paramNames := []string{
|
||||
"can.device",
|
||||
"can.dump",
|
||||
"can.dump.inject",
|
||||
"can.transport",
|
||||
"can.filter",
|
||||
"can.parse.obd2",
|
||||
}
|
||||
|
||||
// Parameters are stored in the session environment
|
||||
for _, param := range paramNames {
|
||||
// This is a simplified check
|
||||
_ = param
|
||||
}
|
||||
|
||||
if mod == nil {
|
||||
t.Error("Module should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBC(t *testing.T) {
|
||||
dbc := &DBC{}
|
||||
if dbc == nil {
|
||||
t.Error("DBC should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOBD2(t *testing.T) {
|
||||
obd2 := &OBD2{}
|
||||
if obd2 == nil {
|
||||
t.Error("OBD2 should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowHandler(t *testing.T) {
|
||||
// Skip this test as it requires CAN to be initialized in the session
|
||||
t.Skip("Skipping show handler test - requires initialized CAN in session")
|
||||
}
|
||||
|
||||
func TestDefaultTransport(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
if mod.transport != "can" {
|
||||
t.Errorf("Expected transport 'can', got '%s'", mod.transport)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultDevice(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
if mod.deviceName != "can0" {
|
||||
t.Errorf("Expected device 'can0', got '%s'", mod.deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterExpression(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Initially filter should be empty
|
||||
if mod.filter != "" {
|
||||
t.Errorf("Expected empty filter, got '%s'", mod.filter)
|
||||
}
|
||||
|
||||
// filterExpr should be nil initially
|
||||
if mod.filterExpr != nil {
|
||||
t.Error("Expected filterExpr to be nil initially")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBCStruct(t *testing.T) {
|
||||
// Test DBC struct initialization
|
||||
dbc := &DBC{}
|
||||
if dbc == nil {
|
||||
t.Error("DBC should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOBD2Struct(t *testing.T) {
|
||||
// Test OBD2 struct initialization
|
||||
obd2 := &OBD2{}
|
||||
if obd2 == nil {
|
||||
t.Error("OBD2 should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCANMessage(t *testing.T) {
|
||||
// Test CAN message creation using NewCanMessage
|
||||
frame := can.Frame{}
|
||||
frame.ID = 0x123
|
||||
frame.Data = [8]byte{0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00}
|
||||
frame.Length = 4
|
||||
|
||||
msg := NewCanMessage(frame)
|
||||
|
||||
if msg.Frame.ID != 0x123 {
|
||||
t.Errorf("Expected ID 0x123, got 0x%x", msg.Frame.ID)
|
||||
}
|
||||
|
||||
if msg.Frame.Length != 4 {
|
||||
t.Errorf("Expected frame length 4, got %d", msg.Frame.Length)
|
||||
}
|
||||
|
||||
if msg.Signals == nil {
|
||||
t.Error("Signals map should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultParameters(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Test default parameter values exist
|
||||
expectedParams := []string{
|
||||
"can.device",
|
||||
"can.transport",
|
||||
"can.dump",
|
||||
"can.filter",
|
||||
"can.dump.inject",
|
||||
"can.parse.obd2",
|
||||
}
|
||||
|
||||
// Check that parameters are defined
|
||||
params := mod.Parameters()
|
||||
if params == nil {
|
||||
t.Error("Parameters should not be nil")
|
||||
}
|
||||
|
||||
// Just verify we have the expected number of parameters
|
||||
if len(expectedParams) != 6 {
|
||||
t.Error("Expected 6 parameters")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerExecution(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Test that we can find all expected handlers
|
||||
handlerTests := []struct {
|
||||
name string
|
||||
args []string
|
||||
shouldFail bool
|
||||
}{
|
||||
{"can.inject FRAME_EXPRESSION", []string{"123#deadbeef"}, true}, // Should fail when not running
|
||||
{"can.fuzz ID_OR_NODE_NAME OPTIONAL_SIZE", []string{"123", "8"}, true}, // Should fail when not running
|
||||
{"can.dbc.load NAME", []string{"test.dbc"}, true}, // Will fail without actual file
|
||||
}
|
||||
|
||||
handlers := mod.Handlers()
|
||||
for _, test := range handlerTests {
|
||||
found := false
|
||||
for _, h := range handlers {
|
||||
if h.Name == test.name {
|
||||
found = true
|
||||
err := h.Exec(test.args)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Errorf("Handler %s should have failed but didn't", test.name)
|
||||
} else if !test.shouldFail && err != nil {
|
||||
t.Errorf("Handler %s failed unexpectedly: %v", test.name, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Handler %s not found", test.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleFields(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Test various fields are initialized correctly
|
||||
if mod.conn != nil {
|
||||
t.Error("conn should be nil initially")
|
||||
}
|
||||
|
||||
if mod.recv != nil {
|
||||
t.Error("recv should be nil initially")
|
||||
}
|
||||
|
||||
if mod.send != nil {
|
||||
t.Error("send should be nil initially")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBCLoadHandler(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewCanModule(s)
|
||||
|
||||
// Find dbc.load handler
|
||||
var dbcHandler *session.ModuleHandler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "can.dbc.load NAME" {
|
||||
dbcHandler = &h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dbcHandler == nil {
|
||||
t.Fatal("DBC load handler not found")
|
||||
}
|
||||
|
||||
// Test with non-existent file
|
||||
err := dbcHandler.Exec([]string{"non_existent.dbc"})
|
||||
if err == nil {
|
||||
t.Error("Expected error when loading non-existent DBC file")
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkNewCanModule(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewCanModule(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClearHandler(b *testing.B) {
|
||||
// Skip this benchmark as it requires CAN to be initialized
|
||||
b.Skip("Skipping clear handler benchmark - requires initialized CAN in session")
|
||||
}
|
||||
|
||||
func BenchmarkInjectHandler(b *testing.B) {
|
||||
s, _ := session.New()
|
||||
mod := NewCanModule(s)
|
||||
|
||||
var handler *session.ModuleHandler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "can.inject FRAME_EXPRESSION" {
|
||||
handler = &h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// This will fail since module is not running, but we're benchmarking the handler
|
||||
_ = handler.Exec([]string{"123#deadbeef"})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/bettercap/bettercap/v2/tls"
|
||||
|
||||
"github.com/evilsocket/islazy/fs"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
)
|
||||
|
||||
type DnsProxy struct {
|
||||
session.SessionModule
|
||||
proxy *DNSProxy
|
||||
}
|
||||
|
||||
func (mod *DnsProxy) Author() string {
|
||||
return "Yarwin Kolff <@buffermet>"
|
||||
}
|
||||
|
||||
func (mod *DnsProxy) Configure() error {
|
||||
var err error
|
||||
var address string
|
||||
var dnsPort int
|
||||
var doRedirect bool
|
||||
var nameserver string
|
||||
var netProtocol string
|
||||
var proxyPort int
|
||||
var scriptPath string
|
||||
var certFile string
|
||||
var keyFile string
|
||||
var whitelist string
|
||||
var blacklist string
|
||||
|
||||
if mod.Running() {
|
||||
return session.ErrAlreadyStarted(mod.Name())
|
||||
} else if err, dnsPort = mod.IntParam("dns.port"); err != nil {
|
||||
return err
|
||||
} else if err, address = mod.StringParam("dns.proxy.address"); err != nil {
|
||||
return err
|
||||
} else if err, certFile = mod.StringParam("dns.proxy.certificate"); err != nil {
|
||||
return err
|
||||
} else if certFile, err = fs.Expand(certFile); err != nil {
|
||||
return err
|
||||
} else if err, keyFile = mod.StringParam("dns.proxy.key"); err != nil {
|
||||
return err
|
||||
} else if keyFile, err = fs.Expand(keyFile); err != nil {
|
||||
return err
|
||||
} else if err, nameserver = mod.StringParam("dns.proxy.nameserver"); err != nil {
|
||||
return err
|
||||
} else if err, proxyPort = mod.IntParam("dns.proxy.port"); err != nil {
|
||||
return err
|
||||
} else if err, netProtocol = mod.StringParam("dns.proxy.protocol"); err != nil {
|
||||
return err
|
||||
} else if err, doRedirect = mod.BoolParam("dns.proxy.redirect"); err != nil {
|
||||
return err
|
||||
} else if err, scriptPath = mod.StringParam("dns.proxy.script"); err != nil {
|
||||
return err
|
||||
} else if err, blacklist = mod.StringParam("dns.proxy.blacklist"); err != nil {
|
||||
return err
|
||||
} else if err, whitelist = mod.StringParam("dns.proxy.whitelist"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mod.proxy.Blacklist = str.Comma(blacklist)
|
||||
mod.proxy.Whitelist = str.Comma(whitelist)
|
||||
|
||||
if netProtocol == "tcp-tls" {
|
||||
if !fs.Exists(certFile) || !fs.Exists(keyFile) {
|
||||
cfg, err := tls.CertConfigFromModule("dns.proxy", mod.SessionModule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mod.Debug("%+v", cfg)
|
||||
mod.Info("generating proxy certification authority TLS key to %s", keyFile)
|
||||
mod.Info("generating proxy certification authority TLS certificate to %s", certFile)
|
||||
if err := tls.Generate(cfg, certFile, keyFile, true); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
mod.Info("loading proxy certification authority TLS key from %s", keyFile)
|
||||
mod.Info("loading proxy certification authority TLS certificate from %s", certFile)
|
||||
}
|
||||
}
|
||||
|
||||
err = mod.proxy.Configure(address, dnsPort, doRedirect, nameserver, netProtocol,
|
||||
proxyPort, scriptPath, certFile, keyFile)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mod *DnsProxy) Description() string {
|
||||
return "A full featured DNS proxy that can be used to manipulate DNS traffic."
|
||||
}
|
||||
|
||||
func (mod *DnsProxy) Name() string {
|
||||
return "dns.proxy"
|
||||
}
|
||||
|
||||
func NewDnsProxy(s *session.Session) *DnsProxy {
|
||||
mod := &DnsProxy{
|
||||
SessionModule: session.NewSessionModule("dns.proxy", s),
|
||||
proxy: NewDNSProxy(s, "dns.proxy"),
|
||||
}
|
||||
|
||||
mod.AddParam(session.NewIntParameter("dns.port",
|
||||
"53",
|
||||
"DNS port to redirect when the proxy is activated."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.address",
|
||||
session.ParamIfaceAddress,
|
||||
session.IPv4Validator,
|
||||
"Address to bind the DNS proxy to."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.blacklist", "", "",
|
||||
"Comma separated list of client IPs to skip while proxying (wildcard allowed)."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.whitelist", "", "",
|
||||
"Comma separated list of client IPs to proxy if the blacklist is used."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.nameserver",
|
||||
"1.1.1.1",
|
||||
session.IPv4Validator,
|
||||
"DNS resolver address."))
|
||||
|
||||
mod.AddParam(session.NewIntParameter("dns.proxy.port",
|
||||
"8053",
|
||||
"Port to bind the DNS proxy to."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.protocol",
|
||||
"udp",
|
||||
"^(udp|tcp|tcp-tls)$",
|
||||
"Network protocol for the DNS proxy server to use. Accepted values: udp, tcp, tcp-tls"))
|
||||
|
||||
mod.AddParam(session.NewBoolParameter("dns.proxy.redirect",
|
||||
"true",
|
||||
"Enable or disable port redirection with iptables."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.certificate",
|
||||
"~/.bettercap-ca.cert.pem",
|
||||
"",
|
||||
"DNS proxy certification authority TLS certificate file."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.key",
|
||||
"~/.bettercap-ca.key.pem",
|
||||
"",
|
||||
"DNS proxy certification authority TLS key file."))
|
||||
|
||||
tls.CertConfigToModule("dns.proxy", &mod.SessionModule, tls.DefaultCloudflareDNSConfig)
|
||||
|
||||
mod.AddParam(session.NewStringParameter("dns.proxy.script",
|
||||
"",
|
||||
"",
|
||||
"Path of a JS proxy script."))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("dns.proxy on", "",
|
||||
"Start the DNS proxy.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("dns.proxy off", "",
|
||||
"Stop the DNS proxy.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (mod *DnsProxy) Start() error {
|
||||
if err := mod.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mod.SetRunning(true, func() {
|
||||
mod.proxy.Start()
|
||||
})
|
||||
}
|
||||
|
||||
func (mod *DnsProxy) Stop() error {
|
||||
return mod.SetRunning(false, func() {
|
||||
mod.proxy.Stop()
|
||||
})
|
||||
}
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/firewall"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/evilsocket/islazy/log"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
const (
|
||||
dialTimeout = 2 * time.Second
|
||||
readTimeout = 2 * time.Second
|
||||
writeTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
type DNSProxy struct {
|
||||
Name string
|
||||
Address string
|
||||
Server *dns.Server
|
||||
Redirection *firewall.Redirection
|
||||
Nameserver string
|
||||
NetProtocol string
|
||||
Script *DnsProxyScript
|
||||
CertFile string
|
||||
KeyFile string
|
||||
Blacklist []string
|
||||
Whitelist []string
|
||||
Sess *session.Session
|
||||
|
||||
doRedirect bool
|
||||
isRunning bool
|
||||
tag string
|
||||
}
|
||||
|
||||
func (p *DNSProxy) shouldProxy(clientIP string) bool {
|
||||
// check if this client is in the whitelist
|
||||
for _, ip := range p.Whitelist {
|
||||
if clientIP == ip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// check if this client is in the blacklist
|
||||
for _, ip := range p.Blacklist {
|
||||
if ip == "*" || clientIP == ip {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Configure(address string, dnsPort int, doRedirect bool, nameserver string, netProtocol string, proxyPort int, scriptPath string, certFile string, keyFile string) error {
|
||||
var err error
|
||||
|
||||
p.Address = address
|
||||
p.doRedirect = doRedirect
|
||||
p.CertFile = certFile
|
||||
p.KeyFile = keyFile
|
||||
|
||||
if scriptPath != "" {
|
||||
if err, p.Script = LoadDnsProxyScript(scriptPath, p.Sess); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.Debug("proxy script %s loaded.", scriptPath)
|
||||
}
|
||||
}
|
||||
|
||||
dnsClient := dns.Client{
|
||||
DialTimeout: dialTimeout,
|
||||
Net: netProtocol,
|
||||
ReadTimeout: readTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
}
|
||||
|
||||
resolverAddr := fmt.Sprintf("%s:%d", nameserver, dnsPort)
|
||||
|
||||
handler := dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
|
||||
m := new(dns.Msg)
|
||||
m.SetReply(req)
|
||||
|
||||
clientIP := strings.Split(w.RemoteAddr().String(), ":")[0]
|
||||
|
||||
req, res := p.onRequestFilter(req, clientIP)
|
||||
if res == nil {
|
||||
// unused var is time til res
|
||||
res, _, err := dnsClient.Exchange(req, resolverAddr)
|
||||
if err != nil {
|
||||
p.Debug("error while resolving DNS query: %s", err.Error())
|
||||
m.SetRcode(req, dns.RcodeServerFailure)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
}
|
||||
res = p.onResponseFilter(req, res, clientIP)
|
||||
if res == nil {
|
||||
p.Debug("response is nil")
|
||||
m.SetRcode(req, dns.RcodeServerFailure)
|
||||
w.WriteMsg(m)
|
||||
return
|
||||
} else {
|
||||
if err := w.WriteMsg(res); err != nil {
|
||||
p.Error("Error writing response: %s", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := w.WriteMsg(res); err != nil {
|
||||
p.Error("Error writing response: %s", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
p.Server = &dns.Server{
|
||||
Addr: fmt.Sprintf("%s:%d", address, proxyPort),
|
||||
Net: netProtocol,
|
||||
Handler: handler,
|
||||
}
|
||||
|
||||
if netProtocol == "tcp-tls" && p.CertFile != "" && p.KeyFile != "" {
|
||||
rawCert, _ := ioutil.ReadFile(p.CertFile)
|
||||
rawKey, _ := ioutil.ReadFile(p.KeyFile)
|
||||
ourCa, err := tls.X509KeyPair(rawCert, rawKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ourCa.Leaf, err = x509.ParseCertificate(ourCa.Certificate[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Server.TLSConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{ourCa},
|
||||
}
|
||||
}
|
||||
|
||||
if p.doRedirect {
|
||||
if !p.Sess.Firewall.IsForwardingEnabled() {
|
||||
p.Info("enabling forwarding.")
|
||||
p.Sess.Firewall.EnableForwarding(true)
|
||||
}
|
||||
|
||||
redirectProtocol := netProtocol
|
||||
if redirectProtocol == "tcp-tls" {
|
||||
redirectProtocol = "tcp"
|
||||
}
|
||||
p.Redirection = firewall.NewRedirection(p.Sess.Interface.Name(),
|
||||
redirectProtocol,
|
||||
dnsPort,
|
||||
p.Address,
|
||||
proxyPort)
|
||||
|
||||
if err := p.Sess.Firewall.EnableRedirection(p.Redirection, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Debug("applied redirection %s", p.Redirection.String())
|
||||
} else {
|
||||
p.Warning("port redirection disabled, the proxy must be set manually to work")
|
||||
}
|
||||
|
||||
p.Sess.UnkCmdCallback = func(cmd string) bool {
|
||||
if p.Script != nil {
|
||||
return p.Script.OnCommand(cmd)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *DNSProxy) dnsWorker() error {
|
||||
p.isRunning = true
|
||||
return p.Server.ListenAndServe()
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Debug(format string, args ...interface{}) {
|
||||
p.Sess.Events.Log(log.DEBUG, p.tag+format, args...)
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Info(format string, args ...interface{}) {
|
||||
p.Sess.Events.Log(log.INFO, p.tag+format, args...)
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Warning(format string, args ...interface{}) {
|
||||
p.Sess.Events.Log(log.WARNING, p.tag+format, args...)
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Error(format string, args ...interface{}) {
|
||||
p.Sess.Events.Log(log.ERROR, p.tag+format, args...)
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Fatal(format string, args ...interface{}) {
|
||||
p.Sess.Events.Log(log.FATAL, p.tag+format, args...)
|
||||
}
|
||||
|
||||
func NewDNSProxy(s *session.Session, tag string) *DNSProxy {
|
||||
p := &DNSProxy{
|
||||
Name: "dns.proxy",
|
||||
Sess: s,
|
||||
Server: nil,
|
||||
doRedirect: true,
|
||||
tag: session.AsTag(tag),
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Start() {
|
||||
go func() {
|
||||
p.Info("started on %s", p.Server.Addr)
|
||||
|
||||
err := p.dnsWorker()
|
||||
// TODO: check the dns server closed error
|
||||
if err != nil && err.Error() != "dns: Server closed" {
|
||||
p.Fatal("%s", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *DNSProxy) Stop() error {
|
||||
if p.Script != nil {
|
||||
if p.Script.Plugin.HasFunc("onExit") {
|
||||
if _, err := p.Script.Call("onExit"); err != nil {
|
||||
log.Error("Error while executing onExit callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.doRedirect && p.Redirection != nil {
|
||||
p.Debug("disabling redirection %s", p.Redirection.String())
|
||||
if err := p.Sess.Firewall.EnableRedirection(p.Redirection, false); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Redirection = nil
|
||||
}
|
||||
|
||||
p.Sess.UnkCmdCallback = nil
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
return p.Server.ShutdownContext(ctx)
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func questionsToStrings(qs []dns.Question) []string {
|
||||
questions := []string{}
|
||||
for _, q := range qs {
|
||||
questions = append(questions, tabsToSpaces(q.String()))
|
||||
}
|
||||
return questions
|
||||
}
|
||||
|
||||
func recordsToStrings(rrs []dns.RR) []string {
|
||||
records := []string{}
|
||||
for _, rr := range rrs {
|
||||
if rr != nil {
|
||||
records = append(records, tabsToSpaces(rr.String()))
|
||||
}
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func tabsToSpaces(s string) string {
|
||||
return strings.ReplaceAll(s, "\t", " ")
|
||||
}
|
||||
|
||||
func (p *DNSProxy) logRequestAction(m *dns.Msg, clientIP string) {
|
||||
p.Sess.Events.Add(p.Name+".spoofed-request", struct {
|
||||
Client string
|
||||
Questions []string
|
||||
}{
|
||||
clientIP,
|
||||
questionsToStrings(m.Question),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *DNSProxy) logResponseAction(m *dns.Msg, clientIP string) {
|
||||
p.Sess.Events.Add(p.Name+".spoofed-response", struct {
|
||||
client string
|
||||
Answers []string
|
||||
Extras []string
|
||||
Nameservers []string
|
||||
Questions []string
|
||||
}{
|
||||
clientIP,
|
||||
recordsToStrings(m.Answer),
|
||||
recordsToStrings(m.Extra),
|
||||
recordsToStrings(m.Ns),
|
||||
questionsToStrings(m.Question),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *DNSProxy) onRequestFilter(query *dns.Msg, clientIP string) (req, res *dns.Msg) {
|
||||
if p.shouldProxy(clientIP) {
|
||||
p.Debug("< %s q[%s]",
|
||||
clientIP,
|
||||
strings.Join(questionsToStrings(query.Question), ","))
|
||||
|
||||
// do we have a proxy script?
|
||||
if p.Script == nil {
|
||||
return query, nil
|
||||
}
|
||||
|
||||
// run the module OnRequest callback if defined
|
||||
jsreq, jsres := p.Script.OnRequest(query, clientIP)
|
||||
if jsreq != nil {
|
||||
// the request has been changed by the script
|
||||
req := jsreq.ToQuery()
|
||||
p.logRequestAction(req, clientIP)
|
||||
return req, nil
|
||||
} else if jsres != nil {
|
||||
// a fake response has been returned by the script
|
||||
res := jsres.ToQuery()
|
||||
p.logResponseAction(res, clientIP)
|
||||
return query, res
|
||||
}
|
||||
}
|
||||
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func (p *DNSProxy) onResponseFilter(req, res *dns.Msg, clientIP string) *dns.Msg {
|
||||
if p.shouldProxy(clientIP) {
|
||||
// sometimes it happens ¯\_(ツ)_/¯
|
||||
if res == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.Debug("> %s q[%s] a[%s] e[%s] n[%s]",
|
||||
clientIP,
|
||||
strings.Join(questionsToStrings(res.Question), ","),
|
||||
strings.Join(recordsToStrings(res.Answer), ","),
|
||||
strings.Join(recordsToStrings(res.Extra), ","),
|
||||
strings.Join(recordsToStrings(res.Ns), ","))
|
||||
|
||||
// do we have a proxy script?
|
||||
if p.Script != nil {
|
||||
_, jsres := p.Script.OnResponse(req, res, clientIP)
|
||||
if jsres != nil {
|
||||
// the response has been changed by the script
|
||||
res := jsres.ToQuery()
|
||||
p.logResponseAction(res, clientIP)
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
|
@ -1,365 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type JSQuery struct {
|
||||
Answers []map[string]interface{}
|
||||
Client map[string]string
|
||||
Compress bool
|
||||
Extras []map[string]interface{}
|
||||
Header JSQueryHeader
|
||||
Nameservers []map[string]interface{}
|
||||
Questions []map[string]interface{}
|
||||
|
||||
refHash string
|
||||
}
|
||||
|
||||
type JSQueryHeader struct {
|
||||
AuthenticatedData bool
|
||||
Authoritative bool
|
||||
CheckingDisabled bool
|
||||
Id uint16
|
||||
Opcode int
|
||||
Rcode int
|
||||
RecursionAvailable bool
|
||||
RecursionDesired bool
|
||||
Response bool
|
||||
Truncated bool
|
||||
Zero bool
|
||||
}
|
||||
|
||||
func jsPropToMap(obj map[string]interface{}, key string) map[string]interface{} {
|
||||
if v, ok := obj[key].(map[string]interface{}); ok {
|
||||
return v
|
||||
}
|
||||
log.Error("error converting JS property to map[string]interface{} where key is: %s", key)
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
func jsPropToMapArray(obj map[string]interface{}, key string) []map[string]interface{} {
|
||||
if v, ok := obj[key].([]map[string]interface{}); ok {
|
||||
return v
|
||||
}
|
||||
log.Error("error converting JS property to []map[string]interface{} where key is: %s", key)
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
|
||||
func jsPropToString(obj map[string]interface{}, key string) string {
|
||||
if v, ok := obj[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
log.Error("error converting JS property to string where key is: %s", key)
|
||||
return ""
|
||||
}
|
||||
|
||||
func jsPropToStringArray(obj map[string]interface{}, key string) []string {
|
||||
if v, ok := obj[key].([]string); ok {
|
||||
return v
|
||||
}
|
||||
log.Error("error converting JS property to []string where key is: %s", key)
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func jsPropToUint8(obj map[string]interface{}, key string) uint8 {
|
||||
if v, ok := obj[key].(int64); ok {
|
||||
if v >= 0 && v <= math.MaxUint8 {
|
||||
return uint8(v)
|
||||
}
|
||||
}
|
||||
log.Error("error converting JS property to uint8 where key is: %s", key)
|
||||
return uint8(0)
|
||||
}
|
||||
|
||||
func jsPropToUint8Array(obj map[string]interface{}, key string) []uint8 {
|
||||
if arr, ok := obj[key].([]interface{}); ok {
|
||||
vArr := make([]uint8, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
if v, ok := item.(int64); ok {
|
||||
if v >= 0 && v <= math.MaxUint8 {
|
||||
vArr = append(vArr, uint8(v))
|
||||
} else {
|
||||
log.Error("error converting JS property to []uint8 where key is: %s", key)
|
||||
return []uint8{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return vArr
|
||||
}
|
||||
log.Error("error converting JS property to []uint8 where key is: %s", key)
|
||||
return []uint8{}
|
||||
}
|
||||
|
||||
func jsPropToUint16(obj map[string]interface{}, key string) uint16 {
|
||||
if v, ok := obj[key].(int64); ok {
|
||||
if v >= 0 && v <= math.MaxUint16 {
|
||||
return uint16(v)
|
||||
}
|
||||
}
|
||||
log.Error("error converting JS property to uint16 where key is: %s", key)
|
||||
return uint16(0)
|
||||
}
|
||||
|
||||
func jsPropToUint16Array(obj map[string]interface{}, key string) []uint16 {
|
||||
if arr, ok := obj[key].([]interface{}); ok {
|
||||
vArr := make([]uint16, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
if v, ok := item.(int64); ok {
|
||||
if v >= 0 && v <= math.MaxUint16 {
|
||||
vArr = append(vArr, uint16(v))
|
||||
} else {
|
||||
log.Error("error converting JS property to []uint16 where key is: %s", key)
|
||||
return []uint16{}
|
||||
}
|
||||
}
|
||||
}
|
||||
return vArr
|
||||
}
|
||||
log.Error("error converting JS property to []uint16 where key is: %s", key)
|
||||
return []uint16{}
|
||||
}
|
||||
|
||||
func jsPropToUint32(obj map[string]interface{}, key string) uint32 {
|
||||
if v, ok := obj[key].(int64); ok {
|
||||
if v >= 0 && v <= math.MaxUint32 {
|
||||
return uint32(v)
|
||||
}
|
||||
}
|
||||
log.Error("error converting JS property to uint32 where key is: %s", key)
|
||||
return uint32(0)
|
||||
}
|
||||
|
||||
func jsPropToUint64(obj map[string]interface{}, key string) uint64 {
|
||||
prop, found := obj[key]
|
||||
if found {
|
||||
switch reflect.TypeOf(prop).String() {
|
||||
case "float64":
|
||||
if f, ok := prop.(float64); ok {
|
||||
bigInt := new(big.Float).SetFloat64(f)
|
||||
v, _ := bigInt.Uint64()
|
||||
if v >= 0 {
|
||||
return v
|
||||
}
|
||||
}
|
||||
break
|
||||
case "int64":
|
||||
if v, ok := prop.(int64); ok {
|
||||
if v >= 0 {
|
||||
return uint64(v)
|
||||
}
|
||||
}
|
||||
break
|
||||
case "uint64":
|
||||
if v, ok := prop.(uint64); ok {
|
||||
return v
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Error("error converting JS property to uint64 where key is: %s", key)
|
||||
return uint64(0)
|
||||
}
|
||||
|
||||
func uint16ArrayToInt64Array(arr []uint16) []int64 {
|
||||
vArr := make([]int64, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
vArr = append(vArr, int64(item))
|
||||
}
|
||||
return vArr
|
||||
}
|
||||
|
||||
func (j *JSQuery) NewHash() string {
|
||||
answers, _ := json.Marshal(j.Answers)
|
||||
extras, _ := json.Marshal(j.Extras)
|
||||
nameservers, _ := json.Marshal(j.Nameservers)
|
||||
questions, _ := json.Marshal(j.Questions)
|
||||
|
||||
headerHash := fmt.Sprintf("%t.%t.%t.%d.%d.%d.%t.%t.%t.%t.%t",
|
||||
j.Header.AuthenticatedData,
|
||||
j.Header.Authoritative,
|
||||
j.Header.CheckingDisabled,
|
||||
j.Header.Id,
|
||||
j.Header.Opcode,
|
||||
j.Header.Rcode,
|
||||
j.Header.RecursionAvailable,
|
||||
j.Header.RecursionDesired,
|
||||
j.Header.Response,
|
||||
j.Header.Truncated,
|
||||
j.Header.Zero)
|
||||
|
||||
hash := fmt.Sprintf("%s.%s.%t.%s.%s.%s.%s",
|
||||
answers,
|
||||
j.Client["IP"],
|
||||
j.Compress,
|
||||
extras,
|
||||
headerHash,
|
||||
nameservers,
|
||||
questions)
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
func NewJSQuery(query *dns.Msg, clientIP string) (jsQuery *JSQuery) {
|
||||
answers := make([]map[string]interface{}, len(query.Answer))
|
||||
extras := make([]map[string]interface{}, len(query.Extra))
|
||||
nameservers := make([]map[string]interface{}, len(query.Ns))
|
||||
questions := make([]map[string]interface{}, len(query.Question))
|
||||
|
||||
for i, rr := range query.Answer {
|
||||
jsRecord, err := NewJSResourceRecord(rr)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
answers[i] = jsRecord
|
||||
}
|
||||
|
||||
for i, rr := range query.Extra {
|
||||
jsRecord, err := NewJSResourceRecord(rr)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
extras[i] = jsRecord
|
||||
}
|
||||
|
||||
for i, rr := range query.Ns {
|
||||
jsRecord, err := NewJSResourceRecord(rr)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
nameservers[i] = jsRecord
|
||||
}
|
||||
|
||||
for i, question := range query.Question {
|
||||
questions[i] = map[string]interface{}{
|
||||
"Name": question.Name,
|
||||
"Qtype": int64(question.Qtype),
|
||||
"Qclass": int64(question.Qclass),
|
||||
}
|
||||
}
|
||||
|
||||
clientMAC := ""
|
||||
clientAlias := ""
|
||||
if endpoint := session.I.Lan.GetByIp(clientIP); endpoint != nil {
|
||||
clientMAC = endpoint.HwAddress
|
||||
clientAlias = endpoint.Alias
|
||||
}
|
||||
client := map[string]string{"IP": clientIP, "MAC": clientMAC, "Alias": clientAlias}
|
||||
|
||||
jsquery := &JSQuery{
|
||||
Answers: answers,
|
||||
Client: client,
|
||||
Compress: query.Compress,
|
||||
Extras: extras,
|
||||
Header: JSQueryHeader{
|
||||
AuthenticatedData: query.MsgHdr.AuthenticatedData,
|
||||
Authoritative: query.MsgHdr.Authoritative,
|
||||
CheckingDisabled: query.MsgHdr.CheckingDisabled,
|
||||
Id: query.MsgHdr.Id,
|
||||
Opcode: query.MsgHdr.Opcode,
|
||||
Rcode: query.MsgHdr.Rcode,
|
||||
RecursionAvailable: query.MsgHdr.RecursionAvailable,
|
||||
RecursionDesired: query.MsgHdr.RecursionDesired,
|
||||
Response: query.MsgHdr.Response,
|
||||
Truncated: query.MsgHdr.Truncated,
|
||||
Zero: query.MsgHdr.Zero,
|
||||
},
|
||||
Nameservers: nameservers,
|
||||
Questions: questions,
|
||||
}
|
||||
jsquery.UpdateHash()
|
||||
|
||||
return jsquery
|
||||
}
|
||||
|
||||
func (j *JSQuery) ToQuery() *dns.Msg {
|
||||
var answers []dns.RR
|
||||
var extras []dns.RR
|
||||
var nameservers []dns.RR
|
||||
var questions []dns.Question
|
||||
|
||||
for _, jsRR := range j.Answers {
|
||||
rr, err := ToRR(jsRR)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
answers = append(answers, rr)
|
||||
}
|
||||
for _, jsRR := range j.Extras {
|
||||
rr, err := ToRR(jsRR)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
extras = append(extras, rr)
|
||||
}
|
||||
for _, jsRR := range j.Nameservers {
|
||||
rr, err := ToRR(jsRR)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
nameservers = append(nameservers, rr)
|
||||
}
|
||||
|
||||
for _, jsQ := range j.Questions {
|
||||
questions = append(questions, dns.Question{
|
||||
Name: jsPropToString(jsQ, "Name"),
|
||||
Qtype: jsPropToUint16(jsQ, "Qtype"),
|
||||
Qclass: jsPropToUint16(jsQ, "Qclass"),
|
||||
})
|
||||
}
|
||||
|
||||
query := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Id: j.Header.Id,
|
||||
Response: j.Header.Response,
|
||||
Opcode: j.Header.Opcode,
|
||||
Authoritative: j.Header.Authoritative,
|
||||
Truncated: j.Header.Truncated,
|
||||
RecursionDesired: j.Header.RecursionDesired,
|
||||
RecursionAvailable: j.Header.RecursionAvailable,
|
||||
Zero: j.Header.Zero,
|
||||
AuthenticatedData: j.Header.AuthenticatedData,
|
||||
CheckingDisabled: j.Header.CheckingDisabled,
|
||||
Rcode: j.Header.Rcode,
|
||||
},
|
||||
Compress: j.Compress,
|
||||
Question: questions,
|
||||
Answer: answers,
|
||||
Ns: nameservers,
|
||||
Extra: extras,
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (j *JSQuery) UpdateHash() {
|
||||
j.refHash = j.NewHash()
|
||||
}
|
||||
|
||||
func (j *JSQuery) WasModified() bool {
|
||||
// check if any of the fields has been changed
|
||||
return j.NewHash() != j.refHash
|
||||
}
|
||||
|
||||
func (j *JSQuery) CheckIfModifiedAndUpdateHash() bool {
|
||||
// check if query was changed and update its hash
|
||||
newHash := j.NewHash()
|
||||
wasModified := j.refHash != newHash
|
||||
j.refHash = newHash
|
||||
return wasModified
|
||||
}
|
||||
|
|
@ -1,961 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func NewJSResourceRecord(rr dns.RR) (jsRecord map[string]interface{}, err error) {
|
||||
header := rr.Header()
|
||||
|
||||
jsRecord = map[string]interface{}{
|
||||
"Header": map[string]interface{}{
|
||||
"Class": int64(header.Class),
|
||||
"Name": header.Name,
|
||||
"Rrtype": int64(header.Rrtype),
|
||||
"Ttl": int64(header.Ttl),
|
||||
},
|
||||
}
|
||||
|
||||
switch rr := rr.(type) {
|
||||
case *dns.A:
|
||||
jsRecord["A"] = rr.A.String()
|
||||
case *dns.AAAA:
|
||||
jsRecord["AAAA"] = rr.AAAA.String()
|
||||
case *dns.APL:
|
||||
jsPrefixes := make([]map[string]interface{}, len(rr.Prefixes))
|
||||
for i, v := range rr.Prefixes {
|
||||
jsPrefixes[i] = map[string]interface{}{
|
||||
"Negation": v.Negation,
|
||||
"Network": v.Network.String(),
|
||||
}
|
||||
}
|
||||
jsRecord["Prefixes"] = jsPrefixes
|
||||
case *dns.CNAME:
|
||||
jsRecord["Target"] = rr.Target
|
||||
case *dns.MB:
|
||||
jsRecord["Mb"] = rr.Mb
|
||||
case *dns.MD:
|
||||
jsRecord["Md"] = rr.Md
|
||||
case *dns.MF:
|
||||
jsRecord["Mf"] = rr.Mf
|
||||
case *dns.MG:
|
||||
jsRecord["Mg"] = rr.Mg
|
||||
case *dns.MR:
|
||||
jsRecord["Mr"] = rr.Mr
|
||||
case *dns.MX:
|
||||
jsRecord["Mx"] = rr.Mx
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.NULL:
|
||||
jsRecord["Data"] = rr.Data
|
||||
case *dns.SOA:
|
||||
jsRecord["Expire"] = int64(rr.Expire)
|
||||
jsRecord["Minttl"] = int64(rr.Minttl)
|
||||
jsRecord["Ns"] = rr.Ns
|
||||
jsRecord["Refresh"] = int64(rr.Refresh)
|
||||
jsRecord["Retry"] = int64(rr.Retry)
|
||||
jsRecord["Mbox"] = rr.Mbox
|
||||
jsRecord["Serial"] = int64(rr.Serial)
|
||||
case *dns.TXT:
|
||||
jsRecord["Txt"] = rr.Txt
|
||||
case *dns.SRV:
|
||||
jsRecord["Port"] = int64(rr.Port)
|
||||
jsRecord["Priority"] = int64(rr.Priority)
|
||||
jsRecord["Target"] = rr.Target
|
||||
jsRecord["Weight"] = int64(rr.Weight)
|
||||
case *dns.PTR:
|
||||
jsRecord["Ptr"] = rr.Ptr
|
||||
case *dns.NS:
|
||||
jsRecord["Ns"] = rr.Ns
|
||||
case *dns.DNAME:
|
||||
jsRecord["Target"] = rr.Target
|
||||
case *dns.AFSDB:
|
||||
jsRecord["Subtype"] = int64(rr.Subtype)
|
||||
jsRecord["Hostname"] = rr.Hostname
|
||||
case *dns.CAA:
|
||||
jsRecord["Flag"] = int64(rr.Flag)
|
||||
jsRecord["Tag"] = rr.Tag
|
||||
jsRecord["Value"] = rr.Value
|
||||
case *dns.HINFO:
|
||||
jsRecord["Cpu"] = rr.Cpu
|
||||
jsRecord["Os"] = rr.Os
|
||||
case *dns.MINFO:
|
||||
jsRecord["Email"] = rr.Email
|
||||
jsRecord["Rmail"] = rr.Rmail
|
||||
case *dns.ISDN:
|
||||
jsRecord["Address"] = rr.Address
|
||||
jsRecord["SubAddress"] = rr.SubAddress
|
||||
case *dns.KX:
|
||||
jsRecord["Exchanger"] = rr.Exchanger
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.LOC:
|
||||
jsRecord["Altitude"] = int64(rr.Altitude)
|
||||
jsRecord["HorizPre"] = int64(rr.HorizPre)
|
||||
jsRecord["Latitude"] = int64(rr.Latitude)
|
||||
jsRecord["Longitude"] = int64(rr.Longitude)
|
||||
jsRecord["Size"] = int64(rr.Size)
|
||||
jsRecord["Version"] = int64(rr.Version)
|
||||
jsRecord["VertPre"] = int64(rr.VertPre)
|
||||
case *dns.SSHFP:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["FingerPrint"] = rr.FingerPrint
|
||||
jsRecord["Type"] = int64(rr.Type)
|
||||
case *dns.TLSA:
|
||||
jsRecord["Certificate"] = rr.Certificate
|
||||
jsRecord["MatchingType"] = int64(rr.MatchingType)
|
||||
jsRecord["Selector"] = int64(rr.Selector)
|
||||
jsRecord["Usage"] = int64(rr.Usage)
|
||||
case *dns.CERT:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["Certificate"] = rr.Certificate
|
||||
jsRecord["KeyTag"] = int64(rr.KeyTag)
|
||||
jsRecord["Type"] = int64(rr.Type)
|
||||
case *dns.DS:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["Digest"] = rr.Digest
|
||||
jsRecord["DigestType"] = int64(rr.DigestType)
|
||||
jsRecord["KeyTag"] = int64(rr.KeyTag)
|
||||
case *dns.NAPTR:
|
||||
jsRecord["Order"] = int64(rr.Order)
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
jsRecord["Flags"] = rr.Flags
|
||||
jsRecord["Service"] = rr.Service
|
||||
jsRecord["Regexp"] = rr.Regexp
|
||||
jsRecord["Replacement"] = rr.Replacement
|
||||
case *dns.RRSIG:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["Expiration"] = int64(rr.Expiration)
|
||||
jsRecord["Inception"] = int64(rr.Inception)
|
||||
jsRecord["KeyTag"] = int64(rr.KeyTag)
|
||||
jsRecord["Labels"] = int64(rr.Labels)
|
||||
jsRecord["OrigTtl"] = int64(rr.OrigTtl)
|
||||
jsRecord["Signature"] = rr.Signature
|
||||
jsRecord["SignerName"] = rr.SignerName
|
||||
jsRecord["TypeCovered"] = int64(rr.TypeCovered)
|
||||
case *dns.NSEC:
|
||||
jsRecord["NextDomain"] = rr.NextDomain
|
||||
jsRecord["TypeBitMap"] = uint16ArrayToInt64Array(rr.TypeBitMap)
|
||||
case *dns.NSEC3:
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Hash"] = int64(rr.Hash)
|
||||
jsRecord["HashLength"] = int64(rr.HashLength)
|
||||
jsRecord["Iterations"] = int64(rr.Iterations)
|
||||
jsRecord["NextDomain"] = rr.NextDomain
|
||||
jsRecord["Salt"] = rr.Salt
|
||||
jsRecord["SaltLength"] = int64(rr.SaltLength)
|
||||
jsRecord["TypeBitMap"] = uint16ArrayToInt64Array(rr.TypeBitMap)
|
||||
case *dns.NSEC3PARAM:
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Hash"] = int64(rr.Hash)
|
||||
jsRecord["Iterations"] = int64(rr.Iterations)
|
||||
jsRecord["Salt"] = rr.Salt
|
||||
jsRecord["SaltLength"] = int64(rr.SaltLength)
|
||||
case *dns.TKEY:
|
||||
jsRecord["Algorithm"] = rr.Algorithm
|
||||
jsRecord["Error"] = int64(rr.Error)
|
||||
jsRecord["Expiration"] = int64(rr.Expiration)
|
||||
jsRecord["Inception"] = int64(rr.Inception)
|
||||
jsRecord["Key"] = rr.Key
|
||||
jsRecord["KeySize"] = int64(rr.KeySize)
|
||||
jsRecord["Mode"] = int64(rr.Mode)
|
||||
jsRecord["OtherData"] = rr.OtherData
|
||||
jsRecord["OtherLen"] = int64(rr.OtherLen)
|
||||
case *dns.TSIG:
|
||||
jsRecord["Algorithm"] = rr.Algorithm
|
||||
jsRecord["Error"] = int64(rr.Error)
|
||||
jsRecord["Fudge"] = int64(rr.Fudge)
|
||||
jsRecord["MACSize"] = int64(rr.MACSize)
|
||||
jsRecord["MAC"] = rr.MAC
|
||||
jsRecord["OrigId"] = int64(rr.OrigId)
|
||||
jsRecord["OtherData"] = rr.OtherData
|
||||
jsRecord["OtherLen"] = int64(rr.OtherLen)
|
||||
jsRecord["TimeSigned"] = int64(rr.TimeSigned)
|
||||
case *dns.IPSECKEY:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["GatewayAddr"] = rr.GatewayAddr.String()
|
||||
jsRecord["GatewayHost"] = rr.GatewayHost
|
||||
jsRecord["GatewayType"] = int64(rr.GatewayType)
|
||||
jsRecord["Precedence"] = int64(rr.Precedence)
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
case *dns.KEY:
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Protocol"] = int64(rr.Protocol)
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
case *dns.CDS:
|
||||
jsRecord["KeyTag"] = int64(rr.KeyTag)
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["DigestType"] = int64(rr.DigestType)
|
||||
jsRecord["Digest"] = rr.Digest
|
||||
case *dns.CDNSKEY:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Protocol"] = int64(rr.Protocol)
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
case *dns.NID:
|
||||
jsRecord["NodeID"] = rr.NodeID
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.L32:
|
||||
jsRecord["Locator32"] = rr.Locator32.String()
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.L64:
|
||||
jsRecord["Locator64"] = rr.Locator64
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.LP:
|
||||
jsRecord["Fqdn"] = rr.Fqdn
|
||||
jsRecord["Preference"] = int16(rr.Preference)
|
||||
case *dns.GPOS:
|
||||
jsRecord["Altitude"] = rr.Altitude
|
||||
jsRecord["Latitude"] = rr.Latitude
|
||||
jsRecord["Longitude"] = rr.Longitude
|
||||
case *dns.RP:
|
||||
jsRecord["Mbox"] = rr.Mbox
|
||||
jsRecord["Txt"] = rr.Txt
|
||||
case *dns.RKEY:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Protocol"] = int64(rr.Protocol)
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
case *dns.SMIMEA:
|
||||
jsRecord["Certificate"] = rr.Certificate
|
||||
jsRecord["MatchingType"] = int64(rr.MatchingType)
|
||||
jsRecord["Selector"] = int64(rr.Selector)
|
||||
jsRecord["Usage"] = int64(rr.Usage)
|
||||
case *dns.AMTRELAY:
|
||||
jsRecord["GatewayAddr"] = rr.GatewayAddr.String()
|
||||
jsRecord["GatewayHost"] = rr.GatewayHost
|
||||
jsRecord["GatewayType"] = int64(rr.GatewayType)
|
||||
jsRecord["Precedence"] = int64(rr.Precedence)
|
||||
case *dns.AVC:
|
||||
jsRecord["Txt"] = rr.Txt
|
||||
case *dns.URI:
|
||||
jsRecord["Priority"] = int64(rr.Priority)
|
||||
jsRecord["Weight"] = int64(rr.Weight)
|
||||
jsRecord["Target"] = rr.Target
|
||||
case *dns.EUI48:
|
||||
jsRecord["Address"] = rr.Address
|
||||
case *dns.EUI64:
|
||||
jsRecord["Address"] = rr.Address
|
||||
case *dns.GID:
|
||||
jsRecord["Gid"] = int64(rr.Gid)
|
||||
case *dns.UID:
|
||||
jsRecord["Uid"] = int64(rr.Uid)
|
||||
case *dns.UINFO:
|
||||
jsRecord["Uinfo"] = rr.Uinfo
|
||||
case *dns.SPF:
|
||||
jsRecord["Txt"] = rr.Txt
|
||||
case *dns.HTTPS:
|
||||
jsRecord["Priority"] = int64(rr.Priority)
|
||||
jsRecord["Target"] = rr.Target
|
||||
kvs := rr.Value
|
||||
var jsKvs []map[string]interface{}
|
||||
for _, kv := range kvs {
|
||||
jsKv, err := NewJSSVCBKeyValue(kv)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
jsKvs = append(jsKvs, jsKv)
|
||||
}
|
||||
jsRecord["Value"] = jsKvs
|
||||
case *dns.SVCB:
|
||||
jsRecord["Priority"] = int64(rr.Priority)
|
||||
jsRecord["Target"] = rr.Target
|
||||
kvs := rr.Value
|
||||
jsKvs := make([]map[string]interface{}, len(kvs))
|
||||
for i, kv := range kvs {
|
||||
jsKv, err := NewJSSVCBKeyValue(kv)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
jsKvs[i] = jsKv
|
||||
}
|
||||
jsRecord["Value"] = jsKvs
|
||||
case *dns.ZONEMD:
|
||||
jsRecord["Digest"] = rr.Digest
|
||||
jsRecord["Hash"] = int64(rr.Hash)
|
||||
jsRecord["Scheme"] = int64(rr.Scheme)
|
||||
jsRecord["Serial"] = int64(rr.Serial)
|
||||
case *dns.CSYNC:
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Serial"] = int64(rr.Serial)
|
||||
jsRecord["TypeBitMap"] = uint16ArrayToInt64Array(rr.TypeBitMap)
|
||||
case *dns.OPENPGPKEY:
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
case *dns.TALINK:
|
||||
jsRecord["NextName"] = rr.NextName
|
||||
jsRecord["PreviousName"] = rr.PreviousName
|
||||
case *dns.NINFO:
|
||||
jsRecord["ZSData"] = rr.ZSData
|
||||
case *dns.DHCID:
|
||||
jsRecord["Digest"] = rr.Digest
|
||||
case *dns.DNSKEY:
|
||||
jsRecord["Flags"] = int64(rr.Flags)
|
||||
jsRecord["Protocol"] = int64(rr.Protocol)
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
case *dns.HIP:
|
||||
jsRecord["Hit"] = rr.Hit
|
||||
jsRecord["HitLength"] = int64(rr.HitLength)
|
||||
jsRecord["PublicKey"] = rr.PublicKey
|
||||
jsRecord["PublicKeyAlgorithm"] = int64(rr.PublicKeyAlgorithm)
|
||||
jsRecord["PublicKeyLength"] = int64(rr.PublicKeyLength)
|
||||
jsRecord["RendezvousServers"] = rr.RendezvousServers
|
||||
case *dns.OPT:
|
||||
options := rr.Option
|
||||
jsOptions := make([]map[string]interface{}, len(options))
|
||||
for i, option := range options {
|
||||
jsOption, err := NewJSEDNS0(option)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
jsOptions[i] = jsOption
|
||||
}
|
||||
jsRecord["Option"] = jsOptions
|
||||
case *dns.NIMLOC:
|
||||
jsRecord["Locator"] = rr.Locator
|
||||
case *dns.EID:
|
||||
jsRecord["Endpoint"] = rr.Endpoint
|
||||
case *dns.NXT:
|
||||
jsRecord["NextDomain"] = rr.NextDomain
|
||||
jsRecord["TypeBitMap"] = uint16ArrayToInt64Array(rr.TypeBitMap)
|
||||
case *dns.PX:
|
||||
jsRecord["Mapx400"] = rr.Mapx400
|
||||
jsRecord["Map822"] = rr.Map822
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.SIG:
|
||||
jsRecord["Algorithm"] = int64(rr.Algorithm)
|
||||
jsRecord["Expiration"] = int64(rr.Expiration)
|
||||
jsRecord["Inception"] = int64(rr.Inception)
|
||||
jsRecord["KeyTag"] = int64(rr.KeyTag)
|
||||
jsRecord["Labels"] = int64(rr.Labels)
|
||||
jsRecord["OrigTtl"] = int64(rr.OrigTtl)
|
||||
jsRecord["Signature"] = rr.Signature
|
||||
jsRecord["SignerName"] = rr.SignerName
|
||||
jsRecord["TypeCovered"] = int64(rr.TypeCovered)
|
||||
case *dns.RT:
|
||||
jsRecord["Host"] = rr.Host
|
||||
jsRecord["Preference"] = int64(rr.Preference)
|
||||
case *dns.NSAPPTR:
|
||||
jsRecord["Ptr"] = rr.Ptr
|
||||
case *dns.X25:
|
||||
jsRecord["PSDNAddress"] = rr.PSDNAddress
|
||||
case *dns.RFC3597:
|
||||
jsRecord["Rdata"] = rr.Rdata
|
||||
// case *dns.ATMA:
|
||||
// case *dns.WKS:
|
||||
// case *dns.DOA:
|
||||
// case *dns.SINK:
|
||||
default:
|
||||
if header.Rrtype == dns.TypeNone {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("error creating JSResourceRecord: unknown type: %d", header.Rrtype)
|
||||
}
|
||||
|
||||
return jsRecord, nil
|
||||
}
|
||||
|
||||
func ToRR(jsRecord map[string]interface{}) (rr dns.RR, err error) {
|
||||
jsHeader := jsPropToMap(jsRecord, "Header")
|
||||
|
||||
header := dns.RR_Header{
|
||||
Class: jsPropToUint16(jsHeader, "Class"),
|
||||
Name: jsPropToString(jsHeader, "Name"),
|
||||
Rrtype: jsPropToUint16(jsHeader, "Rrtype"),
|
||||
Ttl: jsPropToUint32(jsHeader, "Ttl"),
|
||||
}
|
||||
|
||||
switch header.Rrtype {
|
||||
case dns.TypeNone:
|
||||
break
|
||||
case dns.TypeA:
|
||||
rr = &dns.A{
|
||||
Hdr: header,
|
||||
A: net.ParseIP(jsPropToString(jsRecord, "A")),
|
||||
}
|
||||
case dns.TypeAAAA:
|
||||
rr = &dns.AAAA{
|
||||
Hdr: header,
|
||||
AAAA: net.ParseIP(jsPropToString(jsRecord, "AAAA")),
|
||||
}
|
||||
case dns.TypeAPL:
|
||||
jsPrefixes := jsRecord["Prefixes"].([]map[string]interface{})
|
||||
prefixes := make([]dns.APLPrefix, len(jsPrefixes))
|
||||
for i, jsPrefix := range jsPrefixes {
|
||||
jsNetwork := jsPrefix["Network"].(string)
|
||||
_, network, err := net.ParseCIDR(jsNetwork)
|
||||
if err != nil {
|
||||
log.Error("error parsing CIDR: %s", jsNetwork)
|
||||
continue
|
||||
}
|
||||
prefixes[i] = dns.APLPrefix{
|
||||
Negation: jsPrefix["Negation"].(bool),
|
||||
Network: *network,
|
||||
}
|
||||
}
|
||||
rr = &dns.APL{
|
||||
Hdr: header,
|
||||
Prefixes: prefixes,
|
||||
}
|
||||
case dns.TypeCNAME:
|
||||
rr = &dns.CNAME{
|
||||
Hdr: header,
|
||||
Target: jsPropToString(jsRecord, "Target"),
|
||||
}
|
||||
case dns.TypeMB:
|
||||
rr = &dns.MB{
|
||||
Hdr: header,
|
||||
Mb: jsPropToString(jsRecord, "Mb"),
|
||||
}
|
||||
case dns.TypeMD:
|
||||
rr = &dns.MD{
|
||||
Hdr: header,
|
||||
Md: jsPropToString(jsRecord, "Md"),
|
||||
}
|
||||
case dns.TypeMF:
|
||||
rr = &dns.MF{
|
||||
Hdr: header,
|
||||
Mf: jsPropToString(jsRecord, "Mf"),
|
||||
}
|
||||
case dns.TypeMG:
|
||||
rr = &dns.MG{
|
||||
Hdr: header,
|
||||
Mg: jsPropToString(jsRecord, "Mg"),
|
||||
}
|
||||
case dns.TypeMR:
|
||||
rr = &dns.MR{
|
||||
Hdr: header,
|
||||
Mr: jsPropToString(jsRecord, "Mr"),
|
||||
}
|
||||
case dns.TypeMX:
|
||||
rr = &dns.MX{
|
||||
Hdr: header,
|
||||
Mx: jsPropToString(jsRecord, "Mx"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeNULL:
|
||||
rr = &dns.NULL{
|
||||
Hdr: header,
|
||||
Data: jsPropToString(jsRecord, "Data"),
|
||||
}
|
||||
case dns.TypeSOA:
|
||||
rr = &dns.SOA{
|
||||
Hdr: header,
|
||||
Expire: jsPropToUint32(jsRecord, "Expire"),
|
||||
Mbox: jsPropToString(jsRecord, "Mbox"),
|
||||
Minttl: jsPropToUint32(jsRecord, "Minttl"),
|
||||
Ns: jsPropToString(jsRecord, "Ns"),
|
||||
Refresh: jsPropToUint32(jsRecord, "Refresh"),
|
||||
Retry: jsPropToUint32(jsRecord, "Retry"),
|
||||
Serial: jsPropToUint32(jsRecord, "Serial"),
|
||||
}
|
||||
case dns.TypeTXT:
|
||||
rr = &dns.TXT{
|
||||
Hdr: header,
|
||||
Txt: jsPropToStringArray(jsRecord, "Txt"),
|
||||
}
|
||||
case dns.TypeSRV:
|
||||
rr = &dns.SRV{
|
||||
Hdr: header,
|
||||
Port: jsPropToUint16(jsRecord, "Port"),
|
||||
Priority: jsPropToUint16(jsRecord, "Priority"),
|
||||
Target: jsPropToString(jsRecord, "Target"),
|
||||
Weight: jsPropToUint16(jsRecord, "Weight"),
|
||||
}
|
||||
case dns.TypePTR:
|
||||
rr = &dns.PTR{
|
||||
Hdr: header,
|
||||
Ptr: jsPropToString(jsRecord, "Ptr"),
|
||||
}
|
||||
case dns.TypeNS:
|
||||
rr = &dns.NS{
|
||||
Hdr: header,
|
||||
Ns: jsPropToString(jsRecord, "Ns"),
|
||||
}
|
||||
case dns.TypeDNAME:
|
||||
rr = &dns.DNAME{
|
||||
Hdr: header,
|
||||
Target: jsPropToString(jsRecord, "Target"),
|
||||
}
|
||||
case dns.TypeAFSDB:
|
||||
rr = &dns.AFSDB{
|
||||
Hdr: header,
|
||||
Hostname: jsPropToString(jsRecord, "Hostname"),
|
||||
Subtype: jsPropToUint16(jsRecord, "Subtype"),
|
||||
}
|
||||
case dns.TypeCAA:
|
||||
rr = &dns.CAA{
|
||||
Hdr: header,
|
||||
Flag: jsPropToUint8(jsRecord, "Flag"),
|
||||
Tag: jsPropToString(jsRecord, "Tag"),
|
||||
Value: jsPropToString(jsRecord, "Value"),
|
||||
}
|
||||
case dns.TypeHINFO:
|
||||
rr = &dns.HINFO{
|
||||
Hdr: header,
|
||||
Cpu: jsPropToString(jsRecord, "Cpu"),
|
||||
Os: jsPropToString(jsRecord, "Os"),
|
||||
}
|
||||
case dns.TypeMINFO:
|
||||
rr = &dns.MINFO{
|
||||
Hdr: header,
|
||||
Email: jsPropToString(jsRecord, "Email"),
|
||||
Rmail: jsPropToString(jsRecord, "Rmail"),
|
||||
}
|
||||
case dns.TypeISDN:
|
||||
rr = &dns.ISDN{
|
||||
Hdr: header,
|
||||
Address: jsPropToString(jsRecord, "Address"),
|
||||
SubAddress: jsPropToString(jsRecord, "SubAddress"),
|
||||
}
|
||||
case dns.TypeKX:
|
||||
rr = &dns.KX{
|
||||
Hdr: header,
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
Exchanger: jsPropToString(jsRecord, "Exchanger"),
|
||||
}
|
||||
case dns.TypeLOC:
|
||||
rr = &dns.LOC{
|
||||
Hdr: header,
|
||||
Version: jsPropToUint8(jsRecord, "Version"),
|
||||
Size: jsPropToUint8(jsRecord, "Size"),
|
||||
HorizPre: jsPropToUint8(jsRecord, "HorizPre"),
|
||||
VertPre: jsPropToUint8(jsRecord, "VertPre"),
|
||||
Latitude: jsPropToUint32(jsRecord, "Latitude"),
|
||||
Longitude: jsPropToUint32(jsRecord, "Longitude"),
|
||||
Altitude: jsPropToUint32(jsRecord, "Altitude"),
|
||||
}
|
||||
case dns.TypeSSHFP:
|
||||
rr = &dns.SSHFP{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
FingerPrint: jsPropToString(jsRecord, "FingerPrint"),
|
||||
Type: jsPropToUint8(jsRecord, "Type"),
|
||||
}
|
||||
case dns.TypeTLSA:
|
||||
rr = &dns.TLSA{
|
||||
Hdr: header,
|
||||
Certificate: jsPropToString(jsRecord, "Certificate"),
|
||||
MatchingType: jsPropToUint8(jsRecord, "MatchingType"),
|
||||
Selector: jsPropToUint8(jsRecord, "Selector"),
|
||||
Usage: jsPropToUint8(jsRecord, "Usage"),
|
||||
}
|
||||
case dns.TypeCERT:
|
||||
rr = &dns.CERT{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Certificate: jsPropToString(jsRecord, "Certificate"),
|
||||
KeyTag: jsPropToUint16(jsRecord, "KeyTag"),
|
||||
Type: jsPropToUint16(jsRecord, "Type"),
|
||||
}
|
||||
case dns.TypeDS:
|
||||
rr = &dns.DS{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Digest: jsPropToString(jsRecord, "Digest"),
|
||||
DigestType: jsPropToUint8(jsRecord, "DigestType"),
|
||||
KeyTag: jsPropToUint16(jsRecord, "KeyTag"),
|
||||
}
|
||||
case dns.TypeNAPTR:
|
||||
rr = &dns.NAPTR{
|
||||
Hdr: header,
|
||||
Flags: jsPropToString(jsRecord, "Flags"),
|
||||
Order: jsPropToUint16(jsRecord, "Order"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
Regexp: jsPropToString(jsRecord, "Regexp"),
|
||||
Replacement: jsPropToString(jsRecord, "Replacement"),
|
||||
Service: jsPropToString(jsRecord, "Service"),
|
||||
}
|
||||
case dns.TypeRRSIG:
|
||||
rr = &dns.RRSIG{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Expiration: jsPropToUint32(jsRecord, "Expiration"),
|
||||
Inception: jsPropToUint32(jsRecord, "Inception"),
|
||||
KeyTag: jsPropToUint16(jsRecord, "KeyTag"),
|
||||
Labels: jsPropToUint8(jsRecord, "Labels"),
|
||||
OrigTtl: jsPropToUint32(jsRecord, "OrigTtl"),
|
||||
Signature: jsPropToString(jsRecord, "Signature"),
|
||||
SignerName: jsPropToString(jsRecord, "SignerName"),
|
||||
TypeCovered: jsPropToUint16(jsRecord, "TypeCovered"),
|
||||
}
|
||||
case dns.TypeNSEC:
|
||||
rr = &dns.NSEC{
|
||||
Hdr: header,
|
||||
NextDomain: jsPropToString(jsRecord, "NextDomain"),
|
||||
TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"),
|
||||
}
|
||||
case dns.TypeNSEC3:
|
||||
rr = &dns.NSEC3{
|
||||
Hdr: header,
|
||||
Flags: jsPropToUint8(jsRecord, "Flags"),
|
||||
Hash: jsPropToUint8(jsRecord, "Hash"),
|
||||
HashLength: jsPropToUint8(jsRecord, "HashLength"),
|
||||
Iterations: jsPropToUint16(jsRecord, "Iterations"),
|
||||
NextDomain: jsPropToString(jsRecord, "NextDomain"),
|
||||
Salt: jsPropToString(jsRecord, "Salt"),
|
||||
SaltLength: jsPropToUint8(jsRecord, "SaltLength"),
|
||||
TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"),
|
||||
}
|
||||
case dns.TypeNSEC3PARAM:
|
||||
rr = &dns.NSEC3PARAM{
|
||||
Hdr: header,
|
||||
Flags: jsPropToUint8(jsRecord, "Flags"),
|
||||
Hash: jsPropToUint8(jsRecord, "Hash"),
|
||||
Iterations: jsPropToUint16(jsRecord, "Iterations"),
|
||||
Salt: jsPropToString(jsRecord, "Salt"),
|
||||
SaltLength: jsPropToUint8(jsRecord, "SaltLength"),
|
||||
}
|
||||
case dns.TypeTKEY:
|
||||
rr = &dns.TKEY{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToString(jsRecord, "Algorithm"),
|
||||
Error: jsPropToUint16(jsRecord, "Error"),
|
||||
Expiration: jsPropToUint32(jsRecord, "Expiration"),
|
||||
Inception: jsPropToUint32(jsRecord, "Inception"),
|
||||
Key: jsPropToString(jsRecord, "Key"),
|
||||
KeySize: jsPropToUint16(jsRecord, "KeySize"),
|
||||
Mode: jsPropToUint16(jsRecord, "Mode"),
|
||||
OtherData: jsPropToString(jsRecord, "OtherData"),
|
||||
OtherLen: jsPropToUint16(jsRecord, "OtherLen"),
|
||||
}
|
||||
case dns.TypeTSIG:
|
||||
rr = &dns.TSIG{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToString(jsRecord, "Algorithm"),
|
||||
Error: jsPropToUint16(jsRecord, "Error"),
|
||||
Fudge: jsPropToUint16(jsRecord, "Fudge"),
|
||||
MACSize: jsPropToUint16(jsRecord, "MACSize"),
|
||||
MAC: jsPropToString(jsRecord, "MAC"),
|
||||
OrigId: jsPropToUint16(jsRecord, "OrigId"),
|
||||
OtherData: jsPropToString(jsRecord, "OtherData"),
|
||||
OtherLen: jsPropToUint16(jsRecord, "OtherLen"),
|
||||
TimeSigned: jsPropToUint64(jsRecord, "TimeSigned"),
|
||||
}
|
||||
case dns.TypeIPSECKEY:
|
||||
rr = &dns.IPSECKEY{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
GatewayAddr: net.IP(jsPropToString(jsRecord, "GatewayAddr")),
|
||||
GatewayHost: jsPropToString(jsRecord, "GatewayHost"),
|
||||
GatewayType: jsPropToUint8(jsRecord, "GatewayType"),
|
||||
Precedence: jsPropToUint8(jsRecord, "Precedence"),
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
}
|
||||
case dns.TypeKEY:
|
||||
rr = &dns.KEY{
|
||||
DNSKEY: dns.DNSKEY{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Flags: jsPropToUint16(jsRecord, "Flags"),
|
||||
Protocol: jsPropToUint8(jsRecord, "Protocol"),
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
},
|
||||
}
|
||||
case dns.TypeCDS:
|
||||
rr = &dns.CDS{
|
||||
DS: dns.DS{
|
||||
Hdr: header,
|
||||
KeyTag: jsPropToUint16(jsRecord, "KeyTag"),
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
DigestType: jsPropToUint8(jsRecord, "DigestType"),
|
||||
Digest: jsPropToString(jsRecord, "Digest"),
|
||||
},
|
||||
}
|
||||
case dns.TypeCDNSKEY:
|
||||
rr = &dns.CDNSKEY{
|
||||
DNSKEY: dns.DNSKEY{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Flags: jsPropToUint16(jsRecord, "Flags"),
|
||||
Protocol: jsPropToUint8(jsRecord, "Protocol"),
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
},
|
||||
}
|
||||
case dns.TypeNID:
|
||||
rr = &dns.NID{
|
||||
Hdr: header,
|
||||
NodeID: jsPropToUint64(jsRecord, "NodeID"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeL32:
|
||||
rr = &dns.L32{
|
||||
Hdr: header,
|
||||
Locator32: net.IP(jsPropToString(jsRecord, "Locator32")),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeL64:
|
||||
rr = &dns.L64{
|
||||
Hdr: header,
|
||||
Locator64: jsPropToUint64(jsRecord, "Locator64"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeLP:
|
||||
rr = &dns.LP{
|
||||
Hdr: header,
|
||||
Fqdn: jsPropToString(jsRecord, "Fqdn"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeGPOS:
|
||||
rr = &dns.GPOS{
|
||||
Hdr: header,
|
||||
Altitude: jsPropToString(jsRecord, "Altitude"),
|
||||
Latitude: jsPropToString(jsRecord, "Latitude"),
|
||||
Longitude: jsPropToString(jsRecord, "Longitude"),
|
||||
}
|
||||
case dns.TypeRP:
|
||||
rr = &dns.RP{
|
||||
Hdr: header,
|
||||
Mbox: jsPropToString(jsRecord, "Mbox"),
|
||||
Txt: jsPropToString(jsRecord, "Txt"),
|
||||
}
|
||||
case dns.TypeRKEY:
|
||||
rr = &dns.RKEY{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Flags: jsPropToUint16(jsRecord, "Flags"),
|
||||
Protocol: jsPropToUint8(jsRecord, "Protocol"),
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
}
|
||||
case dns.TypeSMIMEA:
|
||||
rr = &dns.SMIMEA{
|
||||
Hdr: header,
|
||||
Certificate: jsPropToString(jsRecord, "Certificate"),
|
||||
MatchingType: jsPropToUint8(jsRecord, "MatchingType"),
|
||||
Selector: jsPropToUint8(jsRecord, "Selector"),
|
||||
Usage: jsPropToUint8(jsRecord, "Usage"),
|
||||
}
|
||||
case dns.TypeAMTRELAY:
|
||||
rr = &dns.AMTRELAY{
|
||||
Hdr: header,
|
||||
GatewayAddr: net.IP(jsPropToString(jsRecord, "GatewayAddr")),
|
||||
GatewayHost: jsPropToString(jsRecord, "GatewayHost"),
|
||||
GatewayType: jsPropToUint8(jsRecord, "GatewayType"),
|
||||
Precedence: jsPropToUint8(jsRecord, "Precedence"),
|
||||
}
|
||||
case dns.TypeAVC:
|
||||
rr = &dns.AVC{
|
||||
Hdr: header,
|
||||
Txt: jsPropToStringArray(jsRecord, "Txt"),
|
||||
}
|
||||
case dns.TypeURI:
|
||||
rr = &dns.URI{
|
||||
Hdr: header,
|
||||
Priority: jsPropToUint16(jsRecord, "Priority"),
|
||||
Weight: jsPropToUint16(jsRecord, "Weight"),
|
||||
Target: jsPropToString(jsRecord, "Target"),
|
||||
}
|
||||
case dns.TypeEUI48:
|
||||
rr = &dns.EUI48{
|
||||
Hdr: header,
|
||||
Address: jsPropToUint64(jsRecord, "Address"),
|
||||
}
|
||||
case dns.TypeEUI64:
|
||||
rr = &dns.EUI64{
|
||||
Hdr: header,
|
||||
Address: jsPropToUint64(jsRecord, "Address"),
|
||||
}
|
||||
case dns.TypeGID:
|
||||
rr = &dns.GID{
|
||||
Hdr: header,
|
||||
Gid: jsPropToUint32(jsRecord, "Gid"),
|
||||
}
|
||||
case dns.TypeUID:
|
||||
rr = &dns.UID{
|
||||
Hdr: header,
|
||||
Uid: jsPropToUint32(jsRecord, "Uid"),
|
||||
}
|
||||
case dns.TypeUINFO:
|
||||
rr = &dns.UINFO{
|
||||
Hdr: header,
|
||||
Uinfo: jsPropToString(jsRecord, "Uinfo"),
|
||||
}
|
||||
case dns.TypeSPF:
|
||||
rr = &dns.SPF{
|
||||
Hdr: header,
|
||||
Txt: jsPropToStringArray(jsRecord, "Txt"),
|
||||
}
|
||||
case dns.TypeHTTPS:
|
||||
jsKvs := jsPropToMapArray(jsRecord, "Value")
|
||||
var kvs []dns.SVCBKeyValue
|
||||
for _, jsKv := range jsKvs {
|
||||
kv, err := ToSVCBKeyValue(jsKv)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
kvs = append(kvs, kv)
|
||||
}
|
||||
rr = &dns.HTTPS{
|
||||
SVCB: dns.SVCB{
|
||||
Hdr: header,
|
||||
Priority: jsPropToUint16(jsRecord, "Priority"),
|
||||
Target: jsPropToString(jsRecord, "Target"),
|
||||
Value: kvs,
|
||||
},
|
||||
}
|
||||
case dns.TypeSVCB:
|
||||
jsKvs := jsPropToMapArray(jsRecord, "Value")
|
||||
var kvs []dns.SVCBKeyValue
|
||||
for _, jsKv := range jsKvs {
|
||||
kv, err := ToSVCBKeyValue(jsKv)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
kvs = append(kvs, kv)
|
||||
}
|
||||
rr = &dns.SVCB{
|
||||
Hdr: header,
|
||||
Priority: jsPropToUint16(jsRecord, "Priority"),
|
||||
Target: jsPropToString(jsRecord, "Target"),
|
||||
Value: kvs,
|
||||
}
|
||||
case dns.TypeZONEMD:
|
||||
rr = &dns.ZONEMD{
|
||||
Hdr: header,
|
||||
Digest: jsPropToString(jsRecord, "Digest"),
|
||||
Hash: jsPropToUint8(jsRecord, "Hash"),
|
||||
Scheme: jsPropToUint8(jsRecord, "Scheme"),
|
||||
Serial: jsPropToUint32(jsRecord, "Serial"),
|
||||
}
|
||||
case dns.TypeCSYNC:
|
||||
rr = &dns.CSYNC{
|
||||
Hdr: header,
|
||||
Flags: jsPropToUint16(jsRecord, "Flags"),
|
||||
Serial: jsPropToUint32(jsRecord, "Serial"),
|
||||
TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"),
|
||||
}
|
||||
case dns.TypeOPENPGPKEY:
|
||||
rr = &dns.OPENPGPKEY{
|
||||
Hdr: header,
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
}
|
||||
case dns.TypeTALINK:
|
||||
rr = &dns.TALINK{
|
||||
Hdr: header,
|
||||
NextName: jsPropToString(jsRecord, "NextName"),
|
||||
PreviousName: jsPropToString(jsRecord, "PreviousName"),
|
||||
}
|
||||
case dns.TypeNINFO:
|
||||
rr = &dns.NINFO{
|
||||
Hdr: header,
|
||||
ZSData: jsPropToStringArray(jsRecord, "ZSData"),
|
||||
}
|
||||
case dns.TypeDHCID:
|
||||
rr = &dns.DHCID{
|
||||
Hdr: header,
|
||||
Digest: jsPropToString(jsRecord, "Digest"),
|
||||
}
|
||||
case dns.TypeDNSKEY:
|
||||
rr = &dns.DNSKEY{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Flags: jsPropToUint16(jsRecord, "Flags"),
|
||||
Protocol: jsPropToUint8(jsRecord, "Protocol"),
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
}
|
||||
case dns.TypeHIP:
|
||||
rr = &dns.HIP{
|
||||
Hdr: header,
|
||||
Hit: jsPropToString(jsRecord, "Hit"),
|
||||
HitLength: jsPropToUint8(jsRecord, "HitLength"),
|
||||
PublicKey: jsPropToString(jsRecord, "PublicKey"),
|
||||
PublicKeyAlgorithm: jsPropToUint8(jsRecord, "PublicKeyAlgorithm"),
|
||||
PublicKeyLength: jsPropToUint16(jsRecord, "PublicKeyLength"),
|
||||
RendezvousServers: jsPropToStringArray(jsRecord, "RendezvousServers"),
|
||||
}
|
||||
case dns.TypeOPT:
|
||||
jsOptions := jsPropToMapArray(jsRecord, "Option")
|
||||
var options []dns.EDNS0
|
||||
for _, jsOption := range jsOptions {
|
||||
option, err := ToEDNS0(jsOption)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
options = append(options, option)
|
||||
}
|
||||
rr = &dns.OPT{
|
||||
Hdr: header,
|
||||
Option: options,
|
||||
}
|
||||
case dns.TypeNIMLOC:
|
||||
rr = &dns.NIMLOC{
|
||||
Hdr: header,
|
||||
Locator: jsPropToString(jsRecord, "Locator"),
|
||||
}
|
||||
case dns.TypeEID:
|
||||
rr = &dns.EID{
|
||||
Hdr: header,
|
||||
Endpoint: jsPropToString(jsRecord, "Endpoint"),
|
||||
}
|
||||
case dns.TypeNXT:
|
||||
rr = &dns.NXT{
|
||||
NSEC: dns.NSEC{
|
||||
Hdr: header,
|
||||
NextDomain: jsPropToString(jsRecord, "NextDomain"),
|
||||
TypeBitMap: jsPropToUint16Array(jsRecord, "TypeBitMap"),
|
||||
},
|
||||
}
|
||||
case dns.TypePX:
|
||||
rr = &dns.PX{
|
||||
Hdr: header,
|
||||
Mapx400: jsPropToString(jsRecord, "Mapx400"),
|
||||
Map822: jsPropToString(jsRecord, "Map822"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeSIG:
|
||||
rr = &dns.SIG{
|
||||
RRSIG: dns.RRSIG{
|
||||
Hdr: header,
|
||||
Algorithm: jsPropToUint8(jsRecord, "Algorithm"),
|
||||
Expiration: jsPropToUint32(jsRecord, "Expiration"),
|
||||
Inception: jsPropToUint32(jsRecord, "Inception"),
|
||||
KeyTag: jsPropToUint16(jsRecord, "KeyTag"),
|
||||
Labels: jsPropToUint8(jsRecord, "Labels"),
|
||||
OrigTtl: jsPropToUint32(jsRecord, "OrigTtl"),
|
||||
Signature: jsPropToString(jsRecord, "Signature"),
|
||||
SignerName: jsPropToString(jsRecord, "SignerName"),
|
||||
TypeCovered: jsPropToUint16(jsRecord, "TypeCovered"),
|
||||
},
|
||||
}
|
||||
case dns.TypeRT:
|
||||
rr = &dns.RT{
|
||||
Hdr: header,
|
||||
Host: jsPropToString(jsRecord, "Host"),
|
||||
Preference: jsPropToUint16(jsRecord, "Preference"),
|
||||
}
|
||||
case dns.TypeNSAPPTR:
|
||||
rr = &dns.NSAPPTR{
|
||||
Hdr: header,
|
||||
Ptr: jsPropToString(jsRecord, "Ptr"),
|
||||
}
|
||||
case dns.TypeX25:
|
||||
rr = &dns.X25{
|
||||
Hdr: header,
|
||||
PSDNAddress: jsPropToString(jsRecord, "PSDNAddress"),
|
||||
}
|
||||
// case dns.TypeATMA:
|
||||
// case dns.TypeWKS:
|
||||
// case dns.TypeDOA:
|
||||
// case dns.TypeSINK:
|
||||
default:
|
||||
if rdata, ok := jsRecord["Rdata"].(string); ok {
|
||||
rr = &dns.RFC3597{
|
||||
Hdr: header,
|
||||
Rdata: rdata,
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("error converting to dns.RR: unknown type: %d", header.Rrtype)
|
||||
}
|
||||
}
|
||||
|
||||
return rr, nil
|
||||
}
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func NewJSEDNS0(e dns.EDNS0) (jsEDNS0 map[string]interface{}, err error) {
|
||||
option := e.Option()
|
||||
|
||||
jsEDNS0 = map[string]interface{}{
|
||||
"Option": option,
|
||||
}
|
||||
|
||||
var jsVal map[string]interface{}
|
||||
|
||||
switch opt := e.(type) {
|
||||
case *dns.EDNS0_LLQ:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Error": opt.Error,
|
||||
"Id": opt.Id,
|
||||
"LeaseLife": opt.LeaseLife,
|
||||
"Opcode": opt.Opcode,
|
||||
"Version": opt.Version,
|
||||
}
|
||||
case *dns.EDNS0_UL:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Lease": opt.Lease,
|
||||
"KeyLease": opt.KeyLease,
|
||||
}
|
||||
case *dns.EDNS0_NSID:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Nsid": opt.Nsid,
|
||||
}
|
||||
case *dns.EDNS0_ESU:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Uri": opt.Uri,
|
||||
}
|
||||
case *dns.EDNS0_DAU:
|
||||
jsVal = map[string]interface{}{
|
||||
"AlgCode": opt.AlgCode,
|
||||
"Code": opt.Code,
|
||||
}
|
||||
case *dns.EDNS0_DHU:
|
||||
jsVal = map[string]interface{}{
|
||||
"AlgCode": opt.AlgCode,
|
||||
"Code": opt.Code,
|
||||
}
|
||||
case *dns.EDNS0_N3U:
|
||||
jsVal = map[string]interface{}{
|
||||
"AlgCode": opt.AlgCode,
|
||||
"Code": opt.Code,
|
||||
}
|
||||
case *dns.EDNS0_SUBNET:
|
||||
jsVal = map[string]interface{}{
|
||||
"Address": opt.Address.String(),
|
||||
"Code": opt.Code,
|
||||
"Family": opt.Family,
|
||||
"SourceNetmask": opt.SourceNetmask,
|
||||
"SourceScope": opt.SourceScope,
|
||||
}
|
||||
case *dns.EDNS0_EXPIRE:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Empty": opt.Empty,
|
||||
"Expire": opt.Expire,
|
||||
}
|
||||
case *dns.EDNS0_COOKIE:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Cookie": opt.Cookie,
|
||||
}
|
||||
case *dns.EDNS0_TCP_KEEPALIVE:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Length": opt.Length,
|
||||
"Timeout": opt.Timeout,
|
||||
}
|
||||
case *dns.EDNS0_PADDING:
|
||||
jsVal = map[string]interface{}{
|
||||
"Padding": string(opt.Padding),
|
||||
}
|
||||
case *dns.EDNS0_EDE:
|
||||
jsVal = map[string]interface{}{
|
||||
"ExtraText": opt.ExtraText,
|
||||
"InfoCode": opt.InfoCode,
|
||||
}
|
||||
case *dns.EDNS0_LOCAL:
|
||||
jsVal = map[string]interface{}{
|
||||
"Code": opt.Code,
|
||||
"Data": string(opt.Data),
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported EDNS0 option: %d", option)
|
||||
}
|
||||
|
||||
jsEDNS0["Value"] = jsVal
|
||||
|
||||
return jsEDNS0, nil
|
||||
}
|
||||
|
||||
func ToEDNS0(jsEDNS0 map[string]interface{}) (e dns.EDNS0, err error) {
|
||||
option := jsPropToUint16(jsEDNS0, "Option")
|
||||
|
||||
jsVal := jsPropToMap(jsEDNS0, "Value")
|
||||
|
||||
switch option {
|
||||
case dns.EDNS0LLQ:
|
||||
e = &dns.EDNS0_LLQ{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Error: jsPropToUint16(jsVal, "Error"),
|
||||
Id: jsPropToUint64(jsVal, "Id"),
|
||||
LeaseLife: jsPropToUint32(jsVal, "LeaseLife"),
|
||||
Opcode: jsPropToUint16(jsVal, "Opcode"),
|
||||
Version: jsPropToUint16(jsVal, "Version"),
|
||||
}
|
||||
case dns.EDNS0UL:
|
||||
e = &dns.EDNS0_UL{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Lease: jsPropToUint32(jsVal, "Lease"),
|
||||
KeyLease: jsPropToUint32(jsVal, "KeyLease"),
|
||||
}
|
||||
case dns.EDNS0NSID:
|
||||
e = &dns.EDNS0_NSID{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Nsid: jsPropToString(jsVal, "Nsid"),
|
||||
}
|
||||
case dns.EDNS0ESU:
|
||||
e = &dns.EDNS0_ESU{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Uri: jsPropToString(jsVal, "Uri"),
|
||||
}
|
||||
case dns.EDNS0DAU:
|
||||
e = &dns.EDNS0_DAU{
|
||||
AlgCode: jsPropToUint8Array(jsVal, "AlgCode"),
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
}
|
||||
case dns.EDNS0DHU:
|
||||
e = &dns.EDNS0_DHU{
|
||||
AlgCode: jsPropToUint8Array(jsVal, "AlgCode"),
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
}
|
||||
case dns.EDNS0N3U:
|
||||
e = &dns.EDNS0_N3U{
|
||||
AlgCode: jsPropToUint8Array(jsVal, "AlgCode"),
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
}
|
||||
case dns.EDNS0SUBNET:
|
||||
e = &dns.EDNS0_SUBNET{
|
||||
Address: net.ParseIP(jsPropToString(jsVal, "Address")),
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Family: jsPropToUint16(jsVal, "Family"),
|
||||
SourceNetmask: jsPropToUint8(jsVal, "SourceNetmask"),
|
||||
SourceScope: jsPropToUint8(jsVal, "SourceScope"),
|
||||
}
|
||||
case dns.EDNS0EXPIRE:
|
||||
if empty, ok := jsVal["Empty"].(bool); !ok {
|
||||
log.Error("invalid or missing EDNS0_EXPIRE.Empty bool value, skipping field.")
|
||||
e = &dns.EDNS0_EXPIRE{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Expire: jsPropToUint32(jsVal, "Expire"),
|
||||
}
|
||||
} else {
|
||||
e = &dns.EDNS0_EXPIRE{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Expire: jsPropToUint32(jsVal, "Expire"),
|
||||
Empty: empty,
|
||||
}
|
||||
}
|
||||
case dns.EDNS0COOKIE:
|
||||
e = &dns.EDNS0_COOKIE{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Cookie: jsPropToString(jsVal, "Cookie"),
|
||||
}
|
||||
case dns.EDNS0TCPKEEPALIVE:
|
||||
e = &dns.EDNS0_TCP_KEEPALIVE{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Length: jsPropToUint16(jsVal, "Length"),
|
||||
Timeout: jsPropToUint16(jsVal, "Timeout"),
|
||||
}
|
||||
case dns.EDNS0PADDING:
|
||||
e = &dns.EDNS0_PADDING{
|
||||
Padding: []byte(jsPropToString(jsVal, "Padding")),
|
||||
}
|
||||
case dns.EDNS0EDE:
|
||||
e = &dns.EDNS0_EDE{
|
||||
ExtraText: jsPropToString(jsVal, "ExtraText"),
|
||||
InfoCode: jsPropToUint16(jsVal, "InfoCode"),
|
||||
}
|
||||
case dns.EDNS0LOCALSTART, dns.EDNS0LOCALEND, 0x8000:
|
||||
// _DO = 0x8000
|
||||
e = &dns.EDNS0_LOCAL{
|
||||
Code: jsPropToUint16(jsVal, "Code"),
|
||||
Data: []byte(jsPropToString(jsVal, "Data")),
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported EDNS0 option: %d", option)
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func NewJSSVCBKeyValue(kv dns.SVCBKeyValue) (map[string]interface{}, error) {
|
||||
key := kv.Key()
|
||||
|
||||
jsKv := map[string]interface{}{
|
||||
"Key": uint16(key),
|
||||
}
|
||||
|
||||
switch v := kv.(type) {
|
||||
case *dns.SVCBAlpn:
|
||||
jsKv["Alpn"] = v.Alpn
|
||||
case *dns.SVCBNoDefaultAlpn:
|
||||
break
|
||||
case *dns.SVCBECHConfig:
|
||||
jsKv["ECH"] = string(v.ECH)
|
||||
case *dns.SVCBPort:
|
||||
jsKv["Port"] = v.Port
|
||||
case *dns.SVCBIPv4Hint:
|
||||
ips := v.Hint
|
||||
jsIps := make([]string, len(ips))
|
||||
for i, ip := range ips {
|
||||
jsIps[i] = ip.String()
|
||||
}
|
||||
jsKv["Hint"] = jsIps
|
||||
case *dns.SVCBIPv6Hint:
|
||||
ips := v.Hint
|
||||
jsIps := make([]string, len(ips))
|
||||
for i, ip := range ips {
|
||||
jsIps[i] = ip.String()
|
||||
}
|
||||
jsKv["Hint"] = jsIps
|
||||
case *dns.SVCBDoHPath:
|
||||
jsKv["Template"] = v.Template
|
||||
case *dns.SVCBOhttp:
|
||||
break
|
||||
case *dns.SVCBMandatory:
|
||||
keys := v.Code
|
||||
jsKeys := make([]uint16, len(keys))
|
||||
for i, _key := range keys {
|
||||
jsKeys[i] = uint16(_key)
|
||||
}
|
||||
jsKv["Code"] = jsKeys
|
||||
default:
|
||||
return nil, fmt.Errorf("error creating JSSVCBKeyValue: unknown key: %d", key)
|
||||
}
|
||||
|
||||
return jsKv, nil
|
||||
}
|
||||
|
||||
func ToSVCBKeyValue(jsKv map[string]interface{}) (dns.SVCBKeyValue, error) {
|
||||
var kv dns.SVCBKeyValue
|
||||
|
||||
key := dns.SVCBKey(jsPropToUint16(jsKv, "Key"))
|
||||
|
||||
switch key {
|
||||
case dns.SVCB_ALPN:
|
||||
kv = &dns.SVCBAlpn{
|
||||
Alpn: jsPropToStringArray(jsKv, "Value"),
|
||||
}
|
||||
case dns.SVCB_NO_DEFAULT_ALPN:
|
||||
kv = &dns.SVCBNoDefaultAlpn{}
|
||||
case dns.SVCB_ECHCONFIG:
|
||||
kv = &dns.SVCBECHConfig{
|
||||
ECH: []byte(jsPropToString(jsKv, "Value")),
|
||||
}
|
||||
case dns.SVCB_PORT:
|
||||
kv = &dns.SVCBPort{
|
||||
Port: jsPropToUint16(jsKv, "Value"),
|
||||
}
|
||||
case dns.SVCB_IPV4HINT:
|
||||
jsIps := jsPropToStringArray(jsKv, "Value")
|
||||
var ips []net.IP
|
||||
for _, jsIp := range jsIps {
|
||||
ip := net.ParseIP(jsIp)
|
||||
if ip == nil {
|
||||
log.Error("error converting to SVCBKeyValue: invalid IPv4Hint IP: %s", jsIp)
|
||||
continue
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
kv = &dns.SVCBIPv4Hint{
|
||||
Hint: ips,
|
||||
}
|
||||
case dns.SVCB_IPV6HINT:
|
||||
jsIps := jsPropToStringArray(jsKv, "Value")
|
||||
var ips []net.IP
|
||||
for _, jsIp := range jsIps {
|
||||
ip := net.ParseIP(jsIp)
|
||||
if ip == nil {
|
||||
log.Error("error converting to SVCBKeyValue: invalid IPv6Hint IP: %s", jsIp)
|
||||
continue
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
kv = &dns.SVCBIPv6Hint{
|
||||
Hint: ips,
|
||||
}
|
||||
case dns.SVCB_DOHPATH:
|
||||
kv = &dns.SVCBDoHPath{
|
||||
Template: jsPropToString(jsKv, "Value"),
|
||||
}
|
||||
case dns.SVCB_OHTTP:
|
||||
kv = &dns.SVCBOhttp{}
|
||||
case dns.SVCB_MANDATORY:
|
||||
v := jsPropToUint16Array(jsKv, "Value")
|
||||
keys := make([]dns.SVCBKey, len(v))
|
||||
for i, jsKey := range v {
|
||||
keys[i] = dns.SVCBKey(jsKey)
|
||||
}
|
||||
kv = &dns.SVCBMandatory{
|
||||
Code: keys,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("error converting to dns.SVCBKeyValue: unknown key: %d", key)
|
||||
}
|
||||
|
||||
return kv, nil
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
package dns_proxy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/evilsocket/islazy/plugin"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
type DnsProxyScript struct {
|
||||
*plugin.Plugin
|
||||
|
||||
doOnRequest bool
|
||||
doOnResponse bool
|
||||
doOnCommand bool
|
||||
}
|
||||
|
||||
func LoadDnsProxyScript(path string, sess *session.Session) (err error, s *DnsProxyScript) {
|
||||
log.Debug("loading proxy script %s ...", path)
|
||||
|
||||
plug, err := plugin.Load(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// define session pointer
|
||||
if err = plug.Set("env", sess.Env.Data); err != nil {
|
||||
log.Error("Error while defining environment: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// define addSessionEvent function
|
||||
err = plug.Set("addSessionEvent", func(call otto.FunctionCall) otto.Value {
|
||||
if len(call.ArgumentList) < 2 {
|
||||
log.Error("Failed to execute 'addSessionEvent' in DNS proxy: 2 arguments required, but only %d given.", len(call.ArgumentList))
|
||||
return otto.FalseValue()
|
||||
}
|
||||
ottoTag := call.Argument(0)
|
||||
if !ottoTag.IsString() {
|
||||
log.Error("Failed to execute 'addSessionEvent' in DNS proxy: first argument must be a string.")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
tag := strings.TrimSpace(ottoTag.String())
|
||||
if tag == "" {
|
||||
log.Error("Failed to execute 'addSessionEvent' in DNS proxy: tag cannot be empty.")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
data := call.Argument(1)
|
||||
sess.Events.Add(tag, data)
|
||||
return otto.TrueValue()
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Error while defining addSessionEvent function: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// run onLoad if defined
|
||||
if plug.HasFunc("onLoad") {
|
||||
if _, err = plug.Call("onLoad"); err != nil {
|
||||
log.Error("Error while executing onLoad callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s = &DnsProxyScript{
|
||||
Plugin: plug,
|
||||
doOnRequest: plug.HasFunc("onRequest"),
|
||||
doOnResponse: plug.HasFunc("onResponse"),
|
||||
doOnCommand: plug.HasFunc("onCommand"),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DnsProxyScript) OnRequest(req *dns.Msg, clientIP string) (jsreq, jsres *JSQuery) {
|
||||
if s.doOnRequest {
|
||||
jsreq := NewJSQuery(req, clientIP)
|
||||
jsres := NewJSQuery(req, clientIP)
|
||||
|
||||
if _, err := s.Call("onRequest", jsreq, jsres); err != nil {
|
||||
log.Error("%s", err)
|
||||
return nil, nil
|
||||
} else if jsreq.CheckIfModifiedAndUpdateHash() {
|
||||
return jsreq, nil
|
||||
} else if jsres.CheckIfModifiedAndUpdateHash() {
|
||||
return nil, jsres
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *DnsProxyScript) OnResponse(req, res *dns.Msg, clientIP string) (jsreq, jsres *JSQuery) {
|
||||
if s.doOnResponse {
|
||||
jsreq := NewJSQuery(req, clientIP)
|
||||
jsres := NewJSQuery(res, clientIP)
|
||||
|
||||
if _, err := s.Call("onResponse", jsreq, jsres); err != nil {
|
||||
log.Error("%s", err)
|
||||
return nil, nil
|
||||
} else if jsres.CheckIfModifiedAndUpdateHash() {
|
||||
return nil, jsres
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *DnsProxyScript) OnCommand(cmd string) bool {
|
||||
if s.doOnCommand {
|
||||
if ret, err := s.Call("onCommand", cmd); err != nil {
|
||||
log.Error("Error while executing onCommand callback: %+v", err)
|
||||
return false
|
||||
} else if v, ok := ret.(bool); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -134,10 +134,8 @@ func (mod *EventsStream) Render(output io.Writer, e session.Event) {
|
|||
mod.viewUpdateEvent(output, e)
|
||||
} else if e.Tag == "gateway.change" {
|
||||
mod.viewGatewayEvent(output, e)
|
||||
} else if strings.HasPrefix(e.Tag, "zeroconf.") {
|
||||
mod.viewZeroConfEvent(output, e)
|
||||
} else if !strings.HasPrefix(e.Tag, "tick") && e.Tag != "session.started" && e.Tag != "session.stopped" {
|
||||
fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e.Data)
|
||||
} else if e.Tag != "tick" && e.Tag != "session.started" && e.Tag != "session.stopped" {
|
||||
fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package events_stream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/modules/zerogod"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/evilsocket/islazy/ops"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
)
|
||||
|
||||
func (mod *EventsStream) viewZeroConfEvent(output io.Writer, e session.Event) {
|
||||
if e.Tag == "zeroconf.service" {
|
||||
event := e.Data.(zerogod.ServiceDiscoveryEvent)
|
||||
fmt.Fprintf(output, "[%s] [%s] service %s detected for %s (%s):%d with %d records\n",
|
||||
e.Time.Format(mod.timeFormat),
|
||||
tui.Green(e.Tag),
|
||||
tui.Bold(event.Service.ServiceInstanceName()),
|
||||
event.Service.AddrIPv4,
|
||||
tui.Dim(event.Service.HostName),
|
||||
event.Service.Port,
|
||||
len(event.Service.Text),
|
||||
)
|
||||
} else if e.Tag == "zeroconf.browsing" {
|
||||
event := e.Data.(zerogod.BrowsingEvent)
|
||||
source := event.Source
|
||||
if event.Endpoint != nil {
|
||||
source = event.Endpoint.ShortString()
|
||||
}
|
||||
|
||||
services := make([]string, 0)
|
||||
for _, q := range event.Services {
|
||||
services = append(services, tui.Yellow(q))
|
||||
}
|
||||
|
||||
instPart := ""
|
||||
if len(event.Instances) > 0 {
|
||||
instances := make([]string, 0)
|
||||
for _, q := range event.Instances {
|
||||
instances = append(instances, tui.Green(q))
|
||||
}
|
||||
instPart = fmt.Sprintf(" and instances %s", strings.Join(instances, ", "))
|
||||
}
|
||||
|
||||
textPart := ""
|
||||
if len(event.Text) > 0 {
|
||||
textPart = fmt.Sprintf("\n text records: %s\n", strings.Join(event.Text, ", "))
|
||||
}
|
||||
|
||||
fmt.Fprintf(output, "[%s] [%s] %s is browsing (%s) for services %s%s\n%s",
|
||||
e.Time.Format(mod.timeFormat),
|
||||
tui.Green(e.Tag),
|
||||
source,
|
||||
ops.Ternary(event.Query.QR, "RESPONSE", "QUERY"),
|
||||
strings.Join(services, ", "),
|
||||
instPart,
|
||||
textPart,
|
||||
)
|
||||
|
||||
} else {
|
||||
fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,8 +27,6 @@ import (
|
|||
"github.com/evilsocket/islazy/log"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -434,14 +432,6 @@ func (p *HTTPProxy) Start() {
|
|||
}
|
||||
|
||||
func (p *HTTPProxy) Stop() error {
|
||||
if p.Script != nil {
|
||||
if p.Script.Plugin.HasFunc("onExit") {
|
||||
if _, err := p.Script.Call("onExit"); err != nil {
|
||||
log.Error("Error while executing onExit callback: %s", "\nTraceback:\n "+err.(*otto.Error).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.doRedirect && p.Redirection != nil {
|
||||
p.Debug("disabling redirection %s", p.Redirection.String())
|
||||
if err := p.Sess.Firewall.EnableRedirection(p.Redirection, false); err != nil {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package http_proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"github.com/elazarl/goproxy"
|
||||
|
||||
|
|
@ -74,10 +74,10 @@ func (p *HTTPProxy) isScriptInjectable(res *http.Response) (bool, string) {
|
|||
return false, ""
|
||||
}
|
||||
|
||||
func (p *HTTPProxy) doScriptInjection(res *http.Response, cType string) error {
|
||||
func (p *HTTPProxy) doScriptInjection(res *http.Response, cType string) (error) {
|
||||
defer res.Body.Close()
|
||||
|
||||
raw, err := io.ReadAll(res.Body)
|
||||
raw, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if html := string(raw); strings.Contains(html, "</head>") {
|
||||
|
|
@ -91,7 +91,7 @@ func (p *HTTPProxy) doScriptInjection(res *http.Response, cType string) error {
|
|||
res.Header.Set("Content-Length", strconv.Itoa(len(html)))
|
||||
|
||||
// reset the response body to the original unread state
|
||||
res.Body = io.NopCloser(strings.NewReader(html))
|
||||
res.Body = ioutil.NopCloser(strings.NewReader(html))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package http_proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
|
@ -253,7 +253,7 @@ func (s *SSLStripper) Process(res *http.Response, ctx *goproxy.ProxyCtx) {
|
|||
// if we have a text or html content type, fetch the body
|
||||
// and perform sslstripping
|
||||
if s.isContentStrippable(res) {
|
||||
raw, err := io.ReadAll(res.Body)
|
||||
raw, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
log.Error("Could not read response body: %s", err)
|
||||
return
|
||||
|
|
@ -297,9 +297,9 @@ func (s *SSLStripper) Process(res *http.Response, ctx *goproxy.ProxyCtx) {
|
|||
|
||||
// reset the response body to the original unread state
|
||||
// but with just a string reader, this way further calls
|
||||
// to ui.ReadAll(res.Body) will just return the content
|
||||
// to ioutil.ReadAll(res.Body) will just return the content
|
||||
// we stripped without downloading anything again.
|
||||
res.Body = io.NopCloser(strings.NewReader(body))
|
||||
res.Body = ioutil.NopCloser(strings.NewReader(body))
|
||||
}
|
||||
|
||||
// fix cookies domain + strip "secure" + "httponly" flags
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package http_proxy
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
|
@ -103,21 +103,7 @@ func (j *JSRequest) WasModified() bool {
|
|||
return j.NewHash() != j.refHash
|
||||
}
|
||||
|
||||
func (j *JSRequest) CheckIfModifiedAndUpdateHash() bool {
|
||||
newHash := j.NewHash()
|
||||
// body was read
|
||||
if j.bodyRead {
|
||||
j.refHash = newHash
|
||||
return true
|
||||
}
|
||||
// check if req was changed and update its hash
|
||||
wasModified := j.refHash != newHash
|
||||
j.refHash = newHash
|
||||
return wasModified
|
||||
}
|
||||
|
||||
func (j *JSRequest) GetHeader(name, deflt string) string {
|
||||
name = strings.ToLower(name)
|
||||
headers := strings.Split(j.Headers, "\r\n")
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if headers[i] != "" {
|
||||
|
|
@ -125,7 +111,8 @@ func (j *JSRequest) GetHeader(name, deflt string) string {
|
|||
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
|
||||
header_name := string(header_parts[0][1])
|
||||
header_value := string(header_parts[0][2])
|
||||
if name == strings.ToLower(header_name) {
|
||||
|
||||
if strings.ToLower(name) == strings.ToLower(header_name) {
|
||||
return header_value
|
||||
}
|
||||
}
|
||||
|
|
@ -134,25 +121,6 @@ func (j *JSRequest) GetHeader(name, deflt string) string {
|
|||
return deflt
|
||||
}
|
||||
|
||||
func (j *JSRequest) GetHeaders(name string) []string {
|
||||
name = strings.ToLower(name)
|
||||
headers := strings.Split(j.Headers, "\r\n")
|
||||
header_values := make([]string, 0, len(headers))
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if headers[i] != "" {
|
||||
header_parts := header_regexp.FindAllSubmatch([]byte(headers[i]), 1)
|
||||
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
|
||||
header_name := string(header_parts[0][1])
|
||||
header_value := string(header_parts[0][2])
|
||||
if name == strings.ToLower(header_name) {
|
||||
header_values = append(header_values, header_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return header_values
|
||||
}
|
||||
|
||||
func (j *JSRequest) SetHeader(name, value string) {
|
||||
name = strings.TrimSpace(name)
|
||||
value = strings.TrimSpace(value)
|
||||
|
|
@ -201,7 +169,7 @@ func (j *JSRequest) RemoveHeader(name string) {
|
|||
}
|
||||
|
||||
func (j *JSRequest) ReadBody() string {
|
||||
raw, err := io.ReadAll(j.req.Body)
|
||||
raw, err := ioutil.ReadAll(j.req.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -209,7 +177,7 @@ func (j *JSRequest) ReadBody() string {
|
|||
j.Body = string(raw)
|
||||
j.bodyRead = true
|
||||
// reset the request body to the original unread state
|
||||
j.req.Body = io.NopCloser(bytes.NewBuffer(raw))
|
||||
j.req.Body = ioutil.NopCloser(bytes.NewBuffer(raw))
|
||||
|
||||
return j.Body
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package http_proxy
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
|
@ -76,29 +76,7 @@ func (j *JSResponse) WasModified() bool {
|
|||
return j.NewHash() != j.refHash
|
||||
}
|
||||
|
||||
func (j *JSResponse) CheckIfModifiedAndUpdateHash() bool {
|
||||
newHash := j.NewHash()
|
||||
if j.bodyRead {
|
||||
// body was read
|
||||
j.refHash = newHash
|
||||
return true
|
||||
} else if j.bodyClear {
|
||||
// body was cleared manually
|
||||
j.refHash = newHash
|
||||
return true
|
||||
} else if j.Body != "" {
|
||||
// body was not read but just set
|
||||
j.refHash = newHash
|
||||
return true
|
||||
}
|
||||
// check if res was changed and update its hash
|
||||
wasModified := j.refHash != newHash
|
||||
j.refHash = newHash
|
||||
return wasModified
|
||||
}
|
||||
|
||||
func (j *JSResponse) GetHeader(name, deflt string) string {
|
||||
name = strings.ToLower(name)
|
||||
headers := strings.Split(j.Headers, "\r\n")
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if headers[i] != "" {
|
||||
|
|
@ -106,7 +84,8 @@ func (j *JSResponse) GetHeader(name, deflt string) string {
|
|||
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
|
||||
header_name := string(header_parts[0][1])
|
||||
header_value := string(header_parts[0][2])
|
||||
if name == strings.ToLower(header_name) {
|
||||
|
||||
if strings.ToLower(name) == strings.ToLower(header_name) {
|
||||
return header_value
|
||||
}
|
||||
}
|
||||
|
|
@ -115,25 +94,6 @@ func (j *JSResponse) GetHeader(name, deflt string) string {
|
|||
return deflt
|
||||
}
|
||||
|
||||
func (j *JSResponse) GetHeaders(name string) []string {
|
||||
name = strings.ToLower(name)
|
||||
headers := strings.Split(j.Headers, "\r\n")
|
||||
header_values := make([]string, 0, len(headers))
|
||||
for i := 0; i < len(headers); i++ {
|
||||
if headers[i] != "" {
|
||||
header_parts := header_regexp.FindAllSubmatch([]byte(headers[i]), 1)
|
||||
if len(header_parts) != 0 && len(header_parts[0]) == 3 {
|
||||
header_name := string(header_parts[0][1])
|
||||
header_value := string(header_parts[0][2])
|
||||
if name == strings.ToLower(header_name) {
|
||||
header_values = append(header_values, header_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return header_values
|
||||
}
|
||||
|
||||
func (j *JSResponse) SetHeader(name, value string) {
|
||||
name = strings.TrimSpace(name)
|
||||
value = strings.TrimSpace(value)
|
||||
|
|
@ -208,7 +168,7 @@ func (j *JSResponse) ToResponse(req *http.Request) (resp *http.Response) {
|
|||
func (j *JSResponse) ReadBody() string {
|
||||
defer j.resp.Body.Close()
|
||||
|
||||
raw, err := io.ReadAll(j.resp.Body)
|
||||
raw, err := ioutil.ReadAll(j.resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -217,7 +177,7 @@ func (j *JSResponse) ReadBody() string {
|
|||
j.bodyRead = true
|
||||
j.bodyClear = false
|
||||
// reset the response body to the original unread state
|
||||
j.resp.Body = io.NopCloser(bytes.NewBuffer(raw))
|
||||
j.resp.Body = ioutil.NopCloser(bytes.NewBuffer(raw))
|
||||
|
||||
return j.Body
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package http_proxy
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/log"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
|
|
@ -34,31 +33,6 @@ func LoadHttpProxyScript(path string, sess *session.Session) (err error, s *Http
|
|||
return
|
||||
}
|
||||
|
||||
// define addSessionEvent function
|
||||
err = plug.Set("addSessionEvent", func(call otto.FunctionCall) otto.Value {
|
||||
if len(call.ArgumentList) < 2 {
|
||||
log.Error("Failed to execute 'addSessionEvent' in HTTP proxy: 2 arguments required, but only %d given.", len(call.ArgumentList))
|
||||
return otto.FalseValue()
|
||||
}
|
||||
ottoTag := call.Argument(0)
|
||||
if !ottoTag.IsString() {
|
||||
log.Error("Failed to execute 'addSessionEvent' in HTTP proxy: first argument must be a string.")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
tag := strings.TrimSpace(ottoTag.String())
|
||||
if tag == "" {
|
||||
log.Error("Failed to execute 'addSessionEvent' in HTTP proxy: tag cannot be empty.")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
data := call.Argument(1)
|
||||
sess.Events.Add(tag, data)
|
||||
return otto.TrueValue()
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Error while defining addSessionEvent function: %+v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// run onLoad if defined
|
||||
if plug.HasFunc("onLoad") {
|
||||
if _, err = plug.Call("onLoad"); err != nil {
|
||||
|
|
@ -84,9 +58,11 @@ func (s *HttpProxyScript) OnRequest(original *http.Request) (jsreq *JSRequest, j
|
|||
if _, err := s.Call("onRequest", jsreq, jsres); err != nil {
|
||||
log.Error("%s", err)
|
||||
return nil, nil
|
||||
} else if jsreq.CheckIfModifiedAndUpdateHash() {
|
||||
} else if jsreq.WasModified() {
|
||||
jsreq.UpdateHash()
|
||||
return jsreq, nil
|
||||
} else if jsres.CheckIfModifiedAndUpdateHash() {
|
||||
} else if jsres.WasModified() {
|
||||
jsres.UpdateHash()
|
||||
return nil, jsres
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +78,8 @@ func (s *HttpProxyScript) OnResponse(res *http.Response) (jsreq *JSRequest, jsre
|
|||
if _, err := s.Call("onResponse", jsreq, jsres); err != nil {
|
||||
log.Error("%s", err)
|
||||
return nil, nil
|
||||
} else if jsres.CheckIfModifiedAndUpdateHash() {
|
||||
} else if jsres.WasModified() {
|
||||
jsres.UpdateHash()
|
||||
return nil, jsres
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,706 +0,0 @@
|
|||
package http_proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/firewall"
|
||||
"github.com/bettercap/bettercap/v2/network"
|
||||
"github.com/bettercap/bettercap/v2/packets"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/evilsocket/islazy/data"
|
||||
)
|
||||
|
||||
// MockFirewall implements a mock firewall for testing
|
||||
type MockFirewall struct {
|
||||
forwardingEnabled bool
|
||||
redirections []firewall.Redirection
|
||||
}
|
||||
|
||||
func NewMockFirewall() *MockFirewall {
|
||||
return &MockFirewall{
|
||||
forwardingEnabled: false,
|
||||
redirections: make([]firewall.Redirection, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockFirewall) IsForwardingEnabled() bool {
|
||||
return m.forwardingEnabled
|
||||
}
|
||||
|
||||
func (m *MockFirewall) EnableForwarding(enabled bool) error {
|
||||
m.forwardingEnabled = enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockFirewall) EnableRedirection(r *firewall.Redirection, enabled bool) error {
|
||||
if enabled {
|
||||
m.redirections = append(m.redirections, *r)
|
||||
} else {
|
||||
for i, red := range m.redirections {
|
||||
if red.String() == r.String() {
|
||||
m.redirections = append(m.redirections[:i], m.redirections[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockFirewall) DisableRedirection(r *firewall.Redirection, enabled bool) error {
|
||||
return m.EnableRedirection(r, false)
|
||||
}
|
||||
|
||||
func (m *MockFirewall) Restore() {
|
||||
m.redirections = make([]firewall.Redirection, 0)
|
||||
m.forwardingEnabled = false
|
||||
}
|
||||
|
||||
// Create a mock session for testing
|
||||
func createMockSession() (*session.Session, *MockFirewall) {
|
||||
// Create interface
|
||||
iface := &network.Endpoint{
|
||||
IpAddress: "192.168.1.100",
|
||||
HwAddress: "aa:bb:cc:dd:ee:ff",
|
||||
Hostname: "eth0",
|
||||
}
|
||||
iface.SetIP("192.168.1.100")
|
||||
iface.SetBits(24)
|
||||
|
||||
// Parse interface addresses
|
||||
ifaceIP := net.ParseIP("192.168.1.100")
|
||||
ifaceHW, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
||||
iface.IP = ifaceIP
|
||||
iface.HW = ifaceHW
|
||||
|
||||
// Create gateway
|
||||
gateway := &network.Endpoint{
|
||||
IpAddress: "192.168.1.1",
|
||||
HwAddress: "11:22:33:44:55:66",
|
||||
}
|
||||
gatewayIP := net.ParseIP("192.168.1.1")
|
||||
gatewayHW, _ := net.ParseMAC("11:22:33:44:55:66")
|
||||
gateway.IP = gatewayIP
|
||||
gateway.HW = gatewayHW
|
||||
|
||||
// Create mock firewall
|
||||
mockFirewall := NewMockFirewall()
|
||||
|
||||
// Create environment
|
||||
env, _ := session.NewEnvironment("")
|
||||
|
||||
// Create LAN
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
lan := network.NewLAN(iface, gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
// Create session
|
||||
sess := &session.Session{
|
||||
Interface: iface,
|
||||
Gateway: gateway,
|
||||
Lan: lan,
|
||||
StartedAt: time.Now(),
|
||||
Active: true,
|
||||
Env: env,
|
||||
Queue: &packets.Queue{},
|
||||
Firewall: mockFirewall,
|
||||
Modules: make(session.ModuleList, 0),
|
||||
}
|
||||
|
||||
// Initialize events
|
||||
sess.Events = session.NewEventPool(false, false)
|
||||
|
||||
return sess, mockFirewall
|
||||
}
|
||||
|
||||
func TestNewHttpProxy(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
|
||||
mod := NewHttpProxy(sess)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewHttpProxy returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "http.proxy" {
|
||||
t.Errorf("expected module name 'http.proxy', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
params := []string{
|
||||
"http.port",
|
||||
"http.proxy.address",
|
||||
"http.proxy.port",
|
||||
"http.proxy.redirect",
|
||||
"http.proxy.script",
|
||||
"http.proxy.injectjs",
|
||||
"http.proxy.blacklist",
|
||||
"http.proxy.whitelist",
|
||||
"http.proxy.sslstrip",
|
||||
}
|
||||
for _, param := range params {
|
||||
if !mod.Session.Env.Has(param) {
|
||||
t.Errorf("parameter %s not registered", param)
|
||||
}
|
||||
}
|
||||
|
||||
// Check handlers
|
||||
handlers := mod.Handlers()
|
||||
expectedHandlers := []string{"http.proxy on", "http.proxy off"}
|
||||
handlerMap := make(map[string]bool)
|
||||
|
||||
for _, h := range handlers {
|
||||
handlerMap[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerMap[expected] {
|
||||
t.Errorf("Expected handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpProxyConfigure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
params map[string]string
|
||||
expectErr bool
|
||||
validate func(*HttpProxy) error
|
||||
}{
|
||||
{
|
||||
name: "default configuration",
|
||||
params: map[string]string{
|
||||
"http.port": "80",
|
||||
"http.proxy.address": "192.168.1.100",
|
||||
"http.proxy.port": "8080",
|
||||
"http.proxy.redirect": "true",
|
||||
"http.proxy.script": "",
|
||||
"http.proxy.injectjs": "",
|
||||
"http.proxy.blacklist": "",
|
||||
"http.proxy.whitelist": "",
|
||||
"http.proxy.sslstrip": "false",
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *HttpProxy) error {
|
||||
if mod.proxy == nil {
|
||||
return fmt.Errorf("proxy not initialized")
|
||||
}
|
||||
if mod.proxy.Address != "192.168.1.100" {
|
||||
return fmt.Errorf("expected address 192.168.1.100, got %s", mod.proxy.Address)
|
||||
}
|
||||
if !mod.proxy.doRedirect {
|
||||
return fmt.Errorf("expected redirect to be true")
|
||||
}
|
||||
if mod.proxy.Stripper == nil {
|
||||
return fmt.Errorf("SSL stripper not initialized")
|
||||
}
|
||||
if mod.proxy.Stripper.Enabled() {
|
||||
return fmt.Errorf("SSL stripper should be disabled")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
// Note: SSL stripping test removed as it requires elevated permissions
|
||||
// to create network capture handles
|
||||
{
|
||||
name: "with blacklist and whitelist",
|
||||
params: map[string]string{
|
||||
"http.port": "80",
|
||||
"http.proxy.address": "192.168.1.100",
|
||||
"http.proxy.port": "8080",
|
||||
"http.proxy.redirect": "false",
|
||||
"http.proxy.script": "",
|
||||
"http.proxy.injectjs": "",
|
||||
"http.proxy.blacklist": "*.evil.com,bad.site.org",
|
||||
"http.proxy.whitelist": "*.good.com,safe.site.org",
|
||||
"http.proxy.sslstrip": "false",
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *HttpProxy) error {
|
||||
if len(mod.proxy.Blacklist) != 2 {
|
||||
return fmt.Errorf("expected 2 blacklist entries, got %d", len(mod.proxy.Blacklist))
|
||||
}
|
||||
if len(mod.proxy.Whitelist) != 2 {
|
||||
return fmt.Errorf("expected 2 whitelist entries, got %d", len(mod.proxy.Whitelist))
|
||||
}
|
||||
if mod.proxy.doRedirect {
|
||||
return fmt.Errorf("expected redirect to be false")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "JavaScript injection with inline code",
|
||||
params: map[string]string{
|
||||
"http.port": "80",
|
||||
"http.proxy.address": "192.168.1.100",
|
||||
"http.proxy.port": "8080",
|
||||
"http.proxy.redirect": "true",
|
||||
"http.proxy.script": "",
|
||||
"http.proxy.injectjs": "alert('injected');",
|
||||
"http.proxy.blacklist": "",
|
||||
"http.proxy.whitelist": "",
|
||||
"http.proxy.sslstrip": "false",
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *HttpProxy) error {
|
||||
if mod.proxy.jsHook == "" {
|
||||
return fmt.Errorf("jsHook should be set")
|
||||
}
|
||||
if !strings.Contains(mod.proxy.jsHook, "alert('injected');") {
|
||||
return fmt.Errorf("jsHook should contain injected code")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "JavaScript injection with URL",
|
||||
params: map[string]string{
|
||||
"http.port": "80",
|
||||
"http.proxy.address": "192.168.1.100",
|
||||
"http.proxy.port": "8080",
|
||||
"http.proxy.redirect": "true",
|
||||
"http.proxy.script": "",
|
||||
"http.proxy.injectjs": "http://evil.com/hook.js",
|
||||
"http.proxy.blacklist": "",
|
||||
"http.proxy.whitelist": "",
|
||||
"http.proxy.sslstrip": "false",
|
||||
},
|
||||
expectErr: false,
|
||||
validate: func(mod *HttpProxy) error {
|
||||
if mod.proxy.jsHook == "" {
|
||||
return fmt.Errorf("jsHook should be set")
|
||||
}
|
||||
if !strings.Contains(mod.proxy.jsHook, "http://evil.com/hook.js") {
|
||||
return fmt.Errorf("jsHook should contain script URL")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid address",
|
||||
params: map[string]string{
|
||||
"http.port": "80",
|
||||
"http.proxy.address": "invalid-address",
|
||||
"http.proxy.port": "8080",
|
||||
"http.proxy.redirect": "true",
|
||||
"http.proxy.script": "",
|
||||
"http.proxy.injectjs": "",
|
||||
"http.proxy.blacklist": "",
|
||||
"http.proxy.whitelist": "",
|
||||
"http.proxy.sslstrip": "false",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid port",
|
||||
params: map[string]string{
|
||||
"http.port": "80",
|
||||
"http.proxy.address": "192.168.1.100",
|
||||
"http.proxy.port": "invalid-port",
|
||||
"http.proxy.redirect": "true",
|
||||
"http.proxy.script": "",
|
||||
"http.proxy.injectjs": "",
|
||||
"http.proxy.blacklist": "",
|
||||
"http.proxy.whitelist": "",
|
||||
"http.proxy.sslstrip": "false",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
mod := NewHttpProxy(sess)
|
||||
|
||||
// Set parameters
|
||||
for k, v := range tt.params {
|
||||
sess.Env.Set(k, v)
|
||||
}
|
||||
|
||||
err := mod.Configure()
|
||||
|
||||
if tt.expectErr && err == nil {
|
||||
t.Error("expected error but got none")
|
||||
} else if !tt.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !tt.expectErr && tt.validate != nil {
|
||||
if err := tt.validate(mod); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpProxyStartStop(t *testing.T) {
|
||||
sess, mockFirewall := createMockSession()
|
||||
mod := NewHttpProxy(sess)
|
||||
|
||||
// Configure with test parameters
|
||||
sess.Env.Set("http.port", "80")
|
||||
sess.Env.Set("http.proxy.address", "127.0.0.1")
|
||||
sess.Env.Set("http.proxy.port", "0") // Use port 0 to get a random available port
|
||||
sess.Env.Set("http.proxy.redirect", "true")
|
||||
sess.Env.Set("http.proxy.sslstrip", "false")
|
||||
|
||||
// Start the proxy
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start proxy: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("Proxy should be running after Start()")
|
||||
}
|
||||
|
||||
// Check that forwarding was enabled
|
||||
if !mockFirewall.IsForwardingEnabled() {
|
||||
t.Error("Forwarding should be enabled after starting proxy")
|
||||
}
|
||||
|
||||
// Check that redirection was added
|
||||
if len(mockFirewall.redirections) != 1 {
|
||||
t.Errorf("Expected 1 redirection, got %d", len(mockFirewall.redirections))
|
||||
}
|
||||
|
||||
// Give the server time to start
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Stop the proxy
|
||||
err = mod.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stop proxy: %v", err)
|
||||
}
|
||||
|
||||
if mod.Running() {
|
||||
t.Error("Proxy should not be running after Stop()")
|
||||
}
|
||||
|
||||
// Check that redirection was removed
|
||||
if len(mockFirewall.redirections) != 0 {
|
||||
t.Errorf("Expected 0 redirections after stop, got %d", len(mockFirewall.redirections))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpProxyAlreadyStarted(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
mod := NewHttpProxy(sess)
|
||||
|
||||
// Configure
|
||||
sess.Env.Set("http.port", "80")
|
||||
sess.Env.Set("http.proxy.address", "127.0.0.1")
|
||||
sess.Env.Set("http.proxy.port", "0")
|
||||
sess.Env.Set("http.proxy.redirect", "false")
|
||||
|
||||
// Start the proxy
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start proxy: %v", err)
|
||||
}
|
||||
|
||||
// Try to configure while running
|
||||
err = mod.Configure()
|
||||
if err == nil {
|
||||
t.Error("Configure should fail when proxy is already running")
|
||||
}
|
||||
|
||||
// Stop the proxy
|
||||
mod.Stop()
|
||||
}
|
||||
|
||||
func TestHTTPProxyDoProxy(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
proxy := NewHTTPProxy(sess, "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
request *http.Request
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "valid request",
|
||||
request: &http.Request{
|
||||
Host: "example.com",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "empty host",
|
||||
request: &http.Request{
|
||||
Host: "",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "localhost request",
|
||||
request: &http.Request{
|
||||
Host: "localhost:8080",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "127.0.0.1 request",
|
||||
request: &http.Request{
|
||||
Host: "127.0.0.1:8080",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := proxy.doProxy(tt.request)
|
||||
if result != tt.expected {
|
||||
t.Errorf("doProxy(%v) = %v, expected %v", tt.request.Host, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPProxyShouldProxy(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
proxy := NewHTTPProxy(sess, "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
blacklist []string
|
||||
whitelist []string
|
||||
host string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no filters",
|
||||
blacklist: []string{},
|
||||
whitelist: []string{},
|
||||
host: "example.com",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "blacklisted exact match",
|
||||
blacklist: []string{"evil.com"},
|
||||
whitelist: []string{},
|
||||
host: "evil.com",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "blacklisted wildcard match",
|
||||
blacklist: []string{"*.evil.com"},
|
||||
whitelist: []string{},
|
||||
host: "sub.evil.com",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "whitelisted exact match",
|
||||
blacklist: []string{"*"},
|
||||
whitelist: []string{"good.com"},
|
||||
host: "good.com",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "not blacklisted",
|
||||
blacklist: []string{"evil.com"},
|
||||
whitelist: []string{},
|
||||
host: "good.com",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "whitelist takes precedence",
|
||||
blacklist: []string{"*"},
|
||||
whitelist: []string{"good.com"},
|
||||
host: "good.com",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
proxy.Blacklist = tt.blacklist
|
||||
proxy.Whitelist = tt.whitelist
|
||||
|
||||
req := &http.Request{
|
||||
Host: tt.host,
|
||||
}
|
||||
|
||||
result := proxy.shouldProxy(req)
|
||||
if result != tt.expected {
|
||||
t.Errorf("shouldProxy(%v) = %v, expected %v", tt.host, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPProxyStripPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"example.com:8080", "example.com"},
|
||||
{"example.com", "example.com"},
|
||||
{"192.168.1.1:443", "192.168.1.1"},
|
||||
{"[::1]:8080", "["}, // stripPort splits on first colon, so IPv6 addresses don't work correctly
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
result := stripPort(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("stripPort(%s) = %s, expected %s", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPProxyJavaScriptInjection(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
proxy := NewHTTPProxy(sess, "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
jsToInject string
|
||||
expectedHook string
|
||||
}{
|
||||
{
|
||||
name: "inline JavaScript",
|
||||
jsToInject: "console.log('test');",
|
||||
expectedHook: `<script type="text/javascript">console.log('test');</script></head>`,
|
||||
},
|
||||
{
|
||||
name: "script tag",
|
||||
jsToInject: `<script>alert('test');</script>`,
|
||||
expectedHook: `<script type="text/javascript"><script>alert('test');</script></script></head>`, // script tags get wrapped
|
||||
},
|
||||
{
|
||||
name: "external URL",
|
||||
jsToInject: "http://example.com/script.js",
|
||||
expectedHook: `<script src="http://example.com/script.js" type="text/javascript"></script></head>`,
|
||||
},
|
||||
{
|
||||
name: "HTTPS URL",
|
||||
jsToInject: "https://example.com/script.js",
|
||||
expectedHook: `<script src="https://example.com/script.js" type="text/javascript"></script></head>`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip test with invalid filename characters on Windows
|
||||
if runtime.GOOS == "windows" && strings.ContainsAny(tt.jsToInject, "<>:\"|?*") {
|
||||
t.Skip("Skipping test with invalid filename characters on Windows")
|
||||
}
|
||||
|
||||
err := proxy.Configure("127.0.0.1", 8080, 80, false, "", tt.jsToInject, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Configure failed: %v", err)
|
||||
}
|
||||
|
||||
if proxy.jsHook != tt.expectedHook {
|
||||
t.Errorf("jsHook = %q, expected %q", proxy.jsHook, tt.expectedHook)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPProxyWithTestServer(t *testing.T) {
|
||||
// Create a test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("<html><head></head><body>Test Page</body></html>"))
|
||||
}))
|
||||
defer testServer.Close()
|
||||
|
||||
sess, _ := createMockSession()
|
||||
proxy := NewHTTPProxy(sess, "test")
|
||||
|
||||
// Configure proxy with JS injection
|
||||
err := proxy.Configure("127.0.0.1", 0, 80, false, "", "console.log('injected');", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Configure failed: %v", err)
|
||||
}
|
||||
|
||||
// Create a simple test to verify proxy is initialized
|
||||
if proxy.Proxy == nil {
|
||||
t.Error("Proxy not initialized")
|
||||
}
|
||||
|
||||
if proxy.jsHook == "" {
|
||||
t.Error("JavaScript hook not set")
|
||||
}
|
||||
|
||||
// Note: Testing actual proxy behavior would require setting up the proxy server
|
||||
// and making HTTP requests through it, which is complex in a unit test environment
|
||||
}
|
||||
|
||||
func TestHTTPProxyScriptLoading(t *testing.T) {
|
||||
sess, _ := createMockSession()
|
||||
proxy := NewHTTPProxy(sess, "test")
|
||||
|
||||
// Create a temporary script file
|
||||
scriptContent := `
|
||||
function onRequest(req, res) {
|
||||
console.log("Request intercepted");
|
||||
}
|
||||
`
|
||||
tmpFile, err := ioutil.TempFile("", "proxy_script_*.js")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
if _, err := tmpFile.Write([]byte(scriptContent)); err != nil {
|
||||
t.Fatalf("Failed to write script: %v", err)
|
||||
}
|
||||
tmpFile.Close()
|
||||
|
||||
// Try to configure with non-existent script
|
||||
err = proxy.Configure("127.0.0.1", 8080, 80, false, "non_existent_script.js", "", false)
|
||||
if err == nil {
|
||||
t.Error("Configure should fail with non-existent script")
|
||||
}
|
||||
|
||||
// Note: Actual script loading would require proper JS engine setup
|
||||
// which is complex to mock. This test verifies the error handling.
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
func BenchmarkHTTPProxyShouldProxy(b *testing.B) {
|
||||
sess, _ := createMockSession()
|
||||
proxy := NewHTTPProxy(sess, "test")
|
||||
|
||||
proxy.Blacklist = []string{"*.evil.com", "bad.site.org", "*.malicious.net"}
|
||||
proxy.Whitelist = []string{"*.good.com", "safe.site.org"}
|
||||
|
||||
req := &http.Request{
|
||||
Host: "example.com",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = proxy.shouldProxy(req)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHTTPProxyStripPort(b *testing.B) {
|
||||
testHost := "example.com:8080"
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = stripPort(testHost)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,20 +31,20 @@ func NewHttpServer(s *session.Session) *HttpServer {
|
|||
mod.AddParam(session.NewStringParameter("http.server.address",
|
||||
session.ParamIfaceAddress,
|
||||
session.IPv4Validator,
|
||||
"Address to bind the HTTP server to."))
|
||||
"Address to bind the http server to."))
|
||||
|
||||
mod.AddParam(session.NewIntParameter("http.server.port",
|
||||
"80",
|
||||
"Port to bind the HTTP server to."))
|
||||
"Port to bind the http server to."))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("http.server on", "",
|
||||
"Start HTTP server.",
|
||||
"Start httpd server.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("http.server off", "",
|
||||
"Stop HTTP server.",
|
||||
"Stop httpd server.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -35,11 +35,11 @@ func NewHttpsServer(s *session.Session) *HttpsServer {
|
|||
mod.AddParam(session.NewStringParameter("https.server.address",
|
||||
session.ParamIfaceAddress,
|
||||
session.IPv4Validator,
|
||||
"Address to bind the HTTPS server to."))
|
||||
"Address to bind the http server to."))
|
||||
|
||||
mod.AddParam(session.NewIntParameter("https.server.port",
|
||||
"443",
|
||||
"Port to bind the HTTPS server to."))
|
||||
"Port to bind the http server to."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("https.server.certificate",
|
||||
"~/.bettercap-httpd.cert.pem",
|
||||
|
|
@ -54,13 +54,13 @@ func NewHttpsServer(s *session.Session) *HttpsServer {
|
|||
tls.CertConfigToModule("https.server", &mod.SessionModule, tls.DefaultLegitConfig)
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("https.server on", "",
|
||||
"Start HTTPS server.",
|
||||
"Start https server.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("https.server off", "",
|
||||
"Stop HTTPS server.",
|
||||
"Stop https server.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
|
|
|||
160
modules/mdns_server/mdns_server.go
Normal file
160
modules/mdns_server/mdns_server.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
package mdns_server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
|
||||
"github.com/hashicorp/mdns"
|
||||
)
|
||||
|
||||
type MDNSServer struct {
|
||||
session.SessionModule
|
||||
hostname string
|
||||
instance string
|
||||
service *mdns.MDNSService
|
||||
server *mdns.Server
|
||||
}
|
||||
|
||||
func NewMDNSServer(s *session.Session) *MDNSServer {
|
||||
host, _ := os.Hostname()
|
||||
mod := &MDNSServer{
|
||||
SessionModule: session.NewSessionModule("mdns.server", s),
|
||||
hostname: host,
|
||||
}
|
||||
|
||||
mod.AddParam(session.NewStringParameter("mdns.server.host",
|
||||
mod.hostname+".",
|
||||
"",
|
||||
"mDNS hostname to advertise on the network."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("mdns.server.service",
|
||||
"_companion-link._tcp.",
|
||||
"",
|
||||
"mDNS service name to advertise on the network."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("mdns.server.domain",
|
||||
"local.",
|
||||
"",
|
||||
"mDNS domain."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("mdns.server.address",
|
||||
session.ParamIfaceAddress,
|
||||
session.IPv4Validator,
|
||||
"IPv4 address of the mDNS service."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("mdns.server.address6",
|
||||
session.ParamIfaceAddress6,
|
||||
session.IPv6Validator,
|
||||
"IPv6 address of the mDNS service."))
|
||||
|
||||
mod.AddParam(session.NewIntParameter("mdns.server.port",
|
||||
"52377",
|
||||
"Port of the mDNS service."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("mdns.server.info",
|
||||
"rpBA=DE:AD:BE:EF:CA:FE, rpAD=abf99d4ff73f, rpHI=ec5fb3caf528, rpHN=20f8fb46e2eb, rpVr=164.16, rpHA=7406bd0eff69",
|
||||
"",
|
||||
"Comma separated list of informative TXT records for the mDNS server."))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("mdns.server on", "",
|
||||
"Start mDNS server.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("mdns.server off", "",
|
||||
"Stop mDNS server.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (mod *MDNSServer) Name() string {
|
||||
return "mdns.server"
|
||||
}
|
||||
|
||||
func (mod *MDNSServer) Description() string {
|
||||
return "A mDNS server module to create multicast services or spoof existing ones."
|
||||
}
|
||||
|
||||
func (mod *MDNSServer) Author() string {
|
||||
return "Simone Margaritelli <evilsocket@gmail.com>"
|
||||
}
|
||||
|
||||
func (mod *MDNSServer) Configure() (err error) {
|
||||
if mod.Running() {
|
||||
return session.ErrAlreadyStarted(mod.Name())
|
||||
}
|
||||
|
||||
var host string
|
||||
var service string
|
||||
var domain string
|
||||
var ip4 string
|
||||
var ip6 string
|
||||
var port int
|
||||
var info string
|
||||
|
||||
if err, host = mod.StringParam("mdns.server.host"); err != nil {
|
||||
return err
|
||||
} else if err, service = mod.StringParam("mdns.server.service"); err != nil {
|
||||
return err
|
||||
} else if err, domain = mod.StringParam("mdns.server.domain"); err != nil {
|
||||
return err
|
||||
} else if err, ip4 = mod.StringParam("mdns.server.address"); err != nil {
|
||||
return err
|
||||
} else if err, ip6 = mod.StringParam("mdns.server.address6"); err != nil {
|
||||
return err
|
||||
} else if err, port = mod.IntParam("mdns.server.port"); err != nil {
|
||||
return err
|
||||
} else if err, info = mod.StringParam("mdns.server.info"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
mod.instance = fmt.Sprintf("%s%s%s", host, service, domain)
|
||||
mod.service, err = mdns.NewMDNSService(
|
||||
mod.instance,
|
||||
service,
|
||||
domain,
|
||||
host,
|
||||
port,
|
||||
[]net.IP{
|
||||
net.ParseIP(ip4),
|
||||
net.ParseIP(ip6),
|
||||
},
|
||||
str.Comma(info))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (mod *MDNSServer) Start() error {
|
||||
if err := mod.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mod.SetRunning(true, func() {
|
||||
var err error
|
||||
mod.Info("advertising service %s -> %s:%d", tui.Bold(mod.instance), mod.service.IPs, mod.service.Port)
|
||||
if mod.server, err = mdns.NewServer(&mdns.Config{Zone: mod.service}); err != nil {
|
||||
mod.Error("%v", err)
|
||||
mod.Stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (mod *MDNSServer) Stop() error {
|
||||
return mod.SetRunning(false, func() {
|
||||
mod.server.Shutdown()
|
||||
})
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/bettercap/bettercap/v2/modules/can"
|
||||
"github.com/bettercap/bettercap/v2/modules/caplets"
|
||||
"github.com/bettercap/bettercap/v2/modules/dhcp6_spoof"
|
||||
"github.com/bettercap/bettercap/v2/modules/dns_proxy"
|
||||
"github.com/bettercap/bettercap/v2/modules/dns_spoof"
|
||||
"github.com/bettercap/bettercap/v2/modules/events_stream"
|
||||
"github.com/bettercap/bettercap/v2/modules/gps"
|
||||
|
|
@ -20,6 +19,7 @@ import (
|
|||
"github.com/bettercap/bettercap/v2/modules/https_proxy"
|
||||
"github.com/bettercap/bettercap/v2/modules/https_server"
|
||||
"github.com/bettercap/bettercap/v2/modules/mac_changer"
|
||||
"github.com/bettercap/bettercap/v2/modules/mdns_server"
|
||||
"github.com/bettercap/bettercap/v2/modules/mysql_server"
|
||||
"github.com/bettercap/bettercap/v2/modules/ndp_spoof"
|
||||
"github.com/bettercap/bettercap/v2/modules/net_probe"
|
||||
|
|
@ -33,7 +33,6 @@ import (
|
|||
"github.com/bettercap/bettercap/v2/modules/update"
|
||||
"github.com/bettercap/bettercap/v2/modules/wifi"
|
||||
"github.com/bettercap/bettercap/v2/modules/wol"
|
||||
"github.com/bettercap/bettercap/v2/modules/zerogod"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
|
@ -46,7 +45,6 @@ func LoadModules(sess *session.Session) {
|
|||
sess.Register(can.NewCanModule(sess))
|
||||
sess.Register(dhcp6_spoof.NewDHCP6Spoofer(sess))
|
||||
sess.Register(net_recon.NewDiscovery(sess))
|
||||
sess.Register(dns_proxy.NewDnsProxy(sess))
|
||||
sess.Register(dns_spoof.NewDNSSpoofer(sess))
|
||||
sess.Register(events_stream.NewEventsStream(sess))
|
||||
sess.Register(gps.NewGPS(sess))
|
||||
|
|
@ -57,7 +55,7 @@ func LoadModules(sess *session.Session) {
|
|||
sess.Register(https_server.NewHttpsServer(sess))
|
||||
sess.Register(mac_changer.NewMacChanger(sess))
|
||||
sess.Register(mysql_server.NewMySQLServer(sess))
|
||||
sess.Register(zerogod.NewZeroGod(sess))
|
||||
sess.Register(mdns_server.NewMDNSServer(sess))
|
||||
sess.Register(net_sniff.NewSniffer(sess))
|
||||
sess.Register(packet_proxy.NewPacketProxy(sess))
|
||||
sess.Register(net_probe.NewProber(sess))
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadModulesWithNilSession(t *testing.T) {
|
||||
// This test verifies that LoadModules handles nil session gracefully
|
||||
// In the actual implementation, this would panic, which is expected behavior
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("expected panic when loading modules with nil session, but didn't get one")
|
||||
}
|
||||
}()
|
||||
|
||||
LoadModules(nil)
|
||||
}
|
||||
|
||||
// Since LoadModules requires a fully initialized session with command-line flags,
|
||||
// which conflicts with the test runner, we can't easily test the actual module loading.
|
||||
// The main functionality is tested through integration tests and the actual application.
|
||||
// This test file at least provides some coverage for the package and demonstrates
|
||||
// the expected behavior with invalid input.
|
||||
|
|
@ -118,7 +118,7 @@ func (mod *Prober) Start() error {
|
|||
}
|
||||
|
||||
if mod.probes.MDNS {
|
||||
mod.Session.Run("zerogod.discovery on")
|
||||
go mod.mdnsProber()
|
||||
}
|
||||
|
||||
fromIP := mod.Session.Interface.IP
|
||||
|
|
@ -129,6 +129,9 @@ func (mod *Prober) Start() error {
|
|||
mod.Info("probing %d addresses on %s", len(addresses), cidr)
|
||||
|
||||
for mod.Running() {
|
||||
if mod.probes.MDNS {
|
||||
mod.sendProbeMDNS(fromIP, fromHW)
|
||||
}
|
||||
|
||||
if mod.probes.UPNP {
|
||||
mod.sendProbeUPNP(fromIP, fromHW)
|
||||
|
|
@ -157,10 +160,6 @@ func (mod *Prober) Start() error {
|
|||
|
||||
func (mod *Prober) Stop() error {
|
||||
return mod.SetRunning(false, func() {
|
||||
if mod.probes.MDNS {
|
||||
mod.Session.Run("zerogod.discovery off")
|
||||
}
|
||||
|
||||
mod.waitGroup.Wait()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
100
modules/net_probe/net_probe_mdns.go
Normal file
100
modules/net_probe/net_probe_mdns.go
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package net_probe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/packets"
|
||||
|
||||
"github.com/hashicorp/mdns"
|
||||
)
|
||||
|
||||
var services = []string{
|
||||
"_hap._tcp.local",
|
||||
"_homekit._tcp.local",
|
||||
"_airplay._tcp.local",
|
||||
"_raop._tcp.local",
|
||||
"_sleep-proxy._udp.local",
|
||||
"_companion-link._tcp.local",
|
||||
"_googlezone._tcp.local",
|
||||
"_googlerpc._tcp.local",
|
||||
"_googlecast._tcp.local",
|
||||
"local",
|
||||
}
|
||||
|
||||
func (mod *Prober) sendProbeMDNS(from net.IP, from_hw net.HardwareAddr) {
|
||||
err, raw := packets.NewMDNSProbe(from, from_hw)
|
||||
if err != nil {
|
||||
mod.Error("error while sending mdns probe: %v", err)
|
||||
return
|
||||
} else if err := mod.Session.Queue.Send(raw); err != nil {
|
||||
mod.Error("error sending mdns packet: %s", err)
|
||||
} else {
|
||||
mod.Debug("sent %d bytes of MDNS probe", len(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func (mod *Prober) mdnsListener(c chan *mdns.ServiceEntry) {
|
||||
mod.Debug("mdns listener started")
|
||||
defer mod.Debug("mdns listener stopped")
|
||||
|
||||
for entry := range c {
|
||||
addrs := []string{}
|
||||
if entry.AddrV4 != nil {
|
||||
addrs = append(addrs, entry.AddrV4.String())
|
||||
}
|
||||
if entry.AddrV6 != nil {
|
||||
addrs = append(addrs, entry.AddrV6.String())
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if host := mod.Session.Lan.GetByIp(addr); host != nil {
|
||||
meta := make(map[string]string)
|
||||
|
||||
meta["mdns:name"] = entry.Name
|
||||
meta["mdns:hostname"] = entry.Host
|
||||
|
||||
if entry.AddrV4 != nil {
|
||||
meta["mdns:ipv4"] = entry.AddrV4.String()
|
||||
}
|
||||
|
||||
if entry.AddrV6 != nil {
|
||||
meta["mdns:ipv6"] = entry.AddrV6.String()
|
||||
}
|
||||
|
||||
meta["mdns:port"] = fmt.Sprintf("%d", entry.Port)
|
||||
|
||||
mod.Debug("meta for %s: %v", addr, meta)
|
||||
|
||||
host.OnMeta(meta)
|
||||
} else {
|
||||
mod.Debug("got mdns entry for unknown ip %s", entry.AddrV4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mod *Prober) mdnsProber() {
|
||||
mod.Debug("mdns prober started")
|
||||
defer mod.Debug("mdns.prober stopped")
|
||||
|
||||
mod.waitGroup.Add(1)
|
||||
defer mod.waitGroup.Done()
|
||||
|
||||
log.SetOutput(ioutil.Discard)
|
||||
|
||||
ch := make(chan *mdns.ServiceEntry)
|
||||
defer close(ch)
|
||||
|
||||
go mod.mdnsListener(ch)
|
||||
|
||||
for mod.Running() {
|
||||
for _, svc := range services {
|
||||
if mod.Running() {
|
||||
mdns.Lookup(svc, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,610 +0,0 @@
|
|||
package net_probe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/network"
|
||||
"github.com/bettercap/bettercap/v2/packets"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/malfunkt/iprange"
|
||||
)
|
||||
|
||||
// MockQueue implements a mock packet queue for testing
|
||||
type MockQueue struct {
|
||||
sync.Mutex
|
||||
sentPackets [][]byte
|
||||
sendError error
|
||||
active bool
|
||||
}
|
||||
|
||||
func NewMockQueue() *MockQueue {
|
||||
return &MockQueue{
|
||||
sentPackets: make([][]byte, 0),
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockQueue) Send(data []byte) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if m.sendError != nil {
|
||||
return m.sendError
|
||||
}
|
||||
|
||||
// Store a copy of the packet
|
||||
packet := make([]byte, len(data))
|
||||
copy(packet, data)
|
||||
m.sentPackets = append(m.sentPackets, packet)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockQueue) GetSentPackets() [][]byte {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return m.sentPackets
|
||||
}
|
||||
|
||||
func (m *MockQueue) ClearSentPackets() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.sentPackets = make([][]byte, 0)
|
||||
}
|
||||
|
||||
func (m *MockQueue) Stop() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
m.active = false
|
||||
}
|
||||
|
||||
// MockSession for testing
|
||||
type MockSession struct {
|
||||
*session.Session
|
||||
runCommands []string
|
||||
skipIPs map[string]bool
|
||||
}
|
||||
|
||||
func (m *MockSession) Run(cmd string) error {
|
||||
m.runCommands = append(m.runCommands, cmd)
|
||||
|
||||
// Handle module commands
|
||||
if cmd == "net.recon on" {
|
||||
// Find and start the net.recon module
|
||||
for _, mod := range m.Modules {
|
||||
if mod.Name() == "net.recon" {
|
||||
if !mod.Running() {
|
||||
return mod.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else if cmd == "net.recon off" {
|
||||
// Find and stop the net.recon module
|
||||
for _, mod := range m.Modules {
|
||||
if mod.Name() == "net.recon" {
|
||||
if mod.Running() {
|
||||
return mod.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else if cmd == "zerogod.discovery on" || cmd == "zerogod.discovery off" {
|
||||
// Mock zerogod.discovery commands
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockSession) Skip(ip net.IP) bool {
|
||||
if m.skipIPs == nil {
|
||||
return false
|
||||
}
|
||||
return m.skipIPs[ip.String()]
|
||||
}
|
||||
|
||||
// MockNetRecon implements a minimal net.recon module for testing
|
||||
type MockNetRecon struct {
|
||||
session.SessionModule
|
||||
}
|
||||
|
||||
func NewMockNetRecon(s *session.Session) *MockNetRecon {
|
||||
mod := &MockNetRecon{
|
||||
SessionModule: session.NewSessionModule("net.recon", s),
|
||||
}
|
||||
|
||||
// Add handlers so the module can be started/stopped via commands
|
||||
mod.AddHandler(session.NewModuleHandler("net.recon on", "",
|
||||
"Start net.recon",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("net.recon off", "",
|
||||
"Stop net.recon",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Name() string {
|
||||
return "net.recon"
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Description() string {
|
||||
return "Mock net.recon module"
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Author() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Start() error {
|
||||
return m.SetRunning(true, nil)
|
||||
}
|
||||
|
||||
func (m *MockNetRecon) Stop() error {
|
||||
return m.SetRunning(false, nil)
|
||||
}
|
||||
|
||||
// Create a mock session for testing
|
||||
func createMockSession() (*MockSession, *MockQueue) {
|
||||
// Create interface
|
||||
iface := &network.Endpoint{
|
||||
IpAddress: "192.168.1.100",
|
||||
HwAddress: "aa:bb:cc:dd:ee:ff",
|
||||
Hostname: "eth0",
|
||||
}
|
||||
iface.SetIP("192.168.1.100")
|
||||
iface.SetBits(24)
|
||||
|
||||
// Parse interface addresses
|
||||
ifaceIP := net.ParseIP("192.168.1.100")
|
||||
ifaceHW, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff")
|
||||
iface.IP = ifaceIP
|
||||
iface.HW = ifaceHW
|
||||
|
||||
// Create gateway
|
||||
gateway := &network.Endpoint{
|
||||
IpAddress: "192.168.1.1",
|
||||
HwAddress: "11:22:33:44:55:66",
|
||||
}
|
||||
|
||||
// Create mock queue
|
||||
mockQueue := NewMockQueue()
|
||||
|
||||
// Create environment
|
||||
env, _ := session.NewEnvironment("")
|
||||
|
||||
// Create session
|
||||
sess := &session.Session{
|
||||
Interface: iface,
|
||||
Gateway: gateway,
|
||||
StartedAt: time.Now(),
|
||||
Active: true,
|
||||
Env: env,
|
||||
Queue: &packets.Queue{
|
||||
Traffic: sync.Map{},
|
||||
Stats: packets.Stats{},
|
||||
},
|
||||
Modules: make(session.ModuleList, 0),
|
||||
}
|
||||
|
||||
// Initialize events
|
||||
sess.Events = session.NewEventPool(false, false)
|
||||
|
||||
// Add mock net.recon module
|
||||
mockNetRecon := NewMockNetRecon(sess)
|
||||
sess.Modules = append(sess.Modules, mockNetRecon)
|
||||
|
||||
// Create mock session wrapper
|
||||
mockSess := &MockSession{
|
||||
Session: sess,
|
||||
runCommands: make([]string, 0),
|
||||
skipIPs: make(map[string]bool),
|
||||
}
|
||||
|
||||
return mockSess, mockQueue
|
||||
}
|
||||
|
||||
func TestNewProber(t *testing.T) {
|
||||
mockSess, _ := createMockSession()
|
||||
|
||||
mod := NewProber(mockSess.Session)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewProber returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "net.probe" {
|
||||
t.Errorf("expected module name 'net.probe', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
// Check parameters
|
||||
params := []string{"net.probe.nbns", "net.probe.mdns", "net.probe.upnp", "net.probe.wsd", "net.probe.throttle"}
|
||||
for _, param := range params {
|
||||
if !mod.Session.Env.Has(param) {
|
||||
t.Errorf("parameter %s not registered", param)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProberConfigure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
params map[string]string
|
||||
expectErr bool
|
||||
expected struct {
|
||||
throttle int
|
||||
nbns bool
|
||||
mdns bool
|
||||
upnp bool
|
||||
wsd bool
|
||||
}
|
||||
}{
|
||||
{
|
||||
name: "default configuration",
|
||||
params: map[string]string{
|
||||
"net.probe.throttle": "10",
|
||||
"net.probe.nbns": "true",
|
||||
"net.probe.mdns": "true",
|
||||
"net.probe.upnp": "true",
|
||||
"net.probe.wsd": "true",
|
||||
},
|
||||
expectErr: false,
|
||||
expected: struct {
|
||||
throttle int
|
||||
nbns bool
|
||||
mdns bool
|
||||
upnp bool
|
||||
wsd bool
|
||||
}{10, true, true, true, true},
|
||||
},
|
||||
{
|
||||
name: "disabled probes",
|
||||
params: map[string]string{
|
||||
"net.probe.throttle": "5",
|
||||
"net.probe.nbns": "false",
|
||||
"net.probe.mdns": "false",
|
||||
"net.probe.upnp": "false",
|
||||
"net.probe.wsd": "false",
|
||||
},
|
||||
expectErr: false,
|
||||
expected: struct {
|
||||
throttle int
|
||||
nbns bool
|
||||
mdns bool
|
||||
upnp bool
|
||||
wsd bool
|
||||
}{5, false, false, false, false},
|
||||
},
|
||||
{
|
||||
name: "invalid throttle",
|
||||
params: map[string]string{
|
||||
"net.probe.throttle": "invalid",
|
||||
"net.probe.nbns": "true",
|
||||
"net.probe.mdns": "true",
|
||||
"net.probe.upnp": "true",
|
||||
"net.probe.wsd": "true",
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockSess, _ := createMockSession()
|
||||
mod := NewProber(mockSess.Session)
|
||||
|
||||
// Set parameters
|
||||
for k, v := range tt.params {
|
||||
mockSess.Env.Set(k, v)
|
||||
}
|
||||
|
||||
err := mod.Configure()
|
||||
|
||||
if tt.expectErr && err == nil {
|
||||
t.Error("expected error but got none")
|
||||
} else if !tt.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !tt.expectErr {
|
||||
if mod.throttle != tt.expected.throttle {
|
||||
t.Errorf("expected throttle %d, got %d", tt.expected.throttle, mod.throttle)
|
||||
}
|
||||
if mod.probes.NBNS != tt.expected.nbns {
|
||||
t.Errorf("expected NBNS %v, got %v", tt.expected.nbns, mod.probes.NBNS)
|
||||
}
|
||||
if mod.probes.MDNS != tt.expected.mdns {
|
||||
t.Errorf("expected MDNS %v, got %v", tt.expected.mdns, mod.probes.MDNS)
|
||||
}
|
||||
if mod.probes.UPNP != tt.expected.upnp {
|
||||
t.Errorf("expected UPNP %v, got %v", tt.expected.upnp, mod.probes.UPNP)
|
||||
}
|
||||
if mod.probes.WSD != tt.expected.wsd {
|
||||
t.Errorf("expected WSD %v, got %v", tt.expected.wsd, mod.probes.WSD)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MockProber wraps Prober to allow mocking probe methods
|
||||
type MockProber struct {
|
||||
*Prober
|
||||
nbnsCount *int32
|
||||
upnpCount *int32
|
||||
wsdCount *int32
|
||||
mockQueue *MockQueue
|
||||
}
|
||||
|
||||
func (m *MockProber) sendProbeNBNS(from net.IP, from_hw net.HardwareAddr, to net.IP) {
|
||||
atomic.AddInt32(m.nbnsCount, 1)
|
||||
m.mockQueue.Send([]byte(fmt.Sprintf("NBNS probe to %s", to)))
|
||||
}
|
||||
|
||||
func (m *MockProber) sendProbeUPNP(from net.IP, from_hw net.HardwareAddr) {
|
||||
atomic.AddInt32(m.upnpCount, 1)
|
||||
m.mockQueue.Send([]byte("UPNP probe"))
|
||||
}
|
||||
|
||||
func (m *MockProber) sendProbeWSD(from net.IP, from_hw net.HardwareAddr) {
|
||||
atomic.AddInt32(m.wsdCount, 1)
|
||||
m.mockQueue.Send([]byte("WSD probe"))
|
||||
}
|
||||
|
||||
func TestProberStartStop(t *testing.T) {
|
||||
mockSess, _ := createMockSession()
|
||||
mod := NewProber(mockSess.Session)
|
||||
|
||||
// Configure with fast throttle for testing
|
||||
mockSess.Env.Set("net.probe.throttle", "1")
|
||||
mockSess.Env.Set("net.probe.nbns", "true")
|
||||
mockSess.Env.Set("net.probe.mdns", "true")
|
||||
mockSess.Env.Set("net.probe.upnp", "true")
|
||||
mockSess.Env.Set("net.probe.wsd", "true")
|
||||
|
||||
// Start the prober
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start prober: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("Prober should be running after Start()")
|
||||
}
|
||||
|
||||
// Give it a moment to initialize
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Stop the prober
|
||||
err = mod.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stop prober: %v", err)
|
||||
}
|
||||
|
||||
if mod.Running() {
|
||||
t.Error("Prober should not be running after Stop()")
|
||||
}
|
||||
|
||||
// Since we can't easily mock the probe methods, we'll verify the module's state
|
||||
// and trust that the actual probe sending is tested in integration tests
|
||||
}
|
||||
|
||||
func TestProberMonitorMode(t *testing.T) {
|
||||
mockSess, _ := createMockSession()
|
||||
mod := NewProber(mockSess.Session)
|
||||
|
||||
// Set interface to monitor mode
|
||||
mockSess.Interface.IpAddress = network.MonitorModeAddress
|
||||
|
||||
// Start the prober
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start prober: %v", err)
|
||||
}
|
||||
|
||||
// Give it time to potentially start probing
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Stop the prober
|
||||
mod.Stop()
|
||||
|
||||
// In monitor mode, the prober should exit early without doing any work
|
||||
// We can't easily verify no probes were sent without mocking network calls,
|
||||
// but we can verify the module starts and stops correctly
|
||||
}
|
||||
|
||||
func TestProberHandlers(t *testing.T) {
|
||||
mockSess, _ := createMockSession()
|
||||
mod := NewProber(mockSess.Session)
|
||||
|
||||
// Test handlers
|
||||
handlers := mod.Handlers()
|
||||
|
||||
expectedHandlers := []string{"net.probe on", "net.probe off"}
|
||||
handlerMap := make(map[string]bool)
|
||||
|
||||
for _, h := range handlers {
|
||||
handlerMap[h.Name] = true
|
||||
}
|
||||
|
||||
for _, expected := range expectedHandlers {
|
||||
if !handlerMap[expected] {
|
||||
t.Errorf("Expected handler '%s' not found", expected)
|
||||
}
|
||||
}
|
||||
|
||||
// Test handler execution
|
||||
for _, h := range handlers {
|
||||
if h.Name == "net.probe on" {
|
||||
// Should start the module
|
||||
err := h.Exec([]string{})
|
||||
if err != nil {
|
||||
t.Errorf("Handler 'net.probe on' failed: %v", err)
|
||||
}
|
||||
if !mod.Running() {
|
||||
t.Error("Module should be running after 'net.probe on'")
|
||||
}
|
||||
mod.Stop()
|
||||
} else if h.Name == "net.probe off" {
|
||||
// Start first, then stop
|
||||
mod.Start()
|
||||
err := h.Exec([]string{})
|
||||
if err != nil {
|
||||
t.Errorf("Handler 'net.probe off' failed: %v", err)
|
||||
}
|
||||
if mod.Running() {
|
||||
t.Error("Module should not be running after 'net.probe off'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProberSelectiveProbes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
enabledProbes map[string]bool
|
||||
}{
|
||||
{
|
||||
name: "only NBNS",
|
||||
enabledProbes: map[string]bool{
|
||||
"nbns": true,
|
||||
"mdns": false,
|
||||
"upnp": false,
|
||||
"wsd": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only UPNP and WSD",
|
||||
enabledProbes: map[string]bool{
|
||||
"nbns": false,
|
||||
"mdns": false,
|
||||
"upnp": true,
|
||||
"wsd": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all probes enabled",
|
||||
enabledProbes: map[string]bool{
|
||||
"nbns": true,
|
||||
"mdns": true,
|
||||
"upnp": true,
|
||||
"wsd": true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockSess, _ := createMockSession()
|
||||
mod := NewProber(mockSess.Session)
|
||||
|
||||
// Configure probes
|
||||
mockSess.Env.Set("net.probe.throttle", "10")
|
||||
mockSess.Env.Set("net.probe.nbns", fmt.Sprintf("%v", tt.enabledProbes["nbns"]))
|
||||
mockSess.Env.Set("net.probe.mdns", fmt.Sprintf("%v", tt.enabledProbes["mdns"]))
|
||||
mockSess.Env.Set("net.probe.upnp", fmt.Sprintf("%v", tt.enabledProbes["upnp"]))
|
||||
mockSess.Env.Set("net.probe.wsd", fmt.Sprintf("%v", tt.enabledProbes["wsd"]))
|
||||
|
||||
// Configure and verify the settings
|
||||
err := mod.Configure()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to configure: %v", err)
|
||||
}
|
||||
|
||||
// Verify configuration
|
||||
if mod.probes.NBNS != tt.enabledProbes["nbns"] {
|
||||
t.Errorf("NBNS probe setting mismatch: expected %v, got %v",
|
||||
tt.enabledProbes["nbns"], mod.probes.NBNS)
|
||||
}
|
||||
if mod.probes.MDNS != tt.enabledProbes["mdns"] {
|
||||
t.Errorf("MDNS probe setting mismatch: expected %v, got %v",
|
||||
tt.enabledProbes["mdns"], mod.probes.MDNS)
|
||||
}
|
||||
if mod.probes.UPNP != tt.enabledProbes["upnp"] {
|
||||
t.Errorf("UPNP probe setting mismatch: expected %v, got %v",
|
||||
tt.enabledProbes["upnp"], mod.probes.UPNP)
|
||||
}
|
||||
if mod.probes.WSD != tt.enabledProbes["wsd"] {
|
||||
t.Errorf("WSD probe setting mismatch: expected %v, got %v",
|
||||
tt.enabledProbes["wsd"], mod.probes.WSD)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPRangeExpansion(t *testing.T) {
|
||||
// Test that we correctly iterate through the subnet
|
||||
cidr := "192.168.1.0/30" // Small subnet for testing
|
||||
list, err := iprange.Parse(cidr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse CIDR: %v", err)
|
||||
}
|
||||
|
||||
addresses := list.Expand()
|
||||
|
||||
// For /30, we should get 4 addresses
|
||||
expectedAddresses := []string{
|
||||
"192.168.1.0",
|
||||
"192.168.1.1",
|
||||
"192.168.1.2",
|
||||
"192.168.1.3",
|
||||
}
|
||||
|
||||
if len(addresses) != len(expectedAddresses) {
|
||||
t.Errorf("Expected %d addresses, got %d", len(expectedAddresses), len(addresses))
|
||||
}
|
||||
|
||||
for i, addr := range addresses {
|
||||
if addr.String() != expectedAddresses[i] {
|
||||
t.Errorf("Expected address %s at position %d, got %s", expectedAddresses[i], i, addr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks
|
||||
func BenchmarkProberConfiguration(b *testing.B) {
|
||||
mockSess, _ := createMockSession()
|
||||
|
||||
// Set up parameters
|
||||
mockSess.Env.Set("net.probe.throttle", "10")
|
||||
mockSess.Env.Set("net.probe.nbns", "true")
|
||||
mockSess.Env.Set("net.probe.mdns", "true")
|
||||
mockSess.Env.Set("net.probe.upnp", "true")
|
||||
mockSess.Env.Set("net.probe.wsd", "true")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod := NewProber(mockSess.Session)
|
||||
mod.Configure()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPRangeExpansion(b *testing.B) {
|
||||
cidr := "192.168.1.0/24"
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
list, _ := iprange.Parse(cidr)
|
||||
_ = list.Expand()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,644 +0,0 @@
|
|||
package net_recon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/modules/utils"
|
||||
"github.com/bettercap/bettercap/v2/network"
|
||||
"github.com/bettercap/bettercap/v2/packets"
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
"github.com/evilsocket/islazy/data"
|
||||
)
|
||||
|
||||
// Mock ArpUpdate function
|
||||
var mockArpUpdateFunc func(string) (network.ArpTable, error)
|
||||
|
||||
// Override the network.ArpUpdate function for testing
|
||||
func mockArpUpdate(iface string) (network.ArpTable, error) {
|
||||
if mockArpUpdateFunc != nil {
|
||||
return mockArpUpdateFunc(iface)
|
||||
}
|
||||
return make(network.ArpTable), nil
|
||||
}
|
||||
|
||||
// MockLAN implements a mock version of the LAN interface
|
||||
type MockLAN struct {
|
||||
sync.RWMutex
|
||||
hosts map[string]*network.Endpoint
|
||||
wasMissed map[string]bool
|
||||
addedHosts []string
|
||||
removedHosts []string
|
||||
}
|
||||
|
||||
func NewMockLAN() *MockLAN {
|
||||
return &MockLAN{
|
||||
hosts: make(map[string]*network.Endpoint),
|
||||
wasMissed: make(map[string]bool),
|
||||
addedHosts: []string{},
|
||||
removedHosts: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockLAN) AddIfNew(ip, mac string) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if _, exists := m.hosts[mac]; !exists {
|
||||
m.hosts[mac] = &network.Endpoint{
|
||||
IpAddress: ip,
|
||||
HwAddress: mac,
|
||||
FirstSeen: time.Now(),
|
||||
LastSeen: time.Now(),
|
||||
}
|
||||
m.addedHosts = append(m.addedHosts, mac)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockLAN) Remove(ip, mac string) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if _, exists := m.hosts[mac]; exists {
|
||||
delete(m.hosts, mac)
|
||||
m.removedHosts = append(m.removedHosts, mac)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockLAN) Clear() {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.hosts = make(map[string]*network.Endpoint)
|
||||
m.wasMissed = make(map[string]bool)
|
||||
m.addedHosts = []string{}
|
||||
m.removedHosts = []string{}
|
||||
}
|
||||
|
||||
func (m *MockLAN) EachHost(cb func(mac string, e *network.Endpoint)) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
for mac, host := range m.hosts {
|
||||
cb(mac, host)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockLAN) List() []*network.Endpoint {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
list := make([]*network.Endpoint, 0, len(m.hosts))
|
||||
for _, host := range m.hosts {
|
||||
list = append(list, host)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (m *MockLAN) WasMissed(mac string) bool {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
return m.wasMissed[mac]
|
||||
}
|
||||
|
||||
func (m *MockLAN) Get(mac string) *network.Endpoint {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
|
||||
return m.hosts[mac]
|
||||
}
|
||||
|
||||
// Create a mock session for testing
|
||||
func createMockSession() *session.Session {
|
||||
iface := &network.Endpoint{
|
||||
IpAddress: "192.168.1.100",
|
||||
HwAddress: "aa:bb:cc:dd:ee:ff",
|
||||
Hostname: "eth0",
|
||||
}
|
||||
iface.SetIP("192.168.1.100")
|
||||
iface.SetBits(24)
|
||||
|
||||
gateway := &network.Endpoint{
|
||||
IpAddress: "192.168.1.1",
|
||||
HwAddress: "11:22:33:44:55:66",
|
||||
}
|
||||
|
||||
// Create environment
|
||||
env, _ := session.NewEnvironment("")
|
||||
|
||||
sess := &session.Session{
|
||||
Interface: iface,
|
||||
Gateway: gateway,
|
||||
StartedAt: time.Now(),
|
||||
Active: true,
|
||||
Env: env,
|
||||
Queue: &packets.Queue{
|
||||
Traffic: sync.Map{},
|
||||
Stats: packets.Stats{},
|
||||
},
|
||||
Modules: make(session.ModuleList, 0),
|
||||
}
|
||||
|
||||
// Initialize the Events field with a mock EventPool
|
||||
sess.Events = session.NewEventPool(false, false)
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
func TestNewDiscovery(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
mod := NewDiscovery(sess)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewDiscovery returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "net.recon" {
|
||||
t.Errorf("expected module name 'net.recon', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.selector == nil {
|
||||
t.Error("selector should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunDiff(t *testing.T) {
|
||||
// Test the basic diff functionality with a simpler approach
|
||||
tests := []struct {
|
||||
name string
|
||||
initialHosts map[string]string // IP -> MAC
|
||||
arpTable network.ArpTable
|
||||
expectedAdded []string
|
||||
expectedRemoved []string
|
||||
}{
|
||||
{
|
||||
name: "no changes",
|
||||
initialHosts: map[string]string{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
"192.168.1.20": "bb:bb:bb:bb:bb:bb",
|
||||
},
|
||||
arpTable: network.ArpTable{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
"192.168.1.20": "bb:bb:bb:bb:bb:bb",
|
||||
},
|
||||
expectedAdded: []string{},
|
||||
expectedRemoved: []string{},
|
||||
},
|
||||
{
|
||||
name: "new host discovered",
|
||||
initialHosts: map[string]string{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
},
|
||||
arpTable: network.ArpTable{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
"192.168.1.20": "bb:bb:bb:bb:bb:bb",
|
||||
},
|
||||
expectedAdded: []string{"bb:bb:bb:bb:bb:bb"},
|
||||
expectedRemoved: []string{},
|
||||
},
|
||||
{
|
||||
name: "host disappeared",
|
||||
initialHosts: map[string]string{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
"192.168.1.20": "bb:bb:bb:bb:bb:bb",
|
||||
},
|
||||
arpTable: network.ArpTable{
|
||||
"192.168.1.10": "aa:aa:aa:aa:aa:aa",
|
||||
},
|
||||
expectedAdded: []string{},
|
||||
expectedRemoved: []string{"bb:bb:bb:bb:bb:bb"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
|
||||
// Track callbacks
|
||||
addedHosts := []string{}
|
||||
removedHosts := []string{}
|
||||
|
||||
newCb := func(e *network.Endpoint) {
|
||||
addedHosts = append(addedHosts, e.HwAddress)
|
||||
}
|
||||
|
||||
lostCb := func(e *network.Endpoint) {
|
||||
removedHosts = append(removedHosts, e.HwAddress)
|
||||
}
|
||||
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
sess.Lan = network.NewLAN(sess.Interface, sess.Gateway, aliases, newCb, lostCb)
|
||||
|
||||
mod := &Discovery{
|
||||
SessionModule: session.NewSessionModule("net.recon", sess),
|
||||
}
|
||||
|
||||
// Add initial hosts
|
||||
for ip, mac := range tt.initialHosts {
|
||||
sess.Lan.AddIfNew(ip, mac)
|
||||
}
|
||||
|
||||
// Reset tracking
|
||||
addedHosts = []string{}
|
||||
removedHosts = []string{}
|
||||
|
||||
// Add interface and gateway to ARP table to avoid them being removed
|
||||
finalArpTable := make(network.ArpTable)
|
||||
for k, v := range tt.arpTable {
|
||||
finalArpTable[k] = v
|
||||
}
|
||||
finalArpTable[sess.Interface.IpAddress] = sess.Interface.HwAddress
|
||||
finalArpTable[sess.Gateway.IpAddress] = sess.Gateway.HwAddress
|
||||
|
||||
// Run the diff multiple times to trigger actual removal (TTL countdown)
|
||||
for i := 0; i < network.LANDefaultttl+1; i++ {
|
||||
mod.runDiff(finalArpTable)
|
||||
}
|
||||
|
||||
// Check results
|
||||
if len(addedHosts) != len(tt.expectedAdded) {
|
||||
t.Errorf("expected %d added hosts, got %d. Added: %v", len(tt.expectedAdded), len(addedHosts), addedHosts)
|
||||
}
|
||||
|
||||
if len(removedHosts) != len(tt.expectedRemoved) {
|
||||
t.Errorf("expected %d removed hosts, got %d. Removed: %v", len(tt.expectedRemoved), len(removedHosts), removedHosts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigure(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
mod := NewDiscovery(sess)
|
||||
|
||||
err := mod.Configure()
|
||||
if err != nil {
|
||||
t.Errorf("Configure() returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartStop(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
sess.Lan = network.NewLAN(sess.Interface, sess.Gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
mod := NewDiscovery(sess)
|
||||
|
||||
// Test starting the module
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Errorf("Start() returned error: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("module should be running after Start()")
|
||||
}
|
||||
|
||||
// Let it run briefly
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Test stopping the module
|
||||
err = mod.Stop()
|
||||
if err != nil {
|
||||
t.Errorf("Stop() returned error: %v", err)
|
||||
}
|
||||
|
||||
if mod.Running() {
|
||||
t.Error("module should not be running after Stop()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestShowMethods(t *testing.T) {
|
||||
// Skip this test as it requires a full session with readline
|
||||
t.Skip("Skipping TestShowMethods as it requires readline initialization")
|
||||
}
|
||||
|
||||
func TestDoSelection(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
sess.Lan = network.NewLAN(sess.Interface, sess.Gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
// Add test endpoints
|
||||
sess.Lan.AddIfNew("192.168.1.10", "aa:aa:aa:aa:aa:aa")
|
||||
sess.Lan.AddIfNew("192.168.1.20", "bb:bb:bb:bb:bb:bb")
|
||||
sess.Lan.AddIfNew("192.168.1.30", "cc:cc:cc:cc:cc:cc")
|
||||
|
||||
// Get endpoints and set additional properties
|
||||
if e, found := sess.Lan.Get("aa:aa:aa:aa:aa:aa"); found {
|
||||
e.Hostname = "host1"
|
||||
e.Vendor = "Vendor1"
|
||||
}
|
||||
|
||||
if e, found := sess.Lan.Get("bb:bb:bb:bb:bb:bb"); found {
|
||||
e.Alias = "mydevice"
|
||||
e.Vendor = "Vendor2"
|
||||
}
|
||||
|
||||
mod := NewDiscovery(sess)
|
||||
mod.selector = utils.ViewSelectorFor(&mod.SessionModule, "net.show",
|
||||
[]string{"ip", "mac", "seen", "sent", "rcvd"}, "ip asc")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
arg string
|
||||
expectedCount int
|
||||
expectedIPs []string
|
||||
}{
|
||||
{
|
||||
name: "select all",
|
||||
arg: "",
|
||||
expectedCount: 3,
|
||||
},
|
||||
{
|
||||
name: "select by IP",
|
||||
arg: "192.168.1.10",
|
||||
expectedCount: 1,
|
||||
expectedIPs: []string{"192.168.1.10"},
|
||||
},
|
||||
{
|
||||
name: "select by MAC",
|
||||
arg: "aa:aa:aa:aa:aa:aa",
|
||||
expectedCount: 1,
|
||||
expectedIPs: []string{"192.168.1.10"},
|
||||
},
|
||||
{
|
||||
name: "select multiple by comma",
|
||||
arg: "192.168.1.10,192.168.1.20",
|
||||
expectedCount: 2,
|
||||
expectedIPs: []string{"192.168.1.10", "192.168.1.20"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err, targets := mod.doSelection(tt.arg)
|
||||
if err != nil {
|
||||
t.Errorf("doSelection returned error: %v", err)
|
||||
}
|
||||
|
||||
if len(targets) != tt.expectedCount {
|
||||
t.Errorf("expected %d targets, got %d", tt.expectedCount, len(targets))
|
||||
}
|
||||
|
||||
if tt.expectedIPs != nil {
|
||||
for _, expectedIP := range tt.expectedIPs {
|
||||
found := false
|
||||
for _, target := range targets {
|
||||
if target.IpAddress == expectedIP {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("expected to find IP %s in targets", expectedIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlers(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
sess.Lan = network.NewLAN(sess.Interface, sess.Gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
mod := NewDiscovery(sess)
|
||||
|
||||
handlers := []struct {
|
||||
name string
|
||||
handler string
|
||||
args []string
|
||||
setup func()
|
||||
validate func() error
|
||||
}{
|
||||
{
|
||||
name: "net.clear",
|
||||
handler: "net.clear",
|
||||
args: []string{},
|
||||
setup: func() {
|
||||
sess.Lan.AddIfNew("192.168.1.10", "aa:aa:aa:aa:aa:aa")
|
||||
},
|
||||
validate: func() error {
|
||||
// Check if hosts were cleared
|
||||
hosts := sess.Lan.List()
|
||||
if len(hosts) != 0 {
|
||||
return fmt.Errorf("expected empty hosts after clear, got %d", len(hosts))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range handlers {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.setup != nil {
|
||||
tt.setup()
|
||||
}
|
||||
|
||||
// Find and execute the handler
|
||||
found := false
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == tt.handler {
|
||||
found = true
|
||||
err := h.Exec(tt.args)
|
||||
if err != nil {
|
||||
t.Errorf("handler %s returned error: %v", tt.handler, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("handler %s not found", tt.handler)
|
||||
}
|
||||
|
||||
if tt.validate != nil {
|
||||
if err := tt.validate(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRow(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
sess.Lan = network.NewLAN(sess.Interface, sess.Gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
mod := NewDiscovery(sess)
|
||||
|
||||
// Test endpoint with metadata
|
||||
endpoint := &network.Endpoint{
|
||||
IpAddress: "192.168.1.10",
|
||||
HwAddress: "aa:aa:aa:aa:aa:aa",
|
||||
Hostname: "testhost",
|
||||
Vendor: "Test Vendor",
|
||||
FirstSeen: time.Now().Add(-time.Hour),
|
||||
LastSeen: time.Now(),
|
||||
Meta: network.NewMeta(),
|
||||
}
|
||||
endpoint.Meta.Set("key1", "value1")
|
||||
endpoint.Meta.Set("key2", "value2")
|
||||
|
||||
// Test without meta
|
||||
rows := mod.getRow(endpoint, false)
|
||||
if len(rows) != 1 {
|
||||
t.Errorf("expected 1 row without meta, got %d", len(rows))
|
||||
}
|
||||
if len(rows[0]) != 7 {
|
||||
t.Errorf("expected 7 columns, got %d", len(rows[0]))
|
||||
}
|
||||
|
||||
// Test with meta
|
||||
rows = mod.getRow(endpoint, true)
|
||||
if len(rows) != 2 { // One main row + one meta row per metadata entry
|
||||
t.Errorf("expected 2 rows with meta, got %d", len(rows))
|
||||
}
|
||||
|
||||
// Test interface endpoint
|
||||
ifaceEndpoint := sess.Interface
|
||||
rows = mod.getRow(ifaceEndpoint, false)
|
||||
if len(rows) != 1 {
|
||||
t.Errorf("expected 1 row for interface, got %d", len(rows))
|
||||
}
|
||||
|
||||
// Test gateway endpoint
|
||||
gatewayEndpoint := sess.Gateway
|
||||
rows = mod.getRow(gatewayEndpoint, false)
|
||||
if len(rows) != 1 {
|
||||
t.Errorf("expected 1 row for gateway, got %d", len(rows))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoFilter(t *testing.T) {
|
||||
sess := createMockSession()
|
||||
mod := NewDiscovery(sess)
|
||||
mod.selector = utils.ViewSelectorFor(&mod.SessionModule, "net.show",
|
||||
[]string{"ip", "mac", "seen", "sent", "rcvd"}, "ip asc")
|
||||
|
||||
// Test that doFilter behavior matches the actual implementation
|
||||
// When Expression is nil, it returns true (no filtering)
|
||||
// When Expression is set, it matches against any of the fields
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
filter string
|
||||
endpoint *network.Endpoint
|
||||
shouldMatch bool
|
||||
}{
|
||||
{
|
||||
name: "no filter",
|
||||
filter: "",
|
||||
endpoint: &network.Endpoint{
|
||||
IpAddress: "192.168.1.10",
|
||||
Meta: network.NewMeta(),
|
||||
},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "ip filter match",
|
||||
filter: "192.168",
|
||||
endpoint: &network.Endpoint{
|
||||
IpAddress: "192.168.1.10",
|
||||
Meta: network.NewMeta(),
|
||||
},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "mac filter match",
|
||||
filter: "aa:bb",
|
||||
endpoint: &network.Endpoint{
|
||||
IpAddress: "192.168.1.10",
|
||||
HwAddress: "aa:bb:cc:dd:ee:ff",
|
||||
Meta: network.NewMeta(),
|
||||
},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "hostname filter match",
|
||||
filter: "myhost",
|
||||
endpoint: &network.Endpoint{
|
||||
IpAddress: "192.168.1.10",
|
||||
Hostname: "myhost.local",
|
||||
Meta: network.NewMeta(),
|
||||
},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "no match - testing unique string",
|
||||
filter: "xyz123nomatch",
|
||||
endpoint: &network.Endpoint{
|
||||
IpAddress: "192.168.1.10",
|
||||
Ip6Address: "",
|
||||
HwAddress: "aa:bb:cc:dd:ee:ff",
|
||||
Hostname: "host.local",
|
||||
Alias: "",
|
||||
Vendor: "",
|
||||
Meta: network.NewMeta(),
|
||||
},
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Reset selector for each test
|
||||
// Set the parameter value that Update() will read
|
||||
sess.Env.Set("net.show.filter", tt.filter)
|
||||
mod.selector.Expression = nil
|
||||
|
||||
// Update will read from the parameter
|
||||
err := mod.selector.Update()
|
||||
if err != nil {
|
||||
t.Fatalf("selector.Update() failed: %v", err)
|
||||
}
|
||||
|
||||
result := mod.doFilter(tt.endpoint)
|
||||
if result != tt.shouldMatch {
|
||||
if mod.selector.Expression != nil {
|
||||
t.Errorf("expected doFilter to return %v, got %v. Regex: %s", tt.shouldMatch, result, mod.selector.Expression.String())
|
||||
} else {
|
||||
t.Errorf("expected doFilter to return %v, got %v. Expression is nil", tt.shouldMatch, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark the runDiff method
|
||||
func BenchmarkRunDiff(b *testing.B) {
|
||||
sess := createMockSession()
|
||||
aliases, _ := data.NewUnsortedKV("", 0)
|
||||
sess.Lan = network.NewLAN(sess.Interface, sess.Gateway, aliases, func(e *network.Endpoint) {}, func(e *network.Endpoint) {})
|
||||
|
||||
mod := &Discovery{
|
||||
SessionModule: session.NewSessionModule("net.recon", sess),
|
||||
}
|
||||
|
||||
// Create a large ARP table
|
||||
arpTable := make(network.ArpTable)
|
||||
for i := 0; i < 100; i++ {
|
||||
ip := fmt.Sprintf("192.168.1.%d", i)
|
||||
mac := fmt.Sprintf("aa:bb:cc:dd:%02x:%02x", i/256, i%256)
|
||||
arpTable[ip] = mac
|
||||
|
||||
// Add half to the existing LAN
|
||||
if i < 50 {
|
||||
sess.Lan.AddIfNew(ip, mac)
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod.runDiff(arpTable)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,11 +59,6 @@ func NewSniffer(s *session.Session) *Sniffer {
|
|||
"",
|
||||
"If set, the sniffer will read from this pcap file instead of the current interface."))
|
||||
|
||||
mod.AddParam(session.NewStringParameter("net.sniff.interface",
|
||||
"",
|
||||
"",
|
||||
"Interface to sniff on."))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("net.sniff stats", "",
|
||||
"Print sniffer session configuration and statistics.",
|
||||
func(args []string) error {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import (
|
|||
|
||||
type SnifferContext struct {
|
||||
Handle *pcap.Handle
|
||||
Interface string
|
||||
Source string
|
||||
DumpLocal bool
|
||||
Verbose bool
|
||||
|
|
@ -38,22 +37,13 @@ func (mod *Sniffer) GetContext() (error, *SnifferContext) {
|
|||
return err, ctx
|
||||
}
|
||||
|
||||
if err, ctx.Interface = mod.StringParam("net.sniff.interface"); err != nil {
|
||||
return err, ctx
|
||||
}
|
||||
|
||||
if ctx.Interface == "" {
|
||||
ctx.Interface = mod.Session.Interface.Name()
|
||||
}
|
||||
|
||||
if ctx.Source == "" {
|
||||
/*
|
||||
* We don't want to pcap.BlockForever otherwise pcap_close(handle)
|
||||
* could hang waiting for a timeout to expire ...
|
||||
*/
|
||||
|
||||
readTimeout := 500 * time.Millisecond
|
||||
if ctx.Handle, err = network.CaptureWithTimeout(ctx.Interface, readTimeout); err != nil {
|
||||
if ctx.Handle, err = network.CaptureWithTimeout(mod.Session.Interface.Name(), readTimeout); err != nil {
|
||||
return err, ctx
|
||||
}
|
||||
} else {
|
||||
|
|
@ -104,8 +94,6 @@ func (mod *Sniffer) GetContext() (error, *SnifferContext) {
|
|||
func NewSnifferContext() *SnifferContext {
|
||||
return &SnifferContext{
|
||||
Handle: nil,
|
||||
Interface: "",
|
||||
Source: "",
|
||||
DumpLocal: false,
|
||||
Verbose: false,
|
||||
Filter: "",
|
||||
|
|
@ -127,8 +115,7 @@ var (
|
|||
)
|
||||
|
||||
func (c *SnifferContext) Log(sess *session.Session) {
|
||||
log.Info("Interface : %s", tui.Bold(c.Interface))
|
||||
log.Info("Skip local packets : %s", yn[!c.DumpLocal])
|
||||
log.Info("Skip local packets : %s", yn[c.DumpLocal])
|
||||
log.Info("Verbose : %s", yn[c.Verbose])
|
||||
log.Info("BPF Filter : '%s'", tui.Yellow(c.Filter))
|
||||
log.Info("Regular expression : '%s'", tui.Yellow(c.Expression))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
|
@ -50,7 +50,7 @@ func toSerializableRequest(req *http.Request) HTTPRequest {
|
|||
body := []byte(nil)
|
||||
ctype := "?"
|
||||
if req.Body != nil {
|
||||
body, _ = io.ReadAll(req.Body)
|
||||
body, _ = ioutil.ReadAll(req.Body)
|
||||
}
|
||||
|
||||
for name, values := range req.Header {
|
||||
|
|
@ -90,7 +90,7 @@ func toSerializableResponse(res *http.Response) HTTPResponse {
|
|||
}
|
||||
|
||||
if res.Body != nil {
|
||||
body, _ = io.ReadAll(res.Body)
|
||||
body, _ = ioutil.ReadAll(res.Body)
|
||||
}
|
||||
|
||||
// attempt decompression, but since this has been parsed by just
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ type PacketProxy struct {
|
|||
rule string
|
||||
queue *nfqueue.Nfqueue
|
||||
queueNum int
|
||||
queueCb func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int
|
||||
queueCb nfqueue.HookFunc
|
||||
pluginPath string
|
||||
plugin *plugin.Plugin
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ func (mod *PacketProxy) Configure() (err error) {
|
|||
return
|
||||
} else if sym, err = mod.plugin.Lookup("OnPacket"); err != nil {
|
||||
return
|
||||
} else if mod.queueCb, ok = sym.(func(q *nfqueue.Nfqueue, a nfqueue.Attribute) int); !ok {
|
||||
} else if mod.queueCb, ok = sym.(func(nfqueue.Attribute) int); !ok {
|
||||
return fmt.Errorf("Symbol OnPacket is not a valid callback function.")
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ func (mod *PacketProxy) Configure() (err error) {
|
|||
// CGO callback ... ¯\_(ツ)_/¯
|
||||
func dummyCallback(attribute nfqueue.Attribute) int {
|
||||
if mod.queueCb != nil {
|
||||
return mod.queueCb(mod.queue, attribute)
|
||||
return mod.queueCb(attribute)
|
||||
} else {
|
||||
id := *attribute.PacketID
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package tcp_proxy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
|
|
@ -56,36 +55,12 @@ func (s *TcpProxyScript) OnData(from, to net.Addr, data []byte, callback func(ca
|
|||
log.Error("error while executing onData callback: %s", err)
|
||||
return nil
|
||||
} else if ret != nil {
|
||||
return toByteArray(ret)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toByteArray(ret interface{}) []byte {
|
||||
// this approach is a bit hacky but it handles all cases
|
||||
|
||||
// serialize ret to JSON
|
||||
if jsonData, err := json.Marshal(ret); err == nil {
|
||||
// attempt to deserialize as []float64
|
||||
var back2Array []float64
|
||||
if err := json.Unmarshal(jsonData, &back2Array); err == nil {
|
||||
result := make([]byte, len(back2Array))
|
||||
for i, num := range back2Array {
|
||||
if num >= 0 && num <= 255 {
|
||||
result[i] = byte(num)
|
||||
} else {
|
||||
log.Error("array element at index %d is not a valid byte value %d", i, num)
|
||||
return nil
|
||||
}
|
||||
array, ok := ret.([]byte)
|
||||
if !ok {
|
||||
log.Error("error while casting exported value to array of byte: value = %+v", ret)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
log.Error("failed to deserialize %+v to []float64: %v", ret, err)
|
||||
return array
|
||||
}
|
||||
} else {
|
||||
log.Error("failed to serialize %+v to JSON: %v", ret, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,169 +0,0 @@
|
|||
package tcp_proxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/evilsocket/islazy/plugin"
|
||||
)
|
||||
|
||||
func TestOnData_NoReturn(t *testing.T) {
|
||||
jsCode := `
|
||||
function onData(from, to, data, callback) {
|
||||
// don't return anything
|
||||
}
|
||||
`
|
||||
|
||||
plug, err := plugin.Parse(jsCode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse plugin: %v", err)
|
||||
}
|
||||
|
||||
script := &TcpProxyScript{
|
||||
Plugin: plug,
|
||||
doOnData: plug.HasFunc("onData"),
|
||||
}
|
||||
|
||||
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
|
||||
data := []byte("test data")
|
||||
|
||||
result := script.OnData(from, to, data, nil)
|
||||
if result != nil {
|
||||
t.Errorf("Expected nil result when callback returns nothing, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnData_ReturnsArrayOfIntegers(t *testing.T) {
|
||||
jsCode := `
|
||||
function onData(from, to, data, callback) {
|
||||
// Return modified data as array of integers
|
||||
return [72, 101, 108, 108, 111]; // "Hello" in ASCII
|
||||
}
|
||||
`
|
||||
|
||||
plug, err := plugin.Parse(jsCode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse plugin: %v", err)
|
||||
}
|
||||
|
||||
script := &TcpProxyScript{
|
||||
Plugin: plug,
|
||||
doOnData: plug.HasFunc("onData"),
|
||||
}
|
||||
|
||||
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
|
||||
data := []byte("test data")
|
||||
|
||||
result := script.OnData(from, to, data, nil)
|
||||
expected := []byte("Hello")
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("Expected non-nil result when callback returns array of integers")
|
||||
}
|
||||
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
|
||||
}
|
||||
|
||||
for i, b := range result {
|
||||
if b != expected[i] {
|
||||
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnData_ReturnsDynamicArray(t *testing.T) {
|
||||
jsCode := `
|
||||
function onData(from, to, data, callback) {
|
||||
var result = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
result.push((data[i] + 1) % 256);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
`
|
||||
|
||||
plug, err := plugin.Parse(jsCode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse plugin: %v", err)
|
||||
}
|
||||
|
||||
script := &TcpProxyScript{
|
||||
Plugin: plug,
|
||||
doOnData: plug.HasFunc("onData"),
|
||||
}
|
||||
|
||||
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.2"), Port: 5678}
|
||||
data := []byte{10, 20, 30, 40, 255}
|
||||
|
||||
result := script.OnData(from, to, data, nil)
|
||||
expected := []byte{11, 21, 31, 41, 0} // 255 + 1 = 256 % 256 = 0
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("Expected non-nil result when callback returns array of integers")
|
||||
}
|
||||
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
|
||||
}
|
||||
|
||||
for i, b := range result {
|
||||
if b != expected[i] {
|
||||
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnData_ReturnsMixedArray(t *testing.T) {
|
||||
jsCode := `
|
||||
function charToInt(value) {
|
||||
return value.charCodeAt()
|
||||
}
|
||||
|
||||
function onData(from, to, data) {
|
||||
st_data = String.fromCharCode.apply(null, data)
|
||||
if( st_data.indexOf("mysearch") != -1 ) {
|
||||
payload = "mypayload";
|
||||
st_data = st_data.replace("mysearch", payload);
|
||||
res_int_arr = st_data.split("").map(charToInt) // []uint16
|
||||
res_int_arr[0] = payload.length + 1; // first index is float64 and rest []uint16
|
||||
return res_int_arr;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
`
|
||||
|
||||
plug, err := plugin.Parse(jsCode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse plugin: %v", err)
|
||||
}
|
||||
|
||||
script := &TcpProxyScript{
|
||||
Plugin: plug,
|
||||
doOnData: plug.HasFunc("onData"),
|
||||
}
|
||||
|
||||
from := &net.TCPAddr{IP: net.ParseIP("192.168.1.1"), Port: 1234}
|
||||
to := &net.TCPAddr{IP: net.ParseIP("192.168.1.6"), Port: 5678}
|
||||
data := []byte("Hello mysearch world")
|
||||
|
||||
result := script.OnData(from, to, data, nil)
|
||||
expected := []byte("\x0aello mypayload world")
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("Expected non-nil result when callback returns array of integers")
|
||||
}
|
||||
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("Expected result length %d, got %d", len(expected), len(result))
|
||||
}
|
||||
|
||||
for i, b := range result {
|
||||
if b != expected[i] {
|
||||
t.Errorf("Expected byte at index %d to be %d, got %d", i, expected[i], b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +1,43 @@
|
|||
package ticker
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
||||
type Params struct {
|
||||
Period time.Duration
|
||||
Commands []string
|
||||
Running bool
|
||||
}
|
||||
|
||||
type Ticker struct {
|
||||
session.SessionModule
|
||||
|
||||
main Params
|
||||
named map[string]*Params
|
||||
Period time.Duration
|
||||
Commands []string
|
||||
}
|
||||
|
||||
func NewTicker(s *session.Session) *Ticker {
|
||||
mod := &Ticker{
|
||||
SessionModule: session.NewSessionModule("ticker", s),
|
||||
named: make(map[string]*Params),
|
||||
}
|
||||
|
||||
mod.AddParam(session.NewStringParameter("ticker.commands",
|
||||
"clear; net.show; events.show 20",
|
||||
"",
|
||||
"List of commands for the main ticker separated by a ;"))
|
||||
"List of commands separated by a ;"))
|
||||
|
||||
mod.AddParam(session.NewIntParameter("ticker.period",
|
||||
"1",
|
||||
"Main ticker period in seconds"))
|
||||
"Ticker period in seconds"))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("ticker on", "",
|
||||
"Start the main ticker.",
|
||||
"Start the ticker.",
|
||||
func(args []string) error {
|
||||
return mod.Start()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("ticker off", "",
|
||||
"Stop the main ticker.",
|
||||
"Stop the ticker.",
|
||||
func(args []string) error {
|
||||
return mod.Stop()
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("ticker.create <name> <period> <commands>",
|
||||
`(?i)^ticker\.create\s+([^\s]+)\s+(\d+)\s+(.+)$`,
|
||||
"Create and start a named ticker.",
|
||||
func(args []string) error {
|
||||
if period, err := strconv.Atoi(args[1]); err != nil {
|
||||
return err
|
||||
} else {
|
||||
return mod.createNamed(args[0], period, args[2])
|
||||
}
|
||||
}))
|
||||
|
||||
mod.AddHandler(session.NewModuleHandler("ticker.destroy <name>",
|
||||
`(?i)^ticker\.destroy\s+([^\s]+)$`,
|
||||
"Stop a named ticker.",
|
||||
func(args []string) error {
|
||||
return mod.destroyNamed(args[0])
|
||||
}))
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
|
|
@ -94,93 +66,38 @@ func (mod *Ticker) Configure() error {
|
|||
return err
|
||||
}
|
||||
|
||||
mod.main = Params{
|
||||
Commands: session.ParseCommands(commands),
|
||||
Period: time.Duration(period) * time.Second,
|
||||
Running: true,
|
||||
}
|
||||
mod.Commands = session.ParseCommands(commands)
|
||||
mod.Period = time.Duration(period) * time.Second
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type TickEvent struct{}
|
||||
|
||||
func (mod *Ticker) worker(name string, params *Params) {
|
||||
isMain := name == "main"
|
||||
eventName := "tick"
|
||||
|
||||
if isMain {
|
||||
mod.Info("main ticker running with period %.fs", params.Period.Seconds())
|
||||
} else {
|
||||
eventName = "ticker." + name
|
||||
mod.Info("ticker '%s' running with period %.fs", name, params.Period.Seconds())
|
||||
}
|
||||
|
||||
tick := time.NewTicker(params.Period)
|
||||
for range tick.C {
|
||||
if !params.Running {
|
||||
break
|
||||
}
|
||||
|
||||
session.I.Events.Add(eventName, TickEvent{})
|
||||
for _, cmd := range params.Commands {
|
||||
if err := mod.Session.Run(cmd); err != nil {
|
||||
mod.Error("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isMain {
|
||||
mod.Info("main ticker stopped")
|
||||
} else {
|
||||
mod.Info("ticker '%s' stopped", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (mod *Ticker) Start() error {
|
||||
if err := mod.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mod.SetRunning(true, func() {
|
||||
mod.worker("main", &mod.main)
|
||||
mod.Info("running with period %.fs", mod.Period.Seconds())
|
||||
tick := time.NewTicker(mod.Period)
|
||||
for range tick.C {
|
||||
if !mod.Running() {
|
||||
break
|
||||
}
|
||||
|
||||
session.I.Events.Add("tick", TickEvent{})
|
||||
|
||||
for _, cmd := range mod.Commands {
|
||||
if err := mod.Session.Run(cmd); err != nil {
|
||||
mod.Error("%s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (mod *Ticker) Stop() error {
|
||||
mod.main.Running = false
|
||||
for _, params := range mod.named {
|
||||
params.Running = false
|
||||
}
|
||||
|
||||
return mod.SetRunning(false, nil)
|
||||
}
|
||||
|
||||
func (mod *Ticker) createNamed(name string, period int, commands string) error {
|
||||
if _, found := mod.named[name]; found {
|
||||
return errors.New("ticker '" + name + "' already exists")
|
||||
}
|
||||
|
||||
params := &Params{
|
||||
Commands: session.ParseCommands(commands),
|
||||
Period: time.Duration(period) * time.Second,
|
||||
Running: true,
|
||||
}
|
||||
|
||||
mod.named[name] = params
|
||||
|
||||
go mod.worker(name, params)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mod *Ticker) destroyNamed(name string) error {
|
||||
if _, found := mod.named[name]; !found {
|
||||
return errors.New("ticker '" + name + "' not found")
|
||||
}
|
||||
|
||||
mod.named[name].Running = false
|
||||
delete(mod.named, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,413 +0,0 @@
|
|||
package ticker
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/v2/session"
|
||||
)
|
||||
|
||||
var (
|
||||
testSession *session.Session
|
||||
sessionOnce sync.Once
|
||||
)
|
||||
|
||||
func createMockSession(t *testing.T) *session.Session {
|
||||
sessionOnce.Do(func() {
|
||||
var err error
|
||||
testSession, err = session.New()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
}
|
||||
})
|
||||
return testSession
|
||||
}
|
||||
|
||||
func TestNewTicker(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
if mod == nil {
|
||||
t.Fatal("NewTicker returned nil")
|
||||
}
|
||||
|
||||
if mod.Name() != "ticker" {
|
||||
t.Errorf("Expected name 'ticker', got '%s'", mod.Name())
|
||||
}
|
||||
|
||||
if mod.Author() != "Simone Margaritelli <evilsocket@gmail.com>" {
|
||||
t.Errorf("Unexpected author: %s", mod.Author())
|
||||
}
|
||||
|
||||
if mod.Description() == "" {
|
||||
t.Error("Empty description")
|
||||
}
|
||||
|
||||
// Check parameters exist
|
||||
if err, _ := mod.StringParam("ticker.commands"); err != nil {
|
||||
t.Error("ticker.commands parameter not found")
|
||||
}
|
||||
|
||||
if err, _ := mod.IntParam("ticker.period"); err != nil {
|
||||
t.Error("ticker.period parameter not found")
|
||||
}
|
||||
|
||||
// Check handlers - only check the main ones since create/destroy have regex patterns
|
||||
handlers := []string{"ticker on", "ticker off"}
|
||||
for _, handler := range handlers {
|
||||
found := false
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == handler {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Handler '%s' not found", handler)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we have handlers for create and destroy (they have regex patterns)
|
||||
hasCreate := false
|
||||
hasDestroy := false
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == "ticker.create <name> <period> <commands>" {
|
||||
hasCreate = true
|
||||
} else if h.Name == "ticker.destroy <name>" {
|
||||
hasDestroy = true
|
||||
}
|
||||
}
|
||||
if !hasCreate {
|
||||
t.Error("ticker.create handler not found")
|
||||
}
|
||||
if !hasDestroy {
|
||||
t.Error("ticker.destroy handler not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerConfigure(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Test configure before start
|
||||
if err := mod.Configure(); err != nil {
|
||||
t.Errorf("Configure failed: %v", err)
|
||||
}
|
||||
|
||||
// Check main params were set
|
||||
if mod.main.Period == 0 {
|
||||
t.Error("Period not set")
|
||||
}
|
||||
|
||||
if len(mod.main.Commands) == 0 {
|
||||
t.Error("Commands not set")
|
||||
}
|
||||
|
||||
if !mod.main.Running {
|
||||
t.Error("Running flag not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerStartStop(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Set a short period for testing using session environment
|
||||
mod.Session.Env.Set("ticker.period", "1")
|
||||
mod.Session.Env.Set("ticker.commands", "help")
|
||||
|
||||
// Start ticker
|
||||
if err := mod.Start(); err != nil {
|
||||
t.Fatalf("Failed to start ticker: %v", err)
|
||||
}
|
||||
|
||||
if !mod.Running() {
|
||||
t.Error("Ticker should be running")
|
||||
}
|
||||
|
||||
// Let it run briefly
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Stop ticker
|
||||
if err := mod.Stop(); err != nil {
|
||||
t.Fatalf("Failed to stop ticker: %v", err)
|
||||
}
|
||||
|
||||
if mod.Running() {
|
||||
t.Error("Ticker should not be running")
|
||||
}
|
||||
|
||||
if mod.main.Running {
|
||||
t.Error("Main ticker should not be running")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerAlreadyStarted(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Start ticker
|
||||
if err := mod.Start(); err != nil {
|
||||
t.Fatalf("Failed to start ticker: %v", err)
|
||||
}
|
||||
|
||||
// Try to configure while running
|
||||
if err := mod.Configure(); err == nil {
|
||||
t.Error("Configure should fail when already running")
|
||||
}
|
||||
|
||||
// Stop ticker
|
||||
mod.Stop()
|
||||
}
|
||||
|
||||
func TestTickerNamedOperations(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Create named ticker
|
||||
name := "test_ticker"
|
||||
if err := mod.createNamed(name, 1, "help"); err != nil {
|
||||
t.Fatalf("Failed to create named ticker: %v", err)
|
||||
}
|
||||
|
||||
// Check it was created
|
||||
if _, found := mod.named[name]; !found {
|
||||
t.Error("Named ticker not found in map")
|
||||
}
|
||||
|
||||
// Try to create duplicate
|
||||
if err := mod.createNamed(name, 1, "help"); err == nil {
|
||||
t.Error("Should not allow duplicate named ticker")
|
||||
}
|
||||
|
||||
// Let it run briefly
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Destroy named ticker
|
||||
if err := mod.destroyNamed(name); err != nil {
|
||||
t.Fatalf("Failed to destroy named ticker: %v", err)
|
||||
}
|
||||
|
||||
// Check it was removed
|
||||
if _, found := mod.named[name]; found {
|
||||
t.Error("Named ticker still in map after destroy")
|
||||
}
|
||||
|
||||
// Try to destroy non-existent
|
||||
if err := mod.destroyNamed("nonexistent"); err == nil {
|
||||
t.Error("Should fail when destroying non-existent ticker")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerHandlers(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
handler string
|
||||
regex string
|
||||
args []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "ticker on",
|
||||
handler: "ticker on",
|
||||
args: []string{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ticker off",
|
||||
handler: "ticker off",
|
||||
args: []string{},
|
||||
wantErr: true, // ticker off will fail if not running
|
||||
},
|
||||
{
|
||||
name: "ticker.create valid",
|
||||
handler: "ticker.create <name> <period> <commands>",
|
||||
args: []string{"myticker", "2", "help; events.show"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ticker.create invalid period",
|
||||
handler: "ticker.create <name> <period> <commands>",
|
||||
args: []string{"myticker", "notanumber", "help"},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "ticker.destroy",
|
||||
handler: "ticker.destroy <name>",
|
||||
args: []string{"myticker"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Find the handler
|
||||
var handler *session.ModuleHandler
|
||||
for _, h := range mod.Handlers() {
|
||||
if h.Name == tt.handler {
|
||||
handler = &h
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if handler == nil {
|
||||
t.Fatalf("Handler '%s' not found", tt.handler)
|
||||
}
|
||||
|
||||
// Create ticker if needed for destroy test
|
||||
if tt.handler == "ticker.destroy <name>" && len(tt.args) > 0 && tt.args[0] == "myticker" {
|
||||
mod.createNamed("myticker", 1, "help")
|
||||
}
|
||||
|
||||
// Execute handler
|
||||
err := handler.Exec(tt.args)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Handler execution error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if tt.handler == "ticker on" || tt.handler == "ticker.create <name> <period> <commands>" {
|
||||
mod.Stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerWorker(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Create params for testing
|
||||
params := &Params{
|
||||
Commands: []string{"help"},
|
||||
Period: 100 * time.Millisecond,
|
||||
Running: true,
|
||||
}
|
||||
|
||||
// Start worker in goroutine
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
mod.worker("test", params)
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Let it tick at least once
|
||||
time.Sleep(150 * time.Millisecond)
|
||||
|
||||
// Stop the worker
|
||||
params.Running = false
|
||||
|
||||
// Wait for worker to finish
|
||||
select {
|
||||
case <-done:
|
||||
// Worker finished successfully
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Error("Worker did not stop in time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerParams(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Test setting invalid period
|
||||
mod.Session.Env.Set("ticker.period", "invalid")
|
||||
if err := mod.Configure(); err == nil {
|
||||
t.Error("Configure should fail with invalid period")
|
||||
}
|
||||
|
||||
// Test empty commands
|
||||
mod.Session.Env.Set("ticker.period", "1")
|
||||
mod.Session.Env.Set("ticker.commands", "")
|
||||
if err := mod.Configure(); err != nil {
|
||||
t.Errorf("Configure should work with empty commands: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickerMultipleNamed(t *testing.T) {
|
||||
s := createMockSession(t)
|
||||
mod := NewTicker(s)
|
||||
|
||||
// Start the ticker first
|
||||
if err := mod.Start(); err != nil {
|
||||
t.Fatalf("Failed to start ticker: %v", err)
|
||||
}
|
||||
|
||||
// Create multiple named tickers
|
||||
names := []string{"ticker1", "ticker2", "ticker3"}
|
||||
for _, name := range names {
|
||||
if err := mod.createNamed(name, 1, "help"); err != nil {
|
||||
t.Errorf("Failed to create ticker '%s': %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check all were created
|
||||
if len(mod.named) != len(names) {
|
||||
t.Errorf("Expected %d named tickers, got %d", len(names), len(mod.named))
|
||||
}
|
||||
|
||||
// Stop all via Stop()
|
||||
if err := mod.Stop(); err != nil {
|
||||
t.Fatalf("Failed to stop: %v", err)
|
||||
}
|
||||
|
||||
// Check all were stopped
|
||||
for name, params := range mod.named {
|
||||
if params.Running {
|
||||
t.Errorf("Ticker '%s' still running after Stop()", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickEvent(t *testing.T) {
|
||||
// Simple test for TickEvent struct
|
||||
event := TickEvent{}
|
||||
// TickEvent is empty, just ensure it can be created
|
||||
_ = event
|
||||
}
|
||||
|
||||
// Benchmark tests
|
||||
func BenchmarkTickerCreate(b *testing.B) {
|
||||
// Use existing session to avoid flag redefinition
|
||||
s := testSession
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = session.New()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
testSession = s
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod := NewTicker(s)
|
||||
_ = mod
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTickerStartStop(b *testing.B) {
|
||||
// Use existing session to avoid flag redefinition
|
||||
s := testSession
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = session.New()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
testSession = s
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
mod := NewTicker(s)
|
||||
// Set period parameter
|
||||
mod.Session.Env.Set("ticker.period", "1")
|
||||
mod.Start()
|
||||
mod.Stop()
|
||||
}
|
||||
}
|
||||
1
modules/ui/ui
Submodule
1
modules/ui/ui
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c671b0be70074e918788fe9f9fd19a5d35bf79dc
|
||||
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
//go:embed ui
|
||||
//go:embed ui/dist/ui
|
||||
web embed.FS
|
||||
)
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ func (mod *UIModule) Configure() (err error) {
|
|||
|
||||
mod.server.Addr = fmt.Sprintf("%s:%d", ip, port)
|
||||
|
||||
dist, _ := fs.Sub(web, "ui")
|
||||
dist, _ := fs.Sub(web, "ui/dist/ui")
|
||||
mod.server.Handler = http.FileServer(http.FS(dist))
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
Font Awesome Free License
|
||||
-------------------------
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
|
||||
packaged as SVG and JS file types.
|
||||
|
||||
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
# Attribution
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
# Brand Icons
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
4329
modules/ui/ui/assets/fontawesome/css/all.css
vendored
4329
modules/ui/ui/assets/fontawesome/css/all.css
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
14
modules/ui/ui/assets/fontawesome/css/brands.css
vendored
14
modules/ui/ui/assets/fontawesome/css/brands.css
vendored
|
|
@ -1,14 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Brands';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-display: auto;
|
||||
src: url("../webfonts/fa-brands-400.eot");
|
||||
src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
|
||||
|
||||
.fab {
|
||||
font-family: 'Font Awesome 5 Brands'; }
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:normal;font-display:auto;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}
|
||||
4296
modules/ui/ui/assets/fontawesome/css/fontawesome.css
vendored
4296
modules/ui/ui/assets/fontawesome/css/fontawesome.css
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
15
modules/ui/ui/assets/fontawesome/css/regular.css
vendored
15
modules/ui/ui/assets/fontawesome/css/regular.css
vendored
|
|
@ -1,15 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: auto;
|
||||
src: url("../webfonts/fa-regular-400.eot");
|
||||
src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
|
||||
|
||||
.far {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 400; }
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400}
|
||||
16
modules/ui/ui/assets/fontawesome/css/solid.css
vendored
16
modules/ui/ui/assets/fontawesome/css/solid.css
vendored
|
|
@ -1,16 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: auto;
|
||||
src: url("../webfonts/fa-solid-900.eot");
|
||||
src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg"); }
|
||||
|
||||
.fa,
|
||||
.fas {
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900; }
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900}
|
||||
346
modules/ui/ui/assets/fontawesome/css/svg-with-js.css
vendored
346
modules/ui/ui/assets/fontawesome/css/svg-with-js.css
vendored
|
|
@ -1,346 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
svg:not(:root).svg-inline--fa {
|
||||
overflow: visible; }
|
||||
|
||||
.svg-inline--fa {
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -.125em; }
|
||||
.svg-inline--fa.fa-lg {
|
||||
vertical-align: -.225em; }
|
||||
.svg-inline--fa.fa-w-1 {
|
||||
width: 0.0625em; }
|
||||
.svg-inline--fa.fa-w-2 {
|
||||
width: 0.125em; }
|
||||
.svg-inline--fa.fa-w-3 {
|
||||
width: 0.1875em; }
|
||||
.svg-inline--fa.fa-w-4 {
|
||||
width: 0.25em; }
|
||||
.svg-inline--fa.fa-w-5 {
|
||||
width: 0.3125em; }
|
||||
.svg-inline--fa.fa-w-6 {
|
||||
width: 0.375em; }
|
||||
.svg-inline--fa.fa-w-7 {
|
||||
width: 0.4375em; }
|
||||
.svg-inline--fa.fa-w-8 {
|
||||
width: 0.5em; }
|
||||
.svg-inline--fa.fa-w-9 {
|
||||
width: 0.5625em; }
|
||||
.svg-inline--fa.fa-w-10 {
|
||||
width: 0.625em; }
|
||||
.svg-inline--fa.fa-w-11 {
|
||||
width: 0.6875em; }
|
||||
.svg-inline--fa.fa-w-12 {
|
||||
width: 0.75em; }
|
||||
.svg-inline--fa.fa-w-13 {
|
||||
width: 0.8125em; }
|
||||
.svg-inline--fa.fa-w-14 {
|
||||
width: 0.875em; }
|
||||
.svg-inline--fa.fa-w-15 {
|
||||
width: 0.9375em; }
|
||||
.svg-inline--fa.fa-w-16 {
|
||||
width: 1em; }
|
||||
.svg-inline--fa.fa-w-17 {
|
||||
width: 1.0625em; }
|
||||
.svg-inline--fa.fa-w-18 {
|
||||
width: 1.125em; }
|
||||
.svg-inline--fa.fa-w-19 {
|
||||
width: 1.1875em; }
|
||||
.svg-inline--fa.fa-w-20 {
|
||||
width: 1.25em; }
|
||||
.svg-inline--fa.fa-pull-left {
|
||||
margin-right: .3em;
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-pull-right {
|
||||
margin-left: .3em;
|
||||
width: auto; }
|
||||
.svg-inline--fa.fa-border {
|
||||
height: 1.5em; }
|
||||
.svg-inline--fa.fa-li {
|
||||
width: 2em; }
|
||||
.svg-inline--fa.fa-fw {
|
||||
width: 1.25em; }
|
||||
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
|
||||
.fa-layers {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
vertical-align: -.125em;
|
||||
width: 1em; }
|
||||
.fa-layers svg.svg-inline--fa {
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-text, .fa-layers-counter {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
text-align: center; }
|
||||
|
||||
.fa-layers-text {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform-origin: center center;
|
||||
transform-origin: center center; }
|
||||
|
||||
.fa-layers-counter {
|
||||
background-color: #ff253a;
|
||||
border-radius: 1em;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
height: 1.5em;
|
||||
line-height: 1;
|
||||
max-width: 5em;
|
||||
min-width: 1.5em;
|
||||
overflow: hidden;
|
||||
padding: .25em;
|
||||
right: 0;
|
||||
text-overflow: ellipsis;
|
||||
top: 0;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-bottom-right {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
top: auto;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: bottom right;
|
||||
transform-origin: bottom right; }
|
||||
|
||||
.fa-layers-bottom-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: auto;
|
||||
top: auto;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: bottom left;
|
||||
transform-origin: bottom left; }
|
||||
|
||||
.fa-layers-top-right {
|
||||
right: 0;
|
||||
top: 0;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: top right;
|
||||
transform-origin: top right; }
|
||||
|
||||
.fa-layers-top-left {
|
||||
left: 0;
|
||||
right: auto;
|
||||
top: 0;
|
||||
-webkit-transform: scale(0.25);
|
||||
transform: scale(0.25);
|
||||
-webkit-transform-origin: top left;
|
||||
transform-origin: top left; }
|
||||
|
||||
.fa-lg {
|
||||
font-size: 1.33333em;
|
||||
line-height: 0.75em;
|
||||
vertical-align: -.0667em; }
|
||||
|
||||
.fa-xs {
|
||||
font-size: .75em; }
|
||||
|
||||
.fa-sm {
|
||||
font-size: .875em; }
|
||||
|
||||
.fa-1x {
|
||||
font-size: 1em; }
|
||||
|
||||
.fa-2x {
|
||||
font-size: 2em; }
|
||||
|
||||
.fa-3x {
|
||||
font-size: 3em; }
|
||||
|
||||
.fa-4x {
|
||||
font-size: 4em; }
|
||||
|
||||
.fa-5x {
|
||||
font-size: 5em; }
|
||||
|
||||
.fa-6x {
|
||||
font-size: 6em; }
|
||||
|
||||
.fa-7x {
|
||||
font-size: 7em; }
|
||||
|
||||
.fa-8x {
|
||||
font-size: 8em; }
|
||||
|
||||
.fa-9x {
|
||||
font-size: 9em; }
|
||||
|
||||
.fa-10x {
|
||||
font-size: 10em; }
|
||||
|
||||
.fa-fw {
|
||||
text-align: center;
|
||||
width: 1.25em; }
|
||||
|
||||
.fa-ul {
|
||||
list-style-type: none;
|
||||
margin-left: 2.5em;
|
||||
padding-left: 0; }
|
||||
.fa-ul > li {
|
||||
position: relative; }
|
||||
|
||||
.fa-li {
|
||||
left: -2em;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 2em;
|
||||
line-height: inherit; }
|
||||
|
||||
.fa-border {
|
||||
border: solid 0.08em #eee;
|
||||
border-radius: .1em;
|
||||
padding: .2em .25em .15em; }
|
||||
|
||||
.fa-pull-left {
|
||||
float: left; }
|
||||
|
||||
.fa-pull-right {
|
||||
float: right; }
|
||||
|
||||
.fa.fa-pull-left,
|
||||
.fas.fa-pull-left,
|
||||
.far.fa-pull-left,
|
||||
.fal.fa-pull-left,
|
||||
.fab.fa-pull-left {
|
||||
margin-right: .3em; }
|
||||
|
||||
.fa.fa-pull-right,
|
||||
.fas.fa-pull-right,
|
||||
.far.fa-pull-right,
|
||||
.fal.fa-pull-right,
|
||||
.fab.fa-pull-right {
|
||||
margin-left: .3em; }
|
||||
|
||||
.fa-spin {
|
||||
-webkit-animation: fa-spin 2s infinite linear;
|
||||
animation: fa-spin 2s infinite linear; }
|
||||
|
||||
.fa-pulse {
|
||||
-webkit-animation: fa-spin 1s infinite steps(8);
|
||||
animation: fa-spin 1s infinite steps(8); }
|
||||
|
||||
@-webkit-keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
@keyframes fa-spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg); } }
|
||||
|
||||
.fa-rotate-90 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg); }
|
||||
|
||||
.fa-rotate-180 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg); }
|
||||
|
||||
.fa-rotate-270 {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
|
||||
-webkit-transform: rotate(270deg);
|
||||
transform: rotate(270deg); }
|
||||
|
||||
.fa-flip-horizontal {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
|
||||
-webkit-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1); }
|
||||
|
||||
.fa-flip-vertical {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
||||
-webkit-transform: scale(1, -1);
|
||||
transform: scale(1, -1); }
|
||||
|
||||
.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
|
||||
-webkit-transform: scale(-1, -1);
|
||||
transform: scale(-1, -1); }
|
||||
|
||||
:root .fa-rotate-90,
|
||||
:root .fa-rotate-180,
|
||||
:root .fa-rotate-270,
|
||||
:root .fa-flip-horizontal,
|
||||
:root .fa-flip-vertical,
|
||||
:root .fa-flip-both {
|
||||
-webkit-filter: none;
|
||||
filter: none; }
|
||||
|
||||
.fa-stack {
|
||||
display: inline-block;
|
||||
height: 2em;
|
||||
position: relative;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-stack-1x,
|
||||
.fa-stack-2x {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0; }
|
||||
|
||||
.svg-inline--fa.fa-stack-1x {
|
||||
height: 1em;
|
||||
width: 1.25em; }
|
||||
|
||||
.svg-inline--fa.fa-stack-2x {
|
||||
height: 2em;
|
||||
width: 2.5em; }
|
||||
|
||||
.fa-inverse {
|
||||
color: #fff; }
|
||||
|
||||
.sr-only {
|
||||
border: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px; }
|
||||
|
||||
.sr-only-focusable:active, .sr-only-focusable:focus {
|
||||
clip: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
overflow: visible;
|
||||
position: static;
|
||||
width: auto; }
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}
|
||||
2166
modules/ui/ui/assets/fontawesome/css/v4-shims.css
vendored
2166
modules/ui/ui/assets/fontawesome/css/v4-shims.css
vendored
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,280 +0,0 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.8.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var _WINDOW = {};
|
||||
var _DOCUMENT = {};
|
||||
|
||||
try {
|
||||
if (typeof window !== 'undefined') _WINDOW = window;
|
||||
if (typeof document !== 'undefined') _DOCUMENT = document;
|
||||
} catch (e) {}
|
||||
|
||||
var _ref = _WINDOW.navigator || {},
|
||||
_ref$userAgent = _ref.userAgent,
|
||||
userAgent = _ref$userAgent === void 0 ? '' : _ref$userAgent;
|
||||
|
||||
var WINDOW = _WINDOW;
|
||||
var DOCUMENT = _DOCUMENT;
|
||||
var IS_BROWSER = !!WINDOW.document;
|
||||
var IS_DOM = !!DOCUMENT.documentElement && !!DOCUMENT.head && typeof DOCUMENT.addEventListener === 'function' && typeof DOCUMENT.createElement === 'function';
|
||||
var IS_IE = ~userAgent.indexOf('MSIE') || ~userAgent.indexOf('Trident/');
|
||||
|
||||
var NAMESPACE_IDENTIFIER = '___FONT_AWESOME___';
|
||||
var PRODUCTION = function () {
|
||||
try {
|
||||
return "production" === 'production';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}();
|
||||
|
||||
function bunker(fn) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
if (!PRODUCTION) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _defineProperty(obj, key, value) {
|
||||
if (key in obj) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
} else {
|
||||
obj[key] = value;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function _objectSpread(target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i] != null ? arguments[i] : {};
|
||||
var ownKeys = Object.keys(source);
|
||||
|
||||
if (typeof Object.getOwnPropertySymbols === 'function') {
|
||||
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
||||
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
||||
}));
|
||||
}
|
||||
|
||||
ownKeys.forEach(function (key) {
|
||||
_defineProperty(target, key, source[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
var w = WINDOW || {};
|
||||
if (!w[NAMESPACE_IDENTIFIER]) w[NAMESPACE_IDENTIFIER] = {};
|
||||
if (!w[NAMESPACE_IDENTIFIER].styles) w[NAMESPACE_IDENTIFIER].styles = {};
|
||||
if (!w[NAMESPACE_IDENTIFIER].hooks) w[NAMESPACE_IDENTIFIER].hooks = {};
|
||||
if (!w[NAMESPACE_IDENTIFIER].shims) w[NAMESPACE_IDENTIFIER].shims = [];
|
||||
var namespace = w[NAMESPACE_IDENTIFIER];
|
||||
|
||||
function defineIcons(prefix, icons) {
|
||||
var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
||||
var _params$skipHooks = params.skipHooks,
|
||||
skipHooks = _params$skipHooks === void 0 ? false : _params$skipHooks;
|
||||
var normalized = Object.keys(icons).reduce(function (acc, iconName) {
|
||||
var icon = icons[iconName];
|
||||
var expanded = !!icon.icon;
|
||||
|
||||
if (expanded) {
|
||||
acc[icon.iconName] = icon.icon;
|
||||
} else {
|
||||
acc[iconName] = icon;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (typeof namespace.hooks.addPack === 'function' && !skipHooks) {
|
||||
namespace.hooks.addPack(prefix, normalized);
|
||||
} else {
|
||||
namespace.styles[prefix] = _objectSpread({}, namespace.styles[prefix] || {}, normalized);
|
||||
}
|
||||
/**
|
||||
* Font Awesome 4 used the prefix of `fa` for all icons. With the introduction
|
||||
* of new styles we needed to differentiate between them. Prefix `fa` is now an alias
|
||||
* for `fas` so we'll easy the upgrade process for our users by automatically defining
|
||||
* this as well.
|
||||
*/
|
||||
|
||||
|
||||
if (prefix === 'fas') {
|
||||
defineIcons('fa', icons);
|
||||
}
|
||||
}
|
||||
|
||||
var icons = {
|
||||
"address-book": [448, 512, [], "f2b9", "M436 160c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20V48c0-26.5-21.5-48-48-48H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h320c26.5 0 48-21.5 48-48v-48h20c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20v-64h20c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-20v-64h20zm-68 304H48V48h320v416zM208 256c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm-89.6 128h179.2c12.4 0 22.4-8.6 22.4-19.2v-19.2c0-31.8-30.1-57.6-67.2-57.6-10.8 0-18.7 8-44.8 8-26.9 0-33.4-8-44.8-8-37.1 0-67.2 25.8-67.2 57.6v19.2c0 10.6 10 19.2 22.4 19.2z"],
|
||||
"address-card": [576, 512, [], "f2bb", "M528 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 400H48V80h480v352zM208 256c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm-89.6 128h179.2c12.4 0 22.4-8.6 22.4-19.2v-19.2c0-31.8-30.1-57.6-67.2-57.6-10.8 0-18.7 8-44.8 8-26.9 0-33.4-8-44.8-8-37.1 0-67.2 25.8-67.2 57.6v19.2c0 10.6 10 19.2 22.4 19.2zM360 320h112c4.4 0 8-3.6 8-8v-16c0-4.4-3.6-8-8-8H360c-4.4 0-8 3.6-8 8v16c0 4.4 3.6 8 8 8zm0-64h112c4.4 0 8-3.6 8-8v-16c0-4.4-3.6-8-8-8H360c-4.4 0-8 3.6-8 8v16c0 4.4 3.6 8 8 8zm0-64h112c4.4 0 8-3.6 8-8v-16c0-4.4-3.6-8-8-8H360c-4.4 0-8 3.6-8 8v16c0 4.4 3.6 8 8 8z"],
|
||||
"angry": [496, 512, [], "f556", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm0-144c-33.6 0-65.2 14.8-86.8 40.6-8.5 10.2-7.1 25.3 3.1 33.8s25.3 7.2 33.8-3c24.8-29.7 75-29.7 99.8 0 8.1 9.7 23.2 11.9 33.8 3 10.2-8.5 11.5-23.6 3.1-33.8-21.6-25.8-53.2-40.6-86.8-40.6zm-48-72c10.3 0 19.9-6.7 23-17.1 3.8-12.7-3.4-26.1-16.1-29.9l-80-24c-12.8-3.9-26.1 3.4-29.9 16.1-3.8 12.7 3.4 26.1 16.1 29.9l28.2 8.5c-3.1 4.9-5.3 10.4-5.3 16.6 0 17.7 14.3 32 32 32s32-14.4 32-32.1zm199-54.9c-3.8-12.7-17.1-19.9-29.9-16.1l-80 24c-12.7 3.8-19.9 17.2-16.1 29.9 3.1 10.4 12.7 17.1 23 17.1 0 17.7 14.3 32 32 32s32-14.3 32-32c0-6.2-2.2-11.7-5.3-16.6l28.2-8.5c12.7-3.7 19.9-17.1 16.1-29.8z"],
|
||||
"arrow-alt-circle-down": [512, 512, [], "f358", "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z"],
|
||||
"arrow-alt-circle-left": [512, 512, [], "f359", "M8 256c0 137 111 248 248 248s248-111 248-248S393 8 256 8 8 119 8 256zm448 0c0 110.5-89.5 200-200 200S56 366.5 56 256 145.5 56 256 56s200 89.5 200 200zm-72-20v40c0 6.6-5.4 12-12 12H256v67c0 10.7-12.9 16-20.5 8.5l-99-99c-4.7-4.7-4.7-12.3 0-17l99-99c7.6-7.6 20.5-2.2 20.5 8.5v67h116c6.6 0 12 5.4 12 12z"],
|
||||
"arrow-alt-circle-right": [512, 512, [], "f35a", "M504 256C504 119 393 8 256 8S8 119 8 256s111 248 248 248 248-111 248-248zm-448 0c0-110.5 89.5-200 200-200s200 89.5 200 200-89.5 200-200 200S56 366.5 56 256zm72 20v-40c0-6.6 5.4-12 12-12h116v-67c0-10.7 12.9-16 20.5-8.5l99 99c4.7 4.7 4.7 12.3 0 17l-99 99c-7.6 7.6-20.5 2.2-20.5-8.5v-67H140c-6.6 0-12-5.4-12-12z"],
|
||||
"arrow-alt-circle-up": [512, 512, [], "f35b", "M256 504c137 0 248-111 248-248S393 8 256 8 8 119 8 256s111 248 248 248zm0-448c110.5 0 200 89.5 200 200s-89.5 200-200 200S56 366.5 56 256 145.5 56 256 56zm20 328h-40c-6.6 0-12-5.4-12-12V256h-67c-10.7 0-16-12.9-8.5-20.5l99-99c4.7-4.7 12.3-4.7 17 0l99 99c7.6 7.6 2.2 20.5-8.5 20.5h-67v116c0 6.6-5.4 12-12 12z"],
|
||||
"bell": [448, 512, [], "f0f3", "M439.39 362.29c-19.32-20.76-55.47-51.99-55.47-154.29 0-77.7-54.48-139.9-127.94-155.16V32c0-17.67-14.32-32-31.98-32s-31.98 14.33-31.98 32v20.84C118.56 68.1 64.08 130.3 64.08 208c0 102.3-36.15 133.53-55.47 154.29-6 6.45-8.66 14.16-8.61 21.71.11 16.4 12.98 32 32.1 32h383.8c19.12 0 32-15.6 32.1-32 .05-7.55-2.61-15.27-8.61-21.71zM67.53 368c21.22-27.97 44.42-74.33 44.53-159.42 0-.2-.06-.38-.06-.58 0-61.86 50.14-112 112-112s112 50.14 112 112c0 .2-.06.38-.06.58.11 85.1 23.31 131.46 44.53 159.42H67.53zM224 512c35.32 0 63.97-28.65 63.97-64H160.03c0 35.35 28.65 64 63.97 64z"],
|
||||
"bell-slash": [640, 512, [], "f1f6", "M633.99 471.02L36 3.51C29.1-2.01 19.03-.9 13.51 6l-10 12.49C-2.02 25.39-.9 35.46 6 40.98l598 467.51c6.9 5.52 16.96 4.4 22.49-2.49l10-12.49c5.52-6.9 4.41-16.97-2.5-22.49zM163.53 368c16.71-22.03 34.48-55.8 41.4-110.58l-45.47-35.55c-3.27 90.73-36.47 120.68-54.84 140.42-6 6.45-8.66 14.16-8.61 21.71.11 16.4 12.98 32 32.1 32h279.66l-61.4-48H163.53zM320 96c61.86 0 112 50.14 112 112 0 .2-.06.38-.06.58.02 16.84 1.16 31.77 2.79 45.73l59.53 46.54c-8.31-22.13-14.34-51.49-14.34-92.85 0-77.7-54.48-139.9-127.94-155.16V32c0-17.67-14.32-32-31.98-32s-31.98 14.33-31.98 32v20.84c-26.02 5.41-49.45 16.94-69.13 32.72l38.17 29.84C275 103.18 296.65 96 320 96zm0 416c35.32 0 63.97-28.65 63.97-64H256.03c0 35.35 28.65 64 63.97 64z"],
|
||||
"bookmark": [384, 512, [], "f02e", "M336 0H48C21.49 0 0 21.49 0 48v464l192-112 192 112V48c0-26.51-21.49-48-48-48zm0 428.43l-144-84-144 84V54a6 6 0 0 1 6-6h276c3.314 0 6 2.683 6 5.996V428.43z"],
|
||||
"building": [448, 512, [], "f1ad", "M128 148v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12h-40c-6.6 0-12-5.4-12-12zm140 12h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm-128 96h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm128 0h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm-76 84v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm76 12h40c6.6 0 12-5.4 12-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12zm180 124v36H0v-36c0-6.6 5.4-12 12-12h19.5V24c0-13.3 10.7-24 24-24h337c13.3 0 24 10.7 24 24v440H436c6.6 0 12 5.4 12 12zM79.5 463H192v-67c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v67h112.5V49L80 48l-.5 415z"],
|
||||
"calendar": [448, 512, [], "f133", "M400 64h-48V12c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v52H160V12c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v52H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zm-6 400H54c-3.3 0-6-2.7-6-6V160h352v298c0 3.3-2.7 6-6 6z"],
|
||||
"calendar-alt": [448, 512, [], "f073", "M148 288h-40c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v40c0 6.6-5.4 12-12 12zm108-12v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 96v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm-96 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm192 0v-40c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12v40c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12zm96-260v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"calendar-check": [448, 512, [], "f274", "M400 64h-48V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v52H160V12c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v52H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 400H54a6 6 0 0 1-6-6V160h352v298a6 6 0 0 1-6 6zm-52.849-200.65L198.842 404.519c-4.705 4.667-12.303 4.637-16.971-.068l-75.091-75.699c-4.667-4.705-4.637-12.303.068-16.971l22.719-22.536c4.705-4.667 12.303-4.637 16.97.069l44.104 44.461 111.072-110.181c4.705-4.667 12.303-4.637 16.971.068l22.536 22.718c4.667 4.705 4.636 12.303-.069 16.97z"],
|
||||
"calendar-minus": [448, 512, [], "f272", "M124 328c-6.6 0-12-5.4-12-12v-24c0-6.6 5.4-12 12-12h200c6.6 0 12 5.4 12 12v24c0 6.6-5.4 12-12 12H124zm324-216v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"calendar-plus": [448, 512, [], "f271", "M336 292v24c0 6.6-5.4 12-12 12h-76v76c0 6.6-5.4 12-12 12h-24c-6.6 0-12-5.4-12-12v-76h-76c-6.6 0-12-5.4-12-12v-24c0-6.6 5.4-12 12-12h76v-76c0-6.6 5.4-12 12-12h24c6.6 0 12 5.4 12 12v76h76c6.6 0 12 5.4 12 12zm112-180v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"calendar-times": [448, 512, [], "f273", "M311.7 374.7l-17 17c-4.7 4.7-12.3 4.7-17 0L224 337.9l-53.7 53.7c-4.7 4.7-12.3 4.7-17 0l-17-17c-4.7-4.7-4.7-12.3 0-17l53.7-53.7-53.7-53.7c-4.7-4.7-4.7-12.3 0-17l17-17c4.7-4.7 12.3-4.7 17 0l53.7 53.7 53.7-53.7c4.7-4.7 12.3-4.7 17 0l17 17c4.7 4.7 4.7 12.3 0 17L257.9 304l53.7 53.7c4.8 4.7 4.8 12.3.1 17zM448 112v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h48V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h128V12c0-6.6 5.4-12 12-12h40c6.6 0 12 5.4 12 12v52h48c26.5 0 48 21.5 48 48zm-48 346V160H48v298c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"caret-square-down": [448, 512, [], "f150", "M125.1 208h197.8c10.7 0 16.1 13 8.5 20.5l-98.9 98.3c-4.7 4.7-12.2 4.7-16.9 0l-98.9-98.3c-7.7-7.5-2.3-20.5 8.4-20.5zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"caret-square-left": [448, 512, [], "f191", "M272 157.1v197.8c0 10.7-13 16.1-20.5 8.5l-98.3-98.9c-4.7-4.7-4.7-12.2 0-16.9l98.3-98.9c7.5-7.7 20.5-2.3 20.5 8.4zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"caret-square-right": [448, 512, [], "f152", "M176 354.9V157.1c0-10.7 13-16.1 20.5-8.5l98.3 98.9c4.7 4.7 4.7 12.2 0 16.9l-98.3 98.9c-7.5 7.7-20.5 2.3-20.5-8.4zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"caret-square-up": [448, 512, [], "f151", "M322.9 304H125.1c-10.7 0-16.1-13-8.5-20.5l98.9-98.3c4.7-4.7 12.2-4.7 16.9 0l98.9 98.3c7.7 7.5 2.3 20.5-8.4 20.5zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"chart-bar": [512, 512, [], "f080", "M396.8 352h22.4c6.4 0 12.8-6.4 12.8-12.8V108.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v230.4c0 6.4 6.4 12.8 12.8 12.8zm-192 0h22.4c6.4 0 12.8-6.4 12.8-12.8V140.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v198.4c0 6.4 6.4 12.8 12.8 12.8zm96 0h22.4c6.4 0 12.8-6.4 12.8-12.8V204.8c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v134.4c0 6.4 6.4 12.8 12.8 12.8zM496 400H48V80c0-8.84-7.16-16-16-16H16C7.16 64 0 71.16 0 80v336c0 17.67 14.33 32 32 32h464c8.84 0 16-7.16 16-16v-16c0-8.84-7.16-16-16-16zm-387.2-48h22.4c6.4 0 12.8-6.4 12.8-12.8v-70.4c0-6.4-6.4-12.8-12.8-12.8h-22.4c-6.4 0-12.8 6.4-12.8 12.8v70.4c0 6.4 6.4 12.8 12.8 12.8z"],
|
||||
"check-circle": [512, 512, [], "f058", "M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 48c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m140.204 130.267l-22.536-22.718c-4.667-4.705-12.265-4.736-16.97-.068L215.346 303.697l-59.792-60.277c-4.667-4.705-12.265-4.736-16.97-.069l-22.719 22.536c-4.705 4.667-4.736 12.265-.068 16.971l90.781 91.516c4.667 4.705 12.265 4.736 16.97.068l172.589-171.204c4.704-4.668 4.734-12.266.067-16.971z"],
|
||||
"check-square": [448, 512, [], "f14a", "M400 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zm0 400H48V80h352v352zm-35.864-241.724L191.547 361.48c-4.705 4.667-12.303 4.637-16.97-.068l-90.781-91.516c-4.667-4.705-4.637-12.303.069-16.971l22.719-22.536c4.705-4.667 12.303-4.637 16.97.069l59.792 60.277 141.352-140.216c4.705-4.667 12.303-4.637 16.97.068l22.536 22.718c4.667 4.706 4.637 12.304-.068 16.971z"],
|
||||
"circle": [512, 512, [], "f111", "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200z"],
|
||||
"clipboard": [384, 512, [], "f328", "M336 64h-80c0-35.3-28.7-64-64-64s-64 28.7-64 64H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zM192 40c13.3 0 24 10.7 24 24s-10.7 24-24 24-24-10.7-24-24 10.7-24 24-24zm144 418c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V118c0-3.3 2.7-6 6-6h42v36c0 6.6 5.4 12 12 12h168c6.6 0 12-5.4 12-12v-36h42c3.3 0 6 2.7 6 6z"],
|
||||
"clock": [512, 512, [], "f017", "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm61.8-104.4l-84.9-61.7c-3.1-2.3-4.9-5.9-4.9-9.7V116c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v141.7l66.8 48.6c5.4 3.9 6.5 11.4 2.6 16.8L334.6 349c-3.9 5.3-11.4 6.5-16.8 2.6z"],
|
||||
"clone": [512, 512, [], "f24d", "M464 0H144c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h320c26.51 0 48-21.49 48-48v-48h48c26.51 0 48-21.49 48-48V48c0-26.51-21.49-48-48-48zM362 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h42v224c0 26.51 21.49 48 48 48h224v42a6 6 0 0 1-6 6zm96-96H150a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h308a6 6 0 0 1 6 6v308a6 6 0 0 1-6 6z"],
|
||||
"closed-captioning": [512, 512, [], "f20a", "M464 64H48C21.5 64 0 85.5 0 112v288c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V112c0-26.5-21.5-48-48-48zm-6 336H54c-3.3 0-6-2.7-6-6V118c0-3.3 2.7-6 6-6h404c3.3 0 6 2.7 6 6v276c0 3.3-2.7 6-6 6zm-211.1-85.7c1.7 2.4 1.5 5.6-.5 7.7-53.6 56.8-172.8 32.1-172.8-67.9 0-97.3 121.7-119.5 172.5-70.1 2.1 2 2.5 3.2 1 5.7l-17.5 30.5c-1.9 3.1-6.2 4-9.1 1.7-40.8-32-94.6-14.9-94.6 31.2 0 48 51 70.5 92.2 32.6 2.8-2.5 7.1-2.1 9.2.9l19.6 27.7zm190.4 0c1.7 2.4 1.5 5.6-.5 7.7-53.6 56.9-172.8 32.1-172.8-67.9 0-97.3 121.7-119.5 172.5-70.1 2.1 2 2.5 3.2 1 5.7L420 220.2c-1.9 3.1-6.2 4-9.1 1.7-40.8-32-94.6-14.9-94.6 31.2 0 48 51 70.5 92.2 32.6 2.8-2.5 7.1-2.1 9.2.9l19.6 27.7z"],
|
||||
"comment": [512, 512, [], "f075", "M256 32C114.6 32 0 125.1 0 240c0 47.6 19.9 91.2 52.9 126.3C38 405.7 7 439.1 6.5 439.5c-6.6 7-8.4 17.2-4.6 26S14.4 480 24 480c61.5 0 110-25.7 139.1-46.3C192 442.8 223.2 448 256 448c141.4 0 256-93.1 256-208S397.4 32 256 32zm0 368c-26.7 0-53.1-4.1-78.4-12.1l-22.7-7.2-19.5 13.8c-14.3 10.1-33.9 21.4-57.5 29 7.3-12.1 14.4-25.7 19.9-40.2l10.6-28.1-20.6-21.8C69.7 314.1 48 282.2 48 240c0-88.2 93.3-160 208-160s208 71.8 208 160-93.3 160-208 160z"],
|
||||
"comment-alt": [512, 512, [], "f27a", "M448 0H64C28.7 0 0 28.7 0 64v288c0 35.3 28.7 64 64 64h96v84c0 7.1 5.8 12 12 12 2.4 0 4.9-.7 7.1-2.4L304 416h144c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64zm16 352c0 8.8-7.2 16-16 16H288l-12.8 9.6L208 428v-60H64c-8.8 0-16-7.2-16-16V64c0-8.8 7.2-16 16-16h384c8.8 0 16 7.2 16 16v288z"],
|
||||
"comment-dots": [512, 512, [], "f4ad", "M144 208c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm112 0c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm112 0c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zM256 32C114.6 32 0 125.1 0 240c0 47.6 19.9 91.2 52.9 126.3C38 405.7 7 439.1 6.5 439.5c-6.6 7-8.4 17.2-4.6 26S14.4 480 24 480c61.5 0 110-25.7 139.1-46.3C192 442.8 223.2 448 256 448c141.4 0 256-93.1 256-208S397.4 32 256 32zm0 368c-26.7 0-53.1-4.1-78.4-12.1l-22.7-7.2-19.5 13.8c-14.3 10.1-33.9 21.4-57.5 29 7.3-12.1 14.4-25.7 19.9-40.2l10.6-28.1-20.6-21.8C69.7 314.1 48 282.2 48 240c0-88.2 93.3-160 208-160s208 71.8 208 160-93.3 160-208 160z"],
|
||||
"comments": [576, 512, [], "f086", "M532 386.2c27.5-27.1 44-61.1 44-98.2 0-80-76.5-146.1-176.2-157.9C368.3 72.5 294.3 32 208 32 93.1 32 0 103.6 0 192c0 37 16.5 71 44 98.2-15.3 30.7-37.3 54.5-37.7 54.9-6.3 6.7-8.1 16.5-4.4 25 3.6 8.5 12 14 21.2 14 53.5 0 96.7-20.2 125.2-38.8 9.2 2.1 18.7 3.7 28.4 4.9C208.1 407.6 281.8 448 368 448c20.8 0 40.8-2.4 59.8-6.8C456.3 459.7 499.4 480 553 480c9.2 0 17.5-5.5 21.2-14 3.6-8.5 1.9-18.3-4.4-25-.4-.3-22.5-24.1-37.8-54.8zm-392.8-92.3L122.1 305c-14.1 9.1-28.5 16.3-43.1 21.4 2.7-4.7 5.4-9.7 8-14.8l15.5-31.1L77.7 256C64.2 242.6 48 220.7 48 192c0-60.7 73.3-112 160-112s160 51.3 160 112-73.3 112-160 112c-16.5 0-33-1.9-49-5.6l-19.8-4.5zM498.3 352l-24.7 24.4 15.5 31.1c2.6 5.1 5.3 10.1 8 14.8-14.6-5.1-29-12.3-43.1-21.4l-17.1-11.1-19.9 4.6c-16 3.7-32.5 5.6-49 5.6-54 0-102.2-20.1-131.3-49.7C338 339.5 416 272.9 416 192c0-3.4-.4-6.7-.7-10C479.7 196.5 528 238.8 528 288c0 28.7-16.2 50.6-29.7 64z"],
|
||||
"compass": [496, 512, [], "f14e", "M347.94 129.86L203.6 195.83a31.938 31.938 0 0 0-15.77 15.77l-65.97 144.34c-7.61 16.65 9.54 33.81 26.2 26.2l144.34-65.97a31.938 31.938 0 0 0 15.77-15.77l65.97-144.34c7.61-16.66-9.54-33.81-26.2-26.2zm-77.36 148.72c-12.47 12.47-32.69 12.47-45.16 0-12.47-12.47-12.47-32.69 0-45.16 12.47-12.47 32.69-12.47 45.16 0 12.47 12.47 12.47 32.69 0 45.16zM248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm0 448c-110.28 0-200-89.72-200-200S137.72 56 248 56s200 89.72 200 200-89.72 200-200 200z"],
|
||||
"copy": [448, 512, [], "f0c5", "M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"],
|
||||
"copyright": [512, 512, [], "f1f9", "M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 448c-110.532 0-200-89.451-200-200 0-110.531 89.451-200 200-200 110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200zm107.351-101.064c-9.614 9.712-45.53 41.396-104.065 41.396-82.43 0-140.484-61.425-140.484-141.567 0-79.152 60.275-139.401 139.762-139.401 55.531 0 88.738 26.62 97.593 34.779a11.965 11.965 0 0 1 1.936 15.322l-18.155 28.113c-3.841 5.95-11.966 7.282-17.499 2.921-8.595-6.776-31.814-22.538-61.708-22.538-48.303 0-77.916 35.33-77.916 80.082 0 41.589 26.888 83.692 78.277 83.692 32.657 0 56.843-19.039 65.726-27.225 5.27-4.857 13.596-4.039 17.82 1.738l19.865 27.17a11.947 11.947 0 0 1-1.152 15.518z"],
|
||||
"credit-card": [576, 512, [], "f09d", "M527.9 32H48.1C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48.1 48h479.8c26.6 0 48.1-21.5 48.1-48V80c0-26.5-21.5-48-48.1-48zM54.1 80h467.8c3.3 0 6 2.7 6 6v42H48.1V86c0-3.3 2.7-6 6-6zm467.8 352H54.1c-3.3 0-6-2.7-6-6V256h479.8v170c0 3.3-2.7 6-6 6zM192 332v40c0 6.6-5.4 12-12 12h-72c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h72c6.6 0 12 5.4 12 12zm192 0v40c0 6.6-5.4 12-12 12H236c-6.6 0-12-5.4-12-12v-40c0-6.6 5.4-12 12-12h136c6.6 0 12 5.4 12 12z"],
|
||||
"dizzy": [496, 512, [], "f567", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-33.8-217.9c7.8-7.8 7.8-20.5 0-28.3L196.3 192l17.9-17.9c7.8-7.8 7.8-20.5 0-28.3-7.8-7.8-20.5-7.8-28.3 0L168 163.7l-17.8-17.8c-7.8-7.8-20.5-7.8-28.3 0-7.8 7.8-7.8 20.5 0 28.3l17.9 17.9-17.9 17.9c-7.8 7.8-7.8 20.5 0 28.3 7.8 7.8 20.5 7.8 28.3 0l17.8-17.8 17.8 17.8c7.9 7.7 20.5 7.7 28.4-.2zm160-92.2c-7.8-7.8-20.5-7.8-28.3 0L328 163.7l-17.8-17.8c-7.8-7.8-20.5-7.8-28.3 0-7.8 7.8-7.8 20.5 0 28.3l17.9 17.9-17.9 17.9c-7.8 7.8-7.8 20.5 0 28.3 7.8 7.8 20.5 7.8 28.3 0l17.8-17.8 17.8 17.8c7.8 7.8 20.5 7.8 28.3 0 7.8-7.8 7.8-20.5 0-28.3l-17.8-18 17.9-17.9c7.7-7.8 7.7-20.4 0-28.2zM248 272c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64z"],
|
||||
"dot-circle": [512, 512, [], "f192", "M256 56c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m0-48C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 168c-44.183 0-80 35.817-80 80s35.817 80 80 80 80-35.817 80-80-35.817-80-80-80z"],
|
||||
"edit": [576, 512, [], "f044", "M402.3 344.9l32-32c5-5 13.7-1.5 13.7 5.7V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V112c0-26.5 21.5-48 48-48h273.5c7.1 0 10.7 8.6 5.7 13.7l-32 32c-1.5 1.5-3.5 2.3-5.7 2.3H48v352h352V350.5c0-2.1.8-4.1 2.3-5.6zm156.6-201.8L296.3 405.7l-90.4 10c-26.2 2.9-48.5-19.2-45.6-45.6l10-90.4L432.9 17.1c22.9-22.9 59.9-22.9 82.7 0l43.2 43.2c22.9 22.9 22.9 60 .1 82.8zM460.1 174L402 115.9 216.2 301.8l-7.3 65.3 65.3-7.3L460.1 174zm64.8-79.7l-43.2-43.2c-4.1-4.1-10.8-4.1-14.8 0L436 82l58.1 58.1 30.9-30.9c4-4.2 4-10.8-.1-14.9z"],
|
||||
"envelope": [512, 512, [], "f0e0", "M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm0 48v40.805c-22.422 18.259-58.168 46.651-134.587 106.49-16.841 13.247-50.201 45.072-73.413 44.701-23.208.375-56.579-31.459-73.413-44.701C106.18 199.465 70.425 171.067 48 152.805V112h416zM48 400V214.398c22.914 18.251 55.409 43.862 104.938 82.646 21.857 17.205 60.134 55.186 103.062 54.955 42.717.231 80.509-37.199 103.053-54.947 49.528-38.783 82.032-64.401 104.947-82.653V400H48z"],
|
||||
"envelope-open": [512, 512, [], "f2b6", "M494.586 164.516c-4.697-3.883-111.723-89.95-135.251-108.657C337.231 38.191 299.437 0 256 0c-43.205 0-80.636 37.717-103.335 55.859-24.463 19.45-131.07 105.195-135.15 108.549A48.004 48.004 0 0 0 0 201.485V464c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V201.509a48 48 0 0 0-17.414-36.993zM464 458a6 6 0 0 1-6 6H54a6 6 0 0 1-6-6V204.347c0-1.813.816-3.526 2.226-4.665 15.87-12.814 108.793-87.554 132.364-106.293C200.755 78.88 232.398 48 256 48c23.693 0 55.857 31.369 73.41 45.389 23.573 18.741 116.503 93.493 132.366 106.316a5.99 5.99 0 0 1 2.224 4.663V458zm-31.991-187.704c4.249 5.159 3.465 12.795-1.745 16.981-28.975 23.283-59.274 47.597-70.929 56.863C336.636 362.283 299.205 400 256 400c-43.452 0-81.287-38.237-103.335-55.86-11.279-8.967-41.744-33.413-70.927-56.865-5.21-4.187-5.993-11.822-1.745-16.981l15.258-18.528c4.178-5.073 11.657-5.843 16.779-1.726 28.618 23.001 58.566 47.035 70.56 56.571C200.143 320.631 232.307 352 256 352c23.602 0 55.246-30.88 73.41-45.389 11.994-9.535 41.944-33.57 70.563-56.568 5.122-4.116 12.601-3.346 16.778 1.727l15.258 18.526z"],
|
||||
"eye": [576, 512, [], "f06e", "M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"],
|
||||
"eye-slash": [640, 512, [], "f070", "M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"],
|
||||
"file": [384, 512, [], "f15b", "M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48z"],
|
||||
"file-alt": [384, 512, [], "f15c", "M288 248v28c0 6.6-5.4 12-12 12H108c-6.6 0-12-5.4-12-12v-28c0-6.6 5.4-12 12-12h168c6.6 0 12 5.4 12 12zm-12 72H108c-6.6 0-12 5.4-12 12v28c0 6.6 5.4 12 12 12h168c6.6 0 12-5.4 12-12v-28c0-6.6-5.4-12-12-12zm108-188.1V464c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V48C0 21.5 21.5 0 48 0h204.1C264.8 0 277 5.1 286 14.1L369.9 98c9 8.9 14.1 21.2 14.1 33.9zm-128-80V128h76.1L256 51.9zM336 464V176H232c-13.3 0-24-10.7-24-24V48H48v416h288z"],
|
||||
"file-archive": [384, 512, [], "f1c6", "M128.3 160v32h32v-32zm64-96h-32v32h32zm-64 32v32h32V96zm64 32h-32v32h32zm177.6-30.1L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM256 51.9l76.1 76.1H256zM336 464H48V48h79.7v16h32V48H208v104c0 13.3 10.7 24 24 24h104zM194.2 265.7c-1.1-5.6-6-9.7-11.8-9.7h-22.1v-32h-32v32l-19.7 97.1C102 385.6 126.8 416 160 416c33.1 0 57.9-30.2 51.5-62.6zm-33.9 124.4c-17.9 0-32.4-12.1-32.4-27s14.5-27 32.4-27 32.4 12.1 32.4 27-14.5 27-32.4 27zm32-198.1h-32v32h32z"],
|
||||
"file-audio": [384, 512, [], "f1c7", "M369.941 97.941l-83.882-83.882A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v416c0 26.51 21.49 48 48 48h288c26.51 0 48-21.49 48-48V131.882a48 48 0 0 0-14.059-33.941zM332.118 128H256V51.882L332.118 128zM48 464V48h160v104c0 13.255 10.745 24 24 24h104v288H48zm144-76.024c0 10.691-12.926 16.045-20.485 8.485L136 360.486h-28c-6.627 0-12-5.373-12-12v-56c0-6.627 5.373-12 12-12h28l35.515-36.947c7.56-7.56 20.485-2.206 20.485 8.485v135.952zm41.201-47.13c9.051-9.297 9.06-24.133.001-33.439-22.149-22.752 12.235-56.246 34.395-33.481 27.198 27.94 27.212 72.444.001 100.401-21.793 22.386-56.947-10.315-34.397-33.481z"],
|
||||
"file-code": [384, 512, [], "f1c9", "M149.9 349.1l-.2-.2-32.8-28.9 32.8-28.9c3.6-3.2 4-8.8.8-12.4l-.2-.2-17.4-18.6c-3.4-3.6-9-3.7-12.4-.4l-57.7 54.1c-3.7 3.5-3.7 9.4 0 12.8l57.7 54.1c1.6 1.5 3.8 2.4 6 2.4 2.4 0 4.8-1 6.4-2.8l17.4-18.6c3.3-3.5 3.1-9.1-.4-12.4zm220-251.2L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM256 51.9l76.1 76.1H256zM336 464H48V48h160v104c0 13.3 10.7 24 24 24h104zM209.6 214c-4.7-1.4-9.5 1.3-10.9 6L144 408.1c-1.4 4.7 1.3 9.6 6 10.9l24.4 7.1c4.7 1.4 9.6-1.4 10.9-6L240 231.9c1.4-4.7-1.3-9.6-6-10.9zm24.5 76.9l.2.2 32.8 28.9-32.8 28.9c-3.6 3.2-4 8.8-.8 12.4l.2.2 17.4 18.6c3.3 3.5 8.9 3.7 12.4.4l57.7-54.1c3.7-3.5 3.7-9.4 0-12.8l-57.7-54.1c-3.5-3.3-9.1-3.2-12.4.4l-17.4 18.6c-3.3 3.5-3.1 9.1.4 12.4z"],
|
||||
"file-excel": [384, 512, [], "f1c3", "M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm212-240h-28.8c-4.4 0-8.4 2.4-10.5 6.3-18 33.1-22.2 42.4-28.6 57.7-13.9-29.1-6.9-17.3-28.6-57.7-2.1-3.9-6.2-6.3-10.6-6.3H124c-9.3 0-15 10-10.4 18l46.3 78-46.3 78c-4.7 8 1.1 18 10.4 18h28.9c4.4 0 8.4-2.4 10.5-6.3 21.7-40 23-45 28.6-57.7 14.9 30.2 5.9 15.9 28.6 57.7 2.1 3.9 6.2 6.3 10.6 6.3H260c9.3 0 15-10 10.4-18L224 320c.7-1.1 30.3-50.5 46.3-78 4.7-8-1.1-18-10.3-18z"],
|
||||
"file-image": [384, 512, [], "f1c5", "M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm32-48h224V288l-23.5-23.5c-4.7-4.7-12.3-4.7-17 0L176 352l-39.5-39.5c-4.7-4.7-12.3-4.7-17 0L80 352v64zm48-240c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z"],
|
||||
"file-pdf": [384, 512, [], "f1c1", "M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm250.2-143.7c-12.2-12-47-8.7-64.4-6.5-17.2-10.5-28.7-25-36.8-46.3 3.9-16.1 10.1-40.6 5.4-56-4.2-26.2-37.8-23.6-42.6-5.9-4.4 16.1-.4 38.5 7 67.1-10 23.9-24.9 56-35.4 74.4-20 10.3-47 26.2-51 46.2-3.3 15.8 26 55.2 76.1-31.2 22.4-7.4 46.8-16.5 68.4-20.1 18.9 10.2 41 17 55.8 17 25.5 0 28-28.2 17.5-38.7zm-198.1 77.8c5.1-13.7 24.5-29.5 30.4-35-19 30.3-30.4 35.7-30.4 35zm81.6-190.6c7.4 0 6.7 32.1 1.8 40.8-4.4-13.9-4.3-40.8-1.8-40.8zm-24.4 136.6c9.7-16.9 18-37 24.7-54.7 8.3 15.1 18.9 27.2 30.1 35.5-20.8 4.3-38.9 13.1-54.8 19.2zm131.6-5s-5 6-37.3-7.8c35.1-2.6 40.9 5.4 37.3 7.8z"],
|
||||
"file-powerpoint": [384, 512, [], "f1c4", "M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm72-60V236c0-6.6 5.4-12 12-12h69.2c36.7 0 62.8 27 62.8 66.3 0 74.3-68.7 66.5-95.5 66.5V404c0 6.6-5.4 12-12 12H132c-6.6 0-12-5.4-12-12zm48.5-87.4h23c7.9 0 13.9-2.4 18.1-7.2 8.5-9.8 8.4-28.5.1-37.8-4.1-4.6-9.9-7-17.4-7h-23.9v52z"],
|
||||
"file-video": [384, 512, [], "f1c8", "M369.941 97.941l-83.882-83.882A48 48 0 0 0 252.118 0H48C21.49 0 0 21.49 0 48v416c0 26.51 21.49 48 48 48h288c26.51 0 48-21.49 48-48V131.882a48 48 0 0 0-14.059-33.941zM332.118 128H256V51.882L332.118 128zM48 464V48h160v104c0 13.255 10.745 24 24 24h104v288H48zm228.687-211.303L224 305.374V268c0-11.046-8.954-20-20-20H100c-11.046 0-20 8.954-20 20v104c0 11.046 8.954 20 20 20h104c11.046 0 20-8.954 20-20v-37.374l52.687 52.674C286.704 397.318 304 390.28 304 375.986V264.011c0-14.311-17.309-21.319-27.313-11.314z"],
|
||||
"file-word": [384, 512, [], "f1c2", "M369.9 97.9L286 14C277 5 264.8-.1 252.1-.1H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V131.9c0-12.7-5.1-25-14.1-34zM332.1 128H256V51.9l76.1 76.1zM48 464V48h160v104c0 13.3 10.7 24 24 24h104v288H48zm220.1-208c-5.7 0-10.6 4-11.7 9.5-20.6 97.7-20.4 95.4-21 103.5-.2-1.2-.4-2.6-.7-4.3-.8-5.1.3.2-23.6-99.5-1.3-5.4-6.1-9.2-11.7-9.2h-13.3c-5.5 0-10.3 3.8-11.7 9.1-24.4 99-24 96.2-24.8 103.7-.1-1.1-.2-2.5-.5-4.2-.7-5.2-14.1-73.3-19.1-99-1.1-5.6-6-9.7-11.8-9.7h-16.8c-7.8 0-13.5 7.3-11.7 14.8 8 32.6 26.7 109.5 33.2 136 1.3 5.4 6.1 9.1 11.7 9.1h25.2c5.5 0 10.3-3.7 11.6-9.1l17.9-71.4c1.5-6.2 2.5-12 3-17.3l2.9 17.3c.1.4 12.6 50.5 17.9 71.4 1.3 5.3 6.1 9.1 11.6 9.1h24.7c5.5 0 10.3-3.7 11.6-9.1 20.8-81.9 30.2-119 34.5-136 1.9-7.6-3.8-14.9-11.6-14.9h-15.8z"],
|
||||
"flag": [512, 512, [], "f024", "M336.174 80c-49.132 0-93.305-32-161.913-32-31.301 0-58.303 6.482-80.721 15.168a48.04 48.04 0 0 0 2.142-20.727C93.067 19.575 74.167 1.594 51.201.104 23.242-1.71 0 20.431 0 48c0 17.764 9.657 33.262 24 41.562V496c0 8.837 7.163 16 16 16h16c8.837 0 16-7.163 16-16v-83.443C109.869 395.28 143.259 384 199.826 384c49.132 0 93.305 32 161.913 32 58.479 0 101.972-22.617 128.548-39.981C503.846 367.161 512 352.051 512 335.855V95.937c0-34.459-35.264-57.768-66.904-44.117C409.193 67.309 371.641 80 336.174 80zM464 336c-21.783 15.412-60.824 32-102.261 32-59.945 0-102.002-32-161.913-32-43.361 0-96.379 9.403-127.826 24V128c21.784-15.412 60.824-32 102.261-32 59.945 0 102.002 32 161.913 32 43.271 0 96.32-17.366 127.826-32v240z"],
|
||||
"flushed": [496, 512, [], "f579", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm96-312c-44.2 0-80 35.8-80 80s35.8 80 80 80 80-35.8 80-80-35.8-80-80-80zm0 128c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm0-72c-13.3 0-24 10.7-24 24s10.7 24 24 24 24-10.7 24-24-10.7-24-24-24zm-112 24c0-44.2-35.8-80-80-80s-80 35.8-80 80 35.8 80 80 80 80-35.8 80-80zm-80 48c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm0-72c-13.3 0-24 10.7-24 24s10.7 24 24 24 24-10.7 24-24-10.7-24-24-24zm160 144H184c-13.2 0-24 10.8-24 24s10.8 24 24 24h128c13.2 0 24-10.8 24-24s-10.8-24-24-24z"],
|
||||
"folder": [512, 512, [], "f07b", "M464 128H272l-54.63-54.63c-6-6-14.14-9.37-22.63-9.37H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V176c0-26.51-21.49-48-48-48zm0 272H48V112h140.12l54.63 54.63c6 6 14.14 9.37 22.63 9.37H464v224z"],
|
||||
"folder-open": [576, 512, [], "f07c", "M527.9 224H480v-48c0-26.5-21.5-48-48-48H272l-64-64H48C21.5 64 0 85.5 0 112v288c0 26.5 21.5 48 48 48h400c16.5 0 31.9-8.5 40.7-22.6l79.9-128c20-31.9-3-73.4-40.7-73.4zM48 118c0-3.3 2.7-6 6-6h134.1l64 64H426c3.3 0 6 2.7 6 6v42H152c-16.8 0-32.4 8.8-41.1 23.2L48 351.4zm400 282H72l77.2-128H528z"],
|
||||
"font-awesome-logo-full": [3992, 512, ["Font Awesome"], "f4e6", "M454.6 0H57.4C25.9 0 0 25.9 0 57.4v397.3C0 486.1 25.9 512 57.4 512h397.3c31.4 0 57.4-25.9 57.4-57.4V57.4C512 25.9 486.1 0 454.6 0zm-58.9 324.9c0 4.8-4.1 6.9-8.9 8.9-19.2 8.1-39.7 15.7-61.5 15.7-40.5 0-68.7-44.8-163.2 2.5v51.8c0 30.3-45.7 30.2-45.7 0v-250c-9-7-15-17.9-15-30.3 0-21 17.1-38.2 38.2-38.2 21 0 38.2 17.1 38.2 38.2 0 12.2-5.8 23.2-14.9 30.2v21c37.1-12 65.5-34.4 146.1-3.4 26.6 11.4 68.7-15.7 76.5-15.7 5.5 0 10.3 4.1 10.3 8.9v160.4zm432.9-174.2h-137v70.1H825c39.8 0 40.4 62.2 0 62.2H691.6v105.6c0 45.5-70.7 46.4-70.7 0V128.3c0-22 18-39.8 39.8-39.8h167.8c39.6 0 40.5 62.2.1 62.2zm191.1 23.4c-169.3 0-169.1 252.4 0 252.4 169.9 0 169.9-252.4 0-252.4zm0 196.1c-81.6 0-82.1-139.8 0-139.8 82.5 0 82.4 139.8 0 139.8zm372.4 53.4c-17.5 0-31.4-13.9-31.4-31.4v-117c0-62.4-72.6-52.5-99.1-16.4v133.4c0 41.5-63.3 41.8-63.3 0V208c0-40 63.1-41.6 63.1 0v3.4c43.3-51.6 162.4-60.4 162.4 39.3v141.5c.3 30.4-31.5 31.4-31.7 31.4zm179.7 2.9c-44.3 0-68.3-22.9-68.3-65.8V235.2H1488c-35.6 0-36.7-55.3 0-55.3h15.5v-37.3c0-41.3 63.8-42.1 63.8 0v37.5h24.9c35.4 0 35.7 55.3 0 55.3h-24.9v108.5c0 29.6 26.1 26.3 27.4 26.3 31.4 0 52.6 56.3-22.9 56.3zM1992 123c-19.5-50.2-95.5-50-114.5 0-107.3 275.7-99.5 252.7-99.5 262.8 0 42.8 58.3 51.2 72.1 14.4l13.5-35.9H2006l13 35.9c14.2 37.7 72.1 27.2 72.1-14.4 0-10.1 5.3 6.8-99.1-262.8zm-108.9 179.1l51.7-142.9 51.8 142.9h-103.5zm591.3-85.6l-53.7 176.3c-12.4 41.2-72 41-84 0l-42.3-135.9-42.3 135.9c-12.4 40.9-72 41.2-84.5 0l-54.2-176.3c-12.5-39.4 49.8-56.1 60.2-16.9L2213 342l45.3-139.5c10.9-32.7 59.6-34.7 71.2 0l45.3 139.5 39.3-142.4c10.3-38.3 72.6-23.8 60.3 16.9zm275.4 75.1c0-42.4-33.9-117.5-119.5-117.5-73.2 0-124.4 56.3-124.4 126 0 77.2 55.3 126.4 128.5 126.4 31.7 0 93-11.5 93-39.8 0-18.3-21.1-31.5-39.3-22.4-49.4 26.2-109 8.4-115.9-43.8h148.3c16.3 0 29.3-13.4 29.3-28.9zM2571 277.7c9.5-73.4 113.9-68.6 118.6 0H2571zm316.7 148.8c-31.4 0-81.6-10.5-96.6-31.9-12.4-17 2.5-39.8 21.8-39.8 16.3 0 36.8 22.9 77.7 22.9 27.4 0 40.4-11 40.4-25.8 0-39.8-142.9-7.4-142.9-102 0-40.4 35.3-75.7 98.6-75.7 31.4 0 74.1 9.9 87.6 29.4 10.8 14.8-1.4 36.2-20.9 36.2-15.1 0-26.7-17.3-66.2-17.3-22.9 0-37.8 10.5-37.8 23.8 0 35.9 142.4 6 142.4 103.1-.1 43.7-37.4 77.1-104.1 77.1zm266.8-252.4c-169.3 0-169.1 252.4 0 252.4 170.1 0 169.6-252.4 0-252.4zm0 196.1c-81.8 0-82-139.8 0-139.8 82.5 0 82.4 139.8 0 139.8zm476.9 22V268.7c0-53.8-61.4-45.8-85.7-10.5v134c0 41.3-63.8 42.1-63.8 0V268.7c0-52.1-59.5-47.4-85.7-10.1v133.6c0 41.5-63.3 41.8-63.3 0V208c0-40 63.1-41.6 63.1 0v3.4c9.9-14.4 41.8-37.3 78.6-37.3 35.3 0 57.7 16.4 66.7 43.8 13.9-21.8 45.8-43.8 82.6-43.8 44.3 0 70.7 23.4 70.7 72.7v145.3c.5 17.3-13.5 31.4-31.9 31.4 3.5.1-31.3 1.1-31.3-31.3zM3992 291.6c0-42.4-32.4-117.5-117.9-117.5-73.2 0-127.5 56.3-127.5 126 0 77.2 58.3 126.4 131.6 126.4 31.7 0 91.5-11.5 91.5-39.8 0-18.3-21.1-31.5-39.3-22.4-49.4 26.2-110.5 8.4-117.5-43.8h149.8c16.3 0 29.1-13.4 29.3-28.9zm-180.5-13.9c9.7-74.4 115.9-68.3 120.1 0h-120.1z"],
|
||||
"frown": [496, 512, [], "f119", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z"],
|
||||
"frown-open": [496, 512, [], "f57a", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-48-248c0-17.7-14.3-32-32-32s-32 14.3-32 32 14.3 32 32 32 32-14.3 32-32zm128-32c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 112c-35.6 0-88.8 21.3-95.8 61.2-2 11.8 9 21.5 20.5 18.1 31.2-9.6 59.4-15.3 75.3-15.3s44.1 5.7 75.3 15.3c11.4 3.5 22.5-6.3 20.5-18.1-7-39.9-60.2-61.2-95.8-61.2z"],
|
||||
"futbol": [496, 512, [], "f1e3", "M483.8 179.4C449.8 74.6 352.6 8 248.1 8c-25.4 0-51.2 3.9-76.7 12.2C41.2 62.5-30.1 202.4 12.2 332.6 46.2 437.4 143.4 504 247.9 504c25.4 0 51.2-3.9 76.7-12.2 130.2-42.3 201.5-182.2 159.2-312.4zm-74.5 193.7l-52.2 6.4-43.7-60.9 24.4-75.2 71.1-22.1 38.9 36.4c-.2 30.7-7.4 61.1-21.7 89.2-4.7 9.3-10.7 17.8-16.8 26.2zm0-235.4l-10.4 53.1-70.7 22-64.2-46.5V92.5l47.4-26.2c39.2 13 73.4 38 97.9 71.4zM184.9 66.4L232 92.5v73.8l-64.2 46.5-70.6-22-10.1-52.5c24.3-33.4 57.9-58.6 97.8-71.9zM139 379.5L85.9 373c-14.4-20.1-37.3-59.6-37.8-115.3l39-36.4 71.1 22.2 24.3 74.3-43.5 61.7zm48.2 67l-22.4-48.1 43.6-61.7H287l44.3 61.7-22.4 48.1c-6.2 1.8-57.6 20.4-121.7 0z"],
|
||||
"gem": [576, 512, [], "f3a5", "M464 0H112c-4 0-7.8 2-10 5.4L2 152.6c-2.9 4.4-2.6 10.2.7 14.2l276 340.8c4.8 5.9 13.8 5.9 18.6 0l276-340.8c3.3-4.1 3.6-9.8.7-14.2L474.1 5.4C471.8 2 468.1 0 464 0zm-19.3 48l63.3 96h-68.4l-51.7-96h56.8zm-202.1 0h90.7l51.7 96H191l51.6-96zm-111.3 0h56.8l-51.7 96H68l63.3-96zm-43 144h51.4L208 352 88.3 192zm102.9 0h193.6L288 435.3 191.2 192zM368 352l68.2-160h51.4L368 352z"],
|
||||
"grimace": [496, 512, [], "f57f", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm16 16H152c-26.5 0-48 21.5-48 48v32c0 26.5 21.5 48 48 48h192c26.5 0 48-21.5 48-48v-32c0-26.5-21.5-48-48-48zm-168 96h-24c-8.8 0-16-7.2-16-16v-8h40v24zm0-40h-40v-8c0-8.8 7.2-16 16-16h24v24zm64 40h-48v-24h48v24zm0-40h-48v-24h48v24zm64 40h-48v-24h48v24zm0-40h-48v-24h48v24zm56 24c0 8.8-7.2 16-16 16h-24v-24h40v8zm0-24h-40v-24h24c8.8 0 16 7.2 16 16v8z"],
|
||||
"grin": [496, 512, [], "f580", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm105.6-151.4c-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.9-3.1-19.4 5.4-17.7 15.3 7.9 47.1 71.3 80 123.3 80s115.3-32.9 123.3-80c1.6-9.8-7.7-18.4-17.7-15.3zM168 240c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32z"],
|
||||
"grin-alt": [496, 512, [], "f581", "M200.3 248c12.4-18.7 15.1-37.3 15.7-56-.5-18.7-3.3-37.3-15.7-56-8-12-25.1-11.4-32.7 0-12.4 18.7-15.1 37.3-15.7 56 .5 18.7 3.3 37.3 15.7 56 8.1 12 25.2 11.4 32.7 0zm128 0c12.4-18.7 15.1-37.3 15.7-56-.5-18.7-3.3-37.3-15.7-56-8-12-25.1-11.4-32.7 0-12.4 18.7-15.1 37.3-15.7 56 .5 18.7 3.3 37.3 15.7 56 8.1 12 25.2 11.4 32.7 0zM248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm105.6-151.4c-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.9-3.1-19.4 5.3-17.7 15.3 7.9 47.2 71.3 80 123.3 80s115.3-32.9 123.3-80c1.6-9.8-7.7-18.4-17.7-15.3z"],
|
||||
"grin-beam": [496, 512, [], "f582", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm105.6-151.4c-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.8-3.1-19.4 5.3-17.7 15.3 7.9 47.1 71.3 80 123.3 80s115.3-32.9 123.3-80c1.6-9.8-7.7-18.4-17.7-15.3zm-235.9-72.9c3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3zm160 0c3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3z"],
|
||||
"grin-beam-sweat": [496, 512, [], "f583", "M440 160c29.5 0 53.3-26.3 53.3-58.7 0-25-31.7-75.5-46.2-97.3-3.6-5.3-10.7-5.3-14.2 0-14.5 21.8-46.2 72.3-46.2 97.3 0 32.4 23.8 58.7 53.3 58.7zM248 400c51.9 0 115.3-32.9 123.3-80 1.7-9.9-7.7-18.5-17.7-15.3-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.8-3.1-19.4 5.3-17.7 15.3 8 47.1 71.4 80 123.3 80zm130.3-168.3c3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3 3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.6 6.2 4.6 9.3 3.7zm105.3-52.9c-24.6 15.7-46 12.9-46.4 12.9 6.9 20.2 10.8 41.8 10.8 64.3 0 110.3-89.7 200-200 200S48 366.3 48 256 137.7 56 248 56c39.8 0 76.8 11.8 108 31.9 1.7-9.5 6.3-24.1 17.2-45.7C336.4 20.6 293.7 8 248 8 111 8 0 119 0 256s111 248 248 248 248-111 248-248c0-27-4.4-52.9-12.4-77.2zM168 189.4c12.3 0 23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3 3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.8 19.2-21.6 31.5-21.6z"],
|
||||
"grin-hearts": [496, 512, [], "f584", "M353.6 304.6c-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.8-3.1-19.4 5.3-17.7 15.3 7.9 47.2 71.3 80 123.3 80s115.3-32.9 123.3-80c1.6-9.8-7.7-18.4-17.7-15.3zm-152.8-48.9c4.5 1.2 9.2-1.5 10.5-6l19.4-69.9c5.6-20.3-7.4-41.1-28.8-44.5-18.6-3-36.4 9.8-41.5 27.9l-2 7.1-7.1-1.9c-18.2-4.7-38.2 4.3-44.9 22-7.7 20.2 3.8 41.9 24.2 47.2l70.2 18.1zm188.8-65.3c-6.7-17.6-26.7-26.7-44.9-22l-7.1 1.9-2-7.1c-5-18.1-22.8-30.9-41.5-27.9-21.4 3.4-34.4 24.2-28.8 44.5l19.4 69.9c1.2 4.5 5.9 7.2 10.5 6l70.2-18.2c20.4-5.3 31.9-26.9 24.2-47.1zM248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200z"],
|
||||
"grin-squint": [496, 512, [], "f585", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm105.6-151.4c-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.9-3.1-19.4 5.4-17.7 15.3 7.9 47.1 71.3 80 123.3 80s115.3-32.9 123.3-80c1.6-9.8-7.7-18.4-17.7-15.3zm-234.7-40.8c3.6 4.2 9.9 5.7 15.3 2.5l80-48c3.6-2.2 5.8-6.1 5.8-10.3s-2.2-8.1-5.8-10.3l-80-48c-5.1-3-11.4-1.9-15.3 2.5-3.8 4.5-3.8 11-.1 15.5l33.6 40.3-33.6 40.3c-3.8 4.5-3.7 11.1.1 15.5zm242.9 2.5c5.4 3.2 11.7 1.7 15.3-2.5 3.8-4.5 3.8-11 .1-15.5L343.6 208l33.6-40.3c3.8-4.5 3.7-11-.1-15.5-3.8-4.4-10.2-5.4-15.3-2.5l-80 48c-3.6 2.2-5.8 6.1-5.8 10.3s2.2 8.1 5.8 10.3l80 48z"],
|
||||
"grin-squint-tears": [512, 512, [], "f586", "M117.1 384.1c-25.8 3.7-84 13.7-100.9 30.6-21.9 21.9-21.5 57.9.9 80.3s58.3 22.8 80.3.9C114.3 479 124.3 420.8 128 395c.8-6.4-4.6-11.8-10.9-10.9zm-41.2-41.7C40.3 268 53 176.1 114.6 114.6 152.4 76.8 202.6 56 256 56c36.2 0 70.8 9.8 101.2 27.7 3.8-20.3 8-36.1 12-48.3C333.8 17.2 294.9 8 256 8 192.5 8 129.1 32.2 80.6 80.6c-74.1 74.1-91.3 183.4-52 274 12.2-4.1 27.7-8.3 47.3-12.2zm352.3-187.6c45 76.6 34.9 176.9-30.8 242.6-37.8 37.8-88 58.6-141.4 58.6-30.5 0-59.8-7-86.4-19.8-3.9 19.5-8 35-12.2 47.2 31.4 13.6 65 20.6 98.7 20.6 63.5 0 126.9-24.2 175.4-72.6 78.1-78.1 93.1-195.4 45.2-288.6-12.3 4-28.2 8.1-48.5 12zm-33.3-26.9c25.8-3.7 84-13.7 100.9-30.6 21.9-21.9 21.5-57.9-.9-80.3s-58.3-22.8-80.3-.9C397.7 33 387.7 91.2 384 117c-.8 6.4 4.6 11.8 10.9 10.9zm-187 108.3c-3-3-7.2-4.2-11.4-3.2L106 255.7c-5.7 1.4-9.5 6.7-9.1 12.6.5 5.8 5.1 10.5 10.9 11l52.3 4.8 4.8 52.3c.5 5.8 5.2 10.4 11 10.9h.9c5.5 0 10.3-3.7 11.7-9.1l22.6-90.5c1-4.2-.2-8.5-3.2-11.5zm39.7-25.1l90.5-22.6c5.7-1.4 9.5-6.7 9.1-12.6-.5-5.8-5.1-10.5-10.9-11l-52.3-4.8-4.8-52.3c-.5-5.8-5.2-10.4-11-10.9-5.6-.1-11.2 3.4-12.6 9.1L233 196.5c-1 4.1.2 8.4 3.2 11.4 5 5 11.3 3.2 11.4 3.2zm52 88.5c-29.1 29.1-59.7 52.9-83.9 65.4-9.2 4.8-10 17.5-1.7 23.4 38.9 27.7 107 6.2 143.7-30.6S416 253 388.3 214.1c-5.8-8.2-18.5-7.6-23.4 1.7-12.3 24.2-36.2 54.7-65.3 83.8z"],
|
||||
"grin-stars": [496, 512, [], "f587", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm105.6-151.4c-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.8-3.1-19.4 5.3-17.7 15.3 7.9 47.2 71.3 80 123.3 80s115.3-32.9 123.3-80c1.6-9.8-7.7-18.4-17.7-15.3zm-227.9-57.5c-1 6.2 5.4 11 11 7.9l31.3-16.3 31.3 16.3c5.6 3.1 12-1.7 11-7.9l-6-34.9 25.4-24.6c4.5-4.5 1.9-12.2-4.3-13.2l-34.9-5-15.5-31.6c-2.9-5.8-11-5.8-13.9 0l-15.5 31.6-34.9 5c-6.2.9-8.9 8.6-4.3 13.2l25.4 24.6-6.1 34.9zm259.7-72.7l-34.9-5-15.5-31.6c-2.9-5.8-11-5.8-13.9 0l-15.5 31.6-34.9 5c-6.2.9-8.9 8.6-4.3 13.2l25.4 24.6-6 34.9c-1 6.2 5.4 11 11 7.9l31.3-16.3 31.3 16.3c5.6 3.1 12-1.7 11-7.9l-6-34.9 25.4-24.6c4.5-4.6 1.8-12.2-4.4-13.2z"],
|
||||
"grin-tears": [640, 512, [], "f588", "M117.1 256.1c-25.8 3.7-84 13.7-100.9 30.6-21.9 21.9-21.5 57.9.9 80.3s58.3 22.8 80.3.9C114.3 351 124.3 292.8 128 267c.8-6.4-4.6-11.8-10.9-10.9zm506.7 30.6c-16.9-16.9-75.1-26.9-100.9-30.6-6.3-.9-11.7 4.5-10.8 10.8 3.7 25.8 13.7 84 30.6 100.9 21.9 21.9 57.9 21.5 80.3-.9 22.3-22.3 22.7-58.3.8-80.2zm-126.6 61.7C463.8 412.3 396.9 456 320 456c-76.9 0-143.8-43.7-177.2-107.6-12.5 37.4-25.2 43.9-28.3 46.5C159.1 460.7 234.5 504 320 504s160.9-43.3 205.5-109.1c-3.2-2.7-15.9-9.2-28.3-46.5zM122.7 224.5C137.9 129.2 220.5 56 320 56c99.5 0 182.1 73.2 197.3 168.5 2.1-.2 5.2-2.4 49.5 7C554.4 106 448.7 8 320 8S85.6 106 73.2 231.4c44.5-9.4 47.1-7.2 49.5-6.9zM320 400c51.9 0 115.3-32.9 123.3-80 1.7-9.9-7.7-18.5-17.7-15.3-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.8-3.1-19.4 5.3-17.7 15.3 8 47.1 71.4 80 123.3 80zm130.3-168.3c3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3 3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.6 6.2 4.6 9.3 3.7zM240 189.4c12.3 0 23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3 3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.8 19.2-21.6 31.5-21.6z"],
|
||||
"grin-tongue": [496, 512, [], "f589", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm64 400c0 35.6-29.1 64.5-64.9 64-35.1-.5-63.1-29.8-63.1-65v-42.8l17.7-8.8c15-7.5 31.5 1.7 34.9 16.5l2.8 12.1c2.1 9.2 15.2 9.2 17.3 0l2.8-12.1c3.4-14.8 19.8-24.1 34.9-16.5l17.7 8.8V408zm28.2 25.3c2.2-8.1 3.8-16.5 3.8-25.3v-43.5c14.2-12.4 24.4-27.5 27.3-44.5 1.7-9.9-7.7-18.5-17.7-15.3-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.9-3.1-19.4 5.3-17.7 15.3 2.9 17 13.1 32.1 27.3 44.5V408c0 8.8 1.6 17.2 3.8 25.3C91.8 399.9 48 333 48 256c0-110.3 89.7-200 200-200s200 89.7 200 200c0 77-43.8 143.9-107.8 177.3zM168 176c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z"],
|
||||
"grin-tongue-squint": [496, 512, [], "f58a", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm64 400c0 35.6-29.1 64.5-64.9 64-35.1-.5-63.1-29.8-63.1-65v-42.8l17.7-8.8c15-7.5 31.5 1.7 34.9 16.5l2.8 12.1c2.1 9.2 15.2 9.2 17.3 0l2.8-12.1c3.4-14.8 19.8-24.1 34.9-16.5l17.7 8.8V408zm28.2 25.3c2.2-8.1 3.8-16.5 3.8-25.3v-43.5c14.2-12.4 24.4-27.5 27.3-44.5 1.7-9.9-7.7-18.5-17.7-15.3-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.9-3.1-19.4 5.3-17.7 15.3 2.9 17 13.1 32.1 27.3 44.5V408c0 8.8 1.6 17.2 3.8 25.3C91.8 399.9 48 333 48 256c0-110.3 89.7-200 200-200s200 89.7 200 200c0 77-43.8 143.9-107.8 177.3zm36.9-281.1c-3.8-4.4-10.3-5.5-15.3-2.5l-80 48c-3.6 2.2-5.8 6.1-5.8 10.3s2.2 8.1 5.8 10.3l80 48c5.4 3.2 11.7 1.7 15.3-2.5 3.8-4.5 3.8-11 .1-15.5L343.6 208l33.6-40.3c3.8-4.5 3.7-11.1-.1-15.5zm-162.9 45.5l-80-48c-5-3-11.4-2-15.3 2.5-3.8 4.5-3.8 11-.1 15.5l33.6 40.3-33.6 40.3c-3.8 4.5-3.7 11 .1 15.5 3.6 4.2 9.9 5.7 15.3 2.5l80-48c3.6-2.2 5.8-6.1 5.8-10.3s-2.2-8.1-5.8-10.3z"],
|
||||
"grin-tongue-wink": [496, 512, [], "f58b", "M152 180c-25.7 0-55.9 16.9-59.8 42.1-.8 5 1.7 10 6.1 12.4 4.4 2.4 9.9 1.8 13.7-1.6l9.5-8.5c14.8-13.2 46.2-13.2 61 0l9.5 8.5c2.5 2.2 8 4.7 13.7 1.6 4.4-2.4 6.9-7.4 6.1-12.4-3.9-25.2-34.1-42.1-59.8-42.1zm176-52c-44.2 0-80 35.8-80 80s35.8 80 80 80 80-35.8 80-80-35.8-80-80-80zm0 128c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm0-72c-13.3 0-24 10.7-24 24s10.7 24 24 24 24-10.7 24-24-10.7-24-24-24zM248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm64 400c0 35.6-29.1 64.5-64.9 64-35.1-.5-63.1-29.8-63.1-65v-42.8l17.7-8.8c15-7.5 31.5 1.7 34.9 16.5l2.8 12.1c2.1 9.2 15.2 9.2 17.3 0l2.8-12.1c3.4-14.8 19.8-24.1 34.9-16.5l17.7 8.8V408zm28.2 25.3c2.2-8.1 3.8-16.5 3.8-25.3v-43.5c14.2-12.4 24.4-27.5 27.3-44.5 1.7-9.9-7.7-18.5-17.7-15.3-25.9 8.3-64.4 13.1-105.6 13.1s-79.6-4.8-105.6-13.1c-9.9-3.1-19.4 5.3-17.7 15.3 2.9 17 13.1 32.1 27.3 44.5V408c0 8.8 1.6 17.2 3.8 25.3C91.8 399.9 48 333 48 256c0-110.3 89.7-200 200-200s200 89.7 200 200c0 77-43.8 143.9-107.8 177.3z"],
|
||||
"grin-wink": [496, 512, [], "f58c", "M328 180c-25.69 0-55.88 16.92-59.86 42.12-1.75 11.22 11.5 18.24 19.83 10.84l9.55-8.48c14.81-13.19 46.16-13.19 60.97 0l9.55 8.48c8.48 7.43 21.56.25 19.83-10.84C383.88 196.92 353.69 180 328 180zm-160 60c17.67 0 32-14.33 32-32s-14.33-32-32-32-32 14.33-32 32 14.33 32 32 32zm185.55 64.64c-25.93 8.3-64.4 13.06-105.55 13.06s-79.62-4.75-105.55-13.06c-9.94-3.13-19.4 5.37-17.71 15.34C132.67 367.13 196.06 400 248 400s115.33-32.87 123.26-80.02c1.68-9.89-7.67-18.48-17.71-15.34zM248 8C111.03 8 0 119.03 0 256s111.03 248 248 248 248-111.03 248-248S384.97 8 248 8zm0 448c-110.28 0-200-89.72-200-200S137.72 56 248 56s200 89.72 200 200-89.72 200-200 200z"],
|
||||
"hand-lizard": [576, 512, [], "f258", "M556.686 290.542L410.328 64.829C397.001 44.272 374.417 32 349.917 32H56C25.121 32 0 57.122 0 88v8c0 44.112 35.888 80 80 80h196.042l-18.333 48H144c-48.523 0-88 39.477-88 88 0 30.879 25.121 56 56 56h131.552c2.987 0 5.914.549 8.697 1.631L352 408.418V480h224V355.829c0-23.225-6.679-45.801-19.314-65.287zM528 432H400v-23.582c0-19.948-12.014-37.508-30.604-44.736l-99.751-38.788A71.733 71.733 0 0 0 243.552 320H112c-4.411 0-8-3.589-8-8 0-22.056 17.944-40 40-40h113.709c19.767 0 37.786-12.407 44.84-30.873l24.552-64.281c8.996-23.553-8.428-48.846-33.63-48.846H80c-17.645 0-32-14.355-32-32v-8c0-4.411 3.589-8 8-8h293.917c8.166 0 15.693 4.09 20.137 10.942l146.358 225.715A71.84 71.84 0 0 1 528 355.829V432z"],
|
||||
"hand-paper": [448, 512, [], "f256", "M372.57 112.641v-10.825c0-43.612-40.52-76.691-83.039-65.546-25.629-49.5-94.09-47.45-117.982.747C130.269 26.456 89.144 57.945 89.144 102v126.13c-19.953-7.427-43.308-5.068-62.083 8.871-29.355 21.796-35.794 63.333-14.55 93.153L132.48 498.569a32 32 0 0 0 26.062 13.432h222.897c14.904 0 27.835-10.289 31.182-24.813l30.184-130.958A203.637 203.637 0 0 0 448 310.564V179c0-40.62-35.523-71.992-75.43-66.359zm27.427 197.922c0 11.731-1.334 23.469-3.965 34.886L368.707 464h-201.92L51.591 302.303c-14.439-20.27 15.023-42.776 29.394-22.605l27.128 38.079c8.995 12.626 29.031 6.287 29.031-9.283V102c0-25.645 36.571-24.81 36.571.691V256c0 8.837 7.163 16 16 16h6.856c8.837 0 16-7.163 16-16V67c0-25.663 36.571-24.81 36.571.691V256c0 8.837 7.163 16 16 16h6.856c8.837 0 16-7.163 16-16V101.125c0-25.672 36.57-24.81 36.57.691V256c0 8.837 7.163 16 16 16h6.857c8.837 0 16-7.163 16-16v-76.309c0-26.242 36.57-25.64 36.57-.691v131.563z"],
|
||||
"hand-peace": [448, 512, [], "f25b", "M362.146 191.976c-13.71-21.649-38.761-34.016-65.006-30.341V74c0-40.804-32.811-74-73.141-74-40.33 0-73.14 33.196-73.14 74L160 168l-18.679-78.85C126.578 50.843 83.85 32.11 46.209 47.208 8.735 62.238-9.571 104.963 5.008 142.85l55.757 144.927c-30.557 24.956-43.994 57.809-24.733 92.218l54.853 97.999C102.625 498.97 124.73 512 148.575 512h205.702c30.744 0 57.558-21.44 64.555-51.797l27.427-118.999a67.801 67.801 0 0 0 1.729-15.203L448 256c0-44.956-43.263-77.343-85.854-64.024zM399.987 326c0 1.488-.169 2.977-.502 4.423l-27.427 119.001c-1.978 8.582-9.29 14.576-17.782 14.576H148.575c-6.486 0-12.542-3.621-15.805-9.449l-54.854-98c-4.557-8.141-2.619-18.668 4.508-24.488l26.647-21.764a16 16 0 0 0 4.812-18.139l-64.09-166.549C37.226 92.956 84.37 74.837 96.51 106.389l59.784 155.357A16 16 0 0 0 171.227 272h11.632c8.837 0 16-7.163 16-16V74c0-34.375 50.281-34.43 50.281 0v182c0 8.837 7.163 16 16 16h6.856c8.837 0 16-7.163 16-16v-28c0-25.122 36.567-25.159 36.567 0v28c0 8.837 7.163 16 16 16h6.856c8.837 0 16-7.163 16-16 0-25.12 36.567-25.16 36.567 0v70z"],
|
||||
"hand-point-down": [448, 512, [], "f0a7", "M188.8 512c45.616 0 83.2-37.765 83.2-83.2v-35.647a93.148 93.148 0 0 0 22.064-7.929c22.006 2.507 44.978-3.503 62.791-15.985C409.342 368.1 448 331.841 448 269.299V248c0-60.063-40-98.512-40-127.2v-2.679c4.952-5.747 8-13.536 8-22.12V32c0-17.673-12.894-32-28.8-32H156.8C140.894 0 128 14.327 128 32v64c0 8.584 3.048 16.373 8 22.12v2.679c0 6.964-6.193 14.862-23.668 30.183l-.148.129-.146.131c-9.937 8.856-20.841 18.116-33.253 25.851C48.537 195.798 0 207.486 0 252.8c0 56.928 35.286 92 83.2 92 8.026 0 15.489-.814 22.4-2.176V428.8c0 45.099 38.101 83.2 83.2 83.2zm0-48c-18.7 0-35.2-16.775-35.2-35.2V270.4c-17.325 0-35.2 26.4-70.4 26.4-26.4 0-35.2-20.625-35.2-44 0-8.794 32.712-20.445 56.1-34.926 14.575-9.074 27.225-19.524 39.875-30.799 18.374-16.109 36.633-33.836 39.596-59.075h176.752C364.087 170.79 400 202.509 400 248v21.299c0 40.524-22.197 57.124-61.325 50.601-8.001 14.612-33.979 24.151-53.625 12.925-18.225 19.365-46.381 17.787-61.05 4.95V428.8c0 18.975-16.225 35.2-35.2 35.2zM328 64c0-13.255 10.745-24 24-24s24 10.745 24 24-10.745 24-24 24-24-10.745-24-24z"],
|
||||
"hand-point-left": [512, 512, [], "f0a5", "M0 220.8C0 266.416 37.765 304 83.2 304h35.647a93.148 93.148 0 0 0 7.929 22.064c-2.507 22.006 3.503 44.978 15.985 62.791C143.9 441.342 180.159 480 242.701 480H264c60.063 0 98.512-40 127.2-40h2.679c5.747 4.952 13.536 8 22.12 8h64c17.673 0 32-12.894 32-28.8V188.8c0-15.906-14.327-28.8-32-28.8h-64c-8.584 0-16.373 3.048-22.12 8H391.2c-6.964 0-14.862-6.193-30.183-23.668l-.129-.148-.131-.146c-8.856-9.937-18.116-20.841-25.851-33.253C316.202 80.537 304.514 32 259.2 32c-56.928 0-92 35.286-92 83.2 0 8.026.814 15.489 2.176 22.4H83.2C38.101 137.6 0 175.701 0 220.8zm48 0c0-18.7 16.775-35.2 35.2-35.2h158.4c0-17.325-26.4-35.2-26.4-70.4 0-26.4 20.625-35.2 44-35.2 8.794 0 20.445 32.712 34.926 56.1 9.074 14.575 19.524 27.225 30.799 39.875 16.109 18.374 33.836 36.633 59.075 39.596v176.752C341.21 396.087 309.491 432 264 432h-21.299c-40.524 0-57.124-22.197-50.601-61.325-14.612-8.001-24.151-33.979-12.925-53.625-19.365-18.225-17.787-46.381-4.95-61.05H83.2C64.225 256 48 239.775 48 220.8zM448 360c13.255 0 24 10.745 24 24s-10.745 24-24 24-24-10.745-24-24 10.745-24 24-24z"],
|
||||
"hand-point-right": [512, 512, [], "f0a4", "M428.8 137.6h-86.177a115.52 115.52 0 0 0 2.176-22.4c0-47.914-35.072-83.2-92-83.2-45.314 0-57.002 48.537-75.707 78.784-7.735 12.413-16.994 23.317-25.851 33.253l-.131.146-.129.148C135.662 161.807 127.764 168 120.8 168h-2.679c-5.747-4.952-13.536-8-22.12-8H32c-17.673 0-32 12.894-32 28.8v230.4C0 435.106 14.327 448 32 448h64c8.584 0 16.373-3.048 22.12-8h2.679c28.688 0 67.137 40 127.2 40h21.299c62.542 0 98.8-38.658 99.94-91.145 12.482-17.813 18.491-40.785 15.985-62.791A93.148 93.148 0 0 0 393.152 304H428.8c45.435 0 83.2-37.584 83.2-83.2 0-45.099-38.101-83.2-83.2-83.2zm0 118.4h-91.026c12.837 14.669 14.415 42.825-4.95 61.05 11.227 19.646 1.687 45.624-12.925 53.625 6.524 39.128-10.076 61.325-50.6 61.325H248c-45.491 0-77.21-35.913-120-39.676V215.571c25.239-2.964 42.966-21.222 59.075-39.596 11.275-12.65 21.725-25.3 30.799-39.875C232.355 112.712 244.006 80 252.8 80c23.375 0 44 8.8 44 35.2 0 35.2-26.4 53.075-26.4 70.4h158.4c18.425 0 35.2 16.5 35.2 35.2 0 18.975-16.225 35.2-35.2 35.2zM88 384c0 13.255-10.745 24-24 24s-24-10.745-24-24 10.745-24 24-24 24 10.745 24 24z"],
|
||||
"hand-point-up": [448, 512, [], "f0a6", "M105.6 83.2v86.177a115.52 115.52 0 0 0-22.4-2.176c-47.914 0-83.2 35.072-83.2 92 0 45.314 48.537 57.002 78.784 75.707 12.413 7.735 23.317 16.994 33.253 25.851l.146.131.148.129C129.807 376.338 136 384.236 136 391.2v2.679c-4.952 5.747-8 13.536-8 22.12v64c0 17.673 12.894 32 28.8 32h230.4c15.906 0 28.8-14.327 28.8-32v-64c0-8.584-3.048-16.373-8-22.12V391.2c0-28.688 40-67.137 40-127.2v-21.299c0-62.542-38.658-98.8-91.145-99.94-17.813-12.482-40.785-18.491-62.791-15.985A93.148 93.148 0 0 0 272 118.847V83.2C272 37.765 234.416 0 188.8 0c-45.099 0-83.2 38.101-83.2 83.2zm118.4 0v91.026c14.669-12.837 42.825-14.415 61.05 4.95 19.646-11.227 45.624-1.687 53.625 12.925 39.128-6.524 61.325 10.076 61.325 50.6V264c0 45.491-35.913 77.21-39.676 120H183.571c-2.964-25.239-21.222-42.966-39.596-59.075-12.65-11.275-25.3-21.725-39.875-30.799C80.712 279.645 48 267.994 48 259.2c0-23.375 8.8-44 35.2-44 35.2 0 53.075 26.4 70.4 26.4V83.2c0-18.425 16.5-35.2 35.2-35.2 18.975 0 35.2 16.225 35.2 35.2zM352 424c13.255 0 24 10.745 24 24s-10.745 24-24 24-24-10.745-24-24 10.745-24 24-24z"],
|
||||
"hand-pointer": [448, 512, [], "f25a", "M358.182 179.361c-19.493-24.768-52.679-31.945-79.872-19.098-15.127-15.687-36.182-22.487-56.595-19.629V67c0-36.944-29.736-67-66.286-67S89.143 30.056 89.143 67v161.129c-19.909-7.41-43.272-5.094-62.083 8.872-29.355 21.795-35.793 63.333-14.55 93.152l109.699 154.001C134.632 501.59 154.741 512 176 512h178.286c30.802 0 57.574-21.5 64.557-51.797l27.429-118.999A67.873 67.873 0 0 0 448 326v-84c0-46.844-46.625-79.273-89.818-62.639zM80.985 279.697l27.126 38.079c8.995 12.626 29.031 6.287 29.031-9.283V67c0-25.12 36.571-25.16 36.571 0v175c0 8.836 7.163 16 16 16h6.857c8.837 0 16-7.164 16-16v-35c0-25.12 36.571-25.16 36.571 0v35c0 8.836 7.163 16 16 16H272c8.837 0 16-7.164 16-16v-21c0-25.12 36.571-25.16 36.571 0v21c0 8.836 7.163 16 16 16h6.857c8.837 0 16-7.164 16-16 0-25.121 36.571-25.16 36.571 0v84c0 1.488-.169 2.977-.502 4.423l-27.43 119.001c-1.978 8.582-9.29 14.576-17.782 14.576H176c-5.769 0-11.263-2.878-14.697-7.697l-109.712-154c-14.406-20.223 14.994-42.818 29.394-22.606zM176.143 400v-96c0-8.837 6.268-16 14-16h6c7.732 0 14 7.163 14 16v96c0 8.837-6.268 16-14 16h-6c-7.733 0-14-7.163-14-16zm75.428 0v-96c0-8.837 6.268-16 14-16h6c7.732 0 14 7.163 14 16v96c0 8.837-6.268 16-14 16h-6c-7.732 0-14-7.163-14-16zM327 400v-96c0-8.837 6.268-16 14-16h6c7.732 0 14 7.163 14 16v96c0 8.837-6.268 16-14 16h-6c-7.732 0-14-7.163-14-16z"],
|
||||
"hand-rock": [512, 512, [], "f255", "M408.864 79.052c-22.401-33.898-66.108-42.273-98.813-23.588-29.474-31.469-79.145-31.093-108.334-.022-47.16-27.02-108.71 5.055-110.671 60.806C44.846 105.407 0 140.001 0 187.429v56.953c0 32.741 14.28 63.954 39.18 85.634l97.71 85.081c4.252 3.702 3.11 5.573 3.11 32.903 0 17.673 14.327 32 32 32h252c17.673 0 32-14.327 32-32 0-23.513-1.015-30.745 3.982-42.37l42.835-99.656c6.094-14.177 9.183-29.172 9.183-44.568V146.963c0-52.839-54.314-88.662-103.136-67.911zM464 261.406a64.505 64.505 0 0 1-5.282 25.613l-42.835 99.655c-5.23 12.171-7.883 25.04-7.883 38.25V432H188v-10.286c0-16.37-7.14-31.977-19.59-42.817l-97.71-85.08C56.274 281.255 48 263.236 48 244.381v-56.953c0-33.208 52-33.537 52 .677v41.228a16 16 0 0 0 5.493 12.067l7 6.095A16 16 0 0 0 139 235.429V118.857c0-33.097 52-33.725 52 .677v26.751c0 8.836 7.164 16 16 16h7c8.836 0 16-7.164 16-16v-41.143c0-33.134 52-33.675 52 .677v40.466c0 8.836 7.163 16 16 16h7c8.837 0 16-7.164 16-16v-27.429c0-33.03 52-33.78 52 .677v26.751c0 8.836 7.163 16 16 16h7c8.837 0 16-7.164 16-16 0-33.146 52-33.613 52 .677v114.445z"],
|
||||
"hand-scissors": [512, 512, [], "f257", "M256 480l70-.013c5.114 0 10.231-.583 15.203-1.729l118.999-27.427C490.56 443.835 512 417.02 512 386.277V180.575c0-23.845-13.03-45.951-34.005-57.69l-97.999-54.853c-34.409-19.261-67.263-5.824-92.218 24.733L142.85 37.008c-37.887-14.579-80.612 3.727-95.642 41.201-15.098 37.642 3.635 80.37 41.942 95.112L168 192l-94-9.141c-40.804 0-74 32.811-74 73.14 0 40.33 33.196 73.141 74 73.141h87.635c-3.675 26.245 8.692 51.297 30.341 65.006C178.657 436.737 211.044 480 256 480zm0-48.013c-25.16 0-25.12-36.567 0-36.567 8.837 0 16-7.163 16-16v-6.856c0-8.837-7.163-16-16-16h-28c-25.159 0-25.122-36.567 0-36.567h28c8.837 0 16-7.163 16-16v-6.856c0-8.837-7.163-16-16-16H74c-34.43 0-34.375-50.281 0-50.281h182c8.837 0 16-7.163 16-16v-11.632a16 16 0 0 0-10.254-14.933L106.389 128.51c-31.552-12.14-13.432-59.283 19.222-46.717l166.549 64.091a16.001 16.001 0 0 0 18.139-4.812l21.764-26.647c5.82-7.127 16.348-9.064 24.488-4.508l98 54.854c5.828 3.263 9.449 9.318 9.449 15.805v205.701c0 8.491-5.994 15.804-14.576 17.782l-119.001 27.427a19.743 19.743 0 0 1-4.423.502h-70z"],
|
||||
"hand-spock": [512, 512, [], "f259", "M21.096 381.79l129.092 121.513a32 32 0 0 0 21.932 8.698h237.6c14.17 0 26.653-9.319 30.68-22.904l31.815-107.313A115.955 115.955 0 0 0 477 348.811v-36.839c0-4.051.476-8.104 1.414-12.045l31.73-133.41c10.099-42.412-22.316-82.738-65.544-82.525-4.144-24.856-22.543-47.165-49.85-53.992-35.803-8.952-72.227 12.655-81.25 48.75L296.599 184 274.924 52.01c-8.286-36.07-44.303-58.572-80.304-50.296-29.616 6.804-50.138 32.389-51.882 61.295-42.637.831-73.455 40.563-64.071 81.844l31.04 136.508c-27.194-22.515-67.284-19.992-91.482 5.722-25.376 26.961-24.098 69.325 2.871 94.707zm32.068-61.811l.002-.001c7.219-7.672 19.241-7.98 26.856-.813l53.012 49.894C143.225 378.649 160 371.4 160 357.406v-69.479c0-1.193-.134-2.383-.397-3.546l-34.13-150.172c-5.596-24.617 31.502-32.86 37.054-8.421l30.399 133.757a16 16 0 0 0 15.603 12.454h8.604c10.276 0 17.894-9.567 15.594-19.583l-41.62-181.153c-5.623-24.469 31.39-33.076 37.035-8.508l45.22 196.828A16 16 0 0 0 288.956 272h13.217a16 16 0 0 0 15.522-12.119l42.372-169.49c6.104-24.422 42.962-15.159 36.865 9.217L358.805 252.12c-2.521 10.088 5.115 19.88 15.522 19.88h9.694a16 16 0 0 0 15.565-12.295L426.509 146.6c5.821-24.448 42.797-15.687 36.966 8.802L431.72 288.81a100.094 100.094 0 0 0-2.72 23.162v36.839c0 6.548-.943 13.051-2.805 19.328L397.775 464h-219.31L53.978 346.836c-7.629-7.18-7.994-19.229-.814-26.857z"],
|
||||
"handshake": [640, 512, [], "f2b5", "M519.2 127.9l-47.6-47.6A56.252 56.252 0 0 0 432 64H205.2c-14.8 0-29.1 5.9-39.6 16.3L118 127.9H0v255.7h64c17.6 0 31.8-14.2 31.9-31.7h9.1l84.6 76.4c30.9 25.1 73.8 25.7 105.6 3.8 12.5 10.8 26 15.9 41.1 15.9 18.2 0 35.3-7.4 48.8-24 22.1 8.7 48.2 2.6 64-16.8l26.2-32.3c5.6-6.9 9.1-14.8 10.9-23h57.9c.1 17.5 14.4 31.7 31.9 31.7h64V127.9H519.2zM48 351.6c-8.8 0-16-7.2-16-16s7.2-16 16-16 16 7.2 16 16c0 8.9-7.2 16-16 16zm390-6.9l-26.1 32.2c-2.8 3.4-7.8 4-11.3 1.2l-23.9-19.4-30 36.5c-6 7.3-15 4.8-18 2.4l-36.8-31.5-15.6 19.2c-13.9 17.1-39.2 19.7-55.3 6.6l-97.3-88H96V175.8h41.9l61.7-61.6c2-.8 3.7-1.5 5.7-2.3H262l-38.7 35.5c-29.4 26.9-31.1 72.3-4.4 101.3 14.8 16.2 61.2 41.2 101.5 4.4l8.2-7.5 108.2 87.8c3.4 2.8 3.9 7.9 1.2 11.3zm106-40.8h-69.2c-2.3-2.8-4.9-5.4-7.7-7.7l-102.7-83.4 12.5-11.4c6.5-6 7-16.1 1-22.6L367 167.1c-6-6.5-16.1-6.9-22.6-1l-55.2 50.6c-9.5 8.7-25.7 9.4-34.6 0-9.3-9.9-8.5-25.1 1.2-33.9l65.6-60.1c7.4-6.8 17-10.5 27-10.5l83.7-.2c2.1 0 4.1.8 5.5 2.3l61.7 61.6H544v128zm48 47.7c-8.8 0-16-7.2-16-16s7.2-16 16-16 16 7.2 16 16c0 8.9-7.2 16-16 16z"],
|
||||
"hdd": [576, 512, [], "f0a0", "M567.403 235.642L462.323 84.589A48 48 0 0 0 422.919 64H153.081a48 48 0 0 0-39.404 20.589L8.597 235.642A48.001 48.001 0 0 0 0 263.054V400c0 26.51 21.49 48 48 48h480c26.51 0 48-21.49 48-48V263.054c0-9.801-3-19.366-8.597-27.412zM153.081 112h269.838l77.913 112H75.168l77.913-112zM528 400H48V272h480v128zm-32-64c0 17.673-14.327 32-32 32s-32-14.327-32-32 14.327-32 32-32 32 14.327 32 32zm-96 0c0 17.673-14.327 32-32 32s-32-14.327-32-32 14.327-32 32-32 32 14.327 32 32z"],
|
||||
"heart": [512, 512, [], "f004", "M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"],
|
||||
"hospital": [448, 512, [], "f0f8", "M128 244v-40c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v40c0 6.627-5.373 12-12 12h-40c-6.627 0-12-5.373-12-12zm140 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm-76 84v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm76 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm180 124v36H0v-36c0-6.627 5.373-12 12-12h19.5V85.035C31.5 73.418 42.245 64 55.5 64H144V24c0-13.255 10.745-24 24-24h112c13.255 0 24 10.745 24 24v40h88.5c13.255 0 24 9.418 24 21.035V464H436c6.627 0 12 5.373 12 12zM79.5 463H192v-67c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v67h112.5V112H304v24c0 13.255-10.745 24-24 24H168c-13.255 0-24-10.745-24-24v-24H79.5v351zM266 64h-26V38a6 6 0 0 0-6-6h-20a6 6 0 0 0-6 6v26h-26a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h26v26a6 6 0 0 0 6 6h20a6 6 0 0 0 6-6V96h26a6 6 0 0 0 6-6V70a6 6 0 0 0-6-6z"],
|
||||
"hourglass": [384, 512, [], "f254", "M368 48h4c6.627 0 12-5.373 12-12V12c0-6.627-5.373-12-12-12H12C5.373 0 0 5.373 0 12v24c0 6.627 5.373 12 12 12h4c0 80.564 32.188 165.807 97.18 208C47.899 298.381 16 383.9 16 464h-4c-6.627 0-12 5.373-12 12v24c0 6.627 5.373 12 12 12h360c6.627 0 12-5.373 12-12v-24c0-6.627-5.373-12-12-12h-4c0-80.564-32.188-165.807-97.18-208C336.102 213.619 368 128.1 368 48zM64 48h256c0 101.62-57.307 184-128 184S64 149.621 64 48zm256 416H64c0-101.62 57.308-184 128-184s128 82.38 128 184z"],
|
||||
"id-badge": [384, 512, [], "f2c1", "M336 0H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zm0 464H48V48h288v416zM144 112h96c8.8 0 16-7.2 16-16s-7.2-16-16-16h-96c-8.8 0-16 7.2-16 16s7.2 16 16 16zm48 176c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm-89.6 128h179.2c12.4 0 22.4-8.6 22.4-19.2v-19.2c0-31.8-30.1-57.6-67.2-57.6-10.8 0-18.7 8-44.8 8-26.9 0-33.4-8-44.8-8-37.1 0-67.2 25.8-67.2 57.6v19.2c0 10.6 10 19.2 22.4 19.2z"],
|
||||
"id-card": [576, 512, [], "f2c2", "M528 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h480c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 400H303.2c.9-4.5.8 3.6.8-22.4 0-31.8-30.1-57.6-67.2-57.6-10.8 0-18.7 8-44.8 8-26.9 0-33.4-8-44.8-8-37.1 0-67.2 25.8-67.2 57.6 0 26-.2 17.9.8 22.4H48V144h480v288zm-168-80h112c4.4 0 8-3.6 8-8v-16c0-4.4-3.6-8-8-8H360c-4.4 0-8 3.6-8 8v16c0 4.4 3.6 8 8 8zm0-64h112c4.4 0 8-3.6 8-8v-16c0-4.4-3.6-8-8-8H360c-4.4 0-8 3.6-8 8v16c0 4.4 3.6 8 8 8zm0-64h112c4.4 0 8-3.6 8-8v-16c0-4.4-3.6-8-8-8H360c-4.4 0-8 3.6-8 8v16c0 4.4 3.6 8 8 8zm-168 96c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64z"],
|
||||
"image": [512, 512, [], "f03e", "M464 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm-6 336H54a6 6 0 0 1-6-6V118a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v276a6 6 0 0 1-6 6zM128 152c-22.091 0-40 17.909-40 40s17.909 40 40 40 40-17.909 40-40-17.909-40-40-40zM96 352h320v-80l-87.515-87.515c-4.686-4.686-12.284-4.686-16.971 0L192 304l-39.515-39.515c-4.686-4.686-12.284-4.686-16.971 0L96 304v48z"],
|
||||
"images": [576, 512, [], "f302", "M480 416v16c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V176c0-26.51 21.49-48 48-48h16v48H54a6 6 0 0 0-6 6v244a6 6 0 0 0 6 6h372a6 6 0 0 0 6-6v-10h48zm42-336H150a6 6 0 0 0-6 6v244a6 6 0 0 0 6 6h372a6 6 0 0 0 6-6V86a6 6 0 0 0-6-6zm6-48c26.51 0 48 21.49 48 48v256c0 26.51-21.49 48-48 48H144c-26.51 0-48-21.49-48-48V80c0-26.51 21.49-48 48-48h384zM264 144c0 22.091-17.909 40-40 40s-40-17.909-40-40 17.909-40 40-40 40 17.909 40 40zm-72 96l39.515-39.515c4.686-4.686 12.284-4.686 16.971 0L288 240l103.515-103.515c4.686-4.686 12.284-4.686 16.971 0L480 208v80H192v-48z"],
|
||||
"keyboard": [576, 512, [], "f11c", "M528 64H48C21.49 64 0 85.49 0 112v288c0 26.51 21.49 48 48 48h480c26.51 0 48-21.49 48-48V112c0-26.51-21.49-48-48-48zm8 336c0 4.411-3.589 8-8 8H48c-4.411 0-8-3.589-8-8V112c0-4.411 3.589-8 8-8h480c4.411 0 8 3.589 8 8v288zM170 270v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm-336 82v-28c0-6.627-5.373-12-12-12H82c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm384 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zM122 188v-28c0-6.627-5.373-12-12-12H82c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm96 0v-28c0-6.627-5.373-12-12-12h-28c-6.627 0-12 5.373-12 12v28c0 6.627 5.373 12 12 12h28c6.627 0 12-5.373 12-12zm-98 158v-16c0-6.627-5.373-12-12-12H180c-6.627 0-12 5.373-12 12v16c0 6.627 5.373 12 12 12h216c6.627 0 12-5.373 12-12z"],
|
||||
"kiss": [496, 512, [], "f596", "M168 176c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm136 132c0-19.2-28.8-41.5-71.5-44-3.8-.4-7.4 2.4-8.2 6.2-.9 3.8 1.1 7.7 4.7 9.2l16.9 7.2c13 5.5 20.8 13.5 20.8 21.5s-7.8 16-20.7 21.5l-17 7.2c-5.7 2.4-6 12.2 0 14.8l16.9 7.2c13 5.5 20.8 13.5 20.8 21.5s-7.8 16-20.7 21.5l-17 7.2c-3.6 1.5-5.6 5.4-4.7 9.2.8 3.6 4.1 6.2 7.8 6.2h.5c42.8-2.5 71.5-24.8 71.5-44 0-13-13.4-27.3-35.2-36C290.6 335.3 304 321 304 308zM248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm80-280c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z"],
|
||||
"kiss-beam": [496, 512, [], "f597", "M168 152c-23.8 0-52.7 29.3-56 71.4-.3 3.7 2 7.2 5.6 8.3 3.5 1 7.5-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 5.9-4.5 5.6-8.3-3.1-42.1-32-71.4-55.8-71.4zM248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm56-148c0-19.2-28.8-41.5-71.5-44-3.8-.4-7.4 2.4-8.2 6.2-.9 3.8 1.1 7.7 4.7 9.2l16.9 7.2c13 5.5 20.8 13.5 20.8 21.5s-7.8 16-20.7 21.5l-17 7.2c-5.7 2.4-6 12.2 0 14.8l16.9 7.2c13 5.5 20.8 13.5 20.8 21.5s-7.8 16-20.7 21.5l-17 7.2c-3.6 1.5-5.6 5.4-4.7 9.2.8 3.6 4.1 6.2 7.8 6.2h.5c42.8-2.5 71.5-24.8 71.5-44 0-13-13.4-27.3-35.2-36C290.6 335.3 304 321 304 308zm24-156c-23.8 0-52.7 29.3-56 71.4-.3 3.7 2 7.2 5.6 8.3 3.5 1 7.5-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 5.9-4.5 5.6-8.3-3.1-42.1-32-71.4-55.8-71.4z"],
|
||||
"kiss-wink-heart": [504, 512, [], "f598", "M304 308.5c0-19.2-28.8-41.5-71.5-44-3.8-.4-7.4 2.4-8.2 6.2-.9 3.8 1.1 7.7 4.7 9.2l16.9 7.2c13 5.5 20.8 13.5 20.8 21.5s-7.8 16-20.7 21.5l-17 7.2c-5.7 2.4-6 12.2 0 14.8l16.9 7.2c13 5.5 20.8 13.5 20.8 21.5s-7.8 16-20.7 21.5l-17 7.2c-3.6 1.5-5.6 5.4-4.7 9.2.8 3.6 4.1 6.2 7.8 6.2h.5c42.8-2.5 71.5-24.8 71.5-44 0-13-13.4-27.3-35.2-36 21.7-9.1 35.1-23.4 35.1-36.4zm70.5-83.5l9.5 8.5c3.8 3.3 9.3 4 13.7 1.6 4.4-2.4 6.9-7.4 6.1-12.4-4-25.2-34.2-42.1-59.8-42.1s-55.9 16.9-59.8 42.1c-.8 5 1.7 10 6.1 12.4 5.8 3.1 11.2.7 13.7-1.6l9.5-8.5c14.8-13.2 46.2-13.2 61 0zM136 208.5c0 17.7 14.3 32 32 32s32-14.3 32-32-14.3-32-32-32-32 14.3-32 32zm365.1 194c-8-20.8-31.5-31.5-53.1-25.9l-8.4 2.2-2.3-8.4c-5.9-21.4-27-36.5-49-33-25.2 4-40.6 28.6-34 52.6l22.9 82.6c1.5 5.3 7 8.5 12.4 7.1l83-21.5c24.1-6.3 37.7-31.8 28.5-55.7zM334 436.3c-26.1 12.5-55.2 19.7-86 19.7-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200c0 22.1-3.7 43.3-10.4 63.2 9 6.4 17 14.2 22.6 23.9 6.4.1 12.6 1.4 18.6 2.9 10.9-27.9 17.1-58.2 17.1-90C496 119 385 8 248 8S0 119 0 256s111 248 248 248c35.4 0 68.9-7.5 99.4-20.9-2.5-7.3 4.3 17.2-13.4-46.8z"],
|
||||
"laugh": [496, 512, [], "f599", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm141.4 389.4c-37.8 37.8-88 58.6-141.4 58.6s-103.6-20.8-141.4-58.6S48 309.4 48 256s20.8-103.6 58.6-141.4S194.6 56 248 56s103.6 20.8 141.4 58.6S448 202.6 448 256s-20.8 103.6-58.6 141.4zM328 224c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm-160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm194.4 64H133.6c-8.2 0-14.5 7-13.5 15 7.5 59.2 58.9 105 121.1 105h13.6c62.2 0 113.6-45.8 121.1-105 1-8-5.3-15-13.5-15z"],
|
||||
"laugh-beam": [496, 512, [], "f59a", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm141.4 389.4c-37.8 37.8-88 58.6-141.4 58.6s-103.6-20.8-141.4-58.6S48 309.4 48 256s20.8-103.6 58.6-141.4S194.6 56 248 56s103.6 20.8 141.4 58.6S448 202.6 448 256s-20.8 103.6-58.6 141.4zM328 152c-23.8 0-52.7 29.3-56 71.4-.7 8.6 10.8 11.9 14.9 4.5l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c4.1 7.4 15.6 4 14.9-4.5-3.1-42.1-32-71.4-55.8-71.4zm-201 75.9l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c4.1 7.4 15.6 4 14.9-4.5-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.6 8.5 10.9 11.9 15.1 4.5zM362.4 288H133.6c-8.2 0-14.5 7-13.5 15 7.5 59.2 58.9 105 121.1 105h13.6c62.2 0 113.6-45.8 121.1-105 1-8-5.3-15-13.5-15z"],
|
||||
"laugh-squint": [496, 512, [], "f59b", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm141.4 389.4c-37.8 37.8-88 58.6-141.4 58.6s-103.6-20.8-141.4-58.6S48 309.4 48 256s20.8-103.6 58.6-141.4S194.6 56 248 56s103.6 20.8 141.4 58.6S448 202.6 448 256s-20.8 103.6-58.6 141.4zM343.6 196l33.6-40.3c8.6-10.3-3.8-24.8-15.4-18l-80 48c-7.8 4.7-7.8 15.9 0 20.6l80 48c11.5 6.8 24-7.6 15.4-18L343.6 196zm-209.4 58.3l80-48c7.8-4.7 7.8-15.9 0-20.6l-80-48c-11.6-6.9-24 7.7-15.4 18l33.6 40.3-33.6 40.3c-8.7 10.4 3.8 24.8 15.4 18zM362.4 288H133.6c-8.2 0-14.5 7-13.5 15 7.5 59.2 58.9 105 121.1 105h13.6c62.2 0 113.6-45.8 121.1-105 1-8-5.3-15-13.5-15z"],
|
||||
"laugh-wink": [496, 512, [], "f59c", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm141.4 389.4c-37.8 37.8-88 58.6-141.4 58.6s-103.6-20.8-141.4-58.6C68.8 359.6 48 309.4 48 256s20.8-103.6 58.6-141.4C144.4 76.8 194.6 56 248 56s103.6 20.8 141.4 58.6c37.8 37.8 58.6 88 58.6 141.4s-20.8 103.6-58.6 141.4zM328 164c-25.7 0-55.9 16.9-59.9 42.1-1.7 11.2 11.5 18.2 19.8 10.8l9.5-8.5c14.8-13.2 46.2-13.2 61 0l9.5 8.5c8.5 7.4 21.6.3 19.8-10.8-3.8-25.2-34-42.1-59.7-42.1zm-160 60c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm194.4 64H133.6c-8.2 0-14.5 7-13.5 15 7.5 59.2 58.9 105 121.1 105h13.6c62.2 0 113.6-45.8 121.1-105 1-8-5.3-15-13.5-15z"],
|
||||
"lemon": [512, 512, [], "f094", "M484.112 27.889C455.989-.233 416.108-8.057 387.059 8.865 347.604 31.848 223.504-41.111 91.196 91.197-41.277 223.672 31.923 347.472 8.866 387.058c-16.922 29.051-9.1 68.932 19.022 97.054 28.135 28.135 68.011 35.938 97.057 19.021 39.423-22.97 163.557 49.969 295.858-82.329 132.474-132.477 59.273-256.277 82.331-295.861 16.922-29.05 9.1-68.931-19.022-97.054zm-22.405 72.894c-38.8 66.609 45.6 165.635-74.845 286.08-120.44 120.443-219.475 36.048-286.076 74.843-22.679 13.207-64.035-27.241-50.493-50.488 38.8-66.609-45.6-165.635 74.845-286.08C245.573 4.702 344.616 89.086 411.219 50.292c22.73-13.24 64.005 27.288 50.488 50.491zm-169.861 8.736c1.37 10.96-6.404 20.957-17.365 22.327-54.846 6.855-135.779 87.787-142.635 142.635-1.373 10.989-11.399 18.734-22.326 17.365-10.961-1.37-18.735-11.366-17.365-22.326 9.162-73.286 104.167-168.215 177.365-177.365 10.953-1.368 20.956 6.403 22.326 17.364z"],
|
||||
"life-ring": [512, 512, [], "f1cd", "M256 504c136.967 0 248-111.033 248-248S392.967 8 256 8 8 119.033 8 256s111.033 248 248 248zm-103.398-76.72l53.411-53.411c31.806 13.506 68.128 13.522 99.974 0l53.411 53.411c-63.217 38.319-143.579 38.319-206.796 0zM336 256c0 44.112-35.888 80-80 80s-80-35.888-80-80 35.888-80 80-80 80 35.888 80 80zm91.28 103.398l-53.411-53.411c13.505-31.806 13.522-68.128 0-99.974l53.411-53.411c38.319 63.217 38.319 143.579 0 206.796zM359.397 84.72l-53.411 53.411c-31.806-13.505-68.128-13.522-99.973 0L152.602 84.72c63.217-38.319 143.579-38.319 206.795 0zM84.72 152.602l53.411 53.411c-13.506 31.806-13.522 68.128 0 99.974L84.72 359.398c-38.319-63.217-38.319-143.579 0-206.796z"],
|
||||
"lightbulb": [352, 512, [], "f0eb", "M176 80c-52.94 0-96 43.06-96 96 0 8.84 7.16 16 16 16s16-7.16 16-16c0-35.3 28.72-64 64-64 8.84 0 16-7.16 16-16s-7.16-16-16-16zM96.06 459.17c0 3.15.93 6.22 2.68 8.84l24.51 36.84c2.97 4.46 7.97 7.14 13.32 7.14h78.85c5.36 0 10.36-2.68 13.32-7.14l24.51-36.84c1.74-2.62 2.67-5.7 2.68-8.84l.05-43.18H96.02l.04 43.18zM176 0C73.72 0 0 82.97 0 176c0 44.37 16.45 84.85 43.56 115.78 16.64 18.99 42.74 58.8 52.42 92.16v.06h48v-.12c-.01-4.77-.72-9.51-2.15-14.07-5.59-17.81-22.82-64.77-62.17-109.67-20.54-23.43-31.52-53.15-31.61-84.14-.2-73.64 59.67-128 127.95-128 70.58 0 128 57.42 128 128 0 30.97-11.24 60.85-31.65 84.14-39.11 44.61-56.42 91.47-62.1 109.46a47.507 47.507 0 0 0-2.22 14.3v.1h48v-.05c9.68-33.37 35.78-73.18 52.42-92.16C335.55 260.85 352 220.37 352 176 352 78.8 273.2 0 176 0z"],
|
||||
"list-alt": [512, 512, [], "f022", "M464 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h416c26.51 0 48-21.49 48-48V80c0-26.51-21.49-48-48-48zm-6 400H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h404a6 6 0 0 1 6 6v340a6 6 0 0 1-6 6zm-42-92v24c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h200c6.627 0 12 5.373 12 12zm0-96v24c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h200c6.627 0 12 5.373 12 12zm0-96v24c0 6.627-5.373 12-12 12H204c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h200c6.627 0 12 5.373 12 12zm-252 12c0 19.882-16.118 36-36 36s-36-16.118-36-36 16.118-36 36-36 36 16.118 36 36zm0 96c0 19.882-16.118 36-36 36s-36-16.118-36-36 16.118-36 36-36 36 16.118 36 36zm0 96c0 19.882-16.118 36-36 36s-36-16.118-36-36 16.118-36 36-36 36 16.118 36 36z"],
|
||||
"map": [576, 512, [], "f279", "M560.02 32c-1.96 0-3.98.37-5.96 1.16L384.01 96H384L212 35.28A64.252 64.252 0 0 0 191.76 32c-6.69 0-13.37 1.05-19.81 3.14L20.12 87.95A32.006 32.006 0 0 0 0 117.66v346.32C0 473.17 7.53 480 15.99 480c1.96 0 3.97-.37 5.96-1.16L192 416l172 60.71a63.98 63.98 0 0 0 40.05.15l151.83-52.81A31.996 31.996 0 0 0 576 394.34V48.02c0-9.19-7.53-16.02-15.98-16.02zM224 90.42l128 45.19v285.97l-128-45.19V90.42zM48 418.05V129.07l128-44.53v286.2l-.64.23L48 418.05zm480-35.13l-128 44.53V141.26l.64-.24L528 93.95v288.97z"],
|
||||
"meh": [496, 512, [], "f11a", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm8 144H160c-13.2 0-24 10.8-24 24s10.8 24 24 24h176c13.2 0 24-10.8 24-24s-10.8-24-24-24z"],
|
||||
"meh-blank": [496, 512, [], "f5a4", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-280c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm160 0c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z"],
|
||||
"meh-rolling-eyes": [496, 512, [], "f5a5", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm88-304c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm0 112c-22.1 0-40-17.9-40-40 0-13.6 7.3-25.1 17.7-32.3-1 2.6-1.7 5.3-1.7 8.3 0 13.3 10.7 24 24 24s24-10.7 24-24c0-2.9-.7-5.7-1.7-8.3 10.4 7.2 17.7 18.7 17.7 32.3 0 22.1-17.9 40-40 40zm-104-40c0-39.8-32.2-72-72-72s-72 32.2-72 72 32.2 72 72 72 72-32.2 72-72zm-112 0c0-13.6 7.3-25.1 17.7-32.3-1 2.6-1.7 5.3-1.7 8.3 0 13.3 10.7 24 24 24s24-10.7 24-24c0-2.9-.7-5.7-1.7-8.3 10.4 7.2 17.7 18.7 17.7 32.3 0 22.1-17.9 40-40 40s-40-17.9-40-40zm192 128H184c-13.2 0-24 10.8-24 24s10.8 24 24 24h128c13.2 0 24-10.8 24-24s-10.8-24-24-24z"],
|
||||
"minus-square": [448, 512, [], "f146", "M108 284c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h232c6.6 0 12 5.4 12 12v32c0 6.6-5.4 12-12 12H108zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"money-bill-alt": [640, 512, [], "f3d1", "M320 144c-53.02 0-96 50.14-96 112 0 61.85 42.98 112 96 112 53 0 96-50.13 96-112 0-61.86-42.98-112-96-112zm40 168c0 4.42-3.58 8-8 8h-64c-4.42 0-8-3.58-8-8v-16c0-4.42 3.58-8 8-8h16v-55.44l-.47.31a7.992 7.992 0 0 1-11.09-2.22l-8.88-13.31a7.992 7.992 0 0 1 2.22-11.09l15.33-10.22a23.99 23.99 0 0 1 13.31-4.03H328c4.42 0 8 3.58 8 8v88h16c4.42 0 8 3.58 8 8v16zM608 64H32C14.33 64 0 78.33 0 96v320c0 17.67 14.33 32 32 32h576c17.67 0 32-14.33 32-32V96c0-17.67-14.33-32-32-32zm-16 272c-35.35 0-64 28.65-64 64H112c0-35.35-28.65-64-64-64V176c35.35 0 64-28.65 64-64h416c0 35.35 28.65 64 64 64v160z"],
|
||||
"moon": [512, 512, [], "f186", "M279.135 512c78.756 0 150.982-35.804 198.844-94.775 28.27-34.831-2.558-85.722-46.249-77.401-82.348 15.683-158.272-47.268-158.272-130.792 0-48.424 26.06-92.292 67.434-115.836 38.745-22.05 28.999-80.788-15.022-88.919A257.936 257.936 0 0 0 279.135 0c-141.36 0-256 114.575-256 256 0 141.36 114.576 256 256 256zm0-464c12.985 0 25.689 1.201 38.016 3.478-54.76 31.163-91.693 90.042-91.693 157.554 0 113.848 103.641 199.2 215.252 177.944C402.574 433.964 344.366 464 279.135 464c-114.875 0-208-93.125-208-208s93.125-208 208-208z"],
|
||||
"newspaper": [576, 512, [], "f1ea", "M552 64H112c-20.858 0-38.643 13.377-45.248 32H24c-13.255 0-24 10.745-24 24v272c0 30.928 25.072 56 56 56h496c13.255 0 24-10.745 24-24V88c0-13.255-10.745-24-24-24zM48 392V144h16v248c0 4.411-3.589 8-8 8s-8-3.589-8-8zm480 8H111.422c.374-2.614.578-5.283.578-8V112h416v288zM172 280h136c6.627 0 12-5.373 12-12v-96c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v96c0 6.627 5.373 12 12 12zm28-80h80v40h-80v-40zm-40 140v-24c0-6.627 5.373-12 12-12h136c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12H172c-6.627 0-12-5.373-12-12zm192 0v-24c0-6.627 5.373-12 12-12h104c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12H364c-6.627 0-12-5.373-12-12zm0-144v-24c0-6.627 5.373-12 12-12h104c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12H364c-6.627 0-12-5.373-12-12zm0 72v-24c0-6.627 5.373-12 12-12h104c6.627 0 12 5.373 12 12v24c0 6.627-5.373 12-12 12H364c-6.627 0-12-5.373-12-12z"],
|
||||
"object-group": [512, 512, [], "f247", "M500 128c6.627 0 12-5.373 12-12V44c0-6.627-5.373-12-12-12h-72c-6.627 0-12 5.373-12 12v12H96V44c0-6.627-5.373-12-12-12H12C5.373 32 0 37.373 0 44v72c0 6.627 5.373 12 12 12h12v256H12c-6.627 0-12 5.373-12 12v72c0 6.627 5.373 12 12 12h72c6.627 0 12-5.373 12-12v-12h320v12c0 6.627 5.373 12 12 12h72c6.627 0 12-5.373 12-12v-72c0-6.627-5.373-12-12-12h-12V128h12zm-52-64h32v32h-32V64zM32 64h32v32H32V64zm32 384H32v-32h32v32zm416 0h-32v-32h32v32zm-40-64h-12c-6.627 0-12 5.373-12 12v12H96v-12c0-6.627-5.373-12-12-12H72V128h12c6.627 0 12-5.373 12-12v-12h320v12c0 6.627 5.373 12 12 12h12v256zm-36-192h-84v-52c0-6.628-5.373-12-12-12H108c-6.627 0-12 5.372-12 12v168c0 6.628 5.373 12 12 12h84v52c0 6.628 5.373 12 12 12h200c6.627 0 12-5.372 12-12V204c0-6.628-5.373-12-12-12zm-268-24h144v112H136V168zm240 176H232v-24h76c6.627 0 12-5.372 12-12v-76h56v112z"],
|
||||
"object-ungroup": [576, 512, [], "f248", "M564 224c6.627 0 12-5.373 12-12v-72c0-6.627-5.373-12-12-12h-72c-6.627 0-12 5.373-12 12v12h-88v-24h12c6.627 0 12-5.373 12-12V44c0-6.627-5.373-12-12-12h-72c-6.627 0-12 5.373-12 12v12H96V44c0-6.627-5.373-12-12-12H12C5.373 32 0 37.373 0 44v72c0 6.627 5.373 12 12 12h12v160H12c-6.627 0-12 5.373-12 12v72c0 6.627 5.373 12 12 12h72c6.627 0 12-5.373 12-12v-12h88v24h-12c-6.627 0-12 5.373-12 12v72c0 6.627 5.373 12 12 12h72c6.627 0 12-5.373 12-12v-12h224v12c0 6.627 5.373 12 12 12h72c6.627 0 12-5.373 12-12v-72c0-6.627-5.373-12-12-12h-12V224h12zM352 64h32v32h-32V64zm0 256h32v32h-32v-32zM64 352H32v-32h32v32zm0-256H32V64h32v32zm32 216v-12c0-6.627-5.373-12-12-12H72V128h12c6.627 0 12-5.373 12-12v-12h224v12c0 6.627 5.373 12 12 12h12v160h-12c-6.627 0-12 5.373-12 12v12H96zm128 136h-32v-32h32v32zm280-64h-12c-6.627 0-12 5.373-12 12v12H256v-12c0-6.627-5.373-12-12-12h-12v-24h88v12c0 6.627 5.373 12 12 12h72c6.627 0 12-5.373 12-12v-72c0-6.627-5.373-12-12-12h-12v-88h88v12c0 6.627 5.373 12 12 12h12v160zm40 64h-32v-32h32v32zm0-256h-32v-32h32v32z"],
|
||||
"paper-plane": [512, 512, [], "f1d8", "M440 6.5L24 246.4c-34.4 19.9-31.1 70.8 5.7 85.9L144 379.6V464c0 46.4 59.2 65.5 86.6 28.6l43.8-59.1 111.9 46.2c5.9 2.4 12.1 3.6 18.3 3.6 8.2 0 16.3-2.1 23.6-6.2 12.8-7.2 21.6-20 23.9-34.5l59.4-387.2c6.1-40.1-36.9-68.8-71.5-48.9zM192 464v-64.6l36.6 15.1L192 464zm212.6-28.7l-153.8-63.5L391 169.5c10.7-15.5-9.5-33.5-23.7-21.2L155.8 332.6 48 288 464 48l-59.4 387.3z"],
|
||||
"pause-circle": [512, 512, [], "f28b", "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm96-280v160c0 8.8-7.2 16-16 16h-48c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16zm-112 0v160c0 8.8-7.2 16-16 16h-48c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16z"],
|
||||
"play-circle": [512, 512, [], "f144", "M371.7 238l-176-107c-15.8-8.8-35.7 2.5-35.7 21v208c0 18.4 19.8 29.8 35.7 21l176-101c16.4-9.1 16.4-32.8 0-42zM504 256C504 119 393 8 256 8S8 119 8 256s111 248 248 248 248-111 248-248zm-448 0c0-110.5 89.5-200 200-200s200 89.5 200 200-89.5 200-200 200S56 366.5 56 256z"],
|
||||
"plus-square": [448, 512, [], "f0fe", "M352 240v32c0 6.6-5.4 12-12 12h-88v88c0 6.6-5.4 12-12 12h-32c-6.6 0-12-5.4-12-12v-88h-88c-6.6 0-12-5.4-12-12v-32c0-6.6 5.4-12 12-12h88v-88c0-6.6 5.4-12 12-12h32c6.6 0 12 5.4 12 12v88h88c6.6 0 12 5.4 12 12zm96-160v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-48 346V86c0-3.3-2.7-6-6-6H54c-3.3 0-6 2.7-6 6v340c0 3.3 2.7 6 6 6h340c3.3 0 6-2.7 6-6z"],
|
||||
"question-circle": [512, 512, [], "f059", "M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 448c-110.532 0-200-89.431-200-200 0-110.495 89.472-200 200-200 110.491 0 200 89.471 200 200 0 110.53-89.431 200-200 200zm107.244-255.2c0 67.052-72.421 68.084-72.421 92.863V300c0 6.627-5.373 12-12 12h-45.647c-6.627 0-12-5.373-12-12v-8.659c0-35.745 27.1-50.034 47.579-61.516 17.561-9.845 28.324-16.541 28.324-29.579 0-17.246-21.999-28.693-39.784-28.693-23.189 0-33.894 10.977-48.942 29.969-4.057 5.12-11.46 6.071-16.666 2.124l-27.824-21.098c-5.107-3.872-6.251-11.066-2.644-16.363C184.846 131.491 214.94 112 261.794 112c49.071 0 101.45 38.304 101.45 88.8zM298 368c0 23.159-18.841 42-42 42s-42-18.841-42-42 18.841-42 42-42 42 18.841 42 42z"],
|
||||
"registered": [512, 512, [], "f25d", "M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 448c-110.532 0-200-89.451-200-200 0-110.531 89.451-200 200-200 110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200zm110.442-81.791c-53.046-96.284-50.25-91.468-53.271-96.085 24.267-13.879 39.482-41.563 39.482-73.176 0-52.503-30.247-85.252-101.498-85.252h-78.667c-6.617 0-12 5.383-12 12V380c0 6.617 5.383 12 12 12h38.568c6.617 0 12-5.383 12-12v-83.663h31.958l47.515 89.303a11.98 11.98 0 0 0 10.593 6.36h42.81c9.14 0 14.914-9.799 10.51-17.791zM256.933 239.906h-33.875v-64.14h27.377c32.417 0 38.929 12.133 38.929 31.709-.001 20.913-11.518 32.431-32.431 32.431z"],
|
||||
"sad-cry": [496, 512, [], "f5b3", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm144 386.4V280c0-13.2-10.8-24-24-24s-24 10.8-24 24v151.4C315.5 447 282.8 456 248 456s-67.5-9-96-24.6V280c0-13.2-10.8-24-24-24s-24 10.8-24 24v114.4c-34.6-36-56-84.7-56-138.4 0-110.3 89.7-200 200-200s200 89.7 200 200c0 53.7-21.4 102.5-56 138.4zM205.8 234.5c4.4-2.4 6.9-7.4 6.1-12.4-4-25.2-34.2-42.1-59.8-42.1s-55.9 16.9-59.8 42.1c-.8 5 1.7 10 6.1 12.4 4.4 2.4 9.9 1.8 13.7-1.6l9.5-8.5c14.8-13.2 46.2-13.2 61 0l9.5 8.5c2.5 2.3 7.9 4.8 13.7 1.6zM344 180c-25.7 0-55.9 16.9-59.8 42.1-.8 5 1.7 10 6.1 12.4 4.5 2.4 9.9 1.8 13.7-1.6l9.5-8.5c14.8-13.2 46.2-13.2 61 0l9.5 8.5c2.5 2.2 8 4.7 13.7 1.6 4.4-2.4 6.9-7.4 6.1-12.4-3.9-25.2-34.1-42.1-59.8-42.1zm-96 92c-30.9 0-56 28.7-56 64s25.1 64 56 64 56-28.7 56-64-25.1-64-56-64z"],
|
||||
"sad-tear": [496, 512, [], "f5b4", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm8-152c-13.2 0-24 10.8-24 24s10.8 24 24 24c23.8 0 46.3 10.5 61.6 28.8 8.1 9.8 23.2 11.9 33.8 3.1 10.2-8.5 11.6-23.6 3.1-33.8C330 320.8 294.1 304 256 304zm-88-64c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-165.6 98.8C151 290.1 126 325.4 126 342.9c0 22.7 18.8 41.1 42 41.1s42-18.4 42-41.1c0-17.5-25-52.8-36.4-68.1-2.8-3.7-8.4-3.7-11.2 0z"],
|
||||
"save": [448, 512, [], "f0c7", "M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM272 80v80H144V80h128zm122 352H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h42v104c0 13.255 10.745 24 24 24h176c13.255 0 24-10.745 24-24V83.882l78.243 78.243a6 6 0 0 1 1.757 4.243V426a6 6 0 0 1-6 6zM224 232c-48.523 0-88 39.477-88 88s39.477 88 88 88 88-39.477 88-88-39.477-88-88-88zm0 128c-22.056 0-40-17.944-40-40s17.944-40 40-40 40 17.944 40 40-17.944 40-40 40z"],
|
||||
"share-square": [576, 512, [], "f14d", "M561.938 158.06L417.94 14.092C387.926-15.922 336 5.097 336 48.032v57.198c-42.45 1.88-84.03 6.55-120.76 17.99-35.17 10.95-63.07 27.58-82.91 49.42C108.22 199.2 96 232.6 96 271.94c0 61.697 33.178 112.455 84.87 144.76 37.546 23.508 85.248-12.651 71.02-55.74-15.515-47.119-17.156-70.923 84.11-78.76V336c0 42.993 51.968 63.913 81.94 33.94l143.998-144c18.75-18.74 18.75-49.14 0-67.88zM384 336V232.16C255.309 234.082 166.492 255.35 206.31 376 176.79 357.55 144 324.08 144 271.94c0-109.334 129.14-118.947 240-119.85V48l144 144-144 144zm24.74 84.493a82.658 82.658 0 0 0 20.974-9.303c7.976-4.952 18.286.826 18.286 10.214V464c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h132c6.627 0 12 5.373 12 12v4.486c0 4.917-2.987 9.369-7.569 11.152-13.702 5.331-26.396 11.537-38.05 18.585a12.138 12.138 0 0 1-6.28 1.777H54a6 6 0 0 0-6 6v340a6 6 0 0 0 6 6h340a6 6 0 0 0 6-6v-25.966c0-5.37 3.579-10.059 8.74-11.541z"],
|
||||
"smile": [496, 512, [], "f118", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z"],
|
||||
"smile-beam": [496, 512, [], "f5b8", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm84-143.4c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.6-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.2-8.4-25.3-7.1-33.8 3.1zM136.5 211c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4s-52.7 29.3-56 71.4c-.3 3.7 2.1 7.2 5.7 8.3 3.4 1.1 7.4-.5 9.3-3.7l9.5-17zM328 152c-23.8 0-52.7 29.3-56 71.4-.3 3.7 2.1 7.2 5.7 8.3 3.5 1.1 7.4-.5 9.3-3.7l9.5-17c7.7-13.7 19.2-21.6 31.5-21.6s23.8 7.9 31.5 21.6l9.5 17c2.1 3.7 6.2 4.7 9.3 3.7 3.6-1.1 6-4.5 5.7-8.3-3.3-42.1-32.2-71.4-56-71.4z"],
|
||||
"smile-wink": [496, 512, [], "f4da", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm117.8-146.4c-10.2-8.5-25.3-7.1-33.8 3.1-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8zM168 240c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-60c-25.7 0-55.9 16.9-59.9 42.1-1.7 11.2 11.5 18.2 19.8 10.8l9.5-8.5c14.8-13.2 46.2-13.2 61 0l9.5 8.5c8.5 7.4 21.6.3 19.8-10.8-3.8-25.2-34-42.1-59.7-42.1z"],
|
||||
"snowflake": [448, 512, [], "f2dc", "M440.1 355.2l-39.2-23 34.1-9.3c8.4-2.3 13.4-11.1 11.1-19.6l-4.1-15.5c-2.2-8.5-10.9-13.6-19.3-11.3L343 298.2 271.2 256l71.9-42.2 79.7 21.7c8.4 2.3 17-2.8 19.3-11.3l4.1-15.5c2.2-8.5-2.7-17.3-11.1-19.6l-34.1-9.3 39.2-23c7.5-4.4 10.1-14.2 5.8-21.9l-7.9-13.9c-4.3-7.7-14-10.3-21.5-5.9l-39.2 23 9.1-34.7c2.2-8.5-2.7-17.3-11.1-19.6l-15.2-4.1c-8.4-2.3-17 2.8-19.3 11.3l-21.3 81-71.9 42.2v-84.5L306 70.4c6.1-6.2 6.1-16.4 0-22.6l-11.1-11.3c-6.1-6.2-16.1-6.2-22.2 0l-24.9 25.4V16c0-8.8-7-16-15.7-16h-15.7c-8.7 0-15.7 7.2-15.7 16v46.1l-24.9-25.4c-6.1-6.2-16.1-6.2-22.2 0L142.1 48c-6.1 6.2-6.1 16.4 0 22.6l58.3 59.3v84.5l-71.9-42.2-21.3-81c-2.2-8.5-10.9-13.6-19.3-11.3L72.7 84c-8.4 2.3-13.4 11.1-11.1 19.6l9.1 34.7-39.2-23c-7.5-4.4-17.1-1.8-21.5 5.9l-7.9 13.9c-4.3 7.7-1.8 17.4 5.8 21.9l39.2 23-34.1 9.1c-8.4 2.3-13.4 11.1-11.1 19.6L6 224.2c2.2 8.5 10.9 13.6 19.3 11.3l79.7-21.7 71.9 42.2-71.9 42.2-79.7-21.7c-8.4-2.3-17 2.8-19.3 11.3l-4.1 15.5c-2.2 8.5 2.7 17.3 11.1 19.6l34.1 9.3-39.2 23c-7.5 4.4-10.1 14.2-5.8 21.9L10 391c4.3 7.7 14 10.3 21.5 5.9l39.2-23-9.1 34.7c-2.2 8.5 2.7 17.3 11.1 19.6l15.2 4.1c8.4 2.3 17-2.8 19.3-11.3l21.3-81 71.9-42.2v84.5l-58.3 59.3c-6.1 6.2-6.1 16.4 0 22.6l11.1 11.3c6.1 6.2 16.1 6.2 22.2 0l24.9-25.4V496c0 8.8 7 16 15.7 16h15.7c8.7 0 15.7-7.2 15.7-16v-46.1l24.9 25.4c6.1 6.2 16.1 6.2 22.2 0l11.1-11.3c6.1-6.2 6.1-16.4 0-22.6l-58.3-59.3v-84.5l71.9 42.2 21.3 81c2.2 8.5 10.9 13.6 19.3 11.3L375 428c8.4-2.3 13.4-11.1 11.1-19.6l-9.1-34.7 39.2 23c7.5 4.4 17.1 1.8 21.5-5.9l7.9-13.9c4.6-7.5 2.1-17.3-5.5-21.7z"],
|
||||
"square": [448, 512, [], "f0c8", "M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm-6 400H54c-3.3 0-6-2.7-6-6V86c0-3.3 2.7-6 6-6h340c3.3 0 6 2.7 6 6v340c0 3.3-2.7 6-6 6z"],
|
||||
"star": [576, 512, [], "f005", "M528.1 171.5L382 150.2 316.7 17.8c-11.7-23.6-45.6-23.9-57.4 0L194 150.2 47.9 171.5c-26.2 3.8-36.7 36.1-17.7 54.6l105.7 103-25 145.5c-4.5 26.3 23.2 46 46.4 33.7L288 439.6l130.7 68.7c23.2 12.2 50.9-7.4 46.4-33.7l-25-145.5 105.7-103c19-18.5 8.5-50.8-17.7-54.6zM388.6 312.3l23.7 138.4L288 385.4l-124.3 65.3 23.7-138.4-100.6-98 139-20.2 62.2-126 62.2 126 139 20.2-100.6 98z"],
|
||||
"star-half": [576, 512, [], "f089", "M288 385.3l-124.3 65.4 23.7-138.4-100.6-98 139-20.2 62.2-126V0c-11.4 0-22.8 5.9-28.7 17.8L194 150.2 47.9 171.4c-26.2 3.8-36.7 36.1-17.7 54.6l105.7 103-25 145.5c-4.5 26.1 23 46 46.4 33.7L288 439.6v-54.3z"],
|
||||
"sticky-note": [448, 512, [], "f249", "M448 348.106V80c0-26.51-21.49-48-48-48H48C21.49 32 0 53.49 0 80v351.988c0 26.51 21.49 48 48 48h268.118a48 48 0 0 0 33.941-14.059l83.882-83.882A48 48 0 0 0 448 348.106zm-128 80v-76.118h76.118L320 428.106zM400 80v223.988H296c-13.255 0-24 10.745-24 24v104H48V80h352z"],
|
||||
"stop-circle": [512, 512, [], "f28d", "M504 256C504 119 393 8 256 8S8 119 8 256s111 248 248 248 248-111 248-248zm-448 0c0-110.5 89.5-200 200-200s200 89.5 200 200-89.5 200-200 200S56 366.5 56 256zm296-80v160c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16h160c8.8 0 16 7.2 16 16z"],
|
||||
"sun": [512, 512, [], "f185", "M494.2 221.9l-59.8-40.5 13.7-71c2.6-13.2-1.6-26.8-11.1-36.4-9.6-9.5-23.2-13.7-36.2-11.1l-70.9 13.7-40.4-59.9c-15.1-22.3-51.9-22.3-67 0l-40.4 59.9-70.8-13.7C98 60.4 84.5 64.5 75 74.1c-9.5 9.6-13.7 23.1-11.1 36.3l13.7 71-59.8 40.5C6.6 229.5 0 242 0 255.5s6.7 26 17.8 33.5l59.8 40.5-13.7 71c-2.6 13.2 1.6 26.8 11.1 36.3 9.5 9.5 22.9 13.7 36.3 11.1l70.8-13.7 40.4 59.9C230 505.3 242.6 512 256 512s26-6.7 33.5-17.8l40.4-59.9 70.9 13.7c13.4 2.7 26.8-1.6 36.3-11.1 9.5-9.5 13.6-23.1 11.1-36.3l-13.7-71 59.8-40.5c11.1-7.5 17.8-20.1 17.8-33.5-.1-13.6-6.7-26.1-17.9-33.7zm-112.9 85.6l17.6 91.2-91-17.6L256 458l-51.9-77-90.9 17.6 17.6-91.2-76.8-52 76.8-52-17.6-91.2 91 17.6L256 53l51.9 76.9 91-17.6-17.6 91.1 76.8 52-76.8 52.1zM256 152c-57.3 0-104 46.7-104 104s46.7 104 104 104 104-46.7 104-104-46.7-104-104-104zm0 160c-30.9 0-56-25.1-56-56s25.1-56 56-56 56 25.1 56 56-25.1 56-56 56z"],
|
||||
"surprise": [496, 512, [], "f5c2", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm0-176c-35.3 0-64 28.7-64 64s28.7 64 64 64 64-28.7 64-64-28.7-64-64-64zm-48-72c0-17.7-14.3-32-32-32s-32 14.3-32 32 14.3 32 32 32 32-14.3 32-32zm128-32c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32z"],
|
||||
"thumbs-down": [512, 512, [], "f165", "M466.27 225.31c4.674-22.647.864-44.538-8.99-62.99 2.958-23.868-4.021-48.565-17.34-66.99C438.986 39.423 404.117 0 327 0c-7 0-15 .01-22.22.01C201.195.01 168.997 40 128 40h-10.845c-5.64-4.975-13.042-8-21.155-8H32C14.327 32 0 46.327 0 64v240c0 17.673 14.327 32 32 32h64c11.842 0 22.175-6.438 27.708-16h7.052c19.146 16.953 46.013 60.653 68.76 83.4 13.667 13.667 10.153 108.6 71.76 108.6 57.58 0 95.27-31.936 95.27-104.73 0-18.41-3.93-33.73-8.85-46.54h36.48c48.602 0 85.82-41.565 85.82-85.58 0-19.15-4.96-34.99-13.73-49.84zM64 296c-13.255 0-24-10.745-24-24s10.745-24 24-24 24 10.745 24 24-10.745 24-24 24zm330.18 16.73H290.19c0 37.82 28.36 55.37 28.36 94.54 0 23.75 0 56.73-47.27 56.73-18.91-18.91-9.46-66.18-37.82-94.54C206.9 342.89 167.28 272 138.92 272H128V85.83c53.611 0 100.001-37.82 171.64-37.82h37.82c35.512 0 60.82 17.12 53.12 65.9 15.2 8.16 26.5 36.44 13.94 57.57 21.581 20.384 18.699 51.065 5.21 65.62 9.45 0 22.36 18.91 22.27 37.81-.09 18.91-16.71 37.82-37.82 37.82z"],
|
||||
"thumbs-up": [512, 512, [], "f164", "M466.27 286.69C475.04 271.84 480 256 480 236.85c0-44.015-37.218-85.58-85.82-85.58H357.7c4.92-12.81 8.85-28.13 8.85-46.54C366.55 31.936 328.86 0 271.28 0c-61.607 0-58.093 94.933-71.76 108.6-22.747 22.747-49.615 66.447-68.76 83.4H32c-17.673 0-32 14.327-32 32v240c0 17.673 14.327 32 32 32h64c14.893 0 27.408-10.174 30.978-23.95 44.509 1.001 75.06 39.94 177.802 39.94 7.22 0 15.22.01 22.22.01 77.117 0 111.986-39.423 112.94-95.33 13.319-18.425 20.299-43.122 17.34-66.99 9.854-18.452 13.664-40.343 8.99-62.99zm-61.75 53.83c12.56 21.13 1.26 49.41-13.94 57.57 7.7 48.78-17.608 65.9-53.12 65.9h-37.82c-71.639 0-118.029-37.82-171.64-37.82V240h10.92c28.36 0 67.98-70.89 94.54-97.46 28.36-28.36 18.91-75.63 37.82-94.54 47.27 0 47.27 32.98 47.27 56.73 0 39.17-28.36 56.72-28.36 94.54h103.99c21.11 0 37.73 18.91 37.82 37.82.09 18.9-12.82 37.81-22.27 37.81 13.489 14.555 16.371 45.236-5.21 65.62zM88 432c0 13.255-10.745 24-24 24s-24-10.745-24-24 10.745-24 24-24 24 10.745 24 24z"],
|
||||
"times-circle": [512, 512, [], "f057", "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z"],
|
||||
"tired": [496, 512, [], "f5c8", "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm129.1-303.8c-3.8-4.4-10.3-5.4-15.3-2.5l-80 48c-3.6 2.2-5.8 6.1-5.8 10.3s2.2 8.1 5.8 10.3l80 48c5.4 3.2 11.8 1.6 15.3-2.5 3.8-4.5 3.9-11 .1-15.5L343.6 208l33.6-40.3c3.8-4.5 3.7-11.1-.1-15.5zM220 208c0-4.2-2.2-8.1-5.8-10.3l-80-48c-5-3-11.5-1.9-15.3 2.5-3.8 4.5-3.9 11-.1 15.5l33.6 40.3-33.6 40.3c-3.8 4.5-3.7 11 .1 15.5 3.5 4.1 9.9 5.7 15.3 2.5l80-48c3.6-2.2 5.8-6.1 5.8-10.3zm28 64c-45.4 0-100.9 38.3-107.8 93.3-1.5 11.8 6.9 21.6 15.5 17.9C178.4 373.5 212 368 248 368s69.6 5.5 92.3 15.2c8.5 3.7 17-6 15.5-17.9-6.9-55-62.4-93.3-107.8-93.3z"],
|
||||
"trash-alt": [448, 512, [], "f2ed", "M268 416h24a12 12 0 0 0 12-12V188a12 12 0 0 0-12-12h-24a12 12 0 0 0-12 12v216a12 12 0 0 0 12 12zM432 80h-82.41l-34-56.7A48 48 0 0 0 274.41 0H173.59a48 48 0 0 0-41.16 23.3L98.41 80H16A16 16 0 0 0 0 96v16a16 16 0 0 0 16 16h16v336a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128h16a16 16 0 0 0 16-16V96a16 16 0 0 0-16-16zM171.84 50.91A6 6 0 0 1 177 48h94a6 6 0 0 1 5.15 2.91L293.61 80H154.39zM368 464H80V128h288zm-212-48h24a12 12 0 0 0 12-12V188a12 12 0 0 0-12-12h-24a12 12 0 0 0-12 12v216a12 12 0 0 0 12 12z"],
|
||||
"user": [448, 512, [], "f007", "M313.6 304c-28.7 0-42.5 16-89.6 16-47.1 0-60.8-16-89.6-16C60.2 304 0 364.2 0 438.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-25.6c0-74.2-60.2-134.4-134.4-134.4zM400 464H48v-25.6c0-47.6 38.8-86.4 86.4-86.4 14.6 0 38.3 16 89.6 16 51.7 0 74.9-16 89.6-16 47.6 0 86.4 38.8 86.4 86.4V464zM224 288c79.5 0 144-64.5 144-144S303.5 0 224 0 80 64.5 80 144s64.5 144 144 144zm0-240c52.9 0 96 43.1 96 96s-43.1 96-96 96-96-43.1-96-96 43.1-96 96-96z"],
|
||||
"user-circle": [496, 512, [], "f2bd", "M248 104c-53 0-96 43-96 96s43 96 96 96 96-43 96-96-43-96-96-96zm0 144c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48zm0-240C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-49.7 0-95.1-18.3-130.1-48.4 14.9-23 40.4-38.6 69.6-39.5 20.8 6.4 40.6 9.6 60.5 9.6s39.7-3.1 60.5-9.6c29.2 1 54.7 16.5 69.6 39.5-35 30.1-80.4 48.4-130.1 48.4zm162.7-84.1c-24.4-31.4-62.1-51.9-105.1-51.9-10.2 0-26 9.6-57.6 9.6-31.5 0-47.4-9.6-57.6-9.6-42.9 0-80.6 20.5-105.1 51.9C61.9 339.2 48 299.2 48 256c0-110.3 89.7-200 200-200s200 89.7 200 200c0 43.2-13.9 83.2-37.3 115.9z"],
|
||||
"window-close": [512, 512, [], "f410", "M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 394c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V86c0-3.3 2.7-6 6-6h404c3.3 0 6 2.7 6 6v340zM356.5 194.6L295.1 256l61.4 61.4c4.6 4.6 4.6 12.1 0 16.8l-22.3 22.3c-4.6 4.6-12.1 4.6-16.8 0L256 295.1l-61.4 61.4c-4.6 4.6-12.1 4.6-16.8 0l-22.3-22.3c-4.6-4.6-4.6-12.1 0-16.8l61.4-61.4-61.4-61.4c-4.6-4.6-4.6-12.1 0-16.8l22.3-22.3c4.6-4.6 12.1-4.6 16.8 0l61.4 61.4 61.4-61.4c4.6-4.6 12.1-4.6 16.8 0l22.3 22.3c4.7 4.6 4.7 12.1 0 16.8z"],
|
||||
"window-maximize": [512, 512, [], "f2d0", "M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 394c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V192h416v234z"],
|
||||
"window-minimize": [512, 512, [], "f2d1", "M480 480H32c-17.7 0-32-14.3-32-32s14.3-32 32-32h448c17.7 0 32 14.3 32 32s-14.3 32-32 32z"],
|
||||
"window-restore": [512, 512, [], "f2d2", "M464 0H144c-26.5 0-48 21.5-48 48v48H48c-26.5 0-48 21.5-48 48v320c0 26.5 21.5 48 48 48h320c26.5 0 48-21.5 48-48v-48h48c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zm-96 464H48V256h320v208zm96-96h-48V144c0-26.5-21.5-48-48-48H144V48h320v320z"]
|
||||
};
|
||||
|
||||
bunker(function () {
|
||||
defineIcons('far', icons);
|
||||
});
|
||||
|
||||
}());
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue