mirror of
https://github.com/aykhans/sarin.git
synced 2026-04-15 04:29:35 +00:00
Compare commits
9 Commits
v1.2.0
...
705f6263fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 705f6263fe | |||
| 9c5b998cda | |||
| 026d05f1bf | |||
| 844f139a10 | |||
|
|
d767ac6f37 | ||
| c299fda79d | |||
| 1f06b43b06 | |||
| e031c8e7a5 | |||
|
|
de24f9d4a4 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,2 +1 @@
|
|||||||
buy_me_a_coffee: aykhan
|
buy_me_a_coffee: aykhan
|
||||||
custom: https://commerce.coinbase.com/checkout/0f33d2fb-54a6-44f5-8783-006ebf70d1a0
|
|
||||||
|
|||||||
4
.github/workflows/lint.yaml
vendored
4
.github/workflows/lint.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: 1.26.0
|
go-version: 1.26.1
|
||||||
- name: go fix
|
- name: go fix
|
||||||
run: |
|
run: |
|
||||||
go fix ./...
|
go fix ./...
|
||||||
@@ -24,4 +24,4 @@ jobs:
|
|||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v9
|
uses: golangci/golangci-lint-action@v9
|
||||||
with:
|
with:
|
||||||
version: v2.9.0
|
version: v2.11.2
|
||||||
|
|||||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(git describe --tags --always)" >> $GITHUB_ENV
|
echo "VERSION=$(git describe --tags --always)" >> $GITHUB_ENV
|
||||||
echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV
|
||||||
echo "GO_VERSION=1.26.0" >> $GITHUB_ENV
|
echo "GO_VERSION=1.26.1" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
if: github.event_name == 'release' || inputs.build_binaries
|
if: github.event_name == 'release' || inputs.build_binaries
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ARG GO_VERSION=1.26.0
|
ARG GO_VERSION=1.26.1
|
||||||
|
|
||||||
FROM docker.io/library/golang:${GO_VERSION}-alpine AS builder
|
FROM docker.io/library/golang:${GO_VERSION}-alpine AS builder
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ version: "3"
|
|||||||
|
|
||||||
vars:
|
vars:
|
||||||
BIN_DIR: ./bin
|
BIN_DIR: ./bin
|
||||||
GOLANGCI_LINT_VERSION: v2.9.0
|
GOLANGCI_LINT_VERSION: v2.11.2
|
||||||
GOLANGCI: "{{.BIN_DIR}}/golangci-lint-{{.GOLANGCI_LINT_VERSION}}"
|
GOLANGCI: "{{.BIN_DIR}}/golangci-lint-{{.GOLANGCI_LINT_VERSION}}"
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec // G118: cancel is called in listenForTermination goroutine
|
||||||
go listenForTermination(func() { cancel() })
|
go listenForTermination(func() { cancel() })
|
||||||
|
|
||||||
combinedConfig := config.ReadAllConfigs()
|
combinedConfig := config.ReadAllConfigs()
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ Sarin supports Go templates in URL paths, methods, bodies, headers, params, cook
|
|||||||
- [General Functions](#general-functions)
|
- [General Functions](#general-functions)
|
||||||
- [String Functions](#string-functions)
|
- [String Functions](#string-functions)
|
||||||
- [Collection Functions](#collection-functions)
|
- [Collection Functions](#collection-functions)
|
||||||
|
- [Time Functions](#time-functions)
|
||||||
|
- [Crypto Functions](#crypto-functions)
|
||||||
- [Body Functions](#body-functions)
|
- [Body Functions](#body-functions)
|
||||||
- [File Functions](#file-functions)
|
- [File Functions](#file-functions)
|
||||||
- [Fake Data Functions](#fake-data-functions)
|
- [Fake Data Functions](#fake-data-functions)
|
||||||
@@ -109,6 +111,24 @@ sarin -U http://example.com/users \
|
|||||||
| `slice_Int(values ...int)` | Create int slice | `{{ slice_Int 1 2 3 }}` |
|
| `slice_Int(values ...int)` | Create int slice | `{{ slice_Int 1 2 3 }}` |
|
||||||
| `slice_Uint(values ...uint)` | Create uint slice | `{{ slice_Uint 1 2 3 }}` |
|
| `slice_Uint(values ...uint)` | Create uint slice | `{{ slice_Uint 1 2 3 }}` |
|
||||||
|
|
||||||
|
### Time Functions
|
||||||
|
|
||||||
|
| Function | Description | Example |
|
||||||
|
| ------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------- |
|
||||||
|
| `time_NowUnix` | Current Unix timestamp (seconds) | `{{ time_NowUnix }}` → `1735689600` |
|
||||||
|
| `time_NowUnixMilli` | Current Unix timestamp (milliseconds) | `{{ time_NowUnixMilli }}` → `1735689600123` |
|
||||||
|
| `time_NowRFC3339` | Current time in RFC3339 format | `{{ time_NowRFC3339 }}` → `"2026-02-26T21:00:00Z"` |
|
||||||
|
| `time_Format(layout, t)` | Format a `time.Time` value with a Go layout | `{{ time_Format "2006-01-02" (strings_ToDate "2024-05-10") }}` → `"2024-05-10"` |
|
||||||
|
|
||||||
|
### Crypto Functions
|
||||||
|
|
||||||
|
| Function | Description | Example |
|
||||||
|
| ------------------------------------ | ------------------------------------------ | -------------------------------------------- |
|
||||||
|
| `crypto_SHA256(s string)` | SHA-256 hash (hex-encoded) | `{{ crypto_SHA256 "hello" }}` |
|
||||||
|
| `crypto_MD5(s string)` | MD5 hash (hex-encoded) | `{{ crypto_MD5 "hello" }}` |
|
||||||
|
| `crypto_HMACSHA256(key, msg string)` | HMAC-SHA256 signature (hex-encoded) | `{{ crypto_HMACSHA256 "secret" "payload" }}` |
|
||||||
|
| `crypto_Base64URL(s string)` | Base64 URL-safe encoding (without padding) | `{{ crypto_Base64URL "hello world" }}` |
|
||||||
|
|
||||||
### Body Functions
|
### Body Functions
|
||||||
|
|
||||||
| Function | Description | Example |
|
| Function | Description | Example |
|
||||||
@@ -153,11 +173,18 @@ body: '{{ body_FormData "twitter" "@@username" }}'
|
|||||||
|
|
||||||
| Function | Description | Example |
|
| Function | Description | Example |
|
||||||
| ---------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
| ---------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
||||||
|
| `file_Read(source string)` | Read a file (local path or URL) and return raw content as string. Files are cached after first read. | `{{ file_Read "/path/to/file.txt" }}` |
|
||||||
| `file_Base64(source string)` | Read a file (local path or URL) and return its Base64 encoded content. Files are cached after first read. | `{{ file_Base64 "/path/to/file.pdf" }}` |
|
| `file_Base64(source string)` | Read a file (local path or URL) and return its Base64 encoded content. Files are cached after first read. | `{{ file_Base64 "/path/to/file.pdf" }}` |
|
||||||
|
|
||||||
**`file_Base64` Details:**
|
**`file_Read` and `file_Base64` Details:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# Local file as plain text
|
||||||
|
body: '{{ file_Read "/path/to/template.json" }}'
|
||||||
|
|
||||||
|
# Remote text file
|
||||||
|
body: '{{ file_Read "https://example.com/payload.txt" }}'
|
||||||
|
|
||||||
# Local file as Base64 in JSON body
|
# Local file as Base64 in JSON body
|
||||||
body: '{"file": "{{ file_Base64 "/path/to/document.pdf" }}", "filename": "document.pdf"}'
|
body: '{"file": "{{ file_Base64 "/path/to/document.pdf" }}", "filename": "document.pdf"}'
|
||||||
|
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -1,9 +1,9 @@
|
|||||||
module go.aykhans.me/sarin
|
module go.aykhans.me/sarin
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/brianvoe/gofakeit/v7 v7.14.0
|
github.com/brianvoe/gofakeit/v7 v7.14.1
|
||||||
github.com/charmbracelet/bubbles v1.0.0
|
github.com/charmbracelet/bubbles v1.0.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/charmbracelet/glamour v0.10.0
|
github.com/charmbracelet/glamour v0.10.0
|
||||||
@@ -15,7 +15,7 @@ require (
|
|||||||
github.com/yuin/gopher-lua v1.1.1
|
github.com/yuin/gopher-lua v1.1.1
|
||||||
go.aykhans.me/utils v1.0.7
|
go.aykhans.me/utils v1.0.7
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||||
golang.org/x/net v0.50.0
|
golang.org/x/net v0.51.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -14,8 +14,8 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
|
|||||||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||||
github.com/brianvoe/gofakeit/v7 v7.14.0 h1:R8tmT/rTDJmD2ngpqBL9rAKydiL7Qr2u3CXPqRt59pk=
|
github.com/brianvoe/gofakeit/v7 v7.14.1 h1:a7fe3fonbj0cW3wgl5VwIKfZtiH9C3cLnwcIXWT7sow=
|
||||||
github.com/brianvoe/gofakeit/v7 v7.14.0/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
|
github.com/brianvoe/gofakeit/v7 v7.14.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
|
||||||
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
|
||||||
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
@@ -111,8 +111,8 @@ go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
|||||||
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ func (config Config) Validate() error {
|
|||||||
validationErrors = append(validationErrors, types.NewFieldValidationError("Duration", "0", errors.New("duration must be greater than 0")))
|
validationErrors = append(validationErrors, types.NewFieldValidationError("Duration", "0", errors.New("duration must be greater than 0")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *config.Timeout < 1 {
|
if config.Timeout == nil || *config.Timeout < 1 {
|
||||||
validationErrors = append(validationErrors, types.NewFieldValidationError("Timeout", "0", errors.New("timeout must be greater than 0")))
|
validationErrors = append(validationErrors, types.NewFieldValidationError("Timeout", "0", errors.New("timeout must be greater than 0")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ func (parser ConfigENVParser) Parse() (*Config, error) {
|
|||||||
types.NewFieldParseError(
|
types.NewFieldParseError(
|
||||||
parser.getFullEnvName("DURATION"),
|
parser.getFullEnvName("DURATION"),
|
||||||
duration,
|
duration,
|
||||||
errors.New("invalid value duration, expected a duration string (e.g., '10s', '1h30m')"),
|
errors.New("invalid value for duration, expected a duration string (e.g., '10s', '1h30m')"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -173,7 +173,7 @@ func (parser ConfigENVParser) Parse() (*Config, error) {
|
|||||||
types.NewFieldParseError(
|
types.NewFieldParseError(
|
||||||
parser.getFullEnvName("TIMEOUT"),
|
parser.getFullEnvName("TIMEOUT"),
|
||||||
timeout,
|
timeout,
|
||||||
errors.New("invalid value duration, expected a duration string (e.g., '10s', '1h30m')"),
|
errors.New("invalid value for duration, expected a duration string (e.g., '10s', '1h30m')"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ func fasthttpSocksDialerDualStackTimeout(ctx context.Context, proxyURL *url.URL,
|
|||||||
return nil, types.NewProxyDialError(proxyStr, err)
|
return nil, types.NewProxyDialError(proxyStr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cap DNS resolution to half the timeout to reserve time for dial
|
|
||||||
dnsCtx, dnsCancel := context.WithTimeout(ctx, timeout)
|
dnsCtx, dnsCancel := context.WithTimeout(ctx, timeout)
|
||||||
ips, err := net.DefaultResolver.LookupIP(dnsCtx, "ip", host)
|
ips, err := net.DefaultResolver.LookupIP(dnsCtx, "ip", host)
|
||||||
dnsCancel()
|
dnsCancel()
|
||||||
@@ -244,7 +243,7 @@ func fasthttpHTTPSDialerDualStackTimeout(proxyURL *url.URL, timeout time.Duratio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade to TLS
|
// Upgrade to TLS
|
||||||
tlsConn := tls.Client(conn, &tls.Config{ //nolint:gosec
|
tlsConn := tls.Client(conn, &tls.Config{
|
||||||
ServerName: proxyURL.Hostname(),
|
ServerName: proxyURL.Hostname(),
|
||||||
})
|
})
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
func NewDefaultRandSource() rand.Source {
|
func NewDefaultRandSource() rand.Source {
|
||||||
now := time.Now().UnixNano()
|
now := time.Now().UnixNano()
|
||||||
return rand.NewPCG(
|
return rand.NewPCG(
|
||||||
uint64(now), //nolint:gosec // G115: Safe conversion; UnixNano timestamp used as random seed, bit pattern is intentional
|
uint64(now),
|
||||||
uint64(now>>32), //nolint:gosec // G115: Safe conversion; right-shifted timestamp for seed entropy, overflow is acceptable
|
uint64(now>>32),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func NewRequestGenerator(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyTemplateFuncMapData.ClearFormDataContenType()
|
bodyTemplateFuncMapData.ClearFormDataContentType()
|
||||||
if err = bodyGenerator(reqData, data); err != nil {
|
if err = bodyGenerator(reqData, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -99,8 +99,8 @@ func NewRequestGenerator(
|
|||||||
if err = headersGenerator(reqData, data); err != nil {
|
if err = headersGenerator(reqData, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if bodyTemplateFuncMapData.GetFormDataContenType() != "" {
|
if bodyTemplateFuncMapData.GetFormDataContentType() != "" {
|
||||||
reqData.Headers["Content-Type"] = append(reqData.Headers["Content-Type"], bodyTemplateFuncMapData.GetFormDataContenType())
|
reqData.Headers["Content-Type"] = append(reqData.Headers["Content-Type"], bodyTemplateFuncMapData.GetFormDataContentType())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = paramsGenerator(reqData, data); err != nil {
|
if err = paramsGenerator(reqData, data); err != nil {
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package sarin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5" // #nosec G501 -- exposed intentionally as a template utility helper
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -81,7 +85,47 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
|
|||||||
"slice_Uint": func(values ...uint) []uint { return values },
|
"slice_Uint": func(values ...uint) []uint { return values },
|
||||||
"slice_Join": strings.Join,
|
"slice_Join": strings.Join,
|
||||||
|
|
||||||
|
// Time
|
||||||
|
"time_NowUnix": func() int64 { return time.Now().Unix() },
|
||||||
|
"time_NowUnixMilli": func() int64 { return time.Now().UnixMilli() },
|
||||||
|
"time_NowRFC3339": func() string { return time.Now().Format(time.RFC3339) },
|
||||||
|
"time_Format": func(layout string, t time.Time) string {
|
||||||
|
return t.Format(layout)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Crypto
|
||||||
|
"crypto_SHA256": func(s string) string {
|
||||||
|
sum := sha256.Sum256([]byte(s))
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
},
|
||||||
|
"crypto_MD5": func(s string) string {
|
||||||
|
sum := md5.Sum([]byte(s)) // #nosec G401 -- MD5 is intentionally provided as a non-security template helper
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
},
|
||||||
|
"crypto_HMACSHA256": func(key string, msg string) string {
|
||||||
|
mac := hmac.New(sha256.New, []byte(key))
|
||||||
|
_, _ = mac.Write([]byte(msg))
|
||||||
|
return hex.EncodeToString(mac.Sum(nil))
|
||||||
|
},
|
||||||
|
"crypto_Base64URL": func(s string) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString([]byte(s))
|
||||||
|
},
|
||||||
|
|
||||||
// File
|
// File
|
||||||
|
// file_Read reads a file (local or remote URL) and returns its content as a string.
|
||||||
|
// Usage: {{ file_Read "/path/to/file.txt" }}
|
||||||
|
// {{ file_Read "https://example.com/data.txt" }}
|
||||||
|
"file_Read": func(source string) (string, error) {
|
||||||
|
if fileCache == nil {
|
||||||
|
return "", types.ErrFileCacheNotInitialized
|
||||||
|
}
|
||||||
|
cached, err := fileCache.GetOrLoad(source)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(cached.Content), nil
|
||||||
|
},
|
||||||
|
|
||||||
// file_Base64 reads a file (local or remote URL) and returns its Base64 encoded content.
|
// file_Base64 reads a file (local or remote URL) and returns its Base64 encoded content.
|
||||||
// Usage: {{ file_Base64 "/path/to/file.pdf" }}
|
// Usage: {{ file_Base64 "/path/to/file.pdf" }}
|
||||||
// {{ file_Base64 "https://example.com/image.png" }}
|
// {{ file_Base64 "https://example.com/image.png" }}
|
||||||
@@ -242,7 +286,7 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
|
|||||||
"fakeit_AdverbFrequencyDefinite": fakeit.AdverbFrequencyDefinite,
|
"fakeit_AdverbFrequencyDefinite": fakeit.AdverbFrequencyDefinite,
|
||||||
"fakeit_AdverbFrequencyIndefinite": fakeit.AdverbFrequencyIndefinite,
|
"fakeit_AdverbFrequencyIndefinite": fakeit.AdverbFrequencyIndefinite,
|
||||||
|
|
||||||
// Propositions
|
// Prepositions
|
||||||
"fakeit_Preposition": fakeit.Preposition,
|
"fakeit_Preposition": fakeit.Preposition,
|
||||||
"fakeit_PrepositionSimple": fakeit.PrepositionSimple,
|
"fakeit_PrepositionSimple": fakeit.PrepositionSimple,
|
||||||
"fakeit_PrepositionDouble": fakeit.PrepositionDouble,
|
"fakeit_PrepositionDouble": fakeit.PrepositionDouble,
|
||||||
@@ -545,15 +589,15 @@ func NewDefaultTemplateFuncMap(randSource rand.Source, fileCache *FileCache) tem
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BodyTemplateFuncMapData struct {
|
type BodyTemplateFuncMapData struct {
|
||||||
formDataContenType string
|
formDataContentType string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data BodyTemplateFuncMapData) GetFormDataContenType() string {
|
func (data BodyTemplateFuncMapData) GetFormDataContentType() string {
|
||||||
return data.formDataContenType
|
return data.formDataContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *BodyTemplateFuncMapData) ClearFormDataContenType() {
|
func (data *BodyTemplateFuncMapData) ClearFormDataContentType() {
|
||||||
data.formDataContenType = ""
|
data.formDataContentType = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultBodyTemplateFuncMap(
|
func NewDefaultBodyTemplateFuncMap(
|
||||||
@@ -584,7 +628,7 @@ func NewDefaultBodyTemplateFuncMap(
|
|||||||
|
|
||||||
var multipartData bytes.Buffer
|
var multipartData bytes.Buffer
|
||||||
writer := multipart.NewWriter(&multipartData)
|
writer := multipart.NewWriter(&multipartData)
|
||||||
data.formDataContenType = writer.FormDataContentType()
|
data.formDataContentType = writer.FormDataContentType()
|
||||||
|
|
||||||
for i := 0; i < len(pairs); i += 2 {
|
for i := 0; i < len(pairs); i += 2 {
|
||||||
key := pairs[i]
|
key := pairs[i]
|
||||||
|
|||||||
Reference in New Issue
Block a user