various fixes

master
Richard O'Dwyer 2019-10-16 21:20:22 +01:00
parent 52471551a8
commit 00c2fb2192
12 changed files with 390 additions and 203 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ build/
dist/ dist/
.pypirc .pypirc
MANIFEST MANIFEST
.vscode/

View File

@ -1,5 +1,5 @@
language: python language: python
python: python:
- "2.7" - "2.7"
install: pip install -r requirements.txt install: pip install -r requirements-dev.txt
script: python tests/unit-tests.py script: python setup.py install && pytest tests/unit-tests.py -x -s -v

View File

@ -1,3 +1,4 @@
import os
from mega import Mega from mega import Mega
@ -9,8 +10,8 @@ def test():
""" """
# user details # user details
email = 'your@email.com' email = os.environ['EMAIL']
password = 'password' password = os.environ['PASS']
mega = Mega() mega = Mega()
# mega = Mega({'verbose': True}) # verbose option for print output # mega = Mega({'verbose': True}) # verbose option for print output
@ -58,5 +59,6 @@ def test():
# empty trash # empty trash
print(m.empty_trash()) print(m.empty_trash())
if __name__ == '__main__': if __name__ == '__main__':
test() test()

View File

@ -1 +1 @@
from .mega import Mega from .mega import Mega # noqa

View File

@ -48,14 +48,14 @@ def prepare_key(arr):
def encrypt_key(a, key): def encrypt_key(a, key):
return sum( return sum(
(aes_cbc_encrypt_a32(a[i:i + 4], key) (aes_cbc_encrypt_a32(a[i:i + 4], key) for i in range(0, len(a), 4)), ()
for i in range(0, len(a), 4)), ()) )
def decrypt_key(a, key): def decrypt_key(a, key):
return sum( return sum(
(aes_cbc_decrypt_a32(a[i:i + 4], key) (aes_cbc_decrypt_a32(a[i:i + 4], key) for i in range(0, len(a), 4)), ()
for i in range(0, len(a), 4)), ()) )
def encrypt_attr(attr, key): def encrypt_attr(attr, key):

View File

@ -11,4 +11,3 @@ class RequestError(Exception):
""" """
# TODO add error response messages # TODO add error response messages
pass pass

View File

