bterlson-httpclient 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ class HTTPClient
10
+
11
+
12
+ # Represents a HTTP response to an asynchronous request. Async methods of
13
+ # HTTPClient such as get_async, post_async, etc. returns an instance of
14
+ # Connection.
15
+ #
16
+ # == How to use
17
+ #
18
+ # 1. Invoke HTTP method asynchronously and check if it's been finished
19
+ # periodically.
20
+ #
21
+ # connection = clnt.post_async(url, body)
22
+ # print 'posting.'
23
+ # while true
24
+ # break if connection.finished?
25
+ # print '.'
26
+ # sleep 1
27
+ # end
28
+ # puts '.'
29
+ # res = connection.pop
30
+ # p res.status
31
+ #
32
+ # 2. Read the response as an IO.
33
+ #
34
+ # connection = clnt.get_async('http://dev.ctor.org/')
35
+ # io = connection.pop.content
36
+ # while str = io.read(40)
37
+ # p str
38
+ # end
39
+ class Connection
40
+ attr_accessor :async_thread
41
+
42
+ def initialize(header_queue = [], body_queue = []) # :nodoc:
43
+ @headers = header_queue
44
+ @body = body_queue
45
+ @async_thread = nil
46
+ @queue = Queue.new
47
+ end
48
+
49
+ # Checks if the asynchronous invocation has been finished or not.
50
+ def finished?
51
+ if !@async_thread
52
+ # Not in async mode.
53
+ true
54
+ elsif @async_thread.alive?
55
+ # Working...
56
+ false
57
+ else
58
+ # Async thread have been finished.
59
+ join
60
+ true
61
+ end
62
+ end
63
+
64
+ # Retrieves a HTTP::Message instance of HTTP response. Do not invoke this
65
+ # method twice for now. The second invocation will be blocked.
66
+ def pop
67
+ @queue.pop
68
+ end
69
+
70
+ def push(result) # :nodoc:
71
+ @queue.push(result)
72
+ end
73
+
74
+ # Waits the completion of the asynchronous invocation.
75
+ def join
76
+ if @async_thread
77
+ @async_thread.join
78
+ end
79
+ nil
80
+ end
81
+ end
82
+
83
+
84
+ end
@@ -0,0 +1,560 @@
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 'uri'
15
+ require 'time'
16
+ require 'monitor'
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
+ domainname = domain.sub(/\.\z/, '').downcase
36
+ hostname = host.sub(/\.\z/, '').downcase
37
+ case domain
38
+ when /\d+\.\d+\.\d+\.\d+/
39
+ return (hostname == domainname)
40
+ when '.'
41
+ return true
42
+ when /^\./
43
+ # allows; host == rubyforge.org, domain == .rubyforge.org
44
+ return tail_match?(domainname, '.' + hostname)
45
+ else
46
+ return (hostname == domainname)
47
+ end
48
+ end
49
+
50
+ def total_dot_num(string)
51
+ string.scan(/\./).length()
52
+ end
53
+
54
+ end
55
+
56
+ class Cookie
57
+ include CookieUtils
58
+
59
+ attr_accessor :name, :value
60
+ attr_accessor :domain, :path
61
+ attr_accessor :expires ## for Netscape Cookie
62
+ attr_accessor :url
63
+ attr_writer :use, :secure, :discard, :domain_orig, :path_orig, :override
64
+
65
+ USE = 1
66
+ SECURE = 2
67
+ DOMAIN = 4
68
+ PATH = 8
69
+ DISCARD = 16
70
+ OVERRIDE = 32
71
+ OVERRIDE_OK = 32
72
+
73
+ def initialize()
74
+ @name = @value = @domain = @path = nil
75
+ @expires = nil
76
+ @url = nil
77
+ @use = @secure = @discard = @domain_orig = @path_orig = @override = nil
78
+ end
79
+
80
+ def discard?
81
+ @discard
82
+ end
83
+
84
+ def use?
85
+ @use
86
+ end
87
+
88
+ def secure?
89
+ @secure
90
+ end
91
+
92
+ def domain_orig?
93
+ @domain_orig
94
+ end
95
+
96
+ def path_orig?
97
+ @path_orig
98
+ end
99
+
100
+ def override?
101
+ @override
102
+ end
103
+
104
+ def flag
105
+ flg = 0
106
+ flg += USE if @use
107
+ flg += SECURE if @secure
108
+ flg += DOMAIN if @domain_orig
109
+ flg += PATH if @path_orig
110
+ flg += DISCARD if @discard
111
+ flg += OVERRIDE if @override
112
+ flg
113
+ end
114
+
115
+ def set_flag(flag)
116
+ flag = flag.to_i
117
+ @use = true if flag & USE > 0
118
+ @secure = true if flag & SECURE > 0
119
+ @domain_orig = true if flag & DOMAIN > 0
120
+ @path_orig = true if flag & PATH > 0
121
+ @discard = true if flag & DISCARD > 0
122
+ @override = true if flag & OVERRIDE > 0
123
+ end
124
+
125
+ def match?(url)
126
+ domainname = url.host
127
+ if (!domainname ||
128
+ !domain_match(domainname, @domain) ||
129
+ (@path && !head_match?(@path, url.path)) ||
130
+ (@secure && (url.scheme != 'https')) )
131
+ return false
132
+ else
133
+ return true
134
+ end
135
+ end
136
+
137
+ def join_quotedstr(array, sep)
138
+ ret = Array.new()
139
+ old_elem = nil
140
+ array.each{|elem|
141
+ if (elem.scan(/"/).length % 2) == 0
142
+ if old_elem
143
+ old_elem << sep << elem
144
+ else
145
+ ret << elem
146
+ old_elem = nil
147
+ end
148
+ else
149
+ if old_elem
150
+ old_elem << sep << elem
151
+ ret << old_elem
152
+ old_elem = nil
153
+ else
154
+ old_elem = elem.dup
155
+ end
156
+ end
157
+ }
158
+ ret
159
+ end
160
+
161
+ def parse(str, url)
162
+ @url = url
163
+ cookie_elem = str.split(/;/)
164
+ cookie_elem = join_quotedstr(cookie_elem, ';')
165
+ first_elem = cookie_elem.shift
166
+ if first_elem !~ /([^=]*)(\=(.*))?/
167
+ return
168
+ ## raise ArgumentError 'invalid cookie value'
169
+ end
170
+ @name = $1.strip
171
+ @value = normalize_cookie_value($3)
172
+ cookie_elem.each{|pair|
173
+ key, value = pair.split(/=/) ## value may nil
174
+ key.strip!
175
+ value = normalize_cookie_value(value)
176
+ case key.downcase
177
+ when 'domain'
178
+ @domain = value
179
+ when 'expires'
180
+ @expires = nil
181
+ begin
182
+ @expires = Time.parse(value).gmtime() if value
183
+ rescue ArgumentError
184
+ end
185
+ when 'path'
186
+ @path = value
187
+ when 'secure'
188
+ @secure = true ## value may nil, but must 'true'.
189
+ else
190
+ ## ignore
191
+ end
192
+ }
193
+ end
194
+
195
+ def normalize_cookie_value(value)
196
+ if value
197
+ value = value.strip.sub(/\A"(.*)"\z/) { $1 }
198
+ value = nil if value.empty?
199
+ end
200
+ value
201
+ end
202
+ private :normalize_cookie_value
203
+ end
204
+
205
+ class CookieManager
206
+ include CookieUtils
207
+
208
+ ### errors
209
+ class Error < StandardError; end
210
+ class ErrorOverrideOK < Error; end
211
+ class SpecialError < Error; end
212
+
213
+ attr_reader :cookies
214
+ attr_accessor :cookies_file
215
+ attr_accessor :accept_domains, :reject_domains
216
+
217
+ # for conformance to http://wp.netscape.com/newsref/std/cookie_spec.html
218
+ attr_accessor :netscape_rule
219
+ SPECIAL_DOMAIN = [".com",".edu",".gov",".mil",".net",".org",".int"]
220
+
221
+ def initialize(file=nil)
222
+ @cookies = Array.new()
223
+ @cookies.extend(MonitorMixin)
224
+ @cookies_file = file
225
+ @is_saved = true
226
+ @reject_domains = Array.new()
227
+ @accept_domains = Array.new()
228
+ @netscape_rule = false
229
+ end
230
+
231
+ def cookies=(cookies)
232
+ @cookies = cookies
233
+ @cookies.extend(MonitorMixin)
234
+ end
235
+
236
+ def save_all_cookies(force = nil, save_unused = true, save_discarded = true)
237
+ @cookies.synchronize do
238
+ check_expired_cookies()
239
+ if @is_saved and !force
240
+ return
241
+ end
242
+ File.open(@cookies_file, 'w') do |f|
243
+ @cookies.each do |cookie|
244
+ if (cookie.use? or save_unused) and
245
+ (!cookie.discard? or save_discarded)
246
+ f.print(cookie.url.to_s,"\t",
247
+ cookie.name,"\t",
248
+ cookie.value,"\t",
249
+ cookie.expires.to_i,"\t",
250
+ cookie.domain,"\t",
251
+ cookie.path,"\t",
252
+ cookie.flag,"\n")
253
+ end
254
+ end
255
+ end
256
+ end
257
+ @is_saved = true
258
+ end
259
+
260
+ def save_cookies(force = nil)
261
+ save_all_cookies(force, false, false)
262
+ end
263
+
264
+ def check_expired_cookies()
265
+ @cookies.reject!{|cookie|
266
+ is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
267
+ if is_expired && !cookie.discard?
268
+ @is_saved = false
269
+ end
270
+ is_expired
271
+ }
272
+ end
273
+
274
+ def parse(str, url)
275
+ cookie = WebAgent::Cookie.new()
276
+ cookie.parse(str, url)
277
+ add(cookie)
278
+ end
279
+
280
+ def make_cookie_str(cookie_list)
281
+ if cookie_list.empty?
282
+ return nil
283
+ end
284
+
285
+ ret = ''
286
+ c = cookie_list.shift
287
+ ret += "#{c.name}=#{c.value}"
288
+ cookie_list.each{|cookie|
289
+ ret += "; #{cookie.name}=#{cookie.value}"
290
+ }
291
+ return ret
292
+ end
293
+ private :make_cookie_str
294
+
295
+
296
+ def find(url)
297
+ return nil if @cookies.empty?
298
+
299
+ cookie_list = Array.new()
300
+ @cookies.each{|cookie|
301
+ is_expired = (cookie.expires && (cookie.expires < Time.now.gmtime))
302
+ if cookie.use? && !is_expired && cookie.match?(url)
303
+ if cookie_list.select{|c1| c1.name == cookie.name}.empty?
304
+ cookie_list << cookie
305
+ end
306
+ end
307
+ }
308
+ return make_cookie_str(cookie_list)
309
+ end
310
+
311
+ def find_cookie_info(domain, path, name)
312
+ @cookies.find{|c|
313
+ c.domain == domain && c.path == path && c.name == name
314
+ }
315
+ end
316
+ private :find_cookie_info
317
+
318
+ # not tested well; used only netscape_rule = true.
319
+ def cookie_error(err, override)
320
+ if !err.kind_of?(ErrorOverrideOK) || !override
321
+ raise err
322
+ end
323
+ end
324
+ private :cookie_error
325
+
326
+ def add(cookie)
327
+ url = cookie.url
328
+ name, value = cookie.name, cookie.value
329
+ expires, domain, path =
330
+ cookie.expires, cookie.domain, cookie.path
331
+ secure, domain_orig, path_orig =
332
+ cookie.secure?, cookie.domain_orig?, cookie.path_orig?
333
+ discard, override =
334
+ cookie.discard?, cookie.override?
335
+
336
+ domainname = url.host
337
+ domain_orig, path_orig = domain, path
338
+ use_security = override
339
+
340
+ if domain
341
+
342
+ # [DRAFT 12] s. 4.2.2 (does not apply in the case that
343
+ # host name is the same as domain attribute for version 0
344
+ # cookie)
345
+ # I think that this rule has almost the same effect as the
346
+ # tail match of [NETSCAPE].
347
+ if domain !~ /^\./ && domainname != domain
348
+ domain = '.'+domain
349
+ end
350
+
351
+ # [NETSCAPE] rule
352
+ if @netscape_rule
353
+ n = total_dot_num(domain)
354
+ if n < 2
355
+ cookie_error(SpecialError.new(), override)
356
+ elsif n == 2
357
+ ## [NETSCAPE] rule
358
+ ok = SPECIAL_DOMAIN.select{|sdomain|
359
+ sdomain == domain[-(sdomain.length)..-1]
360
+ }
361
+ if ok.empty?
362
+ cookie_error(SpecialError.new(), override)
363
+ end
364
+ end
365
+ end
366
+
367
+ # this implementation does not check RFC2109 4.3.2 case 2;
368
+ # the portion of host not in domain does not contain a dot.
369
+ # according to nsCookieService.cpp in Firefox 3.0.4, Firefox 3.0.4
370
+ # and IE does not check, too.
371
+ end
372
+
373
+ path ||= url.path.sub(%r|/[^/]*|, '')
374
+ domain ||= domainname
375
+ @cookies.synchronize do
376
+ cookie = find_cookie_info(domain, path, name)
377
+ if !cookie
378
+ cookie = WebAgent::Cookie.new()
379
+ cookie.use = true
380
+ @cookies << cookie
381
+ end
382
+ check_expired_cookies()
383
+ end
384
+
385
+ cookie.url = url
386
+ cookie.name = name
387
+ cookie.value = value
388
+ cookie.expires = expires
389
+ cookie.domain = domain
390
+ cookie.path = path
391
+
392
+ ## for flag
393
+ cookie.secure = secure
394
+ cookie.domain_orig = domain_orig
395
+ cookie.path_orig = path_orig
396
+ if discard || cookie.expires == nil
397
+ cookie.discard = true
398
+ else
399
+ cookie.discard = false
400
+ @is_saved = false
401
+ end
402
+ end
403
+
404
+ def load_cookies()
405
+ return if !File.readable?(@cookies_file)
406
+ @cookies.synchronize do
407
+ @cookies.clear
408
+ File.open(@cookies_file,'r'){|f|
409
+ while line = f.gets
410
+ cookie = WebAgent::Cookie.new()
411
+ @cookies << cookie
412
+ col = line.chomp.split(/\t/)
413
+ cookie.url = URI.parse(col[0])
414
+ cookie.name = col[1]
415
+ cookie.value = col[2]
416
+ if col[3].empty? or col[3] == '0'
417
+ cookie.expires = nil
418
+ else
419
+ cookie.expires = Time.at(col[3].to_i).gmtime
420
+ end
421
+ cookie.domain = col[4]
422
+ cookie.path = col[5]
423
+ cookie.set_flag(col[6])
424
+ end
425
+ }
426
+ end
427
+ end
428
+
429
+ def check_cookie_accept_domain(domain)
430
+ unless domain
431
+ return false
432
+ end
433
+ @accept_domains.each{|dom|
434
+ if domain_match(domain, dom)
435
+ return true
436
+ end
437
+ }
438
+ @reject_domains.each{|dom|
439
+ if domain_match(domain, dom)
440
+ return false
441
+ end
442
+ }
443
+ return true
444
+ end
445
+ end
446
+ end
447
+
448
+ __END__
449
+
450
+ =begin
451
+
452
+ == WebAgent::CookieManager Class
453
+
454
+ Load, save, parse and send cookies.
455
+
456
+ === Usage
457
+
458
+ ## initialize
459
+ cm = WebAgent::CookieManager.new("/home/foo/bar/cookie")
460
+
461
+ ## load cookie data
462
+ cm.load_cookies()
463
+
464
+ ## parse cookie from string (maybe "Set-Cookie:" header)
465
+ cm.parse(str)
466
+
467
+ ## send cookie data to url
468
+ f.write(cm.find(url))
469
+
470
+ ## save cookie to cookiefile
471
+ cm.save_cookies()
472
+
473
+
474
+ === Class Methods
475
+
476
+ -- CookieManager::new(file=nil)
477
+
478
+ create new CookieManager. If a file is provided,
479
+ use it as cookies' file.
480
+
481
+ === Methods
482
+
483
+ -- CookieManager#save_cookies(force = nil)
484
+
485
+ save cookies' data into file. if argument is true,
486
+ save data although data is not modified.
487
+
488
+ -- CookieManager#parse(str, url)
489
+
490
+ parse string and store cookie (to parse HTTP response header).
491
+
492
+ -- CookieManager#find(url)
493
+
494
+ get cookies and make into string (to send as HTTP request header).
495
+
496
+ -- CookieManager#add(cookie)
497
+
498
+ add new cookie.
499
+
500
+ -- CookieManager#load_cookies()
501
+
502
+ load cookies' data from file.
503
+
504
+
505
+ == WebAgent::CookieUtils Module
506
+
507
+ -- CookieUtils::head_match?(str1, str2)
508
+ -- CookieUtils::tail_match?(str1, str2)
509
+ -- CookieUtils::domain_match(host, domain)
510
+ -- CookieUtils::total_dot_num(str)
511
+
512
+
513
+ == WebAgent::Cookie Class
514
+
515
+ === Class Methods
516
+
517
+ -- Cookie::new()
518
+
519
+ create new cookie.
520
+
521
+ === Methods
522
+
523
+ -- Cookie#match?(url)
524
+
525
+ match cookie by url. if match, return true. otherwise,
526
+ return false.
527
+
528
+ -- Cookie#name
529
+ -- Cookie#name=(name)
530
+ -- Cookie#value
531
+ -- Cookie#value=(value)
532
+ -- Cookie#domain
533
+ -- Cookie#domain=(domain)
534
+ -- Cookie#path
535
+ -- Cookie#path=(path)
536
+ -- Cookie#expires
537
+ -- Cookie#expires=(expires)
538
+ -- Cookie#url
539
+ -- Cookie#url=(url)
540
+
541
+ accessor methods for cookie's items.
542
+
543
+ -- Cookie#discard?
544
+ -- Cookie#discard=(discard)
545
+ -- Cookie#use?
546
+ -- Cookie#use=(use)
547
+ -- Cookie#secure?
548
+ -- Cookie#secure=(secure)
549
+ -- Cookie#domain_orig?
550
+ -- Cookie#domain_orig=(domain_orig)
551
+ -- Cookie#path_orig?
552
+ -- Cookie#path_orig=(path_orig)
553
+ -- Cookie#override?
554
+ -- Cookie#override=(override)
555
+ -- Cookie#flag
556
+ -- Cookie#set_flag(flag_num)
557
+
558
+ accessor methods for flags.
559
+
560
+ =end