ftpd 0.2.2 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,115 @@
1
+ module Ftpd
2
+
3
+ # Information about a file object (file, directory, symlink, etc.)
4
+
5
+ class FileInfo
6
+
7
+ # @return [String] The file's type, as returned by File.lstat
8
+ # One of:
9
+ # * 'file'
10
+ # * 'directory'
11
+ # * 'characterSpecial'
12
+ # * 'blockSpecial'
13
+ # * 'fifo'
14
+ # * 'link'
15
+ # * 'socket'
16
+ # * 'unknown'
17
+
18
+ attr_reader :ftype
19
+
20
+ # @return [String] The group name
21
+
22
+ attr_reader :group
23
+
24
+ # @return [Integer] The mode bits, as returned by File::Stat#mode
25
+ # The bits are:
26
+ # * 0 - others have execute permission
27
+ # * 1 - others have write permission
28
+ # * 2 - others have read permission
29
+ # * 3 - group has execute permission
30
+ # * 4 - group has write permission
31
+ # * 5 - group has read permission
32
+ # * 6 - owner has execute permission
33
+ # * 7 - owner has write permission
34
+ # * 8 - owner has read permission
35
+ # * 9 - sticky bit
36
+ # * 10 - set-group-ID bit
37
+ # * 11 - set UID bit
38
+ # Other bits may be present; they are ignored
39
+
40
+ attr_reader :mode
41
+
42
+ # @return [Time] The modification time
43
+
44
+ attr_reader :mtime
45
+
46
+ # @return [Integer] The number of hard links
47
+
48
+ attr_reader :nlink
49
+
50
+ # @return [String] The owner name
51
+
52
+ attr_reader :owner
53
+
54
+ # @return [Integer] The size, in bytes
55
+
56
+ attr_reader :size
57
+
58
+ # @return [String] The object's path
59
+
60
+ attr_reader :path
61
+
62
+ # @return [String] The object's identifier
63
+ #
64
+ # This uniquely identifies the file: Two objects with the same
65
+ # identifier are expected to refer to the same file or directory.
66
+ #
67
+ # On a disk file system, might be _dev_._inode_,
68
+ # e.g. "8388621.48598"
69
+ #
70
+ # This is optional and does not have to be set. If set, it is
71
+ # used in EPLF output.
72
+
73
+ attr_reader :identifier
74
+
75
+ # Create a new instance. See the various attributes for argument
76
+ # details.
77
+ #
78
+ # @param opts [Hash] The file attributes
79
+ # @option opts [String] :ftype The file type
80
+ # @option opts [String] :group The group name
81
+ # @option opts [String] :identifier The object's identifier
82
+ # @option opts [Integer] :mode The mode bits
83
+ # @option opts [Time] :mtime The modification time
84
+ # @option opts [Integer] :nlink The number of hard links
85
+ # @option opts [String] :owner The owner name
86
+ # @option opts [Integer] :size The size
87
+ # @option opts [String] :path The object's path
88
+
89
+ def initialize(opts)
90
+ @ftype = opts[:ftype]
91
+ @group = opts[:group]
92
+ @identifier = opts[:identifier]
93
+ @mode = opts[:mode]
94
+ @mtime = opts[:mtime]
95
+ @nlink = opts[:nlink]
96
+ @owner = opts[:owner]
97
+ @path = opts[:path]
98
+ @size = opts[:size]
99
+ end
100
+
101
+ # @return true if the object is a file
102
+
103
+ def file?
104
+ @ftype == 'file'
105
+ end
106
+
107
+ # @return true if the object is a directory
108
+
109
+ def directory?
110
+ @ftype == 'directory'
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -25,6 +25,12 @@ module Ftpd
25
25
 
26
26
  attr_accessor :response_delay
27
27
 
28
+ # The class for formatting for LIST output. Defaults to
29
+ # {Ftpd::ListFormat::Ls}. Changes to this attribute only take
30
+ # effect for new sessions.
31
+
32
+ attr_accessor :list_formatter
33
+
28
34
  # Create a new FTP server. The server won't start until the
29
35
  # #start method is called.
30
36
  #
@@ -48,6 +54,7 @@ module Ftpd
48
54
  @debug_path = '/dev/stdout'
49
55
  @debug = false
50
56
  @response_delay = 0
57
+ @list_formatter = ListFormat::Ls
51
58
  end
52
59
 
53
60
  private
@@ -57,6 +64,7 @@ module Ftpd
57
64
  :driver => @driver,
58
65
  :debug => @debug,
59
66
  :debug_path => debug_path,
