mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-11 13:16:53 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5321f8993 | |||
| bcf799732f | |||
| 13ba2182c2 | |||
| 0d25c607e7 | |||
| b3f8866ef7 | |||
| 9bb16dec48 | |||
| bdb35f1c1d | |||
| d421b5aa5a | |||
| 1ec05e8a6c | |||
| 5b941013de | |||
| a93ed35eee | |||
| 76469969f3 | |||
| 8b39ea4acb | |||
| 252ca9a5f9 | |||
| c4eb1a0f5b | |||
| 1e2f4e9ebb | |||
| 2a7aefac45 | |||
| ea39e2d842 | |||
| fc5879a204 | |||
| 5ae2229e37 | |||
| 12e5ce0ff0 | |||
| 5ef3774d11 | |||
| 654e816e6b | |||
| 7cad7bcddb | |||
| 136d514cf7 | |||
| 6e48345d54 | |||
| 8ebdb466f7 | |||
| 1ed7b690a5 | |||
| 5c28a3eda7 |
@ -38,6 +38,7 @@ stages:
|
|||||||
- cache
|
- cache
|
||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
|
- check
|
||||||
- mirror
|
- mirror
|
||||||
|
|
||||||
# Stage: CACHE
|
# Stage: CACHE
|
||||||
@ -107,7 +108,8 @@ test-integration:
|
|||||||
dependency-updates:
|
dependency-updates:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- make updates
|
- "echo 'NOTE: Do not run on go1.15 ( 'if...' can be removed once fully updated to go1.18)'"
|
||||||
|
- if [ 18 -le $(go version | cut -d. -f2 | cut -d " " -f1) ]; then make updates; fi
|
||||||
|
|
||||||
# Stage: BUILD
|
# Stage: BUILD
|
||||||
|
|
||||||
@ -138,9 +140,6 @@ build-qml:
|
|||||||
script:
|
script:
|
||||||
- make build
|
- make build
|
||||||
- git diff && git diff-index --quiet HEAD
|
- git diff && git diff-index --quiet HEAD
|
||||||
- curl -L https://services.nvd.nist.gov/rest/json/cves/1.0/
|
|
||||||
- gobinsec -verbose -wait -config utils/gobinsec_conf.yml
|
|
||||||
cmd/Desktop-Bridge/deploy/linux/proton-bridge
|
|
||||||
artifacts:
|
artifacts:
|
||||||
# Note: The latest artifacts for refs are locked against deletion, and kept
|
# Note: The latest artifacts for refs are locked against deletion, and kept
|
||||||
# regardless of the expiry time. Introduced in GitLab 13.0 behind a
|
# regardless of the expiry time. Introduced in GitLab 13.0 behind a
|
||||||
@ -235,6 +234,27 @@ build-windows-qa:
|
|||||||
artifacts:
|
artifacts:
|
||||||
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
||||||
|
|
||||||
|
# Stage: CHECK
|
||||||
|
check-gobinsec:
|
||||||
|
stage: check
|
||||||
|
only:
|
||||||
|
- branches
|
||||||
|
cache:
|
||||||
|
key: gobinsec-cache
|
||||||
|
paths:
|
||||||
|
- gobinsec-cache.yml
|
||||||
|
policy: pull-push
|
||||||
|
before_script:
|
||||||
|
- mkdir build
|
||||||
|
- tar -xzf bridge_linux_*.tgz -C build
|
||||||
|
- "echo api-key: \"${GOBINSEC_NVD_API_KEY}\" >> utils/gobinsec_conf.yml"
|
||||||
|
script:
|
||||||
|
- "[ ! -f ./gobinsec-cache.yml ] && wget bridgeteam.protontech.ch/bridgeteam/gobinsec-cache.yml"
|
||||||
|
- cat ./gobinsec-cache.yml
|
||||||
|
- gobinsec -wait -cache -config utils/gobinsec_conf.yml build/proton-bridge
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Stage: MIRROR
|
# Stage: MIRROR
|
||||||
|
|
||||||
mirror-repo:
|
mirror-repo:
|
||||||
|
|||||||
31
Changelog.md
31
Changelog.md
@ -2,6 +2,37 @@
|
|||||||
|
|
||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
## [Bridge 2.3.0] Nihonbashi
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-1739: Opt-out All Mail visibility in settings file.
|
||||||
|
* GODT-1794: CLI wording.
|
||||||
|
* GODT-1794: Add confirmation dialog and change wording.
|
||||||
|
* GODT-1741: GUI and CLI settings to change visibility of All Mail folder.
|
||||||
|
* GODT-1740: Opt-out All Mail visibility in settings file.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-1737: Improve logging during import.
|
||||||
|
* GODT-1754: Add logs for unilateral updates and SEARCH.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1840: Use Safe map for mailboxID cache.
|
||||||
|
* GODT-1795: Fix automatic installation of profile for AppleMail on macOS Ventura beta (qt 5).
|
||||||
|
* GODT-1833: Fix gobinsec cache.
|
||||||
|
* GODT-1799: Fix dependency link.
|
||||||
|
* Other: Update SSL certificate fingerprint for test.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 2.2.2] Millau
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Introduced gobinsec cache.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1743: Terminate running bridge if has old version.
|
||||||
|
* GODT-1743: Quit bridge when opening manual install.
|
||||||
|
|
||||||
|
|
||||||
## [Bridge 2.2.1] Millau
|
## [Bridge 2.2.1] Millau
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -10,7 +10,7 @@ TARGET_OS?=${GOOS}
|
|||||||
.PHONY: build build-nogui build-launcher versioner hasher
|
.PHONY: build build-nogui build-launcher versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=2.2.1+git
|
BRIDGE_APP_VERSION?=2.3.0+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
SRC_ICO:=bridge.ico
|
SRC_ICO:=bridge.ico
|
||||||
SRC_ICNS:=Bridge.icns
|
SRC_ICNS:=Bridge.icns
|
||||||
@ -166,7 +166,7 @@ update-qt-docs:
|
|||||||
LINTVER:="v1.39.0"
|
LINTVER:="v1.39.0"
|
||||||
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
LINTSRC:="https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh"
|
||||||
|
|
||||||
install-dev-dependencies: install-devel-tools install-linter install-go-mod-outdated
|
install-dev-dependencies: install-devel-tools install-linter
|
||||||
|
|
||||||
install-devel-tools: check-has-go
|
install-devel-tools: check-has-go
|
||||||
go get -v github.com/golang/mock/gomock
|
go get -v github.com/golang/mock/gomock
|
||||||
|
|||||||
@ -24,6 +24,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||||
@ -57,8 +58,9 @@ func (api *apiServer) ListenAndServe() {
|
|||||||
|
|
||||||
addr := api.getAddress()
|
addr := api.getAddress()
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
|
ReadHeaderTimeout: 5 * time.Second, // fix gosec G112 (vulnerability to [Slowloris](https://www.cloudflare.com/en-gb/learning/ddos/ddos-attack-tools/slowloris/) attack).
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("API listening at ", addr)
|
log.Info("API listening at ", addr)
|
||||||
|
|||||||
@ -57,7 +57,6 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||||
"github.com/allan-simon/go-singleinstance"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@ -153,9 +152,9 @@ func New( //nolint:funlen
|
|||||||
}
|
}
|
||||||
settingsObj := settings.New(settingsPath)
|
settingsObj := settings.New(settingsPath)
|
||||||
|
|
||||||
lock, err := singleinstance.CreateLockFile(locations.GetLockFile())
|
lock, err := checkSingleInstance(locations.GetLockFile(), settingsObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("%v is already running", appName)
|
logrus.WithError(err).Warnf("%v is already running", appName)
|
||||||
return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey))
|
return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIncrementRestartFlag(t *testing.T) {
|
func TestIncrementRestartFlag(t *testing.T) {
|
||||||
@ -47,3 +49,15 @@ func TestIncrementRestartFlag(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVersionLessThan(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
old := semver.MustParse("1.1.0")
|
||||||
|
current := semver.MustParse("1.1.1")
|
||||||
|
newer := semver.MustParse("1.1.2")
|
||||||
|
|
||||||
|
r.True(old.LessThan(current))
|
||||||
|
r.False(current.LessThan(current))
|
||||||
|
r.False(newer.LessThan(current))
|
||||||
|
}
|
||||||
|
|||||||
101
internal/app/base/singleinstance_unix.go
Normal file
101
internal/app/base/singleinstance_unix.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2022 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/>.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||||
|
"github.com/allan-simon/go-singleinstance"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkSingleInstance returns error if a bridge instance is already running
|
||||||
|
// This instance should be stop and window of running window should be brought
|
||||||
|
// to focus.
|
||||||
|
//
|
||||||
|
// For macOS and Linux when already running version is older than this instance
|
||||||
|
// it will kill old and continue with this new bridge (i.e. no error returned).
|
||||||
|
func checkSingleInstance(lockFilePath string, settingsObj *settings.Settings) (*os.File, error) {
|
||||||
|
if lock, err := singleinstance.CreateLockFile(lockFilePath); err == nil {
|
||||||
|
// Bridge is not runnig, continue normally
|
||||||
|
return lock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runningVersionIsOlder(settingsObj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := getPID(lockFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.Kill(pid, unix.SIGTERM); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to wait some time to release file lock
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
return singleinstance.CreateLockFile(lockFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPID(lockFilePath string) (int, error) {
|
||||||
|
file, err := os.Open(filepath.Clean(lockFilePath))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
rawPID := make([]byte, 10) // PID is probably up to 7 digits long, 10 should be enough
|
||||||
|
n, err := file.Read(rawPID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.Atoi(strings.TrimSpace(string(rawPID[:n])))
|
||||||
|
}
|
||||||
|
|
||||||
|
func runningVersionIsOlder(settingsObj *settings.Settings) error {
|
||||||
|
currentVer, err := semver.StrictNewVersion(constants.Version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
runningVer, err := semver.StrictNewVersion(settingsObj.Get(settings.LastVersionKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runningVer.LessThan(currentVer) {
|
||||||
|
return errors.New("running version is not older")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
32
internal/app/base/singleinstance_windows.go
Normal file
32
internal/app/base/singleinstance_windows.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) 2022 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/>.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||||
|
"github.com/allan-simon/go-singleinstance"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkSingleInstance(lockFilePath string, _ *settings.Settings) (*os.File, error) {
|
||||||
|
return singleinstance.CreateLockFile(lockFilePath)
|
||||||
|
}
|
||||||
@ -57,8 +57,9 @@ type Bridge struct {
|
|||||||
// Bridge's global errors list.
|
// Bridge's global errors list.
|
||||||
errors []error
|
errors []error
|
||||||
|
|
||||||
isFirstStart bool
|
isAllMailVisible bool
|
||||||
lastVersion string
|
isFirstStart bool
|
||||||
|
lastVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@ -92,15 +93,16 @@ func New(
|
|||||||
)
|
)
|
||||||
|
|
||||||
b := &Bridge{
|
b := &Bridge{
|
||||||
Users: u,
|
Users: u,
|
||||||
locations: locations,
|
locations: locations,
|
||||||
settings: setting,
|
settings: setting,
|
||||||
clientManager: clientManager,
|
clientManager: clientManager,
|
||||||
updater: updater,
|
updater: updater,
|
||||||
versioner: versioner,
|
versioner: versioner,
|
||||||
cacheProvider: cacheProvider,
|
cacheProvider: cacheProvider,
|
||||||
autostart: autostart,
|
autostart: autostart,
|
||||||
isFirstStart: false,
|
isFirstStart: false,
|
||||||
|
isAllMailVisible: setting.GetBool(settings.IsAllMailVisible),
|
||||||
}
|
}
|
||||||
|
|
||||||
if setting.GetBool(settings.FirstStartKey) {
|
if setting.GetBool(settings.FirstStartKey) {
|
||||||
@ -302,3 +304,14 @@ func (b *Bridge) GetLastVersion() string {
|
|||||||
func (b *Bridge) IsFirstStart() bool {
|
func (b *Bridge) IsFirstStart() bool {
|
||||||
return b.isFirstStart
|
return b.isFirstStart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAllMailVisible can be called extensively by IMAP. Therefore, it is better
|
||||||
|
// to cache the value instead of reading from settings file.
|
||||||
|
func (b *Bridge) IsAllMailVisible() bool {
|
||||||
|
return b.isAllMailVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bridge) SetIsAllMailVisible(isVisible bool) {
|
||||||
|
b.settings.SetBool(settings.IsAllMailVisible, isVisible)
|
||||||
|
b.isAllMailVisible = isVisible
|
||||||
|
}
|
||||||
|
|||||||
@ -55,6 +55,7 @@ const (
|
|||||||
AttachmentWorkers = "attachment_workers"
|
AttachmentWorkers = "attachment_workers"
|
||||||
ColorScheme = "color_scheme"
|
ColorScheme = "color_scheme"
|
||||||
RebrandingMigrationKey = "rebranding_migrated"
|
RebrandingMigrationKey = "rebranding_migrated"
|
||||||
|
IsAllMailVisible = "is_all_mail_visible"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
@ -110,4 +111,6 @@ func (s *Settings) setDefaultValues() {
|
|||||||
|
|
||||||
// By default, stick to STARTTLS. If the user uses catalina+applemail they'll have to change to SSL.
|
// By default, stick to STARTTLS. If the user uses catalina+applemail they'll have to change to SSL.
|
||||||
s.setDefault(SMTPSSLKey, "false")
|
s.setDefault(SMTPSSLKey, "false")
|
||||||
|
|
||||||
|
s.setDefault(IsAllMailVisible, "true")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,18 +24,24 @@ import (
|
|||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsCatalinaOrNewer checks whether the host is MacOS Catalina 10.15.x or higher.
|
// IsCatalinaOrNewer checks whether the host is macOS Catalina 10.15.x or higher.
|
||||||
func IsCatalinaOrNewer() bool {
|
func IsCatalinaOrNewer() bool {
|
||||||
return isThisDarwinNewerOrEqual(getMinCatalina())
|
return isThisDarwinNewerOrEqual(getMinCatalina())
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsBigSurOrNewer checks whether the host is MacOS BigSur 10.16.x or higher.
|
// IsBigSurOrNewer checks whether the host is macOS BigSur 10.16.x or higher.
|
||||||
func IsBigSurOrNewer() bool {
|
func IsBigSurOrNewer() bool {
|
||||||
return isThisDarwinNewerOrEqual(getMinBigSur())
|
return isThisDarwinNewerOrEqual(getMinBigSur())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsVenturaOrNewer checks whether the host is macOS BigSur 13.x or higher.
|
||||||
|
func IsVenturaOrNewer() bool {
|
||||||
|
return isThisDarwinNewerOrEqual(getMinVentura())
|
||||||
|
}
|
||||||
|
|
||||||
func getMinCatalina() *semver.Version { return semver.MustParse("19.0.0") }
|
func getMinCatalina() *semver.Version { return semver.MustParse("19.0.0") }
|
||||||
func getMinBigSur() *semver.Version { return semver.MustParse("20.0.0") }
|
func getMinBigSur() *semver.Version { return semver.MustParse("20.0.0") }
|
||||||
|
func getMinVentura() *semver.Version { return semver.MustParse("22.0.0") }
|
||||||
|
|
||||||
func isThisDarwinNewerOrEqual(minVersion *semver.Version) bool {
|
func isThisDarwinNewerOrEqual(minVersion *semver.Version) bool {
|
||||||
if runtime.GOOS != "darwin" {
|
if runtime.GOOS != "darwin" {
|
||||||
|
|||||||
@ -137,6 +137,23 @@ func New( //nolint:funlen
|
|||||||
})
|
})
|
||||||
fe.AddCmd(dohCmd)
|
fe.AddCmd(dohCmd)
|
||||||
|
|
||||||
|
// All mail visibility commands.
|
||||||
|
allMailCmd := &ishell.Cmd{
|
||||||
|
Name: "all-mail-visibility",
|
||||||
|
Help: "choose not to list the All Mail folder in your local client",
|
||||||
|
}
|
||||||
|
allMailCmd.AddCmd(&ishell.Cmd{
|
||||||
|
Name: "hide",
|
||||||
|
Help: "All Mail folder will not be listed in your local client",
|
||||||
|
Func: fe.hideAllMail,
|
||||||
|
})
|
||||||
|
allMailCmd.AddCmd(&ishell.Cmd{
|
||||||
|
Name: "show",
|
||||||
|
Help: "All Mail folder will be listed in your local client",
|
||||||
|
Func: fe.showAllMail,
|
||||||
|
})
|
||||||
|
fe.AddCmd(allMailCmd)
|
||||||
|
|
||||||
// Cache-On-Disk commands.
|
// Cache-On-Disk commands.
|
||||||
codCmd := &ishell.Cmd{
|
codCmd := &ishell.Cmd{
|
||||||
Name: "local-cache",
|
Name: "local-cache",
|
||||||
|
|||||||
@ -152,6 +152,32 @@ func (f *frontendCLI) disallowProxy(c *ishell.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *frontendCLI) hideAllMail(c *ishell.Context) {
|
||||||
|
if !f.bridge.IsAllMailVisible() {
|
||||||
|
f.Println("All Mail folder is not listed in your local client.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Println("All Mail folder is listed in your client right now.")
|
||||||
|
|
||||||
|
if f.yesNoQuestion("Do you want to hide All Mail folder") {
|
||||||
|
f.bridge.SetIsAllMailVisible(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
||||||
|
if f.bridge.IsAllMailVisible() {
|
||||||
|
f.Println("All Mail folder is listed in your local client.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Println("All Mail folder is not listed in your client right now.")
|
||||||
|
|
||||||
|
if f.yesNoQuestion("Do you want to show All Mail folder") {
|
||||||
|
f.bridge.SetIsAllMailVisible(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
|
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
|
||||||
if f.settings.GetBool(settings.CacheEnabledKey) {
|
if f.settings.GetBool(settings.CacheEnabledKey) {
|
||||||
f.Println("The local cache is already enabled.")
|
f.Println("The local cache is already enabled.")
|
||||||
|
|||||||
@ -36,7 +36,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bigSurPreferncesPane = "/System/Library/PreferencePanes/Profiles.prefPane"
|
bigSurPreferencesPane = "/System/Library/PreferencePanes/Profiles.prefPane"
|
||||||
|
venturaPreferencesPane = "x-apple.systempreferences:com.apple.preferences.configurationprofiles"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { //nolint:gochecknoinit
|
func init() { //nolint:gochecknoinit
|
||||||
@ -56,7 +57,13 @@ func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, use
|
|||||||
}
|
}
|
||||||
|
|
||||||
if useragent.IsBigSurOrNewer() {
|
if useragent.IsBigSurOrNewer() {
|
||||||
return execabs.Command("open", bigSurPreferncesPane, confPath).Run() //nolint:gosec G204: open command is safe, mobileconfig is generated by us
|
prefPane := bigSurPreferencesPane
|
||||||
|
|
||||||
|
if useragent.IsVenturaOrNewer() {
|
||||||
|
prefPane = venturaPreferencesPane
|
||||||
|
}
|
||||||
|
|
||||||
|
return execabs.Command("open", prefPane, confPath).Run() //nolint:gosec // G204 open command is safe, mobileconfig is generated by us
|
||||||
}
|
}
|
||||||
|
|
||||||
return execabs.Command("open", confPath).Run() //nolint:gosec G204: open command is safe, mobileconfig is generated by us
|
return execabs.Command("open", confPath).Run() //nolint:gosec G204: open command is safe, mobileconfig is generated by us
|
||||||
|
|||||||
@ -672,6 +672,10 @@ Window {
|
|||||||
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
||||||
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
||||||
}
|
}
|
||||||
|
RowLayout {
|
||||||
|
Label {colorScheme: root.colorScheme; text: "All Mail disabled:"}
|
||||||
|
Toggle {colorScheme: root.colorScheme; checked: root.isAllMailVisible; onClicked: root.isAllMailVisible = !root.isAllMailVisible}
|
||||||
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
||||||
TextField {
|
TextField {
|
||||||
@ -811,6 +815,13 @@ Window {
|
|||||||
root.isDoHEnabled = makeItActive
|
root.isDoHEnabled = makeItActive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool isAllMailVisible : true
|
||||||
|
function changeIsAllMailVisible(isVisible){
|
||||||
|
console.debug("-> All Mail Visible", isVisible, root.isAllMailVisible)
|
||||||
|
root.isAllMailVisible = isVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
property bool useSSLforSMTP: false
|
property bool useSSLforSMTP: false
|
||||||
function toggleUseSSLforSMTP(makeItActive){
|
function toggleUseSSLforSMTP(makeItActive){
|
||||||
console.debug("-> SMTP SSL", makeItActive, root.useSSLforSMTP)
|
console.debug("-> SMTP SSL", makeItActive, root.useSSLforSMTP)
|
||||||
|
|||||||
@ -156,6 +156,19 @@ SettingsView {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsItem {
|
||||||
|
id: allMail
|
||||||
|
visible: root._isAdvancedShown
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
text: qsTr("Show All Mail")
|
||||||
|
description: qsTr("Choose to list the All Mail folder in your local client.")
|
||||||
|
type: SettingsItem.Toggle
|
||||||
|
checked: root.backend.isAllMailVisible
|
||||||
|
onClicked: root.notifications.askChangeAllMailVisibility(root.backend.isAllMailVisible)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
SettingsItem {
|
SettingsItem {
|
||||||
id: ports
|
id: ports
|
||||||
visible: root._isAdvancedShown
|
visible: root._isAdvancedShown
|
||||||
|
|||||||
@ -110,6 +110,11 @@ Item {
|
|||||||
notification: root.notifications.resetBridge
|
notification: root.notifications.resetBridge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationDialog {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
notification: root.notifications.changeAllMailVisibility
|
||||||
|
}
|
||||||
|
|
||||||
NotificationDialog {
|
NotificationDialog {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
notification: root.notifications.deleteAccount
|
notification: root.notifications.deleteAccount
|
||||||
|
|||||||
@ -34,6 +34,7 @@ QtObject {
|
|||||||
signal askDisableLocalCache()
|
signal askDisableLocalCache()
|
||||||
signal askEnableLocalCache(var path)
|
signal askEnableLocalCache(var path)
|
||||||
signal askResetBridge()
|
signal askResetBridge()
|
||||||
|
signal askChangeAllMailVisibility(var isVisibleNow)
|
||||||
signal askDeleteAccount(var user)
|
signal askDeleteAccount(var user)
|
||||||
|
|
||||||
enum Group {
|
enum Group {
|
||||||
@ -72,6 +73,7 @@ QtObject {
|
|||||||
root.disableLocalCache,
|
root.disableLocalCache,
|
||||||
root.enableLocalCache,
|
root.enableLocalCache,
|
||||||
root.resetBridge,
|
root.resetBridge,
|
||||||
|
root.changeAllMailVisibility,
|
||||||
root.deleteAccount,
|
root.deleteAccount,
|
||||||
root.noKeychain,
|
root.noKeychain,
|
||||||
root.rebuildKeychain,
|
root.rebuildKeychain,
|
||||||
@ -193,6 +195,7 @@ QtObject {
|
|||||||
onTriggered: {
|
onTriggered: {
|
||||||
Qt.openUrlExternally(root.backend.landingPageLink)
|
Qt.openUrlExternally(root.backend.landingPageLink)
|
||||||
root.updateManualError.active = false
|
root.updateManualError.active = false
|
||||||
|
root.backend.quit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Action {
|
Action {
|
||||||
@ -839,6 +842,47 @@ QtObject {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property Notification changeAllMailVisibility: Notification {
|
||||||
|
title: root.changeAllMailVisibility.isVisibleNow ?
|
||||||
|
qsTr("Hide All Mail folder?") :
|
||||||
|
qsTr("Show All Mail folder?")
|
||||||
|
brief: title
|
||||||
|
icon: "./icons/ic-info-circle-filled.svg"
|
||||||
|
description: qsTr("Switching between showing and hiding the All Mail folder will require you to restart your client.")
|
||||||
|
type: Notification.NotificationType.Info
|
||||||
|
group: Notifications.Group.Configuration | Notifications.Group.Dialogs
|
||||||
|
|
||||||
|
property var isVisibleNow
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
onAskChangeAllMailVisibility: {
|
||||||
|
root.changeAllMailVisibility.isVisibleNow = isVisibleNow
|
||||||
|
root.changeAllMailVisibility.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action: [
|
||||||
|
Action {
|
||||||
|
id: allMail_change
|
||||||
|
text: root.changeAllMailVisibility.isVisibleNow ?
|
||||||
|
qsTr("Hide All Mail folder") :
|
||||||
|
qsTr("Show All Mail folder")
|
||||||
|
onTriggered: {
|
||||||
|
root.backend.changeIsAllMailVisible(!root.changeAllMailVisibility.isVisibleNow)
|
||||||
|
root.changeAllMailVisibility.active = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Action {
|
||||||
|
id: allMail_cancel
|
||||||
|
text: qsTr("Cancel")
|
||||||
|
onTriggered: {
|
||||||
|
root.changeAllMailVisibility.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
property Notification deleteAccount: Notification {
|
property Notification deleteAccount: Notification {
|
||||||
title: qsTr("Remove this account?")
|
title: qsTr("Remove this account?")
|
||||||
brief: title
|
brief: title
|
||||||
|
|||||||
@ -155,6 +155,9 @@ type QMLBackend struct {
|
|||||||
_ func() `signal:apiCertIssue`
|
_ func() `signal:apiCertIssue`
|
||||||
|
|
||||||
_ func(userID string) `signal:userChanged`
|
_ func(userID string) `signal:userChanged`
|
||||||
|
|
||||||
|
_ bool `property:"isAllMailVisible"`
|
||||||
|
_ func(isDisabled bool) `slot:"changeIsAllMailVisible"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QMLBackend) setup(f *FrontendQt) {
|
func (q *QMLBackend) setup(f *FrontendQt) {
|
||||||
@ -304,4 +307,11 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
|||||||
f.changeKeychain(k)
|
f.changeKeychain(k)
|
||||||
}()
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
q.SetIsAllMailVisible(f.bridge.IsAllMailVisible())
|
||||||
|
q.ConnectChangeIsAllMailVisible(func(isVisible bool) {
|
||||||
|
f.bridge.SetIsAllMailVisible(isVisible)
|
||||||
|
f.qml.SetIsAllMailVisible(isVisible)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,6 +92,8 @@ type Bridger interface {
|
|||||||
DisableAutostart() error
|
DisableAutostart() error
|
||||||
GetLastVersion() string
|
GetLastVersion() string
|
||||||
IsFirstStart() bool
|
IsFirstStart() bool
|
||||||
|
IsAllMailVisible() bool
|
||||||
|
SetIsAllMailVisible(bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type bridgeWrap struct {
|
type bridgeWrap struct {
|
||||||
|
|||||||
@ -93,10 +93,9 @@ func newIMAPBackend(
|
|||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
listWorkers int,
|
listWorkers int,
|
||||||
) *imapBackend {
|
) *imapBackend {
|
||||||
return &imapBackend{
|
ib := &imapBackend{
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
updates: newIMAPUpdates(),
|
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
|
|
||||||
users: map[string]*imapUser{},
|
users: map[string]*imapUser{},
|
||||||
@ -106,6 +105,8 @@ func newIMAPBackend(
|
|||||||
imapCacheLock: &sync.RWMutex{},
|
imapCacheLock: &sync.RWMutex{},
|
||||||
listWorkers: listWorkers,
|
listWorkers: listWorkers,
|
||||||
}
|
}
|
||||||
|
ib.updates = newIMAPUpdates(ib)
|
||||||
|
return ib
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ib *imapBackend) getUser(address string) (*imapUser, error) {
|
func (ib *imapBackend) getUser(address string) (*imapUser, error) {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ type cacheProvider interface {
|
|||||||
type bridger interface {
|
type bridger interface {
|
||||||
GetUser(query string) (bridgeUser, error)
|
GetUser(query string) (bridgeUser, error)
|
||||||
HasError(err error) bool
|
HasError(err error) bool
|
||||||
|
IsAllMailVisible() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type bridgeUser interface {
|
type bridgeUser interface {
|
||||||
|
|||||||
@ -197,7 +197,7 @@ func (im *imapMailbox) labelExistingMessage(msg storeMessageProvider) error { //
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, body []byte, imapFlags []string, date time.Time) error { //nolint:funlen
|
func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, body []byte, imapFlags []string, date time.Time) error { //nolint:funlen
|
||||||
im.log.Info("Importing external message")
|
im.log.WithField("size", len(body)).Info("Importing external message")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
seen bool
|
seen bool
|
||||||
@ -251,6 +251,7 @@ func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, b
|
|||||||
|
|
||||||
messageID, err := targetMailbox.ImportMessage(enc, seen, labelIDs, flags, time)
|
messageID, err := targetMailbox.ImportMessage(enc, seen, labelIDs, flags, time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.WithField("enc.size", len(enc)).Error("Import failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -332,7 +332,16 @@ func (im *imapMailbox) labelMessages(uid bool, seqSet *imap.SeqSet, targetLabel
|
|||||||
|
|
||||||
// SearchMessages searches messages. The returned list must contain UIDs if
|
// SearchMessages searches messages. The returned list must contain UIDs if
|
||||||
// uid is set to true, or sequence numbers otherwise.
|
// uid is set to true, or sequence numbers otherwise.
|
||||||
func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria) (ids []uint32, err error) { //nolint:gocyclo,funlen
|
func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria) (ids []uint32, err error) {
|
||||||
|
err = im.logCommand(func() error {
|
||||||
|
var searchError error
|
||||||
|
ids, searchError = im.searchMessages(isUID, criteria)
|
||||||
|
return searchError
|
||||||
|
}, "SEARCH", isUID, criteria.Format())
|
||||||
|
return ids, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imapMailbox) searchMessages(isUID bool, criteria *imap.SearchCriteria) (ids []uint32, err error) { //nolint:gocyclo,funlen
|
||||||
// Called from go-imap in goroutines - we need to handle panics for each function.
|
// Called from go-imap in goroutines - we need to handle panics for each function.
|
||||||
defer im.panicHandler.HandlePanic()
|
defer im.panicHandler.HandlePanic()
|
||||||
|
|
||||||
|
|||||||
44
internal/imap/map.go
Normal file
44
internal/imap/map.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2022 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 imap
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type safeMapOfStrings struct {
|
||||||
|
data map[string]string
|
||||||
|
mutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSafeMapOfString() safeMapOfStrings {
|
||||||
|
return safeMapOfStrings{
|
||||||
|
data: map[string]string{},
|
||||||
|
mutex: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *safeMapOfStrings) get(key string) string {
|
||||||
|
m.mutex.RLock()
|
||||||
|
defer m.mutex.RUnlock()
|
||||||
|
return m.data[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *safeMapOfStrings) set(key, value string) {
|
||||||
|
m.mutex.Lock()
|
||||||
|
defer m.mutex.Unlock()
|
||||||
|
m.data[key] = value
|
||||||
|
}
|
||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/store"
|
"github.com/ProtonMail/proton-bridge/v2/internal/store"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/pkg/algo"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/message"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/message"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||||
imap "github.com/emersion/go-imap"
|
imap "github.com/emersion/go-imap"
|
||||||
@ -42,14 +43,16 @@ type imapUpdates struct {
|
|||||||
blocking map[string]bool
|
blocking map[string]bool
|
||||||
delayedExpunges map[string][]chan struct{}
|
delayedExpunges map[string][]chan struct{}
|
||||||
ch chan goIMAPBackend.Update
|
ch chan goIMAPBackend.Update
|
||||||
|
ib *imapBackend
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIMAPUpdates() *imapUpdates {
|
func newIMAPUpdates(ib *imapBackend) *imapUpdates {
|
||||||
return &imapUpdates{
|
return &imapUpdates{
|
||||||
lock: &sync.Mutex{},
|
lock: &sync.Mutex{},
|
||||||
blocking: map[string]bool{},
|
blocking: map[string]bool{},
|
||||||
delayedExpunges: map[string][]chan struct{}{},
|
delayedExpunges: map[string][]chan struct{}{},
|
||||||
ch: make(chan goIMAPBackend.Update),
|
ch: make(chan goIMAPBackend.Update),
|
||||||
|
ib: ib,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,6 +116,8 @@ func (iu *imapUpdates) CanDelete(mailboxID string) (bool, func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) Notice(address, notice string) {
|
func (iu *imapUpdates) Notice(address, notice string) {
|
||||||
|
l := iu.updateLog(address, "")
|
||||||
|
l.Info("Notice")
|
||||||
update := new(goIMAPBackend.StatusUpdate)
|
update := new(goIMAPBackend.StatusUpdate)
|
||||||
update.Update = goIMAPBackend.NewUpdate(address, "")
|
update.Update = goIMAPBackend.NewUpdate(address, "")
|
||||||
update.StatusResp = &imap.StatusResp{
|
update.StatusResp = &imap.StatusResp{
|
||||||
@ -120,7 +125,7 @@ func (iu *imapUpdates) Notice(address, notice string) {
|
|||||||
Code: imap.CodeAlert,
|
Code: imap.CodeAlert,
|
||||||
Info: notice,
|
Info: notice,
|
||||||
}
|
}
|
||||||
iu.sendIMAPUpdate(update, false)
|
iu.sendIMAPUpdate(l, update, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) UpdateMessage(
|
func (iu *imapUpdates) UpdateMessage(
|
||||||
@ -128,14 +133,14 @@ func (iu *imapUpdates) UpdateMessage(
|
|||||||
uid, sequenceNumber uint32,
|
uid, sequenceNumber uint32,
|
||||||
msg *pmapi.Message, hasDeletedFlag bool,
|
msg *pmapi.Message, hasDeletedFlag bool,
|
||||||
) {
|
) {
|
||||||
log.WithFields(logrus.Fields{
|
l := iu.updateLog(address, mailboxName).
|
||||||
"address": address,
|
WithFields(logrus.Fields{
|
||||||
"mailbox": mailboxName,
|
"seqNum": sequenceNumber,
|
||||||
"seqNum": sequenceNumber,
|
"uid": uid,
|
||||||
"uid": uid,
|
"flags": message.GetFlags(msg),
|
||||||
"flags": message.GetFlags(msg),
|
"deleted": hasDeletedFlag,
|
||||||
"deleted": hasDeletedFlag,
|
})
|
||||||
}).Trace("IDLE update")
|
l.Info("IDLE update")
|
||||||
update := new(goIMAPBackend.MessageUpdate)
|
update := new(goIMAPBackend.MessageUpdate)
|
||||||
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
||||||
update.Message = imap.NewMessage(sequenceNumber, []imap.FetchItem{imap.FetchFlags, imap.FetchUid})
|
update.Message = imap.NewMessage(sequenceNumber, []imap.FetchItem{imap.FetchFlags, imap.FetchUid})
|
||||||
@ -144,26 +149,22 @@ func (iu *imapUpdates) UpdateMessage(
|
|||||||
update.Message.Flags = append(update.Message.Flags, imap.DeletedFlag)
|
update.Message.Flags = append(update.Message.Flags, imap.DeletedFlag)
|
||||||
}
|
}
|
||||||
update.Message.Uid = uid
|
update.Message.Uid = uid
|
||||||
iu.sendIMAPUpdate(update, iu.isBlocking(address, mailboxName, operationUpdateMessage))
|
iu.sendIMAPUpdate(l, update, iu.isBlocking(address, mailboxName, operationUpdateMessage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) DeleteMessage(address, mailboxName string, sequenceNumber uint32) {
|
func (iu *imapUpdates) DeleteMessage(address, mailboxName string, sequenceNumber uint32) {
|
||||||
log.WithFields(logrus.Fields{
|
l := iu.updateLog(address, mailboxName).
|
||||||
"address": address,
|
WithField("seqNum", sequenceNumber)
|
||||||
"mailbox": mailboxName,
|
l.Info("IDLE delete")
|
||||||
"seqNum": sequenceNumber,
|
|
||||||
}).Trace("IDLE delete")
|
|
||||||
update := new(goIMAPBackend.ExpungeUpdate)
|
update := new(goIMAPBackend.ExpungeUpdate)
|
||||||
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
||||||
update.SeqNum = sequenceNumber
|
update.SeqNum = sequenceNumber
|
||||||
iu.sendIMAPUpdate(update, iu.isBlocking(address, mailboxName, operationDeleteMessage))
|
iu.sendIMAPUpdate(l, update, iu.isBlocking(address, mailboxName, operationDeleteMessage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) MailboxCreated(address, mailboxName string) {
|
func (iu *imapUpdates) MailboxCreated(address, mailboxName string) {
|
||||||
log.WithFields(logrus.Fields{
|
l := iu.updateLog(address, mailboxName)
|
||||||
"address": address,
|
l.Info("IDLE mailbox info")
|
||||||
"mailbox": mailboxName,
|
|
||||||
}).Trace("IDLE mailbox info")
|
|
||||||
update := new(goIMAPBackend.MailboxInfoUpdate)
|
update := new(goIMAPBackend.MailboxInfoUpdate)
|
||||||
update.Update = goIMAPBackend.NewUpdate(address, "")
|
update.Update = goIMAPBackend.NewUpdate(address, "")
|
||||||
update.MailboxInfo = &imap.MailboxInfo{
|
update.MailboxInfo = &imap.MailboxInfo{
|
||||||
@ -171,29 +172,30 @@ func (iu *imapUpdates) MailboxCreated(address, mailboxName string) {
|
|||||||
Delimiter: store.PathDelimiter,
|
Delimiter: store.PathDelimiter,
|
||||||
Name: mailboxName,
|
Name: mailboxName,
|
||||||
}
|
}
|
||||||
iu.sendIMAPUpdate(update, false)
|
iu.sendIMAPUpdate(l, update, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) MailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint32) {
|
func (iu *imapUpdates) MailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint32) {
|
||||||
log.WithFields(logrus.Fields{
|
l := iu.updateLog(address, mailboxName).
|
||||||
"address": address,
|
WithFields(logrus.Fields{
|
||||||
"mailbox": mailboxName,
|
"total": total,
|
||||||
"total": total,
|
"unread": unread,
|
||||||
"unread": unread,
|
"unreadSeqNum": unreadSeqNum,
|
||||||
"unreadSeqNum": unreadSeqNum,
|
})
|
||||||
}).Trace("IDLE status")
|
l.Info("IDLE status")
|
||||||
update := new(goIMAPBackend.MailboxUpdate)
|
update := new(goIMAPBackend.MailboxUpdate)
|
||||||
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
update.Update = goIMAPBackend.NewUpdate(address, mailboxName)
|
||||||
update.MailboxStatus = imap.NewMailboxStatus(mailboxName, []imap.StatusItem{imap.StatusMessages, imap.StatusUnseen})
|
update.MailboxStatus = imap.NewMailboxStatus(mailboxName, []imap.StatusItem{imap.StatusMessages, imap.StatusUnseen})
|
||||||
update.MailboxStatus.Messages = total
|
update.MailboxStatus.Messages = total
|
||||||
update.MailboxStatus.Unseen = unread
|
update.MailboxStatus.Unseen = unread
|
||||||
update.MailboxStatus.UnseenSeqNum = unreadSeqNum
|
update.MailboxStatus.UnseenSeqNum = unreadSeqNum
|
||||||
iu.sendIMAPUpdate(update, true)
|
iu.sendIMAPUpdate(l, update, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) sendIMAPUpdate(update goIMAPBackend.Update, isBlocking bool) {
|
func (iu *imapUpdates) sendIMAPUpdate(updateLog *logrus.Entry, update goIMAPBackend.Update, isBlocking bool) {
|
||||||
|
l := updateLog.WithField("blocking", isBlocking)
|
||||||
if iu.ch == nil {
|
if iu.ch == nil {
|
||||||
log.Trace("IMAP IDLE unavailable")
|
l.Info("IMAP IDLE unavailable")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +203,7 @@ func (iu *imapUpdates) sendIMAPUpdate(update goIMAPBackend.Update, isBlocking bo
|
|||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
log.Warn("IMAP update could not be sent (timeout)")
|
l.Warn("IMAP update could not be sent (timeout)")
|
||||||
return
|
return
|
||||||
case iu.ch <- update:
|
case iu.ch <- update:
|
||||||
}
|
}
|
||||||
@ -214,7 +216,35 @@ func (iu *imapUpdates) sendIMAPUpdate(update goIMAPBackend.Update, isBlocking bo
|
|||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(1 * time.Second):
|
||||||
log.Warn("IMAP update could not be delivered (timeout)")
|
l.Warn("IMAP update could not be delivered (timeout)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (iu *imapUpdates) getIDs(address, mailboxName string) (addressID, mailboxID string) {
|
||||||
|
addressID = "unknown-" + algo.HashBase64SHA256(address)
|
||||||
|
mailboxID = "unknown-" + algo.HashBase64SHA256(mailboxName)
|
||||||
|
|
||||||
|
if iu == nil || iu.ib == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := iu.ib.getUser(address)
|
||||||
|
if err != nil || user == nil || user.storeAddress == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addressID = user.addressID
|
||||||
|
|
||||||
|
if v := user.mailboxIDs.get(mailboxName); v != "" {
|
||||||
|
mailboxID = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iu *imapUpdates) updateLog(address, mailboxName string) *logrus.Entry {
|
||||||
|
addressID, mailboxID := iu.getIDs(address, mailboxName)
|
||||||
|
return log.
|
||||||
|
WithField("address", addressID).
|
||||||
|
WithField("mailbox", mailboxID)
|
||||||
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestUpdatesCanDelete(t *testing.T) {
|
func TestUpdatesCanDelete(t *testing.T) {
|
||||||
u := newIMAPUpdates()
|
u := newIMAPUpdates(nil)
|
||||||
|
|
||||||
can, _ := u.CanDelete("mbox")
|
can, _ := u.CanDelete("mbox")
|
||||||
require.True(t, can)
|
require.True(t, can)
|
||||||
@ -38,7 +38,7 @@ func TestUpdatesCanDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdatesCannotDelete(t *testing.T) {
|
func TestUpdatesCannotDelete(t *testing.T) {
|
||||||
u := newIMAPUpdates()
|
u := newIMAPUpdates(nil)
|
||||||
|
|
||||||
u.forbidExpunge("mbox")
|
u.forbidExpunge("mbox")
|
||||||
can, wait := u.CanDelete("mbox")
|
can, wait := u.CanDelete("mbox")
|
||||||
|
|||||||
@ -53,6 +53,9 @@ type imapUser struct {
|
|||||||
// not cause huge slow down as EXPUNGE is implicitly called also after
|
// not cause huge slow down as EXPUNGE is implicitly called also after
|
||||||
// UNSELECT, CLOSE, or LOGOUT.
|
// UNSELECT, CLOSE, or LOGOUT.
|
||||||
appendExpungeLock sync.Mutex
|
appendExpungeLock sync.Mutex
|
||||||
|
|
||||||
|
addressID string // cached value for logs to avoid lock
|
||||||
|
mailboxIDs safeMapOfStrings // cached values for logs to avoid lock
|
||||||
}
|
}
|
||||||
|
|
||||||
// newIMAPUser returns struct implementing go-imap/user interface.
|
// newIMAPUser returns struct implementing go-imap/user interface.
|
||||||
@ -84,6 +87,8 @@ func newIMAPUser(
|
|||||||
storeAddress: storeAddress,
|
storeAddress: storeAddress,
|
||||||
|
|
||||||
currentAddressLowercase: strings.ToLower(address),
|
currentAddressLowercase: strings.ToLower(address),
|
||||||
|
addressID: addressID,
|
||||||
|
mailboxIDs: newSafeMapOfString(),
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +133,12 @@ func (iu *imapUser) ListMailboxes(showOnlySubcribed bool) ([]goIMAPBackend.Mailb
|
|||||||
|
|
||||||
mailboxes := []goIMAPBackend.Mailbox{}
|
mailboxes := []goIMAPBackend.Mailbox{}
|
||||||
for _, storeMailbox := range iu.storeAddress.ListMailboxes() {
|
for _, storeMailbox := range iu.storeAddress.ListMailboxes() {
|
||||||
|
iu.mailboxIDs.set(storeMailbox.Name(), storeMailbox.LabelID())
|
||||||
|
|
||||||
|
if storeMailbox.LabelID() == pmapi.AllMailLabel && !iu.backend.bridge.IsAllMailVisible() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if showOnlySubcribed && !iu.isSubscribed(storeMailbox.LabelID()) {
|
if showOnlySubcribed && !iu.isSubscribed(storeMailbox.LabelID()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,7 @@ func (l *Locations) getLicenseFilePath() string {
|
|||||||
|
|
||||||
// GetDependencyLicensesLink returns link to page listing dependencies.
|
// GetDependencyLicensesLink returns link to page listing dependencies.
|
||||||
func (l *Locations) GetDependencyLicensesLink() string {
|
func (l *Locations) GetDependencyLicensesLink() string {
|
||||||
return "https://github.com/ProtonMail/proton-bridge/v2/blob/master/COPYING_NOTES.md#dependencies"
|
return "https://github.com/ProtonMail/proton-bridge/blob/master/COPYING_NOTES.md#dependencies"
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideSettingsPath returns a location for user settings (e.g. ~/.config/<company>/<app>).
|
// ProvideSettingsPath returns a location for user settings (e.g. ~/.config/<company>/<app>).
|
||||||
|
|||||||
14
internal/store/cache/disk.go
vendored
14
internal/store/cache/disk.go
vendored
@ -21,7 +21,6 @@ import (
|
|||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -29,6 +28,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/pkg/algo"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/semaphore"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/semaphore"
|
||||||
"github.com/ricochet2200/go-disk-usage/du"
|
"github.com/ricochet2200/go-disk-usage/du"
|
||||||
)
|
)
|
||||||
@ -100,13 +100,7 @@ func (c *onDiskCache) Lock(userID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *onDiskCache) Unlock(userID string, passphrase []byte) error {
|
func (c *onDiskCache) Unlock(userID string, passphrase []byte) error {
|
||||||
hash := sha256.New()
|
aes, err := aes.NewCipher(algo.Hash256(passphrase))
|
||||||
|
|
||||||
if _, err := hash.Write(passphrase); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aes, err := aes.NewCipher(hash.Sum(nil))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -279,9 +273,9 @@ func (c *onDiskCache) update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *onDiskCache) getUserPath(userID string) string {
|
func (c *onDiskCache) getUserPath(userID string) string {
|
||||||
return filepath.Join(c.path, getHash(userID))
|
return filepath.Join(c.path, algo.HashHexSHA256(userID))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *onDiskCache) getMessagePath(userID, messageID string) string {
|
func (c *onDiskCache) getMessagePath(userID, messageID string) string {
|
||||||
return filepath.Join(c.getUserPath(userID), getHash(messageID))
|
return filepath.Join(c.getUserPath(userID), algo.HashHexSHA256(messageID))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,20 +15,27 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// 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/>.
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package cache
|
package algo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getHash(name string) string {
|
func Hash256(b []byte) []byte {
|
||||||
hash := sha256.New()
|
h := sha256.Sum256(b)
|
||||||
|
return h[:]
|
||||||
if _, err := hash.Write([]byte(name)); err != nil {
|
}
|
||||||
// sha256.Write always returns nill err so this should never happen
|
|
||||||
panic(err)
|
func HashBase64SHA256(s string) string {
|
||||||
}
|
return base64.StdEncoding.EncodeToString(
|
||||||
|
Hash256([]byte(s)),
|
||||||
return hex.EncodeToString(hash.Sum(nil))
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashHexSHA256(s string) string {
|
||||||
|
return hex.EncodeToString(
|
||||||
|
Hash256([]byte(s)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@ -18,8 +18,7 @@
|
|||||||
package message
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/algo"
|
||||||
"encoding/hex"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type boundary struct {
|
type boundary struct {
|
||||||
@ -31,13 +30,6 @@ func newBoundary(seed string) *boundary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bw *boundary) gen() string {
|
func (bw *boundary) gen() string {
|
||||||
hash := sha256.New()
|
bw.val = algo.HashHexSHA256(bw.val)
|
||||||
|
|
||||||
if _, err := hash.Write([]byte(bw.val)); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bw.val = hex.EncodeToString(hash.Sum(nil))
|
|
||||||
|
|
||||||
return bw.val
|
return bw.val
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,13 @@
|
|||||||
package pmapi
|
package pmapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/pkg/algo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrTLSMismatch indicates that no TLS fingerprint match could be found.
|
// ErrTLSMismatch indicates that no TLS fingerprint match could be found.
|
||||||
@ -63,6 +63,5 @@ func (p *pinChecker) checkCertificate(conn net.Conn) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func certFingerprint(cert *x509.Certificate) string {
|
func certFingerprint(cert *x509.Certificate) string {
|
||||||
hash := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
|
return fmt.Sprintf(`pin-sha256=%q`, algo.HashBase64SHA256(string(cert.RawSubjectPublicKeyInfo)))
|
||||||
return fmt.Sprintf(`pin-sha256=%q`, base64.StdEncoding.EncodeToString(hash[:]))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ func TestTLSSignedCertTrustedPublicKey(t *testing.T) {
|
|||||||
|
|
||||||
_, dialer, _ := createClientWithPinningDialer("")
|
_, dialer, _ := createClientWithPinningDialer("")
|
||||||
copyTrustedPins(dialer.pinChecker)
|
copyTrustedPins(dialer.pinChecker)
|
||||||
dialer.pinChecker.trustedPins = append(dialer.pinChecker.trustedPins, `pin-sha256="2opdB7b5INED5jS7duIDR7dM8Er99i7trnwKuW3GMCY="`)
|
dialer.pinChecker.trustedPins = append(dialer.pinChecker.trustedPins, `pin-sha256="SA4v9d2YY4vX5YQOQ1qZHYTBMCTSD/sxPvyj+JL6+vI="`)
|
||||||
_, err := dialer.DialTLS("tcp", "rsa4096.badssl.com:443")
|
_, err := dialer.DialTLS("tcp", "rsa4096.badssl.com:443")
|
||||||
r.NoError(t, err, "expected dial to succeed because public key is known and cert is signed by CA")
|
r.NoError(t, err, "expected dial to succeed because public key is known and cert is signed by CA")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,7 +90,14 @@ type ImportMsgRes struct {
|
|||||||
|
|
||||||
// Import imports messages to the user's account.
|
// Import imports messages to the user's account.
|
||||||
func (c *client) Import(ctx context.Context, reqs ImportMsgReqs) ([]*ImportMsgRes, error) {
|
func (c *client) Import(ctx context.Context, reqs ImportMsgReqs) ([]*ImportMsgRes, error) {
|
||||||
|
if len(reqs) == 0 {
|
||||||
|
return nil, errors.New("missing import requests")
|
||||||
|
}
|
||||||
|
|
||||||
if len(reqs) > MaxImportMessageRequestLength {
|
if len(reqs) > MaxImportMessageRequestLength {
|
||||||
|
log.
|
||||||
|
WithField("count", len(reqs)).
|
||||||
|
Warn("Importing too many messages at once.")
|
||||||
return nil, errors.New("request is too long")
|
return nil, errors.New("request is too long")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,6 +105,10 @@ func (c *client) Import(ctx context.Context, reqs ImportMsgReqs) ([]*ImportMsgRe
|
|||||||
for _, req := range reqs {
|
for _, req := range reqs {
|
||||||
remainingSize -= len(req.Message)
|
remainingSize -= len(req.Message)
|
||||||
if remainingSize < 0 {
|
if remainingSize < 0 {
|
||||||
|
log.
|
||||||
|
WithField("count", len(reqs)).
|
||||||
|
WithField("size", MaxImportMessageRequestLength-remainingSize).
|
||||||
|
Warn("Importing too big message(s)")
|
||||||
return nil, errors.New("request size is too big")
|
return nil, errors.New("request size is too big")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
.PHONY: check-go check-godog install-godog test test-bridge test-live test-live-bridge test-stage test-debug test-live-debug bench
|
.PHONY: check-go check-godog install-godog test test-bridge test-live test-live-bridge test-stage test-debug test-live-debug bench
|
||||||
|
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
export BRIDGE_VERSION:=2.2.1+integrationtests
|
export BRIDGE_VERSION:=2.3.0+integrationtests
|
||||||
export VERBOSITY?=fatal
|
export VERBOSITY?=fatal
|
||||||
export TEST_DATA=testdata
|
export TEST_DATA=testdata
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,8 @@ import (
|
|||||||
func BridgeActionsFeatureContext(s *godog.ScenarioContext) {
|
func BridgeActionsFeatureContext(s *godog.ScenarioContext) {
|
||||||
s.Step(`^bridge starts$`, bridgeStarts)
|
s.Step(`^bridge starts$`, bridgeStarts)
|
||||||
s.Step(`^bridge syncs "([^"]*)"$`, bridgeSyncsUser)
|
s.Step(`^bridge syncs "([^"]*)"$`, bridgeSyncsUser)
|
||||||
|
s.Step(`^All mail mailbox is hidden$`, allMailMailboxIsHidden)
|
||||||
|
s.Step(`^All mail mailbox is visible$`, allMailMailboxIsVisible)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bridgeStarts() error {
|
func bridgeStarts() error {
|
||||||
@ -42,3 +44,13 @@ func bridgeSyncsUser(bddUserID string) error {
|
|||||||
ctx.SetLastError(ctx.GetTestingError())
|
ctx.SetLastError(ctx.GetTestingError())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allMailMailboxIsHidden() error {
|
||||||
|
ctx.GetBridge().SetIsAllMailVisible(false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func allMailMailboxIsVisible() error {
|
||||||
|
ctx.GetBridge().SetIsAllMailVisible(true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -80,8 +80,10 @@ func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
|
|||||||
ctl.labelsByUsername[username] = []*pmapi.Label{}
|
ctl.labelsByUsername[username] = []*pmapi.Label{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userLabels := ctl.labelsByUsername[username]
|
||||||
|
|
||||||
labelName := getLabelNameWithoutPrefix(label.Name)
|
labelName := getLabelNameWithoutPrefix(label.Name)
|
||||||
for _, existingLabel := range ctl.labelsByUsername[username] {
|
for _, existingLabel := range userLabels {
|
||||||
if existingLabel.Name == labelName {
|
if existingLabel.Name == labelName {
|
||||||
return fmt.Errorf("folder or label %s already exists", label.Name)
|
return fmt.Errorf("folder or label %s already exists", label.Name)
|
||||||
}
|
}
|
||||||
@ -97,7 +99,9 @@ func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
|
|||||||
if label.Path == "" {
|
if label.Path == "" {
|
||||||
label.Path = label.Name
|
label.Path = label.Name
|
||||||
}
|
}
|
||||||
ctl.labelsByUsername[username] = append(ctl.labelsByUsername[username], label)
|
userLabels = append(userLabels, label)
|
||||||
|
|
||||||
|
ctl.labelsByUsername[username] = userLabels
|
||||||
ctl.resetUsers()
|
ctl.resetUsers()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package fakeapi
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||||
)
|
)
|
||||||
@ -87,16 +88,23 @@ func (api *FakePMAPI) listLabels(_ context.Context, labeType string, route strin
|
|||||||
if err := api.checkAndRecordCall(GET, route+"/"+labeType, nil); err != nil {
|
if err := api.checkAndRecordCall(GET, route+"/"+labeType, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return api.labels, nil
|
return append([]*pmapi.Label{}, api.labels...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *FakePMAPI) createLabel(_ context.Context, label *pmapi.Label, route string) (*pmapi.Label, error) {
|
func (api *FakePMAPI) createLabel(_ context.Context, label *pmapi.Label, route string) (*pmapi.Label, error) {
|
||||||
if err := api.checkAndRecordCall(POST, route, &pmapi.LabelReq{Label: label}); err != nil {
|
if err := api.checkAndRecordCall(POST, route, &pmapi.LabelReq{Label: label}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// API blocks certain names
|
||||||
|
switch strings.ToLower(label.Name) {
|
||||||
|
case "inbox", "drafts", "trash", "spam", "starred":
|
||||||
|
return nil, fmt.Errorf("Invalid name") //nolint:stylecheck
|
||||||
|
}
|
||||||
|
|
||||||
for _, existingLabel := range api.labels {
|
for _, existingLabel := range api.labels {
|
||||||
if existingLabel.Name == label.Name {
|
if existingLabel.Name == label.Name {
|
||||||
return nil, fmt.Errorf("folder or label %s already exists", label.Name)
|
return nil, fmt.Errorf("A label or folder with this name already exists") //nolint:stylecheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefix := "label"
|
prefix := "label"
|
||||||
|
|||||||
@ -17,11 +17,34 @@ Feature: IMAP create mailbox
|
|||||||
And "user" does not have mailbox "Folders/mbox"
|
And "user" does not have mailbox "Folders/mbox"
|
||||||
And "user" has mailbox "Labels/mbox"
|
And "user" has mailbox "Labels/mbox"
|
||||||
|
|
||||||
|
Scenario: Creating label with existing name is not possible
|
||||||
|
Given there is "user" with mailbox "Folders/mbox"
|
||||||
|
When IMAP client creates mailbox "Labels/mbox"
|
||||||
|
Then IMAP response is "IMAP error: NO A label or folder with this name already exists"
|
||||||
|
And "user" has mailbox "Folders/mbox"
|
||||||
|
And "user" does not have mailbox "Labels/mbox"
|
||||||
|
|
||||||
|
Scenario: Creating folder with existing name is not possible
|
||||||
|
Given there is "user" with mailbox "Labels/mbox"
|
||||||
|
When IMAP client creates mailbox "Folders/mbox"
|
||||||
|
Then IMAP response is "IMAP error: NO A label or folder with this name already exists"
|
||||||
|
And "user" has mailbox "Labels/mbox"
|
||||||
|
And "user" does not have mailbox "Folders/mbox"
|
||||||
|
|
||||||
Scenario: Creating system mailbox is not possible
|
Scenario: Creating system mailbox is not possible
|
||||||
When IMAP client creates mailbox "INBOX"
|
When IMAP client creates mailbox "INBOX"
|
||||||
Then IMAP response is "IMAP error: NO mailbox INBOX already exists"
|
Then IMAP response is "IMAP error: NO mailbox INBOX already exists"
|
||||||
|
When IMAP client creates mailbox "Folders/INBOX"
|
||||||
|
Then IMAP response is "IMAP error: NO Invalid name"
|
||||||
|
# API allows you to create custom folder with naem `All Mail`
|
||||||
|
#When IMAP client creates mailbox "Folders/All mail"
|
||||||
|
#Then IMAP response is "IMAP error: NO mailbox All Mail already exists"
|
||||||
|
|
||||||
Scenario: Creating mailbox without prefix is not possible
|
Scenario: Creating mailbox without prefix is not possible
|
||||||
When IMAP client creates mailbox "mbox"
|
When IMAP client creates mailbox "mbox"
|
||||||
Then IMAP response is "OK"
|
Then IMAP response is "OK"
|
||||||
And "user" does not have mailbox "mbox"
|
And "user" does not have mailbox "mbox"
|
||||||
|
When All mail mailbox is hidden
|
||||||
|
And IMAP client creates mailbox "All mail"
|
||||||
|
Then IMAP response is "OK"
|
||||||
|
And "user" does not have mailbox "All mail"
|
||||||
|
|||||||
@ -15,6 +15,120 @@ Feature: IMAP list mailboxes
|
|||||||
Then IMAP response contains "Folders/mbox1"
|
Then IMAP response contains "Folders/mbox1"
|
||||||
Then IMAP response contains "Labels/mbox2"
|
Then IMAP response contains "Labels/mbox2"
|
||||||
|
|
||||||
|
Scenario: List mailboxes without All Mail
|
||||||
|
Given there is IMAP client logged in as "user"
|
||||||
|
When IMAP client lists mailboxes
|
||||||
|
Then IMAP response contains "INBOX"
|
||||||
|
Then IMAP response contains "Sent"
|
||||||
|
Then IMAP response contains "Archive"
|
||||||
|
Then IMAP response contains "Trash"
|
||||||
|
Then IMAP response contains "All Mail"
|
||||||
|
When All mail mailbox is hidden
|
||||||
|
And IMAP client lists mailboxes
|
||||||
|
Then IMAP response contains "INBOX"
|
||||||
|
Then IMAP response contains "Sent"
|
||||||
|
Then IMAP response contains "Archive"
|
||||||
|
Then IMAP response contains "Trash"
|
||||||
|
Then IMAP response doesn't contain "All Mail"
|
||||||
|
When All mail mailbox is visible
|
||||||
|
And IMAP client lists mailboxes
|
||||||
|
Then IMAP response contains "INBOX"
|
||||||
|
Then IMAP response contains "Sent"
|
||||||
|
Then IMAP response contains "Archive"
|
||||||
|
Then IMAP response contains "Trash"
|
||||||
|
Then IMAP response contains "All Mail"
|
||||||
|
|
||||||
|
Scenario: List multiple times in parallel without crash
|
||||||
|
Given there is "user" with mailboxes
|
||||||
|
| Folders/mbox1 |
|
||||||
|
| Folders/mbox2 |
|
||||||
|
| Folders/mbox3 |
|
||||||
|
| Folders/mbox4 |
|
||||||
|
| Folders/mbox5 |
|
||||||
|
| Folders/mbox6 |
|
||||||
|
| Folders/mbox7 |
|
||||||
|
| Folders/mbox8 |
|
||||||
|
| Folders/mbox9 |
|
||||||
|
| Folders/mbox10 |
|
||||||
|
| Folders/mbox11 |
|
||||||
|
| Folders/mbox12 |
|
||||||
|
| Folders/mbox13 |
|
||||||
|
| Folders/mbox14 |
|
||||||
|
| Folders/mbox15 |
|
||||||
|
| Folders/mbox16 |
|
||||||
|
| Folders/mbox17 |
|
||||||
|
| Folders/mbox18 |
|
||||||
|
| Folders/mbox19 |
|
||||||
|
| Folders/mbox20 |
|
||||||
|
| Labels/lab1 |
|
||||||
|
| Labels/lab2 |
|
||||||
|
| Labels/lab3 |
|
||||||
|
| Labels/lab4 |
|
||||||
|
| Labels/lab5 |
|
||||||
|
| Labels/lab6 |
|
||||||
|
| Labels/lab7 |
|
||||||
|
| Labels/lab8 |
|
||||||
|
| Labels/lab9 |
|
||||||
|
| Labels/lab10 |
|
||||||
|
| Labels/lab11 |
|
||||||
|
| Labels/lab12 |
|
||||||
|
| Labels/lab13 |
|
||||||
|
| Labels/lab14 |
|
||||||
|
| Labels/lab15 |
|
||||||
|
| Labels/lab16 |
|
||||||
|
| Labels/lab17 |
|
||||||
|
| Labels/lab18 |
|
||||||
|
| Labels/lab19 |
|
||||||
|
| Labels/lab20 |
|
||||||
|
| Labels/lab1.1 |
|
||||||
|
| Labels/lab1.2 |
|
||||||
|
| Labels/lab1.3 |
|
||||||
|
| Labels/lab1.4 |
|
||||||
|
| Labels/lab1.5 |
|
||||||
|
| Labels/lab1.6 |
|
||||||
|
| Labels/lab1.7 |
|
||||||
|
| Labels/lab1.8 |
|
||||||
|
| Labels/lab1.9 |
|
||||||
|
| Labels/lab1.10 |
|
||||||
|
| Labels/lab1.11 |
|
||||||
|
| Labels/lab1.12 |
|
||||||
|
| Labels/lab1.13 |
|
||||||
|
| Labels/lab1.14 |
|
||||||
|
| Labels/lab1.15 |
|
||||||
|
| Labels/lab1.16 |
|
||||||
|
| Labels/lab1.17 |
|
||||||
|
| Labels/lab1.18 |
|
||||||
|
| Labels/lab1.19 |
|
||||||
|
| Labels/lab1.20 |
|
||||||
|
| Labels/lab2.1 |
|
||||||
|
| Labels/lab2.2 |
|
||||||
|
| Labels/lab2.3 |
|
||||||
|
| Labels/lab2.4 |
|
||||||
|
| Labels/lab2.5 |
|
||||||
|
| Labels/lab2.6 |
|
||||||
|
| Labels/lab2.7 |
|
||||||
|
| Labels/lab2.8 |
|
||||||
|
| Labels/lab2.9 |
|
||||||
|
| Labels/lab2.10 |
|
||||||
|
| Labels/lab2.11 |
|
||||||
|
| Labels/lab2.12 |
|
||||||
|
| Labels/lab2.13 |
|
||||||
|
| Labels/lab2.14 |
|
||||||
|
| Labels/lab2.15 |
|
||||||
|
| Labels/lab2.16 |
|
||||||
|
| Labels/lab2.17 |
|
||||||
|
| Labels/lab2.18 |
|
||||||
|
| Labels/lab2.19 |
|
||||||
|
| Labels/lab2.20 |
|
||||||
|
And there is IMAP client "A" logged in as "user"
|
||||||
|
And there is IMAP client "B" logged in as "user"
|
||||||
|
When IMAP client "A" lists mailboxes
|
||||||
|
And IMAP client "B" lists mailboxes
|
||||||
|
Then IMAP response to "A" is "OK"
|
||||||
|
And IMAP response to "A" contains "mbox1"
|
||||||
|
And IMAP response to "A" contains "mbox10"
|
||||||
|
And IMAP response to "A" contains "mbox20"
|
||||||
|
|
||||||
@ignore-live
|
@ignore-live
|
||||||
Scenario: List mailboxes with subfolders
|
Scenario: List mailboxes with subfolders
|
||||||
# Escaped slash in the name contains slash in the name.
|
# Escaped slash in the name contains slash in the name.
|
||||||
|
|||||||
@ -28,6 +28,7 @@ func IMAPActionsMailboxFeatureContext(s *godog.ScenarioContext) {
|
|||||||
s.Step(`^IMAP client renames mailbox "([^"]*)" to "([^"]*)"$`, imapClientRenamesMailboxTo)
|
s.Step(`^IMAP client renames mailbox "([^"]*)" to "([^"]*)"$`, imapClientRenamesMailboxTo)
|
||||||
s.Step(`^IMAP client deletes mailbox "([^"]*)"$`, imapClientDeletesMailbox)
|
s.Step(`^IMAP client deletes mailbox "([^"]*)"$`, imapClientDeletesMailbox)
|
||||||
s.Step(`^IMAP client lists mailboxes$`, imapClientListsMailboxes)
|
s.Step(`^IMAP client lists mailboxes$`, imapClientListsMailboxes)
|
||||||
|
s.Step(`^IMAP client "([^"]*)" lists mailboxes$`, imapClientNamedListsMailboxes)
|
||||||
s.Step(`^IMAP client selects "([^"]*)"$`, imapClientSelects)
|
s.Step(`^IMAP client selects "([^"]*)"$`, imapClientSelects)
|
||||||
s.Step(`^IMAP client gets info of "([^"]*)"$`, imapClientGetsInfoOf)
|
s.Step(`^IMAP client gets info of "([^"]*)"$`, imapClientGetsInfoOf)
|
||||||
s.Step(`^IMAP client "([^"]*)" gets info of "([^"]*)"$`, imapClientNamedGetsInfoOf)
|
s.Step(`^IMAP client "([^"]*)" gets info of "([^"]*)"$`, imapClientNamedGetsInfoOf)
|
||||||
@ -63,8 +64,12 @@ func imapClientDeletesMailbox(mailboxName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func imapClientListsMailboxes() error {
|
func imapClientListsMailboxes() error {
|
||||||
res := ctx.GetIMAPClient("imap").ListMailboxes()
|
return imapClientNamedListsMailboxes("imap")
|
||||||
ctx.SetIMAPLastResponse("imap", res)
|
}
|
||||||
|
|
||||||
|
func imapClientNamedListsMailboxes(clientName string) error {
|
||||||
|
res := ctx.GetIMAPClient(clientName).ListMailboxes()
|
||||||
|
ctx.SetIMAPLastResponse(clientName, res)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,7 @@ func IMAPChecksFeatureContext(s *godog.ScenarioContext) {
|
|||||||
s.Step(`^IMAP response to "([^"]*)" contains "([^"]*)"$`, imapResponseNamedContains)
|
s.Step(`^IMAP response to "([^"]*)" contains "([^"]*)"$`, imapResponseNamedContains)
|
||||||
s.Step(`^IMAP response has (\d+) message(?:s)?$`, imapResponseHasNumberOfMessages)
|
s.Step(`^IMAP response has (\d+) message(?:s)?$`, imapResponseHasNumberOfMessages)
|
||||||
s.Step(`^IMAP response to "([^"]*)" has (\d+) message(?:s)?$`, imapResponseNamedHasNumberOfMessages)
|
s.Step(`^IMAP response to "([^"]*)" has (\d+) message(?:s)?$`, imapResponseNamedHasNumberOfMessages)
|
||||||
s.Step(`^IMAP response does not contain "([^"]*)"$`, imapResponseDoesNotContain)
|
s.Step(`^IMAP response does(?: not|n't) contain "([^"]*)"$`, imapResponseDoesNotContain)
|
||||||
s.Step(`^IMAP response to "([^"]*)" does not contain "([^"]*)"$`, imapResponseNamedDoesNotContain)
|
s.Step(`^IMAP response to "([^"]*)" does not contain "([^"]*)"$`, imapResponseNamedDoesNotContain)
|
||||||
s.Step(`^IMAP client receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientReceivesUpdateMarkingMessageSeqAsReadWithin)
|
s.Step(`^IMAP client receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientReceivesUpdateMarkingMessageSeqAsReadWithin)
|
||||||
s.Step(`^IMAP client "([^"]*)" receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientNamedReceivesUpdateMarkingMessageSeqAsReadWithin)
|
s.Step(`^IMAP client "([^"]*)" receives update marking message seq "([^"]*)" as read within (\d+) seconds$`, imapClientNamedReceivesUpdateMarkingMessageSeqAsReadWithin)
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
file:
|
||||||
|
name: "./gobinsec-cache.yml"
|
||||||
|
expiration: 24h
|
||||||
|
|
||||||
ignore:
|
ignore:
|
||||||
# golang.org/x/net wrong match, we are using 2871e0cb, fixed by 37e1c6af
|
# golang.org/x/net wrong match, we are using 2871e0cb, fixed by 37e1c6af
|
||||||
- "CVE-2021-33194"
|
- "CVE-2021-33194"
|
||||||
|
|||||||
Reference in New Issue
Block a user