el_finder_ftp 1.0.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,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
@@ -0,0 +1,5 @@
1
+ *.swp
2
+ pkg/
3
+ Gemfile.lock
4
+ doc/
5
+ .yardoc/
@@ -0,0 +1,7 @@
1
+ --no-private
2
+ --title 'elFinder server side connector for Ruby'
3
+ --charset utf-8
4
+ --readme README.md
5
+ -
6
+ README.md
7
+ TODO
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in el_finder.gemspec
4
+ gemspec
@@ -0,0 +1,115 @@
1
+ ## el_finder
2
+
3
+ * http://elrte.org/redmine/projects/elfinder
4
+
5
+ ## Description:
6
+
7
+ This is based on Phallstrom's excellent Ruby library to provide server side functionality for
8
+ elFinder: https://github.com/phallstrom/el_finder
9
+
10
+ This version provides a Rails backend for elFinder that uses an FTP server as its file storage,
11
+ rather than the local filesystem. elFinder is an open-source file manager for web, written in
12
+ JavaScript using jQuery UI.
13
+
14
+ ## 2.x API
15
+
16
+ This version provides a partial implementation of the 2.x API (the portions that can be used with FTP).
17
+
18
+ Operations such as archive, copy, duplicate, etc are not possible using FTP. Needless to say, thumbnails are also not
19
+ supported.
20
+
21
+ ## Requirements:
22
+
23
+ Net::FTP is used to communicate with the FTP server.
24
+
25
+ ## Install:
26
+
27
+ * Install elFinder (http://elrte.org/redmine/projects/elfinder/wiki/Install_EN)
28
+ * Do whatever is necessary for your Ruby framework to tie it together.
29
+
30
+ ### Rails 3
31
+
32
+ * Add `gem 'el_finder_ftp'` to Gemfile
33
+ * % bundle install
34
+ * Switch to using jQuery instead of Prototype
35
+ * Add the following action to a controller of your choosing.
36
+
37
+ * Use ElFinderFtp::Action and el_finder_ftp, which handles most of the boilerplate for an ElFinderFtp action:
38
+
39
+ ```ruby
40
+ require 'el_finder_ftp/action'
41
+
42
+ class MyController < ApplicationController
43
+ include ElFinderFtp::Action
44
+
45
+ el_finder_ftp(:action_name) do
46
+ {
47
+ :server => { host: 'my.ftp.com', username: 'username', password: 'password' },
48
+ :url: "/ftp",
49
+ :perms => {
50
+ /^(Welcome|README)$/ => {:read => true, :write => false, :rm => false},
51
+ '.' => {:read => true, :write => false, :rm => false}, # '.' is the proper way to specify the home/root directory.
52
+ /^test$/ => {:read => true, :write => true, :rm => false},
53
+ 'logo.png' => {:read => true},
54
+ /\.png$/ => {:read => false} # This will cause 'logo.png' to be unreadable.
55
+ # Permissions err on the safe side. Once false, always false.
56
+ },
57
+ }
58
+ end
59
+ end
60
+ ```
61
+
62
+ * Add the appropriate route to config/routes.rb such as:
63
+
64
+ ```ruby
65
+ match 'ftp' => 'my_controller#action_name'
66
+ ```
67
+
68
+ * Add the following to your layout. The paths may be different depending
69
+ on where you installed the various js/css files.
70
+
71
+ ```erb
72
+ <%= stylesheet_link_tag 'jquery-ui/base/jquery.ui.all', 'elfinder' %>
73
+ <%= javascript_include_tag :defaults, 'elfinder/elfinder.min' %>
74
+ ```
75
+
76
+ * Add the following to the view that will display elFinder:
77
+
78
+ ```erb
79
+ <%= javascript_tag do %>
80
+ $().ready(function() {
81
+ $('#elfinder').elfinder({
82
+ url: '/ftp',
83
+ lang: 'en'
84
+ })
85
+ })
86
+ <% end %>
87
+ <div id='elfinder'></div>
88
+ ```
89
+
90
+ * That's it.
91
+
92
+ ## License:
93
+
94
+ (The MIT License)
95
+
96
+ Copyright (c) 2010-2013 Chris Micacchi, Philip Hallstrom
97
+
98
+ Permission is hereby granted, free of charge, to any person obtaining
99
+ a copy of this software and associated documentation files (the
100
+ 'Software'), to deal in the Software without restriction, including
101
+ without limitation the rights to use, copy, modify, merge, publish,
102
+ distribute, sublicense, and/or sell copies of the Software, and to
103
+ permit persons to whom the Software is furnished to do so, subject to
104
+ the following conditions:
105
+
106
+ The above copyright notice and this permission notice shall be
107
+ included in all copies or substantial portions of the Software.
108
+
109
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
110
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
111
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
112
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
113
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
114
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
115
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require 'rake/testtask'
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << 'lib' << 'test'
9
+ test.pattern = 'test/**/test_*.rb'
10
+ test.verbose = true
11
+ end
12
+
13
+ require 'yard'
14
+ YARD::Rake::YardocTask.new
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ - Complain if root directory is missing.
2
+ - There's probably a lot of redundancy now between ElFinderFtp::Pathname and ElFinderFtp::FtpPathname,
3
+ since this is based off of the preexisting Ruby ElFinder backend, which used the Ruby stdlib's
4
+ ::Pathname where I've used ElFinderFtp::Pathname.
5
+ - I deleted all the tests but didn't write new ones yet. I probably should.
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "el_finder_ftp/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "el_finder_ftp"
7
+ s.version = ElFinderFtp::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris Micacchi", "Philip Hallstrom"]
10
+ s.email = ["cmicacchi@solvco.com", "philip@pjkh.com"]
11
+ s.homepage = "https://github.com/Nivio/el_finder_ftp"
12
+ s.summary = %q{elFinder server side connector for Ruby, with an FTP backend.}
13
+ s.description = %q{Ruby library to provide server side functionality for elFinder. elFinder is an open-source file manager for web, written in JavaScript using jQuery UI.}
14
+ s.license = "MIT"
15
+
16
+ s.rubyforge_project = "el_finder_ftp"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ s.add_dependency('image_size', '>= 1.0.0')
24
+ s.add_dependency('net-ftp-list', '>= 3.2.5')
25
+ s.add_dependency('browser', '>= 0.1.6')
26
+ s.add_development_dependency('yard', '~> 0.8.1')
27
+ s.add_development_dependency('redcarpet', '~> 2.1.1')
28
+
29
+ end
@@ -0,0 +1,13 @@
1
+ require 'fileutils'
2
+ require 'net/ftp'
3
+
4
+ require 'el_finder_ftp/railties'
5
+ require 'el_finder_ftp/base64'
6
+ require 'el_finder_ftp/ftp_authentication_error'
7
+ require 'el_finder_ftp/ftp_adapter'
8
+ require 'el_finder_ftp/ftp_pathname'
9
+ require 'el_finder_ftp/pathname'
10
+ require 'el_finder_ftp/mime_type'
11
+ require 'el_finder_ftp/image'
12
+ require 'el_finder_ftp/connector'
13
+ require 'el_finder_ftp/action'
@@ -0,0 +1,32 @@
1
+ module ElFinderFtp
2
+ module Action
3
+ class << self
4
+ def included(klass)
5
+ klass.send(:extend, ElFinderFtp::ActionClass)
6
+ end
7
+ end
8
+ end
9
+
10
+ module ActionClass
11
+ def el_finder_ftp(name = :elfinder, &block)
12
+ self.send(:define_method, name) do
13
+ h, r = ElFinderFtp::Connector.new(instance_eval(&block)).run(params)
14
+ headers.merge!(h)
15
+ if r.include?(:file_data)
16
+ send_data r[:file_data], type: r[:mime_type], disposition: r[:disposition], filename: r[:filename]
17
+ else
18
+ if browser.ie8? || browser.ie9?
19
+ # IE 8 and IE 9 don't accept application/json as a response to a POST in some cases:
20
+ # http://blog.degree.no/2012/09/jquery-json-ie8ie9-treats-response-as-downloadable-file/
21
+ # so we send text/html instead
22
+ response = (r.empty? ? {:nothing => true} : {:text => r.to_json})
23
+ else
24
+ response = (r.empty? ? {:nothing => true} : {:json => r})
25
+ end
26
+
27
+ render response, :layout => false
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -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,618 @@
1
+ #
2
+ # http://elrte.org/redmine/projects/elfinder/wiki/Client-Server_Protocol_EN
3
+ #
4
+
5
+ require 'logger'
6
+ require 'base64'
7
+
8
+ module ElFinderFtp
9
+
10
+ # Represents ElFinder connector on Rails side.
11
+ class Connector
12
+
13
+ # Valid commands to run.
14
+ # @see #run
15
+ VALID_COMMANDS = %w[archive duplicate put file ls tree extract mkdir mkfile open paste ping get rename resize rm tmb upload]
16
+
17
+ attr_reader :adapter
18
+
19
+ # Default options for instances.
20
+ # @see #initialize
21
+ DEFAULT_OPTIONS = {
22
+ :mime_handler => ElFinderFtp::MimeType,
23
+ :image_handler => ElFinderFtp::Image,
24
+ :original_filename_method => lambda { |file| file.original_filename.respond_to?(:force_encoding) ? file.original_filename.force_encoding('utf-8') : file.original_filename },
25
+ :disabled_commands => ['archive', 'duplicate', 'extract', 'resize', 'tmb'],
26
+ :allow_dot_files => true,
27
+ :upload_max_size => '50M',
28
+ :name_validator => lambda { |name| name.strip != '.' && name =~ /^[^\x00-\x1f\\?*:"><|\/]+$/ },
29
+ :upload_file_mode => 0644,
30
+ :archivers => {},
31
+ :extractors => {},
32
+ :home => 'Home',
33
+ :default_perms => { :read => true, :write => true, :locked => false, :hidden => false },
34
+ :perms => [],
35
+ :thumbs => false,
36
+ :thumbs_directory => '.thumbs',
37
+ :thumbs_size => 48,
38
+ :thumbs_at_once => 5,
39
+ }
40
+
41
+ # Initializes new instance.
42
+ # @param [Hash] options Instance options. :url and :server options are required.
43
+ # @option options [String] :url Entry point of ElFinder router.
44
+ # @option options [String] :server A hash containing the :host, :username, :password, and, optionally, :port to connect to
45
+ # @see DEFAULT_OPTIONS
46
+ def initialize(options)
47
+ @options = DEFAULT_OPTIONS.merge(options)
48
+
49
+ raise(ArgumentError, "Missing required :url option") unless @options.key?(:url)
50
+ raise(ArgumentError, "Missing required :server option") unless @options.key?(:server)
51
+ raise(ArgumentError, "Mime Handler is invalid") unless mime_handler.respond_to?(:for)
52
+ raise(ArgumentError, "Image Handler is invalid") unless image_handler.nil? || ([:size, :resize, :thumbnail].all?{|m| image_handler.respond_to?(m)})
53
+
54
+ @headers = {}
55
+ @response = {}
56
+ end # of initialize
57
+
58
+ # Logger is a class property
59
+ class <<self
60
+ def logger
61
+ @logger ||= Logger.new(STDOUT)
62
+ end
63
+ def logger=(val)
64
+ @logger = val
65
+ end
66
+ end
67
+
68
+ # Runs request-response cycle.
69
+ # @param [Hash] params Request parameters. :cmd option is required.
70
+ # @option params [String] :cmd Command to be performed.
71
+ # @see VALID_COMMANDS
72
+ def run(params)
73
+
74
+ @adapter = ElFinderFtp::FtpAdapter.new(@options[:server])
75
+ @root = ElFinderFtp::Pathname.new(adapter)
76
+
77
+ begin
78
+ @params = params.dup
79
+ @headers = {}
80
+ @response = {}
81
+ @response[:errorData] = {}
82
+
83
+ if VALID_COMMANDS.include?(@params[:cmd])
84
+
85
+ if @options[:thumbs]
86
+ @thumb_directory = @root + @options[:thumbs_directory]
87
+ @thumb_directory.mkdir unless @thumb_directory.exist?
88
+ raise(RuntimeError, "Unable to create thumbs directory") unless @thumb_directory.directory?
89
+ end
90
+
91
+ @current = @params[:current] ? from_hash(@params[:current]) : nil
92
+ @target = (@params[:target] and !@params[:target].empty?) ? from_hash(@params[:target]) : nil
93
+ if params[:targets]
94
+ @targets = @params[:targets].map{|t| from_hash(t)}
95
+ end
96
+
97
+ begin
98
+ send("_#{@params[:cmd]}")
99
+ rescue Net::FTPPermError
100
+ @response[:error] = 'Access Denied'
101
+ end
102
+ else
103
+ invalid_request
104
+ end
105
+
106
+ @response.delete(:errorData) if @response[:errorData].empty?
107
+
108
+ return @headers, @response
109
+ ensure
110
+ adapter.close
111
+ end
112
+ end # of run
113
+
114
+ #
115
+ def to_hash(pathname)
116
+ # note that '=' are removed
117
+ Base64.urlsafe_encode64(pathname.path.to_s).chomp.tr("=\n", "")
118
+ end # of to_hash
119
+
120
+ #
121
+ def from_hash(hash)
122
+ # restore missing '='
123
+ len = hash.length % 4
124
+ hash += '==' if len == 1 or len == 2
125
+ hash += '=' if len == 3
126
+
127
+ decoded_hash = Base64.urlsafe_decode64(hash)
128
+ decoded_hash = decoded_hash.respond_to?(:force_encoding) ? decoded_hash.force_encoding('utf-8') : decoded_hash
129
+ pathname = @root + decoded_hash
130
+ rescue ArgumentError => e
131
+ if e.message != 'invalid base64'
132
+ raise
133
+ end
134
+ nil
135
+ end # of from_hash
136
+
137
+ # @!attribute [w] options
138
+ # Options setter.
139
+ # @param value [Hash] Options to be merged with instance ones.
140
+ # @return [Hash] Updated options.
141
+ def options=(value = {})
142
+ value.each_pair do |k, v|
143
+ @options[k.to_sym] = v
144
+ end
145
+ @options
146
+ end # of options=
147
+
148
+ ################################################################################
149
+ protected
150
+
151
+ #
152
+ def _open(target = nil)
153
+ target ||= @target
154
+
155
+ if target.nil?
156
+ _open(@root)
157
+ return
158
+ end
159
+
160
+ if perms_for(target)[:read] == false
161
+ @response[:error] = 'Access Denied'
162
+ return
163
+ end
164
+
165
+ if target.file?
166
+ command_not_implemented
167
+ elsif target.directory?
168
+ @response[:cwd] = cdc_for(target)
169
+ files = [target].concat( target.files.reject{ |child| perms_for(child)[:hidden] } )
170
+
171
+ if @params[:tree]
172
+ files = files.concat( tree_for(@root) )
173
+ else
174
+ files = files.concat( target.child_directories.reject{ |child| perms_for(child)[:hidden] || child.fullpath == target.fullpath } )
175
+ end
176
+
177
+ @response[:files] = files.map{|e| cdc_for(e)}.compact
178
+
179
+ if @params[:init]
180
+ @response[:api] = 2
181
+ @response[:uplMaxSize] = @options[:upload_max_size]
182
+ @response[:options] = {
183
+ :disabled => @options[:disabled_commands],
184
+ :dotFiles => @options[:allow_dot_files],
185
+ :url => @options[:url]
186
+ }
187
+ end
188
+
189
+ else
190
+ @response[:error] = "Directory does not exist"
191
+ _open(@root) if File.directory?(@root)
192
+ end
193
+
194
+ end # of open
195
+
196
+ def _ls
197
+ if @target.directory?
198
+ files = @target.files.reject{ |child| perms_for(child)[:hidden] }
199
+
200
+ @response[:list] = files.map{|e| e.basename.to_s }.compact
201
+ else
202
+ @response[:error] = "Directory does not exist"
203
+ end
204
+
205
+ end # of open
206
+
207
+ def _tree
208
+ if @target.directory?
209
+ @response[:tree] = tree_for(@target).map{|e| cdc_for(e) }.compact
210
+ else
211
+ @response[:error] = "Directory does not exist"
212
+ end
213
+
214
+ end # of tree
215
+
216
+ #
217
+ def _mkdir
218
+ if perms_for(@target)[:write] == false
219
+ @response[:error] = 'Access Denied'
220
+ return
221
+ end
222
+ unless valid_name?(@params[:name])
223
+ @response[:error] = 'Unable to create folder'
224
+ return
225
+ end
226
+
227
+ dir = @target + @params[:name]
228
+ if !dir.exist? && dir.mkdir
229
+ @response[:added] = [cdc_for(dir)]
230
+ else
231
+ @response[:error] = "Unable to create folder"
232
+ end
233
+ end # of mkdir
234
+
235
+ #
236
+ def _mkfile
237
+ if perms_for(@target)[:write] == false
238
+ @response[:error] = 'Access Denied'
239
+ return
240
+ end
241
+ unless valid_name?(@params[:name])
242
+ @response[:error] = 'Unable to create file'
243
+ return
244
+ end
245
+
246
+ file = @target + @params[:name]
247
+ if !file.exist? && file.touch
248
+ @response[:added] = [cdc_for(file)]
249
+ else
250
+ @response[:error] = "Unable to create file"
251
+ end
252
+ end # of mkfile
253
+
254
+ #
255
+ def _rename
256
+ unless valid_name?(@params[:name])
257
+ @response[:error] = "Unable to rename #{@target.ftype}"
258
+ return
259
+ end
260
+
261
+ to = @target.dirname + @params[:name]
262
+
263
+ perms_for_target = perms_for(@target)
264
+ if perms_for_target[:locked] == true
265
+ @response[:error] = 'Access Denied'
266
+ return
267
+ end
268
+
269
+ perms_for_current = perms_for(@target)
270
+ if perms_for_current[:write] == false
271
+ @response[:error] = 'Access Denied'
272
+ return
273
+ end
274
+
275
+ if to.exist?
276
+ @response[:error] = "Unable to rename #{@target.ftype}. '#{to.basename}' already exists"
277
+ else
278
+ to = @target.rename(to)
279
+ if to
280
+ @response[:added] = [cdc_for(to)]
281
+ @response[:removed] = [to_hash(@target)]
282
+ else
283
+ @response[:error] = "Unable to rename #{@target.ftype}"
284
+ end
285
+ end
286
+ end # of rename
287
+
288
+ #
289
+ def _upload
290
+ if perms_for(@target)[:write] == false
291
+ @response[:error] = 'Access Denied'
292
+ return
293
+ end
294
+ added_list = []
295
+ @params[:upload].to_a.each do |io|
296
+ name = @options[:original_filename_method].call(io)
297
+ unless valid_name?(name)
298
+ @response[:error] = 'Unable to create file'
299
+ return
300
+ end
301
+ dst = @target + name
302
+
303
+ dst.write(io)
304
+
305
+ added_list.push cdc_for(dst)
306
+ end
307
+ @response[:added] = added_list unless added_list.empty?
308
+ _open(@current)
309
+ end # of upload
310
+
311
+ #
312
+ def _ping
313
+ @headers['Connection'] = 'Close'
314
+ end # of ping
315
+
316
+ #
317
+ def _paste
318
+ if perms_for(from_hash(@params[:dst]))[:write] == false
319
+ @response[:error] = 'Access Denied'
320
+ return
321
+ end
322
+
323
+ added_list = []
324
+ removed_list = []
325
+ @targets.to_a.each do |src|
326
+ if perms_for(src)[:read] == false || (@params[:cut].to_i > 0 && perms_for(src)[:locked] == true)
327
+ @response[:error] ||= 'Some files were not moved.'
328
+ @response[:errorData][src.basename.to_s] = "Access Denied"
329
+ return
330
+ else
331
+ dst = from_hash(@params[:dst]) + src.basename
332
+ if dst.exist?
333
+ @response[:error] ||= 'The target file already exists'
334
+ @response[:errorData][src.basename.to_s] = "already exists in '#{dst.dirname}'"
335
+ else
336
+ if @params[:cut].to_i > 0
337
+ adapter.move(src, dst)
338
+
339
+ added_list.push cdc_for(dst)
340
+ removed_list.push to_hash(src)
341
+ else
342
+ command_not_implemented
343
+ return
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ @response[:added] = added_list unless added_list.empty?
350
+ @response[:removed] = removed_list unless removed_list.empty?
351
+ end # of paste
352
+
353
+ #
354
+ def _rm
355
+ if @targets.empty?
356
+ @response[:error] = "No files were selected for removal"
357
+ else
358
+ removed_list = []
359
+ @targets.to_a.each do |target|
360
+ removed_list.concat remove_target(target)
361
+ end
362
+ @response[:removed] = removed_list unless removed_list.empty?
363
+ end
364
+ end # of rm
365
+
366
+ #
367
+ def _duplicate
368
+ command_not_implemented
369
+ end # of duplicate
370
+
371
+ #
372
+ def _get
373
+ if perms_for(@target)[:read] == true
374
+ @response[:content] = @target.read
375
+ else
376
+ @response[:error] = 'Access Denied'
377
+ end
378
+ end # of get
379
+
380
+ #
381
+ def _file
382
+ if perms_for(@target)[:read] == true
383
+ @response[:file_data] = @target.read
384
+ @response[:mime_type] = mime_handler.for(@target)
385
+ @response[:disposition] = 'attachment'
386
+ @response[:filename] = @target.basename.to_s
387
+ else
388
+ @response[:error] = 'Access Denied'
389
+ end
390
+ end # of file
391
+
392
+ #
393
+ def _put
394
+ perms = perms_for(@target)
395
+ if perms[:read] == true && perms[:write] == true
396
+ @target.write @params[:content]
397
+ @response[:changed] = [cdc_for(@target)]
398
+ else
399
+ @response[:error] = 'Access Denied'
400
+ end
401
+ end # of put
402
+
403
+ #
404
+ def _extract
405
+ command_not_implemented
406
+ end # of extract
407
+
408
+ #
409
+ def _archive
410
+ command_not_implemented
411
+ end # of archive
412
+
413
+ #
414
+ def _tmb
415
+ command_not_implemented
416
+ end # of tmb
417
+
418
+ #
419
+ def _resize
420
+ command_not_implemented
421
+ end # of resize
422
+
423
+ ################################################################################
424
+ private
425
+
426
+ #
427
+ def upload_max_size_in_bytes
428
+ bytes = @options[:upload_max_size]
429
+ if bytes.is_a?(String) && bytes.strip =~ /(\d+)([KMG]?)/
430
+ bytes = $1.to_i
431
+ unit = $2
432
+ case unit
433
+ when 'K'
434
+ bytes *= 1024
435
+ when 'M'
436
+ bytes *= 1024 * 1024
437
+ when 'G'
438
+ bytes *= 1024 * 1024 * 1024
439
+ end
440
+ end
441
+ bytes.to_i
442
+ end
443
+
444
+ #
445
+ def thumbnail_for(pathname)
446
+ @thumb_directory + "#{to_hash(pathname)}.png"
447
+ end
448
+
449
+ #
450
+ def remove_target(target)
451
+ removed = []
452
+ if target.directory?
453
+ target.children.each do |child|
454
+ removed.concat remove_target(child)
455
+ end
456
+ end
457
+ if perms_for(target)[:locked] == true
458
+ @response[:error] ||= 'Some files/directories were unable to be removed'
459
+ @response[:errorData][target.basename.to_s] = "Access Denied"
460
+ else
461
+ begin
462
+ removed.push to_hash(target)
463
+ target.unlink
464
+ if @options[:thumbs] && (thumbnail = thumbnail_for(target)).file?
465
+ removed.push to_hash(thumbnail)
466
+ thumbnail.unlink
467
+ end
468
+ rescue Exception => ex
469
+ @response[:error] ||= 'Some files/directories were unable to be removed'
470
+ @response[:errorData][target.basename.to_s] = "Remove failed"
471
+ end
472
+ end
473
+
474
+ removed
475
+ end
476
+
477
+ def mime_handler
478
+ @options[:mime_handler]
479
+ end
480
+
481
+ #
482
+ def image_handler
483
+ @options[:image_handler]
484
+ end
485
+
486
+ def cdc_for(pathname)
487
+ return nil if @options[:thumbs] && pathname.to_s == @thumb_directory.to_s
488
+ response = {
489
+ :name => pathname.is_root? ? "Home" : pathname.basename.to_s,
490
+ :hash => to_hash(pathname),
491
+ :date => pathname.mtime.to_s,
492
+ :ts => pathname.mtime.to_i
493
+ }
494
+ response.merge! perms_for(pathname)
495
+
496
+ response[:phash] = to_hash(pathname.dirname) unless pathname.is_root?
497
+
498
+ if pathname.directory?
499
+ response.merge!(
500
+ :size => 0,
501
+ :mime => 'directory',
502
+ :dirs => pathname.child_directories.size
503
+ )
504
+
505
+ response[:volumeid] = 'Home' if pathname.is_root?
506
+ elsif pathname.file?
507
+ response.merge!(
508
+ :size => pathname.size,
509
+ :mime => mime_handler.for(pathname),
510
+ :url => (@options[:url] + '/' + pathname.path.to_s)
511
+ )
512
+
513
+ if pathname.readable? && response[:mime] =~ /image/ && !image_handler.nil?
514
+ response.merge!(
515
+ :resize => true,
516
+ :dim => image_handler.size(pathname)
517
+ )
518
+ if @options[:thumbs]
519
+ if (thumbnail = thumbnail_for(pathname)).exist?
520
+ response.merge!( :tmb => (@options[:url] + '/' + thumbnail.path.to_s))
521
+ else
522
+ @response[:tmb] = true
523
+ end
524
+ end
525
+ end
526
+
527
+ end
528
+
529
+ if pathname.symlink?
530
+ response.merge!(
531
+ :link => to_hash(@root + pathname.readlink), # hash of file to which point link
532
+ :linkTo => (@root + pathname.readlink).relative_to(pathname.dirname.path).to_s, # relative path to
533
+ :parent => to_hash((@root + pathname.readlink).dirname) # hash of directory in which is linked file
534
+ )
535
+ end
536
+
537
+ return response
538
+ end
539
+
540
+ #
541
+ def tree_for(root)
542
+
543
+ # root.child_directories.
544
+ # reject{ |child|
545
+ # ( @options[:thumbs] && child.to_s == @thumb_directory.to_s ) || perms_for(child)[:hidden]
546
+ # }.
547
+ # sort_by{|e| e.basename.to_s.downcase}.
548
+ # map { |child|
549
+ # {:name => child.basename.to_s,
550
+ # :hash => to_hash(child),
551
+ # :dirs => tree_for(child),
552
+ # }.merge(perms_for(child))
553
+ # }
554
+
555
+ children = [root]
556
+ flattened_tree = []
557
+ while !children.empty? do
558
+ child = children.pop
559
+ unless (@options[:thumbs] && child.to_s == @thumb_directory.to_s ) || perms_for(child)[:hidden]
560
+ flattened_tree.push child
561
+ children.concat child.child_directories
562
+ end
563
+ end
564
+ flattened_tree
565
+ end # of tree_for
566
+
567
+ #
568
+ def perms_for(pathname, options = {})
569
+ skip = [options[:skip]].flatten
570
+ response = {}
571
+
572
+ response[:read] = pathname.readable?
573
+ response[:read] &&= specific_perm_for(pathname, :read)
574
+ response[:read] &&= @options[:default_perms][:read]
575
+
576
+ response[:write] = pathname.writable?
577
+ response[:write] &&= specific_perm_for(pathname, :write)
578
+ response[:write] &&= @options[:default_perms][:write]
579
+
580
+ response[:locked] = pathname.is_root?
581
+ response[:locked] &&= specific_perm_for(pathname, :locked)
582
+ response[:locked] &&= @options[:default_perms][:locked]
583
+
584
+ response[:hidden] = false
585
+ response[:hidden] ||= specific_perm_for(pathname, :hidden)
586
+ response[:hidden] ||= @options[:default_perms][:hidden]
587
+
588
+ response
589
+ end # of perms_for
590
+
591
+ #
592
+ def specific_perm_for(pathname, perm)
593
+ pathname = pathname.path if pathname.is_a?(ElFinderFtp::Pathname)
594
+ matches = @options[:perms].select{ |k,v| pathname.to_s.send((k.is_a?(String) ? :== : :match), k) }
595
+ if perm == :hidden
596
+ matches.one?{|e| e.last[perm] }
597
+ else
598
+ matches.none?{|e| e.last[perm] == false}
599
+ end
600
+ end # of specific_perm_for
601
+
602
+ #
603
+ def valid_name?(name)
604
+ @options[:name_validator].call(name)
605
+ end
606
+
607
+ #
608
+ def invalid_request
609
+ @response[:error] = "Invalid command '#{@params[:cmd]}'"
610
+ end # of invalid_request
611
+
612
+ #
613
+ def command_not_implemented
614
+ @response[:error] = "Command '#{@params[:cmd]}' not implemented"
615
+ end # of command_not_implemented
616
+
617
+ end # of class Connector
618
+ end # of module ElFinderFtp