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/cookie.rb
CHANGED
@@ -1,448 +1,220 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
#
|
4
|
-
# http://www.rubycolor.org/arc/.
|
1
|
+
# do not override if httpclient/webagent-cookie is loaded already
|
2
|
+
unless defined?(HTTPClient::CookieManager)
|
3
|
+
begin # for catching LoadError and load webagent-cookie instead
|
5
4
|
|
5
|
+
require 'http-cookie'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
7
|
+
class HTTPClient
|
8
|
+
class CookieManager
|
9
|
+
attr_reader :format
|
10
|
+
attr_accessor :cookies_file
|
21
11
|
|
22
|
-
def
|
23
|
-
|
12
|
+
def initialize(cookies_file = nil, format = WebAgentSaver)
|
13
|
+
@cookies_file = cookies_file
|
14
|
+
@format = format
|
15
|
+
@jar = HTTP::CookieJar.new
|
16
|
+
load_cookies if @cookies_file
|
24
17
|
end
|
25
18
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
def load_cookies
|
20
|
+
check_cookies_file
|
21
|
+
@jar.clear
|
22
|
+
@jar.load(@cookies_file, :format => @format)
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_cookies(session = false)
|
26
|
+
check_cookies_file
|
27
|
+
@jar.save(@cookies_file, :format => @format, :session => session)
|
28
|
+
end
|
29
|
+
|
30
|
+
def cookies(uri = nil)
|
31
|
+
cookies = @jar.cookies(uri)
|
32
|
+
# TODO: return HTTP::Cookie in the future
|
33
|
+
cookies.map { |cookie|
|
34
|
+
WebAgent::Cookie.new(
|
35
|
+
:name => cookie.name,
|
36
|
+
:value => cookie.value,
|
37
|
+
:domain => cookie.domain,
|
38
|
+
:path => cookie.path,
|
39
|
+
:origin => cookie.origin,
|
40
|
+
:for_domain => cookie.for_domain,
|
41
|
+
:expires => cookie.expires,
|
42
|
+
:httponly => cookie.httponly,
|
43
|
+
:secure => cookie.secure
|
44
|
+
)
|
45
|
+
}
|
32
46
|
end
|
33
47
|
|
34
|
-
def
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
+
def cookie_value(uri)
|
49
|
+
cookies = self.cookies(uri)
|
50
|
+
unless cookies.empty?
|
51
|
+
HTTP::Cookie.cookie_value(cookies)
|
48
52
|
end
|
49
53
|
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 initialize
|
71
|
-
@name = @value = @domain = @path = nil
|
72
|
-
@expires = nil
|
73
|
-
@url = nil
|
74
|
-
@use = @secure = @http_only = @discard = @domain_orig = @path_orig = @override = nil
|
75
|
-
end
|
76
54
|
|
77
|
-
def
|
78
|
-
@
|
55
|
+
def parse(value, uri)
|
56
|
+
@jar.parse(value, uri)
|
79
57
|
end
|
80
58
|
|
81
|
-
def
|
82
|
-
@
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
@secure
|
87
|
-
end
|
88
|
-
|
89
|
-
def http_only?
|
90
|
-
@http_only
|
91
|
-
end
|
92
|
-
|
93
|
-
def domain_orig?
|
94
|
-
@domain_orig
|
59
|
+
def cookies=(cookies)
|
60
|
+
@jar.clear
|
61
|
+
cookies.each do |cookie|
|
62
|
+
add(cookie)
|
63
|
+
end
|
95
64
|
end
|
96
65
|
|
97
|
-
def
|
98
|
-
@
|
66
|
+
def add(cookie)
|
67
|
+
@jar.add(cookie)
|
99
68
|
end
|
100
69
|
|
101
|
-
def
|
102
|
-
|
70
|
+
def find(uri)
|
71
|
+
warn('CookieManager#find is deprecated and will be removed in near future. Use HTTP::Cookie.cookie_value(CookieManager#cookies) instead')
|
72
|
+
if cookie = cookies(uri)
|
73
|
+
HTTP::Cookie.cookie_value(cookie)
|
74
|
+
end
|
103
75
|
end
|
104
76
|
|
105
|
-
|
106
|
-
flg = 0
|
107
|
-
flg += USE if @use
|
108
|
-
flg += SECURE if @secure
|
109
|
-
flg += HTTP_ONLY if @http_only
|
110
|
-
flg += DOMAIN if @domain_orig
|
111
|
-
flg += PATH if @path_orig
|
112
|
-
flg += DISCARD if @discard
|
113
|
-
flg += OVERRIDE if @override
|
114
|
-
flg
|
115
|
-
end
|
77
|
+
private
|
116
78
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@http_only = true if flag & HTTP_ONLY > 0
|
122
|
-
@domain_orig = true if flag & DOMAIN > 0
|
123
|
-
@path_orig = true if flag & PATH > 0
|
124
|
-
@discard = true if flag & DISCARD > 0
|
125
|
-
@override = true if flag & OVERRIDE > 0
|
79
|
+
def check_cookies_file
|
80
|
+
unless @cookies_file
|
81
|
+
raise ArgumentError.new('Cookies file not specified')
|
82
|
+
end
|
126
83
|
end
|
84
|
+
end
|
127
85
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
(@path && !head_match?(@path, url.path.empty? ? '/' : url.path)) ||
|
133
|
-
(@secure && (url.scheme != 'https')) )
|
134
|
-
return false
|
135
|
-
else
|
136
|
-
return true
|
137
|
-
end
|
86
|
+
class WebAgentSaver < HTTP::CookieJar::AbstractSaver
|
87
|
+
# no option
|
88
|
+
def default_options
|
89
|
+
{}
|
138
90
|
end
|
139
91
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
if old_elem
|
146
|
-
old_elem << sep << elem
|
147
|
-
else
|
148
|
-
ret << elem
|
149
|
-
old_elem = nil
|
150
|
-
end
|
151
|
-
else
|
152
|
-
if old_elem
|
153
|
-
old_elem << sep << elem
|
154
|
-
ret << old_elem
|
155
|
-
old_elem = nil
|
156
|
-
else
|
157
|
-
old_elem = elem.dup
|
158
|
-
end
|
159
|
-
end
|
92
|
+
# same as HTTP::CookieJar::CookiestxtSaver
|
93
|
+
def save(io, jar)
|
94
|
+
jar.each { |cookie|
|
95
|
+
next if !@session && cookie.session?
|
96
|
+
io.print cookie_to_record(cookie)
|
160
97
|
}
|
161
|
-
ret
|
162
98
|
end
|
163
99
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
cookie_elem = join_quotedstr(cookie_elem, ';')
|
169
|
-
cookie_elem -= [""] # del empty elements, a cookie might included ";;"
|
170
|
-
first_elem = cookie_elem.shift
|
171
|
-
if first_elem !~ /([^=]*)(\=(.*))?/
|
172
|
-
return
|
173
|
-
## raise ArgumentError 'invalid cookie value'
|
174
|
-
end
|
175
|
-
@name = $1.strip
|
176
|
-
@value = normalize_cookie_value($3)
|
177
|
-
cookie_elem.each{|pair|
|
178
|
-
key, value = pair.split(/=/, 2) ## value may nil
|
179
|
-
key.strip!
|
180
|
-
value = normalize_cookie_value(value)
|
181
|
-
case key.downcase
|
182
|
-
when 'domain'
|
183
|
-
@domain = value
|
184
|
-
when 'expires'
|
185
|
-
@expires = nil
|
186
|
-
begin
|
187
|
-
@expires = Time.parse(value).gmtime if value
|
188
|
-
rescue ArgumentError
|
189
|
-
end
|
190
|
-
when 'path'
|
191
|
-
@path = value
|
192
|
-
when 'secure'
|
193
|
-
@secure = true ## value may nil, but must 'true'.
|
194
|
-
when 'httponly'
|
195
|
-
@http_only = true ## value may nil, but must 'true'.
|
196
|
-
else
|
197
|
-
warn("Unknown key: #{key} = #{value}")
|
198
|
-
end
|
100
|
+
# same as HTTP::CookieJar::CookiestxtSaver
|
101
|
+
def load(io, jar)
|
102
|
+
io.each_line { |line|
|
103
|
+
cookie = parse_record(line) and jar.add(cookie)
|
199
104
|
}
|
200
105
|
end
|
201
106
|
|
202
|
-
|
107
|
+
private
|
203
108
|
|
204
|
-
def
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
109
|
+
def cookie_to_record(cookie)
|
110
|
+
[
|
111
|
+
cookie.origin,
|
112
|
+
cookie.name,
|
113
|
+
cookie.value,
|
114
|
+
cookie.expires.to_i,
|
115
|
+
cookie.dot_domain,
|
116
|
+
cookie.path,
|
117
|
+
self.class.flag(cookie)
|
118
|
+
].join("\t") + "\n"
|
210
119
|
end
|
211
|
-
end
|
212
|
-
|
213
|
-
##
|
214
|
-
# An Array class that already includes the MonitorMixin module.
|
215
|
-
#
|
216
|
-
class SynchronizedArray < Array
|
217
|
-
include MonitorMixin
|
218
|
-
end
|
219
|
-
|
220
|
-
class CookieManager
|
221
|
-
include CookieUtils
|
222
120
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
class SpecialError < Error; end
|
227
|
-
|
228
|
-
attr_reader :cookies
|
229
|
-
attr_accessor :cookies_file
|
230
|
-
attr_accessor :accept_domains, :reject_domains
|
231
|
-
|
232
|
-
def initialize(file=nil)
|
233
|
-
@cookies = SynchronizedArray.new
|
234
|
-
@cookies_file = file
|
235
|
-
@is_saved = true
|
236
|
-
@reject_domains = Array.new
|
237
|
-
@accept_domains = Array.new
|
238
|
-
@netscape_rule = false
|
239
|
-
end
|
121
|
+
def parse_record(line)
|
122
|
+
return nil if /\A#/ =~ line
|
123
|
+
col = line.chomp.split(/\t/)
|
240
124
|
|
241
|
-
|
242
|
-
|
243
|
-
|
125
|
+
origin = col[0]
|
126
|
+
name = col[1]
|
127
|
+
value = col[2]
|
128
|
+
value.chomp!
|
129
|
+
if col[3].empty? or col[3] == '0'
|
130
|
+
expires = nil
|
244
131
|
else
|
245
|
-
|
132
|
+
expires = Time.at(col[3].to_i)
|
133
|
+
return nil if expires < Time.now
|
246
134
|
end
|
247
|
-
|
135
|
+
domain = col[4]
|
136
|
+
path = col[5]
|
248
137
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
if (cookie.use? or save_unused) and
|
258
|
-
(!cookie.discard? or save_discarded)
|
259
|
-
f.print(cookie.url.to_s,"\t",
|
260
|
-
cookie.name,"\t",
|
261
|
-
cookie.value,"\t",
|
262
|
-
cookie.expires.to_i,"\t",
|
263
|
-
cookie.domain,"\t",
|
264
|
-
cookie.path,"\t",
|
265
|
-
cookie.flag,"\n")
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
@is_saved = true
|
138
|
+
cookie = WebAgent::Cookie.new(name, value,
|
139
|
+
:origin => origin,
|
140
|
+
:domain => domain,
|
141
|
+
:path => path,
|
142
|
+
:expires => expires
|
143
|
+
)
|
144
|
+
self.class.set_flag(cookie, col[6].to_i)
|
145
|
+
cookie
|
271
146
|
end
|
272
147
|
|
273
|
-
|
274
|
-
|
275
|
-
|
148
|
+
USE = 1
|
149
|
+
SECURE = 2
|
150
|
+
DOMAIN = 4
|
151
|
+
PATH = 8
|
152
|
+
HTTP_ONLY = 64
|
276
153
|
|
277
|
-
def
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
154
|
+
def self.flag(cookie)
|
155
|
+
flg = 0
|
156
|
+
flg += USE # not used
|
157
|
+
flg += SECURE if cookie.secure?
|
158
|
+
flg += DOMAIN if cookie.for_domain?
|
159
|
+
flg += HTTP_ONLY if cookie.httponly?
|
160
|
+
flg += PATH if cookie.path # not used
|
161
|
+
flg
|
285
162
|
end
|
286
163
|
|
287
|
-
def
|
288
|
-
cookie =
|
289
|
-
cookie.
|
290
|
-
|
164
|
+
def self.set_flag(cookie, flag)
|
165
|
+
cookie.secure = true if flag & SECURE > 0
|
166
|
+
cookie.for_domain = true if flag & DOMAIN > 0
|
167
|
+
cookie.httponly = true if flag & HTTP_ONLY > 0
|
291
168
|
end
|
169
|
+
end
|
170
|
+
end
|
292
171
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
cookie_list = Array.new
|
297
|
-
@cookies.each{|cookie|
|
298
|
-
is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
|
299
|
-
if cookie.use? && !is_expired && cookie.match?(url)
|
300
|
-
if cookie_list.select{|c1| c1.name == cookie.name}.empty?
|
301
|
-
cookie_list << cookie
|
302
|
-
end
|
303
|
-
end
|
304
|
-
}
|
305
|
-
return make_cookie_str(cookie_list)
|
306
|
-
end
|
172
|
+
# for backward compatibility
|
173
|
+
class WebAgent
|
174
|
+
CookieManager = ::HTTPClient::CookieManager
|
307
175
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
domain = given.domain || given.url.host
|
312
|
-
path = given.path || given.url.path.sub(%r|/[^/]*\z|, '')
|
313
|
-
|
314
|
-
cookie = nil
|
315
|
-
@cookies.synchronize do
|
316
|
-
check_expired_cookies
|
317
|
-
cookie = @cookies.find { |c|
|
318
|
-
c.domain == domain && c.path == path && c.name == given.name
|
319
|
-
}
|
320
|
-
if !cookie
|
321
|
-
cookie = WebAgent::Cookie.new
|
322
|
-
cookie.use = true
|
323
|
-
@cookies << cookie
|
324
|
-
end
|
325
|
-
end
|
176
|
+
class Cookie < HTTP::Cookie
|
177
|
+
@@warned = false
|
326
178
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
cookie.name = given.name
|
331
|
-
cookie.value = given.value
|
332
|
-
cookie.expires = given.expires
|
333
|
-
cookie.secure = given.secure?
|
334
|
-
cookie.http_only = given.http_only?
|
335
|
-
cookie.domain_orig = given.domain
|
336
|
-
cookie.path_orig = given.path
|
337
|
-
|
338
|
-
if cookie.discard? || cookie.expires == nil
|
339
|
-
cookie.discard = true
|
340
|
-
else
|
341
|
-
cookie.discard = false
|
342
|
-
@is_saved = false
|
343
|
-
end
|
179
|
+
def url
|
180
|
+
deprecated('url', 'origin')
|
181
|
+
self.origin
|
344
182
|
end
|
345
183
|
|
346
|
-
def
|
347
|
-
|
348
|
-
|
349
|
-
@cookies.clear
|
350
|
-
File.open(@cookies_file,'r'){|f|
|
351
|
-
while line = f.gets
|
352
|
-
cookie = WebAgent::Cookie.new
|
353
|
-
@cookies << cookie
|
354
|
-
col = line.chomp.split(/\t/)
|
355
|
-
cookie.url = HTTPClient::Util.urify(col[0])
|
356
|
-
cookie.name = col[1]
|
357
|
-
cookie.value = col[2]
|
358
|
-
if col[3].empty? or col[3] == '0'
|
359
|
-
cookie.expires = nil
|
360
|
-
else
|
361
|
-
cookie.expires = Time.at(col[3].to_i).gmtime
|
362
|
-
end
|
363
|
-
cookie.domain = col[4]
|
364
|
-
cookie.path = col[5]
|
365
|
-
cookie.set_flag(col[6])
|
366
|
-
end
|
367
|
-
}
|
368
|
-
end
|
184
|
+
def url=(url)
|
185
|
+
deprecated('url=', 'origin=')
|
186
|
+
self.origin = url
|
369
187
|
end
|
370
188
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
return false
|
375
|
-
end
|
376
|
-
@accept_domains.each{|dom|
|
377
|
-
if domain_match(domain, dom)
|
378
|
-
return true
|
379
|
-
end
|
380
|
-
}
|
381
|
-
@reject_domains.each{|dom|
|
382
|
-
if domain_match(domain, dom)
|
383
|
-
return false
|
384
|
-
end
|
385
|
-
}
|
386
|
-
return true
|
189
|
+
def http_only?
|
190
|
+
deprecated('http_only?', 'httponly?')
|
191
|
+
self.httponly?
|
387
192
|
end
|
388
193
|
|
389
|
-
|
390
|
-
|
391
|
-
def make_cookie_str(cookie_list)
|
392
|
-
if cookie_list.empty?
|
393
|
-
return nil
|
394
|
-
end
|
194
|
+
alias original_domain domain
|
395
195
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
cookie_list.each{|cookie|
|
400
|
-
ret += "; #{cookie.name}=#{cookie.value}"
|
401
|
-
}
|
402
|
-
return ret
|
196
|
+
def domain
|
197
|
+
warn('Cookie#domain returns dot-less domain name now. Use Cookie#dot_domain if you need "." at the beginning.')
|
198
|
+
self.original_domain
|
403
199
|
end
|
404
200
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
def check_domain(domain, hostname, override)
|
410
|
-
return unless domain
|
411
|
-
|
412
|
-
# [DRAFT 12] s. 4.2.2 (does not apply in the case that
|
413
|
-
# host name is the same as domain attribute for version 0
|
414
|
-
# cookie)
|
415
|
-
# I think that this rule has almost the same effect as the
|
416
|
-
# tail match of [NETSCAPE].
|
417
|
-
if domain !~ /^\./ && hostname != domain
|
418
|
-
domain = '.'+domain
|
419
|
-
end
|
420
|
-
# [NETSCAPE] rule
|
421
|
-
if @netscape_rule
|
422
|
-
n = domain.scan(/\./).length
|
423
|
-
if n < 2
|
424
|
-
cookie_error(SpecialError.new, override)
|
425
|
-
elsif n == 2
|
426
|
-
## [NETSCAPE] rule
|
427
|
-
ok = SPECIAL_DOMAIN.select{|sdomain|
|
428
|
-
sdomain == domain[-(sdomain.length)..-1]
|
429
|
-
}
|
430
|
-
if ok.empty?
|
431
|
-
cookie_error(SpecialError.new, override)
|
432
|
-
end
|
433
|
-
end
|
434
|
-
end
|
435
|
-
# this implementation does not check RFC2109 4.3.2 case 2;
|
436
|
-
# the portion of host not in domain does not contain a dot.
|
437
|
-
# according to nsCookieService.cpp in Firefox 3.0.4, Firefox 3.0.4
|
438
|
-
# and IE does not check, too.
|
201
|
+
def flag
|
202
|
+
deprecated('flag', 'secure, for_domain, etc.')
|
203
|
+
HTTPClient::WebAgentSaver.flag(self)
|
439
204
|
end
|
440
205
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
206
|
+
private
|
207
|
+
|
208
|
+
def deprecated(old, new)
|
209
|
+
unless @@warned
|
210
|
+
warn("WebAgent::Cookie is deprecated and will be replaced with HTTP::Cookie in the near future. Please use Cookie##{new} instead of Cookie##{old} for the replacement.")
|
211
|
+
@@warned = true
|
445
212
|
end
|
446
213
|
end
|
447
214
|
end
|
448
215
|
end
|
216
|
+
|
217
|
+
rescue LoadError
|
218
|
+
require 'httpclient/webagent-cookie'
|
219
|
+
end
|
220
|
+
end
|