1# = uri/ftp.rb
2#
3# Author:: Akira Yamada <akira@ruby-lang.org>
4# License:: You can redistribute it and/or modify it under the same term as Ruby.
5# Revision:: $Id: ftp.rb 39014 2013-02-02 03:31:56Z zzak $
6#
7# See URI for general documentation
8#
9
10require 'uri/generic'
11
12module URI
13
14  #
15  # FTP URI syntax is defined by RFC1738 section 3.2.
16  #
17  # This class will be redesigned because of difference of implementations;
18  # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it
19  # is a good summary about the de facto spec.
20  # http://tools.ietf.org/html/draft-hoffman-ftp-uri-04
21  #
22  class FTP < Generic
23    # A Default port of 21 for URI::FTP
24    DEFAULT_PORT = 21
25
26    #
27    # An Array of the available components for URI::FTP
28    #
29    COMPONENT = [
30      :scheme,
31      :userinfo, :host, :port,
32      :path, :typecode
33    ].freeze
34
35    #
36    # Typecode is "a", "i" or "d".
37    #
38    # * "a" indicates a text file (the FTP command was ASCII)
39    # * "i" indicates a binary file (FTP command IMAGE)
40    # * "d" indicates the contents of a directory should be displayed
41    #
42    TYPECODE = ['a', 'i', 'd'].freeze
43
44    # Typecode prefix
45    #  ';type='
46    TYPECODE_PREFIX = ';type='.freeze
47
48    def self.new2(user, password, host, port, path,
49                  typecode = nil, arg_check = true) # :nodoc:
50      # Do not use this method!  Not tested.  [Bug #7301]
51      # This methods remains just for compatibility,
52      # Keep it undocumented until the active maintainer is assigned.
53      typecode = nil if typecode.size == 0
54      if typecode && !TYPECODE.include?(typecode)
55        raise ArgumentError,
56          "bad typecode is specified: #{typecode}"
57      end
58
59      # do escape
60
61      self.new('ftp',
62               [user, password],
63               host, port, nil,
64               typecode ? path + TYPECODE_PREFIX + typecode : path,
65               nil, nil, nil, arg_check)
66    end
67
68    #
69    # == Description
70    #
71    # Creates a new URI::FTP object from components, with syntax checking.
72    #
73    # The components accepted are +userinfo+, +host+, +port+, +path+ and
74    # +typecode+.
75    #
76    # The components should be provided either as an Array, or as a Hash
77    # with keys formed by preceding the component names with a colon.
78    #
79    # If an Array is used, the components must be passed in the order
80    # [userinfo, host, port, path, typecode]
81    #
82    # If the path supplied is absolute, it will be escaped in order to
83    # make it absolute in the URI. Examples:
84    #
85    #     require 'uri'
86    #
87    #     uri = URI::FTP.build(['user:password', 'ftp.example.com', nil,
88    #       '/path/file.> zip', 'i'])
89    #     puts uri.to_s  ->  ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=a
90    #
91    #     uri2 = URI::FTP.build({:host => 'ftp.example.com',
92    #       :path => 'ruby/src'})
93    #     puts uri2.to_s  ->  ftp://ftp.example.com/ruby/src
94    #
95    def self.build(args)
96
97      # Fix the incoming path to be generic URL syntax
98      # FTP path  ->  URL path
99      # foo/bar       /foo/bar
100      # /foo/bar      /%2Ffoo/bar
101      #
102      if args.kind_of?(Array)
103        args[3] = '/' + args[3].sub(/^\//, '%2F')
104      else
105        args[:path] = '/' + args[:path].sub(/^\//, '%2F')
106      end
107
108      tmp = Util::make_components_hash(self, args)
109
110      if tmp[:typecode]
111        if tmp[:typecode].size == 1
112          tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode]
113        end
114        tmp[:path] << tmp[:typecode]
115      end
116
117      return super(tmp)
118    end
119
120    #
121    # == Description
122    #
123    # Creates a new URI::FTP object from generic URL components with no
124    # syntax checking.
125    #
126    # Unlike build(), this method does not escape the path component as
127    # required by RFC1738; instead it is treated as per RFC2396.
128    #
129    # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
130    # +opaque+, +query+ and +fragment+, in that order.
131    #
132    def initialize(*arg)
133      raise InvalidURIError unless arg[5]
134      arg[5] = arg[5].sub(/^\//,'').sub(/^%2F/,'/')
135      super(*arg)
136      @typecode = nil
137      tmp = @path.index(TYPECODE_PREFIX)
138      if tmp
139        typecode = @path[tmp + TYPECODE_PREFIX.size..-1]
140        @path = @path[0..tmp - 1]
141
142        if arg[-1]
143          self.typecode = typecode
144        else
145          self.set_typecode(typecode)
146        end
147      end
148    end
149
150    # typecode accessor
151    #
152    # see URI::FTP::COMPONENT
153    attr_reader :typecode
154
155    # validates typecode +v+,
156    # returns a +true+ or +false+ boolean
157    #
158    def check_typecode(v)
159      if TYPECODE.include?(v)
160        return true
161      else
162        raise InvalidComponentError,
163          "bad typecode(expected #{TYPECODE.join(', ')}): #{v}"
164      end
165    end
166    private :check_typecode
167
168    # private setter for the typecode +v+
169    #
170    # see also URI::FTP.typecode=
171    #
172    def set_typecode(v)
173      @typecode = v
174    end
175    protected :set_typecode
176
177    #
178    # == Args
179    #
180    # +v+::
181    #    String
182    #
183    # == Description
184    #
185    # public setter for the typecode +v+.
186    # (with validation)
187    #
188    # see also URI::FTP.check_typecode
189    #
190    # == Usage
191    #
192    #   require 'uri'
193    #
194    #   uri = URI.parse("ftp://john@ftp.example.com/my_file.img")
195    #   #=> #<URI::FTP:0x00000000923650 URL:ftp://john@ftp.example.com/my_file.img>
196    #   uri.typecode = "i"
197    #   # =>  "i"
198    #   uri
199    #   #=> #<URI::FTP:0x00000000923650 URL:ftp://john@ftp.example.com/my_file.img;type=i>
200    #
201    def typecode=(typecode)
202      check_typecode(typecode)
203      set_typecode(typecode)
204      typecode
205    end
206
207    def merge(oth) # :nodoc:
208      tmp = super(oth)
209      if self != tmp
210        tmp.set_typecode(oth.typecode)
211      end
212
213      return tmp
214    end
215
216    # Returns the path from an FTP URI.
217    #
218    # RFC 1738 specifically states that the path for an FTP URI does not
219    # include the / which separates the URI path from the URI host. Example:
220    #
221    #     ftp://ftp.example.com/pub/ruby
222    #
223    # The above URI indicates that the client should connect to
224    # ftp.example.com then cd pub/ruby from the initial login directory.
225    #
226    # If you want to cd to an absolute directory, you must include an
227    # escaped / (%2F) in the path. Example:
228    #
229    #     ftp://ftp.example.com/%2Fpub/ruby
230    #
231    # This method will then return "/pub/ruby"
232    #
233    def path
234      return @path.sub(/^\//,'').sub(/^%2F/,'/')
235    end
236
237    def set_path(v)
238      super("/" + v.sub(/^\//, "%2F"))
239    end
240    protected :set_path
241
242    def to_s
243      save_path = nil
244      if @typecode
245        save_path = @path
246        @path = @path + TYPECODE_PREFIX + @typecode
247      end
248      str = super
249      if @typecode
250        @path = save_path
251      end
252
253      return str
254    end
255  end
256  @@schemes['FTP'] = FTP
257end
258