rufus-verbs 0.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +37 -7
- data/lib/rufus/verbs/conditional.rb +4 -7
- data/lib/rufus/verbs/cookies.rb +320 -0
- data/lib/rufus/verbs/digest.rb +261 -0
- data/lib/rufus/verbs/endpoint.rb +40 -8
- data/lib/rufus/verbs/version.rb +45 -0
- data/test/{auth_test.rb → auth0_test.rb} +2 -2
- data/test/auth1_test.rb +68 -0
- data/test/cookie0_test.rb +122 -0
- data/test/cookie1_test.rb +60 -0
- data/test/items.rb +61 -6
- data/test/test.htdigest +1 -0
- data/test/test.rb +7 -1
- metadata +10 -3
data/README.txt
CHANGED
@@ -21,14 +21,16 @@ currently :
|
|
21
21
|
* proxy-aware (HTTP_PROXY env var or :proxy option)
|
22
22
|
* conditional GET (via ConditionalEndPoint class)
|
23
23
|
* request body built via a block (post and put)
|
24
|
+
* cookie-aware (if :cookies option is explicitely set to true)
|
25
|
+
* http digest authentication (rfc 2617) (auth ok, auth-int not tested)
|
24
26
|
|
25
27
|
maybe later :
|
26
28
|
|
27
29
|
* retry on failure
|
28
|
-
* greediness (automatic parsing for content like JSON or YAML)
|
29
|
-
* http digest authentication
|
30
30
|
* cache awareness
|
31
|
+
* greediness (automatic parsing for content like JSON or YAML)
|
31
32
|
* head, options
|
33
|
+
* persistent cookie jar
|
32
34
|
|
33
35
|
|
34
36
|
== getting it
|
@@ -105,12 +107,29 @@ A ConditionalEndPoint is an EndPoint that will use conditional GETs whenever pos
|
|
105
107
|
# first call will retrieve the representation completely
|
106
108
|
|
107
109
|
res = ep.get :id => 1
|
108
|
-
# the server (provided that it supports conditional
|
110
|
+
# the server (provided that it supports conditional GETs) only
|
109
111
|
# returned a 304 answer, the response is returned from the
|
110
112
|
# ConditionalEndPoint cache
|
111
113
|
|
112
114
|
More about conditional GETs at http://ruturajv.wordpress.com/2005/12/27/conditional-get-request/
|
113
115
|
|
116
|
+
Cookies may be activated for an endpoint in this way :
|
117
|
+
|
118
|
+
ep = EndPoint.new :cookies => true
|
119
|
+
|
120
|
+
res = ep.get "http://resta.farian.zion/tools/3"
|
121
|
+
res = ep.post("http://resta.farian.zion/tools") { "hammer" }
|
122
|
+
|
123
|
+
Digest authentication and basic authentication are available at request or endpoint level :
|
124
|
+
|
125
|
+
ep = EndPoint.new :http_basic_authentication => [ "toto", "secretpass" ]
|
126
|
+
|
127
|
+
ep = EndPoint.new :digest_authentication => [ "toto", "secretpass" ]
|
128
|
+
|
129
|
+
res = get "http://server/doc0", :hba => [ "toto", "secretpass" ]
|
130
|
+
|
131
|
+
res = get "http://server/doc1", :digest_authentication => [ "toto", "secretpass" ]
|
132
|
+
|
114
133
|
The tests may provide good intel as on 'rufus-verbs' usage as well : http://rufus.rubyforge.org/svn/trunk/verbs/test/
|
115
134
|
|
116
135
|
|
@@ -145,12 +164,19 @@ by default, a request returns a Net::HTTPResponse instance, with :body => true,
|
|
145
164
|
* <b>:cache_size</b> (integer, ConditionalEndPoint only)
|
146
165
|
by default, a ConditionalEndPoint will cache 147 results (and it'll start discarded the least recently used cached responses). This option is used to set this cache's size.
|
147
166
|
|
167
|
+
* <b>:cookies</b> (boolean/integer, E)
|
168
|
+
when not set or set to false, rufus-verbs won't care about cookies. If set to true or an integer value, set-cookie requests by the servers will be honoured. The integer value will be interpreted as the size of the 'cookie jar', the default size being 77. The least recently used cookies will be discarded.
|
169
|
+
The cookie jar implementation is not persistent.
|
170
|
+
|
148
171
|
* <b>:d</b> (string, R)
|
149
172
|
the short variant of :data
|
150
173
|
|
151
174
|
* <b>:data</b> (string, R)
|
152
175
|
the data (request body) for a put or a post.
|
153
176
|
|
177
|
+
* <b>:digest_authentication</b> (pair of strings, RE)
|
178
|
+
the pair username/password to be used for digest authentication.
|
179
|
+
|
154
180
|
* <b>:dry_run</b> (boolean, RE)
|
155
181
|
when <tt>:dry_run => true</tt>, the request will be prepared but not executed and will be returned instead of the HTTP response (used for testing)
|
156
182
|
|
@@ -163,13 +189,13 @@ the short version of :form_data
|
|
163
189
|
* <b>:form_data</b>
|
164
190
|
this option expects a hash. The (post or put) request body will then be built with this hash.
|
165
191
|
|
166
|
-
* <b>:hba</b> (pair, RE)
|
192
|
+
* <b>:hba</b> (pair of strings, RE)
|
167
193
|
short for :http_basic_authentication
|
168
194
|
|
169
195
|
* <b>:host</b> (string, RE)
|
170
196
|
the host or IP address for the request
|
171
197
|
|
172
|
-
* <b>:http_basic_authentication</b> (pair, RE)
|
198
|
+
* <b>:http_basic_authentication</b> (pair of strings, RE)
|
173
199
|
will activate HTTP basic authentication, takes a pair (array) argument [ user, pass ]
|
174
200
|
|
175
201
|
* <b>:id</b> (string, R)
|
@@ -205,6 +231,9 @@ the resource (or the middle) of a full resource path (see :base)
|
|
205
231
|
* <b>:scheme</b> (string, R)
|
206
232
|
'http' or 'https'
|
207
233
|
|
234
|
+
* <b>:ssl_verify_peer</b> (boolean, RE)
|
235
|
+
by default, rufus-verbs doesn't verify ssl certificates. With this option set to true, it will.
|
236
|
+
|
208
237
|
* <b>:u</b> (uri, string, RE)
|
209
238
|
the short version of :uri
|
210
239
|
|
@@ -222,9 +251,10 @@ the gem rufus-lru[http://rufus.rubyforge.org/rufus-lru]
|
|
222
251
|
|
223
252
|
== mailing list
|
224
253
|
|
225
|
-
On the
|
254
|
+
On the Rufus-Ruby list[http://groups.google.com/group/rufus-ruby] :
|
255
|
+
|
256
|
+
http://groups.google.com/group/rufus-ruby
|
226
257
|
|
227
|
-
http://groups.google.com/group/openwferu-users
|
228
258
|
|
229
259
|
== issue tracker
|
230
260
|
|
@@ -32,6 +32,10 @@
|
|
32
32
|
# 2008/01/16
|
33
33
|
#
|
34
34
|
|
35
|
+
require 'rubygems'
|
36
|
+
require 'rufus/lru'
|
37
|
+
|
38
|
+
|
35
39
|
module Rufus::Verbs
|
36
40
|
|
37
41
|
#
|
@@ -61,13 +65,6 @@ module Rufus::Verbs
|
|
61
65
|
|
62
66
|
cs = opts[:cache_size] || 147
|
63
67
|
|
64
|
-
require 'rubygems'
|
65
|
-
begin
|
66
|
-
require 'rufus/lru'
|
67
|
-
rescue LoadError
|
68
|
-
raise "gem 'rufus-lru' is missing, please install it."
|
69
|
-
end
|
70
|
-
|
71
68
|
@cache = LruHash.new cs
|
72
69
|
end
|
73
70
|
|
@@ -0,0 +1,320 @@
|
|
1
|
+
#
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
#
|
23
|
+
# (MIT license)
|
24
|
+
#++
|
25
|
+
#
|
26
|
+
|
27
|
+
#
|
28
|
+
# John Mettraux
|
29
|
+
#
|
30
|
+
# Made in Japan
|
31
|
+
#
|
32
|
+
# 2008/01/19
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'webrick/cookie'
|
36
|
+
|
37
|
+
require 'rubygems'
|
38
|
+
require 'rufus/lru'
|
39
|
+
|
40
|
+
|
41
|
+
module Rufus
|
42
|
+
module Verbs
|
43
|
+
|
44
|
+
#
|
45
|
+
# Cookies related methods
|
46
|
+
#
|
47
|
+
# http://www.ietf.org/rfc/rfc2109.txt
|
48
|
+
#
|
49
|
+
module CookieMixin
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
#
|
54
|
+
# Prepares the instance variable @cookies for storing
|
55
|
+
# cooking for this endpoint.
|
56
|
+
#
|
57
|
+
# Reads the :cookies endpoint option for determining the
|
58
|
+
# size of the cookie jar (77 by default).
|
59
|
+
#
|
60
|
+
def prepare_cookie_jar
|
61
|
+
|
62
|
+
o = @opts[:cookies]
|
63
|
+
|
64
|
+
return unless o and o != false
|
65
|
+
|
66
|
+
s = o.to_s.to_i
|
67
|
+
s = 77 if s < 1
|
68
|
+
|
69
|
+
@cookies = CookieJar.new s
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Parses the HTTP response for a potential 'Set-Cookie' header,
|
74
|
+
# parses and returns it as a hash.
|
75
|
+
#
|
76
|
+
def parse_cookies (response)
|
77
|
+
|
78
|
+
c = response['Set-Cookie']
|
79
|
+
return nil unless c
|
80
|
+
Cookie.parse_set_cookies c
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# (This method will have no effect if the EndPoint is not
|
85
|
+
# tracking cookies)
|
86
|
+
#
|
87
|
+
# Registers a potential cookie set by the server.
|
88
|
+
#
|
89
|
+
def register_cookies (response, opts)
|
90
|
+
|
91
|
+
return unless @cookies
|
92
|
+
|
93
|
+
cs = parse_cookies response
|
94
|
+
|
95
|
+
return unless cs
|
96
|
+
|
97
|
+
# "The origin server effectively ends a session by
|
98
|
+
# sending the client a Set-Cookie header with Max-Age=0"
|
99
|
+
|
100
|
+
cs.each do |c|
|
101
|
+
|
102
|
+
host = opts[:host]
|
103
|
+
path = opts[:path]
|
104
|
+
cpath = c.path || "/"
|
105
|
+
|
106
|
+
next unless cookie_acceptable?(opts, c)
|
107
|
+
|
108
|
+
domain = c.domain || host
|
109
|
+
|
110
|
+
if c.max_age == 0
|
111
|
+
@cookies.remove_cookie domain, path, c
|
112
|
+
else
|
113
|
+
@cookies.add_cookie domain, path, c
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Checks if the cookie is acceptable in the context of
|
120
|
+
# the request that sent it.
|
121
|
+
#
|
122
|
+
def cookie_acceptable? (opts, cookie)
|
123
|
+
|
124
|
+
# reject if :
|
125
|
+
#
|
126
|
+
# * The value for the Path attribute is not a prefix of the
|
127
|
+
# request-URI.
|
128
|
+
# * The value for the Domain attribute contains no embedded dots
|
129
|
+
# or does not start with a dot.
|
130
|
+
# * The value for the request-host does not domain-match the
|
131
|
+
# Domain attribute.
|
132
|
+
# * The request-host is a FQDN (not IP address) and has the form
|
133
|
+
# HD, where D is the value of the Domain attribute, and H is a
|
134
|
+
# string that contains one or more dots.
|
135
|
+
|
136
|
+
cdomain = cookie.domain
|
137
|
+
|
138
|
+
if cdomain
|
139
|
+
|
140
|
+
return false unless cdomain.index '.'
|
141
|
+
return false if cdomain[0, 1] != '.'
|
142
|
+
|
143
|
+
h, d = split_host(opts[:host])
|
144
|
+
return false if d != cdomain
|
145
|
+
end
|
146
|
+
|
147
|
+
path = opts[:path]
|
148
|
+
cpath = cookie.path || "/"
|
149
|
+
|
150
|
+
return false if path[0..cpath.length-1] != cpath
|
151
|
+
|
152
|
+
true
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Places the 'Cookie' header in the request if appropriate.
|
157
|
+
#
|
158
|
+
# (This method will have no effect if the EndPoint is not
|
159
|
+
# tracking cookies)
|
160
|
+
#
|
161
|
+
def mention_cookies (request, opts)
|
162
|
+
|
163
|
+
return unless @cookies
|
164
|
+
|
165
|
+
cs = @cookies.fetch_cookies opts[:host], opts[:path]
|
166
|
+
|
167
|
+
request['Cookie'] = cs.collect { |c| c.to_header_s }.join(",")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# An extension of the cookie implementation found in WEBrick.
|
173
|
+
#
|
174
|
+
# Unmodified for now.
|
175
|
+
#
|
176
|
+
class Cookie < WEBrick::Cookie
|
177
|
+
|
178
|
+
def to_header_s
|
179
|
+
|
180
|
+
ret = ""
|
181
|
+
ret << @name << "=" << @value
|
182
|
+
ret << "; " << "$Version=" << @version.to_s if @version > 0
|
183
|
+
ret << "; " << "$Domain=" << @domain if @domain
|
184
|
+
ret << "; " << "$Port=" << @port if @port
|
185
|
+
ret << "; " << "$Path=" << @path if @path
|
186
|
+
ret
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Cookies are stored by domain, they via this CookieKey which gathers
|
192
|
+
# path and name of the cookie.
|
193
|
+
#
|
194
|
+
class CookieKey
|
195
|
+
|
196
|
+
attr_reader :name, :path
|
197
|
+
|
198
|
+
def initialize (path, cookie)
|
199
|
+
|
200
|
+
@name = cookie.name
|
201
|
+
@path = path || cookie.path
|
202
|
+
end
|
203
|
+
|
204
|
+
#
|
205
|
+
# longer paths first
|
206
|
+
#
|
207
|
+
def <=> (other)
|
208
|
+
|
209
|
+
-1 * (@path <=> other.path)
|
210
|
+
end
|
211
|
+
|
212
|
+
def hash
|
213
|
+
"#{@name}|#{@path}".hash
|
214
|
+
end
|
215
|
+
|
216
|
+
def == (other)
|
217
|
+
(@path == other.path and @name == other.name)
|
218
|
+
end
|
219
|
+
|
220
|
+
alias eql? ==
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# A few methods about hostnames.
|
225
|
+
#
|
226
|
+
# (in a mixin... could be helpful somewhere else later)
|
227
|
+
#
|
228
|
+
module HostMixin
|
229
|
+
|
230
|
+
#
|
231
|
+
# Matching a classical IP address (not a v6 though).
|
232
|
+
# Should be sufficient for now.
|
233
|
+
#
|
234
|
+
IP_REGEX = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
235
|
+
|
236
|
+
#
|
237
|
+
# Returns a pair host/domain, note that the domain starts with a dot.
|
238
|
+
#
|
239
|
+
# split_host('localhost') --> [ 'localhost', nil ]
|
240
|
+
# split_host('benz.car.co.nz') --> [ 'benz', '.car.co.nz' ]
|
241
|
+
# split_host('127.0.0.1') --> [ '127.0.0.1', nil ]
|
242
|
+
# split_host('::1') --> [ '::1', nil ]
|
243
|
+
#
|
244
|
+
def split_host (host)
|
245
|
+
|
246
|
+
return [ host, nil ] if IP_REGEX.match host
|
247
|
+
i = host.index('.')
|
248
|
+
return [ host, nil ] unless i
|
249
|
+
[ host[0..i-1], host[i..-1] ]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# The container for cookies. Features methods for storing and retrieving
|
255
|
+
# cookies easily.
|
256
|
+
#
|
257
|
+
class CookieJar
|
258
|
+
include HostMixin
|
259
|
+
|
260
|
+
def initialize (jar_size)
|
261
|
+
|
262
|
+
@per_domain = LruHash.new jar_size
|
263
|
+
end
|
264
|
+
|
265
|
+
#
|
266
|
+
# Returns the count of cookies currently stored in this jar.
|
267
|
+
#
|
268
|
+
def size
|
269
|
+
|
270
|
+
@per_domain.keys.inject(0) { |i, d| i + @per_domain[d].size }
|
271
|
+
end
|
272
|
+
|
273
|
+
def add_cookie (domain, path, cookie)
|
274
|
+
|
275
|
+
(@per_domain[domain] ||= {})[CookieKey.new(path, cookie)] = cookie
|
276
|
+
end
|
277
|
+
|
278
|
+
def remove_cookie (domain, path, cookie)
|
279
|
+
|
280
|
+
(d = @per_domain[domain])
|
281
|
+
return unless d
|
282
|
+
d.delete CookieKey.new(path, cookie)
|
283
|
+
end
|
284
|
+
|
285
|
+
#
|
286
|
+
# Retrieves the cookies that matches the combination host/path.
|
287
|
+
# If the retrieved cookie is expired, will remove it from the jar
|
288
|
+
# and return nil.
|
289
|
+
#
|
290
|
+
def fetch_cookies (host, path)
|
291
|
+
|
292
|
+
c = do_fetch(@per_domain[host], path)
|
293
|
+
|
294
|
+
h, d = split_host host
|
295
|
+
c += do_fetch(@per_domain[d], path) if d
|
296
|
+
|
297
|
+
c
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
#
|
303
|
+
# Returns all the cookies that match a domain (host) and a path.
|
304
|
+
#
|
305
|
+
def do_fetch (dh, path)
|
306
|
+
|
307
|
+
return [] unless dh
|
308
|
+
|
309
|
+
keys = dh.keys.sort.find_all do |k|
|
310
|
+
path[0..k.path.length-1] == k.path
|
311
|
+
end
|
312
|
+
keys.inject([]) do |r, k|
|
313
|
+
r << dh[k]
|
314
|
+
r
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
@@ -0,0 +1,261 @@
|
|
1
|
+
#
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
#
|
23
|
+
# (MIT license)
|
24
|
+
#++
|
25
|
+
#
|
26
|
+
|
27
|
+
#
|
28
|
+
# John Mettraux
|
29
|
+
#
|
30
|
+
# Made in Japan
|
31
|
+
#
|
32
|
+
# 2008/01/21
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'digest/md5'
|
36
|
+
|
37
|
+
|
38
|
+
module Rufus
|
39
|
+
module Verbs
|
40
|
+
|
41
|
+
#
|
42
|
+
# Specified by http://www.ietf.org/rfc/rfc2617.txt
|
43
|
+
# Inspired by http://segment7.net/projects/ruby/snippets/digest_auth.rb
|
44
|
+
#
|
45
|
+
# The EndPoint classes mixes this in to support digest authentication.
|
46
|
+
#
|
47
|
+
module DigestAuthMixin
|
48
|
+
|
49
|
+
#
|
50
|
+
# Makes sure digest_auth is on
|
51
|
+
#
|
52
|
+
def digest_auth (req, opts)
|
53
|
+
|
54
|
+
#return if no_digest_auth
|
55
|
+
# already done in add_authentication()
|
56
|
+
|
57
|
+
@cnonce ||= generate_cnonce
|
58
|
+
@nonce_count ||= 0
|
59
|
+
|
60
|
+
mention_digest_auth(req, opts) \
|
61
|
+
and return
|
62
|
+
|
63
|
+
mention_digest_auth(req, opts) \
|
64
|
+
if request_challenge(req, opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Sets the 'Authorization' header with the appropriate info.
|
69
|
+
#
|
70
|
+
def mention_digest_auth (req, opts)
|
71
|
+
|
72
|
+
return false unless @challenge
|
73
|
+
|
74
|
+
req['Authorization'] = generate_header req, opts
|
75
|
+
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Interprets the information in the response's 'Authorization-Info'
|
81
|
+
# header.
|
82
|
+
#
|
83
|
+
def check_authentication_info (res, opts)
|
84
|
+
|
85
|
+
return if no_digest_auth
|
86
|
+
# not using digest authentication
|
87
|
+
|
88
|
+
return unless @challenge
|
89
|
+
# not yet authenticated
|
90
|
+
|
91
|
+
authinfo = AuthInfo.new res
|
92
|
+
@challenge.nonce = authinfo.nextnonce
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
#
|
98
|
+
# Returns true if :digest_authentication is set at endpoint
|
99
|
+
# or request level.
|
100
|
+
#
|
101
|
+
def no_digest_auth
|
102
|
+
|
103
|
+
(not o(opts, :digest_authentication))
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# To be enhanced.
|
108
|
+
#
|
109
|
+
# (For example http://www.intertwingly.net/blog/1585.html)
|
110
|
+
#
|
111
|
+
def generate_cnonce
|
112
|
+
|
113
|
+
Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
|
114
|
+
end
|
115
|
+
|
116
|
+
def request_challenge (req, opts)
|
117
|
+
|
118
|
+
op = opts.dup
|
119
|
+
|
120
|
+
op[:digest_authentication] = false
|
121
|
+
# preventing an infinite loop
|
122
|
+
|
123
|
+
method = req.class.const_get(:METHOD).downcase.to_sym
|
124
|
+
#method = :get
|
125
|
+
|
126
|
+
res = request(method, op)
|
127
|
+
|
128
|
+
return false if res.code.to_i != 401
|
129
|
+
|
130
|
+
@challenge = Challenge.new res
|
131
|
+
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Generates an MD5 digest of the arguments (joined by ":").
|
137
|
+
#
|
138
|
+
def h (*args)
|
139
|
+
|
140
|
+
Digest::MD5.hexdigest(args.join(":"))
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Generates the Authentication header that will be returned
|
145
|
+
# to the server.
|
146
|
+
#
|
147
|
+
def generate_header (req, opts)
|
148
|
+
|
149
|
+
@nonce_count += 1
|
150
|
+
|
151
|
+
user, pass = o(opts, :digest_authentication)
|
152
|
+
realm = @challenge.realm || ""
|
153
|
+
method = req.class.const_get(:METHOD)
|
154
|
+
path = opts[:path]
|
155
|
+
|
156
|
+
a1 = if @challenge.algorithm == 'MD5-sess'
|
157
|
+
h(h(user, realm, pass), @challenge.nonce, @cnonce)
|
158
|
+
else
|
159
|
+
h(user, realm, pass)
|
160
|
+
end
|
161
|
+
|
162
|
+
a2, qop = if @challenge.qop.include?("auth-int")
|
163
|
+
[ h(method, path, h(req.body)), "auth-int" ]
|
164
|
+
else
|
165
|
+
[ h(method, path), "auth" ]
|
166
|
+
end
|
167
|
+
|
168
|
+
nc = ('%08x' % @nonce_count)
|
169
|
+
|
170
|
+
digest = h(
|
171
|
+
#a1, @challenge.nonce, nc, @cnonce, @challenge.qop, a2)
|
172
|
+
a1, @challenge.nonce, nc, @cnonce, "auth", a2)
|
173
|
+
|
174
|
+
header = ""
|
175
|
+
header << "Digest username=\"#{user}\", "
|
176
|
+
header << "realm=\"#{realm}\", "
|
177
|
+
header << "qop=\"#{qop}\", "
|
178
|
+
header << "uri=\"#{path}\", "
|
179
|
+
header << "nonce=\"#{@challenge.nonce}\", "
|
180
|
+
#header << "nc=##{nc}, "
|
181
|
+
header << "nc=#{nc}, "
|
182
|
+
header << "cnonce=\"#{@cnonce}\", "
|
183
|
+
header << "algorithm=\"#{@challenge.algorithm}\", "
|
184
|
+
#header << "algorithm=\"MD5-sess\", "
|
185
|
+
header << "response=\"#{digest}\", "
|
186
|
+
header << "opaque=\"#{@challenge.opaque}\""
|
187
|
+
|
188
|
+
header
|
189
|
+
end
|
190
|
+
|
191
|
+
#
|
192
|
+
# A common parent class for Challenge and AuthInfo.
|
193
|
+
# Their header parsing code is here.
|
194
|
+
#
|
195
|
+
class ServerReply
|
196
|
+
|
197
|
+
def initialize (res)
|
198
|
+
|
199
|
+
s = res[header_name]
|
200
|
+
return nil unless s
|
201
|
+
|
202
|
+
s = s[7..-1] if s[0, 6] == "Digest"
|
203
|
+
|
204
|
+
s = s.split ","
|
205
|
+
|
206
|
+
s.each do |e|
|
207
|
+
|
208
|
+
k, v = parse_entry e
|
209
|
+
|
210
|
+
if k == 'stale'
|
211
|
+
@stale = (v.downcase == 'true')
|
212
|
+
elsif k == 'nc'
|
213
|
+
@nc = v.to_i
|
214
|
+
elsif k == 'qop'
|
215
|
+
@qop = v.split ","
|
216
|
+
else
|
217
|
+
instance_variable_set "@#{k}".to_sym, v
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
protected
|
223
|
+
|
224
|
+
def parse_entry (e)
|
225
|
+
|
226
|
+
k, v = e.split "=", 2
|
227
|
+
v = v[1..-2] if v[0, 1] == '"'
|
228
|
+
[ k.strip, v.strip ]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# Used when parsing a 'www-authenticate' header challenge.
|
234
|
+
#
|
235
|
+
class Challenge < ServerReply
|
236
|
+
|
237
|
+
attr_accessor \
|
238
|
+
:opaque, :algorithm, :qop, :stale, :nonce, :realm, :charset
|
239
|
+
|
240
|
+
def header_name
|
241
|
+
'www-authenticate'
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
#
|
246
|
+
# Used when parsing a 'authentication-info' header info.
|
247
|
+
#
|
248
|
+
class AuthInfo < ServerReply
|
249
|
+
|
250
|
+
attr_accessor \
|
251
|
+
:cnonce, :rspauth, :nextnonce, :qop, :nc
|
252
|
+
|
253
|
+
def header_name
|
254
|
+
'authentication-info'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
data/lib/rufus/verbs/endpoint.rb
CHANGED
@@ -37,11 +37,14 @@ require 'yaml' # for StringIO (at least for now)
|
|
37
37
|
require 'net/http'
|
38
38
|
require 'zlib'
|
39
39
|
|
40
|
+
require 'rufus/verbs/version'
|
41
|
+
require 'rufus/verbs/cookies'
|
42
|
+
require 'rufus/verbs/digest'
|
43
|
+
|
40
44
|
|
41
45
|
module Rufus
|
42
46
|
module Verbs
|
43
47
|
|
44
|
-
VERSION = "0.2"
|
45
48
|
USER_AGENT = "Ruby rufus-verbs #{VERSION}"
|
46
49
|
|
47
50
|
#
|
@@ -64,6 +67,9 @@ module Verbs
|
|
64
67
|
#
|
65
68
|
class EndPoint
|
66
69
|
|
70
|
+
include CookieMixin
|
71
|
+
include DigestAuthMixin
|
72
|
+
|
67
73
|
#
|
68
74
|
# The endpoint initialization opts (Hash instance)
|
69
75
|
#
|
@@ -81,6 +87,8 @@ module Verbs
|
|
81
87
|
@opts[:user_agent] ||= USER_AGENT
|
82
88
|
|
83
89
|
@opts[:proxy] ||= ENV['HTTP_PROXY']
|
90
|
+
|
91
|
+
prepare_cookie_jar
|
84
92
|
end
|
85
93
|
|
86
94
|
def get (*args)
|
@@ -127,6 +135,8 @@ module Verbs
|
|
127
135
|
#
|
128
136
|
def request (method, args, &block)
|
129
137
|
|
138
|
+
# prepare request
|
139
|
+
|
130
140
|
opts = EndPoint.extract_opts args
|
131
141
|
|
132
142
|
compute_target opts
|
@@ -139,8 +149,14 @@ module Verbs
|
|
139
149
|
|
140
150
|
add_conditional_headers(req, opts) if method == :get
|
141
151
|
|
152
|
+
mention_cookies(req, opts)
|
153
|
+
# if the :cookies option is disabled (the default)
|
154
|
+
# will have no effect
|
155
|
+
|
142
156
|
return req if o(opts, :dry_run) == true
|
143
157
|
|
158
|
+
# trigger request
|
159
|
+
|
144
160
|
http = prepare_http opts
|
145
161
|
|
146
162
|
res = nil
|
@@ -149,8 +165,18 @@ module Verbs
|
|
149
165
|
res = http.request req
|
150
166
|
end
|
151
167
|
|
168
|
+
# handle response
|
169
|
+
|
170
|
+
register_cookies res, opts
|
171
|
+
# if the :cookies option is disabled (the default)
|
172
|
+
# will have no effect
|
173
|
+
|
152
174
|
return res if o(opts, :raw_response)
|
153
175
|
|
176
|
+
check_authentication_info res, opts
|
177
|
+
# used in case of :digest_authentication
|
178
|
+
# will have no effect else
|
179
|
+
|
154
180
|
res = handle_response method, res, opts
|
155
181
|
|
156
182
|
return res.body if o(opts, :body)
|
@@ -187,8 +213,8 @@ module Verbs
|
|
187
213
|
def o (opts, key)
|
188
214
|
|
189
215
|
keys = Array key
|
190
|
-
keys.each { |k| (v = opts[k]
|
191
|
-
keys.each { |k| (v = @opts[k]
|
216
|
+
keys.each { |k| (v = opts[k]; return v if v != nil) }
|
217
|
+
keys.each { |k| (v = @opts[k]; return v if v != nil) }
|
192
218
|
nil
|
193
219
|
end
|
194
220
|
|
@@ -284,15 +310,21 @@ module Verbs
|
|
284
310
|
#
|
285
311
|
def add_authentication (req, opts)
|
286
312
|
|
287
|
-
|
313
|
+
b = o(opts, :http_basic_authentication)
|
314
|
+
d = o(opts, :digest_authentication)
|
315
|
+
o = o(opts, :auth)
|
316
|
+
|
317
|
+
if b and b != false
|
318
|
+
|
319
|
+
req.basic_auth b[0], b[1]
|
288
320
|
|
289
|
-
|
321
|
+
elsif d and d != false
|
290
322
|
|
291
|
-
req
|
323
|
+
digest_auth req, opts
|
292
324
|
|
293
|
-
elsif
|
325
|
+
elsif o and o != false
|
294
326
|
|
295
|
-
|
327
|
+
o.call req
|
296
328
|
end
|
297
329
|
end
|
298
330
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
#--
|
3
|
+
# Copyright (c) 2008, John Mettraux, jmettraux@gmail.com
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
#
|
23
|
+
# (MIT license)
|
24
|
+
#++
|
25
|
+
#
|
26
|
+
|
27
|
+
#
|
28
|
+
# John Mettraux
|
29
|
+
#
|
30
|
+
# Made in Japan
|
31
|
+
#
|
32
|
+
# 2008/01/21
|
33
|
+
#
|
34
|
+
|
35
|
+
|
36
|
+
module Rufus
|
37
|
+
module Verbs
|
38
|
+
|
39
|
+
#
|
40
|
+
# The version of this rufus-verbs [gem]
|
41
|
+
#
|
42
|
+
VERSION = '0.3'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -13,7 +13,7 @@ require 'testbase'
|
|
13
13
|
require 'rufus/verbs'
|
14
14
|
|
15
15
|
|
16
|
-
class
|
16
|
+
class Auth0Test < Test::Unit::TestCase
|
17
17
|
include TestBaseMixin
|
18
18
|
|
19
19
|
include Rufus::Verbs
|
@@ -23,7 +23,7 @@ class AuthTest < Test::Unit::TestCase
|
|
23
23
|
#
|
24
24
|
def setup
|
25
25
|
|
26
|
-
@server = ItemServer.new :auth =>
|
26
|
+
@server = ItemServer.new :auth => :basic
|
27
27
|
@server.start
|
28
28
|
end
|
29
29
|
|
data/test/auth1_test.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Testing rufus-verbs
|
4
|
+
#
|
5
|
+
# jmettraux@gmail.com
|
6
|
+
#
|
7
|
+
# Sun Jan 13 12:33:03 JST 2008
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'test/unit'
|
11
|
+
require 'testbase'
|
12
|
+
|
13
|
+
require 'rufus/verbs'
|
14
|
+
|
15
|
+
|
16
|
+
class Auth1Test < Test::Unit::TestCase
|
17
|
+
include TestBaseMixin
|
18
|
+
|
19
|
+
include Rufus::Verbs
|
20
|
+
|
21
|
+
#
|
22
|
+
# Using an items server with the authentication on.
|
23
|
+
#
|
24
|
+
def setup
|
25
|
+
|
26
|
+
@server = ItemServer.new :auth => :digest
|
27
|
+
@server.start
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def test_0
|
32
|
+
|
33
|
+
#res = get :uri => "http://localhost:7777/items"
|
34
|
+
#assert_equal 200, res.code.to_i
|
35
|
+
#assert_equal "{}", res.body.strip
|
36
|
+
#res = expect 401, nil, get(:uri => "http://localhost:7777/items")
|
37
|
+
#p res['www-authenticate']
|
38
|
+
|
39
|
+
#$DEBUG = true
|
40
|
+
|
41
|
+
ep = EndPoint.new :digest_authentication => [ "test", "pass" ]
|
42
|
+
|
43
|
+
expect 200, {}, ep.get("http://localhost:7777/items")
|
44
|
+
assert_equal 2, $dcount
|
45
|
+
|
46
|
+
expect 200, {}, ep.get("http://localhost:7777/items")
|
47
|
+
assert_equal 3, $dcount
|
48
|
+
|
49
|
+
expect 201, nil, ep.post("http://localhost:7777/items/1") { "hammer" }
|
50
|
+
assert_equal 4, $dcount
|
51
|
+
|
52
|
+
expect 200, { 1 => "hammer" }, ep.get("http://localhost:7777/items")
|
53
|
+
assert_equal 5, $dcount
|
54
|
+
|
55
|
+
expect 401, nil, get(:uri => "http://localhost:7777/items")
|
56
|
+
assert_equal 6, $dcount
|
57
|
+
|
58
|
+
expect 401, nil, get(
|
59
|
+
:uri => "http://localhost:7777/items",
|
60
|
+
:http_basic_authentication => [ "toto", "toto" ])
|
61
|
+
assert_equal 7, $dcount
|
62
|
+
|
63
|
+
expect 200, { 1 => "hammer" }, get(
|
64
|
+
:uri => "http://localhost:7777/items",
|
65
|
+
:digest_authentication => [ "test", "pass" ])
|
66
|
+
assert_equal 9, $dcount
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Testing rufus-verbs
|
4
|
+
#
|
5
|
+
# jmettraux@gmail.com
|
6
|
+
#
|
7
|
+
# Sat Jan 19 18:22:48 JST 2008
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'test/unit'
|
11
|
+
#require 'testbase'
|
12
|
+
|
13
|
+
require 'rufus/verbs'
|
14
|
+
require 'rufus/verbs/cookies'
|
15
|
+
|
16
|
+
|
17
|
+
class Cookie0Test < Test::Unit::TestCase
|
18
|
+
#include TestBaseMixin
|
19
|
+
|
20
|
+
include Rufus::Verbs::CookieMixin
|
21
|
+
include Rufus::Verbs::HostMixin
|
22
|
+
|
23
|
+
#
|
24
|
+
# testing split_host(s)
|
25
|
+
#
|
26
|
+
def test_0
|
27
|
+
|
28
|
+
assert_equal [ 'localhost', nil ], split_host('localhost')
|
29
|
+
assert_equal [ 'benz', '.car.co.nz' ], split_host('benz.car.co.nz')
|
30
|
+
assert_equal [ '127.0.0.1', nil ], split_host('127.0.0.1')
|
31
|
+
assert_equal [ '::1', nil ], split_host('::1')
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# testing the CookieJar
|
36
|
+
#
|
37
|
+
def test_1
|
38
|
+
|
39
|
+
cookie0 = TestCookie.new
|
40
|
+
cookie1 = TestCookie.new
|
41
|
+
|
42
|
+
jar = Rufus::Verbs::CookieJar.new 77
|
43
|
+
assert_equal 0, jar.size
|
44
|
+
|
45
|
+
jar.add_cookie(".rubyforge.org", "/", cookie0)
|
46
|
+
assert_equal 1, jar.size
|
47
|
+
assert_equal [ cookie0 ], jar.fetch_cookies("rufus.rubyforge.org", "/main")
|
48
|
+
|
49
|
+
jar.add_cookie("rufus.rubyforge.org", "/sub", cookie1)
|
50
|
+
assert_equal 2, jar.size
|
51
|
+
assert_equal [ cookie1, cookie0 ], jar.fetch_cookies("rufus.rubyforge.org", "/sub/0")
|
52
|
+
assert_equal [ cookie0 ], jar.fetch_cookies("rufus.rubyforge.org", "/main")
|
53
|
+
assert_equal [ cookie0 ], jar.fetch_cookies("rufus.rubyforge.org", "/")
|
54
|
+
|
55
|
+
jar.remove_cookie("rufus.rubyforge.org", "/sub", cookie1)
|
56
|
+
assert_equal 1, jar.size
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# testing cookie_acceptable?(opts, cookie)
|
61
|
+
#
|
62
|
+
def test_2
|
63
|
+
|
64
|
+
jar = Rufus::Verbs::CookieJar.new 77
|
65
|
+
|
66
|
+
opts = { :host => 'rufus.rubyforge.org', :path => '/' }
|
67
|
+
c = TestCookie.new '.rubyforge.org', '/'
|
68
|
+
assert cookie_acceptable?(opts, c)
|
69
|
+
|
70
|
+
# * The value for the Domain attribute contains no embedded dots
|
71
|
+
# or does not start with a dot.
|
72
|
+
|
73
|
+
opts = { :host => 'rufus.rubyforge.org', :path => '/' }
|
74
|
+
c = TestCookie.new 'rufus.rubyforge.org', '/'
|
75
|
+
assert ! cookie_acceptable?(opts, c)
|
76
|
+
|
77
|
+
opts = { :host => 'rufus.rubyforge.org', :path => '/' }
|
78
|
+
c = TestCookie.new 'org', '/'
|
79
|
+
assert ! cookie_acceptable?(opts, c)
|
80
|
+
|
81
|
+
# * The value for the Path attribute is not a prefix of the
|
82
|
+
# request-URI.
|
83
|
+
|
84
|
+
opts = { :host => 'rufus.rubyforge.org', :path => '/this' }
|
85
|
+
c = TestCookie.new '.rubyforge.org', '/that'
|
86
|
+
assert ! cookie_acceptable?(opts, c)
|
87
|
+
|
88
|
+
# * The value for the request-host does not domain-match the
|
89
|
+
# Domain attribute.
|
90
|
+
|
91
|
+
opts = { :host => 'rufus.rubyforg.org', :path => '/' }
|
92
|
+
c = TestCookie.new '.rubyforge.org', '/'
|
93
|
+
assert ! cookie_acceptable?(opts, c)
|
94
|
+
|
95
|
+
# * The request-host is a FQDN (not IP address) and has the form
|
96
|
+
# HD, where D is the value of the Domain attribute, and H is a
|
97
|
+
# string that contains one or more dots.
|
98
|
+
|
99
|
+
# implicit...
|
100
|
+
end
|
101
|
+
|
102
|
+
#def test_webrick_cookie
|
103
|
+
# require 'webrick/cookie'
|
104
|
+
# cookie = "PREF=ID=18da97219de4985:TM=12007507:LM=12007507:S=Guc1JcA15ySZYl2n; expires=Mon, 18-Jan-2010 09:30:37 GMT; path=/; domain=.google.com"
|
105
|
+
# p WEBrick::Cookie.parse_set_cookie(cookie)
|
106
|
+
# p Rufus::Verbs::Cookie.parse_set_cookie(cookie)
|
107
|
+
#end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
class TestCookie
|
112
|
+
|
113
|
+
attr_reader :domain, :path, :name
|
114
|
+
|
115
|
+
def initialize (domain=nil, path=nil, name='whatever')
|
116
|
+
|
117
|
+
@domain = domain
|
118
|
+
@path = path
|
119
|
+
@name = name
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Testing rufus-verbs
|
4
|
+
#
|
5
|
+
# jmettraux@gmail.com
|
6
|
+
#
|
7
|
+
# Sun Jan 13 12:33:03 JST 2008
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'test/unit'
|
11
|
+
require 'testbase'
|
12
|
+
|
13
|
+
require 'rufus/verbs'
|
14
|
+
|
15
|
+
|
16
|
+
class Cookie1Test < Test::Unit::TestCase
|
17
|
+
include TestBaseMixin
|
18
|
+
|
19
|
+
include Rufus::Verbs
|
20
|
+
|
21
|
+
|
22
|
+
def test_0
|
23
|
+
|
24
|
+
ep = EndPoint.new :cookies => true
|
25
|
+
class << ep
|
26
|
+
attr_reader :cookies
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_equal 0, ep.cookies.size
|
30
|
+
|
31
|
+
ep.get :uri => "http://localhost:7777/cookie"
|
32
|
+
assert_equal 1, ep.cookies.size
|
33
|
+
|
34
|
+
req = ep.get :uri => "http://localhost:7777/cookie", :dry_run => true
|
35
|
+
assert_match /^tcookie=\d*$/, req['Cookie']
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_1
|
39
|
+
|
40
|
+
ep0 = EndPoint.new :cookies => true
|
41
|
+
ep1 = EndPoint.new :cookies => true
|
42
|
+
|
43
|
+
ep0.post("http://localhost:7777/cookie") { "smurf0" }
|
44
|
+
ep1.post("http://localhost:7777/cookie") { "smurf1" }
|
45
|
+
|
46
|
+
expect 200, [ 'smurf0' ], ep0.get("http://localhost:7777/cookie")
|
47
|
+
expect 200, [ 'smurf1' ], ep1.get("http://localhost:7777/cookie")
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_2
|
51
|
+
|
52
|
+
ep = EndPoint.new :cookies => false # explicitely
|
53
|
+
|
54
|
+
res0 = ep.post("http://localhost:7777/cookie") { "smurf0" }
|
55
|
+
res1 = expect 200, [], ep.get("http://localhost:7777/cookie")
|
56
|
+
|
57
|
+
assert_not_equal res0['Set-Cookie'], res1['Set-Cookie']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
data/test/items.rb
CHANGED
@@ -11,6 +11,12 @@ require 'date'
|
|
11
11
|
require 'webrick'
|
12
12
|
|
13
13
|
|
14
|
+
$dcount = 0 # tracking the number of hits when doing digest auth
|
15
|
+
|
16
|
+
|
17
|
+
#
|
18
|
+
# the hash for the /items resource (collection)
|
19
|
+
#
|
14
20
|
class LastModifiedHash
|
15
21
|
|
16
22
|
def initialize
|
@@ -52,8 +58,12 @@ end
|
|
52
58
|
class ItemServlet < WEBrick::HTTPServlet::AbstractServlet
|
53
59
|
|
54
60
|
@@items = {}
|
61
|
+
|
55
62
|
@@lastmod = LastModifiedHash.new
|
56
63
|
|
64
|
+
@@authenticator = WEBrick::HTTPAuth::DigestAuth.new(
|
65
|
+
:UserDB => WEBrick::HTTPAuth::Htdigest.new('test/test.htdigest'),
|
66
|
+
:Realm => 'test_realm')
|
57
67
|
|
58
68
|
def initialize (server, *options)
|
59
69
|
|
@@ -66,9 +76,17 @@ class ItemServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
66
76
|
#
|
67
77
|
def service (req, res)
|
68
78
|
|
69
|
-
|
70
|
-
|
71
|
-
|
79
|
+
if @auth == :basic
|
80
|
+
|
81
|
+
WEBrick::HTTPAuth.basic_auth(req, res, "items") do |u, p|
|
82
|
+
(u != nil and u == p)
|
83
|
+
end
|
84
|
+
|
85
|
+
elsif @auth == :digest
|
86
|
+
|
87
|
+
$dcount += 1
|
88
|
+
@@authenticator.authenticate(req, res)
|
89
|
+
end
|
72
90
|
|
73
91
|
super
|
74
92
|
end
|
@@ -166,9 +184,9 @@ class ItemServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
166
184
|
|
167
185
|
return true unless since or match
|
168
186
|
|
169
|
-
puts
|
170
|
-
p [ since, match ]
|
171
|
-
puts
|
187
|
+
#puts
|
188
|
+
#p [ since, match ]
|
189
|
+
#puts
|
172
190
|
|
173
191
|
(since or match)
|
174
192
|
end
|
@@ -217,6 +235,42 @@ class ThingServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
217
235
|
end
|
218
236
|
end
|
219
237
|
|
238
|
+
#
|
239
|
+
# testing Rufus::Verbs cookies...
|
240
|
+
#
|
241
|
+
class CookieServlet < WEBrick::HTTPServlet::AbstractServlet
|
242
|
+
|
243
|
+
@@sessions = {}
|
244
|
+
|
245
|
+
def do_GET (req, res)
|
246
|
+
|
247
|
+
res.body = get_session(req, res).inspect
|
248
|
+
end
|
249
|
+
|
250
|
+
def do_POST (req, res)
|
251
|
+
|
252
|
+
get_session(req, res) << req.body.strip
|
253
|
+
res.body = "ok."
|
254
|
+
end
|
255
|
+
|
256
|
+
protected
|
257
|
+
|
258
|
+
def get_session (req, res)
|
259
|
+
|
260
|
+
c = req.cookies.find { |c| c.name == 'tcookie' }
|
261
|
+
|
262
|
+
if c
|
263
|
+
@@sessions[c.value]
|
264
|
+
else
|
265
|
+
s = []
|
266
|
+
key = (Time.now.to_f * 100000).to_i.to_s
|
267
|
+
@@sessions[key] = s
|
268
|
+
res.cookies << WEBrick::Cookie.new('tcookie', key)
|
269
|
+
s
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
220
274
|
#
|
221
275
|
# Serving items, a dummy resource...
|
222
276
|
# Also serving things, which just redirect to items...
|
@@ -241,6 +295,7 @@ class ItemServer
|
|
241
295
|
|
242
296
|
@server.mount "/items", ItemServlet
|
243
297
|
@server.mount "/things", ThingServlet
|
298
|
+
@server.mount "/cookie", CookieServlet
|
244
299
|
|
245
300
|
[ 'INT', 'TERM' ].each do |signal|
|
246
301
|
trap(signal) { shutdown }
|
data/test/test.htdigest
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
test:test_realm:fbab5e064c6fa79c414735bcdcf2ce4a
|
data/test/test.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
|
2
2
|
require 'dryrun_test'
|
3
3
|
require 'iconditional_test'
|
4
|
+
|
4
5
|
require 'simple_test'
|
5
|
-
|
6
|
+
|
7
|
+
require 'auth0_test'
|
8
|
+
require 'auth1_test'
|
9
|
+
|
6
10
|
require 'redir_test'
|
7
11
|
require 'block_test'
|
8
12
|
require 'https_test'
|
9
13
|
require 'proxy_test'
|
10
14
|
|
11
15
|
require 'conditional_test'
|
16
|
+
require 'cookie0_test'
|
17
|
+
require 'cookie1_test'
|
12
18
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rufus-verbs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.3"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-01-
|
12
|
+
date: 2008-01-23 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -25,11 +25,17 @@ files:
|
|
25
25
|
- lib/rufus
|
26
26
|
- lib/rufus/verbs
|
27
27
|
- lib/rufus/verbs/conditional.rb
|
28
|
+
- lib/rufus/verbs/cookies.rb
|
29
|
+
- lib/rufus/verbs/digest.rb
|
28
30
|
- lib/rufus/verbs/endpoint.rb
|
31
|
+
- lib/rufus/verbs/version.rb
|
29
32
|
- lib/rufus/verbs.rb
|
30
|
-
- test/
|
33
|
+
- test/auth0_test.rb
|
34
|
+
- test/auth1_test.rb
|
31
35
|
- test/block_test.rb
|
32
36
|
- test/conditional_test.rb
|
37
|
+
- test/cookie0_test.rb
|
38
|
+
- test/cookie1_test.rb
|
33
39
|
- test/dryrun_test.rb
|
34
40
|
- test/https_test.rb
|
35
41
|
- test/iconditional_test.rb
|
@@ -37,6 +43,7 @@ files:
|
|
37
43
|
- test/proxy_test.rb
|
38
44
|
- test/redir_test.rb
|
39
45
|
- test/simple_test.rb
|
46
|
+
- test/test.htdigest
|
40
47
|
- test/test.rb
|
41
48
|
- test/testbase.rb
|
42
49
|
- README.txt
|