rcs-common 9.6.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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +49 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +1 -0
  5. data/Rakefile +27 -0
  6. data/lib/rcs-common.rb +21 -0
  7. data/lib/rcs-common/binary.rb +64 -0
  8. data/lib/rcs-common/cgi.rb +7 -0
  9. data/lib/rcs-common/component.rb +87 -0
  10. data/lib/rcs-common/crypt.rb +71 -0
  11. data/lib/rcs-common/deploy.rb +96 -0
  12. data/lib/rcs-common/diagnosticable.rb +136 -0
  13. data/lib/rcs-common/evidence.rb +261 -0
  14. data/lib/rcs-common/evidence/addressbook.rb +173 -0
  15. data/lib/rcs-common/evidence/application.rb +59 -0
  16. data/lib/rcs-common/evidence/calendar.rb +62 -0
  17. data/lib/rcs-common/evidence/call.rb +185 -0
  18. data/lib/rcs-common/evidence/camera.rb +25 -0
  19. data/lib/rcs-common/evidence/chat.rb +272 -0
  20. data/lib/rcs-common/evidence/clibpoard.rb +58 -0
  21. data/lib/rcs-common/evidence/command.rb +50 -0
  22. data/lib/rcs-common/evidence/common.rb +78 -0
  23. data/lib/rcs-common/evidence/content/camera/001.jpg +0 -0
  24. data/lib/rcs-common/evidence/content/coin/wallet_bit.dat +0 -0
  25. data/lib/rcs-common/evidence/content/coin/wallet_lite.dat +0 -0
  26. data/lib/rcs-common/evidence/content/file/Einstein.docx +0 -0
  27. data/lib/rcs-common/evidence/content/file/arabic.docx +0 -0
  28. data/lib/rcs-common/evidence/content/mouse/001.jpg +0 -0
  29. data/lib/rcs-common/evidence/content/mouse/002.jpg +0 -0
  30. data/lib/rcs-common/evidence/content/mouse/003.jpg +0 -0
  31. data/lib/rcs-common/evidence/content/mouse/004.jpg +0 -0
  32. data/lib/rcs-common/evidence/content/print/001.jpg +0 -0
  33. data/lib/rcs-common/evidence/content/screenshot/001.jpg +0 -0
  34. data/lib/rcs-common/evidence/content/screenshot/002.jpg +0 -0
  35. data/lib/rcs-common/evidence/content/screenshot/003.jpg +0 -0
  36. data/lib/rcs-common/evidence/content/url/001.jpg +0 -0
  37. data/lib/rcs-common/evidence/content/url/002.jpg +0 -0
  38. data/lib/rcs-common/evidence/content/url/003.jpg +0 -0
  39. data/lib/rcs-common/evidence/device.rb +23 -0
  40. data/lib/rcs-common/evidence/download.rb +54 -0
  41. data/lib/rcs-common/evidence/exec.rb +0 -0
  42. data/lib/rcs-common/evidence/file.rb +129 -0
  43. data/lib/rcs-common/evidence/filesystem.rb +71 -0
  44. data/lib/rcs-common/evidence/info.rb +24 -0
  45. data/lib/rcs-common/evidence/keylog.rb +84 -0
  46. data/lib/rcs-common/evidence/mail.rb +237 -0
  47. data/lib/rcs-common/evidence/mic.rb +39 -0
  48. data/lib/rcs-common/evidence/mms.rb +36 -0
  49. data/lib/rcs-common/evidence/money.rb +676 -0
  50. data/lib/rcs-common/evidence/mouse.rb +62 -0
  51. data/lib/rcs-common/evidence/password.rb +60 -0
  52. data/lib/rcs-common/evidence/photo.rb +80 -0
  53. data/lib/rcs-common/evidence/position.rb +303 -0
  54. data/lib/rcs-common/evidence/print.rb +50 -0
  55. data/lib/rcs-common/evidence/screenshot.rb +53 -0
  56. data/lib/rcs-common/evidence/sms.rb +91 -0
  57. data/lib/rcs-common/evidence/url.rb +133 -0
  58. data/lib/rcs-common/fixnum.rb +48 -0
  59. data/lib/rcs-common/gridfs.rb +294 -0
  60. data/lib/rcs-common/heartbeat.rb +96 -0
  61. data/lib/rcs-common/keywords.rb +50 -0
  62. data/lib/rcs-common/mime.rb +65 -0
  63. data/lib/rcs-common/mongoid.rb +19 -0
  64. data/lib/rcs-common/pascalize.rb +62 -0
  65. data/lib/rcs-common/path_utils.rb +67 -0
  66. data/lib/rcs-common/resolver.rb +40 -0
  67. data/lib/rcs-common/rest.rb +17 -0
  68. data/lib/rcs-common/sanitize.rb +42 -0
  69. data/lib/rcs-common/serializer.rb +404 -0
  70. data/lib/rcs-common/signature.rb +141 -0
  71. data/lib/rcs-common/stats.rb +94 -0
  72. data/lib/rcs-common/symbolize.rb +10 -0
  73. data/lib/rcs-common/systemstatus.rb +136 -0
  74. data/lib/rcs-common/temporary.rb +13 -0
  75. data/lib/rcs-common/time.rb +24 -0
  76. data/lib/rcs-common/trace.rb +138 -0
  77. data/lib/rcs-common/trace.yaml +42 -0
  78. data/lib/rcs-common/updater/client.rb +354 -0
  79. data/lib/rcs-common/updater/dsl.rb +178 -0
  80. data/lib/rcs-common/updater/payload.rb +79 -0
  81. data/lib/rcs-common/updater/server.rb +126 -0
  82. data/lib/rcs-common/updater/shared_key.rb +55 -0
  83. data/lib/rcs-common/updater/tmp_dir.rb +13 -0
  84. data/lib/rcs-common/utf16le.rb +83 -0
  85. data/lib/rcs-common/version.rb +5 -0
  86. data/lib/rcs-common/winfirewall.rb +235 -0
  87. data/rcs-common.gemspec +64 -0
  88. data/spec/gridfs_spec.rb +637 -0
  89. data/spec/mongoid.yaml +6 -0
  90. data/spec/signature_spec.rb +105 -0
  91. data/spec/spec_helper.rb +22 -0
  92. data/spec/updater_spec.rb +80 -0
  93. data/tasks/deploy.rake +21 -0
  94. data/tasks/protect.rake +90 -0
  95. data/test/helper.rb +17 -0
  96. data/test/test_binary.rb +107 -0
  97. data/test/test_cgi.rb +14 -0
  98. data/test/test_crypt.rb +125 -0
  99. data/test/test_evidence.rb +52 -0
  100. data/test/test_evidence_manager.rb +119 -0
  101. data/test/test_fixnum.rb +35 -0
  102. data/test/test_keywords.rb +137 -0
  103. data/test/test_mime.rb +49 -0
  104. data/test/test_pascalize.rb +100 -0
  105. data/test/test_path_utils.rb +24 -0
  106. data/test/test_rcs-common.rb +7 -0
  107. data/test/test_sanitize.rb +40 -0
  108. data/test/test_serialization.rb +20 -0
  109. data/test/test_stats.rb +90 -0
  110. data/test/test_symbolize.rb +20 -0
  111. data/test/test_systemstatus.rb +35 -0
  112. data/test/test_time.rb +56 -0
  113. data/test/test_trace.rb +25 -0
  114. data/test/test_utf16le.rb +71 -0
  115. data/test/test_winfirewall.rb +68 -0
  116. metadata +423 -0