67
+ :list_formatter => @list_formatter,
60
68
  :response_delay => response_delay,
61
69
  :tls => @tls).run
62
70
  end
@@ -0,0 +1,74 @@
1
+ module Ftpd
2
+ module ListFormat
3
+
4
+ # Easily Parsed LIST Format (EPLF) Directory formatter
5
+ # See: {http://cr.yp.to/ftp/list/eplf.html}
6
+
7
+ class Eplf
8
+
9
+ extend Forwardable
10
+
11
+ # Create a new formatter for a file object
12
+ # @param file_info [FileInfo]
13
+
14
+ def initialize(file_info)
15
+ @file_info = file_info
16
+ end
17
+
18
+ # Return the formatted directory entry.
19
+ # For example:
20
+ # +i8388621.48598,m824253270,r,s612, 514.html
21
+ # Note: The calling code adds the \r\n
22
+
23
+ def to_s
24
+ "+%s\t%s" % [facts, filename]
25
+ end
26
+
27
+ private
28
+
29
+ def facts
30
+ [
31
+ retrievable_fact,
32
+ cwd_target_fact,
33
+ size_fact,
34
+ mtime_fact,
35
+ identifier_fact,
36
+ ].compact.join(',')
37
+ end
38
+
39
+ def retrievable_fact
40
+ 'r' if retrievable?
41
+ end
42
+
43
+ def cwd_target_fact
44
+ '/' if cwd_target?
45
+ end
46
+
47
+ def size_fact
48
+ "s#{@file_info.size}" if retrievable?
49
+ end
50
+
51
+ def mtime_fact
52
+ "m#{@file_info.mtime.to_i}"
53
+ end
54
+
55
+ def identifier_fact
56
+ "i#{@file_info.identifier}" if @file_info.identifier
57
+ end
58
+
59
+ def filename
60
+ File.basename(@file_info.path)
61
+ end
62
+
63
+ def retrievable?
64
+ @file_info.file?
65
+ end
66
+
67
+ def cwd_target?
68
+ @file_info.directory?
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,154 @@
1
+ module Ftpd
2
+ module ListFormat
3
+
4
+ # Directory formatter that approximates the output of "ls -l"
5
+
6
+ class Ls
7
+
8
+ extend Forwardable
9
+
10
+ # Create a new formatter for a file object
11
+ # @param file_info [FileInfo]
12
+
13
+ def initialize(file_info)
14
+ @file_info = file_info
15
+ end
16
+
17
+ # Return the formatted directory entry, for example:
18
+ # -rw-r--r-- 1 user group Mar 3 08:38 foo
19
+
20
+ def to_s
21
+ '%s%s %d %-8s %-8s %8d %s %s' % [
22
+ file_type,
23
+ file_mode_letters,
24
+ @file_info.nlink,
25
+ @file_info.owner,
26
+ @file_info.group,
27
+ @file_info.size,
28
+ format_time(@file_info.mtime),
29
+ filename,
30
+ ]
31
+ end
32
+
33
+ private
34
+
35
+ SIX_MONTHS = 180 * 24 * 60 * 60
36
+
37
+ def filename
38
+ File.basename(@file_info.path)
39
+ end
40
+
41
+ def file_type
42
+ FileType.letter(@file_info.ftype)
43
+ end
44
+
45
+ def file_mode_letters
46
+ FileMode.new(@file_info.mode).letters
47
+ end
48
+
49
+ def self.format_time(mtime)
50
+ age = Time.now - mtime
51
+ format = '%b %e ' + if age < 0 || age > SIX_MONTHS
52
+ ' %Y'
53
+ else
54
+ '%H:%M'
55
+ end
56
+ mtime.strftime(format)
57
+ end
58
+ def_delegator self, :format_time
59
+
60
+ # Map file type strings to ls file type letters
61
+
62
+ class FileType
63
+
64
+ # Map a file type string to a file type letter.
65
+ # @param ftype [String] file type as returned by File::Stat#ftype
66
+ # @return [String] File type letter
67
+
68
+ def self.letter(ftype)
69
+ case ftype
70
+ when 'file'
71
+ '-'
72
+ when 'directory'
73
+ 'd'
74
+ when 'characterSpecial'
75
+ 'c'
76
+ when 'blockSpecial'
77
+ 'b'
78
+ when 'fifo'
79
+ 'p'
80
+ when 'link'
81
+ 'l'
82
+ when 'socket'
83
+ 's'
84
+ else # 'unknown', etc.
85
+ '?'
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ # Map file mode bits into ls style file mode letters
92
+
93
+ class FileMode
94
+
95
+ # @param mode [Integer] File mode bits, as returned by
96
+ # File::Stat#mode
97
+
98
+ def initialize(mode)
99
+ @mode = mode
100
+ end
101
+
102
+ # Return the mode bits as ls style letters.
103
+ # For example, "-rw-r--r--"
104
+
105
+ def letters
106
+ [
107
+ triad(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, SET_UID, 'Ss'),
108
+ triad(GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, SET_GID, 'Ss'),
109
+ triad(OTHER_READ, OTHER_WRITE, OTHER_EXECUTE, STICKY, 'Tt'),
110
+ ].join
111
+ end
112
+
113
+ private
114
+
115
+ def bit(bit_number)
116
+ @mode >> bit_number & 1
117
+ end
118
+
119
+ def triad(read_bit, write_bit, execute_bit, special_bit, special_letters)
120
+ execute_chars = if bit(special_bit) != 0
121
+ special_letters
122
+ else
123
+ '-x'
124
+ end
125
+ [
126
+ pick_char('-r', read_bit),
127
+ pick_char('-w', write_bit),
128
+ pick_char(execute_chars, execute_bit),
129
+ ]
130
+ end
131
+
132
+ def pick_char(s, bit_number)
133
+ s[bit(bit_number), 1]
134
+ end
135
+
136
+ OTHER_EXECUTE = 0
137
+ OTHER_WRITE = 1
138
+ OTHER_READ = 2
139
+ GROUP_EXECUTE = 3
140
+ GROUP_WRITE = 4
141
+ GROUP_READ = 5
142
+ OWNER_EXECUTE = 6
143
+ OWNER_WRITE = 7
144
+ OWNER_READ = 8
145
+ STICKY = 9
146
+ SET_GID = 10
147
+ SET_UID = 11
148
+
149
+ end
150
+
151
+ end
152
+
153
+ end
154
+ end
data/lib/ftpd/session.rb CHANGED
@@ -15,6 +15,7 @@ module Ftpd
15
15
  @name_prefix = '/'
