rmega 0.1.7 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -0
  3. data/CHANGELOG.md +16 -0
  4. data/README.md +1 -1
  5. data/TODO.md +3 -5
  6. data/bin/rmega-dl +47 -0
  7. data/bin/rmega-up +31 -0
  8. data/lib/rmega.rb +35 -3
  9. data/lib/rmega/api_response.rb +80 -0
  10. data/lib/rmega/cli.rb +121 -0
  11. data/lib/rmega/crypto.rb +20 -0
  12. data/lib/rmega/crypto/aes_cbc.rb +46 -0
  13. data/lib/rmega/crypto/aes_ctr.rb +15 -84
  14. data/lib/rmega/crypto/aes_ecb.rb +25 -0
  15. data/lib/rmega/crypto/rsa.rb +21 -12
  16. data/lib/rmega/errors.rb +3 -51
  17. data/lib/rmega/loggable.rb +0 -3
  18. data/lib/rmega/net.rb +56 -0
  19. data/lib/rmega/nodes/deletable.rb +0 -3
  20. data/lib/rmega/nodes/downloadable.rb +73 -30
  21. data/lib/rmega/nodes/expandable.rb +14 -10
  22. data/lib/rmega/nodes/factory.rb +30 -17
  23. data/lib/rmega/nodes/file.rb +0 -4
  24. data/lib/rmega/nodes/folder.rb +4 -14
  25. data/lib/rmega/nodes/inbox.rb +0 -2
  26. data/lib/rmega/nodes/node.rb +48 -25
  27. data/lib/rmega/nodes/node_key.rb +44 -0
  28. data/lib/rmega/nodes/root.rb +0 -4
  29. data/lib/rmega/nodes/trash.rb +0 -3
  30. data/lib/rmega/nodes/uploadable.rb +42 -33
  31. data/lib/rmega/not_inspectable.rb +10 -0
  32. data/lib/rmega/options.rb +22 -5
  33. data/lib/rmega/pool.rb +18 -7
  34. data/lib/rmega/progress.rb +53 -13
  35. data/lib/rmega/session.rb +125 -52
  36. data/lib/rmega/storage.rb +25 -21
  37. data/lib/rmega/utils.rb +23 -183
  38. data/lib/rmega/version.rb +2 -1
  39. data/rmega.gemspec +3 -5
  40. data/spec/integration/file_download_spec.rb +14 -32
  41. data/spec/integration/file_integrity_spec.rb +41 -0
  42. data/spec/integration/file_upload_spec.rb +11 -57
  43. data/spec/integration/folder_download_spec.rb +17 -0
  44. data/spec/integration/folder_operations_spec.rb +30 -30
  45. data/spec/integration/login_spec.rb +3 -3
  46. data/spec/integration/resume_download_spec.rb +53 -0
  47. data/spec/integration_spec_helper.rb +9 -4
  48. data/spec/rmega/lib/cli_spec.rb +12 -0
  49. data/spec/rmega/lib/session_spec.rb +31 -0
  50. data/spec/rmega/lib/storage_spec.rb +27 -0
  51. data/spec/rmega/lib/utils_spec.rb +16 -78
  52. data/spec/spec_helper.rb +1 -4
  53. metadata +30 -40
  54. data/lib/rmega/crypto/aes.rb +0 -35
  55. data/lib/rmega/crypto/crypto.rb +0 -107
  56. data/lib/rmega/crypto/rsa_mega.js +0 -455
  57. data/spec/rmega/lib/crypto/aes_spec.rb +0 -12
  58. 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
- (r != 0) ? enc[0..r - 4] : enc
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
- data += '=='[((2-data.length*3)&3)..-1]
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 mpi2b(s)
104
- bn = 1
105
- r = [0]
106
- rn = 0
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 b2s(b)
142
- bs = 28
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 chunks(size)
180
- list = {}
181
- p = 0
182
- pp = 0
183
- i = 1
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
- while i <= 8 and p < size - (i * 0x20000)
186
- list[p] = i * 0x20000
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
- while p < size
193
- list[p] = 0x100000
194
- pp = p
195
- p += list[p]
196
- end
35
+ bytes = string.bytes.to_a
197
36
 
198
- if size - pp > 0
199
- list[pp] = size - pp
200
- end
201
- list
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
@@ -1,3 +1,4 @@
1
1
  module Rmega
2
- VERSION = "0.1.7"
2
+ VERSION = "0.2.0"
3
+ HOMEPAGE = "https://github.com/topac/rmega"
3
4
  end
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 = ["Daniele Molteni"]
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 = "https://github.com/topac/rmega"
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 "httpclient"
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
- if account_file_exists?
5
+ context 'given a public mega url (a small file)' do
7
6
 
8
- let(:storage) { login }
7
+ let(:url) { 'https://mega.co.nz/#!MAkg2Iab!bc9Y2U6d93IlRRKVYpcC9hLZjS4G278OPdH6nTFPDNQ' }
9
8
 
10
- context 'given a public mega url (a small file)' do
11
-
12
- # A file called testfile.txt containting the string "helloworld!"
13
- let(:url) { 'https://mega.co.nz/#!MAkg2Iab!bc9Y2U6d93IlRRKVYpcC9hLZjS4G278OPdH6nTFPDNQ' }
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
- context 'given a public mega url (a big file)' do
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
- count = 0
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
- expect(count).to eql(15_728_640 / 5)
42
- end
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
- context 'upload a small file to the root folder' do
9
+ let(:name) { "test_file" }
11
10
 
12
- before(:all) do
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
- context 'download the same file' do
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
- file = find_file
40
- file.download(download_path)
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 'has the expected content' do
45
- expect(File.read(download_path)).to eql @content
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
- File.open(@path, 'wb') do |f|
58
- 512.times { f.write(@buffer) }
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