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/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
|
|