httpclient 2.1.5 → 2.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +85 -0
  3. data/bin/httpclient +77 -0
  4. data/bin/jsonclient +85 -0
  5. data/lib/hexdump.rb +50 -0
  6. data/lib/http-access2.rb +6 -4
  7. data/lib/httpclient/auth.rb +575 -173
  8. data/lib/httpclient/cacert.pem +3952 -0
  9. data/lib/httpclient/cacert1024.pem +3866 -0
  10. data/lib/httpclient/connection.rb +6 -2
  11. data/lib/httpclient/cookie.rb +162 -504
  12. data/lib/httpclient/http.rb +334 -119
  13. data/lib/httpclient/include_client.rb +85 -0
  14. data/lib/httpclient/jruby_ssl_socket.rb +588 -0
  15. data/lib/httpclient/session.rb +385 -288
  16. data/lib/httpclient/ssl_config.rb +195 -155
  17. data/lib/httpclient/ssl_socket.rb +150 -0
  18. data/lib/httpclient/timeout.rb +14 -10
  19. data/lib/httpclient/util.rb +142 -6
  20. data/lib/httpclient/version.rb +3 -0
  21. data/lib/httpclient/webagent-cookie.rb +459 -0
  22. data/lib/httpclient.rb +509 -202
  23. data/lib/jsonclient.rb +63 -0
  24. data/lib/oauthclient.rb +111 -0
  25. data/sample/async.rb +8 -0
  26. data/sample/auth.rb +11 -0
  27. data/sample/cookie.rb +18 -0
  28. data/sample/dav.rb +103 -0
  29. data/sample/howto.rb +49 -0
  30. data/sample/jsonclient.rb +67 -0
  31. data/sample/oauth_buzz.rb +57 -0
  32. data/sample/oauth_friendfeed.rb +59 -0
  33. data/sample/oauth_twitter.rb +61 -0
  34. data/sample/ssl/0cert.pem +22 -0
  35. data/sample/ssl/0key.pem +30 -0
  36. data/sample/ssl/1000cert.pem +19 -0
  37. data/sample/ssl/1000key.pem +18 -0
  38. data/sample/ssl/htdocs/index.html +10 -0
  39. data/sample/ssl/ssl_client.rb +22 -0
  40. data/sample/ssl/webrick_httpsd.rb +29 -0
  41. data/sample/stream.rb +21 -0
  42. data/sample/thread.rb +27 -0
  43. data/sample/wcat.rb +21 -0
  44. data/test/ca-chain.pem +44 -0
  45. data/test/ca.cert +23 -0
  46. data/test/client-pass.key +18 -0
  47. data/test/client.cert +19 -0
  48. data/test/client.key +15 -0
  49. data/test/helper.rb +131 -0
  50. data/test/htdigest +1 -0
  51. data/test/htpasswd +2 -0
  52. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  53. data/test/runner.rb +2 -0
  54. data/test/server.cert +19 -0
  55. data/test/server.key +15 -0
  56. data/test/sslsvr.rb +65 -0
  57. data/test/subca.cert +21 -0
  58. data/test/test_auth.rb +492 -0
  59. data/test/test_cookie.rb +309 -0
  60. data/test/test_hexdump.rb +14 -0
  61. data/test/test_http-access2.rb +508 -0
  62. data/test/test_httpclient.rb +2145 -0
  63. data/test/test_include_client.rb +52 -0
  64. data/test/test_jsonclient.rb +80 -0
  65. data/test/test_ssl.rb +559 -0
  66. data/test/test_webagent-cookie.rb +465 -0
  67. metadata +85 -44
  68. data/lib/httpclient/auth.rb.orig +0 -513
  69. data/lib/httpclient/cacert.p7s +0 -1579
  70. data/lib/httpclient.rb.orig +0 -1020
  71. data/lib/tags +0 -908
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
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
- def timeout(sec, ex = nil, &block)
123
- return yield if sec == nil or sec.zero?
124
- scheduler = nil
125
- begin
126
- scheduler = HTTPClient.timeout_scheduler
127
- period = scheduler.register(Thread.current, sec, ex)
128
- yield(sec)
129
- ensure
130
- scheduler.cancel(period) if scheduler and period
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
@@ -1,12 +1,49 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
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
- require 'uri'
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].is_a?(Hash)
39
- args[0].values_at(*field)
40
- else
41
- args
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,3 @@
1
+ class HTTPClient
2
+ VERSION = '2.8.3'
3
+ end
@@ -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)