httpclient 2.1.5 → 2.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.
- checksums.yaml +7 -0
- data/README.md +85 -0
- data/bin/httpclient +77 -0
- data/bin/jsonclient +85 -0
- data/lib/hexdump.rb +50 -0
- data/lib/http-access2.rb +6 -4
- data/lib/httpclient/auth.rb +575 -173
- data/lib/httpclient/cacert.pem +3952 -0
- data/lib/httpclient/cacert1024.pem +3866 -0
- data/lib/httpclient/connection.rb +6 -2
- data/lib/httpclient/cookie.rb +162 -504
- data/lib/httpclient/http.rb +334 -119
- data/lib/httpclient/include_client.rb +85 -0
- data/lib/httpclient/jruby_ssl_socket.rb +588 -0
- data/lib/httpclient/session.rb +385 -288
- data/lib/httpclient/ssl_config.rb +195 -155
- data/lib/httpclient/ssl_socket.rb +150 -0
- data/lib/httpclient/timeout.rb +14 -10
- data/lib/httpclient/util.rb +142 -6
- data/lib/httpclient/version.rb +3 -0
- data/lib/httpclient/webagent-cookie.rb +459 -0
- data/lib/httpclient.rb +509 -202
- data/lib/jsonclient.rb +63 -0
- data/lib/oauthclient.rb +111 -0
- data/sample/async.rb +8 -0
- data/sample/auth.rb +11 -0
- data/sample/cookie.rb +18 -0
- data/sample/dav.rb +103 -0
- data/sample/howto.rb +49 -0
- data/sample/jsonclient.rb +67 -0
- data/sample/oauth_buzz.rb +57 -0
- data/sample/oauth_friendfeed.rb +59 -0
- data/sample/oauth_twitter.rb +61 -0
- data/sample/ssl/0cert.pem +22 -0
- data/sample/ssl/0key.pem +30 -0
- data/sample/ssl/1000cert.pem +19 -0
- data/sample/ssl/1000key.pem +18 -0
- data/sample/ssl/htdocs/index.html +10 -0
- data/sample/ssl/ssl_client.rb +22 -0
- data/sample/ssl/webrick_httpsd.rb +29 -0
- data/sample/stream.rb +21 -0
- data/sample/thread.rb +27 -0
- data/sample/wcat.rb +21 -0
- data/test/ca-chain.pem +44 -0
- data/test/ca.cert +23 -0
- data/test/client-pass.key +18 -0
- data/test/client.cert +19 -0
- data/test/client.key +15 -0
- data/test/helper.rb +131 -0
- data/test/htdigest +1 -0
- data/test/htpasswd +2 -0
- data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
- data/test/runner.rb +2 -0
- data/test/server.cert +19 -0
- data/test/server.key +15 -0
- data/test/sslsvr.rb +65 -0
- data/test/subca.cert +21 -0
- data/test/test_auth.rb +492 -0
- data/test/test_cookie.rb +309 -0
- data/test/test_hexdump.rb +14 -0
- data/test/test_http-access2.rb +508 -0
- data/test/test_httpclient.rb +2145 -0
- data/test/test_include_client.rb +52 -0
- data/test/test_jsonclient.rb +80 -0
- data/test/test_ssl.rb +559 -0
- data/test/test_webagent-cookie.rb +465 -0
- metadata +85 -44
- data/lib/httpclient/auth.rb.orig +0 -513
- data/lib/httpclient/cacert.p7s +0 -1579
- data/lib/httpclient.rb.orig +0 -1020
- data/lib/tags +0 -908
data/lib/httpclient/timeout.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# HTTPClient - HTTP client library.
|
2
|
-
# Copyright (C) 2000-
|
2
|
+
# Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
3
|
#
|
4
4
|
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
5
|
# redistribute it and/or modify it under the same terms of Ruby's license;
|
@@ -23,6 +23,7 @@ class HTTPClient
|
|
23
23
|
# timeout scheduler.
|
24
24
|
# * Do not wakeup the scheduler thread so often. Let scheduler thread sleep
|
25
25
|
# until the nearest period.
|
26
|
+
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
|
26
27
|
class TimeoutScheduler
|
27
28
|
|
28
29
|
# Represents timeout period.
|
@@ -117,17 +118,20 @@ class HTTPClient
|
|
117
118
|
end
|
118
119
|
end
|
119
120
|
timeout_scheduler # initialize at first time.
|
121
|
+
end
|
120
122
|
|
121
123
|
module Timeout
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
124
|
+
if !defined?(JRUBY_VERSION) and RUBY_VERSION < '1.9'
|
125
|
+
def timeout(sec, ex = nil, &block)
|
126
|
+
return yield if sec == nil or sec.zero?
|
127
|
+
scheduler = nil
|
128
|
+
begin
|
129
|
+
scheduler = HTTPClient.timeout_scheduler
|
130
|
+
period = scheduler.register(Thread.current, sec, ex)
|
131
|
+
yield(sec)
|
132
|
+
ensure
|
133
|
+
scheduler.cancel(period) if scheduler and period
|
134
|
+
end
|
131
135
|
end
|
132
136
|
end
|
133
137
|
end
|
data/lib/httpclient/util.rb
CHANGED
@@ -1,12 +1,49 @@
|
|
1
1
|
# HTTPClient - HTTP client library.
|
2
|
-
# Copyright (C) 2000-
|
2
|
+
# Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
|
3
3
|
#
|
4
4
|
# This program is copyrighted free software by NAKAMURA, Hiroshi. You can
|
5
5
|
# redistribute it and/or modify it under the same terms of Ruby's license;
|
6
6
|
# either the dual license version in 2003, or any later version.
|
7
7
|
|
8
8
|
|
9
|
-
|
9
|
+
unless ''.respond_to?(:bytesize)
|
10
|
+
class String
|
11
|
+
alias bytesize size
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if RUBY_VERSION < "1.9.3"
|
16
|
+
require 'uri'
|
17
|
+
module URI
|
18
|
+
class Generic
|
19
|
+
def hostname
|
20
|
+
v = self.host
|
21
|
+
/\A\[(.*)\]\z/ =~ v ? $1 : v
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# With recent JRuby 1.7 + jruby-openssl, X509CRL#extentions_to_text causes
|
28
|
+
# StringIndexOOBException when we try to dump SSL Server Certificate.
|
29
|
+
# when one of extensions has "" as value.
|
30
|
+
if defined?(JRUBY_VERSION)
|
31
|
+
require 'openssl'
|
32
|
+
require 'java'
|
33
|
+
module OpenSSL
|
34
|
+
module X509
|
35
|
+
class Certificate
|
36
|
+
java_import 'java.security.cert.Certificate'
|
37
|
+
java_import 'java.security.cert.CertificateFactory'
|
38
|
+
java_import 'java.io.ByteArrayInputStream'
|
39
|
+
def to_text
|
40
|
+
cf = CertificateFactory.getInstance('X.509')
|
41
|
+
cf.generateCertificate(ByteArrayInputStream.new(self.to_der.to_java_bytes)).toString
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
10
47
|
|
11
48
|
|
12
49
|
class HTTPClient
|
@@ -14,6 +51,47 @@ class HTTPClient
|
|
14
51
|
|
15
52
|
# A module for common function.
|
16
53
|
module Util
|
54
|
+
|
55
|
+
# URI abstraction; Addressable::URI or URI
|
56
|
+
require 'uri'
|
57
|
+
begin
|
58
|
+
require 'addressable/uri'
|
59
|
+
# Older versions doesn't have #default_port
|
60
|
+
unless Addressable::URI.instance_methods.include?(:default_port) # 1.9 only
|
61
|
+
raise LoadError
|
62
|
+
end
|
63
|
+
class AddressableURI < Addressable::URI
|
64
|
+
# Overwrites the original definition just for one line...
|
65
|
+
def authority
|
66
|
+
self.host && @authority ||= (begin
|
67
|
+
authority = ""
|
68
|
+
if self.userinfo != nil
|
69
|
+
authority << "#{self.userinfo}@"
|
70
|
+
end
|
71
|
+
authority << self.host
|
72
|
+
if self.port != self.default_port # ...HERE! Compares with default_port because self.port is not nil in this wrapper.
|
73
|
+
authority << ":#{self.port}"
|
74
|
+
end
|
75
|
+
authority
|
76
|
+
end)
|
77
|
+
end
|
78
|
+
|
79
|
+
# HTTPClient expects urify("http://foo/").port to be not nil but 80 like URI.
|
80
|
+
def port
|
81
|
+
super || default_port
|
82
|
+
end
|
83
|
+
|
84
|
+
# Captured from uri/generic.rb
|
85
|
+
def hostname
|
86
|
+
v = self.host
|
87
|
+
/\A\[(.*)\]\z/ =~ v ? $1 : v
|
88
|
+
end
|
89
|
+
end
|
90
|
+
AddressableEnabled = true
|
91
|
+
rescue LoadError
|
92
|
+
AddressableEnabled = false
|
93
|
+
end
|
94
|
+
|
17
95
|
# Keyword argument helper.
|
18
96
|
# args:: given arguments.
|
19
97
|
# *field:: a list of arguments to be extracted.
|
@@ -35,11 +113,35 @@ class HTTPClient
|
|
35
113
|
# end
|
36
114
|
#
|
37
115
|
def keyword_argument(args, *field)
|
38
|
-
if args.size == 1 and args[0]
|
39
|
-
args[0]
|
40
|
-
|
41
|
-
|
116
|
+
if args.size == 1 and Hash === args[0]
|
117
|
+
h = args[0]
|
118
|
+
if field.any? { |f| h.key?(f) }
|
119
|
+
return h.values_at(*field)
|
120
|
+
end
|
42
121
|
end
|
122
|
+
args
|
123
|
+
end
|
124
|
+
|
125
|
+
# Keyword argument to hash helper.
|
126
|
+
# args:: given arguments.
|
127
|
+
# *field:: a list of arguments to be extracted.
|
128
|
+
#
|
129
|
+
# Returns hash which has defined keys. When a Hash given, returns it
|
130
|
+
# including undefined keys. When an Array given, returns a Hash which only
|
131
|
+
# includes defined keys.
|
132
|
+
def argument_to_hash(args, *field)
|
133
|
+
return nil if args.empty?
|
134
|
+
if args.size == 1 and Hash === args[0]
|
135
|
+
h = args[0]
|
136
|
+
if field.any? { |f| h.key?(f) }
|
137
|
+
return h
|
138
|
+
end
|
139
|
+
end
|
140
|
+
h = {}
|
141
|
+
field.each_with_index do |e, idx|
|
142
|
+
h[e] = args[idx]
|
143
|
+
end
|
144
|
+
h
|
43
145
|
end
|
44
146
|
|
45
147
|
# Gets an URI instance.
|
@@ -48,10 +150,13 @@ class HTTPClient
|
|
48
150
|
nil
|
49
151
|
elsif uri.is_a?(URI)
|
50
152
|
uri
|
153
|
+
elsif AddressableEnabled
|
154
|
+
AddressableURI.parse(uri.to_s)
|
51
155
|
else
|
52
156
|
URI.parse(uri.to_s)
|
53
157
|
end
|
54
158
|
end
|
159
|
+
module_function :urify
|
55
160
|
|
56
161
|
# Returns true if the given 2 URIs have a part_of relationship.
|
57
162
|
# * the same scheme
|
@@ -80,6 +185,37 @@ class HTTPClient
|
|
80
185
|
v ? v[1] : nil
|
81
186
|
end
|
82
187
|
module_function :hash_find_value
|
188
|
+
|
189
|
+
# Try to require a feature and returns true/false if loaded
|
190
|
+
#
|
191
|
+
# It returns 'true' for the second require in contrast of the standard
|
192
|
+
# require returns false if the feature is already loaded.
|
193
|
+
def try_require(feature)
|
194
|
+
require feature
|
195
|
+
true
|
196
|
+
rescue LoadError
|
197
|
+
false
|
198
|
+
end
|
199
|
+
module_function :try_require
|
200
|
+
|
201
|
+
# show one warning message only once by caching message
|
202
|
+
#
|
203
|
+
# it cached all messages in memory so be careful not to show many kinds of warning message.
|
204
|
+
@@__warned = {}
|
205
|
+
def warning(message)
|
206
|
+
return if @@__warned.key?(message)
|
207
|
+
warn(message)
|
208
|
+
@@__warned[message] = true
|
209
|
+
end
|
210
|
+
|
211
|
+
# Checks if the given URI is https.
|
212
|
+
def https?(uri)
|
213
|
+
uri.scheme && uri.scheme.downcase == 'https'
|
214
|
+
end
|
215
|
+
|
216
|
+
def http?(uri)
|
217
|
+
uri.scheme && uri.scheme.downcase == 'http'
|
218
|
+
end
|
83
219
|
end
|
84
220
|
|
85
221
|
|
@@ -0,0 +1,459 @@
|
|
1
|
+
# cookie.rb is redistributed file which is originally included in Webagent
|
2
|
+
# version 0.6.2 by TAKAHASHI `Maki' Masayoshi. And it contains some bug fixes.
|
3
|
+
# You can download the entire package of Webagent from
|
4
|
+
# http://www.rubycolor.org/arc/.
|
5
|
+
|
6
|
+
|
7
|
+
# Cookie class
|
8
|
+
#
|
9
|
+
# I refered to w3m's source to make these classes. Some comments
|
10
|
+
# are quoted from it. I'm thanksful for author(s) of it.
|
11
|
+
#
|
12
|
+
# w3m homepage: http://ei5nazha.yz.yamagata-u.ac.jp/~aito/w3m/eng/
|
13
|
+
|
14
|
+
require 'time'
|
15
|
+
require 'monitor'
|
16
|
+
require 'httpclient/util'
|
17
|
+
|
18
|
+
class WebAgent
|
19
|
+
|
20
|
+
module CookieUtils
|
21
|
+
|
22
|
+
def head_match?(str1, str2)
|
23
|
+
str1 == str2[0, str1.length]
|
24
|
+
end
|
25
|
+
|
26
|
+
def tail_match?(str1, str2)
|
27
|
+
if str1.length > 0
|
28
|
+
str1 == str2[-str1.length..-1].to_s
|
29
|
+
else
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def domain_match(host, domain)
|
35
|
+
return false if domain.nil?
|
36
|
+
domainname = domain.sub(/\.\z/, '').downcase
|
37
|
+
hostname = host.sub(/\.\z/, '').downcase
|
38
|
+
case domain
|
39
|
+
when /\d+\.\d+\.\d+\.\d+/
|
40
|
+
return (hostname == domainname)
|
41
|
+
when '.'
|
42
|
+
return true
|
43
|
+
when /^\./
|
44
|
+
# allows; host == rubyforge.org, domain == .rubyforge.org
|
45
|
+
return tail_match?(domainname, '.' + hostname)
|
46
|
+
else
|
47
|
+
return (hostname == domainname)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Cookie
|
53
|
+
include CookieUtils
|
54
|
+
|
55
|
+
attr_accessor :name, :value
|
56
|
+
attr_accessor :domain, :path
|
57
|
+
attr_accessor :expires ## for Netscape Cookie
|
58
|
+
attr_accessor :url
|
59
|
+
attr_writer :use, :secure, :http_only, :discard, :domain_orig, :path_orig, :override
|
60
|
+
|
61
|
+
USE = 1
|
62
|
+
SECURE = 2
|
63
|
+
DOMAIN = 4
|
64
|
+
PATH = 8
|
65
|
+
DISCARD = 16
|
66
|
+
OVERRIDE = 32
|
67
|
+
OVERRIDE_OK = 32
|
68
|
+
HTTP_ONLY = 64
|
69
|
+
|
70
|
+
def self.parse(str, url)
|
71
|
+
cookie = new
|
72
|
+
cookie.parse(str, url)
|
73
|
+
cookie
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@name = @value = @domain = @path = nil
|
78
|
+
@expires = nil
|
79
|
+
@url = nil
|
80
|
+
@use = @secure = @http_only = @discard = @domain_orig = @path_orig = @override = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def discard?
|
84
|
+
@discard
|
85
|
+
end
|
86
|
+
|
87
|
+
def use?
|
88
|
+
@use
|
89
|
+
end
|
90
|
+
|
91
|
+
def secure?
|
92
|
+
@secure
|
93
|
+
end
|
94
|
+
|
95
|
+
def http_only?
|
96
|
+
@http_only
|
97
|
+
end
|
98
|
+
|
99
|
+
def domain_orig?
|
100
|
+
@domain_orig
|
101
|
+
end
|
102
|
+
|
103
|
+
def path_orig?
|
104
|
+
@path_orig
|
105
|
+
end
|
106
|
+
|
107
|
+
def override?
|
108
|
+
@override
|
109
|
+
end
|
110
|
+
|
111
|
+
def flag
|
112
|
+
flg = 0
|
113
|
+
flg += USE if @use
|
114
|
+
flg += SECURE if @secure
|
115
|
+
flg += HTTP_ONLY if @http_only
|
116
|
+
flg += DOMAIN if @domain_orig
|
117
|
+
flg += PATH if @path_orig
|
118
|
+
flg += DISCARD if @discard
|
119
|
+
flg += OVERRIDE if @override
|
120
|
+
flg
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_flag(flag)
|
124
|
+
flag = flag.to_i
|
125
|
+
@use = true if flag & USE > 0
|
126
|
+
@secure = true if flag & SECURE > 0
|
127
|
+
@http_only = true if flag & HTTP_ONLY > 0
|
128
|
+
@domain_orig = true if flag & DOMAIN > 0
|
129
|
+
@path_orig = true if flag & PATH > 0
|
130
|
+
@discard = true if flag & DISCARD > 0
|
131
|
+
@override = true if flag & OVERRIDE > 0
|
132
|
+
end
|
133
|
+
|
134
|
+
def match?(url)
|
135
|
+
domainname = url.host
|
136
|
+
if (!domainname ||
|
137
|
+
!domain_match(domainname, @domain) ||
|
138
|
+
(@path && !head_match?(@path, url.path.empty? ? '/' : url.path)) ||
|
139
|
+
(@secure && (url.scheme != 'https')) )
|
140
|
+
return false
|
141
|
+
else
|
142
|
+
return true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def join_quotedstr(array, sep)
|
147
|
+
ret = Array.new
|
148
|
+
old_elem = nil
|
149
|
+
array.each{|elem|
|
150
|
+
if (elem.scan(/"/).length % 2) == 0
|
151
|
+
if old_elem
|
152
|
+
old_elem << sep << elem
|
153
|
+
else
|
154
|
+
ret << elem
|
155
|
+
old_elem = nil
|
156
|
+
end
|
157
|
+
else
|
158
|
+
if old_elem
|
159
|
+
old_elem << sep << elem
|
160
|
+
ret << old_elem
|
161
|
+
old_elem = nil
|
162
|
+
else
|
163
|
+
old_elem = elem.dup
|
164
|
+
end
|
165
|
+
end
|
166
|
+
}
|
167
|
+
ret
|
168
|
+
end
|
169
|
+
|
170
|
+
def parse(str, url)
|
171
|
+
@url = url
|
172
|
+
# TODO: should not depend on join_quotedstr. scan with escape like CSV.
|
173
|
+
cookie_elem = str.split(/;/)
|
174
|
+
cookie_elem = join_quotedstr(cookie_elem, ';')
|
175
|
+
cookie_elem -= [""] # del empty elements, a cookie might included ";;"
|
176
|
+
first_elem = cookie_elem.shift
|
177
|
+
if first_elem !~ /([^=]*)(\=(.*))?/
|
178
|
+
return
|
179
|
+
## raise ArgumentError 'invalid cookie value'
|
180
|
+
end
|
181
|
+
@name = $1.strip
|
182
|
+
@value = normalize_cookie_value($3)
|
183
|
+
cookie_elem.each{|pair|
|
184
|
+
key, value = pair.split(/=/, 2) ## value may nil
|
185
|
+
key.strip!
|
186
|
+
value = normalize_cookie_value(value)
|
187
|
+
case key.downcase
|
188
|
+
when 'domain'
|
189
|
+
@domain = value
|
190
|
+
when 'expires'
|
191
|
+
@expires = nil
|
192
|
+
begin
|
193
|
+
@expires = Time.parse(value).gmtime if value
|
194
|
+
rescue ArgumentError
|
195
|
+
end
|
196
|
+
when 'path'
|
197
|
+
@path = value
|
198
|
+
when 'secure'
|
199
|
+
@secure = true ## value may nil, but must 'true'.
|
200
|
+
when 'httponly'
|
201
|
+
@http_only = true ## value may nil, but must 'true'.
|
202
|
+
else
|
203
|
+
warn("Unknown key: #{key} = #{value}")
|
204
|
+
end
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def normalize_cookie_value(value)
|
211
|
+
if value
|
212
|
+
value = value.strip.sub(/\A"(.*)"\z/) { $1 }
|
213
|
+
value = nil if value.empty?
|
214
|
+
end
|
215
|
+
value
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# An Array class that already includes the MonitorMixin module.
|
221
|
+
#
|
222
|
+
class SynchronizedArray < Array
|
223
|
+
include MonitorMixin
|
224
|
+
end
|
225
|
+
|
226
|
+
class CookieManager
|
227
|
+
include CookieUtils
|
228
|
+
|
229
|
+
### errors
|
230
|
+
class Error < StandardError; end
|
231
|
+
class ErrorOverrideOK < Error; end
|
232
|
+
class SpecialError < Error; end
|
233
|
+
|
234
|
+
attr_reader :cookies
|
235
|
+
attr_accessor :cookies_file
|
236
|
+
attr_accessor :accept_domains, :reject_domains
|
237
|
+
|
238
|
+
def initialize(file=nil)
|
239
|
+
@cookies = SynchronizedArray.new
|
240
|
+
@cookies_file = file
|
241
|
+
@is_saved = true
|
242
|
+
@reject_domains = Array.new
|
243
|
+
@accept_domains = Array.new
|
244
|
+
@netscape_rule = false
|
245
|
+
end
|
246
|
+
|
247
|
+
def cookies=(cookies)
|
248
|
+
if cookies.is_a?(SynchronizedArray)
|
249
|
+
@cookies = cookies
|
250
|
+
else
|
251
|
+
@cookies = SynchronizedArray.new(cookies)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def save_all_cookies(force = nil, save_unused = true, save_discarded = true)
|
256
|
+
@cookies.synchronize do
|
257
|
+
check_expired_cookies
|
258
|
+
if @is_saved and !force
|
259
|
+
return
|
260
|
+
end
|
261
|
+
File.open(@cookies_file, 'w') do |f|
|
262
|
+
@cookies.each do |cookie|
|
263
|
+
if (cookie.use? or save_unused) and
|
264
|
+
(!cookie.discard? or save_discarded)
|
265
|
+
f.print(cookie.url.to_s,"\t",
|
266
|
+
cookie.name,"\t",
|
267
|
+
cookie.value,"\t",
|
268
|
+
cookie.expires.to_i,"\t",
|
269
|
+
cookie.domain,"\t",
|
270
|
+
cookie.path,"\t",
|
271
|
+
cookie.flag,"\n")
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
@is_saved = true
|
277
|
+
end
|
278
|
+
|
279
|
+
def save_cookies(force = nil)
|
280
|
+
save_all_cookies(force, false, false)
|
281
|
+
end
|
282
|
+
|
283
|
+
def check_expired_cookies
|
284
|
+
@cookies.reject!{|cookie|
|
285
|
+
is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
|
286
|
+
if is_expired && !cookie.discard?
|
287
|
+
@is_saved = false
|
288
|
+
end
|
289
|
+
is_expired
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
def parse(str, url)
|
294
|
+
cookie = WebAgent::Cookie.new
|
295
|
+
cookie.parse(str, url)
|
296
|
+
add(cookie)
|
297
|
+
end
|
298
|
+
|
299
|
+
def find(url)
|
300
|
+
return nil if @cookies.empty?
|
301
|
+
|
302
|
+
cookie_list = Array.new
|
303
|
+
@cookies.each{|cookie|
|
304
|
+
is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
|
305
|
+
if cookie.use? && !is_expired && cookie.match?(url)
|
306
|
+
if cookie_list.select{|c1| c1.name == cookie.name}.empty?
|
307
|
+
cookie_list << cookie
|
308
|
+
end
|
309
|
+
end
|
310
|
+
}
|
311
|
+
return make_cookie_str(cookie_list)
|
312
|
+
end
|
313
|
+
alias cookie_value find
|
314
|
+
|
315
|
+
def add(given)
|
316
|
+
check_domain(given.domain, given.url.host, given.override?)
|
317
|
+
|
318
|
+
domain = given.domain || given.url.host
|
319
|
+
path = given.path || given.url.path.sub(%r|/[^/]*\z|, '')
|
320
|
+
|
321
|
+
cookie = nil
|
322
|
+
@cookies.synchronize do
|
323
|
+
check_expired_cookies
|
324
|
+
cookie = @cookies.find { |c|
|
325
|
+
c.domain == domain && c.path == path && c.name == given.name
|
326
|
+
}
|
327
|
+
if !cookie
|
328
|
+
cookie = WebAgent::Cookie.new
|
329
|
+
cookie.use = true
|
330
|
+
@cookies << cookie
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
cookie.domain = domain
|
335
|
+
cookie.path = path
|
336
|
+
cookie.url = given.url
|
337
|
+
cookie.name = given.name
|
338
|
+
cookie.value = given.value
|
339
|
+
cookie.expires = given.expires
|
340
|
+
cookie.secure = given.secure?
|
341
|
+
cookie.http_only = given.http_only?
|
342
|
+
cookie.domain_orig = given.domain
|
343
|
+
cookie.path_orig = given.path
|
344
|
+
|
345
|
+
if cookie.discard? || cookie.expires.nil?
|
346
|
+
cookie.discard = true
|
347
|
+
else
|
348
|
+
cookie.discard = false
|
349
|
+
@is_saved = false
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def load_cookies
|
354
|
+
return if !File.readable?(@cookies_file)
|
355
|
+
@cookies.synchronize do
|
356
|
+
@cookies.clear
|
357
|
+
File.open(@cookies_file,'r'){|f|
|
358
|
+
while line = f.gets
|
359
|
+
cookie = WebAgent::Cookie.new
|
360
|
+
@cookies << cookie
|
361
|
+
col = line.chomp.split(/\t/)
|
362
|
+
cookie.url = HTTPClient::Util.urify(col[0])
|
363
|
+
cookie.name = col[1]
|
364
|
+
cookie.value = col[2]
|
365
|
+
if col[3].empty? or col[3] == '0'
|
366
|
+
cookie.expires = nil
|
367
|
+
else
|
368
|
+
cookie.expires = Time.at(col[3].to_i).gmtime
|
369
|
+
end
|
370
|
+
cookie.domain = col[4]
|
371
|
+
cookie.path = col[5]
|
372
|
+
cookie.set_flag(col[6])
|
373
|
+
end
|
374
|
+
}
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# Who use it?
|
379
|
+
def check_cookie_accept_domain(domain)
|
380
|
+
unless domain
|
381
|
+
return false
|
382
|
+
end
|
383
|
+
@accept_domains.each{|dom|
|
384
|
+
if domain_match(domain, dom)
|
385
|
+
return true
|
386
|
+
end
|
387
|
+
}
|
388
|
+
@reject_domains.each{|dom|
|
389
|
+
if domain_match(domain, dom)
|
390
|
+
return false
|
391
|
+
end
|
392
|
+
}
|
393
|
+
return true
|
394
|
+
end
|
395
|
+
|
396
|
+
private
|
397
|
+
|
398
|
+
def make_cookie_str(cookie_list)
|
399
|
+
if cookie_list.empty?
|
400
|
+
return nil
|
401
|
+
end
|
402
|
+
|
403
|
+
ret = ''
|
404
|
+
c = cookie_list.shift
|
405
|
+
ret += "#{c.name}=#{c.value}"
|
406
|
+
cookie_list.each{|cookie|
|
407
|
+
ret += "; #{cookie.name}=#{cookie.value}"
|
408
|
+
}
|
409
|
+
return ret
|
410
|
+
end
|
411
|
+
|
412
|
+
# for conformance to http://wp.netscape.com/newsref/std/cookie_spec.html
|
413
|
+
attr_accessor :netscape_rule
|
414
|
+
SPECIAL_DOMAIN = [".com",".edu",".gov",".mil",".net",".org",".int"]
|
415
|
+
|
416
|
+
def check_domain(domain, hostname, override)
|
417
|
+
return unless domain
|
418
|
+
|
419
|
+
# [DRAFT 12] s. 4.2.2 (does not apply in the case that
|
420
|
+
# host name is the same as domain attribute for version 0
|
421
|
+
# cookie)
|
422
|
+
# I think that this rule has almost the same effect as the
|
423
|
+
# tail match of [NETSCAPE].
|
424
|
+
if domain !~ /^\./ && hostname != domain
|
425
|
+
domain = '.'+domain
|
426
|
+
end
|
427
|
+
# [NETSCAPE] rule
|
428
|
+
if @netscape_rule
|
429
|
+
n = domain.scan(/\./).length
|
430
|
+
if n < 2
|
431
|
+
cookie_error(SpecialError.new, override)
|
432
|
+
elsif n == 2
|
433
|
+
## [NETSCAPE] rule
|
434
|
+
ok = SPECIAL_DOMAIN.select{|sdomain|
|
435
|
+
sdomain == domain[-(sdomain.length)..-1]
|
436
|
+
}
|
437
|
+
if ok.empty?
|
438
|
+
cookie_error(SpecialError.new, override)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
# this implementation does not check RFC2109 4.3.2 case 2;
|
443
|
+
# the portion of host not in domain does not contain a dot.
|
444
|
+
# according to nsCookieService.cpp in Firefox 3.0.4, Firefox 3.0.4
|
445
|
+
# and IE does not check, too.
|
446
|
+
end
|
447
|
+
|
448
|
+
# not tested well; used only netscape_rule = true.
|
449
|
+
def cookie_error(err, override)
|
450
|
+
if !err.kind_of?(ErrorOverrideOK) || !override
|
451
|
+
raise err
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
class HTTPClient
|
458
|
+
CookieManager = WebAgent::CookieManager
|
459
|
+
end unless defined?(HTTPClient::CookieManager)
|