rmega 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/CHANGELOG.md +16 -0
- data/README.md +1 -1
- data/TODO.md +3 -5
- data/bin/rmega-dl +47 -0
- data/bin/rmega-up +31 -0
- data/lib/rmega.rb +35 -3
- data/lib/rmega/api_response.rb +80 -0
- data/lib/rmega/cli.rb +121 -0
- data/lib/rmega/crypto.rb +20 -0
- data/lib/rmega/crypto/aes_cbc.rb +46 -0
- data/lib/rmega/crypto/aes_ctr.rb +15 -84
- data/lib/rmega/crypto/aes_ecb.rb +25 -0
- data/lib/rmega/crypto/rsa.rb +21 -12
- data/lib/rmega/errors.rb +3 -51
- data/lib/rmega/loggable.rb +0 -3
- data/lib/rmega/net.rb +56 -0
- data/lib/rmega/nodes/deletable.rb +0 -3
- data/lib/rmega/nodes/downloadable.rb +73 -30
- data/lib/rmega/nodes/expandable.rb +14 -10
- data/lib/rmega/nodes/factory.rb +30 -17
- data/lib/rmega/nodes/file.rb +0 -4
- data/lib/rmega/nodes/folder.rb +4 -14
- data/lib/rmega/nodes/inbox.rb +0 -2
- data/lib/rmega/nodes/node.rb +48 -25
- data/lib/rmega/nodes/node_key.rb +44 -0
- data/lib/rmega/nodes/root.rb +0 -4
- data/lib/rmega/nodes/trash.rb +0 -3
- data/lib/rmega/nodes/uploadable.rb +42 -33
- data/lib/rmega/not_inspectable.rb +10 -0
- data/lib/rmega/options.rb +22 -5
- data/lib/rmega/pool.rb +18 -7
- data/lib/rmega/progress.rb +53 -13
- data/lib/rmega/session.rb +125 -52
- data/lib/rmega/storage.rb +25 -21
- data/lib/rmega/utils.rb +23 -183
- data/lib/rmega/version.rb +2 -1
- data/rmega.gemspec +3 -5
- data/spec/integration/file_download_spec.rb +14 -32
- data/spec/integration/file_integrity_spec.rb +41 -0
- data/spec/integration/file_upload_spec.rb +11 -57
- data/spec/integration/folder_download_spec.rb +17 -0
- data/spec/integration/folder_operations_spec.rb +30 -30
- data/spec/integration/login_spec.rb +3 -3
- data/spec/integration/resume_download_spec.rb +53 -0
- data/spec/integration_spec_helper.rb +9 -4
- data/spec/rmega/lib/cli_spec.rb +12 -0
- data/spec/rmega/lib/session_spec.rb +31 -0
- data/spec/rmega/lib/storage_spec.rb +27 -0
- data/spec/rmega/lib/utils_spec.rb +16 -78
- data/spec/spec_helper.rb +1 -4
- metadata +30 -40
- data/lib/rmega/crypto/aes.rb +0 -35
- data/lib/rmega/crypto/crypto.rb +0 -107
- data/lib/rmega/crypto/rsa_mega.js +0 -455
- data/spec/rmega/lib/crypto/aes_spec.rb +0 -12
- data/spec/rmega/lib/crypto/crypto_spec.rb +0 -27
data/lib/rmega/utils.rb
CHANGED
@@ -2,203 +2,43 @@ module Rmega
|
|
2
2
|
module Utils
|
3
3
|
extend self
|
4
4
|
|
5
|
-
def str_to_a32(string)
|
6
|
-
size = (string.bytesize + 3) >> 2
|
7
|
-
string = string.ljust (string.bytesize + 3), "\x00"
|
8
|
-
string.unpack "l>#{size}"
|
9
|
-
end
|
10
|
-
|
11
|
-
def a32_to_str(a32, len = nil)
|
12
|
-
if len
|
13
|
-
b = []
|
14
|
-
len.times do |i|
|
15
|
-
# TODO: should be ((a32[i>>2] >>> (24-(i & 3)*8)) & 255)
|
16
|
-
b << (((a32[i>>2] || 0) >> (24-(i & 3)*8)) & 255)
|
17
|
-
end
|
18
|
-
b.pack 'C*'
|
19
|
-
else
|
20
|
-
a32.pack 'l>*'
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def b64a
|
25
|
-
@b64a ||= ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ["-", "_", "="]
|
26
|
-
end
|
27
|
-
|
28
|
-
def a32_to_base64(a32)
|
29
|
-
base64urlencode a32_to_str(a32)
|
30
|
-
end
|
31
|
-
|
32
|
-
def base64_to_a32(base64)
|
33
|
-
str_to_a32 base64urldecode(base64)
|
34
|
-
end
|
35
|
-
|
36
5
|
def base64urlencode(string)
|
37
|
-
i = 0
|
38
|
-
tmp_arr = []
|
39
|
-
|
40
|
-
while i < string.size + 1
|
41
|
-
o1 = string[i].ord rescue 0
|
42
|
-
i += 1
|
43
|
-
o2 = string[i].ord rescue 0
|
44
|
-
i += 1
|
45
|
-
o3 = string[i].ord rescue 0
|
46
|
-
i += 1
|
47
|
-
|
48
|
-
bits = o1 << 16 | o2 << 8 | o3
|
49
|
-
|
50
|
-
h1 = bits >> 18 & 0x3f
|
51
|
-
h2 = bits >> 12 & 0x3f
|
52
|
-
h3 = bits >> 6 & 0x3f
|
53
|
-
h4 = bits & 0x3f
|
54
|
-
|
55
|
-
tmp_arr.push b64a[h1] + b64a[h2] + b64a[h3] + b64a[h4]
|
56
|
-
end
|
57
|
-
|
58
|
-
enc = tmp_arr.join ''
|
59
6
|
r = string.size % 3
|
60
|
-
|
7
|
+
encoded = Base64.urlsafe_encode64(string)
|
8
|
+
return (r != 0) ? encoded[0..r - 4] : encoded
|
61
9
|
end
|
62
10
|
|
63
11
|
def base64urldecode(data)
|
64
|
-
|
65
|
-
|
66
|
-
i = 0
|
67
|
-
ac = 0
|
68
|
-
dec = ""
|
69
|
-
tmp_arr = []
|
70
|
-
|
71
|
-
return data unless data
|
72
|
-
|
73
|
-
while i < data.size
|
74
|
-
h1 = b64a.index(data[i]) || -1
|
75
|
-
i += 1
|
76
|
-
h2 = b64a.index(data[i]) || -1
|
77
|
-
i += 1
|
78
|
-
h3 = b64a.index(data[i]) || -1
|
79
|
-
i += 1
|
80
|
-
h4 = b64a.index(data[i]) || -1
|
81
|
-
i += 1
|
82
|
-
|
83
|
-
bits = (h1 << 18) | (h2 << 12) | (h3 << 6) | h4
|
84
|
-
|
85
|
-
o1 = bits >> 16 & 0xff
|
86
|
-
o2 = bits >> 8 & 0xff
|
87
|
-
o3 = bits & 0xff
|
88
|
-
|
89
|
-
if h3 == 64
|
90
|
-
tmp_arr[ac] = o1.chr
|
91
|
-
elsif h4 == 64
|
92
|
-
tmp_arr[ac] = o1.chr + o2.chr
|
93
|
-
else
|
94
|
-
tmp_arr[ac] = o1.chr + o2.chr + o3.chr
|
95
|
-
end
|
96
|
-
|
97
|
-
ac += 1
|
98
|
-
end
|
99
|
-
|
100
|
-
tmp_arr.join ''
|
12
|
+
fix_for_decoding = '=='[((2-data.length*3)&3)..-1]
|
13
|
+
return Base64.urlsafe_decode64("#{data}#{fix_for_decoding}")
|
101
14
|
end
|
102
15
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
sb = 256
|
108
|
-
sn = s.size
|
109
|
-
bm = 268435455
|
110
|
-
c = nil
|
111
|
-
|
112
|
-
return 0 if sn < 2
|
113
|
-
|
114
|
-
len = (sn - 2) * 8
|
115
|
-
bits = s[0].ord * 256 + s[1].ord
|
116
|
-
|
117
|
-
return 0 if bits > len or bits < len - 8
|
118
|
-
|
119
|
-
len.times do |n|
|
120
|
-
sb = sb << 1
|
121
|
-
|
122
|
-
if sb > 255
|
123
|
-
sb = 1
|
124
|
-
c = s[sn -= 1].ord
|
125
|
-
end
|
126
|
-
|
127
|
-
if bn > bm
|
128
|
-
bn = 1
|
129
|
-
r[rn += 1] = 0
|
130
|
-
end
|
131
|
-
|
132
|
-
if (c & sb) and (c & sb != 0)
|
133
|
-
r[rn] = r[rn] ? (r[rn] | bn) : bn
|
134
|
-
end
|
135
|
-
|
136
|
-
bn = bn << 1
|
137
|
-
end
|
138
|
-
r
|
16
|
+
def hexstr_to_bstr(h)
|
17
|
+
bstr = ''
|
18
|
+
(0..h.length-1).step(2) {|n| bstr << h[n,2].to_i(16).chr }
|
19
|
+
bstr
|
139
20
|
end
|
140
21
|
|
141
|
-
def
|
142
|
-
|
143
|
-
bm = 268435455
|
144
|
-
bn = 1; bc = 0; r = [0]; rb = 1; rn = 0
|
145
|
-
bits = b.length * bs
|
146
|
-
rr = ''
|
147
|
-
|
148
|
-
bits.times do |n|
|
149
|
-
if (b[bc] & bn) and (b[bc] & bn) != 0
|
150
|
-
r[rn] = r[rn] ? (r[rn] | rb) : rb
|
151
|
-
end
|
152
|
-
|
153
|
-
rb = rb << 1
|
154
|
-
|
155
|
-
if rb > 255
|
156
|
-
rb = 1
|
157
|
-
r[rn += 1] = 0
|
158
|
-
end
|
159
|
-
|
160
|
-
bn = bn << 1
|
161
|
-
|
162
|
-
if bn > bm
|
163
|
-
bn = 1
|
164
|
-
bc += 1
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
while rn >= 0 && r[rn] == 0
|
169
|
-
rn -= 1
|
170
|
-
end
|
171
|
-
|
172
|
-
(rn + 1).times do |n|
|
173
|
-
rr = r[n].chr + rr
|
174
|
-
end
|
175
|
-
|
176
|
-
rr
|
22
|
+
def string_to_bignum(string)
|
23
|
+
string.bytes.inject { |a, b| (a << 8) + b }
|
177
24
|
end
|
178
25
|
|
179
|
-
def
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
26
|
+
def base64_mpi_to_bn(s)
|
27
|
+
data = ::Rmega::Utils.base64urldecode(s)
|
28
|
+
len = ((data[0].ord * 256 + data[1].ord + 7) / 8) + 2
|
29
|
+
data[2,len+2].unpack('H*').first.to_i(16)
|
30
|
+
end
|
184
31
|
|
185
|
-
|
186
|
-
|
187
|
-
pp = p
|
188
|
-
p += list[p]
|
189
|
-
i += 1
|
190
|
-
end
|
32
|
+
def compact_to_8_bytes(string)
|
33
|
+
raise("Invalid data length") if string.size != 16
|
191
34
|
|
192
|
-
|
193
|
-
list[p] = 0x100000
|
194
|
-
pp = p
|
195
|
-
p += list[p]
|
196
|
-
end
|
35
|
+
bytes = string.bytes.to_a
|
197
36
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
37
|
+
return 8.times.inject([]) do |ary, i|
|
38
|
+
n = i < 4 ? 0 : 4
|
39
|
+
ary[i] = bytes[i+n] ^ bytes[i+n+4]
|
40
|
+
ary
|
41
|
+
end.map(&:chr).join
|
202
42
|
end
|
203
43
|
end
|
204
44
|
end
|
data/lib/rmega/version.rb
CHANGED
data/rmega.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
require File.expand_path('../lib/rmega/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = ["
|
5
|
+
gem.authors = ["topac"]
|
6
6
|
gem.email = ["dani.m.mobile@gmail.com"]
|
7
7
|
gem.description = %q{mega.co.nz ruby api}
|
8
8
|
gem.summary = %q{mega.co.nz ruby api}
|
9
|
-
gem.homepage =
|
9
|
+
gem.homepage = Rmega::HOMEPAGE
|
10
10
|
|
11
11
|
gem.files = `git ls-files`.split($\)
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -21,7 +21,5 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_development_dependency "rspec"
|
22
22
|
gem.add_development_dependency "rake"
|
23
23
|
|
24
|
-
gem.add_dependency "
|
25
|
-
gem.add_dependency 'activesupport'
|
26
|
-
gem.add_dependency "execjs"
|
24
|
+
gem.add_dependency "activesupport"
|
27
25
|
end
|
@@ -1,45 +1,27 @@
|
|
1
1
|
require 'integration_spec_helper'
|
2
|
-
require 'fileutils'
|
3
2
|
|
4
3
|
describe 'File download' do
|
5
4
|
|
6
|
-
|
5
|
+
context 'given a public mega url (a small file)' do
|
7
6
|
|
8
|
-
let(:
|
7
|
+
let(:url) { 'https://mega.co.nz/#!MAkg2Iab!bc9Y2U6d93IlRRKVYpcC9hLZjS4G278OPdH6nTFPDNQ' }
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
it 'downloads the related file' do
|
16
|
-
storage.download(url, temp_folder)
|
17
|
-
related_file = File.join(temp_folder, 'testfile.txt')
|
18
|
-
expect(File.read(related_file)).to eq "helloworld!\n"
|
19
|
-
end
|
9
|
+
it 'downloads the related file' do
|
10
|
+
Rmega.download(url, temp_folder)
|
11
|
+
related_file = File.join(temp_folder, 'testfile.txt')
|
12
|
+
expect(File.read(related_file)).to eq "helloworld!\n"
|
20
13
|
end
|
14
|
+
end
|
21
15
|
|
22
|
-
|
23
|
-
|
24
|
-
# A file called testfile_big_15mb.txt containting the word "topac" repeated 3145728 times (~ 15mb)
|
25
|
-
let(:url) { 'https://mega.co.nz/#!NYVkDaLD!BKyN5SRpOaEtGnTcwiAqcxmJc7p-k0IPWKAW-471KRE' }
|
26
|
-
|
27
|
-
it 'downloads the related file' do
|
28
|
-
storage.download(url, temp_folder)
|
29
|
-
related_file = File.join(temp_folder, 'testfile_big_15mb.txt')
|
30
|
-
|
31
|
-
expect(File.size(related_file)).to eql 15_728_640
|
16
|
+
context 'given a public mega url (a big file)' do
|
32
17
|
|
33
|
-
|
34
|
-
File.open(related_file, 'rb') do |f|
|
35
|
-
while (word = f.read(3840))
|
36
|
-
break if word != "topac"*768
|
37
|
-
count += 768
|
38
|
-
end
|
39
|
-
end
|
18
|
+
let(:url) { 'https://mega.co.nz/#!NYVkDaLD!BKyN5SRpOaEtGnTcwiAqcxmJc7p-k0IPWKAW-471KRE' }
|
40
19
|
|
41
|
-
|
42
|
-
|
20
|
+
it 'downloads the related file' do
|
21
|
+
Rmega.download(url, temp_folder)
|
22
|
+
related_file = File.join(temp_folder, 'testfile_big_15mb.txt')
|
23
|
+
md5 = Digest::MD5.file(related_file).hexdigest
|
24
|
+
expect(md5).to eq("0451dc82ac003dbef703342e40a1b8f6")
|
43
25
|
end
|
44
26
|
end
|
45
27
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'integration_spec_helper'
|
2
|
+
|
3
|
+
describe 'File integrity over upload/download operations' do
|
4
|
+
|
5
|
+
if account_file_exists?
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@storage = login
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:name) { "test_file" }
|
12
|
+
|
13
|
+
let(:path) { File.join(temp_folder, name)}
|
14
|
+
|
15
|
+
[12, 1_024_000].each do |size|
|
16
|
+
|
17
|
+
context "when a file (#{size} bytes) is uploaded and then downloaded" do
|
18
|
+
|
19
|
+
let(:content) { OpenSSL::Random.random_bytes(size) }
|
20
|
+
|
21
|
+
let(:content_hash) { Digest::MD5.hexdigest(content) }
|
22
|
+
|
23
|
+
before do
|
24
|
+
File.open(path, 'wb') { |f| f.write(content) }
|
25
|
+
file = @storage.root.upload(path)
|
26
|
+
@file = @storage.nodes.find { |n| n.handle == file.handle }
|
27
|
+
expect(@file.name).to eq(name)
|
28
|
+
@file.download(path+".downloaded")
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'it does not get corrupted' do
|
32
|
+
expect(Digest::MD5.file(path+".downloaded").hexdigest).to eq(content_hash)
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
@file.delete if @file
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'integration_spec_helper'
|
2
|
-
require 'fileutils'
|
3
2
|
|
4
3
|
describe 'File upload' do
|
5
4
|
|
@@ -7,72 +6,27 @@ describe 'File upload' do
|
|
7
6
|
|
8
7
|
before(:all) { @storage = login }
|
9
8
|
|
10
|
-
|
9
|
+
let(:name) { "test_file" }
|
11
10
|
|
12
|
-
|
13
|
-
@name = "i_like_trains_#{rand(1E20)}"
|
14
|
-
@content = @name
|
15
|
-
end
|
16
|
-
|
17
|
-
def find_file
|
18
|
-
@storage.root.files.find { |f| f.name == @name }
|
19
|
-
end
|
20
|
-
|
21
|
-
let(:path) { File.join(temp_folder, @name) }
|
22
|
-
|
23
|
-
before do
|
24
|
-
File.open(path, 'wb') { |f| f.write(@content) }
|
25
|
-
@storage.root.upload(path)
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'finds the uploaded file' do
|
29
|
-
file = find_file
|
30
|
-
file.delete
|
31
|
-
expect(file).not_to be_nil
|
32
|
-
end
|
11
|
+
let(:path) { File.join(temp_folder, name)}
|
33
12
|
|
34
|
-
|
35
|
-
|
36
|
-
let(:download_path) { "#{path}.downloaded" }
|
13
|
+
[12, 1_024_000].each do |size|
|
14
|
+
context "when a file (#{size} bytes) is uploaded" do
|
37
15
|
|
38
16
|
before do
|
39
|
-
|
40
|
-
file.
|
41
|
-
file.delete
|
17
|
+
File.open(path, 'wb') { |f| f.write(OpenSSL::Random.random_bytes(size)) }
|
18
|
+
@file = @storage.root.upload(path)
|
42
19
|
end
|
43
20
|
|
44
|
-
it '
|
45
|
-
|
21
|
+
it 'it can be found as a file node' do
|
22
|
+
found_node = @storage.root.files.find { |f| f.handle == @file.handle }
|
23
|
+
expect(found_node).not_to be_nil
|
46
24
|
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
context 'upload a big file to a specific folder' do
|
51
|
-
|
52
|
-
before(:all) do
|
53
|
-
@name = "mine_turtles_#{rand(1E20)}"
|
54
|
-
@path = File.join(temp_folder, @name)
|
55
|
-
@buffer = "rofl" * 1024
|
56
25
|
|
57
|
-
|
58
|
-
|
26
|
+
after do
|
27
|
+
@file.delete if @file
|
59
28
|
end
|
60
|
-
|
61
|
-
@folder = @storage.root.create_folder(@name)
|
62
|
-
@folder.upload(@path)
|
63
29
|
end
|
64
|
-
|
65
|
-
it 'finds the uploaded file and verify its content' do
|
66
|
-
file = @folder.files.find { |f| f.name == @name }
|
67
|
-
download_path = "#{@path}.downloaded"
|
68
|
-
file.download(download_path)
|
69
|
-
|
70
|
-
File.open(download_path, 'rb') do |f|
|
71
|
-
512.times { expect(f.read(@buffer.size)).to eq(@buffer) }
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
after { @folder.delete if @folder }
|
76
30
|
end
|
77
31
|
end
|
78
32
|
end
|