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.
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