mirror of
https://github.com/axllent/mailpit.git
synced 2026-03-03 02:17:01 +00:00
Merge branch 'release/v1.27.0'
This commit is contained in:
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -8,16 +8,16 @@ updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "quarterly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "quarterly"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "quarterly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
interval: "quarterly"
|
||||
|
||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -26,6 +26,7 @@
|
||||
"Mechs",
|
||||
"navhtml",
|
||||
"neostandard",
|
||||
"nolint",
|
||||
"popperjs",
|
||||
"readyz",
|
||||
"RSET",
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -2,7 +2,7 @@
|
||||
|
||||
Notable changes to Mailpit will be documented in this file.
|
||||
|
||||
## [v1.26.2]
|
||||
## [v1.27.0]
|
||||
|
||||
### Feature
|
||||
- Store username with messages, auto-tag, and UI display ([#521](https://github.com/axllent/mailpit/issues/521))
|
||||
@@ -12,9 +12,17 @@ Notable changes to Mailpit will be documented in this file.
|
||||
- Apply linting to all JavaScript/Vue files with eslint & prettier
|
||||
- Update Go dependencies
|
||||
- Update node dependencies
|
||||
- Remove unused functionality/deadcode (golangci-lint)
|
||||
- Refactor error handling and resource management across multiple files (golangci-lint)
|
||||
- Refactor API Swagger definitions and remove unused structs
|
||||
- Bump minimum Go version to v1.24.3 for jhillyerd/enmime/v2
|
||||
- Switch version checks & self-updater to use ghru/v2
|
||||
- Update Go dependencies
|
||||
- Update node dependencies
|
||||
|
||||
### Fix
|
||||
- Improve version polling, add thread safety and exponential backoff ([#523](https://github.com/axllent/mailpit/issues/523))
|
||||
- Align websocket new message values with global Message Summary (no null values) ([#526](https://github.com/axllent/mailpit/issues/526))
|
||||
|
||||
### Test
|
||||
- Add JavaScript linting tests to CI
|
||||
|
||||
@@ -55,7 +55,7 @@ The --recent flag will only consider files with a modification date within the l
|
||||
logger.Log().Errorf("%s: %s", path, err.Error())
|
||||
return nil
|
||||
}
|
||||
defer f.Close() // #nosec
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
body, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
|
||||
@@ -67,14 +67,6 @@ func Execute() {
|
||||
}
|
||||
}
|
||||
|
||||
// SendmailExecute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func SendmailExecute() {
|
||||
args := []string{"mailpit", "sendmail"}
|
||||
|
||||
rootCmd.Run(sendmailCmd, args)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// hide autocompletion
|
||||
rootCmd.CompletionOptions.HiddenDefaultCmd = true
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"runtime"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/updater"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -15,29 +14,41 @@ var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display the current version & update information",
|
||||
Long: `Display the current version & update information (if available).`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
updater.AllowPrereleases = true
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
update, _ := cmd.Flags().GetBool("update")
|
||||
|
||||
if update {
|
||||
return updateApp()
|
||||
// Update the application
|
||||
rel, err := config.GHRUConfig.SelfUpdate()
|
||||
if err != nil {
|
||||
fmt.Printf("Error updating: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Updated %s to version %s\n", os.Args[0], rel.Tag)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s compiled with %s on %s/%s\n",
|
||||
os.Args[0], config.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName)
|
||||
if err == nil && updater.GreaterThan(latest, config.Version) {
|
||||
fmt.Printf(
|
||||
"\nUpdate available: %s\nRun `%s version -u` to update (requires read/write access to install directory).\n",
|
||||
latest,
|
||||
os.Args[0],
|
||||
)
|
||||
release, err := config.GHRUConfig.Latest()
|
||||
if err != nil {
|
||||
fmt.Printf("Error checking for latest release: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return nil
|
||||
// The latest version is the same version
|
||||
if release.Tag == config.Version {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// A newer release is available
|
||||
fmt.Printf(
|
||||
"\nUpdate available: %s\nRun `%s version -u` to update (requires read/write access to install directory).\n",
|
||||
release.Tag,
|
||||
os.Args[0],
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,13 +58,3 @@ func init() {
|
||||
versionCmd.Flags().
|
||||
BoolP("update", "u", false, "update to latest version")
|
||||
}
|
||||
|
||||
func updateApp() error {
|
||||
rel, err := updater.GithubUpdate(config.Repo, config.RepoBinaryName, config.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Updated %s to version %s\n", os.Args[0], rel)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/axllent/ghru/v2"
|
||||
"github.com/axllent/mailpit/internal/auth"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
@@ -19,6 +20,18 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// Version is the Mailpit version, updated with every release
|
||||
Version = "dev"
|
||||
|
||||
// GHRUConfig is the configuration for the GitHub Release Updater
|
||||
// used to check for updates and self-update
|
||||
GHRUConfig = ghru.Config{
|
||||
Repo: "axllent/mailpit",
|
||||
ArchiveName: "mailpit-{{.OS}}-{{.Arch}}",
|
||||
BinaryName: "mailpit",
|
||||
CurrentVersion: Version,
|
||||
}
|
||||
|
||||
// SMTPListen to listen on <interface>:<port>
|
||||
SMTPListen = "[::]:1025"
|
||||
|
||||
@@ -198,15 +211,6 @@ var (
|
||||
// Empty = disabled, true= use existing web server, address = separate server
|
||||
PrometheusListen string
|
||||
|
||||
// Version is the default application version, updated on release
|
||||
Version = "dev"
|
||||
|
||||
// Repo on Github for updater
|
||||
Repo = "axllent/mailpit"
|
||||
|
||||
// RepoBinaryName on Github for updater
|
||||
RepoBinaryName = "mailpit"
|
||||
|
||||
// ChaosTriggers are parsed and set in the chaos module
|
||||
ChaosTriggers string
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// IsFile returns whether a file exists and is readable
|
||||
func isFile(path string) bool {
|
||||
f, err := os.Open(filepath.Clean(path))
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
||||
21
go.mod
21
go.mod
@@ -1,21 +1,17 @@
|
||||
module github.com/axllent/mailpit
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
// https://github.com/jaytaylor/html2text/issues/67
|
||||
replace github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.5
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.10.3
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
|
||||
github.com/axllent/ghru/v2 v2.0.1
|
||||
github.com/axllent/semver v0.0.1
|
||||
github.com/goccy/go-yaml v1.18.0
|
||||
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jhillyerd/enmime/v2 v2.1.0
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/kovidgoyal/imaging v1.6.4
|
||||
github.com/leporo/sqlf v1.4.0
|
||||
@@ -42,19 +38,23 @@ require (
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/css v1.0.1 // indirect
|
||||
github.com/inbucket/html2text v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/common v0.65.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/reiver/go-oi v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
@@ -64,9 +64,10 @@ require (
|
||||
github.com/vanng822/css v1.0.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||
golang.org/x/image v0.28.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
modernc.org/libc v1.66.0 // indirect
|
||||
modernc.org/libc v1.66.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
||||
31
go.sum
31
go.sum
@@ -6,6 +6,8 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA=
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
|
||||
github.com/axllent/ghru/v2 v2.0.1 h1:/9XHbMqJRGRxlyNMq2XiHQpBLz7hlq8xCdgJ4ZhjCoM=
|
||||
github.com/axllent/ghru/v2 v2.0.1/go.mod h1:seMMjx8/0r5ZAL7c0vwTPIRoyN0AoTUqAylZEWZWGK4=
|
||||
github.com/axllent/semver v0.0.1 h1:QqF+KSGxgj8QZzSXAvKFqjGWE5792ksOnQhludToK8E=
|
||||
github.com/axllent/semver v0.0.1/go.mod h1:2xSPzvG8n9mRfdtxSvWvfTfQGWfHsMsHO1iZnKATMSc=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -20,6 +22,8 @@ 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
@@ -41,12 +45,12 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/inbucket/html2text v1.0.0 h1:N5kza++4uBBDJ2Z3KUnTRyPNoBcW+YfOgNiNmNB+sgs=
|
||||
github.com/inbucket/html2text v1.0.0/go.mod h1:5TrhXQKGU+LXurODaSm55Y9eXoPBRnYiOz4x2XfUoJU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
|
||||
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jhillyerd/enmime/v2 v2.1.0 h1:c8Qwi5Xq5EdtMN6byQWoZ/8I2RMTo6OJ7Xay+s1oPO0=
|
||||
github.com/jhillyerd/enmime/v2 v2.1.0/go.mod h1:EJ74dcRbBcqHSP2TBu08XRoy6y3Yx0cevwb1YkGMEmQ=
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0 h1:Pe35MB96eZK5Q0XjlvPftOgWypQpd1gcbfJKAt7rsB8=
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0/go.mod h1:SOBXlCemjhiV2DvHhAKnJiWrtJGS/Ffuw4Iy7NjBTaI=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kovidgoyal/imaging v1.6.4 h1:K0idhRPXnRrJBKnBYcTfI1HTWSNDeAn7hYDvf9I0dCk=
|
||||
@@ -57,9 +61,10 @@ github.com/leporo/sqlf v1.4.0 h1:SyWnX/8GSGOzVmanG0Ub1c04mR9nNl6Tq3IeFKX2/4c=
|
||||
github.com/leporo/sqlf v1.4.0/go.mod h1:pgN9yKsAnQ+2ewhbZogr98RcasUjPsHF3oXwPPhHvBw=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
|
||||
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-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
@@ -71,8 +76,12 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
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=
|
||||
@@ -81,8 +90,8 @@ github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/
|
||||
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4=
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
|
||||
@@ -224,8 +233,8 @@ modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI=
|
||||
modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.0 h1:eoFuDb1ozurUY5WSWlgvxHp0FuL+AncMwNjFqGYMJPQ=
|
||||
modernc.org/libc v1.66.0/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo=
|
||||
modernc.org/libc v1.66.1 h1:4uQsntXbVyAgrV+j6NhKvDiUypoJL48BWQx6sy9y8ok=
|
||||
modernc.org/libc v1.66.1/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
|
||||
@@ -39,14 +39,14 @@ func Sync(d string) error {
|
||||
|
||||
if URL != "" {
|
||||
if !linkRe.MatchString(URL) {
|
||||
return errors.New("Invalid URL")
|
||||
return errors.New("invalid URL")
|
||||
}
|
||||
|
||||
base = strings.TrimRight(URL, "/") + "/"
|
||||
}
|
||||
|
||||
if base == "" && config.Database == "" {
|
||||
return errors.New("No database or API URL specified")
|
||||
return errors.New("no database or API URL specified")
|
||||
}
|
||||
|
||||
if !tools.IsDir(outDir) {
|
||||
@@ -109,7 +109,7 @@ func loadIDs() error {
|
||||
}
|
||||
|
||||
if len(summary) == 0 {
|
||||
return errors.New("No messages found")
|
||||
return errors.New("no messages found")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -193,10 +193,10 @@ func downloadToBytes(url string) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
err := fmt.Errorf("Error downloading %s", url)
|
||||
err := fmt.Errorf("error downloading %s", url)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -152,13 +152,14 @@ func (c CanIEmail) getTest(k string) (Warning, error) {
|
||||
s.Platform = platform
|
||||
s.Version = version
|
||||
|
||||
if support == "y" {
|
||||
switch support {
|
||||
case "y":
|
||||
y++
|
||||
s.Support = "yes"
|
||||
} else if support == "n" {
|
||||
case "n":
|
||||
n++
|
||||
s.Support = "no"
|
||||
} else {
|
||||
default:
|
||||
p++
|
||||
s.Support = "partial"
|
||||
|
||||
|
||||
@@ -66,17 +66,6 @@ func PrettyPrint(i interface{}) {
|
||||
fmt.Println(string(s))
|
||||
}
|
||||
|
||||
// CleanIP returns a human-readable IP for the logging interface
|
||||
// when starting services. It translates [::]:<port> to "0.0.0.0:<port>"
|
||||
func CleanIP(s string) string {
|
||||
re := regexp.MustCompile(`^\[\:\:\]\:\d+`)
|
||||
if re.MatchString(s) {
|
||||
return "0.0.0.0:" + s[5:]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// CleanHTTPIP returns a human-readable IP for the logging interface
|
||||
// when starting services. It translates [::]:<port> to "localhost:<port>"
|
||||
func CleanHTTPIP(s string) string {
|
||||
|
||||
@@ -18,7 +18,7 @@ func authUser(username, password string) bool {
|
||||
|
||||
// Send a response with debug logging
|
||||
func sendResponse(c net.Conn, m string) {
|
||||
fmt.Fprintf(c, "%s\r\n", m)
|
||||
_, _ = fmt.Fprintf(c, "%s\r\n", m)
|
||||
logger.Log().Debugf("[pop3] response: %s", m)
|
||||
|
||||
if strings.HasPrefix(m, "-ERR ") {
|
||||
@@ -29,7 +29,7 @@ func sendResponse(c net.Conn, m string) {
|
||||
|
||||
// Send a response without debug logging (for data)
|
||||
func sendData(c net.Conn, m string) {
|
||||
fmt.Fprintf(c, "%s\r\n", m)
|
||||
_, _ = fmt.Fprintf(c, "%s\r\n", m)
|
||||
}
|
||||
|
||||
// Get the latest 100 messages
|
||||
|
||||
@@ -29,22 +29,21 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
// connect with bad password
|
||||
t.Log("Testing invalid login")
|
||||
c, err := connectBadAuth()
|
||||
if err == nil {
|
||||
if _, err := connectBadAuth(); err == nil {
|
||||
t.Error("invalid login gained access")
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("Testing valid login")
|
||||
c, err = connectAuth()
|
||||
c, err := connectAuth()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, size, err := c.Stat()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
// quit else we get old data
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -63,13 +62,13 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
c, err = connectAuth()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, _, err = c.Stat()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -80,7 +79,7 @@ func TestPOP3(t *testing.T) {
|
||||
for i := 1; i <= 20; i++ {
|
||||
_, err := c.Retr(i)
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -89,14 +88,14 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
for i := 1; i <= 25; i++ {
|
||||
if err := c.Dele(i); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// messages get deleted after a QUIT
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -105,7 +104,7 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
c, err = connectAuth()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -113,7 +112,7 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
count, _, err = c.Stat()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -121,13 +120,13 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
// messages get deleted after a QUIT
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c, err = connectAuth()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -135,7 +134,7 @@ func TestPOP3(t *testing.T) {
|
||||
|
||||
for i := 1; i <= 25; i++ {
|
||||
if err := c.Dele(i); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -143,31 +142,31 @@ func TestPOP3(t *testing.T) {
|
||||
t.Log("Undeleting messages")
|
||||
|
||||
if err := c.Rset(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c, err = connectAuth()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
count, _, err = c.Stat()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
assertEqual(t, count, 25, "incorrect message count")
|
||||
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -190,7 +189,7 @@ func TestAuthentication(t *testing.T) {
|
||||
// non-authenticated connection
|
||||
c, err := connect()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -207,7 +206,7 @@ func TestAuthentication(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -216,7 +215,7 @@ func TestAuthentication(t *testing.T) {
|
||||
// authenticated connection
|
||||
c, err = connectAuth()
|
||||
if err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -233,13 +232,15 @@ func TestAuthentication(t *testing.T) {
|
||||
}
|
||||
|
||||
if err := c.Quit(); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func setup() {
|
||||
auth.SetPOP3Auth("username:password")
|
||||
if err := auth.SetPOP3Auth("username:password"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logger.NoLogging = true
|
||||
config.MaxMessages = 0
|
||||
config.Database = os.Getenv("MP_DATABASE")
|
||||
|
||||
@@ -271,7 +271,7 @@ func handleTransactionCommand(conn net.Conn, cmd string, args []string, messages
|
||||
// begins with the termination octet, the line is "byte-stuffed" by
|
||||
// pre-pending the termination octet to that line of the response.
|
||||
// @see: https://www.ietf.org/rfc/rfc1939.txt
|
||||
sendData(conn, strings.Replace(string(raw), "\n.", "\n..", -1))
|
||||
sendData(conn, strings.ReplaceAll(string(raw), "\n.", "\n.."))
|
||||
sendResponse(conn, ".")
|
||||
case "TOP":
|
||||
arg, err := getSafeArg(args, 0)
|
||||
|
||||
@@ -422,7 +422,7 @@ func (c *Conn) Noop() error {
|
||||
// Message deletions (DELE command) are only executed by the server on a graceful
|
||||
// quit and close.
|
||||
func (c *Conn) Quit() error {
|
||||
defer c.conn.Close()
|
||||
defer func() { _ = c.conn.Close() }()
|
||||
|
||||
if _, err := c.Cmd("QUIT", false); err != nil {
|
||||
return err
|
||||
|
||||
@@ -179,10 +179,10 @@ func StartSeparateServer() {
|
||||
func GetMode() string {
|
||||
mode := strings.ToLower(strings.TrimSpace(config.PrometheusListen))
|
||||
|
||||
switch {
|
||||
case mode == "false", mode == "":
|
||||
switch mode {
|
||||
case "false", "":
|
||||
return "disabled"
|
||||
case mode == "true":
|
||||
case "true":
|
||||
return "integrated"
|
||||
default:
|
||||
return "separate"
|
||||
|
||||
@@ -25,7 +25,8 @@ var (
|
||||
)
|
||||
|
||||
// Triggers for the Chaos configuration
|
||||
// swagger:model Triggers
|
||||
//
|
||||
// swagger:model ChaosTriggers
|
||||
type Triggers struct {
|
||||
// Sender trigger to fail on From, Sender
|
||||
Sender Trigger
|
||||
@@ -36,7 +37,8 @@ type Triggers struct {
|
||||
}
|
||||
|
||||
// Trigger for Chaos
|
||||
// swagger:model Trigger
|
||||
//
|
||||
// swagger:model ChaosTrigger
|
||||
type Trigger struct {
|
||||
// SMTP error code to return. The value must range from 400 to 599.
|
||||
// required: true
|
||||
|
||||
@@ -37,7 +37,7 @@ func createForwardingSMTPClient(config config.SMTPForwardConfigStruct, addr stri
|
||||
|
||||
client, err := smtp.NewClient(conn, tlsConf.ServerName)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
return nil, fmt.Errorf("SMTP client error: %v", err)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func createForwardingSMTPClient(config config.SMTPForwardConfigStruct, addr stri
|
||||
tlsConf.InsecureSkipVerify = config.AllowInsecure
|
||||
|
||||
if err = client.StartTLS(tlsConf); err != nil {
|
||||
client.Close()
|
||||
_ = client.Close()
|
||||
return nil, fmt.Errorf("error creating StartTLS config: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ func forward(from string, msg []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
defer func() { _ = c.Close() }()
|
||||
|
||||
auth := forwardAuthFromConfig()
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func createRelaySMTPClient(config config.SMTPRelayConfigStruct, addr string) (*s
|
||||
|
||||
client, err := smtp.NewClient(conn, tlsConf.ServerName)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
return nil, fmt.Errorf("SMTP client error: %v", err)
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func createRelaySMTPClient(config config.SMTPRelayConfigStruct, addr string) (*s
|
||||
tlsConf.InsecureSkipVerify = config.AllowInsecure
|
||||
|
||||
if err = client.StartTLS(tlsConf); err != nil {
|
||||
client.Close()
|
||||
_ = client.Close()
|
||||
return nil, fmt.Errorf("error creating StartTLS config: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func Relay(from string, to []string, msg []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
defer func() { _ = c.Close() }()
|
||||
|
||||
auth := relayAuthFromConfig()
|
||||
|
||||
@@ -193,7 +193,7 @@ func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
case "Password:":
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, errors.New("Unknown fromServer")
|
||||
return nil, errors.New("unknown fromServer")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ func (srv *Server) Serve(ln net.Listener) error {
|
||||
return ErrServerClosed
|
||||
}
|
||||
|
||||
defer ln.Close()
|
||||
defer func() { _ = ln.Close() }()
|
||||
|
||||
for {
|
||||
// if we are shutting down, don't accept new connections
|
||||
@@ -229,7 +229,7 @@ func (srv *Server) Serve(ln net.Listener) error {
|
||||
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
@@ -356,7 +356,9 @@ func (srv *Server) Shutdown(ctx context.Context) error {
|
||||
// Function called to handle connection requests.
|
||||
func (s *session) serve() {
|
||||
defer atomic.AddInt32(&s.srv.openSessions, -1)
|
||||
defer s.conn.Close()
|
||||
// pass the connection into the defer function to ensure it is closed,
|
||||
// otherwise results in a 5s timeout for each connection
|
||||
defer func(c net.Conn) { _ = c.Close() }(s.conn)
|
||||
|
||||
var from string
|
||||
var gotFrom bool
|
||||
@@ -517,9 +519,9 @@ loop:
|
||||
// On other errors, allow the client to try again.
|
||||
data, err := s.readData()
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
switch err := err.(type) {
|
||||
case net.Error:
|
||||
if err.(net.Error).Timeout() {
|
||||
if err.Timeout() {
|
||||
s.writef("421 4.4.2 %s %s ESMTP Service closing transmission channel after timeout exceeded", s.srv.Hostname, s.srv.AppName)
|
||||
}
|
||||
break loop
|
||||
@@ -749,7 +751,7 @@ func (s *session) writef(format string, args ...interface{}) {
|
||||
}
|
||||
|
||||
line := fmt.Sprintf(format, args...)
|
||||
fmt.Fprintf(s.bw, "%s\r\n", line)
|
||||
_, _ = fmt.Fprintf(s.bw, "%s\r\n", line)
|
||||
_ = s.bw.Flush()
|
||||
|
||||
if Debug {
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -40,7 +39,7 @@ func newConn(t *testing.T, server *Server) net.Conn {
|
||||
|
||||
// Send a command and verify the 3 digit code from the response.
|
||||
func cmdCode(t *testing.T, conn net.Conn, cmd string, code string) string {
|
||||
fmt.Fprintf(conn, "%s\r\n", cmd)
|
||||
_, _ = fmt.Fprintf(conn, "%s\r\n", cmd)
|
||||
resp, err := bufio.NewReader(conn).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response from test server: %v", err)
|
||||
@@ -72,7 +71,9 @@ func TestSimpleCommands(t *testing.T) {
|
||||
conn := newConn(t, &Server{})
|
||||
cmdCode(t, conn, tt.cmd, tt.code)
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Errorf("Failed to close connection after command %s: %v", tt.cmd, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +91,7 @@ func TestCmdHELO(t *testing.T) {
|
||||
cmdCode(t, conn, "DATA", "503")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdEHLO(t *testing.T) {
|
||||
@@ -107,7 +108,7 @@ func TestCmdEHLO(t *testing.T) {
|
||||
cmdCode(t, conn, "DATA", "503")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdRSET(t *testing.T) {
|
||||
@@ -121,7 +122,7 @@ func TestCmdRSET(t *testing.T) {
|
||||
cmdCode(t, conn, "DATA", "503")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdMAIL(t *testing.T) {
|
||||
@@ -162,7 +163,7 @@ func TestCmdMAIL(t *testing.T) {
|
||||
// TODO: MAIL with invalid AUTH parameter must return 501 syntax error
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdMAILMaxSize(t *testing.T) {
|
||||
@@ -192,7 +193,7 @@ func TestCmdMAILMaxSize(t *testing.T) {
|
||||
|
||||
// Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2).
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdRCPT(t *testing.T) {
|
||||
@@ -239,7 +240,7 @@ func TestCmdRCPT(t *testing.T) {
|
||||
cmdCode(t, conn, "RCPT TO: <recipient@example.com>", "501")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdMaxRecipients(t *testing.T) {
|
||||
@@ -256,7 +257,7 @@ func TestCmdMaxRecipients(t *testing.T) {
|
||||
cmdCode(t, conn, "RCPT TO: <recipient5@example.com>", "452")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdDATA(t *testing.T) {
|
||||
@@ -286,7 +287,7 @@ func TestCmdDATA(t *testing.T) {
|
||||
cmdCode(t, conn, "Test message.\r\n.", "250")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdDATAWithMaxSize(t *testing.T) {
|
||||
@@ -323,7 +324,7 @@ func TestCmdDATAWithMaxSize(t *testing.T) {
|
||||
|
||||
// Clients should send either RSET or QUIT after receiving 552 (RFC 1870 section 6.2).
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
type mockHandler struct {
|
||||
@@ -347,7 +348,7 @@ func TestCmdDATAWithHandler(t *testing.T) {
|
||||
cmdCode(t, conn, "DATA", "354")
|
||||
cmdCode(t, conn, "Test message.\r\n.", "250")
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
|
||||
if m.handlerCalled != 1 {
|
||||
t.Errorf("MailHandler called %d times, want one call", m.handlerCalled)
|
||||
@@ -364,7 +365,7 @@ func TestCmdDATAWithHandlerError(t *testing.T) {
|
||||
cmdCode(t, conn, "DATA", "354")
|
||||
cmdCode(t, conn, "Test message.\r\n.", "451")
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
|
||||
if m.handlerCalled != 1 {
|
||||
t.Errorf("MailHandler called %d times, want one call", m.handlerCalled)
|
||||
@@ -382,7 +383,7 @@ func TestCmdSTARTTLS(t *testing.T) {
|
||||
cmdCode(t, conn, "STARTTLS FOO", "501")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdSTARTTLSFailure(t *testing.T) {
|
||||
@@ -411,7 +412,7 @@ func TestCmdSTARTTLSFailure(t *testing.T) {
|
||||
}
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
// Utility function to make a valid TLS certificate for use by the server.
|
||||
@@ -497,7 +498,7 @@ func TestCmdSTARTTLSSuccess(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "STARTTLS", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestCmdSTARTTLSRequired(t *testing.T) {
|
||||
@@ -548,7 +549,7 @@ func TestCmdSTARTTLSRequired(t *testing.T) {
|
||||
}
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestMakeHeaders(t *testing.T) {
|
||||
@@ -798,8 +799,8 @@ func TestMakeEHLOResponse(t *testing.T) {
|
||||
t.Errorf("AUTH does not appear in the extension list when an AuthHandler is specified")
|
||||
}
|
||||
|
||||
reLogin := regexp.MustCompile("\\bLOGIN\\b")
|
||||
rePlain := regexp.MustCompile("\\bPLAIN\\b")
|
||||
reLogin := regexp.MustCompile(`\bLOGIN\b`)
|
||||
rePlain := regexp.MustCompile(`\bPLAIN\b`)
|
||||
|
||||
// RFC 4954 specifies that, without TLS in use, plaintext authentication mechanisms must not be advertised.
|
||||
s.tls = false
|
||||
@@ -822,86 +823,86 @@ func TestMakeEHLOResponse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func createTmpFile(content string) (file *os.File, err error) {
|
||||
file, err = os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = file.Write([]byte(content))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = file.Close()
|
||||
return
|
||||
}
|
||||
// func createTmpFile(content string) (file *os.File, err error) {
|
||||
// file, err = os.CreateTemp("", "")
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// _, err = file.Write([]byte(content))
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// err = file.Close()
|
||||
// return
|
||||
// }
|
||||
|
||||
func createTLSFiles() (
|
||||
certFile *os.File,
|
||||
keyFile *os.File,
|
||||
passphrase string,
|
||||
err error,
|
||||
) {
|
||||
const certPEM = `-----BEGIN CERTIFICATE-----
|
||||
MIIDRzCCAi+gAwIBAgIJAKtg4oViVwv4MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDAgFw0xODA0MjAxMzMxNTBaGA8yMDg2MDUwODEzMzE1MFow
|
||||
FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEA8h7vl0gUquis5jRtcnETyD+8WITZO0s53aIzp0Y+9HXiHW6FGJjbOZjM
|
||||
IvozNVni+83QWKumRTgeSzIIW2j4V8iFMSNrvWmhmCKloesXS1aY6H979e01Ve8J
|
||||
WAJFRe6vZJd6gC6Z/P+ELU3ie4Vtr1GYfkV7nZ6VFp5/V/5nxGFag5TUlpP5hcoS
|
||||
9r2kvXofosVwe3x3udT8SEbv5eBD4bKeVyJs/RLbxSuiU1358Y1cDdVuHjcvfm3c
|
||||
ajhheQ4vX9WXsk7LGGhnf1SrrPN/y+IDTXfvoHn+nJh4vMAB4yzQdE1V1N1AB8RA
|
||||
0yBVJ6dwxRrSg4BFrNWhj3gfsvrA7wIDAQABo4GZMIGWMB0GA1UdDgQWBBQ4/ncp
|
||||
befFuKH1hoYkPqLwuRrPRjAfBgNVHSMEGDAWgBQ4/ncpbefFuKH1hoYkPqLwuRrP
|
||||
RjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDALBgNVHQ8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBJBetEXiEIzKAEpXGX87j6aUON51Fdf6BiLMCghuGKyhnaOG32
|
||||
4KJhtvVoS3ZUKPylh9c2VdItYlhWp76zd7YKk+3xUOixWeTMQHIvCvRGTyFibOPT
|
||||
mApwp2pEnJCe4vjUrBaRhiyI+xnB70cWVF2qeernlLUeJA1mfYyQLz+v06ebDWOL
|
||||
c/hPVQFB94lEdiyjGO7RZfIe8KwcK48g7iv0LQU4+c9MoWM2ZsVM1AL2tHzokSeA
|
||||
u64gDTW4K0Tzx1ab7KmOFXYUjbz/xWuReMt33EwDXAErKCjbVt2T55Qx8UoKzSh1
|
||||
tY0KDHdnYOzgsm2HIj2xcJqbeylYQvckNnoC
|
||||
-----END CERTIFICATE-----`
|
||||
// func createTLSFiles() (
|
||||
// certFile *os.File,
|
||||
// keyFile *os.File,
|
||||
// passphrase string,
|
||||
// err error,
|
||||
// ) {
|
||||
// const certPEM = `-----BEGIN CERTIFICATE-----
|
||||
// MIIDRzCCAi+gAwIBAgIJAKtg4oViVwv4MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
// BAMMCWxvY2FsaG9zdDAgFw0xODA0MjAxMzMxNTBaGA8yMDg2MDUwODEzMzE1MFow
|
||||
// FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
// CgKCAQEA8h7vl0gUquis5jRtcnETyD+8WITZO0s53aIzp0Y+9HXiHW6FGJjbOZjM
|
||||
// IvozNVni+83QWKumRTgeSzIIW2j4V8iFMSNrvWmhmCKloesXS1aY6H979e01Ve8J
|
||||
// WAJFRe6vZJd6gC6Z/P+ELU3ie4Vtr1GYfkV7nZ6VFp5/V/5nxGFag5TUlpP5hcoS
|
||||
// 9r2kvXofosVwe3x3udT8SEbv5eBD4bKeVyJs/RLbxSuiU1358Y1cDdVuHjcvfm3c
|
||||
// ajhheQ4vX9WXsk7LGGhnf1SrrPN/y+IDTXfvoHn+nJh4vMAB4yzQdE1V1N1AB8RA
|
||||
// 0yBVJ6dwxRrSg4BFrNWhj3gfsvrA7wIDAQABo4GZMIGWMB0GA1UdDgQWBBQ4/ncp
|
||||
// befFuKH1hoYkPqLwuRrPRjAfBgNVHSMEGDAWgBQ4/ncpbefFuKH1hoYkPqLwuRrP
|
||||
// RjAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDALBgNVHQ8EBAMCBaAwEwYD
|
||||
// VR0lBAwwCgYIKwYBBQUHAwEwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3
|
||||
// DQEBCwUAA4IBAQBJBetEXiEIzKAEpXGX87j6aUON51Fdf6BiLMCghuGKyhnaOG32
|
||||
// 4KJhtvVoS3ZUKPylh9c2VdItYlhWp76zd7YKk+3xUOixWeTMQHIvCvRGTyFibOPT
|
||||
// mApwp2pEnJCe4vjUrBaRhiyI+xnB70cWVF2qeernlLUeJA1mfYyQLz+v06ebDWOL
|
||||
// c/hPVQFB94lEdiyjGO7RZfIe8KwcK48g7iv0LQU4+c9MoWM2ZsVM1AL2tHzokSeA
|
||||
// u64gDTW4K0Tzx1ab7KmOFXYUjbz/xWuReMt33EwDXAErKCjbVt2T55Qx8UoKzSh1
|
||||
// tY0KDHdnYOzgsm2HIj2xcJqbeylYQvckNnoC
|
||||
// -----END CERTIFICATE-----`
|
||||
|
||||
const keyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-256-CBC,C16BF8745B2CDB53AC2B1D7609893AA0
|
||||
// const keyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||
// Proc-Type: 4,ENCRYPTED
|
||||
// DEK-Info: AES-256-CBC,C16BF8745B2CDB53AC2B1D7609893AA0
|
||||
|
||||
O13z7Yq7butaJmMfg9wRis9YnIDPsp4coYI6Ud+JGcP7iXoy95QMhovKWx25o1ol
|
||||
tvUTsrsG27fHGf9qG02KizApIVtO9c1e0swCWzFrKRQX0JDiZDmilb9xosBNNst1
|
||||
BOzOTRZEwFGSOCKZRBfSXyqC93TvLJ3DO9IUnKIeGt7upipvg29b/Dur/fyCy2WV
|
||||
bLHXwUTDBm7j49yfoEyGkDjoB2QO9wgcgbacbnQJQ25fTFUwZpZJEJv6o1tRhoYM
|
||||
ZMOhC9x1URmdHKN1+z2y5BrB6oNpParfeAMEvs/9FE6jJwYUR28Ql6Mhphfvr9W2
|
||||
5Gxd3J65Ao9Vi2I5j5X6aBuNjyhXN3ScLjPG4lVZm9RU/uTPEt81pig/d5nSAjvF
|
||||
Nfc08NuG3cnMyJSE/xScJ4D+GtX8U969wO4oKPCR4E/NFyXPR730ppupDFG6hzPD
|
||||
PDmiszDtU438JAZ8AuFa1LkbyFnEW6KVD4h7VRr8YDjirCqnkgjNSI6dFY0NQ8H7
|
||||
SyexB0lrceX6HZc+oNdAtkX3tYdzY3ExzUM5lSF1dkldnRbApLbqc4uuNIVXhXFM
|
||||
dJnoPdKAzM6i+2EeVUxWNdafKDxnjVSHIHzHfIFJLQ4GS5rnz9keRFdyDjQL07tT
|
||||
Lu9pPOmsadDXp7oSa81RgoCUfNZeR4jKpCk2BOft0L6ZSqwYFLcQHLIfJaGfn902
|
||||
TUOTxHt0KzEUYeYSrXC2a6cyvXAd1YI7lOgy60qG89VHyCc2v5Bs4c4FNUDC/+Dj
|
||||
4ZwogaAbSNkLaE0q3sYQRPdxSqLftyX0KitAgE7oGtdzBfe1cdBoozw3U67NEMMT
|
||||
6qvk5j7RepPRSrapHtK5pMMdg5XpKFWcOXZ26VHVrDCj4JKdjVb4iyiQi94VveV0
|
||||
w9+KcOtyrM7/jbQlCWnXpsIkP8VA/RIgh7CBn/h4oF1sO8ywP25OGQ7VWAVq1R9D
|
||||
8bl8GzIdR9PZpFyOxuIac4rPa8tkDeoXKs4cxoao7H/OZO9o9aTB7CJMTL9yv0Kb
|
||||
ntWuYxQchE6syoGsOgdGyZhaw4JeFkasDUP5beyNY+278NkzgGTOIMMTXIX46woP
|
||||
ehzHKGHXVGf7ZiSFF+zAHMXZRSwNVMkOYwlIoRg1IbvIRbAXqAR6xXQTCVzNG0SU
|
||||
cskojycBca1Cz3hDVIKYZd9beDhprVdr2a4K2nft2g2xRNjKPopsaqXx+VPibFUx
|
||||
X7542eQ3eAlhkWUuXvt0q5a9WJdjJp9ODA0/d0akF6JQlEHIAyLfoUKB1HYwgUGG
|
||||
6uRm651FDAab9U4cVC5PY1hfv/QwzpkNDkzgJAZ5SMOfZhq7IdBcqGd3lzPmq2FP
|
||||
Vy1LVZIl3eM+9uJx5TLsBHH6NhMwtNhFCNa/5ksodQYlTvR8IrrgWlYg4EL69vjS
|
||||
yt6HhhEN3lFCWvrQXQMp93UklbTlpVt6qcDXiC7HYbs3+EINargRd5Z+xL5i5vkN
|
||||
f9k7s0xqhloWNPZcyOXMrox8L81WOY+sP4mVlGcfDRLdEJ8X2ofJpOAcwYCnjsKd
|
||||
uEGsi+l2fTj/F+eZLE6sYoMprgJrbfeqtRWFguUgTn7s5hfU0tZ46al5d0vz8fWK
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
// O13z7Yq7butaJmMfg9wRis9YnIDPsp4coYI6Ud+JGcP7iXoy95QMhovKWx25o1ol
|
||||
// tvUTsrsG27fHGf9qG02KizApIVtO9c1e0swCWzFrKRQX0JDiZDmilb9xosBNNst1
|
||||
// BOzOTRZEwFGSOCKZRBfSXyqC93TvLJ3DO9IUnKIeGt7upipvg29b/Dur/fyCy2WV
|
||||
// bLHXwUTDBm7j49yfoEyGkDjoB2QO9wgcgbacbnQJQ25fTFUwZpZJEJv6o1tRhoYM
|
||||
// ZMOhC9x1URmdHKN1+z2y5BrB6oNpParfeAMEvs/9FE6jJwYUR28Ql6Mhphfvr9W2
|
||||
// 5Gxd3J65Ao9Vi2I5j5X6aBuNjyhXN3ScLjPG4lVZm9RU/uTPEt81pig/d5nSAjvF
|
||||
// Nfc08NuG3cnMyJSE/xScJ4D+GtX8U969wO4oKPCR4E/NFyXPR730ppupDFG6hzPD
|
||||
// PDmiszDtU438JAZ8AuFa1LkbyFnEW6KVD4h7VRr8YDjirCqnkgjNSI6dFY0NQ8H7
|
||||
// SyexB0lrceX6HZc+oNdAtkX3tYdzY3ExzUM5lSF1dkldnRbApLbqc4uuNIVXhXFM
|
||||
// dJnoPdKAzM6i+2EeVUxWNdafKDxnjVSHIHzHfIFJLQ4GS5rnz9keRFdyDjQL07tT
|
||||
// Lu9pPOmsadDXp7oSa81RgoCUfNZeR4jKpCk2BOft0L6ZSqwYFLcQHLIfJaGfn902
|
||||
// TUOTxHt0KzEUYeYSrXC2a6cyvXAd1YI7lOgy60qG89VHyCc2v5Bs4c4FNUDC/+Dj
|
||||
// 4ZwogaAbSNkLaE0q3sYQRPdxSqLftyX0KitAgE7oGtdzBfe1cdBoozw3U67NEMMT
|
||||
// 6qvk5j7RepPRSrapHtK5pMMdg5XpKFWcOXZ26VHVrDCj4JKdjVb4iyiQi94VveV0
|
||||
// w9+KcOtyrM7/jbQlCWnXpsIkP8VA/RIgh7CBn/h4oF1sO8ywP25OGQ7VWAVq1R9D
|
||||
// 8bl8GzIdR9PZpFyOxuIac4rPa8tkDeoXKs4cxoao7H/OZO9o9aTB7CJMTL9yv0Kb
|
||||
// ntWuYxQchE6syoGsOgdGyZhaw4JeFkasDUP5beyNY+278NkzgGTOIMMTXIX46woP
|
||||
// ehzHKGHXVGf7ZiSFF+zAHMXZRSwNVMkOYwlIoRg1IbvIRbAXqAR6xXQTCVzNG0SU
|
||||
// cskojycBca1Cz3hDVIKYZd9beDhprVdr2a4K2nft2g2xRNjKPopsaqXx+VPibFUx
|
||||
// X7542eQ3eAlhkWUuXvt0q5a9WJdjJp9ODA0/d0akF6JQlEHIAyLfoUKB1HYwgUGG
|
||||
// 6uRm651FDAab9U4cVC5PY1hfv/QwzpkNDkzgJAZ5SMOfZhq7IdBcqGd3lzPmq2FP
|
||||
// Vy1LVZIl3eM+9uJx5TLsBHH6NhMwtNhFCNa/5ksodQYlTvR8IrrgWlYg4EL69vjS
|
||||
// yt6HhhEN3lFCWvrQXQMp93UklbTlpVt6qcDXiC7HYbs3+EINargRd5Z+xL5i5vkN
|
||||
// f9k7s0xqhloWNPZcyOXMrox8L81WOY+sP4mVlGcfDRLdEJ8X2ofJpOAcwYCnjsKd
|
||||
// uEGsi+l2fTj/F+eZLE6sYoMprgJrbfeqtRWFguUgTn7s5hfU0tZ46al5d0vz8fWK
|
||||
// -----END RSA PRIVATE KEY-----`
|
||||
|
||||
passphrase = "test"
|
||||
// passphrase = "test"
|
||||
|
||||
certFile, err = createTmpFile(certPEM)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
keyFile, err = createTmpFile(keyPEM)
|
||||
return
|
||||
}
|
||||
// certFile, err = createTmpFile(certPEM)
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// keyFile, err = createTmpFile(keyPEM)
|
||||
// return
|
||||
// }
|
||||
|
||||
func TestAuthMechs(t *testing.T) {
|
||||
s := session{}
|
||||
@@ -966,7 +967,7 @@ func TestCmdAUTH(t *testing.T) {
|
||||
cmdCode(t, conn, "AUTH", "502")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHOptional(t *testing.T) {
|
||||
@@ -1007,7 +1008,7 @@ func TestCmdAUTHOptional(t *testing.T) {
|
||||
cmdCode(t, conn, "*", "501")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHRequired(t *testing.T) {
|
||||
@@ -1056,7 +1057,7 @@ func TestCmdAUTHRequired(t *testing.T) {
|
||||
cmdCode(t, conn, "AUTH PLAIN", "504")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHLOGIN(t *testing.T) {
|
||||
@@ -1113,7 +1114,7 @@ func TestCmdAUTHLOGIN(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHLOGINFast(t *testing.T) {
|
||||
@@ -1165,7 +1166,7 @@ func TestCmdAUTHLOGINFast(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHPLAIN(t *testing.T) {
|
||||
@@ -1224,7 +1225,7 @@ func TestCmdAUTHPLAIN(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHPLAINEmpty(t *testing.T) {
|
||||
@@ -1283,7 +1284,7 @@ func TestCmdAUTHPLAINEmpty(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHPLAINFast(t *testing.T) {
|
||||
@@ -1335,7 +1336,7 @@ func TestCmdAUTHPLAINFast(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHPLAINFastAndEmpty(t *testing.T) {
|
||||
@@ -1387,7 +1388,7 @@ func TestCmdAUTHPLAINFastAndEmpty(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
// makeCRAMMD5Response is a helper function to create the CRAM-MD5 hash.
|
||||
@@ -1457,7 +1458,7 @@ func TestCmdAUTHCRAMMD5(t *testing.T) {
|
||||
cmdCode(t, conn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
func TestCmdAUTHCRAMMD5WithTLS(t *testing.T) {
|
||||
@@ -1523,7 +1524,7 @@ func TestCmdAUTHCRAMMD5WithTLS(t *testing.T) {
|
||||
cmdCode(t, tlsConn, "AUTH CRAM-MD5", "503")
|
||||
|
||||
cmdCode(t, tlsConn, "QUIT", "221")
|
||||
tlsConn.Close()
|
||||
_ = tlsConn.Close()
|
||||
}
|
||||
|
||||
// Benchmark the mail handling without the network stack introducing latency.
|
||||
@@ -1540,17 +1541,17 @@ func BenchmarkReceive(b *testing.B) {
|
||||
|
||||
// Benchmark a full mail transaction.
|
||||
for i := 0; i < b.N; i++ {
|
||||
fmt.Fprintf(clientConn, "%s\r\n", "HELO host.example.com")
|
||||
_, _ = fmt.Fprintf(clientConn, "%s\r\n", "HELO host.example.com")
|
||||
_, _ = reader.ReadString('\n')
|
||||
fmt.Fprintf(clientConn, "%s\r\n", "MAIL FROM:<sender@example.com>")
|
||||
_, _ = fmt.Fprintf(clientConn, "%s\r\n", "MAIL FROM:<sender@example.com>")
|
||||
_, _ = reader.ReadString('\n')
|
||||
fmt.Fprintf(clientConn, "%s\r\n", "RCPT TO:<recipient@example.com>")
|
||||
_, _ = fmt.Fprintf(clientConn, "%s\r\n", "RCPT TO:<recipient@example.com>")
|
||||
_, _ = reader.ReadString('\n')
|
||||
fmt.Fprintf(clientConn, "%s\r\n", "DATA")
|
||||
_, _ = fmt.Fprintf(clientConn, "%s\r\n", "DATA")
|
||||
_, _ = reader.ReadString('\n')
|
||||
fmt.Fprintf(clientConn, "%s\r\n", "Test message.\r\n.")
|
||||
_, _ = fmt.Fprintf(clientConn, "%s\r\n", "Test message.\r\n.")
|
||||
_, _ = reader.ReadString('\n')
|
||||
fmt.Fprintf(clientConn, "%s\r\n", "QUIT")
|
||||
_, _ = fmt.Fprintf(clientConn, "%s\r\n", "QUIT")
|
||||
_, _ = reader.ReadString('\n')
|
||||
}
|
||||
}
|
||||
@@ -1589,11 +1590,11 @@ func TestCmdShutdown(t *testing.T) {
|
||||
cmdCode(t, conn, "QUIT", "221")
|
||||
|
||||
// connection should now be closed
|
||||
fmt.Fprintf(conn, "%s\r\n", "HELO host.example.com")
|
||||
_, _ = fmt.Fprintf(conn, "%s\r\n", "HELO host.example.com")
|
||||
_, err := bufio.NewReader(conn).ReadString('\n')
|
||||
if err != io.EOF {
|
||||
t.Errorf("Expected connection to be closed\n")
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func Check(email []byte, timeout int) (Response, error) {
|
||||
return r, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&r)
|
||||
|
||||
|
||||
@@ -57,13 +57,6 @@ func SetService(s string) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetTimeout defines the timeout
|
||||
func SetTimeout(t int) {
|
||||
if t > 0 {
|
||||
timeout = t
|
||||
}
|
||||
}
|
||||
|
||||
// Ping returns whether a service is active or not
|
||||
func Ping() error {
|
||||
if service == "postmark" {
|
||||
|
||||
@@ -70,21 +70,22 @@ type Result struct {
|
||||
|
||||
// dial connects to spamd through TCP or a Unix socket.
|
||||
func (c *Client) dial() (connection, error) {
|
||||
if c.net == "tcp" {
|
||||
switch c.net {
|
||||
case "tcp":
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", c.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.DialTCP("tcp", nil, tcpAddr)
|
||||
} else if c.net == "unix" {
|
||||
case "unix":
|
||||
unixAddr, err := net.ResolveUnixAddr("unix", c.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.DialUnix("unix", nil, unixAddr)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported network type: %s", c.net)
|
||||
}
|
||||
|
||||
panic("Client.net must be either \"tcp\" or \"unix\"")
|
||||
}
|
||||
|
||||
// Report checks if message is spam or not, and returns score plus report
|
||||
@@ -103,7 +104,7 @@ func (c *Client) report(email []byte) ([]string, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(time.Duration(c.timeout) * time.Second)); err != nil {
|
||||
return nil, err
|
||||
@@ -221,7 +222,7 @@ func (c *Client) Ping() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(time.Duration(c.timeout) * time.Second)); err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/axllent/mailpit/internal/updater"
|
||||
)
|
||||
|
||||
// Stores cached version along with its expiry time and error count.
|
||||
@@ -113,10 +112,10 @@ func Load() AppInformation {
|
||||
if time.Now().Before(vCache.expiry) {
|
||||
info.LatestVersion = vCache.value
|
||||
} else {
|
||||
latest, _, _, err := updater.GithubLatest(config.Repo, config.RepoBinaryName)
|
||||
latest, err := config.GHRUConfig.Latest()
|
||||
if err == nil {
|
||||
vCache = versionCache{value: latest, expiry: time.Now().Add(15 * time.Minute)}
|
||||
info.LatestVersion = latest
|
||||
vCache = versionCache{value: latest.Tag, expiry: time.Now().Add(15 * time.Minute)}
|
||||
info.LatestVersion = latest.Tag
|
||||
} else {
|
||||
logger.Log().Errorf("Failed to fetch latest version: %v", err)
|
||||
vCache.errCount++
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
|
||||
var (
|
||||
db *sql.DB
|
||||
dbFile string
|
||||
sqlDriver string
|
||||
dbLastAction time.Time
|
||||
|
||||
@@ -139,7 +138,6 @@ func InitDB() error {
|
||||
|
||||
LoadTagFilters()
|
||||
|
||||
dbFile = p
|
||||
dbLastAction = time.Now()
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
|
||||
@@ -86,7 +86,7 @@ func Store(body *[]byte, username *string) (string, error) {
|
||||
}
|
||||
|
||||
// roll back if it fails
|
||||
defer tx.Rollback()
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
subject := env.GetHeader("Subject")
|
||||
size := uint64(len(*body))
|
||||
@@ -118,8 +118,6 @@ func Store(body *[]byte, username *string) (string, error) {
|
||||
} else {
|
||||
_, err = tx.Exec(fmt.Sprintf(`INSERT INTO %s (ID, Email, Compressed) VALUES(?, ?, 1)`, tenant("mailbox_data")), id, compressed) // #nosec
|
||||
}
|
||||
|
||||
compressed = nil
|
||||
} else {
|
||||
// insert uncompressed raw message
|
||||
_, err = tx.Exec(fmt.Sprintf(`INSERT INTO %s (ID, Email, Compressed) VALUES(?, ?, 0)`, tenant("mailbox_data")), id, string(*body)) // #nosec
|
||||
@@ -170,6 +168,24 @@ func Store(body *[]byte, username *string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// we do not want to to broadcast null values for MetaData else this does not align
|
||||
// with the message summary documented in the API docs, so we set them to empty slices.
|
||||
if c.From == nil {
|
||||
c.From = &mail.Address{}
|
||||
}
|
||||
if c.To == nil {
|
||||
c.To = []*mail.Address{}
|
||||
}
|
||||
if c.Cc == nil {
|
||||
c.Cc = []*mail.Address{}
|
||||
}
|
||||
if c.Bcc == nil {
|
||||
c.Bcc = []*mail.Address{}
|
||||
}
|
||||
if c.ReplyTo == nil {
|
||||
c.ReplyTo = []*mail.Address{}
|
||||
}
|
||||
|
||||
c.Created = created
|
||||
c.ID = id
|
||||
c.MessageID = messageID
|
||||
@@ -639,7 +655,7 @@ func DeleteMessages(ids []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
toDelete := []string{}
|
||||
var totalSize uint64
|
||||
@@ -738,7 +754,7 @@ func DeleteAllMessages() error {
|
||||
}
|
||||
|
||||
// roll back if it fails
|
||||
defer tx.Rollback()
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
tables := []string{"mailbox", "mailbox_data", "tags", "message_tags"}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func ReindexAll() {
|
||||
}
|
||||
|
||||
// roll back if it fails
|
||||
defer tx.Rollback()
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
// insert mail summary data
|
||||
for _, u := range updates {
|
||||
|
||||
@@ -193,7 +193,7 @@ func DeleteSearch(search, timezone string) error {
|
||||
}
|
||||
|
||||
// roll back if it fails
|
||||
defer tx.Rollback()
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
for _, ids := range chunks {
|
||||
delIDs := make([]interface{}, len(ids))
|
||||
|
||||
@@ -138,17 +138,6 @@ func deleteMessageTag(id, name string) error {
|
||||
return pruneUnusedTags()
|
||||
}
|
||||
|
||||
// DeleteAllMessageTags deleted all tags from a message
|
||||
func DeleteAllMessageTags(id string) error {
|
||||
if _, err := sqlf.DeleteFrom(tenant("message_tags")).
|
||||
Where(tenant("message_tags.ID")+" = ?", id).
|
||||
ExecAndClose(context.TODO(), db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pruneUnusedTags()
|
||||
}
|
||||
|
||||
// GetAllTags returns all used tags
|
||||
func GetAllTags() []string {
|
||||
var tags = []string{}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/leporo/sqlf"
|
||||
)
|
||||
|
||||
func TestTags(t *testing.T) {
|
||||
@@ -83,7 +85,7 @@ func TestTags(t *testing.T) {
|
||||
assertEqual(t, strings.Join(newTags[1:], "|"), strings.Join(returnedTags, "|"), "Message tags do not match after deleting 1")
|
||||
|
||||
// remove all tags
|
||||
if err := DeleteAllMessageTags(id); err != nil {
|
||||
if err := deleteAllMessageTags(id); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
@@ -97,7 +99,7 @@ func TestTags(t *testing.T) {
|
||||
}
|
||||
returnedTags = getMessageTags(id)
|
||||
assertEqual(t, "Duplicate Tag", strings.Join(returnedTags, "|"), "Message tags should be duplicated")
|
||||
if err := DeleteAllMessageTags(id); err != nil {
|
||||
if err := deleteAllMessageTags(id); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
@@ -109,7 +111,7 @@ func TestTags(t *testing.T) {
|
||||
}
|
||||
returnedTags = getMessageTags(id)
|
||||
assertEqual(t, "Dirty Tag", strings.Join(returnedTags, "|"), "Dirty message tag did not clean as expected")
|
||||
if err := DeleteAllMessageTags(id); err != nil {
|
||||
if err := deleteAllMessageTags(id); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
@@ -132,7 +134,7 @@ func TestTags(t *testing.T) {
|
||||
|
||||
returnedTags = getMessageTags(id)
|
||||
assertEqual(t, "BccTag|CcTag|FromFag|ToTag|X-tag1|X-tag2", strings.Join(returnedTags, "|"), "Tags not detected correctly")
|
||||
if err := DeleteAllMessageTags(id); err != nil {
|
||||
if err := deleteAllMessageTags(id); err != nil {
|
||||
t.Log("error ", err)
|
||||
t.Fail()
|
||||
}
|
||||
@@ -186,3 +188,14 @@ func TestUsernameAutoTagging(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAllMessageTags deleted all tags from a message
|
||||
func deleteAllMessageTags(id string) error {
|
||||
if _, err := sqlf.DeleteFrom(tenant("message_tags")).
|
||||
Where(tenant("message_tags.ID")+" = ?", id).
|
||||
ExecAndClose(context.TODO(), db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return pruneUnusedTags()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
// IsFile returns whether a file exists and is readable
|
||||
func IsFile(path string) bool {
|
||||
f, err := os.Open(filepath.Clean(path))
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ func ListUnsubscribeParser(v string) ([]string, error) {
|
||||
comments := reComments.FindAllStringSubmatch(v, -1)
|
||||
for _, c := range comments {
|
||||
// strip comments
|
||||
v = strings.Replace(v, c[0], "", -1)
|
||||
v = strings.ReplaceAll(v, c[0], "")
|
||||
v = strings.TrimSpace(v)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// TarGZExtract extracts a archive from the file inputFilePath.
|
||||
// It tries to create the directory structure outputFilePath contains if it doesn't exist.
|
||||
// It returns potential errors to be checked or nil if everything works.
|
||||
func TarGZExtract(inputFilePath, outputFilePath string) (err error) {
|
||||
outputFilePath = stripTrailingSlashes(outputFilePath)
|
||||
inputFilePath, outputFilePath, err = makeAbsolute(inputFilePath, outputFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
undoDir, err := mkdirAll(outputFilePath, 0750)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
undoDir()
|
||||
}
|
||||
}()
|
||||
|
||||
return extract(inputFilePath, outputFilePath)
|
||||
}
|
||||
|
||||
// Creates all directories with os.MakedirAll and returns a function to remove the first created directory so cleanup is possible.
|
||||
func mkdirAll(dirPath string, perm os.FileMode) (func(), error) {
|
||||
var undoDir string
|
||||
|
||||
for p := dirPath; ; p = filepath.Dir(p) {
|
||||
finfo, err := os.Stat(p)
|
||||
if err == nil {
|
||||
if finfo.IsDir() {
|
||||
break
|
||||
}
|
||||
|
||||
finfo, err = os.Lstat(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if finfo.IsDir() {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("mkdirAll (%s): %v", p, syscall.ENOTDIR)
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
undoDir = p
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if undoDir == "" {
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dirPath, perm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func() {
|
||||
if err := os.RemoveAll(undoDir); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Remove trailing slash if any.
|
||||
func stripTrailingSlashes(path string) string {
|
||||
if len(path) > 0 && path[len(path)-1] == '/' {
|
||||
path = path[0 : len(path)-1]
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// Make input and output paths absolute.
|
||||
func makeAbsolute(inputFilePath, outputFilePath string) (string, string, error) {
|
||||
inputFilePath, err := filepath.Abs(inputFilePath)
|
||||
if err == nil {
|
||||
outputFilePath, err = filepath.Abs(outputFilePath)
|
||||
}
|
||||
|
||||
return inputFilePath, outputFilePath, err
|
||||
}
|
||||
|
||||
// Extract the file in filePath to directory.
|
||||
func extract(filePath string, directory string) error {
|
||||
file, err := os.Open(filepath.Clean(filePath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
fmt.Printf("Error closing file: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
gzipReader, err := gzip.NewReader(bufio.NewReader(file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzipReader)
|
||||
|
||||
// Post extraction directory permissions & timestamps
|
||||
type DirInfo struct {
|
||||
Path string
|
||||
Header *tar.Header
|
||||
}
|
||||
|
||||
// slice to add all extracted directory info for post-processing
|
||||
postExtraction := []DirInfo{}
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo := header.FileInfo()
|
||||
// paths could contain a '..', is used in a file system operations
|
||||
if strings.Contains(fileInfo.Name(), "..") {
|
||||
continue
|
||||
}
|
||||
dir := filepath.Join(directory, filepath.Dir(header.Name))
|
||||
filename := filepath.Join(dir, fileInfo.Name())
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
// create the directory 755 in case writing permissions prohibit writing before files added
|
||||
if err := os.MkdirAll(filename, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set file ownership (if allowed)
|
||||
// Chtimes() && Chmod() only set after once extraction is complete
|
||||
_ = os.Chown(filename, header.Uid, header.Gid)
|
||||
|
||||
// add directory info to slice to process afterwards
|
||||
postExtraction = append(postExtraction, DirInfo{filename, header})
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure parent directory exists (may not be included in tar)
|
||||
if !fileInfo.IsDir() && !isDir(dir) {
|
||||
err = os.MkdirAll(dir, 0750)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Create(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writer := bufio.NewWriter(file)
|
||||
|
||||
buffer := make([]byte, 4096)
|
||||
for {
|
||||
n, err := tarReader.Read(buffer)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
_, err = writer.Write(buffer[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set file permissions, timestamps & uid/gid
|
||||
_ = os.Chmod(filename, os.FileMode(header.Mode)) // #nosec
|
||||
_ = os.Chtimes(filename, header.AccessTime, header.ModTime)
|
||||
_ = os.Chown(filename, header.Uid, header.Gid)
|
||||
}
|
||||
|
||||
if len(postExtraction) > 0 {
|
||||
for _, dir := range postExtraction {
|
||||
_ = os.Chtimes(dir.Path, dir.Header.AccessTime, dir.Header.ModTime)
|
||||
_ = os.Chmod(dir.Path, dir.Header.FileInfo().Mode().Perm())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Unzip will decompress a zip archive, moving all files and folders
|
||||
// within the zip file (src) to an output directory (dest).
|
||||
func Unzip(src string, dest string) ([]string, error) {
|
||||
|
||||
var filenames []string
|
||||
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
|
||||
// Store filename/path for returning and using later on
|
||||
fpath := filepath.Join(dest, filepath.Clean(f.Name))
|
||||
|
||||
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
|
||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return filenames, fmt.Errorf("%s: illegal file path", fpath)
|
||||
}
|
||||
|
||||
filenames = append(filenames, fpath)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
// Make Folder
|
||||
if err := os.MkdirAll(fpath, os.ModePerm); /* #nosec */ err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Make File
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); /* #nosec */ err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
outFile, err := os.OpenFile(filepath.Clean(fpath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, rc) // #nosec - file is streamed from zip to file
|
||||
|
||||
// Close the file without defer to close before next iteration of loop
|
||||
if err := outFile.Close(); err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
if err := rc.Close(); err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return filenames, err
|
||||
}
|
||||
}
|
||||
|
||||
return filenames, nil
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
// Package updater checks and downloads new versions
|
||||
package updater
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/semver"
|
||||
)
|
||||
|
||||
var (
|
||||
// AllowPrereleases defines whether pre-releases may be included
|
||||
AllowPrereleases = false
|
||||
|
||||
// temporary directory
|
||||
tempDir string
|
||||
)
|
||||
|
||||
// Releases struct for Github releases json
|
||||
type Releases []struct {
|
||||
Name string `json:"name"` // release name
|
||||
Tag string `json:"tag_name"` // release tag
|
||||
Prerelease bool `json:"prerelease"` // Github pre-release
|
||||
Assets []struct {
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
} `json:"assets"`
|
||||
}
|
||||
|
||||
// Release struct contains the file data for downloadable release
|
||||
type Release struct {
|
||||
Name string
|
||||
Tag string
|
||||
URL string
|
||||
Size int64
|
||||
}
|
||||
|
||||
// GithubLatest fetches the latest release info & returns release tag, filename & download url
|
||||
func GithubLatest(repo, name string) (string, string, string, error) {
|
||||
releaseURL := fmt.Sprintf("https://api.github.com/repos/%s/releases", repo)
|
||||
|
||||
timeout := time.Duration(5 * time.Second)
|
||||
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", releaseURL, nil)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Mailpit/"+config.Version)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
linkOS := runtime.GOOS
|
||||
linkArch := runtime.GOARCH
|
||||
linkExt := ".tar.gz"
|
||||
if linkOS == "windows" {
|
||||
// Windows uses .zip instead
|
||||
linkExt = ".zip"
|
||||
}
|
||||
|
||||
var allReleases = []Release{}
|
||||
|
||||
var releases Releases
|
||||
|
||||
if err := json.Unmarshal(body, &releases); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
archiveName := fmt.Sprintf("%s-%s-%s%s", name, linkOS, linkArch, linkExt)
|
||||
|
||||
// loop through releases
|
||||
for _, r := range releases {
|
||||
if !semver.IsValid(r.Tag) {
|
||||
// Invalid semversion, skip
|
||||
continue
|
||||
}
|
||||
|
||||
if !AllowPrereleases && (semver.Prerelease(r.Tag) != "" || r.Prerelease) {
|
||||
// we don't accept AllowPrereleases, skip
|
||||
continue
|
||||
}
|
||||
|
||||
for _, a := range r.Assets {
|
||||
if a.Name == archiveName {
|
||||
thisRelease := Release{a.Name, r.Tag, a.BrowserDownloadURL, a.Size}
|
||||
allReleases = append(allReleases, thisRelease)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(allReleases) == 0 {
|
||||
// no releases with suitable assets found
|
||||
return "", "", "", fmt.Errorf("No binary releases found")
|
||||
}
|
||||
|
||||
var latestRelease = Release{}
|
||||
|
||||
for _, r := range allReleases {
|
||||
// detect the latest release
|
||||
if semver.Compare(r.Tag, latestRelease.Tag) == 1 {
|
||||
latestRelease = r
|
||||
}
|
||||
}
|
||||
|
||||
return latestRelease.Tag, latestRelease.Name, latestRelease.URL, nil
|
||||
}
|
||||
|
||||
// GreaterThan compares the current version to a different version
|
||||
// returning < 1 not upgradeable
|
||||
func GreaterThan(toVer, fromVer string) bool {
|
||||
return semver.Compare(toVer, fromVer) == 1
|
||||
}
|
||||
|
||||
// GithubUpdate the running binary with the latest release binary from Github
|
||||
func GithubUpdate(repo, appName, currentVersion string) (string, error) {
|
||||
ver, filename, downloadURL, err := GithubLatest(repo, appName)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if ver == currentVersion {
|
||||
return "", fmt.Errorf("No new release found")
|
||||
}
|
||||
|
||||
if semver.Compare(ver, currentVersion) < 1 {
|
||||
return "", fmt.Errorf("No newer releases found (latest %s)", ver)
|
||||
}
|
||||
|
||||
tmpDir := getTempDir()
|
||||
|
||||
// outFile can be a tar.gz or a zip, depending on architecture
|
||||
outFile := filepath.Join(tmpDir, filename)
|
||||
|
||||
if err := downloadToFile(downloadURL, outFile); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newExec := filepath.Join(tmpDir, "mailpit")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if _, err := Unzip(outFile, tmpDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
newExec = filepath.Join(tmpDir, "mailpit.exe")
|
||||
} else {
|
||||
if err := TarGZExtract(outFile, tmpDir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
err := os.Chmod(newExec, 0755) // #nosec
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the new binary is executable (mainly for inconsistent darwin builds)
|
||||
/* #nosec G204 */
|
||||
cmd := exec.Command(newExec, "-h")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get the running binary
|
||||
oldExec, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = replaceFile(oldExec, newExec); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
// DownloadToFile downloads a URL to a file
|
||||
func downloadToFile(url, fileName string) error {
|
||||
// Get the data
|
||||
resp, err := http.Get(url) // #nosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create the file
|
||||
out, err := os.Create(filepath.Clean(fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := out.Close(); err != nil {
|
||||
logger.Log().Errorf("error closing file: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
// Write the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ReplaceFile replaces one file with another.
|
||||
// Running files cannot be overwritten, so it has to be moved
|
||||
// and the new binary saved to the original path. This requires
|
||||
// read & write permissions to both the original file and directory.
|
||||
// Note, on Windows it is not possible to delete a running program,
|
||||
// so the old exe is renamed and moved to os.TempDir()
|
||||
func replaceFile(dst, src string) error {
|
||||
// open the source file for reading
|
||||
source, err := os.Open(filepath.Clean(src))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// destination directory eg: /usr/local/bin
|
||||
dstDir := filepath.Dir(dst)
|
||||
// binary filename
|
||||
binaryFilename := filepath.Base(dst)
|
||||
// old binary tmp name
|
||||
dstOld := fmt.Sprintf("%s.old", binaryFilename)
|
||||
// new binary tmp name
|
||||
dstNew := fmt.Sprintf("%s.new", binaryFilename)
|
||||
// absolute path of new tmp file
|
||||
newTmpAbs := filepath.Join(dstDir, dstNew)
|
||||
// absolute path of old tmp file
|
||||
oldTmpAbs := filepath.Join(dstDir, dstOld)
|
||||
|
||||
// get src permissions
|
||||
fi, _ := os.Stat(dst)
|
||||
srcPerms := fi.Mode().Perm()
|
||||
|
||||
// create the new file
|
||||
tmpNew, err := os.OpenFile(filepath.Clean(newTmpAbs), os.O_CREATE|os.O_RDWR, srcPerms) // #nosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy new binary to <binary>.new
|
||||
if _, err := io.Copy(tmpNew, source); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// close immediately else Windows has a fit
|
||||
if err := tmpNew.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := source.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rename the current executable to <binary>.old
|
||||
if err := os.Rename(dst, oldTmpAbs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rename the <binary>.new to current executable
|
||||
if err := os.Rename(newTmpAbs, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete the old binary
|
||||
if runtime.GOOS == "windows" {
|
||||
tmpDir := os.TempDir()
|
||||
delFile := filepath.Join(tmpDir, filepath.Base(oldTmpAbs))
|
||||
if err := os.Rename(oldTmpAbs, delFile); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Remove(oldTmpAbs); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// remove the src file
|
||||
return os.Remove(src)
|
||||
}
|
||||
|
||||
// GetTempDir will create & return a temporary directory if one has not been specified
|
||||
func getTempDir() string {
|
||||
if tempDir == "" {
|
||||
randBytes := make([]byte, 6)
|
||||
if _, err := rand.Read(randBytes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tempDir = filepath.Join(os.TempDir(), "updater-"+hex.EncodeToString(randBytes))
|
||||
}
|
||||
if err := mkDirIfNotExists(tempDir); err != nil {
|
||||
// need a better way to exit
|
||||
logger.Log().Errorf("error: %s", err.Error())
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
return tempDir
|
||||
}
|
||||
|
||||
// MkDirIfNotExists will create a directory if it doesn't exist
|
||||
func mkDirIfNotExists(path string) error {
|
||||
if !isDir(path) {
|
||||
return os.MkdirAll(path, os.ModePerm) // #nosec
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDir returns if a path is a directory
|
||||
func isDir(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
if os.IsNotExist(err) || !info.IsDir() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
394
package-lock.json
generated
394
package-lock.json
generated
@@ -68,12 +68,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.27.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
|
||||
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
|
||||
"version": "7.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz",
|
||||
"integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.27.3"
|
||||
"@babel/types": "^7.27.7"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -95,9 +95,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
|
||||
"integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
|
||||
"version": "7.27.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz",
|
||||
"integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
@@ -108,9 +108,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.2.tgz",
|
||||
"integrity": "sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.6.0.tgz",
|
||||
"integrity": "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg==",
|
||||
"dev": true,
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)",
|
||||
"peer": true
|
||||
@@ -617,9 +617,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
|
||||
"integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
|
||||
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -632,9 +632,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz",
|
||||
"integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==",
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
|
||||
"integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -679,9 +679,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
|
||||
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz",
|
||||
"integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -702,13 +702,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz",
|
||||
"integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==",
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz",
|
||||
"integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.15.0",
|
||||
"@eslint/core": "^0.15.1",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -716,9 +716,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz",
|
||||
"integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==",
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
|
||||
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -1905,17 +1905,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
|
||||
"integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz",
|
||||
"integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/type-utils": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/type-utils": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -1929,7 +1929,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.34.1",
|
||||
"@typescript-eslint/parser": "^8.35.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <5.9.0"
|
||||
}
|
||||
@@ -1945,16 +1945,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz",
|
||||
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz",
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1970,14 +1970,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz",
|
||||
"integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz",
|
||||
"integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.34.1",
|
||||
"@typescript-eslint/types": "^8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.35.0",
|
||||
"@typescript-eslint/types": "^8.35.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1992,14 +1992,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz",
|
||||
"integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz",
|
||||
"integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1"
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2010,9 +2010,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz",
|
||||
"integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2027,14 +2027,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz",
|
||||
"integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz",
|
||||
"integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -2051,9 +2051,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz",
|
||||
"integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz",
|
||||
"integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2065,16 +2065,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz",
|
||||
"integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz",
|
||||
"integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.34.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/visitor-keys": "8.34.1",
|
||||
"@typescript-eslint/project-service": "8.35.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/visitor-keys": "8.35.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -2120,16 +2120,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz",
|
||||
"integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz",
|
||||
"integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.34.1",
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/typescript-estree": "8.34.1"
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"@typescript-eslint/typescript-estree": "8.35.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2144,13 +2144,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz",
|
||||
"integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz",
|
||||
"integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.34.1",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2162,9 +2162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-android-arm-eabi": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.1.tgz",
|
||||
"integrity": "sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz",
|
||||
"integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2176,9 +2176,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-android-arm64": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.1.tgz",
|
||||
"integrity": "sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz",
|
||||
"integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2190,9 +2190,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-darwin-arm64": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.1.tgz",
|
||||
"integrity": "sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz",
|
||||
"integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2204,9 +2204,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-darwin-x64": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.1.tgz",
|
||||
"integrity": "sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz",
|
||||
"integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2218,9 +2218,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-freebsd-x64": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.1.tgz",
|
||||
"integrity": "sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz",
|
||||
"integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2232,9 +2232,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.1.tgz",
|
||||
"integrity": "sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz",
|
||||
"integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2246,9 +2246,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.1.tgz",
|
||||
"integrity": "sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz",
|
||||
"integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2260,9 +2260,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.1.tgz",
|
||||
"integrity": "sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2274,9 +2274,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-arm64-musl": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.1.tgz",
|
||||
"integrity": "sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz",
|
||||
"integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2288,9 +2288,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.1.tgz",
|
||||
"integrity": "sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2302,9 +2302,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.1.tgz",
|
||||
"integrity": "sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2316,9 +2316,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.1.tgz",
|
||||
"integrity": "sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz",
|
||||
"integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2330,9 +2330,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.1.tgz",
|
||||
"integrity": "sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2344,9 +2344,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-x64-gnu": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.1.tgz",
|
||||
"integrity": "sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2358,9 +2358,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-linux-x64-musl": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.1.tgz",
|
||||
"integrity": "sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz",
|
||||
"integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2372,9 +2372,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-wasm32-wasi": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.1.tgz",
|
||||
"integrity": "sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz",
|
||||
"integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -2389,9 +2389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.1.tgz",
|
||||
"integrity": "sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz",
|
||||
"integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2403,9 +2403,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.1.tgz",
|
||||
"integrity": "sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz",
|
||||
"integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2417,9 +2417,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@unrs/resolver-binding-win32-x64-msvc": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.1.tgz",
|
||||
"integrity": "sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz",
|
||||
"integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3330,9 +3330,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
|
||||
"version": "5.18.2",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
|
||||
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3613,19 +3613,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.29.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
|
||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"version": "9.30.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz",
|
||||
"integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.20.1",
|
||||
"@eslint/config-helpers": "^0.2.1",
|
||||
"@eslint/config-array": "^0.21.0",
|
||||
"@eslint/config-helpers": "^0.3.0",
|
||||
"@eslint/core": "^0.14.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.29.0",
|
||||
"@eslint/js": "9.30.0",
|
||||
"@eslint/plugin-kit": "^0.3.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
@@ -3706,14 +3706,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-context": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz",
|
||||
"integrity": "sha512-bq+F7nyc65sKpZGT09dY0S0QrOnQtuDVIfyTGQ8uuvtMIF7oHp6CEP3mouN0rrnYF3Jqo6Ke0BfU/5wASZue1w==",
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz",
|
||||
"integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-tsconfig": "^4.10.1",
|
||||
"stable-hash-x": "^0.1.1"
|
||||
"stable-hash-x": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
@@ -3788,21 +3788,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x": {
|
||||
"version": "4.15.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.15.2.tgz",
|
||||
"integrity": "sha512-J5gx7sN6DTm0LRT//eP3rVVQ2Yi4hrX0B+DbWxa5er8PZ6JjLo9GUBwogIFvEDdwJaSqZplpQT+haK/cXhb7VQ==",
|
||||
"version": "4.16.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz",
|
||||
"integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "^8.34.0",
|
||||
"@typescript-eslint/types": "^8.35.0",
|
||||
"comment-parser": "^1.4.1",
|
||||
"debug": "^4.4.1",
|
||||
"eslint-import-context": "^0.1.8",
|
||||
"eslint-import-context": "^0.1.9",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.3 || ^10.0.1",
|
||||
"semver": "^7.7.2",
|
||||
"stable-hash-x": "^0.1.1",
|
||||
"unrs-resolver": "^1.9.0"
|
||||
"stable-hash-x": "^0.2.0",
|
||||
"unrs-resolver": "^1.9.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -5894,9 +5894,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -6822,9 +6822,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/stable-hash-x": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.1.1.tgz",
|
||||
"integrity": "sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==",
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz",
|
||||
"integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6983,9 +6983,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/swagger-client": {
|
||||
"version": "3.35.5",
|
||||
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.35.5.tgz",
|
||||
"integrity": "sha512-ayCrpDAgm5jIdq1kmcVWJRfp27cqU9tSRiAfKg3BKeplOmvu3+lKTPPtz4x1uI8v5l5/92Aopvq0EzRkXEr7Rw==",
|
||||
"version": "3.35.6",
|
||||
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.35.6.tgz",
|
||||
"integrity": "sha512-OgwNneIdC45KXwOfwrlkwgWPeAKiV4K75mOnZioTddo1mpp9dTboCDVJas7185Ww1ziBwzShBqXpNGmyha9ZQg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime-corejs3": "^7.22.15",
|
||||
@@ -7302,15 +7302,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.34.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz",
|
||||
"integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz",
|
||||
"integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.34.1",
|
||||
"@typescript-eslint/parser": "8.34.1",
|
||||
"@typescript-eslint/utils": "8.34.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.35.0",
|
||||
"@typescript-eslint/parser": "8.35.0",
|
||||
"@typescript-eslint/utils": "8.35.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -7350,38 +7350,38 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unrs-resolver": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz",
|
||||
"integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==",
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz",
|
||||
"integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"napi-postinstall": "^0.2.2"
|
||||
"napi-postinstall": "^0.2.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unrs-resolver"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@unrs/resolver-binding-android-arm-eabi": "1.9.1",
|
||||
"@unrs/resolver-binding-android-arm64": "1.9.1",
|
||||
"@unrs/resolver-binding-darwin-arm64": "1.9.1",
|
||||
"@unrs/resolver-binding-darwin-x64": "1.9.1",
|
||||
"@unrs/resolver-binding-freebsd-x64": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-arm-musleabihf": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-arm64-gnu": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-arm64-musl": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-ppc64-gnu": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-riscv64-gnu": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-riscv64-musl": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-s390x-gnu": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-x64-gnu": "1.9.1",
|
||||
"@unrs/resolver-binding-linux-x64-musl": "1.9.1",
|
||||
"@unrs/resolver-binding-wasm32-wasi": "1.9.1",
|
||||
"@unrs/resolver-binding-win32-arm64-msvc": "1.9.1",
|
||||
"@unrs/resolver-binding-win32-ia32-msvc": "1.9.1",
|
||||
"@unrs/resolver-binding-win32-x64-msvc": "1.9.1"
|
||||
"@unrs/resolver-binding-android-arm-eabi": "1.9.2",
|
||||
"@unrs/resolver-binding-android-arm64": "1.9.2",
|
||||
"@unrs/resolver-binding-darwin-arm64": "1.9.2",
|
||||
"@unrs/resolver-binding-darwin-x64": "1.9.2",
|
||||
"@unrs/resolver-binding-freebsd-x64": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-arm64-gnu": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-arm64-musl": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-riscv64-musl": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-s390x-gnu": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-x64-gnu": "1.9.2",
|
||||
"@unrs/resolver-binding-linux-x64-musl": "1.9.2",
|
||||
"@unrs/resolver-binding-wasm32-wasi": "1.9.2",
|
||||
"@unrs/resolver-binding-win32-arm64-msvc": "1.9.2",
|
||||
"@unrs/resolver-binding-win32-ia32-msvc": "1.9.2",
|
||||
"@unrs/resolver-binding-win32-x64-msvc": "1.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
@@ -7440,9 +7440,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-eslint-parser": {
|
||||
"version": "10.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz",
|
||||
"integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==",
|
||||
"version": "10.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.4.tgz",
|
||||
"integrity": "sha512-EIZvCukIEMHEb3mxOKemtvWR1fcUAdWWAgkfyjmRHzvyhrZvBvH9oz69+thDIWhGiIQjZnPkCn8yHqvjM+a9eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
||||
@@ -121,14 +121,14 @@ func Run() {
|
||||
// handles `sendmail -bs`
|
||||
// telnet directly to SMTP
|
||||
if UseB && UseS {
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
if isSocket {
|
||||
var caller = telnet.StandardCaller
|
||||
switch isSocket {
|
||||
case true:
|
||||
if err := telnet.DialToAndCallUnix(socketAddr, caller); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
if err := telnet.DialToAndCall(SMTPAddr, caller); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -18,7 +18,7 @@ func fourOFour(w http.ResponseWriter) {
|
||||
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
fmt.Fprint(w, "404 page not found")
|
||||
_, _ = fmt.Fprint(w, "404 page not found")
|
||||
}
|
||||
|
||||
// HTTPError returns a basic error message (400 response)
|
||||
@@ -27,7 +27,7 @@ func httpError(w http.ResponseWriter, msg string) {
|
||||
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
fmt.Fprint(w, msg)
|
||||
_, _ = fmt.Fprint(w, msg)
|
||||
}
|
||||
|
||||
// httpJSONError returns a basic error message (400 response) in JSON format
|
||||
@@ -35,9 +35,7 @@ func httpJSONError(w http.ResponseWriter, msg string) {
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
e := JSONErrorMessage{
|
||||
Error: msg,
|
||||
}
|
||||
e := struct{ Error string }{Error: msg}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(e); err != nil {
|
||||
|
||||
@@ -10,16 +10,7 @@ import (
|
||||
"github.com/axllent/mailpit/internal/stats"
|
||||
)
|
||||
|
||||
// Application information
|
||||
// swagger:response AppInfoResponse
|
||||
type appInfoResponse struct {
|
||||
// Application information
|
||||
//
|
||||
// in: body
|
||||
Body stats.AppInformation
|
||||
}
|
||||
|
||||
// AppInfo returns some basic details about the running app, and latest release.
|
||||
// AppInfo returns some basic details about the running app including the latest release (unless disabled).
|
||||
func AppInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
// swagger:route GET /api/v1/info application AppInformation
|
||||
//
|
||||
@@ -42,59 +33,9 @@ func AppInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Response includes global web UI settings
|
||||
//
|
||||
// swagger:model WebUIConfiguration
|
||||
type webUIConfiguration struct {
|
||||
// Optional label to identify this Mailpit instance
|
||||
Label string
|
||||
// Message Relay information
|
||||
MessageRelay struct {
|
||||
// Whether message relaying (release) is enabled
|
||||
Enabled bool
|
||||
// The configured SMTP server address
|
||||
SMTPServer string
|
||||
// Enforced Return-Path (if set) for relay bounces
|
||||
ReturnPath string
|
||||
// Only allow relaying to these recipients (regex)
|
||||
AllowedRecipients string
|
||||
// Block relaying to these recipients (regex)
|
||||
BlockedRecipients string
|
||||
// Overrides the "From" address for all relayed messages
|
||||
OverrideFrom string
|
||||
// Preserve the original Message-IDs when relaying messages
|
||||
PreserveMessageIDs bool
|
||||
|
||||
// DEPRECATED 2024/03/12
|
||||
// swagger:ignore
|
||||
RecipientAllowlist string
|
||||
}
|
||||
|
||||
// Whether SpamAssassin is enabled
|
||||
SpamAssassin bool
|
||||
|
||||
// Whether Chaos support is enabled at runtime
|
||||
ChaosEnabled bool
|
||||
|
||||
// Whether messages with duplicate IDs are ignored
|
||||
DuplicatesIgnored bool
|
||||
|
||||
// Whether the delete button should be hidden
|
||||
HideDeleteAllButton bool
|
||||
}
|
||||
|
||||
// Web UI configuration response
|
||||
// swagger:response WebUIConfigurationResponse
|
||||
type webUIConfigurationResponse struct {
|
||||
// Web UI configuration settings
|
||||
//
|
||||
// in: body
|
||||
Body webUIConfiguration
|
||||
}
|
||||
|
||||
// WebUIConfig returns configuration settings for the web UI.
|
||||
func WebUIConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
// swagger:route GET /api/v1/webui application WebUIConfiguration
|
||||
// swagger:route GET /api/v1/webui application WebUIConfigurationResponse
|
||||
//
|
||||
// # Get web UI configuration
|
||||
//
|
||||
@@ -110,29 +51,29 @@ func WebUIConfig(w http.ResponseWriter, _ *http.Request) {
|
||||
// 200: WebUIConfigurationResponse
|
||||
// 400: ErrorResponse
|
||||
|
||||
conf := webUIConfiguration{}
|
||||
conf := webUIConfigurationResponse{}
|
||||
|
||||
conf.Label = config.Label
|
||||
conf.MessageRelay.Enabled = config.ReleaseEnabled
|
||||
conf.Body.Label = config.Label
|
||||
conf.Body.MessageRelay.Enabled = config.ReleaseEnabled
|
||||
if config.ReleaseEnabled {
|
||||
conf.MessageRelay.SMTPServer = fmt.Sprintf("%s:%d", config.SMTPRelayConfig.Host, config.SMTPRelayConfig.Port)
|
||||
conf.MessageRelay.ReturnPath = config.SMTPRelayConfig.ReturnPath
|
||||
conf.MessageRelay.AllowedRecipients = config.SMTPRelayConfig.AllowedRecipients
|
||||
conf.MessageRelay.BlockedRecipients = config.SMTPRelayConfig.BlockedRecipients
|
||||
conf.MessageRelay.OverrideFrom = config.SMTPRelayConfig.OverrideFrom
|
||||
conf.MessageRelay.PreserveMessageIDs = config.SMTPRelayConfig.PreserveMessageIDs
|
||||
conf.Body.MessageRelay.SMTPServer = fmt.Sprintf("%s:%d", config.SMTPRelayConfig.Host, config.SMTPRelayConfig.Port)
|
||||
conf.Body.MessageRelay.ReturnPath = config.SMTPRelayConfig.ReturnPath
|
||||
conf.Body.MessageRelay.AllowedRecipients = config.SMTPRelayConfig.AllowedRecipients
|
||||
conf.Body.MessageRelay.BlockedRecipients = config.SMTPRelayConfig.BlockedRecipients
|
||||
conf.Body.MessageRelay.OverrideFrom = config.SMTPRelayConfig.OverrideFrom
|
||||
conf.Body.MessageRelay.PreserveMessageIDs = config.SMTPRelayConfig.PreserveMessageIDs
|
||||
|
||||
// DEPRECATED 2024/03/12
|
||||
conf.MessageRelay.RecipientAllowlist = config.SMTPRelayConfig.AllowedRecipients
|
||||
conf.Body.MessageRelay.RecipientAllowlist = config.SMTPRelayConfig.AllowedRecipients
|
||||
}
|
||||
|
||||
conf.SpamAssassin = config.EnableSpamAssassin != ""
|
||||
conf.ChaosEnabled = chaos.Enabled
|
||||
conf.DuplicatesIgnored = config.IgnoreDuplicateIDs
|
||||
conf.HideDeleteAllButton = config.HideDeleteAllButton
|
||||
conf.Body.SpamAssassin = config.EnableSpamAssassin != ""
|
||||
conf.Body.ChaosEnabled = chaos.Enabled
|
||||
conf.Body.DuplicatesIgnored = config.IgnoreDuplicateIDs
|
||||
conf.Body.HideDeleteAllButton = config.HideDeleteAllButton
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(conf); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(conf.Body); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,6 @@ import (
|
||||
"github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
)
|
||||
|
||||
// ChaosTriggers are the Chaos triggers
|
||||
type ChaosTriggers chaos.Triggers
|
||||
|
||||
// Response for the Chaos triggers configuration
|
||||
// swagger:response ChaosResponse
|
||||
type chaosResponse struct {
|
||||
// The current Chaos triggers
|
||||
//
|
||||
// in: body
|
||||
Body ChaosTriggers
|
||||
}
|
||||
|
||||
// GetChaos returns the current Chaos triggers
|
||||
func GetChaos(w http.ResponseWriter, _ *http.Request) {
|
||||
// swagger:route GET /api/v1/chaos testing getChaos
|
||||
@@ -50,12 +38,6 @@ func GetChaos(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters setChaosParams
|
||||
type setChaosParams struct {
|
||||
// in: body
|
||||
Body ChaosTriggers
|
||||
}
|
||||
|
||||
// SetChaos sets the Chaos configuration.
|
||||
func SetChaos(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/chaos testing setChaosParams
|
||||
|
||||
@@ -11,15 +11,6 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// swagger:parameters GetMessageParams
|
||||
type getMessageParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetMessage (method: GET) returns the Message as JSON
|
||||
func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID} message GetMessageParams
|
||||
@@ -49,7 +40,7 @@ func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -66,19 +57,6 @@ func GetMessage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters GetHeadersParams
|
||||
type getHeadersParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// Message headers
|
||||
// swagger:model MessageHeadersResponse
|
||||
type messageHeaders map[string][]string
|
||||
|
||||
// GetHeaders (method: GET) returns the message headers as JSON
|
||||
func GetHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/headers message GetHeadersParams
|
||||
@@ -108,7 +86,7 @@ func GetHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -132,21 +110,6 @@ func GetHeaders(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters AttachmentParams
|
||||
type attachmentParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Attachment part ID
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
PartID string
|
||||
}
|
||||
|
||||
// DownloadAttachment (method: GET) returns the attachment data
|
||||
func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/part/{PartID} message AttachmentParams
|
||||
@@ -179,7 +142,7 @@ func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -199,15 +162,6 @@ func DownloadAttachment(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write(a.Content)
|
||||
}
|
||||
|
||||
// swagger:parameters DownloadRawParams
|
||||
type downloadRawParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// DownloadRaw (method: GET) returns the full email source as plain text
|
||||
func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/raw message DownloadRawParams
|
||||
@@ -238,7 +192,7 @@ func DownloadRaw(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,35 +8,6 @@ import (
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
)
|
||||
|
||||
// swagger:parameters GetMessagesParams
|
||||
type getMessagesParams struct {
|
||||
// Pagination offset
|
||||
//
|
||||
// in: query
|
||||
// name: start
|
||||
// required: false
|
||||
// default: 0
|
||||
// type: integer
|
||||
Start int `json:"start"`
|
||||
|
||||
// Limit number of results
|
||||
//
|
||||
// in: query
|
||||
// name: limit
|
||||
// required: false
|
||||
// default: 50
|
||||
// type: integer
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
// Summary of messages
|
||||
// swagger:response MessagesSummaryResponse
|
||||
type messagesSummaryResponse struct {
|
||||
// The messages summary
|
||||
// in: body
|
||||
Body MessagesSummary
|
||||
}
|
||||
|
||||
// MessagesSummary is a summary of a list of messages
|
||||
type MessagesSummary struct {
|
||||
// Total number of messages in mailbox
|
||||
@@ -111,39 +82,6 @@ func GetMessages(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SetReadStatusParams
|
||||
type setReadStatusParams struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
// Read status
|
||||
//
|
||||
// required: false
|
||||
// default: false
|
||||
// example: true
|
||||
Read bool
|
||||
|
||||
// Optional array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// default: []
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
|
||||
// Optional messages matching a search
|
||||
//
|
||||
// required: false
|
||||
// example: tag:backups
|
||||
Search string
|
||||
}
|
||||
|
||||
// Optional [timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// SetReadStatus (method: PUT) will update the status to Read/Unread for all provided IDs.
|
||||
func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/messages messages SetReadStatusParams
|
||||
@@ -225,19 +163,6 @@ func SetReadStatus(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteMessagesParams
|
||||
type deleteMessagesParams struct {
|
||||
// Delete request
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteMessages (method: DELETE) deletes all messages matching IDS.
|
||||
func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/messages messages DeleteMessagesParams
|
||||
@@ -279,39 +204,6 @@ func DeleteMessages(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters SearchParams
|
||||
type searchParams struct {
|
||||
// Search query
|
||||
//
|
||||
// in: query
|
||||
// required: true
|
||||
// type: string
|
||||
Query string `json:"query"`
|
||||
|
||||
// Pagination offset
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: 0
|
||||
// type integer
|
||||
Start string `json:"start"`
|
||||
|
||||
// Limit results
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: 50
|
||||
// type integer
|
||||
Limit string `json:"limit"`
|
||||
|
||||
// Optional [timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// Search returns the latest messages as JSON
|
||||
func Search(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/search messages SearchParams
|
||||
@@ -369,23 +261,6 @@ func Search(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteSearchParams
|
||||
type deleteSearchParams struct {
|
||||
// Search query
|
||||
//
|
||||
// in: query
|
||||
// required: true
|
||||
// type: string
|
||||
Query string `json:"query"`
|
||||
|
||||
// [Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// DeleteSearch will delete all messages matching a search
|
||||
func DeleteSearch(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/search messages DeleteSearchParams
|
||||
|
||||
@@ -15,19 +15,6 @@ import (
|
||||
"github.com/jhillyerd/enmime/v2"
|
||||
)
|
||||
|
||||
// swagger:parameters HTMLCheckParams
|
||||
type htmlCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// HTMLCheckResponse summary response
|
||||
type HTMLCheckResponse = htmlcheck.Response
|
||||
|
||||
// HTMLCheck returns a summary of the HTML client support
|
||||
func HTMLCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/html-check other HTMLCheckParams
|
||||
@@ -93,25 +80,6 @@ func HTMLCheck(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters LinkCheckParams
|
||||
type linkCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Follow redirects
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: false
|
||||
Follow string `json:"follow"`
|
||||
}
|
||||
|
||||
// LinkCheckResponse summary response
|
||||
type LinkCheckResponse = linkcheck.Response
|
||||
|
||||
// LinkCheck returns a summary of links in the email
|
||||
func LinkCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/link-check other LinkCheckParams
|
||||
@@ -170,18 +138,6 @@ func LinkCheck(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SpamAssassinCheckParams
|
||||
type spamAssassinCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// SpamAssassinResponse summary response
|
||||
type SpamAssassinResponse = spamassassin.Result
|
||||
|
||||
// SpamAssassinCheck returns a summary of SpamAssassin results (if enabled)
|
||||
func SpamAssassinCheck(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/sa-check other SpamAssassinCheckParams
|
||||
@@ -210,7 +166,7 @@ func SpamAssassinCheck(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,25 +17,6 @@ import (
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
// swagger:parameters ReleaseMessageParams
|
||||
type releaseMessageParams struct {
|
||||
// Message database ID
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of email addresses to relay the message to
|
||||
//
|
||||
// required: true
|
||||
// example: ["user1@example.com", "user2@example.com"]
|
||||
To []string
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseMessage (method: POST) will release a message via a pre-configured external SMTP server.
|
||||
func ReleaseMessage(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route POST /api/v1/message/{ID}/release message ReleaseMessageParams
|
||||
|
||||
@@ -17,130 +17,6 @@ import (
|
||||
"github.com/jhillyerd/enmime/v2"
|
||||
)
|
||||
|
||||
// swagger:parameters SendMessageParams
|
||||
type sendMessageParams struct {
|
||||
// in: body
|
||||
Body *SendRequest
|
||||
}
|
||||
|
||||
// SendRequest to send a message via HTTP
|
||||
// swagger:model SendRequest
|
||||
type SendRequest struct {
|
||||
// "From" recipient
|
||||
// required: true
|
||||
From struct {
|
||||
// Optional name
|
||||
// example: John Doe
|
||||
Name string
|
||||
// Email address
|
||||
// example: john@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// "To" recipients
|
||||
To []struct {
|
||||
// Optional name
|
||||
// example: Jane Doe
|
||||
Name string
|
||||
// Email address
|
||||
// example: jane@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Cc recipients
|
||||
Cc []struct {
|
||||
// Optional name
|
||||
// example: Manager
|
||||
Name string
|
||||
// Email address
|
||||
// example: manager@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Bcc recipients email addresses only
|
||||
// example: ["jack@example.com"]
|
||||
Bcc []string
|
||||
|
||||
// Optional Reply-To recipients
|
||||
ReplyTo []struct {
|
||||
// Optional name
|
||||
// example: Secretary
|
||||
Name string
|
||||
// Email address
|
||||
// example: secretary@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Subject
|
||||
// example: Mailpit message via the HTTP API
|
||||
Subject string
|
||||
|
||||
// Message body (text)
|
||||
// example: Mailpit is awesome!
|
||||
Text string
|
||||
|
||||
// Message body (HTML)
|
||||
// example: <div style="text-align:center"><p style="font-family: arial; font-size: 24px;">Mailpit is <b>awesome</b>!</p><p><img src="cid:mailpit-logo" /></p></div>
|
||||
HTML string
|
||||
|
||||
// Attachments
|
||||
Attachments []struct {
|
||||
// Base64-encoded string of the file content
|
||||
// required: true
|
||||
// example: iVBORw0KGgoAAAANSUhEUgAAAEEAAAA8CAMAAAAOlSdoAAAACXBIWXMAAAHrAAAB6wGM2bZBAAAAS1BMVEVHcEwRfnUkZ2gAt4UsSF8At4UtSV4At4YsSV4At4YsSV8At4YsSV4At4YsSV4sSV4At4YsSV4At4YtSV4At4YsSV4At4YtSV8At4YsUWYNAAAAGHRSTlMAAwoXGiktRE5dbnd7kpOlr7zJ0d3h8PD8PCSRAAACWUlEQVR42pXT4ZaqIBSG4W9rhqQYocG+/ys9Y0Z0Br+x3j8zaxUPewFh65K+7yrIMeIY4MT3wPfEJCidKXEMnLaVkxDiELiMz4WEOAZSFghxBIypCOlKiAMgXfIqTnBgSm8CIQ6BImxEUxEckClVQiHGj4Ba4AQHikAIClwTE9KtIghAhUJwoLkmLnCiAHJLRKgIMsEtVUKbBUIwoAg2C4QgQBE6l4VCnApBgSKYLLApCnCa0+96AEMW2BQcmC+Pr3nfp7o5Exy49gIADcIqUELGfeA+bp93LmAJp8QJoEcN3C7NY3sbVANixMyI0nku20/n5/ZRf3KI2k6JEDWQtxcbdGuAqu3TAXG+/799Oyyas1B1MnMiA+XyxHp9q0PUKGPiRAau1fZbLRZV09wZcT8/gHk8QQAxXn8VgaDqcUmU6O/r28nbVwXAqca2mRNtPAF5+zoP2MeN9Fy4NgC6RfcbgE7XITBRYTtOE3U3C2DVff7pk+PkUxgAbvtnPXJaD6DxulMLwOhPS/M3MQkgg1ZFrIXnmfaZoOfpKiFgzeZD/WuKqQEGrfJYkyWf6vlG3xUgTuscnkNkQsb599q124kdpMUjCa/XARHs1gZymVtGt3wLkiFv8rUgTxitYCex5EVGec0Y9VmoDTFBSQte2TfXGXlf7hbdaUM9Sk7fisEN9qfBBTK+FZcvM9fQSdkl2vj4W2oX/bRogO3XasiNH7R0eW7fgRM834ImTg+Lg6BEnx4vz81rhr+MYPBBQg1v8GndEOrthxaCTxNAOut8WKLGZQl+MPz88Q9tAO/hVuSeqQAAAABJRU5ErkJggg==
|
||||
Content string
|
||||
// Filename
|
||||
// required: true
|
||||
// example: mailpit.png
|
||||
Filename string
|
||||
// Optional Content Type for the the attachment.
|
||||
// If this field is not set (or empty) then the content type is automatically detected.
|
||||
// required: false
|
||||
// example: image/png
|
||||
ContentType string
|
||||
// Optional Content-ID (`cid`) for attachment.
|
||||
// If this field is set then the file is attached inline.
|
||||
// required: false
|
||||
// example: mailpit-logo
|
||||
ContentID string
|
||||
}
|
||||
|
||||
// Mailpit tags
|
||||
// example: ["Tag 1","Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Optional headers in {"key":"value"} format
|
||||
// example: {"X-IP":"1.2.3.4"}
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
// JSONErrorMessage struct
|
||||
type JSONErrorMessage struct {
|
||||
// Error message
|
||||
// example: invalid format
|
||||
Error string
|
||||
}
|
||||
|
||||
// Confirmation message for HTTP send API
|
||||
// swagger:response sendMessageResponse
|
||||
type sendMessageResponse struct {
|
||||
// Response for sending messages via the HTTP API
|
||||
//
|
||||
// in: body
|
||||
Body SendMessageConfirmation
|
||||
}
|
||||
|
||||
// SendMessageConfirmation struct
|
||||
type SendMessageConfirmation struct {
|
||||
// Database ID
|
||||
// example: iAfZVVe2UQfNSG5BAjgYwa
|
||||
ID string
|
||||
}
|
||||
|
||||
// SendMessageHandler handles HTTP requests to send a new message
|
||||
func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route POST /api/v1/send message SendMessageParams
|
||||
@@ -158,8 +34,8 @@ func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: sendMessageResponse
|
||||
// 400: jsonErrorResponse
|
||||
// 200: SendMessageResponse
|
||||
// 400: JSONErrorResponse
|
||||
|
||||
if config.DemoMode {
|
||||
httpJSONError(w, "this functionality has been disabled for demonstration purposes")
|
||||
@@ -168,9 +44,9 @@ func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
|
||||
data := SendRequest{}
|
||||
data := sendMessageParams{}
|
||||
|
||||
if err := decoder.Decode(&data); err != nil {
|
||||
if err := decoder.Decode(&data.Body); err != nil {
|
||||
httpJSONError(w, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -188,14 +64,14 @@ func SendMessageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(SendMessageConfirmation{ID: id}); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(struct{ ID string }{ID: id}); err != nil {
|
||||
httpError(w, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Send will validate the message structure and attempt to send to Mailpit.
|
||||
// It returns a sending summary or an error.
|
||||
func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, error) {
|
||||
func (d sendMessageParams) Send(remoteAddr string, httpAuthUser *string) (string, error) {
|
||||
ip, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing request RemoteAddr: %s", err.Error())
|
||||
@@ -206,16 +82,16 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
addresses := []string{}
|
||||
|
||||
msg := enmime.Builder().
|
||||
From(d.From.Name, d.From.Email).
|
||||
Subject(d.Subject).
|
||||
Text([]byte(d.Text))
|
||||
From(d.Body.From.Name, d.Body.From.Email).
|
||||
Subject(d.Body.Subject).
|
||||
Text([]byte(d.Body.Text))
|
||||
|
||||
if d.HTML != "" {
|
||||
msg = msg.HTML([]byte(d.HTML))
|
||||
if d.Body.HTML != "" {
|
||||
msg = msg.HTML([]byte(d.Body.HTML))
|
||||
}
|
||||
|
||||
if len(d.To) > 0 {
|
||||
for _, a := range d.To {
|
||||
if len(d.Body.To) > 0 {
|
||||
for _, a := range d.Body.To {
|
||||
if _, err := mail.ParseAddress(a.Email); err == nil {
|
||||
msg = msg.To(a.Name, a.Email)
|
||||
addresses = append(addresses, a.Email)
|
||||
@@ -225,8 +101,8 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Cc) > 0 {
|
||||
for _, a := range d.Cc {
|
||||
if len(d.Body.Cc) > 0 {
|
||||
for _, a := range d.Body.Cc {
|
||||
if _, err := mail.ParseAddress(a.Email); err == nil {
|
||||
msg = msg.CC(a.Name, a.Email)
|
||||
addresses = append(addresses, a.Email)
|
||||
@@ -236,8 +112,8 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Bcc) > 0 {
|
||||
for _, e := range d.Bcc {
|
||||
if len(d.Body.Bcc) > 0 {
|
||||
for _, e := range d.Body.Bcc {
|
||||
if _, err := mail.ParseAddress(e); err == nil {
|
||||
msg = msg.BCC("", e)
|
||||
addresses = append(addresses, e)
|
||||
@@ -247,8 +123,8 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.ReplyTo) > 0 {
|
||||
for _, a := range d.ReplyTo {
|
||||
if len(d.Body.ReplyTo) > 0 {
|
||||
for _, a := range d.Body.ReplyTo {
|
||||
if _, err := mail.ParseAddress(a.Email); err == nil {
|
||||
msg = msg.ReplyTo(a.Name, a.Email)
|
||||
} else {
|
||||
@@ -259,13 +135,13 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
|
||||
restrictedHeaders := []string{"To", "From", "Cc", "Bcc", "Reply-To", "Date", "Subject", "Content-Type", "Mime-Version"}
|
||||
|
||||
if len(d.Tags) > 0 {
|
||||
msg = msg.Header("X-Tags", strings.Join(d.Tags, ", "))
|
||||
if len(d.Body.Tags) > 0 {
|
||||
msg = msg.Header("X-Tags", strings.Join(d.Body.Tags, ", "))
|
||||
restrictedHeaders = append(restrictedHeaders, "X-Tags")
|
||||
}
|
||||
|
||||
if len(d.Headers) > 0 {
|
||||
for k, v := range d.Headers {
|
||||
if len(d.Body.Headers) > 0 {
|
||||
for k, v := range d.Body.Headers {
|
||||
// check header isn't in "restricted" headers
|
||||
if tools.InArray(k, restrictedHeaders) {
|
||||
return "", fmt.Errorf("cannot overwrite header: \"%s\"", k)
|
||||
@@ -274,8 +150,8 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
}
|
||||
}
|
||||
|
||||
if len(d.Attachments) > 0 {
|
||||
for _, a := range d.Attachments {
|
||||
if len(d.Body.Attachments) > 0 {
|
||||
for _, a := range d.Body.Attachments {
|
||||
// workaround: split string because JS readAsDataURL() returns the base64 string
|
||||
// with the mime type prefix eg: data:image/png;base64,<base64String>
|
||||
parts := strings.Split(a.Content, ",")
|
||||
@@ -307,5 +183,5 @@ func (d SendRequest) Send(remoteAddr string, httpAuthUser *string) (string, erro
|
||||
return "", fmt.Errorf("error building message: %s", err.Error())
|
||||
}
|
||||
|
||||
return smtpd.SaveToDatabase(ipAddr, d.From.Email, addresses, buff.Bytes(), httpAuthUser)
|
||||
return smtpd.SaveToDatabase(ipAddr, d.Body.From.Email, addresses, buff.Bytes(), httpAuthUser)
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package apiv1
|
||||
|
||||
// These structs are for the purpose of defining swagger HTTP parameters & responses
|
||||
|
||||
// Binary data response which inherits the attachment's content type.
|
||||
// swagger:response BinaryResponse
|
||||
type binaryResponse string
|
||||
|
||||
// Plain text response
|
||||
// swagger:response TextResponse
|
||||
type textResponse string
|
||||
|
||||
// HTML response
|
||||
// swagger:response HTMLResponse
|
||||
type htmlResponse string
|
||||
|
||||
// Server error will return with a 400 status code
|
||||
// with the error message in the body
|
||||
// swagger:response ErrorResponse
|
||||
type errorResponse string
|
||||
|
||||
// Not found error will return a 404 status code
|
||||
// swagger:response NotFoundResponse
|
||||
type notFoundResponse string
|
||||
|
||||
// Plain text "ok" response
|
||||
// swagger:response OKResponse
|
||||
type okResponse string
|
||||
|
||||
// Plain JSON array response
|
||||
// swagger:response ArrayResponse
|
||||
type arrayResponse []string
|
||||
|
||||
// JSON error response
|
||||
// swagger:response jsonErrorResponse
|
||||
type jsonErrorResponse struct {
|
||||
// A JSON-encoded error response
|
||||
//
|
||||
// in: body
|
||||
Body JSONErrorMessage
|
||||
}
|
||||
416
server/apiv1/swaggerParams.go
Normal file
416
server/apiv1/swaggerParams.go
Normal file
@@ -0,0 +1,416 @@
|
||||
// Package apiv1 provides the API v1 endpoints for Mailpit.
|
||||
//
|
||||
// These structs are for the purpose of defining swagger HTTP parameters in go-swagger
|
||||
// in order to generate a spec file. They are lowercased to avoid exporting them as public types.
|
||||
//
|
||||
//nolint:unused
|
||||
package apiv1
|
||||
|
||||
import "github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
|
||||
// swagger:parameters setChaosParams
|
||||
type setChaosParams struct {
|
||||
// in: body
|
||||
Body chaos.Triggers
|
||||
}
|
||||
|
||||
// swagger:parameters AttachmentParams
|
||||
type attachmentParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Attachment part ID
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
PartID string
|
||||
}
|
||||
|
||||
// swagger:parameters DownloadRawParams
|
||||
type downloadRawParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters GetMessageParams
|
||||
type getMessageParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters GetHeadersParams
|
||||
type getHeadersParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters GetMessagesParams
|
||||
type getMessagesParams struct {
|
||||
// Pagination offset
|
||||
//
|
||||
// in: query
|
||||
// name: start
|
||||
// required: false
|
||||
// default: 0
|
||||
// type: integer
|
||||
Start int `json:"start"`
|
||||
|
||||
// Limit number of results
|
||||
//
|
||||
// in: query
|
||||
// name: limit
|
||||
// required: false
|
||||
// default: 50
|
||||
// type: integer
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
|
||||
// swagger:parameters SetReadStatusParams
|
||||
type setReadStatusParams struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
// Read status
|
||||
//
|
||||
// required: false
|
||||
// default: false
|
||||
// example: true
|
||||
Read bool
|
||||
|
||||
// Optional array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// default: []
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
|
||||
// Optional messages matching a search
|
||||
//
|
||||
// required: false
|
||||
// example: tag:backups
|
||||
Search string
|
||||
}
|
||||
|
||||
// Optional [timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteMessagesParams
|
||||
type deleteMessagesParams struct {
|
||||
// Delete request
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: false
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SearchParams
|
||||
type searchParams struct {
|
||||
// Search query
|
||||
//
|
||||
// in: query
|
||||
// required: true
|
||||
// type: string
|
||||
Query string `json:"query"`
|
||||
|
||||
// Pagination offset
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: 0
|
||||
// type integer
|
||||
Start string `json:"start"`
|
||||
|
||||
// Limit results
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: 50
|
||||
// type integer
|
||||
Limit string `json:"limit"`
|
||||
|
||||
// Optional [timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteSearchParams
|
||||
type deleteSearchParams struct {
|
||||
// Search query
|
||||
//
|
||||
// in: query
|
||||
// required: true
|
||||
// type: string
|
||||
Query string `json:"query"`
|
||||
|
||||
// [Timezone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) used only for `before:` & `after:` searches (eg: "Pacific/Auckland").
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type string
|
||||
TZ string `json:"tz"`
|
||||
}
|
||||
|
||||
// swagger:parameters HTMLCheckParams
|
||||
type htmlCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID or "latest"
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters LinkCheckParams
|
||||
type linkCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Follow redirects
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// default: false
|
||||
Follow string `json:"follow"`
|
||||
}
|
||||
|
||||
// swagger:parameters ReleaseMessageParams
|
||||
type releaseMessageParams struct {
|
||||
// Message database ID
|
||||
//
|
||||
// in: path
|
||||
// description: Message database ID
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of email addresses to relay the message to
|
||||
//
|
||||
// required: true
|
||||
// example: ["user1@example.com", "user2@example.com"]
|
||||
To []string
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SendMessageParams
|
||||
type sendMessageParams struct {
|
||||
// in: body
|
||||
// Body SendRequest
|
||||
Body struct {
|
||||
// "From" recipient
|
||||
// required: true
|
||||
From struct {
|
||||
// Optional name
|
||||
// example: John Doe
|
||||
Name string
|
||||
// Email address
|
||||
// example: john@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// "To" recipients
|
||||
To []struct {
|
||||
// Optional name
|
||||
// example: Jane Doe
|
||||
Name string
|
||||
// Email address
|
||||
// example: jane@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Cc recipients
|
||||
Cc []struct {
|
||||
// Optional name
|
||||
// example: Manager
|
||||
Name string
|
||||
// Email address
|
||||
// example: manager@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Bcc recipients email addresses only
|
||||
// example: ["jack@example.com"]
|
||||
Bcc []string
|
||||
|
||||
// Optional Reply-To recipients
|
||||
ReplyTo []struct {
|
||||
// Optional name
|
||||
// example: Secretary
|
||||
Name string
|
||||
// Email address
|
||||
// example: secretary@example.com
|
||||
// required: true
|
||||
Email string
|
||||
}
|
||||
|
||||
// Subject
|
||||
// example: Mailpit message via the HTTP API
|
||||
Subject string
|
||||
|
||||
// Message body (text)
|
||||
// example: Mailpit is awesome!
|
||||
Text string
|
||||
|
||||
// Message body (HTML)
|
||||
// example: <div style="text-align:center"><p style="font-family: arial; font-size: 24px;">Mailpit is <b>awesome</b>!</p><p><img src="cid:mailpit-logo" /></p></div>
|
||||
HTML string
|
||||
|
||||
// Attachments
|
||||
Attachments []struct {
|
||||
// Base64-encoded string of the file content
|
||||
// required: true
|
||||
// example: iVBORw0KGgoAAAANSUhEUgAAAEEAAAA8CAMAAAAOlSdoAAAACXBIWXMAAAHrAAAB6wGM2bZBAAAAS1BMVEVHcEwRfnUkZ2gAt4UsSF8At4UtSV4At4YsSV4At4YsSV8At4YsSV4At4YsSV4sSV4At4YsSV4At4YtSV4At4YsSV4At4YtSV8At4YsUWYNAAAAGHRSTlMAAwoXGiktRE5dbnd7kpOlr7zJ0d3h8PD8PCSRAAACWUlEQVR42pXT4ZaqIBSG4W9rhqQYocG+/ys9Y0Z0Br+x3j8zaxUPewFh65K+7yrIMeIY4MT3wPfEJCidKXEMnLaVkxDiELiMz4WEOAZSFghxBIypCOlKiAMgXfIqTnBgSm8CIQ6BImxEUxEckClVQiHGj4Ba4AQHikAIClwTE9KtIghAhUJwoLkmLnCiAHJLRKgIMsEtVUKbBUIwoAg2C4QgQBE6l4VCnApBgSKYLLApCnCa0+96AEMW2BQcmC+Pr3nfp7o5Exy49gIADcIqUELGfeA+bp93LmAJp8QJoEcN3C7NY3sbVANixMyI0nku20/n5/ZRf3KI2k6JEDWQtxcbdGuAqu3TAXG+/799Oyyas1B1MnMiA+XyxHp9q0PUKGPiRAau1fZbLRZV09wZcT8/gHk8QQAxXn8VgaDqcUmU6O/r28nbVwXAqca2mRNtPAF5+zoP2MeN9Fy4NgC6RfcbgE7XITBRYTtOE3U3C2DVff7pk+PkUxgAbvtnPXJaD6DxulMLwOhPS/M3MQkgg1ZFrIXnmfaZoOfpKiFgzeZD/WuKqQEGrfJYkyWf6vlG3xUgTuscnkNkQsb599q124kdpMUjCa/XARHs1gZymVtGt3wLkiFv8rUgTxitYCex5EVGec0Y9VmoDTFBSQte2TfXGXlf7hbdaUM9Sk7fisEN9qfBBTK+FZcvM9fQSdkl2vj4W2oX/bRogO3XasiNH7R0eW7fgRM834ImTg+Lg6BEnx4vz81rhr+MYPBBQg1v8GndEOrthxaCTxNAOut8WKLGZQl+MPz88Q9tAO/hVuSeqQAAAABJRU5ErkJggg==
|
||||
Content string
|
||||
// Filename
|
||||
// required: true
|
||||
// example: mailpit.png
|
||||
Filename string
|
||||
// Optional Content Type for the the attachment.
|
||||
// If this field is not set (or empty) then the content type is automatically detected.
|
||||
// required: false
|
||||
// example: image/png
|
||||
ContentType string
|
||||
// Optional Content-ID (`cid`) for attachment.
|
||||
// If this field is set then the file is attached inline.
|
||||
// required: false
|
||||
// example: mailpit-logo
|
||||
ContentID string
|
||||
}
|
||||
|
||||
// Mailpit tags
|
||||
// example: ["Tag 1","Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Optional headers in {"key":"value"} format
|
||||
// example: {"X-IP":"1.2.3.4"}
|
||||
Headers map[string]string
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SetTagsParams
|
||||
type setTagsParams struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of tag names to set
|
||||
//
|
||||
// required: true
|
||||
// example: ["Tag 1", "Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: true
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters RenameTagParams
|
||||
type renameTagParams struct {
|
||||
// The url-encoded tag name to rename
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// type: string
|
||||
Tag string
|
||||
|
||||
// in: body
|
||||
Body struct {
|
||||
// New name
|
||||
//
|
||||
// required: true
|
||||
// example: New name
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteTagParams
|
||||
type deleteTagParams struct {
|
||||
// The url-encoded tag name to delete
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
Tag string
|
||||
}
|
||||
|
||||
// swagger:parameters GetMessageHTMLParams
|
||||
type getMessageHTMLParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// If this is route is to be embedded in an iframe, set embed to `1` in the URL to add `target="_blank"` and `rel="noreferrer noopener"` to all links.
|
||||
//
|
||||
// In addition, a small script will be added to the end of the document to post (postMessage()) the height of the document back to the parent window for optional iframe height resizing.
|
||||
//
|
||||
// Note that this will also *transform* the message into a full HTML document (if it isn't already), so this option is useful for viewing but not programmatic testing.
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type: string
|
||||
Embed string `json:"embed"`
|
||||
}
|
||||
|
||||
// swagger:parameters GetMessageTextParams
|
||||
type getMessageTextParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters SpamAssassinCheckParams
|
||||
type spamAssassinCheckParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// swagger:parameters ThumbnailParams
|
||||
type thumbnailParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Attachment part ID
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
PartID string
|
||||
}
|
||||
142
server/apiv1/swaggerResponses.go
Normal file
142
server/apiv1/swaggerResponses.go
Normal file
@@ -0,0 +1,142 @@
|
||||
// Package apiv1 provides the API v1 endpoints for Mailpit.
|
||||
//
|
||||
// These structs are for the purpose of defining swagger HTTP responses in go-swagger
|
||||
// in order to generate a spec file. They are lowercased to avoid exporting them as public types.
|
||||
//
|
||||
//nolint:unused
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
"github.com/axllent/mailpit/internal/stats"
|
||||
)
|
||||
|
||||
// Binary data response which inherits the attachment's content type.
|
||||
// swagger:response BinaryResponse
|
||||
type binaryResponse string
|
||||
|
||||
// Plain text response
|
||||
// swagger:response TextResponse
|
||||
type textResponse string
|
||||
|
||||
// HTML response
|
||||
// swagger:response HTMLResponse
|
||||
type htmlResponse string
|
||||
|
||||
// Server error will return with a 400 status code
|
||||
// with the error message in the body
|
||||
// swagger:response ErrorResponse
|
||||
type errorResponse string
|
||||
|
||||
// Not found error will return a 404 status code
|
||||
// swagger:response NotFoundResponse
|
||||
type notFoundResponse string
|
||||
|
||||
// Plain text "ok" response
|
||||
// swagger:response OKResponse
|
||||
type okResponse string
|
||||
|
||||
// Plain JSON array response
|
||||
// swagger:response ArrayResponse
|
||||
type arrayResponse []string
|
||||
|
||||
// JSON error response
|
||||
// swagger:response JSONErrorResponse
|
||||
type jsonErrorResponse struct {
|
||||
// A JSON-encoded error response
|
||||
//
|
||||
// in: body
|
||||
Body struct {
|
||||
// Error message
|
||||
// example: invalid format
|
||||
Error string
|
||||
}
|
||||
}
|
||||
|
||||
// Web UI configuration response
|
||||
// swagger:response WebUIConfigurationResponse
|
||||
type webUIConfigurationResponse struct {
|
||||
// Web UI configuration settings
|
||||
//
|
||||
// in: body
|
||||
Body struct {
|
||||
// Optional label to identify this Mailpit instance
|
||||
Label string
|
||||
// Message Relay information
|
||||
MessageRelay struct {
|
||||
// Whether message relaying (release) is enabled
|
||||
Enabled bool
|
||||
// The configured SMTP server address
|
||||
SMTPServer string
|
||||
// Enforced Return-Path (if set) for relay bounces
|
||||
ReturnPath string
|
||||
// Only allow relaying to these recipients (regex)
|
||||
AllowedRecipients string
|
||||
// Block relaying to these recipients (regex)
|
||||
BlockedRecipients string
|
||||
// Overrides the "From" address for all relayed messages
|
||||
OverrideFrom string
|
||||
// Preserve the original Message-IDs when relaying messages
|
||||
PreserveMessageIDs bool
|
||||
|
||||
// DEPRECATED 2024/03/12
|
||||
// swagger:ignore
|
||||
RecipientAllowlist string
|
||||
}
|
||||
|
||||
// Whether SpamAssassin is enabled
|
||||
SpamAssassin bool
|
||||
|
||||
// Whether Chaos support is enabled at runtime
|
||||
ChaosEnabled bool
|
||||
|
||||
// Whether messages with duplicate IDs are ignored
|
||||
DuplicatesIgnored bool
|
||||
|
||||
// Whether the delete button should be hidden
|
||||
HideDeleteAllButton bool
|
||||
}
|
||||
}
|
||||
|
||||
// Application information
|
||||
// swagger:response AppInfoResponse
|
||||
type appInfoResponse struct {
|
||||
// Application information
|
||||
//
|
||||
// in: body
|
||||
Body stats.AppInformation
|
||||
}
|
||||
|
||||
// Response for the Chaos triggers configuration
|
||||
// swagger:response ChaosResponse
|
||||
type chaosResponse struct {
|
||||
// The current Chaos triggers
|
||||
//
|
||||
// in: body
|
||||
Body chaos.Triggers
|
||||
}
|
||||
|
||||
// Message headers
|
||||
// swagger:model MessageHeadersResponse
|
||||
type messageHeadersResponse map[string][]string
|
||||
|
||||
// Summary of messages
|
||||
// swagger:response MessagesSummaryResponse
|
||||
type messagesSummaryResponse struct {
|
||||
// The messages summary
|
||||
// in: body
|
||||
Body MessagesSummary
|
||||
}
|
||||
|
||||
// Confirmation message for HTTP send API
|
||||
// swagger:response SendMessageResponse
|
||||
type sendMessageResponse struct {
|
||||
// Response for sending messages via the HTTP API
|
||||
//
|
||||
// in: body
|
||||
Body struct {
|
||||
// Database ID
|
||||
// example: iAfZVVe2UQfNSG5BAjgYwa
|
||||
ID string
|
||||
}
|
||||
}
|
||||
@@ -32,24 +32,6 @@ func GetAllTags(w http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// swagger:parameters SetTagsParams
|
||||
type setTagsParams struct {
|
||||
// in: body
|
||||
Body struct {
|
||||
// Array of tag names to set
|
||||
//
|
||||
// required: true
|
||||
// example: ["Tag 1", "Tag 2"]
|
||||
Tags []string
|
||||
|
||||
// Array of message database IDs
|
||||
//
|
||||
// required: true
|
||||
// example: ["4oRBnPtCXgAqZniRhzLNmS", "hXayS6wnCgNnt6aFTvmOF6"]
|
||||
IDs []string
|
||||
}
|
||||
}
|
||||
|
||||
// SetMessageTags (method: PUT) will set the tags for all provided IDs
|
||||
func SetMessageTags(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/tags tags SetTagsParams
|
||||
@@ -98,25 +80,6 @@ func SetMessageTags(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters RenameTagParams
|
||||
type renameTagParams struct {
|
||||
// The url-encoded tag name to rename
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
// type: string
|
||||
Tag string
|
||||
|
||||
// in: body
|
||||
Body struct {
|
||||
// New name
|
||||
//
|
||||
// required: true
|
||||
// example: New name
|
||||
Name string
|
||||
}
|
||||
}
|
||||
|
||||
// RenameTag (method: PUT) used to rename a tag
|
||||
func RenameTag(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route PUT /api/v1/tags/{Tag} tags RenameTagParams
|
||||
@@ -161,15 +124,6 @@ func RenameTag(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
}
|
||||
|
||||
// swagger:parameters DeleteTagParams
|
||||
type deleteTagParams struct {
|
||||
// The url-encoded tag name to delete
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
Tag string
|
||||
}
|
||||
|
||||
// DeleteTag (method: DELETE) used to delete a tag
|
||||
func DeleteTag(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route DELETE /api/v1/tags/{Tag} tags DeleteTagParams
|
||||
|
||||
@@ -16,26 +16,6 @@ import (
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
// swagger:parameters GetMessageHTMLParams
|
||||
type getMessageHTMLParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// If this is route is to be embedded in an iframe, set embed to `1` in the URL to add `target="_blank"` and `rel="noreferrer noopener"` to all links.
|
||||
//
|
||||
// In addition, a small script will be added to the end of the document to post (postMessage()) the height of the document back to the parent window for optional iframe height resizing.
|
||||
//
|
||||
// Note that this will also *transform* the message into a full HTML document (if it isn't already), so this option is useful for viewing but not programmatic testing.
|
||||
//
|
||||
// in: query
|
||||
// required: false
|
||||
// type: string
|
||||
Embed string `json:"embed"`
|
||||
}
|
||||
|
||||
// GetMessageHTML (method: GET) returns a rendered version of a message's HTML part
|
||||
func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /view/{ID}.html testing GetMessageHTMLParams
|
||||
@@ -67,7 +47,7 @@ func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -75,12 +55,12 @@ func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "Message not found")
|
||||
_, _ = fmt.Fprint(w, "Message not found")
|
||||
return
|
||||
}
|
||||
if msg.HTML == "" {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "This message does not contain a HTML part")
|
||||
_, _ = fmt.Fprint(w, "This message does not contain a HTML part")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -123,15 +103,6 @@ func GetMessageHTML(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(htmlStr))
|
||||
}
|
||||
|
||||
// swagger:parameters GetMessageTextParams
|
||||
type getMessageTextParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetMessageText (method: GET) returns a message's text part
|
||||
func GetMessageText(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /view/{ID}.txt testing GetMessageTextParams
|
||||
@@ -161,7 +132,7 @@ func GetMessageText(w http.ResponseWriter, r *http.Request) {
|
||||
id, err = storage.LatestID(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, err.Error())
|
||||
_, _ = fmt.Fprint(w, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -169,7 +140,7 @@ func GetMessageText(w http.ResponseWriter, r *http.Request) {
|
||||
msg, err := storage.GetMessage(id)
|
||||
if err != nil {
|
||||
w.WriteHeader(404)
|
||||
fmt.Fprint(w, "Message not found")
|
||||
_, _ = fmt.Fprint(w, "Message not found")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -22,21 +22,6 @@ var (
|
||||
thumbHeight = 120
|
||||
)
|
||||
|
||||
// swagger:parameters ThumbnailParams
|
||||
type thumbnailParams struct {
|
||||
// Message database ID or "latest"
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
ID string
|
||||
|
||||
// Attachment part ID
|
||||
//
|
||||
// in: path
|
||||
// required: true
|
||||
PartID string
|
||||
}
|
||||
|
||||
// Thumbnail returns a thumbnail image for an attachment (images only)
|
||||
func Thumbnail(w http.ResponseWriter, r *http.Request) {
|
||||
// swagger:route GET /api/v1/message/{ID}/part/{PartID}/thumb message ThumbnailParams
|
||||
|
||||
@@ -39,5 +39,5 @@ func RedirectToLatestMessage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r, uri, 302)
|
||||
http.Redirect(w, r, uri, http.StatusFound)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ func ProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
logger.Log().Warnf("[proxy] %s", err.Error())
|
||||
@@ -141,7 +142,7 @@ func absoluteURL(link, baseURL string) (string, error) {
|
||||
|
||||
// ensure link is HTTP(S)
|
||||
if result.Scheme != "http" && result.Scheme != "https" {
|
||||
return link, fmt.Errorf("Invalid URL: %s", result.String())
|
||||
return link, fmt.Errorf("invalid URL: %s", result.String())
|
||||
}
|
||||
|
||||
return result.String(), nil
|
||||
@@ -153,5 +154,5 @@ func httpError(w http.ResponseWriter, msg string) {
|
||||
w.Header().Set("Content-Security-Policy", config.ContentSecurityPolicy)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
fmt.Fprint(w, msg)
|
||||
_, _ = fmt.Fprint(w, msg)
|
||||
}
|
||||
|
||||
@@ -290,8 +290,9 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Check basic authentication headers if configured.
|
||||
// OPTIONS requests are skipped if CORS is enabled, since browsers omit credentials for preflight.
|
||||
if !(AccessControlAllowOrigin != "" && r.Method == http.MethodOptions) && auth.UICredentials != nil {
|
||||
// OPTIONS requests are skipped if CORS is enabled, since browsers omit credentials for preflight checks.
|
||||
isCORSOptionsRequest := AccessControlAllowOrigin != "" && r.Method == http.MethodOptions
|
||||
if !isCORSOptionsRequest && auth.UICredentials != nil {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
|
||||
if !ok {
|
||||
@@ -312,7 +313,7 @@ func middleWareFunc(fn http.HandlerFunc) http.HandlerFunc {
|
||||
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
gz := gzip.NewWriter(w)
|
||||
defer gz.Close()
|
||||
defer func() { _ = gz.Close() }()
|
||||
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
fn(gzr, r)
|
||||
}
|
||||
|
||||
@@ -283,7 +283,9 @@ func TestAPIv1Send(t *testing.T) {
|
||||
t.Errorf("Expected nil, received %s", err.Error())
|
||||
}
|
||||
|
||||
resp := apiv1.SendMessageConfirmation{}
|
||||
resp := struct {
|
||||
ID string
|
||||
}{}
|
||||
|
||||
if err := json.Unmarshal(b, &resp); err != nil {
|
||||
t.Error(err.Error())
|
||||
@@ -345,7 +347,9 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
|
||||
|
||||
// Set up UI auth that would normally block requests
|
||||
testHash, _ := bcrypt.GenerateFromPassword([]byte("testpass"), bcrypt.DefaultCost)
|
||||
auth.SetUIAuth("testuser:" + string(testHash))
|
||||
if err := auth.SetUIAuth("testuser:" + string(testHash)); err != nil {
|
||||
t.Fatalf("Failed to set UI auth: %s", err.Error())
|
||||
}
|
||||
|
||||
r := apiRoutes()
|
||||
ts := httptest.NewServer(r)
|
||||
@@ -374,11 +378,15 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
|
||||
|
||||
// Set up UI auth
|
||||
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
|
||||
auth.SetUIAuth("uiuser:" + string(uiHash))
|
||||
if err := auth.SetUIAuth("uiuser:" + string(uiHash)); err != nil {
|
||||
t.Fatalf("Failed to set UI auth: %s", err.Error())
|
||||
}
|
||||
|
||||
// Set up dedicated Send API auth
|
||||
sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost)
|
||||
auth.SetSendAPIAuth("senduser:" + string(sendHash))
|
||||
if err := auth.SetSendAPIAuth("senduser:" + string(sendHash)); err != nil {
|
||||
t.Fatalf("Failed to set Send API auth: %s", err.Error())
|
||||
}
|
||||
|
||||
r := apiRoutes()
|
||||
ts := httptest.NewServer(r)
|
||||
@@ -421,7 +429,9 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
|
||||
|
||||
// Set up only UI auth
|
||||
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
|
||||
auth.SetUIAuth("uiuser:" + string(uiHash))
|
||||
if err := auth.SetUIAuth("uiuser:" + string(uiHash)); err != nil {
|
||||
t.Fatalf("Failed to set UI auth: %s", err.Error())
|
||||
}
|
||||
|
||||
r := apiRoutes()
|
||||
ts := httptest.NewServer(r)
|
||||
@@ -455,9 +465,14 @@ func TestSendAPIAuthMiddleware(t *testing.T) {
|
||||
|
||||
// Set up UI auth and Send API auth
|
||||
uiHash, _ := bcrypt.GenerateFromPassword([]byte("uipass"), bcrypt.DefaultCost)
|
||||
auth.SetUIAuth("uiuser:" + string(uiHash))
|
||||
if err := auth.SetUIAuth("uiuser:" + string(uiHash)); err != nil {
|
||||
t.Fatalf("Failed to set UI auth: %s", err.Error())
|
||||
}
|
||||
|
||||
sendHash, _ := bcrypt.GenerateFromPassword([]byte("sendpass"), bcrypt.DefaultCost)
|
||||
auth.SetSendAPIAuth("senduser:" + string(sendHash))
|
||||
if err := auth.SetSendAPIAuth("senduser:" + string(sendHash)); err != nil {
|
||||
t.Fatalf("Failed to set Send API auth: %s", err.Error())
|
||||
}
|
||||
|
||||
r := apiRoutes()
|
||||
ts := httptest.NewServer(r)
|
||||
@@ -604,7 +619,7 @@ func clientGet(url string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
|
||||
@@ -625,7 +640,7 @@ func clientDelete(url, body string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
@@ -650,7 +665,7 @@ func clientPut(url, body string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
@@ -675,7 +690,7 @@ func clientPost(url, body string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
@@ -702,7 +717,7 @@ func clientPostWithAuth(url, body, username, password string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
@@ -728,7 +743,7 @@ func clientGetWithAuth(url, username, password string) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("%s returned status %d", url, resp.StatusCode)
|
||||
|
||||
@@ -785,16 +785,185 @@
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SendRequest"
|
||||
"type": "object",
|
||||
"required": [
|
||||
"From"
|
||||
],
|
||||
"properties": {
|
||||
"Attachments": {
|
||||
"description": "Attachments",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Content",
|
||||
"Filename"
|
||||
],
|
||||
"properties": {
|
||||
"Content": {
|
||||
"description": "Base64-encoded string of the file content",
|
||||
"type": "string",
|
||||
"example": "iVBORw0KGgoAAAANSUhEUgAAAEEAAAA8CAMAAAAOlSdoAAAACXBIWXMAAAHrAAAB6wGM2bZBAAAAS1BMVEVHcEwRfnUkZ2gAt4UsSF8At4UtSV4At4YsSV4At4YsSV8At4YsSV4At4YsSV4sSV4At4YsSV4At4YtSV4At4YsSV4At4YtSV8At4YsUWYNAAAAGHRSTlMAAwoXGiktRE5dbnd7kpOlr7zJ0d3h8PD8PCSRAAACWUlEQVR42pXT4ZaqIBSG4W9rhqQYocG+/ys9Y0Z0Br+x3j8zaxUPewFh65K+7yrIMeIY4MT3wPfEJCidKXEMnLaVkxDiELiMz4WEOAZSFghxBIypCOlKiAMgXfIqTnBgSm8CIQ6BImxEUxEckClVQiHGj4Ba4AQHikAIClwTE9KtIghAhUJwoLkmLnCiAHJLRKgIMsEtVUKbBUIwoAg2C4QgQBE6l4VCnApBgSKYLLApCnCa0+96AEMW2BQcmC+Pr3nfp7o5Exy49gIADcIqUELGfeA+bp93LmAJp8QJoEcN3C7NY3sbVANixMyI0nku20/n5/ZRf3KI2k6JEDWQtxcbdGuAqu3TAXG+/799Oyyas1B1MnMiA+XyxHp9q0PUKGPiRAau1fZbLRZV09wZcT8/gHk8QQAxXn8VgaDqcUmU6O/r28nbVwXAqca2mRNtPAF5+zoP2MeN9Fy4NgC6RfcbgE7XITBRYTtOE3U3C2DVff7pk+PkUxgAbvtnPXJaD6DxulMLwOhPS/M3MQkgg1ZFrIXnmfaZoOfpKiFgzeZD/WuKqQEGrfJYkyWf6vlG3xUgTuscnkNkQsb599q124kdpMUjCa/XARHs1gZymVtGt3wLkiFv8rUgTxitYCex5EVGec0Y9VmoDTFBSQte2TfXGXlf7hbdaUM9Sk7fisEN9qfBBTK+FZcvM9fQSdkl2vj4W2oX/bRogO3XasiNH7R0eW7fgRM834ImTg+Lg6BEnx4vz81rhr+MYPBBQg1v8GndEOrthxaCTxNAOut8WKLGZQl+MPz88Q9tAO/hVuSeqQAAAABJRU5ErkJggg=="
|
||||
},
|
||||
"ContentID": {
|
||||
"description": "Optional Content-ID (`cid`) for attachment.\nIf this field is set then the file is attached inline.",
|
||||
"type": "string",
|
||||
"example": "mailpit-logo"
|
||||
},
|
||||
"ContentType": {
|
||||
"description": "Optional Content Type for the the attachment.\nIf this field is not set (or empty) then the content type is automatically detected.",
|
||||
"type": "string",
|
||||
"example": "image/png"
|
||||
},
|
||||
"Filename": {
|
||||
"description": "Filename",
|
||||
"type": "string",
|
||||
"example": "mailpit.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bcc": {
|
||||
"description": "Bcc recipients email addresses only",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"jack@example.com"
|
||||
]
|
||||
},
|
||||
"Cc": {
|
||||
"description": "Cc recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "manager@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Manager"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"From": {
|
||||
"description": "\"From\" recipient",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "john@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "John Doe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTML": {
|
||||
"description": "Message body (HTML)",
|
||||
"type": "string",
|
||||
"example": "\u003cdiv style=\"text-align:center\"\u003e\u003cp style=\"font-family: arial; font-size: 24px;\"\u003eMailpit is \u003cb\u003eawesome\u003c/b\u003e!\u003c/p\u003e\u003cp\u003e\u003cimg src=\"cid:mailpit-logo\" /\u003e\u003c/p\u003e\u003c/div\u003e"
|
||||
},
|
||||
"Headers": {
|
||||
"description": "Optional headers in {\"key\":\"value\"} format",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"X-IP": "1.2.3.4"
|
||||
}
|
||||
},
|
||||
"ReplyTo": {
|
||||
"description": "Optional Reply-To recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "secretary@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Secretary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Subject": {
|
||||
"description": "Subject",
|
||||
"type": "string",
|
||||
"example": "Mailpit message via the HTTP API"
|
||||
},
|
||||
"Tags": {
|
||||
"description": "Mailpit tags",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"Tag 1",
|
||||
"Tag 2"
|
||||
]
|
||||
},
|
||||
"Text": {
|
||||
"description": "Message body (text)",
|
||||
"type": "string",
|
||||
"example": "Mailpit is awesome!"
|
||||
},
|
||||
"To": {
|
||||
"description": "\"To\" recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "jane@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Jane Doe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/sendMessageResponse"
|
||||
"$ref": "#/responses/SendMessageResponse"
|
||||
},
|
||||
"400": {
|
||||
"$ref": "#/responses/jsonErrorResponse"
|
||||
"$ref": "#/responses/JSONErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -984,7 +1153,7 @@
|
||||
"application"
|
||||
],
|
||||
"summary": "Get web UI configuration",
|
||||
"operationId": "WebUIConfiguration",
|
||||
"operationId": "WebUIConfigurationResponse",
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/WebUIConfigurationResponse"
|
||||
@@ -1203,9 +1372,46 @@
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/storage"
|
||||
},
|
||||
"ChaosTrigger": {
|
||||
"description": "Trigger for Chaos",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ErrorCode",
|
||||
"Probability"
|
||||
],
|
||||
"properties": {
|
||||
"ErrorCode": {
|
||||
"description": "SMTP error code to return. The value must range from 400 to 599.",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 451
|
||||
},
|
||||
"Probability": {
|
||||
"description": "Probability (chance) of triggering the error. The value must range from 0 to 100.",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 5
|
||||
}
|
||||
},
|
||||
"x-go-name": "Trigger",
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
},
|
||||
"ChaosTriggers": {
|
||||
"description": "ChaosTriggers are the Chaos triggers",
|
||||
"$ref": "#/definitions/Triggers"
|
||||
"description": "Triggers for the Chaos configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Authentication": {
|
||||
"$ref": "#/definitions/ChaosTrigger"
|
||||
},
|
||||
"Recipient": {
|
||||
"$ref": "#/definitions/ChaosTrigger"
|
||||
},
|
||||
"Sender": {
|
||||
"$ref": "#/definitions/ChaosTrigger"
|
||||
}
|
||||
},
|
||||
"x-go-name": "Triggers",
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
},
|
||||
"HTMLCheckResponse": {
|
||||
"description": "Response represents the HTML check response struct",
|
||||
@@ -1384,18 +1590,6 @@
|
||||
"x-go-name": "Warning",
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/htmlcheck"
|
||||
},
|
||||
"JSONErrorMessage": {
|
||||
"description": "JSONErrorMessage struct",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Error": {
|
||||
"description": "Error message",
|
||||
"type": "string",
|
||||
"example": "invalid format"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"Link": {
|
||||
"description": "Link struct",
|
||||
"type": "object",
|
||||
@@ -1571,7 +1765,7 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"x-go-name": "messageHeaders",
|
||||
"x-go-name": "messageHeadersResponse",
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"MessageSummary": {
|
||||
@@ -1731,192 +1925,6 @@
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/spamassassin"
|
||||
},
|
||||
"SendMessageConfirmation": {
|
||||
"description": "SendMessageConfirmation struct",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ID": {
|
||||
"description": "Database ID",
|
||||
"type": "string",
|
||||
"example": "iAfZVVe2UQfNSG5BAjgYwa"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"SendRequest": {
|
||||
"description": "SendRequest to send a message via HTTP",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"From"
|
||||
],
|
||||
"properties": {
|
||||
"Attachments": {
|
||||
"description": "Attachments",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Content",
|
||||
"Filename"
|
||||
],
|
||||
"properties": {
|
||||
"Content": {
|
||||
"description": "Base64-encoded string of the file content",
|
||||
"type": "string",
|
||||
"example": "iVBORw0KGgoAAAANSUhEUgAAAEEAAAA8CAMAAAAOlSdoAAAACXBIWXMAAAHrAAAB6wGM2bZBAAAAS1BMVEVHcEwRfnUkZ2gAt4UsSF8At4UtSV4At4YsSV4At4YsSV8At4YsSV4At4YsSV4sSV4At4YsSV4At4YtSV4At4YsSV4At4YtSV8At4YsUWYNAAAAGHRSTlMAAwoXGiktRE5dbnd7kpOlr7zJ0d3h8PD8PCSRAAACWUlEQVR42pXT4ZaqIBSG4W9rhqQYocG+/ys9Y0Z0Br+x3j8zaxUPewFh65K+7yrIMeIY4MT3wPfEJCidKXEMnLaVkxDiELiMz4WEOAZSFghxBIypCOlKiAMgXfIqTnBgSm8CIQ6BImxEUxEckClVQiHGj4Ba4AQHikAIClwTE9KtIghAhUJwoLkmLnCiAHJLRKgIMsEtVUKbBUIwoAg2C4QgQBE6l4VCnApBgSKYLLApCnCa0+96AEMW2BQcmC+Pr3nfp7o5Exy49gIADcIqUELGfeA+bp93LmAJp8QJoEcN3C7NY3sbVANixMyI0nku20/n5/ZRf3KI2k6JEDWQtxcbdGuAqu3TAXG+/799Oyyas1B1MnMiA+XyxHp9q0PUKGPiRAau1fZbLRZV09wZcT8/gHk8QQAxXn8VgaDqcUmU6O/r28nbVwXAqca2mRNtPAF5+zoP2MeN9Fy4NgC6RfcbgE7XITBRYTtOE3U3C2DVff7pk+PkUxgAbvtnPXJaD6DxulMLwOhPS/M3MQkgg1ZFrIXnmfaZoOfpKiFgzeZD/WuKqQEGrfJYkyWf6vlG3xUgTuscnkNkQsb599q124kdpMUjCa/XARHs1gZymVtGt3wLkiFv8rUgTxitYCex5EVGec0Y9VmoDTFBSQte2TfXGXlf7hbdaUM9Sk7fisEN9qfBBTK+FZcvM9fQSdkl2vj4W2oX/bRogO3XasiNH7R0eW7fgRM834ImTg+Lg6BEnx4vz81rhr+MYPBBQg1v8GndEOrthxaCTxNAOut8WKLGZQl+MPz88Q9tAO/hVuSeqQAAAABJRU5ErkJggg=="
|
||||
},
|
||||
"ContentID": {
|
||||
"description": "Optional Content-ID (`cid`) for attachment.\nIf this field is set then the file is attached inline.",
|
||||
"type": "string",
|
||||
"example": "mailpit-logo"
|
||||
},
|
||||
"ContentType": {
|
||||
"description": "Optional Content Type for the the attachment.\nIf this field is not set (or empty) then the content type is automatically detected.",
|
||||
"type": "string",
|
||||
"example": "image/png"
|
||||
},
|
||||
"Filename": {
|
||||
"description": "Filename",
|
||||
"type": "string",
|
||||
"example": "mailpit.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bcc": {
|
||||
"description": "Bcc recipients email addresses only",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"jack@example.com"
|
||||
]
|
||||
},
|
||||
"Cc": {
|
||||
"description": "Cc recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "manager@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Manager"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"From": {
|
||||
"description": "\"From\" recipient",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "john@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "John Doe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"HTML": {
|
||||
"description": "Message body (HTML)",
|
||||
"type": "string",
|
||||
"example": "\u003cdiv style=\"text-align:center\"\u003e\u003cp style=\"font-family: arial; font-size: 24px;\"\u003eMailpit is \u003cb\u003eawesome\u003c/b\u003e!\u003c/p\u003e\u003cp\u003e\u003cimg src=\"cid:mailpit-logo\" /\u003e\u003c/p\u003e\u003c/div\u003e"
|
||||
},
|
||||
"Headers": {
|
||||
"description": "Optional headers in {\"key\":\"value\"} format",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": {
|
||||
"X-IP": "1.2.3.4"
|
||||
}
|
||||
},
|
||||
"ReplyTo": {
|
||||
"description": "Optional Reply-To recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "secretary@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Secretary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Subject": {
|
||||
"description": "Subject",
|
||||
"type": "string",
|
||||
"example": "Mailpit message via the HTTP API"
|
||||
},
|
||||
"Tags": {
|
||||
"description": "Mailpit tags",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"example": [
|
||||
"Tag 1",
|
||||
"Tag 2"
|
||||
]
|
||||
},
|
||||
"Text": {
|
||||
"description": "Message body (text)",
|
||||
"type": "string",
|
||||
"example": "Mailpit is awesome!"
|
||||
},
|
||||
"To": {
|
||||
"description": "\"To\" recipients",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"Email"
|
||||
],
|
||||
"properties": {
|
||||
"Email": {
|
||||
"description": "Email address",
|
||||
"type": "string",
|
||||
"example": "jane@example.com"
|
||||
},
|
||||
"Name": {
|
||||
"description": "Optional name",
|
||||
"type": "string",
|
||||
"example": "Jane Doe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
},
|
||||
"SpamAssassinResponse": {
|
||||
"description": "Result is a SpamAssassin result",
|
||||
"type": "object",
|
||||
@@ -1944,107 +1952,6 @@
|
||||
},
|
||||
"x-go-name": "Result",
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/spamassassin"
|
||||
},
|
||||
"Trigger": {
|
||||
"description": "Trigger for Chaos",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ErrorCode",
|
||||
"Probability"
|
||||
],
|
||||
"properties": {
|
||||
"ErrorCode": {
|
||||
"description": "SMTP error code to return. The value must range from 400 to 599.",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 451
|
||||
},
|
||||
"Probability": {
|
||||
"description": "Probability (chance) of triggering the error. The value must range from 0 to 100.",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"example": 5
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
},
|
||||
"Triggers": {
|
||||
"description": "Triggers for the Chaos configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Authentication": {
|
||||
"$ref": "#/definitions/Trigger"
|
||||
},
|
||||
"Recipient": {
|
||||
"$ref": "#/definitions/Trigger"
|
||||
},
|
||||
"Sender": {
|
||||
"$ref": "#/definitions/Trigger"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/axllent/mailpit/internal/smtpd/chaos"
|
||||
},
|
||||
"WebUIConfiguration": {
|
||||
"description": "Response includes global web UI settings",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ChaosEnabled": {
|
||||
"description": "Whether Chaos support is enabled at runtime",
|
||||
"type": "boolean"
|
||||
},
|
||||
"DuplicatesIgnored": {
|
||||
"description": "Whether messages with duplicate IDs are ignored",
|
||||
"type": "boolean"
|
||||
},
|
||||
"HideDeleteAllButton": {
|
||||
"description": "Whether the delete button should be hidden",
|
||||
"type": "boolean"
|
||||
},
|
||||
"Label": {
|
||||
"description": "Optional label to identify this Mailpit instance",
|
||||
"type": "string"
|
||||
},
|
||||
"MessageRelay": {
|
||||
"description": "Message Relay information",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"AllowedRecipients": {
|
||||
"description": "Only allow relaying to these recipients (regex)",
|
||||
"type": "string"
|
||||
},
|
||||
"BlockedRecipients": {
|
||||
"description": "Block relaying to these recipients (regex)",
|
||||
"type": "string"
|
||||
},
|
||||
"Enabled": {
|
||||
"description": "Whether message relaying (release) is enabled",
|
||||
"type": "boolean"
|
||||
},
|
||||
"OverrideFrom": {
|
||||
"description": "Overrides the \"From\" address for all relayed messages",
|
||||
"type": "string"
|
||||
},
|
||||
"PreserveMessageIDs": {
|
||||
"description": "Preserve the original Message-IDs when relaying messages",
|
||||
"type": "boolean"
|
||||
},
|
||||
"ReturnPath": {
|
||||
"description": "Enforced Return-Path (if set) for relay bounces",
|
||||
"type": "string"
|
||||
},
|
||||
"SMTPServer": {
|
||||
"description": "The configured SMTP server address",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SpamAssassin": {
|
||||
"description": "Whether SpamAssassin is enabled",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"x-go-name": "webUIConfiguration",
|
||||
"x-go-package": "github.com/axllent/mailpit/server/apiv1"
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
@@ -2087,6 +1994,19 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"JSONErrorResponse": {
|
||||
"description": "JSON error response",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Error": {
|
||||
"description": "Error message",
|
||||
"type": "string",
|
||||
"example": "invalid format"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"MessagesSummaryResponse": {
|
||||
"description": "Summary of messages",
|
||||
"schema": {
|
||||
@@ -2105,6 +2025,19 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"SendMessageResponse": {
|
||||
"description": "Confirmation message for HTTP send API",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ID": {
|
||||
"description": "Database ID",
|
||||
"type": "string",
|
||||
"example": "iAfZVVe2UQfNSG5BAjgYwa"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"TextResponse": {
|
||||
"description": "Plain text response",
|
||||
"schema": {
|
||||
@@ -2114,19 +2047,63 @@
|
||||
"WebUIConfigurationResponse": {
|
||||
"description": "Web UI configuration response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/WebUIConfiguration"
|
||||
}
|
||||
},
|
||||
"jsonErrorResponse": {
|
||||
"description": "JSON error response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/JSONErrorMessage"
|
||||
}
|
||||
},
|
||||
"sendMessageResponse": {
|
||||
"description": "Confirmation message for HTTP send API",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/SendMessageConfirmation"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ChaosEnabled": {
|
||||
"description": "Whether Chaos support is enabled at runtime",
|
||||
"type": "boolean"
|
||||
},
|
||||
"DuplicatesIgnored": {
|
||||
"description": "Whether messages with duplicate IDs are ignored",
|
||||
"type": "boolean"
|
||||
},
|
||||
"HideDeleteAllButton": {
|
||||
"description": "Whether the delete button should be hidden",
|
||||
"type": "boolean"
|
||||
},
|
||||
"Label": {
|
||||
"description": "Optional label to identify this Mailpit instance",
|
||||
"type": "string"
|
||||
},
|
||||
"MessageRelay": {
|
||||
"description": "Message Relay information",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"AllowedRecipients": {
|
||||
"description": "Only allow relaying to these recipients (regex)",
|
||||
"type": "string"
|
||||
},
|
||||
"BlockedRecipients": {
|
||||
"description": "Block relaying to these recipients (regex)",
|
||||
"type": "string"
|
||||
},
|
||||
"Enabled": {
|
||||
"description": "Whether message relaying (release) is enabled",
|
||||
"type": "boolean"
|
||||
},
|
||||
"OverrideFrom": {
|
||||
"description": "Overrides the \"From\" address for all relayed messages",
|
||||
"type": "string"
|
||||
},
|
||||
"PreserveMessageIDs": {
|
||||
"description": "Preserve the original Message-IDs when relaying messages",
|
||||
"type": "boolean"
|
||||
},
|
||||
"ReturnPath": {
|
||||
"description": "Enforced Return-Path (if set) for relay bounces",
|
||||
"type": "string"
|
||||
},
|
||||
"SMTPServer": {
|
||||
"description": "The configured SMTP server address",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SpamAssassin": {
|
||||
"description": "Whether SpamAssassin is enabled",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ var (
|
||||
)
|
||||
|
||||
// Send will post the MessageSummary to a webhook (if configured)
|
||||
func Send(msg interface{}) {
|
||||
func Send(msg any) {
|
||||
if config.WebhookURL == "" {
|
||||
return
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func Send(msg interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
_ = resp.Body.Close()
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user