diff --git a/go.mod b/go.mod index 806608cf..49e78f2b 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.20 require ( github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 github.com/Masterminds/semver/v3 v3.2.0 - github.com/ProtonMail/gluon v0.16.1-0.20230628130101-52391e3cbf12 + github.com/ProtonMail/gluon v0.16.1-0.20230704083024-d901d16834de github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.4.1-0.20230628092916-81cb3f87f184 + github.com/ProtonMail/go-proton-api v0.4.1-0.20230704060229-a77a437ec052 github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible diff --git a/go.sum b/go.sum index 642c975b..c230fe19 100644 --- a/go.sum +++ b/go.sum @@ -23,12 +23,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= -github.com/ProtonMail/gluon v0.16.1-0.20230628083422-e2cfbe8c5823 h1:mOaTSgPEUu0KZjH1SN7ZxqWIljlZFvyST48coZlvC1o= -github.com/ProtonMail/gluon v0.16.1-0.20230628083422-e2cfbe8c5823/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= -github.com/ProtonMail/gluon v0.16.1-0.20230628112102-80c05f878b53 h1:38aw1TGmZlmY8l7uo+RRHnm97nZJz8xkEG//yPUS+9k= -github.com/ProtonMail/gluon v0.16.1-0.20230628112102-80c05f878b53/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= -github.com/ProtonMail/gluon v0.16.1-0.20230628130101-52391e3cbf12 h1:/3S2RdUUASLW/JD+17yeJ+tanU6yWD4yzQSEXdilMMk= -github.com/ProtonMail/gluon v0.16.1-0.20230628130101-52391e3cbf12/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= +github.com/ProtonMail/gluon v0.16.1-0.20230704083024-d901d16834de h1:th289W4w6aE6IAdHwZgfHIgtK1ew9ZvAdXnie0BtVuw= +github.com/ProtonMail/gluon v0.16.1-0.20230704083024-d901d16834de/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= 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-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= @@ -39,8 +35,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230628092916-81cb3f87f184 h1:gw8sgQMCIDS/lw5xbF2iqlTfvY0HhuafjlGsKcN3VsE= -github.com/ProtonMail/go-proton-api v0.4.1-0.20230628092916-81cb3f87f184/go.mod h1:+aTJoYu8bqzGECXL2DOdiZTZ64bGn3w0NC8VcFpJrFM= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230704060229-a77a437ec052 h1:uIq0RX4gU9PSZ9x5b2LmJUXNOuBXRRVSOkM1RGnSy68= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230704060229-a77a437ec052/go.mod h1:+aTJoYu8bqzGECXL2DOdiZTZ64bGn3w0NC8VcFpJrFM= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton h1:YS6M20yvjCJPR1r4ADW5TPn6rahs4iAyZaACei86bEc= @@ -270,7 +266,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 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/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/internal/bridge/config_status.go b/internal/bridge/config_status.go index 05b801e0..755e1627 100644 --- a/internal/bridge/config_status.go +++ b/internal/bridge/config_status.go @@ -24,7 +24,7 @@ import ( func (bridge *Bridge) ReportBugClicked() { safe.Lock(func() { for _, user := range bridge.users { - user.ReportBugSent() + user.ReportBugClicked() } }, bridge.usersLock) } diff --git a/internal/bridge/draft_test.go b/internal/bridge/draft_test.go new file mode 100644 index 00000000..85ccf3c3 --- /dev/null +++ b/internal/bridge/draft_test.go @@ -0,0 +1,175 @@ +// 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 . + +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()) +} diff --git a/internal/bridge/sync_test.go b/internal/bridge/sync_test.go index 00a0105e..6b330d4c 100644 --- a/internal/bridge/sync_test.go +++ b/internal/bridge/sync_test.go @@ -399,6 +399,10 @@ func createNumMessages(ctx context.Context, t *testing.T, c *proton.Client, addr } func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, labelID string, messages ...[]byte) []string { + return createMessagesWithFlags(ctx, t, c, addrID, labelID, 0, messages...) +} + +func createMessagesWithFlags(ctx context.Context, t *testing.T, c *proton.Client, addrID, labelID string, flags proton.MessageFlag, messages ...[]byte) []string { user, err := c.GetUser(ctx) require.NoError(t, err) @@ -417,6 +421,13 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, _, ok := addrKRs[addrID] require.True(t, ok) + var msgFlags proton.MessageFlag + if flags == 0 { + msgFlags = proton.MessageFlagReceived + } else { + msgFlags = flags + } + str, err := c.ImportMessages( ctx, addrKRs[addrID], @@ -427,7 +438,7 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, Metadata: proton.ImportMetadata{ AddressID: addrID, LabelIDs: []string{labelID}, - Flags: proton.MessageFlagReceived, + Flags: msgFlags, }, Message: message, } diff --git a/internal/configstatus/config_status.go b/internal/configstatus/config_status.go index 6f590936..429a10a6 100644 --- a/internal/configstatus/config_status.go +++ b/internal/configstatus/config_status.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "os" + "strconv" "time" "github.com/ProtonMail/proton-bridge/v3/internal/safe" @@ -74,8 +75,9 @@ func (status *ConfigurationStatus) Save() error { if err != nil { return err } - - err = json.NewEncoder(f).Encode(status.Data) + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + err = enc.Encode(status.Data) if err := f.Close(); err != nil { logrus.WithError(err).Error("Error while closing configstatus file.") } @@ -197,3 +199,23 @@ func (data *ConfigurationStatusData) hasLinkClicked(pos uint) bool { val := data.DataV1.ClickedLink & (1 << pos) return val > 0 } + +func (data *ConfigurationStatusData) clickedLinkToString() string { + var str = "" + var first = true + for i := 0; i < 64; i++ { + if data.hasLinkClicked(uint(i)) { + if !first { + str += "," + } else { + first = false + str += "[" + } + str += strconv.Itoa(i) + } + } + if str != "" { + str += "]" + } + return str +} diff --git a/internal/configstatus/configuration_abort.go b/internal/configstatus/configuration_abort.go index 29e5a147..36ad8f5c 100644 --- a/internal/configstatus/configuration_abort.go +++ b/internal/configstatus/configuration_abort.go @@ -17,16 +17,19 @@ package configstatus -import "time" +import ( + "strconv" + "time" +) type ConfigAbortValues struct { Duration int `json:"duration"` } type ConfigAbortDimensions struct { - ReportClick interface{} `json:"report_click"` - ReportSent interface{} `json:"report_sent"` - ClickedLink uint64 `json:"clicked_link"` + ReportClick string `json:"report_click"` + ReportSent string `json:"report_sent"` + ClickedLink string `json:"clicked_link"` } type ConfigAbortData struct { @@ -46,9 +49,9 @@ func (*ConfigAbortBuilder) New(data *ConfigurationStatusData) ConfigAbortData { Duration: int(time.Since(data.DataV1.PendingSince).Minutes()), }, Dimensions: ConfigSuccessDimensions{ - ReportClick: data.DataV1.ReportClick, - ReportSent: data.DataV1.ReportSent, - ClickedLink: data.DataV1.ClickedLink, + ReportClick: strconv.FormatBool(data.DataV1.ReportClick), + ReportSent: strconv.FormatBool(data.DataV1.ReportSent), + ClickedLink: data.clickedLinkToString(), }, } } diff --git a/internal/configstatus/configuration_abort_test.go b/internal/configstatus/configuration_abort_test.go index e076952a..c9851b2a 100644 --- a/internal/configstatus/configuration_abort_test.go +++ b/internal/configstatus/configuration_abort_test.go @@ -38,9 +38,9 @@ func TestConfigurationAbort_default(t *testing.T) { require.Equal(t, "bridge.any.configuration", req.MeasurementGroup) require.Equal(t, "bridge_config_abort", req.Event) require.Equal(t, 0, req.Values.Duration) - require.Equal(t, false, req.Dimensions.ReportClick) - require.Equal(t, false, req.Dimensions.ReportSent) - require.Equal(t, uint64(0), req.Dimensions.ClickedLink) + require.Equal(t, "false", req.Dimensions.ReportClick) + require.Equal(t, "false", req.Dimensions.ReportSent) + require.Equal(t, "", req.Dimensions.ClickedLink) } func TestConfigurationAbort_fed(t *testing.T) { @@ -69,7 +69,7 @@ func TestConfigurationAbort_fed(t *testing.T) { require.Equal(t, "bridge.any.configuration", req.MeasurementGroup) require.Equal(t, "bridge_config_abort", req.Event) require.Equal(t, 10, req.Values.Duration) - require.Equal(t, true, req.Dimensions.ReportClick) - require.Equal(t, false, req.Dimensions.ReportSent) - require.Equal(t, uint64(42), req.Dimensions.ClickedLink) + require.Equal(t, "true", req.Dimensions.ReportClick) + require.Equal(t, "false", req.Dimensions.ReportSent) + require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink) } diff --git a/internal/configstatus/configuration_progress.go b/internal/configstatus/configuration_progress.go index 8046e689..d25a6019 100644 --- a/internal/configstatus/configuration_progress.go +++ b/internal/configstatus/configuration_progress.go @@ -46,7 +46,7 @@ func (*ConfigProgressBuilder) New(data *ConfigurationStatusData) ConfigProgressD func numberOfDay(now, prev time.Time) int { if now.IsZero() || prev.IsZero() { - return 0 + return 1 } if now.Year() > prev.Year() { if now.YearDay() > prev.YearDay() { diff --git a/internal/configstatus/configuration_progress_test.go b/internal/configstatus/configuration_progress_test.go index ff0cd88c..e772af01 100644 --- a/internal/configstatus/configuration_progress_test.go +++ b/internal/configstatus/configuration_progress_test.go @@ -38,7 +38,7 @@ func TestConfigurationProgress_default(t *testing.T) { require.Equal(t, "bridge.any.configuration", req.MeasurementGroup) require.Equal(t, "bridge_config_progress", req.Event) require.Equal(t, 0, req.Values.NbDay) - require.Equal(t, 0, req.Values.NbDaySinceLast) + require.Equal(t, 1, req.Values.NbDaySinceLast) } func TestConfigurationProgress_fed(t *testing.T) { diff --git a/internal/configstatus/configuration_recovery.go b/internal/configstatus/configuration_recovery.go index dae2b96e..850a7dfc 100644 --- a/internal/configstatus/configuration_recovery.go +++ b/internal/configstatus/configuration_recovery.go @@ -18,6 +18,7 @@ package configstatus import ( + "strconv" "time" ) @@ -26,11 +27,11 @@ type ConfigRecoveryValues struct { } type ConfigRecoveryDimensions struct { - Autoconf string `json:"autoconf"` - ReportClick interface{} `json:"report_click"` - ReportSent interface{} `json:"report_sent"` - ClickedLink uint64 `json:"clicked_link"` - FailureDetails string `json:"failure_details"` + Autoconf string `json:"autoconf"` + ReportClick string `json:"report_click"` + ReportSent string `json:"report_sent"` + ClickedLink string `json:"clicked_link"` + FailureDetails string `json:"failure_details"` } type ConfigRecoveryData struct { @@ -51,9 +52,9 @@ func (*ConfigRecoveryBuilder) New(data *ConfigurationStatusData) ConfigRecoveryD }, Dimensions: ConfigRecoveryDimensions{ Autoconf: data.DataV1.Autoconf, - ReportClick: data.DataV1.ReportClick, - ReportSent: data.DataV1.ReportSent, - ClickedLink: data.DataV1.ClickedLink, + ReportClick: strconv.FormatBool(data.DataV1.ReportClick), + ReportSent: strconv.FormatBool(data.DataV1.ReportSent), + ClickedLink: data.clickedLinkToString(), FailureDetails: data.DataV1.FailureDetails, }, } diff --git a/internal/configstatus/configuration_recovery_test.go b/internal/configstatus/configuration_recovery_test.go index 722e5245..c05ac32a 100644 --- a/internal/configstatus/configuration_recovery_test.go +++ b/internal/configstatus/configuration_recovery_test.go @@ -39,9 +39,9 @@ func TestConfigurationRecovery_default(t *testing.T) { require.Equal(t, "bridge_config_recovery", req.Event) require.Equal(t, 0, req.Values.Duration) require.Equal(t, "", req.Dimensions.Autoconf) - require.Equal(t, false, req.Dimensions.ReportClick) - require.Equal(t, false, req.Dimensions.ReportSent) - require.Equal(t, uint64(0), req.Dimensions.ClickedLink) + require.Equal(t, "false", req.Dimensions.ReportClick) + require.Equal(t, "false", req.Dimensions.ReportSent) + require.Equal(t, "", req.Dimensions.ClickedLink) require.Equal(t, "", req.Dimensions.FailureDetails) } @@ -72,8 +72,8 @@ func TestConfigurationRecovery_fed(t *testing.T) { require.Equal(t, "bridge_config_recovery", req.Event) require.Equal(t, 10, req.Values.Duration) require.Equal(t, "Mr TBird", req.Dimensions.Autoconf) - require.Equal(t, true, req.Dimensions.ReportClick) - require.Equal(t, false, req.Dimensions.ReportSent) - require.Equal(t, uint64(42), req.Dimensions.ClickedLink) + require.Equal(t, "true", req.Dimensions.ReportClick) + require.Equal(t, "false", req.Dimensions.ReportSent) + require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink) require.Equal(t, "Not an error", req.Dimensions.FailureDetails) } diff --git a/internal/configstatus/configuration_success.go b/internal/configstatus/configuration_success.go index 2089a16e..84c421e1 100644 --- a/internal/configstatus/configuration_success.go +++ b/internal/configstatus/configuration_success.go @@ -18,6 +18,7 @@ package configstatus import ( + "strconv" "time" ) @@ -26,10 +27,10 @@ type ConfigSuccessValues struct { } type ConfigSuccessDimensions struct { - Autoconf string `json:"autoconf"` - ReportClick interface{} `json:"report_click"` - ReportSent interface{} `json:"report_sent"` - ClickedLink uint64 `json:"clicked_link"` + Autoconf string `json:"autoconf"` + ReportClick string `json:"report_click"` + ReportSent string `json:"report_sent"` + ClickedLink string `json:"clicked_link"` } type ConfigSuccessData struct { @@ -50,9 +51,9 @@ func (*ConfigSuccessBuilder) New(data *ConfigurationStatusData) ConfigSuccessDat }, Dimensions: ConfigSuccessDimensions{ Autoconf: data.DataV1.Autoconf, - ReportClick: data.DataV1.ReportClick, - ReportSent: data.DataV1.ReportSent, - ClickedLink: data.DataV1.ClickedLink, + ReportClick: strconv.FormatBool(data.DataV1.ReportClick), + ReportSent: strconv.FormatBool(data.DataV1.ReportSent), + ClickedLink: data.clickedLinkToString(), }, } } diff --git a/internal/configstatus/configuration_success_test.go b/internal/configstatus/configuration_success_test.go index 83b73f24..9f98990f 100644 --- a/internal/configstatus/configuration_success_test.go +++ b/internal/configstatus/configuration_success_test.go @@ -39,9 +39,9 @@ func TestConfigurationSuccess_default(t *testing.T) { require.Equal(t, "bridge_config_success", req.Event) require.Equal(t, 0, req.Values.Duration) require.Equal(t, "", req.Dimensions.Autoconf) - require.Equal(t, false, req.Dimensions.ReportClick) - require.Equal(t, false, req.Dimensions.ReportSent) - require.Equal(t, uint64(0), req.Dimensions.ClickedLink) + require.Equal(t, "false", req.Dimensions.ReportClick) + require.Equal(t, "false", req.Dimensions.ReportSent) + require.Equal(t, "", req.Dimensions.ClickedLink) } func TestConfigurationSuccess_fed(t *testing.T) { @@ -71,7 +71,7 @@ func TestConfigurationSuccess_fed(t *testing.T) { require.Equal(t, "bridge_config_success", req.Event) require.Equal(t, 10, req.Values.Duration) require.Equal(t, "Mr TBird", req.Dimensions.Autoconf) - require.Equal(t, true, req.Dimensions.ReportClick) - require.Equal(t, false, req.Dimensions.ReportSent) - require.Equal(t, uint64(42), req.Dimensions.ClickedLink) + require.Equal(t, "true", req.Dimensions.ReportClick) + require.Equal(t, "false", req.Dimensions.ReportSent) + require.Equal(t, "[1,3,5]", req.Dimensions.ClickedLink) } diff --git a/internal/user/config_status.go b/internal/user/config_status.go index b973989b..8a93c8f3 100644 --- a/internal/user/config_status.go +++ b/internal/user/config_status.go @@ -124,10 +124,12 @@ func (user *User) SendConfigStatusProgress() { if !user.configStatus.IsPending() { return } - var builder configstatus.ConfigProgressBuilder progress := builder.New(user.configStatus.Data) - if progress.Values.NbDaySinceLast == 0 || progress.Values.NbDay == 0 { + if progress.Values.NbDay == 0 { + return + } + if progress.Values.NbDaySinceLast == 0 { return } diff --git a/internal/user/events.go b/internal/user/events.go index 1a7067fd..277abbe2 100644 --- a/internal/user/events.go +++ b/internal/user/events.go @@ -514,9 +514,9 @@ func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proto case proton.EventUpdate, proton.EventUpdateFlags: // Draft update means to completely remove old message and upload the new data again, but we should // only do this if the event is of type EventUpdate otherwise label switch operations will not work. - if event.Message.IsDraft() && event.Action == proton.EventUpdate { - updates, err := user.handleUpdateDraftEvent( - logging.WithLogrusField(ctx, "action", "update draft"), + if (event.Message.IsDraft() || (event.Message.Flags&proton.MessageFlagSent != 0)) && event.Action == proton.EventUpdate { + updates, err := user.handleUpdateDraftOrSentMessage( + logging.WithLogrusField(ctx, "action", "update draft or sent message"), event, ) if err != nil { @@ -701,18 +701,19 @@ func (user *User) handleDeleteMessageEvent(_ context.Context, event proton.Messa }, user.updateChLock) } -func (user *User) handleUpdateDraftEvent(ctx context.Context, event proton.MessageEvent) ([]imap.Update, error) { +func (user *User) handleUpdateDraftOrSentMessage(ctx context.Context, event proton.MessageEvent) ([]imap.Update, error) { return safe.RLockRetErr(func() ([]imap.Update, error) { user.log.WithFields(logrus.Fields{ "messageID": event.ID, "subject": logging.Sensitive(event.Message.Subject), - }).Info("Handling draft updated event") + "isDraft": event.Message.IsDraft(), + }).Info("Handling draft or sent updated event") full, err := user.client.GetFullMessage(ctx, event.Message.ID, newProtonAPIScheduler(user.panicHandler), proton.NewDefaultAttachmentAllocator()) if err != nil { // If the message is not found, it means that it has been deleted before we could fetch it. if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity { - user.log.WithField("messageID", event.Message.ID).Warn("Cannot update draft: full message is missing on API") + user.log.WithField("messageID", event.Message.ID).Warn("Cannot update message: full message is missing on API") return nil, nil }