mirror of
https://github.com/henrybear327/Proton-API-Bridge.git
synced 2025-12-25 08:39:49 -05:00
319 lines
8.5 KiB
Go
319 lines
8.5 KiB
Go
package proton_api_bridge
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
|
"github.com/henrybear327/go-proton-api"
|
|
)
|
|
|
|
type ProtonDirectoryData struct {
|
|
Link *proton.Link
|
|
Name string
|
|
IsFolder bool
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) ListDirectory(
|
|
ctx context.Context,
|
|
folderLinkID string) ([]*ProtonDirectoryData, error) {
|
|
ret := make([]*ProtonDirectoryData, 0)
|
|
|
|
folderLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, folderLinkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if folderLink.State == proton.LinkStateActive {
|
|
childrenLinks, err := protonDrive.c.ListChildren(ctx, protonDrive.MainShare.ShareID, folderLink.LinkID, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if childrenLinks != nil {
|
|
folderParentKR, err := protonDrive.getNodeKRByID(ctx, folderLink.ParentLinkID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer folderParentKR.ClearPrivateParams()
|
|
folderLinkKR, err := folderLink.GetKeyRing(folderParentKR, protonDrive.AddrKR)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer folderLinkKR.ClearPrivateParams()
|
|
|
|
for i := range childrenLinks {
|
|
if childrenLinks[i].State != proton.LinkStateActive {
|
|
continue
|
|
}
|
|
|
|
name, err := childrenLinks[i].GetName(folderLinkKR, protonDrive.AddrKR)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, &ProtonDirectoryData{
|
|
Link: &childrenLinks[i],
|
|
Name: name,
|
|
IsFolder: childrenLinks[i].Type == proton.LinkTypeFolder,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) ListDirectoriesRecursively(
|
|
ctx context.Context,
|
|
parentNodeKR *crypto.KeyRing,
|
|
link *proton.Link,
|
|
download bool,
|
|
maxDepth, curDepth /* 0-based */ int,
|
|
excludeRoot bool,
|
|
pathSoFar string,
|
|
paths *[]string) error {
|
|
/*
|
|
Assumptions:
|
|
- we only care about the active ones
|
|
*/
|
|
if link.State != proton.LinkStateActive {
|
|
return nil
|
|
}
|
|
// log.Println("curDepth", curDepth, "pathSoFar", pathSoFar)
|
|
|
|
var currentPath = ""
|
|
|
|
if !(excludeRoot && curDepth == 0) {
|
|
name, err := link.GetName(parentNodeKR, protonDrive.AddrKR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
currentPath = pathSoFar + "/" + name
|
|
// log.Println("currentPath", currentPath)
|
|
if paths != nil {
|
|
*paths = append(*paths, currentPath)
|
|
}
|
|
}
|
|
|
|
if download {
|
|
if protonDrive.Config.DataFolderName == "" {
|
|
return ErrDataFolderNameIsEmpty
|
|
}
|
|
|
|
if link.Type == proton.LinkTypeFile {
|
|
log.Println("Downloading", currentPath)
|
|
defer log.Println("Completes downloading", currentPath)
|
|
|
|
byteArray, _, err := protonDrive.DownloadFile(ctx, link)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.WriteFile("./"+protonDrive.Config.DataFolderName+"/"+currentPath, byteArray, 0777)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else /* folder */ {
|
|
if !(excludeRoot && curDepth == 0) {
|
|
// log.Println("Creating folder", currentPath)
|
|
// defer log.Println("Completes creating folder", currentPath)
|
|
|
|
err := os.Mkdir("./"+protonDrive.Config.DataFolderName+"/"+currentPath, 0777)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if maxDepth == -1 || curDepth < maxDepth {
|
|
if link.Type == proton.LinkTypeFolder {
|
|
childrenLinks, err := protonDrive.c.ListChildren(ctx, protonDrive.MainShare.ShareID, link.LinkID, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// log.Printf("childrenLinks len = %v, %#v", len(childrenLinks), childrenLinks)
|
|
|
|
if childrenLinks != nil {
|
|
// get current node's keyring
|
|
linkKR, err := link.GetKeyRing(parentNodeKR, protonDrive.AddrKR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer linkKR.ClearPrivateParams()
|
|
|
|
for _, childLink := range childrenLinks {
|
|
err = protonDrive.ListDirectoriesRecursively(ctx, linkKR, &childLink, download, maxDepth, curDepth+1, excludeRoot, currentPath, paths)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) CreateNewFolderByID(ctx context.Context, parentLinkID string, folderName string) (string, error) {
|
|
parentLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, parentLinkID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return protonDrive.CreateNewFolder(ctx, &parentLink, folderName)
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) CreateNewFolder(ctx context.Context, parentLink *proton.Link, folderName string) (string, error) {
|
|
// TODO: check for duplicated folder name
|
|
|
|
parentNodeKR, err := protonDrive.getNodeKR(ctx, parentLink)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
newNodeKey, newNodePassphraseEnc, newNodePassphraseSignature, err := generateNodeKeys(parentNodeKR, protonDrive.AddrKR)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
createFolderReq := proton.CreateFolderReq{
|
|
ParentLinkID: parentLink.LinkID,
|
|
|
|
// Name string
|
|
// Hash string
|
|
|
|
NodeKey: newNodeKey,
|
|
// NodeHashKey string
|
|
|
|
NodePassphrase: newNodePassphraseEnc,
|
|
NodePassphraseSignature: newNodePassphraseSignature,
|
|
|
|
SignatureAddress: protonDrive.signatureAddress,
|
|
}
|
|
|
|
/* Name is encrypted using the parent's keyring, and signed with address key */
|
|
err = createFolderReq.SetName(folderName, protonDrive.AddrKR, parentNodeKR)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
parentHashKey, err := parentLink.GetHashKey(parentNodeKR)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = createFolderReq.SetHash(folderName, parentHashKey)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
newNodeKR, err := getKeyRing(parentNodeKR, protonDrive.AddrKR, newNodeKey, newNodePassphraseEnc, newNodePassphraseSignature)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = createFolderReq.SetNodeHashKey(newNodeKR)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
createFolderResp, err := protonDrive.c.CreateFolder(ctx, protonDrive.MainShare.ShareID, createFolderReq)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// log.Printf("createFolderResp %#v", createFolderResp)
|
|
|
|
return createFolderResp.ID, nil
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) MoveFileByID(ctx context.Context, srcLinkID, dstParentLinkID string, dstName string) error {
|
|
srcLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, srcLinkID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if srcLink.State != proton.LinkStateActive {
|
|
return ErrLinkMustBeActive
|
|
}
|
|
|
|
dstParentLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, dstParentLinkID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if dstParentLink.State != proton.LinkStateActive {
|
|
return ErrLinkMustBeActive
|
|
}
|
|
|
|
return protonDrive.MoveFile(ctx, &srcLink, &dstParentLink, dstName)
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) MoveFile(ctx context.Context, srcLink *proton.Link, dstParentLink *proton.Link, dstName string) error {
|
|
return protonDrive.MoveFolder(ctx, srcLink, dstParentLink, dstName)
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) MoveFolderByID(ctx context.Context, srcLinkID, dstParentLinkID, dstName string) error {
|
|
srcLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, srcLinkID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if srcLink.State != proton.LinkStateActive {
|
|
return ErrLinkMustBeActive
|
|
}
|
|
|
|
dstParentLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, dstParentLinkID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if dstParentLink.State != proton.LinkStateActive {
|
|
return ErrLinkMustBeActive
|
|
}
|
|
|
|
return protonDrive.MoveFolder(ctx, &srcLink, &dstParentLink, dstName)
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) MoveFolder(ctx context.Context, srcLink *proton.Link, dstParentLink *proton.Link, dstName string) error {
|
|
return protonDrive.moveLink(ctx, srcLink, dstParentLink, dstName)
|
|
}
|
|
|
|
func (protonDrive *ProtonDrive) moveLink(ctx context.Context, srcLink *proton.Link, dstParentLink *proton.Link, dstName string) error {
|
|
// we are moving the srcLink to under dstParentLink, with name dstName
|
|
req := proton.MoveLinkReq{
|
|
ParentLinkID: dstParentLink.LinkID,
|
|
OriginalHash: srcLink.Hash,
|
|
SignatureAddress: protonDrive.signatureAddress,
|
|
}
|
|
|
|
dstParentKR, err := protonDrive.getNodeKR(ctx, dstParentLink)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = req.SetName(dstName, protonDrive.AddrKR, dstParentKR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dstParentHashKey, err := dstParentLink.GetHashKey(dstParentKR)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = req.SetHash(dstName, dstParentHashKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcParentKR, err := protonDrive.getNodeKRByID(ctx, srcLink.ParentLinkID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodePassphrase, err := reencryptKeyPacket(srcParentKR, dstParentKR, protonDrive.AddrKR, srcLink.NodePassphrase)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.NodePassphrase = nodePassphrase
|
|
req.NodePassphraseSignature = srcLink.NodePassphraseSignature
|
|
|
|
return protonDrive.c.MoveLink(ctx, protonDrive.MainShare.ShareID, srcLink.LinkID, req)
|
|
}
|