el_finder_s3 0.1.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.
@@ -0,0 +1,24 @@
1
+ if RUBY_VERSION < '1.9'
2
+ begin
3
+ require 'base64'
4
+ rescue LoadError
5
+ end
6
+
7
+ if defined? ::Base64
8
+ # The Base64 module provides for the encoding (encode64, strict_encode64, urlsafe_encode64) and decoding (decode64, strict_decode64, urlsafe_decode64) of binary data using a Base64 representation.
9
+ # @note stdlib module.
10
+ module ::Base64
11
+ # Returns the Base64-encoded version of bin. This method complies with "Base 64 Encoding with URL and Filename Safe Alphabet" in RFC 4648. The alphabet uses '-' instead of '+' and '_' instead of '/'.
12
+ # @note This method will be defined only on ruby 1.8 due to its absence in stdlib.
13
+ def self.urlsafe_encode64(bin)
14
+ [bin].pack("m0").tr("+/", "-_")
15
+ end
16
+
17
+ # Returns the Base64-decoded version of str. This method complies with "Base 64 Encoding with URL and Filename Safe Alphabet" in RFC 4648. The alphabet uses '-' instead of '+' and '_' instead of '/'.
18
+ # @note This method will be defined only on ruby 1.8 due to its absence in stdlib.
19
+ def self.urlsafe_decode64(str)
20
+ str.tr("-_", "+/").unpack("m0").first
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module ElFinderS3
2
+ class CacheConnector
3
+
4
+ def ls_folder(folder)
5
+ raise 'This is not implemented!'
6
+ end
7
+
8
+ def tree_for(root)
9
+ raise 'This is not implemented!'
10
+ end
11
+
12
+ def mkdirSuccess(folder)
13
+ raise 'This is not implemented!'
14
+ end
15
+
16
+ def list_objects search_parameters
17
+ raise 'This is not implemented!'
18
+ end
19
+
20
+
21
+
22
+ def update_ls_folder_results query, response
23
+ raise 'This is not implemented!'
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,676 @@
1
+ require 'logger'
2
+ require 'base64'
3
+
4
+ module ElFinderS3
5
+
6
+ # Represents ElFinder connector on Rails side.
7
+ class Connector
8
+
9
+ # Valid commands to run.
10
+ # @see #run
11
+ VALID_COMMANDS = %w[archive duplicate put file ls tree extract mkdir mkfile open paste ping get rename resize rm tmb upload]
12
+
13
+ attr_reader :adapter
14
+
15
+ # Default options for instances.
16
+ # @see #initialize
17
+ DEFAULT_OPTIONS = {
18
+ :mime_handler => ElFinderS3::MimeType,
19
+ :image_handler => ElFinderS3::Image,
20
+ :original_filename_method => lambda { |file| file.original_filename.respond_to?(:force_encoding) ? file.original_filename.force_encoding('utf-8') : file.original_filename },
21
+ :disabled_commands => %w(archive duplicate extract resize tmb),
22
+ :allow_dot_files => true,
23
+ :upload_max_size => '50M',
24
+ :name_validator => lambda { |name| name.strip != '.' && name =~ /^[^\x00-\x1f\\?*:"><|\/]+$/ },
25
+ :upload_file_mode => 0644,
26
+ :archivers => {},
27
+ :extractors => {},
28
+ :home => 'Home',
29
+ :default_perms => {:read => true, :write => true, :locked => false, :hidden => false},
30
+ :perms => [],
31
+ :thumbs => false,
32
+ :thumbs_directory => '.thumbs',
33
+ :thumbs_size => 48,
34
+ :thumbs_at_once => 5,
35
+ }
36
+
37
+ # Initializes new instance.
38
+ # @param [Hash] options Instance options. :url and :server options are required.
39
+ # @option options [String] :url Entry point of ElFinder router.
40
+ # @option options [String] :server A hash containing the :host, :username, :password, and, optionally, :port to connect to
41
+ # @see DEFAULT_OPTIONS
42
+ def initialize(options)
43
+ @options = DEFAULT_OPTIONS.merge(options)
44
+
45
+ raise(ArgumentError, 'Missing required :url option') unless @options.key?(:url)
46
+ raise(ArgumentError, 'Missing required :server option') unless @options.key?(:server)
47
+ raise(ArgumentError, 'Mime Handler is invalid') unless mime_handler.respond_to?(:for)
48
+ raise(ArgumentError, 'Image Handler is invalid') unless image_handler.nil? || ([:size, :resize, :thumbnail].all? { |m| image_handler.respond_to?(m) })
49
+
50
+ raise(ArgumentError, 'Missing required :region option') unless @options[:server].key?(:region)
51
+ raise(ArgumentError, 'Missing required :access_key_id option') unless @options[:server].key?(:access_key_id)
52
+ raise(ArgumentError, 'Missing required :secret_access_key option') unless @options[:server].key?(:secret_access_key)
53
+ raise(ArgumentError, 'Missing required :bucket_name option') unless @options[:server].key?(:bucket_name)
54
+
55
+ @options[:url] = 'https://' + @options[:server][:bucket] + '.s3.amazonaws.com' unless @options.key?(:url)
56
+
57
+ @headers = {}
58
+ @response = {}
59
+ end
60
+
61
+ # of initialize
62
+
63
+ # Logger is a class property
64
+ class <<self
65
+ def logger
66
+ @logger ||= Logger.new(STDOUT)
67
+ end
68
+
69
+ def logger=(val)
70
+ @logger = val
71
+ end
72
+ end
73
+
74
+ # Runs request-response cycle.
75
+ # @param [Hash] params Request parameters. :cmd option is required.
76
+ # @option params [String] :cmd Command to be performed.
77
+ # @see VALID_COMMANDS
78
+ def run(params)
79
+
80
+ @adapter = ElFinderS3::Adapter.new(@options[:server])
81
+ # @adapter = ElFinderS3::FtpAdapter.new(@options[:server])
82
+ @root = ElFinderS3::Pathname.new(adapter)
83
+
84
+ begin
85
+ @params = params.dup
86
+ @headers = {}
87
+ @response = {}
88
+ @response[:errorData] = {}
89
+
90
+ if VALID_COMMANDS.include?(@params[:cmd])
91
+
92
+ if @options[:thumbs]
93
+ @thumb_directory = @root + @options[:thumbs_directory]
94
+ @thumb_directory.mkdir unless @thumb_directory.exist?
95
+ raise(RuntimeError, "Unable to create thumbs directory") unless @thumb_directory.directory?
96
+ end
97
+
98
+ @current = @params[:current] ? from_hash(@params[:current]) : nil
99
+ @target = (@params[:target] and !@params[:target].empty?) ? from_hash(@params[:target]) : nil
100
+ if params[:targets]
101
+ @targets = @params[:targets].map { |t| from_hash(t) }
102
+ end
103
+
104
+ begin
105
+ send("_#{@params[:cmd]}")
106
+ rescue Net::FTPPermError
107
+ @response[:error] = 'Access Denied'
108
+ end
109
+ else
110
+ invalid_request
111
+ end
112
+
113
+ @response.delete(:errorData) if @response[:errorData].empty?
114
+
115
+ return @headers, @response
116
+ ensure
117
+ adapter.close
118
+ end
119
+ end
120
+
121
+ # of run
122
+
123
+ #
124
+ def to_hash(pathname)
125
+ # note that '=' are removed
126
+ Base64.urlsafe_encode64(pathname.path.to_s).chomp.tr("=\n", "")
127
+ end
128
+
129
+ # of to_hash
130
+
131
+ #
132
+ def from_hash(hash)
133
+ # restore missing '='
134
+ len = hash.length % 4
135
+ hash += '==' if len == 1 or len == 2
136
+ hash += '=' if len == 3
137
+
138
+ decoded_hash = Base64.urlsafe_decode64(hash)
139
+ decoded_hash = decoded_hash.respond_to?(:force_encoding) ? decoded_hash.force_encoding('utf-8') : decoded_hash
140
+ pathname = @root + decoded_hash
141
+ rescue ArgumentError => e
142
+ if e.message != 'invalid base64'
143
+ raise
144
+ end
145
+ nil
146
+ end
147
+
148
+ # of from_hash
149
+
150
+ # @!attribute [w] options
151
+ # Options setter.
152
+ # @param value [Hash] Options to be merged with instance ones.
153
+ # @return [Hash] Updated options.
154
+ def options=(value = {})
155
+ value.each_pair do |k, v|
156
+ @options[k.to_sym] = v
157
+ end
158
+ @options
159
+ end
160
+
161
+ # of options=
162
+
163
+ ################################################################################
164
+ protected
165
+
166
+ #
167
+ def _open(target = nil)
168
+ target ||= @target
169
+
170
+ if target.nil?
171
+ _open(@root)
172
+ return
173
+ end
174
+
175
+ if perms_for(target)[:read] == false
176
+ @response[:error] = 'Access Denied'
177
+ return
178
+ end
179
+
180
+ if target.file?
181
+ command_not_implemented
182
+ elsif target.directory?
183
+ @response[:cwd] = cwd_for(target)
184
+ @response[:cdc] = target.children.
185
+ reject { |child| perms_for(child)[:hidden] }.
186
+ sort_by { |e| e.basename.to_s.downcase }.map { |e| cdc_for(e) }.compact
187
+
188
+ if @params[:tree]
189
+ @response[:tree] = {
190
+ :name => @options[:home],
191
+ :hash => to_hash(@root),
192
+ :dirs => tree_for(@root),
193
+ }.merge(perms_for(@root))
194
+ end
195
+
196
+ if @params[:init]
197
+ @response[:disabled] = @options[:disabled_commands]
198
+ @response[:params] = {
199
+ :dotFiles => @options[:allow_dot_files],
200
+ :uplMaxSize => @options[:upload_max_size],
201
+ :archives => @options[:archivers].keys,
202
+ :extract => @options[:extractors].keys,
203
+ :url => @options[:url]
204
+ }
205
+ end
206
+
207
+ else
208
+ @response[:error] = "Directory does not exist"
209
+ _open(@root) if File.directory?(@root)
210
+ end
211
+
212
+ end
213
+
214
+ # of open
215
+
216
+ def _ls
217
+ if @target.directory?
218
+ files = @target.files.reject { |child| perms_for(child)[:hidden] }
219
+
220
+ @response[:list] = files.map { |e| e.basename.to_s }.compact
221
+ else
222
+ @response[:error] = "Directory does not exist"
223
+ end
224
+
225
+ end
226
+
227
+ # of open
228
+
229
+ def _tree
230
+ if @target.directory?
231
+ @response[:tree] = tree_for(@target).map { |e| cdc_for(e) }.compact
232
+ else
233
+ @response[:error] = "Directory does not exist"
234
+ end
235
+
236
+ end
237
+
238
+ # of tree
239
+
240
+ #
241
+ def _mkdir
242
+ if perms_for(@target)[:write] == false
243
+ @response[:error] = 'Access Denied'
244
+ return
245
+ end
246
+ unless valid_name?(@params[:name])
247
+ @response[:error] = 'Unable to create folder'
248
+ return
249
+ end
250
+
251
+ dir = @target + @params[:name]
252
+ if !dir.exist? && dir.mkdir
253
+ @params[:tree] = true
254
+ @response[:added] = [to_hash(dir)]
255
+ _open(@target)
256
+ else
257
+ @response[:error] = "Unable to create folder"
258
+ end
259
+ end
260
+
261
+ # of mkdir
262
+
263
+ #
264
+ def _mkfile
265
+ if perms_for(@target)[:write] == false
266
+ @response[:error] = 'Access Denied'
267
+ return
268
+ end
269
+ unless valid_name?(@params[:name])
270
+ @response[:error] = 'Unable to create file'
271
+ return
272
+ end
273
+
274
+ file = @target + @params[:name]
275
+ if !file.exist? && file.touch
276
+ @response[:select] = [to_hash(file)]
277
+ _open(@target)
278
+ else
279
+ @response[:error] = "Unable to create file"
280
+ end
281
+ end
282
+
283
+ # of mkfile
284
+
285
+ #
286
+ def _rename
287
+ unless valid_name?(@params[:name])
288
+ @response[:error] = "Unable to rename #{@target.ftype}"
289
+ return
290
+ end
291
+
292
+ to = @target.dirname + @params[:name]
293
+
294
+ perms_for_target = perms_for(@target)
295
+ if perms_for_target[:locked] == true
296
+ @response[:error] = 'Access Denied'
297
+ return
298
+ end
299
+
300
+ perms_for_current = perms_for(@target)
301
+ if perms_for_current[:write] == false
302
+ @response[:error] = 'Access Denied'
303
+ return
304
+ end
305
+
306
+ if to.exist?
307
+ @response[:error] = "Unable to rename #{@target.ftype}. '#{to.basename}' already exists"
308
+ else
309
+ to = @target.rename(to)
310
+ if to
311
+ @response[:added] = [cdc_for(to)]
312
+ @response[:removed] = [to_hash(@target)]
313
+ else
314
+ @response[:error] = "Unable to rename #{@target.ftype}"
315
+ end
316
+ end
317
+ end
318
+
319
+ # of rename
320
+
321
+ #
322
+ def _upload
323
+ if perms_for(@current)[:write] == false
324
+ @response[:error] = 'Access Denied'
325
+ return
326
+ end
327
+ select = []
328
+ @params[:upload].to_a.each do |io|
329
+ name = @options[:original_filename_method].call(io)
330
+ unless valid_name?(name)
331
+ @response[:error] = 'Unable to create file'
332
+ return
333
+ end
334
+ dst = @current + name
335
+
336
+ dst.write(io)
337
+
338
+ select << to_hash(dst)
339
+ end
340
+ @response[:select] = select unless select.empty?
341
+ _open(@current)
342
+ end
343
+
344
+ # of upload
345
+
346
+ #
347
+ def _ping
348
+ @headers['Connection'] = 'Close'
349
+ end
350
+
351
+ # of ping
352
+
353
+ #
354
+ def _paste
355
+ if perms_for(from_hash(@params[:dst]))[:write] == false
356
+ @response[:error] = 'Access Denied'
357
+ return
358
+ end
359
+
360
+ added_list = []
361
+ removed_list = []
362
+ @targets.to_a.each do |src|
363
+ if perms_for(src)[:read] == false || (@params[:cut].to_i > 0 && perms_for(src)[:locked] == true)
364
+ @response[:error] ||= 'Some files were not moved.'
365
+ @response[:errorData][src.basename.to_s] = "Access Denied"
366
+ return
367
+ else
368
+ dst = from_hash(@params[:dst]) + src.basename
369
+ if dst.exist?
370
+ @response[:error] ||= 'The target file already exists'
371
+ @response[:errorData][src.basename.to_s] = "already exists in '#{dst.dirname}'"
372
+ else
373
+ if @params[:cut].to_i > 0
374
+ adapter.move(src, dst)
375
+
376
+ added_list.push cdc_for(dst)
377
+ removed_list.push to_hash(src)
378
+ else
379
+ command_not_implemented
380
+ return
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ @response[:added] = added_list unless added_list.empty?
387
+ @response[:removed] = removed_list unless removed_list.empty?
388
+ end
389
+
390
+ # of paste
391
+
392
+ #
393
+ def _rm
394
+ if @targets.empty?
395
+ @response[:error] = "No files were selected for removal"
396
+ else
397
+ removed_list = []
398
+ @targets.to_a.each do |target|
399
+ removed_list.concat remove_target(target)
400
+ end
401
+ @response[:removed] = removed_list unless removed_list.empty?
402
+ end
403
+ end
404
+
405
+ # of rm
406
+
407
+ #
408
+ def _duplicate
409
+ command_not_implemented
410
+ end
411
+
412
+ # of duplicate
413
+
414
+ #
415
+ def _get
416
+ if perms_for(@target)[:read] == true
417
+ @response[:content] = @target.read
418
+ else
419
+ @response[:error] = 'Access Denied'
420
+ end
421
+ end
422
+
423
+ # of get
424
+
425
+ #
426
+ def _file
427
+ if perms_for(@target)[:read] == true
428
+ @response[:file_data] = @target.read
429
+ @response[:mime_type] = mime_handler.for(@target)
430
+ @response[:disposition] = 'attachment'
431
+ @response[:filename] = @target.basename.to_s
432
+ else
433
+ @response[:error] = 'Access Denied'
434
+ end
435
+ end
436
+
437
+ # of file
438
+
439
+ #
440
+ def _put
441
+ perms = perms_for(@target)
442
+ if perms[:read] == true && perms[:write] == true
443
+ @target.write @params[:content]
444
+ @response[:changed] = [cdc_for(@target)]
445
+ else
446
+ @response[:error] = 'Access Denied'
447
+ end
448
+ end
449
+
450
+ # of put
451
+
452
+ #
453
+ def _extract
454
+ command_not_implemented
455
+ end
456
+
457
+ # of extract
458
+
459
+ #
460
+ def _archive
461
+ command_not_implemented
462
+ end
463
+
464
+ # of archive
465
+
466
+ #
467
+ def _tmb
468
+ command_not_implemented
469
+ end
470
+
471
+ # of tmb
472
+
473
+ #
474
+ def _resize
475
+ command_not_implemented
476
+ end
477
+
478
+ # of resize
479
+
480
+ ################################################################################
481
+ private
482
+
483
+ #
484
+ def upload_max_size_in_bytes
485
+ bytes = @options[:upload_max_size]
486
+ if bytes.is_a?(String) && bytes.strip =~ /(\d+)([KMG]?)/
487
+ bytes = $1.to_i
488
+ unit = $2
489
+ case unit
490
+ when 'K'
491
+ bytes *= 1024
492
+ when 'M'
493
+ bytes *= 1024 * 1024
494
+ when 'G'
495
+ bytes *= 1024 * 1024 * 1024
496
+ end
497
+ end
498
+ bytes.to_i
499
+ end
500
+
501
+ #
502
+ def thumbnail_for(pathname)
503
+ @thumb_directory + "#{to_hash(pathname)}.png"
504
+ end
505
+
506
+ #
507
+ def remove_target(target)
508
+ removed = []
509
+ if target.directory?
510
+ target.children.each do |child|
511
+ removed.concat remove_target(child)
512
+ end
513
+ end
514
+ if perms_for(target)[:locked] == true
515
+ @response[:error] ||= 'Some files/directories were unable to be removed'
516
+ @response[:errorData][target.basename.to_s] = "Access Denied"
517
+ else
518
+ begin
519
+ removed.push to_hash(target)
520
+ target.unlink
521
+ if @options[:thumbs] && (thumbnail = thumbnail_for(target)).file?
522
+ removed.push to_hash(thumbnail)
523
+ thumbnail.unlink
524
+ end
525
+ rescue Exception => ex
526
+ @response[:error] ||= 'Some files/directories were unable to be removed'
527
+ @response[:errorData][target.basename.to_s] = "Remove failed"
528
+ end
529
+ end
530
+
531
+ removed
532
+ end
533
+
534
+ def mime_handler
535
+ @options[:mime_handler]
536
+ end
537
+
538
+ #
539
+ def image_handler
540
+ @options[:image_handler]
541
+ end
542
+
543
+ def cwd_for(pathname)
544
+ {
545
+ :name => pathname.basename.to_s,
546
+ :hash => to_hash(pathname),
547
+ :mime => 'directory',
548
+ :rel => pathname.is_root? ? @options[:home] : (@options[:home] + '/' + pathname.path.to_s),
549
+ :size => 0,
550
+ :date => pathname.mtime.to_s,
551
+ }.merge(perms_for(pathname))
552
+ end
553
+
554
+ def cdc_for(pathname)
555
+ return nil if @options[:thumbs] && pathname.to_s == @thumb_directory.to_s
556
+ response = {
557
+ :name => pathname.basename.to_s,
558
+ :hash => to_hash(pathname),
559
+ :date => pathname.mtime.to_s
560
+ }
561
+ response.merge! perms_for(pathname)
562
+
563
+ if pathname.directory?
564
+ response.merge!(
565
+ :size => 0,
566
+ :mime => 'directory'
567
+ )
568
+ elsif pathname.file?
569
+ response.merge!(
570
+ :size => pathname.size,
571
+ :mime => mime_handler.for(pathname),
572
+ :url => (@options[:url] + '/' + pathname.path.to_s)
573
+ )
574
+
575
+ if pathname.readable? && response[:mime] =~ /image/ && !image_handler.nil?
576
+ response.merge!(
577
+ :resize => true,
578
+ :dim => image_handler.size(pathname)
579
+ )
580
+ if @options[:thumbs]
581
+ if (thumbnail = thumbnail_for(pathname)).exist?
582
+ response.merge!(:tmb => (@options[:url] + '/' + thumbnail.path.to_s))
583
+ else
584
+ @response[:tmb] = true
585
+ end
586
+ end
587
+ end
588
+
589
+ end
590
+
591
+ if pathname.symlink?
592
+ response.merge!(
593
+ :link => to_hash(@root + pathname.readlink), # hash of file to which point link
594
+ :linkTo => (@root + pathname.readlink).relative_to(pathname.dirname.path).to_s, # relative path to
595
+ :parent => to_hash((@root + pathname.readlink).dirname) # hash of directory in which is linked file
596
+ )
597
+ end
598
+
599
+ return response
600
+ end
601
+
602
+ #
603
+ def tree_for(root)
604
+ root.child_directories(@options[:tree_sub_folders]).
605
+ reject { |child|
606
+ (@options[:thumbs] && child.to_s == @thumb_directory.to_s) || perms_for(child)[:hidden]
607
+ }.
608
+ sort_by { |e| e.basename.to_s.downcase }.
609
+ map { |child|
610
+ {:name => child.basename.to_s,
611
+ :hash => to_hash(child),
612
+ :dirs => tree_for(child),
613
+ }.merge(perms_for(child))
614
+ }
615
+ end
616
+
617
+ # of tree_for
618
+
619
+ #
620
+ def perms_for(pathname, options = {})
621
+ # skip = [options[:skip]].flatten
622
+ response = {}
623
+
624
+ response[:read] = pathname.readable?
625
+ response[:read] &&= specific_perm_for(pathname, :read)
626
+ response[:read] &&= @options[:default_perms][:read]
627
+
628
+ response[:write] = pathname.writable?
629
+ response[:write] &&= specific_perm_for(pathname, :write)
630
+ response[:write] &&= @options[:default_perms][:write]
631
+
632
+ response[:locked] = pathname.is_root?
633
+ response[:locked] &&= specific_perm_for(pathname, :locked)
634
+ response[:locked] &&= @options[:default_perms][:locked]
635
+
636
+ response[:hidden] = false
637
+ response[:hidden] ||= specific_perm_for(pathname, :hidden)
638
+ response[:hidden] ||= @options[:default_perms][:hidden]
639
+
640
+ response
641
+ end
642
+
643
+ # of perms_for
644
+
645
+ #
646
+ def specific_perm_for(pathname, perm)
647
+ pathname = pathname.path if pathname.is_a?(ElFinderS3::Pathname)
648
+ matches = @options[:perms].select { |k, v| pathname.to_s.send((k.is_a?(String) ? :== : :match), k) }
649
+ if perm == :hidden
650
+ matches.one? { |e| e.last[perm] }
651
+ else
652
+ matches.none? { |e| e.last[perm] == false }
653
+ end
654
+ end
655
+
656
+ # of specific_perm_for
657
+
658
+ #
659
+ def valid_name?(name)
660
+ @options[:name_validator].call(name)
661
+ end
662
+
663
+ #
664
+ def invalid_request
665
+ @response[:error] = "Invalid command '#{@params[:cmd]}'"
666
+ end
667
+
668
+ # of invalid_request
669
+
670
+ #
671
+ def command_not_implemented
672
+ @response[:error] = "Command '#{@params[:cmd]}' not implemented"
673
+ end # of command_not_implemented
674
+
675
+ end # of class Connector
676
+ end # of module ElFinderS3