httpclient 2.5.3.3 → 2.6.0
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 +4 -4
- data/bin/httpclient +11 -5
- data/bin/jsonclient +32 -78
- data/lib/httpclient.rb +49 -17
- data/lib/httpclient/auth.rb +60 -87
- data/lib/httpclient/cookie.rb +160 -388
- data/lib/httpclient/http.rb +40 -11
- data/lib/httpclient/session.rb +6 -22
- data/lib/httpclient/version.rb +1 -1
- data/lib/httpclient/webagent-cookie.rb +459 -0
- data/lib/jsonclient.rb +63 -0
- data/test/test_auth.rb +51 -6
- data/test/test_cookie.rb +128 -231
- data/test/test_http-access2.rb +6 -8
- data/test/test_httpclient.rb +96 -33
- data/test/test_jsonclient.rb +80 -0
- data/test/test_webagent-cookie.rb +465 -0
- metadata +6 -2
data/lib/httpclient/http.rb
CHANGED
@@ -198,6 +198,7 @@ module HTTP
|
|
198
198
|
@request_uri = uri || NIL_URI
|
199
199
|
@request_query = query
|
200
200
|
@request_absolute_uri = false
|
201
|
+
self
|
201
202
|
end
|
202
203
|
|
203
204
|
# Initialize this instance as a response.
|
@@ -209,6 +210,7 @@ module HTTP
|
|
209
210
|
@request_uri = req.request_uri
|
210
211
|
@request_query = req.request_query
|
211
212
|
end
|
213
|
+
self
|
212
214
|
end
|
213
215
|
|
214
216
|
# Sets status code and reason phrase.
|
@@ -441,6 +443,8 @@ module HTTP
|
|
441
443
|
attr_reader :size
|
442
444
|
# maxbytes of IO#read for streaming request. See DEFAULT_CHUNK_SIZE.
|
443
445
|
attr_accessor :chunk_size
|
446
|
+
# Hash that keeps IO positions
|
447
|
+
attr_accessor :positions
|
444
448
|
|
445
449
|
# Default value for chunk_size
|
446
450
|
DEFAULT_CHUNK_SIZE = 1024 * 16
|
@@ -460,6 +464,7 @@ module HTTP
|
|
460
464
|
@positions = {}
|
461
465
|
set_content(body, boundary)
|
462
466
|
@chunk_size = DEFAULT_CHUNK_SIZE
|
467
|
+
self
|
463
468
|
end
|
464
469
|
|
465
470
|
# Initialize this instance as a response.
|
@@ -472,6 +477,7 @@ module HTTP
|
|
472
477
|
else
|
473
478
|
@size = nil
|
474
479
|
end
|
480
|
+
self
|
475
481
|
end
|
476
482
|
|
477
483
|
# Dumps message body to given dev.
|
@@ -481,13 +487,15 @@ module HTTP
|
|
481
487
|
# reason. (header is dumped to dev, too)
|
482
488
|
# If no dev (the second argument) given, this method returns a dumped
|
483
489
|
# String.
|
490
|
+
#
|
491
|
+
# assert: @size is not nil
|
484
492
|
def dump(header = '', dev = '')
|
485
493
|
if @body.is_a?(Parts)
|
486
494
|
dev << header
|
487
495
|
@body.parts.each do |part|
|
488
496
|
if Message.file?(part)
|
489
497
|
reset_pos(part)
|
490
|
-
dump_file(part, dev)
|
498
|
+
dump_file(part, dev, @body.sizes[part])
|
491
499
|
else
|
492
500
|
dev << part
|
493
501
|
end
|
@@ -495,7 +503,7 @@ module HTTP
|
|
495
503
|
elsif Message.file?(@body)
|
496
504
|
dev << header
|
497
505
|
reset_pos(@body)
|
498
|
-
dump_file(@body, dev)
|
506
|
+
dump_file(@body, dev, @size)
|
499
507
|
elsif @body
|
500
508
|
dev << header + @body
|
501
509
|
else
|
@@ -563,10 +571,14 @@ module HTTP
|
|
563
571
|
io.pos = @positions[io] if @positions.key?(io)
|
564
572
|
end
|
565
573
|
|
566
|
-
def dump_file(io, dev)
|
574
|
+
def dump_file(io, dev, sz)
|
567
575
|
buf = ''
|
568
|
-
|
576
|
+
rest = sz
|
577
|
+
while rest > 0
|
578
|
+
n = io.read([rest, @chunk_size].min, buf)
|
579
|
+
raise ArgumentError.new("Illegal size value: #size returns #{sz} but cannot read") if n.nil?
|
569
580
|
dev << buf
|
581
|
+
rest -= n.bytesize
|
570
582
|
end
|
571
583
|
end
|
572
584
|
|
@@ -591,10 +603,12 @@ module HTTP
|
|
591
603
|
|
592
604
|
class Parts
|
593
605
|
attr_reader :size
|
606
|
+
attr_reader :sizes
|
594
607
|
|
595
608
|
def initialize
|
596
609
|
@body = []
|
597
|
-
@
|
610
|
+
@sizes = {}
|
611
|
+
@size = 0 # total
|
598
612
|
@as_stream = false
|
599
613
|
end
|
600
614
|
|
@@ -603,15 +617,18 @@ module HTTP
|
|
603
617
|
@as_stream = true
|
604
618
|
@body << part
|
605
619
|
if part.respond_to?(:lstat)
|
606
|
-
|
620
|
+
sz = part.lstat.size
|
621
|
+
add_size(part, sz)
|
607
622
|
elsif part.respond_to?(:size)
|
608
623
|
if sz = part.size
|
609
|
-
|
624
|
+
add_size(part, sz)
|
610
625
|
else
|
626
|
+
@sizes.clear
|
611
627
|
@size = nil
|
612
628
|
end
|
613
629
|
else
|
614
630
|
# use chunked upload
|
631
|
+
@sizes.clear
|
615
632
|
@size = nil
|
616
633
|
end
|
617
634
|
elsif @body[-1].is_a?(String)
|
@@ -630,6 +647,15 @@ module HTTP
|
|
630
647
|
[@body.join]
|
631
648
|
end
|
632
649
|
end
|
650
|
+
|
651
|
+
private
|
652
|
+
|
653
|
+
def add_size(part, sz)
|
654
|
+
if @size
|
655
|
+
@sizes[part] = sz
|
656
|
+
@size += sz
|
657
|
+
end
|
658
|
+
end
|
633
659
|
end
|
634
660
|
|
635
661
|
def build_query_multipart_str(query, boundary)
|
@@ -908,12 +934,17 @@ module HTTP
|
|
908
934
|
# used for retrieving the response.
|
909
935
|
attr_accessor :peer_cert
|
910
936
|
|
937
|
+
# The other Message object when this Message is generated instead of
|
938
|
+
# the Message because of redirection, negotiation, or format conversion.
|
939
|
+
attr_accessor :previous
|
940
|
+
|
911
941
|
# Creates a Message. This method should be used internally.
|
912
942
|
# Use Message.new_connect_request, Message.new_request or
|
913
943
|
# Message.new_response instead.
|
914
944
|
def initialize # :nodoc:
|
915
945
|
@http_header = Headers.new
|
916
946
|
@http_body = @peer_cert = nil
|
947
|
+
@previous = nil
|
917
948
|
end
|
918
949
|
|
919
950
|
# Dumps message (header and body) to given dev.
|
@@ -1023,10 +1054,8 @@ module HTTP
|
|
1023
1054
|
unless set_cookies.empty?
|
1024
1055
|
uri = http_header.request_uri
|
1025
1056
|
set_cookies.map { |str|
|
1026
|
-
|
1027
|
-
|
1028
|
-
cookie
|
1029
|
-
}
|
1057
|
+
WebAgent::Cookie.parse(str, uri)
|
1058
|
+
}.flatten
|
1030
1059
|
end
|
1031
1060
|
end
|
1032
1061
|
|
data/lib/httpclient/session.rb
CHANGED
@@ -17,7 +17,7 @@ require 'thread'
|
|
17
17
|
require 'stringio'
|
18
18
|
require 'zlib'
|
19
19
|
|
20
|
-
require 'httpclient/timeout'
|
20
|
+
require 'httpclient/timeout' # TODO: remove this once we drop 1.8 support
|
21
21
|
require 'httpclient/ssl_config'
|
22
22
|
require 'httpclient/http'
|
23
23
|
|
@@ -183,16 +183,6 @@ class HTTPClient
|
|
183
183
|
add_cached_session(sess)
|
184
184
|
end
|
185
185
|
|
186
|
-
def invalidate(site)
|
187
|
-
@sess_pool_mutex.synchronize do
|
188
|
-
if pool = @sess_pool[site]
|
189
|
-
pool.each do |sess|
|
190
|
-
sess.invalidate
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
186
|
private
|
197
187
|
|
198
188
|
# TODO: create PR for webmock's httpclient adapter to use get_session
|
@@ -253,6 +243,9 @@ class HTTPClient
|
|
253
243
|
end
|
254
244
|
|
255
245
|
def get_cached_session(site)
|
246
|
+
if Thread.current[:HTTPClient_AcquireNewConnection]
|
247
|
+
return nil
|
248
|
+
end
|
256
249
|
@sess_pool_mutex.synchronize do
|
257
250
|
now = Time.now
|
258
251
|
if now > @sess_pool_last_checked + @keep_alive_timeout
|
@@ -284,7 +277,7 @@ class HTTPClient
|
|
284
277
|
end
|
285
278
|
|
286
279
|
def valid_session?(sess, now)
|
287
|
-
|
280
|
+
(now <= sess.last_used + @keep_alive_timeout)
|
288
281
|
end
|
289
282
|
|
290
283
|
def add_cached_session(sess)
|
@@ -578,7 +571,6 @@ class HTTPClient
|
|
578
571
|
def initialize(client, dest, agent_name, from)
|
579
572
|
@client = client
|
580
573
|
@dest = dest
|
581
|
-
@invalidated = false
|
582
574
|
@proxy = nil
|
583
575
|
@socket_sync = true
|
584
576
|
@requested_version = nil
|
@@ -662,14 +654,6 @@ class HTTPClient
|
|
662
654
|
@state == :INIT
|
663
655
|
end
|
664
656
|
|
665
|
-
def invalidate
|
666
|
-
@invalidated = true
|
667
|
-
end
|
668
|
-
|
669
|
-
def invalidated?
|
670
|
-
@invalidated
|
671
|
-
end
|
672
|
-
|
673
657
|
def get_header
|
674
658
|
begin
|
675
659
|
if @state != :META
|
@@ -855,7 +839,7 @@ class HTTPClient
|
|
855
839
|
filter.filter_response(req, res)
|
856
840
|
}
|
857
841
|
if commands.find { |command| command == :retry }
|
858
|
-
raise RetryableResponse.new
|
842
|
+
raise RetryableResponse.new(res)
|
859
843
|
end
|
860
844
|
unless @status == 200
|
861
845
|
raise BadResponseError.new("connect to ssl proxy failed with status #{@status} #{@reason}", res)
|
data/lib/httpclient/version.rb
CHANGED
@@ -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
|