176 lines
5.6 KiB
Go
176 lines
5.6 KiB
Go
// Copyright (c) 2023 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package bridge_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/gluon/rfc822"
|
|
"github.com/ProtonMail/go-proton-api"
|
|
"github.com/ProtonMail/go-proton-api/server"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
|
go_imap "github.com/emersion/go-imap"
|
|
"github.com/emersion/go-sasl"
|
|
"github.com/emersion/go-smtp"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestBridge_HandleDraftsSendFromOtherClient(t *testing.T) {
|
|
getGluonHeaderID := func(literal []byte) (string, string) {
|
|
h, err := rfc822.NewHeader(literal)
|
|
require.NoError(t, err)
|
|
|
|
gluonID, ok := h.GetChecked("X-Pm-Gluon-Id")
|
|
require.True(t, ok)
|
|
|
|
externalID, ok := h.GetChecked("Message-Id")
|
|
require.True(t, ok)
|
|
|
|
return gluonID, externalID
|
|
}
|
|
|
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) {
|
|
_, _, err := s.CreateUser("imap", password)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = s.CreateUser("bar", password)
|
|
require.NoError(t, err)
|
|
|
|
// The initial user should be fully synced.
|
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, _ *bridge.Mocks) {
|
|
waiter := waitForIMAPServerReady(b)
|
|
defer waiter.Done()
|
|
|
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
|
defer done()
|
|
|
|
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, userID, (<-syncCh).UserID)
|
|
waiter.Wait()
|
|
|
|
info, err := b.GetUserInfo(userID)
|
|
require.NoError(t, err)
|
|
require.True(t, info.State == bridge.Connected)
|
|
|
|
client, err := eventuallyDial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
|
require.NoError(t, err)
|
|
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
|
|
defer func() { _ = client.Logout() }()
|
|
|
|
// Create first draft in client.
|
|
literal := fmt.Sprintf(`From: %v
|
|
To: %v
|
|
Date: Fri, 3 Feb 2023 01:04:32 +0100
|
|
Subject: Foo
|
|
|
|
Hello
|
|
`, info.Addresses[0], "bar@proton.local")
|
|
|
|
require.NoError(t, client.Append("Drafts", nil, time.Now(), strings.NewReader(literal)))
|
|
// Verify the draft is available in client.
|
|
require.Eventually(t, func() bool {
|
|
status, err := client.Status("Drafts", []go_imap.StatusItem{go_imap.StatusMessages})
|
|
require.NoError(t, err)
|
|
return status.Messages == 1
|
|
}, 2*time.Second, time.Second)
|
|
|
|
// Retrieve the new literal so we can have the Proton Message ID.
|
|
messages, err := clientFetch(client, "Drafts")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(messages))
|
|
|
|
newLiteral, err := io.ReadAll(messages[0].GetBody(must(go_imap.ParseBodySectionName("BODY[]"))))
|
|
require.NoError(t, err)
|
|
logrus.Info(string(newLiteral))
|
|
|
|
newLiteralID, newLiteralExternID := getGluonHeaderID(newLiteral)
|
|
|
|
// Modify new literal.
|
|
newLiteralModified := append(newLiteral, []byte(" world from client2")...) //nolint:gocritic
|
|
|
|
func() {
|
|
smtpClient, err := smtp.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(b.GetSMTPPort())))
|
|
require.NoError(t, err)
|
|
defer func() { _ = smtpClient.Close() }()
|
|
|
|
// Upgrade to TLS.
|
|
require.NoError(t, smtpClient.StartTLS(&tls.Config{InsecureSkipVerify: true}))
|
|
|
|
// Authorize with SASL PLAIN.
|
|
require.NoError(t, smtpClient.Auth(sasl.NewPlainClient(
|
|
info.Addresses[0],
|
|
info.Addresses[0],
|
|
string(info.BridgePass)),
|
|
))
|
|
|
|
// Send the message.
|
|
require.NoError(t, smtpClient.SendMail(
|
|
info.Addresses[0],
|
|
[]string{"bar@proton.local"},
|
|
bytes.NewReader(newLiteralModified),
|
|
))
|
|
}()
|
|
|
|
// Append message to Sent as the imap client would.
|
|
require.NoError(t, client.Append("Sent", nil, time.Now(), strings.NewReader(literal)))
|
|
|
|
// Verify the sent message gets updated with the new literal.
|
|
require.Eventually(t, func() bool {
|
|
// Check if sent message matches the latest draft.
|
|
messagesClient1, err := clientFetch(client, "Sent", "BODY[TEXT]", "BODY[]")
|
|
require.NoError(t, err)
|
|
|
|
if len(messagesClient1) != 1 {
|
|
return false
|
|
}
|
|
|
|
sentLiteral, err := io.ReadAll(messagesClient1[0].GetBody(must(go_imap.ParseBodySectionName("BODY[]"))))
|
|
require.NoError(t, err)
|
|
|
|
sentLiteralID, sentLiteralExternID := getGluonHeaderID(sentLiteral)
|
|
|
|
sentLiteralText, err := io.ReadAll(messagesClient1[0].GetBody(must(go_imap.ParseBodySectionName("BODY[TEXT]"))))
|
|
require.NoError(t, err)
|
|
|
|
sentLiteralStr := string(sentLiteralText)
|
|
|
|
literalMatches := sentLiteralStr == "Hello\r\n world from client2\r\n"
|
|
|
|
idIsDifferent := sentLiteralID != newLiteralID
|
|
|
|
externIDMatches := sentLiteralExternID == newLiteralExternID
|
|
|
|
return literalMatches && idIsDifferent && externIDMatches
|
|
}, 2*time.Second, time.Second)
|
|
})
|
|
}, server.WithMessageDedup())
|
|
}
|