ftpd 0.2.2 → 0.3.1

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,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