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.
- data/.autotest +23 -0
- data/.gitignore +5 -0
- data/.yardopts +7 -0
- data/Gemfile +4 -0
- data/README.md +115 -0
- data/Rakefile +14 -0
- data/TODO +5 -0
- data/el_finder_ftp.gemspec +29 -0
- data/lib/el_finder_ftp.rb +13 -0
- data/lib/el_finder_ftp/action.rb +32 -0
- data/lib/el_finder_ftp/base64.rb +24 -0
- data/lib/el_finder_ftp/connector.rb +618 -0
- data/lib/el_finder_ftp/ftp_adapter.rb +309 -0
- data/lib/el_finder_ftp/ftp_authentication_error.rb +4 -0
- data/lib/el_finder_ftp/ftp_pathname.rb +161 -0
- data/lib/el_finder_ftp/image.rb +30 -0
- data/lib/el_finder_ftp/mime_type.rb +83 -0
- data/lib/el_finder_ftp/pathname.rb +197 -0
- data/lib/el_finder_ftp/railties.rb +7 -0
- data/lib/el_finder_ftp/version.rb +5 -0
- metadata +150 -0
data/.autotest
ADDED
@@ -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
|
data/.yardopts
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|