rufus-verbs 0.10 → 1.0.0
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.
- 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/digest.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,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/21
|
33
|
-
#
|
34
25
|
|
35
26
|
require 'digest/md5'
|
36
27
|
|
@@ -38,223 +29,223 @@ require 'digest/md5'
|
|
38
29
|
module Rufus
|
39
30
|
module Verbs
|
40
31
|
|
32
|
+
#
|
33
|
+
# Specified by http://www.ietf.org/rfc/rfc2617.txt
|
34
|
+
# Inspired by http://segment7.net/projects/ruby/snippets/digest_auth.rb
|
35
|
+
#
|
36
|
+
# The EndPoint classes mixes this in to support digest authentication.
|
37
|
+
#
|
38
|
+
module DigestAuthMixin
|
39
|
+
|
41
40
|
#
|
42
|
-
#
|
43
|
-
# Inspired by http://segment7.net/projects/ruby/snippets/digest_auth.rb
|
44
|
-
#
|
45
|
-
# The EndPoint classes mixes this in to support digest authentication.
|
41
|
+
# Makes sure digest_auth is on
|
46
42
|
#
|
47
|
-
|
43
|
+
def digest_auth (req, opts)
|
48
44
|
|
49
|
-
|
50
|
-
#
|
51
|
-
#
|
52
|
-
def digest_auth (req, opts)
|
45
|
+
#return if no_digest_auth
|
46
|
+
# already done in add_authentication()
|
53
47
|
|
54
|
-
|
55
|
-
|
48
|
+
@cnonce ||= generate_cnonce
|
49
|
+
@nonce_count ||= 0
|
56
50
|
|
57
|
-
|
58
|
-
|
51
|
+
mention_digest_auth(req, opts) \
|
52
|
+
and return
|
59
53
|
|
60
|
-
|
61
|
-
|
54
|
+
mention_digest_auth(req, opts) \
|
55
|
+
if request_challenge(req, opts)
|
56
|
+
end
|
62
57
|
|
63
|
-
|
64
|
-
|
65
|
-
|
58
|
+
#
|
59
|
+
# Sets the 'Authorization' header with the appropriate info.
|
60
|
+
#
|
61
|
+
def mention_digest_auth (req, opts)
|
66
62
|
|
67
|
-
|
68
|
-
# Sets the 'Authorization' header with the appropriate info.
|
69
|
-
#
|
70
|
-
def mention_digest_auth (req, opts)
|
63
|
+
return false unless @challenge
|
71
64
|
|
72
|
-
|
65
|
+
req['Authorization'] = generate_header req, opts
|
73
66
|
|
74
|
-
|
67
|
+
true
|
68
|
+
end
|
75
69
|
|
76
|
-
|
77
|
-
|
70
|
+
#
|
71
|
+
# Interprets the information in the response's 'Authorization-Info'
|
72
|
+
# header.
|
73
|
+
#
|
74
|
+
def check_authentication_info (res, opts)
|
78
75
|
|
79
|
-
|
80
|
-
#
|
81
|
-
# header.
|
82
|
-
#
|
83
|
-
def check_authentication_info (res, opts)
|
76
|
+
return if no_digest_auth
|
77
|
+
# not using digest authentication
|
84
78
|
|
85
|
-
|
86
|
-
|
79
|
+
return unless @challenge
|
80
|
+
# not yet authenticated
|
87
81
|
|
88
|
-
|
89
|
-
|
82
|
+
authinfo = AuthInfo.new res
|
83
|
+
@challenge.nonce = authinfo.nextnonce
|
84
|
+
end
|
90
85
|
|
91
|
-
|
92
|
-
@challenge.nonce = authinfo.nextnonce
|
93
|
-
end
|
86
|
+
protected
|
94
87
|
|
95
|
-
|
88
|
+
#
|
89
|
+
# Returns true if :digest_authentication is set at endpoint
|
90
|
+
# or request level.
|
91
|
+
#
|
92
|
+
def no_digest_auth
|
96
93
|
|
97
|
-
|
98
|
-
|
99
|
-
# or request level.
|
100
|
-
#
|
101
|
-
def no_digest_auth
|
94
|
+
(not o(opts, :digest_authentication))
|
95
|
+
end
|
102
96
|
|
103
|
-
|
104
|
-
|
97
|
+
#
|
98
|
+
# To be enhanced.
|
99
|
+
#
|
100
|
+
# (For example http://www.intertwingly.net/blog/1585.html)
|
101
|
+
#
|
102
|
+
def generate_cnonce
|
105
103
|
|
106
|
-
|
107
|
-
|
108
|
-
#
|
109
|
-
# (For example http://www.intertwingly.net/blog/1585.html)
|
110
|
-
#
|
111
|
-
def generate_cnonce
|
104
|
+
Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
|
105
|
+
end
|
112
106
|
|
113
|
-
|
114
|
-
end
|
107
|
+
def request_challenge (req, opts)
|
115
108
|
|
116
|
-
|
109
|
+
op = opts.dup
|
117
110
|
|
118
|
-
|
111
|
+
op[:digest_authentication] = false
|
112
|
+
# preventing an infinite loop
|
119
113
|
|
120
|
-
|
121
|
-
|
114
|
+
method = req.class.const_get(:METHOD).downcase.to_sym
|
115
|
+
#method = :get
|
122
116
|
|
123
|
-
|
124
|
-
#method = :get
|
117
|
+
res = request(method, op)
|
125
118
|
|
126
|
-
|
119
|
+
return false if res.code.to_i != 401
|
127
120
|
|
128
|
-
|
121
|
+
@challenge = Challenge.new res
|
129
122
|
|
130
|
-
|
123
|
+
true
|
124
|
+
end
|
131
125
|
|
132
|
-
|
133
|
-
|
126
|
+
#
|
127
|
+
# Generates an MD5 digest of the arguments (joined by ":").
|
128
|
+
#
|
129
|
+
def h (*args)
|
134
130
|
|
135
|
-
|
136
|
-
|
137
|
-
#
|
138
|
-
def h (*args)
|
131
|
+
Digest::MD5.hexdigest(args.join(":"))
|
132
|
+
end
|
139
133
|
|
140
|
-
|
141
|
-
|
134
|
+
#
|
135
|
+
# Generates the Authentication header that will be returned
|
136
|
+
# to the server.
|
137
|
+
#
|
138
|
+
def generate_header (req, opts)
|
139
|
+
|
140
|
+
@nonce_count += 1
|
141
|
+
|
142
|
+
user, pass = o(opts, :digest_authentication)
|
143
|
+
realm = @challenge.realm || ""
|
144
|
+
method = req.class.const_get(:METHOD)
|
145
|
+
path = opts[:path]
|
146
|
+
|
147
|
+
a1 = if @challenge.algorithm == 'MD5-sess'
|
148
|
+
h(h(user, realm, pass), @challenge.nonce, @cnonce)
|
149
|
+
else
|
150
|
+
h(user, realm, pass)
|
151
|
+
end
|
152
|
+
|
153
|
+
a2, qop = if @challenge.qop.include?("auth-int")
|
154
|
+
[ h(method, path, h(req.body)), "auth-int" ]
|
155
|
+
else
|
156
|
+
[ h(method, path), "auth" ]
|
157
|
+
end
|
158
|
+
|
159
|
+
nc = ('%08x' % @nonce_count)
|
160
|
+
|
161
|
+
digest = h(
|
162
|
+
#a1, @challenge.nonce, nc, @cnonce, @challenge.qop, a2)
|
163
|
+
a1, @challenge.nonce, nc, @cnonce, "auth", a2)
|
164
|
+
|
165
|
+
header = ""
|
166
|
+
header << "Digest username=\"#{user}\", "
|
167
|
+
header << "realm=\"#{realm}\", "
|
168
|
+
header << "qop=\"#{qop}\", "
|
169
|
+
header << "uri=\"#{path}\", "
|
170
|
+
header << "nonce=\"#{@challenge.nonce}\", "
|
171
|
+
#header << "nc=##{nc}, "
|
172
|
+
header << "nc=#{nc}, "
|
173
|
+
header << "cnonce=\"#{@cnonce}\", "
|
174
|
+
header << "algorithm=\"#{@challenge.algorithm}\", "
|
175
|
+
#header << "algorithm=\"MD5-sess\", "
|
176
|
+
header << "response=\"#{digest}\", "
|
177
|
+
header << "opaque=\"#{@challenge.opaque}\""
|
178
|
+
|
179
|
+
header
|
180
|
+
end
|
142
181
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
182
|
+
#
|
183
|
+
# A common parent class for Challenge and AuthInfo.
|
184
|
+
# Their header parsing code is here.
|
185
|
+
#
|
186
|
+
class ServerReply
|
148
187
|
|
149
|
-
|
188
|
+
def initialize (res)
|
150
189
|
|
151
|
-
|
152
|
-
|
153
|
-
method = req.class.const_get(:METHOD)
|
154
|
-
path = opts[:path]
|
190
|
+
s = res[header_name]
|
191
|
+
return nil unless s
|
155
192
|
|
156
|
-
|
157
|
-
h(h(user, realm, pass), @challenge.nonce, @cnonce)
|
158
|
-
else
|
159
|
-
h(user, realm, pass)
|
160
|
-
end
|
193
|
+
s = s[7..-1] if s[0, 6] == "Digest"
|
161
194
|
|
162
|
-
|
163
|
-
[ h(method, path, h(req.body)), "auth-int" ]
|
164
|
-
else
|
165
|
-
[ h(method, path), "auth" ]
|
166
|
-
end
|
195
|
+
s = s.split ","
|
167
196
|
|
168
|
-
|
197
|
+
s.each do |e|
|
169
198
|
|
170
|
-
|
171
|
-
#a1, @challenge.nonce, nc, @cnonce, @challenge.qop, a2)
|
172
|
-
a1, @challenge.nonce, nc, @cnonce, "auth", a2)
|
199
|
+
k, v = parse_entry e
|
173
200
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
header << "response=\"#{digest}\", "
|
186
|
-
header << "opaque=\"#{@challenge.opaque}\""
|
201
|
+
if k == 'stale'
|
202
|
+
@stale = (v.downcase == 'true')
|
203
|
+
elsif k == 'nc'
|
204
|
+
@nc = v.to_i
|
205
|
+
elsif k == 'qop'
|
206
|
+
@qop = v.split ","
|
207
|
+
else
|
208
|
+
instance_variable_set "@#{k}".to_sym, v
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
187
212
|
|
188
|
-
|
189
|
-
end
|
213
|
+
protected
|
190
214
|
|
191
|
-
|
192
|
-
# A common parent class for Challenge and AuthInfo.
|
193
|
-
# Their header parsing code is here.
|
194
|
-
#
|
195
|
-
class ServerReply
|
215
|
+
def parse_entry (e)
|
196
216
|
|
197
|
-
|
217
|
+
k, v = e.split '=', 2
|
218
|
+
v = v[1..-2] if v[0, 1] == '"'
|
219
|
+
[ k.strip, v.strip ]
|
220
|
+
end
|
221
|
+
end
|
198
222
|
|
199
|
-
|
200
|
-
|
223
|
+
#
|
224
|
+
# Used when parsing a 'www-authenticate' header challenge.
|
225
|
+
#
|
226
|
+
class Challenge < ServerReply
|
201
227
|
|
202
|
-
|
228
|
+
attr_accessor \
|
229
|
+
:opaque, :algorithm, :qop, :stale, :nonce, :realm, :charset
|
203
230
|
|
204
|
-
|
231
|
+
def header_name
|
232
|
+
'www-authenticate'
|
233
|
+
end
|
234
|
+
end
|
205
235
|
|
206
|
-
|
236
|
+
#
|
237
|
+
# Used when parsing a 'authentication-info' header info.
|
238
|
+
#
|
239
|
+
class AuthInfo < ServerReply
|
207
240
|
|
208
|
-
|
241
|
+
attr_accessor \
|
242
|
+
:cnonce, :rspauth, :nextnonce, :qop, :nc
|
209
243
|
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
244
|
+
def header_name
|
245
|
+
'authentication-info'
|
246
|
+
end
|
257
247
|
end
|
248
|
+
end
|
258
249
|
|
259
250
|
end
|
260
251
|
end
|
data/lib/rufus/verbs/endpoint.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,18 +19,11 @@
|
|
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
|
|
26
|
+
require 'cgi'
|
35
27
|
require 'uri'
|
36
28
|
require 'yaml' # for StringIO (at least for now)
|
37
29
|
require 'net/http'
|
@@ -46,602 +38,604 @@ require 'rufus/verbs/verbose'
|
|
46
38
|
module Rufus
|
47
39
|
module Verbs
|
48
40
|
|
49
|
-
|
41
|
+
USER_AGENT = "Ruby rufus-verbs #{VERSION}"
|
42
|
+
|
43
|
+
#
|
44
|
+
# An EndPoint can be used to share common options among a set of
|
45
|
+
# requests.
|
46
|
+
#
|
47
|
+
# ep = EndPoint.new(
|
48
|
+
# :host => "restful.server",
|
49
|
+
# :port => 7080,
|
50
|
+
# :resource => "inventory/tools")
|
51
|
+
#
|
52
|
+
# res = ep.get :id => 1
|
53
|
+
# # still a silver bullet ?
|
54
|
+
#
|
55
|
+
# res = ep.get :id => 0
|
56
|
+
# # where did the hammer go ?
|
57
|
+
#
|
58
|
+
# When a request gets prepared, the option values will be looked up
|
59
|
+
# in (1) its local (request) options, then (2) in the EndPoint options.
|
60
|
+
#
|
61
|
+
class EndPoint
|
62
|
+
|
63
|
+
include CookieMixin
|
64
|
+
include DigestAuthMixin
|
65
|
+
include VerboseMixin
|
50
66
|
|
51
67
|
#
|
52
|
-
#
|
53
|
-
#
|
68
|
+
# The endpoint initialization opts (Hash instance)
|
69
|
+
#
|
70
|
+
attr_reader :opts
|
71
|
+
|
72
|
+
def initialize (opts)
|
73
|
+
|
74
|
+
@opts = opts
|
75
|
+
|
76
|
+
compute_target @opts
|
77
|
+
|
78
|
+
@opts[:http_basic_authentication] =
|
79
|
+
opts[:http_basic_authentication] || opts[:hba]
|
80
|
+
|
81
|
+
@opts[:user_agent] ||= USER_AGENT
|
82
|
+
|
83
|
+
@opts[:proxy] ||= ENV['HTTP_PROXY']
|
84
|
+
|
85
|
+
prepare_cookie_jar
|
86
|
+
end
|
87
|
+
|
88
|
+
def get (*args)
|
89
|
+
|
90
|
+
request :get, args
|
91
|
+
end
|
92
|
+
|
93
|
+
def post (*args, &block)
|
94
|
+
|
95
|
+
request :post, args, &block
|
96
|
+
end
|
97
|
+
|
98
|
+
def put (*args, &block)
|
99
|
+
|
100
|
+
request :put, args, &block
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete (*args)
|
104
|
+
|
105
|
+
request :delete, args
|
106
|
+
end
|
107
|
+
|
108
|
+
def head (*args)
|
109
|
+
|
110
|
+
request :head, args
|
111
|
+
end
|
112
|
+
|
113
|
+
def options (*args)
|
114
|
+
|
115
|
+
request :options, args
|
116
|
+
end
|
117
|
+
|
54
118
|
#
|
55
|
-
#
|
56
|
-
# :host => "restful.server",
|
57
|
-
# :port => 7080,
|
58
|
-
# :resource => "inventory/tools")
|
119
|
+
# This is the method called by the module methods verbs.
|
59
120
|
#
|
60
|
-
#
|
61
|
-
# # still a silver bullet ?
|
121
|
+
# For example,
|
62
122
|
#
|
63
|
-
#
|
64
|
-
# # where did the hammer go ?
|
123
|
+
# RufusVerbs.get(args)
|
65
124
|
#
|
66
|
-
#
|
67
|
-
# in (1) its local (request) options, then (2) in the EndPoint options.
|
125
|
+
# calls
|
68
126
|
#
|
69
|
-
|
127
|
+
# RufusVerbs::EndPoint.request(:get, args)
|
128
|
+
#
|
129
|
+
def self.request (method, args, &block)
|
70
130
|
|
71
|
-
|
72
|
-
include DigestAuthMixin
|
73
|
-
include VerboseMixin
|
131
|
+
opts = extract_opts args
|
74
132
|
|
75
|
-
|
76
|
-
|
77
|
-
#
|
78
|
-
attr_reader :opts
|
133
|
+
EndPoint.new(opts).request(method, opts, &block)
|
134
|
+
end
|
79
135
|
|
80
|
-
|
136
|
+
#
|
137
|
+
# The instance methods get, post, put and delete ultimately calls
|
138
|
+
# this request() method. All the work is done here.
|
139
|
+
#
|
140
|
+
def request (method, args, &block)
|
81
141
|
|
82
|
-
|
142
|
+
# prepare request
|
83
143
|
|
84
|
-
|
144
|
+
opts = EndPoint.extract_opts args
|
85
145
|
|
86
|
-
|
87
|
-
opts[:http_basic_authentication] || opts[:hba]
|
146
|
+
compute_target opts
|
88
147
|
|
89
|
-
|
148
|
+
req = create_request method, opts
|
90
149
|
|
91
|
-
|
150
|
+
add_payload(req, opts, &block) if method == :post or method == :put
|
92
151
|
|
93
|
-
|
94
|
-
end
|
152
|
+
add_authentication(req, opts)
|
95
153
|
|
96
|
-
|
154
|
+
add_conditional_headers(req, opts) if method == :get
|
97
155
|
|
98
|
-
|
99
|
-
|
156
|
+
mention_cookies(req, opts)
|
157
|
+
# if the :cookies option is disabled (the default)
|
158
|
+
# will have no effect
|
100
159
|
|
101
|
-
|
160
|
+
vlog_request opts, req
|
102
161
|
|
103
|
-
|
104
|
-
end
|
162
|
+
return req if o(opts, :dry_run) == true
|
105
163
|
|
106
|
-
|
164
|
+
# trigger request
|
107
165
|
|
108
|
-
|
109
|
-
end
|
166
|
+
http = prepare_http opts
|
110
167
|
|
111
|
-
|
168
|
+
vlog_http opts, http
|
112
169
|
|
113
|
-
|
114
|
-
end
|
170
|
+
res = nil
|
115
171
|
|
116
|
-
|
172
|
+
http.start do
|
173
|
+
res = http.request req
|
174
|
+
end
|
117
175
|
|
118
|
-
|
119
|
-
end
|
176
|
+
# handle response
|
120
177
|
|
121
|
-
|
178
|
+
class << res
|
179
|
+
attr_accessor :request
|
180
|
+
end
|
181
|
+
res.request = req
|
122
182
|
|
123
|
-
|
124
|
-
end
|
183
|
+
vlog_response opts, res
|
125
184
|
|
126
|
-
|
127
|
-
#
|
128
|
-
#
|
129
|
-
# For example,
|
130
|
-
#
|
131
|
-
# RufusVerbs.get(args)
|
132
|
-
#
|
133
|
-
# calls
|
134
|
-
#
|
135
|
-
# RufusVerbs::EndPoint.request(:get, args)
|
136
|
-
#
|
137
|
-
def self.request (method, args, &block)
|
138
|
-
|
139
|
-
opts = extract_opts args
|
140
|
-
|
141
|
-
EndPoint.new(opts).request(method, opts, &block)
|
142
|
-
end
|
185
|
+
register_cookies res, opts
|
186
|
+
# if the :cookies option is disabled (the default)
|
187
|
+
# will have no effect
|
143
188
|
|
144
|
-
|
145
|
-
# The instance methods get, post, put and delete ultimately calls
|
146
|
-
# this request() method. All the work is done here.
|
147
|
-
#
|
148
|
-
def request (method, args, &block)
|
189
|
+
return res if o(opts, :raw_response)
|
149
190
|
|
150
|
-
|
191
|
+
check_authentication_info res, opts
|
192
|
+
# used in case of :digest_authentication
|
193
|
+
# will have no effect else
|
151
194
|
|
152
|
-
|
195
|
+
res = handle_response method, res, opts
|
153
196
|
|
154
|
-
|
197
|
+
return parse_options(res) if method == :options
|
155
198
|
|
156
|
-
|
199
|
+
return res.body if o(opts, :body)
|
157
200
|
|
158
|
-
|
201
|
+
res
|
202
|
+
end
|
159
203
|
|
160
|
-
|
204
|
+
private
|
161
205
|
|
162
|
-
|
206
|
+
#
|
207
|
+
# Manages various args formats :
|
208
|
+
#
|
209
|
+
# uri
|
210
|
+
# [ uri ]
|
211
|
+
# [ uri, opts ]
|
212
|
+
# opts
|
213
|
+
#
|
214
|
+
def self.extract_opts (args)
|
163
215
|
|
164
|
-
|
165
|
-
# if the :cookies option is disabled (the default)
|
166
|
-
# will have no effect
|
216
|
+
opts = {}
|
167
217
|
|
168
|
-
|
218
|
+
args = [ args ] unless args.is_a?(Array)
|
169
219
|
|
170
|
-
|
220
|
+
opts = args.last \
|
221
|
+
if args.last.is_a?(Hash)
|
171
222
|
|
172
|
-
|
223
|
+
opts[:uri] = args.first \
|
224
|
+
if args.first.is_a?(String) or args.first.is_a?(URI)
|
173
225
|
|
174
|
-
|
226
|
+
opts
|
227
|
+
end
|
175
228
|
|
176
|
-
|
229
|
+
#
|
230
|
+
# Returns the value from the [request] opts or from the
|
231
|
+
# [endpoint] @opts.
|
232
|
+
#
|
233
|
+
def o (opts, key)
|
234
|
+
|
235
|
+
keys = Array key
|
236
|
+
keys.each { |k| (v = opts[k]; return v if v != nil) }
|
237
|
+
keys.each { |k| (v = @opts[k]; return v if v != nil) }
|
238
|
+
nil
|
239
|
+
end
|
177
240
|
|
178
|
-
|
241
|
+
#
|
242
|
+
# Returns scheme, host, port, path, query
|
243
|
+
#
|
244
|
+
def compute_target (opts)
|
245
|
+
|
246
|
+
u = opts[:uri] || opts[:u]
|
247
|
+
|
248
|
+
r = if opts[:host]
|
249
|
+
|
250
|
+
[ opts[:scheme] || 'http',
|
251
|
+
opts[:host],
|
252
|
+
opts[:port] || 80,
|
253
|
+
opts[:path] || '/',
|
254
|
+
opts[:query] || opts[:params] ]
|
255
|
+
|
256
|
+
elsif u
|
257
|
+
|
258
|
+
u = URI.parse u.to_s unless u.is_a?(URI)
|
259
|
+
[ u.scheme,
|
260
|
+
u.host,
|
261
|
+
u.port,
|
262
|
+
u.path,
|
263
|
+
query_to_h(u.query) ]
|
264
|
+
else
|
265
|
+
|
266
|
+
[]
|
267
|
+
end
|
268
|
+
|
269
|
+
opts[:scheme] = r[0] || @opts[:scheme]
|
270
|
+
opts[:host] = r[1] || @opts[:host]
|
271
|
+
opts[:port] = r[2] || @opts[:port]
|
272
|
+
opts[:path] = r[3] || @opts[:path]
|
273
|
+
|
274
|
+
opts[:query] =
|
275
|
+
r[4] ||
|
276
|
+
opts[:params] || opts[:query] ||
|
277
|
+
@opts[:query] || @opts[:params] ||
|
278
|
+
{}
|
279
|
+
|
280
|
+
opts.delete :path if opts[:path] == ''
|
281
|
+
|
282
|
+
opts[:c_uri] = [
|
283
|
+
opts[:scheme],
|
284
|
+
opts[:host],
|
285
|
+
opts[:port],
|
286
|
+
opts[:path],
|
287
|
+
opts[:query] ].inspect
|
288
|
+
#
|
289
|
+
# can be used for conditional gets
|
290
|
+
|
291
|
+
r
|
292
|
+
end
|
179
293
|
|
180
|
-
|
181
|
-
|
182
|
-
|
294
|
+
#
|
295
|
+
# Creates the Net::HTTP request instance.
|
296
|
+
#
|
297
|
+
# If :fake_put is set, will use Net::HTTP::Post
|
298
|
+
# and make sure the query string contains '_method=put' (or
|
299
|
+
# '_method=delete').
|
300
|
+
#
|
301
|
+
# This call will also advertise this rufus-verbs as
|
302
|
+
# 'accepting the gzip encoding' (in case of GET).
|
303
|
+
#
|
304
|
+
def create_request (method, opts)
|
183
305
|
|
184
|
-
|
306
|
+
if (o(opts, :fake_put) and
|
307
|
+
(method == :put or method == :delete))
|
185
308
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
res.request = req
|
309
|
+
opts[:query][:_method] = method.to_s
|
310
|
+
method = :post
|
311
|
+
end
|
190
312
|
|
191
|
-
|
313
|
+
path = compute_path opts
|
192
314
|
|
193
|
-
|
194
|
-
# if the :cookies option is disabled (the default)
|
195
|
-
# will have no effect
|
315
|
+
r = eval("Net::HTTP::#{method.to_s.capitalize}").new path
|
196
316
|
|
197
|
-
|
317
|
+
r['User-Agent'] = o(opts, :user_agent)
|
318
|
+
# potentially overriden by opts[:headers]
|
198
319
|
|
199
|
-
|
200
|
-
|
201
|
-
# will have no effect else
|
320
|
+
h = opts[:headers] || opts[:h]
|
321
|
+
h.each { |k, v| r[k] = v } if h
|
202
322
|
|
203
|
-
|
323
|
+
r['Accept-Encoding'] = 'gzip' \
|
324
|
+
if method == :get and not o(opts, :nozip)
|
204
325
|
|
205
|
-
|
326
|
+
r
|
327
|
+
end
|
206
328
|
|
207
|
-
|
329
|
+
#
|
330
|
+
# If @user and @pass are set, will activate basic authentication.
|
331
|
+
# Else if the @auth option is set, will assume it contains a Proc
|
332
|
+
# and will call it (with the request as a parameter).
|
333
|
+
#
|
334
|
+
# This comment is too much... Just read the code...
|
335
|
+
#
|
336
|
+
def add_authentication (req, opts)
|
208
337
|
|
209
|
-
|
210
|
-
|
338
|
+
b = o(opts, :http_basic_authentication)
|
339
|
+
d = o(opts, :digest_authentication)
|
340
|
+
o = o(opts, :auth)
|
211
341
|
|
212
|
-
|
213
|
-
|
214
|
-
#
|
215
|
-
# Manages various args formats :
|
216
|
-
#
|
217
|
-
# uri
|
218
|
-
# [ uri ]
|
219
|
-
# [ uri, opts ]
|
220
|
-
# opts
|
221
|
-
#
|
222
|
-
def self.extract_opts (args)
|
223
|
-
|
224
|
-
opts = {}
|
225
|
-
|
226
|
-
args = [ args ] unless args.is_a?(Array)
|
227
|
-
|
228
|
-
opts = args.last \
|
229
|
-
if args.last.is_a?(Hash)
|
230
|
-
|
231
|
-
opts[:uri] = args.first \
|
232
|
-
if args.first.is_a?(String) or args.first.is_a?(URI)
|
342
|
+
if b and b != false
|
233
343
|
|
234
|
-
|
235
|
-
end
|
344
|
+
req.basic_auth b[0], b[1]
|
236
345
|
|
237
|
-
|
238
|
-
# Returns the value from the [request] opts or from the
|
239
|
-
# [endpoint] @opts.
|
240
|
-
#
|
241
|
-
def o (opts, key)
|
242
|
-
|
243
|
-
keys = Array key
|
244
|
-
keys.each { |k| (v = opts[k]; return v if v != nil) }
|
245
|
-
keys.each { |k| (v = @opts[k]; return v if v != nil) }
|
246
|
-
nil
|
247
|
-
end
|
248
|
-
|
249
|
-
#
|
250
|
-
# Returns scheme, host, port, path, query
|
251
|
-
#
|
252
|
-
def compute_target (opts)
|
346
|
+
elsif d and d != false
|
253
347
|
|
254
|
-
|
348
|
+
digest_auth req, opts
|
255
349
|
|
256
|
-
|
350
|
+
elsif o and o != false
|
257
351
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
opts[:path] || '/',
|
262
|
-
opts[:query] || opts[:params] ]
|
352
|
+
o.call req
|
353
|
+
end
|
354
|
+
end
|
263
355
|
|
264
|
-
|
356
|
+
#
|
357
|
+
# In that base class, it's empty.
|
358
|
+
# It's implemented in ConditionalEndPoint.
|
359
|
+
#
|
360
|
+
# Only called for a GET.
|
361
|
+
#
|
362
|
+
def add_conditional_headers (req, opts)
|
265
363
|
|
266
|
-
|
267
|
-
|
268
|
-
u.host,
|
269
|
-
u.port,
|
270
|
-
u.path,
|
271
|
-
query_to_h(u.query) ]
|
272
|
-
else
|
364
|
+
# nada
|
365
|
+
end
|
273
366
|
|
274
|
-
|
275
|
-
|
367
|
+
#
|
368
|
+
# Prepares a Net::HTTP instance, with potentially some
|
369
|
+
# https settings.
|
370
|
+
#
|
371
|
+
def prepare_http (opts)
|
276
372
|
|
277
|
-
|
278
|
-
opts[:host] = r[1] || @opts[:host]
|
279
|
-
opts[:port] = r[2] || @opts[:port]
|
280
|
-
opts[:path] = r[3] || @opts[:path]
|
373
|
+
compute_proxy opts
|
281
374
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
{}
|
375
|
+
http = Net::HTTP.new(
|
376
|
+
opts[:host], opts[:port],
|
377
|
+
opts[:proxy_host], opts[:proxy_port],
|
378
|
+
opts[:proxy_user], opts[:proxy_pass])
|
287
379
|
|
288
|
-
|
380
|
+
set_timeout http, opts
|
289
381
|
|
290
|
-
|
291
|
-
opts[:scheme],
|
292
|
-
opts[:host],
|
293
|
-
opts[:port],
|
294
|
-
opts[:path],
|
295
|
-
opts[:query] ].inspect
|
296
|
-
#
|
297
|
-
# can be used for conditional gets
|
382
|
+
return http unless opts[:scheme].to_s == 'https'
|
298
383
|
|
299
|
-
|
300
|
-
end
|
384
|
+
require 'net/https'
|
301
385
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
# If :fake_put is set, will use Net::HTTP::Post
|
306
|
-
# and make sure the query string contains '_method=put' (or
|
307
|
-
# '_method=delete').
|
308
|
-
#
|
309
|
-
# This call will also advertise this rufus-verbs as
|
310
|
-
# 'accepting the gzip encoding' (in case of GET).
|
311
|
-
#
|
312
|
-
def create_request (method, opts)
|
386
|
+
http.use_ssl = true
|
387
|
+
http.enable_post_connection_check = true \
|
388
|
+
if http.respond_to?(:enable_post_connection_check=)
|
313
389
|
|
314
|
-
|
315
|
-
|
390
|
+
http.verify_mode = if o(opts, :ssl_verify_peer)
|
391
|
+
OpenSSL::SSL::VERIFY_PEER
|
392
|
+
else
|
393
|
+
OpenSSL::SSL::VERIFY_NONE
|
394
|
+
end
|
316
395
|
|
317
|
-
|
318
|
-
|
319
|
-
|
396
|
+
store = OpenSSL::X509::Store.new
|
397
|
+
store.set_default_paths
|
398
|
+
http.cert_store = store
|
320
399
|
|
321
|
-
|
400
|
+
http
|
401
|
+
end
|
322
402
|
|
323
|
-
|
403
|
+
#
|
404
|
+
# Sets both the open_timeout and the read_timeout for the http
|
405
|
+
# instance
|
406
|
+
#
|
407
|
+
def set_timeout (http, opts)
|
324
408
|
|
325
|
-
|
326
|
-
|
409
|
+
to = o(opts, :timeout) || o(opts, :to)
|
410
|
+
to = to.to_i
|
327
411
|
|
328
|
-
|
329
|
-
h.each { |k, v| r[k] = v } if h
|
412
|
+
return if to == 0
|
330
413
|
|
331
|
-
|
332
|
-
|
414
|
+
http.open_timeout = to
|
415
|
+
http.read_timeout = to
|
416
|
+
end
|
333
417
|
|
334
|
-
|
335
|
-
|
418
|
+
#
|
419
|
+
# Makes sure the request opts hold the proxy information.
|
420
|
+
#
|
421
|
+
# If the option :proxy is set to false, no proxy will be used.
|
422
|
+
#
|
423
|
+
def compute_proxy (opts)
|
336
424
|
|
337
|
-
|
338
|
-
# If @user and @pass are set, will activate basic authentication.
|
339
|
-
# Else if the @auth option is set, will assume it contains a Proc
|
340
|
-
# and will call it (with the request as a parameter).
|
341
|
-
#
|
342
|
-
# This comment is too much... Just read the code...
|
343
|
-
#
|
344
|
-
def add_authentication (req, opts)
|
425
|
+
p = o(opts, :proxy)
|
345
426
|
|
346
|
-
|
347
|
-
d = o(opts, :digest_authentication)
|
348
|
-
o = o(opts, :auth)
|
427
|
+
return unless p
|
349
428
|
|
350
|
-
|
429
|
+
u = URI.parse p.to_s
|
351
430
|
|
352
|
-
|
431
|
+
raise "not an HTTP[S] proxy '#{u.host}'" \
|
432
|
+
unless u.scheme.match(/^http/)
|
353
433
|
|
354
|
-
|
434
|
+
opts[:proxy_host] = u.host
|
435
|
+
opts[:proxy_port] = u.port
|
436
|
+
opts[:proxy_user] = u.user
|
437
|
+
opts[:proxy_pass] = u.password
|
438
|
+
end
|
355
439
|
|
356
|
-
|
440
|
+
#
|
441
|
+
# Determines the full path of the request (path_info and
|
442
|
+
# query_string).
|
443
|
+
#
|
444
|
+
# For example :
|
445
|
+
#
|
446
|
+
# /items/4?style=whatever&maxcount=12
|
447
|
+
#
|
448
|
+
def compute_path (opts)
|
357
449
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
end
|
362
|
-
end
|
450
|
+
b = o(opts, :base)
|
451
|
+
r = o(opts, [ :res, :resource ])
|
452
|
+
i = o(opts, :id)
|
363
453
|
|
364
|
-
|
365
|
-
# In that base class, it's empty.
|
366
|
-
# It's implemented in ConditionalEndPoint.
|
367
|
-
#
|
368
|
-
# Only called for a GET.
|
369
|
-
#
|
370
|
-
def add_conditional_headers (req, opts)
|
454
|
+
path = o(opts, :path)
|
371
455
|
|
372
|
-
|
373
|
-
|
456
|
+
if b or r or i
|
457
|
+
path = b ? "/#{b}" : ''
|
458
|
+
path += "/#{r}" if r
|
459
|
+
path += "/#{i}" if i
|
460
|
+
end
|
374
461
|
|
375
|
-
|
376
|
-
# Prepares a Net::HTTP instance, with potentially some
|
377
|
-
# https settings.
|
378
|
-
#
|
379
|
-
def prepare_http (opts)
|
462
|
+
path = path[1..-1] if path[0..1] == '//'
|
380
463
|
|
381
|
-
|
464
|
+
query = opts[:query] || opts[:params]
|
382
465
|
|
383
|
-
|
384
|
-
opts[:host], opts[:port],
|
385
|
-
opts[:proxy_host], opts[:proxy_port],
|
386
|
-
opts[:proxy_user], opts[:proxy_pass])
|
466
|
+
return path if not query or query.size < 1
|
387
467
|
|
388
|
-
|
468
|
+
path + '?' + h_to_query(query, opts)
|
469
|
+
end
|
389
470
|
|
390
|
-
|
471
|
+
#
|
472
|
+
# "a=A&b=B" -> { "a" => "A", "b" => "B" }
|
473
|
+
#
|
474
|
+
def query_to_h (q)
|
391
475
|
|
392
|
-
|
476
|
+
return nil unless q
|
393
477
|
|
394
|
-
|
395
|
-
|
478
|
+
q.split('&').inject({}) do |r, e|
|
479
|
+
s = e.split('=')
|
480
|
+
r[s[0]] = s[1]
|
481
|
+
r
|
482
|
+
end
|
483
|
+
end
|
396
484
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
485
|
+
#
|
486
|
+
# { "a" => "A", "b" => "B" } -> "a=A&b=B"
|
487
|
+
#
|
488
|
+
def h_to_query (h, opts)
|
489
|
+
|
490
|
+
h.entries.collect { |k, v|
|
491
|
+
unless o(opts, :no_escape)
|
492
|
+
#k = URI.escape k.to_s
|
493
|
+
#v = URI.escape v.to_s
|
494
|
+
k = CGI.escape(k.to_s)
|
495
|
+
v = CGI.escape(v.to_s)
|
496
|
+
end
|
497
|
+
"#{k}=#{v}"
|
498
|
+
}.join('&')
|
499
|
+
end
|
402
500
|
|
403
|
-
|
404
|
-
|
405
|
-
|
501
|
+
#
|
502
|
+
# Fills the request body (with the content of :d or :fd).
|
503
|
+
#
|
504
|
+
def add_payload (req, opts, &block)
|
505
|
+
|
506
|
+
d = opts[:d] || opts[:data]
|
507
|
+
fd = opts[:fd] || opts[:form_data]
|
508
|
+
|
509
|
+
if d
|
510
|
+
req.body = d
|
511
|
+
elsif fd
|
512
|
+
sep = opts[:fd_sep] #|| nil
|
513
|
+
req.set_form_data fd, sep
|
514
|
+
elsif block
|
515
|
+
req.body = block.call req
|
516
|
+
else
|
517
|
+
req.body = ''
|
518
|
+
end
|
519
|
+
end
|
406
520
|
|
407
|
-
|
408
|
-
|
521
|
+
#
|
522
|
+
# Handles the server response.
|
523
|
+
# Eventually follows redirections.
|
524
|
+
#
|
525
|
+
# Once the final response has been hit, will make sure
|
526
|
+
# it's decompressed.
|
527
|
+
#
|
528
|
+
def handle_response (method, res, opts)
|
409
529
|
|
410
|
-
|
411
|
-
# Sets both the open_timeout and the read_timeout for the http
|
412
|
-
# instance
|
413
|
-
#
|
414
|
-
def set_timeout (http, opts)
|
530
|
+
nored = o(opts, [ :no_redirections, :noredir ])
|
415
531
|
|
416
|
-
|
417
|
-
|
532
|
+
#if res.is_a?(Net::HTTPRedirection)
|
533
|
+
if [ 301, 302, 303, 307 ].include?(res.code.to_i) and (nored != true)
|
418
534
|
|
419
|
-
|
535
|
+
maxr = o(opts, :max_redirections)
|
420
536
|
|
421
|
-
|
422
|
-
|
423
|
-
|
537
|
+
if maxr
|
538
|
+
maxr = maxr - 1
|
539
|
+
raise 'too many redirections' if maxr == -1
|
540
|
+
opts[:max_redirections] = maxr
|
541
|
+
end
|
424
542
|
|
425
|
-
|
426
|
-
# Makes sure the request opts hold the proxy information.
|
427
|
-
#
|
428
|
-
# If the option :proxy is set to false, no proxy will be used.
|
429
|
-
#
|
430
|
-
def compute_proxy (opts)
|
431
|
-
|
432
|
-
p = o(opts, :proxy)
|
433
|
-
|
434
|
-
return unless p
|
435
|
-
|
436
|
-
u = URI.parse p.to_s
|
437
|
-
|
438
|
-
raise "not an HTTP[S] proxy '#{u.host}'" \
|
439
|
-
unless u.scheme.match(/^http/)
|
440
|
-
|
441
|
-
opts[:proxy_host] = u.host
|
442
|
-
opts[:proxy_port] = u.port
|
443
|
-
opts[:proxy_user] = u.user
|
444
|
-
opts[:proxy_pass] = u.password
|
445
|
-
end
|
446
|
-
|
447
|
-
#
|
448
|
-
# Determines the full path of the request (path_info and
|
449
|
-
# query_string).
|
450
|
-
#
|
451
|
-
# For example :
|
452
|
-
#
|
453
|
-
# /items/4?style=whatever&maxcount=12
|
454
|
-
#
|
455
|
-
def compute_path (opts)
|
456
|
-
|
457
|
-
b = o(opts, :base)
|
458
|
-
r = o(opts, [ :res, :resource ])
|
459
|
-
i = o(opts, :id)
|
460
|
-
|
461
|
-
path = o(opts, :path)
|
462
|
-
|
463
|
-
if b or r or i
|
464
|
-
path = ""
|
465
|
-
path = "/#{b}" if b
|
466
|
-
path += "/#{r}" if r
|
467
|
-
path += "/#{i}" if i
|
468
|
-
end
|
469
|
-
|
470
|
-
path = path[1..-1] if path[0..1] == '//'
|
471
|
-
|
472
|
-
query = opts[:query] || opts[:params]
|
473
|
-
|
474
|
-
return path if not query or query.size < 1
|
475
|
-
|
476
|
-
path + '?' + h_to_query(query, opts)
|
477
|
-
end
|
478
|
-
|
479
|
-
#
|
480
|
-
# "a=A&b=B" -> { "a" => "A", "b" => "B" }
|
481
|
-
#
|
482
|
-
def query_to_h (q)
|
483
|
-
|
484
|
-
return nil unless q
|
485
|
-
|
486
|
-
q.split("&").inject({}) do |r, e|
|
487
|
-
s = e.split("=")
|
488
|
-
r[s[0]] = s[1]
|
489
|
-
r
|
490
|
-
end
|
491
|
-
end
|
492
|
-
|
493
|
-
#
|
494
|
-
# { "a" => "A", "b" => "B" } -> "a=A&b=B"
|
495
|
-
#
|
496
|
-
def h_to_query (h, opts)
|
497
|
-
|
498
|
-
h.entries.collect { |k, v|
|
499
|
-
unless o(opts, :no_escape)
|
500
|
-
k = URI.escape k.to_s
|
501
|
-
v = URI.escape v.to_s
|
502
|
-
end
|
503
|
-
"#{k}=#{v}"
|
504
|
-
}.join("&")
|
505
|
-
end
|
506
|
-
|
507
|
-
#
|
508
|
-
# Fills the request body (with the content of :d or :fd).
|
509
|
-
#
|
510
|
-
def add_payload (req, opts, &block)
|
511
|
-
|
512
|
-
d = opts[:d] || opts[:data]
|
513
|
-
fd = opts[:fd] || opts[:form_data]
|
514
|
-
|
515
|
-
if d
|
516
|
-
req.body = d
|
517
|
-
elsif fd
|
518
|
-
sep = opts[:fd_sep] #|| nil
|
519
|
-
req.set_form_data fd, sep
|
520
|
-
elsif block
|
521
|
-
req.body = block.call req
|
522
|
-
else
|
523
|
-
req.body = ""
|
524
|
-
end
|
525
|
-
end
|
526
|
-
|
527
|
-
#
|
528
|
-
# Handles the server response.
|
529
|
-
# Eventually follows redirections.
|
530
|
-
#
|
531
|
-
# Once the final response has been hit, will make sure
|
532
|
-
# it's decompressed.
|
533
|
-
#
|
534
|
-
def handle_response (method, res, opts)
|
535
|
-
|
536
|
-
nored = o(opts, [ :no_redirections, :noredir ])
|
537
|
-
|
538
|
-
#if res.is_a?(Net::HTTPRedirection)
|
539
|
-
if [ 301, 303, 307 ].include?(res.code.to_i) and (nored != true)
|
540
|
-
|
541
|
-
maxr = o(opts, :max_redirections)
|
542
|
-
|
543
|
-
if maxr
|
544
|
-
maxr = maxr - 1
|
545
|
-
raise "too many redirections" if maxr == -1
|
546
|
-
opts[:max_redirections] = maxr
|
547
|
-
end
|
548
|
-
|
549
|
-
location = res['Location']
|
550
|
-
|
551
|
-
prev_host = [ opts[:scheme], opts[:host] ]
|
552
|
-
|
553
|
-
if location.match /^http/
|
554
|
-
u = URI::parse location
|
555
|
-
opts[:scheme] = u.scheme
|
556
|
-
opts[:host] = u.host
|
557
|
-
opts[:port] = u.port
|
558
|
-
opts[:path] = u.path
|
559
|
-
opts[:query] = u.query
|
560
|
-
else
|
561
|
-
opts[:path], opts[:query] = location.split "?"
|
562
|
-
end
|
563
|
-
|
564
|
-
if (authentication_is_on?(opts) and
|
565
|
-
[ opts[:scheme], opts[:host] ] != prev_host)
|
566
|
-
|
567
|
-
raise(
|
568
|
-
"getting redirected to #{location} while " +
|
569
|
-
"authentication is on. Stopping.")
|
570
|
-
end
|
571
|
-
|
572
|
-
opts[:query] = query_to_h opts[:query]
|
573
|
-
|
574
|
-
return request(method, opts)
|
575
|
-
#
|
576
|
-
# following the redirection
|
577
|
-
end
|
578
|
-
|
579
|
-
decompress res
|
580
|
-
|
581
|
-
res
|
582
|
-
end
|
583
|
-
|
584
|
-
#
|
585
|
-
# Returns an array of symbols, like for example
|
586
|
-
#
|
587
|
-
# [ :get, :post ]
|
588
|
-
#
|
589
|
-
# obtained by parsing the 'Allow' response header.
|
590
|
-
#
|
591
|
-
# This method is used to provide the result of an OPTIONS
|
592
|
-
# HTTP method.
|
593
|
-
#
|
594
|
-
def parse_options (res)
|
595
|
-
|
596
|
-
s = res['Allow']
|
597
|
-
|
598
|
-
return [] unless s
|
599
|
-
|
600
|
-
s.split(",").collect do |m|
|
601
|
-
m.strip.downcase.to_sym
|
602
|
-
end
|
603
|
-
end
|
604
|
-
|
605
|
-
#
|
606
|
-
# Returns true if the current request has authentication
|
607
|
-
# going on.
|
608
|
-
#
|
609
|
-
def authentication_is_on? (opts)
|
610
|
-
|
611
|
-
(o(opts, [ :http_basic_authentication, :hba, :auth ]) != nil)
|
612
|
-
end
|
613
|
-
|
614
|
-
#
|
615
|
-
# Inflates the response body if necessary.
|
616
|
-
#
|
617
|
-
def decompress (res)
|
618
|
-
|
619
|
-
if res['content-encoding'] == 'gzip'
|
620
|
-
|
621
|
-
class << res
|
622
|
-
|
623
|
-
attr_accessor :deflated_body
|
624
|
-
|
625
|
-
alias :old_body :body
|
626
|
-
|
627
|
-
def body
|
628
|
-
@deflated_body || old_body
|
629
|
-
end
|
630
|
-
end
|
631
|
-
#
|
632
|
-
# reopened the response to add
|
633
|
-
# a 'deflated_body' attr and let the the body
|
634
|
-
# method point to it
|
543
|
+
location = res['Location']
|
635
544
|
|
636
|
-
|
545
|
+
prev_host = [ opts[:scheme], opts[:host] ]
|
637
546
|
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
547
|
+
if location.match /^http/
|
548
|
+
u = URI::parse location
|
549
|
+
opts[:scheme] = u.scheme
|
550
|
+
opts[:host] = u.host
|
551
|
+
opts[:port] = u.port
|
552
|
+
opts[:path] = u.path
|
553
|
+
opts[:query] = u.query
|
554
|
+
else
|
555
|
+
opts[:path], opts[:query] = location.split "?"
|
556
|
+
end
|
557
|
+
|
558
|
+
if (authentication_is_on?(opts) and
|
559
|
+
[ opts[:scheme], opts[:host] ] != prev_host)
|
560
|
+
|
561
|
+
raise(
|
562
|
+
"getting redirected to #{location} while " +
|
563
|
+
"authentication is on. Stopping.")
|
564
|
+
end
|
565
|
+
|
566
|
+
opts[:query] = query_to_h opts[:query]
|
567
|
+
|
568
|
+
return request(method, opts)
|
569
|
+
#
|
570
|
+
# following the redirection
|
571
|
+
end
|
572
|
+
|
573
|
+
decompress res
|
574
|
+
|
575
|
+
res
|
576
|
+
end
|
577
|
+
|
578
|
+
#
|
579
|
+
# Returns an array of symbols, like for example
|
580
|
+
#
|
581
|
+
# [ :get, :post ]
|
582
|
+
#
|
583
|
+
# obtained by parsing the 'Allow' response header.
|
584
|
+
#
|
585
|
+
# This method is used to provide the result of an OPTIONS
|
586
|
+
# HTTP method.
|
587
|
+
#
|
588
|
+
def parse_options (res)
|
589
|
+
|
590
|
+
s = res['Allow']
|
591
|
+
|
592
|
+
return [] unless s
|
593
|
+
|
594
|
+
s.split(',').collect do |m|
|
595
|
+
m.strip.downcase.to_sym
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
#
|
600
|
+
# Returns true if the current request has authentication
|
601
|
+
# going on.
|
602
|
+
#
|
603
|
+
def authentication_is_on? (opts)
|
604
|
+
|
605
|
+
(o(opts, [ :http_basic_authentication, :hba, :auth ]) != nil)
|
606
|
+
end
|
607
|
+
|
608
|
+
#
|
609
|
+
# Inflates the response body if necessary.
|
610
|
+
#
|
611
|
+
def decompress (res)
|
612
|
+
|
613
|
+
if res['content-encoding'] == 'gzip'
|
614
|
+
|
615
|
+
class << res
|
616
|
+
|
617
|
+
attr_accessor :deflated_body
|
618
|
+
|
619
|
+
alias :old_body :body
|
620
|
+
|
621
|
+
def body
|
622
|
+
@deflated_body || old_body
|
623
|
+
end
|
624
|
+
end
|
625
|
+
#
|
626
|
+
# reopened the response to add
|
627
|
+
# a 'deflated_body' attr and let the the body
|
628
|
+
# method point to it
|
629
|
+
|
630
|
+
# now deflate...
|
631
|
+
|
632
|
+
io = StringIO.new res.body
|
633
|
+
gz = Zlib::GzipReader.new io
|
634
|
+
res.deflated_body = gz.read
|
635
|
+
gz.close
|
636
|
+
end
|
644
637
|
end
|
638
|
+
end
|
645
639
|
end
|
646
640
|
end
|
647
641
|
|