@ -9,7 +9,11 @@ import binascii
import requests import requests
import shutil import shutil
from .errors import ValidationError, RequestError from .errors import ValidationError, RequestError
from .crypto import * from .crypto import (
a32_to_base64, encrypt_key, base64_url_encode, encrypt_attr, base64_to_a32,
base64_url_decode, decrypt_attr, a32_to_str, get_chunks, str_to_a32,
decrypt_key, mpi_to_int, stringhash, prepare_key, make_id,
)
import tempfile import tempfile
@ -17,7 +21,7 @@ class Mega(object):
def __init__(self, options=None): def __init__(self, options=None):
self.schema = 'https' self.schema = 'https'
self.domain = 'mega.co.nz' self.domain = 'mega.co.nz'
self.timeout = 160 # max time (secs) to wait for resp from api requests self.timeout = 160 # max secs to wait for resp from api requests
self.sid = None self.sid = None
self.sequence_num = random.randint(0, 0xFFFFFFFF) self.sequence_num = random.randint(0, 0xFFFFFFFF)
self.request_id = make_id(10) self.request_id = make_id(10)
@ -47,12 +51,20 @@ class Mega(object):
password_key = [random.randint(0, 0xFFFFFFFF)] * 4 password_key = [random.randint(0, 0xFFFFFFFF)] * 4
session_self_challenge = [random.randint(0, 0xFFFFFFFF)] * 4 session_self_challenge = [random.randint(0, 0xFFFFFFFF)] * 4
user = self._api_request({ user = self._api_request(
'a': 'up', {
'k': a32_to_base64(encrypt_key(master_key, password_key)), 'a':
'ts': base64_url_encode(a32_to_str(session_self_challenge) + 'up',
a32_to_str(encrypt_key(session_self_challenge, master_key))) 'k':
}) a32_to_base64(encrypt_key(master_key, password_key)),
'ts':
base64_url_encode(
a32_to_str(session_self_challenge) + a32_to_str(
encrypt_key(session_self_challenge, master_key)
)
)
}
)
resp = self._api_request({'a': 'us', 'user': user}) resp = self._api_request({'a': 'us', 'user': user})
# if numeric error code response # if numeric error code response
@ -66,27 +78,34 @@ class Mega(object):
if 'tsid' in resp: if 'tsid' in resp:
tsid = base64_url_decode(resp['tsid']) tsid = base64_url_decode(resp['tsid'])
key_encrypted = a32_to_str( key_encrypted = a32_to_str(
encrypt_key(str_to_a32(tsid[:16]), self.master_key)) encrypt_key(str_to_a32(tsid[:16]), self.master_key)
)
if key_encrypted == tsid[-16:]: if key_encrypted == tsid[-16:]:
self.sid = resp['tsid'] self.sid = resp['tsid']
elif 'csid' in resp: elif 'csid' in resp:
encrypted_rsa_private_key = base64_to_a32(resp['privk']) encrypted_rsa_private_key = base64_to_a32(resp['privk'])
rsa_private_key = decrypt_key(encrypted_rsa_private_key, rsa_private_key = decrypt_key(
self.master_key) encrypted_rsa_private_key, self.master_key
)
private_key = a32_to_str(rsa_private_key) private_key = a32_to_str(rsa_private_key)
self.rsa_private_key = [0, 0, 0, 0] self.rsa_private_key = [0, 0, 0, 0]
for i in range(4): for i in range(4):
l = ((ord(private_key[0]) * 256 + ord(private_key[1]) + 7) / 8) + 2 l = (
(ord(private_key[0]) * 256 + ord(private_key[1]) + 7) / 8
) + 2
self.rsa_private_key[i] = mpi_to_int(private_key[:l]) self.rsa_private_key[i] = mpi_to_int(private_key[:l])
private_key = private_key[l:] private_key = private_key[l:]
encrypted_sid = mpi_to_int(base64_url_decode(resp['csid'])) encrypted_sid = mpi_to_int(base64_url_decode(resp['csid']))
rsa_decrypter = RSA.construct( rsa_decrypter = RSA.construct(
(self.rsa_private_key[0] * self.rsa_private_key[1], (
0L, self.rsa_private_key[2], self.rsa_private_key[0], self.rsa_private_key[0] * self.rsa_private_key[1], 0L,
self.rsa_private_key[1])) self.rsa_private_key[2], self.rsa_private_key[0],
self.rsa_private_key[1]
)
)
sid = '%x' % rsa_decrypter.key._decrypt(encrypted_sid) sid = '%x' % rsa_decrypter.key._decrypt(encrypted_sid)
sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid) sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid)
@ -103,11 +122,23 @@ class Mega(object):
if not isinstance(data, list): if not isinstance(data, list):
data = [data] data = [data]
url = '{0}://g.api.{1}/cs'.format(self.schema, self.domain)
req = requests.post( req = requests.post(
'{0}://g.api.{1}/cs'.format(self.schema, self.domain), url,
params=params, params=params,
data=json.dumps(data), data=json.dumps(data),
timeout=self.timeout) timeout=self.timeout,
headers={
'Origin':
'https://mega.nz',
'Referer':
'https://mega.nz/login',
'User-Agent': (
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:69.0) '
'Gecko/20100101 Firefox/69.0'
),
}
)
json_resp = json.loads(req.text) json_resp = json.loads(req.text)
# if numeric error code response # if numeric error code response
@ -129,7 +160,11 @@ class Mega(object):
Process a file Process a file
""" """
if file['t'] == 0 or file['t'] == 1: if file['t'] == 0 or file['t'] == 1:
keys = dict(keypart.split(':', 1) for keypart in file['k'].split('/') if ':' in keypart) keys = dict(
keypart.split(':', 1)
for keypart in file['k'].split('/')
if ':' in keypart
)
uid = file['u'] uid = file['u']
key = None key = None
# my objects # my objects
@ -137,7 +172,9 @@ class Mega(object):
key = decrypt_key(base64_to_a32(keys[uid]), self.master_key) key = decrypt_key(base64_to_a32(keys[uid]), self.master_key)
# shared folders # shared folders
elif 'su' in file and 'sk' in file and ':' in file['k']: elif 'su' in file and 'sk' in file and ':' in file['k']:
shared_key = decrypt_key(base64_to_a32(file['sk']), self.master_key) shared_key = decrypt_key(
base64_to_a32(file['sk']), self.master_key
)
key = decrypt_key(base64_to_a32(keys[file['h']]), shared_key) key = decrypt_key(base64_to_a32(keys[file['h']]), shared_key)
if file['su'] not in shared_keys: if file['su'] not in shared_keys:
shared_keys[file['su']] = {} shared_keys[file['su']] = {}
@ -153,8 +190,10 @@ class Mega(object):
if key is not None: if key is not None:
# file # file
if file['t'] == 0: if file['t'] == 0:
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], k = (
key[3] ^ key[7]) key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6],
key[3] ^ key[7]
)
file['iv'] = key[4:6] + (0, 0) file['iv'] = key[4:6] + (0, 0)
file['meta_mac'] = key[6:8] file['meta_mac'] = key[6:8]
# folder # folder
@ -189,7 +228,9 @@ class Mega(object):
""" """
ok_dict = {} ok_dict = {}
for ok_item in files['ok']: for ok_item in files['ok']:
shared_key = decrypt_key(base64_to_a32(ok_item['k']), self.master_key) shared_key = decrypt_key(
base64_to_a32(ok_item['k']), self.master_key
)
ok_dict[ok_item['h']] = shared_key ok_dict[ok_item['h']] = shared_key
for s_item in files['s']: for s_item in files['s']:
if s_item['u'] not in shared_keys: if s_item['u'] not in shared_keys:
@ -233,6 +274,8 @@ class Mega(object):
""" """
files = self.get_files() files = self.get_files()
for file in files.items(): for file in files.items():
if not isinstance(file[1]['a'], dict):
continue
if file[1]['a'] and file[1]['a']['n'] == filename: if file[1]['a'] and file[1]['a']['n'] == filename:
return file return file
@ -260,15 +303,17 @@ class Mega(object):
file = file['f'][0] file = file['f'][0]
public_handle = self._api_request({'a': 'l', 'n': file['h']}) public_handle = self._api_request({'a': 'l', 'n': file['h']})
file_key = file['k'][file['k'].index(':') + 1:] file_key = file['k'][file['k'].index(':') + 1:]
decrypted_key = a32_to_base64(decrypt_key(base64_to_a32(file_key), decrypted_key = a32_to_base64(
self.master_key)) decrypt_key(base64_to_a32(file_key), self.master_key)
return '{0}://{1}/#!{2}!{3}'.format(self.schema, )
self.domain, return '{0}://{1}/#!{2}!{3}'.format(
public_handle, self.schema, self.domain, public_handle, decrypted_key
decrypted_key) )
else: else:
raise ValueError('''Upload() response required as input, raise ValueError(
use get_link() for regular file input''') '''Upload() response required as input,
use get_link() for regular file input'''
)
def get_link(self, file): def get_link(self, file):
""" """
@ -278,12 +323,14 @@ class Mega(object):
if 'h' in file and 'k' in file: if 'h' in file and 'k' in file:
public_handle = self._api_request({'a': 'l', 'n': file['h']}) public_handle = self._api_request({'a': 'l', 'n': file['h']})
if public_handle == -11: if public_handle == -11:
raise RequestError("Can't get a public link from that file (is this a shared file?)") raise RequestError(
"Can't get a public link from that file "
"(is this a shared file?)"
)
decrypted_key = a32_to_base64(file['key']) decrypted_key = a32_to_base64(file['key'])
return '{0}://{1}/#!{2}!{3}'.format(self.schema, return '{0}://{1}/#!{2}!{3}'.format(
self.domain, self.schema, self.domain, public_handle, decrypted_key
public_handle, )
decrypted_key)
else: else:
raise ValidationError('File id and key must be present') raise ValidationError('File id and key must be present')
@ -338,7 +385,7 @@ class Mega(object):
node_id = None node_id = None
for i in node_data['f']: for i in node_data['f']:
if i['h'] is not u'': if i['h'] != u'':
node_id = i['h'] node_id = i['h']
return node_id return node_id
@ -346,7 +393,14 @@ class Mega(object):
""" """
Get current remaining disk quota in MegaBytes Get current remaining disk quota in MegaBytes
""" """
json_resp = self._api_request({'a': 'uq', 'xfer': 1}) json_resp = self._api_request(
{
'a': 'uq',
'xfer': 1,
'strg': 1,
'v': 1
}
)
# convert bytes to megabyes # convert bytes to megabyes
return json_resp['mstrg'] / 1048576 return json_resp['mstrg'] / 1048576
@ -402,9 +456,13 @@ class Mega(object):
""" """
Destroy a file by its private id Destroy a file by its private id
""" """
return self._api_request({'a': 'd', return self._api_request(
{
'a': 'd',
'n': file_id, 'n': file_id,
'i': self.request_id}) 'i': self.request_id
}
)
def destroy_url(self, url): def destroy_url(self, url):
""" """
@ -423,9 +481,7 @@ class Mega(object):
if files != {}: if files != {}:
post_list = [] post_list = []
for file in files: for file in files:
post_list.append({"a": "d", post_list.append({"a": "d", "n": file, "i": self.request_id})
"n": file,
"i": self.request_id})
return self._api_request(post_list) return self._api_request(post_list)
########################################################################## ##########################################################################
@ -434,7 +490,14 @@ class Mega(object):
""" """
Download a file by it's file object Download a file by it's file object
""" """
self._download_file(None, None, file=file[1], dest_path=dest_path, dest_filename=dest_filename, is_public=False) self._download_file(
None,
None,
file=file[1],
dest_path=dest_path,
dest_filename=dest_filename,
is_public=False
)
def download_url(self, url, dest_path=None, dest_filename=None): def download_url(self, url, dest_path=None, dest_filename=None):
""" """
@ -443,18 +506,42 @@ class Mega(object):
path = self._parse_url(url).split('!') path = self._parse_url(url).split('!')
file_id = path[0] file_id = path[0]
file_key = path[1] file_key = path[1]
self._download_file(file_id, file_key, dest_path, dest_filename, is_public=True) self._download_file(
file_id, file_key, dest_path, dest_filename, is_public=True
)
def _download_file(self, file_handle, file_key, dest_path=None, dest_filename=None, is_public=False, file=None): def _download_file(
self,
file_handle,
file_key,
dest_path=None,
dest_filename=None,
is_public=False,
file=None
):
if file is None: if file is None:
if is_public: if is_public:
file_key = base64_to_a32(file_key) file_key = base64_to_a32(file_key)
file_data = self._api_request({'a': 'g', 'g': 1, 'p': file_handle}) file_data = self._api_request(
{
'a': 'g',
'g': 1,
'p': file_handle
}
)
else: else:
file_data = self._api_request({'a': 'g', 'g': 1, 'n': file_handle}) file_data = self._api_request(
{
'a': 'g',
'g': 1,
'n': file_handle
}
)
k = (file_key[0] ^ file_key[4], file_key[1] ^ file_key[5], k = (
file_key[2] ^ file_key[6], file_key[3] ^ file_key[7]) file_key[0] ^ file_key[4], file_key[1] ^ file_key[5],
file_key[2] ^ file_key[6], file_key[3] ^ file_key[7]
)
iv = file_key[4:6] + (0, 0) iv = file_key[4:6] + (0, 0)
meta_mac = file_key[6:8] meta_mac = file_key[6:8]
else: else:
@ -485,11 +572,12 @@ class Mega(object):
else: else:
dest_path += '/' dest_path += '/'
temp_output_file = tempfile.NamedTemporaryFile(mode='w+b', prefix='megapy_', delete=False) temp_output_file = tempfile.NamedTemporaryFile(
mode='w+b', prefix='megapy_', delete=False
)
k_str = a32_to_str(k) k_str = a32_to_str(k)
counter = Counter.new( counter = Counter.new(128, initial_value=((iv[0] << 32) + iv[1]) << 64)
128, initial_value=((iv[0] << 32) + iv[1]) << 64)
aes = AES.new(k_str, AES.MODE_CTR, counter=counter) aes = AES.new(k_str, AES.MODE_CTR, counter=counter)
mac_str = '\0' * 16 mac_str = '\0' * 16
@ -520,7 +608,11 @@ class Mega(object):
if self.options.get('verbose') is True: if self.options.get('verbose') is True:
# temp file size # temp file size
file_info = os.stat(temp_output_file.name) file_info = os.stat(temp_output_file.name)
print('{0} of {1} downloaded'.format(file_info.st_size, file_size)) print(
'{0} of {1} downloaded'.format(
file_info.st_size, file_size
)
)
file_mac = str_to_a32(mac_str) file_mac = str_to_a32(mac_str)
@ -550,7 +642,9 @@ class Mega(object):
# generate random aes key (128) for file # generate random aes key (128) for file
ul_key = [random.randint(0, 0xFFFFFFFF) for _ in range(6)] ul_key = [random.randint(0, 0xFFFFFFFF) for _ in range(6)]
k_str = a32_to_str(ul_key[:4]) k_str = a32_to_str(ul_key[:4])
count = Counter.new(128, initial_value=((ul_key[4] << 32) + ul_key[5]) << 64) count = Counter.new(
128, initial_value=((ul_key[4] << 32) + ul_key[5]) << 64
)
aes = AES.new(k_str, AES.MODE_CTR, counter=count) aes = AES.new(k_str, AES.MODE_CTR, counter=count)
upload_progress = 0 upload_progress = 0
@ -582,16 +676,24 @@ class Mega(object):
# encrypt file and upload # encrypt file and upload
chunk = aes.encrypt(chunk) chunk = aes.encrypt(chunk)
output_file = requests.post(ul_url + "/" + str(chunk_start), output_file = requests.post(
data=chunk, timeout=self.timeout) ul_url + "/" + str(chunk_start),
data=chunk,
timeout=self.timeout
)
completion_file_handle = output_file.text completion_file_handle = output_file.text
if self.options.get('verbose') is True: if self.options.get('verbose') is True:
# upload progress # upload progress
print('{0} of {1} uploaded'.format(upload_progress, file_size)) print(
'{0} of {1} uploaded'.format(
upload_progress, file_size
)
)
else: else:
output_file = requests.post(ul_url + "/0", output_file = requests.post(
data='', timeout=self.timeout) ul_url + "/0", data='', timeout=self.timeout
)
completion_file_handle = output_file.text completion_file_handle = output_file.text
file_mac = str_to_a32(mac_str) file_mac = str_to_a32(mac_str)
@ -605,22 +707,33 @@ class Mega(object):
attribs = {'n': os.path.basename(filename)} attribs = {'n': os.path.basename(filename)}
encrypt_attribs = base64_url_encode(encrypt_attr(attribs, ul_key[:4])) encrypt_attribs = base64_url_encode(encrypt_attr(attribs, ul_key[:4]))
key = [ul_key[0] ^ ul_key[4], ul_key[1] ^ ul_key[5], key = [
ul_key[2] ^ meta_mac[0], ul_key[3] ^ meta_mac[1], ul_key[0] ^ ul_key[4], ul_key[1] ^ ul_key[5],
ul_key[4], ul_key[5], meta_mac[0], meta_mac[1]] ul_key[2] ^ meta_mac[0], ul_key[3] ^ meta_mac[1], ul_key[4],
ul_key[5], meta_mac[0], meta_mac[1]
]
encrypted_key = a32_to_base64(encrypt_key(key, self.master_key)) encrypted_key = a32_to_base64(encrypt_key(key, self.master_key))
# update attributes # update attributes
data = self._api_request({'a': 'p', 't': dest, 'n': [{ data = self._api_request(
{
'a':
'p',
't':
dest,
'n': [
{
'h': completion_file_handle, 'h': completion_file_handle,
't': 0, 't': 0,
'a': encrypt_attribs, 'a': encrypt_attribs,
'k': encrypted_key}]}) 'k': encrypted_key
}
]
}
)
# close input file and return API msg # close input file and return API msg
input_file.close() input_file.close()
return data return data
##########################################################################
# OTHER OPERATIONS
def create_folder(self, name, dest=None): def create_folder(self, name, dest=None):
# determine storage node # determine storage node
if dest is None: if dest is None:
@ -638,16 +751,24 @@ class Mega(object):
encrypted_key = a32_to_base64(encrypt_key(ul_key[:4], self.master_key)) encrypted_key = a32_to_base64(encrypt_key(ul_key[:4], self.master_key))
# update attributes # update attributes
data = self._api_request({'a': 'p', data = self._api_request(
't': dest, {
'n': [{ 'a':
'p',
't':
dest,
'n': [
{
'h': 'xxxxxxxx', 'h': 'xxxxxxxx',
't': 1, 't': 1,
'a': encrypt_attribs, 'a': encrypt_attribs,
'k': encrypted_key} 'k': encrypted_key
}
], ],
'i': self.request_id}) 'i':
#return API msg self.request_id
}
)
return data return data
def rename(self, file, new_name): def rename(self, file, new_name):
@ -656,17 +777,22 @@ class Mega(object):
attribs = {'n': new_name} attribs = {'n': new_name}
# encrypt attribs # encrypt attribs
encrypt_attribs = base64_url_encode(encrypt_attr(attribs, file['k'])) encrypt_attribs = base64_url_encode(encrypt_attr(attribs, file['k']))
encrypted_key = a32_to_base64(encrypt_key(file['key'], self.master_key)) encrypted_key = a32_to_base64(
encrypt_key(file['key'], self.master_key)
)
# update attributes # update attributes
data = self._api_request([{ data = self._api_request(
[
{
'a': 'a', 'a': 'a',
'attr': encrypt_attribs, 'attr': encrypt_attribs,
'key': encrypted_key, 'key': encrypted_key,
'n': file['h'], 'n': file['h'],
'i': self.request_id}]) 'i': self.request_id
}
#return API msg ]
)
return data return data
def move(self, file_id, target): def move(self, file_id, target):
@ -697,10 +823,14 @@ class Mega(object):
else: else:
file = target[1] file = target[1]
target_node_id = file['h'] target_node_id = file['h']
return self._api_request({'a': 'm', return self._api_request(
{
'a': 'm',
'n': file_id, 'n': file_id,
't': target_node_id, 't': target_node_id,
'i': self.request_id}) 'i': self.request_id
}
)
def add_contact(self, email): def add_contact(self, email):
""" """
@ -728,17 +858,20 @@ class Mega(object):
if not re.match(r"[^@]+@[^@]+\.[^@]+", email): if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
ValidationError('add_contact requires a valid email address') ValidationError('add_contact requires a valid email address')
else: else:
return self._api_request({'a': 'ur', return self._api_request(
{
'a': 'ur',
'u': email, 'u': email,
'l': l, 'l': l,
'i': self.request_id}) 'i': self.request_id
}
)
def get_contacts(self): def get_contacts(self):
raise NotImplementedError() raise NotImplementedError()
# TODO implement this # TODO implement this
# sn param below = maxaction var with function getsc() in mega.co.nz js # sn param below = maxaction var with function getsc() in mega.co.nz js
# seens to be the 'sn' attrib of the previous request response... # seens to be the 'sn' attrib of the previous request response...
# mega.co.nz js full source @ http://homepages.shu.ac.uk/~rjodwyer/mega-scripts-all.js
# requests goto /sc rather than # requests goto /sc rather than
# req = requests.post( # req = requests.post(
@ -761,16 +894,15 @@ class Mega(object):
Import the public url into user account Import the public url into user account
""" """
file_handle, file_key = self._parse_url(url).split('!') file_handle, file_key = self._parse_url(url).split('!')
return self.import_public_file(file_handle, file_key, dest_node=dest_node, dest_name=dest_name) return self.import_public_file(
file_handle, file_key, dest_node=dest_node, dest_name=dest_name
)
def get_public_file_info(self, file_handle, file_key): def get_public_file_info(self, file_handle, file_key):
""" """
Get size and name of a public file Get size and name of a public file
""" """
data = self._api_request({ data = self._api_request({'a': 'g', 'p': file_handle, 'ssm': 1})
'a': 'g',
'p': file_handle,
'ssm': 1})
# if numeric error code response # if numeric error code response
if isinstance(data, int): if isinstance(data, int):
@ -780,20 +912,22 @@ class Mega(object):
raise ValueError("Unexpected result", data) raise ValueError("Unexpected result", data)
key = base64_to_a32(file_key) key = base64_to_a32(file_key)
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]) k = (
key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]
)
size = data['s'] size = data['s']
unencrypted_attrs = decrypt_attr(base64_url_decode(data['at']), k) unencrypted_attrs = decrypt_attr(base64_url_decode(data['at']), k)
if not unencrypted_attrs: if not unencrypted_attrs:
return None return None
result = { result = {'size': size, 'name': unencrypted_attrs['n']}
'size': size,
'name': unencrypted_attrs['n']}
return result return result
def import_public_file(self, file_handle, file_key, dest_node=None, dest_name=None): def import_public_file(
self, file_handle, file_key, dest_node=None, dest_name=None
):
""" """
Import the public file into user account Import the public file into user account
""" """
@ -809,19 +943,27 @@ class Mega(object):
dest_name = pl_info['name'] dest_name = pl_info['name']
key = base64_to_a32(file_key) key = base64_to_a32(file_key)
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]) k = (
key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7]
)
encrypted_key = a32_to_base64(encrypt_key(key, self.master_key)) encrypted_key = a32_to_base64(encrypt_key(key, self.master_key))
encrypted_name = base64_url_encode(encrypt_attr({'n': dest_name}, k)) encrypted_name = base64_url_encode(encrypt_attr({'n': dest_name}, k))
data = self._api_request({ data = self._api_request(
'a': 'p', {
't': dest_node['h'], 'a':
'n': [{ 'p',
't':
dest_node['h'],
'n': [
{
'ph': file_handle, 'ph': file_handle,
't': 0, 't': 0,
'a': encrypted_name, 'a': encrypted_name,
'k': encrypted_key}]}) 'k': encrypted_key
}
#return API msg ]
}
)
return data return data

8
requirements-dev.txt Normal file
View File

@ -0,0 +1,8 @@
-r requirements.txt
pytest
ipdb
flake8
pep8-naming
autoflake
mccabe
yapf

View File

@ -1,3 +1,2 @@
requests>=0.10 requests>=0.10
pycrypto pycrypto
mega.py

28
setup.cfg Normal file
View File

@ -0,0 +1,28 @@
[bdist_wheel]
universal = 1
[zest.releaser]
create-wheel = yes
[tool:pytest]
addopts = -x -s -v
norecursedirs = .git
[flake8]
exclude = .git,__pycache__,legacy,build,dist,.tox
max-complexity = 15
ignore = E741
[yapf]
based_on_style = pep8
spaces_before_comment = 2
split_before_logical_operator = true
indent_width = 4
split_complex_comprehension = true
column_limit = 79
dedent_closing_brackets = true
spaces_around_power_operator = true
no_spaces_around_selected_binary_operators = false
split_penalty_import_names = 500
join_multiple_lines = true

View File

@ -9,25 +9,31 @@ def get_packages(package):
""" """
Return root package & all sub-packages. Return root package & all sub-packages.
""" """
return [dirpath return [
for dirpath, dirnames, filenames in os.walk(package) dirpath for dirpath, dirnames, filenames in os.walk(package)
if os.path.exists(os.path.join(dirpath, '__init__.py'))] if os.path.exists(os.path.join(dirpath, '__init__.py'))
]
def get_package_data(package): def get_package_data(package):
""" """
Return all files under the root package, that are not in a Return all files under the root package, that are not in a
package themselves. package themselves.
""" """
walk = [(dirpath.replace(package + os.sep, '', 1), filenames) walk = [
(dirpath.replace(package + os.sep, '', 1), filenames)
for dirpath, dirnames, filenames in os.walk(package) for dirpath, dirnames, filenames in os.walk(package)
if not os.path.exists(os.path.join(dirpath, '__init__.py'))] if not os.path.exists(os.path.join(dirpath, '__init__.py'))
]
filepaths = [] filepaths = []
for base, filenames in walk: for base, filenames in walk:
filepaths.extend([os.path.join(base, filename) filepaths.extend(
for filename in filenames]) [os.path.join(base, filename) for filename in filenames]
)
return {package: filepaths} return {package: filepaths}
setup( setup(
name='mega.py', name='mega.py',
version='0.9.17', version='0.9.17',
@ -41,8 +47,7 @@ setup(
install_requires=['pycrypto', 'requests'], install_requires=['pycrypto', 'requests'],
classifiers=[ classifiers=[
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Operating System :: OS Independent', 'Operating System :: OS Independent', 'Programming Language :: Python',
'Programming Language :: Python',
'Topic :: Internet :: WWW/HTTP' 'Topic :: Internet :: WWW/HTTP'
] ]
) )

View File

@ -3,13 +3,14 @@ These unit tests will upload a test file,a test folder and a test contact,
Perform api operations on them, Perform api operations on them,
And them remove them from your account. And them remove them from your account.
""" """
from mega import Mega
import unittest import unittest
import random import random
import os import os
email = 'your@email.com' from mega import Mega
password = 'password'
email = os.environ['EMAIL']
password = os.environ['PASS']
mega = Mega() mega = Mega()
# anonymous login # anonymous login
@ -19,7 +20,9 @@ m = mega.login()
FIND_RESP = None FIND_RESP = None
TEST_CONTACT = 'test@mega.co.nz' TEST_CONTACT = 'test@mega.co.nz'
TEST_PUBLIC_URL = 'https://mega.co.nz/#!EYI2VagT!Ic1yblki8oM4v6XHquCe4gu84kxc4glFchj8OvcT5lw' TEST_PUBLIC_URL = (
'https://mega.nz/#!hYVmXKqL!r0d0-WRnFwulR_shhuEDwrY1Vo103-am1MyUy8oV6Ps'
)
TEST_FILE = os.path.basename(__file__) TEST_FILE = os.path.basename(__file__)
TEST_FOLDER = 'mega.py_testfolder_{0}'.format(random.random()) TEST_FOLDER = 'mega.py_testfolder_{0}'.format(random.random())