Merge master into devel
This commit is contained in:
7
Makefile
7
Makefile
@ -294,6 +294,7 @@ LOG?=debug
|
|||||||
LOG_IMAP?=client # client/server/all, or empty to turn it off
|
LOG_IMAP?=client # client/server/all, or empty to turn it off
|
||||||
LOG_SMTP?=--log-smtp # empty to turn it off
|
LOG_SMTP?=--log-smtp # empty to turn it off
|
||||||
RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
||||||
|
RUN_FLAGS_IE?=-m -l=${LOG}
|
||||||
|
|
||||||
run: run-nogui-cli
|
run: run-nogui-cli
|
||||||
|
|
||||||
@ -316,11 +317,11 @@ run-ie-qml-preview:
|
|||||||
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
|
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
|
||||||
|
|
||||||
run-ie:
|
run-ie:
|
||||||
TARGET_CMD=Import-Export $(MAKE) run
|
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run
|
||||||
run-ie-qt:
|
run-ie-qt:
|
||||||
TARGET_CMD=Import-Export $(MAKE) run-qt
|
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-qt
|
||||||
run-ie-nogui:
|
run-ie-nogui:
|
||||||
TARGET_CMD=Import-Export $(MAKE) run-nogui
|
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-nogui
|
||||||
|
|
||||||
clean-frontend-qt:
|
clean-frontend-qt:
|
||||||
$(MAKE) -C internal/frontend/qt -f Makefile.local clean
|
$(MAKE) -C internal/frontend/qt -f Makefile.local clean
|
||||||
|
|||||||
@ -136,6 +136,7 @@ func New( // nolint[funlen]
|
|||||||
if err := logging.Init(logsPath); err != nil {
|
if err := logging.Init(logsPath); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
logging.SetLevel("debug") // Proper level is set later in run.
|
||||||
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
||||||
|
|
||||||
if err := migrateFiles(configName); err != nil {
|
if err := migrateFiles(configName); err != nil {
|
||||||
|
|||||||
@ -29,10 +29,12 @@ import (
|
|||||||
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
|
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
|
||||||
// We can remove this eventually.
|
// We can remove this eventually.
|
||||||
//
|
//
|
||||||
// | entity | old location | new location |
|
// | entity | old location | new location |
|
||||||
// |--------|-------------------------------------------|----------------------------------------|
|
// |-----------|-------------------------------------------|----------------------------------------|
|
||||||
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
|
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
|
||||||
// | c11 | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
|
// | c11 1.5.x | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
|
||||||
|
// | c11 1.6.x | ~/.cache/protonmail/<app>/cache/c11 | ~/.config/protonmail/<app>/cache/c11 |
|
||||||
|
// | updates | ~/.cache/protonmail/<app>/updates | ~/.config/protonmail/<app>/updates |
|
||||||
func migrateFiles(configName string) error {
|
func migrateFiles(configName string) error {
|
||||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -41,43 +43,81 @@ func migrateFiles(configName string) error {
|
|||||||
|
|
||||||
locations := locations.New(locationsProvider, configName)
|
locations := locations.New(locationsProvider, configName)
|
||||||
userCacheDir := locationsProvider.UserCache()
|
userCacheDir := locationsProvider.UserCache()
|
||||||
|
|
||||||
|
if err := migratePrefsFrom15x(locations, userCacheDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := migrateCacheFromBoth15xAnd16x(locations, userCacheDir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := migrateUpdatesFrom16x(locations); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migratePrefsFrom15x(locations *locations.Locations, userCacheDir string) error {
|
||||||
newSettingsDir, err := locations.ProvideSettingsPath()
|
newSettingsDir, err := locations.ProvideSettingsPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := moveIfExists(
|
return moveIfExists(
|
||||||
filepath.Join(userCacheDir, "c11", "prefs.json"),
|
filepath.Join(userCacheDir, "c11", "prefs.json"),
|
||||||
filepath.Join(newSettingsDir, "prefs.json"),
|
filepath.Join(newSettingsDir, "prefs.json"),
|
||||||
); err != nil {
|
)
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
newCacheDir, err := locations.ProvideCachePath()
|
func migrateCacheFromBoth15xAnd16x(locations *locations.Locations, userCacheDir string) error {
|
||||||
|
olderCacheDir := userCacheDir
|
||||||
|
newerCacheDir := locations.GetOldCachePath()
|
||||||
|
latestCacheDir, err := locations.ProvideCachePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration for versions before 1.6.x.
|
||||||
if err := moveIfExists(
|
if err := moveIfExists(
|
||||||
filepath.Join(userCacheDir, "c11"),
|
filepath.Join(olderCacheDir, "c11"),
|
||||||
filepath.Join(newCacheDir, "c11"),
|
filepath.Join(latestCacheDir, "c11"),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// Migration for versions 1.6.x.
|
||||||
|
return moveIfExists(
|
||||||
|
filepath.Join(newerCacheDir, "c11"),
|
||||||
|
filepath.Join(latestCacheDir, "c11"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateUpdatesFrom16x(locations *locations.Locations) error {
|
||||||
|
oldUpdatesPath := locations.GetOldUpdatesPath()
|
||||||
|
// Do not use ProvideUpdatesPath, that creates dir right away.
|
||||||
|
newUpdatesPath := locations.GetUpdatesPath()
|
||||||
|
|
||||||
|
return moveIfExists(oldUpdatesPath, newUpdatesPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveIfExists(source, destination string) error {
|
func moveIfExists(source, destination string) error {
|
||||||
|
l := logrus.WithField("source", source).WithField("destination", destination)
|
||||||
|
|
||||||
if _, err := os.Stat(source); os.IsNotExist(err) {
|
if _, err := os.Stat(source); os.IsNotExist(err) {
|
||||||
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file")
|
l.Debug("No need to migrate file, source doesn't exist")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(destination); !os.IsNotExist(err) {
|
if _, err := os.Stat(destination); !os.IsNotExist(err) {
|
||||||
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file")
|
// Once migrated, files should not stay in source anymore. Therefore
|
||||||
|
// if some files are still in source location but target already exist,
|
||||||
|
// it's suspicious. Could happen by installing new version, then the
|
||||||
|
// old one because of some reason, and then the new one again.
|
||||||
|
// Good to see as warning because it could be a reason why Bridge is
|
||||||
|
// behaving weirdly, like wrong configuration, or db re-sync and so on.
|
||||||
|
l.Warn("No need to migrate file, target already exists")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
l.Info("Migrating files")
|
||||||
return os.Rename(source, destination)
|
return os.Rename(source, destination)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ func (f *frontendCLI) importLocalMessages(c *ishell.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := f.ie.GetLocalImporter(user.GetPrimaryAddress(), path)
|
t, err := f.ie.GetLocalImporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||||
f.transfer(t, err, false, true)
|
f.transfer(t, err, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ func (f *frontendCLI) importRemoteMessages(c *ishell.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := f.ie.GetRemoteImporter(user.GetPrimaryAddress(), username, password, host, port)
|
t, err := f.ie.GetRemoteImporter(user.Username(), user.GetPrimaryAddress(), username, password, host, port)
|
||||||
f.transfer(t, err, false, true)
|
f.transfer(t, err, false, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ func (f *frontendCLI) exportMessagesToEML(c *ishell.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := f.ie.GetEMLExporter(user.GetPrimaryAddress(), path)
|
t, err := f.ie.GetEMLExporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||||
f.transfer(t, err, true, false)
|
f.transfer(t, err, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ func (f *frontendCLI) exportMessagesToMBOX(c *ishell.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := f.ie.GetMBOXExporter(user.GetPrimaryAddress(), path)
|
t, err := f.ie.GetMBOXExporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||||
f.transfer(t, err, true, false)
|
f.transfer(t, err, true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -165,6 +165,7 @@ Column {
|
|||||||
textColor : Style.main.textBlue
|
textColor : Style.main.textBlue
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dialogExport.currentIndex = 0
|
dialogExport.currentIndex = 0
|
||||||
|
dialogExport.account = account
|
||||||
dialogExport.address = account
|
dialogExport.address = account
|
||||||
dialogExport.show()
|
dialogExport.show()
|
||||||
}
|
}
|
||||||
@ -321,6 +322,7 @@ Column {
|
|||||||
textBold: true
|
textBold: true
|
||||||
textColor: Style.main.textBlue
|
textColor: Style.main.textBlue
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
dialogExport.account = account
|
||||||
dialogExport.address = listalias[index]
|
dialogExport.address = listalias[index]
|
||||||
dialogExport.show()
|
dialogExport.show()
|
||||||
}
|
}
|
||||||
@ -339,6 +341,7 @@ Column {
|
|||||||
textBold: true
|
textBold: true
|
||||||
textColor: enabled ? Style.main.textBlue : Style.main.textDisabled
|
textColor: enabled ? Style.main.textBlue : Style.main.textDisabled
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
dialogImport.account = account
|
||||||
dialogImport.address = listalias[index]
|
dialogImport.address = listalias[index]
|
||||||
dialogImport.show()
|
dialogImport.show()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ Dialog {
|
|||||||
|
|
||||||
title : set_title()
|
title : set_title()
|
||||||
|
|
||||||
|
property string account
|
||||||
property string address
|
property string address
|
||||||
property alias finish: finish
|
property alias finish: finish
|
||||||
|
|
||||||
@ -428,7 +429,7 @@ Dialog {
|
|||||||
onTriggered : {
|
onTriggered : {
|
||||||
switch (currentIndex) {
|
switch (currentIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
go.loadStructureForExport(root.address)
|
go.loadStructureForExport(root.account, root.address)
|
||||||
sourceFoldersInput.hasItems = (transferRules.rowCount() > 0)
|
sourceFoldersInput.hasItems = (transferRules.rowCount() > 0)
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
|
|||||||
@ -34,6 +34,7 @@ Dialog {
|
|||||||
|
|
||||||
isDialogBusy: currentIndex==3 || currentIndex==4
|
isDialogBusy: currentIndex==3 || currentIndex==4
|
||||||
|
|
||||||
|
property string account
|
||||||
property string address
|
property string address
|
||||||
property string inputPath : ""
|
property string inputPath : ""
|
||||||
property bool isFromFile : inputEmail.text == "" && root.inputPath != ""
|
property bool isFromFile : inputEmail.text == "" && root.inputPath != ""
|
||||||
@ -1032,6 +1033,7 @@ Dialog {
|
|||||||
root.isFromIMAP,
|
root.isFromIMAP,
|
||||||
root.inputPath,
|
root.inputPath,
|
||||||
inputEmail.text, inputPassword.text, inputServer.text, inputPort.text,
|
inputEmail.text, inputPassword.text, inputServer.text, inputPort.text,
|
||||||
|
root.account,
|
||||||
root.address
|
root.address
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
// Dialog with adding new user
|
// Dialog with adding new user
|
||||||
|
|
||||||
import QtQuick 2.8
|
import QtQuick 2.8
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import ProtonUI 1.0
|
import ProtonUI 1.0
|
||||||
|
|
||||||
@ -83,6 +84,9 @@ StackLayout {
|
|||||||
text : ""
|
text : ""
|
||||||
color: Style.main.textBlue
|
color: Style.main.textBlue
|
||||||
visible: false
|
visible: false
|
||||||
|
width: root.width
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent any action below
|
// prevent any action below
|
||||||
|
|||||||
@ -70,7 +70,8 @@ Dialog {
|
|||||||
id: topSep
|
id: topSep
|
||||||
color : "transparent"
|
color : "transparent"
|
||||||
width : Style.main.dummy
|
width : Style.main.dummy
|
||||||
height : root.height/2 - (dialogNameAndPassword.heightInputs)/2
|
// Hacky hack: +10 is to make title of Dialog bigger so longer error can fit just fine.
|
||||||
|
height : root.height/2 + 10 - (dialogNameAndPassword.heightInputs)/2
|
||||||
}
|
}
|
||||||
|
|
||||||
InputField {
|
InputField {
|
||||||
|
|||||||
@ -29,7 +29,7 @@ const (
|
|||||||
TypeMBOX = "MBOX"
|
TypeMBOX = "MBOX"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *FrontendQt) LoadStructureForExport(addressOrID string) {
|
func (f *FrontendQt) LoadStructureForExport(username, addressOrID string) {
|
||||||
errCode := errUnknownError
|
errCode := errUnknownError
|
||||||
var err error
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -41,7 +41,7 @@ func (f *FrontendQt) LoadStructureForExport(addressOrID string) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if f.transfer, err = f.ie.GetEMLExporter(addressOrID, ""); err != nil {
|
if f.transfer, err = f.ie.GetEMLExporter(username, addressOrID, ""); err != nil {
|
||||||
// The only error can be problem to load PM user and address.
|
// The only error can be problem to load PM user and address.
|
||||||
errCode = errPMLoadFailed
|
errCode = errPMLoadFailed
|
||||||
return
|
return
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// wrapper for QML
|
// wrapper for QML
|
||||||
func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServer, sourcePort, targetAddress string) {
|
func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServer, sourcePort, targetUsername, targetAddress string) {
|
||||||
errCode := errUnknownError
|
errCode := errUnknownError
|
||||||
var err error
|
var err error
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -39,7 +39,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if isFromIMAP {
|
if isFromIMAP {
|
||||||
f.transfer, err = f.ie.GetRemoteImporter(targetAddress, sourceEmail, sourcePassword, sourceServer, sourcePort)
|
f.transfer, err = f.ie.GetRemoteImporter(targetUsername, targetAddress, sourceEmail, sourcePassword, sourceServer, sourcePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, &transfer.ErrIMAPConnection{}):
|
case errors.Is(err, &transfer.ErrIMAPConnection{}):
|
||||||
@ -54,7 +54,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.transfer, err = f.ie.GetLocalImporter(targetAddress, sourcePath)
|
f.transfer, err = f.ie.GetLocalImporter(targetUsername, targetAddress, sourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// The only error can be problem to load PM user and address.
|
// The only error can be problem to load PM user and address.
|
||||||
errCode = errPMLoadFailed
|
errCode = errPMLoadFailed
|
||||||
|
|||||||
@ -77,8 +77,8 @@ type GoQMLInterface struct {
|
|||||||
_ string `property:"credentialsNotRemoved"`
|
_ string `property:"credentialsNotRemoved"`
|
||||||
_ string `property:"versionCheckFailed"`
|
_ string `property:"versionCheckFailed"`
|
||||||
//
|
//
|
||||||
_ func(isAvailable bool) `signal:"setConnectionStatus"`
|
_ func(isAvailable bool) `signal:"setConnectionStatus"`
|
||||||
_ func() `slot:"checkInternet"`
|
_ func() `slot:"checkInternet"`
|
||||||
|
|
||||||
_ func() `slot:"setToRestart"`
|
_ func() `slot:"setToRestart"`
|
||||||
|
|
||||||
@ -108,14 +108,14 @@ type GoQMLInterface struct {
|
|||||||
|
|
||||||
_ func(description, client, address string) bool `slot:"sendBug"`
|
_ func(description, client, address string) bool `slot:"sendBug"`
|
||||||
_ func(address string) bool `slot:"sendImportReport"`
|
_ func(address string) bool `slot:"sendImportReport"`
|
||||||
_ func(address string) `slot:"loadStructureForExport"`
|
_ func(username, address string) `slot:"loadStructureForExport"`
|
||||||
_ func() string `slot:"leastUsedColor"`
|
_ func() string `slot:"leastUsedColor"`
|
||||||
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
|
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
|
||||||
_ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"`
|
_ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"`
|
||||||
_ func(email string, importEncrypted bool) `slot:"startImport"`
|
_ func(email string, importEncrypted bool) `slot:"startImport"`
|
||||||
_ func() `slot:"resetSource"`
|
_ func() `slot:"resetSource"`
|
||||||
|
|
||||||
_ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetAddress string) `slot:"setupAndLoadForImport"`
|
_ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetUsername, targetAddress string) `slot:"setupAndLoadForImport"`
|
||||||
|
|
||||||
_ string `property:"progressInit"`
|
_ string `property:"progressInit"`
|
||||||
|
|
||||||
|
|||||||
@ -114,10 +114,10 @@ func (b *bridgeWrap) GetUser(query string) (User, error) {
|
|||||||
type ImportExporter interface {
|
type ImportExporter interface {
|
||||||
UserManager
|
UserManager
|
||||||
|
|
||||||
GetLocalImporter(string, string) (*transfer.Transfer, error)
|
GetLocalImporter(string, string, string) (*transfer.Transfer, error)
|
||||||
GetRemoteImporter(string, string, string, string, string) (*transfer.Transfer, error)
|
GetRemoteImporter(string, string, string, string, string, string) (*transfer.Transfer, error)
|
||||||
GetEMLExporter(string, string) (*transfer.Transfer, error)
|
GetEMLExporter(string, string, string) (*transfer.Transfer, error)
|
||||||
GetMBOXExporter(string, string) (*transfer.Transfer, error)
|
GetMBOXExporter(string, string, string) (*transfer.Transfer, error)
|
||||||
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
|
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
|
||||||
ReportFile(osType, osVersion, accountName, address string, logdata []byte) error
|
ReportFile(osType, osVersion, accountName, address string, logdata []byte) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -564,7 +564,7 @@ func (im *imapMailbox) writeRelatedPart(p io.Writer, m *pmapi.Message, inlines [
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h := message.GetAttachmentHeader(inline)
|
h := message.GetAttachmentHeader(inline, true)
|
||||||
if p, err = related.CreatePart(h); err != nil {
|
if p, err = related.CreatePart(h); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -738,7 +738,7 @@ func (im *imapMailbox) buildMessageInner(m *pmapi.Message, kr *crypto.KeyRing) (
|
|||||||
defer buf.Reset()
|
defer buf.Reset()
|
||||||
att := atts[idx]
|
att := atts[idx]
|
||||||
|
|
||||||
attachmentHeader := message.GetAttachmentHeader(att)
|
attachmentHeader := message.GetAttachmentHeader(att, true)
|
||||||
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
|
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,9 +118,9 @@ func (ie *ImportExport) ReportFile(osType, osVersion, accountName, address strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetLocalImporter returns transferrer from local EML or MBOX structure to ProtonMail account.
|
// GetLocalImporter returns transferrer from local EML or MBOX structure to ProtonMail account.
|
||||||
func (ie *ImportExport) GetLocalImporter(address, path string) (*transfer.Transfer, error) {
|
func (ie *ImportExport) GetLocalImporter(username, address, path string) (*transfer.Transfer, error) {
|
||||||
source := transfer.NewLocalProvider(path)
|
source := transfer.NewLocalProvider(path)
|
||||||
target, err := ie.getPMAPIProvider(address)
|
target, err := ie.getPMAPIProvider(username, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -132,12 +132,12 @@ func (ie *ImportExport) GetLocalImporter(address, path string) (*transfer.Transf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetRemoteImporter returns transferrer from remote IMAP to ProtonMail account.
|
// GetRemoteImporter returns transferrer from remote IMAP to ProtonMail account.
|
||||||
func (ie *ImportExport) GetRemoteImporter(address, username, password, host, port string) (*transfer.Transfer, error) {
|
func (ie *ImportExport) GetRemoteImporter(username, address, remoteUsername, remotePassword, host, port string) (*transfer.Transfer, error) {
|
||||||
source, err := transfer.NewIMAPProvider(username, password, host, port)
|
source, err := transfer.NewIMAPProvider(remoteUsername, remotePassword, host, port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
target, err := ie.getPMAPIProvider(address)
|
target, err := ie.getPMAPIProvider(username, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -149,8 +149,8 @@ func (ie *ImportExport) GetRemoteImporter(address, username, password, host, por
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetEMLExporter returns transferrer from ProtonMail account to local EML structure.
|
// GetEMLExporter returns transferrer from ProtonMail account to local EML structure.
|
||||||
func (ie *ImportExport) GetEMLExporter(address, path string) (*transfer.Transfer, error) {
|
func (ie *ImportExport) GetEMLExporter(username, address, path string) (*transfer.Transfer, error) {
|
||||||
source, err := ie.getPMAPIProvider(address)
|
source, err := ie.getPMAPIProvider(username, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -163,8 +163,8 @@ func (ie *ImportExport) GetEMLExporter(address, path string) (*transfer.Transfer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetMBOXExporter returns transferrer from ProtonMail account to local MBOX structure.
|
// GetMBOXExporter returns transferrer from ProtonMail account to local MBOX structure.
|
||||||
func (ie *ImportExport) GetMBOXExporter(address, path string) (*transfer.Transfer, error) {
|
func (ie *ImportExport) GetMBOXExporter(username, address, path string) (*transfer.Transfer, error) {
|
||||||
source, err := ie.getPMAPIProvider(address)
|
source, err := ie.getPMAPIProvider(username, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -176,8 +176,8 @@ func (ie *ImportExport) GetMBOXExporter(address, path string) (*transfer.Transfe
|
|||||||
return transfer.New(ie.panicHandler, newExportMetricsManager(ie), logsPath, ie.cache.GetTransferDir(), source, target)
|
return transfer.New(ie.panicHandler, newExportMetricsManager(ie), logsPath, ie.cache.GetTransferDir(), source, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ie *ImportExport) getPMAPIProvider(address string) (*transfer.PMAPIProvider, error) {
|
func (ie *ImportExport) getPMAPIProvider(username, address string) (*transfer.PMAPIProvider, error) {
|
||||||
user, err := ie.Users.GetUser(address)
|
user, err := ie.Users.GetUser(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,8 +32,8 @@ import (
|
|||||||
// On linux:
|
// On linux:
|
||||||
// - settings: ~/.config/protonmail/<app>
|
// - settings: ~/.config/protonmail/<app>
|
||||||
// - logs: ~/.cache/protonmail/<app>/logs
|
// - logs: ~/.cache/protonmail/<app>/logs
|
||||||
// - cache: ~/.cache/protonmail/<app>/cache
|
// - cache: ~/.config/protonmail/<app>/cache
|
||||||
// - updates: ~/.cache/protonmail/<app>/updates
|
// - updates: ~/.config/protonmail/<app>/updates
|
||||||
// - lockfile: ~/.cache/protonmail/<app>/<app>.lock
|
// - lockfile: ~/.cache/protonmail/<app>/<app>.lock
|
||||||
type Locations struct {
|
type Locations struct {
|
||||||
userConfig, userCache string
|
userConfig, userCache string
|
||||||
@ -129,7 +129,7 @@ func (l *Locations) ProvideLogsPath() (string, error) {
|
|||||||
return l.getLogsPath(), nil
|
return l.getLogsPath(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideCachePath returns a location for user cache dirs (e.g. ~/.cache/<company>/<app>/cache).
|
// ProvideCachePath returns a location for user cache dirs (e.g. ~/.config/<company>/<app>/cache).
|
||||||
// It creates it if it doesn't already exist.
|
// It creates it if it doesn't already exist.
|
||||||
func (l *Locations) ProvideCachePath() (string, error) {
|
func (l *Locations) ProvideCachePath() (string, error) {
|
||||||
if err := os.MkdirAll(l.getCachePath(), 0700); err != nil {
|
if err := os.MkdirAll(l.getCachePath(), 0700); err != nil {
|
||||||
@ -139,6 +139,11 @@ func (l *Locations) ProvideCachePath() (string, error) {
|
|||||||
return l.getCachePath(), nil
|
return l.getCachePath(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOldCachePath returns a former location for user cache dirs used for migration scripts only.
|
||||||
|
func (l *Locations) GetOldCachePath() string {
|
||||||
|
return filepath.Join(l.userCache, "cache")
|
||||||
|
}
|
||||||
|
|
||||||
// ProvideUpdatesPath returns a location for update files (e.g. ~/.cache/<company>/<app>/updates).
|
// ProvideUpdatesPath returns a location for update files (e.g. ~/.cache/<company>/<app>/updates).
|
||||||
// It creates it if it doesn't already exist.
|
// It creates it if it doesn't already exist.
|
||||||
func (l *Locations) ProvideUpdatesPath() (string, error) {
|
func (l *Locations) ProvideUpdatesPath() (string, error) {
|
||||||
@ -149,6 +154,16 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
|
|||||||
return l.getUpdatesPath(), nil
|
return l.getUpdatesPath(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUpdatesPath returns a new location for update files used for migration scripts only.
|
||||||
|
func (l *Locations) GetUpdatesPath() string {
|
||||||
|
return l.getUpdatesPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOldUpdatesPath returns a former location for update files used for migration scripts only.
|
||||||
|
func (l *Locations) GetOldUpdatesPath() string {
|
||||||
|
return filepath.Join(l.userCache, "updates")
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Locations) getSettingsPath() string {
|
func (l *Locations) getSettingsPath() string {
|
||||||
return l.userConfig
|
return l.userConfig
|
||||||
}
|
}
|
||||||
@ -158,11 +173,25 @@ func (l *Locations) getLogsPath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Locations) getCachePath() string {
|
func (l *Locations) getCachePath() string {
|
||||||
return filepath.Join(l.userCache, "cache")
|
// Bridge cache is not a typical cache which can be deleted with only
|
||||||
|
// downside that the app has to download everything again.
|
||||||
|
// Cache for bridge is database with IMAP UIDs and UIDVALIDITY, and also
|
||||||
|
// other IMAP setup. Deleting such data leads to either re-sync of client,
|
||||||
|
// or mix of headers and bodies. Both is caused because of need of re-sync
|
||||||
|
// between Bridge and API which will happen in different order than before.
|
||||||
|
// In the first case, UIDVALIDITY is also changed and causes the better
|
||||||
|
// outcome to "just" re-sync everything; in the later, UIDVALIDITY stays
|
||||||
|
// the same, causing the client to not re-sync but UIDs in the client does
|
||||||
|
// not match UIDs in Bridge.
|
||||||
|
// Because users might use tools to regularly clear caches, Bridge cache
|
||||||
|
// cannot be located in a standard cache folder.
|
||||||
|
return filepath.Join(l.userConfig, "cache")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Locations) getUpdatesPath() string {
|
func (l *Locations) getUpdatesPath() string {
|
||||||
return filepath.Join(l.userCache, "updates")
|
// Users might use tools to regularly clear caches, which would mean always
|
||||||
|
// removing updates, therefore Bridge updates have to be somewhere else.
|
||||||
|
return filepath.Join(l.userConfig, "updates")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear removes everything except the lock and update files.
|
// Clear removes everything except the lock and update files.
|
||||||
|
|||||||
@ -45,7 +45,8 @@ func TestClearRemovesEverythingExceptLockAndUpdateFiles(t *testing.T) {
|
|||||||
assert.NoError(t, l.Clear())
|
assert.NoError(t, l.Clear())
|
||||||
|
|
||||||
assert.FileExists(t, l.GetLockFile())
|
assert.FileExists(t, l.GetLockFile())
|
||||||
assert.NoDirExists(t, l.getSettingsPath())
|
assert.DirExists(t, l.getSettingsPath())
|
||||||
|
assert.NoFileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||||
assert.NoDirExists(t, l.getLogsPath())
|
assert.NoDirExists(t, l.getLogsPath())
|
||||||
assert.NoDirExists(t, l.getCachePath())
|
assert.NoDirExists(t, l.getCachePath())
|
||||||
assert.DirExists(t, l.getUpdatesPath())
|
assert.DirExists(t, l.getUpdatesPath())
|
||||||
@ -58,6 +59,7 @@ func TestClearUpdateFiles(t *testing.T) {
|
|||||||
|
|
||||||
assert.FileExists(t, l.GetLockFile())
|
assert.FileExists(t, l.GetLockFile())
|
||||||
assert.DirExists(t, l.getSettingsPath())
|
assert.DirExists(t, l.getSettingsPath())
|
||||||
|
assert.FileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||||
assert.DirExists(t, l.getLogsPath())
|
assert.DirExists(t, l.getLogsPath())
|
||||||
assert.DirExists(t, l.getCachePath())
|
assert.DirExists(t, l.getCachePath())
|
||||||
assert.NoDirExists(t, l.getUpdatesPath())
|
assert.NoDirExists(t, l.getUpdatesPath())
|
||||||
@ -75,6 +77,7 @@ func TestCleanLeavesStandardLocationsUntouched(t *testing.T) {
|
|||||||
|
|
||||||
assert.FileExists(t, l.GetLockFile())
|
assert.FileExists(t, l.GetLockFile())
|
||||||
assert.DirExists(t, l.getSettingsPath())
|
assert.DirExists(t, l.getSettingsPath())
|
||||||
|
assert.FileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||||
assert.DirExists(t, l.getLogsPath())
|
assert.DirExists(t, l.getLogsPath())
|
||||||
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log1.txt"))
|
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log1.txt"))
|
||||||
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log2.txt"))
|
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log2.txt"))
|
||||||
@ -138,6 +141,9 @@ func newTestLocations(t *testing.T) *Locations {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.DirExists(t, settings)
|
require.DirExists(t, settings)
|
||||||
|
|
||||||
|
createFilesInDir(t, settings, "prefs.json")
|
||||||
|
require.FileExists(t, filepath.Join(settings, "prefs.json"))
|
||||||
|
|
||||||
logs, err := l.ProvideLogsPath()
|
logs, err := l.ProvideLogsPath()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.DirExists(t, logs)
|
require.DirExists(t, logs)
|
||||||
|
|||||||
@ -268,8 +268,6 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
|
|||||||
return false, errors.New("received empty event")
|
return false, errors.New("received empty event")
|
||||||
}
|
}
|
||||||
|
|
||||||
l = l.WithField("newEventID", event.EventID)
|
|
||||||
|
|
||||||
if err = loop.processEvent(event); err != nil {
|
if err = loop.processEvent(event); err != nil {
|
||||||
return false, errors.Wrap(err, "failed to process event")
|
return false, errors.Wrap(err, "failed to process event")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,10 +67,14 @@ func (storeMailbox *Mailbox) ImportMessage(msg *pmapi.Message, body []byte, labe
|
|||||||
}
|
}
|
||||||
|
|
||||||
res, err := storeMailbox.client().Import([]*pmapi.ImportMsgReq{importReqs})
|
res, err := storeMailbox.client().Import([]*pmapi.ImportMsgReq{importReqs})
|
||||||
if err == nil && len(res) > 0 {
|
if err != nil {
|
||||||
msg.ID = res[0].MessageID
|
return err
|
||||||
}
|
}
|
||||||
return err
|
if len(res) == 0 {
|
||||||
|
return errors.New("no import response")
|
||||||
|
}
|
||||||
|
msg.ID = res[0].MessageID
|
||||||
|
return res[0].Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelMessages adds the label by calling an API.
|
// LabelMessages adds the label by calling an API.
|
||||||
|
|||||||
@ -57,19 +57,23 @@ func WriteAttachmentBody(w io.Writer, kr *crypto.KeyRing, m *pmapi.Message, att
|
|||||||
att.Name += ".gpg"
|
att.Name += ".gpg"
|
||||||
att.MIMEType = "application/pgp-encrypted" //nolint
|
att.MIMEType = "application/pgp-encrypted" //nolint
|
||||||
} else if err != nil && err != openpgperrors.ErrSignatureExpired {
|
} else if err != nil && err != openpgperrors.ErrSignatureExpired {
|
||||||
err = fmt.Errorf("cannot decrypt attachment: %v", err)
|
return fmt.Errorf("cannot decrypt attachment: %v", err)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
// Don't encode message/rfc822 attachments; they should be embedded and preserved.
|
||||||
|
if att.MIMEType == rfc822Message {
|
||||||
|
if n, err := io.Copy(w, dr); err != nil {
|
||||||
|
return fmt.Errorf("cannot write attached message: %v (wrote %v bytes)", err, n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode it.
|
// Encode it.
|
||||||
ww := textwrapper.NewRFC822(w)
|
ww := textwrapper.NewRFC822(w)
|
||||||
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
||||||
|
|
||||||
var n int64
|
if n, err := io.Copy(bw, dr); err != nil {
|
||||||
if n, err = io.Copy(bw, dr); err != nil {
|
return fmt.Errorf("cannot write attachment: %v (wrote %v bytes)", err, n)
|
||||||
err = fmt.Errorf("cannot write attachment: %v (wrote %v bytes)", err, n)
|
|
||||||
}
|
}
|
||||||
|
return bw.Close()
|
||||||
_ = bw.Close()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,7 +124,7 @@ func (bld *Builder) writeRelatedPart(p io.Writer, inlines []*pmapi.Attachment) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
h := GetAttachmentHeader(inline)
|
h := GetAttachmentHeader(inline, false)
|
||||||
if p, err = related.CreatePart(h); err != nil {
|
if p, err = related.CreatePart(h); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ func (bld *Builder) BuildMessage() (structure *BodyStructure, message []byte, er
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentHeader := GetAttachmentHeader(att)
|
attachmentHeader := GetAttachmentHeader(att, false)
|
||||||
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
|
if partWriter, err = mw.CreatePart(attachmentHeader); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -311,16 +311,11 @@ func BuildEncrypted(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (
|
|||||||
for i := 0; i < len(m.Attachments); i++ {
|
for i := 0; i < len(m.Attachments); i++ {
|
||||||
att := m.Attachments[i]
|
att := m.Attachments[i]
|
||||||
r := readers[i]
|
r := readers[i]
|
||||||
h := GetAttachmentHeader(att)
|
h := GetAttachmentHeader(att, false)
|
||||||
p, err := mw.CreatePart(h)
|
p, err := mw.CreatePart(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create line wrapper writer.
|
|
||||||
ww := textwrapper.NewRFC822(p)
|
|
||||||
|
|
||||||
// Create base64 writer.
|
|
||||||
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(r)
|
data, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -332,6 +327,9 @@ func BuildEncrypted(m *pmapi.Message, readers []io.Reader, kr *crypto.KeyRing) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ww := textwrapper.NewRFC822(p)
|
||||||
|
bw := base64.NewEncoder(base64.StdEncoding, ww)
|
||||||
if _, err := bw.Write(pgpMessage.GetBinary()); err != nil {
|
if _, err := bw.Write(pgpMessage.GetBinary()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,12 +107,17 @@ func GetRelatedHeader(m *pmapi.Message) textproto.MIMEHeader {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader {
|
func GetAttachmentHeader(att *pmapi.Attachment, buildForIMAP bool) textproto.MIMEHeader {
|
||||||
mediaType := att.MIMEType
|
mediaType := att.MIMEType
|
||||||
if mediaType == "application/pgp-encrypted" {
|
if mediaType == "application/pgp-encrypted" {
|
||||||
mediaType = "application/octet-stream"
|
mediaType = "application/octet-stream"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transferEncoding := "base64"
|
||||||
|
if mediaType == rfc822Message && buildForIMAP {
|
||||||
|
transferEncoding = "8bit"
|
||||||
|
}
|
||||||
|
|
||||||
encodedName := pmmime.EncodeHeader(att.Name)
|
encodedName := pmmime.EncodeHeader(att.Name)
|
||||||
disposition := "attachment" //nolint[goconst]
|
disposition := "attachment" //nolint[goconst]
|
||||||
if strings.Contains(att.Header.Get("Content-Disposition"), "inline") {
|
if strings.Contains(att.Header.Get("Content-Disposition"), "inline") {
|
||||||
@ -121,7 +126,9 @@ func GetAttachmentHeader(att *pmapi.Attachment) textproto.MIMEHeader {
|
|||||||
|
|
||||||
h := make(textproto.MIMEHeader)
|
h := make(textproto.MIMEHeader)
|
||||||
h.Set("Content-Type", mime.FormatMediaType(mediaType, map[string]string{"name": encodedName}))
|
h.Set("Content-Type", mime.FormatMediaType(mediaType, map[string]string{"name": encodedName}))
|
||||||
h.Set("Content-Transfer-Encoding", "base64")
|
if transferEncoding != "" {
|
||||||
|
h.Set("Content-Transfer-Encoding", transferEncoding)
|
||||||
|
}
|
||||||
h.Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": encodedName}))
|
h.Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": encodedName}))
|
||||||
|
|
||||||
// Forward some original header lines.
|
// Forward some original header lines.
|
||||||
|
|||||||
@ -26,6 +26,10 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rfc822Message = "message/rfc822"
|
||||||
|
)
|
||||||
|
|
||||||
var log = logrus.WithField("pkg", "pkg/message") //nolint[gochecknoglobals]
|
var log = logrus.WithField("pkg", "pkg/message") //nolint[gochecknoglobals]
|
||||||
|
|
||||||
func GetBoundary(m *pmapi.Message) string {
|
func GetBoundary(m *pmapi.Message) string {
|
||||||
|
|||||||
@ -201,7 +201,7 @@ 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)
|
newPath := append(currentPath, 1)
|
||||||
|
|
||||||
var br *boundaryReader
|
var br *boundaryReader
|
||||||
|
|||||||
@ -1,3 +1,13 @@
|
|||||||
|
## v1.6.6
|
||||||
|
- 2021-02-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed update notifications
|
||||||
|
- Fixed GUI freeze while switching to early update channel
|
||||||
|
- Fixed Bridge autostart
|
||||||
|
- Improved signing of update packages
|
||||||
|
|
||||||
## v1.6.5
|
## v1.6.5
|
||||||
- 2021-02-22
|
- 2021-02-22
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,24 @@
|
|||||||
|
## v1.6.6
|
||||||
|
- 2021-03-04
|
||||||
|
|
||||||
|
### New
|
||||||
|
|
||||||
|
- Allow to choose which keychain is used by Bridge on Linux
|
||||||
|
- Added automatic update CLI commands
|
||||||
|
- Improved performance during slow connection
|
||||||
|
- Added IMAP requests to the logs for easier debugging
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed update notifications
|
||||||
|
- Fixed GUI freeze while switching to early update channel
|
||||||
|
- Fixed Bridge autostart
|
||||||
|
- Improved signing of update packages
|
||||||
|
- NoGUI bulid
|
||||||
|
- Background of GUI welcome message
|
||||||
|
- Incorrect total mailbox size displayed in Apple Mail
|
||||||
|
|
||||||
|
|
||||||
## v1.6.3
|
## v1.6.3
|
||||||
- 2021-02-16
|
- 2021-02-16
|
||||||
|
|
||||||
|
|||||||
@ -117,3 +117,36 @@ Feature: IMAP import messages
|
|||||||
Then IMAP response is "OK"
|
Then IMAP response is "OK"
|
||||||
And API mailbox "INBOX" for "user" has 0 message
|
And API mailbox "INBOX" for "user" has 0 message
|
||||||
And API mailbox "Sent" for "user" has 1 message
|
And API mailbox "Sent" for "user" has 1 message
|
||||||
|
|
||||||
|
Scenario: Import embedded message
|
||||||
|
When IMAP client imports message to "INBOX"
|
||||||
|
"""
|
||||||
|
From: Foo <foo@example.com>
|
||||||
|
To: Bridge Test <bridgetest@pm.test>
|
||||||
|
Subject: Embedded message
|
||||||
|
Content-Type: multipart/mixed; boundary="boundary"
|
||||||
|
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--boundary
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
|
||||||
|
--boundary
|
||||||
|
Content-Type: message/rfc822; name="embedded.eml"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: attachment; filename="embedded.eml"
|
||||||
|
|
||||||
|
From: Bar <bar@example.com>
|
||||||
|
To: Bridge Test <bridgetest@pm.test>
|
||||||
|
Subject: (No Subject)
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
hello
|
||||||
|
|
||||||
|
--boundary--
|
||||||
|
|
||||||
|
"""
|
||||||
|
Then IMAP response is "OK"
|
||||||
|
|||||||
38
test/features/bridge/smtp/send/embedded_message.feature
Normal file
38
test/features/bridge/smtp/send/embedded_message.feature
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
Feature: SMTP sending embedded message
|
||||||
|
Scenario: Send it
|
||||||
|
Given there is connected user "user"
|
||||||
|
And there is SMTP client logged in as "user"
|
||||||
|
When SMTP client sends message
|
||||||
|
"""
|
||||||
|
From: Bridge Test <[userAddress]>
|
||||||
|
To: Internal Bridge <bridgetest@protonmail.com>
|
||||||
|
Subject: Embedded message
|
||||||
|
Content-Type: multipart/mixed; boundary="boundary"
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--boundary
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
|
||||||
|
--boundary
|
||||||
|
Content-Type: message/rfc822; name="embedded.eml"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: attachment; filename="embedded.eml"
|
||||||
|
|
||||||
|
From: Bar <bar@example.com>
|
||||||
|
To: Bridge Test <bridgetest@pm.test>
|
||||||
|
Subject: (No Subject)
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
hello
|
||||||
|
|
||||||
|
--boundary--
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Then SMTP response is "OK"
|
||||||
|
And mailbox "Sent" for "user" has messages
|
||||||
|
| from | to | subject |
|
||||||
|
| [userAddress] | bridgetest@protonmail.com | Embedded message |
|
||||||
43
test/features/ie/transfer/import_embedded.feature
Normal file
43
test/features/ie/transfer/import_embedded.feature
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
Feature: Import embedded message
|
||||||
|
Background:
|
||||||
|
Given there is connected user "user"
|
||||||
|
And there is EML file "Inbox/hello.eml"
|
||||||
|
"""
|
||||||
|
From: Foo <foo@example.com>
|
||||||
|
To: Bridge Test <bridgetest@pm.test>
|
||||||
|
Subject: Embedded message
|
||||||
|
Content-Type: multipart/mixed; boundary="boundary"
|
||||||
|
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
|
||||||
|
|
||||||
|
This is a multi-part message in MIME format.
|
||||||
|
--boundary
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
|
||||||
|
|
||||||
|
--boundary
|
||||||
|
Content-Type: message/rfc822; name="embedded.eml"
|
||||||
|
Content-Transfer-Encoding: 7bit
|
||||||
|
Content-Disposition: attachment; filename="embedded.eml"
|
||||||
|
|
||||||
|
From: Bar <bar@example.com>
|
||||||
|
To: Bridge Test <bridgetest@pm.test>
|
||||||
|
Subject: (No Subject)
|
||||||
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
hello
|
||||||
|
|
||||||
|
--boundary--
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: Import it
|
||||||
|
When user "user" imports local files
|
||||||
|
Then progress result is "OK"
|
||||||
|
And transfer exported 1 messages
|
||||||
|
And transfer imported 1 messages
|
||||||
|
And transfer failed for 0 messages
|
||||||
|
And API mailbox "INBOX" for "user" has messages
|
||||||
|
| from | to | subject |
|
||||||
|
| foo@example.com | bridgetest@pm.test | Embedded message |
|
||||||
@ -60,9 +60,9 @@ func userImportsLocalFilesToAddress(bddUserID, bddAddressID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userImportsLocalFilesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
func userImportsLocalFilesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||||
path := ctx.GetTransferLocalRootForImport()
|
path := ctx.GetTransferLocalRootForImport()
|
||||||
return ctx.GetImportExport().GetLocalImporter(address, path)
|
return ctx.GetImportExport().GetLocalImporter(username, address, path)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +81,9 @@ func userImportsRemoteMessagesToAddress(bddUserID, bddAddressID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userImportsRemoteMessagesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
func userImportsRemoteMessagesToAddressWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||||
imapServer := ctx.GetTransferRemoteIMAPServer()
|
imapServer := ctx.GetTransferRemoteIMAPServer()
|
||||||
return ctx.GetImportExport().GetRemoteImporter(address, imapServer.Username, imapServer.Password, imapServer.Host, imapServer.Port)
|
return ctx.GetImportExport().GetRemoteImporter(username, address, imapServer.Username, imapServer.Password, imapServer.Host, imapServer.Port)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,9 +102,9 @@ func userExportsAddressToEMLFiles(bddUserID, bddAddressID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userExportsAddressToEMLFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
func userExportsAddressToEMLFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||||
path := ctx.GetTransferLocalRootForExport()
|
path := ctx.GetTransferLocalRootForExport()
|
||||||
return ctx.GetImportExport().GetEMLExporter(address, path)
|
return ctx.GetImportExport().GetEMLExporter(username, address, path)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,20 +123,20 @@ func userExportsAddressToMBOXFiles(bddUserID, bddAddressID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userExportsAddressToMBOXFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
func userExportsAddressToMBOXFilesWithRules(bddUserID, bddAddressID string, rules *gherkin.DataTable) error {
|
||||||
return doTransfer(bddUserID, bddAddressID, rules, func(address string) (*transfer.Transfer, error) {
|
return doTransfer(bddUserID, bddAddressID, rules, func(username, address string) (*transfer.Transfer, error) {
|
||||||
path := ctx.GetTransferLocalRootForExport()
|
path := ctx.GetTransferLocalRootForExport()
|
||||||
return ctx.GetImportExport().GetMBOXExporter(address, path)
|
return ctx.GetImportExport().GetMBOXExporter(username, address, path)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers.
|
// Helpers.
|
||||||
|
|
||||||
func doTransfer(bddUserID, bddAddressID string, rules *gherkin.DataTable, getTransferrer func(string) (*transfer.Transfer, error)) error {
|
func doTransfer(bddUserID, bddAddressID string, rules *gherkin.DataTable, getTransferrer func(string, string) (*transfer.Transfer, error)) error {
|
||||||
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
|
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
|
||||||
if account == nil {
|
if account == nil {
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
transferrer, err := getTransferrer(account.Address())
|
transferrer, err := getTransferrer(account.Username(), account.Address())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return internalError(err, "failed to init transfer")
|
return internalError(err, "failed to init transfer")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user