@@ -0,0 +1,64 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rcs-common/version"
4
+
5
+ gemspec = Gem::Specification.new do |s|
6
+ s.name = "rcs-common"
7
+ s.version = RCS::Common::VERSION
8
+ s.authors = ["alor", "daniele"]
9
+ s.email = ["alor@hackingteam.it", "daniele@hackingteam.it"]
10
+ s.homepage = ""
11
+ s.summary = %q{rcs-common}
12
+ s.description = %q{Common components for the RCS Backend}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_dependency("log4r", ">= 1.1.9")
20
+ s.add_dependency('eventmachine', '= 1.0.3')
21
+ s.add_dependency('mongoid', '= 4.0.1')
22
+ s.add_dependency('mime-types')
23
+ s.add_dependency('sys-filesystem')
24
+ s.add_dependency('sys-cpu')
25
+ s.add_dependency('ffi')
26
+ s.add_dependency('mail')
27
+ s.add_dependency('sbdb')
28
+ s.add_dependency('yajl-ruby')
29
+ s.add_dependency('em-http-server')
30
+
31
+ s.add_development_dependency("bundler")
32
+ s.add_development_dependency('rake')
33
+ s.add_development_dependency('test-unit')
34
+ s.add_development_dependency('simplecov')
35
+ s.add_development_dependency('rspec')
36
+ s.add_development_dependency('pry')
37
+ end
38
+
39
+ if ENV['PROTECTED']
40
+ files = gemspec.files.dup
41
+
42
+ gemspec.instance_variable_set(:'@test_files', [])
43
+ gemspec.instance_variable_set(:'@files', [])
44
+
45
+ exclusions = [
46
+ /.gitignore/,
47
+ /.ruby-version/,
48
+ /Rakefile/,
49
+ /lib\/rcs-common\/evidence\/content\//,
50
+ /^test\//,
51
+ /^tasks\//,
52
+ /^spec\//
53
+ ]
54
+
55
+ files.reject! do |path|
56
+ exclusions.find { |regexp| path =~ regexp }
57
+ end
58
+
59
+ files.concat(Dir["lib/rgloader/**/*"])
60
+
61
+ gemspec.instance_variable_set(:'@files', files)
62
+ end
63
+
64
+ gemspec
@@ -0,0 +1,637 @@
1
+ require 'spec_helper'
2
+ require 'rcs-common/gridfs'
3
+
4
+ module RCS::Common::GridFS
5
+
6
+ describe 'compatibility' do
7
+
8
+ begin
9
+ # Note: use the modified version of mongo-ruby-driver (with append support)
10
+ # path = File.expand_path('~/.rvm/gems/ruby-2.0.0-p451@rcs-project/bundler/gems/mongo-ruby-driver-b1d59bfe1700/lib')
11
+ # $LOAD_PATH << path if File.exist?(path)
12
+
13
+ require 'mongo'
14
+ rescue LoadError
15
+ $mongo_gem_missing = true
16
+ end
17
+
18
+ break if $mongo_gem_missing
19
+
20
+ let(:bucket) { Bucket.new }
21
+
22
+ let(:db_name) { bucket.session.options[:database] }
23
+
24
+ let(:mongo_client_db) { Mongo::MongoClient.new[db_name] }
25
+
26
+ let(:grid) { Mongo::Grid.new(mongo_client_db)}
27
+
28
+ let(:grid_filesystem) { Mongo::GridFileSystem.new(mongo_client_db) }
29
+
30
+ let(:chunk_size) { Bucket::DEFAULT_CHUNK_SIZE }
31
+
32
+ it 'is compatibile with mongo grid#put' do
33
+ data = 'foo bar'
34
+ id = grid.put(data)
35
+ expect(bucket.get(id.to_s).read).to eq(data)
36
+
37
+ data = ('bar'*chunk_size)+'foo'
38
+ id = grid.put(data)
39
+ expect(bucket.get(id.to_s).read).to eq(data)
40
+ end
41
+
42
+ it 'is compatibile with mongo grid#get' do
43
+ data = 'foo bar'
44
+ id = bucket.put(data)
45
+ id = ::BSON::ObjectId.from_string(id.to_s)
46
+ expect(grid.get(id).read).to eq(data)
47
+
48
+ data = ('bar'*chunk_size)+'foo'
49
+ id = bucket.put(data)
50
+ id = ::BSON::ObjectId.from_string(id.to_s)
51
+ expect(grid.get(id).read).to eq(data)
52
+ end
53
+
54
+ it 'is compatibile with mongo file#read' do
55
+ data = ('bar'*chunk_size*5)+'foo'
56
+
57
+ id = grid.put(data)
58
+
59
+ bucket_file = bucket.get(id.to_s)
60
+ grid_file = grid.get(id)
61
+
62
+ loop do
63
+ bytes = rand(0..1000)
64
+ buff1 = grid_file.read(bytes)
65
+ buff2 = bucket_file.read(bytes)
66
+ expect(buff1).to eq(buff2)
67
+ break if buff1 == '' or buff2 == ''
68
+ end
69
+ end
70
+
71
+ it 'is compatibile with mongo-ruby-driver append' do
72
+ data = "foo"*chunk_size
73
+
74
+ id = bucket.put('a')
75
+ id2 = ::BSON::ObjectId.new
76
+ fs = grid_filesystem.open(id2, 'w') { |f| f.write('a') }
77
+
78
+ index, seek = 0, (chunk_size / 2 - 10)
79
+
80
+ loop {
81
+ cnt = data[index..index+seek-1]
82
+ break if cnt.nil? or cnt.size == 0
83
+ index += seek
84
+
85
+ bucket.append(id, cnt)
86
+ grid_filesystem.open(id2, 'a') { |f| f.write(cnt) }
87
+
88
+ expect(bucket.content(id)).to eq('a'+data[0..index - 1])
89
+ expect(grid_filesystem.open(id2, 'r') { |f| f.read }).to eq('a'+data[0..index - 1])
90
+ }
91
+ end
92
+ end
93
+
94
+ # Keep it at least >= 3 in these tests
95
+ $chunk_size = 5
96
+
97
+ describe File do
98
+
99
+ before do
100
+ Bucket.__send__(:remove_const, :DEFAULT_CHUNK_SIZE)
101
+ Bucket.const_set(:DEFAULT_CHUNK_SIZE, $chunk_size)
102
+ end
103
+
104
+ let(:bucket) { Bucket.new }
105
+
106
+ let(:content) { ('a'*$chunk_size)+'b' }
107
+
108
+ let(:file) { bucket.get(bucket.put(content)) }
109
+
110
+ describe '#eof?' do
111
+
112
+ it 'is false when there are bytes to read' do
113
+ expect(file.eof?).to be_falsey
114
+ end
115
+
116
+ context 'when file#read has been called' do
117
+
118
+ before { file.read }
119
+
120
+ it 'is true' do
121
+ expect(file.eof?).to be_truthy
122
+ end
123
+ end
124
+
125
+ context 'when the file has been read sequentially' do
126
+
127
+ let(:content) { ('a'*$chunk_size)+('b'*$chunk_size)+'c' }
128
+
129
+ let(:file) { bucket.get(bucket.put(content)) }
130
+
131
+ it 'is false until the are no more bytes to read' do
132
+ file.read(1)
133
+ expect(file.eof?).to be_falsey
134
+ file.read($chunk_size**2)
135
+ expect(file.eof?).to be_truthy
136
+
137
+ file.rewind
138
+ file.read(1)
139
+ expect(file.eof?).to be_falsey
140
+ file.read($chunk_size*2)
141
+ expect(file.eof?).to be_truthy
142
+ end
143
+ end
144
+ end
145
+
146
+ describe '#read' do
147
+
148
+ it 'reads the entire file without parameters' do
149
+ expect(file.read).to eq(content)
150
+ end
151
+
152
+ context 'when the content is binary' do
153
+
154
+ let(:content_size) { $chunk_size*20+$chunk_size/2 }
155
+
156
+ let(:content) do
157
+ content = ""
158
+ content_size.times { content << rand(0..255).chr }
159
+ content
160
+ end
161
+
162
+ let(:file) { bucket.get(bucket.put(content)) }
163
+
164
+ def print_bytes(str)
165
+ str2 = []
166
+ str.bytes.each { |b| str2 << b.to_i }
167
+ str2.join(", ")
168
+ end
169
+
170
+ it 'reads the file sequentially (binary)' do
171
+ readed = ""
172
+ loop {
173
+ break if file.eof?;
174
+ readed << file.read($chunk_size/2)
175
+ }
176
+ expect(readed).to eq(content)
177
+ end
178
+ end
179
+
180
+ it 'reads the file sequentially' do
181
+ expect(file.read($chunk_size)).to eq('a'*$chunk_size)
182
+ expect(file.read).to eq('b')
183
+
184
+ file.rewind
185
+ expect(file.read($chunk_size)).to eq('a'*$chunk_size)
186
+ expect(file.read($chunk_size)).to eq('b')
187
+
188
+ file.rewind
189
+ expect(file.read($chunk_size-1)).to eq(('a'*($chunk_size-1)))
190
+ expect(file.read).to eq('ab')
191
+ expect(file.read).to be_empty
192
+
193
+ file.rewind
194
+ $chunk_size.times { expect(file.read(1)).to eq('a') }
195
+ expect(file.read(1)).to eq('b')
196
+ expect(file.read(1)).to be_empty
197
+
198
+ file.rewind
199
+ expect(file.read($chunk_size**2)).to eq(content)
200
+ end
201
+ end
202
+ end
203
+
204
+ describe Bucket do
205
+
206
+ before do
207
+ described_class.__send__(:remove_const, :DEFAULT_CHUNK_SIZE)
208
+ described_class.const_set(:DEFAULT_CHUNK_SIZE, $chunk_size)
209
+ end
210
+
211
+ it "deal with different encodings" do
212
+ bucket = Bucket.new
213
+
214
+ non_utf8_string = "- Men\xFC -"
215
+ expect(non_utf8_string.valid_encoding?).to be_falsey
216
+ id = bucket.put non_utf8_string
217
+ file = bucket.get(id)
218
+ expect(file.read.encoding.to_s).to eq('ASCII-8BIT')
219
+
220
+ utf8_string = "ciao"
221
+ id = bucket.put utf8_string
222
+ file = bucket.get(id)
223
+ expect(file.read.encoding.to_s).to eq('UTF-8')
224
+ end
225
+
226
+ describe '#append' do
227
+
228
+ let(:bucket) { Bucket.new }
229
+
230
+ it 'appends the given data to the file' do
231
+ content = 'a'*$chunk_size
232
+ file_id = bucket.put(content)
233
+
234
+ content << 'b'
235
+ bucket.append(file_id, 'b')
236
+ expect(bucket.content(file_id)).to eq(content)
237
+
238
+ content << 'c'*$chunk_size
239
+ bucket.append(file_id, 'c'*$chunk_size)
240
+ expect(bucket.content(file_id)).to eq(content)
241
+
242
+ content << 'd'*($chunk_size-1)
243
+ bucket.append(file_id, 'd'*($chunk_size-1))
244
+ expect(bucket.content(file_id)).to eq(content)
245
+ end
246
+
247
+ it 'updates the file length' do
248
+ content = 'a'*$chunk_size
249
+ file_id = bucket.put(content[1])
250
+ expect(bucket.get(file_id).length).to eq(1)
251
+ bucket.append(file_id, content[1..-1])
252
+ expect(bucket.get(file_id).length).to eq(content.bytesize)
253
+
254
+ content = 'a'*$chunk_size * 2
255
+ file_id = bucket.put(content[0..$chunk_size-1])
256
+ expect(bucket.get(file_id).length).to eq($chunk_size)
257
+ bucket.append(file_id, content[$chunk_size..-1])
258
+ expect(bucket.get(file_id).length).to eq($chunk_size * 2)
259
+ end
260
+
261
+ it 'updates the md5 (by default)' do
262
+ content = 'a'*$chunk_size
263
+ file_id = bucket.put(content[1])
264
+ expect(bucket.get(file_id).md5).to eq(Digest::MD5.hexdigest('a'))
265
+ bucket.append(file_id, content[1..-1])
266
+ expect(bucket.get(file_id).md5).to eq(Digest::MD5.hexdigest(content))
267
+ end
268
+
269
+ context 'when the file is missing' do
270
+
271
+ it 'raises an error' do
272
+ fake_id = ::BSON::ObjectId.new
273
+ expect { bucket.append(fake_id, 'foo') }.to raise_error(/not found/)
274
+ end
275
+
276
+ context 'when option :create is passed' do
277
+
278
+ it 'creates the file' do
279
+ fake_id = ::BSON::ObjectId.new
280
+ expect { bucket.append(fake_id, 'foo', create: true) }.not_to raise_error
281
+
282
+ fake_id = ::BSON::ObjectId.new
283
+ file_id, length = bucket.append(fake_id, 'foo', create: {filename: 'bar'})
284
+ file = bucket.get(file_id)
285
+ expect(file.filename).to eq('bar')
286
+ expect(file.content).to eq('foo')
287
+ end
288
+ end
289
+ end
290
+
291
+ context "when {md5: false} is given as options" do
292
+
293
+ it 'sets the md5 attribute to nil' do
294
+ content = 'a'*$chunk_size
295
+ file_id = bucket.put(content[1])
296
+ expect(bucket.get(file_id).md5).to eq(Digest::MD5.hexdigest('a'))
297
+ bucket.append(file_id, content[1..-1], md5: false)
298
+ expect(bucket.get(file_id).md5).to be_nil
299
+ end
300
+ end
301
+ end
302
+
303
+ describe '#delete' do
304
+
305
+ let(:bucket) { Bucket.new }
306
+
307
+ let(:content) { ('a'*$chunk_size)+'b' }
308
+
309
+ it 'returns nil when the file exists' do
310
+ id = bucket.put(content)
311
+ expect(bucket.delete(id)).to be_nil
312
+ end
313
+
314
+ it 'returns nil when the file is missing' do
315
+ id = ::BSON::ObjectId.new
316
+ expect(bucket.delete(id)).to be_nil
317
+ end
318
+
319
+ it 'removes the file and its chunks' do
320
+ id = bucket.put(content)
321
+ expect(id).not_to be_nil
322
+ bucket.delete(id)
323
+ expect(bucket.get(id)).to be_nil
324
+ expect(bucket.files_collection.find(_id: id).count).to be_zero
325
+ expect(bucket.chunks_collection.find(files_id: id).count).to be_zero
326
+ end
327
+
328
+ it 'does not deletes other files' do
329
+ id = bucket.put(content)
330
+ id2 = bucket.put(content+'c')
331
+
332
+ bucket.delete(id)
333
+
334
+ expect(bucket.get(id)).to be_nil
335
+ expect(bucket.get(id2).read).to eq(content+'c')
336
+ end
337
+ end
338
+
339
+ describe '#drop' do
340
+
341
+ it 'removes the collections' do
342
+ bucket = Bucket.new(nil, lazy: false)
343
+ bucket.drop
344
+ expect(bucket.session.collections).to be_empty
345
+ end
346
+
347
+ context 'when the collections are missing' do
348
+
349
+ let(:bucket) do
350
+ bucket = Bucket.new
351
+ end
352
+
353
+ context '#get' do
354
+
355
+ it 'returns nil (without errors)' do
356
+ expect(bucket.get(::BSON::ObjectId.new)).to be_nil
357
+ end
358
+ end
359
+
360
+ context '#put' do
361
+
362
+ it 'creates the collections and the indexes' do
363
+ bucket.put 'foo bar'
364
+ expect(bucket.session['fs.chunks'].indexes.count).to eq(2)
365
+ expect(bucket.session['fs.files'].indexes.count).to eq(2)
366
+ end
367
+ end
368
+ end
369
+ end
370
+
371
+ describe '#initialize' do
372
+
373
+ it 'creates the requied collections with the default name' do
374
+ session = Bucket.new(nil, lazy: false).session
375
+ names = session.collections.map(&:name).sort
376
+ expect(names).to eq(['fs.chunks', 'fs.files'])
377
+ end
378
+
379
+ it 'creates the requied collections with the given prefix' do
380
+ session = Bucket.new('foo.bar', lazy: false).session
381
+ names = session.collections.map(&:name).sort
382
+ expect(names).to eq(['foo.bar.chunks', 'foo.bar.files'])
383
+ end
384
+
385
+ it 'creates the indexes' do
386
+ session = Bucket.new(nil, lazy: false).session
387
+
388
+ db_name = session.options[:database]
389
+
390
+ chunks_indexes = session['fs.chunks'].indexes
391
+ files_indexes = session['fs.files'].indexes
392
+
393
+ expect(chunks_indexes.count).to eq(2)
394
+ expect(chunks_indexes.to_a[1]).to eq("v"=>1, "key"=>{"files_id"=>1, "n"=>1}, "unique"=>true, "ns"=>"#{db_name}.fs.chunks", "name"=>"files_id_1_n_1")
395
+
396
+ expect(files_indexes.count).to eq(2)
397
+ expect(files_indexes.to_a[1]).to eq("v"=>1, "key"=>{"filename"=>1}, "ns"=>"#{db_name}.fs.files", "background"=>true, "name"=>"filename_1")
398
+ end
399
+
400
+ context 'when nil is passed as name' do
401
+
402
+ it 'creates the requied collections with the default name' do
403
+ session = Bucket.new(nil, lazy: false).session
404
+ names = session.collections.map(&:name).sort
405
+ expect(names).to eq(['fs.chunks', 'fs.files'])
406
+ end
407
+ end
408
+
409
+ context 'when a blank string is passed as name' do
410
+
411
+ it 'creates the requied collections with the default name' do
412
+ session = Bucket.new(' ', lazy: false).session
413
+ names = session.collections.map(&:name).sort
414
+ expect(names).to eq(['fs.chunks', 'fs.files'])
415
+ end
416
+ end
417
+
418
+ context 'when lazy' do
419
+
420
+ it 'does not create the collections' do
421
+ session = Bucket.new('fs', lazy: true).session
422
+ expect(session.collections).to be_empty
423
+ end
424
+ end
425
+ end
426
+
427
+ describe '#md5' do
428
+
429
+ let(:bucket) { Bucket.new }
430
+
431
+ let(:content) { "foo bar" }
432
+
433
+ let(:md5) { Digest::MD5.hexdigest(content) }
434
+
435
+ it 'gets the md5 of a stored file' do
436
+ id = bucket.put(content)
437
+ expect(bucket.md5(id)).to eq(md5)
438
+ end
439
+
440
+ it 'does not read the md5 attribute on the file document' do
441
+ id = bucket.put(content)
442
+ bucket.files_collection.find(_id: id).update('$set' => {md5: 'foo'})
443
+
444
+ expect(bucket.md5(id)).to eq(md5)
445
+ expect(bucket.get(id).md5).to eq('foo')
446
+ end
447
+ end
448
+
449
+ describe '#get' do
450
+
451
+ let(:bucket) { Bucket.new }
452
+
453
+ let(:content) { "foo bar" }
454
+
455
+ it 'retuns a object with a read method' do
456
+ id = bucket.put(content)
457
+ expect(bucket.get(id)).to respond_to(:read)
458
+ end
459
+
460
+ it 'returns nil when nothing is found' do
461
+ id = ::BSON::ObjectId.new
462
+ expect(bucket.get(id)).to be_nil
463
+ end
464
+
465
+ it 'works if the given id is a string' do
466
+ id = bucket.put(content)
467
+ expect(bucket.get(id.to_s)).not_to be_nil
468
+ end
469
+
470
+ it 'works if the given id is a (Moped::)BSON::ObjectId' do
471
+ id = bucket.put(content)
472
+ id = BSON ? ::BSON::ObjectId.from_string(id.to_s) : ::BSON::ObjectId.from_string(id.to_s)
473
+ expect(bucket.get(id)).not_to be_nil
474
+ end
475
+
476
+ it 'does not works with filenames (raise an error)' do
477
+ id = bucket.put(content, filename: 'foo.bar')
478
+ expect{ bucket.get('foo.bar') }.to raise_error(BSON::ObjectId::Invalid)
479
+ end
480
+ end
481
+
482
+ describe '#put' do
483
+
484
+ let(:bucket) { Bucket.new }
485
+
486
+ let(:now) { Time.now.utc }
487
+
488
+ context 'the file size is 7 bytes (< DEFAULT_CHUNK_SIZE)' do
489
+
490
+ let(:content) { "foo bar" }
491
+
492
+ it 'returns nil when nil (or empty string) is passed as content' do
493
+ expect(bucket.put(nil)).to be_nil
494
+ expect(bucket.files_collection.find.count).to be_zero
495
+ expect(bucket.chunks_collection.find.count).to be_zero
496
+ end
497
+
498
+ it 'stores a file with the given (valid) attributes' do
499
+ id = bucket.put(content, filename: 'prova.txt', upload_date: now, metadata: {a: 1})
500
+ file = bucket.get(id)
501
+
502
+ expect(file.read).to eq(content)
503
+ expect(file.filename).to eq('prova.txt')
504
+ expect(file.upload_date.to_i).to eq(now.to_i)
505
+ expect(file.metadata).to eq({'a' => 1})
506
+ end
507
+
508
+ it 'raise an error with invalid attributes' do
509
+ id = bucket.put(content, foo: 'bar', content_type: 'text')
510
+ file = bucket.get(id)
511
+
512
+ expect{ file.foo }.to raise_error(NoMethodError)
513
+ expect(file.content_type).to eq('text')
514
+ end
515
+
516
+ it 'forces the aliases attribute to be an array' do
517
+ id = bucket.put(content, aliases: 'bar')
518
+ file = bucket.get(id)
519
+
520
+ expect(file.aliases).to eq(['bar'])
521
+
522
+ id = bucket.put(content, aliases: ['bar', 'foo'])
523
+ file = bucket.get(id)
524
+
525
+ expect(file.aliases).to eq(['bar', 'foo'])
526
+ end
527
+
528
+
529
+ it 'forces the metadata attribute to be an hash when its empty' do
530
+ id = bucket.put(content, metadata: '')
531
+ file = bucket.get(id)
532
+
533
+ expect(file.metadata).to eq({})
534
+
535
+ id = bucket.put(content, metadata: nil)
536
+ file = bucket.get(id)
537
+
538
+ expect(file.metadata).to eq({})
539
+ end
540
+
541
+ it 'does not overwrite calculated attributes' do
542
+ id = bucket.put(content, md5: 'foo', chunk_size: 'foo', chunkSize: 'foo', length: 'foo')
543
+ file = bucket.get(id)
544
+
545
+ expect(file.md5).to eq(Digest::MD5.hexdigest(content))
546
+ expect(file.length).to eq(content.bytesize)
547
+ expect(file.chunk_size).to eq($chunk_size)
548
+ expect(file.chunkSize).to eq($chunk_size)
549
+ end
550
+
551
+ it 'uses default values for some attributes' do
552
+ id = bucket.put(content, filename: 'prova.txt')
553
+ file = bucket.get(id)
554
+
555
+ expect(file.content_type).to eq('application/octet-stream')
556
+ expect(file.aliases).to eq([])
557
+ expect(file.metadata).to eq({})
558
+ end
559
+ end
560
+
561
+ context 'the file size is DEFAULT_CHUNK_SIZE bytes long' do
562
+
563
+ let(:content) { 'a'*$chunk_size }
564
+
565
+ it 'uses only a chunk' do
566
+ id = bucket.put(content)
567
+ expect(bucket.chunks_collection.find(files_id: id).count).to eq(1)
568
+ end
569
+
570
+ it 'the chunk number is zero' do
571
+ id = bucket.put(content)
572
+ expect(bucket.chunks_collection.find(files_id: id).first['n']).to eq(0)
573
+ end
574
+
575
+ it 'stores the files correctly' do
576
+ id = bucket.put(content)
577
+ file = bucket.get(id)
578
+
579
+ expect(file.read).to eq(content)
580
+ expect(file.md5).to eq(bucket.md5(id))
581
+ expect(file.length).to eq($chunk_size)
582
+ end
583
+ end
584
+
585
+ context 'the file size is DEFAULT_CHUNK_SIZE + 1 bytes long' do
586
+
587
+ let(:content) { ('a'*$chunk_size)+'b' }
588
+
589
+ it 'uses 2 chunks' do
590
+ id = bucket.put(content)
591
+ expect(bucket.chunks_collection.find(files_id: id).count).to eq(2)
592
+ end
593
+
594
+ it 'numbers the chunks correctly' do
595
+ id = bucket.put(content)
596
+ numbers = bucket.chunks_collection.find(files_id: id).map { |doc| doc['n'] }
597
+ expect(numbers).to eq([0, 1])
598
+ end
599
+
600
+ it 'stores the files correctly' do
601
+ id = bucket.put(content)
602
+ file = bucket.get(id)
603
+
604
+ expect(file.read).to eq(content)
605
+ expect(file.md5).to eq(bucket.md5(id))
606
+ expect(file.length).to eq($chunk_size + 1)
607
+ end
608
+ end
609
+
610
+
611
+ context 'the file size is DEFAULT_CHUNK_SIZE * 2 bytes long' do
612
+
613
+ let(:content) { 'a'*$chunk_size*2 }
614
+
615
+ it 'uses 2 chunks' do
616
+ id = bucket.put(content)
617
+ expect(bucket.chunks_collection.find(files_id: id).count).to eq(2)
618
+ end
619
+
620
+ it 'numbers the chunks correctly' do
621
+ id = bucket.put(content)
622
+ numbers = bucket.chunks_collection.find(files_id: id).map { |doc| doc['n'] }
623
+ expect(numbers).to eq([0, 1])
624
+ end
625
+
626
+ it 'stores the files correctly' do
627
+ id = bucket.put(content)
628
+ file = bucket.get(id)
629
+
630
+ expect(file.read).to eq(content)
631
+ expect(file.md5).to eq(bucket.md5(id))
632
+ expect(file.length).to eq($chunk_size*2)
633
+ end
634
+ end
635
+ end
636
+ end
637
+ end