httpclient 2.5.3.3 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,448 +1,220 @@
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/.
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
- # 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
7
+ class HTTPClient
8
+ class CookieManager
9
+ attr_reader :format
10
+ attr_accessor :cookies_file
21
11
 
22
- def head_match?(str1, str2)
23
- str1 == str2[0, str1.length]
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 tail_match?(str1, str2)
27
- if str1.length > 0
28
- str1 == str2[-str1.length..-1].to_s
29
- else
30
- true
31
- end
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 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
+ 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 discard?
78
- @discard
55
+ def parse(value, uri)
56
+ @jar.parse(value, uri)
79
57
  end
80
58
 
81
- def use?
82
- @use
83
- end
84
-
85
- def secure?
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 path_orig?
98
- @path_orig
66
+ def add(cookie)
67
+ @jar.add(cookie)
99
68
  end
100
69
 
101
- def override?
102
- @override
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
- def flag
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 set_flag(flag)
118
- flag = flag.to_i
119
- @use = true if flag & USE > 0
120
- @secure = true if flag & SECURE > 0
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
- def match?(url)
129
- domainname = url.host
130
- if (!domainname ||
131
- !domain_match(domainname, @domain) ||
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
- def join_quotedstr(array, sep)
141
- ret = Array.new
142
- old_elem = nil
143
- array.each{|elem|
144
- if (elem.scan(/"/).length % 2) == 0
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
- def parse(str, url)
165
- @url = url
166
- # TODO: should not depend on join_quotedstr. scan with escape like CSV.
167
- cookie_elem = str.split(/;/)
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
- private
107
+ private
203
108
 
204
- def normalize_cookie_value(value)
205
- if value
206
- value = value.strip.sub(/\A"(.*)"\z/) { $1 }
207
- value = nil if value.empty?
208
- end
209
- value
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
- ### errors
224
- class Error < StandardError; end
225
- class ErrorOverrideOK < Error; end
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
- def cookies=(cookies)
242
- if cookies.is_a?(SynchronizedArray)
243
- @cookies = cookies
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
- @cookies = SynchronizedArray.new(cookies)
132
+ expires = Time.at(col[3].to_i)
133
+ return nil if expires < Time.now
246
134
  end
247
- end
135
+ domain = col[4]
136
+ path = col[5]
248
137
 
249
- def save_all_cookies(force = nil, save_unused = true, save_discarded = true)
250
- @cookies.synchronize do
251
- check_expired_cookies
252
- if @is_saved and !force
253
- return
254
- end
255
- File.open(@cookies_file, 'w') do |f|
256
- @cookies.each do |cookie|
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
- def save_cookies(force = nil)
274
- save_all_cookies(force, false, false)
275
- end
148
+ USE = 1
149
+ SECURE = 2
150
+ DOMAIN = 4
151
+ PATH = 8
152
+ HTTP_ONLY = 64
276
153
 
277
- def check_expired_cookies
278
- @cookies.reject!{|cookie|
279
- is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
280
- if is_expired && !cookie.discard?
281
- @is_saved = false
282
- end
283
- is_expired
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 parse(str, url)
288
- cookie = WebAgent::Cookie.new
289
- cookie.parse(str, url)
290
- add(cookie)
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
- def find(url)
294
- return nil if @cookies.empty?
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
- def add(given)
309
- check_domain(given.domain, given.url.host, given.override?)
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
- cookie.domain = domain
328
- cookie.path = path
329
- cookie.url = given.url
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 load_cookies
347
- return if !File.readable?(@cookies_file)
348
- @cookies.synchronize do
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
- # Who use it?
372
- def check_cookie_accept_domain(domain)
373
- unless domain
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
- private
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
- ret = ''
397
- c = cookie_list.shift
398
- ret += "#{c.name}=#{c.value}"
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
- # for conformance to http://wp.netscape.com/newsref/std/cookie_spec.html
406
- attr_accessor :netscape_rule
407
- SPECIAL_DOMAIN = [".com",".edu",".gov",".mil",".net",".org",".int"]
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
- # not tested well; used only netscape_rule = true.
442
- def cookie_error(err, override)
443
- if !err.kind_of?(ErrorOverrideOK) || !override
444
- raise err
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