mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-23 02:26:42 +00:00
Import/Export final touches
This commit is contained in:
@ -200,7 +200,7 @@ func (c *Config) GetTLSKeyPath() string {
|
||||
|
||||
// GetDBDir returns folder for db files.
|
||||
func (c *Config) GetDBDir() string {
|
||||
return filepath.Join(c.appDirsVersion.UserCache())
|
||||
return c.appDirsVersion.UserCache()
|
||||
}
|
||||
|
||||
// GetEventsPath returns path to events file containing the last processed event IDs.
|
||||
@ -228,9 +228,9 @@ func (c *Config) GetPreferencesPath() string {
|
||||
return filepath.Join(c.appDirsVersion.UserCache(), "prefs.json")
|
||||
}
|
||||
|
||||
// GetTransferDir returns folder for import/export rule and report files.
|
||||
// GetTransferDir returns folder for import-export rules files.
|
||||
func (c *Config) GetTransferDir() string {
|
||||
return filepath.Join(c.appDirsVersion.UserCache())
|
||||
return c.appDirsVersion.UserCache()
|
||||
}
|
||||
|
||||
// GetDefaultAPIPort returns default Bridge local API port.
|
||||
|
||||
@ -39,13 +39,13 @@ type Builder struct {
|
||||
cl pmapi.Client
|
||||
msg *pmapi.Message
|
||||
|
||||
EncryptedToHTML bool
|
||||
succDcrpt bool
|
||||
EncryptedToHTML bool
|
||||
successfullyDecrypted bool
|
||||
}
|
||||
|
||||
// NewBuilder initiated with client and message meta info.
|
||||
func NewBuilder(client pmapi.Client, message *pmapi.Message) *Builder {
|
||||
return &Builder{cl: client, msg: message, EncryptedToHTML: true, succDcrpt: false}
|
||||
return &Builder{cl: client, msg: message, EncryptedToHTML: true, successfullyDecrypted: false}
|
||||
}
|
||||
|
||||
// fetchMessage will update original PM message if successful
|
||||
@ -212,7 +212,7 @@ func (bld *Builder) BuildMessage() (structure *BodyStructure, message []byte, er
|
||||
}
|
||||
|
||||
// SuccessfullyDecrypted is true when message was fetched and decrypted successfully
|
||||
func (bld *Builder) SuccessfullyDecrypted() bool { return bld.succDcrpt }
|
||||
func (bld *Builder) SuccessfullyDecrypted() bool { return bld.successfullyDecrypted }
|
||||
|
||||
// WriteBody decrypts PM message and writes main body section. The external PGP
|
||||
// message is written as is (including attachments)
|
||||
@ -225,7 +225,7 @@ func (bld *Builder) WriteBody(w io.Writer) error {
|
||||
if err := bld.msg.Decrypt(kr); err != nil && err != openpgperrors.ErrSignatureExpired {
|
||||
return err
|
||||
}
|
||||
bld.succDcrpt = true
|
||||
bld.successfullyDecrypted = true
|
||||
if bld.msg.MIMEType != pmapi.ContentTypeMultipartMixed {
|
||||
// transfer encoding
|
||||
qp := quotedprintable.NewWriter(w)
|
||||
|
||||
@ -95,6 +95,15 @@ func (l AddressList) ByID(id string) *Address {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllEmails returns all emails.
|
||||
func (l AddressList) AllEmails() (addresses []string) {
|
||||
for _, a := range l {
|
||||
addresses = append(addresses, a.Email)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ActiveEmails returns only active emails.
|
||||
func (l AddressList) ActiveEmails() (addresses []string) {
|
||||
for _, a := range l {
|
||||
if a.Receive == CanReceive {
|
||||
|
||||
@ -452,11 +452,10 @@ func (cm *ClientManager) HandleAuth(ca ClientAuth) {
|
||||
if ca.Auth == nil {
|
||||
cm.clearToken(ca.UserID)
|
||||
go cm.LogoutClient(ca.UserID)
|
||||
return
|
||||
} else {
|
||||
cm.setToken(ca.UserID, ca.Auth.GenToken(), time.Duration(ca.Auth.ExpiresIn)*time.Second)
|
||||
}
|
||||
|
||||
cm.setToken(ca.UserID, ca.Auth.GenToken(), time.Duration(ca.Auth.ExpiresIn)*time.Second)
|
||||
|
||||
logrus.Debug("ClientManager is forwarding auth update...")
|
||||
cm.authUpdates <- ca
|
||||
logrus.Debug("Auth update was forwarded")
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
qdwhrw==
|
||||
=B6/F
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@ -1,102 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var nonVersionChars = regexp.MustCompile(`([^0-9.]+)`) //nolint[gochecknoglobals]
|
||||
|
||||
// sanitizeVersion returns only numbers and periods.
|
||||
func sanitizeVersion(version string) string {
|
||||
return nonVersionChars.ReplaceAllString(version, "")
|
||||
}
|
||||
|
||||
// Result can be false positive, but must not be false negative.
|
||||
// Assuming
|
||||
// * dot separated integers format e.g. "A.B.C.…" where A,B,C,… are integers
|
||||
// * `1.1` == `1.1.0` (i.e. first is not newer)
|
||||
// * `1.1.1` > `1.1` (i.e. first is newer)
|
||||
func isFirstVersionNewer(first, second string) (firstIsNewer bool, err error) {
|
||||
first = sanitizeVersion(first)
|
||||
second = sanitizeVersion(second)
|
||||
|
||||
firstIsNewer, err = false, nil
|
||||
if first == second {
|
||||
return
|
||||
}
|
||||
|
||||
firstIsNewer = true
|
||||
var firstArr, secondArr []int
|
||||
if firstArr, err = versionStrToInts(first); err != nil {
|
||||
return
|
||||
}
|
||||
if secondArr, err = versionStrToInts(second); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
verLength := max(len(firstArr), len(secondArr))
|
||||
firstArr = appendZeros(firstArr, verLength)
|
||||
secondArr = appendZeros(secondArr, verLength)
|
||||
|
||||
for i := 0; i < verLength; i++ {
|
||||
if firstArr[i] == secondArr[i] {
|
||||
continue
|
||||
}
|
||||
return firstArr[i] > secondArr[i], nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func versionStrToInts(version string) (intArr []int, err error) {
|
||||
strArr := strings.Split(version, ".")
|
||||
intArr = make([]int, len(strArr))
|
||||
for index, item := range strArr {
|
||||
if item == "" {
|
||||
intArr[index] = 0
|
||||
continue
|
||||
}
|
||||
intArr[index], err = strconv.Atoi(item)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendZeros(ints []int, newsize int) []int {
|
||||
size := len(ints)
|
||||
if size >= newsize {
|
||||
return ints
|
||||
}
|
||||
zeros := make([]int, newsize-size)
|
||||
return append(ints, zeros...)
|
||||
}
|
||||
|
||||
func max(ints ...int) (max int) {
|
||||
max = ints[0]
|
||||
for _, a := range ints {
|
||||
if max < a {
|
||||
max = a
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testDataValues struct {
|
||||
expectErr, expectedNewer bool
|
||||
first, second string
|
||||
}
|
||||
type testDataList []testDataValues
|
||||
|
||||
func (tdl *testDataList) add(err, newer bool, first, second string) { //nolint[unparam]
|
||||
*tdl = append(*tdl, testDataValues{err, newer, first, second})
|
||||
}
|
||||
|
||||
func (tdl *testDataList) addFirstIsNewer(first, second string) {
|
||||
tdl.add(false, true, first, second)
|
||||
tdl.add(false, false, second, first)
|
||||
}
|
||||
|
||||
func TestCompareVersion(t *testing.T) {
|
||||
testData := testDataList{}
|
||||
// same is never newer
|
||||
testData.add(false, false, "1.1.1", "1.1.1")
|
||||
testData.add(false, false, "1.1.0", "1.1")
|
||||
testData.add(false, false, "1.0.0", "1")
|
||||
testData.add(false, false, ".1.1", "0.1.1")
|
||||
testData.add(false, false, "0.1.1", ".1.1")
|
||||
|
||||
testData.addFirstIsNewer("1.1.10", "1.1.1")
|
||||
testData.addFirstIsNewer("1.10.1", "1.1.1")
|
||||
testData.addFirstIsNewer("10.1.1", "1.1.1")
|
||||
|
||||
testData.addFirstIsNewer("1.1.1", "0.1.1")
|
||||
testData.addFirstIsNewer("1.1.1", "1.0.1")
|
||||
testData.addFirstIsNewer("1.1.1", "1.1.0")
|
||||
|
||||
testData.addFirstIsNewer("1.1.1", "1")
|
||||
testData.addFirstIsNewer("1.1.1", "1.1")
|
||||
testData.addFirstIsNewer("1.1.1.1", "1.1.1")
|
||||
|
||||
testData.addFirstIsNewer("1.1.1 beta", "1.1.0")
|
||||
testData.addFirstIsNewer("1z.1z.1z", "1.1.0")
|
||||
testData.addFirstIsNewer("1a.1b.1c", "1.1.0")
|
||||
|
||||
for _, td := range testData {
|
||||
t.Log(td)
|
||||
isNewer, err := isFirstVersionNewer(td.first, td.second)
|
||||
if td.expectErr {
|
||||
require.True(t, err != nil, "expected error but got nil for %#v", td)
|
||||
require.True(t, true == isNewer, "error expected but first is not newer for %#v", td)
|
||||
continue
|
||||
}
|
||||
|
||||
require.True(t, err == nil, "expected no error but have %v for %#v", err, td)
|
||||
require.True(t, isNewer == td.expectedNewer, "expected %v but have %v for %#v", td.expectedNewer, isNewer, err, td)
|
||||
}
|
||||
}
|
||||
@ -1,131 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/dialer"
|
||||
)
|
||||
|
||||
func mkdirAllClear(path string) error {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(path, 0750)
|
||||
}
|
||||
|
||||
func downloadToBytes(path string) (out []byte, err error) {
|
||||
var (
|
||||
client *http.Client
|
||||
response *http.Response
|
||||
)
|
||||
client = dialer.DialTimeoutClient()
|
||||
log.WithField("path", path).Trace("Downloading")
|
||||
|
||||
response, err = client.Get(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out, err = ioutil.ReadAll(response.Body)
|
||||
_ = response.Body.Close()
|
||||
if response.StatusCode < http.StatusOK || http.StatusIMUsed < response.StatusCode {
|
||||
err = errors.New(path + " " + response.Status)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func downloadWithProgress(status *Progress, sourceURL, targetPath string) (err error) {
|
||||
targetFile, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
log.Warnf("Cannot create update file %s: %v", targetPath, err)
|
||||
return
|
||||
}
|
||||
defer targetFile.Close() //nolint[errcheck]
|
||||
|
||||
var (
|
||||
client *http.Client
|
||||
response *http.Response
|
||||
)
|
||||
client = dialer.DialTimeoutClient()
|
||||
response, err = client.Get(sourceURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer response.Body.Close() //nolint[errcheck]
|
||||
|
||||
contentLength, _ := strconv.ParseUint(response.Header.Get("Content-Length"), 10, 64)
|
||||
|
||||
wc := WriteCounter{
|
||||
Status: status,
|
||||
Target: targetFile,
|
||||
Size: contentLength,
|
||||
}
|
||||
|
||||
err = wc.ReadAll(response.Body)
|
||||
return
|
||||
}
|
||||
|
||||
func downloadWithSignature(status *Progress, sourceURL, targetDir string) (localPath string, err error) {
|
||||
localPath = filepath.Join(targetDir, filepath.Base(sourceURL))
|
||||
|
||||
if err = downloadWithProgress(nil, sourceURL+sigExtension, localPath+sigExtension); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = downloadWithProgress(status, sourceURL, localPath); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type WriteCounter struct {
|
||||
Status *Progress
|
||||
Target io.Writer
|
||||
processed, Size, counter uint64
|
||||
}
|
||||
|
||||
func (s *WriteCounter) ReadAll(source io.Reader) (err error) {
|
||||
s.counter = uint64(0)
|
||||
if s.Target == nil {
|
||||
return errors.New("can not read all, target unset")
|
||||
}
|
||||
if source == nil {
|
||||
return errors.New("can not read all, source unset")
|
||||
}
|
||||
_, err = io.Copy(s.Target, io.TeeReader(source, s))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *WriteCounter) Write(p []byte) (int, error) {
|
||||
if s.Status != nil && s.Size != 0 {
|
||||
s.processed += uint64(len(p))
|
||||
fraction := float32(s.processed) / float32(s.Size)
|
||||
if s.counter%uint64(100) == 0 || fraction == 1. {
|
||||
s.Status.UpdateProcessed(fraction)
|
||||
}
|
||||
}
|
||||
s.counter++
|
||||
return len(p), nil
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
const (
|
||||
InfoCurrentVersion = 1 + iota
|
||||
InfoDownloading
|
||||
InfoVerifying
|
||||
InfoUnpacking
|
||||
InfoUpgrading
|
||||
InfoQuitApp
|
||||
InfoRestartApp
|
||||
)
|
||||
|
||||
type Progress struct {
|
||||
Processed float32 // fraction of finished procedure [0.0-1.0]
|
||||
Description int // description by code (needs to be translated anyway)
|
||||
Err error // occurred error
|
||||
channel chan<- Progress
|
||||
}
|
||||
|
||||
func (s *Progress) Update() {
|
||||
s.channel <- *s
|
||||
}
|
||||
|
||||
func (s *Progress) UpdateDescription(description int) {
|
||||
s.Description = description
|
||||
s.Processed = 0
|
||||
s.Update()
|
||||
}
|
||||
|
||||
func (s *Progress) UpdateProcessed(processed float32) {
|
||||
s.Processed = processed
|
||||
s.Update()
|
||||
}
|
||||
@ -1,108 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
// gpg --export D51E64D3E63EDC3EEF7864CEE2C75D68E6234B07 | xxd -p | tr -d '\n' | xclip
|
||||
const (
|
||||
keyID = "D51E64D3E63EDC3EEF7864CEE2C75D68E6234B07"
|
||||
pubkeyHex = "99020d045a3d39e1011000be7cfacb714058f9851ce5888cad8250ea882b2563060b4d21f5f02fdcfb2b1e4073d33edc4050f235d35ab689050ed6435f5d79334bf30f093936472398eec259b7fa9c265cc18a8e4ab0681fcc0d4fdb934bfea9477007935af70bfdfa406de25b1e96838c9c4645d6613a13dffca1e70684e416cb8bff5348101d7c9cd3cb1b78229646dce4cc9cec2ecfa78d456547900e3c089c9d592e8cf33fa322c07016ae273880a1bcb8c8178d8fb804f555a8826129b2e2c535631c56a1fe73f476345e0851a5deda508833008b1751b6845e1ff788264350c3792f0932027fabe63dd230dce4da1b45f15eea584f25758355ae9784c32a2bd31d70333a5b6ff0b863cc177bcacfd35774029887551113cec424d9eb1f5ee4ab042b69c8b73a113d6596e88bdac55451e9403ee7944253b26177cbd97f79f22d138010a2e9044f5f16cf8c23ec7755332cf09250d50efbfedfb256426b1dba775af591b1f324b00dd497abeabc681036848954825139c7832902ab9ace0b73d270611d39a222e07e5ce98159acb99dcf8d3d62624458b2883d5feee43a48f981a601f01ebeb8e430181004e9990fc1a9d7d2e746d9aa8d5876ad576bf327399c08e834e6706a73300f9bc258f51510b597b9b34506ff21a993311d9a961ab07cc7c86476088d9aecaab31cc198e1091d62e5bdb161bc784879d4fca5a53fb292ffa89996a77101e10011010001b44c50726f746f6e20546563686e6f6c6f67696573204147202850726f746f6e4d61696c2042726964676520646576656c6f7065727329203c6272696467654070726f746f6e6d61696c2e63683e89025404130108003e021b03050b09080702061508090a0b020416020301021e01021780162104d51e64d3e63edc3eef7864cee2c75d68e6234b0705025bf7efbc050903bcdedb000a0910e2c75d68e6234b07e19e0fff58d859aabd12b6e37829b61d406f8563e68b8a32e18952378b88fac148acfbb51614d31419e3ec054ac5ef33abce8e15a75e85491861a6f7886c3315260e321a5cf037e8d9afd141bc5f3391b3aa8da8e44c5deb3b2a92402a5956079a991b7a7388064133b07f0c41e0ae66560e2bbfae484882a32d4b42676ee0a43e9e26cdb92e1c9942723ae491e91c39156abee84369db5afd2d9f6c2bad428113d851339267d7119a1d892b6313de1cd0c7aeac495036cf7c985c6b9e0cf7d6ecf50ba6dca913d56594e7fd2dac0d15f1e5196bddf3c9d2cdca724972a6ea294601bce9e9ccbff497785d7df9a8969517585ae514f94e2d54252b829cea842a4b62960724901e0f64953b68d69f5ca4d644dec3b9f947e57b25d6d1d37a0dc53fc62c2860a27ffb1e09658b6fa87fe43fb82f7b6a339c4b0b330ea4d906683b51a2844a685e939ecece3af8447f606c78645da77b66e6627ce838e6416c65f0b0c65335a6bd4091db7356f3f638e320505cd739ab762d27b1b8e5bcbba011821b49bfd63f4c0ac06ebacd36adb25448436ba795424d9d66f413854b833f72ab9ed0a6fc52ec3df5d9d655d0d9c0c21c8323ee785c08af5d341bd1f0067dc81bb4a74aa496f575c00c6fd58e067a6b04aed72ec59b263e6fe6707702e59eafb361532241fe881642da91518d5b01c238701d7317062afcf2f4b1b7c01c3c7164417a5e18b9020d045a3d39e1011000b74f40e9514f5a261e58f19cdeb88c5b835d74886facea681bd4c1d6280e957675f466cc6925b02540ed61d70660d66d47bd6af80edce3bb00dc3afc9cf36233fe8f0226a4983b712a93a96f00d54e9361f1975545776bfa1b204b6d97b6c8f41b1436098bb01608c07a2ffa097f6a32907402d81c29aadc18faa3b0417963e956993d7738dcb0b55b98db333463f5224e5d284d236548ba87e62856e244f68ffe4fe8f1a2a151a5736c66a58e10a9821c3cfa4d3b35893a7e0a867fe60d1e2a987a5c9f93b8da2a17de3dd48604167f68c73b2820191ec1363f7c95a65dffbe727a41383ad92c5fc6a1115cecc5bd29a7a5f6f990d09ee68ccd9b16cdf4f251f4ad1adf99d94b014e622a03efd1a9319e79d4aaf09ca1e905314443b4fdf3d3d2995ee38c9edf37e2eb5ded62def8b643e9dc6cfab3e1c83896f3413c05d973d49a3e73683ab6820dfae0fbfdfe60c1a65a5e1241d283bb7d6495568f87269ad606799ff5fae02aad4e712d0136f03ee008a9da1845a962406d44170b231a794d964d6233cb4150e92105172fd133c3b93b51c7b03623b4889cf5027f3ea483d2a37b2cc9648d1f00756bc0c66f798b91a9c4364e0e576c3a779eba69afb27dc8d630a850b4e293b88955d46e635577bdfb1802479f18dfe7da06b732f6ad0898a9c28086b0c8145c64dda245cea8a060301b812c29318f6c25a25d91f69f11001101000189023c041801080026021b0c162104d51e64d3e63edc3eef7864cee2c75d68e6234b0705025bf7f281050903bce1a0000a0910e2c75d68e6234b07b7930ffe31f08ec8ecc415eb767c8434a1262c528b8f5b7d77b293396b672ac06ea36bec9ac82b3817e45d474d4d091779be54692196930895317f8cbabd7f5bf3991ba23efbb1fe322d5d86e71c58619457a4c19917c789d2e62c23a4c0ede82d4445fb6ff99e721caf75999b2be9e35c7c0e9578874a0507a796d66f0d5b2a1d2544b03861b0a278ef7b5abb0b8f5e4b3b72aabcb6c4a05731f344ef8257a1d5d5bdccba2cb9640ef7442d68206613979cc8935334876148c827df7d3044859fdd00c12bcf072881303e100abe3a5ca94e2c36497643471e6c43c4fed35f24777cee259e0f873243a7343adbbce1cbed4ce073d838733f9e2a4e9281a5f43f2aac56ff0c472843a07814a5515ae809ed976d0699ebce1f5e5661fd6752f22af8521cc485ea2925bc8c650865dab398fbd64460fd873f687fd2b7db55d1920fd5787010063eba5d4b08fd9882e9c2244270886f8c6411194d4e55d207e374d6bf9ea3463ce4db2f2e6818f57ac964f76f79b1df0b9dc3e688f0c5d73f33010809f9ed2effc6e4387ce0f1eb634e7a67bf04e9e30126de137999e7fdea05b9ee6088154d8369e5fac81b7c0af16d6be8d636bd84348812333822319cd0e362ab06969032b57f9233e618ec9f67d5d65a52c51f5f942cf83f5522bfe3de3bab39b3de867f5f6a108e0b661789c2a049b990c812f2b1b1722d3e403299f969eb34a9dc21af"
|
||||
)
|
||||
|
||||
var (
|
||||
pubkeyRing = openpgp.EntityList{} //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
func singAndVerify(pathToFile string) (err error) {
|
||||
err = signFile(pathToFile)
|
||||
if err != nil {
|
||||
err = verifyFile(pathToFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func signFile(pathToFile string) (err error) {
|
||||
if runtime.GOOS != "linux" { //nolint[goconst]
|
||||
return errors.New("tar not implemented only for linux")
|
||||
}
|
||||
// assuming gpg detach-sign creates file with suffix .sig by default.
|
||||
// Lstat does not follow the link i.e. only link is deleted (not link target).
|
||||
if _, err := os.Lstat(pathToFile + sigExtension); !os.IsNotExist(err) {
|
||||
_ = os.Remove(pathToFile + sigExtension)
|
||||
}
|
||||
cmd := exec.Command("gpg", "--local-user", keyID, "--detach-sign", pathToFile) //nolint[gosec]
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func verifyFile(pathToFile string) error {
|
||||
fileReader, err := os.Open(pathToFile) //nolint[gosec]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close() //nolint[errcheck]
|
||||
|
||||
signatureReader, err := os.Open(pathToFile + sigExtension) //nolint[gosec]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer signatureReader.Close() //nolint[errcheck]
|
||||
|
||||
return verifyBytes(fileReader, signatureReader)
|
||||
}
|
||||
|
||||
func verifyBytes(fileReader, signatureReader io.Reader) (err error) {
|
||||
if _, err = getPubKey(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = openpgp.CheckDetachedSignature(pubkeyRing, fileReader, signatureReader, nil)
|
||||
/*
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if signer == nil || signer.PrimaryKey.KeyId != keyID {
|
||||
return errors.New("Signer with wrong key ID")
|
||||
}
|
||||
*/
|
||||
return
|
||||
}
|
||||
|
||||
// from opengpg/read_test.go
|
||||
func getPubKey() (el openpgp.EntityList, err error) {
|
||||
if pubkeyRing != nil && len(pubkeyRing) != 0 {
|
||||
return pubkeyRing, nil
|
||||
}
|
||||
data, err := hex.DecodeString(pubkeyHex)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubkeyRing, err = openpgp.ReadKeyRing(bytes.NewBuffer(data))
|
||||
return pubkeyRing, err
|
||||
}
|
||||
@ -1,239 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func syncFolders(localPath, updatePath string) (err error) {
|
||||
backupDir := filepath.Join(filepath.Dir(updatePath), "backup")
|
||||
if err = createBackup(localPath, backupDir); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = removeMissing(localPath, updatePath); err != nil {
|
||||
restoreFromBackup(backupDir, localPath)
|
||||
return
|
||||
}
|
||||
|
||||
if err = copyRecursively(updatePath, localPath); err != nil {
|
||||
restoreFromBackup(backupDir, localPath)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeMissing(folderToCleanPath, itemsToKeepPath string) (err error) {
|
||||
log.Debug("remove missing")
|
||||
// Create list of files.
|
||||
existingRelPaths := map[string]bool{}
|
||||
err = filepath.Walk(itemsToKeepPath, func(keepThis string, _ os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
relPath, walkErr := filepath.Rel(itemsToKeepPath, keepThis)
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
log.Debug("path to keep ", relPath)
|
||||
existingRelPaths[relPath] = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
delList := []string{}
|
||||
err = filepath.Walk(folderToCleanPath, func(removeThis string, _ os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
relPath, walkErr := filepath.Rel(folderToCleanPath, removeThis)
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
log.Debug("check path ", relPath)
|
||||
if !existingRelPaths[relPath] {
|
||||
log.Debug("path not in list, removing ", removeThis)
|
||||
delList = append(delList, removeThis)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, removeThis := range delList {
|
||||
if err = os.RemoveAll(removeThis); err != nil && !os.IsNotExist(err) {
|
||||
log.Error("remove error ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreFromBackup(backupDir, localPath string) {
|
||||
log.Error("recovering from ", backupDir, " to ", localPath)
|
||||
_ = copyRecursively(backupDir, localPath)
|
||||
}
|
||||
|
||||
func createBackup(srcFile, dstDir string) (err error) {
|
||||
log.Debug("backup ", srcFile, " in ", dstDir)
|
||||
if err = mkdirAllClear(dstDir); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return copyRecursively(srcFile, dstDir)
|
||||
}
|
||||
|
||||
// checksum assumes the file is a regular file and that it exists.
|
||||
func checksum(path string) (hash string) {
|
||||
file, err := os.Open(path) //nolint[gosec]
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer file.Close() //nolint[errcheck]
|
||||
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(hasher, file); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return string(hasher.Sum(nil))
|
||||
}
|
||||
|
||||
// srcDir including app folder.
|
||||
// dstDir including app folder.
|
||||
func copyRecursively(srcDir, dstDir string) error { // nolint[funlen]
|
||||
return filepath.Walk(srcDir, func(srcPath string, srcInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcIsLink := srcInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
||||
srcIsDir := srcInfo.IsDir()
|
||||
|
||||
// Non regular source (e.g. named pipes, sockets, devices...).
|
||||
if !srcIsLink && !srcIsDir && !srcInfo.Mode().IsRegular() {
|
||||
log.Error("File ", srcPath, " with mode ", srcInfo.Mode())
|
||||
return errors.New("irregular source file. Copy not implemented")
|
||||
}
|
||||
|
||||
// Destination path.
|
||||
srcRelPath, err := filepath.Rel(srcDir, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstPath := filepath.Join(dstDir, srcRelPath)
|
||||
log.Debug("src: ", srcPath, " dst: ", dstPath)
|
||||
|
||||
// Destination exists.
|
||||
dstInfo, err := os.Lstat(dstPath)
|
||||
if err == nil {
|
||||
dstIsLink := dstInfo.Mode()&os.ModeSymlink == os.ModeSymlink
|
||||
dstIsDir := dstInfo.IsDir()
|
||||
|
||||
// Non regular destination (e.g. named pipes, sockets, devices...).
|
||||
if !dstIsLink && !dstIsDir && !dstInfo.Mode().IsRegular() {
|
||||
log.Error("File ", dstPath, " with mode ", dstInfo.Mode())
|
||||
return errors.New("irregular target file. Copy not implemented")
|
||||
}
|
||||
|
||||
if dstIsLink {
|
||||
if err = os.Remove(dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !dstIsLink && dstIsDir && !srcIsDir {
|
||||
if err = os.RemoveAll(dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Do not return if !dstIsLink && dstIsDir && srcIsDir: the permissions might change.
|
||||
|
||||
if dstInfo.Mode().IsRegular() && !srcInfo.Mode().IsRegular() {
|
||||
if err = os.Remove(dstPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create symbolic link and return.
|
||||
if srcIsLink {
|
||||
log.Debug("It is a symlink")
|
||||
linkPath, err := os.Readlink(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("link to ", linkPath)
|
||||
return os.Symlink(linkPath, dstPath)
|
||||
}
|
||||
|
||||
// Create dir and return.
|
||||
if srcIsDir {
|
||||
log.Debug("It is a dir")
|
||||
return os.MkdirAll(dstPath, srcInfo.Mode())
|
||||
}
|
||||
|
||||
// Regular files only.
|
||||
// If files are same return.
|
||||
if os.SameFile(srcInfo, dstInfo) || checksum(srcPath) == checksum(dstPath) {
|
||||
log.Debug("Same files, skip copy")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create/overwrite regular file.
|
||||
srcReader, err := os.Open(srcPath) //nolint[gosec]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcReader.Close() //nolint[errcheck]
|
||||
return copyToTmpFileRename(srcReader, dstPath, srcInfo.Mode())
|
||||
})
|
||||
}
|
||||
|
||||
func copyToTmpFileRename(srcReader io.Reader, dstPath string, dstMode os.FileMode) error {
|
||||
log.Debug("Tmp and rename ", dstPath)
|
||||
tmpPath := dstPath + ".tmp"
|
||||
if err := copyToFileTruncate(srcReader, tmpPath, dstMode); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpPath, dstPath)
|
||||
}
|
||||
|
||||
func copyToFileTruncate(srcReader io.Reader, dstPath string, dstMode os.FileMode) error {
|
||||
log.Debug("Copy and truncate ", dstPath)
|
||||
dstWriter, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, dstMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dstWriter.Close() //nolint[errcheck]
|
||||
_, err = io.Copy(dstWriter, srcReader)
|
||||
return err
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
FileType = "File"
|
||||
SymlinkType = "Symlink"
|
||||
DirType = "Dir"
|
||||
EmptyType = "Empty"
|
||||
NewType = "New"
|
||||
)
|
||||
|
||||
func TestSyncFolder(t *testing.T) {
|
||||
for _, srcType := range []string{EmptyType, FileType, SymlinkType, DirType} {
|
||||
for _, dstType := range []string{EmptyType, FileType, SymlinkType, DirType} {
|
||||
require.NoError(t, checkCopyWorks(srcType, dstType))
|
||||
log.Warn("OK: from ", srcType, " to ", dstType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkCopyWorks(srcType, dstType string) error {
|
||||
dirName := "from_" + srcType + "_to_" + dstType
|
||||
AppCacheDir := "/tmp"
|
||||
srcDir := filepath.Join(AppCacheDir, "sync_src", dirName)
|
||||
destDir := filepath.Join(AppCacheDir, "sync_dst", dirName)
|
||||
|
||||
// clear before
|
||||
log.Info("remove all ", srcDir)
|
||||
err := os.RemoveAll(srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("remove all ", destDir)
|
||||
err = os.RemoveAll(destDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create
|
||||
err = createTestFolder(srcDir, srcType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = createTestFolder(destDir, dstType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy
|
||||
log.Info("Sync from ", srcDir, " to ", destDir)
|
||||
err = syncFolders(destDir, srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check
|
||||
log.Info("check ", srcDir, " and ", destDir)
|
||||
err = checkThatFilesAreSame(srcDir, destDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// clear after
|
||||
log.Info("remove all ", srcDir)
|
||||
err = os.RemoveAll(srcDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("remove all ", destDir)
|
||||
err = os.RemoveAll(destDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func checkThatFilesAreSame(src, dst string) error {
|
||||
cmd := exec.Command("diff", "-qr", src, dst) //nolint[gosec]
|
||||
cmd.Stderr = log.WriterLevel(logrus.ErrorLevel)
|
||||
cmd.Stdout = log.WriterLevel(logrus.InfoLevel)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func createTestFolder(dirPath, dirType string) error {
|
||||
log.Info("creating folder ", dirPath, " type ", dirType)
|
||||
if dirType == NewType {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := mkdirAllClear(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dirType == EmptyType {
|
||||
return nil
|
||||
}
|
||||
|
||||
path := filepath.Join(dirPath, "testpath")
|
||||
switch dirType {
|
||||
case FileType:
|
||||
err = ioutil.WriteFile(path, []byte("This is a test"), 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case SymlinkType:
|
||||
err = os.Symlink("../../", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case DirType:
|
||||
err = os.MkdirAll(path, 0750)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(path, "another_file"), []byte("This is a test"), 0640)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func createTar(tarPath, sourcePath string) error { //nolint[unused]
|
||||
if runtime.GOOS != "linux" {
|
||||
return errors.New("tar not implemented only for linux")
|
||||
}
|
||||
// Check whether it exists and is a directory.
|
||||
if _, err := os.Lstat(sourcePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(tarPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("tar", "-zvcf", absPath, filepath.Base(sourcePath)) //nolint[gosec]
|
||||
cmd.Dir = filepath.Dir(sourcePath)
|
||||
cmd.Stderr = log.WriterLevel(logrus.ErrorLevel)
|
||||
cmd.Stdout = log.WriterLevel(logrus.InfoLevel)
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func untarToDir(tarPath, targetDir string, status *Progress) error { //nolint[funlen]
|
||||
// Check whether it exists and is a directory.
|
||||
if ls, err := os.Lstat(targetDir); err == nil {
|
||||
if !ls.IsDir() {
|
||||
return errors.New("not a dir")
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
tgzReader, err := os.Open(tarPath) //nolint[gosec]
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tgzReader.Close() //nolint[errcheck]
|
||||
|
||||
size := uint64(0)
|
||||
if info, err := tgzReader.Stat(); err == nil {
|
||||
size = uint64(info.Size())
|
||||
}
|
||||
|
||||
wc := &WriteCounter{
|
||||
Status: status,
|
||||
Size: size,
|
||||
}
|
||||
|
||||
tarReader, err := gzip.NewReader(io.TeeReader(tgzReader, wc))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileReader := tar.NewReader(tarReader)
|
||||
for {
|
||||
header, err := fileReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if header == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
targetFile := filepath.Join(targetDir, header.Name)
|
||||
info := header.FileInfo()
|
||||
|
||||
// Create symlink.
|
||||
if header.Typeflag == tar.TypeSymlink {
|
||||
if header.Linkname == "" {
|
||||
return errors.New("missing linkname")
|
||||
}
|
||||
if err := os.Symlink(header.Linkname, targetFile); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle case that it is a directory.
|
||||
if info.IsDir() {
|
||||
if err := os.MkdirAll(targetFile, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle case that it is a regular file.
|
||||
if err := copyToFileTruncate(fileReader, targetFile, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
{"Version":"1.1.6","ReleaseDate":"10 Jul 19 11:02 +0200","ReleaseNotes":"• Necessary updates reflecting API changes\n• Report wrongly formated messages\n","ReleaseFixedBugs":"• Fixed verification for contacts signed by older or missing key\n• Outlook always shows attachment icon\n","FixedBugs":["• Fixed verification for contacts signed by older or missing key","• Outlook always shows attachment icon",""],"URL":"https://protonmail.com/download/Bridge-Installer.sh","LandingPage":"https://protonmail.com/bridge/download","UpdateFile":"https://protonmail.com/download/bridge_upgrade_linux.tgz","InstallerFile":"https://protonmail.com/download/Bridge-Installer.sh","DebFile":"https://protonmail.com/download/protonmail-bridge_1.1.6-1_amd64.deb","RpmFile":"https://protonmail.com/download/protonmail-bridge-1.1.6-1.x86_64.rpm","PkgFile":"https://protonmail.com/download/PKGBUILD"}
|
||||
BIN
pkg/updates/testdata/current_version_linux.json.sig
vendored
BIN
pkg/updates/testdata/current_version_linux.json.sig
vendored
Binary file not shown.
@ -1,330 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
||||
"github.com/kardianos/osext"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
sigExtension = ".sig"
|
||||
)
|
||||
|
||||
var (
|
||||
Host = "https://protonmail.com" //nolint[gochecknoglobals]
|
||||
DownloadPath = "download" //nolint[gochecknoglobals]
|
||||
|
||||
// BuildType specifies type of build (e.g. QA or beta).
|
||||
BuildType = "" //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "bridgeUtils/updates") //nolint[gochecknoglobals]
|
||||
|
||||
installFileSuffix = map[string]string{ //nolint[gochecknoglobals]
|
||||
"darwin": ".dmg",
|
||||
"windows": ".exe",
|
||||
"linux": ".sh",
|
||||
}
|
||||
|
||||
ErrDownloadFailed = errors.New("error happened during download") //nolint[gochecknoglobals]
|
||||
ErrUpdateVerifyFailed = errors.New("cannot verify signature") //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
type Updates struct {
|
||||
version string
|
||||
revision string
|
||||
buildTime string
|
||||
releaseNotes string
|
||||
releaseFixedBugs string
|
||||
updateTempDir string
|
||||
landingPagePath string // Based on Host/; default landing page for download.
|
||||
installerFileBaseName string // File for initial install or manual reinstall. per goos [exe, dmg, sh].
|
||||
versionFileBaseName string // Text file containing information about current file. per goos [_linux,_darwin,_windows].json (have .sig file).
|
||||
updateFileBaseName string // File for automatic update. per goos [_linux,_darwin,_windows].tgz (have .sig file).
|
||||
linuxFileBaseName string // Prefix of linux package names.
|
||||
macAppBundleName string // Name of Mac app file in the bundle for update procedure.
|
||||
cachedNewerVersion *VersionInfo // To have info about latest version even when the internet connection drops.
|
||||
}
|
||||
|
||||
// NewBridge inits Updates struct for bridge.
|
||||
func NewBridge(updateTempDir string) *Updates {
|
||||
return &Updates{
|
||||
version: constants.Version,
|
||||
revision: constants.Revision,
|
||||
buildTime: constants.BuildTime,
|
||||
releaseNotes: bridge.ReleaseNotes,
|
||||
releaseFixedBugs: bridge.ReleaseFixedBugs,
|
||||
updateTempDir: updateTempDir,
|
||||
landingPagePath: "bridge/download",
|
||||
installerFileBaseName: "Bridge-Installer",
|
||||
versionFileBaseName: "current_version",
|
||||
updateFileBaseName: "bridge_upgrade",
|
||||
linuxFileBaseName: "protonmail-bridge",
|
||||
macAppBundleName: "ProtonMail Bridge.app",
|
||||
}
|
||||
}
|
||||
|
||||
// NewImportExport inits Updates struct for import/export.
|
||||
func NewImportExport(updateTempDir string) *Updates {
|
||||
return &Updates{
|
||||
version: constants.Version,
|
||||
revision: constants.Revision,
|
||||
buildTime: constants.BuildTime,
|
||||
releaseNotes: importexport.ReleaseNotes,
|
||||
releaseFixedBugs: importexport.ReleaseFixedBugs,
|
||||
updateTempDir: updateTempDir,
|
||||
landingPagePath: "blog/import-export-beta/",
|
||||
installerFileBaseName: "Import-Export-Installer",
|
||||
versionFileBaseName: "current_version_ie",
|
||||
updateFileBaseName: "ie_upgrade",
|
||||
linuxFileBaseName: "protonmail-import-export",
|
||||
macAppBundleName: "ProtonMail Import-Export.app",
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updates) CreateJSONAndSign(deployDir, goos string) error {
|
||||
versionInfo := u.getLocalVersion(goos)
|
||||
versionInfo.Version = sanitizeVersion(versionInfo.Version)
|
||||
|
||||
versionFileName := filepath.Base(u.versionFileURL(goos))
|
||||
versionFilePath := filepath.Join(deployDir, versionFileName)
|
||||
|
||||
txt, err := json.Marshal(versionInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(versionFilePath, txt, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := singAndVerify(versionFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateFileName := filepath.Base(versionInfo.UpdateFile)
|
||||
updateFilePath := filepath.Join(deployDir, updateFileName)
|
||||
if err := singAndVerify(updateFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Updates) CheckIsUpToDate() (isUpToDate bool, latestVersion VersionInfo, err error) {
|
||||
localVersion := u.GetLocalVersion()
|
||||
latestVersion, err = u.getLatestVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
localIsOld, err := isFirstVersionNewer(latestVersion.Version, localVersion.Version)
|
||||
return !localIsOld, latestVersion, err
|
||||
}
|
||||
|
||||
func (u *Updates) GetDownloadLink() string {
|
||||
latestVersion, err := u.getLatestVersion()
|
||||
if err != nil || latestVersion.InstallerFile == "" {
|
||||
localVersion := u.GetLocalVersion()
|
||||
return localVersion.GetDownloadLink()
|
||||
}
|
||||
return latestVersion.GetDownloadLink()
|
||||
}
|
||||
|
||||
func (u *Updates) GetLocalVersion() VersionInfo {
|
||||
return u.getLocalVersion(runtime.GOOS)
|
||||
}
|
||||
|
||||
func (u *Updates) getLocalVersion(goos string) VersionInfo {
|
||||
version := u.version
|
||||
if BuildType != "" {
|
||||
version += " " + BuildType
|
||||
}
|
||||
|
||||
versionInfo := VersionInfo{
|
||||
Version: version,
|
||||
Revision: u.revision,
|
||||
ReleaseDate: u.buildTime,
|
||||
ReleaseNotes: u.releaseNotes,
|
||||
ReleaseFixedBugs: u.releaseFixedBugs,
|
||||
FixedBugs: strings.Split(u.releaseFixedBugs, "\n"),
|
||||
URL: u.installerFileURL(goos),
|
||||
|
||||
LandingPage: u.landingPageURL(),
|
||||
UpdateFile: u.updateFileURL(goos),
|
||||
InstallerFile: u.installerFileURL(goos),
|
||||
}
|
||||
|
||||
if goos == "linux" {
|
||||
pkgName := u.linuxFileBaseName
|
||||
pkgRel := "1"
|
||||
pkgBase := strings.Join([]string{Host, DownloadPath, pkgName}, "/")
|
||||
|
||||
versionInfo.DebFile = pkgBase + "_" + u.version + "-" + pkgRel + "_amd64.deb"
|
||||
versionInfo.RpmFile = pkgBase + "-" + u.version + "-" + pkgRel + ".x86_64.rpm"
|
||||
versionInfo.PkgFile = strings.Join([]string{Host, DownloadPath, "PKGBUILD"}, "/")
|
||||
}
|
||||
|
||||
return versionInfo
|
||||
}
|
||||
|
||||
func (u *Updates) getLatestVersion() (latestVersion VersionInfo, err error) {
|
||||
version, err := downloadToBytes(u.versionFileURL(runtime.GOOS))
|
||||
if err != nil {
|
||||
if u.cachedNewerVersion != nil {
|
||||
return *u.cachedNewerVersion, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
signature, err := downloadToBytes(u.signatureFileURL(runtime.GOOS))
|
||||
if err != nil {
|
||||
if u.cachedNewerVersion != nil {
|
||||
return *u.cachedNewerVersion, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = verifyBytes(bytes.NewReader(version), bytes.NewReader(signature)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = json.NewDecoder(bytes.NewReader(version)).Decode(&latestVersion); err != nil {
|
||||
return
|
||||
}
|
||||
if localIsOld, _ := isFirstVersionNewer(latestVersion.Version, u.version); localIsOld {
|
||||
u.cachedNewerVersion = &latestVersion
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *Updates) landingPageURL() string {
|
||||
return strings.Join([]string{Host, u.landingPagePath}, "/")
|
||||
}
|
||||
|
||||
func (u *Updates) signatureFileURL(goos string) string {
|
||||
return u.versionFileURL(goos) + sigExtension
|
||||
}
|
||||
|
||||
func (u *Updates) versionFileURL(goos string) string {
|
||||
return strings.Join([]string{Host, DownloadPath, u.versionFileBaseName + "_" + goos + ".json"}, "/")
|
||||
}
|
||||
|
||||
func (u *Updates) installerFileURL(goos string) string {
|
||||
return strings.Join([]string{Host, DownloadPath, u.installerFileBaseName + installFileSuffix[goos]}, "/")
|
||||
}
|
||||
|
||||
func (u *Updates) updateFileURL(goos string) string {
|
||||
return strings.Join([]string{Host, DownloadPath, u.updateFileBaseName + "_" + goos + ".tgz"}, "/")
|
||||
}
|
||||
|
||||
func (u *Updates) StartUpgrade(currentStatus chan<- Progress) { // nolint[funlen]
|
||||
status := &Progress{channel: currentStatus}
|
||||
defer status.Update()
|
||||
|
||||
// Get latest version.
|
||||
var verInfo VersionInfo
|
||||
status.UpdateDescription(InfoCurrentVersion)
|
||||
if verInfo, status.Err = u.getLatestVersion(); status.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if verInfo.UpdateFile == "" {
|
||||
log.Warn("Empty update URL. Update manually.")
|
||||
status.Err = ErrDownloadFailed
|
||||
return
|
||||
}
|
||||
|
||||
// Download.
|
||||
status.UpdateDescription(InfoDownloading)
|
||||
if status.Err = mkdirAllClear(u.updateTempDir); status.Err != nil {
|
||||
return
|
||||
}
|
||||
var updateTar string
|
||||
updateTar, status.Err = downloadWithSignature(
|
||||
status,
|
||||
verInfo.UpdateFile,
|
||||
u.updateTempDir,
|
||||
)
|
||||
if status.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check signature.
|
||||
status.UpdateDescription(InfoVerifying)
|
||||
status.Err = verifyFile(updateTar)
|
||||
if status.Err != nil {
|
||||
log.Warnf("Cannot verify update file %s: %v", updateTar, status.Err)
|
||||
status.Err = ErrUpdateVerifyFailed
|
||||
return
|
||||
}
|
||||
|
||||
// Untar.
|
||||
status.UpdateDescription(InfoUnpacking)
|
||||
status.Err = untarToDir(updateTar, u.updateTempDir, status)
|
||||
if status.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Run upgrade (OS specific).
|
||||
status.UpdateDescription(InfoUpgrading)
|
||||
switch runtime.GOOS {
|
||||
case "windows": //nolint[goconst]
|
||||
cmd := exec.Command("./" + u.installerFileBaseName) // nolint[gosec]
|
||||
cmd.Dir = u.updateTempDir
|
||||
status.Err = cmd.Start()
|
||||
case "darwin":
|
||||
// current path is better then appDir = filepath.Join("/Applications")
|
||||
var exePath string
|
||||
exePath, status.Err = osext.Executable()
|
||||
if status.Err != nil {
|
||||
return
|
||||
}
|
||||
localPath := filepath.Dir(exePath) // Macos
|
||||
localPath = filepath.Dir(localPath) // Contents
|
||||
localPath = filepath.Dir(localPath) // .app
|
||||
|
||||
updatePath := filepath.Join(u.updateTempDir, u.macAppBundleName)
|
||||
log.Warn("localPath ", localPath)
|
||||
log.Warn("updatePath ", updatePath)
|
||||
status.Err = syncFolders(localPath, updatePath)
|
||||
if status.Err != nil {
|
||||
return
|
||||
}
|
||||
status.UpdateDescription(InfoRestartApp)
|
||||
return
|
||||
default:
|
||||
status.Err = errors.New("upgrade for " + runtime.GOOS + " not implemented")
|
||||
}
|
||||
|
||||
status.UpdateDescription(InfoQuitApp)
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// +build build_beta
|
||||
|
||||
package updates
|
||||
|
||||
func init() {
|
||||
DownloadPath = "download/beta"
|
||||
BuildType = "beta"
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// +build build_qa
|
||||
|
||||
package updates
|
||||
|
||||
func init() {
|
||||
Host = "https://bridgeteam.protontech.ch"
|
||||
DownloadPath = "download/qa"
|
||||
BuildType = "QA"
|
||||
}
|
||||
@ -1,184 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testServerPort = "8999"
|
||||
|
||||
var testUpdateDir string //nolint[gochecknoglobals]
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setup()
|
||||
code := m.Run()
|
||||
shutdown()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func setup() {
|
||||
var err error
|
||||
testUpdateDir, err = ioutil.TempDir("", "upgrade")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Host = "http://localhost:" + testServerPort
|
||||
go startServer()
|
||||
}
|
||||
|
||||
func shutdown() {
|
||||
_ = os.RemoveAll(testUpdateDir)
|
||||
}
|
||||
|
||||
func startServer() {
|
||||
http.HandleFunc("/download/current_version_linux.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./testdata/current_version_linux.json")
|
||||
})
|
||||
http.HandleFunc("/download/current_version_linux.json.sig", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./testdata/current_version_linux.json.sig")
|
||||
})
|
||||
http.HandleFunc("/download/current_version_darwin.json", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./testdata/current_version_linux.json")
|
||||
})
|
||||
http.HandleFunc("/download/current_version_darwin.json.sig", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./testdata/current_version_linux.json.sig")
|
||||
})
|
||||
panic(http.ListenAndServe(":"+testServerPort, nil))
|
||||
}
|
||||
|
||||
func TestCheckBridgeIsUpToDate(t *testing.T) {
|
||||
updates := newTestUpdates("1.1.6")
|
||||
isUpToDate, _, err := updates.CheckIsUpToDate()
|
||||
require.NoError(t, err)
|
||||
require.True(t, isUpToDate, "Bridge should be up to date")
|
||||
}
|
||||
|
||||
func TestCheckBridgeIsNotUpToDate(t *testing.T) {
|
||||
updates := newTestUpdates("1.1.5")
|
||||
isUpToDate, _, err := updates.CheckIsUpToDate()
|
||||
require.NoError(t, err)
|
||||
require.True(t, !isUpToDate, "Bridge should not be up to date")
|
||||
}
|
||||
|
||||
func TestGetLocalVersion(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping test because local version for windows is currently not supported by tests.")
|
||||
}
|
||||
updates := newTestUpdates("1")
|
||||
expectedVersion := VersionInfo{
|
||||
Version: "1",
|
||||
Revision: "rev123",
|
||||
ReleaseDate: "42",
|
||||
ReleaseNotes: "• new feature",
|
||||
ReleaseFixedBugs: "• fixed foo",
|
||||
FixedBugs: []string{"• fixed foo"},
|
||||
URL: Host + "/" + DownloadPath + "/Bridge-Installer.sh",
|
||||
|
||||
LandingPage: Host + "/bridge/download",
|
||||
UpdateFile: Host + "/" + DownloadPath + "/bridge_upgrade_linux.tgz",
|
||||
InstallerFile: Host + "/" + DownloadPath + "/Bridge-Installer.sh",
|
||||
|
||||
DebFile: Host + "/" + DownloadPath + "/protonmail-bridge_1-1_amd64.deb",
|
||||
RpmFile: Host + "/" + DownloadPath + "/protonmail-bridge-1-1.x86_64.rpm",
|
||||
PkgFile: Host + "/" + DownloadPath + "/PKGBUILD",
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
expectedVersion.URL = Host + "/" + DownloadPath + "/Bridge-Installer.dmg"
|
||||
expectedVersion.UpdateFile = Host + "/" + DownloadPath + "/bridge_upgrade_darwin.tgz"
|
||||
expectedVersion.InstallerFile = expectedVersion.URL
|
||||
expectedVersion.DebFile = ""
|
||||
expectedVersion.RpmFile = ""
|
||||
expectedVersion.PkgFile = ""
|
||||
}
|
||||
version := updates.GetLocalVersion()
|
||||
require.Equal(t, expectedVersion, version)
|
||||
}
|
||||
|
||||
func TestGetLatestVersion(t *testing.T) {
|
||||
updates := newTestUpdates("1")
|
||||
expectedVersion := VersionInfo{
|
||||
Version: "1.1.6",
|
||||
Revision: "",
|
||||
ReleaseDate: "10 Jul 19 11:02 +0200",
|
||||
ReleaseNotes: "• Necessary updates reflecting API changes\n• Report wrongly formated messages\n",
|
||||
ReleaseFixedBugs: "• Fixed verification for contacts signed by older or missing key\n• Outlook always shows attachment icon\n",
|
||||
FixedBugs: []string{
|
||||
"• Fixed verification for contacts signed by older or missing key",
|
||||
"• Outlook always shows attachment icon",
|
||||
"",
|
||||
},
|
||||
URL: "https://protonmail.com/download/Bridge-Installer.sh",
|
||||
|
||||
LandingPage: "https://protonmail.com/bridge/download",
|
||||
UpdateFile: "https://protonmail.com/download/bridge_upgrade_linux.tgz",
|
||||
InstallerFile: "https://protonmail.com/download/Bridge-Installer.sh",
|
||||
|
||||
DebFile: "https://protonmail.com/download/protonmail-bridge_1.1.6-1_amd64.deb",
|
||||
RpmFile: "https://protonmail.com/download/protonmail-bridge-1.1.6-1.x86_64.rpm",
|
||||
PkgFile: "https://protonmail.com/download/PKGBUILD",
|
||||
}
|
||||
version, err := updates.getLatestVersion()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedVersion, version)
|
||||
}
|
||||
|
||||
func TestStartUpgrade(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("skipping test because only upgrading on windows is currently supported by tests.")
|
||||
}
|
||||
|
||||
updates := newTestUpdates("1")
|
||||
progress := make(chan Progress, 1)
|
||||
done := make(chan error)
|
||||
|
||||
go func() {
|
||||
for current := range progress {
|
||||
log.Infof("progress descr: %d processed %f err %v", current.Description, current.Processed, current.Err)
|
||||
if current.Err != nil {
|
||||
done <- current.Err
|
||||
break
|
||||
}
|
||||
}
|
||||
done <- nil
|
||||
}()
|
||||
|
||||
updates.StartUpgrade(progress)
|
||||
close(progress)
|
||||
require.NoError(t, <-done)
|
||||
}
|
||||
|
||||
func newTestUpdates(version string) *Updates {
|
||||
u := NewBridge(testUpdateDir)
|
||||
u.version = version
|
||||
u.revision = "rev123"
|
||||
u.buildTime = "42"
|
||||
u.releaseNotes = "• new feature"
|
||||
u.releaseFixedBugs = "• fixed foo"
|
||||
return u
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
// Copyright (c) 2020 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 updates
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VersionInfo struct {
|
||||
Version string
|
||||
Revision string
|
||||
ReleaseDate string // Timestamp generated automatically
|
||||
ReleaseNotes string // List of features, new line separated with leading dot e.g. `• example\n`
|
||||
ReleaseFixedBugs string // List of fixed bugs, same usage as release notes
|
||||
FixedBugs []string // Deprecated list of fixed bugs keeping for backward compatibility (mandatory for working versions up to 1.1.5)
|
||||
URL string // Open browser and download (obsolete replaced by InstallerFile)
|
||||
|
||||
LandingPage string // landing page for manual download
|
||||
UpdateFile string // automatic update file
|
||||
InstallerFile string `json:",omitempty"` // manual update file
|
||||
DebFile string `json:",omitempty"` // debian package file
|
||||
RpmFile string `json:",omitempty"` // red hat package file
|
||||
PkgFile string `json:",omitempty"` // arch PKGBUILD file
|
||||
}
|
||||
|
||||
func (info *VersionInfo) GetDownloadLink() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
return strings.Join([]string{info.DebFile, info.RpmFile, info.PkgFile}, "\n")
|
||||
default:
|
||||
return info.InstallerFile
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user