el_finder_s3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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