Class: URI::FTP

Inherits:
Generic show all
Includes:
OpenURI::OpenRead
Defined in:
lib/uri/ftp.rb,
lib/open-uri.rb

Overview

FTP URI syntax is defined by RFC1738 section 3.2.

This class will be redesigned because of difference of implementations; the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it is a good summary about the de facto spec. tools.ietf.org/html/draft-hoffman-ftp-uri-04

Constant Summary

DEFAULT_PORT =
21
COMPONENT =
[
  :scheme,
  :userinfo, :host, :port,
  :path, :typecode
].freeze
TYPECODE =

Typecode is "a", "i" or "d".

  • "a" indicates a text file (the FTP command was ASCII)

  • "i" indicates a binary file (FTP command IMAGE)

  • "d" indicates the contents of a directory should be displayed

['a', 'i', 'd'].freeze
TYPECODE_PREFIX =
';type='.freeze

Constants inherited from Generic

Generic::USE_REGISTRY

Constants included from URI

DEFAULT_PARSER, HTML5ASCIIINCOMPAT, TBLDECWWWCOMP_, TBLENCWWWCOMP_, VERSION, VERSION_CODE, WFKV_

Instance Attribute Summary (collapse)

Attributes inherited from Generic

#fragment, #host, #opaque, #port, #query, #registry, #scheme

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods included from OpenURI::OpenRead

#open, #read

Methods inherited from Generic

#==, #absolute?, build2, #coerce, component, #component, #default_port, default_port, #eql?, #find_proxy, #hash, #hierarchical?, #inspect, #merge!, #normalize, #normalize!, #parser, #password, #password=, #relative?, #route_from, #route_to, #select, use_registry, #user, #user=, #userinfo, #userinfo=

Methods included from URI

decode_www_form, decode_www_form_component, encode_www_form, encode_www_form_component, extract, join, parse, regexp, scheme_list, split

Methods included from Escape

#escape, #unescape

Constructor Details

- (FTP) initialize(*arg)

Description

Creates a new URI::FTP object from generic URL components with no syntax checking.

Unlike build(), this method does not escape the path component as required by RFC1738; instead it is treated as per RFC2396.

Arguments are scheme, userinfo, host, port, registry, path, opaque, query and fragment, in that order.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/uri/ftp.rb', line 120

