mirror of
				https://github.com/axllent/mailpit.git
				synced 2025-10-31 00:07:43 +02:00 
			
		
		
		
	feature: Switch backend storage to use SQLite
BREAKING CHANGE: This release includes a major backend storage change (SQLite) that will render any previously-saved messages useless. Please delete old data to free up space. For more information see https://github.com/axllent/mailpit/issues/10
This commit is contained in:
		
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -2,5 +2,6 @@ | ||||
| /send | ||||
| /server/ui/dist | ||||
| /Makefile | ||||
| /mailpit | ||||
| /mailpit* | ||||
| *.old | ||||
| *.db | ||||
|   | ||||
| @@ -23,9 +23,9 @@ Mailpit is inspired by [MailHog](#why-rewrite-mailhog), but much, much faster. | ||||
| - Real-time web UI updates using web sockets for new mail | ||||
| - Optional browser notifications for new mail (HTTPS only) | ||||
| - Configurable automatic email pruning (default keeps the most recent 500 emails) | ||||
| - Email storage either in memory or disk ([see wiki](https://github.com/axllent/mailpit/wiki/Email-storage)) | ||||
| - Email storage either in a temporary or persistent database ([see wiki](https://github.com/axllent/mailpit/wiki/Email-storage)) | ||||
| - Fast SMTP processing & storing - approximately 300-600 emails per second depending on CPU, network speed & email size | ||||
| - Can handle tens of thousands of emails | ||||
| - Can handle hundreds of thousands of emails | ||||
| - Optional SMTP with STARTTLS & SMTP authentication ([see wiki](https://github.com/axllent/mailpit/wiki/SMTP-with-STARTTLS-and-authentication)) | ||||
| - Optional HTTPS for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/HTTPS)) | ||||
| - Optional basic authentication for web UI ([see wiki](https://github.com/axllent/mailpit/wiki/Basic-authentication)) | ||||
|   | ||||
							
								
								
									
										13
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								cmd/root.go
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| package cmd | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
|  | ||||
| @@ -72,8 +73,8 @@ func init() { | ||||
| 	rootCmd.PersistentFlags().Lookup("help").Hidden = true | ||||
|  | ||||
| 	// defaults from envars if provided | ||||
| 	if len(os.Getenv("MP_DATA_DIR")) > 0 { | ||||
| 		config.DataDir = os.Getenv("MP_DATA_DIR") | ||||
| 	if len(os.Getenv("MP_DATA_FILE")) > 0 { | ||||
| 		config.DataFile = os.Getenv("MP_DATA_FILE") | ||||
| 	} | ||||
| 	if len(os.Getenv("MP_SMTP_BIND_ADDR")) > 0 { | ||||
| 		config.SMTPListen = os.Getenv("MP_SMTP_BIND_ADDR") | ||||
| @@ -116,7 +117,13 @@ func init() { | ||||
| 		config.UISSLKey = os.Getenv("MP_SSL_KEY") | ||||
| 	} | ||||
|  | ||||
| 	rootCmd.Flags().StringVarP(&config.DataDir, "data", "d", config.DataDir, "Optional path to store persistent data") | ||||
| 	// deprecated 2022/08/28 | ||||
| 	if len(os.Getenv("MP_DATA_DIR")) > 0 { | ||||
| 		fmt.Println("MP_DATA_DIR has been deprecated, use MP_DATA_FILE") | ||||
| 		config.DataFile = os.Getenv("MP_DATA_DIR") | ||||
| 	} | ||||
|  | ||||
| 	rootCmd.Flags().StringVarP(&config.DataFile, "data", "d", config.DataFile, "Database file to store persistent data") | ||||
| 	rootCmd.Flags().StringVarP(&config.SMTPListen, "smtp", "s", config.SMTPListen, "SMTP bind interface and port") | ||||
| 	rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI") | ||||
| 	rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store") | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
|  | ||||
| 	"github.com/tg123/go-htpasswd" | ||||
| @@ -16,8 +17,8 @@ var ( | ||||
| 	// HTTPListen to listen on <interface>:<port> | ||||
| 	HTTPListen = "0.0.0.0:8025" | ||||
|  | ||||
| 	// DataDir for mail (optional) | ||||
| 	DataDir string | ||||
| 	// DataFile for mail (optional) | ||||
| 	DataFile string | ||||
|  | ||||
| 	// MaxMessages is the maximum number of messages a mailbox can have (auto-pruned every minute) | ||||
| 	MaxMessages = 500 | ||||
| @@ -55,6 +56,10 @@ var ( | ||||
|  | ||||
| // VerifyConfig wil do some basic checking | ||||
| func VerifyConfig() error { | ||||
| 	if DataFile != "" && isDir(DataFile) { | ||||
| 		DataFile = filepath.Join(DataFile, "mailpit.db") | ||||
| 	} | ||||
|  | ||||
| 	re := regexp.MustCompile(`^[a-zA-Z0-9\.\-]{3,}:\d{2,}$`) | ||||
| 	if !re.MatchString(SMTPListen) { | ||||
| 		return errors.New("SMTP bind should be in the format of <ip>:<port>") | ||||
| @@ -131,3 +136,13 @@ func isFile(path string) bool { | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // IsDir returns whether 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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										51
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								go.mod
									
									
									
									
									
								
							| @@ -3,54 +3,59 @@ module github.com/axllent/mailpit | ||||
| go 1.18 | ||||
|  | ||||
| require ( | ||||
| 	github.com/GuiaBolso/darwin v0.0.0-20191218124601-fd6d2aa3d244 | ||||
| 	github.com/axllent/semver v0.0.1 | ||||
| 	github.com/gorilla/mux v1.8.0 | ||||
| 	github.com/gorilla/websocket v1.5.0 | ||||
| 	github.com/jhillyerd/enmime v0.10.0 | ||||
| 	github.com/k3a/html2text v1.0.8 | ||||
| 	github.com/klauspost/compress v1.15.9 | ||||
| 	github.com/leporo/sqlf v1.3.0 | ||||
| 	github.com/mattn/go-shellwords v1.0.12 | ||||
| 	github.com/mhale/smtpd v0.8.0 | ||||
| 	github.com/ostafen/clover/v2 v2.0.0-alpha.2 | ||||
| 	github.com/satori/go.uuid v1.2.0 | ||||
| 	github.com/sirupsen/logrus v1.9.0 | ||||
| 	github.com/spf13/cobra v1.5.0 | ||||
| 	github.com/spf13/pflag v1.0.5 | ||||
| 	github.com/tg123/go-htpasswd v1.2.0 | ||||
| 	modernc.org/sqlite v1.18.1 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect | ||||
| 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect | ||||
| 	github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect | ||||
| 	github.com/cespare/xxhash v1.1.0 // indirect | ||||
| 	github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||||
| 	github.com/dgraph-io/badger/v3 v3.2103.2 // indirect | ||||
| 	github.com/dgraph-io/ristretto v0.1.0 // indirect | ||||
| 	github.com/dustin/go-humanize v1.0.0 // indirect | ||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | ||||
| 	github.com/cznic/ql v1.2.0 // indirect | ||||
| 	github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect | ||||
| 	github.com/golang/glog v1.0.0 // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||
| 	github.com/golang/protobuf v1.5.2 // indirect | ||||
| 	github.com/golang/snappy v0.0.4 // indirect | ||||
| 	github.com/google/flatbuffers v2.0.6+incompatible // indirect | ||||
| 	github.com/google/go-cmp v0.5.8 // indirect | ||||
| 	github.com/google/orderedcode v0.0.1 // indirect | ||||
| 	github.com/inconshreveable/mousetrap v1.0.0 // indirect | ||||
| 	github.com/google/uuid v1.3.0 // indirect | ||||
| 	github.com/inconshreveable/mousetrap v1.0.1 // indirect | ||||
| 	github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba // indirect | ||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||
| 	github.com/kr/pretty v0.3.0 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.16 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.13 // indirect | ||||
| 	github.com/olekukonko/tablewriter v0.0.5 // indirect | ||||
| 	github.com/pkg/errors v0.9.1 // indirect | ||||
| 	github.com/rivo/uniseg v0.3.1 // indirect | ||||
| 	github.com/satori/go.uuid v1.2.0 // indirect | ||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect | ||||
| 	github.com/rivo/uniseg v0.3.4 // indirect | ||||
| 	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect | ||||
| 	github.com/stretchr/testify v1.7.2 // indirect | ||||
| 	github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect | ||||
| 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect | ||||
| 	go.opencensus.io v0.23.0 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect | ||||
| 	golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b // indirect | ||||
| 	golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 // indirect | ||||
| 	github.com/valyala/bytebufferpool v1.0.0 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect | ||||
| 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect | ||||
| 	golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect | ||||
| 	golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect | ||||
| 	golang.org/x/text v0.3.7 // indirect | ||||
| 	google.golang.org/protobuf v1.28.1 // indirect | ||||
| 	golang.org/x/tools v0.1.12 // indirect | ||||
| 	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect | ||||
| 	lukechampine.com/uint128 v1.2.0 // indirect | ||||
| 	modernc.org/cc/v3 v3.36.3 // indirect | ||||
| 	modernc.org/ccgo/v3 v3.16.9 // indirect | ||||
| 	modernc.org/libc v1.17.1 // indirect | ||||
| 	modernc.org/mathutil v1.5.0 // indirect | ||||
| 	modernc.org/memory v1.2.1 // indirect | ||||
| 	modernc.org/opt v0.1.3 // indirect | ||||
| 	modernc.org/strutil v1.1.3 // indirect | ||||
| 	modernc.org/token v1.0.1 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										267
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										267
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,102 +1,62 @@ | ||||
| cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= | ||||
| github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= | ||||
| github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw= | ||||
| github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo= | ||||
| github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= | ||||
| github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= | ||||
| github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= | ||||
| github.com/GuiaBolso/darwin v0.0.0-20191218124601-fd6d2aa3d244 h1:dqzm54OhCqY8RinR/cx+Ppb0y56Ds5I3wwWhx4XybDg= | ||||
| github.com/GuiaBolso/darwin v0.0.0-20191218124601-fd6d2aa3d244/go.mod h1:3sqgkckuISJ5rs1EpOp6vCvwOUKe/z9vPmyuIlq8Q/A= | ||||
| github.com/axllent/semver v0.0.1 h1:QqF+KSGxgj8QZzSXAvKFqjGWE5792ksOnQhludToK8E= | ||||
| github.com/axllent/semver v0.0.1/go.mod h1:2xSPzvG8n9mRfdtxSvWvfTfQGWfHsMsHO1iZnKATMSc= | ||||
| github.com/brianvoe/gofakeit/v6 v6.17.0 h1:obbQTJeHfktJtiZzq0Q1bEpsNUs+yHrYlPVWt7BtmJ4= | ||||
| github.com/brianvoe/gofakeit/v6 v6.17.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
| github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI= | ||||
| github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8= | ||||
| github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= | ||||
| github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | ||||
| github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= | ||||
| github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||
| github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | ||||
| github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | ||||
| github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= | ||||
| github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | ||||
| github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= | ||||
| github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= | ||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||
| github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07 h1:UHFGPvSxX4C4YBApSPvmUfL8tTvWLj2ryqvT9K4Jcuk= | ||||
| github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= | ||||
| github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f h1:7uSNgsgcarNk4oiN/nNkO0J7KAjlsF5Yv5Gf/tFdHas= | ||||
| github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= | ||||
| github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4 h1:CVAqftqbj+exlab+8KJQrE+kNIVlQfJt58j4GxCMF1s= | ||||
| github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= | ||||
| github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00 h1:FHpbUtp2K8X53/b4aFNj4my5n+i3x+CQCZWNuHWH/+E= | ||||
| github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= | ||||
| github.com/cznic/lldb v1.1.0 h1:AIA+ham6TSJ+XkMe8imQ/g8KPzMUVWAwqUQQdtuMsHs= | ||||
| github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= | ||||
| github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369 h1:XNT/Zf5l++1Pyg08/HV04ppB0gKxAqtZQBRYiYrUuYk= | ||||
| github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= | ||||
| github.com/cznic/ql v1.2.0 h1:lcKp95ZtdF0XkWhGnVIXGF8dVD2X+ClS08tglKtf+ak= | ||||
| github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= | ||||
| github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65 h1:hxuZop6tSoOi0sxFzoGGYdRqNrPubyaIf9KoBG9tPiE= | ||||
| github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= | ||||
| github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186 h1:0rkFMAbn5KBKNpJyHQ6Prb95vIKanmAe62KxsrN+sqA= | ||||
| github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= | ||||
| github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc h1:YKKpTb2BrXN2GYyGaygIdis1vXbE7SSAG9axGWIMClg= | ||||
| github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| 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/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= | ||||
| github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= | ||||
| github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= | ||||
| github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= | ||||
| github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= | ||||
| github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= | ||||
| github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | ||||
| github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | ||||
| github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||
| github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= | ||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= | ||||
| github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= | ||||
| github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= | ||||
| github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= | ||||
| github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | ||||
| github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | ||||
| github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= | ||||
| github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs= | ||||
| github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= | ||||
| github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= | ||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= | ||||
| github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||||
| github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= | ||||
| github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= | ||||
| github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= | ||||
| github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= | ||||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= | ||||
| github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= | ||||
| github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= | ||||
| github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||
| github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= | ||||
| github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||
| github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= | ||||
| github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= | ||||
| github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw= | ||||
| github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= | ||||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||||
| github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= | ||||
| github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||
| github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= | ||||
| github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= | ||||
| github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= | ||||
| github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||
| github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= | ||||
| github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||
| github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||||
| github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||||
| github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= | ||||
| github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||||
| github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= | ||||
| github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | ||||
| github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= | ||||
| github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||||
| github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= | ||||
| github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg= | ||||
| github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= | ||||
| @@ -106,47 +66,46 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 | ||||
| github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||||
| github.com/k3a/html2text v1.0.8 h1:rVanLhKilpnJUJs/CNKWzMC4YaQINGxK0rSG8ssmnV0= | ||||
| github.com/k3a/html2text v1.0.8/go.mod h1:ieEXykM67iT8lTvEWBh6fhpH4B23kB9OMKPdIBmgUqA= | ||||
| github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||
| github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | ||||
| github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= | ||||
| github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= | ||||
| github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= | ||||
| github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||
| github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/leporo/sqlf v1.3.0 h1:nAkuPYxMIJg/sUmcd1h4avV5iYo8tBTGEGOIR4BIZO8= | ||||
| github.com/leporo/sqlf v1.3.0/go.mod h1:f4dHqIi1+nLl6k1IsNQ8QIEbGWK49th2ei1IxTXk+2E= | ||||
| github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||||
| github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= | ||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= | ||||
| github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= | ||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||
| github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= | ||||
| github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= | ||||
| github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= | ||||
| github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||
| github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= | ||||
| github.com/mhale/smtpd v0.8.0 h1:5JvdsehCg33PQrZBvFyDMMUDQmvbzVpZgKob7eYBJc0= | ||||
| github.com/mhale/smtpd v0.8.0/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4= | ||||
| github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||||
| github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||||
| 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/ostafen/clover/v2 v2.0.0-alpha.2 h1:PgOWohvpj4qNCyASJ7Q8Ke8ld/wsoi+dQJ05b1ebwus= | ||||
| github.com/ostafen/clover/v2 v2.0.0-alpha.2/go.mod h1:7UyIG46NglzTDRKB4LJiS/enXpuo67Lj05eM8mdhL6M= | ||||
| github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||||
| github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= | ||||
| github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= | ||||
| github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.3.1 h1:SDPP7SHNl1L7KrEFCSJslJ/DM9DT02Nq2C61XrfHMmk= | ||||
| github.com/rivo/uniseg v0.3.1/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||||
| github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= | ||||
| github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||||
| github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= | ||||
| github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= | ||||
| github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= | ||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||
| github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= | ||||
| github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= | ||||
| @@ -156,143 +115,107 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE | ||||
| github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||||
| github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= | ||||
| github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= | ||||
| github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | ||||
| github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= | ||||
| github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | ||||
| github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= | ||||
| github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||||
| github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= | ||||
| github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= | ||||
| github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= | ||||
| github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||
| github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= | ||||
| github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= | ||||
| github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= | ||||
| github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= | ||||
| github.com/tg123/go-htpasswd v1.2.0 h1:UKp34m9H467/xklxUxU15wKRru7fwXoTojtxg25ITF0= | ||||
| github.com/tg123/go-htpasswd v1.2.0/go.mod h1:h7IzlfpvIWnVJhNZ0nQ9HaFxHb7pn5uFJYLlEUJa2sM= | ||||
| github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | ||||
| github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= | ||||
| github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= | ||||
| github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= | ||||
| github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= | ||||
| github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | ||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= | ||||
| github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= | ||||
| github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= | ||||
| go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= | ||||
| go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= | ||||
| golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= | ||||
| golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||
| golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||||
| golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= | ||||
| golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= | ||||
| golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= | ||||
| golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= | ||||
| golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= | ||||
| golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= | ||||
| golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b h1:3ogNYyK4oIQdIKzTu68hQrr4iuVxF3AxKl9Aj/eDrw0= | ||||
| golang.org/x/net v0.0.0-20220802222814-0bcc04d9c69b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= | ||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= | ||||
| golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= | ||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704 h1:Y7NOhdqIOU8kYI7BxsgL38d0ot0raxvcW+EMQU2QrT4= | ||||
| golang.org/x/sys v0.0.0-20220803195053-6e608f9ce704/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= | ||||
| golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= | ||||
| golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||
| golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||||
| golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= | ||||
| golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | ||||
| golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= | ||||
| golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= | ||||
| google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||||
| google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= | ||||
| google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= | ||||
| google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | ||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||
| google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= | ||||
| google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= | ||||
| google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= | ||||
| google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= | ||||
| google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||||
| google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= | ||||
| google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= | ||||
| google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= | ||||
| google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= | ||||
| google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | ||||
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= | ||||
| lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= | ||||
| lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= | ||||
| modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= | ||||
| modernc.org/cc/v3 v3.36.3 h1:uISP3F66UlixxWEcKuIWERa4TwrZENHSL8tWxZz8bHg= | ||||
| modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= | ||||
| modernc.org/ccgo/v3 v3.16.9 h1:AXquSwg7GuMk11pIdw7fmO1Y/ybgazVkMhsZWCV0mHM= | ||||
| modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= | ||||
| modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= | ||||
| modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= | ||||
| modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= | ||||
| modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= | ||||
| modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= | ||||
| modernc.org/libc v1.17.1 h1:Q8/Cpi36V/QBfuQaFVeisEBs3WqoGAJprZzmf7TfEYI= | ||||
| modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= | ||||
| modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= | ||||
| modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= | ||||
| modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= | ||||
| modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= | ||||
| modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= | ||||
| modernc.org/memory v1.2.1 h1:dkRh86wgmq/bJu2cAS2oqBCz/KsMZU7TUM4CibQ7eBs= | ||||
| modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= | ||||
| modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= | ||||
| modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= | ||||
| modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= | ||||
| modernc.org/sqlite v1.18.1 h1:ko32eKt3jf7eqIkCgPAeHMBXw3riNSLhl2f3loEF7o8= | ||||
| modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= | ||||
| modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= | ||||
| modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= | ||||
| modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= | ||||
| modernc.org/tcl v1.13.1 h1:npxzTwFTZYM8ghWicVIX1cRWzj7Nd8i6AqqX2p+IYao= | ||||
| modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= | ||||
| modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= | ||||
| modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= | ||||
| modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM= | ||||
|   | ||||
| @@ -20,12 +20,8 @@ type messagesResult struct { | ||||
| } | ||||
|  | ||||
| // Return a list of available mailboxes | ||||
| func apiListMailboxes(w http.ResponseWriter, _ *http.Request) { | ||||
| 	res, err := storage.ListMailboxes() | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| func apiMailboxStats(w http.ResponseWriter, _ *http.Request) { | ||||
| 	res := storage.StatsGet() | ||||
|  | ||||
| 	bytes, _ := json.Marshal(res) | ||||
| 	w.Header().Add("Content-Type", "application/json") | ||||
| @@ -33,24 +29,15 @@ func apiListMailboxes(w http.ResponseWriter, _ *http.Request) { | ||||
| } | ||||
|  | ||||
| func apiListMailbox(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
|  | ||||
| 	if !storage.MailboxExists(mailbox) { | ||||
| 		fourOFour(w) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	start, limit := getStartLimit(r) | ||||
|  | ||||
| 	messages, err := storage.List(mailbox, start, limit) | ||||
| 	messages, err := storage.List(start, limit) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	stats := storage.StatsGet(mailbox) | ||||
| 	stats := storage.StatsGet() | ||||
|  | ||||
| 	var res messagesResult | ||||
|  | ||||
| @@ -72,25 +59,17 @@ func apiSearchMailbox(w http.ResponseWriter, r *http.Request) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	vars := mux.Vars(r) | ||||
| 	mailbox := vars["mailbox"] | ||||
|  | ||||
| 	if !storage.MailboxExists(mailbox) { | ||||
| 		fourOFour(w) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// we will only return up to 200 results | ||||
| 	start := 0 | ||||
| 	limit := 200 | ||||
| 	// limit := 200 | ||||
|  | ||||
| 	messages, err := storage.Search(mailbox, search, start, limit) | ||||
| 	messages, err := storage.Search(search) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	stats := storage.StatsGet(mailbox) | ||||
| 	stats := storage.StatsGet() | ||||
|  | ||||
| 	var res messagesResult | ||||
|  | ||||
| @@ -109,10 +88,9 @@ func apiSearchMailbox(w http.ResponseWriter, r *http.Request) { | ||||
| func apiOpenMessage(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
| 	id := vars["id"] | ||||
|  | ||||
| 	msg, err := storage.GetMessage(mailbox, id) | ||||
| 	msg, err := storage.GetMessage(id) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| @@ -127,11 +105,10 @@ func apiOpenMessage(w http.ResponseWriter, r *http.Request) { | ||||
| func apiDownloadAttachment(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
| 	id := vars["id"] | ||||
| 	partID := vars["partID"] | ||||
|  | ||||
| 	a, err := storage.GetAttachmentPart(mailbox, id, partID) | ||||
| 	a, err := storage.GetAttachmentPart(id, partID) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| @@ -150,12 +127,11 @@ func apiDownloadAttachment(w http.ResponseWriter, r *http.Request) { | ||||
| func apiDownloadSource(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
| 	id := vars["id"] | ||||
|  | ||||
| 	dl := r.FormValue("dl") | ||||
|  | ||||
| 	data, err := storage.GetMessageRaw(mailbox, id) | ||||
| 	data, err := storage.GetMessageRaw(id) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| @@ -170,11 +146,7 @@ func apiDownloadSource(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| // Delete all messages in the mailbox | ||||
| func apiDeleteAll(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
|  | ||||
| 	err := storage.DeleteAllMessages(mailbox) | ||||
| 	err := storage.DeleteAllMessages() | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| @@ -188,10 +160,9 @@ func apiDeleteAll(w http.ResponseWriter, r *http.Request) { | ||||
| func apiDeleteOne(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
| 	id := vars["id"] | ||||
|  | ||||
| 	err := storage.DeleteOneMessage(mailbox, id) | ||||
| 	err := storage.DeleteOneMessage(id) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| @@ -205,10 +176,9 @@ func apiDeleteOne(w http.ResponseWriter, r *http.Request) { | ||||
| func apiUnreadOne(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
| 	id := vars["id"] | ||||
|  | ||||
| 	err := storage.UnreadMessage(mailbox, id) | ||||
| 	err := storage.MarkUnread(id) | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
| @@ -220,11 +190,7 @@ func apiUnreadOne(w http.ResponseWriter, r *http.Request) { | ||||
|  | ||||
| // Mark single message as unread | ||||
| func apiMarkAllRead(w http.ResponseWriter, r *http.Request) { | ||||
| 	vars := mux.Vars(r) | ||||
|  | ||||
| 	mailbox := vars["mailbox"] | ||||
|  | ||||
| 	err := storage.MarkAllRead(mailbox) | ||||
| 	err := storage.MarkAllRead() | ||||
| 	if err != nil { | ||||
| 		httpError(w, err.Error()) | ||||
| 		return | ||||
|   | ||||
| @@ -34,17 +34,17 @@ func Listen() { | ||||
| 	go websockets.MessageHub.Run() | ||||
|  | ||||
| 	r := mux.NewRouter() | ||||
| 	r.HandleFunc("/api/mailboxes", middleWareFunc(apiListMailboxes)) | ||||
| 	r.HandleFunc("/api/{mailbox}/messages", middleWareFunc(apiListMailbox)) | ||||
| 	r.HandleFunc("/api/{mailbox}/search", middleWareFunc(apiSearchMailbox)) | ||||
| 	r.HandleFunc("/api/{mailbox}/delete", middleWareFunc(apiDeleteAll)) | ||||
| 	r.HandleFunc("/api/{mailbox}/events", apiWebsocket) | ||||
| 	r.HandleFunc("/api/{mailbox}/read", apiMarkAllRead) | ||||
| 	r.HandleFunc("/api/{mailbox}/{id}/source", middleWareFunc(apiDownloadSource)) | ||||
| 	r.HandleFunc("/api/{mailbox}/{id}/part/{partID}", middleWareFunc(apiDownloadAttachment)) | ||||
| 	r.HandleFunc("/api/{mailbox}/{id}/delete", middleWareFunc(apiDeleteOne)) | ||||
| 	r.HandleFunc("/api/{mailbox}/{id}/unread", middleWareFunc(apiUnreadOne)) | ||||
| 	r.HandleFunc("/api/{mailbox}/{id}", middleWareFunc(apiOpenMessage)) | ||||
| 	r.HandleFunc("/api/stats", middleWareFunc(apiMailboxStats)) | ||||
| 	r.HandleFunc("/api/messages", middleWareFunc(apiListMailbox)) | ||||
| 	r.HandleFunc("/api/search", middleWareFunc(apiSearchMailbox)) | ||||
| 	r.HandleFunc("/api/delete", middleWareFunc(apiDeleteAll)) | ||||
| 	r.HandleFunc("/api/events", apiWebsocket) | ||||
| 	r.HandleFunc("/api/read", apiMarkAllRead) | ||||
| 	r.HandleFunc("/api/{id}/source", middleWareFunc(apiDownloadSource)) | ||||
| 	r.HandleFunc("/api/{id}/part/{partID}", middleWareFunc(apiDownloadAttachment)) | ||||
| 	r.HandleFunc("/api/{id}/delete", middleWareFunc(apiDeleteOne)) | ||||
| 	r.HandleFunc("/api/{id}/unread", middleWareFunc(apiUnreadOne)) | ||||
| 	r.HandleFunc("/api/{id}", middleWareFunc(apiOpenMessage)) | ||||
| 	r.PathPrefix("/").Handler(middlewareHandler(http.FileServer(http.FS(serverRoot)))) | ||||
| 	http.Handle("/", r) | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,6 @@ export default { | ||||
| 	data() { | ||||
| 		return { | ||||
| 			currentPath: window.location.hash, | ||||
| 			mailbox: "catchall", | ||||
| 			items: [], | ||||
| 			limit: 50, | ||||
| 			total: 0, | ||||
| @@ -62,11 +61,11 @@ export default { | ||||
|             let self = this; | ||||
|             let params = {}; | ||||
|  | ||||
|             let uri = 'api/'+self.mailbox+'/messages'; | ||||
|             let uri = 'api/messages'; | ||||
|             if (self.search) { | ||||
|                 self.searching = true; | ||||
| 				self.items = []; | ||||
|                 uri = 'api/'+self.mailbox+'/search' | ||||
|                 uri = 'api/search' | ||||
|                 self.start = 0; // search is displayed on one page | ||||
|                 params['query'] = self.search; | ||||
|             } else { | ||||
| @@ -131,7 +130,7 @@ export default { | ||||
| 			let self = this; | ||||
|             let params = {}; | ||||
|  | ||||
|             let uri = 'api/' +  self.mailbox + '/' + self.currentPath | ||||
|             let uri = 'api/' + self.currentPath | ||||
| 			self.get(uri, params, function(response) { | ||||
| 				for (let i in self.items) { | ||||
| 					if (self.items[i].ID == self.currentPath) { | ||||
| @@ -149,7 +148,7 @@ export default { | ||||
| 						if (a.ContentID != '') { | ||||
| 							d.HTML = d.HTML.replace( | ||||
| 								new RegExp('cid:'+a.ContentID, 'g'),  | ||||
| 								window.location.origin+'/api/'+self.mailbox+'/'+d.ID+'/part/'+a.PartID | ||||
| 								window.location.origin+'/api/'+d.ID+'/part/'+a.PartID | ||||
| 							); | ||||
| 						} | ||||
| 					} | ||||
| @@ -161,7 +160,7 @@ export default { | ||||
| 						if (a.ContentID != '') { | ||||
| 							d.HTML = d.HTML.replace( | ||||
| 								new RegExp('cid:'+a.ContentID, 'g'),  | ||||
| 								window.location.origin+'/api/'+self.mailbox+'/'+d.ID+'/part/'+a.PartID | ||||
| 								window.location.origin+'/api/'+d.ID+'/part/'+a.PartID | ||||
| 							); | ||||
| 						} | ||||
| 					} | ||||
| @@ -173,7 +172,7 @@ export default { | ||||
|  | ||||
| 		deleteAll: function() { | ||||
| 			let self = this; | ||||
| 			let uri = 'api/' +  self.mailbox + '/delete' | ||||
| 			let uri = 'api/delete' | ||||
| 			self.get(uri, false, function(response) { | ||||
| 				self.reloadMessages(); | ||||
| 			}); | ||||
| @@ -184,7 +183,7 @@ export default { | ||||
| 			if (!self.message) { | ||||
| 				return false; | ||||
| 			} | ||||
| 			let uri = 'api/' +  self.mailbox + '/' + self.message.ID + '/delete' | ||||
| 			let uri = 'api/' + self.message.ID + '/delete' | ||||
| 			self.get(uri, false, function(response) { | ||||
| 				window.location.hash = ""; | ||||
| 				self.scrollInPlace = true; | ||||
| @@ -198,7 +197,7 @@ export default { | ||||
| 			if (!self.message) { | ||||
| 				return false; | ||||
| 			} | ||||
| 			let uri = 'api/' +  self.mailbox + '/' + self.message.ID + '/unread' | ||||
| 			let uri = 'api/' + self.message.ID + '/unread' | ||||
| 			self.get(uri, false, function(response) { | ||||
| 				window.location.hash = ""; | ||||
| 				self.scrollInPlace = true; | ||||
| @@ -208,7 +207,7 @@ export default { | ||||
|  | ||||
| 		markAllRead: function() { | ||||
| 			let self = this; | ||||
| 			let uri = 'api/' +  self.mailbox + '/read' | ||||
| 			let uri = 'api/read' | ||||
| 			self.get(uri, false, function(response) { | ||||
| 				window.location.hash = ""; | ||||
| 				self.scrollInPlace = true; | ||||
| @@ -219,7 +218,7 @@ export default { | ||||
| 		// websocket connect | ||||
|         connect: function () { | ||||
|             let wsproto = location.protocol == 'https:' ? 'wss' : 'ws'; | ||||
|             let ws = new WebSocket(wsproto + "://" + document.location.host + "/api/"+this.mailbox+"/events"); | ||||
|             let ws = new WebSocket(wsproto + "://" + document.location.host + "/api/events"); | ||||
|             let self = this; | ||||
|             ws.onmessage = function (e) { | ||||
| 				let response = JSON.parse(e.data); | ||||
| @@ -246,7 +245,6 @@ export default { | ||||
| 					self.scrollInPlace = true; | ||||
| 					self.loadMessages(); | ||||
| 				} | ||||
|  | ||||
|             } | ||||
|  | ||||
|             ws.onopen = function () { | ||||
| @@ -337,7 +335,7 @@ export default { | ||||
| 			<button class="btn btn-outline-secondary me-2" title="Mark unread" v-on:click="markUnread"> | ||||
| 				<i class="bi bi-envelope"></i> <span class="d-none d-md-inline">Mark unread</span> | ||||
| 			</button> | ||||
| 			<a :href="'api/' + mailbox + '/' + message.ID + '/source?dl=1'" class="btn btn-outline-secondary me-2 float-end" title="Download message"> | ||||
| 			<a :href="'api/' + message.ID + '/source?dl=1'" class="btn btn-outline-secondary me-2 float-end" title="Download message"> | ||||
| 				<i class="bi bi-file-arrow-down-fill"></i> <span class="d-none d-md-inline">Download</span> | ||||
| 			</a> | ||||
| 		</div> | ||||
| @@ -393,7 +391,7 @@ export default { | ||||
| 	<div class="row flex-fill" style="min-height:0"> | ||||
| 		<div class="d-none d-md-block col-lg-2 col-md-3 mh-100 position-relative" style="overflow-y: auto;"> | ||||
| 			<ul class="list-unstyled mt-3 mb-5"> | ||||
| 				<li v-if="isConnected" title="Messages will auto-load" class="mb-2"> | ||||
| 				<li v-if="isConnected" title="Messages will auto-load" class="mb-3"> | ||||
| 					<i class="bi bi-power text-success"></i> | ||||
| 					Connected | ||||
| 				</li> | ||||
| @@ -406,7 +404,7 @@ export default { | ||||
| 						<i class="bi bi-envelope me-1" v-if="isConnected"></i> | ||||
| 						<i class="bi bi-arrow-clockwise me-1" v-else></i> | ||||
| 						Inbox  | ||||
| 						<span class="position-absolute mt-2 ms-4 start-100 translate-middle badge rounded-pill text-bg-secondary" title="Unread messages" v-if="unread"> | ||||
| 						<span style="margin-top: -5px; margin-left: 5px;" class="position-absolute badge rounded-pill text-bg-secondary" title="Unread messages" v-if="unread"> | ||||
| 							{{ formatNumber(unread) }} | ||||
| 						</span> | ||||
| 					</a> | ||||
| @@ -484,7 +482,7 @@ export default { | ||||
| 				</div> | ||||
| 			</div> | ||||
|  | ||||
| 			<Message v-if="message" :message="message" :mailbox="mailbox"></Message> | ||||
| 			<Message v-if="message" :message="message"></Message> | ||||
| 		</div> | ||||
| 		<div id="loading" v-if="loading"> | ||||
| 			<div class="d-flex justify-content-center align-items-center h-100"> | ||||
|   | ||||
| @@ -5,8 +5,7 @@ import moment from 'moment' | ||||
|  | ||||
| export default { | ||||
| 	props: { | ||||
| 		message: Object, | ||||
| 		mailbox: Object, | ||||
| 		message: Object | ||||
| 	}, | ||||
| 	mixins: [commonMixins], | ||||
| 	data() { | ||||
| @@ -45,7 +44,7 @@ export default { | ||||
|  | ||||
| 		var tabEl = document.getElementById('nav-source-tab'); | ||||
| 		tabEl.addEventListener('shown.bs.tab', function (event) { | ||||
| 			self.srcURI = 'api/' + self.mailbox + '/' + self.message.ID + '/source'; | ||||
| 			self.srcURI = 'api/' + self.message.ID + '/source'; | ||||
| 		}); | ||||
| 	}, | ||||
| 	 | ||||
| @@ -181,7 +180,7 @@ export default { | ||||
| 			<div class="tab-pane fade" id="nav-mime" role="tabpanel" aria-labelledby="nav-mime-tab" | ||||
| 				tabindex="0"> | ||||
| 				<div v-if="allAttachments(message)" v-for="part in allAttachments(message)" class="mime-part mb-2"> | ||||
| 					<a :href="'api/'+mailbox+'/'+message.ID+'/part/'+part.PartID" type="button" | ||||
| 					<a :href="'api/'+message.ID+'/part/'+part.PartID" type="button" | ||||
| 						class="btn btn-outline-secondary btn-sm me-2" target="_blank"> | ||||
| 						<i class="bi bi-file-arrow-down-fill"></i> | ||||
| 						{{ part.FileName != '' ? part.FileName : '[ unknown ]' }} | ||||
|   | ||||
| @@ -49,7 +49,7 @@ func (h *Hub) Run() { | ||||
| 				close(client.send) | ||||
| 			} | ||||
| 		case message := <-h.Broadcast: | ||||
| 			logger.Log().Debugf("Message received: %s", message) | ||||
| 			// logger.Log().Debugf("[broadcast] %s", message) | ||||
| 			for client := range h.Clients { | ||||
| 				select { | ||||
| 				case client.send <- message: | ||||
|   | ||||
| @@ -19,7 +19,7 @@ func mailHandler(origin net.Addr, from string, to []string, data []byte) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if _, err := storage.Store(storage.DefaultMailbox, data); err != nil { | ||||
| 	if _, err := storage.Store(data); err != nil { | ||||
| 		// Value with size 4800709 exceeded 1048576 limit | ||||
| 		re := regexp.MustCompile(`(Value with size \d+ exceeded \d+ limit)`) | ||||
| 		tooLarge := re.FindStringSubmatch(err.Error()) | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -5,145 +5,104 @@ import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"math/rand" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/axllent/mailpit/config" | ||||
| 	"github.com/jhillyerd/enmime" | ||||
| 	"github.com/ostafen/clover/v2" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	testTextEmail []byte | ||||
| 	testMimeEmail []byte | ||||
| 	testRuns      = 1000 | ||||
| 	testRuns      = 100 | ||||
| ) | ||||
|  | ||||
| func TestTextEmailInserts(t *testing.T) { | ||||
| 	setup(false) | ||||
| 	t.Log("Testing memory storage") | ||||
| 	setup() | ||||
| 	defer Close() | ||||
|  | ||||
| 	t.Log("Testing text email storage") | ||||
|  | ||||
| RepeatTest: | ||||
| 	start := time.Now() | ||||
|  | ||||
| 	assertEqualStats(t, 0, 0) | ||||
|  | ||||
| 	for i := 0; i < testRuns; i++ { | ||||
| 		if _, err := Store(DefaultMailbox, testTextEmail); err != nil { | ||||
| 		if _, err := Store(testTextEmail); err != nil { | ||||
| 			t.Log("error ", err) | ||||
| 			t.Fail() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	count, err := Count(DefaultMailbox) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	assertEqual(t, CountTotal(), testRuns, "Incorrect number of text emails stored") | ||||
|  | ||||
| 	assertEqual(t, count, testRuns, "incorrect number of text emails stored") | ||||
|  | ||||
| 	t.Logf("inserted %d text emails in %s", testRuns, time.Since(start)) | ||||
| 	t.Logf("Inserted %d text emails in %s", testRuns, time.Since(start)) | ||||
|  | ||||
| 	assertEqualStats(t, testRuns, testRuns) | ||||
|  | ||||
| 	delStart := time.Now() | ||||
| 	if err := DeleteAllMessages(DefaultMailbox); err != nil { | ||||
| 	if err := DeleteAllMessages(); err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
|  | ||||
| 	count, err = Count(DefaultMailbox) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
|  | ||||
| 	assertEqual(t, count, 0, "incorrect number of text emails deleted") | ||||
| 	assertEqual(t, CountTotal(), 0, "incorrect number of text emails deleted") | ||||
|  | ||||
| 	t.Logf("deleted %d text emails in %s", testRuns, time.Since(delStart)) | ||||
|  | ||||
| 	assertEqualStats(t, 0, 0) | ||||
|  | ||||
| 	db.Close() | ||||
| 	if config.DataDir == "" { | ||||
| 		setup(true) | ||||
| 		t.Logf("Testing physical storage to %s", config.DataDir) | ||||
| 		defer os.RemoveAll(config.DataDir) | ||||
| 		goto RepeatTest | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestMimeEmailInserts(t *testing.T) { | ||||
| 	setup(false) | ||||
| 	t.Log("Testing memory storage") | ||||
| 	setup() | ||||
| 	defer Close() | ||||
|  | ||||
| 	t.Log("Testing mime email storage") | ||||
|  | ||||
| RepeatTest: | ||||
| 	start := time.Now() | ||||
|  | ||||
| 	assertEqualStats(t, 0, 0) | ||||
|  | ||||
| 	for i := 0; i < testRuns; i++ { | ||||
| 		if _, err := Store(DefaultMailbox, testMimeEmail); err != nil { | ||||
| 		if _, err := Store(testMimeEmail); err != nil { | ||||
| 			t.Log("error ", err) | ||||
| 			t.Fail() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	count, err := Count(DefaultMailbox) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	assertEqual(t, CountTotal(), testRuns, "Incorrect number of mime emails stored") | ||||
|  | ||||
| 	assertEqual(t, count, testRuns, "incorrect number of emails with mime attachments stored") | ||||
|  | ||||
| 	t.Logf("inserted %d emails with mime attachments in %s", testRuns, time.Since(start)) | ||||
| 	t.Logf("Inserted %d text emails in %s", testRuns, time.Since(start)) | ||||
|  | ||||
| 	assertEqualStats(t, testRuns, testRuns) | ||||
|  | ||||
| 	delStart := time.Now() | ||||
| 	if err := DeleteAllMessages(DefaultMailbox); err != nil { | ||||
| 	if err := DeleteAllMessages(); err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
|  | ||||
| 	count, err = Count(DefaultMailbox) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	assertEqual(t, CountTotal(), 0, "incorrect number of mime emails deleted") | ||||
|  | ||||
| 	assertEqual(t, count, 0, "incorrect number of emails with mime attachments deleted") | ||||
|  | ||||
| 	t.Logf("deleted %d emails with mime attachments in %s", testRuns, time.Since(delStart)) | ||||
| 	t.Logf("deleted %d mime emails in %s", testRuns, time.Since(delStart)) | ||||
|  | ||||
| 	assertEqualStats(t, 0, 0) | ||||
|  | ||||
| 	db.Close() | ||||
| 	if config.DataDir == "" { | ||||
| 		setup(true) | ||||
| 		t.Logf("Testing physical storage to %s", config.DataDir) | ||||
| 		defer os.RemoveAll(config.DataDir) | ||||
| 		goto RepeatTest | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestRetrieveMimeEmail(t *testing.T) { | ||||
| 	setup(false) | ||||
| 	t.Log("Testing memory storage") | ||||
| 	setup() | ||||
| 	defer Close() | ||||
|  | ||||
| RepeatTest: | ||||
| 	id, err := Store(DefaultMailbox, testMimeEmail) | ||||
| 	t.Log("Testing mime email retrieval") | ||||
|  | ||||
| 	id, err := Store(testMimeEmail) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
|  | ||||
| 	msg, err := GetMessage(DefaultMailbox, id) | ||||
| 	msg, err := GetMessage(id) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| @@ -160,79 +119,26 @@ RepeatTest: | ||||
| 	assertEqual(t, len(msg.Inline), 1, "incorrect number of inline attachments") | ||||
| 	assertEqual(t, msg.Inline[0].FileName, "inline-image.jpg", "inline attachment filename does not match") | ||||
|  | ||||
| 	attachmentData, err := GetAttachmentPart(DefaultMailbox, id, msg.Attachments[0].PartID) | ||||
| 	attachmentData, err := GetAttachmentPart(id, msg.Attachments[0].PartID) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	assertEqual(t, len(attachmentData.Content), msg.Attachments[0].Size, "attachment size does not match") | ||||
|  | ||||
| 	inlineData, err := GetAttachmentPart(DefaultMailbox, id, msg.Inline[0].PartID) | ||||
| 	inlineData, err := GetAttachmentPart(id, msg.Inline[0].PartID) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	assertEqual(t, len(inlineData.Content), msg.Inline[0].Size, "inline attachment size does not match") | ||||
|  | ||||
| 	db.Close() | ||||
|  | ||||
| 	if config.DataDir == "" { | ||||
| 		setup(true) | ||||
| 		t.Logf("Testing physical storage to %s", config.DataDir) | ||||
| 		defer os.RemoveAll(config.DataDir) | ||||
| 		goto RepeatTest | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDatabaseStats(t *testing.T) { | ||||
| 	setup(false) | ||||
| 	t.Log("Testing database stats") | ||||
| 	assertEqualStats(t, 0, 0) | ||||
|  | ||||
| 	for i := 0; i < 100; i++ { | ||||
| 		if _, err := Store(DefaultMailbox, testTextEmail); err != nil { | ||||
| 			t.Log("error ", err) | ||||
| 			t.Fail() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	assertEqualStats(t, 100, 100) | ||||
|  | ||||
| 	// mark 10 as read | ||||
| 	docs, err := db.FindAll( | ||||
| 		clover.NewQuery(DefaultMailbox). | ||||
| 			Limit(10), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
|  | ||||
| 	for _, d := range docs { | ||||
| 		_, err := GetMessage(DefaultMailbox, d.ObjectId()) | ||||
| 		if err != nil { | ||||
| 			t.Log("error ", err) | ||||
| 			t.Fail() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	assertEqualStats(t, 100, 90) | ||||
|  | ||||
| 	if err := MarkAllRead(DefaultMailbox); err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
|  | ||||
| 	assertEqualStats(t, 100, 0) | ||||
|  | ||||
| 	db.Close() | ||||
| } | ||||
|  | ||||
| func TestSearch(t *testing.T) { | ||||
| 	setup(false) | ||||
| 	t.Log("Testing memory storage") | ||||
| 	setup() | ||||
| 	defer Close() | ||||
|  | ||||
| RepeatTest: | ||||
| 	t.Log("Testing search") | ||||
| 	for i := 0; i < testRuns; i++ { | ||||
| 		msg := enmime.Builder(). | ||||
| 			From(fmt.Sprintf("From %d", i), fmt.Sprintf("from-%d@example.com", i)). | ||||
| @@ -253,7 +159,7 @@ RepeatTest: | ||||
| 			t.Fail() | ||||
| 		} | ||||
|  | ||||
| 		if _, err := Store(DefaultMailbox, buf.Bytes()); err != nil { | ||||
| 		if _, err := Store(buf.Bytes()); err != nil { | ||||
| 			t.Log("error ", err) | ||||
| 			t.Fail() | ||||
| 		} | ||||
| @@ -274,7 +180,7 @@ RepeatTest: | ||||
| 			search = fmt.Sprintf("\"the email body %d jdsauk dwqmdqw\"", i) | ||||
| 		} | ||||
|  | ||||
| 		summaries, err := Search(DefaultMailbox, search, 0, 10) | ||||
| 		summaries, err := Search(search) | ||||
| 		if err != nil { | ||||
| 			t.Log("error ", err) | ||||
| 			t.Fail() | ||||
| @@ -290,57 +196,43 @@ RepeatTest: | ||||
| 	} | ||||
|  | ||||
| 	// search something that will return 200 rsults | ||||
| 	summaries, err := Search(DefaultMailbox, "This is the email body", 0, 50) | ||||
| 	summaries, err := Search("This is the email body") | ||||
| 	if err != nil { | ||||
| 		t.Log("error ", err) | ||||
| 		t.Fail() | ||||
| 	} | ||||
| 	assertEqual(t, len(summaries), 50, "50 search results expected") | ||||
|  | ||||
| 	db.Close() | ||||
|  | ||||
| 	if config.DataDir == "" { | ||||
| 		setup(true) | ||||
| 		t.Logf("Testing physical storage to %s", config.DataDir) | ||||
| 		defer os.RemoveAll(config.DataDir) | ||||
| 		goto RepeatTest | ||||
| 	} | ||||
| 	assertEqual(t, len(summaries), testRuns, "search results expected") | ||||
| } | ||||
|  | ||||
| func BenchmarkImportText(b *testing.B) { | ||||
| 	setup(false) | ||||
| 	setup() | ||||
| 	defer Close() | ||||
|  | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		if _, err := Store(DefaultMailbox, testTextEmail); err != nil { | ||||
| 		if _, err := Store(testTextEmail); err != nil { | ||||
| 			b.Log("error ", err) | ||||
| 			b.Fail() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	db.Close() | ||||
| } | ||||
|  | ||||
| func BenchmarkImportMime(b *testing.B) { | ||||
| 	setup(false) | ||||
| 	setup() | ||||
| 	defer Close() | ||||
|  | ||||
| 	for i := 0; i < b.N; i++ { | ||||
| 		if _, err := Store(DefaultMailbox, testMimeEmail); err != nil { | ||||
| 		if _, err := Store(testMimeEmail); err != nil { | ||||
| 			b.Log("error ", err) | ||||
| 			b.Fail() | ||||
| 		} | ||||
| 	} | ||||
| 	db.Close() | ||||
|  | ||||
| } | ||||
|  | ||||
| func setup(dataDir bool) { | ||||
| func setup() { | ||||
| 	config.NoLogging = true | ||||
| 	config.MaxMessages = 0 | ||||
|  | ||||
| 	if dataDir { | ||||
| 		config.DataDir = fmt.Sprintf("%s-%d", path.Join(os.TempDir(), "mailpit-tests"), time.Now().UnixNano()) | ||||
| 	} else { | ||||
| 		config.DataDir = "" | ||||
| 	} | ||||
| 	config.DataFile = "" | ||||
|  | ||||
| 	if err := InitDB(); err != nil { | ||||
| 		panic(err) | ||||
| @@ -368,7 +260,7 @@ func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { | ||||
| } | ||||
|  | ||||
| func assertEqualStats(t *testing.T, total int, unread int) { | ||||
| 	s := StatsGet(DefaultMailbox) | ||||
| 	s := StatsGet() | ||||
| 	if total != s.Total { | ||||
| 		t.Fatal(fmt.Sprintf("Incorrect total mailbox stats: \"%d\" != \"%d\"", total, s.Total)) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										115
									
								
								storage/stats.go
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								storage/stats.go
									
									
									
									
									
								
							| @@ -1,115 +0,0 @@ | ||||
| package storage | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/axllent/mailpit/data" | ||||
| 	"github.com/axllent/mailpit/logger" | ||||
| 	"github.com/ostafen/clover/v2" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	mailboxStats = map[string]data.MailboxStats{} | ||||
| 	statsLock    = sync.RWMutex{} | ||||
| ) | ||||
|  | ||||
| // StatsGet returns the total/unread statistics for a mailbox | ||||
| func StatsGet(mailbox string) data.MailboxStats { | ||||
| 	mailbox = sanitizeMailboxName(mailbox) | ||||
|  | ||||
| 	statsLock.Lock() | ||||
| 	defer statsLock.Unlock() | ||||
| 	s, ok := mailboxStats[mailbox] | ||||
| 	if !ok { | ||||
| 		return data.MailboxStats{ | ||||
| 			Total:  0, | ||||
| 			Unread: 0, | ||||
| 		} | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
|  | ||||
| // Refresh will completely refresh the existing stats for a given mailbox | ||||
| func statsRefresh(mailbox string) error { | ||||
| 	logger.Log().Debugf("[stats] refreshing stats for %s", mailbox) | ||||
|  | ||||
| 	total, err := db.Count(clover.NewQuery(mailbox)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	unread, err := db.Count(clover.NewQuery(mailbox).Where(clover.Field("Read").IsFalse())) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	statsLock.Lock() | ||||
| 	mailboxStats[mailbox] = data.MailboxStats{ | ||||
| 		Total:  total, | ||||
| 		Unread: unread, | ||||
| 	} | ||||
| 	statsLock.Unlock() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func statsAddNewMessage(mailbox string) { | ||||
| 	statsLock.Lock() | ||||
| 	s, ok := mailboxStats[mailbox] | ||||
| 	if ok { | ||||
| 		mailboxStats[mailbox] = data.MailboxStats{ | ||||
| 			Total:  s.Total + 1, | ||||
| 			Unread: s.Unread + 1, | ||||
| 		} | ||||
| 	} | ||||
| 	statsLock.Unlock() | ||||
| } | ||||
|  | ||||
| // Delete one message from the totals. If the message was unread, | ||||
| // then it will also deduct one from the Unread status. | ||||
| func statsDeleteOneMessage(mailbox string, unread bool) { | ||||
| 	statsLock.Lock() | ||||
| 	s, ok := mailboxStats[mailbox] | ||||
| 	if ok { | ||||
| 		// deduct from the totals | ||||
| 		if s.Total > 0 { | ||||
| 			s.Total = s.Total - 1 | ||||
| 		} | ||||
| 		// only deduct if the original was unread | ||||
| 		if unread && s.Unread > 0 { | ||||
| 			s.Unread = s.Unread - 1 | ||||
| 		} | ||||
|  | ||||
| 		mailboxStats[mailbox] = data.MailboxStats{ | ||||
| 			Total:  s.Total, | ||||
| 			Unread: s.Unread, | ||||
| 		} | ||||
| 	} | ||||
| 	statsLock.Unlock() | ||||
| } | ||||
|  | ||||
| // Mark one message as read | ||||
| func statsReadOneMessage(mailbox string) { | ||||
| 	statsLock.Lock() | ||||
| 	s, ok := mailboxStats[mailbox] | ||||
| 	if ok { | ||||
| 		mailboxStats[mailbox] = data.MailboxStats{ | ||||
| 			Total:  s.Total, | ||||
| 			Unread: s.Unread - 1, | ||||
| 		} | ||||
| 	} | ||||
| 	statsLock.Unlock() | ||||
| } | ||||
|  | ||||
| // Mark one message as unread | ||||
| func statsUnreadOneMessage(mailbox string) { | ||||
| 	statsLock.Lock() | ||||
| 	s, ok := mailboxStats[mailbox] | ||||
| 	if ok { | ||||
| 		mailboxStats[mailbox] = data.MailboxStats{ | ||||
| 			Total:  s.Total, | ||||
| 			Unread: s.Unread + 1, | ||||
| 		} | ||||
| 	} | ||||
| 	statsLock.Unlock() | ||||
| } | ||||
							
								
								
									
										122
									
								
								storage/utils.go
									
									
									
									
									
								
							
							
						
						
									
										122
									
								
								storage/utils.go
									
									
									
									
									
								
							| @@ -1,7 +1,10 @@ | ||||
| package storage | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"database/sql" | ||||
| 	"net/mail" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| @@ -11,7 +14,7 @@ import ( | ||||
| 	"github.com/axllent/mailpit/server/websockets" | ||||
| 	"github.com/jhillyerd/enmime" | ||||
| 	"github.com/k3a/html2text" | ||||
| 	"github.com/ostafen/clover/v2" | ||||
| 	"github.com/leporo/sqlf" | ||||
| ) | ||||
|  | ||||
| // Return a header field as a []*mail.Address, or "null" is not found/empty | ||||
| @@ -47,55 +50,116 @@ func createSearchText(env *enmime.Envelope) string { | ||||
| 	return d | ||||
| } | ||||
|  | ||||
| // cleanString removed unwanted characters from stored search text and search queries | ||||
| // CleanString removes unwanted characters from stored search text and search queries | ||||
| func cleanString(str string) string { | ||||
| 	// remove/replace new lines | ||||
| 	re := regexp.MustCompile(`(\r?\n|\t|>|<|"|:|\,|;)`) | ||||
| 	str = re.ReplaceAllString(str, " ") | ||||
|  | ||||
| 	// remove duplicate whitespace and trim | ||||
| 	return strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(str)), " ")) | ||||
| } | ||||
|  | ||||
| // Auto-prune runs every minute to automatically delete oldest messages | ||||
| // if total is greater than the threshold | ||||
| func pruneCron() { | ||||
| func dbCron() { | ||||
| 	for { | ||||
| 		time.Sleep(60 * time.Second) | ||||
| 		mailboxes, err := db.ListCollections() | ||||
| 		if err != nil { | ||||
| 			logger.Log().Errorf("[db] %s", err) | ||||
| 		start := time.Now() | ||||
|  | ||||
| 		// check if database contains deleted data and has not beein in use | ||||
| 		// for 5 minutes, if so VACUUM | ||||
| 		currentTime := time.Now() | ||||
| 		diff := currentTime.Sub(dbLastAction) | ||||
| 		if dbDataDeleted && diff.Minutes() > 5 { | ||||
| 			dbDataDeleted = false | ||||
| 			_, err := db.Exec("VACUUM") | ||||
| 			if err == nil { | ||||
| 				elapsed := time.Since(start) | ||||
| 				logger.Log().Debugf("[db] compressed idle database in %s", elapsed) | ||||
| 			} | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		for _, m := range mailboxes { | ||||
| 			total, _ := db.Count(clover.NewQuery(m)) | ||||
| 			if total > config.MaxMessages { | ||||
| 				limit := total - config.MaxMessages | ||||
| 				if limit > 5000 { | ||||
| 					limit = 5000 | ||||
| 		if config.MaxMessages > 0 { | ||||
| 			q := sqlf.Select("ID"). | ||||
| 				From("mailbox"). | ||||
| 				OrderBy("Sort DESC"). | ||||
| 				Limit(5000). | ||||
| 				Offset(config.MaxMessages) | ||||
|  | ||||
| 			ids := []string{} | ||||
| 			if err := q.Query(nil, db, func(row *sql.Rows) { | ||||
| 				var id string | ||||
|  | ||||
| 				if err := row.Scan(&id); err != nil { | ||||
| 					logger.Log().Errorf("[db] %s", err.Error()) | ||||
| 					return | ||||
| 				} | ||||
| 				start := time.Now() | ||||
| 				if err := db.Delete(clover.NewQuery(m). | ||||
| 					Sort(clover.SortOption{Field: "Created", Direction: 1}). | ||||
| 					Limit(limit)); err != nil { | ||||
| 					logger.Log().Warnf("Error pruning %s: %s", m, err.Error()) | ||||
| 					continue | ||||
| 				} | ||||
| 				elapsed := time.Since(start) | ||||
| 				logger.Log().Infof("Pruned %d messages from %s in %s", limit, m, elapsed) | ||||
| 				_ = statsRefresh(m) | ||||
| 				if !strings.HasSuffix(m, "_data") { | ||||
| 					websockets.Broadcast("prune", nil) | ||||
| 				ids = append(ids, id) | ||||
|  | ||||
| 			}); err != nil { | ||||
| 				logger.Log().Errorf("[db] %s", err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			if len(ids) == 0 { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			tx, err := db.BeginTx(context.Background(), nil) | ||||
| 			if err != nil { | ||||
| 				logger.Log().Errorf("[db] %s", err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			args := make([]interface{}, len(ids)) | ||||
| 			for i, id := range ids { | ||||
| 				args[i] = id | ||||
| 			} | ||||
|  | ||||
| 			_, err = tx.Query(`DELETE FROM mailbox WHERE ID IN (?`+strings.Repeat(",?", len(ids)-1)+`)`, args...) // #nosec | ||||
| 			if err != nil { | ||||
| 				logger.Log().Errorf("[db] %s", err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			_, err = tx.Query(`DELETE FROM mailbox_data WHERE ID IN (?`+strings.Repeat(",?", len(ids)-1)+`)`, args...) // #nosec | ||||
| 			if err != nil { | ||||
| 				logger.Log().Errorf("[db] %s", err.Error()) | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			err = tx.Commit() | ||||
|  | ||||
| 			if err != nil { | ||||
| 				logger.Log().Errorf(err.Error()) | ||||
| 				if err := tx.Rollback(); err != nil { | ||||
| 					logger.Log().Errorf(err.Error()) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			dbDataDeleted = true | ||||
|  | ||||
| 			elapsed := time.Since(start) | ||||
| 			logger.Log().Debugf("[db] auto-pruned %d messages in %s", len(ids), elapsed) | ||||
|  | ||||
| 			websockets.Broadcast("prune", nil) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SanitizeMailboxName returns a clean mailbox name | ||||
| // allowing only `alphanumeric` characters and `-“ | ||||
| func sanitizeMailboxName(mailbox string) string { | ||||
| 	re := regexp.MustCompile(`[^a-zA-Z0-9\-]`) | ||||
| // IsFile returns whether a path is a file | ||||
| func isFile(path string) bool { | ||||
| 	info, err := os.Stat(path) | ||||
| 	if os.IsNotExist(err) || !info.Mode().IsRegular() { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return re.ReplaceAllString(mailbox, "") | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // escPercentChar replaces `%` with `%%` for SQL searches | ||||
| func escPercentChar(s string) string { | ||||
| 	return strings.ReplaceAll(s, "%", "%%") | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user