rubygems-update 0.8.3
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.
Potentially problematic release.
This version of rubygems-update might be problematic. Click here for more details.
- data/ChangeLog +2335 -0
- data/README +54 -0
- data/Rakefile +293 -0
- data/Releases +98 -0
- data/TODO +7 -0
- data/bin/gem +11 -0
- data/bin/gem_server +111 -0
- data/bin/generate_yaml_index.rb +58 -0
- data/bin/update_rubygems +18 -0
- data/doc/doc.css +73 -0
- data/doc/makedoc.rb +4 -0
- data/examples/application/an-app.gemspec +26 -0
- data/examples/application/bin/myapp +3 -0
- data/examples/application/lib/somefunctionality.rb +3 -0
- data/gemspecs/README +4 -0
- data/gemspecs/cgikit-1.1.0.gemspec +18 -0
- data/gemspecs/jabber4r.gemspec +26 -0
- data/gemspecs/linguistics.gemspec +22 -0
- data/gemspecs/ook.gemspec +21 -0
- data/gemspecs/progressbar.gemspec +22 -0
- data/gemspecs/redcloth.gemspec +22 -0
- data/gemspecs/rublog.gemspec +23 -0
- data/gemspecs/ruby-doom.gemspec +21 -0
- data/gemspecs/rubyjdwp.gemspec +21 -0
- data/gemspecs/statistics.gemspec +21 -0
- data/lib/rubygems.rb +353 -0
- data/lib/rubygems/builder.rb +54 -0
- data/lib/rubygems/cmd_manager.rb +127 -0
- data/lib/rubygems/command.rb +191 -0
- data/lib/rubygems/config_file.rb +57 -0
- data/lib/rubygems/doc_manager.rb +94 -0
- data/lib/rubygems/format.rb +65 -0
- data/lib/rubygems/gem_commands.rb +925 -0
- data/lib/rubygems/gem_runner.rb +23 -0
- data/lib/rubygems/installer.rb +621 -0
- data/lib/rubygems/loadpath_manager.rb +108 -0
- data/lib/rubygems/old_format.rb +150 -0
- data/lib/rubygems/open-uri.rb +604 -0
- data/lib/rubygems/package.rb +740 -0
- data/lib/rubygems/remote_installer.rb +499 -0
- data/lib/rubygems/rubygems_version.rb +6 -0
- data/lib/rubygems/source_index.rb +130 -0
- data/lib/rubygems/specification.rb +613 -0
- data/lib/rubygems/user_interaction.rb +176 -0
- data/lib/rubygems/validator.rb +148 -0
- data/lib/rubygems/version.rb +279 -0
- data/lib/ubygems.rb +4 -0
- data/pkgs/sources/lib/sources.rb +6 -0
- data/pkgs/sources/sources.gemspec +14 -0
- data/post-install.rb +75 -0
- data/redist/session.gem +433 -0
- data/scripts/buildtests.rb +25 -0
- data/scripts/gemdoc.rb +62 -0
- data/scripts/runtest.rb +17 -0
- data/scripts/specdoc.rb +164 -0
- data/setup.rb +1360 -0
- data/test/bogussources.rb +5 -0
- data/test/data/legacy/keyedlist-0.4.0.ruby +11 -0
- data/test/data/legacy/keyedlist-0.4.0.yaml +16 -0
- data/test/data/lib/code.rb +1 -0
- data/test/data/one/README.one +1 -0
- data/test/data/one/lib/one.rb +3 -0
- data/test/data/one/one.gemspec +17 -0
- data/test/data/one/one.yaml +40 -0
- data/test/functional.rb +145 -0
- data/test/gemenvironment.rb +45 -0
- data/test/gemutilities.rb +18 -0
- data/test/insure_session.rb +46 -0
- data/test/mock/gems/gems/sources-0.0.1/lib/sources.rb +5 -0
- data/test/mock/gems/specifications/sources-0.0.1.gemspec +8 -0
- data/test/mockgemui.rb +45 -0
- data/test/onegem.rb +23 -0
- data/test/simple_gem.rb +66 -0
- data/test/test_builder.rb +13 -0
- data/test/test_cached_fetcher.rb +60 -0
- data/test/test_check_command.rb +28 -0
- data/test/test_command.rb +130 -0
- data/test/test_configfile.rb +36 -0
- data/test/test_format.rb +70 -0
- data/test/test_gemloadpaths.rb +45 -0
- data/test/test_gempaths.rb +115 -0
- data/test/test_loadmanager.rb +40 -0
- data/test/test_local_cache.rb +157 -0
- data/test/test_package.rb +600 -0
- data/test/test_parse_commands.rb +179 -0
- data/test/test_process_commands.rb +21 -0
- data/test/test_remote_fetcher.rb +162 -0
- data/test/test_remote_installer.rb +154 -0
- data/test/test_source_index.rb +58 -0
- data/test/test_specification.rb +286 -0
- data/test/test_validator.rb +53 -0
- data/test/test_version_comparison.rb +204 -0
- data/test/testgem.rc +6 -0
- data/test/user_capture.rb +1 -0
- data/test/yaml_data.rb +59 -0
- metadata +151 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
module Kernel
|
2
|
+
alias require__ require
|
3
|
+
def require(file)
|
4
|
+
Gem::LoadPathManager.search_loadpath(file) || Gem::LoadPathManager.search_gempath(file)
|
5
|
+
require__(file)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Gem
|
10
|
+
module LoadPathManager
|
11
|
+
@paths = nil
|
12
|
+
|
13
|
+
# These local versions of Gem::Version and Gem::Specification are
|
14
|
+
# used by the load path manager because they are faster than the
|
15
|
+
# fully functional ones. Full functionality is not required
|
16
|
+
# during the load phase.
|
17
|
+
module Gem
|
18
|
+
class Version
|
19
|
+
class Requirement
|
20
|
+
def initialize(string)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
class Specification
|
25
|
+
def initialize(&block)
|
26
|
+
@require_paths = ['lib']
|
27
|
+
@platform = nil
|
28
|
+
yield self
|
29
|
+
end
|
30
|
+
attr_reader :version
|
31
|
+
attr_accessor :files, :require_paths, :name
|
32
|
+
def platform=(platform)
|
33
|
+
@platform = platform unless platform == "ruby"
|
34
|
+
end
|
35
|
+
def requirements; []; end
|
36
|
+
def version=(version)
|
37
|
+
@version = ::Gem::Version.create(version)
|
38
|
+
end
|
39
|
+
def full_name
|
40
|
+
@full_name ||=
|
41
|
+
if @platform.nil? || @platform == "ruby" || @platform == ""
|
42
|
+
"#{@name}-#{@version}"
|
43
|
+
else
|
44
|
+
"#{@name}-#{@version}-#{@platform}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
def method_missing(method, *args)
|
48
|
+
end
|
49
|
+
def <=>(other)
|
50
|
+
r = @name<=>other.name
|
51
|
+
r = other.version<=>@version if r == 0
|
52
|
+
r
|
53
|
+
end
|
54
|
+
def to_s
|
55
|
+
"#<Gem::Specification name=#{@name} version=#{@version} quick=true>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.paths
|
61
|
+
@paths
|
62
|
+
end
|
63
|
+
|
64
|
+
# Prep the list of potential paths for require file resolution.
|
65
|
+
def self.build_paths
|
66
|
+
@specs ||= []
|
67
|
+
@paths = []
|
68
|
+
::Gem.path.each do |gempath|
|
69
|
+
newspecs = Dir.glob("#{gempath}/specifications/*.gemspec").collect { |specfile|
|
70
|
+
eval(File.read(specfile))
|
71
|
+
}.sort
|
72
|
+
@specs.concat(newspecs)
|
73
|
+
newspecs.each do |spec|
|
74
|
+
spec.require_paths.each {|path| @paths << "#{gempath}/gems/#{spec.full_name}/#{path}"}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# True if the file can be resolved with the existing load path.
|
80
|
+
def self.search_loadpath(file)
|
81
|
+
glob_over($LOAD_PATH, file).size > 0
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.search_gempath(file)
|
85
|
+
build_paths unless @paths
|
86
|
+
fullname = glob_over(@paths, file).first
|
87
|
+
return false unless fullname
|
88
|
+
@specs.each do |spec|
|
89
|
+
if fullname.include?("/#{spec.full_name}/")
|
90
|
+
::Gem.activate(spec.name, false, spec.version.to_s)
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
SUFFIX_PATTERN = "{,.rb,.so,.bundle,.dll,.sl}"
|
100
|
+
|
101
|
+
def self.glob_over(list, file)
|
102
|
+
files = Dir.glob("{#{(list).join(',')}}/#{file}#{SUFFIX_PATTERN}").map{|x| Marshal.load(Marshal.dump(x))}
|
103
|
+
files.delete_if { |f| File.directory?(f) }
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module Gem
|
2
|
+
|
3
|
+
##
|
4
|
+
# Used to raise parsing and loading errors
|
5
|
+
#
|
6
|
+
class FormatException < Gem::Exception
|
7
|
+
attr_accessor :file_path
|
8
|
+
#I go back and forth on whether or not to create custom exception classes
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# The format class knows the guts of the RubyGem .gem file format
|
13
|
+
# and provides the capability to read gem files
|
14
|
+
#
|
15
|
+
class OldFormat
|
16
|
+
attr_accessor :spec, :file_entries, :gem_path
|
17
|
+
|
18
|
+
##
|
19
|
+
# Constructs an instance of a Format object, representing the gem's
|
20
|
+
# data structure.
|
21
|
+
#
|
22
|
+
# gem:: [String] The file name of the gem
|
23
|
+
#
|
24
|
+
def initialize(gem_path)
|
25
|
+
@gem_path = gem_path
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Reads the named gem file and returns a Format object, representing
|
30
|
+
# the data from the gem file
|
31
|
+
#
|
32
|
+
# file_path:: [String] Path to the gem file
|
33
|
+
#
|
34
|
+
def self.from_file_by_path(file_path)
|
35
|
+
unless File.exist?(file_path)
|
36
|
+
raise Gem::Exception, "Cannot load gem file [#{file_path}]"
|
37
|
+
end
|
38
|
+
require 'fileutils'
|
39
|
+
File.open(file_path, 'r') do |file|
|
40
|
+
from_io(file, file_path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Reads a gem from an io stream and returns a Format object, representing
|
46
|
+
# the data from the gem file
|
47
|
+
#
|
48
|
+
# io:: [IO] Stream from which to read the gem
|
49
|
+
#
|
50
|
+
def self.from_io(io, gem_path="(io)")
|
51
|
+
format = self.new(gem_path)
|
52
|
+
skip_ruby(io)
|
53
|
+
format.spec = read_spec(io)
|
54
|
+
format.file_entries = []
|
55
|
+
read_files_from_gem(io) do |entry, file_data|
|
56
|
+
format.file_entries << [entry, file_data]
|
57
|
+
end
|
58
|
+
format
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
##
|
63
|
+
# Skips the Ruby self-install header. After calling this method, the
|
64
|
+
# IO index will be set after the Ruby code.
|
65
|
+
#
|
66
|
+
# file:: [IO] The IO to process (skip the Ruby code)
|
67
|
+
#
|
68
|
+
def self.skip_ruby(file)
|
69
|
+
end_seen = false
|
70
|
+
loop {
|
71
|
+
line = file.gets
|
72
|
+
if(line == nil || line.chomp == "__END__") then
|
73
|
+
end_seen = true
|
74
|
+
break
|
75
|
+
end
|
76
|
+
}
|
77
|
+
if(end_seen == false) then
|
78
|
+
raise Gem::Exception.new("Failed to find end of ruby script while reading gem")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Reads the specification YAML from the supplied IO and constructs
|
84
|
+
# a Gem::Specification from it. After calling this method, the
|
85
|
+
# IO index will be set after the specification header.
|
86
|
+
#
|
87
|
+
# file:: [IO] The IO to process
|
88
|
+
#
|
89
|
+
def self.read_spec(file)
|
90
|
+
require 'yaml'
|
91
|
+
yaml = ''
|
92
|
+
begin
|
93
|
+
read_until_dashes(file) do |line|
|
94
|
+
yaml << line
|
95
|
+
end
|
96
|
+
Specification.from_yaml(yaml)
|
97
|
+
rescue YAML::Error => e
|
98
|
+
raise Gem::Exception.new("Failed to parse gem specification out of gem file")
|
99
|
+
rescue ArgumentError => e
|
100
|
+
raise Gem::Exception.new("Failed to parse gem specification out of gem file")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Reads lines from the supplied IO until a end-of-yaml (---) is
|
106
|
+
# reached
|
107
|
+
#
|
108
|
+
# file:: [IO] The IO to process
|
109
|
+
# block:: [String] The read line
|
110
|
+
#
|
111
|
+
def self.read_until_dashes(file)
|
112
|
+
while((line = file.gets) && line.chomp.strip != "---") do
|
113
|
+
yield line
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
##
|
119
|
+
# Reads the embedded file data from a gem file, yielding an entry
|
120
|
+
# containing metadata about the file and the file contents themselves
|
121
|
+
# for each file that's archived in the gem.
|
122
|
+
# NOTE: Many of these methods should be extracted into some kind of
|
123
|
+
# Gem file read/writer
|
124
|
+
#
|
125
|
+
# gem_file:: [IO] The IO to process
|
126
|
+
#
|
127
|
+
def self.read_files_from_gem(gem_file)
|
128
|
+
require 'zlib'
|
129
|
+
require 'yaml'
|
130
|
+
errstr = "Error reading files from gem"
|
131
|
+
header_yaml = ''
|
132
|
+
begin
|
133
|
+
self.read_until_dashes(gem_file) do |line|
|
134
|
+
header_yaml << line
|
135
|
+
end
|
136
|
+
header = YAML.load(header_yaml)
|
137
|
+
raise Gem::Exception.new(errstr) unless header
|
138
|
+
header.each do |entry|
|
139
|
+
file_data = ''
|
140
|
+
self.read_until_dashes(gem_file) do |line|
|
141
|
+
file_data << line
|
142
|
+
end
|
143
|
+
yield [entry, Zlib::Inflate.inflate(file_data.strip.unpack("m")[0])]
|
144
|
+
end
|
145
|
+
rescue Exception,Zlib::DataError => e
|
146
|
+
raise Gem::Exception.new(errstr)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,604 @@
|
|
1
|
+
#= open-uri.rb
|
2
|
+
#
|
3
|
+
#open-uri.rb is easy-to-use wrapper for net/http and net/ftp.
|
4
|
+
#
|
5
|
+
#== Example
|
6
|
+
#
|
7
|
+
#It is possible to open http/ftp URL as usual a file:
|
8
|
+
#
|
9
|
+
# open("http://www.ruby-lang.org/") {|f|
|
10
|
+
# f.each_line {|line| p line}
|
11
|
+
# }
|
12
|
+
#
|
13
|
+
#The opened file has several methods for meta information as follows since
|
14
|
+
#it is extended by OpenURI::Meta.
|
15
|
+
#
|
16
|
+
# open("http://www.ruby-lang.org/en") {|f|
|
17
|
+
# f.each_line {|line| p line}
|
18
|
+
# p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
|
19
|
+
# p f.content_type # "text/html"
|
20
|
+
# p f.charset # "iso-8859-1"
|
21
|
+
# p f.content_encoding # []
|
22
|
+
# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
#Additional header fields can be specified by an optional hash argument.
|
26
|
+
#
|
27
|
+
# open("http://www.ruby-lang.org/en/",
|
28
|
+
# "User-Agent" => "Ruby/#{RUBY_VERSION}",
|
29
|
+
# "From" => "foo@bar.invalid",
|
30
|
+
# "Referer" => "http://www.ruby-lang.org/") {|f|
|
31
|
+
# ...
|
32
|
+
# }
|
33
|
+
#
|
34
|
+
#The environment variables such as http_proxy and ftp_proxy are in effect by
|
35
|
+
#default. :proxy => nil disables proxy.
|
36
|
+
#
|
37
|
+
# open("http://www.ruby-lang.org/en/raa.html",
|
38
|
+
# :proxy => nil) {|f|
|
39
|
+
# ...
|
40
|
+
# }
|
41
|
+
#
|
42
|
+
#URI objects can be opened in similar way.
|
43
|
+
#
|
44
|
+
# uri = URI.parse("http://www.ruby-lang.org/en/")
|
45
|
+
# uri.open {|f|
|
46
|
+
# ...
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
#URI objects can be read directly.
|
50
|
+
#The returned string is also extended by OpenURI::Meta.
|
51
|
+
#
|
52
|
+
# str = uri.read
|
53
|
+
# p str.base_uri
|
54
|
+
#
|
55
|
+
#Author:: Tanaka Akira <akr@m17n.org>
|
56
|
+
|
57
|
+
require 'uri'
|
58
|
+
require 'stringio'
|
59
|
+
require 'time'
|
60
|
+
|
61
|
+
module Kernel
|
62
|
+
private
|
63
|
+
alias open_uri_original_open open # :nodoc:
|
64
|
+
|
65
|
+
# makes possible to open various resources including URIs.
|
66
|
+
# If the first argument respond to `open' method,
|
67
|
+
# the method is called with the rest arguments.
|
68
|
+
#
|
69
|
+
# If the first argument is a string which begins with xxx://,
|
70
|
+
# it is parsed by URI.parse. If the parsed object respond to `open' method,
|
71
|
+
# the method is called with the rest arguments.
|
72
|
+
#
|
73
|
+
# Otherwise original open is called.
|
74
|
+
#
|
75
|
+
# Since open-uri.rb provides URI::HTTP#open and URI::FTP#open,
|
76
|
+
# Kernel[#.]open can accepts such URIs and strings which begins with
|
77
|
+
# http:// and ftp://. In this http and ftp case, the opened file object
|
78
|
+
# is extended by OpenURI::Meta.
|
79
|
+
def open(name, *rest, &block) # :doc:
|
80
|
+
if name.respond_to?(:open)
|
81
|
+
name.open(*rest, &block)
|
82
|
+
elsif name.respond_to?(:to_str) &&
|
83
|
+
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
|
84
|
+
(uri = URI.parse(name)).respond_to?(:open)
|
85
|
+
uri.open(*rest, &block)
|
86
|
+
else
|
87
|
+
open_uri_original_open(name, *rest, &block)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
module_function :open
|
91
|
+
end
|
92
|
+
|
93
|
+
module OpenURI
|
94
|
+
Options = {
|
95
|
+
:proxy => true,
|
96
|
+
:progress_proc => true,
|
97
|
+
:content_length_proc => true,
|
98
|
+
}
|
99
|
+
|
100
|
+
|
101
|
+
def OpenURI.check_options(options) # :nodoc:
|
102
|
+
options.each {|k, v|
|
103
|
+
next unless Symbol === k
|
104
|
+
unless Options.include? k
|
105
|
+
raise ArgumentError, "unrecognized option: #{k}"
|
106
|
+
end
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def OpenURI.scan_open_optional_arguments(*rest) # :nodoc:
|
111
|
+
if !rest.empty? && (String === rest.first || Integer === rest.first)
|
112
|
+
mode = rest.shift
|
113
|
+
if !rest.empty? && Integer === rest.first
|
114
|
+
perm = rest.shift
|
115
|
+
end
|
116
|
+
end
|
117
|
+
return mode, perm, rest
|
118
|
+
end
|
119
|
+
|
120
|
+
def OpenURI.open_uri(name, *rest) # :nodoc:
|
121
|
+
uri = URI::Generic === name ? name : URI.parse(name)
|
122
|
+
mode, perm, rest = OpenURI.scan_open_optional_arguments(*rest)
|
123
|
+
options = rest.shift if !rest.empty? && Hash === rest.first
|
124
|
+
raise ArgumentError.new("extra arguments") if !rest.empty?
|
125
|
+
options ||= {}
|
126
|
+
OpenURI.check_options(options)
|
127
|
+
|
128
|
+
unless mode == nil ||
|
129
|
+
mode == 'r' || mode == 'rb' ||
|
130
|
+
mode == File::RDONLY
|
131
|
+
raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)")
|
132
|
+
end
|
133
|
+
|
134
|
+
io = open_loop(uri, options)
|
135
|
+
if block_given?
|
136
|
+
begin
|
137
|
+
yield io
|
138
|
+
ensure
|
139
|
+
io.close
|
140
|
+
end
|
141
|
+
else
|
142
|
+
io
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def OpenURI.open_loop(uri, options) # :nodoc:
|
147
|
+
case opt_proxy = options.fetch(:proxy, true)
|
148
|
+
when true
|
149
|
+
find_proxy = lambda {|u| u.find_proxy}
|
150
|
+
when nil, false
|
151
|
+
find_proxy = lambda {|u| nil}
|
152
|
+
when String
|
153
|
+
opt_proxy = URI.parse(opt_proxy)
|
154
|
+
find_proxy = lambda {|u| opt_proxy}
|
155
|
+
when URI::Generic
|
156
|
+
find_proxy = lambda {|u| opt_proxy}
|
157
|
+
else
|
158
|
+
raise ArgumentError.new("Invalid proxy option: #{opt_proxy}")
|
159
|
+
end
|
160
|
+
|
161
|
+
uri_set = {}
|
162
|
+
buf = nil
|
163
|
+
while true
|
164
|
+
redirect = catch(:open_uri_redirect) {
|
165
|
+
buf = Buffer.new
|
166
|
+
if proxy_uri = find_proxy.call(uri)
|
167
|
+
proxy_uri.proxy_open(buf, uri, options)
|
168
|
+
else
|
169
|
+
uri.direct_open(buf, options)
|
170
|
+
end
|
171
|
+
nil
|
172
|
+
}
|
173
|
+
if redirect
|
174
|
+
if redirect.relative?
|
175
|
+
# Although it violates RFC2616, Location: field may have relative
|
176
|
+
# URI. It is converted to absolute URI using uri as a base URI.
|
177
|
+
redirect = uri + redirect
|
178
|
+
end
|
179
|
+
unless OpenURI.redirectable?(uri, redirect)
|
180
|
+
raise "redirection forbidden: #{uri} -> #{redirect}"
|
181
|
+
end
|
182
|
+
uri = redirect
|
183
|
+
raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s
|
184
|
+
uri_set[uri.to_s] = true
|
185
|
+
else
|
186
|
+
break
|
187
|
+
end
|
188
|
+
end
|
189
|
+
io = buf.io
|
190
|
+
io.base_uri = uri
|
191
|
+
io
|
192
|
+
end
|
193
|
+
|
194
|
+
def OpenURI.redirectable?(uri1, uri2) # :nodoc:
|
195
|
+
# This test is intended to forbid a redirection from http://... to
|
196
|
+
# file:///etc/passwd.
|
197
|
+
# However this is ad hoc. It should be extensible/configurable.
|
198
|
+
uri1.scheme.downcase == uri2.scheme.downcase ||
|
199
|
+
(/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme)
|
200
|
+
end
|
201
|
+
|
202
|
+
class HTTPError < StandardError
|
203
|
+
def initialize(message, io)
|
204
|
+
super(message)
|
205
|
+
@io = io
|
206
|
+
end
|
207
|
+
attr_reader :io
|
208
|
+
end
|
209
|
+
|
210
|
+
class Buffer # :nodoc:
|
211
|
+
def initialize
|
212
|
+
@io = StringIO.new
|
213
|
+
@size = 0
|
214
|
+
end
|
215
|
+
attr_reader :size
|
216
|
+
|
217
|
+
StringMax = 10240
|
218
|
+
def <<(str)
|
219
|
+
@io << str
|
220
|
+
@size += str.length
|
221
|
+
if StringIO === @io && StringMax < @size
|
222
|
+
require 'tempfile'
|
223
|
+
io = Tempfile.new('open-uri')
|
224
|
+
io.binmode
|
225
|
+
Meta.init io, @io if Meta === @io
|
226
|
+
io << @io.string
|
227
|
+
@io = io
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def io
|
232
|
+
Meta.init @io unless Meta === @io
|
233
|
+
@io
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Mixin for holding meta-information.
|
238
|
+
module Meta
|
239
|
+
def Meta.init(obj, src=nil) # :nodoc:
|
240
|
+
obj.extend Meta
|
241
|
+
obj.instance_eval {
|
242
|
+
@base_uri = nil
|
243
|
+
@meta = {}
|
244
|
+
}
|
245
|
+
if src
|
246
|
+
obj.status = src.status
|
247
|
+
obj.base_uri = src.base_uri
|
248
|
+
src.meta.each {|name, value|
|
249
|
+
obj.meta_add_field(name, value)
|
250
|
+
}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# returns an Array which consists status code and message.
|
255
|
+
attr_accessor :status
|
256
|
+
|
257
|
+
# returns a URI which is base of relative URIs in the data.
|
258
|
+
# It may differ from the URI supplied by a user because redirection.
|
259
|
+
attr_accessor :base_uri
|
260
|
+
|
261
|
+
# returns a Hash which represents header fields.
|
262
|
+
# The Hash keys are downcased for canonicalization.
|
263
|
+
attr_reader :meta
|
264
|
+
|
265
|
+
def meta_add_field(name, value) # :nodoc:
|
266
|
+
@meta[name.downcase] = value
|
267
|
+
end
|
268
|
+
|
269
|
+
# returns a Time which represents Last-Modified field.
|
270
|
+
def last_modified
|
271
|
+
if v = @meta['last-modified']
|
272
|
+
Time.httpdate(v)
|
273
|
+
else
|
274
|
+
nil
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
RE_LWS = /[\r\n\t ]+/n
|
279
|
+
RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n
|
280
|
+
RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])"}n
|
281
|
+
RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n
|
282
|
+
|
283
|
+
def content_type_parse # :nodoc:
|
284
|
+
v = @meta['content-type']
|
285
|
+
# The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045.
|
286
|
+
if v && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ v
|
287
|
+
type = $1.downcase
|
288
|
+
subtype = $2.downcase
|
289
|
+
parameters = []
|
290
|
+
$3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval|
|
291
|
+
val = qval.gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/) { $1 ? $1[1,1] : $& } if qval
|
292
|
+
parameters << [att.downcase, val]
|
293
|
+
}
|
294
|
+
["#{type}/#{subtype}", *parameters]
|
295
|
+
else
|
296
|
+
nil
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# returns "type/subtype" which is MIME Content-Type.
|
301
|
+
# It is downcased for canonicalization.
|
302
|
+
# Content-Type parameters are stripped.
|
303
|
+
def content_type
|
304
|
+
type, *parameters = content_type_parse
|
305
|
+
type || 'application/octet-stream'
|
306
|
+
end
|
307
|
+
|
308
|
+
# returns a charset parameter in Content-Type field.
|
309
|
+
# It is downcased for canonicalization.
|
310
|
+
#
|
311
|
+
# If charset parameter is not given but a block is given,
|
312
|
+
# the block is called and its result is returned.
|
313
|
+
# It can be used to guess charset.
|
314
|
+
#
|
315
|
+
# If charset parameter and block is not given,
|
316
|
+
# nil is returned except text type in HTTP.
|
317
|
+
# In that case, "iso-8859-1" is returned as defined by RFC2616 3.7.1.
|
318
|
+
def charset
|
319
|
+
type, *parameters = content_type_parse
|
320
|
+
if pair = parameters.assoc('charset')
|
321
|
+
pair.last.downcase
|
322
|
+
elsif block_given?
|
323
|
+
yield
|
324
|
+
elsif type && %r{\Atext/} =~ type &&
|
325
|
+
@base_uri && /\Ahttp\z/i =~ @base_uri.scheme
|
326
|
+
"iso-8859-1" # RFC2616 3.7.1
|
327
|
+
else
|
328
|
+
nil
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# returns a list of encodings in Content-Encoding field
|
333
|
+
# as an Array of String.
|
334
|
+
# The encodings are downcased for canonicalization.
|
335
|
+
def content_encoding
|
336
|
+
v = @meta['content-encoding']
|
337
|
+
if v && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ v
|
338
|
+
v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase}
|
339
|
+
else
|
340
|
+
[]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Mixin for HTTP and FTP URIs.
|
346
|
+
module OpenRead
|
347
|
+
# OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP.
|
348
|
+
#
|
349
|
+
# OpenURI::OpenRead#open takes optional 3 arguments as:
|
350
|
+
# OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }]
|
351
|
+
#
|
352
|
+
# `mode', `perm' is same as Kernel#open.
|
353
|
+
#
|
354
|
+
# However, `mode' must be read mode because OpenURI::OpenRead#open doesn't
|
355
|
+
# support write mode (yet).
|
356
|
+
# Also `perm' is just ignored because it is meaningful only for file
|
357
|
+
# creation.
|
358
|
+
#
|
359
|
+
# `options' must be a hash.
|
360
|
+
#
|
361
|
+
# Each pairs which key is a string in the hash specify a extra header
|
362
|
+
# field for HTTP.
|
363
|
+
# I.e. it is ignored for FTP without HTTP proxy.
|
364
|
+
#
|
365
|
+
# The hash may include other option which key is a symbol:
|
366
|
+
#
|
367
|
+
# :proxy => "http://proxy.foo.com:8000/"
|
368
|
+
# :proxy => URI.parse("http://proxy.foo.com:8000/")
|
369
|
+
# :proxy => true
|
370
|
+
# :proxy => false
|
371
|
+
# :proxy => nil
|
372
|
+
#
|
373
|
+
# If :proxy option is specified, the value should be String, URI,
|
374
|
+
# boolean or nil.
|
375
|
+
# When String or URI is given, it is treated as proxy URI.
|
376
|
+
# When true is given or the option itself is not specified,
|
377
|
+
# environment variable `scheme_proxy'(or `SCHEME_PROXY') is examined.
|
378
|
+
# `scheme' is replaced by `http' or `ftp'.
|
379
|
+
# When false or nil is given, the environment variables are ignored and
|
380
|
+
# connection will be made to a server directly.
|
381
|
+
#
|
382
|
+
# :content_length_proc => lambda {|content_length| ... }
|
383
|
+
#
|
384
|
+
# If :content_length_proc option is specified, the option value procedure
|
385
|
+
# is called before actual transfer is started.
|
386
|
+
# It takes one argument which is expected content length in bytes.
|
387
|
+
#
|
388
|
+
# If two or more transfer is done by HTTP redirection, the procedure
|
389
|
+
# is called only one for a last transfer.
|
390
|
+
#
|
391
|
+
# When expected content length is unknown, the procedure is called with
|
392
|
+
# nil.
|
393
|
+
# It is happen when HTTP response has no Content-Length header.
|
394
|
+
#
|
395
|
+
# :progress_proc => lambda {|size| ...}
|
396
|
+
#
|
397
|
+
# If :progress_proc option is specified, the proc is called with one
|
398
|
+
# argument each time when `open' gets content fragment from network.
|
399
|
+
# The argument `size' `size' is a accumulated transfered size in bytes.
|
400
|
+
#
|
401
|
+
# If two or more transfer is done by HTTP redirection, the procedure
|
402
|
+
# is called only one for a last transfer.
|
403
|
+
#
|
404
|
+
# :progress_proc and :content_length_proc are intended to be used for
|
405
|
+
# progress bar.
|
406
|
+
# For example, it can be implemented as follows using Ruby/ProgressBar.
|
407
|
+
#
|
408
|
+
# pbar = nil
|
409
|
+
# open("http://...",
|
410
|
+
# :content_length_proc => lambda {|t|
|
411
|
+
# if t && 0 < t
|
412
|
+
# pbar = ProgressBar.new("...", t)
|
413
|
+
# pbar.file_transfer_mode
|
414
|
+
# end
|
415
|
+
# },
|
416
|
+
# :progress_proc => lambda {|s|
|
417
|
+
# pbar.set s if pbar
|
418
|
+
# }) {|f| ... }
|
419
|
+
#
|
420
|
+
# OpenURI::OpenRead#open returns an IO like object if block is not given.
|
421
|
+
# Otherwise it yields the IO object and return the value of the block.
|
422
|
+
# The IO object is extended with OpenURI::Meta.
|
423
|
+
def open(*rest, &block)
|
424
|
+
OpenURI.open_uri(self, *rest, &block)
|
425
|
+
end
|
426
|
+
|
427
|
+
# OpenURI::OpenRead#read([options]) reads a content referenced by self and
|
428
|
+
# returns the content as string.
|
429
|
+
# The string is extended with OpenURI::Meta.
|
430
|
+
# The argument `options' is same as OpenURI::OpenRead#open.
|
431
|
+
def read(options={})
|
432
|
+
self.open(options) {|f|
|
433
|
+
str = f.read
|
434
|
+
Meta.init str, f
|
435
|
+
str
|
436
|
+
}
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
module URI
|
442
|
+
class Generic
|
443
|
+
# returns a proxy URI.
|
444
|
+
# The proxy URI is obtained from environment variables such as http_proxy,
|
445
|
+
# ftp_proxy, no_proxy, etc.
|
446
|
+
# If there is no proper proxy, nil is returned.
|
447
|
+
#
|
448
|
+
# Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.)
|
449
|
+
# are examined too.
|
450
|
+
#
|
451
|
+
# But http_proxy and HTTP_PROXY is treated specially under CGI environment.
|
452
|
+
# It's because HTTP_PROXY may be set by Proxy: header.
|
453
|
+
# So HTTP_PROXY is not used.
|
454
|
+
# http_proxy is not used too if the variable is case insensitive.
|
455
|
+
# CGI_HTTP_PROXY can be used instead.
|
456
|
+
def find_proxy
|
457
|
+
name = self.scheme.downcase + '_proxy'
|
458
|
+
proxy_uri = nil
|
459
|
+
if name == 'http_proxy' && ENV.include?('REQUEST_METHOD') # CGI?
|
460
|
+
# HTTP_PROXY conflicts with *_proxy for proxy settings and
|
461
|
+
# HTTP_* for header information in CGI.
|
462
|
+
# So it should be careful to use it.
|
463
|
+
pairs = ENV.reject {|k, v| /\Ahttp_proxy\z/i !~ k }
|
464
|
+
case pairs.length
|
465
|
+
when 0 # no proxy setting anyway.
|
466
|
+
proxy_uri = nil
|
467
|
+
when 1
|
468
|
+
k, v = pairs.shift
|
469
|
+
if k == 'http_proxy' && ENV[k.upcase] == nil
|
470
|
+
# http_proxy is safe to use because ENV is case sensitive.
|
471
|
+
proxy_uri = ENV[name]
|
472
|
+
else
|
473
|
+
proxy_uri = nil
|
474
|
+
end
|
475
|
+
else # http_proxy is safe to use because ENV is case sensitive.
|
476
|
+
proxy_uri = ENV[name]
|
477
|
+
end
|
478
|
+
if !proxy_uri
|
479
|
+
# Use CGI_HTTP_PROXY. cf. libwww-perl.
|
480
|
+
proxy_uri = ENV["CGI_#{name.upcase}"]
|
481
|
+
end
|
482
|
+
elsif name == 'http_proxy'
|
483
|
+
unless proxy_uri = ENV[name]
|
484
|
+
if proxy_uri = ENV[name.upcase]
|
485
|
+
warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.'
|
486
|
+
end
|
487
|
+
end
|
488
|
+
else
|
489
|
+
proxy_uri = ENV[name] || ENV[name.upcase]
|
490
|
+
end
|
491
|
+
|
492
|
+
if proxy_uri && self.host
|
493
|
+
require 'socket'
|
494
|
+
begin
|
495
|
+
addr = IPSocket.getaddress(self.host)
|
496
|
+
proxy_uri = nil if /\A127\.|\A::1\z/ =~ addr
|
497
|
+
rescue SocketError
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
if proxy_uri
|
502
|
+
proxy_uri = URI.parse(proxy_uri)
|
503
|
+
unless URI::HTTP === proxy_uri
|
504
|
+
raise "Non-HTTP proxy URI: #{proxy_uri}"
|
505
|
+
end
|
506
|
+
name = 'no_proxy'
|
507
|
+
if no_proxy = ENV[name] || ENV[name.upcase]
|
508
|
+
no_proxy.scan(/([^:,]*)(?::(\d+))?/) {|host, port|
|
509
|
+
if /(\A|\.)#{Regexp.quote host}\z/i =~ self.host &&
|
510
|
+
(!port || self.port == port.to_i)
|
511
|
+
proxy_uri = nil
|
512
|
+
break
|
513
|
+
end
|
514
|
+
}
|
515
|
+
end
|
516
|
+
proxy_uri
|
517
|
+
else
|
518
|
+
nil
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
class HTTP
|
524
|
+
def direct_open(buf, options) # :nodoc:
|
525
|
+
proxy_open(buf, request_uri, options)
|
526
|
+
end
|
527
|
+
|
528
|
+
def proxy_open(buf, uri, options) # :nodoc:
|
529
|
+
header = {}
|
530
|
+
options.each {|k, v| header[k] = v if String === k }
|
531
|
+
|
532
|
+
if uri.respond_to? :host
|
533
|
+
# According to RFC2616 14.23, Host: request-header field should be set
|
534
|
+
# an origin server.
|
535
|
+
# But net/http wrongly set a proxy server if an absolute URI is
|
536
|
+
# specified as a request URI.
|
537
|
+
# So open-uri override it here explicitly.
|
538
|
+
header['host'] = uri.host
|
539
|
+
header['host'] += ":#{uri.port}" if uri.port
|
540
|
+
end
|
541
|
+
|
542
|
+
require 'net/http'
|
543
|
+
resp = nil
|
544
|
+
Net::HTTP.start(self.host, self.port) {|http|
|
545
|
+
http.request_get(uri.to_s, header) {|response|
|
546
|
+
resp = response
|
547
|
+
if options[:content_length_proc] && Net::HTTPSuccess === resp
|
548
|
+
if resp.key?('Content-Length')
|
549
|
+
options[:content_length_proc].call(resp['Content-Length'].to_i)
|
550
|
+
else
|
551
|
+
options[:content_length_proc].call(nil)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
resp.read_body {|str|
|
555
|
+
buf << str
|
556
|
+
if options[:progress_proc] && Net::HTTPSuccess === resp
|
557
|
+
options[:progress_proc].call(buf.size)
|
558
|
+
end
|
559
|
+
}
|
560
|
+
}
|
561
|
+
}
|
562
|
+
io = buf.io
|
563
|
+
io.rewind
|
564
|
+
io.status = [resp.code, resp.message]
|
565
|
+
resp.each {|name,value| buf.io.meta_add_field name, value }
|
566
|
+
case resp
|
567
|
+
when Net::HTTPSuccess
|
568
|
+
when Net::HTTPMovedPermanently, # 301
|
569
|
+
Net::HTTPFound, # 302
|
570
|
+
Net::HTTPSeeOther, # 303
|
571
|
+
Net::HTTPTemporaryRedirect # 307
|
572
|
+
throw :open_uri_redirect, URI.parse(resp['location'])
|
573
|
+
else
|
574
|
+
raise OpenURI::HTTPError.new(io.status.join(' '), io)
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
include OpenURI::OpenRead
|
579
|
+
end
|
580
|
+
|
581
|
+
class FTP
|
582
|
+
def direct_open(buf, options) # :nodoc:
|
583
|
+
require 'net/ftp'
|
584
|
+
# todo: extract user/passwd from .netrc.
|
585
|
+
user = 'anonymous'
|
586
|
+
passwd = nil
|
587
|
+
user, passwd = self.userinfo.split(/:/) if self.userinfo
|
588
|
+
|
589
|
+
ftp = Net::FTP.open(self.host)
|
590
|
+
ftp.login(user, passwd)
|
591
|
+
if options[:content_length_proc]
|
592
|
+
options[:content_length_proc].call(ftp.size(self.path))
|
593
|
+
end
|
594
|
+
ftp.getbinaryfile(self.path, '/dev/null', Net::FTP::DEFAULT_BLOCKSIZE) {|str|
|
595
|
+
buf << str
|
596
|
+
options[:progress_proc].call(buf.size) if options[:progress_proc]
|
597
|
+
}
|
598
|
+
ftp.close
|
599
|
+
buf.io.rewind
|
600
|
+
end
|
601
|
+
|
602
|
+
include OpenURI::OpenRead
|
603
|
+
end
|
604
|
+
end
|