def initialize(*arg)
  arg[5] = arg[5].sub(/^\//,'').sub(/^%2F/,'/')
  super(*arg)
  @typecode = nil
  tmp = @path.index(TYPECODE_PREFIX)
  if tmp
    typecode = @path[tmp + TYPECODE_PREFIX.size..-1]
    @path = @path[0..tmp - 1]

    if arg[-1]
      self.typecode = typecode
    else
      self.set_typecode(typecode)
    end
  end
end

Instance Attribute Details

- (Object) typecode

Returns the value of attribute typecode



136
137
138
# File 'lib/uri/ftp.rb', line 136

def typecode
  @typecode
end

Class Method Details

+ (Object) build(args)

Description

Creates a new URI::FTP object from components, with syntax checking.

The components accepted are userinfo, host, port, path and typecode.

The components should be provided either as an Array, or as a Hash with keys formed by preceding the component names with a colon.

If an Array is used, the components must be passed in the order

userinfo, host, port, path, typecode

If the path supplied is absolute, it will be escaped in order to make it absolute in the URI. Examples:

require 'uri'

uri = URI::FTP.build(['user:password', 'ftp.example.com', nil,
  '/path/file.> zip', 'i'])
puts uri.to_s  ->  ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=a

uri2 = URI::FTP.build({:host => 'ftp.example.com',
  :path => 'ruby/src'})
puts uri2.to_s  ->  ftp://ftp.example.com/ruby/src


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/uri/ftp.rb', line 83

def self.build(args)

  # Fix the incoming path to be generic URL syntax
  # FTP path  ->  URL path
  # foo/bar       /foo/bar
  # /foo/bar      /%2Ffoo/bar
  #
  if args.kind_of?(Array)
    args[3] = '/' + args[3].sub(/^\//, '%2F')
  else
    args[:path] = '/' + args[:path].sub(/^\//, '%2F')
  end

  tmp = Util::make_components_hash(self, args)

  if tmp[:typecode]
    if tmp[:typecode].size == 1
      tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode]
    end
    tmp[:path] << tmp[:typecode]
  end

  return super(tmp)
end

+ (Object) new2(user, password, host, port, path, typecode = nil, arg_check = true)



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/uri/ftp.rb', line 39

def self.new2(user, password, host, port, path,
              typecode = nil, arg_check = true)
  typecode = nil if typecode.size == 0
  if typecode && !TYPECODE.include?(typecode)
    raise ArgumentError,
      "bad typecode is specified: #{typecode}"
  end

  # do escape

  self.new('ftp',
           [user, password],
           host, port, nil,
           typecode ? path + TYPECODE_PREFIX + typecode : path,
           nil, nil, nil, arg_check)
end

Instance Method Details

- (Object) buffer_open(buf, proxy, options)

:nodoc:



776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
# File 'lib/open-uri.rb', line 776

def buffer_open(buf, proxy, options) # :nodoc:
  if proxy
    OpenURI.open_http(buf, self, proxy, options)
    return
  end
  require 'net/ftp'

  path = self.path
  path = path.sub(%r{\A/}, '%2F') # re-encode the beginning slash because uri library decodes it.
  directories = path.split(%r{/}, -1)
  directories.each {|d|
    d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
  }
  unless filename = directories.pop
    raise ArgumentError, "no filename: #{self.inspect}"
  end
  directories.each {|d|
    if /[\r\n]/ =~ d
      raise ArgumentError, "invalid directory: #{d.inspect}"
    end
  }
  if /[\r\n]/ =~ filename
    raise ArgumentError, "invalid filename: #{filename.inspect}"
  end
  typecode = self.typecode
  if typecode && /\A[aid]\z/ !~ typecode
    raise ArgumentError, "invalid typecode: #{typecode.inspect}"
  end

  # The access sequence is defined by RFC 1738
  ftp = Net::FTP.new
  ftp.connect(self.host, self.port)
  ftp.passive = true if !options[:ftp_active_mode]
  # todo: extract user/passwd from .netrc.
  user = 'anonymous'
  passwd = nil
  user, passwd = self.userinfo.split(/:/) if self.userinfo
  ftp.(user, passwd)
  directories.each {|cwd|
    ftp.voidcmd("CWD #{cwd}")
  }
  if typecode
    # xxx: typecode D is not handled.
    ftp.voidcmd("TYPE #{typecode.upcase}")
  end
  if options[:content_length_proc]
    options[:content_length_proc].call(ftp.size(filename))
  end
  ftp.retrbinary("RETR #{filename}", 4096) { |str|
    buf << str
    options[:progress_proc].call(buf.size) if options[:progress_proc]
  }
  ftp.close
  buf.io.rewind
end

- (Object) merge(oth)

:nodoc:



159
160
161
162
163
164
165
166
# File 'lib/uri/ftp.rb', line 159

def merge(oth) # :nodoc:
  tmp = super(oth)
  if self != tmp
    tmp.set_typecode(oth.typecode)
  end

  return tmp
end

- (Object) path

Returns the path from an FTP URI.

RFC 1738 specifically states that the path for an FTP URI does not include the / which separates the URI path from the URI host. Example:

ftp://ftp.example.com/pub/ruby

The above URI indicates that the client should connect to ftp.example.com then cd pub/ruby from the initial login directory.

If you want to cd to an absolute directory, you must include an escaped / (%2F) in the path. Example:

ftp://ftp.example.com/%2Fpub/ruby

This method will then return "/pub/ruby"



185
186
187
# File 'lib/uri/ftp.rb', line 185

def path
  return @path.sub(/^\//,'').sub(/^%2F/,'/')
end

- (Object) to_s



194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/uri/ftp.rb', line 194

def to_s
  save_path = nil
  if @typecode
    save_path = @path
    @path = @path + TYPECODE_PREFIX + @typecode
  end
  str = super
  if @typecode
    @path = save_path
  end

  return str
end