el_finder_ftp 1.0.0

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