rufus-verbs 0.10 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +64 -0
- data/CREDITS.txt +9 -0
- data/LICENSE.txt +21 -0
- data/README.txt +4 -4
- data/Rakefile +80 -0
- data/doc/rdoc-style.css +320 -0
- data/lib/rufus-verbs.rb +3 -0
- data/lib/rufus/verbs.rb +67 -76
- data/lib/rufus/verbs/conditional.rb +78 -88
- data/lib/rufus/verbs/cookies.rb +214 -224
- data/lib/rufus/verbs/digest.rb +173 -182
- data/lib/rufus/verbs/endpoint.rb +496 -502
- data/lib/rufus/verbs/verbose.rb +52 -61
- data/lib/rufus/verbs/version.rb +6 -16
- data/rufus-verbs.gemspec +87 -0
- data/test/auth0_test.rb +22 -25
- data/test/auth1_test.rb +1 -4
- data/test/base.rb +56 -0
- data/test/block_test.rb +25 -27
- data/test/conditional_test.rb +24 -26
- data/test/cookie0_test.rb +98 -100
- data/test/cookie1_test.rb +28 -30
- data/test/dryrun_test.rb +53 -40
- data/test/escape_test.rb +16 -18
- data/test/fopen_test.rb +31 -33
- data/test/https_test.rb +19 -22
- data/test/iconditional_test.rb +36 -38
- data/test/items.rb +189 -189
- data/test/proxy_test.rb +53 -55
- data/test/redir_test.rb +16 -18
- data/test/simple_test.rb +77 -82
- data/test/test.rb +22 -26
- data/test/timeout_test.rb +15 -19
- data/test/uri_test.rb +12 -14
- metadata +38 -17
- data/test/testbase.rb +0 -47
data/lib/rufus-verbs.rb
ADDED
data/lib/rufus/verbs.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
#
|
2
1
|
#--
|
3
|
-
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
2
|
+
# Copyright (c) 2008-2010, John Mettraux, jmettraux@gmail.com
|
4
3
|
#
|
5
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -20,17 +19,9 @@
|
|
20
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
20
|
# THE SOFTWARE.
|
22
21
|
#
|
23
|
-
#
|
22
|
+
# Made in Japan.
|
24
23
|
#++
|
25
|
-
#
|
26
24
|
|
27
|
-
#
|
28
|
-
# John Mettraux
|
29
|
-
#
|
30
|
-
# Made in Japan
|
31
|
-
#
|
32
|
-
# 2008/01/11
|
33
|
-
#
|
34
25
|
|
35
26
|
require 'rufus/verbs/endpoint'
|
36
27
|
require 'rufus/verbs/conditional'
|
@@ -39,94 +30,94 @@ require 'rufus/verbs/conditional'
|
|
39
30
|
module Rufus::Verbs
|
40
31
|
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
EndPoint.request :get, args
|
48
|
-
end
|
33
|
+
#
|
34
|
+
# GET
|
35
|
+
#
|
36
|
+
def get (*args)
|
49
37
|
|
50
|
-
|
51
|
-
|
52
|
-
#
|
53
|
-
def post (*args, &block)
|
38
|
+
EndPoint.request :get, args
|
39
|
+
end
|
54
40
|
|
55
|
-
|
56
|
-
|
41
|
+
#
|
42
|
+
# POST
|
43
|
+
#
|
44
|
+
def post (*args, &block)
|
57
45
|
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
def put (*args, &block)
|
46
|
+
EndPoint.request :post, args, &block
|
47
|
+
end
|
62
48
|
|
63
|
-
|
64
|
-
|
49
|
+
#
|
50
|
+
# PUT
|
51
|
+
#
|
52
|
+
def put (*args, &block)
|
65
53
|
|
66
|
-
|
67
|
-
|
68
|
-
#
|
69
|
-
def delete (*args)
|
54
|
+
EndPoint.request :put, args, &block
|
55
|
+
end
|
70
56
|
|
71
|
-
|
72
|
-
|
57
|
+
#
|
58
|
+
# DELETE
|
59
|
+
#
|
60
|
+
def delete (*args)
|
73
61
|
|
74
|
-
|
75
|
-
|
76
|
-
#
|
77
|
-
def head (*args)
|
62
|
+
EndPoint.request :delete, args
|
63
|
+
end
|
78
64
|
|
79
|
-
|
80
|
-
|
65
|
+
#
|
66
|
+
# HEAD
|
67
|
+
#
|
68
|
+
def head (*args)
|
81
69
|
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
def options (*args)
|
70
|
+
EndPoint.request :head, args
|
71
|
+
end
|
86
72
|
|
87
|
-
|
88
|
-
|
73
|
+
#
|
74
|
+
# OPTIONS
|
75
|
+
#
|
76
|
+
def options (*args)
|
89
77
|
|
90
|
-
|
91
|
-
|
92
|
-
# a HTTP[S] URI or a file path.
|
93
|
-
#
|
94
|
-
# It is not named open() in order not to collide with File.open and
|
95
|
-
# open-uri's open.
|
96
|
-
#
|
97
|
-
def fopen (uri, *args, &block)
|
78
|
+
EndPoint.request :options, args
|
79
|
+
end
|
98
80
|
|
99
|
-
|
81
|
+
#
|
82
|
+
# Opens a file or a URI (GETs it and return the reply). Will tolerate
|
83
|
+
# a HTTP[S] URI or a file path.
|
84
|
+
#
|
85
|
+
# It is not named open() in order not to collide with File.open and
|
86
|
+
# open-uri's open.
|
87
|
+
#
|
88
|
+
def fopen (uri, *args, &block)
|
100
89
|
|
101
|
-
|
90
|
+
# should I really care about blocks here ? ...
|
102
91
|
|
103
|
-
|
104
|
-
if u.scheme == nil
|
92
|
+
u = URI.parse uri.to_s
|
105
93
|
|
106
|
-
|
107
|
-
|
94
|
+
return File.open(uri.to_s, &block) \
|
95
|
+
if u.scheme == nil
|
108
96
|
|
109
|
-
|
97
|
+
return File.open(uri.to_s[5..-1], &block) \
|
98
|
+
if u.scheme == 'file'
|
110
99
|
|
111
|
-
|
100
|
+
if u.scheme == 'http' or u.scheme == 'https'
|
112
101
|
|
113
|
-
|
114
|
-
block.call r
|
115
|
-
return
|
116
|
-
end
|
102
|
+
r = EndPoint.request(:get, [ uri ] + args) \
|
117
103
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
end unless r.respond_to?(:read)
|
104
|
+
if block
|
105
|
+
block.call r
|
106
|
+
return
|
107
|
+
end
|
123
108
|
|
124
|
-
|
109
|
+
class << r
|
110
|
+
def read
|
111
|
+
self.body
|
125
112
|
end
|
113
|
+
end unless r.respond_to?(:read)
|
126
114
|
|
127
|
-
|
115
|
+
return r
|
128
116
|
end
|
129
117
|
|
130
|
-
|
118
|
+
raise "can't handle scheme '#{u.scheme}' for #{u.to_s}"
|
119
|
+
end
|
120
|
+
|
121
|
+
extend self
|
131
122
|
end
|
132
123
|
|
@@ -1,6 +1,5 @@
|
|
1
|
-
#
|
2
1
|
#--
|
3
|
-
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
2
|
+
# Copyright (c) 2008-2010, John Mettraux, jmettraux@gmail.com
|
4
3
|
#
|
5
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -8,10 +7,10 @@
|
|
8
7
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
8
|
# copies of the Software, and to permit persons to whom the Software is
|
10
9
|
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
10
|
+
#
|
12
11
|
# The above copyright notice and this permission notice shall be included in
|
13
12
|
# all copies or substantial portions of the Software.
|
14
|
-
#
|
13
|
+
#
|
15
14
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
15
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
16
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
@@ -20,120 +19,111 @@
|
|
20
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
20
|
# THE SOFTWARE.
|
22
21
|
#
|
23
|
-
#
|
22
|
+
# Made in Japan.
|
24
23
|
#++
|
25
|
-
#
|
26
24
|
|
27
|
-
#
|
28
|
-
# John Mettraux
|
29
|
-
#
|
30
|
-
# Made in Japan
|
31
|
-
#
|
32
|
-
# 2008/01/16
|
33
|
-
#
|
34
25
|
|
35
|
-
#require 'rubygems'
|
36
26
|
require 'rufus/lru'
|
37
27
|
|
38
28
|
|
39
29
|
module Rufus::Verbs
|
40
30
|
|
31
|
+
#
|
32
|
+
# An EndPoint with a cache for conditional GETs.
|
33
|
+
#
|
34
|
+
# ep = ConditionalEndPoint.new(
|
35
|
+
# :host => "restful.server",
|
36
|
+
# :port => 7080,
|
37
|
+
# :resource => "inventory/tools")
|
38
|
+
#
|
39
|
+
# res = ep.get :id => 1
|
40
|
+
# # first call will retrieve the representation completely
|
41
|
+
#
|
42
|
+
# res = ep.get :id => 1
|
43
|
+
# # the server (provided that it supports conditional GET) only
|
44
|
+
# # returned a 304 answer, the response is returned from the
|
45
|
+
# # ConditionalEndPoint cache
|
46
|
+
#
|
47
|
+
# The :cache_size option allows to set the size of the conditional GET
|
48
|
+
# cache. The default size is currently 147.
|
49
|
+
#
|
50
|
+
class ConditionalEndPoint < EndPoint
|
51
|
+
|
52
|
+
def initialize (opts)
|
53
|
+
|
54
|
+
super
|
55
|
+
|
56
|
+
cs = opts[:cache_size] || 147
|
57
|
+
|
58
|
+
@cache = LruHash.new cs
|
59
|
+
end
|
60
|
+
|
41
61
|
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# ep = ConditionalEndPoint.new(
|
45
|
-
# :host => "restful.server",
|
46
|
-
# :port => 7080,
|
47
|
-
# :resource => "inventory/tools")
|
48
|
-
#
|
49
|
-
# res = ep.get :id => 1
|
50
|
-
# # first call will retrieve the representation completely
|
51
|
-
#
|
52
|
-
# res = ep.get :id => 1
|
53
|
-
# # the server (provided that it supports conditional GET) only
|
54
|
-
# # returned a 304 answer, the response is returned from the
|
55
|
-
# # ConditionalEndPoint cache
|
56
|
-
#
|
57
|
-
# The :cache_size option allows to set the size of the conditional GET
|
58
|
-
# cache. The default size is currently 147.
|
62
|
+
# Returns the count of representation 'cached' here for the
|
63
|
+
# purpose of conditional GET requests.
|
59
64
|
#
|
60
|
-
|
61
|
-
|
62
|
-
def initialize (opts)
|
63
|
-
|
64
|
-
super
|
65
|
+
def cache_current_size
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
@cache = LruHash.new cs
|
69
|
-
end
|
70
|
-
|
71
|
-
#
|
72
|
-
# Returns the count of representation 'cached' here for the
|
73
|
-
# purpose of conditional GET requests.
|
74
|
-
#
|
75
|
-
def cache_current_size
|
76
|
-
|
77
|
-
@cache.size
|
78
|
-
end
|
67
|
+
@cache.size
|
68
|
+
end
|
79
69
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
70
|
+
#
|
71
|
+
# Returns the max size of the conditional GET cache.
|
72
|
+
#
|
73
|
+
def cache_size
|
84
74
|
|
85
|
-
|
86
|
-
|
75
|
+
@cache.maxsize
|
76
|
+
end
|
87
77
|
|
88
|
-
|
78
|
+
private
|
89
79
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
80
|
+
#
|
81
|
+
# If the representation has already been gotten, send
|
82
|
+
# potential If-Modified-Since and/or If-None-Match.
|
83
|
+
#
|
84
|
+
def add_conditional_headers (req, opts)
|
95
85
|
|
96
|
-
|
86
|
+
# if path is cached send since and/or match
|
97
87
|
|
98
|
-
|
88
|
+
e = @cache[opts[:c_uri]]
|
99
89
|
|
100
|
-
|
90
|
+
return unless e # not cached
|
101
91
|
|
102
|
-
|
103
|
-
|
92
|
+
req['If-Modified-Since'] = e.lastmod if e.lastmod
|
93
|
+
req['If-None-Match'] = e.etag if e.etag
|
104
94
|
|
105
|
-
|
106
|
-
|
95
|
+
opts[:c_cached] = e
|
96
|
+
end
|
107
97
|
|
108
|
-
|
98
|
+
def handle_response (method, res, opts)
|
109
99
|
|
110
|
-
|
111
|
-
|
100
|
+
# if method is get and reply is 200, cache (if et and/or lm)
|
101
|
+
# if method is get and reply is 304, return from cache
|
112
102
|
|
113
|
-
|
103
|
+
super
|
114
104
|
|
115
|
-
|
105
|
+
code = res.code.to_i
|
116
106
|
|
117
|
-
|
107
|
+
return opts[:c_cached] if code == 304
|
118
108
|
|
119
|
-
|
109
|
+
cache(res, opts) if code == 200
|
120
110
|
|
121
|
-
|
122
|
-
|
111
|
+
res
|
112
|
+
end
|
123
113
|
|
124
|
-
|
114
|
+
def cache (res, opts)
|
125
115
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
116
|
+
class << res
|
117
|
+
def lastmod
|
118
|
+
self['Last-Modified']
|
119
|
+
end
|
120
|
+
def etag
|
121
|
+
self['Etag']
|
122
|
+
end
|
123
|
+
end
|
134
124
|
|
135
|
-
|
136
|
-
end
|
125
|
+
@cache[opts[:c_uri]] = res
|
137
126
|
end
|
127
|
+
end
|
138
128
|
end
|
139
129
|
|
data/lib/rufus/verbs/cookies.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
#
|
2
1
|
#--
|
3
|
-
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
2
|
+
# Copyright (c) 2008-2010, John Mettraux, jmettraux@gmail.com
|
4
3
|
#
|
5
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -8,10 +7,10 @@
|
|
8
7
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
8
|
# copies of the Software, and to permit persons to whom the Software is
|
10
9
|
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
10
|
+
#
|
12
11
|
# The above copyright notice and this permission notice shall be included in
|
13
12
|
# all copies or substantial portions of the Software.
|
14
|
-
#
|
13
|
+
#
|
15
14
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
15
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
16
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
@@ -20,308 +19,299 @@
|
|
20
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
20
|
# THE SOFTWARE.
|
22
21
|
#
|
23
|
-
#
|
22
|
+
# Made in Japan.
|
24
23
|
#++
|
25
|
-
#
|
26
24
|
|
27
|
-
#
|
28
|
-
# John Mettraux
|
29
|
-
#
|
30
|
-
# Made in Japan
|
31
|
-
#
|
32
|
-
# 2008/01/19
|
33
|
-
#
|
34
25
|
|
35
26
|
require 'webrick/cookie'
|
36
27
|
|
37
|
-
#require 'rubygems'
|
38
28
|
require 'rufus/lru'
|
39
29
|
|
40
30
|
|
41
31
|
module Rufus
|
42
32
|
module Verbs
|
43
33
|
|
34
|
+
#
|
35
|
+
# Cookies related methods
|
36
|
+
#
|
37
|
+
# http://www.ietf.org/rfc/rfc2109.txt
|
38
|
+
#
|
39
|
+
module CookieMixin
|
40
|
+
|
44
41
|
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# http://www.ietf.org/rfc/rfc2109.txt
|
42
|
+
# making the cookie jar available
|
48
43
|
#
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
# making the cookie jar available
|
53
|
-
#
|
54
|
-
attr_reader :cookies
|
44
|
+
attr_reader :cookies
|
55
45
|
|
56
|
-
|
46
|
+
protected
|
57
47
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
48
|
+
#
|
49
|
+
# Prepares the instance variable @cookies for storing
|
50
|
+
# cooking for this endpoint.
|
51
|
+
#
|
52
|
+
# Reads the :cookies endpoint option for determining the
|
53
|
+
# size of the cookie jar (77 by default).
|
54
|
+
#
|
55
|
+
def prepare_cookie_jar
|
66
56
|
|
67
|
-
|
57
|
+
o = @opts[:cookies]
|
68
58
|
|
69
|
-
|
59
|
+
return unless o and o != false
|
70
60
|
|
71
|
-
|
72
|
-
|
61
|
+
s = o.to_s.to_i
|
62
|
+
s = 77 if s < 1
|
73
63
|
|
74
|
-
|
75
|
-
|
64
|
+
@cookies = CookieJar.new s
|
65
|
+
end
|
76
66
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
67
|
+
#
|
68
|
+
# Parses the HTTP response for a potential 'Set-Cookie' header,
|
69
|
+
# parses and returns it as a hash.
|
70
|
+
#
|
71
|
+
def parse_cookies (response)
|
82
72
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
73
|
+
c = response['Set-Cookie']
|
74
|
+
return nil unless c
|
75
|
+
Cookie.parse_set_cookies c
|
76
|
+
end
|
87
77
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
78
|
+
#
|
79
|
+
# (This method will have no effect if the EndPoint is not
|
80
|
+
# tracking cookies)
|
81
|
+
#
|
82
|
+
# Registers a potential cookie set by the server.
|
83
|
+
#
|
84
|
+
def register_cookies (response, opts)
|
95
85
|
|
96
|
-
|
86
|
+
return unless @cookies
|
97
87
|
|
98
|
-
|
88
|
+
cs = parse_cookies response
|
99
89
|
|
100
|
-
|
90
|
+
return unless cs
|
101
91
|
|
102
|
-
|
103
|
-
|
92
|
+
# "The origin server effectively ends a session by
|
93
|
+
# sending the client a Set-Cookie header with Max-Age=0"
|
104
94
|
|
105
|
-
|
95
|
+
cs.each do |c|
|
106
96
|
|
107
|
-
|
108
|
-
|
109
|
-
|
97
|
+
host = opts[:host]
|
98
|
+
path = opts[:path]
|
99
|
+
cpath = c.path || "/"
|
110
100
|
|
111
|
-
|
101
|
+
next unless cookie_acceptable?(opts, response, c)
|
112
102
|
|
113
|
-
|
103
|
+
domain = c.domain || host
|
114
104
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
105
|
+
if c.max_age == 0
|
106
|
+
@cookies.remove_cookie domain, path, c
|
107
|
+
else
|
108
|
+
@cookies.add_cookie domain, path, c
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
122
112
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
113
|
+
#
|
114
|
+
# Checks if the cookie is acceptable in the context of
|
115
|
+
# the request that sent it.
|
116
|
+
#
|
117
|
+
def cookie_acceptable? (opts, response, cookie)
|
128
118
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
119
|
+
# reject if :
|
120
|
+
#
|
121
|
+
# * The value for the Path attribute is not a prefix of the
|
122
|
+
# request-URI.
|
123
|
+
# * The value for the Domain attribute contains no embedded dots
|
124
|
+
# or does not start with a dot.
|
125
|
+
# * The value for the request-host does not domain-match the
|
126
|
+
# Domain attribute.
|
127
|
+
# * The request-host is a FQDN (not IP address) and has the form
|
128
|
+
# HD, where D is the value of the Domain attribute, and H is a
|
129
|
+
# string that contains one or more dots.
|
140
130
|
|
141
|
-
|
131
|
+
cdomain = cookie.domain
|
142
132
|
|
143
|
-
|
133
|
+
if cdomain
|
144
134
|
|
145
|
-
|
146
|
-
|
135
|
+
return false unless cdomain.index '.'
|
136
|
+
return false if cdomain[0, 1] != '.'
|
147
137
|
|
148
|
-
|
149
|
-
|
150
|
-
|
138
|
+
h, d = split_host(opts[:host])
|
139
|
+
return false if d != cdomain
|
140
|
+
end
|
151
141
|
|
152
|
-
|
153
|
-
|
142
|
+
#path = opts[:path]
|
143
|
+
path = response.request.path
|
154
144
|
|
155
|
-
|
145
|
+
cpath = cookie.path || "/"
|
156
146
|
|
157
|
-
|
147
|
+
return false if path[0..cpath.length-1] != cpath
|
158
148
|
|
159
|
-
|
160
|
-
|
149
|
+
true
|
150
|
+
end
|
161
151
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
152
|
+
#
|
153
|
+
# Places the 'Cookie' header in the request if appropriate.
|
154
|
+
#
|
155
|
+
# (This method will have no effect if the EndPoint is not
|
156
|
+
# tracking cookies)
|
157
|
+
#
|
158
|
+
def mention_cookies (request, opts)
|
169
159
|
|
170
|
-
|
160
|
+
return unless @cookies
|
171
161
|
|
172
|
-
|
162
|
+
cs = @cookies.fetch_cookies opts[:host], opts[:path]
|
173
163
|
|
174
|
-
|
175
|
-
|
164
|
+
request['Cookie'] = cs.collect { |c| c.to_header_s }.join(",")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# An extension of the cookie implementation found in WEBrick.
|
170
|
+
#
|
171
|
+
# Unmodified for now.
|
172
|
+
#
|
173
|
+
class Cookie < WEBrick::Cookie
|
174
|
+
|
175
|
+
def to_header_s
|
176
|
+
|
177
|
+
ret = ''
|
178
|
+
ret << @name << '=' << @value
|
179
|
+
ret << '; ' << '$Version=' << @version.to_s if @version > 0
|
180
|
+
ret << '; ' << '$Domain=' << @domain if @domain
|
181
|
+
ret << '; ' << '$Port=' << @port if @port
|
182
|
+
ret << '; ' << '$Path=' << @path if @path
|
183
|
+
ret
|
176
184
|
end
|
185
|
+
end
|
177
186
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
class Cookie < WEBrick::Cookie
|
187
|
+
#
|
188
|
+
# Cookies are stored by domain, they via this CookieKey which gathers
|
189
|
+
# path and name of the cookie.
|
190
|
+
#
|
191
|
+
class CookieKey
|
184
192
|
|
185
|
-
|
193
|
+
attr_reader :name, :path
|
186
194
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
ret << "; " << "$Port=" << @port if @port
|
192
|
-
ret << "; " << "$Path=" << @path if @path
|
193
|
-
ret
|
194
|
-
end
|
195
|
+
def initialize (path, cookie)
|
196
|
+
|
197
|
+
@name = cookie.name
|
198
|
+
@path = path || cookie.path
|
195
199
|
end
|
196
200
|
|
197
201
|
#
|
198
|
-
#
|
199
|
-
# path and name of the cookie.
|
202
|
+
# longer paths first
|
200
203
|
#
|
201
|
-
|
202
|
-
|
203
|
-
attr_reader :name, :path
|
204
|
+
def <=> (other)
|
204
205
|
|
205
|
-
|
206
|
+
-1 * (@path <=> other.path)
|
207
|
+
end
|
206
208
|
|
207
|
-
|
208
|
-
|
209
|
-
|
209
|
+
def hash
|
210
|
+
"#{@name}|#{@path}".hash
|
211
|
+
end
|
210
212
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
def <=> (other)
|
213
|
+
def == (other)
|
214
|
+
(@path == other.path and @name == other.name)
|
215
|
+
end
|
215
216
|
|
216
|
-
|
217
|
-
|
217
|
+
alias eql? ==
|
218
|
+
end
|
218
219
|
|
219
|
-
|
220
|
-
|
221
|
-
|
220
|
+
#
|
221
|
+
# A few methods about hostnames.
|
222
|
+
#
|
223
|
+
# (in a mixin... could be helpful somewhere else later)
|
224
|
+
#
|
225
|
+
module HostMixin
|
222
226
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
227
|
+
#
|
228
|
+
# Matching a classical IP address (not a v6 though).
|
229
|
+
# Should be sufficient for now.
|
230
|
+
#
|
231
|
+
IP_REGEX = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
229
232
|
|
230
233
|
#
|
231
|
-
#
|
234
|
+
# Returns a pair host/domain, note that the domain starts with a dot.
|
232
235
|
#
|
233
|
-
#
|
236
|
+
# split_host('localhost') --> [ 'localhost', nil ]
|
237
|
+
# split_host('benz.car.co.nz') --> [ 'benz', '.car.co.nz' ]
|
238
|
+
# split_host('127.0.0.1') --> [ '127.0.0.1', nil ]
|
239
|
+
# split_host('::1') --> [ '::1', nil ]
|
234
240
|
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
IP_REGEX = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
242
|
-
|
243
|
-
#
|
244
|
-
# Returns a pair host/domain, note that the domain starts with a dot.
|
245
|
-
#
|
246
|
-
# split_host('localhost') --> [ 'localhost', nil ]
|
247
|
-
# split_host('benz.car.co.nz') --> [ 'benz', '.car.co.nz' ]
|
248
|
-
# split_host('127.0.0.1') --> [ '127.0.0.1', nil ]
|
249
|
-
# split_host('::1') --> [ '::1', nil ]
|
250
|
-
#
|
251
|
-
def split_host (host)
|
252
|
-
|
253
|
-
return [ host, nil ] if IP_REGEX.match host
|
254
|
-
i = host.index('.')
|
255
|
-
return [ host, nil ] unless i
|
256
|
-
[ host[0..i-1], host[i..-1] ]
|
257
|
-
end
|
241
|
+
def split_host (host)
|
242
|
+
|
243
|
+
return [ host, nil ] if IP_REGEX.match host
|
244
|
+
i = host.index('.')
|
245
|
+
return [ host, nil ] unless i
|
246
|
+
[ host[0..i-1], host[i..-1] ]
|
258
247
|
end
|
248
|
+
end
|
259
249
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
250
|
+
#
|
251
|
+
# The container for cookies. Features methods for storing and retrieving
|
252
|
+
# cookies easily.
|
253
|
+
#
|
254
|
+
class CookieJar
|
255
|
+
include HostMixin
|
266
256
|
|
267
|
-
|
257
|
+
def initialize (jar_size)
|
268
258
|
|
269
|
-
|
270
|
-
|
259
|
+
@per_domain = LruHash.new jar_size
|
260
|
+
end
|
271
261
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
262
|
+
#
|
263
|
+
# Returns the count of cookies currently stored in this jar.
|
264
|
+
#
|
265
|
+
def size
|
276
266
|
|
277
|
-
|
278
|
-
|
267
|
+
@per_domain.keys.inject(0) { |i, d| i + @per_domain[d].size }
|
268
|
+
end
|
279
269
|
|
280
|
-
|
270
|
+
def add_cookie (domain, path, cookie)
|
281
271
|
|
282
|
-
|
283
|
-
|
272
|
+
(@per_domain[domain] ||= {})[CookieKey.new(path, cookie)] = cookie
|
273
|
+
end
|
284
274
|
|
285
|
-
|
275
|
+
def remove_cookie (domain, path, cookie)
|
286
276
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
277
|
+
(d = @per_domain[domain])
|
278
|
+
return unless d
|
279
|
+
d.delete CookieKey.new(path, cookie)
|
280
|
+
end
|
291
281
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
282
|
+
#
|
283
|
+
# Retrieves the cookies that matches the combination host/path.
|
284
|
+
# If the retrieved cookie is expired, will remove it from the jar
|
285
|
+
# and return nil.
|
286
|
+
#
|
287
|
+
def fetch_cookies (host, path)
|
298
288
|
|
299
|
-
|
289
|
+
c = do_fetch(@per_domain[host], path)
|
300
290
|
|
301
|
-
|
302
|
-
|
291
|
+
h, d = split_host host
|
292
|
+
c += do_fetch(@per_domain[d], path) if d
|
303
293
|
|
304
|
-
|
305
|
-
|
294
|
+
c
|
295
|
+
end
|
306
296
|
|
307
|
-
|
297
|
+
private
|
308
298
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
299
|
+
#
|
300
|
+
# Returns all the cookies that match a domain (host) and a path.
|
301
|
+
#
|
302
|
+
def do_fetch (dh, path)
|
313
303
|
|
314
|
-
|
304
|
+
return [] unless dh
|
315
305
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
end
|
306
|
+
keys = dh.keys.sort.find_all do |k|
|
307
|
+
path[0..k.path.length-1] == k.path
|
308
|
+
end
|
309
|
+
keys.inject([]) do |r, k|
|
310
|
+
r << dh[k]
|
311
|
+
r
|
312
|
+
end
|
324
313
|
end
|
314
|
+
end
|
325
315
|
end
|
326
316
|
end
|
327
317
|
|