mirror of
https://github.com/axllent/mailpit.git
synced 2026-06-28 06:56:06 +00:00
Chore: Replace lithammer/shortuuid with custom shortuuid implementation and update tests
This commit is contained in:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
# https://olegk.dev/github-actions-and-go
|
||||
run: gofmt -s -w . && git diff --exit-code
|
||||
- name: Run Go tests
|
||||
run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/htmlcheck ./internal/linkcheck -v
|
||||
run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/htmlcheck ./internal/linkcheck ./internal/shortuuid -v
|
||||
- name: Run Go benchmarking
|
||||
run: go test -p 1 ./internal/storage ./internal/html2text -bench=.
|
||||
|
||||
|
||||
3
go.mod
3
go.mod
@@ -9,12 +9,12 @@ require (
|
||||
github.com/axllent/semver v1.0.0
|
||||
github.com/goccy/go-yaml v1.19.2
|
||||
github.com/gomarkdown/markdown v0.0.0-20260412113850-134a5b2cce7f
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jhillyerd/enmime/v2 v2.3.0
|
||||
github.com/klauspost/compress v1.18.5
|
||||
github.com/kovidgoyal/imaging v1.8.21
|
||||
github.com/leporo/sqlf v1.4.0
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/rqlite/gorqlite v0.0.0-20250609141355-ac86a4a1c9a8
|
||||
@@ -41,7 +41,6 @@ require (
|
||||
github.com/fatih/color v1.19.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // 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
|
||||
|
||||
2
go.sum
2
go.sum
@@ -69,8 +69,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
|
||||
53
internal/shortuuid/shortuuid.go
Normal file
53
internal/shortuuid/shortuuid.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Package shortuuid provides a simple way to generate short, unique, alphanumeric identifiers.
|
||||
// The generated IDs are 22 characters long and consist of uppercase letters, lowercase letters, and digits.
|
||||
package shortuuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
length = 22
|
||||
nDigits = 10
|
||||
divisor = 839299365868340224 // 62^10, max power of 62 that fits in uint64
|
||||
)
|
||||
|
||||
// New returns a 22-character alphanumeric unique identifier.
|
||||
func New() string {
|
||||
id := uuid.New()
|
||||
num := [2]uint64{
|
||||
binary.BigEndian.Uint64(id[8:]),
|
||||
binary.BigEndian.Uint64(id[:8]),
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
var r uint64
|
||||
i := length - 1
|
||||
for num[1] > 0 || num[0] > 0 {
|
||||
num, r = quoRem64(num, divisor)
|
||||
for j := 0; j < nDigits && i >= 0; j++ {
|
||||
buf[i] = alphabet[r%62]
|
||||
r /= 62
|
||||
i--
|
||||
}
|
||||
}
|
||||
for ; i >= 0; i-- {
|
||||
buf[i] = alphabet[0]
|
||||
}
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// quoRem64 divides a 128-bit number (represented as [lo, hi] uint64) by v,
|
||||
// returning the quotient and remainder.
|
||||
func quoRem64(u [2]uint64, v uint64) ([2]uint64, uint64) {
|
||||
var q [2]uint64
|
||||
var r uint64
|
||||
q[1], r = bits.Div64(0, u[1], v)
|
||||
q[0], r = bits.Div64(r, u[0], v)
|
||||
return q, r
|
||||
}
|
||||
52
internal/shortuuid/shortuuid_test.go
Normal file
52
internal/shortuuid/shortuuid_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package shortuuid
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// alphanumeric matches IDs that contain only digits and ASCII letters.
|
||||
var alphanumeric = regexp.MustCompile(`^[0-9A-Za-z]+$`)
|
||||
|
||||
// TestLength verifies that every generated ID is exactly 22 characters long,
|
||||
// including when the UUID encodes to a value with leading zero-padding.
|
||||
func TestLength(t *testing.T) {
|
||||
for range 100 {
|
||||
id := New()
|
||||
if len(id) != length {
|
||||
t.Errorf("expected length %d, got %d: %q", length, len(id), id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAlphanumeric verifies that no ID contains hyphens, underscores, or any
|
||||
// other non-alphanumeric character that would be unsafe in a URL path segment.
|
||||
func TestAlphanumeric(t *testing.T) {
|
||||
for range 100 {
|
||||
id := New()
|
||||
if !alphanumeric.MatchString(id) {
|
||||
t.Errorf("non-alphanumeric characters in ID: %q", id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnique verifies that IDs are unique across a large sample. Collisions are
|
||||
// cryptographically implausible given the 122-bit UUID entropy, so any hit here
|
||||
// indicates a bug in the encoding (e.g. truncation, constant output).
|
||||
func TestUnique(t *testing.T) {
|
||||
seen := make(map[string]struct{}, 1000000)
|
||||
for range 1000000 {
|
||||
id := New()
|
||||
if _, exists := seen[id]; exists {
|
||||
t.Fatalf("duplicate ID generated: %q", id)
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkNew measures the cost of generating a single ID, including UUID generation.
|
||||
func BenchmarkNew(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_ = New()
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,11 @@ import (
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/auth"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/shortuuid"
|
||||
"github.com/axllent/mailpit/internal/stats"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/axllent/mailpit/internal/tools"
|
||||
"github.com/axllent/mailpit/server/websockets"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ import (
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/shortuuid"
|
||||
"github.com/axllent/mailpit/internal/tools"
|
||||
"github.com/axllent/mailpit/server/webhook"
|
||||
"github.com/axllent/mailpit/server/websockets"
|
||||
"github.com/jhillyerd/enmime/v2"
|
||||
"github.com/leporo/sqlf"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
// Store will save an email to the database tables.
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
|
||||
"github.com/axllent/mailpit/config"
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/shortuuid"
|
||||
"github.com/axllent/mailpit/internal/smtpd"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
"github.com/axllent/mailpit/internal/tools"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
// ReleaseMessage (method: POST) will release a message via a pre-configured external SMTP server.
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/axllent/mailpit/internal/logger"
|
||||
"github.com/axllent/mailpit/internal/pop3"
|
||||
"github.com/axllent/mailpit/internal/prometheus"
|
||||
"github.com/axllent/mailpit/internal/shortuuid"
|
||||
"github.com/axllent/mailpit/internal/snakeoil"
|
||||
"github.com/axllent/mailpit/internal/stats"
|
||||
"github.com/axllent/mailpit/internal/storage"
|
||||
@@ -28,7 +29,6 @@ import (
|
||||
"github.com/axllent/mailpit/server/apiv1"
|
||||
"github.com/axllent/mailpit/server/handlers"
|
||||
"github.com/axllent/mailpit/server/websockets"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Reference in New Issue
Block a user