mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d9855a190 | |||
| cea33bebe2 | |||
| 9d405a1549 | |||
| 47f468e4b7 | |||
| b9c6c00709 | |||
| 5ce9cb8eec | |||
| bc7133e401 | |||
| a219ecf3cb | |||
| 8061b1e6fa | |||
| 6509df523f | |||
| 0d1abaec0d | |||
| 4d1ace5de7 | |||
| 1250621a4d | |||
| 8d6e55ba54 | |||
| a4a29cbf82 | |||
| 39bccc2747 | |||
| 0cf1b38c2b | |||
| 6b7e706100 | |||
| bb90ed5446 | |||
| 107843d58f | |||
| 63f089540e | |||
| c35ff4f913 | |||
| cd6b5cdcc3 | |||
| 8f1ca00c9d | |||
| f21f583d05 | |||
| e940d9f6fe | |||
| 1ed8e939b8 | |||
| b5d63783f7 | |||
| e0113ec267 | |||
| 22d2bcc21d | |||
| 91dcb2f773 | |||
| c676c732ab | |||
| 444f2d8a12 | |||
| f10da3c7f0 | |||
| b8dd9f82bd | |||
| 1157e60972 | |||
| e9e4d8c725 | |||
| 186fa24106 | |||
| 63780b7b8d | |||
| e3e4769d78 | |||
| b2e9c4e4e9 | |||
| db4cb36538 | |||
| 984864553e | |||
| 2707a5627c | |||
| 8e0d6d41a6 | |||
| 2b76a45e17 | |||
| 7ab54da8c4 | |||
| 2cce29e858 | |||
| ef1223391b | |||
| fb98a797ba | |||
| 6be31bdeb2 | |||
| fce5990d19 | |||
| 1d4ee0c33e | |||
| a3e102e456 | |||
| 21dcac9fac | |||
| f0ee82fdd2 | |||
| 0c6a098af9 | |||
| 5bf359d34f | |||
| 6d784f2444 | |||
| 835bf1e77f | |||
| df5fbda72f | |||
| c482f768d9 | |||
| 21cf7459c9 | |||
| cf1ba6588a | |||
| 858f2c7f29 | |||
| f63238faed | |||
| f6ff85f69d | |||
| ec5b5939b9 | |||
| dec00ff9cc | |||
| 9fddd77f0d | |||
| aae65c9d38 | |||
| f3b197fa56 | |||
| 0a9ce5f526 | |||
| a2029002c4 | |||
| 7c41c8e23a | |||
| 36fdb88d96 | |||
| c69239ca16 | |||
| e10aa89313 | |||
| d0a97a3f4a | |||
| e01dc77a61 | |||
| 509ba52ba2 | |||
| c37a0338c5 | |||
| 9f23d5a6f4 | |||
| 3f50bf66f4 | |||
| 233c55ab19 | |||
| cb30dd91e3 | |||
| 41d82e10f9 | |||
| 8496c9e181 | |||
| 3dadad5131 | |||
| 00146e7474 | |||
| 12ac47e949 |
@ -85,11 +85,17 @@ dependency-updates:
|
|||||||
stage: build
|
stage: build
|
||||||
only:
|
only:
|
||||||
- branches
|
- branches
|
||||||
|
before_script:
|
||||||
|
- mkdir -p .cache/bin
|
||||||
|
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||||
|
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||||
script:
|
script:
|
||||||
- make build
|
- make build
|
||||||
|
- git diff && git diff-index --quiet HEAD
|
||||||
artifacts:
|
artifacts:
|
||||||
# Note: The latest artifacts for refs are locked against deletion, and kept regardless of the expiry time.
|
# Note: The latest artifacts for refs are locked against deletion, and kept
|
||||||
# Introduced in GitLab 13.0 behind a disabled feature flag, and made the default behavior in GitLab 13.4.
|
# regardless of the expiry time. Introduced in GitLab 13.0 behind a
|
||||||
|
# disabled feature flag, and made the default behavior in GitLab 13.4.
|
||||||
expire_in: 1 day
|
expire_in: 1 day
|
||||||
tags:
|
tags:
|
||||||
- large
|
- large
|
||||||
@ -187,77 +193,6 @@ build-ie-darwin-qa:
|
|||||||
paths:
|
paths:
|
||||||
- ie_*.tgz
|
- ie_*.tgz
|
||||||
|
|
||||||
.build-windows-base:
|
|
||||||
extends: .build-base
|
|
||||||
services:
|
|
||||||
- docker:dind
|
|
||||||
variables:
|
|
||||||
DOCKER_HOST: tcp://docker:2375
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
extends: .build-windows-base
|
|
||||||
script:
|
|
||||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
|
||||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
|
||||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
|
||||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
|
||||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
|
||||||
- go mod download
|
|
||||||
- TARGET_OS=windows make build
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-windows-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
build-windows-qa:
|
|
||||||
extends: .build-windows-base
|
|
||||||
only:
|
|
||||||
- web
|
|
||||||
script:
|
|
||||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
|
||||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
|
||||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
|
||||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
|
||||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
|
||||||
- go mod download
|
|
||||||
- TARGET_OS=windows BUILD_TAGS="build_qa" make build
|
|
||||||
artifacts:
|
|
||||||
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- bridge_*.tgz
|
|
||||||
|
|
||||||
build-ie-windows:
|
|
||||||
extends: .build-windows-base
|
|
||||||
script:
|
|
||||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
|
||||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
|
||||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
|
||||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
|
||||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
|
||||||
- go mod download
|
|
||||||
- TARGET_OS=windows make build-ie
|
|
||||||
artifacts:
|
|
||||||
name: "ie-windows-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- ie_*.tgz
|
|
||||||
|
|
||||||
build-ie-windows-qa:
|
|
||||||
extends: .build-windows-base
|
|
||||||
only:
|
|
||||||
- web
|
|
||||||
script:
|
|
||||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
|
||||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
|
||||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
|
||||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
|
||||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
|
||||||
- go mod download
|
|
||||||
- TARGET_OS=windows BUILD_TAGS="build_qa" make build-ie
|
|
||||||
artifacts:
|
|
||||||
name: "ie-windows-qa-$CI_COMMIT_SHORT_SHA"
|
|
||||||
paths:
|
|
||||||
- ie_*.tgz
|
|
||||||
|
|
||||||
# Stage: MIRROR
|
# Stage: MIRROR
|
||||||
|
|
||||||
mirror-repo:
|
mirror-repo:
|
||||||
|
|||||||
97
Changelog.md
97
Changelog.md
@ -2,6 +2,101 @@
|
|||||||
|
|
||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
## [Bridge 1.8.11] James
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1415: Only messages which are in Spam should be moved to INBOX once they are marked as not-a-spam.
|
||||||
|
* GODT-1405: Integration test fix: Prevent unilateral update in FETCH when copying message by append.
|
||||||
|
* GODT-1392: Fix broken header fields for attachments.
|
||||||
|
* GODT-1360: Fix live integration test.
|
||||||
|
* GODT-968: Messages in All Mail should not be able to mark as deleted.
|
||||||
|
* GODT-967: Append external message to All Mail should be APPEND to Archive instead.
|
||||||
|
* GODT-966: Append internal message to AllMail should be no action.
|
||||||
|
* GODT-965: MOVE command should end with error for All Mail.
|
||||||
|
* GODT-963: STORE removing junk or adding nojunk should move message to inbox.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-1397: Update bbolt to v1.3.6.
|
||||||
|
* GODT-1410: Remove event ID from sentry report description.
|
||||||
|
* GODT-1395: CI should fail on go.sum changed.
|
||||||
|
|
||||||
|
## [Bridge 1.8.10] James
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1348: Max 100 conn per host.
|
||||||
|
* GODT-1204: Handle importing too big messages.
|
||||||
|
* GODT-1202: Do not update package if it's version older than launcher.
|
||||||
|
* GODT-1318: Bump gopenpgp to v2.2.2, go-srp to v0.0.1, go-crypto to 52430bf6.
|
||||||
|
* GODT-219: Update to godog v0.12.1.
|
||||||
|
* GODT-1205: "RCPT TO" does not contain all addressed from "CC".
|
||||||
|
* GODT-1103: Cleanup on windows when uninstalling Bridge.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.8.9] James
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1263: Fix crash on invalid or empty header.
|
||||||
|
* GODT-1235: Fix 401 response error handling.
|
||||||
|
* GODT-1261: Fix building messages with long key.
|
||||||
|
* Other: use windows-compatible filename when dumping message in QA builds.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.8.8] James
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-1234 Set attachment name 'message.eml' for `message/rfc822` attachments.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.8.7] James
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-1201: Update gopenpgp to 2.1.10.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1193: Do not doubly encode parts.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.8.6] James
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* GODT-1187: Remove IMAP/SMTP blocking when no internet.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-1166: Reduce the number of auth for live test.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1193: Do not use message.Read permit non-UTF-8 charsets.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.8.5] James
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1189: Draft created on Outlook is synced on web.
|
||||||
|
* GODT-1190: Fix some random crashes of Bridge on Windows.
|
||||||
|
* GODT-1191: Fix data loss of some drafts messages when restarting outlook on Windows.
|
||||||
|
|
||||||
|
## [Bridge 1.8.4] James
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-1155: Update gopenpgp v2.1.9 and use go-srp.
|
||||||
|
* GODT-1044: Lite parser for appended messages.
|
||||||
|
* GODT-1183: Add test for getting contact emails by email.
|
||||||
|
* GODT-1184: Preserve signatures in externally signed messages.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-949: Ignore some InvalidMediaParameter errors in lite parser.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1161: Guarantee order of responses when creating new message.
|
||||||
|
* GODT-1162: Fix wrong section 1 error when email has no MIME parts.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.8.3] James
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1182: Use correct contact route.
|
||||||
|
|
||||||
|
|
||||||
## [Bridge 1.8.2] James
|
## [Bridge 1.8.2] James
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@ -20,11 +115,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
|||||||
* GODT-1056 Check encrypted size of the message before upload.
|
* GODT-1056 Check encrypted size of the message before upload.
|
||||||
* GODT-1143 Turn off SMTP server while no connection.
|
* GODT-1143 Turn off SMTP server while no connection.
|
||||||
* GODT-1089 Explicitly open system preferences window on BigSur.
|
* GODT-1089 Explicitly open system preferences window on BigSur.
|
||||||
|
* GODT-35: Connection manager with resty.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* GODT-1159 SMTP server not restarting after restored internet.
|
* GODT-1159 SMTP server not restarting after restored internet.
|
||||||
* GODT-1146 Refactor handling of fetching BODY[HEADER] (and similar) regarding trailing newline.
|
* GODT-1146 Refactor handling of fetching BODY[HEADER] (and similar) regarding trailing newline.
|
||||||
* GODT-1152 Correctly resolve wildcard sequence/UID set.
|
* GODT-1152 Correctly resolve wildcard sequence/UID set.
|
||||||
|
* GODT-876 Set default from if empty for importing draft.
|
||||||
* Other: Avoid API jail.
|
* Other: Avoid API jail.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@ -10,7 +10,7 @@ TARGET_OS?=${GOOS}
|
|||||||
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
|
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie 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?=1.8.2+git
|
BRIDGE_APP_VERSION?=1.8.11+git
|
||||||
IE_APP_VERSION?=1.3.3+git
|
IE_APP_VERSION?=1.3.3+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
SRC_ICO:=logo.ico
|
SRC_ICO:=logo.ico
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||||
@ -86,7 +87,7 @@ func main() { // nolint[funlen]
|
|||||||
|
|
||||||
versioner := versioner.New(updatesPath)
|
versioner := versioner.New(updatesPath)
|
||||||
|
|
||||||
exe, err := getPathToExecutable(ExeName, versioner, kr, reporter)
|
exe, err := getPathToUpdatedExecutable(ExeName, versioner, kr, reporter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exe, err = getFallbackExecutable(ExeName, versioner); err != nil {
|
if exe, err = getFallbackExecutable(ExeName, versioner); err != nil {
|
||||||
logrus.WithError(err).Fatal("Failed to find any launchable executable")
|
logrus.WithError(err).Fatal("Failed to find any launchable executable")
|
||||||
@ -142,7 +143,7 @@ func appendLauncherPath(path string, args []string) []string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPathToExecutable(
|
func getPathToUpdatedExecutable(
|
||||||
name string,
|
name string,
|
||||||
versioner *versioner.Versioner,
|
versioner *versioner.Versioner,
|
||||||
kr *crypto.KeyRing,
|
kr *crypto.KeyRing,
|
||||||
@ -153,6 +154,11 @@ func getPathToExecutable(
|
|||||||
return "", errors.Wrap(err, "failed to list available versions")
|
return "", errors.Wrap(err, "failed to list available versions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentVersion, err := semver.StrictNewVersion(constants.Version)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithField("version", constants.Version).WithError(err).Error("Failed to parse current version")
|
||||||
|
}
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
vlog := logrus.WithField("version", version)
|
vlog := logrus.WithField("version", version)
|
||||||
|
|
||||||
@ -170,6 +176,11 @@ func getPathToExecutable(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip versions that are less or equal to launcher version.
|
||||||
|
if currentVersion != nil && !version.SemVer().GreaterThan(currentVersion) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
exe, err := version.GetExecutable(name)
|
exe, err := version.GetExecutable(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vlog.WithError(err).Error("Failed to get executable")
|
vlog.WithError(err).Error("Failed to get executable")
|
||||||
@ -179,7 +190,7 @@ func getPathToExecutable(
|
|||||||
return exe, nil
|
return exe, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("no available versions")
|
return "", errors.New("no available newer versions")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {
|
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
# Encryption
|
# Encryption
|
||||||
|
|
||||||
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
|
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
|
||||||
in PMAPI and bridge utils (in pacakge such as messages). All packages are using our high-level
|
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
|
||||||
GopenPGP library on top of openpgp.
|
GopenPGP library on top of OpenPGP.
|
||||||
|
|
||||||
## `gopenpgp.KeyRing`
|
## `gopenpgp.KeyRing`
|
||||||
|
|
||||||
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
|
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
|
||||||
key is always on the first position, then there old ones to be able to decrypt last e-mail.
|
key is always on the first position, then there old ones to be able to decrypt last e-mail.
|
||||||
Openpgp encrypts given message with all available keys, so we need to first get first (primary)
|
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
|
||||||
key for encryption to have message encrypted only once with primary key.
|
key for encryption to have message encrypted only once with primary key.
|
||||||
|
|||||||
24
go.mod
24
go.mod
@ -1,33 +1,34 @@
|
|||||||
module github.com/ProtonMail/proton-bridge
|
module github.com/ProtonMail/proton-bridge
|
||||||
|
|
||||||
go 1.13
|
go 1.15
|
||||||
|
|
||||||
// These dependencies are `replace`d below, so the version numbers should be ignored.
|
// These dependencies are `replace`d below, so the version numbers should be ignored.
|
||||||
// They are in a separate require block to highlight this.
|
// They are in a separate require block to highlight this.
|
||||||
require (
|
require (
|
||||||
github.com/docker/docker-credential-helpers v0.6.3
|
github.com/docker/docker-credential-helpers v0.6.3
|
||||||
github.com/emersion/go-imap v1.0.6
|
github.com/emersion/go-imap v1.0.6
|
||||||
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998
|
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
||||||
github.com/Masterminds/semver/v3 v3.1.0
|
github.com/Masterminds/semver/v3 v3.1.0
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||||
github.com/ProtonMail/go-rfc5322 v0.5.0
|
github.com/ProtonMail/go-rfc5322 v0.8.0
|
||||||
|
github.com/ProtonMail/go-srp v0.0.1
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.1.3
|
github.com/ProtonMail/gopenpgp/v2 v2.2.2
|
||||||
github.com/PuerkitoBio/goquery v1.5.1
|
github.com/PuerkitoBio/goquery v1.5.1
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
|
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
|
||||||
github.com/chzyer/logex v1.1.10 // indirect
|
github.com/chzyer/logex v1.1.10 // indirect
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||||
github.com/cucumber/godog v0.8.1
|
github.com/cucumber/godog v0.12.1
|
||||||
|
github.com/cucumber/messages-go/v16 v16.0.1
|
||||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
|
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
|
||||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4
|
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
||||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c
|
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c
|
||||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26
|
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26
|
||||||
@ -57,13 +58,14 @@ require (
|
|||||||
github.com/sirupsen/logrus v1.7.0
|
github.com/sirupsen/logrus v1.7.0
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||||
github.com/urfave/cli/v2 v2.2.0
|
github.com/urfave/cli/v2 v2.2.0
|
||||||
github.com/vmihailenco/msgpack/v5 v5.1.3
|
github.com/vmihailenco/msgpack/v5 v5.1.3
|
||||||
go.etcd.io/bbolt v1.3.5
|
go.etcd.io/bbolt v1.3.6
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
|
||||||
)
|
)
|
||||||
@ -71,6 +73,6 @@ require (
|
|||||||
replace (
|
replace (
|
||||||
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
||||||
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
||||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
|
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
|
||||||
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c
|
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
|
||||||
)
|
)
|
||||||
|
|||||||
316
go.sum
316
go.sum
@ -1,3 +1,16 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
|
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
||||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
@ -8,28 +21,35 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
|
|||||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 h1:pHA4K54ifoogVLunGGHi3xyF5Nz4x+Uh3dJuy3NwGQQ=
|
||||||
github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c h1:iaVbEOnskSGgcH7XQWHG6VPirHDRoYe+Idd0/dl4m8A=
|
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||||
github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
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/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2 h1:pQkjJELHayW59jp7r4G5Dlmnicr5McejDfwsjcwI1SU=
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
|
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
|
||||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
|
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
||||||
|
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0=
|
||||||
|
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
||||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.5.0 h1:LbKWjgfvumYZCr8BgGyTUk3ETGkFLAjQdkuSUpZ5CcE=
|
github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.5.0/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us=
|
||||||
|
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff h1:eiue56XAPSkOpsy5Fwnyz4+Vd7i2cN5D4orc++Irt1g=
|
||||||
|
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
|
||||||
|
github.com/ProtonMail/go-srp v0.0.1 h1:J0O9Zb5XTC6iDrB7feH41cu+TUEB+l7uHctXIK6oS2o=
|
||||||
|
github.com/ProtonMail/go-srp v0.0.1/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.1.3 h1:4+nFDJ9WtcUQTip/je2Ll3P21XhAUl4asWsafLrw97c=
|
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.1.3/go.mod h1:WeYndoqEcRR4/QbgRL24z6OwYX5T1RWerRk8NfZ6rJM=
|
github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||||
@ -38,29 +58,53 @@ github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzg
|
|||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
|
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
|
||||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
|
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
|
||||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
|
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
|
||||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8=
|
github.com/cronokirby/saferith v0.31.0 h1:TIlhldetKLeGAb19bZvWiuwQEzfzwSPthDEyJ9Ah8xs=
|
||||||
github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
|
github.com/cronokirby/saferith v0.31.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
||||||
|
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
|
||||||
|
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
|
||||||
|
github.com/cucumber/godog v0.12.1 h1:IhWVYFKDReM5WsuA9AuRLRPWOyvFNO9UBUKrNfLPais=
|
||||||
|
github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
|
||||||
|
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
|
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||||
|
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
||||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -69,12 +113,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
|
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
|
||||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
|
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
|
||||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4 h1:/JIALzmCduf5o8TWJSiOBzTb9+R0SChwElUrJLlp2po=
|
|
||||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4/go.mod h1:o14zPKCmEH5WC1vU5SdPoZGgNvQx7zzKSnxPQlobo78=
|
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
||||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
||||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1yFiZjBgi7gGnQiLhpSgghJ0YTnKD0l4EUqqc=
|
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1yFiZjBgi7gGnQiLhpSgghJ0YTnKD0l4EUqqc=
|
||||||
@ -83,21 +126,18 @@ github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8
|
|||||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
|
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
|
||||||
github.com/emersion/go-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
|
github.com/emersion/go-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
|
||||||
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
|
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
|
||||||
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
|
||||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b h1:xYuhW6egTaCP+zjbUcfoy/Dr3ASdVPR9W7fmkHvZHPE=
|
|
||||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b/go.mod h1:N1JWdZQ2WRUalmdHAX308CWBq747VJ8oUorFI3VCBwU=
|
|
||||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
||||||
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
|
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
|
||||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
|
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
|
||||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
@ -107,40 +147,96 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||||
github.com/getsentry/sentry-go v0.8.0 h1:F52cjBVLuiTfdW6p4JFuxlt3pOjKfWYT/aka7cdJ7v0=
|
github.com/getsentry/sentry-go v0.8.0 h1:F52cjBVLuiTfdW6p4JFuxlt3pOjKfWYT/aka7cdJ7v0=
|
||||||
github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
|
github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||||
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
||||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
|
||||||
|
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
@ -151,9 +247,13 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw
|
|||||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
|
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
||||||
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
||||||
@ -163,12 +263,17 @@ github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubc
|
|||||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
|
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
|
||||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
|
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
|
||||||
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -178,12 +283,12 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
|
|||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/martinlindhe/base36 v1.1.0 h1:cIwvvwYse/0+1CkUPYH5ZvVIYG3JrILmQEIbLuar02Y=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/martinlindhe/base36 v1.1.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
@ -193,61 +298,95 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
|||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
|
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
|
||||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
|
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||||
|
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||||
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -258,15 +397,17 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
||||||
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=
|
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
|
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
|
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
|
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
|
||||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
|
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
@ -285,75 +426,147 @@ github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq
|
|||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
|
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec h1:A1qYjneJuzBZZ2gIB8rd6zrfq6l7SoEMJ8EsSilNK/U=
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec h1:A1qYjneJuzBZZ2gIB8rd6zrfq6l7SoEMJ8EsSilNK/U=
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
@ -361,18 +574,53 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
|||||||
@ -68,7 +68,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.Println("Authenticating ... ")
|
f.Println("Authenticating ... ")
|
||||||
client, auth, err := f.ie.Login(loginName, password)
|
client, auth, err := f.ie.Login(loginName, []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.processAPIError(err)
|
f.processAPIError(err)
|
||||||
return
|
return
|
||||||
@ -96,7 +96,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.Println("Adding account ...")
|
f.Println("Adding account ...")
|
||||||
user, err := f.ie.FinishLogin(client, auth, mailboxPassword)
|
user, err := f.ie.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
||||||
f.Println("Adding account was unsuccessful:", err)
|
f.Println("Adding account was unsuccessful:", err)
|
||||||
|
|||||||
@ -115,7 +115,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.Println("Authenticating ... ")
|
f.Println("Authenticating ... ")
|
||||||
client, auth, err := f.bridge.Login(loginName, password)
|
client, auth, err := f.bridge.Login(loginName, []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.processAPIError(err)
|
f.processAPIError(err)
|
||||||
return
|
return
|
||||||
@ -143,7 +143,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.Println("Adding account ...")
|
f.Println("Adding account ...")
|
||||||
user, err := f.bridge.FinishLogin(client, auth, mailboxPassword)
|
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
||||||
f.Println("Adding account was unsuccessful:", err)
|
f.Println("Adding account was unsuccessful:", err)
|
||||||
|
|||||||
@ -94,7 +94,7 @@ Item {
|
|||||||
font.pointSize : Style.main.fontSize * Style.pt
|
font.pointSize : Style.main.fontSize * Style.pt
|
||||||
text:
|
text:
|
||||||
"ProtonMail Bridge "+go.getBackendVersion()+"\n"+
|
"ProtonMail Bridge "+go.getBackendVersion()+"\n"+
|
||||||
"© 2020 Proton Technologies AG"
|
"© 2021 Proton Technologies AG"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row {
|
Row {
|
||||||
|
|||||||
@ -81,7 +81,7 @@ Item {
|
|||||||
color: Style.main.textDisabled
|
color: Style.main.textDisabled
|
||||||
horizontalAlignment: Qt.AlignHCenter
|
horizontalAlignment: Qt.AlignHCenter
|
||||||
font.pointSize : Style.main.fontSize * Style.pt
|
font.pointSize : Style.main.fontSize * Style.pt
|
||||||
text: "ProtonMail Import-Export app Version "+go.getBackendVersion()+"\n© 2020 Proton Technologies AG"
|
text: "ProtonMail Import-Export app Version "+go.getBackendVersion()+"\n© 2021 Proton Technologies AG"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -186,7 +186,7 @@ func (a *Accounts) showLoginError(err error, scope string) bool {
|
|||||||
// 2: when has no 2FA but have MBOX
|
// 2: when has no 2FA but have MBOX
|
||||||
func (a *Accounts) Login(login, password string) int {
|
func (a *Accounts) Login(login, password string) int {
|
||||||
var err error
|
var err error
|
||||||
a.authClient, a.auth, err = a.um.Login(login, password)
|
a.authClient, a.auth, err = a.um.Login(login, []byte(password))
|
||||||
if a.showLoginError(err, "login") {
|
if a.showLoginError(err, "login") {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@ -230,7 +230,7 @@ func (a *Accounts) AddAccount(mailboxPassword string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := a.um.FinishLogin(a.authClient, a.auth, mailboxPassword)
|
user, err := a.um.FinishLogin(a.authClient, a.auth, []byte(mailboxPassword))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Login was unsuccessful")
|
log.WithError(err).Error("Login was unsuccessful")
|
||||||
a.qml.SetAddAccountWarning("Failure: "+err.Error(), -2)
|
a.qml.SetAddAccountWarning("Failure: "+err.Error(), -2)
|
||||||
|
|||||||
@ -152,7 +152,7 @@ func (s *FrontendQt) showLoginError(err error, scope string) bool {
|
|||||||
// 2: when has no 2FA but have MBOX
|
// 2: when has no 2FA but have MBOX
|
||||||
func (s *FrontendQt) login(login, password string) int {
|
func (s *FrontendQt) login(login, password string) int {
|
||||||
var err error
|
var err error
|
||||||
s.authClient, s.auth, err = s.bridge.Login(login, password)
|
s.authClient, s.auth, err = s.bridge.Login(login, []byte(password))
|
||||||
if s.showLoginError(err, "login") {
|
if s.showLoginError(err, "login") {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ func (s *FrontendQt) addAccount(mailboxPassword string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := s.bridge.FinishLogin(s.authClient, s.auth, mailboxPassword)
|
user, err := s.bridge.FinishLogin(s.authClient, s.auth, []byte(mailboxPassword))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("Login was unsuccessful")
|
log.WithError(err).Error("Login was unsuccessful")
|
||||||
s.Qml.SetAddAccountWarning("Failure: "+err.Error(), -2)
|
s.Qml.SetAddAccountWarning("Failure: "+err.Error(), -2)
|
||||||
|
|||||||
@ -49,8 +49,8 @@ type Updater interface {
|
|||||||
|
|
||||||
// UserManager is an interface of users needed by frontend.
|
// UserManager is an interface of users needed by frontend.
|
||||||
type UserManager interface {
|
type UserManager interface {
|
||||||
Login(username, password string) (pmapi.Client, *pmapi.Auth, error)
|
Login(username string, password []byte) (pmapi.Client, *pmapi.Auth, error)
|
||||||
FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword string) (User, error)
|
FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error)
|
||||||
GetUsers() []User
|
GetUsers() []User
|
||||||
GetUser(query string) (User, error)
|
GetUser(query string) (User, error)
|
||||||
DeleteUser(userID string, clearCache bool) error
|
DeleteUser(userID string, clearCache bool) error
|
||||||
@ -94,7 +94,7 @@ func NewBridgeWrap(bridge *bridge.Bridge) *bridgeWrap { //nolint[golint]
|
|||||||
return &bridgeWrap{Bridge: bridge}
|
return &bridgeWrap{Bridge: bridge}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bridgeWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword string) (User, error) {
|
func (b *bridgeWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) {
|
||||||
return b.Bridge.FinishLogin(client, auth, mailboxPassword)
|
return b.Bridge.FinishLogin(client, auth, mailboxPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ func NewImportExportWrap(ie *importexport.ImportExport) *importExportWrap { //no
|
|||||||
return &importExportWrap{ImportExport: ie}
|
return &importExportWrap{ImportExport: ie}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *importExportWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword string) (User, error) {
|
func (b *importExportWrap) FinishLogin(client pmapi.Client, auth *pmapi.Auth, mailboxPassword []byte) (User, error) {
|
||||||
return b.ImportExport.FinishLogin(client, auth, mailboxPassword)
|
return b.ImportExport.FinishLogin(client, auth, mailboxPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
85
internal/imap/idle/extension.go
Normal file
85
internal/imap/idle/extension.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package idle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-imap/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
idleCommand = "IDLE" // Capability and Command identificator
|
||||||
|
doneLine = "DONE"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler for IDLE extension.
|
||||||
|
type Handler struct{}
|
||||||
|
|
||||||
|
// Command for IDLE handler.
|
||||||
|
func (h *Handler) Command() *imap.Command {
|
||||||
|
return &imap.Command{Name: idleCommand}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse for IDLE handler.
|
||||||
|
func (h *Handler) Parse(fields []interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the IDLE request.
|
||||||
|
func (h *Handler) Handle(conn server.Conn) error {
|
||||||
|
cont := &imap.ContinuationReq{Info: "idling"}
|
||||||
|
if err := conn.WriteResp(cont); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for DONE
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
scanner.Scan()
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ToUpper(scanner.Text()) != doneLine {
|
||||||
|
return errors.New("expected DONE")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type extension struct{}
|
||||||
|
|
||||||
|
func (ext *extension) Capabilities(c server.Conn) []string {
|
||||||
|
return []string{idleCommand}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ext *extension) Command(name string) server.HandlerFactory {
|
||||||
|
if name != idleCommand {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() server.Handler {
|
||||||
|
return &Handler{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExtension() server.Extension {
|
||||||
|
return &extension{}
|
||||||
|
}
|
||||||
@ -18,7 +18,10 @@
|
|||||||
package imap
|
package imap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -28,6 +31,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
|
"github.com/emersion/go-message/textproto"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,11 +47,15 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
|
|||||||
}, "APPEND", flags, date)
|
}, "APPEND", flags, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) createMessage(flags []string, date time.Time, body imap.Literal) error { //nolint[funlen]
|
func (im *imapMailbox) createMessage(imapFlags []string, date time.Time, r imap.Literal) error { //nolint[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()
|
||||||
|
|
||||||
m, _, _, readers, err := message.Parse(body)
|
// NOTE: Is this lock meant to be here?
|
||||||
|
im.user.appendExpungeLock.Lock()
|
||||||
|
defer im.user.appendExpungeLock.Unlock()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -56,113 +64,101 @@ func (im *imapMailbox) createMessage(flags []string, date time.Time, body imap.L
|
|||||||
if addr == nil {
|
if addr == nil {
|
||||||
return errors.New("no available address for encryption")
|
return errors.New("no available address for encryption")
|
||||||
}
|
}
|
||||||
m.AddressID = addr.ID
|
|
||||||
|
|
||||||
kr, err := im.user.client().KeyRingForAddressID(addr.ID)
|
kr, err := im.user.client().KeyRingForAddressID(addr.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle imported messages which have no "Sender" address.
|
|
||||||
// This sometimes occurs with outlook which reports errors as imported emails or for drafts.
|
|
||||||
if m.Sender == nil {
|
|
||||||
im.log.Warning("Append: Missing email sender. Will use main address")
|
|
||||||
m.Sender = &mail.Address{
|
|
||||||
Name: "",
|
|
||||||
Address: addr.Email,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Drafts" needs to call special API routes.
|
|
||||||
// Clients always append the whole message again and remove the old one.
|
|
||||||
if im.storeMailbox.LabelID() == pmapi.DraftLabel {
|
if im.storeMailbox.LabelID() == pmapi.DraftLabel {
|
||||||
// Sender address needs to be sanitised (drafts need to match cases exactly).
|
return im.createDraftMessage(kr, addr.Email, body)
|
||||||
m.Sender.Address = pmapi.ConstructAddress(m.Sender.Address, addr.Email)
|
|
||||||
|
|
||||||
draft, _, err := im.user.storeUser.CreateDraft(kr, m, readers, "", "", "")
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create draft")
|
|
||||||
}
|
|
||||||
|
|
||||||
targetSeq := im.storeMailbox.GetUIDList([]string{draft.ID})
|
|
||||||
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to make sure this is an import, and not a sent message from this account
|
|
||||||
// (sent messages from the account will be added by the event loop).
|
|
||||||
if im.storeMailbox.LabelID() == pmapi.SentLabel {
|
if im.storeMailbox.LabelID() == pmapi.SentLabel {
|
||||||
sanitizedSender := pmapi.SanitizeEmail(m.Sender.Address)
|
m, _, _, _, err := message.Parse(bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether this message was sent by a bridge user.
|
if m.Sender == nil {
|
||||||
user, err := im.user.backend.bridge.GetUser(sanitizedSender)
|
m.Sender = &mail.Address{Address: addr.Email}
|
||||||
if err == nil && user.ID() == im.storeUser.UserID() {
|
}
|
||||||
logEntry := im.log.WithField("addr", sanitizedSender).WithField("extID", m.Header.Get("Message-Id"))
|
|
||||||
|
if user, err := im.user.backend.bridge.GetUser(pmapi.SanitizeEmail(m.Sender.Address)); err == nil && user.ID() == im.storeUser.UserID() {
|
||||||
|
logEntry := im.log.WithField("sender", m.Sender).WithField("extID", m.Header.Get("Message-Id")).WithField("date", date)
|
||||||
|
|
||||||
// If we find the message in the store already, we can skip importing it.
|
|
||||||
if foundUID := im.storeMailbox.GetUIDByHeader(&m.Header); foundUID != uint32(0) {
|
if foundUID := im.storeMailbox.GetUIDByHeader(&m.Header); foundUID != uint32(0) {
|
||||||
logEntry.Info("Ignoring APPEND of duplicate to Sent folder")
|
logEntry.Info("Ignoring APPEND of duplicate to Sent folder")
|
||||||
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), &uidplus.OrderedSeq{foundUID})
|
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), &uidplus.OrderedSeq{foundUID})
|
||||||
}
|
}
|
||||||
|
|
||||||
// We didn't find the message in the store, so we are currently sending it.
|
logEntry.Info("No matching UID, continuing APPEND to Sent")
|
||||||
logEntry.WithField("time", date).Info("No matching UID, continuing APPEND to Sent")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message.ParseFlags(m, flags)
|
hdr, err := textproto.ReadHeader(bufio.NewReader(bytes.NewReader(body)))
|
||||||
if !date.IsZero() {
|
if err != nil {
|
||||||
m.Time = date.Unix()
|
|
||||||
}
|
|
||||||
|
|
||||||
internalID := m.Header.Get("X-Pm-Internal-Id")
|
|
||||||
references := m.Header.Get("References")
|
|
||||||
referenceList := strings.Fields(references)
|
|
||||||
|
|
||||||
// In case there is a mail client which corrupts headers, try
|
|
||||||
// "References" too.
|
|
||||||
if internalID == "" && len(referenceList) > 0 {
|
|
||||||
lastReference := referenceList[len(referenceList)-1]
|
|
||||||
match := pmapi.RxInternalReferenceFormat.FindStringSubmatch(lastReference)
|
|
||||||
if len(match) == 2 {
|
|
||||||
internalID = match[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
im.user.appendExpungeLock.Lock()
|
|
||||||
defer im.user.appendExpungeLock.Unlock()
|
|
||||||
|
|
||||||
// Avoid appending a message which is already on the server. Apply the
|
|
||||||
// new label instead. This always happens with Outlook (it uses APPEND
|
|
||||||
// instead of COPY).
|
|
||||||
if internalID != "" {
|
|
||||||
// Check to see if this belongs to a different address in split mode or another ProtonMail account.
|
|
||||||
msg, err := im.storeMailbox.GetMessage(internalID)
|
|
||||||
if err == nil && (im.user.user.IsCombinedAddressMode() || (im.storeAddress.AddressID() == msg.Message().AddressID)) {
|
|
||||||
IDs := []string{internalID}
|
|
||||||
|
|
||||||
// See the comment bellow.
|
|
||||||
if msg.IsMarkedDeleted() {
|
|
||||||
if err := im.storeMailbox.MarkMessagesUndeleted(IDs); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to undelete re-imported internal message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = im.storeMailbox.LabelMessages(IDs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
targetSeq := im.storeMailbox.GetUIDList(IDs)
|
|
||||||
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
im.log.Info("Importing external message")
|
|
||||||
if err := im.importMessage(m, readers, kr); err != nil {
|
|
||||||
im.log.Error("Import failed: ", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid appending a message which is already on the server. Apply the new label instead.
|
||||||
|
// This always happens with Outlook because it uses APPEND instead of COPY.
|
||||||
|
internalID := hdr.Get("X-Pm-Internal-Id")
|
||||||
|
|
||||||
|
// In case there is a mail client which corrupts headers, try "References" too.
|
||||||
|
if internalID == "" {
|
||||||
|
if references := strings.Fields(hdr.Get("References")); len(references) > 0 {
|
||||||
|
if match := pmapi.RxInternalReferenceFormat.FindStringSubmatch(references[len(references)-1]); len(match) == 2 {
|
||||||
|
internalID = match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if internalID != "" {
|
||||||
|
if msg, err := im.storeMailbox.GetMessage(internalID); err == nil {
|
||||||
|
if im.user.user.IsCombinedAddressMode() || im.storeAddress.AddressID() == msg.Message().AddressID {
|
||||||
|
return im.labelExistingMessage(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return im.importMessage(kr, hdr, body, imapFlags, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imapMailbox) createDraftMessage(kr *crypto.KeyRing, email string, body []byte) error {
|
||||||
|
im.log.Info("Creating draft message")
|
||||||
|
|
||||||
|
m, _, _, readers, err := message.Parse(bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Sender == nil {
|
||||||
|
m.Sender = &mail.Address{}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Sender.Address = pmapi.ConstructAddress(m.Sender.Address, email)
|
||||||
|
|
||||||
|
draft, _, err := im.user.storeUser.CreateDraft(kr, m, readers, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create draft")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), im.storeMailbox.GetUIDList([]string{draft.ID}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMailboxForAddress(address storeAddressProvider, labelID string) (storeMailboxProvider, error) {
|
||||||
|
for _, mailBox := range address.ListMailboxes() {
|
||||||
|
if mailBox.LabelID() == labelID {
|
||||||
|
return mailBox, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("could not find %v label in mailbox for user %v", labelID,
|
||||||
|
address.AddressString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imapMailbox) labelExistingMessage(msg storeMessageProvider) error { //nolint[funlen]
|
||||||
|
im.log.Info("Labelling existing message")
|
||||||
|
|
||||||
// IMAP clients can move message to local folder (setting \Deleted flag)
|
// IMAP clients can move message to local folder (setting \Deleted flag)
|
||||||
// and then move it back (IMAP client does not remember the message,
|
// and then move it back (IMAP client does not remember the message,
|
||||||
// so instead removing the flag it imports duplicate message).
|
// so instead removing the flag it imports duplicate message).
|
||||||
@ -170,29 +166,104 @@ func (im *imapMailbox) createMessage(flags []string, date time.Time, body imap.L
|
|||||||
// not delete the message (EXPUNGE would delete the original message and
|
// not delete the message (EXPUNGE would delete the original message and
|
||||||
// the new duplicate one would stay). API detects duplicates; therefore
|
// the new duplicate one would stay). API detects duplicates; therefore
|
||||||
// we need to remove \Deleted flag if IMAP client re-imports.
|
// we need to remove \Deleted flag if IMAP client re-imports.
|
||||||
msg, err := im.storeMailbox.GetMessage(m.ID)
|
if msg.IsMarkedDeleted() {
|
||||||
if err == nil && msg.IsMarkedDeleted() {
|
if err := im.storeMailbox.MarkMessagesUndeleted([]string{msg.ID()}); err != nil {
|
||||||
if err := im.storeMailbox.MarkMessagesUndeleted([]string{m.ID}); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to undelete re-imported message")
|
log.WithError(err).Error("Failed to undelete re-imported message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
targetSeq := im.storeMailbox.GetUIDList([]string{m.ID})
|
// Outlook Uses APPEND instead of COPY. There is no need to copy to All Mail because messages are already there.
|
||||||
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
|
// If the message is copied from Spam or Trash, it must be moved otherwise we will have data loss.
|
||||||
|
// If the message is moved from any folder, the moment when expunge happens on source we will move message trash unless we move it to archive.
|
||||||
|
// If the message is already in Archive we should not call API at all.
|
||||||
|
// Otherwise the message is already in All mail, Return OK.
|
||||||
|
var storeMBox = im.storeMailbox
|
||||||
|
if pmapi.AllMailLabel == storeMBox.LabelID() {
|
||||||
|
if msg.Message().HasLabelID(pmapi.ArchiveLabel) {
|
||||||
|
return uidplus.AppendResponse(storeMBox.UIDValidity(), storeMBox.GetUIDList([]string{msg.ID()}))
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
storeMBox, err = findMailboxForAddress(im.storeAddress, pmapi.ArchiveLabel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := storeMBox.LabelMessages([]string{msg.ID()}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), im.storeMailbox.GetUIDList([]string{msg.ID()}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) importMessage(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (err error) {
|
func (im *imapMailbox) importMessage(kr *crypto.KeyRing, hdr textproto.Header, body []byte, imapFlags []string, date time.Time) error { //nolint[funlen]
|
||||||
body, err := message.BuildEncrypted(m, readers, kr)
|
im.log.Info("Importing external message")
|
||||||
|
|
||||||
|
var (
|
||||||
|
seen bool
|
||||||
|
flags int64
|
||||||
|
labelIDs []string
|
||||||
|
time int64
|
||||||
|
)
|
||||||
|
|
||||||
|
if hdr.Get("received") == "" {
|
||||||
|
flags = pmapi.FlagSent
|
||||||
|
} else {
|
||||||
|
flags = pmapi.FlagReceived
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, flag := range imapFlags {
|
||||||
|
switch flag {
|
||||||
|
case imap.DraftFlag:
|
||||||
|
flags &= ^pmapi.FlagSent
|
||||||
|
flags &= ^pmapi.FlagReceived
|
||||||
|
|
||||||
|
case imap.SeenFlag:
|
||||||
|
seen = true
|
||||||
|
|
||||||
|
case imap.FlaggedFlag:
|
||||||
|
labelIDs = append(labelIDs, pmapi.StarredLabel)
|
||||||
|
|
||||||
|
case imap.AnsweredFlag:
|
||||||
|
flags |= pmapi.FlagReplied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !date.IsZero() {
|
||||||
|
time = date.Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := message.EncryptRFC822(kr, bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := []string{}
|
var targetMailbox = im.storeMailbox
|
||||||
for _, l := range m.LabelIDs {
|
if targetMailbox.LabelID() == pmapi.AllMailLabel {
|
||||||
if l == pmapi.StarredLabel {
|
// Importing mail in directly into All Mail is not allowed. Instead we redirect the import to Archive
|
||||||
labels = append(labels, pmapi.StarredLabel)
|
// The mail will automatically appear in All mail. The appends response still reports that the mail was
|
||||||
|
// successfully APPEND to All Mail.
|
||||||
|
targetMailbox, err = findMailboxForAddress(im.storeAddress, pmapi.ArchiveLabel)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return im.storeMailbox.ImportMessage(m, body, labels)
|
messageID, err := targetMailbox.ImportMessage(enc, seen, labelIDs, flags, time)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := targetMailbox.GetMessage(messageID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.IsMarkedDeleted() {
|
||||||
|
if err := targetMailbox.MarkMessagesUndeleted([]string{messageID}); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to undelete re-imported message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), im.storeMailbox.GetUIDList([]string{messageID}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,11 @@
|
|||||||
package imap
|
package imap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func filterHeader(header []byte, section *imap.BodySectionName) []byte {
|
func filterHeader(header []byte, section *imap.BodySectionName) []byte {
|
||||||
@ -53,7 +51,7 @@ func filterHeader(header []byte, section *imap.BodySectionName) []byte {
|
|||||||
func filterHeaderLines(header []byte, wantField func(string) bool) []byte {
|
func filterHeaderLines(header []byte, wantField func(string) bool) []byte {
|
||||||
var res []byte
|
var res []byte
|
||||||
|
|
||||||
for _, line := range headerLines(header) {
|
for _, line := range message.HeaderLines(header) {
|
||||||
if len(bytes.TrimSpace(line)) == 0 {
|
if len(bytes.TrimSpace(line)) == 0 {
|
||||||
res = append(res, line...)
|
res = append(res, line...)
|
||||||
} else {
|
} else {
|
||||||
@ -71,34 +69,3 @@ func filterHeaderLines(header []byte, wantField func(string) bool) []byte {
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This sucks because we trim and split stuff here already, only to do it again when we use this function!
|
|
||||||
func headerLines(header []byte) [][]byte {
|
|
||||||
var lines [][]byte
|
|
||||||
|
|
||||||
r := bufio.NewReader(bytes.NewReader(header))
|
|
||||||
|
|
||||||
for {
|
|
||||||
b, err := r.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
panic(errors.Wrap(err, "failed to read header line"))
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(bytes.TrimSpace(b)) == 0:
|
|
||||||
lines = append(lines, b)
|
|
||||||
|
|
||||||
case len(bytes.SplitN(b, []byte(": "), 2)) != 2:
|
|
||||||
lines[len(lines)-1] = append(lines[len(lines)-1], b...)
|
|
||||||
|
|
||||||
default:
|
|
||||||
lines = append(lines, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|||||||
@ -137,8 +137,14 @@ func (im *imapMailbox) setFlags(messageIDs, flags []string) error { //nolint
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) addOrRemoveFlags(operation imap.FlagsOp, messageIDs, flags []string) error {
|
func (im *imapMailbox) addOrRemoveFlags(operation imap.FlagsOp, messageIDs, flags []string) error { //nolint[funlen]
|
||||||
for _, f := range flags {
|
for _, f := range flags {
|
||||||
|
// Adding flag 'nojunk' is equivalent to removing flag 'junk'
|
||||||
|
if (operation == imap.AddFlags) && (f == "nojunk") {
|
||||||
|
operation = imap.RemoveFlags
|
||||||
|
f = "junk"
|
||||||
|
}
|
||||||
|
|
||||||
switch f {
|
switch f {
|
||||||
case imap.SeenFlag:
|
case imap.SeenFlag:
|
||||||
switch operation { //nolint[exhaustive] imap.SetFlags is processed by im.setFlags
|
switch operation { //nolint[exhaustive] imap.SetFlags is processed by im.setFlags
|
||||||
@ -175,23 +181,37 @@ func (im *imapMailbox) addOrRemoveFlags(operation imap.FlagsOp, messageIDs, flag
|
|||||||
}
|
}
|
||||||
case imap.AnsweredFlag, imap.DraftFlag, imap.RecentFlag:
|
case imap.AnsweredFlag, imap.DraftFlag, imap.RecentFlag:
|
||||||
// Not supported.
|
// Not supported.
|
||||||
case message.AppleMailJunkFlag, message.ThunderbirdJunkFlag:
|
case strings.ToLower(message.AppleMailJunkFlag), strings.ToLower(message.ThunderbirdJunkFlag):
|
||||||
storeMailbox, err := im.storeAddress.GetMailbox("Spam")
|
spamMailbox, err := im.storeAddress.GetMailbox("Spam")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle custom junk flags for Apple Mail and Thunderbird.
|
// Handle custom junk flags for Apple Mail and Thunderbird.
|
||||||
switch operation { //nolint[exhaustive] imap.SetFlag is processed by im.setFlags
|
switch operation { //nolint[exhaustive] imap.SetFlag is processed by im.setFlags
|
||||||
// No label removal is necessary because Spam and Inbox are both exclusive labels so the backend
|
// No label removal is necessary because Spam and Inbox are both exclusive labels so the backend
|
||||||
// will automatically take care of label removal.
|
// will automatically take care of label removal.
|
||||||
case imap.AddFlags:
|
case imap.AddFlags:
|
||||||
if err := storeMailbox.LabelMessages(messageIDs); err != nil {
|
if err := spamMailbox.LabelMessages(messageIDs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case imap.RemoveFlags:
|
case imap.RemoveFlags:
|
||||||
if err := storeMailbox.UnlabelMessages(messageIDs); err != nil {
|
// During spam flag removal only messages which
|
||||||
return err
|
// are in Spam folder should be moved to Inbox.
|
||||||
|
// For other messages it is NOOP.
|
||||||
|
messagesInSpam := []string{}
|
||||||
|
for _, mID := range messageIDs {
|
||||||
|
if uid := spamMailbox.GetUIDList([]string{mID}); len(*uid) != 0 {
|
||||||
|
messagesInSpam = append(messagesInSpam, mID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(messagesInSpam) != 0 {
|
||||||
|
inboxMailbox, err := im.storeAddress.GetMailbox("INBOX")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := inboxMailbox.LabelMessages(messagesInSpam); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -230,6 +250,10 @@ func (im *imapMailbox) moveMessages(uid bool, seqSet *imap.SeqSet, targetLabel s
|
|||||||
// 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()
|
||||||
|
|
||||||
|
// Moving from All Mail is not allowed.
|
||||||
|
if im.storeMailbox.LabelID() == pmapi.AllMailLabel {
|
||||||
|
return errors.New("move from All Mail is not allowed")
|
||||||
|
}
|
||||||
return im.labelMessages(uid, seqSet, targetLabel, true)
|
return im.labelMessages(uid, seqSet, targetLabel, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,63 +23,76 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
imapid "github.com/ProtonMail/go-imap-id"
|
imapid "github.com/ProtonMail/go-imap-id"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap/id"
|
"github.com/ProtonMail/proton-bridge/internal/imap/id"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/imap/idle"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
|
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/serverutil"
|
"github.com/ProtonMail/proton-bridge/internal/serverutil"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
imapappendlimit "github.com/emersion/go-imap-appendlimit"
|
imapappendlimit "github.com/emersion/go-imap-appendlimit"
|
||||||
imapidle "github.com/emersion/go-imap-idle"
|
|
||||||
imapmove "github.com/emersion/go-imap-move"
|
imapmove "github.com/emersion/go-imap-move"
|
||||||
imapquota "github.com/emersion/go-imap-quota"
|
imapquota "github.com/emersion/go-imap-quota"
|
||||||
imapunselect "github.com/emersion/go-imap-unselect"
|
imapunselect "github.com/emersion/go-imap-unselect"
|
||||||
"github.com/emersion/go-imap/backend"
|
"github.com/emersion/go-imap/backend"
|
||||||
imapserver "github.com/emersion/go-imap/server"
|
imapserver "github.com/emersion/go-imap/server"
|
||||||
"github.com/emersion/go-sasl"
|
"github.com/emersion/go-sasl"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type imapServer struct {
|
// Server takes care of IMAP listening serving. It implements serverutil.Server.
|
||||||
panicHandler panicHandler
|
type Server struct {
|
||||||
server *imapserver.Server
|
panicHandler panicHandler
|
||||||
userAgent *useragent.UserAgent
|
userAgent *useragent.UserAgent
|
||||||
eventListener listener.Listener
|
debugClient bool
|
||||||
debugClient bool
|
debugServer bool
|
||||||
debugServer bool
|
port int
|
||||||
port int
|
|
||||||
isRunning atomic.Value
|
server *imapserver.Server
|
||||||
|
controller serverutil.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIMAPServer constructs a new IMAP server configured with the given options.
|
// NewIMAPServer constructs a new IMAP server configured with the given options.
|
||||||
func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, port int, tls *tls.Config, imapBackend backend.Backend, userAgent *useragent.UserAgent, eventListener listener.Listener) *imapServer { // nolint[golint]
|
func NewIMAPServer(
|
||||||
s := imapserver.New(imapBackend)
|
panicHandler panicHandler,
|
||||||
s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
debugClient, debugServer bool,
|
||||||
s.TLSConfig = tls
|
port int,
|
||||||
s.AllowInsecureAuth = true
|
tls *tls.Config,
|
||||||
s.ErrorLog = newServerErrorLogger("server-imap")
|
imapBackend backend.Backend,
|
||||||
s.AutoLogout = 30 * time.Minute
|
userAgent *useragent.UserAgent,
|
||||||
|
eventListener listener.Listener,
|
||||||
if debugServer {
|
) *Server {
|
||||||
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
server := &Server{
|
||||||
log.Warning("================================================")
|
panicHandler: panicHandler,
|
||||||
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
userAgent: userAgent,
|
||||||
log.Warning("================================================")
|
debugClient: debugClient,
|
||||||
|
debugServer: debugServer,
|
||||||
|
port: port,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server.server = newGoIMAPServer(tls, imapBackend, server.Address(), userAgent)
|
||||||
|
server.controller = serverutil.NewController(server, eventListener)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGoIMAPServer(tls *tls.Config, backend backend.Backend, address string, userAgent *useragent.UserAgent) *imapserver.Server {
|
||||||
|
server := imapserver.New(backend)
|
||||||
|
server.TLSConfig = tls
|
||||||
|
server.AllowInsecureAuth = true
|
||||||
|
server.ErrorLog = serverutil.NewServerErrorLogger(serverutil.IMAP)
|
||||||
|
server.AutoLogout = 30 * time.Minute
|
||||||
|
server.Addr = address
|
||||||
|
|
||||||
serverID := imapid.ID{
|
serverID := imapid.ID{
|
||||||
imapid.FieldName: "ProtonMail Bridge",
|
imapid.FieldName: "ProtonMail Bridge",
|
||||||
imapid.FieldVendor: "Proton Technologies AG",
|
imapid.FieldVendor: "Proton Technologies AG",
|
||||||
imapid.FieldSupportURL: "https://protonmail.com/support",
|
imapid.FieldSupportURL: "https://protonmail.com/support",
|
||||||
}
|
}
|
||||||
|
|
||||||
s.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server {
|
server.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server {
|
||||||
return sasl.NewLoginServer(func(address, password string) error {
|
return sasl.NewLoginServer(func(address, password string) error {
|
||||||
user, err := conn.Server().Backend.Login(nil, address, password)
|
user, err := conn.Server().Backend.Login(nil, address, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -93,8 +106,8 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
s.Enable(
|
server.Enable(
|
||||||
imapidle.NewExtension(),
|
idle.NewExtension(),
|
||||||
imapmove.NewExtension(),
|
imapmove.NewExtension(),
|
||||||
id.NewExtension(serverID, userAgent),
|
id.NewExtension(serverID, userAgent),
|
||||||
imapquota.NewExtension(),
|
imapquota.NewExtension(),
|
||||||
@ -103,87 +116,35 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
|
|||||||
uidplus.NewExtension(),
|
uidplus.NewExtension(),
|
||||||
)
|
)
|
||||||
|
|
||||||
server := &imapServer{
|
|
||||||
panicHandler: panicHandler,
|
|
||||||
server: s,
|
|
||||||
userAgent: userAgent,
|
|
||||||
eventListener: eventListener,
|
|
||||||
debugClient: debugClient,
|
|
||||||
debugServer: debugServer,
|
|
||||||
port: port,
|
|
||||||
}
|
|
||||||
server.isRunning.Store(false)
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *imapServer) HandlePanic() { s.panicHandler.HandlePanic() }
|
// ListenAndServe will run server and all monitors.
|
||||||
func (s *imapServer) IsRunning() bool { return s.isRunning.Load().(bool) }
|
func (s *Server) ListenAndServe() { s.controller.ListenAndServe() }
|
||||||
func (s *imapServer) Port() int { return s.port }
|
|
||||||
|
|
||||||
// ListenAndServe starts the server and keeps it on based on internet
|
// Close turns off server and monitors.
|
||||||
// availability.
|
func (s *Server) Close() { s.controller.Close() }
|
||||||
func (s *imapServer) ListenAndServe() {
|
|
||||||
serverutil.ListenAndServe(s, s.eventListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenRetryAndServe will start listener. If port is occupied it will try
|
// Implements serverutil.Server interface.
|
||||||
// again after coolDown time. Once listener is OK it will serve.
|
|
||||||
func (s *imapServer) ListenRetryAndServe(retries int, retryAfter time.Duration) {
|
|
||||||
if s.IsRunning() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.isRunning.Store(true)
|
|
||||||
|
|
||||||
l := log.WithField("address", s.server.Addr)
|
func (Server) Protocol() serverutil.Protocol { return serverutil.IMAP }
|
||||||
l.Info("IMAP server is starting")
|
func (s *Server) UseSSL() bool { return false }
|
||||||
listener, err := net.Listen("tcp", s.server.Addr)
|
func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) }
|
||||||
if err != nil {
|
func (s *Server) TLSConfig() *tls.Config { return s.server.TLSConfig }
|
||||||
s.isRunning.Store(false)
|
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
|
||||||
if retries > 0 {
|
|
||||||
l.WithError(err).WithField("retries", retries).Warn("IMAP listener failed")
|
|
||||||
time.Sleep(retryAfter)
|
|
||||||
s.ListenRetryAndServe(retries-1, retryAfter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.WithError(err).Error("IMAP listener failed")
|
func (s *Server) DebugServer() bool { return s.debugServer }
|
||||||
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
|
func (s *Server) DebugClient() bool { return s.debugClient }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.server.Serve(&connListener{
|
func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) {
|
||||||
Listener: listener,
|
s.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug)
|
||||||
server: s,
|
|
||||||
userAgent: s.userAgent,
|
|
||||||
})
|
|
||||||
// Serve returns error every time, even after closing the server.
|
|
||||||
// User shouldn't be notified about error if server shouldn't be running,
|
|
||||||
// but it should in case it was not closed by `s.Close()`.
|
|
||||||
if err != nil && s.IsRunning() {
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
l.WithError(err).Error("IMAP server failed")
|
|
||||||
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer s.server.Close() //nolint[errcheck]
|
|
||||||
|
|
||||||
l.Info("IMAP server stopped")
|
if !s.userAgent.HasClient() {
|
||||||
}
|
s.userAgent.SetClient("UnknownClient", "0.0.1")
|
||||||
|
|
||||||
// Stops the server.
|
|
||||||
func (s *imapServer) Close() {
|
|
||||||
if !s.IsRunning() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
|
|
||||||
log.Info("Closing IMAP server")
|
|
||||||
if err := s.server.Close(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to close the connection")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *imapServer) DisconnectUser(address string) {
|
func (s *Server) DisconnectUser(address string) {
|
||||||
log.Info("Disconnecting all open IMAP connections for ", address)
|
log.Info("Disconnecting all open IMAP connections for ", address)
|
||||||
s.server.ForEachConn(func(conn imapserver.Conn) {
|
s.server.ForEachConn(func(conn imapserver.Conn) {
|
||||||
connUser := conn.Context().User
|
connUser := conn.Context().User
|
||||||
@ -195,60 +156,5 @@ func (s *imapServer) DisconnectUser(address string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// connListener sets debug loggers on server containing fields with local
|
func (s *Server) Serve(listener net.Listener) error { return s.server.Serve(listener) }
|
||||||
// and remote addresses right after new connection is accepted.
|
func (s *Server) StopServe() error { return s.server.Close() }
|
||||||
type connListener struct {
|
|
||||||
net.Listener
|
|
||||||
|
|
||||||
server *imapServer
|
|
||||||
userAgent *useragent.UserAgent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *connListener) Accept() (net.Conn, error) {
|
|
||||||
conn, err := l.Listener.Accept()
|
|
||||||
|
|
||||||
if err == nil && (l.server.debugServer || l.server.debugClient) {
|
|
||||||
debugLog := log
|
|
||||||
if addr := conn.LocalAddr(); addr != nil {
|
|
||||||
debugLog = debugLog.WithField("loc", addr.String())
|
|
||||||
}
|
|
||||||
if addr := conn.RemoteAddr(); addr != nil {
|
|
||||||
debugLog = debugLog.WithField("rem", addr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var localDebug, remoteDebug io.Writer
|
|
||||||
if l.server.debugServer {
|
|
||||||
localDebug = debugLog.WithField("pkg", "imap/server").WriterLevel(logrus.DebugLevel)
|
|
||||||
}
|
|
||||||
if l.server.debugClient {
|
|
||||||
remoteDebug = debugLog.WithField("pkg", "imap/client").WriterLevel(logrus.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
l.server.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !l.userAgent.HasClient() {
|
|
||||||
l.userAgent.SetClient("UnknownClient", "0.0.1")
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// serverErrorLogger implements go-imap/logger interface.
|
|
||||||
type serverErrorLogger struct {
|
|
||||||
tag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServerErrorLogger(tag string) *serverErrorLogger {
|
|
||||||
return &serverErrorLogger{tag}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *serverErrorLogger) Printf(format string, args ...interface{}) {
|
|
||||||
err := fmt.Sprintf(format, args...)
|
|
||||||
log.WithField("pkg", s.tag).Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *serverErrorLogger) Println(args ...interface{}) {
|
|
||||||
err := fmt.Sprintln(args...)
|
|
||||||
log.WithField("pkg", s.tag).Error(err)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -89,7 +89,7 @@ type storeMailboxProvider interface {
|
|||||||
MarkMessagesUnstarred(apiID []string) error
|
MarkMessagesUnstarred(apiID []string) error
|
||||||
MarkMessagesDeleted(apiID []string) error
|
MarkMessagesDeleted(apiID []string) error
|
||||||
MarkMessagesUndeleted(apiID []string) error
|
MarkMessagesUndeleted(apiID []string) error
|
||||||
ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error
|
ImportMessage(enc []byte, seen bool, labelIDs []string, flags, time int64) (string, error)
|
||||||
RemoveDeleted(apiIDs []string) error
|
RemoveDeleted(apiIDs []string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -188,10 +188,10 @@ func (iu *imapUpdates) MailboxStatus(address, mailboxName string, total, unread,
|
|||||||
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, false)
|
iu.sendIMAPUpdate(update, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iu *imapUpdates) sendIMAPUpdate(update goIMAPBackend.Update, block bool) {
|
func (iu *imapUpdates) sendIMAPUpdate(update goIMAPBackend.Update, isBlocking bool) {
|
||||||
if iu.ch == nil {
|
if iu.ch == nil {
|
||||||
log.Trace("IMAP IDLE unavailable")
|
log.Trace("IMAP IDLE unavailable")
|
||||||
return
|
return
|
||||||
@ -207,7 +207,7 @@ func (iu *imapUpdates) sendIMAPUpdate(update goIMAPBackend.Update, block bool) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if !block {
|
if !isBlocking {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -61,9 +61,17 @@ func NewReporter(appName, appVersion string, userAgent fmt.Stringer) *Reporter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reporter) ReportException(i interface{}) error {
|
func (r *Reporter) ReportException(i interface{}) error {
|
||||||
|
return r.ReportExceptionWithContext(i, make(map[string]interface{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reporter) ReportMessage(msg string) error {
|
||||||
|
return r.ReportMessageWithContext(msg, make(map[string]interface{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reporter) ReportExceptionWithContext(i interface{}, context map[string]interface{}) error {
|
||||||
err := fmt.Errorf("recover: %v", i)
|
err := fmt.Errorf("recover: %v", i)
|
||||||
|
|
||||||
return r.scopedReport(func() {
|
return r.scopedReport(context, func() {
|
||||||
if eventID := sentry.CaptureException(err); eventID != nil {
|
if eventID := sentry.CaptureException(err); eventID != nil {
|
||||||
logrus.WithError(err).
|
logrus.WithError(err).
|
||||||
WithField("reportID", *eventID).
|
WithField("reportID", *eventID).
|
||||||
@ -72,8 +80,8 @@ func (r *Reporter) ReportException(i interface{}) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reporter) ReportMessage(msg string) error {
|
func (r *Reporter) ReportMessageWithContext(msg string, context map[string]interface{}) error {
|
||||||
return r.scopedReport(func() {
|
return r.scopedReport(context, func() {
|
||||||
if eventID := sentry.CaptureMessage(msg); eventID != nil {
|
if eventID := sentry.CaptureMessage(msg); eventID != nil {
|
||||||
logrus.WithField("message", msg).
|
logrus.WithField("message", msg).
|
||||||
WithField("reportID", *eventID).
|
WithField("reportID", *eventID).
|
||||||
@ -83,7 +91,7 @@ func (r *Reporter) ReportMessage(msg string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Report reports a sentry crash with stacktrace from all goroutines.
|
// Report reports a sentry crash with stacktrace from all goroutines.
|
||||||
func (r *Reporter) scopedReport(doReport func()) error {
|
func (r *Reporter) scopedReport(context map[string]interface{}, doReport func()) error {
|
||||||
SkipDuringUnwind()
|
SkipDuringUnwind()
|
||||||
|
|
||||||
if os.Getenv("PROTONMAIL_ENV") == "dev" {
|
if os.Getenv("PROTONMAIL_ENV") == "dev" {
|
||||||
@ -101,6 +109,7 @@ func (r *Reporter) scopedReport(doReport func()) error {
|
|||||||
sentry.WithScope(func(scope *sentry.Scope) {
|
sentry.WithScope(func(scope *sentry.Scope) {
|
||||||
SkipDuringUnwind()
|
SkipDuringUnwind()
|
||||||
scope.SetTags(tags)
|
scope.SetTags(tags)
|
||||||
|
scope.SetContexts(context)
|
||||||
doReport()
|
doReport()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
117
internal/serverutil/controller.go
Normal file
117
internal/serverutil/controller.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package serverutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Controller will make sure that server is listening and serving and if needed
|
||||||
|
// users are disconnected.
|
||||||
|
type Controller interface {
|
||||||
|
ListenAndServe()
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewController return simple server controller.
|
||||||
|
func NewController(s Server, l listener.Listener) Controller {
|
||||||
|
log := logrus.WithField("pkg", "serverutil").WithField("protocol", s.Protocol())
|
||||||
|
c := &controller{
|
||||||
|
server: s,
|
||||||
|
signals: l,
|
||||||
|
log: log,
|
||||||
|
closeDisconnectUsers: make(chan void),
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.DebugServer() {
|
||||||
|
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||||
|
log.Warning("================================================")
|
||||||
|
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||||
|
log.Warning("================================================")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type void struct{}
|
||||||
|
|
||||||
|
type controller struct {
|
||||||
|
server Server
|
||||||
|
signals listener.Listener
|
||||||
|
log *logrus.Entry
|
||||||
|
|
||||||
|
closeDisconnectUsers chan void
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *controller) Close() {
|
||||||
|
c.closeDisconnectUsers <- void{}
|
||||||
|
if err := c.server.StopServe(); err != nil {
|
||||||
|
c.log.WithError(err).Error("Issue when closing server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe starts the server and keeps it on based on internet
|
||||||
|
// availability. It also monitors and disconnect users if requested.
|
||||||
|
func (c *controller) ListenAndServe() {
|
||||||
|
go monitorDisconnectedUsers(c.server, c.signals, c.closeDisconnectUsers)
|
||||||
|
|
||||||
|
defer c.server.HandlePanic()
|
||||||
|
|
||||||
|
l := c.log.WithField("useSSL", c.server.UseSSL()).
|
||||||
|
WithField("address", c.server.Address())
|
||||||
|
|
||||||
|
var listener net.Listener
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if c.server.UseSSL() {
|
||||||
|
listener, err = tls.Listen("tcp", c.server.Address(), c.server.TLSConfig())
|
||||||
|
} else {
|
||||||
|
listener, err = net.Listen("tcp", c.server.Address())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.WithError(err).Error("Cannot start listner.")
|
||||||
|
c.signals.Emit(events.ErrorEvent, string(c.server.Protocol())+" failed: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// When starting the Bridge, we don't want to retry to notify user
|
||||||
|
// quickly about the issue. Very probably retry will not help anyway.
|
||||||
|
l.Info("Starting server")
|
||||||
|
err = c.server.Serve(&connListener{listener, c.server})
|
||||||
|
l.WithError(err).Debug("GoSMTP not serving")
|
||||||
|
}
|
||||||
|
|
||||||
|
func monitorDisconnectedUsers(s Server, l listener.Listener, done <-chan void) {
|
||||||
|
ch := make(chan string)
|
||||||
|
l.Add(events.CloseConnectionEvent, ch)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return
|
||||||
|
case address := <-ch:
|
||||||
|
s.DisconnectUser(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,29 +15,25 @@
|
|||||||
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package smtp
|
package serverutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/serverutil/mocks"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSMTPServerTurnOffAndOnAgain(t *testing.T) {
|
// ServerErrorLogger implements go-imap/logger interface.
|
||||||
r := require.New(t)
|
type ServerErrorLogger struct {
|
||||||
ts := mocks.NewTestServer(12342)
|
l *logrus.Entry
|
||||||
|
}
|
||||||
s := &Server{
|
|
||||||
panicHandler: ts.PanicHandler,
|
func NewServerErrorLogger(protocol Protocol) *ServerErrorLogger {
|
||||||
port: ts.WantPort,
|
return &ServerErrorLogger{l: logrus.WithField("protocol", protocol)}
|
||||||
eventListener: ts.EventListener,
|
}
|
||||||
}
|
|
||||||
s.isRunning.Store(false)
|
func (s *ServerErrorLogger) Printf(format string, args ...interface{}) {
|
||||||
|
s.l.Errorf(format, args...)
|
||||||
r.True(ts.IsPortFree())
|
}
|
||||||
|
|
||||||
go s.ListenAndServe()
|
func (s *ServerErrorLogger) Println(args ...interface{}) {
|
||||||
ts.RunServerTests(r)
|
s.l.Errorln(args...)
|
||||||
}
|
}
|
||||||
59
internal/serverutil/listener.go
Normal file
59
internal/serverutil/listener.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package serverutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// connListener sets debug loggers on server containing fields with local
|
||||||
|
// and remote addresses right after new connection is accepted.
|
||||||
|
type connListener struct {
|
||||||
|
net.Listener
|
||||||
|
|
||||||
|
server Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *connListener) Accept() (net.Conn, error) {
|
||||||
|
conn, err := l.Listener.Accept()
|
||||||
|
|
||||||
|
if err == nil && (l.server.DebugServer() || l.server.DebugClient()) {
|
||||||
|
debugLog := logrus.WithField("pkg", l.server.Protocol())
|
||||||
|
if addr := conn.LocalAddr(); addr != nil {
|
||||||
|
debugLog = debugLog.WithField("loc", addr.String())
|
||||||
|
}
|
||||||
|
if addr := conn.RemoteAddr(); addr != nil {
|
||||||
|
debugLog = debugLog.WithField("rem", addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var localDebug, remoteDebug io.Writer
|
||||||
|
if l.server.DebugServer() {
|
||||||
|
localDebug = debugLog.WithField("comm", "server").WriterLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
if l.server.DebugClient() {
|
||||||
|
remoteDebug = debugLog.WithField("comm", "client").WriterLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.server.SetLoggers(localDebug, remoteDebug)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
@ -1,150 +0,0 @@
|
|||||||
// Copyright (c) 2021 Proton Technologies AG
|
|
||||||
//
|
|
||||||
// This file is part of ProtonMail Bridge.
|
|
||||||
//
|
|
||||||
// ProtonMail 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.
|
|
||||||
//
|
|
||||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package mocks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DummyPanicHandler struct{}
|
|
||||||
|
|
||||||
func (ph *DummyPanicHandler) HandlePanic() {}
|
|
||||||
|
|
||||||
type TestServer struct {
|
|
||||||
PanicHandler *DummyPanicHandler
|
|
||||||
WantPort int
|
|
||||||
EventListener listener.Listener
|
|
||||||
|
|
||||||
isRunning atomic.Value
|
|
||||||
srv *http.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestServer(port int) *TestServer {
|
|
||||||
s := &TestServer{
|
|
||||||
PanicHandler: &DummyPanicHandler{},
|
|
||||||
EventListener: listener.New(),
|
|
||||||
WantPort: ports.FindFreePortFrom(port),
|
|
||||||
}
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) IsPortFree() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) IsPortOccupied() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) Emit(event string, try, iEvt int) int {
|
|
||||||
// Emit has separate go routine so it is needed to wait here to
|
|
||||||
// prevent event race condition.
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
iEvt++
|
|
||||||
s.EventListener.Emit(event, fmt.Sprintf("%d:%d", try, iEvt))
|
|
||||||
return iEvt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) HandlePanic() {}
|
|
||||||
func (s *TestServer) DisconnectUser(string) {}
|
|
||||||
func (s *TestServer) Port() int { return s.WantPort }
|
|
||||||
func (s *TestServer) IsRunning() bool { return s.isRunning.Load().(bool) }
|
|
||||||
|
|
||||||
func (s *TestServer) ListenRetryAndServe(retries int, retryAfter time.Duration) {
|
|
||||||
if s.isRunning.Load().(bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.isRunning.Store(true)
|
|
||||||
|
|
||||||
// There can be delay when starting server
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
s.srv = &http.Server{
|
|
||||||
Addr: fmt.Sprintf("127.0.0.1:%d", s.WantPort),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.srv.ListenAndServe()
|
|
||||||
if err != nil {
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
if retries > 0 {
|
|
||||||
time.Sleep(retryAfter)
|
|
||||||
s.ListenRetryAndServe(retries-1, retryAfter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.IsRunning() {
|
|
||||||
logrus.Error("Not serving but isRunning is true")
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) Close() {
|
|
||||||
if !s.isRunning.Load().(bool) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
|
|
||||||
// There can be delay when stopping server
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
if err := s.srv.Close(); err != nil {
|
|
||||||
logrus.WithError(err).Error("Closing dummy server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TestServer) RunServerTests(r *require.Assertions) {
|
|
||||||
// NOTE About choosing tick durations:
|
|
||||||
// In order to avoid ticks to synchronise and cause occasional race
|
|
||||||
// condition we choose the tick duration around 100ms but not exactly
|
|
||||||
// to have large common multiple.
|
|
||||||
r.Eventually(s.IsPortOccupied, 5*time.Second, 97*time.Millisecond)
|
|
||||||
|
|
||||||
// There was an issue where second time we were not able to restore server.
|
|
||||||
for try := 0; try < 3; try++ {
|
|
||||||
i := s.Emit(events.InternetOffEvent, try, 0)
|
|
||||||
r.Eventually(s.IsPortFree, 10*time.Second, 99*time.Millisecond, "signal off try %d : %d", try, i)
|
|
||||||
|
|
||||||
i = s.Emit(events.InternetOnEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOffEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOffEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOffEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOffEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOnEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOnEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOffEvent, try, i)
|
|
||||||
// Wait a bit longer if needed to process all events
|
|
||||||
r.Eventually(s.IsPortFree, 20*time.Second, 101*time.Millisecond, "again signal off number %d : %d", try, i)
|
|
||||||
|
|
||||||
i = s.Emit(events.InternetOnEvent, try, i)
|
|
||||||
r.Eventually(s.IsPortOccupied, 10*time.Second, 103*time.Millisecond, "signal on number %d : %d", try, i)
|
|
||||||
|
|
||||||
i = s.Emit(events.InternetOffEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOnEvent, try, i)
|
|
||||||
i = s.Emit(events.InternetOnEvent, try, i)
|
|
||||||
r.Eventually(s.IsPortOccupied, 10*time.Second, 107*time.Millisecond, "again signal on number %d : %d", try, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -17,19 +17,10 @@
|
|||||||
|
|
||||||
package serverutil
|
package serverutil
|
||||||
|
|
||||||
import (
|
type Protocol string
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/serverutil/mocks"
|
const (
|
||||||
"github.com/stretchr/testify/require"
|
HTTP = Protocol("HTTP")
|
||||||
|
IMAP = Protocol("IMAP")
|
||||||
|
SMTP = Protocol("SMTP")
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServerTurnOffAndOnAgain(t *testing.T) {
|
|
||||||
r := require.New(t)
|
|
||||||
s := mocks.NewTestServer(12321)
|
|
||||||
|
|
||||||
r.True(s.IsPortFree())
|
|
||||||
|
|
||||||
go ListenAndServe(s, s.EventListener)
|
|
||||||
s.RunServerTests(r)
|
|
||||||
}
|
|
||||||
@ -18,115 +18,24 @@
|
|||||||
package serverutil
|
package serverutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"net"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server which can handle disconnected users and lost internet connection.
|
// Server can handle disconnected users.
|
||||||
type Server interface {
|
type Server interface {
|
||||||
|
Protocol() Protocol
|
||||||
|
UseSSL() bool
|
||||||
|
Address() string
|
||||||
|
TLSConfig() *tls.Config
|
||||||
|
|
||||||
|
DebugServer() bool
|
||||||
|
DebugClient() bool
|
||||||
|
SetLoggers(localDebug, remoteDebug io.Writer)
|
||||||
|
|
||||||
HandlePanic()
|
HandlePanic()
|
||||||
DisconnectUser(string)
|
DisconnectUser(string)
|
||||||
ListenRetryAndServe(int, time.Duration)
|
Serve(net.Listener) error
|
||||||
Close()
|
StopServe() error
|
||||||
Port() int
|
|
||||||
IsRunning() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func monitorDisconnectedUsers(s Server, l listener.Listener) {
|
|
||||||
ch := make(chan string)
|
|
||||||
l.Add(events.CloseConnectionEvent, ch)
|
|
||||||
for address := range ch {
|
|
||||||
s.DisconnectUser(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func redirectInternetEventsToOneChannel(l listener.Listener) (isInternetOn chan bool) {
|
|
||||||
on := make(chan string)
|
|
||||||
l.Add(events.InternetOnEvent, on)
|
|
||||||
off := make(chan string)
|
|
||||||
l.Add(events.InternetOffEvent, off)
|
|
||||||
|
|
||||||
// Redirect two channels into one. When select was used the algorithm
|
|
||||||
// first read all on channels and then read all off channels.
|
|
||||||
isInternetOn = make(chan bool, 20)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
logrus.WithField("try", <-on).Trace("Internet ON")
|
|
||||||
isInternetOn <- true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
logrus.WithField("try", <-off).Trace("Internet OFF")
|
|
||||||
isInternetOn <- false
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
recheckPortAfter = 50 * time.Millisecond
|
|
||||||
stopPortChecksAfter = 15 * time.Second
|
|
||||||
retryListnerAfter = 5 * time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
func monitorInternetConnection(s Server, l listener.Listener) {
|
|
||||||
isInternetOn := redirectInternetEventsToOneChannel(l)
|
|
||||||
for {
|
|
||||||
var expectedIsPortFree bool
|
|
||||||
if <-isInternetOn {
|
|
||||||
if s.IsRunning() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
defer s.HandlePanic()
|
|
||||||
// We had issues on Mac that from time to time something
|
|
||||||
// blocked our port for a bit after we closed IMAP server
|
|
||||||
// due to connection issues.
|
|
||||||
// Restart always helped, so we do retry to not bother user.
|
|
||||||
s.ListenRetryAndServe(10, retryListnerAfter)
|
|
||||||
}()
|
|
||||||
expectedIsPortFree = false
|
|
||||||
} else {
|
|
||||||
if !s.IsRunning() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.Close()
|
|
||||||
expectedIsPortFree = true
|
|
||||||
}
|
|
||||||
start := time.Now()
|
|
||||||
for {
|
|
||||||
isPortFree := ports.IsPortFree(s.Port())
|
|
||||||
logrus.
|
|
||||||
WithField("port", s.Port()).
|
|
||||||
WithField("isFree", isPortFree).
|
|
||||||
WithField("wantToBeFree", expectedIsPortFree).
|
|
||||||
Trace("Check port")
|
|
||||||
if isPortFree == expectedIsPortFree {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Safety stop if something went wrong.
|
|
||||||
if time.Since(start) > stopPortChecksAfter {
|
|
||||||
logrus.WithField("expectedIsPortFree", expectedIsPortFree).Warn("Server start/stop check timeouted")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(recheckPortAfter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenAndServe starts the server and keeps it on based on internet
|
|
||||||
// availability. It also monitors and disconnect users if requested.
|
|
||||||
func ListenAndServe(s Server, l listener.Listener) {
|
|
||||||
go monitorDisconnectedUsers(s, l)
|
|
||||||
go monitorInternetConnection(s, l)
|
|
||||||
|
|
||||||
// When starting the Bridge, we don't want to retry to notify user
|
|
||||||
// quickly about the issue. Very probably retry will not help anyway.
|
|
||||||
s.ListenRetryAndServe(0, 0)
|
|
||||||
}
|
}
|
||||||
|
|||||||
153
internal/serverutil/test/controller_test.go
Normal file
153
internal/serverutil/test/controller_test.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/serverutil"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setup(t *testing.T) (*require.Assertions, *testServer, listener.Listener, serverutil.Controller) {
|
||||||
|
r := require.New(t)
|
||||||
|
s := newTestServer()
|
||||||
|
l := listener.New()
|
||||||
|
c := serverutil.NewController(s, l)
|
||||||
|
|
||||||
|
return r, s, l, c
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControllerListernServeClose(t *testing.T) {
|
||||||
|
r, s, l, c := setup(t)
|
||||||
|
|
||||||
|
errorCh := l.ProvideChannel(events.ErrorEvent)
|
||||||
|
|
||||||
|
r.True(s.portIsFree())
|
||||||
|
go c.ListenAndServe()
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
|
r.NoError(s.ping())
|
||||||
|
|
||||||
|
r.Nil(s.localDebug)
|
||||||
|
r.Nil(s.remoteDebug)
|
||||||
|
|
||||||
|
c.Close()
|
||||||
|
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case msg := <-errorCh:
|
||||||
|
r.Fail("Expected no error but have %q", msg)
|
||||||
|
case <-time.Tick(100 * time.Millisecond):
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControllerFailOnBusyPort(t *testing.T) {
|
||||||
|
r, s, l, c := setup(t)
|
||||||
|
|
||||||
|
ocupator := http.Server{Addr: s.Address()}
|
||||||
|
defer ocupator.Close() //nolint[errcheck]
|
||||||
|
|
||||||
|
go ocupator.ListenAndServe() //nolint[errcheck]
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
|
errorCh := l.ProvideChannel(events.ErrorEvent)
|
||||||
|
go c.ListenAndServe()
|
||||||
|
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-errorCh:
|
||||||
|
break
|
||||||
|
case <-time.Tick(time.Second):
|
||||||
|
r.Fail("Expected error but have none.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestControllerCallDisconnectUser(t *testing.T) {
|
||||||
|
r, s, l, c := setup(t)
|
||||||
|
|
||||||
|
go c.ListenAndServe()
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
r.NoError(s.ping())
|
||||||
|
|
||||||
|
l.Emit(events.CloseConnectionEvent, "")
|
||||||
|
r.Eventually(func() bool { return s.calledDisconnected == 1 }, time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
|
c.Close()
|
||||||
|
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
|
||||||
|
|
||||||
|
l.Emit(events.CloseConnectionEvent, "")
|
||||||
|
r.Equal(1, s.calledDisconnected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugClient(t *testing.T) {
|
||||||
|
r, s, _, c := setup(t)
|
||||||
|
|
||||||
|
s.debugServer = false
|
||||||
|
s.debugClient = true
|
||||||
|
|
||||||
|
go c.ListenAndServe()
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
r.NoError(s.ping())
|
||||||
|
|
||||||
|
r.Nil(s.localDebug)
|
||||||
|
r.NotNil(s.remoteDebug)
|
||||||
|
|
||||||
|
c.Close()
|
||||||
|
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugServer(t *testing.T) {
|
||||||
|
r, s, _, c := setup(t)
|
||||||
|
|
||||||
|
s.debugServer = true
|
||||||
|
s.debugClient = false
|
||||||
|
|
||||||
|
go c.ListenAndServe()
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
r.NoError(s.ping())
|
||||||
|
|
||||||
|
r.NotNil(s.localDebug)
|
||||||
|
r.Nil(s.remoteDebug)
|
||||||
|
|
||||||
|
c.Close()
|
||||||
|
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebugBoth(t *testing.T) {
|
||||||
|
r, s, _, c := setup(t)
|
||||||
|
|
||||||
|
s.debugServer = true
|
||||||
|
s.debugClient = true
|
||||||
|
|
||||||
|
go c.ListenAndServe()
|
||||||
|
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
|
||||||
|
r.NoError(s.ping())
|
||||||
|
|
||||||
|
r.NotNil(s.localDebug)
|
||||||
|
r.NotNil(s.remoteDebug)
|
||||||
|
|
||||||
|
c.Close()
|
||||||
|
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
|
||||||
|
}
|
||||||
88
internal/serverutil/test/server.go
Normal file
88
internal/serverutil/test/server.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/serverutil"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestServer() *testServer {
|
||||||
|
return &testServer{port: 11188}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testServer struct {
|
||||||
|
http http.Server
|
||||||
|
|
||||||
|
useSSL,
|
||||||
|
debugServer,
|
||||||
|
debugClient bool
|
||||||
|
calledDisconnected int
|
||||||
|
|
||||||
|
port int
|
||||||
|
tls *tls.Config
|
||||||
|
|
||||||
|
localDebug, remoteDebug io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*testServer) Protocol() serverutil.Protocol { return serverutil.HTTP }
|
||||||
|
func (s *testServer) UseSSL() bool { return s.useSSL }
|
||||||
|
func (s *testServer) Address() string { return fmt.Sprintf("127.0.0.1:%d", s.port) }
|
||||||
|
func (s *testServer) TLSConfig() *tls.Config { return s.tls }
|
||||||
|
func (s *testServer) HandlePanic() {}
|
||||||
|
|
||||||
|
func (s *testServer) DebugServer() bool { return s.debugServer }
|
||||||
|
func (s *testServer) DebugClient() bool { return s.debugClient }
|
||||||
|
func (s *testServer) SetLoggers(localDebug, remoteDebug io.Writer) {
|
||||||
|
s.localDebug = localDebug
|
||||||
|
s.remoteDebug = remoteDebug
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) DisconnectUser(string) {
|
||||||
|
s.calledDisconnected++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) Serve(l net.Listener) error {
|
||||||
|
return s.http.Serve(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) StopServe() error { return s.http.Close() }
|
||||||
|
|
||||||
|
func (s *testServer) portIsFree() bool {
|
||||||
|
return ports.IsPortFree(s.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) portIsOccupied() bool {
|
||||||
|
return !ports.IsPortFree(s.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testServer) ping() error {
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Get("http://" + s.Address() + "/ping")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.Body.Close()
|
||||||
|
}
|
||||||
@ -48,7 +48,7 @@ func dumpMessageData(b []byte, subject string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(
|
if err := ioutil.WriteFile(
|
||||||
filepath.Join(path, fmt.Sprintf("%v-%v.eml", subject, time.Now().Format(time.RFC3339Nano))),
|
filepath.Join(path, fmt.Sprintf("%v-%v.eml", subject, time.Now().Unix())),
|
||||||
b,
|
b,
|
||||||
0600,
|
0600,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|||||||
@ -20,72 +20,60 @@ package smtp
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/serverutil"
|
"github.com/ProtonMail/proton-bridge/internal/serverutil"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/emersion/go-sasl"
|
"github.com/emersion/go-sasl"
|
||||||
goSMTP "github.com/emersion/go-smtp"
|
goSMTP "github.com/emersion/go-smtp"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is Bridge SMTP server implementation.
|
// Server is Bridge SMTP server implementation.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
panicHandler panicHandler
|
panicHandler panicHandler
|
||||||
backend goSMTP.Backend
|
backend goSMTP.Backend
|
||||||
server *goSMTP.Server
|
debug bool
|
||||||
eventListener listener.Listener
|
useSSL bool
|
||||||
debug bool
|
port int
|
||||||
useSSL bool
|
tls *tls.Config
|
||||||
port int
|
|
||||||
tls *tls.Config
|
server *goSMTP.Server
|
||||||
isRunning atomic.Value
|
controller serverutil.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSMTPServer returns an SMTP server configured with the given options.
|
// NewSMTPServer returns an SMTP server configured with the given options.
|
||||||
func NewSMTPServer(panicHandler panicHandler, debug bool, port int, useSSL bool, tls *tls.Config, smtpBackend goSMTP.Backend, eventListener listener.Listener) *Server {
|
func NewSMTPServer(
|
||||||
if debug {
|
panicHandler panicHandler,
|
||||||
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
debug bool, port int, useSSL bool,
|
||||||
log.Warning("================================================")
|
tls *tls.Config,
|
||||||
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
smtpBackend goSMTP.Backend,
|
||||||
log.Warning("================================================")
|
eventListener listener.Listener,
|
||||||
|
) *Server {
|
||||||
|
server := &Server{
|
||||||
|
panicHandler: panicHandler,
|
||||||
|
backend: smtpBackend,
|
||||||
|
debug: debug,
|
||||||
|
useSSL: useSSL,
|
||||||
|
port: port,
|
||||||
|
tls: tls,
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &Server{
|
server.server = newGoSMTPServer(server)
|
||||||
panicHandler: panicHandler,
|
server.controller = serverutil.NewController(server, eventListener)
|
||||||
backend: smtpBackend,
|
|
||||||
eventListener: eventListener,
|
|
||||||
debug: debug,
|
|
||||||
useSSL: useSSL,
|
|
||||||
port: port,
|
|
||||||
tls: tls,
|
|
||||||
}
|
|
||||||
server.isRunning.Store(false)
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
|
func newGoSMTPServer(s *Server) *goSMTP.Server {
|
||||||
func (s *Server) IsRunning() bool { return s.isRunning.Load().(bool) }
|
newSMTP := goSMTP.NewServer(s.backend)
|
||||||
func (s *Server) Port() int { return s.port }
|
newSMTP.Addr = s.Address()
|
||||||
|
newSMTP.TLSConfig = s.tls
|
||||||
func newGoSMTPServer(debug bool, smtpBackend goSMTP.Backend, port int, tls *tls.Config) *goSMTP.Server {
|
|
||||||
newSMTP := goSMTP.NewServer(smtpBackend)
|
|
||||||
newSMTP.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
|
||||||
newSMTP.TLSConfig = tls
|
|
||||||
newSMTP.Domain = bridge.Host
|
newSMTP.Domain = bridge.Host
|
||||||
|
newSMTP.ErrorLog = serverutil.NewServerErrorLogger(serverutil.SMTP)
|
||||||
newSMTP.AllowInsecureAuth = true
|
newSMTP.AllowInsecureAuth = true
|
||||||
newSMTP.MaxLineLength = 1 << 16
|
newSMTP.MaxLineLength = 1 << 16
|
||||||
|
|
||||||
if debug {
|
|
||||||
newSMTP.Debug = logrus.
|
|
||||||
WithField("pkg", "smtp/server").
|
|
||||||
WriterLevel(logrus.DebugLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
newSMTP.EnableAuth(sasl.Login, func(conn *goSMTP.Conn) sasl.Server {
|
newSMTP.EnableAuth(sasl.Login, func(conn *goSMTP.Conn) sasl.Server {
|
||||||
return sasl.NewLoginServer(func(address, password string) error {
|
return sasl.NewLoginServer(func(address, password string) error {
|
||||||
user, err := conn.Server().Backend.Login(nil, address, password)
|
user, err := conn.Server().Backend.Login(nil, address, password)
|
||||||
@ -100,80 +88,24 @@ func newGoSMTPServer(debug bool, smtpBackend goSMTP.Backend, port int, tls *tls.
|
|||||||
return newSMTP
|
return newSMTP
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe starts the server and keeps it on based on internet
|
// ListenAndServe will run server and all monitors.
|
||||||
// availability.
|
func (s *Server) ListenAndServe() { s.controller.ListenAndServe() }
|
||||||
func (s *Server) ListenAndServe() {
|
|
||||||
serverutil.ListenAndServe(s, s.eventListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) ListenRetryAndServe(retries int, retryAfter time.Duration) {
|
// Close turns off server and monitors.
|
||||||
if s.IsRunning() {
|
func (s *Server) Close() { s.controller.Close() }
|
||||||
return
|
|
||||||
}
|
|
||||||
s.isRunning.Store(true)
|
|
||||||
|
|
||||||
s.server = newGoSMTPServer(s.debug, s.backend, s.port, s.tls)
|
// Implements servertutil.Server interface.
|
||||||
|
|
||||||
l := log.WithField("useSSL", s.useSSL).WithField("address", s.server.Addr)
|
func (Server) Protocol() serverutil.Protocol { return serverutil.SMTP }
|
||||||
l.Info("SMTP server is starting")
|
func (s *Server) UseSSL() bool { return s.useSSL }
|
||||||
|
func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) }
|
||||||
|
func (s *Server) TLSConfig() *tls.Config { return s.tls }
|
||||||
|
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
|
||||||
|
|
||||||
var listener net.Listener
|
func (s *Server) DebugServer() bool { return s.debug }
|
||||||
var err error
|
func (s *Server) DebugClient() bool { return s.debug }
|
||||||
if s.useSSL {
|
|
||||||
listener, err = tls.Listen("tcp", s.server.Addr, s.server.TLSConfig)
|
|
||||||
} else {
|
|
||||||
listener, err = net.Listen("tcp", s.server.Addr)
|
|
||||||
}
|
|
||||||
l.WithError(err).Debug("Listener for SMTP created")
|
|
||||||
if err != nil {
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
if retries > 0 {
|
|
||||||
l.WithError(err).WithField("retries", retries).Warn("SMTP listener failed")
|
|
||||||
time.Sleep(retryAfter)
|
|
||||||
s.ListenRetryAndServe(retries-1, retryAfter)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l.WithError(err).Error("SMTP listener failed")
|
func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) { s.server.Debug = localDebug }
|
||||||
s.eventListener.Emit(events.ErrorEvent, "SMTP failed: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.server.Serve(listener)
|
|
||||||
l.WithError(err).Debug("GoSMTP not serving")
|
|
||||||
// Serve returns error every time, even after closing the server.
|
|
||||||
// User shouldn't be notified about error if server shouldn't be running,
|
|
||||||
// but it should in case it was not closed by `s.Close()`.
|
|
||||||
if err != nil && s.IsRunning() {
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
l.WithError(err).Error("SMTP server failed")
|
|
||||||
s.eventListener.Emit(events.ErrorEvent, "SMTP failed: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
// Go SMTP server instance can be closed only once. Otherwise
|
|
||||||
// it returns an error. The error is not export therefore we
|
|
||||||
// will check the string value.
|
|
||||||
err := s.server.Close()
|
|
||||||
if err == nil || err.Error() != "smtp: server already closed" {
|
|
||||||
l.WithError(err).Warn("Server was not closed")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
l.Info("SMTP server closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops the server.
|
|
||||||
func (s *Server) Close() {
|
|
||||||
if !s.IsRunning() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
|
|
||||||
if err := s.server.Close(); err != nil {
|
|
||||||
log.WithError(err).Error("Cannot close the server")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) DisconnectUser(address string) {
|
func (s *Server) DisconnectUser(address string) {
|
||||||
log.Info("Disconnecting all open SMTP connections for ", address)
|
log.Info("Disconnecting all open SMTP connections for ", address)
|
||||||
@ -186,3 +118,6 @@ func (s *Server) DisconnectUser(address string) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) Serve(l net.Listener) error { return s.server.Serve(l) }
|
||||||
|
func (s *Server) StopServe() error { return s.server.Close() }
|
||||||
|
|||||||
@ -362,7 +362,8 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
|
|||||||
req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
|
req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
|
||||||
containsUnencryptedRecipients := false
|
containsUnencryptedRecipients := false
|
||||||
|
|
||||||
for _, email := range to {
|
for _, recipient := range message.Recipients() {
|
||||||
|
email := recipient.Address
|
||||||
if !looksLikeEmail(email) {
|
if !looksLikeEmail(email) {
|
||||||
return errors.New(`"` + email + `" is not a valid recipient.`)
|
return errors.New(`"` + email + `" is not a valid recipient.`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -247,7 +247,12 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
|
|||||||
l.WithError(err).WithField("errors", loop.errCounter).Error("Error skipped")
|
l.WithError(err).WithField("errors", loop.errCounter).Error("Error skipped")
|
||||||
loop.errCounter++
|
loop.errCounter++
|
||||||
if loop.errCounter == errMaxSentry {
|
if loop.errCounter == errMaxSentry {
|
||||||
if sentryErr := loop.store.sentryReporter.ReportMessage("Warning: event loop issues: " + err.Error() + ", " + loop.currentEventID); sentryErr != nil {
|
context := map[string]interface{}{
|
||||||
|
"EventLoop": map[string]interface{}{
|
||||||
|
"EventID": loop.currentEventID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if sentryErr := loop.store.sentryReporter.ReportMessageWithContext("Warning: event loop issues: "+err.Error(), context); sentryErr != nil {
|
||||||
l.WithError(sentryErr).Error("Failed to report error to sentry")
|
l.WithError(sentryErr).Error("Failed to report error to sentry")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,7 +307,12 @@ func (loop *eventLoop) processEvent(event *pmapi.Event) (err error) {
|
|||||||
eventLog.Info("Processing refresh event")
|
eventLog.Info("Processing refresh event")
|
||||||
loop.store.triggerSync()
|
loop.store.triggerSync()
|
||||||
|
|
||||||
if sentryErr := loop.store.sentryReporter.ReportMessage("Warning: refresh occurred, " + loop.currentEventID); sentryErr != nil {
|
context := map[string]interface{}{
|
||||||
|
"EventLoop": map[string]interface{}{
|
||||||
|
"EventID": loop.currentEventID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if sentryErr := loop.store.sentryReporter.ReportMessageWithContext("Warning: refresh occurred", context); sentryErr != nil {
|
||||||
loop.log.WithError(sentryErr).Error("Failed to report refresh to sentry")
|
loop.log.WithError(sentryErr).Error("Failed to report refresh to sentry")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,9 +48,7 @@ func (storeMailbox *Mailbox) FetchMessage(apiID string) (*Message, error) {
|
|||||||
return newStoreMessage(storeMailbox, msg), nil
|
return newStoreMessage(storeMailbox, msg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportMessage imports the message by calling an API.
|
func (storeMailbox *Mailbox) ImportMessage(enc []byte, seen bool, labelIDs []string, flags, time int64) (string, error) {
|
||||||
// It has to be propagated to all mailboxes which is done by the event loop.
|
|
||||||
func (storeMailbox *Mailbox) ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error {
|
|
||||||
defer storeMailbox.pollNow()
|
defer storeMailbox.pollNow()
|
||||||
|
|
||||||
if storeMailbox.labelID != pmapi.AllMailLabel {
|
if storeMailbox.labelID != pmapi.AllMailLabel {
|
||||||
@ -59,24 +57,25 @@ func (storeMailbox *Mailbox) ImportMessage(msg *pmapi.Message, body []byte, labe
|
|||||||
|
|
||||||
importReqs := &pmapi.ImportMsgReq{
|
importReqs := &pmapi.ImportMsgReq{
|
||||||
Metadata: &pmapi.ImportMetadata{
|
Metadata: &pmapi.ImportMetadata{
|
||||||
AddressID: msg.AddressID,
|
AddressID: storeMailbox.storeAddress.addressID,
|
||||||
Unread: msg.Unread,
|
Unread: pmapi.Boolean(!seen),
|
||||||
Flags: msg.Flags,
|
Flags: flags,
|
||||||
Time: msg.Time,
|
Time: time,
|
||||||
LabelIDs: labelIDs,
|
LabelIDs: labelIDs,
|
||||||
},
|
},
|
||||||
Message: body,
|
Message: append(enc, "\r\n"...),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := storeMailbox.client().Import(exposeContextForIMAP(), pmapi.ImportMsgReqs{importReqs})
|
res, err := storeMailbox.client().Import(exposeContextForIMAP(), pmapi.ImportMsgReqs{importReqs})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res) == 0 {
|
if len(res) == 0 {
|
||||||
return errors.New("no import response")
|
return "", errors.New("no import response")
|
||||||
}
|
}
|
||||||
msg.ID = res[0].MessageID
|
|
||||||
return res[0].Error
|
return res[0].MessageID, res[0].Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelMessages adds the label by calling an API.
|
// LabelMessages adds the label by calling an API.
|
||||||
@ -355,6 +354,10 @@ func (storeMailbox *Mailbox) txCreateOrUpdateMessages(tx *bolt.Tx, msgs []*pmapi
|
|||||||
// Buckets are not initialized right away because it's a heavy operation.
|
// Buckets are not initialized right away because it's a heavy operation.
|
||||||
// The best option is to get the same bucket only once and only when needed.
|
// The best option is to get the same bucket only once and only when needed.
|
||||||
var apiBucket, imapBucket, deletedBucket *bolt.Bucket
|
var apiBucket, imapBucket, deletedBucket *bolt.Bucket
|
||||||
|
|
||||||
|
// Collect updates to send them later, after possibly sending the status/EXISTS update.
|
||||||
|
updates := make([]func(), 0, len(msgs))
|
||||||
|
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
if storeMailbox.txSkipAndRemoveFromMailbox(tx, msg) {
|
if storeMailbox.txSkipAndRemoveFromMailbox(tx, msg) {
|
||||||
continue
|
continue
|
||||||
@ -417,14 +420,18 @@ func (storeMailbox *Mailbox) txCreateOrUpdateMessages(tx *bolt.Tx, msgs []*pmapi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "cannot get sequence number from UID")
|
return errors.Wrap(err, "cannot get sequence number from UID")
|
||||||
}
|
}
|
||||||
storeMailbox.store.notifyUpdateMessage(
|
|
||||||
storeMailbox.storeAddress.address,
|
updates = append(updates, func() {
|
||||||
storeMailbox.labelName,
|
storeMailbox.store.notifyUpdateMessage(
|
||||||
uid,
|
storeMailbox.storeAddress.address,
|
||||||
seqNum,
|
storeMailbox.labelName,
|
||||||
msg,
|
uid,
|
||||||
false, // new message is never marked as deleted
|
seqNum,
|
||||||
)
|
msg,
|
||||||
|
false, // new message is never marked as deleted
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
shouldSendMailboxUpdate = true
|
shouldSendMailboxUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,6 +441,10 @@ func (storeMailbox *Mailbox) txCreateOrUpdateMessages(tx *bolt.Tx, msgs []*pmapi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, update := range updates {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,89 +12,89 @@ import (
|
|||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockPanicHandler is a mock of PanicHandler interface
|
// MockPanicHandler is a mock of PanicHandler interface.
|
||||||
type MockPanicHandler struct {
|
type MockPanicHandler struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockPanicHandlerMockRecorder
|
recorder *MockPanicHandlerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
|
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
|
||||||
type MockPanicHandlerMockRecorder struct {
|
type MockPanicHandlerMockRecorder struct {
|
||||||
mock *MockPanicHandler
|
mock *MockPanicHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockPanicHandler creates a new mock instance
|
// NewMockPanicHandler creates a new mock instance.
|
||||||
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
||||||
mock := &MockPanicHandler{ctrl: ctrl}
|
mock := &MockPanicHandler{ctrl: ctrl}
|
||||||
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePanic mocks base method
|
// HandlePanic mocks base method.
|
||||||
func (m *MockPanicHandler) HandlePanic() {
|
func (m *MockPanicHandler) HandlePanic() {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "HandlePanic")
|
m.ctrl.Call(m, "HandlePanic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePanic indicates an expected call of HandlePanic
|
// HandlePanic indicates an expected call of HandlePanic.
|
||||||
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockBridgeUser is a mock of BridgeUser interface
|
// MockBridgeUser is a mock of BridgeUser interface.
|
||||||
type MockBridgeUser struct {
|
type MockBridgeUser struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockBridgeUserMockRecorder
|
recorder *MockBridgeUserMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockBridgeUserMockRecorder is the mock recorder for MockBridgeUser
|
// MockBridgeUserMockRecorder is the mock recorder for MockBridgeUser.
|
||||||
type MockBridgeUserMockRecorder struct {
|
type MockBridgeUserMockRecorder struct {
|
||||||
mock *MockBridgeUser
|
mock *MockBridgeUser
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockBridgeUser creates a new mock instance
|
// NewMockBridgeUser creates a new mock instance.
|
||||||
func NewMockBridgeUser(ctrl *gomock.Controller) *MockBridgeUser {
|
func NewMockBridgeUser(ctrl *gomock.Controller) *MockBridgeUser {
|
||||||
mock := &MockBridgeUser{ctrl: ctrl}
|
mock := &MockBridgeUser{ctrl: ctrl}
|
||||||
mock.recorder = &MockBridgeUserMockRecorder{mock}
|
mock.recorder = &MockBridgeUserMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockBridgeUser) EXPECT() *MockBridgeUserMockRecorder {
|
func (m *MockBridgeUser) EXPECT() *MockBridgeUserMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseAllConnections mocks base method
|
// CloseAllConnections mocks base method.
|
||||||
func (m *MockBridgeUser) CloseAllConnections() {
|
func (m *MockBridgeUser) CloseAllConnections() {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "CloseAllConnections")
|
m.ctrl.Call(m, "CloseAllConnections")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseAllConnections indicates an expected call of CloseAllConnections
|
// CloseAllConnections indicates an expected call of CloseAllConnections.
|
||||||
func (mr *MockBridgeUserMockRecorder) CloseAllConnections() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) CloseAllConnections() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAllConnections", reflect.TypeOf((*MockBridgeUser)(nil).CloseAllConnections))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAllConnections", reflect.TypeOf((*MockBridgeUser)(nil).CloseAllConnections))
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseConnection mocks base method
|
// CloseConnection mocks base method.
|
||||||
func (m *MockBridgeUser) CloseConnection(arg0 string) {
|
func (m *MockBridgeUser) CloseConnection(arg0 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "CloseConnection", arg0)
|
m.ctrl.Call(m, "CloseConnection", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CloseConnection indicates an expected call of CloseConnection
|
// CloseConnection indicates an expected call of CloseConnection.
|
||||||
func (mr *MockBridgeUserMockRecorder) CloseConnection(arg0 interface{}) *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) CloseConnection(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseConnection", reflect.TypeOf((*MockBridgeUser)(nil).CloseConnection), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseConnection", reflect.TypeOf((*MockBridgeUser)(nil).CloseConnection), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddressID mocks base method
|
// GetAddressID mocks base method.
|
||||||
func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) {
|
func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetAddressID", arg0)
|
ret := m.ctrl.Call(m, "GetAddressID", arg0)
|
||||||
@ -103,13 +103,13 @@ func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddressID indicates an expected call of GetAddressID
|
// GetAddressID indicates an expected call of GetAddressID.
|
||||||
func (mr *MockBridgeUserMockRecorder) GetAddressID(arg0 interface{}) *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) GetAddressID(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddressID", reflect.TypeOf((*MockBridgeUser)(nil).GetAddressID), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddressID", reflect.TypeOf((*MockBridgeUser)(nil).GetAddressID), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient mocks base method
|
// GetClient mocks base method.
|
||||||
func (m *MockBridgeUser) GetClient() pmapi.Client {
|
func (m *MockBridgeUser) GetClient() pmapi.Client {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetClient")
|
ret := m.ctrl.Call(m, "GetClient")
|
||||||
@ -117,13 +117,13 @@ func (m *MockBridgeUser) GetClient() pmapi.Client {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetClient indicates an expected call of GetClient
|
// GetClient indicates an expected call of GetClient.
|
||||||
func (mr *MockBridgeUserMockRecorder) GetClient() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) GetClient() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockBridgeUser)(nil).GetClient))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockBridgeUser)(nil).GetClient))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrimaryAddress mocks base method
|
// GetPrimaryAddress mocks base method.
|
||||||
func (m *MockBridgeUser) GetPrimaryAddress() string {
|
func (m *MockBridgeUser) GetPrimaryAddress() string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetPrimaryAddress")
|
ret := m.ctrl.Call(m, "GetPrimaryAddress")
|
||||||
@ -131,13 +131,13 @@ func (m *MockBridgeUser) GetPrimaryAddress() string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrimaryAddress indicates an expected call of GetPrimaryAddress
|
// GetPrimaryAddress indicates an expected call of GetPrimaryAddress.
|
||||||
func (mr *MockBridgeUserMockRecorder) GetPrimaryAddress() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) GetPrimaryAddress() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryAddress", reflect.TypeOf((*MockBridgeUser)(nil).GetPrimaryAddress))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryAddress", reflect.TypeOf((*MockBridgeUser)(nil).GetPrimaryAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStoreAddresses mocks base method
|
// GetStoreAddresses mocks base method.
|
||||||
func (m *MockBridgeUser) GetStoreAddresses() []string {
|
func (m *MockBridgeUser) GetStoreAddresses() []string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetStoreAddresses")
|
ret := m.ctrl.Call(m, "GetStoreAddresses")
|
||||||
@ -145,13 +145,13 @@ func (m *MockBridgeUser) GetStoreAddresses() []string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStoreAddresses indicates an expected call of GetStoreAddresses
|
// GetStoreAddresses indicates an expected call of GetStoreAddresses.
|
||||||
func (mr *MockBridgeUserMockRecorder) GetStoreAddresses() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) GetStoreAddresses() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStoreAddresses", reflect.TypeOf((*MockBridgeUser)(nil).GetStoreAddresses))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStoreAddresses", reflect.TypeOf((*MockBridgeUser)(nil).GetStoreAddresses))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID mocks base method
|
// ID mocks base method.
|
||||||
func (m *MockBridgeUser) ID() string {
|
func (m *MockBridgeUser) ID() string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ID")
|
ret := m.ctrl.Call(m, "ID")
|
||||||
@ -159,13 +159,13 @@ func (m *MockBridgeUser) ID() string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID indicates an expected call of ID
|
// ID indicates an expected call of ID.
|
||||||
func (mr *MockBridgeUserMockRecorder) ID() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) ID() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockBridgeUser)(nil).ID))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockBridgeUser)(nil).ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCombinedAddressMode mocks base method
|
// IsCombinedAddressMode mocks base method.
|
||||||
func (m *MockBridgeUser) IsCombinedAddressMode() bool {
|
func (m *MockBridgeUser) IsCombinedAddressMode() bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "IsCombinedAddressMode")
|
ret := m.ctrl.Call(m, "IsCombinedAddressMode")
|
||||||
@ -173,13 +173,13 @@ func (m *MockBridgeUser) IsCombinedAddressMode() bool {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCombinedAddressMode indicates an expected call of IsCombinedAddressMode
|
// IsCombinedAddressMode indicates an expected call of IsCombinedAddressMode.
|
||||||
func (mr *MockBridgeUserMockRecorder) IsCombinedAddressMode() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) IsCombinedAddressMode() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCombinedAddressMode", reflect.TypeOf((*MockBridgeUser)(nil).IsCombinedAddressMode))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCombinedAddressMode", reflect.TypeOf((*MockBridgeUser)(nil).IsCombinedAddressMode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsConnected mocks base method
|
// IsConnected mocks base method.
|
||||||
func (m *MockBridgeUser) IsConnected() bool {
|
func (m *MockBridgeUser) IsConnected() bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "IsConnected")
|
ret := m.ctrl.Call(m, "IsConnected")
|
||||||
@ -187,13 +187,13 @@ func (m *MockBridgeUser) IsConnected() bool {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsConnected indicates an expected call of IsConnected
|
// IsConnected indicates an expected call of IsConnected.
|
||||||
func (mr *MockBridgeUserMockRecorder) IsConnected() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) IsConnected() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockBridgeUser)(nil).IsConnected))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockBridgeUser)(nil).IsConnected))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout mocks base method
|
// Logout mocks base method.
|
||||||
func (m *MockBridgeUser) Logout() error {
|
func (m *MockBridgeUser) Logout() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Logout")
|
ret := m.ctrl.Call(m, "Logout")
|
||||||
@ -201,13 +201,13 @@ func (m *MockBridgeUser) Logout() error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout indicates an expected call of Logout
|
// Logout indicates an expected call of Logout.
|
||||||
func (mr *MockBridgeUserMockRecorder) Logout() *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) Logout() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockBridgeUser)(nil).Logout))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockBridgeUser)(nil).Logout))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser mocks base method
|
// UpdateUser mocks base method.
|
||||||
func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
|
func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateUser", arg0)
|
ret := m.ctrl.Call(m, "UpdateUser", arg0)
|
||||||
@ -215,36 +215,36 @@ func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser indicates an expected call of UpdateUser
|
// UpdateUser indicates an expected call of UpdateUser.
|
||||||
func (mr *MockBridgeUserMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
|
func (mr *MockBridgeUserMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockBridgeUser)(nil).UpdateUser), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockBridgeUser)(nil).UpdateUser), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockChangeNotifier is a mock of ChangeNotifier interface
|
// MockChangeNotifier is a mock of ChangeNotifier interface.
|
||||||
type MockChangeNotifier struct {
|
type MockChangeNotifier struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockChangeNotifierMockRecorder
|
recorder *MockChangeNotifierMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier
|
// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier.
|
||||||
type MockChangeNotifierMockRecorder struct {
|
type MockChangeNotifierMockRecorder struct {
|
||||||
mock *MockChangeNotifier
|
mock *MockChangeNotifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockChangeNotifier creates a new mock instance
|
// NewMockChangeNotifier creates a new mock instance.
|
||||||
func NewMockChangeNotifier(ctrl *gomock.Controller) *MockChangeNotifier {
|
func NewMockChangeNotifier(ctrl *gomock.Controller) *MockChangeNotifier {
|
||||||
mock := &MockChangeNotifier{ctrl: ctrl}
|
mock := &MockChangeNotifier{ctrl: ctrl}
|
||||||
mock.recorder = &MockChangeNotifierMockRecorder{mock}
|
mock.recorder = &MockChangeNotifierMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockChangeNotifier) EXPECT() *MockChangeNotifierMockRecorder {
|
func (m *MockChangeNotifier) EXPECT() *MockChangeNotifierMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanDelete mocks base method
|
// CanDelete mocks base method.
|
||||||
func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) {
|
func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CanDelete", arg0)
|
ret := m.ctrl.Call(m, "CanDelete", arg0)
|
||||||
@ -253,67 +253,67 @@ func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanDelete indicates an expected call of CanDelete
|
// CanDelete indicates an expected call of CanDelete.
|
||||||
func (mr *MockChangeNotifierMockRecorder) CanDelete(arg0 interface{}) *gomock.Call {
|
func (mr *MockChangeNotifierMockRecorder) CanDelete(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDelete", reflect.TypeOf((*MockChangeNotifier)(nil).CanDelete), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDelete", reflect.TypeOf((*MockChangeNotifier)(nil).CanDelete), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMessage mocks base method
|
// DeleteMessage mocks base method.
|
||||||
func (m *MockChangeNotifier) DeleteMessage(arg0, arg1 string, arg2 uint32) {
|
func (m *MockChangeNotifier) DeleteMessage(arg0, arg1 string, arg2 uint32) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "DeleteMessage", arg0, arg1, arg2)
|
m.ctrl.Call(m, "DeleteMessage", arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMessage indicates an expected call of DeleteMessage
|
// DeleteMessage indicates an expected call of DeleteMessage.
|
||||||
func (mr *MockChangeNotifierMockRecorder) DeleteMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockChangeNotifierMockRecorder) DeleteMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockChangeNotifier)(nil).DeleteMessage), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockChangeNotifier)(nil).DeleteMessage), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailboxCreated mocks base method
|
// MailboxCreated mocks base method.
|
||||||
func (m *MockChangeNotifier) MailboxCreated(arg0, arg1 string) {
|
func (m *MockChangeNotifier) MailboxCreated(arg0, arg1 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "MailboxCreated", arg0, arg1)
|
m.ctrl.Call(m, "MailboxCreated", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailboxCreated indicates an expected call of MailboxCreated
|
// MailboxCreated indicates an expected call of MailboxCreated.
|
||||||
func (mr *MockChangeNotifierMockRecorder) MailboxCreated(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockChangeNotifierMockRecorder) MailboxCreated(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxCreated", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxCreated), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxCreated", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxCreated), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailboxStatus mocks base method
|
// MailboxStatus mocks base method.
|
||||||
func (m *MockChangeNotifier) MailboxStatus(arg0, arg1 string, arg2, arg3, arg4 uint32) {
|
func (m *MockChangeNotifier) MailboxStatus(arg0, arg1 string, arg2, arg3, arg4 uint32) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "MailboxStatus", arg0, arg1, arg2, arg3, arg4)
|
m.ctrl.Call(m, "MailboxStatus", arg0, arg1, arg2, arg3, arg4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailboxStatus indicates an expected call of MailboxStatus
|
// MailboxStatus indicates an expected call of MailboxStatus.
|
||||||
func (mr *MockChangeNotifierMockRecorder) MailboxStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
func (mr *MockChangeNotifierMockRecorder) MailboxStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxStatus", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxStatus), arg0, arg1, arg2, arg3, arg4)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxStatus", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxStatus), arg0, arg1, arg2, arg3, arg4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notice mocks base method
|
// Notice mocks base method.
|
||||||
func (m *MockChangeNotifier) Notice(arg0, arg1 string) {
|
func (m *MockChangeNotifier) Notice(arg0, arg1 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Notice", arg0, arg1)
|
m.ctrl.Call(m, "Notice", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notice indicates an expected call of Notice
|
// Notice indicates an expected call of Notice.
|
||||||
func (mr *MockChangeNotifierMockRecorder) Notice(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockChangeNotifierMockRecorder) Notice(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notice", reflect.TypeOf((*MockChangeNotifier)(nil).Notice), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notice", reflect.TypeOf((*MockChangeNotifier)(nil).Notice), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMessage mocks base method
|
// UpdateMessage mocks base method.
|
||||||
func (m *MockChangeNotifier) UpdateMessage(arg0, arg1 string, arg2, arg3 uint32, arg4 *pmapi.Message, arg5 bool) {
|
func (m *MockChangeNotifier) UpdateMessage(arg0, arg1 string, arg2, arg3 uint32, arg4 *pmapi.Message, arg5 bool) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "UpdateMessage", arg0, arg1, arg2, arg3, arg4, arg5)
|
m.ctrl.Call(m, "UpdateMessage", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMessage indicates an expected call of UpdateMessage
|
// UpdateMessage indicates an expected call of UpdateMessage.
|
||||||
func (mr *MockChangeNotifierMockRecorder) UpdateMessage(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
func (mr *MockChangeNotifierMockRecorder) UpdateMessage(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessage", reflect.TypeOf((*MockChangeNotifier)(nil).UpdateMessage), arg0, arg1, arg2, arg3, arg4, arg5)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessage", reflect.TypeOf((*MockChangeNotifier)(nil).UpdateMessage), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
|
|||||||
@ -11,54 +11,54 @@ import (
|
|||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockListener is a mock of Listener interface
|
// MockListener is a mock of Listener interface.
|
||||||
type MockListener struct {
|
type MockListener struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockListenerMockRecorder
|
recorder *MockListenerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockListenerMockRecorder is the mock recorder for MockListener
|
// MockListenerMockRecorder is the mock recorder for MockListener.
|
||||||
type MockListenerMockRecorder struct {
|
type MockListenerMockRecorder struct {
|
||||||
mock *MockListener
|
mock *MockListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockListener creates a new mock instance
|
// NewMockListener creates a new mock instance.
|
||||||
func NewMockListener(ctrl *gomock.Controller) *MockListener {
|
func NewMockListener(ctrl *gomock.Controller) *MockListener {
|
||||||
mock := &MockListener{ctrl: ctrl}
|
mock := &MockListener{ctrl: ctrl}
|
||||||
mock.recorder = &MockListenerMockRecorder{mock}
|
mock.recorder = &MockListenerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
|
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add mocks base method
|
// Add mocks base method.
|
||||||
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
|
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Add", arg0, arg1)
|
m.ctrl.Call(m, "Add", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add indicates an expected call of Add
|
// Add indicates an expected call of Add.
|
||||||
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit mocks base method
|
// Emit mocks base method.
|
||||||
func (m *MockListener) Emit(arg0, arg1 string) {
|
func (m *MockListener) Emit(arg0, arg1 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Emit", arg0, arg1)
|
m.ctrl.Call(m, "Emit", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit indicates an expected call of Emit
|
// Emit indicates an expected call of Emit.
|
||||||
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideChannel mocks base method
|
// ProvideChannel mocks base method.
|
||||||
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
|
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
|
||||||
@ -66,55 +66,55 @@ func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideChannel indicates an expected call of ProvideChannel
|
// ProvideChannel indicates an expected call of ProvideChannel.
|
||||||
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove mocks base method
|
// Remove mocks base method.
|
||||||
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
|
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Remove", arg0, arg1)
|
m.ctrl.Call(m, "Remove", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove indicates an expected call of Remove
|
// Remove indicates an expected call of Remove.
|
||||||
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetryEmit mocks base method
|
// RetryEmit mocks base method.
|
||||||
func (m *MockListener) RetryEmit(arg0 string) {
|
func (m *MockListener) RetryEmit(arg0 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "RetryEmit", arg0)
|
m.ctrl.Call(m, "RetryEmit", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetryEmit indicates an expected call of RetryEmit
|
// RetryEmit indicates an expected call of RetryEmit.
|
||||||
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBuffer mocks base method
|
// SetBuffer mocks base method.
|
||||||
func (m *MockListener) SetBuffer(arg0 string) {
|
func (m *MockListener) SetBuffer(arg0 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetBuffer", arg0)
|
m.ctrl.Call(m, "SetBuffer", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBuffer indicates an expected call of SetBuffer
|
// SetBuffer indicates an expected call of SetBuffer.
|
||||||
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLimit mocks base method
|
// SetLimit mocks base method.
|
||||||
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
|
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetLimit", arg0, arg1)
|
m.ctrl.Call(m, "SetLimit", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLimit indicates an expected call of SetLimit
|
// SetLimit indicates an expected call of SetLimit.
|
||||||
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)
|
||||||
|
|||||||
@ -12,65 +12,65 @@ import (
|
|||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockPanicHandler is a mock of PanicHandler interface
|
// MockPanicHandler is a mock of PanicHandler interface.
|
||||||
type MockPanicHandler struct {
|
type MockPanicHandler struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockPanicHandlerMockRecorder
|
recorder *MockPanicHandlerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
|
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
|
||||||
type MockPanicHandlerMockRecorder struct {
|
type MockPanicHandlerMockRecorder struct {
|
||||||
mock *MockPanicHandler
|
mock *MockPanicHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockPanicHandler creates a new mock instance
|
// NewMockPanicHandler creates a new mock instance.
|
||||||
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
||||||
mock := &MockPanicHandler{ctrl: ctrl}
|
mock := &MockPanicHandler{ctrl: ctrl}
|
||||||
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePanic mocks base method
|
// HandlePanic mocks base method.
|
||||||
func (m *MockPanicHandler) HandlePanic() {
|
func (m *MockPanicHandler) HandlePanic() {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "HandlePanic")
|
m.ctrl.Call(m, "HandlePanic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePanic indicates an expected call of HandlePanic
|
// HandlePanic indicates an expected call of HandlePanic.
|
||||||
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockIMAPClientProvider is a mock of IMAPClientProvider interface
|
// MockIMAPClientProvider is a mock of IMAPClientProvider interface.
|
||||||
type MockIMAPClientProvider struct {
|
type MockIMAPClientProvider struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockIMAPClientProviderMockRecorder
|
recorder *MockIMAPClientProviderMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider
|
// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider.
|
||||||
type MockIMAPClientProviderMockRecorder struct {
|
type MockIMAPClientProviderMockRecorder struct {
|
||||||
mock *MockIMAPClientProvider
|
mock *MockIMAPClientProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockIMAPClientProvider creates a new mock instance
|
// NewMockIMAPClientProvider creates a new mock instance.
|
||||||
func NewMockIMAPClientProvider(ctrl *gomock.Controller) *MockIMAPClientProvider {
|
func NewMockIMAPClientProvider(ctrl *gomock.Controller) *MockIMAPClientProvider {
|
||||||
mock := &MockIMAPClientProvider{ctrl: ctrl}
|
mock := &MockIMAPClientProvider{ctrl: ctrl}
|
||||||
mock.recorder = &MockIMAPClientProviderMockRecorder{mock}
|
mock.recorder = &MockIMAPClientProviderMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockIMAPClientProvider) EXPECT() *MockIMAPClientProviderMockRecorder {
|
func (m *MockIMAPClientProvider) EXPECT() *MockIMAPClientProviderMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate mocks base method
|
// Authenticate mocks base method.
|
||||||
func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error {
|
func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Authenticate", arg0)
|
ret := m.ctrl.Call(m, "Authenticate", arg0)
|
||||||
@ -78,13 +78,13 @@ func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate indicates an expected call of Authenticate
|
// Authenticate indicates an expected call of Authenticate.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) Authenticate(arg0 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) Authenticate(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockIMAPClientProvider)(nil).Authenticate), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockIMAPClientProvider)(nil).Authenticate), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capability mocks base method
|
// Capability mocks base method.
|
||||||
func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) {
|
func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Capability")
|
ret := m.ctrl.Call(m, "Capability")
|
||||||
@ -93,13 +93,13 @@ func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capability indicates an expected call of Capability
|
// Capability indicates an expected call of Capability.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) Capability() *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) Capability() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capability", reflect.TypeOf((*MockIMAPClientProvider)(nil).Capability))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capability", reflect.TypeOf((*MockIMAPClientProvider)(nil).Capability))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch mocks base method
|
// Fetch mocks base method.
|
||||||
func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
|
func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Fetch", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "Fetch", arg0, arg1, arg2)
|
||||||
@ -107,13 +107,13 @@ func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem,
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch indicates an expected call of Fetch
|
// Fetch indicates an expected call of Fetch.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) Fetch(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) Fetch(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).Fetch), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).Fetch), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List mocks base method
|
// List mocks base method.
|
||||||
func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.MailboxInfo) error {
|
func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.MailboxInfo) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "List", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "List", arg0, arg1, arg2)
|
||||||
@ -121,13 +121,13 @@ func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.Mailbox
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// List indicates an expected call of List
|
// List indicates an expected call of List.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIMAPClientProvider)(nil).List), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIMAPClientProvider)(nil).List), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login mocks base method
|
// Login mocks base method.
|
||||||
func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error {
|
func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Login", arg0, arg1)
|
ret := m.ctrl.Call(m, "Login", arg0, arg1)
|
||||||
@ -135,13 +135,13 @@ func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login indicates an expected call of Login
|
// Login indicates an expected call of Login.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIMAPClientProvider)(nil).Login), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIMAPClientProvider)(nil).Login), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select mocks base method
|
// Select mocks base method.
|
||||||
func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxStatus, error) {
|
func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxStatus, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Select", arg0, arg1)
|
ret := m.ctrl.Call(m, "Select", arg0, arg1)
|
||||||
@ -150,13 +150,13 @@ func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxSt
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select indicates an expected call of Select
|
// Select indicates an expected call of Select.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockIMAPClientProvider)(nil).Select), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockIMAPClientProvider)(nil).Select), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// State mocks base method
|
// State mocks base method.
|
||||||
func (m *MockIMAPClientProvider) State() imap.ConnState {
|
func (m *MockIMAPClientProvider) State() imap.ConnState {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "State")
|
ret := m.ctrl.Call(m, "State")
|
||||||
@ -164,13 +164,13 @@ func (m *MockIMAPClientProvider) State() imap.ConnState {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// State indicates an expected call of State
|
// State indicates an expected call of State.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) State() *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) State() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIMAPClientProvider)(nil).State))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIMAPClientProvider)(nil).State))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support mocks base method
|
// Support mocks base method.
|
||||||
func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) {
|
func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Support", arg0)
|
ret := m.ctrl.Call(m, "Support", arg0)
|
||||||
@ -179,13 +179,13 @@ func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support indicates an expected call of Support
|
// Support indicates an expected call of Support.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) Support(arg0 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) Support(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Support", reflect.TypeOf((*MockIMAPClientProvider)(nil).Support), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Support", reflect.TypeOf((*MockIMAPClientProvider)(nil).Support), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportAuth mocks base method
|
// SupportAuth mocks base method.
|
||||||
func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) {
|
func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SupportAuth", arg0)
|
ret := m.ctrl.Call(m, "SupportAuth", arg0)
|
||||||
@ -194,13 +194,13 @@ func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportAuth indicates an expected call of SupportAuth
|
// SupportAuth indicates an expected call of SupportAuth.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) SupportAuth(arg0 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) SupportAuth(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportAuth", reflect.TypeOf((*MockIMAPClientProvider)(nil).SupportAuth), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportAuth", reflect.TypeOf((*MockIMAPClientProvider)(nil).SupportAuth), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UidFetch mocks base method
|
// UidFetch mocks base method.
|
||||||
func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
|
func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UidFetch", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "UidFetch", arg0, arg1, arg2)
|
||||||
@ -208,7 +208,7 @@ func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchIt
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UidFetch indicates an expected call of UidFetch
|
// UidFetch indicates an expected call of UidFetch.
|
||||||
func (mr *MockIMAPClientProviderMockRecorder) UidFetch(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockIMAPClientProviderMockRecorder) UidFetch(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UidFetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).UidFetch), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UidFetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).UidFetch), arg0, arg1, arg2)
|
||||||
|
|||||||
@ -47,8 +47,8 @@ type Credentials struct {
|
|||||||
UserID, // Do not marshal; used as a key.
|
UserID, // Do not marshal; used as a key.
|
||||||
Name,
|
Name,
|
||||||
Emails,
|
Emails,
|
||||||
APIToken,
|
APIToken string
|
||||||
MailboxPassword,
|
MailboxPassword []byte
|
||||||
BridgePassword,
|
BridgePassword,
|
||||||
Version string
|
Version string
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
@ -58,15 +58,15 @@ type Credentials struct {
|
|||||||
|
|
||||||
func (s *Credentials) Marshal() string {
|
func (s *Credentials) Marshal() string {
|
||||||
items := []string{
|
items := []string{
|
||||||
s.Name, // 0
|
s.Name, // 0
|
||||||
s.Emails, // 1
|
s.Emails, // 1
|
||||||
s.APIToken, // 2
|
s.APIToken, // 2
|
||||||
s.MailboxPassword, // 3
|
string(s.MailboxPassword), // 3
|
||||||
s.BridgePassword, // 4
|
s.BridgePassword, // 4
|
||||||
s.Version, // 5
|
s.Version, // 5
|
||||||
"", // 6
|
"", // 6
|
||||||
"", // 7
|
"", // 7
|
||||||
"", // 8
|
"", // 8
|
||||||
}
|
}
|
||||||
|
|
||||||
items[6] = fmt.Sprint(s.Timestamp)
|
items[6] = fmt.Sprint(s.Timestamp)
|
||||||
@ -97,7 +97,7 @@ func (s *Credentials) Unmarshal(secret string) error {
|
|||||||
s.Name = items[0]
|
s.Name = items[0]
|
||||||
s.Emails = items[1]
|
s.Emails = items[1]
|
||||||
s.APIToken = items[2]
|
s.APIToken = items[2]
|
||||||
s.MailboxPassword = items[3]
|
s.MailboxPassword = []byte(items[3])
|
||||||
|
|
||||||
switch len(items) {
|
switch len(items) {
|
||||||
case itemLengthBridge:
|
case itemLengthBridge:
|
||||||
@ -143,11 +143,16 @@ func (s *Credentials) CheckPassword(password string) error {
|
|||||||
|
|
||||||
func (s *Credentials) Logout() {
|
func (s *Credentials) Logout() {
|
||||||
s.APIToken = ""
|
s.APIToken = ""
|
||||||
s.MailboxPassword = ""
|
|
||||||
|
for i := range s.MailboxPassword {
|
||||||
|
s.MailboxPassword[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
s.MailboxPassword = []byte{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Credentials) IsConnected() bool {
|
func (s *Credentials) IsConnected() bool {
|
||||||
return s.APIToken != "" && s.MailboxPassword != ""
|
return s.APIToken != "" && len(s.MailboxPassword) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Credentials) SplitAPIToken() (string, string, error) {
|
func (s *Credentials) SplitAPIToken() (string, string, error) {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ var wantCredentials = Credentials{
|
|||||||
Name: "name",
|
Name: "name",
|
||||||
Emails: "email1;email2",
|
Emails: "email1;email2",
|
||||||
APIToken: "token",
|
APIToken: "token",
|
||||||
MailboxPassword: "mailbox pass",
|
MailboxPassword: []byte("mailbox pass"),
|
||||||
BridgePassword: "bridge pass",
|
BridgePassword: "bridge pass",
|
||||||
Version: "k11",
|
Version: "k11",
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
@ -52,7 +52,7 @@ func TestUnmarshallImportExport(t *testing.T) {
|
|||||||
wantCredentials.Name,
|
wantCredentials.Name,
|
||||||
wantCredentials.Emails,
|
wantCredentials.Emails,
|
||||||
wantCredentials.APIToken,
|
wantCredentials.APIToken,
|
||||||
wantCredentials.MailboxPassword,
|
string(wantCredentials.MailboxPassword),
|
||||||
"k11",
|
"k11",
|
||||||
fmt.Sprint(wantCredentials.Timestamp),
|
fmt.Sprint(wantCredentials.Timestamp),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
@ -39,7 +40,7 @@ func NewStore(keychain *keychain.Keychain) *Store {
|
|||||||
return &Store{secrets: keychain}
|
return &Store{secrets: keychain}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*Credentials, error) {
|
func (s *Store) Add(userID, userName, uid, ref string, mailboxPassword []byte, emails []string) (*Credentials, error) {
|
||||||
storeLocker.Lock()
|
storeLocker.Lock()
|
||||||
defer storeLocker.Unlock()
|
defer storeLocker.Unlock()
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ func (s *Store) UpdateEmails(userID string, emails []string) (*Credentials, erro
|
|||||||
return credentials, s.saveCredentials(credentials)
|
return credentials, s.saveCredentials(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) UpdatePassword(userID, password string) (*Credentials, error) {
|
func (s *Store) UpdatePassword(userID string, password []byte) (*Credentials, error) {
|
||||||
storeLocker.Lock()
|
storeLocker.Lock()
|
||||||
defer storeLocker.Unlock()
|
defer storeLocker.Unlock()
|
||||||
|
|
||||||
@ -228,21 +229,28 @@ func (s *Store) Get(userID string) (creds *Credentials, err error) {
|
|||||||
return s.get(userID)
|
return s.get(userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) get(userID string) (creds *Credentials, err error) {
|
func (s *Store) get(userID string) (*Credentials, error) {
|
||||||
log := log.WithField("user", userID)
|
log := log.WithField("user", userID)
|
||||||
|
|
||||||
_, secret, err := s.secrets.Get(userID)
|
_, secret, err := s.secrets.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Warn("Could not get credentials from native keychain")
|
return nil, err
|
||||||
return
|
}
|
||||||
|
|
||||||
|
if secret == "" {
|
||||||
|
return nil, errors.New("secret is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials := &Credentials{UserID: userID}
|
credentials := &Credentials{UserID: userID}
|
||||||
if err = credentials.Unmarshal(secret); err != nil {
|
|
||||||
err = fmt.Errorf("backend/credentials: malformed secret: %v", err)
|
if err := credentials.Unmarshal(secret); err != nil {
|
||||||
_ = s.secrets.Delete(userID)
|
log.WithError(fmt.Errorf("malformed secret: %w", err)).Error("Could not unmarshal secret")
|
||||||
log.WithError(err).Error("Could not unmarshal secret")
|
|
||||||
return
|
if err := s.secrets.Delete(userID); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to remove malformed secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return credentials, nil
|
return credentials, nil
|
||||||
|
|||||||
@ -277,7 +277,7 @@ func TestMarshal(t *testing.T) {
|
|||||||
Name: "007",
|
Name: "007",
|
||||||
Emails: "ja@pm.me;aj@cus.tom",
|
Emails: "ja@pm.me;aj@cus.tom",
|
||||||
APIToken: "sdfdsfsdfsdfsdf",
|
APIToken: "sdfdsfsdfsdfsdf",
|
||||||
MailboxPassword: "cdcdcdcd",
|
MailboxPassword: []byte("cdcdcdcd"),
|
||||||
BridgePassword: "wew123",
|
BridgePassword: "wew123",
|
||||||
Version: "k11",
|
Version: "k11",
|
||||||
Timestamp: 152469263742,
|
Timestamp: 152469263742,
|
||||||
|
|||||||
@ -11,54 +11,54 @@ import (
|
|||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockListener is a mock of Listener interface
|
// MockListener is a mock of Listener interface.
|
||||||
type MockListener struct {
|
type MockListener struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockListenerMockRecorder
|
recorder *MockListenerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockListenerMockRecorder is the mock recorder for MockListener
|
// MockListenerMockRecorder is the mock recorder for MockListener.
|
||||||
type MockListenerMockRecorder struct {
|
type MockListenerMockRecorder struct {
|
||||||
mock *MockListener
|
mock *MockListener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockListener creates a new mock instance
|
// NewMockListener creates a new mock instance.
|
||||||
func NewMockListener(ctrl *gomock.Controller) *MockListener {
|
func NewMockListener(ctrl *gomock.Controller) *MockListener {
|
||||||
mock := &MockListener{ctrl: ctrl}
|
mock := &MockListener{ctrl: ctrl}
|
||||||
mock.recorder = &MockListenerMockRecorder{mock}
|
mock.recorder = &MockListenerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
|
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add mocks base method
|
// Add mocks base method.
|
||||||
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
|
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Add", arg0, arg1)
|
m.ctrl.Call(m, "Add", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add indicates an expected call of Add
|
// Add indicates an expected call of Add.
|
||||||
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit mocks base method
|
// Emit mocks base method.
|
||||||
func (m *MockListener) Emit(arg0, arg1 string) {
|
func (m *MockListener) Emit(arg0, arg1 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Emit", arg0, arg1)
|
m.ctrl.Call(m, "Emit", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit indicates an expected call of Emit
|
// Emit indicates an expected call of Emit.
|
||||||
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideChannel mocks base method
|
// ProvideChannel mocks base method.
|
||||||
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
|
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
|
||||||
@ -66,55 +66,55 @@ func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideChannel indicates an expected call of ProvideChannel
|
// ProvideChannel indicates an expected call of ProvideChannel.
|
||||||
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove mocks base method
|
// Remove mocks base method.
|
||||||
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
|
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "Remove", arg0, arg1)
|
m.ctrl.Call(m, "Remove", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove indicates an expected call of Remove
|
// Remove indicates an expected call of Remove.
|
||||||
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetryEmit mocks base method
|
// RetryEmit mocks base method.
|
||||||
func (m *MockListener) RetryEmit(arg0 string) {
|
func (m *MockListener) RetryEmit(arg0 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "RetryEmit", arg0)
|
m.ctrl.Call(m, "RetryEmit", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetryEmit indicates an expected call of RetryEmit
|
// RetryEmit indicates an expected call of RetryEmit.
|
||||||
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBuffer mocks base method
|
// SetBuffer mocks base method.
|
||||||
func (m *MockListener) SetBuffer(arg0 string) {
|
func (m *MockListener) SetBuffer(arg0 string) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetBuffer", arg0)
|
m.ctrl.Call(m, "SetBuffer", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBuffer indicates an expected call of SetBuffer
|
// SetBuffer indicates an expected call of SetBuffer.
|
||||||
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLimit mocks base method
|
// SetLimit mocks base method.
|
||||||
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
|
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetLimit", arg0, arg1)
|
m.ctrl.Call(m, "SetLimit", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLimit indicates an expected call of SetLimit
|
// SetLimit indicates an expected call of SetLimit.
|
||||||
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)
|
||||||
|
|||||||
@ -12,30 +12,30 @@ import (
|
|||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockLocator is a mock of Locator interface
|
// MockLocator is a mock of Locator interface.
|
||||||
type MockLocator struct {
|
type MockLocator struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockLocatorMockRecorder
|
recorder *MockLocatorMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockLocatorMockRecorder is the mock recorder for MockLocator
|
// MockLocatorMockRecorder is the mock recorder for MockLocator.
|
||||||
type MockLocatorMockRecorder struct {
|
type MockLocatorMockRecorder struct {
|
||||||
mock *MockLocator
|
mock *MockLocator
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockLocator creates a new mock instance
|
// NewMockLocator creates a new mock instance.
|
||||||
func NewMockLocator(ctrl *gomock.Controller) *MockLocator {
|
func NewMockLocator(ctrl *gomock.Controller) *MockLocator {
|
||||||
mock := &MockLocator{ctrl: ctrl}
|
mock := &MockLocator{ctrl: ctrl}
|
||||||
mock.recorder = &MockLocatorMockRecorder{mock}
|
mock.recorder = &MockLocatorMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockLocator) EXPECT() *MockLocatorMockRecorder {
|
func (m *MockLocator) EXPECT() *MockLocatorMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear mocks base method
|
// Clear mocks base method.
|
||||||
func (m *MockLocator) Clear() error {
|
func (m *MockLocator) Clear() error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Clear")
|
ret := m.ctrl.Call(m, "Clear")
|
||||||
@ -43,72 +43,72 @@ func (m *MockLocator) Clear() error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear indicates an expected call of Clear
|
// Clear indicates an expected call of Clear.
|
||||||
func (mr *MockLocatorMockRecorder) Clear() *gomock.Call {
|
func (mr *MockLocatorMockRecorder) Clear() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockLocator)(nil).Clear))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockLocator)(nil).Clear))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockPanicHandler is a mock of PanicHandler interface
|
// MockPanicHandler is a mock of PanicHandler interface.
|
||||||
type MockPanicHandler struct {
|
type MockPanicHandler struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockPanicHandlerMockRecorder
|
recorder *MockPanicHandlerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
|
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
|
||||||
type MockPanicHandlerMockRecorder struct {
|
type MockPanicHandlerMockRecorder struct {
|
||||||
mock *MockPanicHandler
|
mock *MockPanicHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockPanicHandler creates a new mock instance
|
// NewMockPanicHandler creates a new mock instance.
|
||||||
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
|
||||||
mock := &MockPanicHandler{ctrl: ctrl}
|
mock := &MockPanicHandler{ctrl: ctrl}
|
||||||
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
mock.recorder = &MockPanicHandlerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePanic mocks base method
|
// HandlePanic mocks base method.
|
||||||
func (m *MockPanicHandler) HandlePanic() {
|
func (m *MockPanicHandler) HandlePanic() {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "HandlePanic")
|
m.ctrl.Call(m, "HandlePanic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlePanic indicates an expected call of HandlePanic
|
// HandlePanic indicates an expected call of HandlePanic.
|
||||||
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockCredentialsStorer is a mock of CredentialsStorer interface
|
// MockCredentialsStorer is a mock of CredentialsStorer interface.
|
||||||
type MockCredentialsStorer struct {
|
type MockCredentialsStorer struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockCredentialsStorerMockRecorder
|
recorder *MockCredentialsStorerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer
|
// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer.
|
||||||
type MockCredentialsStorerMockRecorder struct {
|
type MockCredentialsStorerMockRecorder struct {
|
||||||
mock *MockCredentialsStorer
|
mock *MockCredentialsStorer
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockCredentialsStorer creates a new mock instance
|
// NewMockCredentialsStorer creates a new mock instance.
|
||||||
func NewMockCredentialsStorer(ctrl *gomock.Controller) *MockCredentialsStorer {
|
func NewMockCredentialsStorer(ctrl *gomock.Controller) *MockCredentialsStorer {
|
||||||
mock := &MockCredentialsStorer{ctrl: ctrl}
|
mock := &MockCredentialsStorer{ctrl: ctrl}
|
||||||
mock.recorder = &MockCredentialsStorerMockRecorder{mock}
|
mock.recorder = &MockCredentialsStorerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder {
|
func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add mocks base method
|
// Add mocks base method.
|
||||||
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3, arg4 string, arg5 []string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []byte, arg5 []string) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4, arg5)
|
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
ret0, _ := ret[0].(*credentials.Credentials)
|
ret0, _ := ret[0].(*credentials.Credentials)
|
||||||
@ -116,13 +116,13 @@ func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3, arg4 string, arg5 []
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add indicates an expected call of Add
|
// Add indicates an expected call of Add.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4, arg5)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4, arg5)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete mocks base method
|
// Delete mocks base method.
|
||||||
func (m *MockCredentialsStorer) Delete(arg0 string) error {
|
func (m *MockCredentialsStorer) Delete(arg0 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Delete", arg0)
|
ret := m.ctrl.Call(m, "Delete", arg0)
|
||||||
@ -130,13 +130,13 @@ func (m *MockCredentialsStorer) Delete(arg0 string) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete indicates an expected call of Delete
|
// Delete indicates an expected call of Delete.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCredentialsStorer)(nil).Delete), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCredentialsStorer)(nil).Delete), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get mocks base method
|
// Get mocks base method.
|
||||||
func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Get", arg0)
|
ret := m.ctrl.Call(m, "Get", arg0)
|
||||||
@ -145,13 +145,13 @@ func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, erro
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get indicates an expected call of Get
|
// Get indicates an expected call of Get.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) Get(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCredentialsStorer)(nil).Get), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCredentialsStorer)(nil).Get), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List mocks base method
|
// List mocks base method.
|
||||||
func (m *MockCredentialsStorer) List() ([]string, error) {
|
func (m *MockCredentialsStorer) List() ([]string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "List")
|
ret := m.ctrl.Call(m, "List")
|
||||||
@ -160,13 +160,13 @@ func (m *MockCredentialsStorer) List() ([]string, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// List indicates an expected call of List
|
// List indicates an expected call of List.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCredentialsStorer)(nil).List))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCredentialsStorer)(nil).List))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout mocks base method
|
// Logout mocks base method.
|
||||||
func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Logout", arg0)
|
ret := m.ctrl.Call(m, "Logout", arg0)
|
||||||
@ -175,13 +175,13 @@ func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, e
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout indicates an expected call of Logout
|
// Logout indicates an expected call of Logout.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockCredentialsStorer)(nil).Logout), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockCredentialsStorer)(nil).Logout), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SwitchAddressMode mocks base method
|
// SwitchAddressMode mocks base method.
|
||||||
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SwitchAddressMode", arg0)
|
ret := m.ctrl.Call(m, "SwitchAddressMode", arg0)
|
||||||
@ -190,13 +190,13 @@ func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Cre
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SwitchAddressMode indicates an expected call of SwitchAddressMode
|
// SwitchAddressMode indicates an expected call of SwitchAddressMode.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchAddressMode", reflect.TypeOf((*MockCredentialsStorer)(nil).SwitchAddressMode), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchAddressMode", reflect.TypeOf((*MockCredentialsStorer)(nil).SwitchAddressMode), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEmails mocks base method
|
// UpdateEmails mocks base method.
|
||||||
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1)
|
ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1)
|
||||||
@ -205,14 +205,14 @@ func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*crede
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEmails indicates an expected call of UpdateEmails
|
// UpdateEmails indicates an expected call of UpdateEmails.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmails", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateEmails), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmails", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateEmails), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePassword mocks base method
|
// UpdatePassword mocks base method.
|
||||||
func (m *MockCredentialsStorer) UpdatePassword(arg0, arg1 string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) UpdatePassword(arg0 string, arg1 []byte) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1)
|
ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*credentials.Credentials)
|
ret0, _ := ret[0].(*credentials.Credentials)
|
||||||
@ -220,13 +220,13 @@ func (m *MockCredentialsStorer) UpdatePassword(arg0, arg1 string) (*credentials.
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePassword indicates an expected call of UpdatePassword
|
// UpdatePassword indicates an expected call of UpdatePassword.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePassword", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdatePassword), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePassword", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdatePassword), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateToken mocks base method
|
// UpdateToken mocks base method.
|
||||||
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentials.Credentials, error) {
|
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentials.Credentials, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1, arg2)
|
||||||
@ -235,36 +235,36 @@ func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentia
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateToken indicates an expected call of UpdateToken
|
// UpdateToken indicates an expected call of UpdateToken.
|
||||||
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockStoreMaker is a mock of StoreMaker interface
|
// MockStoreMaker is a mock of StoreMaker interface.
|
||||||
type MockStoreMaker struct {
|
type MockStoreMaker struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockStoreMakerMockRecorder
|
recorder *MockStoreMakerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockStoreMakerMockRecorder is the mock recorder for MockStoreMaker
|
// MockStoreMakerMockRecorder is the mock recorder for MockStoreMaker.
|
||||||
type MockStoreMakerMockRecorder struct {
|
type MockStoreMakerMockRecorder struct {
|
||||||
mock *MockStoreMaker
|
mock *MockStoreMaker
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockStoreMaker creates a new mock instance
|
// NewMockStoreMaker creates a new mock instance.
|
||||||
func NewMockStoreMaker(ctrl *gomock.Controller) *MockStoreMaker {
|
func NewMockStoreMaker(ctrl *gomock.Controller) *MockStoreMaker {
|
||||||
mock := &MockStoreMaker{ctrl: ctrl}
|
mock := &MockStoreMaker{ctrl: ctrl}
|
||||||
mock.recorder = &MockStoreMakerMockRecorder{mock}
|
mock.recorder = &MockStoreMakerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockStoreMaker) EXPECT() *MockStoreMakerMockRecorder {
|
func (m *MockStoreMaker) EXPECT() *MockStoreMakerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// New mocks base method
|
// New mocks base method.
|
||||||
func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) {
|
func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "New", arg0)
|
ret := m.ctrl.Call(m, "New", arg0)
|
||||||
@ -273,13 +273,13 @@ func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// New indicates an expected call of New
|
// New indicates an expected call of New.
|
||||||
func (mr *MockStoreMakerMockRecorder) New(arg0 interface{}) *gomock.Call {
|
func (mr *MockStoreMakerMockRecorder) New(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockStoreMaker)(nil).New), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockStoreMaker)(nil).New), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove mocks base method
|
// Remove mocks base method.
|
||||||
func (m *MockStoreMaker) Remove(arg0 string) error {
|
func (m *MockStoreMaker) Remove(arg0 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Remove", arg0)
|
ret := m.ctrl.Call(m, "Remove", arg0)
|
||||||
@ -287,7 +287,7 @@ func (m *MockStoreMaker) Remove(arg0 string) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove indicates an expected call of Remove
|
// Remove indicates an expected call of Remove.
|
||||||
func (mr *MockStoreMakerMockRecorder) Remove(arg0 interface{}) *gomock.Call {
|
func (mr *MockStoreMakerMockRecorder) Remove(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStoreMaker)(nil).Remove), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStoreMaker)(nil).Remove), arg0)
|
||||||
|
|||||||
@ -32,11 +32,11 @@ type PanicHandler interface {
|
|||||||
|
|
||||||
type CredentialsStorer interface {
|
type CredentialsStorer interface {
|
||||||
List() (userIDs []string, err error)
|
List() (userIDs []string, err error)
|
||||||
Add(userID, userName, uid, ref, mailboxPassword string, emails []string) (*credentials.Credentials, error)
|
Add(userID, userName, uid, ref string, mailboxPassword []byte, emails []string) (*credentials.Credentials, error)
|
||||||
Get(userID string) (*credentials.Credentials, error)
|
Get(userID string) (*credentials.Credentials, error)
|
||||||
SwitchAddressMode(userID string) (*credentials.Credentials, error)
|
SwitchAddressMode(userID string) (*credentials.Credentials, error)
|
||||||
UpdateEmails(userID string, emails []string) (*credentials.Credentials, error)
|
UpdateEmails(userID string, emails []string) (*credentials.Credentials, error)
|
||||||
UpdatePassword(userID, password string) (*credentials.Credentials, error)
|
UpdatePassword(userID string, password []byte) (*credentials.Credentials, error)
|
||||||
UpdateToken(userID, uid, ref string) (*credentials.Credentials, error)
|
UpdateToken(userID, uid, ref string) (*credentials.Credentials, error)
|
||||||
Logout(userID string) (*credentials.Credentials, error)
|
Logout(userID string) (*credentials.Credentials, error)
|
||||||
Delete(userID string) error
|
Delete(userID string) error
|
||||||
|
|||||||
@ -227,7 +227,7 @@ func (u *User) unlockIfNecessary() error {
|
|||||||
// client. Unlock should only finish unlocking when connection is back up.
|
// client. Unlock should only finish unlocking when connection is back up.
|
||||||
// That means it should try it fast enough and not retry if connection
|
// That means it should try it fast enough and not retry if connection
|
||||||
// is still down.
|
// is still down.
|
||||||
err := u.client.Unlock(pmapi.ContextWithoutRetry(context.Background()), []byte(u.creds.MailboxPassword))
|
err := u.client.Unlock(pmapi.ContextWithoutRetry(context.Background()), u.creds.MailboxPassword)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -279,26 +279,6 @@ func (u *User) GetStoreAddresses() []string {
|
|||||||
return u.creds.EmailList()
|
return u.creds.EmailList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStoreAddresses returns a user's used addresses (with the original address in first place).
|
|
||||||
func (u *User) getStoreAddresses() []string { // nolint[unused]
|
|
||||||
addrInfo, err := u.store.GetAddressInfo()
|
|
||||||
if err != nil {
|
|
||||||
u.log.WithError(err).Error("Failed getting address info from store")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses := []string{}
|
|
||||||
for _, addr := range addrInfo {
|
|
||||||
addresses = append(addresses, addr.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.IsCombinedAddressMode() {
|
|
||||||
return addresses[:1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAddresses returns list of all addresses.
|
// GetAddresses returns list of all addresses.
|
||||||
func (u *User) GetAddresses() []string {
|
func (u *User) GetAddresses() []string {
|
||||||
u.lock.RLock()
|
u.lock.RLock()
|
||||||
@ -364,7 +344,7 @@ func (u *User) UpdateUser(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.client.ReloadKeys(ctx, []byte(u.creds.MailboxPassword)); err != nil {
|
if err := u.client.ReloadKeys(ctx, u.creds.MailboxPassword); err != nil {
|
||||||
return errors.Wrap(err, "failed to reload keys")
|
return errors.Wrap(err, "failed to reload keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ func TestUpdateUser(t *testing.T) {
|
|||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().UpdateUser(gomock.Any()).Return(nil, nil),
|
m.pmapiClient.EXPECT().UpdateUser(gomock.Any()).Return(nil, nil),
|
||||||
m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
m.pmapiClient.EXPECT().ReloadKeys(gomock.Any(), testCredentials.MailboxPassword).Return(nil),
|
||||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||||
|
|
||||||
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
m.credentialsStore.EXPECT().UpdateEmails("user", []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
||||||
|
|||||||
@ -46,7 +46,7 @@ func TestNewUserUnlockFails(t *testing.T) {
|
|||||||
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
m.credentialsStore.EXPECT().Get("user").Return(testCredentials, nil),
|
||||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()),
|
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any()),
|
||||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
m.pmapiClient.EXPECT().IsUnlocked().Return(false),
|
||||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("bad password")),
|
m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(errors.New("bad password")),
|
||||||
|
|
||||||
// Handle of unlock error.
|
// Handle of unlock error.
|
||||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||||
|
|||||||
@ -200,14 +200,14 @@ func (u *Users) closeAllConnections() {
|
|||||||
|
|
||||||
// Login authenticates a user by username/password, returning an authorised client and an auth object.
|
// Login authenticates a user by username/password, returning an authorised client and an auth object.
|
||||||
// The authorisation scope may not yet be full if the user has 2FA enabled.
|
// The authorisation scope may not yet be full if the user has 2FA enabled.
|
||||||
func (u *Users) Login(username, password string) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
|
func (u *Users) Login(username string, password []byte) (authClient pmapi.Client, auth *pmapi.Auth, err error) {
|
||||||
u.crashBandicoot(username)
|
u.crashBandicoot(username)
|
||||||
|
|
||||||
return u.clientManager.NewClientWithLogin(context.Background(), username, password)
|
return u.clientManager.NewClientWithLogin(context.Background(), username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinishLogin finishes the login procedure and adds the user into the credentials store.
|
// FinishLogin finishes the login procedure and adds the user into the credentials store.
|
||||||
func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password string) (user *User, err error) { //nolint[funlen]
|
func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []byte) (user *User, err error) { //nolint[funlen]
|
||||||
apiUser, passphrase, err := getAPIUser(context.Background(), client, password)
|
apiUser, passphrase, err := getAPIUser(context.Background(), client, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -228,7 +228,7 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the password in case the user changed it.
|
// Update the password in case the user changed it.
|
||||||
creds, err := u.credStorer.UpdatePassword(apiUser.ID, string(passphrase))
|
creds, err := u.credStorer.UpdatePassword(apiUser.ID, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to update password of user in credentials store")
|
return nil, errors.Wrap(err, "failed to update password of user in credentials store")
|
||||||
}
|
}
|
||||||
@ -264,7 +264,7 @@ func (u *Users) addNewUser(client pmapi.Client, apiUser *pmapi.User, auth *pmapi
|
|||||||
emails = client.Addresses().AllEmails()
|
emails = client.Addresses().AllEmails()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, string(passphrase), emails); err != nil {
|
if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, passphrase, emails); err != nil {
|
||||||
return errors.Wrap(err, "failed to add user credentials to credentials store")
|
return errors.Wrap(err, "failed to add user credentials to credentials store")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ func (u *Users) addNewUser(client pmapi.Client, apiUser *pmapi.User, auth *pmapi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAPIUser(ctx context.Context, client pmapi.Client, password string) (*pmapi.User, []byte, error) {
|
func getAPIUser(ctx context.Context, client pmapi.Client, password []byte) (*pmapi.User, []byte, error) {
|
||||||
salt, err := client.AuthSalt(ctx)
|
salt, err := client.AuthSalt(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "failed to get salt")
|
return nil, nil, errors.Wrap(err, "failed to get salt")
|
||||||
|
|||||||
@ -37,7 +37,7 @@ func TestUsersFinishLoginBadMailboxPassword(t *testing.T) {
|
|||||||
|
|
||||||
// Set up mocks for FinishLogin.
|
// Set up mocks for FinishLogin.
|
||||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil)
|
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil)
|
||||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("no keys could be unlocked"))
|
m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(errors.New("no keys could be unlocked"))
|
||||||
|
|
||||||
checkUsersFinishLogin(t, m, testAuthRefresh, testCredentials.MailboxPassword, "", ErrWrongMailboxPassword)
|
checkUsersFinishLogin(t, m, testAuthRefresh, testCredentials.MailboxPassword, "", ErrWrongMailboxPassword)
|
||||||
}
|
}
|
||||||
@ -69,7 +69,7 @@ func TestUsersFinishLoginExistingDisconnectedUser(t *testing.T) {
|
|||||||
// Mock process of FinishLogin of already added user.
|
// Mock process of FinishLogin of already added user.
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
||||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(nil),
|
||||||
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUserDisconnected, nil),
|
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUserDisconnected, nil),
|
||||||
m.credentialsStore.EXPECT().UpdateToken(testCredentialsDisconnected.UserID, testAuthRefresh.UID, testAuthRefresh.RefreshToken).Return(testCredentials, nil),
|
m.credentialsStore.EXPECT().UpdateToken(testCredentialsDisconnected.UserID, testAuthRefresh.UID, testAuthRefresh.RefreshToken).Return(testCredentials, nil),
|
||||||
m.credentialsStore.EXPECT().UpdatePassword(testCredentialsDisconnected.UserID, testCredentials.MailboxPassword).Return(testCredentials, nil),
|
m.credentialsStore.EXPECT().UpdatePassword(testCredentialsDisconnected.UserID, testCredentials.MailboxPassword).Return(testCredentials, nil),
|
||||||
@ -101,7 +101,7 @@ func TestUsersFinishLoginConnectedUser(t *testing.T) {
|
|||||||
// Mock process of FinishLogin of already connected user.
|
// Mock process of FinishLogin of already connected user.
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
||||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(nil),
|
||||||
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil),
|
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil),
|
||||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
m.pmapiClient.EXPECT().AuthDelete(gomock.Any()).Return(nil),
|
||||||
)
|
)
|
||||||
@ -113,7 +113,7 @@ func TestUsersFinishLoginConnectedUser(t *testing.T) {
|
|||||||
r.EqualError(t, err, "user is already connected")
|
r.EqualError(t, err, "user is already connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword string, expectedUserID string, expectedErr error) {
|
func checkUsersFinishLogin(t *testing.T, m mocks, auth *pmapi.Auth, mailboxPassword []byte, expectedUserID string, expectedErr error) {
|
||||||
users := testNewUsers(t, m)
|
users := testNewUsers(t, m)
|
||||||
defer cleanUpUsersData(users)
|
defer cleanUpUsersData(users)
|
||||||
|
|
||||||
|
|||||||
@ -84,7 +84,7 @@ func TestNewUsersWithConnectedUserWithBadToken(t *testing.T) {
|
|||||||
m.clientManager.EXPECT().NewClient("uid", "", "acc", time.Time{}).Return(m.pmapiClient)
|
m.clientManager.EXPECT().NewClient("uid", "", "acc", time.Time{}).Return(m.pmapiClient)
|
||||||
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any())
|
m.pmapiClient.EXPECT().AddAuthRefreshHandler(gomock.Any())
|
||||||
m.pmapiClient.EXPECT().IsUnlocked().Return(false)
|
m.pmapiClient.EXPECT().IsUnlocked().Return(false)
|
||||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(errors.New("not authorized"))
|
m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(errors.New("not authorized"))
|
||||||
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
m.pmapiClient.EXPECT().AuthDelete(gomock.Any())
|
||||||
|
|
||||||
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
|
m.credentialsStore.EXPECT().List().Return([]string{"user"}, nil)
|
||||||
|
|||||||
@ -63,7 +63,7 @@ var (
|
|||||||
Name: "username",
|
Name: "username",
|
||||||
Emails: "user@pm.me",
|
Emails: "user@pm.me",
|
||||||
APIToken: "uid:acc",
|
APIToken: "uid:acc",
|
||||||
MailboxPassword: "pass",
|
MailboxPassword: []byte("pass"),
|
||||||
BridgePassword: "0123456789abcdef",
|
BridgePassword: "0123456789abcdef",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Timestamp: 123456789,
|
Timestamp: 123456789,
|
||||||
@ -76,7 +76,7 @@ var (
|
|||||||
Name: "usersname",
|
Name: "usersname",
|
||||||
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
|
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
|
||||||
APIToken: "uid:acc",
|
APIToken: "uid:acc",
|
||||||
MailboxPassword: "pass",
|
MailboxPassword: []byte("pass"),
|
||||||
BridgePassword: "0123456789abcdef",
|
BridgePassword: "0123456789abcdef",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Timestamp: 123456789,
|
Timestamp: 123456789,
|
||||||
@ -89,7 +89,7 @@ var (
|
|||||||
Name: "username",
|
Name: "username",
|
||||||
Emails: "user@pm.me",
|
Emails: "user@pm.me",
|
||||||
APIToken: "",
|
APIToken: "",
|
||||||
MailboxPassword: "",
|
MailboxPassword: []byte{},
|
||||||
BridgePassword: "0123456789abcdef",
|
BridgePassword: "0123456789abcdef",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Timestamp: 123456789,
|
Timestamp: 123456789,
|
||||||
@ -102,7 +102,7 @@ var (
|
|||||||
Name: "usersname",
|
Name: "usersname",
|
||||||
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
|
Emails: "users@pm.me;anotheruser@pm.me;alsouser@pm.me",
|
||||||
APIToken: "",
|
APIToken: "",
|
||||||
MailboxPassword: "",
|
MailboxPassword: []byte{},
|
||||||
BridgePassword: "0123456789abcdef",
|
BridgePassword: "0123456789abcdef",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Timestamp: 123456789,
|
Timestamp: 123456789,
|
||||||
@ -249,7 +249,7 @@ func mockAddingConnectedUser(m mocks) {
|
|||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
// Mock of users.FinishLogin.
|
// Mock of users.FinishLogin.
|
||||||
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
m.pmapiClient.EXPECT().AuthSalt(gomock.Any()).Return("", nil),
|
||||||
m.pmapiClient.EXPECT().Unlock(gomock.Any(), []byte(testCredentials.MailboxPassword)).Return(nil),
|
m.pmapiClient.EXPECT().Unlock(gomock.Any(), testCredentials.MailboxPassword).Return(nil),
|
||||||
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil),
|
m.pmapiClient.EXPECT().CurrentUser(gomock.Any()).Return(testPMAPIUser, nil),
|
||||||
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
m.pmapiClient.EXPECT().Addresses().Return([]*pmapi.Address{testPMAPIAddress}),
|
||||||
m.credentialsStore.EXPECT().Add("user", "username", testAuthRefresh.UID, testAuthRefresh.RefreshToken, testCredentials.MailboxPassword, []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
m.credentialsStore.EXPECT().Add("user", "username", testAuthRefresh.UID, testAuthRefresh.RefreshToken, testCredentials.MailboxPassword, []string{testPMAPIAddress.Email}).Return(testCredentials, nil),
|
||||||
|
|||||||
@ -59,6 +59,10 @@ func (v *Version) Equal(version *semver.Version) bool {
|
|||||||
return v.version.Equal(version)
|
return v.version.Equal(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Version) SemVer() *semver.Version {
|
||||||
|
return v.version
|
||||||
|
}
|
||||||
|
|
||||||
// VerifyFiles verifies all files in the version directory.
|
// VerifyFiles verifies all files in the version directory.
|
||||||
func (v *Version) VerifyFiles(kr *crypto.KeyRing) error {
|
func (v *Version) VerifyFiles(kr *crypto.KeyRing) error {
|
||||||
fileBytes, err := ioutil.ReadFile(filepath.Join(v.path, sumFile)) // nolint[gosec]
|
fileBytes, err := ioutil.ReadFile(filepath.Join(v.path, sumFile)) // nolint[gosec]
|
||||||
|
|||||||
@ -90,7 +90,7 @@ func (l *listener) Add(eventName string, channel chan<- string) {
|
|||||||
|
|
||||||
log := log.WithField("name", eventName).WithField("i", len(l.channels[eventName]))
|
log := log.WithField("name", eventName).WithField("i", len(l.channels[eventName]))
|
||||||
l.channels[eventName] = append(l.channels[eventName], channel)
|
l.channels[eventName] = append(l.channels[eventName], channel)
|
||||||
log.Debug("Added event listner")
|
log.Debug("Added event listener")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes an event listener.
|
// Remove removes an event listener.
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/text/encoding/htmlindex"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTestFetcher(
|
func newTestFetcher(
|
||||||
@ -66,6 +67,10 @@ func newTestMessage(
|
|||||||
arm, err := enc.GetArmored()
|
arm, err := enc.GetArmored()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return newRawTestMessage(messageID, addressID, mimeType, arm, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRawTestMessage(messageID, addressID, mimeType, body string, date time.Time) *pmapi.Message {
|
||||||
return &pmapi.Message{
|
return &pmapi.Message{
|
||||||
ID: messageID,
|
ID: messageID,
|
||||||
AddressID: addressID,
|
AddressID: addressID,
|
||||||
@ -74,7 +79,7 @@ func newTestMessage(
|
|||||||
"Content-Type": {mimeType},
|
"Content-Type": {mimeType},
|
||||||
"Date": {date.In(time.UTC).Format(time.RFC1123Z)},
|
"Date": {date.In(time.UTC).Format(time.RFC1123Z)},
|
||||||
},
|
},
|
||||||
Body: arm,
|
Body: body,
|
||||||
Time: date.Unix(),
|
Time: date.Unix(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,7 +138,7 @@ func section(t *testing.T, b []byte, section ...int) *testSection {
|
|||||||
bs, err := NewBodyStructure(bytes.NewReader(b))
|
bs, err := NewBodyStructure(bytes.NewReader(b))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
raw, err := bs.GetSection(bytes.NewReader(b), append([]int{}, section...))
|
raw, err := bs.GetSection(bytes.NewReader(b), section)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return &testSection{
|
return &testSection{
|
||||||
@ -298,6 +303,25 @@ func decryptsTo(kr *crypto.KeyRing, want string) decryptsToMatcher {
|
|||||||
return decryptsToMatcher{kr: kr, want: want}
|
return decryptsToMatcher{kr: kr, want: want}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type decodesToMatcher struct {
|
||||||
|
charset string
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (matcher decodesToMatcher) match(t *testing.T, have string) {
|
||||||
|
enc, err := htmlindex.Get(matcher.charset)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dec, err := enc.NewDecoder().String(have)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, matcher.want, dec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodesTo(charset string, want string) decodesToMatcher {
|
||||||
|
return decodesToMatcher{charset: charset, want: want}
|
||||||
|
}
|
||||||
|
|
||||||
type verifiesAgainstMatcher struct {
|
type verifiesAgainstMatcher struct {
|
||||||
kr *crypto.KeyRing
|
kr *crypto.KeyRing
|
||||||
sig *crypto.PGPSignature
|
sig *crypto.PGPSignature
|
||||||
|
|||||||
@ -20,7 +20,6 @@ package message
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io/ioutil"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
@ -31,6 +30,7 @@ import (
|
|||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/emersion/go-message"
|
"github.com/emersion/go-message"
|
||||||
|
"github.com/emersion/go-message/textproto"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ func buildRFC822(kr *crypto.KeyRing, msg *pmapi.Message, attData [][]byte, opts
|
|||||||
return buildMultipartRFC822(kr, msg, attData, opts)
|
return buildMultipartRFC822(kr, msg, attData, opts)
|
||||||
|
|
||||||
case msg.MIMEType == "multipart/mixed":
|
case msg.MIMEType == "multipart/mixed":
|
||||||
return buildExternallyEncryptedRFC822(kr, msg, opts)
|
return buildPGPRFC822(kr, msg, opts)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return buildSimpleRFC822(kr, msg, opts)
|
return buildSimpleRFC822(kr, msg, opts)
|
||||||
@ -212,49 +212,31 @@ func writeRelatedParts(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildExternallyEncryptedRFC822(kr *crypto.KeyRing, msg *pmapi.Message, opts JobOptions) ([]byte, error) {
|
func buildPGPRFC822(kr *crypto.KeyRing, msg *pmapi.Message, opts JobOptions) ([]byte, error) {
|
||||||
dec, err := msg.Decrypt(kr)
|
dec, err := msg.Decrypt(kr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !opts.IgnoreDecryptionErrors {
|
if !opts.IgnoreDecryptionErrors {
|
||||||
return nil, errors.Wrap(ErrDecryptionFailed, err.Error())
|
return nil, errors.Wrap(ErrDecryptionFailed, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildPGPMIMERFC822(msg, opts)
|
return buildPGPMIMEFallbackRFC822(msg, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr := getMessageHeader(msg, opts)
|
hdr := getMessageHeader(msg, opts)
|
||||||
|
|
||||||
hdr.SetContentType("multipart/mixed", map[string]string{"boundary": newBoundary(msg.ID).gen()})
|
sigs, err := msg.ExtractSignatures(kr)
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
w, err := message.CreateWriter(buf, hdr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
log.WithError(err).WithField("id", msg.ID).Warn("Extract signature failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
ent, err := message.Read(bytes.NewReader(dec))
|
if len(sigs) > 0 {
|
||||||
if err != nil {
|
return writeMultipartSignedRFC822(hdr, dec, sigs[0])
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(ent.Body)
|
return writeMultipartEncryptedRFC822(hdr, dec)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := writePart(w, ent.Header, body); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPGPMIMERFC822(msg *pmapi.Message, opts JobOptions) ([]byte, error) {
|
func buildPGPMIMEFallbackRFC822(msg *pmapi.Message, opts JobOptions) ([]byte, error) {
|
||||||
hdr := getMessageHeader(msg, opts)
|
hdr := getMessageHeader(msg, opts)
|
||||||
|
|
||||||
hdr.SetContentType("multipart/encrypted", map[string]string{
|
hdr.SetContentType("multipart/encrypted", map[string]string{
|
||||||
@ -295,6 +277,99 @@ func buildPGPMIMERFC822(msg *pmapi.Message, opts JobOptions) ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Signature) ([]byte, error) { //nolint[funlen]
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
boundary := newBoundary("").gen()
|
||||||
|
|
||||||
|
header.SetContentType("multipart/signed", map[string]string{
|
||||||
|
"micalg": sig.Hash,
|
||||||
|
"protocol": "application/pgp-signature",
|
||||||
|
"boundary": boundary,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := textproto.WriteHeader(buf, header.Header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mw := textproto.NewMultipartWriter(buf)
|
||||||
|
|
||||||
|
if err := mw.SetBoundary(boundary); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyHeader, bodyData, err := readHeaderBody(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyPart, err := mw.CreatePart(*bodyHeader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := bodyPart.Write(bodyData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var sigHeader message.Header
|
||||||
|
|
||||||
|
sigHeader.SetContentType("application/pgp-signature", map[string]string{"name": "OpenPGP_signature.asc"})
|
||||||
|
sigHeader.SetContentDisposition("attachment", map[string]string{"filename": "OpenPGP_signature"})
|
||||||
|
sigHeader.Set("Content-Description", "OpenPGP digital signature")
|
||||||
|
|
||||||
|
sigPart, err := mw.CreatePart(sigHeader.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigData, err := crypto.NewPGPSignature(sig.Data).GetArmored()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := sigPart.Write([]byte(sigData)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mw.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMultipartEncryptedRFC822(header message.Header, body []byte) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
bodyHeader, bodyData, err := readHeaderBody(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If parsed header is empty then either it is malformed or it is missing.
|
||||||
|
// Anyway message could not be considered multipart/mixed anymore since there will be no boundary.
|
||||||
|
if bodyHeader.Len() == 0 {
|
||||||
|
header.Del("Content-Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
entFields := bodyHeader.Fields()
|
||||||
|
|
||||||
|
for entFields.Next() {
|
||||||
|
header.Set(entFields.Key(), entFields.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := textproto.WriteHeader(buf, header.Header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := buf.Write(bodyData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func getMessageHeader(msg *pmapi.Message, opts JobOptions) message.Header { // nolint[funlen]
|
func getMessageHeader(msg *pmapi.Message, opts JobOptions) message.Header { // nolint[funlen]
|
||||||
hdr := toMessageHeader(msg.Header)
|
hdr := toMessageHeader(msg.Header)
|
||||||
|
|
||||||
@ -411,7 +486,7 @@ func getAttachmentPartHeader(att *pmapi.Attachment) message.Header {
|
|||||||
hdr.SetContentDisposition(att.Disposition, map[string]string{"filename": mime.QEncoding.Encode("utf-8", att.Name)})
|
hdr.SetContentDisposition(att.Disposition, map[string]string{"filename": mime.QEncoding.Encode("utf-8", att.Name)})
|
||||||
|
|
||||||
// Use base64 for all attachments except embedded RFC822 messages.
|
// Use base64 for all attachments except embedded RFC822 messages.
|
||||||
if att.MIMEType != "message/rfc822" {
|
if att.MIMEType != rfc822Message {
|
||||||
hdr.Set("Content-Transfer-Encoding", "base64")
|
hdr.Set("Content-Transfer-Encoding", "base64")
|
||||||
} else {
|
} else {
|
||||||
hdr.Del("Content-Transfer-Encoding")
|
hdr.Del("Content-Transfer-Encoding")
|
||||||
@ -425,7 +500,10 @@ func toMessageHeader(hdr mail.Header) message.Header {
|
|||||||
|
|
||||||
for key, val := range hdr {
|
for key, val := range hdr {
|
||||||
for _, val := range val {
|
for _, val := range val {
|
||||||
res.Add(key, val)
|
// Using AddRaw instead of Add to save key-value pair as byte buffer within Header.
|
||||||
|
// This buffer is used latter on in message writer to construct message and avoid crash
|
||||||
|
// when key length is more than 76 characters long.
|
||||||
|
res.AddRaw([]byte(key + ": " + val + "\r\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/message/mocks"
|
"github.com/ProtonMail/proton-bridge/pkg/message/mocks"
|
||||||
tests "github.com/ProtonMail/proton-bridge/test"
|
tests "github.com/ProtonMail/proton-bridge/test"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
@ -51,6 +52,27 @@ func TestBuildPlainMessage(t *testing.T) {
|
|||||||
expectTransferEncoding(is(`quoted-printable`))
|
expectTransferEncoding(is(`quoted-printable`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainMessageWithLongKey(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now())
|
||||||
|
msg.Header["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"}
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(is(`body`)).
|
||||||
|
expectTransferEncoding(is(`quoted-printable`)).
|
||||||
|
expectHeader(`ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue`, is(`value`))
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildHTMLMessage(t *testing.T) {
|
func TestBuildHTMLMessage(t *testing.T) {
|
||||||
m := gomock.NewController(t)
|
m := gomock.NewController(t)
|
||||||
defer m.Finish()
|
defer m.Finish()
|
||||||
@ -87,20 +109,162 @@ func TestBuildPlainEncryptedMessage(t *testing.T) {
|
|||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/mixed`)).
|
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`plain no pubkey no sign`)).
|
expectHeader(`Subject`, is(`plain no pubkey no sign`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`text/plain`)).
|
expectContentType(is(`text/plain`)).
|
||||||
expectBody(contains(`Where do fruits go on vacation? Pear-is!`))
|
expectBody(contains(`Where do fruits go on vacation? Pear-is!`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainEncryptedMessageMissingHeader(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("plaintext-missing-header.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now())
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(is("How do we know that the ocean is friendly? It waves!\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainEncryptedMessageInvalidHeader(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("plaintext-invalid-header.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now())
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(is("MalformedKey Value\r\n\r\nHow do we know that the ocean is friendly? It waves!\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainSignedEncryptedMessageMissingHeader(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("plaintext-missing-header.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
sig := tests.MakeKeyRing(t)
|
||||||
|
|
||||||
|
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
arm, err := enc.GetArmored()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`multipart/signed`)).
|
||||||
|
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
|
||||||
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
|
||||||
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
||||||
|
|
||||||
|
section(t, res, 1).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(is("How do we know that the ocean is friendly? It waves!\r\n"))
|
||||||
|
|
||||||
|
section(t, res, 2).
|
||||||
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
|
expectContentDisposition(is(`attachment`)).
|
||||||
|
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainSignedEncryptedMessageInvalidHeader(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("plaintext-invalid-header.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
sig := tests.MakeKeyRing(t)
|
||||||
|
|
||||||
|
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
arm, err := enc.GetArmored()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`multipart/signed`)).
|
||||||
|
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
|
||||||
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
|
||||||
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
||||||
|
|
||||||
|
section(t, res, 1).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(is("MalformedKey Value\r\n\r\nHow do we know that the ocean is friendly? It waves!\r\n"))
|
||||||
|
|
||||||
|
section(t, res, 2).
|
||||||
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
|
expectContentDisposition(is(`attachment`)).
|
||||||
|
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainEncryptedLatin2Message(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("pgp-mime-body-plaintext-latin2.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectContentTypeParam("charset", is(`iso-8859-2`)).
|
||||||
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
|
expectHeader(`Subject`, is(`plain no pubkey no sign`)).
|
||||||
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
|
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||||
|
expectBody(decodesTo("iso-8859-2", "řšřšřš\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildHTMLEncryptedMessage(t *testing.T) {
|
func TestBuildHTMLEncryptedMessage(t *testing.T) {
|
||||||
m := gomock.NewController(t)
|
m := gomock.NewController(t)
|
||||||
defer m.Finish()
|
defer m.Finish()
|
||||||
@ -118,21 +282,101 @@ func TestBuildHTMLEncryptedMessage(t *testing.T) {
|
|||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/mixed`)).
|
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`html no pubkey no sign`)).
|
expectHeader(`Subject`, is(`html no pubkey no sign`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`text/html`)).
|
expectContentType(is(`text/html`)).
|
||||||
expectBody(contains(`What do you call a poor Santa Claus`)).
|
expectBody(contains(`What do you call a poor Santa Claus`)).
|
||||||
expectBody(contains(`Where do boats go when they're sick`))
|
expectBody(contains(`Where do boats go when they're sick`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainSignedMessage(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("text_plain.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
sig := tests.MakeKeyRing(t)
|
||||||
|
|
||||||
|
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
arm, err := enc.GetArmored()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`multipart/signed`)).
|
||||||
|
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
|
||||||
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
|
||||||
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
||||||
|
|
||||||
|
section(t, res, 1).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(is(`body`)).
|
||||||
|
expectSection(verifiesAgainst(sig, section(t, res, 2).signature()))
|
||||||
|
|
||||||
|
section(t, res, 2).
|
||||||
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
|
expectContentDisposition(is(`attachment`)).
|
||||||
|
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPlainSignedBase64Message(t *testing.T) {
|
||||||
|
m := gomock.NewController(t)
|
||||||
|
defer m.Finish()
|
||||||
|
|
||||||
|
b := NewBuilder(1, 1, 1)
|
||||||
|
defer b.Done()
|
||||||
|
|
||||||
|
body := readerToString(getFileReader("text_plain_base64.eml"))
|
||||||
|
|
||||||
|
kr := tests.MakeKeyRing(t)
|
||||||
|
sig := tests.MakeKeyRing(t)
|
||||||
|
|
||||||
|
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
arm, err := enc.GetArmored()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
|
||||||
|
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, res).
|
||||||
|
expectContentType(is(`multipart/signed`)).
|
||||||
|
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
|
||||||
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
|
||||||
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
||||||
|
|
||||||
|
section(t, res, 1).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectTransferEncoding(is(`base64`)).
|
||||||
|
expectBody(is(`body`)).
|
||||||
|
expectSection(verifiesAgainst(sig, section(t, res, 2).signature()))
|
||||||
|
|
||||||
|
section(t, res, 2).
|
||||||
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
|
expectContentDisposition(is(`attachment`)).
|
||||||
|
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildSignedPlainEncryptedMessage(t *testing.T) {
|
func TestBuildSignedPlainEncryptedMessage(t *testing.T) {
|
||||||
m := gomock.NewController(t)
|
m := gomock.NewController(t)
|
||||||
defer m.Finish()
|
defer m.Finish()
|
||||||
@ -149,27 +393,24 @@ func TestBuildSignedPlainEncryptedMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/signed`)).
|
expectContentType(is(`multipart/signed`)).
|
||||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`plain body no pubkey`)).
|
expectHeader(`Subject`, is(`plain body no pubkey`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1).
|
section(t, res, 1, 1).
|
||||||
expectContentType(is(`text/plain`)).
|
expectContentType(is(`text/plain`)).
|
||||||
expectBody(contains(`Why do seagulls fly over the ocean`)).
|
expectBody(contains(`Why do seagulls fly over the ocean`)).
|
||||||
expectBody(contains(`Because if they flew over the bay, we'd call them bagels`))
|
expectBody(contains(`Because if they flew over the bay, we'd call them bagels`))
|
||||||
|
|
||||||
section(t, res, 1, 2).
|
section(t, res, 2).
|
||||||
expectContentType(is(`application/pgp-signature`)).
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
@ -192,29 +433,26 @@ func TestBuildSignedHTMLEncryptedMessage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/signed`)).
|
expectContentType(is(`multipart/signed`)).
|
||||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`html body no pubkey`)).
|
expectHeader(`Subject`, is(`html body no pubkey`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
expectHeader(`To`, is(`schizofrenic@pm.me`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1).
|
section(t, res, 1, 1).
|
||||||
expectContentType(is(`text/html`)).
|
expectContentType(is(`text/html`)).
|
||||||
expectBody(contains(`Behold another <font color="#ee24cc">HTML</font>`)).
|
expectBody(contains(`Behold another <font color="#ee24cc">HTML</font>`)).
|
||||||
expectBody(contains(`I only know 25 letters of the alphabet`)).
|
expectBody(contains(`I only know 25 letters of the alphabet`)).
|
||||||
expectBody(contains(`What did one wall say to the other`)).
|
expectBody(contains(`What did one wall say to the other`)).
|
||||||
expectBody(contains(`What did the zero say to the eight`))
|
expectBody(contains(`What did the zero say to the eight`))
|
||||||
|
|
||||||
section(t, res, 1, 2).
|
section(t, res, 2).
|
||||||
expectContentType(is(`application/pgp-signature`)).
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
@ -237,36 +475,33 @@ func TestBuildSignedPlainEncryptedMessageWithPubKey(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/signed`)).
|
expectContentType(is(`multipart/signed`)).
|
||||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`simple plaintext body`)).
|
expectHeader(`Subject`, is(`simple plaintext body`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 1, 2).pubKey(), section(t, res, 1, 2).signature()))
|
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1).
|
section(t, res, 1, 1).
|
||||||
expectContentType(is(`multipart/mixed`))
|
expectContentType(is(`multipart/mixed`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 1).
|
section(t, res, 1, 1, 1).
|
||||||
expectContentType(is(`text/plain`)).
|
expectContentType(is(`text/plain`)).
|
||||||
expectBody(contains(`Why don't crabs give to charity? Because they're shellfish.`))
|
expectBody(contains(`Why don't crabs give to charity? Because they're shellfish.`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 2).
|
section(t, res, 1, 1, 2).
|
||||||
expectContentType(is(`application/pgp-keys`)).
|
expectContentType(is(`application/pgp-keys`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
||||||
|
|
||||||
section(t, res, 1, 2).
|
section(t, res, 2).
|
||||||
expectContentType(is(`application/pgp-signature`)).
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
@ -289,37 +524,34 @@ func TestBuildSignedHTMLEncryptedMessageWithPubKey(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/signed`)).
|
expectContentType(is(`multipart/signed`)).
|
||||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`simple html body`)).
|
expectHeader(`Subject`, is(`simple html body`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 1, 2).pubKey(), section(t, res, 1, 2).signature()))
|
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1).
|
section(t, res, 1, 1).
|
||||||
expectContentType(is(`multipart/mixed`))
|
expectContentType(is(`multipart/mixed`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 1).
|
section(t, res, 1, 1, 1).
|
||||||
expectContentType(is(`text/html`)).
|
expectContentType(is(`text/html`)).
|
||||||
expectBody(contains(`Do I enjoy making courthouse puns`)).
|
expectBody(contains(`Do I enjoy making courthouse puns`)).
|
||||||
expectBody(contains(`Can February March`))
|
expectBody(contains(`Can February March`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 2).
|
section(t, res, 1, 1, 2).
|
||||||
expectContentType(is(`application/pgp-keys`)).
|
expectContentType(is(`application/pgp-keys`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
||||||
|
|
||||||
section(t, res, 1, 2).
|
section(t, res, 2).
|
||||||
expectContentType(is(`application/pgp-signature`)).
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
@ -342,53 +574,50 @@ func TestBuildSignedMultipartAlternativeEncryptedMessageWithPubKey(t *testing.T)
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/signed`)).
|
expectContentType(is(`multipart/signed`)).
|
||||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`Alternative`)).
|
expectHeader(`Subject`, is(`Alternative`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 1, 3).pubKey(), section(t, res, 1, 2).signature()))
|
expectSection(verifiesAgainst(section(t, res, 1, 1, 3).pubKey(), section(t, res, 2).signature()))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1).
|
section(t, res, 1, 1).
|
||||||
expectContentType(is(`multipart/mixed`))
|
expectContentType(is(`multipart/mixed`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 1).
|
section(t, res, 1, 1, 1).
|
||||||
expectContentType(is(`multipart/alternative`))
|
expectContentType(is(`multipart/alternative`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 1, 1).
|
section(t, res, 1, 1, 1, 1).
|
||||||
expectContentType(is(`text/plain`)).
|
expectContentType(is(`text/plain`)).
|
||||||
expectBody(contains(`This Rich formated text`)).
|
expectBody(contains(`This Rich formated text`)).
|
||||||
expectBody(contains(`What kind of shoes do ninjas wear`)).
|
expectBody(contains(`What kind of shoes do ninjas wear`)).
|
||||||
expectBody(contains(`How does a penguin build its house`))
|
expectBody(contains(`How does a penguin build its house`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 1, 2).
|
section(t, res, 1, 1, 1, 2).
|
||||||
expectContentType(is(`text/html`)).
|
expectContentType(is(`text/html`)).
|
||||||
expectBody(contains(`This <font color="#ee24cc">Rich</font> formated text`)).
|
expectBody(contains(`This <font color="#ee24cc">Rich</font> formated text`)).
|
||||||
expectBody(contains(`What kind of shoes do ninjas wear`)).
|
expectBody(contains(`What kind of shoes do ninjas wear`)).
|
||||||
expectBody(contains(`How does a penguin build its house`))
|
expectBody(contains(`How does a penguin build its house`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 2).
|
section(t, res, 1, 1, 2).
|
||||||
expectContentType(is(`application/pdf`)).
|
expectContentType(is(`application/pdf`)).
|
||||||
expectTransferEncoding(is(`base64`)).
|
expectTransferEncoding(is(`base64`)).
|
||||||
expectContentTypeParam(`name`, is(`minimal.pdf`)).
|
expectContentTypeParam(`name`, is(`minimal.pdf`)).
|
||||||
expectContentDispositionParam(`filename`, is(`minimal.pdf`))
|
expectContentDispositionParam(`filename`, is(`minimal.pdf`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 3).
|
section(t, res, 1, 1, 3).
|
||||||
expectContentType(is(`application/pgp-keys`)).
|
expectContentType(is(`application/pgp-keys`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
||||||
|
|
||||||
section(t, res, 1, 2).
|
section(t, res, 2).
|
||||||
expectContentType(is(`application/pgp-signature`)).
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
@ -411,41 +640,38 @@ func TestBuildSignedEmbeddedMessageRFC822EncryptedMessageWithPubKey(t *testing.T
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
section(t, res).
|
section(t, res).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
|
||||||
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
|
|
||||||
|
|
||||||
section(t, res, 1).
|
|
||||||
expectContentType(is(`multipart/signed`)).
|
expectContentType(is(`multipart/signed`)).
|
||||||
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
expectContentTypeParam(`micalg`, is(`pgp-sha256`)).
|
||||||
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
expectContentTypeParam(`protocol`, is(`application/pgp-signature`))
|
||||||
|
|
||||||
section(t, res, 1, 1).
|
section(t, res, 1).
|
||||||
expectContentType(is(`multipart/mixed`)).
|
expectContentType(is(`multipart/mixed`)).
|
||||||
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
expectContentTypeParam(`protected-headers`, is(`v1`)).
|
||||||
expectHeader(`Subject`, is(`Fwd: HTML with attachment external PGP`)).
|
expectHeader(`Subject`, is(`Fwd: HTML with attachment external PGP`)).
|
||||||
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
|
||||||
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
expectHeader(`To`, is(`schizofrenic@pm.me`)).
|
||||||
expectSection(verifiesAgainst(section(t, res, 1, 1, 1, 2).pubKey(), section(t, res, 1, 2).signature()))
|
expectSection(verifiesAgainst(section(t, res, 1, 1, 2).pubKey(), section(t, res, 2).signature()))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1).
|
section(t, res, 1, 1).
|
||||||
expectContentType(is(`multipart/mixed`))
|
expectContentType(is(`multipart/mixed`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 1).
|
section(t, res, 1, 1, 1).
|
||||||
expectContentType(is(`text/plain`))
|
expectContentType(is(`text/plain`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 2).
|
section(t, res, 1, 1, 2).
|
||||||
expectContentType(is(`application/pgp-keys`)).
|
expectContentType(is(`application/pgp-keys`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_0x161C0875822359F7.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
expectContentDispositionParam(`filename`, is(`OpenPGP_0x161C0875822359F7.asc`))
|
||||||
|
|
||||||
section(t, res, 1, 1, 1, 3).
|
section(t, res, 1, 1, 3).
|
||||||
expectContentType(is(`message/rfc822`)).
|
expectContentType(is(`message/rfc822`)).
|
||||||
expectContentTypeParam(`name`, is(`HTML with attachment external PGP.eml`)).
|
expectContentTypeParam(`name`, is(`HTML with attachment external PGP.eml`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
expectContentDispositionParam(`filename`, is(`HTML with attachment external PGP.eml`))
|
expectContentDispositionParam(`filename`, is(`HTML with attachment external PGP.eml`))
|
||||||
|
|
||||||
section(t, res, 1, 2).
|
section(t, res, 2).
|
||||||
expectContentType(is(`application/pgp-signature`)).
|
expectContentType(is(`application/pgp-signature`)).
|
||||||
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
|
||||||
expectContentDisposition(is(`attachment`)).
|
expectContentDisposition(is(`attachment`)).
|
||||||
|
|||||||
246
pkg/message/encrypt.go
Normal file
246
pkg/message/encrypt.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"mime/quotedprintable"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
pmmime "github.com/ProtonMail/proton-bridge/pkg/mime"
|
||||||
|
"github.com/emersion/go-message/textproto"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EncryptRFC822(kr *crypto.KeyRing, r io.Reader) ([]byte, error) {
|
||||||
|
b, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
header, body, err := readHeaderBody(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
result, err := writeEncryptedPart(kr, header, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := textproto.WriteHeader(buf, *header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := result.WriteTo(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEncryptedPart(kr *crypto.KeyRing, header *textproto.Header, r io.Reader) (io.WriterTo, error) {
|
||||||
|
decoder := getTransferDecoder(r, header.Get("Content-Transfer-Encoding"))
|
||||||
|
encoded := new(bytes.Buffer)
|
||||||
|
|
||||||
|
contentType, contentParams, err := parseContentType(header.Get("Content-Type"))
|
||||||
|
// Ignoring invalid media parameter makes it work for invalid tutanota RFC2047-encoded attachment filenames since we often only really need the content type and not the optional media parameters.
|
||||||
|
if err != nil && !errors.Is(err, mime.ErrInvalidMediaParameter) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case contentType == "", strings.HasPrefix(contentType, "text/"), strings.HasPrefix(contentType, "message/"):
|
||||||
|
header.Del("Content-Transfer-Encoding")
|
||||||
|
|
||||||
|
if charset, ok := contentParams["charset"]; ok {
|
||||||
|
if reader, err := pmmime.CharsetReader(charset, decoder); err == nil {
|
||||||
|
decoder = reader
|
||||||
|
|
||||||
|
// We can decode the charset to utf-8 so let's set that as the content type charset parameter.
|
||||||
|
contentParams["charset"] = "utf-8"
|
||||||
|
|
||||||
|
header.Set("Content-Type", mime.FormatMediaType(contentType, contentParams))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := encode(&writeCloser{encoded}, func(w io.Writer) error {
|
||||||
|
return writeEncryptedTextPart(w, decoder, kr)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case contentType == "multipart/encrypted":
|
||||||
|
if _, err := encoded.ReadFrom(decoder); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case strings.HasPrefix(contentType, "multipart/"):
|
||||||
|
if err := encode(&writeCloser{encoded}, func(w io.Writer) error {
|
||||||
|
return writeEncryptedMultiPart(kr, w, header, decoder)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
header.Set("Content-Transfer-Encoding", "base64")
|
||||||
|
|
||||||
|
if err := encode(base64.NewEncoder(base64.StdEncoding, encoded), func(w io.Writer) error {
|
||||||
|
return writeEncryptedAttachmentPart(w, decoder, kr)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEncryptedTextPart(w io.Writer, r io.Reader, kr *crypto.KeyRing) error {
|
||||||
|
dec, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var arm string
|
||||||
|
|
||||||
|
if msg, err := crypto.NewPGPMessageFromArmored(string(dec)); err != nil {
|
||||||
|
enc, err := kr.Encrypt(crypto.NewPlainMessage(dec), kr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if arm, err = enc.GetArmored(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if arm, err = msg.GetArmored(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.WriteString(w, arm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEncryptedAttachmentPart(w io.Writer, r io.Reader, kr *crypto.KeyRing) error {
|
||||||
|
dec, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := kr.Encrypt(crypto.NewPlainMessage(dec), kr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := w.Write(enc.GetBinary()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEncryptedMultiPart(kr *crypto.KeyRing, w io.Writer, header *textproto.Header, r io.Reader) error {
|
||||||
|
_, contentParams, err := parseContentType(header.Get("Content-Type"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner, err := newPartScanner(r, contentParams["boundary"])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parts, err := scanner.scanAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := newPartWriter(w, contentParams["boundary"])
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
header, body, err := readHeaderBody(part.b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := writeEncryptedPart(kr, header, bytes.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writer.createPart(func(w io.Writer) error {
|
||||||
|
if err := textproto.WriteHeader(w, *header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := result.WriteTo(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writer.done()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTransferDecoder(r io.Reader, encoding string) io.Reader {
|
||||||
|
switch strings.ToLower(encoding) {
|
||||||
|
case "base64":
|
||||||
|
return base64.NewDecoder(base64.StdEncoding, r)
|
||||||
|
|
||||||
|
case "quoted-printable":
|
||||||
|
return quotedprintable.NewReader(r)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(wc io.WriteCloser, fn func(io.Writer) error) error {
|
||||||
|
if err := fn(wc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return wc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeCloser struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (writeCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
func parseContentType(val string) (string, map[string]string, error) {
|
||||||
|
if val == "" {
|
||||||
|
val = "text/plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
return pmmime.ParseMediaType(val)
|
||||||
|
}
|
||||||
101
pkg/message/encrypt_test.go
Normal file
101
pkg/message/encrypt_test.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncryptRFC822(t *testing.T) {
|
||||||
|
literal, err := ioutil.ReadFile("testdata/text_plain_latin1.eml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key, err := crypto.GenerateKey("name", "email", "rsa", 2048)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
kr, err := crypto.NewKeyRing(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
enc, err := EncryptRFC822(kr, bytes.NewReader(literal))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, enc).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectContentTypeParam(`charset`, is(`utf-8`)).
|
||||||
|
expectBody(decryptsTo(kr, `ééééééé`))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncryptRFC822Multipart(t *testing.T) {
|
||||||
|
literal, err := ioutil.ReadFile("testdata/multipart_alternative_nested.eml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
key, err := crypto.GenerateKey("name", "email", "rsa", 2048)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
kr, err := crypto.NewKeyRing(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
enc, err := EncryptRFC822(kr, bytes.NewReader(literal))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
section(t, enc).
|
||||||
|
expectContentType(is(`multipart/alternative`))
|
||||||
|
|
||||||
|
section(t, enc, 1).
|
||||||
|
expectContentType(is(`multipart/alternative`))
|
||||||
|
|
||||||
|
section(t, enc, 1, 1).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(decryptsTo(kr, "*multipart 1.1*\n\n"))
|
||||||
|
|
||||||
|
section(t, enc, 1, 2).
|
||||||
|
expectContentType(is(`text/html`)).
|
||||||
|
expectBody(decryptsTo(kr, `<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>multipart 1.2</b>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
|
||||||
|
section(t, enc, 2).
|
||||||
|
expectContentType(is(`multipart/alternative`))
|
||||||
|
|
||||||
|
section(t, enc, 2, 1).
|
||||||
|
expectContentType(is(`text/plain`)).
|
||||||
|
expectBody(decryptsTo(kr, "*multipart 2.1*\n\n"))
|
||||||
|
|
||||||
|
section(t, enc, 2, 2).
|
||||||
|
expectContentType(is(`text/html`)).
|
||||||
|
expectBody(decryptsTo(kr, `<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<b>multipart 2.2</b>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`))
|
||||||
|
}
|
||||||
@ -59,30 +59,3 @@ func GetFlags(m *pmapi.Message) (flags []string) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFlags sets attributes to pmapi messages based on imap flags.
|
|
||||||
func ParseFlags(m *pmapi.Message, flags []string) {
|
|
||||||
if m.Header.Get("received") == "" {
|
|
||||||
m.Flags = pmapi.FlagSent
|
|
||||||
} else {
|
|
||||||
m.Flags = pmapi.FlagReceived
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Unread = true
|
|
||||||
for _, f := range flags {
|
|
||||||
switch f {
|
|
||||||
case imap.SeenFlag:
|
|
||||||
m.Unread = false
|
|
||||||
case imap.DraftFlag:
|
|
||||||
m.Flags &= ^pmapi.FlagSent
|
|
||||||
m.Flags &= ^pmapi.FlagReceived
|
|
||||||
m.LabelIDs = append(m.LabelIDs, pmapi.DraftLabel)
|
|
||||||
case imap.FlaggedFlag:
|
|
||||||
m.LabelIDs = append(m.LabelIDs, pmapi.StarredLabel)
|
|
||||||
case imap.AnsweredFlag:
|
|
||||||
m.Flags |= pmapi.FlagReplied
|
|
||||||
case AppleMailJunkFlag, ThunderbirdJunkFlag:
|
|
||||||
m.LabelIDs = append(m.LabelIDs, pmapi.SpamLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
150
pkg/message/header.go
Normal file
150
pkg/message/header.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/emersion/go-message/textproto"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderLines returns each line in the given header.
|
||||||
|
func HeaderLines(header []byte) [][]byte {
|
||||||
|
var (
|
||||||
|
lines [][]byte
|
||||||
|
quote int
|
||||||
|
)
|
||||||
|
|
||||||
|
forEachLine(bufio.NewReader(bytes.NewReader(header)), func(line []byte) {
|
||||||
|
l := bytes.SplitN(line, []byte(`: `), 2)
|
||||||
|
isLineContinuation := quote%2 != 0 || // no quotes opened
|
||||||
|
!bytes.Equal(bytes.TrimLeftFunc(l[0], unicode.IsSpace), l[0]) // has whitespace indent at beginning
|
||||||
|
switch {
|
||||||
|
case len(bytes.TrimSpace(line)) == 0:
|
||||||
|
lines = append(lines, line)
|
||||||
|
|
||||||
|
case isLineContinuation:
|
||||||
|
if len(lines) > 0 {
|
||||||
|
lines[len(lines)-1] = append(lines[len(lines)-1], line...)
|
||||||
|
} else {
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
quote += bytes.Count(line, []byte(`"`))
|
||||||
|
})
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func forEachLine(br *bufio.Reader, fn func([]byte)) {
|
||||||
|
for {
|
||||||
|
b, err := br.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) > 0 {
|
||||||
|
fn(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fn(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readHeaderBody(b []byte) (*textproto.Header, []byte, error) {
|
||||||
|
rawHeader, body, err := splitHeaderBody(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := HeaderLines(rawHeader)
|
||||||
|
|
||||||
|
var header textproto.Header
|
||||||
|
|
||||||
|
// We assume that everything before first occurrence of empty line is header.
|
||||||
|
// If header is invalid for any reason or empty - put everything as body and let header be empty.
|
||||||
|
if !isHeaderValid(lines) {
|
||||||
|
return &header, b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We add lines in reverse so that calling textproto.WriteHeader later writes with the correct order.
|
||||||
|
for i := len(lines) - 1; i >= 0; i-- {
|
||||||
|
if len(bytes.TrimSpace(lines[i])) > 0 {
|
||||||
|
header.AddRaw(lines[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &header, body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHeaderValid(headerLines [][]byte) bool {
|
||||||
|
if len(headerLines) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range headerLines {
|
||||||
|
if (bytes.IndexByte(line, ':') == -1) && (len(bytes.TrimSpace(line)) > 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitHeaderBody(b []byte) ([]byte, []byte, error) {
|
||||||
|
br := bufio.NewReader(bytes.NewReader(b))
|
||||||
|
|
||||||
|
var header []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
b, err := br.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
header = append(header, b...)
|
||||||
|
|
||||||
|
if len(bytes.TrimSpace(b)) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return header, body, nil
|
||||||
|
}
|
||||||
112
pkg/message/header_test.go
Normal file
112
pkg/message/header_test.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHeaderLines(t *testing.T) {
|
||||||
|
want := [][]byte{
|
||||||
|
[]byte("To: somebody\r\n"),
|
||||||
|
[]byte("From: somebody else\r\n"),
|
||||||
|
[]byte("Subject: RE: this is\r\n\ta multiline field: with colon\r\n\tor: many: more: colons\r\n"),
|
||||||
|
[]byte("X-Special: \r\n\tNothing on the first line\r\n\tbut has something on the other lines\r\n"),
|
||||||
|
[]byte("\r\n"),
|
||||||
|
}
|
||||||
|
var header []byte
|
||||||
|
for _, line := range want {
|
||||||
|
header = append(header, line...)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, want, HeaderLines(header))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderLinesMultilineFilename(t *testing.T) {
|
||||||
|
const header = "Content-Type: application/msword; name=\"this is a very long\nfilename.doc\""
|
||||||
|
|
||||||
|
assert.Equal(t, [][]byte{
|
||||||
|
[]byte("Content-Type: application/msword; name=\"this is a very long\nfilename.doc\""),
|
||||||
|
}, HeaderLines([]byte(header)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderLinesMultilineFilenameWithColon(t *testing.T) {
|
||||||
|
const header = "Content-Type: application/msword; name=\"this is a very long\nfilename: too long.doc\""
|
||||||
|
|
||||||
|
assert.Equal(t, [][]byte{
|
||||||
|
[]byte("Content-Type: application/msword; name=\"this is a very long\nfilename: too long.doc\""),
|
||||||
|
}, HeaderLines([]byte(header)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderLinesMultilineFilenameWithColonAndNewline(t *testing.T) {
|
||||||
|
const header = "Content-Type: application/msword; name=\"this is a very long\nfilename: too long.doc\"\n"
|
||||||
|
|
||||||
|
assert.Equal(t, [][]byte{
|
||||||
|
[]byte("Content-Type: application/msword; name=\"this is a very long\nfilename: too long.doc\"\n"),
|
||||||
|
}, HeaderLines([]byte(header)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderLinesMultipleMultilineFilenames(t *testing.T) {
|
||||||
|
const header = `Content-Type: application/msword; name="=E5=B8=B6=E6=9C=89=E5=A4=96=E5=9C=8B=E5=AD=97=E7=AC=A6=E7=9A=84=E9=99=84=E4=
|
||||||
|
=BB=B6.DOC"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment; filename="=E5=B8=B6=E6=9C=89=E5=A4=96=E5=9C=8B=E5=AD=97=E7=AC=A6=E7=9A=84=E9=99=84=E4=
|
||||||
|
=BB=B6.DOC"
|
||||||
|
Content-ID: <>
|
||||||
|
`
|
||||||
|
|
||||||
|
assert.Equal(t, [][]byte{
|
||||||
|
[]byte("Content-Type: application/msword; name=\"=E5=B8=B6=E6=9C=89=E5=A4=96=E5=9C=8B=E5=AD=97=E7=AC=A6=E7=9A=84=E9=99=84=E4=\n=BB=B6.DOC\"\n"),
|
||||||
|
[]byte("Content-Transfer-Encoding: base64\n"),
|
||||||
|
[]byte("Content-Disposition: attachment; filename=\"=E5=B8=B6=E6=9C=89=E5=A4=96=E5=9C=8B=E5=AD=97=E7=AC=A6=E7=9A=84=E9=99=84=E4=\n=BB=B6.DOC\"\n"),
|
||||||
|
[]byte("Content-ID: <>\n"),
|
||||||
|
}, HeaderLines([]byte(header)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadHeaderBody(t *testing.T) {
|
||||||
|
const data = "key: value\r\n\r\nbody\n"
|
||||||
|
|
||||||
|
header, body, err := readHeaderBody([]byte(data))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, header.Len())
|
||||||
|
assert.Equal(t, "value", header.Get("key"))
|
||||||
|
assert.Equal(t, []byte("body\n"), body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadHeaderBodyWithoutHeader(t *testing.T) {
|
||||||
|
const data = "body\n"
|
||||||
|
|
||||||
|
header, body, err := readHeaderBody([]byte(data))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, header.Len())
|
||||||
|
assert.Equal(t, []byte(data), body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadHeaderBodyInvalidHeader(t *testing.T) {
|
||||||
|
const data = "value\r\n\r\nbody\n"
|
||||||
|
|
||||||
|
header, body, err := readHeaderBody([]byte(data))
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 0, header.Len())
|
||||||
|
assert.Equal(t, []byte(data), body)
|
||||||
|
}
|
||||||
@ -14,30 +14,30 @@ import (
|
|||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockFetcher is a mock of Fetcher interface
|
// MockFetcher is a mock of Fetcher interface.
|
||||||
type MockFetcher struct {
|
type MockFetcher struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockFetcherMockRecorder
|
recorder *MockFetcherMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockFetcherMockRecorder is the mock recorder for MockFetcher
|
// MockFetcherMockRecorder is the mock recorder for MockFetcher.
|
||||||
type MockFetcherMockRecorder struct {
|
type MockFetcherMockRecorder struct {
|
||||||
mock *MockFetcher
|
mock *MockFetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockFetcher creates a new mock instance
|
// NewMockFetcher creates a new mock instance.
|
||||||
func NewMockFetcher(ctrl *gomock.Controller) *MockFetcher {
|
func NewMockFetcher(ctrl *gomock.Controller) *MockFetcher {
|
||||||
mock := &MockFetcher{ctrl: ctrl}
|
mock := &MockFetcher{ctrl: ctrl}
|
||||||
mock.recorder = &MockFetcherMockRecorder{mock}
|
mock.recorder = &MockFetcherMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockFetcher) EXPECT() *MockFetcherMockRecorder {
|
func (m *MockFetcher) EXPECT() *MockFetcherMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAttachment mocks base method
|
// GetAttachment mocks base method.
|
||||||
func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) {
|
func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1)
|
||||||
@ -46,13 +46,13 @@ func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadC
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAttachment indicates an expected call of GetAttachment
|
// GetAttachment indicates an expected call of GetAttachment.
|
||||||
func (mr *MockFetcherMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockFetcherMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockFetcher)(nil).GetAttachment), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockFetcher)(nil).GetAttachment), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage mocks base method
|
// GetMessage mocks base method.
|
||||||
func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) {
|
func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetMessage", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetMessage", arg0, arg1)
|
||||||
@ -61,13 +61,13 @@ func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Mess
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage indicates an expected call of GetMessage
|
// GetMessage indicates an expected call of GetMessage.
|
||||||
func (mr *MockFetcherMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockFetcherMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockFetcher)(nil).GetMessage), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockFetcher)(nil).GetMessage), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyRingForAddressID mocks base method
|
// KeyRingForAddressID mocks base method.
|
||||||
func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
|
func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
|
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
|
||||||
@ -76,7 +76,7 @@ func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error)
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID
|
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID.
|
||||||
func (mr *MockFetcherMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
|
func (mr *MockFetcherMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockFetcher)(nil).KeyRingForAddressID), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockFetcher)(nil).KeyRingForAddressID), arg0)
|
||||||
|
|||||||
@ -528,6 +528,9 @@ func parseAttachment(h message.Header) (*pmapi.Attachment, error) {
|
|||||||
if att.Name == "" {
|
if att.Name == "" {
|
||||||
att.Name = mimeTypeParams["name"]
|
att.Name = mimeTypeParams["name"]
|
||||||
}
|
}
|
||||||
|
if att.Name == "" && mimeType == rfc822Message {
|
||||||
|
att.Name = "message.eml"
|
||||||
|
}
|
||||||
if att.Name == "" {
|
if att.Name == "" {
|
||||||
att.Name = "attachment.bin"
|
att.Name = "attachment.bin"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -222,6 +222,22 @@ func TestParseTextPlainWithOctetAttachmentGoodFilename(t *testing.T) {
|
|||||||
assert.Equal(t, "😁😂.txt", m.Attachments[0].Name)
|
assert.Equal(t, "😁😂.txt", m.Attachments[0].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseTextPlainWithRFC822Attachment(t *testing.T) {
|
||||||
|
f := getFileReader("text_plain_rfc822_attachment.eml")
|
||||||
|
|
||||||
|
m, _, plainBody, attReaders, err := Parse(f)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, `"Sender" <sender@pm.me>`, m.Sender.String())
|
||||||
|
assert.Equal(t, `"Receiver" <receiver@pm.me>`, m.ToList[0].String())
|
||||||
|
|
||||||
|
assert.Equal(t, "body", m.Body)
|
||||||
|
assert.Equal(t, "body", plainBody)
|
||||||
|
|
||||||
|
assert.Len(t, attReaders, 1)
|
||||||
|
assert.Equal(t, "message.eml", m.Attachments[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseTextPlainWithOctetAttachmentBadFilename(t *testing.T) {
|
func TestParseTextPlainWithOctetAttachmentBadFilename(t *testing.T) {
|
||||||
f := getFileReader("text_plain_octet_attachment_bad_2231_filename.eml")
|
f := getFileReader("text_plain_octet_attachment_bad_2231_filename.eml")
|
||||||
|
|
||||||
|
|||||||
96
pkg/message/scanner.go
Normal file
96
pkg/message/scanner.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type partScanner struct {
|
||||||
|
r *bufio.Reader
|
||||||
|
|
||||||
|
boundary string
|
||||||
|
progress int
|
||||||
|
}
|
||||||
|
|
||||||
|
type part struct {
|
||||||
|
b []byte
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPartScanner(r io.Reader, boundary string) (*partScanner, error) {
|
||||||
|
scanner := &partScanner{r: bufio.NewReader(r), boundary: boundary}
|
||||||
|
|
||||||
|
if _, _, err := scanner.readToBoundary(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *partScanner) scanAll() ([]part, error) {
|
||||||
|
var parts []part
|
||||||
|
|
||||||
|
for {
|
||||||
|
offset := s.progress
|
||||||
|
|
||||||
|
b, more, err := s.readToBoundary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !more {
|
||||||
|
return parts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts = append(parts, part{b: b, offset: offset})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *partScanner) readToBoundary() ([]byte, bool, error) {
|
||||||
|
var res []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := s.r.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.progress += len(line)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(bytes.TrimSpace(line), []byte("--"+s.boundary)):
|
||||||
|
return bytes.TrimSuffix(bytes.TrimSuffix(res, []byte("\n")), []byte("\r")), true, nil
|
||||||
|
|
||||||
|
case bytes.HasSuffix(bytes.TrimSpace(line), []byte(s.boundary+"--")):
|
||||||
|
return bytes.TrimSuffix(bytes.TrimSuffix(res, []byte("\n")), []byte("\r")), false, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
res = append(res, line...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
pkg/message/scanner_test.go
Normal file
136
pkg/message/scanner_test.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScanner(t *testing.T) {
|
||||||
|
const literal = `this part of the text should be ignored
|
||||||
|
|
||||||
|
--longrandomstring
|
||||||
|
|
||||||
|
body1
|
||||||
|
|
||||||
|
--longrandomstring
|
||||||
|
|
||||||
|
body2
|
||||||
|
|
||||||
|
--longrandomstring--
|
||||||
|
`
|
||||||
|
|
||||||
|
scanner, err := newPartScanner(strings.NewReader(literal), "longrandomstring")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
parts, err := scanner.scanAll()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "\nbody1\n", string(parts[0].b))
|
||||||
|
assert.Equal(t, "\nbody2\n", string(parts[1].b))
|
||||||
|
|
||||||
|
assert.Equal(t, "\nbody1\n", literal[parts[0].offset:parts[0].offset+len(parts[0].b)])
|
||||||
|
assert.Equal(t, "\nbody2\n", literal[parts[1].offset:parts[1].offset+len(parts[1].b)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScannerNested(t *testing.T) {
|
||||||
|
const literal = `This is the preamble. It is to be ignored, though it
|
||||||
|
is a handy place for mail composers to include an
|
||||||
|
explanatory note to non-MIME compliant readers.
|
||||||
|
--simple boundary
|
||||||
|
Content-type: multipart/mixed; boundary="nested boundary"
|
||||||
|
|
||||||
|
This is the preamble. It is to be ignored, though it
|
||||||
|
is a handy place for mail composers to include an
|
||||||
|
explanatory note to non-MIME compliant readers.
|
||||||
|
--nested boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does not end with a linebreak.
|
||||||
|
--nested boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does end with a linebreak.
|
||||||
|
|
||||||
|
--nested boundary--
|
||||||
|
--simple boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does end with a linebreak.
|
||||||
|
|
||||||
|
--simple boundary--
|
||||||
|
This is the epilogue. It is also to be ignored.
|
||||||
|
`
|
||||||
|
|
||||||
|
scanner, err := newPartScanner(strings.NewReader(literal), "simple boundary")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
parts, err := scanner.scanAll()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, `Content-type: multipart/mixed; boundary="nested boundary"
|
||||||
|
|
||||||
|
This is the preamble. It is to be ignored, though it
|
||||||
|
is a handy place for mail composers to include an
|
||||||
|
explanatory note to non-MIME compliant readers.
|
||||||
|
--nested boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does not end with a linebreak.
|
||||||
|
--nested boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does end with a linebreak.
|
||||||
|
|
||||||
|
--nested boundary--`, string(parts[0].b))
|
||||||
|
assert.Equal(t, `Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does end with a linebreak.
|
||||||
|
`, string(parts[1].b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScannerNoFinalLinebreak(t *testing.T) {
|
||||||
|
const literal = `--nested boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does not end with a linebreak.
|
||||||
|
--nested boundary
|
||||||
|
Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does end with a linebreak.
|
||||||
|
|
||||||
|
--nested boundary--`
|
||||||
|
|
||||||
|
scanner, err := newPartScanner(strings.NewReader(literal), "nested boundary")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
parts, err := scanner.scanAll()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, `Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does not end with a linebreak.`, string(parts[0].b))
|
||||||
|
assert.Equal(t, `Content-type: text/plain; charset=us-ascii
|
||||||
|
|
||||||
|
This part does end with a linebreak.
|
||||||
|
`, string(parts[1].b))
|
||||||
|
}
|
||||||
@ -103,8 +103,8 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
|
|||||||
mediaType, params, _ := pmmime.ParseMediaType(info.Header.Get("Content-Type"))
|
mediaType, params, _ := pmmime.ParseMediaType(info.Header.Get("Content-Type"))
|
||||||
|
|
||||||
// If multipart, call getAllParts, else read to count lines.
|
// If multipart, call getAllParts, else read to count lines.
|
||||||
if (strings.HasPrefix(mediaType, "multipart/") || mediaType == "message/rfc822") && params["boundary"] != "" {
|
if (strings.HasPrefix(mediaType, "multipart/") || mediaType == rfc822Message) && params["boundary"] != "" {
|
||||||
newPath := append(currentPath, 1)
|
nextPath := getChildPath(currentPath)
|
||||||
|
|
||||||
var br *boundaryReader
|
var br *boundaryReader
|
||||||
br, err = newBoundaryReader(bodyReader, params["boundary"])
|
br, err = newBoundaryReader(bodyReader, params["boundary"])
|
||||||
@ -121,9 +121,9 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = bs.parseAllChildSections(part, newPath, start)
|
err = bs.parseAllChildSections(part, nextPath, start)
|
||||||
part.Reset()
|
part.Reset()
|
||||||
newPath[len(newPath)-1]++
|
nextPath[len(nextPath)-1]++
|
||||||
}
|
}
|
||||||
br.reader = nil
|
br.reader = nil
|
||||||
|
|
||||||
@ -152,7 +152,7 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
|
|||||||
(*bs)[path] = info
|
(*bs)[path] = info
|
||||||
|
|
||||||
// Fix start of subsections.
|
// Fix start of subsections.
|
||||||
newPath := append(currentPath, 1)
|
newPath := getChildPath(currentPath)
|
||||||
shift := info.Size - info.BSize
|
shift := info.Size - info.BSize
|
||||||
subInfo, err := bs.getInfo(newPath)
|
subInfo, err := bs.getInfo(newPath)
|
||||||
|
|
||||||
@ -197,6 +197,14 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getChildPath will return the first child path of parent path.
|
||||||
|
// NOTE: Return value can be used to iterate over parts so it is necessary to
|
||||||
|
// copy parrent values in order to not rewrite values in parent.
|
||||||
|
func getChildPath(parent []int) []int {
|
||||||
|
// append alloc inline is the fasted way to copy
|
||||||
|
return append(append(make([]int, 0, len(parent)+1), parent...), 1)
|
||||||
|
}
|
||||||
|
|
||||||
func stringPathFromInts(ints []int) (ret string) {
|
func stringPathFromInts(ints []int) (ret string) {
|
||||||
for i, n := range ints {
|
for i, n := range ints {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
@ -212,6 +220,13 @@ func (bs *BodyStructure) hasInfo(sectionPath []int) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bs *BodyStructure) getInfoCheckSection(sectionPath []int) (sectionInfo *SectionInfo, err error) {
|
||||||
|
if len(*bs) == 1 && len(sectionPath) == 1 && sectionPath[0] == 1 {
|
||||||
|
sectionPath = []int{}
|
||||||
|
}
|
||||||
|
return bs.getInfo(sectionPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (bs *BodyStructure) getInfo(sectionPath []int) (sectionInfo *SectionInfo, err error) {
|
func (bs *BodyStructure) getInfo(sectionPath []int) (sectionInfo *SectionInfo, err error) {
|
||||||
path := stringPathFromInts(sectionPath)
|
path := stringPathFromInts(sectionPath)
|
||||||
sectionInfo, ok := (*bs)[path]
|
sectionInfo, ok := (*bs)[path]
|
||||||
@ -223,7 +238,7 @@ func (bs *BodyStructure) getInfo(sectionPath []int) (sectionInfo *SectionInfo, e
|
|||||||
|
|
||||||
// GetSection returns bytes of section including MIME header.
|
// GetSection returns bytes of section including MIME header.
|
||||||
func (bs *BodyStructure) GetSection(wholeMail io.ReadSeeker, sectionPath []int) (section []byte, err error) {
|
func (bs *BodyStructure) GetSection(wholeMail io.ReadSeeker, sectionPath []int) (section []byte, err error) {
|
||||||
info, err := bs.getInfo(sectionPath)
|
info, err := bs.getInfoCheckSection(sectionPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -232,7 +247,7 @@ func (bs *BodyStructure) GetSection(wholeMail io.ReadSeeker, sectionPath []int)
|
|||||||
|
|
||||||
// GetSectionContent returns bytes of section content (excluding MIME header).
|
// GetSectionContent returns bytes of section content (excluding MIME header).
|
||||||
func (bs *BodyStructure) GetSectionContent(wholeMail io.ReadSeeker, sectionPath []int) (section []byte, err error) {
|
func (bs *BodyStructure) GetSectionContent(wholeMail io.ReadSeeker, sectionPath []int) (section []byte, err error) {
|
||||||
info, err := bs.getInfo(sectionPath)
|
info, err := bs.getInfoCheckSection(sectionPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -251,8 +266,11 @@ func (bs *BodyStructure) GetMailHeaderBytes(wholeMail io.ReadSeeker) (header []b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func goToOffsetAndReadNBytes(wholeMail io.ReadSeeker, offset, length int) ([]byte, error) {
|
func goToOffsetAndReadNBytes(wholeMail io.ReadSeeker, offset, length int) ([]byte, error) {
|
||||||
if length < 1 {
|
if length == 0 {
|
||||||
return nil, errors.New("requested non positive length")
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
if length < 0 {
|
||||||
|
return nil, errors.New("requested negative length")
|
||||||
}
|
}
|
||||||
if offset > 0 {
|
if offset > 0 {
|
||||||
if _, err := wholeMail.Seek(int64(offset), io.SeekStart); err != nil {
|
if _, err := wholeMail.Seek(int64(offset), io.SeekStart); err != nil {
|
||||||
@ -266,7 +284,7 @@ func goToOffsetAndReadNBytes(wholeMail io.ReadSeeker, offset, length int) ([]byt
|
|||||||
|
|
||||||
// GetSectionHeader returns the mime header of specified section.
|
// GetSectionHeader returns the mime header of specified section.
|
||||||
func (bs *BodyStructure) GetSectionHeader(sectionPath []int) (header textproto.MIMEHeader, err error) {
|
func (bs *BodyStructure) GetSectionHeader(sectionPath []int) (header textproto.MIMEHeader, err error) {
|
||||||
info, err := bs.getInfo(sectionPath)
|
info, err := bs.getInfoCheckSection(sectionPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -275,7 +293,7 @@ func (bs *BodyStructure) GetSectionHeader(sectionPath []int) (header textproto.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (bs *BodyStructure) GetSectionHeaderBytes(wholeMail io.ReadSeeker, sectionPath []int) (header []byte, err error) {
|
func (bs *BodyStructure) GetSectionHeaderBytes(wholeMail io.ReadSeeker, sectionPath []int) (header []byte, err error) {
|
||||||
info, err := bs.getInfo(sectionPath)
|
info, err := bs.getInfoCheckSection(sectionPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package message
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -70,7 +71,7 @@ func TestParseBodyStructure(t *testing.T) {
|
|||||||
debug("%10s: %-50s %5s %5s %5s %5s", "section", "type", "start", "size", "bsize", "lines")
|
debug("%10s: %-50s %5s %5s %5s %5s", "section", "type", "start", "size", "bsize", "lines")
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
sec := (*bs)[path]
|
sec := (*bs)[path]
|
||||||
contentType := sec.Header.Get("Content-Type")
|
contentType := (*bs)[path].Header.Get("Content-Type")
|
||||||
debug("%10s: %-50s %5d %5d %5d %5d", path, contentType, sec.Start, sec.Size, sec.BSize, sec.Lines)
|
debug("%10s: %-50s %5d %5d %5d %5d", path, contentType, sec.Start, sec.Size, sec.BSize, sec.Lines)
|
||||||
require.Equal(t, expectedStructure[path], contentType)
|
require.Equal(t, expectedStructure[path], contentType)
|
||||||
}
|
}
|
||||||
@ -78,10 +79,45 @@ func TestParseBodyStructure(t *testing.T) {
|
|||||||
require.True(t, len(*bs) == len(expectedStructure), "Wrong number of sections expected %d but have %d", len(expectedStructure), len(*bs))
|
require.True(t, len(*bs) == len(expectedStructure), "Wrong number of sections expected %d but have %d", len(expectedStructure), len(*bs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseBodyStructurePGP(t *testing.T) {
|
||||||
|
expectedStructure := map[string]string{
|
||||||
|
"": "multipart/signed; micalg=pgp-sha256; protocol=\"application/pgp-signature\"; boundary=\"MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM\"",
|
||||||
|
"1": "multipart/mixed; boundary=\"FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378\"; protected-headers=\"v1\"",
|
||||||
|
"1.1": "multipart/mixed; boundary=\"------------F97C8ED4878E94675762AE43\"",
|
||||||
|
"1.1.1": "multipart/alternative; boundary=\"------------041318B15DD3FA540FED32C6\"",
|
||||||
|
"1.1.1.1": "text/plain; charset=utf-8; format=flowed",
|
||||||
|
"1.1.1.2": "text/html; charset=utf-8",
|
||||||
|
"1.1.2": "application/pdf; name=\"minimal.pdf\"",
|
||||||
|
"1.1.3": "application/pgp-keys; name=\"OpenPGP_0x161C0875822359F7.asc\"",
|
||||||
|
"2": "application/pgp-signature; name=\"OpenPGP_signature.asc\"",
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile("testdata/enc-body-structure.eml")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bs, err := NewBodyStructure(bytes.NewReader(b))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
haveStructure := map[string]string{}
|
||||||
|
for path := range *bs {
|
||||||
|
haveStructure[path] = (*bs)[path].Header.Get("Content-Type")
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, expectedStructure, haveStructure)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetSection(t *testing.T) {
|
func TestGetSection(t *testing.T) {
|
||||||
structReader := strings.NewReader(sampleMail)
|
structReader := strings.NewReader(sampleMail)
|
||||||
bs, err := NewBodyStructure(structReader)
|
bs, err := NewBodyStructure(structReader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Bad paths
|
||||||
|
wantPaths := [][]int{{0}, {-1}, {3, 2, 3}}
|
||||||
|
for _, wantPath := range wantPaths {
|
||||||
|
_, err = bs.getInfo(wantPath)
|
||||||
|
require.Error(t, err, "path %v", wantPath)
|
||||||
|
}
|
||||||
|
|
||||||
// Whole section.
|
// Whole section.
|
||||||
for _, try := range testPaths {
|
for _, try := range testPaths {
|
||||||
mailReader := strings.NewReader(sampleMail)
|
mailReader := strings.NewReader(sampleMail)
|
||||||
@ -108,6 +144,60 @@ func TestGetSection(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetSecionNoMIMEParts(t *testing.T) {
|
||||||
|
wantBody := "This is just a simple mail with no multipart structure.\n"
|
||||||
|
wantHeader := `Subject: Sample mail
|
||||||
|
From: John Doe <jdoe@machine.example>
|
||||||
|
To: Mary Smith <mary@example.net>
|
||||||
|
Date: Fri, 21 Nov 1997 09:55:06 -0600
|
||||||
|
Content-Type: plain/text
|
||||||
|
|
||||||
|
`
|
||||||
|
wantMail := wantHeader + wantBody
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
bs, err := NewBodyStructure(strings.NewReader(wantMail))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
// Bad parts
|
||||||
|
wantPaths := [][]int{{0}, {2}, {1, 2, 3}}
|
||||||
|
for _, wantPath := range wantPaths {
|
||||||
|
_, err = bs.getInfoCheckSection(wantPath)
|
||||||
|
r.Error(err, "path %v: %d %d\n__\n%s\n", wantPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug := func(wantPath []int, info *SectionInfo, section []byte) string {
|
||||||
|
if info == nil {
|
||||||
|
info = &SectionInfo{}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("path %v %q: %d %d\n___\n%s\n‾‾‾\n",
|
||||||
|
wantPath, stringPathFromInts(wantPath), info.Start, info.Size,
|
||||||
|
string(section),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok Parts
|
||||||
|
wantPaths = [][]int{{}, {1}}
|
||||||
|
for _, p := range wantPaths {
|
||||||
|
wantPath := append([]int{}, p...)
|
||||||
|
|
||||||
|
info, err := bs.getInfoCheckSection(wantPath)
|
||||||
|
r.NoError(err, debug(wantPath, info, []byte{}))
|
||||||
|
|
||||||
|
section, err := bs.GetSection(strings.NewReader(wantMail), wantPath)
|
||||||
|
r.NoError(err, debug(wantPath, info, section))
|
||||||
|
r.Equal(wantMail, string(section), debug(wantPath, info, section))
|
||||||
|
|
||||||
|
haveBody, err := bs.GetSectionContent(strings.NewReader(wantMail), wantPath)
|
||||||
|
r.NoError(err, debug(wantPath, info, haveBody))
|
||||||
|
r.Equal(wantBody, string(haveBody), debug(wantPath, info, haveBody))
|
||||||
|
|
||||||
|
haveHeader, err := bs.GetSectionHeaderBytes(strings.NewReader(wantMail), wantPath)
|
||||||
|
r.NoError(err, debug(wantPath, info, haveHeader))
|
||||||
|
r.Equal(wantHeader, string(haveHeader), debug(wantPath, info, haveHeader))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetMainHeaderBytes(t *testing.T) {
|
func TestGetMainHeaderBytes(t *testing.T) {
|
||||||
wantHeader := []byte(`Subject: Sample mail
|
wantHeader := []byte(`Subject: Sample mail
|
||||||
From: John Doe <jdoe@machine.example>
|
From: John Doe <jdoe@machine.example>
|
||||||
|
|||||||
164
pkg/message/testdata/enc-body-structure.eml
vendored
Normal file
164
pkg/message/testdata/enc-body-structure.eml
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
Mime-Version: 1.0
|
||||||
|
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||||
|
protocol="application/pgp-signature";
|
||||||
|
boundary="MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM"
|
||||||
|
Message-Id: <messageID@protonmail.internalid>
|
||||||
|
Date: Wed, 01 Jan 2020 00:00:00 +0000
|
||||||
|
|
||||||
|
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||||
|
--MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM
|
||||||
|
Content-Type: multipart/mixed; boundary="FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378";
|
||||||
|
protected-headers="v1"
|
||||||
|
Subject: Alternative
|
||||||
|
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||||
|
To: schizofrenic@pm.me
|
||||||
|
Message-ID: <753d0314-0286-2c88-2abb-f8080ac7a4cb@gmail.com>
|
||||||
|
|
||||||
|
--FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378
|
||||||
|
Content-Type: multipart/mixed;
|
||||||
|
boundary="------------F97C8ED4878E94675762AE43"
|
||||||
|
Content-Language: en-US
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--------------F97C8ED4878E94675762AE43
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="------------041318B15DD3FA540FED32C6"
|
||||||
|
|
||||||
|
|
||||||
|
--------------041318B15DD3FA540FED32C6
|
||||||
|
Content-Type: text/plain; charset=utf-8; format=flowed
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
This Rich formated text
|
||||||
|
|
||||||
|
* /What kind of shoes do ninjas wear? /*Sneakers!*
|
||||||
|
* /How does a penguin build its house?/**_/*Igloos it together.*/_
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--------------041318B15DD3FA540FED32C6
|
||||||
|
Content-Type: text/html; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta http-equiv=3D"content-type" content=3D"text/html; charset=3DUTF=
|
||||||
|
-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This <font color=3D"#ee24cc">Rich</font> formated text</p>
|
||||||
|
<ul>
|
||||||
|
<li><i>What kind of shoes do ninjas wear? </i><b>Sneakers!</b></li>=
|
||||||
|
|
||||||
|
<li><i>How does a penguin build its house?</i><b> </b><u><i><b>Iglo=
|
||||||
|
os
|
||||||
|
it together.</b></i></u></li>
|
||||||
|
</ul>
|
||||||
|
<p><br>
|
||||||
|
</p>
|
||||||
|
<p><br>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
--------------041318B15DD3FA540FED32C6--
|
||||||
|
|
||||||
|
--------------F97C8ED4878E94675762AE43
|
||||||
|
Content-Type: application/pdf;
|
||||||
|
name="minimal.pdf"
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
Content-Disposition: attachment;
|
||||||
|
filename="minimal.pdf"
|
||||||
|
|
||||||
|
JVBERi0xLjEKJcKlwrHDqwoKMSAwIG9iagogIDw8IC9UeXBlIC9DYXRhbG9nCiAgICAgL1Bh
|
||||||
|
Z2VzIDIgMCBSCiAgPj4KZW5kb2JqCgoyIDAgb2JqCiAgPDwgL1R5cGUgL1BhZ2VzCiAgICAg
|
||||||
|
L0tpZHMgWzMgMCBSXQogICAgIC9Db3VudCAxCiAgICAgL01lZGlhQm94IFswIDAgMzAwIDE0
|
||||||
|
NF0KICA+PgplbmRvYmoKCjMgMCBvYmoKICA8PCAgL1R5cGUgL1BhZ2UKICAgICAgL1BhcmVu
|
||||||
|
dCAyIDAgUgogICAgICAvUmVzb3VyY2VzCiAgICAgICA8PCAvRm9udAogICAgICAgICAgIDw8
|
||||||
|
IC9GMQogICAgICAgICAgICAgICA8PCAvVHlwZSAvRm9udAogICAgICAgICAgICAgICAgICAv
|
||||||
|
U3VidHlwZSAvVHlwZTEKICAgICAgICAgICAgICAgICAgL0Jhc2VGb250IC9UaW1lcy1Sb21h
|
||||||
|
bgogICAgICAgICAgICAgICA+PgogICAgICAgICAgID4+CiAgICAgICA+PgogICAgICAvQ29u
|
||||||
|
dGVudHMgNCAwIFIKICA+PgplbmRvYmoKCjQgMCBvYmoKICA8PCAvTGVuZ3RoIDU1ID4+CnN0
|
||||||
|
cmVhbQogIEJUCiAgICAvRjEgMTggVGYKICAgIDAgMCBUZAogICAgKEhlbGxvIFdvcmxkKSBU
|
||||||
|
agogIEVUCmVuZHN0cmVhbQplbmRvYmoKCnhyZWYKMCA1CjAwMDAwMDAwMDAgNjU1MzUgZiAK
|
||||||
|
MDAwMDAwMDAxOCAwMDAwMCBuIAowMDAwMDAwMDc3IDAwMDAwIG4gCjAwMDAwMDAxNzggMDAw
|
||||||
|
MDAgbiAKMDAwMDAwMDQ1NyAwMDAwMCBuIAp0cmFpbGVyCiAgPDwgIC9Sb290IDEgMCBSCiAg
|
||||||
|
ICAgIC9TaXplIDUKICA+PgpzdGFydHhyZWYKNTY1CiUlRU9GCg==
|
||||||
|
--------------F97C8ED4878E94675762AE43
|
||||||
|
Content-Type: application/pgp-keys;
|
||||||
|
name="OpenPGP_0x161C0875822359F7.asc"
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
Content-Disposition: attachment;
|
||||||
|
filename="OpenPGP_0x161C0875822359F7.asc"
|
||||||
|
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
xsBNBFxlUPwBCACx954Ey4SD88f8DSKFw9BaZNXrNwYxNYSgqaqOGHQ0WllF3mstEhTfuxxCZ=
|
||||||
|
pDh
|
||||||
|
I5IhWCXUNxanzsFkn88mRDwFRVl2sf2aAG4/P/p1381oh2kd0UElMRQaQGzoCadQMaQOL9WYT=
|
||||||
|
f4S
|
||||||
|
PWSCzjrPyKgjq5FbqjbF/ndu376na9L+tnsEXyL6RrI6aZhjWG73xlqxS65dzTIYzsyM/P97x=
|
||||||
|
Snd
|
||||||
|
NvlvWtGvLlpFkzxfAEGpVzfOYVYFKoc8rGmUDwrDWYfk5JczRDDogJnY+BNMZf9pjSqk6rTyB=
|
||||||
|
OfN
|
||||||
|
H5fpU8r7A5Q7l+HVakvMUQ9DzDWJtg2ru1Y8hexnJOF68avO4+a1ABEBAAHNKEJyaWRnZSBLe=
|
||||||
|
XUt
|
||||||
|
RWh5aiA8cG0uYnJpZGdlLnFhQGdtYWlsLmNvbT7CwJQEEwEIAD4CGwMFCwkIBwIGFQoJCAsCB=
|
||||||
|
BYC
|
||||||
|
AwECHgECF4AWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCYC32ygUJB4sMzgAKCRAWHAh1giNZ9=
|
||||||
|
/K8
|
||||||
|
B/4qs84Ii/zKH+q+C8vwO4jUJkOM73qD0pgB7zBs651zWbpgopyol1YUKNpFaHlx/Qch7RDI7=
|
||||||
|
Vcz
|
||||||
|
1+60/KZJSJR19/N2EDVbCUdh8ueioUp9X/218YWV2TRJNxTnljd4FAn7smZnXuP1TsLjQ6sKO=
|
||||||
|
V0U
|
||||||
|
u6JoiG6LZFXqDgxYpA++58Rkl6xaY6R71VkmVQlbEKtubX9AjHydq97Y+Jvn11XzWZaKhv4L7=
|
||||||
|
6Pa
|
||||||
|
4tMKXvvrKh1oywMmh6mZJo+5ZA/ABTkr45cwlTPYqGTS9+uvOHt+PH/oYwwJB4ls2cIAUldSj=
|
||||||
|
TVQ
|
||||||
|
IsseYz3LlbcCfKJiiCFxeHOQXA5J6zNLKOT58TsczsBNBFxlUPwBCADh2HsX23yVnJt9fxFz3=
|
||||||
|
D07
|
||||||
|
kCBNvu4HQfps6h1rgNxGhE32VmpESHebvIB5xjL6xKbIqqRa3x/7KDVBNJvca0gUsqEt5kzYF=
|
||||||
|
88F
|
||||||
|
yf2NBcejpIbcP7BS/g+C6KOowYj+Et26T6GdwFXExUcl80JvoX8yHQOfvJpdiBRbjyB8UqfCa=
|
||||||
|
knm
|
||||||
|
3c7dNuXmhflz/w3aBj32q9ZyGqA1NpHCpLyVAlvSNQ/pat/rGUCPZ9duw4KhUUqEmatQPVFPk=
|
||||||
|
utT
|
||||||
|
ouEZQbMK+i+chOH3AsKCuNDfvCDwirnsSqIJmAgl1lC4de+bsWYCMqN9ei99hOCRUyhZ3g3sr=
|
||||||
|
8RB
|
||||||
|
owVAdcvjZxeIDKALABEBAAHCwHwEGAEIACYCGwwWIQRc5gl5cC8oW/Mo+bEWHAh1giNZ9wUCY=
|
||||||
|
C32
|
||||||
|
lAUJB4sMmAAKCRAWHAh1giNZ9+Y2B/9rTKZaKviae+ummXNumXcrKvbkAAvfuLpKUn53FlQLm=
|
||||||
|
L6H
|
||||||
|
jB++lJnPWvVSzdZxdv8FiPP3d632XHKUrkQRQM/9byRDXDommi7Qttx7YCkhd4JLVYqJqpnAQ=
|
||||||
|
xI5
|
||||||
|
RMkXiZNWyr1lz8JOM1XvDk1M7sJwPMWews8VOIE03E1nt7AsQGnvHtadgEnQaufrYNX3hFA8S=
|
||||||
|
osO
|
||||||
|
HSnedcys6yrzCSIGCqCD9VHbnMtS4DOv0XJGh2hwc8omzH0KZA517dyKBorJRwadcVauGXDKx=
|
||||||
|
Etv
|
||||||
|
Im4rl94PR/3An1Mj6HeeVVpLqDQ5Jb9J90BahWeQ53FzRa4EQzYCw0nLnxcsT1ZEEP5u
|
||||||
|
=3Dv/1p
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
--------------F97C8ED4878E94675762AE43--
|
||||||
|
|
||||||
|
--FBBl2LNv76z8UkvHhSkT9vLwVwxqV8378--
|
||||||
|
|
||||||
|
--MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM
|
||||||
|
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||||
|
Content-Description: OpenPGP digital signature
|
||||||
|
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||||
|
|
||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
wsB5BAABCAAjFiEEXOYJeXAvKFvzKPmxFhwIdYIjWfcFAmBciUoFAwAAAAAACgkQFhwIdYIjWfez
|
||||||
|
rgf+NZCibnCUTovpWRVRiiPQtBPGeHUPEwz2xq2zz4AaqrHC2v4mYUIPe6am7INk8fkBLsa8Dj/A
|
||||||
|
UN/28Qh7tNb7JsXtHDT4PIoXszukQ8VIRbe09mSkkP6jR4WzNR166d6n3rSxzHpviOyQldjjpOMr
|
||||||
|
Zl7LxmgGr4ojsgCf6pvurWwCCOGJqbSusrD6JVv6DsmPmmQeBmnlTK/0oG9pnlNkugpNB1WS2K5d
|
||||||
|
RY6+kWkSrxbq95HrgILpHip8Y/+ITWvQocm14PBIAAdW8Hr7iFQLETFJ/KDA+VP19Bt8n4Kitdi8
|
||||||
|
DPqMsV0oOhATqBjnD63AePJ0VWg8R1z6GEK5A+WOpg==
|
||||||
|
=Bc6p
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
|
|
||||||
|
--MHEDFShwcX18dyE3X7RXujo5fjpgdjHNM--
|
||||||
8
pkg/message/testdata/pgp-mime-body-plaintext-latin2.eml
vendored
Normal file
8
pkg/message/testdata/pgp-mime-body-plaintext-latin2.eml
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Subject: plain no pubkey no sign
|
||||||
|
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
|
||||||
|
To: schizofrenic@pm.me
|
||||||
|
Message-ID: <564b9c7c-91eb-6508-107a-35108f383a44@gmail.com>
|
||||||
|
Content-Type: text/plain; charset=iso-8859-2; format=flowed
|
||||||
|
Content-Language: en-US
|
||||||
|
|
||||||
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
3
pkg/message/testdata/plaintext-invalid-header.eml
vendored
Normal file
3
pkg/message/testdata/plaintext-invalid-header.eml
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
MalformedKey Value
|
||||||
|
|
||||||
|
How do we know that the ocean is friendly? It waves!
|
||||||
1
pkg/message/testdata/plaintext-missing-header.eml
vendored
Normal file
1
pkg/message/testdata/plaintext-missing-header.eml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
How do we know that the ocean is friendly? It waves!
|
||||||
5
pkg/message/testdata/text_plain_base64.eml
vendored
Normal file
5
pkg/message/testdata/text_plain_base64.eml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
From: Sender <sender@pm.me>
|
||||||
|
To: Receiver <receiver@pm.me>
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
Ym9keQ==
|
||||||
16
pkg/message/testdata/text_plain_rfc822_attachment.eml
vendored
Normal file
16
pkg/message/testdata/text_plain_rfc822_attachment.eml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
From: Sender <sender@pm.me>
|
||||||
|
To: Receiver <receiver@pm.me>
|
||||||
|
Content-Type: multipart/mixed; boundary=longrandomstring
|
||||||
|
|
||||||
|
--longrandomstring
|
||||||
|
|
||||||
|
body
|
||||||
|
--longrandomstring
|
||||||
|
Content-Type: message/rfc822
|
||||||
|
Content-Disposition: attachment
|
||||||
|
|
||||||
|
From: Sender <sender@pm.me>
|
||||||
|
To: Receiver <receiver@pm.me>
|
||||||
|
|
||||||
|
inner body
|
||||||
|
--longrandomstring--
|
||||||
48
pkg/message/writer.go
Normal file
48
pkg/message/writer.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail 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.
|
||||||
|
//
|
||||||
|
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package message
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type partWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
boundary string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPartWriter(w io.Writer, boundary string) *partWriter {
|
||||||
|
return &partWriter{w: w, boundary: boundary}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *partWriter) createPart(fn func(io.Writer) error) error {
|
||||||
|
if _, err := fmt.Fprintf(w.w, "\r\n--%v\r\n", w.boundary); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(w.w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *partWriter) done() error {
|
||||||
|
if _, err := fmt.Fprintf(w.w, "\r\n--%v--\r\n", w.boundary); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -33,16 +33,24 @@ import (
|
|||||||
|
|
||||||
pmmime "github.com/ProtonMail/proton-bridge/pkg/mime"
|
pmmime "github.com/ProtonMail/proton-bridge/pkg/mime"
|
||||||
|
|
||||||
a "github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
r "github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const testAttachmentCleartext = `cc,
|
||||||
|
dille.
|
||||||
|
`
|
||||||
|
|
||||||
|
// Attachment cleartext encrypted with testPrivateKeyRing.
|
||||||
|
const testKeyPacket = `wcBMA0fcZ7XLgmf2AQf/cHhfDRM9zlIuBi+h2W6DKjbbyIHMkgF6ER3JEvn/tSruUH8KTGt0N7Z+a80FFMCuXn1Y1I/nW7MVrNhGuJZAF4OymD8ugvuoAMIQX0eCYEpPXzRIWJBZg82AuowmFMsv8Dgvq4bTZq4cttI3CZcxKUNXuAearmNpmgplUKWj5USmRXK4iGB3VFGjidXkxbElrP4fD5A/rfEZ5aJgCsegqcXxX3MEjWXi9pFzgd/9phOvl1ZFm9U9hNoVAW3QsgmVeihnKaDZUyf2Qsigij21QKAUxw9U3y89eTUIqZAcmIgqeDujA3RWBgJwjtY/lOyhEmkf3AWKzehvf1xtJmCWDg==`
|
||||||
|
const testDataPacket = `0ksB6S4f4l8C1NB8yzmd/jNi0xqEZsyTDLdTP+N4Qxh3NZjla+yGRvC9rGmoUL7XVyowsG/GKTf2LXF/5E5FkX/3WMYwIv1n11ExyAE=`
|
||||||
|
|
||||||
var testAttachment = &Attachment{
|
var testAttachment = &Attachment{
|
||||||
ID: "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw==",
|
ID: "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw==",
|
||||||
Name: "croutonmail.txt",
|
Name: "croutonmail.txt",
|
||||||
Size: 77,
|
Size: 77,
|
||||||
MIMEType: "text/plain",
|
MIMEType: "text/plain",
|
||||||
KeyPackets: "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==",
|
KeyPackets: testKeyPacket,
|
||||||
|
|
||||||
Header: textproto.MIMEHeader{
|
Header: textproto.MIMEHeader{
|
||||||
"Content-Description": {"You'll never believe what's in this text file"},
|
"Content-Description": {"You'll never believe what's in this text file"},
|
||||||
"X-Mailer": {"Microsoft Outlook 15.0", "Microsoft Live Mail 42.0"},
|
"X-Mailer": {"Microsoft Outlook 15.0", "Microsoft Live Mail 42.0"},
|
||||||
@ -50,12 +58,13 @@ var testAttachment = &Attachment{
|
|||||||
MessageID: "h3CD-DT7rLoAw1vmpcajvIPAl-wwDfXR2MHtWID3wuQURDBKTiGUAwd6E2WBbS44QQKeXImW-axm6X0hAfcVCA==",
|
MessageID: "h3CD-DT7rLoAw1vmpcajvIPAl-wwDfXR2MHtWID3wuQURDBKTiGUAwd6E2WBbS44QQKeXImW-axm6X0hAfcVCA==",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Part of GET /mail/messages/{id} response from server.
|
||||||
const testAttachmentJSON = `{
|
const testAttachmentJSON = `{
|
||||||
"ID": "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw==",
|
"ID": "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw==",
|
||||||
"Name": "croutonmail.txt",
|
"Name": "croutonmail.txt",
|
||||||
"Size": 77,
|
"Size": 77,
|
||||||
"MIMEType": "text/plain",
|
"MIMEType": "text/plain",
|
||||||
"KeyPackets": "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==",
|
"KeyPackets": "` + testKeyPacket + `",
|
||||||
"Headers": {
|
"Headers": {
|
||||||
"content-description": "You'll never believe what's in this text file",
|
"content-description": "You'll never believe what's in this text file",
|
||||||
"x-mailer": [
|
"x-mailer": [
|
||||||
@ -66,68 +75,66 @@ const testAttachmentJSON = `{
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const testAttachmentCleartext = `cc,
|
// POST /mail/attachment/ response from server.
|
||||||
dille.
|
const testCreatedAttachmentBody = `{
|
||||||
`
|
|
||||||
|
|
||||||
const testAttachmentEncrypted = `wcBMA0fcZ7XLgmf2AQf/cHhfDRM9zlIuBi+h2W6DKjbbyIHMkgF6ER3JEvn/tSruUH8KTGt0N7Z+a80FFMCuXn1Y1I/nW7MVrNhGuJZAF4OymD8ugvuoAMIQX0eCYEpPXzRIWJBZg82AuowmFMsv8Dgvq4bTZq4cttI3CZcxKUNXuAearmNpmgplUKWj5USmRXK4iGB3VFGjidXkxbElrP4fD5A/rfEZ5aJgCsegqcXxX3MEjWXi9pFzgd/9phOvl1ZFm9U9hNoVAW3QsgmVeihnKaDZUyf2Qsigij21QKAUxw9U3y89eTUIqZAcmIgqeDujA3RWBgJwjtY/lOyhEmkf3AWKzehvf1xtJmCWDtJLAekuH+JfAtTQfMs5nf4zYtMahGbMkwy3Uz/jeEMYdzWY5WvshkbwvaxpqFC+11cqMLBvxik39i1xf+RORZF/91jGMCL9Z9dRMcgB`
|
|
||||||
|
|
||||||
const testCreateAttachmentBody = `{
|
|
||||||
"Code": 1000,
|
"Code": 1000,
|
||||||
"Attachment": {"ID": "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw=="}
|
"Attachment": {"ID": "y6uKIlc2HdoHPAwPSrvf7dXoZNMYvBgxshYUN67cY5DJjL2O8NYewuvGHcYvCfd8LpEoAI_GdymO0Jr0mHlsEw=="}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
func TestAttachment_UnmarshalJSON(t *testing.T) {
|
func TestAttachment_UnmarshalJSON(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
att := new(Attachment)
|
att := new(Attachment)
|
||||||
err := json.Unmarshal([]byte(testAttachmentJSON), att)
|
err := json.Unmarshal([]byte(testAttachmentJSON), att)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
att.MessageID = testAttachment.MessageID // This isn't in the JSON object
|
att.MessageID = testAttachment.MessageID // This isn't in the server response
|
||||||
|
|
||||||
r.Equal(t, testAttachment, att)
|
r.Equal(testAttachment, att)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_CreateAttachment(t *testing.T) {
|
func TestClient_CreateAttachment(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
r.NoError(t, checkMethodAndPath(req, "POST", "/mail/v4/attachments"))
|
r.NoError(checkMethodAndPath(req, "POST", "/mail/v4/attachments"))
|
||||||
|
|
||||||
contentType, params, err := pmmime.ParseMediaType(req.Header.Get("Content-Type"))
|
contentType, params, err := pmmime.ParseMediaType(req.Header.Get("Content-Type"))
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
r.Equal(t, "multipart/form-data", contentType)
|
r.Equal("multipart/form-data", contentType)
|
||||||
|
|
||||||
mr := multipart.NewReader(req.Body, params["boundary"])
|
mr := multipart.NewReader(req.Body, params["boundary"])
|
||||||
form, err := mr.ReadForm(10 * 1024)
|
form, err := mr.ReadForm(10 * 1024)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
defer r.NoError(t, form.RemoveAll())
|
defer r.NoError(form.RemoveAll())
|
||||||
|
|
||||||
r.Equal(t, testAttachment.Name, form.Value["Filename"][0])
|
r.Equal(testAttachment.Name, form.Value["Filename"][0])
|
||||||
r.Equal(t, testAttachment.MessageID, form.Value["MessageID"][0])
|
r.Equal(testAttachment.MessageID, form.Value["MessageID"][0])
|
||||||
r.Equal(t, testAttachment.MIMEType, form.Value["MIMEType"][0])
|
r.Equal(testAttachment.MIMEType, form.Value["MIMEType"][0])
|
||||||
|
|
||||||
dataFile, err := form.File["DataPacket"][0].Open()
|
dataFile, err := form.File["DataPacket"][0].Open()
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
defer r.NoError(t, dataFile.Close())
|
defer r.NoError(dataFile.Close())
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(dataFile)
|
b, err := ioutil.ReadAll(dataFile)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
r.Equal(t, testAttachmentCleartext, string(b))
|
r.Equal(testAttachmentCleartext, string(b))
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
fmt.Fprint(w, testCreateAttachmentBody)
|
fmt.Fprint(w, testCreatedAttachmentBody)
|
||||||
}))
|
}))
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
reader := strings.NewReader(testAttachmentCleartext) // In reality, this thing is encrypted
|
reader := strings.NewReader(testAttachmentCleartext) // In reality, this thing is encrypted
|
||||||
created, err := c.CreateAttachment(context.Background(), testAttachment, reader, strings.NewReader(""))
|
created, err := c.CreateAttachment(context.Background(), testAttachment, reader, strings.NewReader(""))
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
r.Equal(t, testAttachment.ID, created.ID)
|
r.Equal(testAttachment.ID, created.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_GetAttachment(t *testing.T) {
|
func TestClient_GetAttachment(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
r.NoError(t, checkMethodAndPath(req, "GET", "/mail/v4/attachments/"+testAttachment.ID))
|
r.NoError(checkMethodAndPath(req, "GET", "/mail/v4/attachments/"+testAttachment.ID))
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
fmt.Fprint(w, testAttachmentCleartext)
|
fmt.Fprint(w, testAttachmentCleartext)
|
||||||
@ -135,39 +142,61 @@ func TestClient_GetAttachment(t *testing.T) {
|
|||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
att, err := c.GetAttachment(context.Background(), testAttachment.ID)
|
att, err := c.GetAttachment(context.Background(), testAttachment.ID)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
defer att.Close() //nolint[errcheck]
|
defer att.Close() //nolint[errcheck]
|
||||||
|
|
||||||
// In reality, r contains encrypted data
|
// In reality, r contains encrypted data
|
||||||
b, err := ioutil.ReadAll(att)
|
b, err := ioutil.ReadAll(att)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
r.Equal(t, testAttachmentCleartext, string(b))
|
r.Equal(testAttachmentCleartext, string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAttachment_Encrypt(t *testing.T) {
|
func TestAttachmentDecrypt(t *testing.T) {
|
||||||
data := bytes.NewBufferString(testAttachmentCleartext)
|
r := require.New(t)
|
||||||
r, err := testAttachment.Encrypt(testPublicKeyRing, data)
|
|
||||||
a.Nil(t, err)
|
|
||||||
b, err := ioutil.ReadAll(r)
|
|
||||||
a.Nil(t, err)
|
|
||||||
|
|
||||||
// Result is always different, so the best way is to test it by decrypting again.
|
rawKeyPacket, err := base64.StdEncoding.DecodeString(testKeyPacket)
|
||||||
// Another test for decrypting will help us to be sure it's working.
|
r.NoError(err)
|
||||||
dataEnc := bytes.NewBuffer(b)
|
|
||||||
decryptAndCheck(t, dataEnc)
|
rawDataPacket, err := base64.StdEncoding.DecodeString(testDataPacket)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
decryptAndCheck(r, bytes.NewBuffer(append(rawKeyPacket, rawDataPacket...)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAttachment_Decrypt(t *testing.T) {
|
func TestAttachmentEncrypt(t *testing.T) {
|
||||||
dataBytes, _ := base64.StdEncoding.DecodeString(testAttachmentEncrypted)
|
r := require.New(t)
|
||||||
dataReader := bytes.NewBuffer(dataBytes)
|
|
||||||
decryptAndCheck(t, dataReader)
|
encryptedReader, err := testAttachment.Encrypt(
|
||||||
|
testPublicKeyRing,
|
||||||
|
bytes.NewBufferString(testAttachmentCleartext),
|
||||||
|
)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
// The result is always different due to session key. The best way is to
|
||||||
|
// test result of encryption by decrypting again acn coparet to cleartext.
|
||||||
|
decryptAndCheck(r, encryptedReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptAndCheck(t *testing.T, data io.Reader) {
|
func decryptAndCheck(r *require.Assertions, data io.Reader) {
|
||||||
r, err := testAttachment.Decrypt(data, testPrivateKeyRing)
|
// First separate KeyPacket from encrypted data. In our case keypacket
|
||||||
a.Nil(t, err)
|
// has 271 bytes.
|
||||||
b, err := ioutil.ReadAll(r)
|
raw, err := ioutil.ReadAll(data)
|
||||||
a.Nil(t, err)
|
r.NoError(err)
|
||||||
a.Equal(t, testAttachmentCleartext, string(b))
|
rawKeyPacket := raw[:271]
|
||||||
|
rawDataPacket := raw[271:]
|
||||||
|
|
||||||
|
// KeyPacket is retrieve by get GET /mail/messages/{id}
|
||||||
|
haveAttachment := &Attachment{
|
||||||
|
KeyPackets: base64.StdEncoding.EncodeToString(rawKeyPacket),
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataPacket is received from GET /mail/attachments/{id}
|
||||||
|
decryptedReader, err := haveAttachment.Decrypt(bytes.NewBuffer(rawDataPacket), testPrivateKeyRing)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(decryptedReader)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.Equal(testAttachmentCleartext, string(b))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -217,3 +217,13 @@ func randomString(length int) string {
|
|||||||
|
|
||||||
return base64.StdEncoding.EncodeToString(noise)[:length]
|
return base64.StdEncoding.EncodeToString(noise)[:length]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) GetCurrentAuth() *Auth {
|
||||||
|
return &Auth{
|
||||||
|
UserID: c.user.ID,
|
||||||
|
AuthRefresh: AuthRefresh{
|
||||||
|
UID: c.uid,
|
||||||
|
RefreshToken: c.ref,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -143,6 +143,51 @@ func Test401RevokedAuth(t *testing.T) {
|
|||||||
r.EqualError(t, err, ErrUnauthorized.Error())
|
r.EqualError(t, err, ErrUnauthorized.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test401RevokedAuthTokenUpdate(t *testing.T) {
|
||||||
|
var oldAuth = &AuthRefresh{
|
||||||
|
UID: "UID",
|
||||||
|
AccessToken: "oldAcc",
|
||||||
|
RefreshToken: "oldRef",
|
||||||
|
ExpiresIn: 3600,
|
||||||
|
}
|
||||||
|
|
||||||
|
var newAuth = &AuthRefresh{
|
||||||
|
UID: "UID",
|
||||||
|
AccessToken: "newAcc",
|
||||||
|
RefreshToken: "newRef",
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/auth/refresh", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(newAuth); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/addresses", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Authorization") == ("Bearer " + oldAuth.AccessToken) {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Header.Get("Authorization") == ("Bearer " + newAuth.AccessToken) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ts := httptest.NewServer(mux)
|
||||||
|
|
||||||
|
c := New(Config{HostURL: ts.URL}).
|
||||||
|
NewClient(oldAuth.UID, oldAuth.AccessToken, oldAuth.RefreshToken, time.Now().Add(time.Hour))
|
||||||
|
|
||||||
|
// The request will fail with 401, triggering a refresh. After the refresh it should succeed.
|
||||||
|
_, err := c.GetAddresses(context.Background())
|
||||||
|
r.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuth2FA(t *testing.T) {
|
func TestAuth2FA(t *testing.T) {
|
||||||
twoFACode := "code"
|
twoFACode := "code"
|
||||||
|
|
||||||
|
|||||||
@ -84,6 +84,8 @@ func (c *client) r(ctx context.Context) (*resty.Request, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do executes fn and may repeate it in case "401 Unauthorized" error is returned.
|
||||||
|
// Note: fn may be called more than once.
|
||||||
func (c *client) do(ctx context.Context, fn func(*resty.Request) (*resty.Response, error)) (*resty.Response, error) {
|
func (c *client) do(ctx context.Context, fn func(*resty.Request) (*resty.Response, error)) (*resty.Response, error) {
|
||||||
r, err := c.r(ctx)
|
r, err := c.r(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -102,6 +104,12 @@ func (c *client) do(ctx context.Context, fn func(*resty.Request) (*resty.Respons
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to reconstruct request since access token is changed with authRefresh.
|
||||||
|
r, err := c.r(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return wrapNoConnection(fn(r))
|
return wrapNoConnection(fn(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -64,29 +64,6 @@ var errVerificationFailed = errors.New("signature verification failed")
|
|||||||
|
|
||||||
// ================= Public utility functions ======================
|
// ================= Public utility functions ======================
|
||||||
|
|
||||||
func (c *client) EncryptAndSignCards(cards []Card) ([]Card, error) {
|
|
||||||
var err error
|
|
||||||
for i := range cards {
|
|
||||||
card := &cards[i]
|
|
||||||
if isEncryptedCardType(card.Type) {
|
|
||||||
if isSignedCardType(card.Type) {
|
|
||||||
if card.Signature, err = c.sign(card.Data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if card.Data, err = c.encrypt(card.Data, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if isSignedCardType(card.Type) {
|
|
||||||
if card.Signature, err = c.sign(card.Data); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cards, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) DecryptAndVerifyCards(cards []Card) ([]Card, error) {
|
func (c *client) DecryptAndVerifyCards(cards []Card) ([]Card, error) {
|
||||||
for i := range cards {
|
for i := range cards {
|
||||||
card := &cards[i]
|
card := &cards[i]
|
||||||
@ -136,7 +113,7 @@ func (c *client) GetContactEmailByEmail(ctx context.Context, email string, page
|
|||||||
if pageSize != 0 {
|
if pageSize != 0 {
|
||||||
r.SetQueryParam("PageSize", strconv.Itoa(pageSize))
|
r.SetQueryParam("PageSize", strconv.Itoa(pageSize))
|
||||||
}
|
}
|
||||||
return r.SetResult(&res).Get("/contacts/v4")
|
return r.SetResult(&res).Get("/contacts/v4/emails")
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,29 @@ var testGetContactByIDResponseBody = `{
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
var testGetContactEmailByEmailResponseBody = `{
|
||||||
|
"Code": 1000,
|
||||||
|
"ContactEmails": [
|
||||||
|
{
|
||||||
|
"ID": "aefew4323jFv0BhSMw==",
|
||||||
|
"Name": "ProtonMail Features",
|
||||||
|
"Email": "features@protonmail.black",
|
||||||
|
"Type": [
|
||||||
|
"work"
|
||||||
|
],
|
||||||
|
"Defaults": 1,
|
||||||
|
"Order": 1,
|
||||||
|
"ContactID": "a29olIjFv0rnXxBhSMw==",
|
||||||
|
"LabelIDs": [
|
||||||
|
"I6hgx3Ol-d3HYa3E394T_ACXDmTaBub14w=="
|
||||||
|
],
|
||||||
|
"CanonicalEmail": "features@protonmail.black",
|
||||||
|
"LastUsedTime": 1612546350
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Total": 2
|
||||||
|
}`
|
||||||
|
|
||||||
var testGetContactByID = Contact{
|
var testGetContactByID = Contact{
|
||||||
ID: "s_SN9y1q0jczjYCH4zhvfOdHv1QNovKhnJ9bpDcTE0u7WCr2Z-NV9uubHXvOuRozW-HRVam6bQupVYRMC3BCqg==",
|
ID: "s_SN9y1q0jczjYCH4zhvfOdHv1QNovKhnJ9bpDcTE0u7WCr2Z-NV9uubHXvOuRozW-HRVam6bQupVYRMC3BCqg==",
|
||||||
Name: "Alice",
|
Name: "Alice",
|
||||||
@ -105,6 +128,19 @@ var testGetContactByID = Contact{
|
|||||||
LabelIDs: []string{},
|
LabelIDs: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var testGetContactEmailByEmail = []ContactEmail{
|
||||||
|
{
|
||||||
|
ID: "aefew4323jFv0BhSMw==",
|
||||||
|
Name: "ProtonMail Features",
|
||||||
|
Email: "features@protonmail.black",
|
||||||
|
Type: []string{"work"},
|
||||||
|
Defaults: 1,
|
||||||
|
Order: 1,
|
||||||
|
ContactID: "a29olIjFv0rnXxBhSMw==",
|
||||||
|
LabelIDs: []string{"I6hgx3Ol-d3HYa3E394T_ACXDmTaBub14w=="},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestContact_GetContactById(t *testing.T) {
|
func TestContact_GetContactById(t *testing.T) {
|
||||||
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
r.NoError(t, checkMethodAndPath(req, "GET", "/contacts/v4/s_SN9y1q0jczjYCH4zhvfOdHv1QNovKhnJ9bpDcTE0u7WCr2Z-NV9uubHXvOuRozW-HRVam6bQupVYRMC3BCqg=="))
|
r.NoError(t, checkMethodAndPath(req, "GET", "/contacts/v4/s_SN9y1q0jczjYCH4zhvfOdHv1QNovKhnJ9bpDcTE0u7WCr2Z-NV9uubHXvOuRozW-HRVam6bQupVYRMC3BCqg=="))
|
||||||
@ -122,6 +158,23 @@ func TestContact_GetContactById(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContact_GetContactEmailByEmail(t *testing.T) {
|
||||||
|
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
r.NoError(t, checkMethodAndPath(req, "GET", "/contacts/v4/emails?Email=someone%40pm.me&Page=1&PageSize=10"))
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
fmt.Fprint(w, testGetContactEmailByEmailResponseBody)
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
contact, err := c.GetContactEmailByEmail(context.Background(), "someone@pm.me", 1, 10)
|
||||||
|
r.NoError(t, err)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(contact, testGetContactEmailByEmail) {
|
||||||
|
t.Fatalf("Invalid got contact: expected %+v, got %+v", testGetContactByID, contact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContact_isSignedCardType(t *testing.T) {
|
func TestContact_isSignedCardType(t *testing.T) {
|
||||||
if !isSignedCardType(SignedCard) || !isSignedCardType(EncryptedSignedCard) {
|
if !isSignedCardType(SignedCard) || !isSignedCardType(EncryptedSignedCard) {
|
||||||
t.Fatal("isSignedCardType shouldn't return false for signed card types")
|
t.Fatal("isSignedCardType shouldn't return false for signed card types")
|
||||||
@ -156,20 +209,6 @@ var testCardsCleartext = []Card{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClient_Encrypt(t *testing.T) {
|
|
||||||
c := newClient(newManager(Config{}), "")
|
|
||||||
c.userKeyRing = testPrivateKeyRing
|
|
||||||
|
|
||||||
cardEncrypted, err := c.EncryptAndSignCards(testCardsCleartext)
|
|
||||||
r.Nil(t, err)
|
|
||||||
|
|
||||||
// Result is always different, so the best way is to test it by decrypting again.
|
|
||||||
// Another test for decrypting will help us to be sure it's working.
|
|
||||||
cardCleartext, err := c.DecryptAndVerifyCards(cardEncrypted)
|
|
||||||
r.Nil(t, err)
|
|
||||||
r.Equal(t, testCardsCleartext[0].Data, cardCleartext[0].Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient_Decrypt(t *testing.T) {
|
func TestClient_Decrypt(t *testing.T) {
|
||||||
c := newClient(newManager(Config{}), "")
|
c := newClient(newManager(Config{}), "")
|
||||||
c.userKeyRing = testPrivateKeyRing
|
c.userKeyRing = testPrivateKeyRing
|
||||||
|
|||||||
@ -33,9 +33,11 @@ func CreateTransportWithDialer(dialer TLSDialer) *http.Transport {
|
|||||||
return &http.Transport{
|
return &http.Transport{
|
||||||
DialTLS: dialer.DialTLS,
|
DialTLS: dialer.DialTLS,
|
||||||
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 100,
|
||||||
IdleConnTimeout: 5 * time.Minute,
|
MaxIdleConnsPerHost: 100,
|
||||||
|
IdleConnTimeout: 5 * time.Minute,
|
||||||
|
|
||||||
ExpectContinueTimeout: 500 * time.Millisecond,
|
ExpectContinueTimeout: 500 * time.Millisecond,
|
||||||
|
|
||||||
// GODT-126: this was initially 10s but logs from users showed a significant number
|
// GODT-126: this was initially 10s but logs from users showed a significant number
|
||||||
|
|||||||
@ -27,7 +27,10 @@ import (
|
|||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const MaxImportMessageRequestLength = 10
|
const (
|
||||||
|
MaxImportMessageRequestLength = 10
|
||||||
|
MaxImportMessageRequestSize = 25 * 1024 * 1024 // 25 MB total limit
|
||||||
|
)
|
||||||
|
|
||||||
type ImportMsgReq struct {
|
type ImportMsgReq struct {
|
||||||
Metadata *ImportMetadata // Metadata about the message to import.
|
Metadata *ImportMetadata // Metadata about the message to import.
|
||||||
@ -91,6 +94,14 @@ func (c *client) Import(ctx context.Context, reqs ImportMsgReqs) ([]*ImportMsgRe
|
|||||||
return nil, errors.New("request is too long")
|
return nil, errors.New("request is too long")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remainingSize := MaxImportMessageRequestSize
|
||||||
|
for _, req := range reqs {
|
||||||
|
remainingSize -= len(req.Message)
|
||||||
|
if remainingSize < 0 {
|
||||||
|
return nil, errors.New("request size is too big")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fields, err := reqs.buildMultipartFormData()
|
fields, err := reqs.buildMultipartFormData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
@ -115,3 +116,32 @@ func TestClient_Import(t *testing.T) { // nolint[funlen]
|
|||||||
r.Equal(t, 1, len(imported))
|
r.Equal(t, 1, len(imported))
|
||||||
r.Equal(t, testImportRes, imported[0])
|
r.Equal(t, testImportRes, imported[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientImportBigSize(t *testing.T) {
|
||||||
|
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
r.FailNow(t, "request is not dropped")
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
const size = MaxImportMessageRequestSize + 1
|
||||||
|
msg := make([]byte, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
msg[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||||
|
}
|
||||||
|
|
||||||
|
importRequest := []*ImportMsgReq{
|
||||||
|
{
|
||||||
|
Metadata: &ImportMetadata{
|
||||||
|
AddressID: "addressID",
|
||||||
|
Unread: Boolean(false),
|
||||||
|
Flags: FlagReceived | FlagImported,
|
||||||
|
LabelIDs: []string{ArchiveLabel},
|
||||||
|
},
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.Import(context.Background(), importRequest)
|
||||||
|
r.EqualError(t, err, "request size is too big")
|
||||||
|
}
|
||||||
|
|||||||
@ -36,8 +36,8 @@ type PMKey struct {
|
|||||||
Fingerprint string
|
Fingerprint string
|
||||||
PrivateKey *crypto.Key
|
PrivateKey *crypto.Key
|
||||||
Primary int
|
Primary int
|
||||||
Token *string `json:",omitempty"`
|
Token string
|
||||||
Signature *string `json:",omitempty"`
|
Signature string
|
||||||
}
|
}
|
||||||
|
|
||||||
type clearable []byte
|
type clearable []byte
|
||||||
@ -84,12 +84,12 @@ func (key PMKey) getPassphraseFromToken(kr *crypto.KeyRing) (passphrase []byte,
|
|||||||
return nil, errors.New("no user key was provided")
|
return nil, errors.New("no user key was provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := crypto.NewPGPMessageFromArmored(*key.Token)
|
msg, err := crypto.NewPGPMessageFromArmored(key.Token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := crypto.NewPGPSignatureFromArmored(*key.Signature)
|
sig, err := crypto.NewPGPSignatureFromArmored(key.Signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ func (keys *PMKeys) UnlockAll(passphrase []byte, userKey *crypto.KeyRing) (kr *c
|
|||||||
for _, key := range *keys {
|
for _, key := range *keys {
|
||||||
var secret []byte
|
var secret []byte
|
||||||
|
|
||||||
if key.Token == nil || key.Signature == nil {
|
if key.Token == "" || key.Signature == "" {
|
||||||
secret = passphrase
|
secret = passphrase
|
||||||
} else if secret, err = key.getPassphraseFromToken(userKey); err != nil {
|
} else if secret, err = key.getPassphraseFromToken(userKey); err != nil {
|
||||||
return
|
return
|
||||||
@ -166,10 +166,6 @@ func (keys *PMKeys) UnlockAll(passphrase []byte, userKey *crypto.KeyRing) (kr *c
|
|||||||
// ErrNoKeyringAvailable represents an error caused by a keyring being nil or having no entities.
|
// ErrNoKeyringAvailable represents an error caused by a keyring being nil or having no entities.
|
||||||
var ErrNoKeyringAvailable = errors.New("no keyring available")
|
var ErrNoKeyringAvailable = errors.New("no keyring available")
|
||||||
|
|
||||||
func (c *client) encrypt(plain string, signer *crypto.KeyRing) (armored string, err error) {
|
|
||||||
return encrypt(c.userKeyRing, plain, signer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encrypt(encrypter *crypto.KeyRing, plain string, signer *crypto.KeyRing) (armored string, err error) {
|
func encrypt(encrypter *crypto.KeyRing, plain string, signer *crypto.KeyRing) (armored string, err error) {
|
||||||
if encrypter == nil {
|
if encrypter == nil {
|
||||||
return "", ErrNoKeyringAvailable
|
return "", ErrNoKeyringAvailable
|
||||||
@ -209,18 +205,6 @@ func decrypt(decrypter *crypto.KeyRing, armored string) (plainBody []byte, err e
|
|||||||
return plainMessage.GetBinary(), nil
|
return plainMessage.GetBinary(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) sign(plain string) (armoredSignature string, err error) {
|
|
||||||
if c.userKeyRing == nil {
|
|
||||||
return "", ErrNoKeyringAvailable
|
|
||||||
}
|
|
||||||
plainMessage := crypto.NewPlainMessageFromString(plain)
|
|
||||||
pgpSignature, err := c.userKeyRing.SignDetached(plainMessage)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return pgpSignature.GetArmored()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *client) verify(plain, amroredSignature string) (err error) {
|
func (c *client) verify(plain, amroredSignature string) (err error) {
|
||||||
plainMessage := crypto.NewPlainMessageFromString(plain)
|
plainMessage := crypto.NewPlainMessageFromString(plain)
|
||||||
pgpSignature, err := crypto.NewPGPSignatureFromArmored(amroredSignature)
|
pgpSignature, err := crypto.NewPGPSignatureFromArmored(amroredSignature)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadPMKeys(jsonKeys string) (keys *PMKeys) {
|
func loadPMKeys(jsonKeys string) (keys *PMKeys) {
|
||||||
@ -31,6 +31,7 @@ func loadPMKeys(jsonKeys string) (keys *PMKeys) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
addrKeysWithTokens := loadPMKeys(readTestFile("keyring_addressKeysWithTokens_JSON", false))
|
addrKeysWithTokens := loadPMKeys(readTestFile("keyring_addressKeysWithTokens_JSON", false))
|
||||||
addrKeysWithoutTokens := loadPMKeys(readTestFile("keyring_addressKeysWithoutTokens_JSON", false))
|
addrKeysWithoutTokens := loadPMKeys(readTestFile("keyring_addressKeysWithoutTokens_JSON", false))
|
||||||
addrKeysPrimaryHasToken := loadPMKeys(readTestFile("keyring_addressKeysPrimaryHasToken_JSON", false))
|
addrKeysPrimaryHasToken := loadPMKeys(readTestFile("keyring_addressKeysPrimaryHasToken_JSON", false))
|
||||||
@ -42,7 +43,7 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userKey, err := crypto.NewKeyRing(key)
|
userKey, err := crypto.NewKeyRing(key)
|
||||||
assert.NoError(t, err, "Expected not to receive an error unlocking user key")
|
r.NoError(err, "Expected not to receive an error unlocking user key")
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
userKeyring *crypto.KeyRing
|
userKeyring *crypto.KeyRing
|
||||||
@ -77,9 +78,7 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
kr, err := tt.keys.UnlockAll(tt.args.passphrase, tt.args.userKeyring) // nolint[scopelint]
|
kr, err := tt.keys.UnlockAll(tt.args.passphrase, tt.args.userKeyring) // nolint[scopelint]
|
||||||
if !assert.NoError(t, err) {
|
r.NoError(err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert at least one key has been decrypted
|
// assert at least one key has been decrypted
|
||||||
atLeastOneDecrypted := false
|
atLeastOneDecrypted := false
|
||||||
@ -96,7 +95,21 @@ func TestPMKeys_GetKeyRingAndUnlock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.True(t, atLeastOneDecrypted)
|
r.True(atLeastOneDecrypted)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGopenpgpEncryptAttachment(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
wantMessage := crypto.NewPlainMessage([]byte(testAttachmentCleartext))
|
||||||
|
|
||||||
|
pgpSplitMessage, err := testPublicKeyRing.EncryptAttachment(wantMessage, "")
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
haveMessage, err := testPrivateKeyRing.DecryptAttachment(pgpSplitMessage)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.Equal(wantMessage.Data, haveMessage.Data)
|
||||||
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ func New(cfg Config) Manager {
|
|||||||
func newManager(cfg Config) *manager {
|
func newManager(cfg Config) *manager {
|
||||||
m := &manager{
|
m := &manager{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
rc: resty.New(),
|
rc: resty.New().EnableTrace(),
|
||||||
locker: &sync.Mutex{},
|
locker: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +59,8 @@ func newManager(cfg Config) *manager {
|
|||||||
// wrapped in JSON. If error is returned, `handleRequestFailure` is called,
|
// wrapped in JSON. If error is returned, `handleRequestFailure` is called,
|
||||||
// otherwise `handleRequestSuccess` is called.
|
// otherwise `handleRequestSuccess` is called.
|
||||||
m.rc.SetError(&Error{})
|
m.rc.SetError(&Error{})
|
||||||
|
m.rc.OnAfterResponse(logConnReuse)
|
||||||
|
m.rc.OnAfterResponse(updateTime)
|
||||||
m.rc.OnAfterResponse(m.catchAPIError)
|
m.rc.OnAfterResponse(m.catchAPIError)
|
||||||
m.rc.OnAfterResponse(m.handleRequestSuccess)
|
m.rc.OnAfterResponse(m.handleRequestSuccess)
|
||||||
m.rc.OnError(m.handleRequestFailure)
|
m.rc.OnError(m.handleRequestFailure)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/srp"
|
"github.com/ProtonMail/go-srp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *manager) NewClient(uid, acc, ref string, exp time.Time) Client {
|
func (m *manager) NewClient(uid, acc, ref string, exp time.Time) Client {
|
||||||
@ -44,7 +44,7 @@ func (m *manager) NewClientWithRefresh(ctx context.Context, uid, ref string) (Cl
|
|||||||
return c.withAuth(auth.AccessToken, auth.RefreshToken, expiresIn(auth.ExpiresIn)), auth, nil
|
return c.withAuth(auth.AccessToken, auth.RefreshToken, expiresIn(auth.ExpiresIn)), auth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *manager) NewClientWithLogin(ctx context.Context, username, password string) (Client, *Auth, error) {
|
func (m *manager) NewClientWithLogin(ctx context.Context, username string, password []byte) (Client, *Auth, error) {
|
||||||
log.Trace("New client with login")
|
log.Trace("New client with login")
|
||||||
|
|
||||||
info, err := m.getAuthInfo(ctx, GetAuthInfoReq{Username: username})
|
info, err := m.getAuthInfo(ctx, GetAuthInfoReq{Username: username})
|
||||||
@ -52,12 +52,12 @@ func (m *manager) NewClientWithLogin(ctx context.Context, username, password str
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
srpAuth, err := srp.NewSrpAuth(info.Version, username, password, info.Salt, info.Modulus, info.ServerEphemeral)
|
srpAuth, err := srp.NewAuth(info.Version, username, password, info.Salt, info.Modulus, info.ServerEphemeral)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
proofs, err := srpAuth.GenerateSrpProofs(2048)
|
proofs, err := srpAuth.GenerateProofs(2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import (
|
|||||||
type Manager interface {
|
type Manager interface {
|
||||||
NewClient(string, string, string, time.Time) Client
|
NewClient(string, string, string, time.Time) Client
|
||||||
NewClientWithRefresh(context.Context, string, string) (Client, *AuthRefresh, error)
|
NewClientWithRefresh(context.Context, string, string) (Client, *AuthRefresh, error)
|
||||||
NewClientWithLogin(context.Context, string, string) (Client, *Auth, error)
|
NewClientWithLogin(context.Context, string, []byte) (Client, *Auth, error)
|
||||||
|
|
||||||
DownloadAndVerify(kr *crypto.KeyRing, url, sig string) ([]byte, error)
|
DownloadAndVerify(kr *crypto.KeyRing, url, sig string) ([]byte, error)
|
||||||
ReportBug(context.Context, ReportBugReq) error
|
ReportBug(context.Context, ReportBugReq) error
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -34,9 +35,11 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/openpgp/armor"
|
||||||
"golang.org/x/crypto/openpgp/packet"
|
"golang.org/x/crypto/openpgp/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -293,6 +296,54 @@ func (m *Message) Decrypt(kr *crypto.KeyRing) ([]byte, error) {
|
|||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Signature struct {
|
||||||
|
Hash string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) ExtractSignatures(kr *crypto.KeyRing) ([]Signature, error) {
|
||||||
|
var entities openpgp.EntityList
|
||||||
|
|
||||||
|
for _, key := range kr.GetKeys() {
|
||||||
|
entities = append(entities, key.GetEntity())
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := armor.Decode(strings.NewReader(m.Body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := openpgp.ReadMessage(p.Body, entities, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := ioutil.ReadAll(msg.UnverifiedBody); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !msg.IsSigned {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
signatures := make([]Signature, 0, len(msg.UnverifiedSignatures))
|
||||||
|
|
||||||
|
for _, signature := range msg.UnverifiedSignatures {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if err := signature.Serialize(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatures = append(signatures, Signature{
|
||||||
|
Hash: signature.Hash.String(),
|
||||||
|
Data: buf.Bytes(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return signatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Message) decryptLegacy(kr *crypto.KeyRing) (dec []byte, err error) {
|
func (m *Message) decryptLegacy(kr *crypto.KeyRing) (dec []byte, err error) {
|
||||||
randomKeyStart := strings.Index(m.Body, RandomKeyHeader) + len(RandomKeyHeader)
|
randomKeyStart := strings.Index(m.Body, RandomKeyHeader) + len(RandomKeyHeader)
|
||||||
randomKeyEnd := strings.Index(m.Body, RandomKeyTail)
|
randomKeyEnd := strings.Index(m.Body, RandomKeyTail)
|
||||||
@ -378,6 +429,14 @@ func (m *Message) Has(flag int64) bool {
|
|||||||
return (m.Flags & flag) == flag
|
return (m.Flags & flag) == flag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Message) Recipients() []*mail.Address {
|
||||||
|
var recipients []*mail.Address
|
||||||
|
recipients = append(recipients, m.ToList...)
|
||||||
|
recipients = append(recipients, m.CCList...)
|
||||||
|
recipients = append(recipients, m.BCCList...)
|
||||||
|
return recipients
|
||||||
|
}
|
||||||
|
|
||||||
// MessagesCount contains message counts for one label.
|
// MessagesCount contains message counts for one label.
|
||||||
type MessagesCount struct {
|
type MessagesCount struct {
|
||||||
LabelID string
|
LabelID string
|
||||||
|
|||||||
@ -24,8 +24,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
a "github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
r "github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const testMessageCleartext = `<div>jeej saas<br></div><div><br></div><div class="protonmail_signature_block"><div>Sent from <a href="https://protonmail.ch">ProtonMail</a>, encrypted email based in Switzerland.<br></div><div><br></div></div>`
|
const testMessageCleartext = `<div>jeej saas<br></div><div><br></div><div class="protonmail_signature_block"><div>Sent from <a href="https://protonmail.ch">ProtonMail</a>, encrypted email based in Switzerland.<br></div><div><br></div></div>`
|
||||||
@ -127,70 +126,78 @@ ClW54lp9eeOfYTsdTSbn9VaSO0E6m2/Q4Tk=
|
|||||||
-----END PGP PUBLIC KEY BLOCK-----`
|
-----END PGP PUBLIC KEY BLOCK-----`
|
||||||
|
|
||||||
func TestMessage_IsBodyEncrypted(t *testing.T) {
|
func TestMessage_IsBodyEncrypted(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
msg := &Message{Body: testMessageEncrypted}
|
msg := &Message{Body: testMessageEncrypted}
|
||||||
r.True(t, msg.IsBodyEncrypted(), "the body should be encrypted")
|
r.True(msg.IsBodyEncrypted(), "the body should be encrypted")
|
||||||
|
|
||||||
msg.Body = testMessageCleartext
|
msg.Body = testMessageCleartext
|
||||||
r.True(t, !msg.IsBodyEncrypted(), "the body should not be encrypted")
|
r.True(!msg.IsBodyEncrypted(), "the body should not be encrypted")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessage_Decrypt(t *testing.T) {
|
func TestMessage_Decrypt(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
msg := &Message{Body: testMessageEncrypted}
|
msg := &Message{Body: testMessageEncrypted}
|
||||||
dec, err := msg.Decrypt(testPrivateKeyRing)
|
dec, err := msg.Decrypt(testPrivateKeyRing)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
r.Equal(t, testMessageCleartext, string(dec))
|
r.Equal(testMessageCleartext, string(dec))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessage_Decrypt_Legacy(t *testing.T) {
|
func TestMessage_Decrypt_Legacy(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
testPrivateKeyLegacy := readTestFile("testPrivateKeyLegacy", false)
|
testPrivateKeyLegacy := readTestFile("testPrivateKeyLegacy", false)
|
||||||
|
|
||||||
key, err := crypto.NewKeyFromArmored(testPrivateKeyLegacy)
|
key, err := crypto.NewKeyFromArmored(testPrivateKeyLegacy)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
unlockedKey, err := key.Unlock([]byte(testMailboxPasswordLegacy))
|
unlockedKey, err := key.Unlock([]byte(testMailboxPasswordLegacy))
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
testPrivateKeyRingLegacy, err := crypto.NewKeyRing(unlockedKey)
|
testPrivateKeyRingLegacy, err := crypto.NewKeyRing(unlockedKey)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
msg := &Message{Body: testMessageEncryptedLegacy}
|
msg := &Message{Body: testMessageEncryptedLegacy}
|
||||||
|
|
||||||
dec, err := msg.Decrypt(testPrivateKeyRingLegacy)
|
dec, err := msg.Decrypt(testPrivateKeyRingLegacy)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
r.Equal(t, testMessageCleartextLegacy, string(dec))
|
r.Equal(testMessageCleartextLegacy, string(dec))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessage_Decrypt_signed(t *testing.T) {
|
func TestMessage_Decrypt_signed(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
msg := &Message{Body: testMessageSigned}
|
msg := &Message{Body: testMessageSigned}
|
||||||
dec, err := msg.Decrypt(testPrivateKeyRing)
|
dec, err := msg.Decrypt(testPrivateKeyRing)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
r.Equal(t, testMessageCleartext, string(dec))
|
r.Equal(testMessageCleartext, string(dec))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessage_Encrypt(t *testing.T) {
|
func TestMessage_Encrypt(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
key, err := crypto.NewKeyFromArmored(testMessageSigner)
|
key, err := crypto.NewKeyFromArmored(testMessageSigner)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
signer, err := crypto.NewKeyRing(key)
|
signer, err := crypto.NewKeyRing(key)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
msg := &Message{Body: testMessageCleartext}
|
msg := &Message{Body: testMessageCleartext}
|
||||||
r.NoError(t, msg.Encrypt(testPrivateKeyRing, testPrivateKeyRing))
|
r.NoError(msg.Encrypt(testPrivateKeyRing, testPrivateKeyRing))
|
||||||
|
|
||||||
dec, err := msg.Decrypt(testPrivateKeyRing)
|
dec, err := msg.Decrypt(testPrivateKeyRing)
|
||||||
r.NoError(t, err)
|
r.NoError(err)
|
||||||
|
|
||||||
r.Equal(t, testMessageCleartext, string(dec))
|
r.Equal(testMessageCleartext, string(dec))
|
||||||
r.Equal(t, testIdentity, signer.GetIdentities()[0])
|
r.Equal(testIdentity, signer.GetIdentities()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func routeLabelMessages(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
|
func routeLabelMessages(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
|
||||||
r.NoError(tb, checkMethodAndPath(req, "PUT", "/mail/v4/messages/label"))
|
require.NoError(tb, checkMethodAndPath(req, "PUT", "/mail/v4/messages/label"))
|
||||||
return "messages/label/put_response.json"
|
return "messages/label/put_response.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessage_LabelMessages_NoPaging(t *testing.T) {
|
func TestMessage_LabelMessages_NoPaging(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
// This should be only enough IDs to produce one page.
|
// This should be only enough IDs to produce one page.
|
||||||
testIDs := []string{}
|
testIDs := []string{}
|
||||||
for i := 0; i < messageIDPageSize-1; i++ {
|
for i := 0; i < messageIDPageSize-1; i++ {
|
||||||
@ -203,10 +210,12 @@ func TestMessage_LabelMessages_NoPaging(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer finish()
|
defer finish()
|
||||||
|
|
||||||
a.NoError(t, c.LabelMessages(context.Background(), testIDs, "mylabel"))
|
r.NoError(c.LabelMessages(context.Background(), testIDs, "mylabel"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMessage_LabelMessages_Paging(t *testing.T) {
|
func TestMessage_LabelMessages_Paging(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
// This should be enough IDs to produce three pages.
|
// This should be enough IDs to produce three pages.
|
||||||
testIDs := []string{}
|
testIDs := []string{}
|
||||||
for i := 0; i < 3*messageIDPageSize; i++ {
|
for i := 0; i < 3*messageIDPageSize; i++ {
|
||||||
@ -221,5 +230,26 @@ func TestMessage_LabelMessages_Paging(t *testing.T) {
|
|||||||
)
|
)
|
||||||
defer finish()
|
defer finish()
|
||||||
|
|
||||||
a.NoError(t, c.LabelMessages(context.Background(), testIDs, "mylabel"))
|
r.NoError(c.LabelMessages(context.Background(), testIDs, "mylabel"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestClient_GetMessage might look like no actual functionality is tested
|
||||||
|
// here. But there was case when API was responding with bad payload and it was
|
||||||
|
// useful to have this to quickly test it.
|
||||||
|
func TestClient_GetMessage(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
testID := "AeUizgtA3H44qRgcr-HdBApwLiUhlQg5kB81mg_QalWotmQJIHep9OScWIo7Wu9pnYxM4RqQxJnr3BE4kh4y_Q=="
|
||||||
|
|
||||||
|
finish, c := newTestClientCallbacks(t,
|
||||||
|
func(tb testing.TB, w http.ResponseWriter, req *http.Request) string {
|
||||||
|
r.NoError(checkMethodAndPath(req, "GET", "/mail/v4/messages/"+testID))
|
||||||
|
|
||||||
|
return "/messages/get_response.json"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
defer finish()
|
||||||
|
|
||||||
|
msg, err := c.GetMessage(context.Background(), testID)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(testID, msg.ID)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,42 +17,42 @@ import (
|
|||||||
logrus "github.com/sirupsen/logrus"
|
logrus "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockClient is a mock of Client interface
|
// MockClient is a mock of Client interface.
|
||||||
type MockClient struct {
|
type MockClient struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockClientMockRecorder
|
recorder *MockClientMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockClientMockRecorder is the mock recorder for MockClient
|
// MockClientMockRecorder is the mock recorder for MockClient.
|
||||||
type MockClientMockRecorder struct {
|
type MockClientMockRecorder struct {
|
||||||
mock *MockClient
|
mock *MockClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockClient creates a new mock instance
|
// NewMockClient creates a new mock instance.
|
||||||
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
func NewMockClient(ctrl *gomock.Controller) *MockClient {
|
||||||
mock := &MockClient{ctrl: ctrl}
|
mock := &MockClient{ctrl: ctrl}
|
||||||
mock.recorder = &MockClientMockRecorder{mock}
|
mock.recorder = &MockClientMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAuthRefreshHandler mocks base method
|
// AddAuthRefreshHandler mocks base method.
|
||||||
func (m *MockClient) AddAuthRefreshHandler(arg0 pmapi.AuthRefreshHandler) {
|
func (m *MockClient) AddAuthRefreshHandler(arg0 pmapi.AuthRefreshHandler) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "AddAuthRefreshHandler", arg0)
|
m.ctrl.Call(m, "AddAuthRefreshHandler", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddAuthRefreshHandler indicates an expected call of AddAuthRefreshHandler
|
// AddAuthRefreshHandler indicates an expected call of AddAuthRefreshHandler.
|
||||||
func (mr *MockClientMockRecorder) AddAuthRefreshHandler(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) AddAuthRefreshHandler(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAuthRefreshHandler", reflect.TypeOf((*MockClient)(nil).AddAuthRefreshHandler), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAuthRefreshHandler", reflect.TypeOf((*MockClient)(nil).AddAuthRefreshHandler), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addresses mocks base method
|
// Addresses mocks base method.
|
||||||
func (m *MockClient) Addresses() pmapi.AddressList {
|
func (m *MockClient) Addresses() pmapi.AddressList {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Addresses")
|
ret := m.ctrl.Call(m, "Addresses")
|
||||||
@ -60,13 +60,13 @@ func (m *MockClient) Addresses() pmapi.AddressList {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addresses indicates an expected call of Addresses
|
// Addresses indicates an expected call of Addresses.
|
||||||
func (mr *MockClientMockRecorder) Addresses() *gomock.Call {
|
func (mr *MockClientMockRecorder) Addresses() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockClient)(nil).Addresses))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockClient)(nil).Addresses))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth2FA mocks base method
|
// Auth2FA mocks base method.
|
||||||
func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error {
|
func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Auth2FA", arg0, arg1)
|
ret := m.ctrl.Call(m, "Auth2FA", arg0, arg1)
|
||||||
@ -74,13 +74,13 @@ func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth2FA indicates an expected call of Auth2FA
|
// Auth2FA indicates an expected call of Auth2FA.
|
||||||
func (mr *MockClientMockRecorder) Auth2FA(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) Auth2FA(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth2FA", reflect.TypeOf((*MockClient)(nil).Auth2FA), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth2FA", reflect.TypeOf((*MockClient)(nil).Auth2FA), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthDelete mocks base method
|
// AuthDelete mocks base method.
|
||||||
func (m *MockClient) AuthDelete(arg0 context.Context) error {
|
func (m *MockClient) AuthDelete(arg0 context.Context) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AuthDelete", arg0)
|
ret := m.ctrl.Call(m, "AuthDelete", arg0)
|
||||||
@ -88,13 +88,13 @@ func (m *MockClient) AuthDelete(arg0 context.Context) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthDelete indicates an expected call of AuthDelete
|
// AuthDelete indicates an expected call of AuthDelete.
|
||||||
func (mr *MockClientMockRecorder) AuthDelete(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) AuthDelete(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthDelete", reflect.TypeOf((*MockClient)(nil).AuthDelete), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthDelete", reflect.TypeOf((*MockClient)(nil).AuthDelete), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthSalt mocks base method
|
// AuthSalt mocks base method.
|
||||||
func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) {
|
func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AuthSalt", arg0)
|
ret := m.ctrl.Call(m, "AuthSalt", arg0)
|
||||||
@ -103,13 +103,13 @@ func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthSalt indicates an expected call of AuthSalt
|
// AuthSalt indicates an expected call of AuthSalt.
|
||||||
func (mr *MockClientMockRecorder) AuthSalt(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) AuthSalt(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthSalt", reflect.TypeOf((*MockClient)(nil).AuthSalt), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthSalt", reflect.TypeOf((*MockClient)(nil).AuthSalt), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountMessages mocks base method
|
// CountMessages mocks base method.
|
||||||
func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi.MessagesCount, error) {
|
func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi.MessagesCount, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CountMessages", arg0, arg1)
|
ret := m.ctrl.Call(m, "CountMessages", arg0, arg1)
|
||||||
@ -118,13 +118,13 @@ func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi.
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountMessages indicates an expected call of CountMessages
|
// CountMessages indicates an expected call of CountMessages.
|
||||||
func (mr *MockClientMockRecorder) CountMessages(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) CountMessages(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMessages", reflect.TypeOf((*MockClient)(nil).CountMessages), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMessages", reflect.TypeOf((*MockClient)(nil).CountMessages), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAttachment mocks base method
|
// CreateAttachment mocks base method.
|
||||||
func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachment, arg2, arg3 io.Reader) (*pmapi.Attachment, error) {
|
func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachment, arg2, arg3 io.Reader) (*pmapi.Attachment, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreateAttachment", arg0, arg1, arg2, arg3)
|
ret := m.ctrl.Call(m, "CreateAttachment", arg0, arg1, arg2, arg3)
|
||||||
@ -133,13 +133,13 @@ func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachme
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAttachment indicates an expected call of CreateAttachment
|
// CreateAttachment indicates an expected call of CreateAttachment.
|
||||||
func (mr *MockClientMockRecorder) CreateAttachment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) CreateAttachment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockClient)(nil).CreateAttachment), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockClient)(nil).CreateAttachment), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDraft mocks base method
|
// CreateDraft mocks base method.
|
||||||
func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2 string, arg3 int) (*pmapi.Message, error) {
|
func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2 string, arg3 int) (*pmapi.Message, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreateDraft", arg0, arg1, arg2, arg3)
|
ret := m.ctrl.Call(m, "CreateDraft", arg0, arg1, arg2, arg3)
|
||||||
@ -148,13 +148,13 @@ func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateDraft indicates an expected call of CreateDraft
|
// CreateDraft indicates an expected call of CreateDraft.
|
||||||
func (mr *MockClientMockRecorder) CreateDraft(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) CreateDraft(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraft", reflect.TypeOf((*MockClient)(nil).CreateDraft), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraft", reflect.TypeOf((*MockClient)(nil).CreateDraft), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLabel mocks base method
|
// CreateLabel mocks base method.
|
||||||
func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) {
|
func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CreateLabel", arg0, arg1)
|
ret := m.ctrl.Call(m, "CreateLabel", arg0, arg1)
|
||||||
@ -163,13 +163,13 @@ func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmap
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateLabel indicates an expected call of CreateLabel
|
// CreateLabel indicates an expected call of CreateLabel.
|
||||||
func (mr *MockClientMockRecorder) CreateLabel(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) CreateLabel(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLabel", reflect.TypeOf((*MockClient)(nil).CreateLabel), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLabel", reflect.TypeOf((*MockClient)(nil).CreateLabel), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentUser mocks base method
|
// CurrentUser mocks base method.
|
||||||
func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) {
|
func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "CurrentUser", arg0)
|
ret := m.ctrl.Call(m, "CurrentUser", arg0)
|
||||||
@ -178,13 +178,13 @@ func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentUser indicates an expected call of CurrentUser
|
// CurrentUser indicates an expected call of CurrentUser.
|
||||||
func (mr *MockClientMockRecorder) CurrentUser(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) CurrentUser(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockClient)(nil).CurrentUser), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockClient)(nil).CurrentUser), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptAndVerifyCards mocks base method
|
// DecryptAndVerifyCards mocks base method.
|
||||||
func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, error) {
|
func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DecryptAndVerifyCards", arg0)
|
ret := m.ctrl.Call(m, "DecryptAndVerifyCards", arg0)
|
||||||
@ -193,13 +193,13 @@ func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, err
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards
|
// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards.
|
||||||
func (mr *MockClientMockRecorder) DecryptAndVerifyCards(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) DecryptAndVerifyCards(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptAndVerifyCards", reflect.TypeOf((*MockClient)(nil).DecryptAndVerifyCards), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptAndVerifyCards", reflect.TypeOf((*MockClient)(nil).DecryptAndVerifyCards), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLabel mocks base method
|
// DeleteLabel mocks base method.
|
||||||
func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error {
|
func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DeleteLabel", arg0, arg1)
|
ret := m.ctrl.Call(m, "DeleteLabel", arg0, arg1)
|
||||||
@ -207,13 +207,13 @@ func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLabel indicates an expected call of DeleteLabel
|
// DeleteLabel indicates an expected call of DeleteLabel.
|
||||||
func (mr *MockClientMockRecorder) DeleteLabel(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) DeleteLabel(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLabel", reflect.TypeOf((*MockClient)(nil).DeleteLabel), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLabel", reflect.TypeOf((*MockClient)(nil).DeleteLabel), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMessages mocks base method
|
// DeleteMessages mocks base method.
|
||||||
func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error {
|
func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DeleteMessages", arg0, arg1)
|
ret := m.ctrl.Call(m, "DeleteMessages", arg0, arg1)
|
||||||
@ -221,13 +221,13 @@ func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMessages indicates an expected call of DeleteMessages
|
// DeleteMessages indicates an expected call of DeleteMessages.
|
||||||
func (mr *MockClientMockRecorder) DeleteMessages(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) DeleteMessages(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessages", reflect.TypeOf((*MockClient)(nil).DeleteMessages), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessages", reflect.TypeOf((*MockClient)(nil).DeleteMessages), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmptyFolder mocks base method
|
// EmptyFolder mocks base method.
|
||||||
func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error {
|
func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "EmptyFolder", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "EmptyFolder", arg0, arg1, arg2)
|
||||||
@ -235,13 +235,13 @@ func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmptyFolder indicates an expected call of EmptyFolder
|
// EmptyFolder indicates an expected call of EmptyFolder.
|
||||||
func (mr *MockClientMockRecorder) EmptyFolder(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) EmptyFolder(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmptyFolder", reflect.TypeOf((*MockClient)(nil).EmptyFolder), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmptyFolder", reflect.TypeOf((*MockClient)(nil).EmptyFolder), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddresses mocks base method
|
// GetAddresses mocks base method.
|
||||||
func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, error) {
|
func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetAddresses", arg0)
|
ret := m.ctrl.Call(m, "GetAddresses", arg0)
|
||||||
@ -250,13 +250,13 @@ func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, erro
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAddresses indicates an expected call of GetAddresses
|
// GetAddresses indicates an expected call of GetAddresses.
|
||||||
func (mr *MockClientMockRecorder) GetAddresses(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetAddresses(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddresses", reflect.TypeOf((*MockClient)(nil).GetAddresses), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddresses", reflect.TypeOf((*MockClient)(nil).GetAddresses), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAttachment mocks base method
|
// GetAttachment mocks base method.
|
||||||
func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) {
|
func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1)
|
||||||
@ -265,13 +265,13 @@ func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCl
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAttachment indicates an expected call of GetAttachment
|
// GetAttachment indicates an expected call of GetAttachment.
|
||||||
func (mr *MockClientMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockClient)(nil).GetAttachment), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockClient)(nil).GetAttachment), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContactByID mocks base method
|
// GetContactByID mocks base method.
|
||||||
func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Contact, error) {
|
func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Contact, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetContactByID", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetContactByID", arg0, arg1)
|
||||||
@ -280,13 +280,13 @@ func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Co
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContactByID indicates an expected call of GetContactByID
|
// GetContactByID indicates an expected call of GetContactByID.
|
||||||
func (mr *MockClientMockRecorder) GetContactByID(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetContactByID(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactByID", reflect.TypeOf((*MockClient)(nil).GetContactByID), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactByID", reflect.TypeOf((*MockClient)(nil).GetContactByID), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContactEmailByEmail mocks base method
|
// GetContactEmailByEmail mocks base method.
|
||||||
func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, arg2, arg3 int) ([]pmapi.ContactEmail, error) {
|
func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, arg2, arg3 int) ([]pmapi.ContactEmail, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetContactEmailByEmail", arg0, arg1, arg2, arg3)
|
ret := m.ctrl.Call(m, "GetContactEmailByEmail", arg0, arg1, arg2, arg3)
|
||||||
@ -295,13 +295,13 @@ func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, a
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail
|
// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail.
|
||||||
func (mr *MockClientMockRecorder) GetContactEmailByEmail(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetContactEmailByEmail(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactEmailByEmail", reflect.TypeOf((*MockClient)(nil).GetContactEmailByEmail), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactEmailByEmail", reflect.TypeOf((*MockClient)(nil).GetContactEmailByEmail), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEvent mocks base method
|
// GetEvent mocks base method.
|
||||||
func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event, error) {
|
func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetEvent", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetEvent", arg0, arg1)
|
||||||
@ -310,13 +310,13 @@ func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event,
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEvent indicates an expected call of GetEvent
|
// GetEvent indicates an expected call of GetEvent.
|
||||||
func (mr *MockClientMockRecorder) GetEvent(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetEvent(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvent", reflect.TypeOf((*MockClient)(nil).GetEvent), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvent", reflect.TypeOf((*MockClient)(nil).GetEvent), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMailSettings mocks base method
|
// GetMailSettings mocks base method.
|
||||||
func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings, error) {
|
func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetMailSettings", arg0)
|
ret := m.ctrl.Call(m, "GetMailSettings", arg0)
|
||||||
@ -325,13 +325,13 @@ func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings,
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMailSettings indicates an expected call of GetMailSettings
|
// GetMailSettings indicates an expected call of GetMailSettings.
|
||||||
func (mr *MockClientMockRecorder) GetMailSettings(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetMailSettings(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailSettings", reflect.TypeOf((*MockClient)(nil).GetMailSettings), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailSettings", reflect.TypeOf((*MockClient)(nil).GetMailSettings), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage mocks base method
|
// GetMessage mocks base method.
|
||||||
func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) {
|
func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetMessage", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetMessage", arg0, arg1)
|
||||||
@ -340,13 +340,13 @@ func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Messa
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMessage indicates an expected call of GetMessage
|
// GetMessage indicates an expected call of GetMessage.
|
||||||
func (mr *MockClientMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockClient)(nil).GetMessage), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockClient)(nil).GetMessage), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicKeysForEmail mocks base method
|
// GetPublicKeysForEmail mocks base method.
|
||||||
func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([]pmapi.PublicKey, bool, error) {
|
func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([]pmapi.PublicKey, bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetPublicKeysForEmail", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetPublicKeysForEmail", arg0, arg1)
|
||||||
@ -356,13 +356,13 @@ func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([
|
|||||||
return ret0, ret1, ret2
|
return ret0, ret1, ret2
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail
|
// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail.
|
||||||
func (mr *MockClientMockRecorder) GetPublicKeysForEmail(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) GetPublicKeysForEmail(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockClient)(nil).GetPublicKeysForEmail), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockClient)(nil).GetPublicKeysForEmail), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import mocks base method
|
// Import mocks base method.
|
||||||
func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]*pmapi.ImportMsgRes, error) {
|
func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]*pmapi.ImportMsgRes, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Import", arg0, arg1)
|
ret := m.ctrl.Call(m, "Import", arg0, arg1)
|
||||||
@ -371,13 +371,13 @@ func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]*
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import indicates an expected call of Import
|
// Import indicates an expected call of Import.
|
||||||
func (mr *MockClientMockRecorder) Import(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) Import(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockClient)(nil).Import), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockClient)(nil).Import), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUnlocked mocks base method
|
// IsUnlocked mocks base method.
|
||||||
func (m *MockClient) IsUnlocked() bool {
|
func (m *MockClient) IsUnlocked() bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "IsUnlocked")
|
ret := m.ctrl.Call(m, "IsUnlocked")
|
||||||
@ -385,13 +385,13 @@ func (m *MockClient) IsUnlocked() bool {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUnlocked indicates an expected call of IsUnlocked
|
// IsUnlocked indicates an expected call of IsUnlocked.
|
||||||
func (mr *MockClientMockRecorder) IsUnlocked() *gomock.Call {
|
func (mr *MockClientMockRecorder) IsUnlocked() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUnlocked", reflect.TypeOf((*MockClient)(nil).IsUnlocked))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUnlocked", reflect.TypeOf((*MockClient)(nil).IsUnlocked))
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyRingForAddressID mocks base method
|
// KeyRingForAddressID mocks base method.
|
||||||
func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
|
func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
|
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
|
||||||
@ -400,13 +400,13 @@ func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID
|
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID.
|
||||||
func (mr *MockClientMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockClient)(nil).KeyRingForAddressID), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockClient)(nil).KeyRingForAddressID), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelMessages mocks base method
|
// LabelMessages mocks base method.
|
||||||
func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 string) error {
|
func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "LabelMessages", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "LabelMessages", arg0, arg1, arg2)
|
||||||
@ -414,13 +414,13 @@ func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 str
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelMessages indicates an expected call of LabelMessages
|
// LabelMessages indicates an expected call of LabelMessages.
|
||||||
func (mr *MockClientMockRecorder) LabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) LabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LabelMessages", reflect.TypeOf((*MockClient)(nil).LabelMessages), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LabelMessages", reflect.TypeOf((*MockClient)(nil).LabelMessages), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListLabels mocks base method
|
// ListLabels mocks base method.
|
||||||
func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) {
|
func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ListLabels", arg0)
|
ret := m.ctrl.Call(m, "ListLabels", arg0)
|
||||||
@ -429,13 +429,13 @@ func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListLabels indicates an expected call of ListLabels
|
// ListLabels indicates an expected call of ListLabels.
|
||||||
func (mr *MockClientMockRecorder) ListLabels(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) ListLabels(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockClient)(nil).ListLabels), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockClient)(nil).ListLabels), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMessages mocks base method
|
// ListMessages mocks base method.
|
||||||
func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
|
func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ListMessages", arg0, arg1)
|
ret := m.ctrl.Call(m, "ListMessages", arg0, arg1)
|
||||||
@ -445,13 +445,13 @@ func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilt
|
|||||||
return ret0, ret1, ret2
|
return ret0, ret1, ret2
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMessages indicates an expected call of ListMessages
|
// ListMessages indicates an expected call of ListMessages.
|
||||||
func (mr *MockClientMockRecorder) ListMessages(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) ListMessages(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMessages", reflect.TypeOf((*MockClient)(nil).ListMessages), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMessages", reflect.TypeOf((*MockClient)(nil).ListMessages), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkMessagesRead mocks base method
|
// MarkMessagesRead mocks base method.
|
||||||
func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error {
|
func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "MarkMessagesRead", arg0, arg1)
|
ret := m.ctrl.Call(m, "MarkMessagesRead", arg0, arg1)
|
||||||
@ -459,13 +459,13 @@ func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkMessagesRead indicates an expected call of MarkMessagesRead
|
// MarkMessagesRead indicates an expected call of MarkMessagesRead.
|
||||||
func (mr *MockClientMockRecorder) MarkMessagesRead(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) MarkMessagesRead(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesRead", reflect.TypeOf((*MockClient)(nil).MarkMessagesRead), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesRead", reflect.TypeOf((*MockClient)(nil).MarkMessagesRead), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkMessagesUnread mocks base method
|
// MarkMessagesUnread mocks base method.
|
||||||
func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) error {
|
func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "MarkMessagesUnread", arg0, arg1)
|
ret := m.ctrl.Call(m, "MarkMessagesUnread", arg0, arg1)
|
||||||
@ -473,13 +473,13 @@ func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) err
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkMessagesUnread indicates an expected call of MarkMessagesUnread
|
// MarkMessagesUnread indicates an expected call of MarkMessagesUnread.
|
||||||
func (mr *MockClientMockRecorder) MarkMessagesUnread(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) MarkMessagesUnread(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesUnread", reflect.TypeOf((*MockClient)(nil).MarkMessagesUnread), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesUnread", reflect.TypeOf((*MockClient)(nil).MarkMessagesUnread), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadKeys mocks base method
|
// ReloadKeys mocks base method.
|
||||||
func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error {
|
func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ReloadKeys", arg0, arg1)
|
ret := m.ctrl.Call(m, "ReloadKeys", arg0, arg1)
|
||||||
@ -487,13 +487,13 @@ func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReloadKeys indicates an expected call of ReloadKeys
|
// ReloadKeys indicates an expected call of ReloadKeys.
|
||||||
func (mr *MockClientMockRecorder) ReloadKeys(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) ReloadKeys(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadKeys", reflect.TypeOf((*MockClient)(nil).ReloadKeys), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadKeys", reflect.TypeOf((*MockClient)(nil).ReloadKeys), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReorderAddresses mocks base method
|
// ReorderAddresses mocks base method.
|
||||||
func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error {
|
func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ReorderAddresses", arg0, arg1)
|
ret := m.ctrl.Call(m, "ReorderAddresses", arg0, arg1)
|
||||||
@ -501,13 +501,13 @@ func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReorderAddresses indicates an expected call of ReorderAddresses
|
// ReorderAddresses indicates an expected call of ReorderAddresses.
|
||||||
func (mr *MockClientMockRecorder) ReorderAddresses(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) ReorderAddresses(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReorderAddresses", reflect.TypeOf((*MockClient)(nil).ReorderAddresses), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReorderAddresses", reflect.TypeOf((*MockClient)(nil).ReorderAddresses), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessage mocks base method
|
// SendMessage mocks base method.
|
||||||
func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
|
func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SendMessage", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "SendMessage", arg0, arg1, arg2)
|
||||||
@ -517,13 +517,13 @@ func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi.
|
|||||||
return ret0, ret1, ret2
|
return ret0, ret1, ret2
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMessage indicates an expected call of SendMessage
|
// SendMessage indicates an expected call of SendMessage.
|
||||||
func (mr *MockClientMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockClient)(nil).SendMessage), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockClient)(nil).SendMessage), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlabelMessages mocks base method
|
// UnlabelMessages mocks base method.
|
||||||
func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 string) error {
|
func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UnlabelMessages", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "UnlabelMessages", arg0, arg1, arg2)
|
||||||
@ -531,13 +531,13 @@ func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 s
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnlabelMessages indicates an expected call of UnlabelMessages
|
// UnlabelMessages indicates an expected call of UnlabelMessages.
|
||||||
func (mr *MockClientMockRecorder) UnlabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) UnlabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlabelMessages", reflect.TypeOf((*MockClient)(nil).UnlabelMessages), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlabelMessages", reflect.TypeOf((*MockClient)(nil).UnlabelMessages), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock mocks base method
|
// Unlock mocks base method.
|
||||||
func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error {
|
func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Unlock", arg0, arg1)
|
ret := m.ctrl.Call(m, "Unlock", arg0, arg1)
|
||||||
@ -545,13 +545,13 @@ func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock indicates an expected call of Unlock
|
// Unlock indicates an expected call of Unlock.
|
||||||
func (mr *MockClientMockRecorder) Unlock(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) Unlock(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockClient)(nil).Unlock), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockClient)(nil).Unlock), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateLabel mocks base method
|
// UpdateLabel mocks base method.
|
||||||
func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) {
|
func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateLabel", arg0, arg1)
|
ret := m.ctrl.Call(m, "UpdateLabel", arg0, arg1)
|
||||||
@ -560,13 +560,13 @@ func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmap
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateLabel indicates an expected call of UpdateLabel
|
// UpdateLabel indicates an expected call of UpdateLabel.
|
||||||
func (mr *MockClientMockRecorder) UpdateLabel(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) UpdateLabel(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLabel", reflect.TypeOf((*MockClient)(nil).UpdateLabel), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLabel", reflect.TypeOf((*MockClient)(nil).UpdateLabel), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser mocks base method
|
// UpdateUser mocks base method.
|
||||||
func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) {
|
func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "UpdateUser", arg0)
|
ret := m.ctrl.Call(m, "UpdateUser", arg0)
|
||||||
@ -575,72 +575,72 @@ func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) {
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUser indicates an expected call of UpdateUser
|
// UpdateUser indicates an expected call of UpdateUser.
|
||||||
func (mr *MockClientMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
|
func (mr *MockClientMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockClient)(nil).UpdateUser), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockClient)(nil).UpdateUser), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockManager is a mock of Manager interface
|
// MockManager is a mock of Manager interface.
|
||||||
type MockManager struct {
|
type MockManager struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockManagerMockRecorder
|
recorder *MockManagerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockManagerMockRecorder is the mock recorder for MockManager
|
// MockManagerMockRecorder is the mock recorder for MockManager.
|
||||||
type MockManagerMockRecorder struct {
|
type MockManagerMockRecorder struct {
|
||||||
mock *MockManager
|
mock *MockManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockManager creates a new mock instance
|
// NewMockManager creates a new mock instance.
|
||||||
func NewMockManager(ctrl *gomock.Controller) *MockManager {
|
func NewMockManager(ctrl *gomock.Controller) *MockManager {
|
||||||
mock := &MockManager{ctrl: ctrl}
|
mock := &MockManager{ctrl: ctrl}
|
||||||
mock.recorder = &MockManagerMockRecorder{mock}
|
mock.recorder = &MockManagerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
func (m *MockManager) EXPECT() *MockManagerMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddConnectionObserver mocks base method
|
// AddConnectionObserver mocks base method.
|
||||||
func (m *MockManager) AddConnectionObserver(arg0 pmapi.ConnectionObserver) {
|
func (m *MockManager) AddConnectionObserver(arg0 pmapi.ConnectionObserver) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "AddConnectionObserver", arg0)
|
m.ctrl.Call(m, "AddConnectionObserver", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddConnectionObserver indicates an expected call of AddConnectionObserver
|
// AddConnectionObserver indicates an expected call of AddConnectionObserver.
|
||||||
func (mr *MockManagerMockRecorder) AddConnectionObserver(arg0 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) AddConnectionObserver(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddConnectionObserver", reflect.TypeOf((*MockManager)(nil).AddConnectionObserver), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddConnectionObserver", reflect.TypeOf((*MockManager)(nil).AddConnectionObserver), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowProxy mocks base method
|
// AllowProxy mocks base method.
|
||||||
func (m *MockManager) AllowProxy() {
|
func (m *MockManager) AllowProxy() {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "AllowProxy")
|
m.ctrl.Call(m, "AllowProxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllowProxy indicates an expected call of AllowProxy
|
// AllowProxy indicates an expected call of AllowProxy.
|
||||||
func (mr *MockManagerMockRecorder) AllowProxy() *gomock.Call {
|
func (mr *MockManagerMockRecorder) AllowProxy() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockManager)(nil).AllowProxy))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockManager)(nil).AllowProxy))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisallowProxy mocks base method
|
// DisallowProxy mocks base method.
|
||||||
func (m *MockManager) DisallowProxy() {
|
func (m *MockManager) DisallowProxy() {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "DisallowProxy")
|
m.ctrl.Call(m, "DisallowProxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisallowProxy indicates an expected call of DisallowProxy
|
// DisallowProxy indicates an expected call of DisallowProxy.
|
||||||
func (mr *MockManagerMockRecorder) DisallowProxy() *gomock.Call {
|
func (mr *MockManagerMockRecorder) DisallowProxy() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisallowProxy", reflect.TypeOf((*MockManager)(nil).DisallowProxy))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisallowProxy", reflect.TypeOf((*MockManager)(nil).DisallowProxy))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadAndVerify mocks base method
|
// DownloadAndVerify mocks base method.
|
||||||
func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string) ([]byte, error) {
|
func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string) ([]byte, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "DownloadAndVerify", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "DownloadAndVerify", arg0, arg1, arg2)
|
||||||
@ -649,13 +649,13 @@ func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string)
|
|||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadAndVerify indicates an expected call of DownloadAndVerify
|
// DownloadAndVerify indicates an expected call of DownloadAndVerify.
|
||||||
func (mr *MockManagerMockRecorder) DownloadAndVerify(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) DownloadAndVerify(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadAndVerify", reflect.TypeOf((*MockManager)(nil).DownloadAndVerify), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadAndVerify", reflect.TypeOf((*MockManager)(nil).DownloadAndVerify), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient mocks base method
|
// NewClient mocks base method.
|
||||||
func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.Client {
|
func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.Client {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "NewClient", arg0, arg1, arg2, arg3)
|
ret := m.ctrl.Call(m, "NewClient", arg0, arg1, arg2, arg3)
|
||||||
@ -663,14 +663,14 @@ func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.C
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient indicates an expected call of NewClient
|
// NewClient indicates an expected call of NewClient.
|
||||||
func (mr *MockManagerMockRecorder) NewClient(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) NewClient(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClient", reflect.TypeOf((*MockManager)(nil).NewClient), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClient", reflect.TypeOf((*MockManager)(nil).NewClient), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithLogin mocks base method
|
// NewClientWithLogin mocks base method.
|
||||||
func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1, arg2 string) (pmapi.Client, *pmapi.Auth, error) {
|
func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1 string, arg2 []byte) (pmapi.Client, *pmapi.Auth, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "NewClientWithLogin", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "NewClientWithLogin", arg0, arg1, arg2)
|
||||||
ret0, _ := ret[0].(pmapi.Client)
|
ret0, _ := ret[0].(pmapi.Client)
|
||||||
@ -679,13 +679,13 @@ func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1, arg2 string
|
|||||||
return ret0, ret1, ret2
|
return ret0, ret1, ret2
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithLogin indicates an expected call of NewClientWithLogin
|
// NewClientWithLogin indicates an expected call of NewClientWithLogin.
|
||||||
func (mr *MockManagerMockRecorder) NewClientWithLogin(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) NewClientWithLogin(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithLogin", reflect.TypeOf((*MockManager)(nil).NewClientWithLogin), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithLogin", reflect.TypeOf((*MockManager)(nil).NewClientWithLogin), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithRefresh mocks base method
|
// NewClientWithRefresh mocks base method.
|
||||||
func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 string) (pmapi.Client, *pmapi.AuthRefresh, error) {
|
func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 string) (pmapi.Client, *pmapi.AuthRefresh, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "NewClientWithRefresh", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "NewClientWithRefresh", arg0, arg1, arg2)
|
||||||
@ -695,13 +695,13 @@ func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 stri
|
|||||||
return ret0, ret1, ret2
|
return ret0, ret1, ret2
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientWithRefresh indicates an expected call of NewClientWithRefresh
|
// NewClientWithRefresh indicates an expected call of NewClientWithRefresh.
|
||||||
func (mr *MockManagerMockRecorder) NewClientWithRefresh(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) NewClientWithRefresh(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithRefresh", reflect.TypeOf((*MockManager)(nil).NewClientWithRefresh), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithRefresh", reflect.TypeOf((*MockManager)(nil).NewClientWithRefresh), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportBug mocks base method
|
// ReportBug mocks base method.
|
||||||
func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) error {
|
func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "ReportBug", arg0, arg1)
|
ret := m.ctrl.Call(m, "ReportBug", arg0, arg1)
|
||||||
@ -709,13 +709,13 @@ func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) e
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportBug indicates an expected call of ReportBug
|
// ReportBug indicates an expected call of ReportBug.
|
||||||
func (mr *MockManagerMockRecorder) ReportBug(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) ReportBug(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBug", reflect.TypeOf((*MockManager)(nil).ReportBug), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBug", reflect.TypeOf((*MockManager)(nil).ReportBug), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendSimpleMetric mocks base method
|
// SendSimpleMetric mocks base method.
|
||||||
func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 string) error {
|
func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 string) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SendSimpleMetric", arg0, arg1, arg2, arg3)
|
ret := m.ctrl.Call(m, "SendSimpleMetric", arg0, arg1, arg2, arg3)
|
||||||
@ -723,55 +723,55 @@ func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 st
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendSimpleMetric indicates an expected call of SendSimpleMetric
|
// SendSimpleMetric indicates an expected call of SendSimpleMetric.
|
||||||
func (mr *MockManagerMockRecorder) SendSimpleMetric(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) SendSimpleMetric(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSimpleMetric", reflect.TypeOf((*MockManager)(nil).SendSimpleMetric), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSimpleMetric", reflect.TypeOf((*MockManager)(nil).SendSimpleMetric), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCookieJar mocks base method
|
// SetCookieJar mocks base method.
|
||||||
func (m *MockManager) SetCookieJar(arg0 http.CookieJar) {
|
func (m *MockManager) SetCookieJar(arg0 http.CookieJar) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetCookieJar", arg0)
|
m.ctrl.Call(m, "SetCookieJar", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCookieJar indicates an expected call of SetCookieJar
|
// SetCookieJar indicates an expected call of SetCookieJar.
|
||||||
func (mr *MockManagerMockRecorder) SetCookieJar(arg0 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) SetCookieJar(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCookieJar", reflect.TypeOf((*MockManager)(nil).SetCookieJar), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCookieJar", reflect.TypeOf((*MockManager)(nil).SetCookieJar), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogging mocks base method
|
// SetLogging mocks base method.
|
||||||
func (m *MockManager) SetLogging(arg0 *logrus.Entry, arg1 bool) {
|
func (m *MockManager) SetLogging(arg0 *logrus.Entry, arg1 bool) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetLogging", arg0, arg1)
|
m.ctrl.Call(m, "SetLogging", arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogging indicates an expected call of SetLogging
|
// SetLogging indicates an expected call of SetLogging.
|
||||||
func (mr *MockManagerMockRecorder) SetLogging(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) SetLogging(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogging", reflect.TypeOf((*MockManager)(nil).SetLogging), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogging", reflect.TypeOf((*MockManager)(nil).SetLogging), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRetryCount mocks base method
|
// SetRetryCount mocks base method.
|
||||||
func (m *MockManager) SetRetryCount(arg0 int) {
|
func (m *MockManager) SetRetryCount(arg0 int) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetRetryCount", arg0)
|
m.ctrl.Call(m, "SetRetryCount", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRetryCount indicates an expected call of SetRetryCount
|
// SetRetryCount indicates an expected call of SetRetryCount.
|
||||||
func (mr *MockManagerMockRecorder) SetRetryCount(arg0 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) SetRetryCount(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRetryCount", reflect.TypeOf((*MockManager)(nil).SetRetryCount), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRetryCount", reflect.TypeOf((*MockManager)(nil).SetRetryCount), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTransport mocks base method
|
// SetTransport mocks base method.
|
||||||
func (m *MockManager) SetTransport(arg0 http.RoundTripper) {
|
func (m *MockManager) SetTransport(arg0 http.RoundTripper) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
m.ctrl.Call(m, "SetTransport", arg0)
|
m.ctrl.Call(m, "SetTransport", arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTransport indicates an expected call of SetTransport
|
// SetTransport indicates an expected call of SetTransport.
|
||||||
func (mr *MockManagerMockRecorder) SetTransport(arg0 interface{}) *gomock.Call {
|
func (mr *MockManagerMockRecorder) SetTransport(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransport", reflect.TypeOf((*MockManager)(nil).SetTransport), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransport", reflect.TypeOf((*MockManager)(nil).SetTransport), arg0)
|
||||||
|
|||||||
@ -20,13 +20,14 @@ package pmapi
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/jameskeane/bcrypt"
|
"github.com/ProtonMail/go-srp"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HashMailboxPassword(password, salt string) ([]byte, error) {
|
// HashMailboxPassword expectects 128bit long salt encoded by standard base64.
|
||||||
|
func HashMailboxPassword(password []byte, salt string) ([]byte, error) {
|
||||||
if salt == "" {
|
if salt == "" {
|
||||||
return []byte(password), nil
|
return password, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedSalt, err := base64.StdEncoding.DecodeString(salt)
|
decodedSalt, err := base64.StdEncoding.DecodeString(salt)
|
||||||
@ -34,15 +35,10 @@ func HashMailboxPassword(password, salt string) ([]byte, error) {
|
|||||||
return nil, errors.Wrap(err, "failed to decode salt")
|
return nil, errors.Wrap(err, "failed to decode salt")
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedSalt := base64.NewEncoding("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").WithPadding(base64.NoPadding).EncodeToString(decodedSalt)
|
hash, err := srp.MailboxPassword(password, decodedSalt)
|
||||||
hashResult, err := bcrypt.Hash(password, "$2y$10$"+encodedSalt)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to bcrypt-hash password")
|
return nil, errors.Wrap(err, "failed to hash password")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hashResult) != 60 {
|
return hash[len(hash)-31:], nil
|
||||||
return nil, errors.New("pmapi: invalid mailbox password hash")
|
|
||||||
}
|
|
||||||
|
|
||||||
return []byte(hashResult[len(hashResult)-31:]), nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,38 +15,30 @@
|
|||||||
// 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package imap
|
package pmapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/serverutil/mocks"
|
|
||||||
imapserver "github.com/emersion/go-imap/server"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIMAPServerTurnOffAndOnAgain(t *testing.T) {
|
func TestMailboxPassword(t *testing.T) {
|
||||||
|
// wantHash was generated with passprase and salt defined below. It
|
||||||
|
// should not change when changing implementation of the function.
|
||||||
|
wantHash := []byte("B5nwpsJQSTJ16ldr64Vdq6oeCCn32Fi")
|
||||||
|
|
||||||
|
// Valid salt is 128bit long (16bytes)
|
||||||
|
// $echo aaaabbbbccccdddd | base64
|
||||||
|
salt := "YWFhYWJiYmJjY2NjZGRkZAo="
|
||||||
|
|
||||||
|
passphrase := []byte("random")
|
||||||
|
|
||||||
r := require.New(t)
|
r := require.New(t)
|
||||||
ts := mocks.NewTestServer(12345)
|
_, err := HashMailboxPassword(passphrase, "badsalt")
|
||||||
|
r.Error(err)
|
||||||
|
|
||||||
server := imapserver.New(nil)
|
haveHash, err := HashMailboxPassword(passphrase, salt)
|
||||||
server.Addr = fmt.Sprintf("%v:%v", bridge.Host, ts.WantPort)
|
r.NoError(err)
|
||||||
|
r.Equal(wantHash, haveHash)
|
||||||
s := &imapServer{
|
|
||||||
panicHandler: ts.PanicHandler,
|
|
||||||
server: server,
|
|
||||||
port: ts.WantPort,
|
|
||||||
eventListener: ts.EventListener,
|
|
||||||
userAgent: useragent.New(),
|
|
||||||
}
|
|
||||||
s.isRunning.Store(false)
|
|
||||||
|
|
||||||
r.True(ts.IsPortFree())
|
|
||||||
|
|
||||||
go s.ListenAndServe()
|
|
||||||
ts.RunServerTests(r)
|
|
||||||
}
|
}
|
||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -71,6 +72,24 @@ func (m *manager) catchAPIError(_ *resty.Client, res *resty.Response) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateTime(_ *resty.Client, res *resty.Response) error {
|
||||||
|
if date, err := time.Parse(time.RFC1123, res.Header().Get("Date")); err != nil {
|
||||||
|
log.WithError(err).Warning("Cannot parse header date")
|
||||||
|
} else {
|
||||||
|
crypto.UpdateTime(date.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func logConnReuse(_ *resty.Client, res *resty.Response) error {
|
||||||
|
if !res.Request.TraceInfo().IsConnReused {
|
||||||
|
logrus.WithField("host", res.Request.URL).Trace("Connection was NOT reused")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func catchRetryAfter(_ *resty.Client, res *resty.Response) (time.Duration, error) {
|
func catchRetryAfter(_ *resty.Client, res *resty.Response) (time.Duration, error) {
|
||||||
if res.StatusCode() == http.StatusTooManyRequests {
|
if res.StatusCode() == http.StatusTooManyRequests {
|
||||||
if after := res.Header().Get("Retry-After"); after != "" {
|
if after := res.Header().Get("Retry-After"); after != "" {
|
||||||
|
|||||||
@ -42,9 +42,11 @@
|
|||||||
"MIMEType": "text/plain",
|
"MIMEType": "text/plain",
|
||||||
"KeyPackets": "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==",
|
"KeyPackets": "wcBMA0fcZ7XLgmf2AQgAiRsOlnm1kSB4/lr7tYe6pBsRGn10GqwUhrwU5PMKOHdCgnO12jO3y3CzP0Yl/jGhAYja9wLDqH8X0sk3tY32u4Sb1Qe5IuzggAiCa4dwOJj5gEFMTHMzjIMPHR7A70XqUxMhmILye8V4KRm/j4c1sxbzA1rM3lYBumQuB5l/ck0Kgt4ZqxHVXHK5Q1l65FHhSXRj8qnunasHa30TYNzP8nmBA8BinnJxpiQ7FGc2umnUhgkFtjm5ixu9vyjr9ukwDTbwAXXfmY+o7tK7kqIXJcmTL6k2UeC6Mz1AagQtRCRtU+bv/3zGojq/trZo9lom3naIeQYa36Ketmcpj2Qwjg==",
|
||||||
"Headers": {
|
"Headers": {
|
||||||
"content-description": "You'll never believe what's in this text file"
|
"content-description": "attachment",
|
||||||
|
"x-pm-incorporated":"1",
|
||||||
|
"x-pm-notes": ["You'll never believe", "what's in this text file"]
|
||||||
},
|
},
|
||||||
"MessageID": "h3CD-DT7rLoAw1vmpcajvIPAl-wwDfXR2MHtWID3wuQURDBKTiGUAwd6E2WBbS44QQKeXImW-axm6X0hAfcVCA=="
|
"MessageID": "AeUizgtA3H44qRgcr-HdBApwLiUhlQg5kB81mg_QalWotmQJIHep9OScWIo7Wu9pnYxM4RqQxJnr3BE4kh4y_Q=="
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"LabelIDs": [
|
"LabelIDs": [
|
||||||
|
|||||||
107
pkg/srp/hash.go
107
pkg/srp/hash.go
@ -1,107 +0,0 @@
|
|||||||
// Copyright (c) 2021 Proton Technologies AG
|
|
||||||
//
|
|
||||||
// This file is part of ProtonMail Bridge.
|
|
||||||
//
|
|
||||||
// ProtonMail 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.
|
|
||||||
//
|
|
||||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package srp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5" //nolint[gosec]
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jameskeane/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BCryptHash function bcrypt algorithm to hash password with salt.
|
|
||||||
func BCryptHash(password string, salt string) (string, error) {
|
|
||||||
return bcrypt.Hash(password, salt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpandHash extends the byte data for SRP flow.
|
|
||||||
func ExpandHash(data []byte) []byte {
|
|
||||||
part0 := sha512.Sum512(append(data, 0))
|
|
||||||
part1 := sha512.Sum512(append(data, 1))
|
|
||||||
part2 := sha512.Sum512(append(data, 2))
|
|
||||||
part3 := sha512.Sum512(append(data, 3))
|
|
||||||
return bytes.Join([][]byte{
|
|
||||||
part0[:],
|
|
||||||
part1[:],
|
|
||||||
part2[:],
|
|
||||||
part3[:],
|
|
||||||
}, []byte{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashPassword returns the hash of password argument. Based on version number
|
|
||||||
// following arguments are used in addition to password:
|
|
||||||
// * 0, 1, 2: userName and modulus
|
|
||||||
// * 3, 4: salt and modulus.
|
|
||||||
func HashPassword(authVersion int, password, userName string, salt, modulus []byte) ([]byte, error) {
|
|
||||||
switch authVersion {
|
|
||||||
case 4, 3:
|
|
||||||
return hashPasswordVersion3(password, salt, modulus)
|
|
||||||
case 2:
|
|
||||||
return hashPasswordVersion2(password, userName, modulus)
|
|
||||||
case 1:
|
|
||||||
return hashPasswordVersion1(password, userName, modulus)
|
|
||||||
case 0:
|
|
||||||
return hashPasswordVersion0(password, userName, modulus)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("pmapi: unsupported auth version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanUserName returns the input string in lower-case without characters `_`,
|
|
||||||
// `.` and `-`.
|
|
||||||
func CleanUserName(userName string) string {
|
|
||||||
userName = strings.ReplaceAll(userName, "-", "")
|
|
||||||
userName = strings.ReplaceAll(userName, ".", "")
|
|
||||||
userName = strings.ReplaceAll(userName, "_", "")
|
|
||||||
return strings.ToLower(userName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPasswordVersion3(password string, salt, modulus []byte) (res []byte, err error) {
|
|
||||||
encodedSalt := base64.NewEncoding("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789").WithPadding(base64.NoPadding).EncodeToString(append(salt, []byte("proton")...))
|
|
||||||
crypted, err := BCryptHash(password, "$2y$10$"+encodedSalt)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExpandHash(append([]byte(crypted), modulus...)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPasswordVersion2(password, userName string, modulus []byte) (res []byte, err error) {
|
|
||||||
return hashPasswordVersion1(password, CleanUserName(userName), modulus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPasswordVersion1(password, userName string, modulus []byte) (res []byte, err error) {
|
|
||||||
prehashed := md5.Sum([]byte(strings.ToLower(userName))) //nolint[gosec]
|
|
||||||
encodedSalt := hex.EncodeToString(prehashed[:])
|
|
||||||
crypted, err := BCryptHash(password, "$2y$10$"+encodedSalt)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return ExpandHash(append([]byte(crypted), modulus...)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPasswordVersion0(password, userName string, modulus []byte) (res []byte, err error) {
|
|
||||||
prehashed := sha512.Sum512([]byte(password))
|
|
||||||
return hashPasswordVersion1(base64.StdEncoding.EncodeToString(prehashed[:]), userName, modulus)
|
|
||||||
}
|
|
||||||
219
pkg/srp/srp.go
219
pkg/srp/srp.go
@ -1,219 +0,0 @@
|
|||||||
// Copyright (c) 2021 Proton Technologies AG
|
|
||||||
//
|
|
||||||
// This file is part of ProtonMail Bridge.
|
|
||||||
//
|
|
||||||
// ProtonMail 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.
|
|
||||||
//
|
|
||||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package srp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/openpgp"
|
|
||||||
"golang.org/x/crypto/openpgp/clearsign"
|
|
||||||
)
|
|
||||||
|
|
||||||
//nolint[gochecknoglobals]
|
|
||||||
var (
|
|
||||||
ErrDataAfterModulus = errors.New("pm-srp: extra data after modulus")
|
|
||||||
ErrInvalidSignature = errors.New("pm-srp: invalid modulus signature")
|
|
||||||
RandReader = rand.Reader
|
|
||||||
)
|
|
||||||
|
|
||||||
// Store random reader in a variable to be able to overwrite it in tests
|
|
||||||
|
|
||||||
// Amored pubkey for modulus verification.
|
|
||||||
const modulusPubkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
xjMEXAHLgxYJKwYBBAHaRw8BAQdAFurWXXwjTemqjD7CXjXVyKf0of7n9Ctm
|
|
||||||
L8v9enkzggHNEnByb3RvbkBzcnAubW9kdWx1c8J3BBAWCgApBQJcAcuDBgsJ
|
|
||||||
BwgDAgkQNQWFxOlRjyYEFQgKAgMWAgECGQECGwMCHgEAAPGRAP9sauJsW12U
|
|
||||||
MnTQUZpsbJb53d0Wv55mZIIiJL2XulpWPQD/V6NglBd96lZKBmInSXX/kXat
|
|
||||||
Sv+y0io+LR8i2+jV+AbOOARcAcuDEgorBgEEAZdVAQUBAQdAeJHUz1c9+KfE
|
|
||||||
kSIgcBRE3WuXC4oj5a2/U3oASExGDW4DAQgHwmEEGBYIABMFAlwBy4MJEDUF
|
|
||||||
hcTpUY8mAhsMAAD/XQD8DxNI6E78meodQI+wLsrKLeHn32iLvUqJbVDhfWSU
|
|
||||||
WO4BAMcm1u02t4VKw++ttECPt+HUgPUq5pqQWe5Q2cW4TMsE
|
|
||||||
=Y4Mw
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----`
|
|
||||||
|
|
||||||
// ReadClearSignedMessage reads the clear text from signed message and verifies
|
|
||||||
// signature. There must be no data appended after signed message in input string.
|
|
||||||
// The message must be sign by key corresponding to `modulusPubkey`.
|
|
||||||
func ReadClearSignedMessage(signedMessage string) (string, error) {
|
|
||||||
modulusBlock, rest := clearsign.Decode([]byte(signedMessage))
|
|
||||||
if len(rest) != 0 {
|
|
||||||
return "", ErrDataAfterModulus
|
|
||||||
}
|
|
||||||
|
|
||||||
modulusKeyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader([]byte(modulusPubkey)))
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New("pm-srp: can not read modulus pubkey")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = openpgp.CheckDetachedSignature(modulusKeyring, bytes.NewReader(modulusBlock.Bytes), modulusBlock.ArmoredSignature.Body, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", ErrInvalidSignature
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(modulusBlock.Bytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SrpProofs object.
|
|
||||||
type SrpProofs struct { //nolint[golint]
|
|
||||||
ClientProof, ClientEphemeral, ExpectedServerProof []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// SrpAuth stores byte data for the calculation of SRP proofs.
|
|
||||||
type SrpAuth struct { //nolint[golint]
|
|
||||||
Modulus, ServerEphemeral, HashedPassword []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSrpAuth creates new SrpAuth from strings input. Salt and server ephemeral are in
|
|
||||||
// base64 format. Modulus is base64 with signature attached. The signature is
|
|
||||||
// verified against server key. The version controls password hash algorithm.
|
|
||||||
func NewSrpAuth(version int, username, password, salt, signedModulus, serverEphemeral string) (auth *SrpAuth, err error) {
|
|
||||||
data := &SrpAuth{}
|
|
||||||
|
|
||||||
// Modulus
|
|
||||||
var modulus string
|
|
||||||
modulus, err = ReadClearSignedMessage(signedModulus)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data.Modulus, err = base64.StdEncoding.DecodeString(modulus)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password
|
|
||||||
var decodedSalt []byte
|
|
||||||
if version >= 3 {
|
|
||||||
decodedSalt, err = base64.StdEncoding.DecodeString(salt)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.HashedPassword, err = HashPassword(version, password, username, decodedSalt, data.Modulus)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server ephermeral
|
|
||||||
data.ServerEphemeral, err = base64.StdEncoding.DecodeString(serverEphemeral)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSrpProofs calculates SPR proofs.
|
|
||||||
func (s *SrpAuth) GenerateSrpProofs(length int) (res *SrpProofs, err error) { //nolint[funlen]
|
|
||||||
toInt := func(arr []byte) *big.Int {
|
|
||||||
var reversed = make([]byte, len(arr))
|
|
||||||
for i := 0; i < len(arr); i++ {
|
|
||||||
reversed[len(arr)-i-1] = arr[i]
|
|
||||||
}
|
|
||||||
return big.NewInt(0).SetBytes(reversed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fromInt := func(num *big.Int) []byte {
|
|
||||||
var arr = num.Bytes()
|
|
||||||
var reversed = make([]byte, length/8)
|
|
||||||
for i := 0; i < len(arr); i++ {
|
|
||||||
reversed[len(arr)-i-1] = arr[i]
|
|
||||||
}
|
|
||||||
return reversed
|
|
||||||
}
|
|
||||||
|
|
||||||
generator := big.NewInt(2)
|
|
||||||
multiplier := toInt(ExpandHash(append(fromInt(generator), s.Modulus...)))
|
|
||||||
|
|
||||||
modulus := toInt(s.Modulus)
|
|
||||||
serverEphemeral := toInt(s.ServerEphemeral)
|
|
||||||
hashedPassword := toInt(s.HashedPassword)
|
|
||||||
|
|
||||||
modulusMinusOne := big.NewInt(0).Sub(modulus, big.NewInt(1))
|
|
||||||
|
|
||||||
if modulus.BitLen() != length {
|
|
||||||
return nil, errors.New("pm-srp: SRP modulus has incorrect size")
|
|
||||||
}
|
|
||||||
|
|
||||||
multiplier = multiplier.Mod(multiplier, modulus)
|
|
||||||
|
|
||||||
if multiplier.Cmp(big.NewInt(1)) <= 0 || multiplier.Cmp(modulusMinusOne) >= 0 {
|
|
||||||
return nil, errors.New("pm-srp: SRP multiplier is out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if generator.Cmp(big.NewInt(1)) <= 0 || generator.Cmp(modulusMinusOne) >= 0 {
|
|
||||||
return nil, errors.New("pm-srp: SRP generator is out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverEphemeral.Cmp(big.NewInt(1)) <= 0 || serverEphemeral.Cmp(modulusMinusOne) >= 0 {
|
|
||||||
return nil, errors.New("pm-srp: SRP server ephemeral is out of bounds")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check primality
|
|
||||||
// Doing exponentiation here is faster than a full call to ProbablyPrime while
|
|
||||||
// still perfectly accurate by Pocklington's theorem
|
|
||||||
if big.NewInt(0).Exp(big.NewInt(2), modulusMinusOne, modulus).Cmp(big.NewInt(1)) != 0 {
|
|
||||||
return nil, errors.New("pm-srp: SRP modulus is not prime")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check safe primality
|
|
||||||
if !big.NewInt(0).Rsh(modulus, 1).ProbablyPrime(10) {
|
|
||||||
return nil, errors.New("pm-srp: SRP modulus is not a safe prime")
|
|
||||||
}
|
|
||||||
|
|
||||||
var clientSecret, clientEphemeral, scramblingParam *big.Int
|
|
||||||
for {
|
|
||||||
for {
|
|
||||||
clientSecret, err = rand.Int(RandReader, modulusMinusOne)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientSecret.Cmp(big.NewInt(int64(length*2))) > 0 { // Very likely
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clientEphemeral = big.NewInt(0).Exp(generator, clientSecret, modulus)
|
|
||||||
scramblingParam = toInt(ExpandHash(append(fromInt(clientEphemeral), fromInt(serverEphemeral)...)))
|
|
||||||
if scramblingParam.Cmp(big.NewInt(0)) != 0 { // Very likely
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subtracted := big.NewInt(0).Sub(serverEphemeral, big.NewInt(0).Mod(big.NewInt(0).Mul(big.NewInt(0).Exp(generator, hashedPassword, modulus), multiplier), modulus))
|
|
||||||
if subtracted.Cmp(big.NewInt(0)) < 0 {
|
|
||||||
subtracted.Add(subtracted, modulus)
|
|
||||||
}
|
|
||||||
exponent := big.NewInt(0).Mod(big.NewInt(0).Add(big.NewInt(0).Mul(scramblingParam, hashedPassword), clientSecret), modulusMinusOne)
|
|
||||||
sharedSession := big.NewInt(0).Exp(subtracted, exponent, modulus)
|
|
||||||
|
|
||||||
clientProof := ExpandHash(bytes.Join([][]byte{fromInt(clientEphemeral), fromInt(serverEphemeral), fromInt(sharedSession)}, []byte{}))
|
|
||||||
serverProof := ExpandHash(bytes.Join([][]byte{fromInt(clientEphemeral), clientProof, fromInt(sharedSession)}, []byte{}))
|
|
||||||
|
|
||||||
return &SrpProofs{ClientEphemeral: fromInt(clientEphemeral), ClientProof: clientProof, ExpectedServerProof: serverProof}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateVerifier verifier for update pwds and create accounts.
|
|
||||||
func (s *SrpAuth) GenerateVerifier(length int) ([]byte, error) {
|
|
||||||
return nil, errors.New("pm-srp: the client doesn't need SRP GenerateVerifier")
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
// Copyright (c) 2021 Proton Technologies AG
|
|
||||||
//
|
|
||||||
// This file is part of ProtonMail Bridge.
|
|
||||||
//
|
|
||||||
// ProtonMail 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.
|
|
||||||
//
|
|
||||||
// ProtonMail 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 ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package srp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testServerEphemeral = "l13IQSVFBEV0ZZREuRQ4ZgP6OpGiIfIjbSDYQG3Yp39FkT2B/k3n1ZhwqrAdy+qvPPFq/le0b7UDtayoX4aOTJihoRvifas8Hr3icd9nAHqd0TUBbkZkT6Iy6UpzmirCXQtEhvGQIdOLuwvy+vZWh24G2ahBM75dAqwkP961EJMh67/I5PA5hJdQZjdPT5luCyVa7BS1d9ZdmuR0/VCjUOdJbYjgtIH7BQoZs+KacjhUN8gybu+fsycvTK3eC+9mCN2Y6GdsuCMuR3pFB0RF9eKae7cA6RbJfF1bjm0nNfWLXzgKguKBOeF3GEAsnCgK68q82/pq9etiUDizUlUBcA=="
|
|
||||||
testServerProof = "ffYFIhnhZJAflFJr9FfXbtdsBLkDGH+TUR5sj98wg0iVHyIhIVT6BeZD8tZA75tYlz7uYIanswweB3bjrGfITXfxERgQysQSoPUB284cX4VQm1IfTB/9LPma618MH8OULNluXVu2eizPWnvIn9VLXCaIX+38Xd6xOjmCQgfkpJy3Sh3ndikjqNCGWiKyvERVJi0nTmpAbHmcdeEp1K++ZRbebRhm2d018o/u4H2gu+MF39Hx12zMzEGNMwkNkgKSEQYlqmj57S6tW9JuB30zVZFnw6Krftg1QfJR6zCT1/J57OGp0A/7X/lC6Xz/I33eJvXOpG9GCRCbNiozFg9IXQ=="
|
|
||||||
|
|
||||||
testClientProof = "8dQtp6zIeEmu3D93CxPdEiCWiAE86uDmK33EpxyqReMwUrm/bTL+zCkWa/X7QgLNrt2FBAriyROhz5TEONgZq/PqZnBEBym6Rvo708KHu6S4LFdZkVc0+lgi7yQpNhU8bqB0BCqdSWd3Fjd3xbOYgO7/vnFK+p9XQZKwEh2RmGv97XHwoxefoyXK6BB+VVMkELd4vL7vdqBiOBU3ufOlSp+0XBMVltQ4oi5l1y21pzOA9cw5WTPIPMcQHffNFq/rReHYnqbBqiLlSLyw6K0PcVuN3bvr3rVYfdS1CsM/Rv1DzXlBUl39B2j82y6hdyGcTeplGyAnAcu0CimvynKBvQ=="
|
|
||||||
testModulus = "W2z5HBi8RvsfYzZTS7qBaUxxPhsfHJFZpu3Kd6s1JafNrCCH9rfvPLrfuqocxWPgWDH2R8neK7PkNvjxto9TStuY5z7jAzWRvFWN9cQhAKkdWgy0JY6ywVn22+HFpF4cYesHrqFIKUPDMSSIlWjBVmEJZ/MusD44ZT29xcPrOqeZvwtCffKtGAIjLYPZIEbZKnDM1Dm3q2K/xS5h+xdhjnndhsrkwm9U9oyA2wxzSXFL+pdfj2fOdRwuR5nW0J2NFrq3kJjkRmpO/Genq1UW+TEknIWAb6VzJJJA244K/H8cnSx2+nSNZO3bbo6Ys228ruV9A8m6DhxmS+bihN3ttQ=="
|
|
||||||
testModulusClearSign = `-----BEGIN PGP SIGNED MESSAGE-----
|
|
||||||
Hash: SHA256
|
|
||||||
|
|
||||||
W2z5HBi8RvsfYzZTS7qBaUxxPhsfHJFZpu3Kd6s1JafNrCCH9rfvPLrfuqocxWPgWDH2R8neK7PkNvjxto9TStuY5z7jAzWRvFWN9cQhAKkdWgy0JY6ywVn22+HFpF4cYesHrqFIKUPDMSSIlWjBVmEJZ/MusD44ZT29xcPrOqeZvwtCffKtGAIjLYPZIEbZKnDM1Dm3q2K/xS5h+xdhjnndhsrkwm9U9oyA2wxzSXFL+pdfj2fOdRwuR5nW0J2NFrq3kJjkRmpO/Genq1UW+TEknIWAb6VzJJJA244K/H8cnSx2+nSNZO3bbo6Ys228ruV9A8m6DhxmS+bihN3ttQ==
|
|
||||||
-----BEGIN PGP SIGNATURE-----
|
|
||||||
Version: ProtonMail
|
|
||||||
Comment: https://protonmail.com
|
|
||||||
|
|
||||||
wl4EARYIABAFAlwB1j0JEDUFhcTpUY8mAAD8CgEAnsFnF4cF0uSHKkXa1GIa
|
|
||||||
GO86yMV4zDZEZcDSJo0fgr8A/AlupGN9EdHlsrZLmTA1vhIx+rOgxdEff28N
|
|
||||||
kvNM7qIK
|
|
||||||
=q6vu
|
|
||||||
-----END PGP SIGNATURE-----`
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Only for tests, replace the default random reader by something that always
|
|
||||||
// return the same thing
|
|
||||||
RandReader = rand.New(rand.NewSource(42))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadClearSigned(t *testing.T) {
|
|
||||||
cleartext, err := ReadClearSignedMessage(testModulusClearSign)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected no error but have ", err)
|
|
||||||
}
|
|
||||||
if cleartext != testModulus {
|
|
||||||
t.Fatalf("Expected message\n\t'%s'\nbut have\n\t'%s'", testModulus, cleartext)
|
|
||||||
}
|
|
||||||
|
|
||||||
lastChar := len(testModulusClearSign)
|
|
||||||
wrongSignature := testModulusClearSign[:lastChar-100]
|
|
||||||
wrongSignature += "c"
|
|
||||||
wrongSignature += testModulusClearSign[lastChar-99:]
|
|
||||||
_, err = ReadClearSignedMessage(wrongSignature)
|
|
||||||
if err != ErrInvalidSignature {
|
|
||||||
t.Fatal("Expected the ErrInvalidSignature but have ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wrongSignature = testModulusClearSign + "data after modulus"
|
|
||||||
_, err = ReadClearSignedMessage(wrongSignature)
|
|
||||||
if err != ErrDataAfterModulus {
|
|
||||||
t.Fatal("Expected the ErrDataAfterModulus but have ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSRPauth(t *testing.T) {
|
|
||||||
srp, err := NewSrpAuth(4, "bridgetest", "test", "yKlc5/CvObfoiw==", testModulusClearSign, testServerEphemeral)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected no error but have ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proofs, err := srp.GenerateSrpProofs(2048)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected no error but have ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedProof, err := base64.StdEncoding.DecodeString(testServerProof)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected no error but have ", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(proofs.ExpectedServerProof, expectedProof) {
|
|
||||||
t.Fatalf("Expected server proof\n\t'%s'\nbut have\n\t'%s'",
|
|
||||||
testServerProof,
|
|
||||||
base64.StdEncoding.EncodeToString(proofs.ExpectedServerProof),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedProof, err = base64.StdEncoding.DecodeString(testClientProof)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Expected no error but have ", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(proofs.ClientProof, expectedProof) {
|
|
||||||
t.Fatalf("Expected client proof\n\t'%s'\nbut have\n\t'%s'",
|
|
||||||
testClientProof,
|
|
||||||
base64.StdEncoding.EncodeToString(proofs.ClientProof),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user