16
16
  @debug_path = opts[:debug_path]
17
17
  @debug = opts[:debug]
18
+ @list_formatter = opts[:list_formatter]
18
19
  @data_type = 'A'
19
20
  @mode = 'S'
20
21
  @format = 'N'
@@ -164,24 +165,23 @@ module Ftpd
164
165
  def cmd_list(argument)
165
166
  close_data_server_socket_when_done do
166
167
  ensure_logged_in
167
- ensure_file_system_supports :list
168
+ ensure_file_system_supports :dir
169
+ ensure_file_system_supports :file_info
168
170
  path = argument
169
171
  path ||= '.'
170
172
  path = File.expand_path(path, @name_prefix)
171
- list = @file_system.list(path)
172
- transmit_file(list, 'A')
173
+ transmit_file(list(path), 'A')
173
174
  end
174
175
  end
175
176
 
176
177
  def cmd_nlst(argument)
177
178
  close_data_server_socket_when_done do
178
179
  ensure_logged_in
179
- ensure_file_system_supports :name_list
180
+ ensure_file_system_supports :dir
180
181
  path = argument
181
182
  path ||= '.'
182
183
  path = File.expand_path(path, @name_prefix)
183
- list = @file_system.name_list(path)
184
- transmit_file(list, 'A')
184
+ transmit_file(name_list(path), 'A')
185
185
  end
186
186
  end
187
187
 
@@ -657,7 +657,7 @@ module Ftpd
657
657
  def generate_suffix
658
658
  set = ('a'..'z').to_a
659
659
  8.times.map do
660
- set.sample
660
+ set[rand(set.size)]
661
661
  end.join
662
662
  end
663
663
 
@@ -679,5 +679,29 @@ module Ftpd
679
679
  checker
680
680
  end
681
681
 
682
+ def list(path)
683
+ format_list(path_list(path))
684
+ end
685
+
686
+ def format_list(paths)
687
+ paths.map do |path|
688
+ file_info = @file_system.file_info(path)
689
+ @list_formatter.new(file_info).to_s + "\n"
690
+ end.join
691
+ end
692
+
693
+ def name_list(path)
694
+ path_list(path).map do |path|
695
+ File.basename(path) + "\n"
696
+ end.join
697
+ end
698
+
699
+ def path_list(path)
700
+ if @file_system.directory?(path)
701
+ path = File.join(path, '*')
702
+ end
703
+ @file_system.dir(path).sort
704
+ end
705
+
682
706
  end
683
707
  end