📄 open-uri.rb
字号:
if src obj.status = src.status obj.base_uri = src.base_uri src.meta.each {|name, value| obj.meta_add_field(name, value) } end end # returns an Array which consists status code and message. attr_accessor :status # returns a URI which is base of relative URIs in the data. # It may differ from the URI supplied by a user because redirection. attr_accessor :base_uri # returns a Hash which represents header fields. # The Hash keys are downcased for canonicalization. attr_reader :meta def meta_add_field(name, value) # :nodoc: @meta[name.downcase] = value end # returns a Time which represents Last-Modified field. def last_modified if v = @meta['last-modified'] Time.httpdate(v) else nil end end RE_LWS = /[\r\n\t ]+/n RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n def content_type_parse # :nodoc: v = @meta['content-type'] # The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045. if v && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ v type = $1.downcase subtype = $2.downcase parameters = [] $3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval| val = qval.gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/) { $1 ? $1[1,1] : $& } if qval parameters << [att.downcase, val] } ["#{type}/#{subtype}", *parameters] else nil end end # returns "type/subtype" which is MIME Content-Type. # It is downcased for canonicalization. # Content-Type parameters are stripped. def content_type type, *parameters = content_type_parse type || 'application/octet-stream' end # returns a charset parameter in Content-Type field. # It is downcased for canonicalization. # # If charset parameter is not given but a block is given, # the block is called and its result is returned. # It can be used to guess charset. # # If charset parameter and block is not given, # nil is returned except text type in HTTP. # In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1. def charset type, *parameters = content_type_parse if pair = parameters.assoc('charset') pair.last.downcase elsif block_given? yield elsif type && %r{\Atext/} =~ type && @base_uri && /\Ahttp\z/i =~ @base_uri.scheme "iso-8859-1" # RFC2616 3.7.1 else nil end end # returns a list of encodings in Content-Encoding field # as an Array of String. # The encodings are downcased for canonicalization. def content_encoding v = @meta['content-encoding'] if v && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ v v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase} else [] end end end # Mixin for HTTP and FTP URIs. module OpenRead # OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP. # # OpenURI::OpenRead#open takes optional 3 arguments as: # OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }] # # `mode', `perm' is same as Kernel#open. # # However, `mode' must be read mode because OpenURI::OpenRead#open doesn't # support write mode (yet). # Also `perm' is just ignored because it is meaningful only for file # creation. # # `options' must be a hash. # # Each pairs which key is a string in the hash specify a extra header # field for HTTP. # I.e. it is ignored for FTP without HTTP proxy. # # The hash may include other options which key is a symbol: # # [:proxy] # Synopsis: # :proxy => "http://proxy.foo.com:8000/" # :proxy => URI.parse("http://proxy.foo.com:8000/") # :proxy => true # :proxy => false # :proxy => nil # # If :proxy option is specified, the value should be String, URI, # boolean or nil. # When String or URI is given, it is treated as proxy URI. # When true is given or the option itself is not specified, # environment variable `scheme_proxy' is examined. # `scheme' is replaced by `http', `https' or `ftp'. # When false or nil is given, the environment variables are ignored and # connection will be made to a server directly. # # [:proxy_http_basic_authentication] # Synopsis: # :proxy_http_basic_authentication => ["http://proxy.foo.com:8000/", "proxy-user", "proxy-password"] # :proxy_http_basic_authentication => [URI.parse("http://proxy.foo.com:8000/"), "proxy-user", "proxy-password"] # # If :proxy option is specified, the value should be an Array with 3 elements. # It should contain a proxy URI, a proxy user name and a proxy password. # The proxy URI should be a String, an URI or nil. # The proxy user name and password should be a String. # # If nil is given for the proxy URI, this option is just ignored. # # If :proxy and :proxy_http_basic_authentication is specified, # ArgumentError is raised. # # [:http_basic_authentication] # Synopsis: # :http_basic_authentication=>[user, password] # # If :http_basic_authentication is specified, # the value should be an array which contains 2 strings: # username and password. # It is used for HTTP Basic authentication defined by RFC 2617. # # [:content_length_proc] # Synopsis: # :content_length_proc => lambda {|content_length| ... } # # If :content_length_proc option is specified, the option value procedure # is called before actual transfer is started. # It takes one argument which is expected content length in bytes. # # If two or more transfer is done by HTTP redirection, the procedure # is called only one for a last transfer. # # When expected content length is unknown, the procedure is called with # nil. # It is happen when HTTP response has no Content-Length header. # # [:progress_proc] # Synopsis: # :progress_proc => lambda {|size| ...} # # If :progress_proc option is specified, the proc is called with one # argument each time when `open' gets content fragment from network. # The argument `size' `size' is a accumulated transfered size in bytes. # # If two or more transfer is done by HTTP redirection, the procedure # is called only one for a last transfer. # # :progress_proc and :content_length_proc are intended to be used for # progress bar. # For example, it can be implemented as follows using Ruby/ProgressBar. # # pbar = nil # open("http://...", # :content_length_proc => lambda {|t| # if t && 0 < t # pbar = ProgressBar.new("...", t) # pbar.file_transfer_mode # end # }, # :progress_proc => lambda {|s| # pbar.set s if pbar # }) {|f| ... } # # [:read_timeout] # Synopsis: # :read_timeout=>nil (no timeout) # :read_timeout=>10 (10 second) # # :read_timeout option specifies a timeout of read for http connections. # # OpenURI::OpenRead#open returns an IO like object if block is not given. # Otherwise it yields the IO object and return the value of the block. # The IO object is extended with OpenURI::Meta. def open(*rest, &block) OpenURI.open_uri(self, *rest, &block) end # OpenURI::OpenRead#read([options]) reads a content referenced by self and # returns the content as string. # The string is extended with OpenURI::Meta. # The argument `options' is same as OpenURI::OpenRead#open. def read(options={}) self.open(options) {|f| str = f.read Meta.init str, f str } end endendmodule URI class Generic # returns a proxy URI. # The proxy URI is obtained from environment variables such as http_proxy, # ftp_proxy, no_proxy, etc. # If there is no proper proxy, nil is returned. # # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) # are examined too. # # But http_proxy and HTTP_PROXY is treated specially under CGI environment. # It's because HTTP_PROXY may be set by Proxy: header. # So HTTP_PROXY is not used. # http_proxy is not used too if the variable is case insensitive. # CGI_HTTP_PROXY can be used instead. def find_proxy name = self.scheme.downcase + '_proxy' proxy_uri = nil if name == 'http_proxy' && ENV.include?('REQUEST_METHOD') # CGI? # HTTP_PROXY conflicts with *_proxy for proxy settings and # HTTP_* for header information in CGI. # So it should be careful to use it. pairs = ENV.reject {|k, v| /\Ahttp_proxy\z/i !~ k } case pairs.length when 0 # no proxy setting anyway. proxy_uri = nil when 1 k, v = pairs.shift if k == 'http_proxy' && ENV[k.upcase] == nil # http_proxy is safe to use because ENV is case sensitive. proxy_uri = ENV[name] else proxy_uri = nil end else # http_proxy is safe to use because ENV is case sensitive. proxy_uri = ENV[name] end if !proxy_uri # Use CGI_HTTP_PROXY. cf. libwww-perl. proxy_uri = ENV["CGI_#{name.upcase}"] end elsif name == 'http_proxy' unless proxy_uri = ENV[name] if proxy_uri = ENV[name.upcase] warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.' end end else proxy_uri = ENV[name] || ENV[name.upcase] end if proxy_uri && self.host require 'socket' begin addr = IPSocket.getaddress(self.host) proxy_uri = nil if /\A127\.|\A::1\z/ =~ addr rescue SocketError end end if proxy_uri proxy_uri = URI.parse(proxy_uri) name = 'no_proxy' if no_proxy = ENV[name] || ENV[name.upcase] no_proxy.scan(/([^:,]*)(?::(\d+))?/) {|host, port| if /(\A|\.)#{Regexp.quote host}\z/i =~ self.host && (!port || self.port == port.to_i) proxy_uri = nil break end } end proxy_uri else nil end end end class HTTP def buffer_open(buf, proxy, options) # :nodoc: OpenURI.open_http(buf, self, proxy, options) end include OpenURI::OpenRead end class FTP def buffer_open(buf, proxy, options) # :nodoc: if proxy OpenURI.open_http(buf, self, proxy, options) return end require 'net/ftp' directories = self.path.split(%r{/}, -1) directories.shift if directories[0] == '' # strip a field before leading slash 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.open(self.host) # todo: extract user/passwd from .netrc. user = 'anonymous' passwd = nil user, passwd = self.userinfo.split(/:/) if self.userinfo ftp.login(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 include OpenURI::OpenRead endend
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -