diff --git a/go.mod b/go.mod index 9ab21d8b..c5f9b7dc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 github.com/Masterminds/semver/v3 v3.1.1 - github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0 + github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/ProtonMail/go-rfc5322 v0.11.0 github.com/ProtonMail/gopenpgp/v2 v2.4.10 @@ -38,7 +38,7 @@ require ( github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.0 github.com/urfave/cli/v2 v2.16.3 - gitlab.protontech.ch/go/liteapi v0.32.1 + gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b golang.org/x/exp v0.0.0-20220921164117-439092de6870 golang.org/x/net v0.1.0 golang.org/x/sys v0.1.0 diff --git a/go.sum b/go.sum index 26db26b0..2dfa6fdc 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,10 @@ github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkF github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0 h1:SsacIP40QP64FNZrBlm5XDLHZMIx0i36mUYmTSWI2Y4= github.com/ProtonMail/gluon v0.11.1-0.20221004153055-7d144337dbd0/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI= +github.com/ProtonMail/gluon v0.11.1-0.20221006085838-c527b37bb418 h1:YvWdTvj2s+ZqEy0HwCIRFnGcWRhF0OY0EuIiknGChwM= +github.com/ProtonMail/gluon v0.11.1-0.20221006085838-c527b37bb418/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI= +github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d h1:EgrtTr3sx5cHkkd8/fviO5YOIcT9iOTibqJek2uhwM0= +github.com/ProtonMail/gluon v0.11.1-0.20221006090633-634f8b906f8d/go.mod h1:9k3URQEASX9XSA+JEcukjIiK3S6aR9GzhLhwccy8AnI= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= @@ -465,6 +469,12 @@ github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0 github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= gitlab.protontech.ch/go/liteapi v0.32.1 h1:EiaLP+LkVVDqFxU6Uux4w5HHfPj6dSnOILJPzIny5l4= gitlab.protontech.ch/go/liteapi v0.32.1/go.mod h1:SVxEeF4uYYYpSlfeAj2ZqluVEP95pbZ8LyoieSxU0pM= +gitlab.protontech.ch/go/liteapi v0.33.0 h1:lkerCG7Y3Tn+ICPwVkyZxPu5jJjiTTfaYiuqUwXRF0E= +gitlab.protontech.ch/go/liteapi v0.33.0/go.mod h1:+70trwxSrBP1fU1m2q5wzhYkr9/NUrFGuBr6cKl3ixk= +gitlab.protontech.ch/go/liteapi v0.33.1 h1:Ks8YdojRwYTLTUmGLG9MzFvxNeiwYJYQEaTHUQjOvsA= +gitlab.protontech.ch/go/liteapi v0.33.1/go.mod h1:9nsslyEJn7Utbielp4c+hc7qT6hqIJ52aGFR/tX+tYk= +gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b h1:Obu2CCCYdVi3NJmoYf/iJco1mat6EzJezInKoUTo+Dc= +gitlab.protontech.ch/go/liteapi v0.33.2-0.20221006095946-fc4061f2140b/go.mod h1:9nsslyEJn7Utbielp4c+hc7qT6hqIJ52aGFR/tX+tYk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index 9e3fb782..0d7e1221 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -22,7 +22,7 @@ import ( "github.com/bradenaw/juniper/xslices" "github.com/stretchr/testify/require" "gitlab.protontech.ch/go/liteapi/server" - "gitlab.protontech.ch/go/liteapi/server/account" + "gitlab.protontech.ch/go/liteapi/server/backend" ) const ( @@ -39,7 +39,7 @@ var ( func init() { user.DefaultEventPeriod = 100 * time.Millisecond user.DefaultEventJitter = 0 - account.GenerateKey = tests.FastGenerateKey + backend.GenerateKey = tests.FastGenerateKey certs.GenerateCert = tests.FastGenerateCert } diff --git a/internal/user/user_test.go b/internal/user/user_test.go index 887f156d..877c1603 100644 --- a/internal/user/user_test.go +++ b/internal/user/user_test.go @@ -16,13 +16,13 @@ import ( "github.com/stretchr/testify/require" "gitlab.protontech.ch/go/liteapi" "gitlab.protontech.ch/go/liteapi/server" - "gitlab.protontech.ch/go/liteapi/server/account" + "gitlab.protontech.ch/go/liteapi/server/backend" ) func init() { user.DefaultEventPeriod = 100 * time.Millisecond user.DefaultEventJitter = 0 - account.GenerateKey = tests.FastGenerateKey + backend.GenerateKey = tests.FastGenerateKey certs.GenerateCert = tests.FastGenerateCert } diff --git a/tests/api_test.go b/tests/api_test.go index 2d686ec3..9c64e195 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -1,10 +1,7 @@ package tests import ( - "net/mail" - "github.com/Masterminds/semver/v3" - "github.com/ProtonMail/gluon/rfc822" "gitlab.protontech.ch/go/liteapi" "gitlab.protontech.ch/go/liteapi/server" ) @@ -23,8 +20,9 @@ type API interface { GetLabels(userID string) ([]liteapi.Label, error) CreateLabel(userID, name string, labelType liteapi.LabelType) (string, error) - GetMessages(userID string) ([]liteapi.Message, error) - CreateMessage(userID, addrID string, labelIDs []string, subject string, sender *mail.Address, toList, ccList, bccList []*mail.Address, decBody string, mimeType rfc822.MIMEType, read, starred bool) (string, error) + CreateMessage(userID, addrID string, literal []byte, flags liteapi.MessageFlag, unread, starred bool) (string, error) + LabelMessage(userID, messageID, labelID string) error + UnlabelMessage(userID, messageID, labelID string) error Close() } diff --git a/tests/bdd_test.go b/tests/bdd_test.go index d9c5f36b..2d2015da 100644 --- a/tests/bdd_test.go +++ b/tests/bdd_test.go @@ -28,12 +28,12 @@ import ( "github.com/ProtonMail/proton-bridge/v2/internal/user" "github.com/cucumber/godog" "github.com/stretchr/testify/require" - "gitlab.protontech.ch/go/liteapi/server/account" + "gitlab.protontech.ch/go/liteapi/server/backend" ) func init() { // Use the fast key generation for tests. - account.GenerateKey = FastGenerateKey + backend.GenerateKey = FastGenerateKey // Use the fast cert generation for tests. certs.GenerateCert = FastGenerateCert diff --git a/tests/features/smtp/send/one_account_to_another.feature b/tests/features/smtp/send/one_account_to_another.feature new file mode 100644 index 00000000..44aaeac6 --- /dev/null +++ b/tests/features/smtp/send/one_account_to_another.feature @@ -0,0 +1,142 @@ +Feature: SMTP sending two messages + Background: + Given there exists an account with username "user@pm.me" and password "password" + And there exists an account with username "other@pm.me" and password "other" + And bridge starts + And the user logs in with username "user@pm.me" and password "password" + And the user logs in with username "other@pm.me" and password "other" + + Scenario: Send from one account to the other + When user "user@pm.me" connects and authenticates SMTP client "1" + And SMTP client "1" sends the following message from "user@pm.me" to "other@pm.me": + """ + From: Bridge Test + To: Internal Bridge + Subject: One account to the other + + hello + + """ + Then it succeeds + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "One account to the other", + "Sender": { + "Name": "Bridge Test", + "Address": "user@pm.me" + }, + "ToList": [ + { + "Name": "Internal Bridge", + "Address": "other@pm.me" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/plain" + } + } + """ + And the body in the "POST" request to "/mail/v4/messages/.*" is: + """ + { + "Packages":[ + { + "Addresses":{ + "other@pm.me":{ + "Type":1 + } + }, + "Type":1, + "MIMEType":"text/plain" + } + ] + } + """ + When user "other@pm.me" connects and authenticates IMAP client "1" + Then IMAP client "1" eventually sees the following messages in "Inbox": + | from | to | subject | body | + | user@pm.me | other@pm.me | One account to the other | hello | + + Scenario: Send from one account to the other with attachments + When user "user@pm.me" connects and authenticates SMTP client "1" + And SMTP client "1" sends the following message from "user@pm.me" to "other@pm.me": + """ + From: Bridge Test + To: Internal Bridge + Subject: Plain with attachment internal + Content-Type: multipart/related; boundary=bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606 + + --bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606 + Content-Disposition: inline + Content-Transfer-Encoding: quoted-printable + Content-Type: text/plain; charset=utf-8 + + This is the body + + --bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606 + Content-Disposition: attachment; filename=outline-light-instagram-48.png + Content-Id: <9114fe6f0adfaf7fdf7a@protonmail.com> + Content-Transfer-Encoding: base64 + Content-Type: image/png + + iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAALVBMVEUAAAD///////////////// + //////////////////////////////////////+hSKubAAAADnRSTlMAgO8QQM+/IJ9gj1AwcIQd + OXUAAAGdSURBVDjLXJC9SgNBFIVPXDURTYhgIQghINgowyLYCAYtRFAIgtYhpAjYhC0srCRW6YIg + WNpoHVSsg/gEii+Qnfxq4DyDc3cyMfrBwl2+O+fOHTi8p7LS5RUf/9gpMKL7iT9sK47Q95ggpkzv + 1cvRcsGYNMYsmP+zKN27NR2vcDyTNVdfkOuuniNPMWafvIbljt+YoMEvW8y7lt+ARwhvrgPjhA0I + BTng7S1GLPlypBvtIBPidY4YBDJFdtnkscQ5JGaGqxC9i7jSDwcwnB8qHWBaQjw1ABI8wYgtVoG6 + 9pFkH8iZIiJeulFt4JLvJq8I5N2GMWYbHWDWzM3JZTMdeSWla0kW86FcuI0mfStiNKQ/AhEeh8h0 + YUTffFwrMTT5oSwdojIQ0UKcocgAKRH1HiqhFQmmJa5qRaYHNbRiSsOgslY0NdixItUTUWlZkedP + HXVyAgAIA1F0wP5btQZPIyTwvAqa/Fl4oacuP+e4XHAjSYpkQkxSiMX+T7FPoZJToSStzED70HCy + KE3NGCg4jJrC6Ti7AFwZLhnW0gMbzFZc0RmmeAAAAABJRU5ErkJggg== + --bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606-- + + """ + Then it succeeds + And the body in the "POST" request to "/mail/v4/messages" is: + """ + { + "Message": { + "Subject": "Plain with attachment internal", + "Sender": { + "Name": "Bridge Test" + }, + "ToList": [ + { + "Address": "other@pm.me", + "Name": "Internal Bridge" + } + ], + "CCList": [], + "BCCList": [], + "MIMEType": "text/plain" + } + } + """ + And the body in the "POST" request to "/mail/v4/messages/.*" is: + """ + { + "Packages":[ + { + "Addresses":{ + "other@pm.me":{ + "Type":1 + } + }, + "Type":1, + "MIMEType":"text/plain" + } + ] + } + """ + When user "user@pm.me" connects and authenticates IMAP client "1" + Then IMAP client "1" eventually sees the following messages in "Sent": + | from | to | subject | body | attachments | unread | + | user@pm.me | other@pm.me | Plain with attachment internal | This is the body | outline-light-instagram-48.png | false | + When user "other@pm.me" connects and authenticates IMAP client "2" + Then IMAP client "2" eventually sees the following messages in "Inbox": + | from | to | subject | body | attachments | unread | + | user@pm.me | other@pm.me | Plain with attachment internal | This is the body | outline-light-instagram-48.png | true | \ No newline at end of file diff --git a/tests/imap_test.go b/tests/imap_test.go index eb3a4fb7..8edeafcc 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -444,7 +444,7 @@ func clientFetch(client *client.Client, mailbox string) ([]*imap.Message, error) go func() { if err := client.Fetch( &imap.SeqSet{Set: []imap.Seq{{Start: 1, Stop: status.Messages}}}, - []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid}, + []imap.FetchItem{imap.FetchFlags, imap.FetchEnvelope, imap.FetchUid, "BODY.PEEK[]"}, resCh, ); err != nil { panic(err) diff --git a/tests/types_test.go b/tests/types_test.go index c9142a1e..db654d7e 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -4,15 +4,21 @@ import ( "fmt" "reflect" "strconv" + "strings" "time" + "github.com/ProtonMail/gluon/rfc822" + "github.com/ProtonMail/proton-bridge/v2/pkg/message" + "github.com/bradenaw/juniper/xslices" "github.com/cucumber/messages-go/v16" "github.com/emersion/go-imap" "golang.org/x/exp/slices" ) type Message struct { - Subject string `bdd:"subject"` + Subject string `bdd:"subject"` + Body string `bdd:"body"` + Attachments string `bdd:"attachments"` From string `bdd:"from"` To string `bdd:"to"` @@ -22,10 +28,60 @@ type Message struct { Unread bool `bdd:"unread"` } +func (msg Message) Build() []byte { + var b []byte + + if msg.From != "" { + b = append(b, "From: "+msg.From+"\r\n"...) + } + + if msg.To != "" { + b = append(b, "To: "+msg.To+"\r\n"...) + } + + if msg.CC != "" { + b = append(b, "Cc: "+msg.CC+"\r\n"...) + } + + if msg.BCC != "" { + b = append(b, "Bcc: "+msg.BCC+"\r\n"...) + } + + if msg.Subject != "" { + b = append(b, "Subject: "+msg.Subject+"\r\n"...) + } + + if msg.Body != "" { + b = append(b, "\r\n"+msg.Body+"\r\n"...) + } + + return b +} + func newMessageFromIMAP(msg *imap.Message) Message { + section, err := imap.ParseBodySectionName("BODY[]") + if err != nil { + panic(err) + } + + m, err := message.Parse(msg.GetBody(section)) + if err != nil { + panic(err) + } + + var body string + + if m.MIMEType == rfc822.TextPlain { + body = strings.TrimSpace(string(m.PlainBody)) + } else { + body = strings.TrimSpace(string(m.RichBody)) + } + message := Message{ - Subject: msg.Envelope.Subject, - Unread: slices.Contains(msg.Flags, imap.SeenFlag), + Subject: msg.Envelope.Subject, + Body: body, + Attachments: strings.Join(xslices.Map(m.Attachments, func(att message.Attachment) string { return att.Name }), ", "), + Unread: !slices.Contains(msg.Flags, imap.SeenFlag), } if len(msg.Envelope.From) > 0 { @@ -48,6 +104,14 @@ func newMessageFromIMAP(msg *imap.Message) Message { } func matchMessages(have, want []Message) error { + slices.SortFunc(have, func(a, b Message) bool { + return a.Subject < b.Subject + }) + + slices.SortFunc(want, func(a, b Message) bool { + return a.Subject < b.Subject + }) + if !IsSub(ToAny(have), ToAny(want)) { return fmt.Errorf("missing messages: %v", want) } diff --git a/tests/user_test.go b/tests/user_test.go index 335f36fa..0aa68bce 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -4,10 +4,8 @@ import ( "context" "errors" "fmt" - "net/mail" "time" - "github.com/ProtonMail/gluon/rfc822" "github.com/cucumber/godog" "github.com/google/uuid" "gitlab.protontech.ch/go/liteapi" @@ -120,20 +118,12 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, } for _, wantMessage := range wantMessages { - if _, err := s.t.api.CreateMessage( - userID, - addrID, - []string{mboxID}, - wantMessage.Subject, - &mail.Address{Address: wantMessage.From}, - []*mail.Address{{Address: wantMessage.To}}, - []*mail.Address{{Address: wantMessage.CC}}, - []*mail.Address{{Address: wantMessage.BCC}}, - "some body goes here", - rfc822.TextPlain, - wantMessage.Unread, - false, - ); err != nil { + messageID, err := s.t.api.CreateMessage(userID, addrID, wantMessage.Build(), liteapi.MessageFlagReceived, wantMessage.Unread, false) + if err != nil { + return err + } + + if err := s.t.api.LabelMessage(userID, messageID, mboxID); err != nil { return err } } @@ -147,20 +137,17 @@ func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username str mboxID := s.t.getMBoxID(userID, mailbox) for idx := 0; idx < count; idx++ { - if _, err := s.t.api.CreateMessage( - userID, - addrID, - []string{mboxID}, - fmt.Sprintf("subject %d", idx), - &mail.Address{Address: fmt.Sprintf("sender %d", idx)}, - []*mail.Address{{Address: fmt.Sprintf("recipient %d", idx)}}, - []*mail.Address{}, - []*mail.Address{}, - fmt.Sprintf("body %d", idx), - rfc822.TextPlain, - idx%2 == 0, - false, - ); err != nil { + messageID, err := s.t.api.CreateMessage(userID, addrID, Message{ + Subject: fmt.Sprintf("subject %d", idx), + To: fmt.Sprintf("to %d", idx), + From: fmt.Sprintf("from %d", idx), + Body: fmt.Sprintf("body %d", idx), + }.Build(), liteapi.MessageFlagReceived, idx%2 == 0, false) + if err != nil { + return err + } + + if err := s.t.api.LabelMessage(userID, messageID, mboxID); err != nil { return err } }