bterlson-httpclient 2.1.4

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.
@@ -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