Skip to content

Commit e699d75

Browse files
committed
feat: add smtp and urn
1 parent 2506220 commit e699d75

16 files changed

+960
-0
lines changed

Diff for: go.mod

+6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ require (
88
firebase.google.com/go/v4 v4.15.2
99
github.com/creack/pty v1.1.24
1010
github.com/fatih/color v1.18.0
11+
github.com/go-playground/validator/v10 v10.19.0
1112
github.com/gofiber/fiber/v2 v2.52.6
1213
github.com/google/uuid v1.6.0
1314
github.com/jackc/pgx/v5 v5.7.2
15+
github.com/katallaxie/pkg v0.6.6
1416
github.com/openfga/go-sdk v0.6.5
1517
github.com/spf13/cobra v1.9.1
1618
github.com/spf13/pflag v1.0.6
@@ -41,12 +43,15 @@ require (
4143
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
4244
github.com/felixge/httpsnoop v1.0.4 // indirect
4345
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
46+
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
4447
github.com/ghodss/yaml v1.0.0 // indirect
4548
github.com/go-logr/logr v1.4.2 // indirect
4649
github.com/go-logr/stdr v1.2.2 // indirect
4750
github.com/go-openapi/jsonpointer v0.21.0 // indirect
4851
github.com/go-openapi/jsonreference v0.20.2 // indirect
4952
github.com/go-openapi/swag v0.23.0 // indirect
53+
github.com/go-playground/locales v0.14.1 // indirect
54+
github.com/go-playground/universal-translator v0.18.1 // indirect
5055
github.com/gobwas/glob v0.2.3 // indirect
5156
github.com/gogo/protobuf v1.3.2 // indirect
5257
github.com/golang/protobuf v1.5.4 // indirect
@@ -67,6 +72,7 @@ require (
6772
github.com/klauspost/compress v1.17.9 // indirect
6873
github.com/kr/pretty v0.3.1 // indirect
6974
github.com/kr/text v0.2.0 // indirect
75+
github.com/leodido/go-urn v1.4.0 // indirect
7076
github.com/mailru/easyjson v0.7.7 // indirect
7177
github.com/mattn/go-colorable v0.1.13 // indirect
7278
github.com/mattn/go-isatty v0.0.20 // indirect

Diff for: go.sum

+14
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
3232
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
3333
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
3434
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
35+
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
36+
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
3537
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
3638
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
3739
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -49,6 +51,14 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
4951
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
5052
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
5153
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
54+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
55+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
56+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
57+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
58+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
59+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
60+
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
61+
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
5262
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
5363
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
5464
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
@@ -97,6 +107,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
97107
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
98108
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
99109
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
110+
github.com/katallaxie/pkg v0.6.6 h1:+qQBCoz5vRYENaL/uwTlks15Px4GDCdhybAZFg8s79c=
111+
github.com/katallaxie/pkg v0.6.6/go.mod h1:FJNit/KFQGJ5o0XEJN711gfLVxNU1RlXFTboctkVP4A=
100112
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
101113
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
102114
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
@@ -108,6 +120,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
108120
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
109121
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
110122
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
123+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
124+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
111125
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
112126
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
113127
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=

Diff for: smtp/doc.go

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package smtp provides SMTP primitives.
2+
package smtp

Diff for: smtp/email.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package smtp
2+
3+
import (
4+
"github.com/katallaxie/pkg/ulid"
5+
)
6+
7+
// Header ...
8+
type Header string
9+
10+
// Subject ...
11+
const Subject Header = "Subject"
12+
13+
// From ...
14+
const From Header = "From"
15+
16+
// To ...
17+
const To Header = "To"
18+
19+
// Cc ...
20+
const Cc Header = "Cc"
21+
22+
// Bcc ...
23+
const Bcc Header = "Bcc"
24+
25+
// ReplyTo ...
26+
const ReplyTo Header = "Reply-To"
27+
28+
// Date ...
29+
const Date Header = "Date"
30+
31+
// Message ...
32+
type Message struct {
33+
// ID ...
34+
ID string `json:"id" yaml:"id"`
35+
// Headers ...
36+
Headers map[Header][]string `json:"headers" yaml:"headers"`
37+
}
38+
39+
// SetID ...
40+
func (m *Message) SetID(id string) {
41+
m.ID = id
42+
}
43+
44+
// NewMessage ...
45+
func NewMessage() (*Message, error) {
46+
id, err := ulid.NewReverse()
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
m := new(Message)
52+
m.SetID(id.String())
53+
m.Headers = map[Header][]string{}
54+
55+
return m, nil
56+
}

Diff for: smtp/email_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package smtp
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestNewMesage(t *testing.T) {
11+
t.Parallel()
12+
13+
m, err := NewMessage()
14+
require.NoError(t, err)
15+
assert.NotNil(t, m)
16+
assert.NotEmpty(t, m.ID)
17+
assert.Equal(t, map[Header][]string{}, m.Headers)
18+
}

Diff for: smtp/parser.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package smtp
2+
3+
// Verb is a command verb of SMTP.
4+
type Verb string
5+
6+
const (
7+
// HELO is the SMTP verb for HELO.
8+
HELO Verb = "HELO"
9+
// EHLO is the SMTP verb for EHLO.
10+
EHLO Verb = "EHLO"
11+
// MAIL is the SMTP verb for MAIL.
12+
MAIL Verb = "MAIL"
13+
// RCPT is the SMTP verb for RCPT.
14+
RCPT Verb = "RCPT"
15+
// DATA is the SMTP verb for DATA.
16+
DATA Verb = "DATA"
17+
// QUI is the SMTP verb for QUIT.
18+
QUIT Verb = "QUIT"
19+
// RSET is the SMTP verb for RSET.
20+
RSET Verb = "RSET"
21+
// NOOP is the SMTP verb for NOOP.
22+
NOOP Verb = "NOOP"
23+
// HELP is the SMTP verb for HELP.
24+
HELP Verb = "HELP"
25+
// STARTTLS is the SMTP verb for STARTTLS.
26+
STARTTLS Verb = "STARTTLS"
27+
// AUTH is the SMTP verb for AUTH.
28+
AUTH Verb = "AUTH"
29+
)

Diff for: smtp/rfc3463.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package smtp
2+
3+
import "fmt"
4+
5+
// EnhancedStatusCodeUnknown is the default value for the enhanced status code.
6+
var EnhancedStatusCodeUnknown EnhancedMailSystemStatusCode = EnhancedMailSystemStatusCode{-1, -1, -1}
7+
8+
// EnhancedStatusCode is a data structure to contain enhanced
9+
// mail system status codes from RFC 3463 (https://datatracker.ietf.org/doc/html/rfc3463).
10+
type EnhancedStatusCode [3]int
11+
12+
// Class returns the class of the enhanced status code.
13+
func (e EnhancedStatusCode) Class() int {
14+
return e[0]
15+
}
16+
17+
// Subject returns the subject of the enhanced status code.
18+
func (e EnhancedStatusCode) Subject() int {
19+
return e[1]
20+
}
21+
22+
// Detail returns the detail of the enhanced status code.
23+
func (e EnhancedStatusCode) Detail() int {
24+
return e[2]
25+
}
26+
27+
// String returns the string representation of the enhanced status code.
28+
func (e EnhancedMailSystemStatusCode) String() string {
29+
return fmt.Sprintf("%v.%v.%v", e[0], e[1], e[2])
30+
}
31+
32+
const (
33+
// Signals a positive delivery action.
34+
EnhancedStatusCodeClassSuccess int = 2
35+
// Signals that there is a temporary failure in positively delivery action.
36+
EnhancedStatusCodeClassPersistentTransientFailure int = 4
37+
// Signals that there is a permanent failure in the delivery action.
38+
EnhancedStatusCodeClassPermanentFailure int = 5
39+
)

Diff for: smtp/rfc5321.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package smtp
2+
3+
// ReplyCode is the status code for the SMTP server.
4+
type ReplyCode int
5+
6+
const (
7+
// ReplyCodeSystemStatus is the status code for the system status.
8+
ReplyCodeSystemStatus = 211
9+
// ReplyCodeHelpMessage is the status code for the help message.
10+
ReplyCodeHelpMessage = 214
11+
// ReplyCodeServiceReady is the status code for the service ready.
12+
ReplyCodeServiceReady = 220
13+
// ReplyCodeServiceClosing is the status code for the service closing.
14+
ReplyCodeServiceClosing = 221
15+
// ReplyCodeAuthenticationSuccessful is the status code for the authentication successful.
16+
ReplyCodeMailActionOkay = 250
17+
// ReplyCodeUserNotLocal is the status code for the user not local.
18+
ReplyCodeMailActionCompleted = 250
19+
// ReplyCodeCannotVerifyUser is the status code for the cannot verify user.
20+
ReplyCodeUserNotLocal = 251
21+
// ReplyCodeStartMailInput is the status code for the start mail input.
22+
ReplyCodeCannotVerifyUser = 252
23+
ReplyCodeStartMailInput = 354
24+
ReplyCodeServiceNotAvailable = 421
25+
ReplyCodeMailboxUnavailable = 450
26+
ReplyCodeLocalError = 451
27+
ReplyCodeInsufficientStorage = 452
28+
ReplyCodeSyntaxError = 500
29+
ReplyCodeSyntaxErrorInParameters = 501
30+
ReplyCodeCommandNotImplemented = 502
31+
ReplyCodeCommandBadSequence = 503
32+
ReplyCodeCommandParameterNotImplemented = 504
33+
ReplyCodeRequestActionNotTaken = 550
34+
ReplyCodeUserNotLocalForThisHost = 551
35+
ReplyCodeRequestedActionAborted = 552
36+
ReplyCodeRequestedActionNotTaken = 553
37+
ReplyCodeTransactionFailed = 554
38+
ReplyCodeMailFromOrRcptToError = 555
39+
)

Diff for: smtp/server.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package smtp
2+
3+
import (
4+
"context"
5+
"net"
6+
"sync"
7+
8+
"github.com/katallaxie/pkg/group"
9+
"github.com/katallaxie/pkg/server"
10+
)
11+
12+
type mta struct {
13+
opts *Opts
14+
15+
sync.Mutex
16+
}
17+
18+
// MTA ...
19+
type MTA interface {
20+
server.Listener
21+
}
22+
23+
const (
24+
Network = "tcp"
25+
)
26+
27+
// Opt ...
28+
type Opt func(*Opts)
29+
30+
// Opts ...
31+
type Opts struct {
32+
Addr string
33+
}
34+
35+
// Configure ...
36+
func (o *Opts) Configure(opts ...Opt) {
37+
for _, opt := range opts {
38+
opt(o)
39+
}
40+
}
41+
42+
// New ...
43+
func New(opts ...Opt) *mta {
44+
options := new(Opts)
45+
options.Configure(opts...)
46+
47+
return &mta{
48+
opts: options,
49+
}
50+
}
51+
52+
// Start ...
53+
func (m *mta) Start(ctx context.Context, ready server.ReadyFunc, run server.RunFunc) func() error {
54+
return func() error {
55+
l, err := net.Listen(Network, m.opts.Addr)
56+
if err != nil {
57+
return err
58+
}
59+
60+
g, _ := group.WithContext(ctx)
61+
62+
for {
63+
c, err := l.Accept()
64+
if err != nil {
65+
return err
66+
}
67+
68+
s := NewSession(c)
69+
g.Run(s.Serve())
70+
}
71+
}
72+
}

Diff for: smtp/session.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package smtp
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"fmt"
7+
"net"
8+
"net/textproto"
9+
"sync"
10+
11+
"github.com/katallaxie/pkg/group"
12+
)
13+
14+
// Session ...
15+
type Session struct {
16+
// Text is the textproto.Conn of the connection.
17+
Text *textproto.Conn
18+
// keep a reference to the connection so it can be used to create a TLS
19+
// connection later
20+
conn net.Conn
21+
// tls indicates whether the connection is already in TLS mode
22+
tls bool
23+
24+
sync.Mutex
25+
}
26+
27+
// NewSession...
28+
func NewSession(conn net.Conn) *Session {
29+
s := new(Session)
30+
s.Text = textproto.NewConn(conn)
31+
s.conn = conn
32+
_, s.tls = conn.(*tls.Conn)
33+
34+
return s
35+
}
36+
37+
// Serve ...
38+
func (s *Session) Serve() group.RunFunc {
39+
return func(ctx context.Context) {
40+
for {
41+
s.Text.PrintfLine("220 localhost ESMTP Service Ready")
42+
l, err := s.Text.ReadLine()
43+
if err != nil {
44+
return
45+
}
46+
47+
fmt.Println(l)
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)