Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for downloading public files #24

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 130 additions & 7 deletions mega.go
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,112 @@ func (m *Mega) NewDownload(src *Node) (*Download, error) {
return d, nil
}

// Create a new public Download from the hash and key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you get the hash for a public file? From the URL?

What about directories?

Copy link
Author

@jackwilsdon jackwilsdon Aug 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you get the hash for a public file? From the URL?

https://mega.nz/file/hash#key

Maybe hash isn't the best name... ID might be better?

What about directories?

I've not actually tested this with directories, but I'd guess that they work completely differently (looking at the URLs at least).

//
// Call Chunks to find out how many chunks there are, then for id =
// 0..chunks-1 call DownloadChunk. Finally call Finish() to receive
// the error status.
func (m *Mega) NewPublicDownload(hash, key string) (*Download, error) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too sure on using the existing Download type for this, as we end up faking a Node instance which technically doesn't exist in the user's filesystem.

A nicer option may be a whole new PublicDownload type, but that would be a lot more code (primarily duplicated from the existing Download type). I'm open to suggestions here on how this can be improved.

var msg [1]DownloadMsg
var res [1]DownloadResp

// XXX: Do we need this mutex here?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the mutex is required

m.FS.mutex.Lock()
msg[0].Cmd = "g"
msg[0].G = 1
msg[0].P = hash
m.FS.mutex.Unlock()

request, err := json.Marshal(msg)
if err != nil {
return nil, err
}
result, err := m.api_request(request)
if err != nil {
return nil, err
}

err = json.Unmarshal(result, &res)
if err != nil {
return nil, err
}

// DownloadResp has an embedded error in it for some reason
if res[0].Err != 0 {
return nil, parseError(res[0].Err)
}

rawkey, err := base64urldecode(key)
if err != nil {
return nil, err
}

compkey, err := bytes_to_a32(rawkey)
if err != nil {
return nil, err
}

aes_key, err := a32_to_bytes([]uint32{compkey[0] ^ compkey[4], compkey[1] ^ compkey[5], compkey[2] ^ compkey[6], compkey[3] ^ compkey[7]})
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been copied from addFSNode (as has some of the other logic in this function), which I'm not too happy about. Open to suggestions on how we can remove some of the duplicate code here.

if err != nil {
return nil, err
}

attr, err := decryptAttr(aes_key, res[0].Attr)
if err != nil {
return nil, err
}

chunks := getChunkSizes(int64(res[0].Size))

aes_block, err := aes.NewCipher(aes_key)
if err != nil {
return nil, err
}

mac_enc := cipher.NewCBCEncrypter(aes_block, zero_iv)

var meta NodeMeta
meta.key = aes_key
meta.iv, err = a32_to_bytes([]uint32{compkey[4], compkey[5], 0, 0})
if err != nil {
return nil, err
}
meta.mac, err = a32_to_bytes([]uint32{compkey[6], compkey[7]})
if err != nil {
return nil, err
}
meta.compkey, err = a32_to_bytes(compkey)
if err != nil {
return nil, err
}

t, err := bytes_to_a32(meta.iv)
if err != nil {
return nil, err
}
iv, err := a32_to_bytes([]uint32{t[0], t[1], t[0], t[1]})
if err != nil {
return nil, err
}

d := &Download{
m: m,
src: &Node{
fs: m.FS,
name: attr.Name,
hash: hash,
meta: meta,
},
resourceUrl: res[0].G,
aes_block: aes_block,
iv: iv,
mac_enc: mac_enc,
chunks: chunks,
chunk_macs: make([][]byte, len(chunks)),
}
return d, nil
}

// Chunks returns The number of chunks in the download.
func (d *Download) Chunks() int {
return len(d.chunks)
Expand Down Expand Up @@ -1170,19 +1276,14 @@ func (d *Download) Finish() (err error) {
}

// Download file from filesystem reporting progress if not nil
func (m *Mega) DownloadFile(src *Node, dstpath string, progress *chan int) error {
func (m *Mega) downloadFile(d *Download, dstpath string, progress *chan int) error {
defer func() {
if progress != nil {
close(*progress)
}
}()

d, err := m.NewDownload(src)
if err != nil {
return err
}

_, err = os.Stat(dstpath)
_, err := os.Stat(dstpath)
if os.IsExist(err) {
err = os.Remove(dstpath)
if err != nil {
Expand Down Expand Up @@ -1258,6 +1359,28 @@ func (m *Mega) DownloadFile(src *Node, dstpath string, progress *chan int) error
return d.Finish()
}

// Download file from filesystem reporting progress if not nil
func (m *Mega) DownloadFile(src *Node, dstpath string, progress *chan int) error {
d, err := m.NewDownload(src)

if err != nil {
return err
}

return m.downloadFile(d, dstpath, progress)
}

// Download public file from filesystem reporting progress if not nil
func (m *Mega) DownloadPublicFile(hash, key, dstpath string, progress *chan int) error {
d, err := m.NewPublicDownload(hash, key)

if err != nil {
return err
}

return m.downloadFile(d, dstpath, progress)
}

// Upload contains the internal state of a upload
type Upload struct {
m *Mega
Expand Down