From 8d7db1ea7ba38592a3213069a133a73f70db9a01 Mon Sep 17 00:00:00 2001 From: Chun-Hung Tseng Date: Mon, 17 Jul 2023 21:51:04 +0200 Subject: [PATCH] Implememt the file download seek support Update to support more fields in RevisionXAttrCommon --- drive_test.go | 50 ++++++++++++ drive_test_helper.go | 9 ++- error.go | 1 + file.go | 189 +++++++++++++++++++++++++++++-------------- folder.go | 2 +- go.mod | 10 +-- go.sum | 16 ++-- 7 files changed, 202 insertions(+), 75 deletions(-) diff --git a/drive_test.go b/drive_test.go index cc2b39a..71842c1 100644 --- a/drive_test.go +++ b/drive_test.go @@ -406,3 +406,53 @@ func TestUploadLargeNumberOfBlocks(t *testing.T) { deleteBySearchingFromRoot(t, ctx, protonDrive, filename, false, false) checkActiveFileListing(t, ctx, protonDrive, []string{}) } + +func TestFileSeek(t *testing.T) { + ctx, cancel, protonDrive := setup(t, false) + t.Cleanup(func() { + defer cancel() + defer tearDown(t, ctx, protonDrive) + }) + + // in order to simulate seeking over blocks + // we use 1KB for the UPLOAD_BLOCK_SIZE + ORIGINAL_UPLOAD_BLOCK_SIZE := UPLOAD_BLOCK_SIZE + defer func() { + UPLOAD_BLOCK_SIZE = ORIGINAL_UPLOAD_BLOCK_SIZE + }() + blocks := 10 + UPLOAD_BLOCK_SIZE = 10 + + filename := "fileContent.txt" + file1Content := RandomString(UPLOAD_BLOCK_SIZE*blocks + 5) // intentionally make the data not aligned to a block + file1ContentReader := strings.NewReader(file1Content) + + log.Println("Upload fileContent.txt") + uploadFileByReader(t, ctx, protonDrive, "", filename, file1ContentReader, 0) + checkRevisions(protonDrive, ctx, t, filename, 1, 1, 0, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{"/" + filename}) + + { + log.Println("Download fileContent.txt with offset 0") + downloadFileWithOffset(t, ctx, protonDrive, "", filename, "", file1Content, 0) + } + { + offset := int64(UPLOAD_BLOCK_SIZE) + log.Println("Download fileContent.txt with offset", offset) + downloadFileWithOffset(t, ctx, protonDrive, "", filename, "", file1Content[offset:], offset) + } + { + offset := int64(UPLOAD_BLOCK_SIZE + 5) + log.Println("Download fileContent.txt with offset", offset) + downloadFileWithOffset(t, ctx, protonDrive, "", filename, "", file1Content[offset:], offset) + } + { + offset := int64(UPLOAD_BLOCK_SIZE*blocks/2 + 3) + log.Println("Download fileContent.txt with offset", offset) + downloadFileWithOffset(t, ctx, protonDrive, "", filename, "", file1Content[offset:], offset) + } + + log.Println("Delete file fileContent.txt") + deleteBySearchingFromRoot(t, ctx, protonDrive, filename, false, false) + checkActiveFileListing(t, ctx, protonDrive, []string{}) +} diff --git a/drive_test_helper.go b/drive_test_helper.go index 0ee32dd..78e9038 100644 --- a/drive_test_helper.go +++ b/drive_test_helper.go @@ -188,6 +188,10 @@ func uploadFileByFilepath(t *testing.T, ctx context.Context, protonDrive *Proton } func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, data string) { + downloadFileWithOffset(t, ctx, protonDrive, parent, name, filepath, data, 0) +} + +func downloadFileWithOffset(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, data string, offset int64) { parentLink := protonDrive.RootLink if parent != "" { targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false) @@ -211,7 +215,7 @@ func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p if targetFileLink == nil { t.Fatalf("File %v not found", name) } else { - reader, sizeOnServer, fileSystemAttr, err := protonDrive.DownloadFileByID(ctx, targetFileLink.LinkID) + reader, sizeOnServer, fileSystemAttr, err := protonDrive.DownloadFileByID(ctx, targetFileLink.LinkID, offset) if err != nil { t.Fatal(err) } @@ -228,7 +232,7 @@ func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p if fileSystemAttr.Size != 0 && sizeOnServer == fileSystemAttr.Size { t.Fatalf("Not possible due to encryption file overhead") } - if len(downloadedData) != int(fileSystemAttr.Size) { + if offset == 0 && len(downloadedData) != int(fileSystemAttr.Size) { t.Fatalf("Downloaded file size != uploaded file size: %#v vs %#v", len(downloadedData), int(fileSystemAttr.Size)) } } @@ -238,6 +242,7 @@ func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p if err != nil { t.Fatal(err) } + originalData = originalData[offset:] if !bytes.Equal(downloadedData, originalData) { t.Fatalf("Downloaded content is different from the original content") diff --git a/error.go b/error.go index 9e2bf6d..003c8a4 100644 --- a/error.go +++ b/error.go @@ -19,4 +19,5 @@ var ( ErrCantFindDraftRevision = errors.New("can't find a draft revision") ErrWrongUsageOfGetLinkKR = errors.New("internal error for GetLinkKR - nil passed in for link") ErrWrongUsageOfGetLink = errors.New("internal error for getLink - empty linkID passed in") + ErrSeekOffsetAfterSkippingBlocks = errors.New("internal error for download seek - the offset after skipping blocks is wrong") ) diff --git a/file.go b/file.go index 5481cc6..5df24ce 100644 --- a/file.go +++ b/file.go @@ -4,9 +4,12 @@ import ( "bufio" "bytes" "context" + "crypto/sha1" "crypto/sha256" "encoding/base64" + "encoding/hex" "io" + "log" "mime" "os" "path/filepath" @@ -20,6 +23,8 @@ import ( type FileSystemAttrs struct { ModificationTime time.Time Size int64 + BlockSizes []int64 + Digests string // sha1 string } type FileDownloadReader struct { @@ -33,12 +38,18 @@ type FileDownloadReader struct { nextRevision int isEOF bool + + // TODO: integrity check if the entire file is read } func (r *FileDownloadReader) Read(p []byte) (int, error) { if r.data.Len() == 0 { + // TODO: do we have memory sharing bug? + // to avoid sharing the underlying buffer array across re-population + r.data = bytes.NewBuffer(nil) + // we download and decrypt more content - err := r.downloadFileOnRead() + err := r.populateBufferOnRead() if err != nil { return 0, err } @@ -58,7 +69,33 @@ func (r *FileDownloadReader) Close() error { return nil } -func (protonDrive *ProtonDrive) DownloadFileByID(ctx context.Context, linkID string) (io.ReadCloser, int64, *FileSystemAttrs, error) { +func (reader *FileDownloadReader) populateBufferOnRead() error { + if len(reader.revision.Blocks) == 0 || len(reader.revision.Blocks) == reader.nextRevision { + reader.isEOF = true + return nil + } + + offset := reader.nextRevision + for i := offset; i-offset < DOWNLOAD_BATCH_BLOCK_SIZE && i < len(reader.revision.Blocks); i++ { + // TODO: parallel download + blockReader, err := reader.protonDrive.c.GetBlock(reader.ctx, reader.revision.Blocks[i].BareURL, reader.revision.Blocks[i].Token) + if err != nil { + return err + } + defer blockReader.Close() + + err = decryptBlockIntoBuffer(reader.sessionKey, reader.protonDrive.AddrKR, reader.nodeKR, reader.revision.Blocks[i].Hash, reader.revision.Blocks[i].EncSignature, reader.data, blockReader) + if err != nil { + return err + } + + reader.nextRevision = i + 1 + } + + return nil +} + +func (protonDrive *ProtonDrive) DownloadFileByID(ctx context.Context, linkID string, offset int64) (io.ReadCloser, int64, *FileSystemAttrs, error) { /* It's like event system, we need to get the latest information before creating the move request! */ protonDrive.removeLinkIDFromCache(linkID, false) @@ -67,7 +104,7 @@ func (protonDrive *ProtonDrive) DownloadFileByID(ctx context.Context, linkID str return nil, 0, nil, err } - return protonDrive.DownloadFile(ctx, link) + return protonDrive.DownloadFile(ctx, link, offset) } func (protonDrive *ProtonDrive) GetRevisions(ctx context.Context, link *proton.Link, revisionType proton.RevisionState) ([]*proton.RevisionMetadata, error) { @@ -88,6 +125,15 @@ func (protonDrive *ProtonDrive) GetRevisions(ctx context.Context, link *proton.L return ret, nil } +func (protonDrive *ProtonDrive) GetActiveRevisionAttrsByID(ctx context.Context, linkID string) (*FileSystemAttrs, error) { + link, err := protonDrive.getLink(ctx, linkID) + if err != nil { + return nil, err + } + + return protonDrive.GetActiveRevisionAttrs(ctx, link) +} + func (protonDrive *ProtonDrive) GetActiveRevisionAttrs(ctx context.Context, link *proton.Link) (*FileSystemAttrs, error) { if link == nil { return nil, ErrLinkMustNotBeNil @@ -120,6 +166,8 @@ func (protonDrive *ProtonDrive) GetActiveRevisionAttrs(ctx context.Context, link return &FileSystemAttrs{ ModificationTime: modificationTime, Size: revisionXAttrCommon.Size, + BlockSizes: revisionXAttrCommon.BlockSizes, + Digests: revisionXAttrCommon.Digests, }, nil } @@ -160,10 +208,12 @@ func (protonDrive *ProtonDrive) GetActiveRevisionWithAttrs(ctx context.Context, return &revision, &FileSystemAttrs{ ModificationTime: modificationTime, Size: revisionXAttrCommon.Size, + BlockSizes: revisionXAttrCommon.BlockSizes, + Digests: revisionXAttrCommon.Digests, }, nil } -func (protonDrive *ProtonDrive) DownloadFile(ctx context.Context, link *proton.Link) (io.ReadCloser, int64, *FileSystemAttrs, error) { +func (protonDrive *ProtonDrive) DownloadFile(ctx context.Context, link *proton.Link, offset int64) (io.ReadCloser, int64, *FileSystemAttrs, error) { if link.Type != proton.LinkTypeFile { return nil, 0, nil, ErrLinkTypeMustToBeFileType } @@ -201,59 +251,67 @@ func (protonDrive *ProtonDrive) DownloadFile(ctx context.Context, link *proton.L isEOF: false, } - err = reader.downloadFileOnRead() - if err != nil { - return nil, 0, nil, err + useFallbackDownload := false + if fileSystemAttrs != nil { + // based on offset, infer the nextRevision (0-based) + if fileSystemAttrs.BlockSizes == nil { + useFallbackDownload = true + } else { + // infer nextRevision + totalBytes := int64(0) + for i := 0; i < len(fileSystemAttrs.BlockSizes); i++ { + prevTotalBytes := totalBytes + totalBytes += fileSystemAttrs.BlockSizes[i] + if offset <= totalBytes { + offset = offset - prevTotalBytes + reader.nextRevision = i + break + } + } + + // download will start from the specified block + n, err := io.CopyN(io.Discard, reader, offset) + if err != nil { + return nil, 0, nil, err + } + if int64(n) != offset { + return nil, 0, nil, ErrSeekOffsetAfterSkippingBlocks + } + } } + if useFallbackDownload { + log.Println("Performing inefficient seek as metadata of encrypted file is missing") + n, err := io.CopyN(io.Discard, reader, offset) + if err != nil { + return nil, 0, nil, err + } + if int64(n) != offset { + return nil, 0, nil, ErrSeekOffsetAfterSkippingBlocks + } + } return reader, link.Size, fileSystemAttrs, nil } -func (reader *FileDownloadReader) downloadFileOnRead() error { - if len(reader.revision.Blocks) == 0 || len(reader.revision.Blocks) == reader.nextRevision { - reader.isEOF = true - return nil - } - - offset := reader.nextRevision - for i := offset; i-offset < DOWNLOAD_BATCH_BLOCK_SIZE && i < len(reader.revision.Blocks); i++ { - // TODO: parallel download - blockReader, err := reader.protonDrive.c.GetBlock(reader.ctx, reader.revision.Blocks[i].BareURL, reader.revision.Blocks[i].Token) - if err != nil { - return err - } - defer blockReader.Close() - - err = decryptBlockIntoBuffer(reader.sessionKey, reader.protonDrive.AddrKR, reader.nodeKR, reader.revision.Blocks[i].Hash, reader.revision.Blocks[i].EncSignature, reader.data, blockReader) - if err != nil { - return err - } - - reader.nextRevision = i + 1 - } - - return nil -} - -func (protonDrive *ProtonDrive) UploadFileByReader(ctx context.Context, parentLinkID string, filename string, modTime time.Time, file io.Reader, testParam int) (string, int64, error) { +func (protonDrive *ProtonDrive) UploadFileByReader(ctx context.Context, parentLinkID string, filename string, modTime time.Time, file io.Reader, testParam int) (string, *proton.RevisionXAttrCommon, error) { parentLink, err := protonDrive.getLink(ctx, parentLinkID) if err != nil { - return "", 0, err + return "", nil, err } return protonDrive.uploadFile(ctx, parentLink, filename, modTime, file, testParam) } -func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink *proton.Link, filename string, filePath string, testParam int) (string, int64, error) { +func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink *proton.Link, filename string, filePath string, testParam int) (string, *proton.RevisionXAttrCommon, error) { f, err := os.Open(filePath) if err != nil { - return "", 0, err + return "", nil, err } defer f.Close() info, err := os.Stat(filePath) if err != nil { - return "", 0, err + return "", nil, err } in := bufio.NewReader(f) @@ -455,14 +513,14 @@ func (protonDrive *ProtonDrive) createFileUploadDraft(ctx context.Context, paren return linkID, revisionID, newSessionKey, newNodeKR, nil } -func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, newSessionKey *crypto.SessionKey, newNodeKR *crypto.KeyRing, file io.Reader, linkID, revisionID string) ([]byte, int64, error) { +func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, newSessionKey *crypto.SessionKey, newNodeKR *crypto.KeyRing, file io.Reader, linkID, revisionID string) ([]byte, int64, []int64, string, error) { type PendingUploadBlocks struct { blockUploadInfo proton.BlockUploadInfo encData []byte } if newSessionKey == nil || newNodeKR == nil { - return nil, 0, ErrMissingInputUploadAndCollectBlockData + return nil, 0, nil, "", ErrMissingInputUploadAndCollectBlockData } totalFileSize := int64(0) @@ -520,11 +578,13 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n } shouldContinue := true + sha1Digests := sha1.New() + blockSizes := make([]int64, 0) for i := 1; shouldContinue; i++ { if (i-1) > 0 && (i-1)%UPLOAD_BATCH_BLOCK_SIZE == 0 { err := uploadPendingBlocks() if err != nil { - return nil, 0, err + return nil, 0, nil, "", err } } @@ -541,26 +601,28 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n shouldContinue = false } else { // all other errors - return nil, 0, err + return nil, 0, nil, "", err } } data = data[:readBytes] totalFileSize += int64(readBytes) + sha1Digests.Write(data) + blockSizes = append(blockSizes, int64(readBytes)) // encrypt data dataPlainMessage := crypto.NewPlainMessage(data) encData, err := newSessionKey.Encrypt(dataPlainMessage) if err != nil { - return nil, 0, err + return nil, 0, nil, "", err } encSignature, err := protonDrive.AddrKR.SignDetachedEncrypted(dataPlainMessage, newNodeKR) if err != nil { - return nil, 0, err + return nil, 0, nil, "", err } encSignatureStr, err := encSignature.GetArmored() if err != nil { - return nil, 0, err + return nil, 0, nil, "", err } h := sha256.New() @@ -568,7 +630,7 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n hash := h.Sum(nil) base64Hash := base64.StdEncoding.EncodeToString(hash) if err != nil { - return nil, 0, err + return nil, 0, nil, "", err } manifestSignatureData = append(manifestSignatureData, hash...) @@ -584,13 +646,15 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n } err := uploadPendingBlocks() if err != nil { - return nil, 0, err + return nil, 0, nil, "", err } - return manifestSignatureData, totalFileSize, nil + sha1Hash := sha1Digests.Sum(nil) + sha1String := hex.EncodeToString(sha1Hash) + return manifestSignatureData, totalFileSize, blockSizes, sha1String, nil } -func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *crypto.KeyRing, modificationTime time.Time, size int64, manifestSignatureData []byte, linkID, revisionID string) error { +func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *crypto.KeyRing, xAttrCommon *proton.RevisionXAttrCommon, manifestSignatureData []byte, linkID, revisionID string) error { manifestSignature, err := protonDrive.AddrKR.SignDetached(crypto.NewPlainMessage(manifestSignatureData)) if err != nil { return err @@ -604,7 +668,8 @@ func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *c ManifestSignature: manifestSignatureString, SignatureAddress: protonDrive.signatureAddress, } - err = commitRevisionReq.SetEncXAttrString(protonDrive.AddrKR, nodeKR, modificationTime, size) + + err = commitRevisionReq.SetEncXAttrString(protonDrive.AddrKR, nodeKR, xAttrCommon) if err != nil { return err } @@ -621,7 +686,7 @@ func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *c // 0 = normal mode // 1 = up to create revision // 2 = up to block upload -func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, file io.Reader, testParam int) (string, int64, error) { +func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, file io.Reader, testParam int) (string, *proton.RevisionXAttrCommon, error) { // TODO: if we should use github.com/gabriel-vasile/mimetype to detect the MIME type from the file content itself // Note: this approach might cause the upload progress to display the "fake" progress, since we read in all the content all-at-once // mimetype.SetLimit(0) @@ -638,32 +703,38 @@ func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *prot /* step 1: create a draft */ linkID, revisionID, newSessionKey, newNodeKR, err := protonDrive.createFileUploadDraft(ctx, parentLink, filename, modTime, mimeType) if err != nil { - return "", 0, err + return "", nil, err } if testParam == 1 { - return "", 0, nil + return "", nil, nil } /* step 2: upload blocks and collect block data */ - manifestSignature, fileSize, err := protonDrive.uploadAndCollectBlockData(ctx, newSessionKey, newNodeKR, file, linkID, revisionID) + manifestSignature, fileSize, blockSizes, digests, err := protonDrive.uploadAndCollectBlockData(ctx, newSessionKey, newNodeKR, file, linkID, revisionID) if err != nil { - return "", 0, err + return "", nil, err } if testParam == 2 { // for integration tests // we try to simulate blocks uploaded but not yet commited - return "", 0, nil + return "", nil, nil } /* step 3: mark the file as active by commiting the revision */ - err = protonDrive.commitNewRevision(ctx, newNodeKR, modTime, fileSize, manifestSignature, linkID, revisionID) + xAttrCommon := &proton.RevisionXAttrCommon{ + ModificationTime: modTime.Format("2006-01-02T15:04:05-0700"), /* ISO8601 */ + Size: fileSize, + BlockSizes: blockSizes, + Digests: digests, + } + err = protonDrive.commitNewRevision(ctx, newNodeKR, xAttrCommon, manifestSignature, linkID, revisionID) if err != nil { - return "", 0, err + return "", nil, err } - return linkID, fileSize, nil + return linkID, xAttrCommon, nil } /* diff --git a/folder.go b/folder.go index f08198b..3f223cb 100644 --- a/folder.go +++ b/folder.go @@ -106,7 +106,7 @@ func (protonDrive *ProtonDrive) ListDirectoriesRecursively( log.Println("Downloading", currentPath) defer log.Println("Completes downloading", currentPath) - reader, _, _, err := protonDrive.DownloadFile(ctx, link) + reader, _, _, err := protonDrive.DownloadFile(ctx, link, 0) if err != nil { return err } diff --git a/go.mod b/go.mod index 2e9ce0f..4e713ac 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,21 @@ module github.com/henrybear327/Proton-API-Bridge go 1.18 require ( - github.com/ProtonMail/gopenpgp/v2 v2.7.1 - github.com/henrybear327/go-proton-api v0.0.0-20230713211354-02be61689e29 + github.com/ProtonMail/gopenpgp/v2 v2.7.2 + github.com/henrybear327/go-proton-api v0.0.0-20230717103708-031d819d74ab github.com/relvacode/iso8601 v1.3.0 + golang.org/x/sync v0.3.0 ) require ( github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect github.com/ProtonMail/gluon v0.17.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/ProtonMail/go-srp v0.0.7 // indirect github.com/PuerkitoBio/goquery v1.8.1 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect - github.com/bradenaw/juniper v0.13.0 // indirect + github.com/bradenaw/juniper v0.13.1 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cronokirby/saferith v0.33.0 // indirect github.com/emersion/go-message v0.16.0 // indirect @@ -29,7 +30,6 @@ require ( golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/net v0.12.0 // indirect - golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index caae598..2c209b9 100644 --- a/go.sum +++ b/go.sum @@ -5,21 +5,21 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9 github.com/ProtonMail/gluon v0.17.0 h1:QfMRUcXd47MANHmoerj1ZHXsNzfW9gjsLmF+7Dim5ZU= github.com/ProtonMail/gluon v0.17.0/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb h1:RU+Ff2vE68zFQSoBqlb/LChFztEWWJ9EZ8LU4gA3ubU= -github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= -github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s= -github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs= +github.com/ProtonMail/gopenpgp/v2 v2.7.2 h1:mIwxSUPezxNYq0RA5106VPWyKC+Ly3FvBUnBJh/7GWw= +github.com/ProtonMail/gopenpgp/v2 v2.7.2/go.mod h1:IhkNEDaxec6NyzSI0PlxapinnwPVIESk8/76da3Ct3g= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= -github.com/bradenaw/juniper v0.13.0 h1:KKMAiWDkRt45YUNzzw00Jec4nOgWDLVtztjf39E0ppI= -github.com/bradenaw/juniper v0.13.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI= +github.com/bradenaw/juniper v0.13.1 h1:9P7/xeaYuEyqPuJHSHCJoisWyPvZH4FAi59BxJLh7F8= +github.com/bradenaw/juniper v0.13.1/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= @@ -49,8 +49,8 @@ github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSM github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/henrybear327/go-proton-api v0.0.0-20230713211354-02be61689e29 h1:OUVzxoIPZ6T4yC5hzvvIEjRaYwom3c9N5VwJgJvr9cs= -github.com/henrybear327/go-proton-api v0.0.0-20230713211354-02be61689e29/go.mod h1:l42xBSOrCmkAxzWUHcoUsG/cP8m1hMhV72GoChOX3bg= +github.com/henrybear327/go-proton-api v0.0.0-20230717103708-031d819d74ab h1:Lj7+orKyKsOo3UwlopF+IxC7RWdyboAi800RWHiI8Ig= +github.com/henrybear327/go-proton-api v0.0.0-20230717103708-031d819d74ab/go.mod h1:l42xBSOrCmkAxzWUHcoUsG/cP8m1hMhV72GoChOX3bg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=