curb 0.9.4 → 0.9.10
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.
- checksums.yaml +5 -5
- data/README.markdown +39 -19
- data/Rakefile +26 -10
- data/ext/curb.c +33 -6
- data/ext/curb.h +11 -4
- data/ext/curb_easy.c +375 -57
- data/ext/curb_easy.h +4 -0
- data/ext/curb_errors.c +86 -0
- data/ext/curb_multi.c +126 -165
- data/ext/curb_multi.h +0 -1
- data/ext/extconf.rb +31 -6
- data/lib/curb.rb +1 -0
- data/lib/curl.rb +7 -2
- data/lib/curl/easy.rb +10 -4
- data/lib/curl/multi.rb +50 -11
- data/tests/bug_issue277.rb +32 -0
- data/tests/helper.rb +89 -7
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_easy.rb +120 -16
- data/tests/tc_curl_easy_resolve.rb +16 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +68 -5
- metadata +27 -21
data/ext/curb_multi.h
CHANGED
data/ext/extconf.rb
CHANGED
@@ -59,6 +59,9 @@ def have_constant(name)
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
have_constant "curlopt_tcp_keepalive"
|
63
|
+
have_constant "curlopt_tcp_keepidle"
|
64
|
+
have_constant "curlopt_tcp_keepintvl"
|
62
65
|
have_constant "curlinfo_appconnect_time"
|
63
66
|
have_constant "curlinfo_redirect_time"
|
64
67
|
have_constant "curlinfo_response_code"
|
@@ -84,6 +87,7 @@ have_constant "curlproxy_http"
|
|
84
87
|
have_constant "curlproxy_socks4"
|
85
88
|
have_constant "curlproxy_socks4a"
|
86
89
|
have_constant "curlproxy_socks5"
|
90
|
+
have_constant "curlproxy_socks5_hostname"
|
87
91
|
have_constant "curlauth_basic"
|
88
92
|
have_constant "curlauth_digest"
|
89
93
|
have_constant "curlauth_gssnegotiate"
|
@@ -102,6 +106,9 @@ have_constant "curle_send_fail_rewind"
|
|
102
106
|
have_constant "curle_ssl_engine_initfailed"
|
103
107
|
have_constant "curle_login_denied"
|
104
108
|
|
109
|
+
# older than 7.10.0
|
110
|
+
have_constant "curlopt_nosignal"
|
111
|
+
|
105
112
|
# older than 7.16.0
|
106
113
|
have_constant "curlmopt_pipelining"
|
107
114
|
|
@@ -149,6 +156,8 @@ have_func("curl_multi_timeout")
|
|
149
156
|
have_func("curl_multi_fdset")
|
150
157
|
have_func("curl_multi_perform")
|
151
158
|
|
159
|
+
have_constant "curlopt_haproxyprotocol"
|
160
|
+
|
152
161
|
# constants
|
153
162
|
have_constant "curlopt_interleavefunction"
|
154
163
|
have_constant "curlopt_interleavedata"
|
@@ -213,6 +222,7 @@ have_constant "curlopt_httppost"
|
|
213
222
|
have_constant "curlopt_referer"
|
214
223
|
have_constant "curlopt_useragent"
|
215
224
|
have_constant "curlopt_httpheader"
|
225
|
+
have_constant "curlopt_proxyheader"
|
216
226
|
have_constant "curlopt_http200aliases"
|
217
227
|
have_constant "curlopt_cookie"
|
218
228
|
have_constant "curlopt_cookiefile"
|
@@ -322,14 +332,14 @@ have_constant "curlopt_sslengine"
|
|
322
332
|
have_constant "curlopt_sslengine_default"
|
323
333
|
have_constant "curlopt_sslversion"
|
324
334
|
have_constant "curl_sslversion_default"
|
325
|
-
have_constant
|
326
|
-
have_constant
|
327
|
-
have_constant
|
335
|
+
have_constant :CURL_SSLVERSION_TLSv1
|
336
|
+
have_constant :CURL_SSLVERSION_SSLv2
|
337
|
+
have_constant :CURL_SSLVERSION_SSLv3
|
328
338
|
|
329
339
|
# Added in 7.34.0
|
330
|
-
have_constant
|
331
|
-
have_constant
|
332
|
-
have_constant
|
340
|
+
have_constant :CURL_SSLVERSION_TLSv1_0
|
341
|
+
have_constant :CURL_SSLVERSION_TLSv1_1
|
342
|
+
have_constant :CURL_SSLVERSION_TLSv1_2
|
333
343
|
|
334
344
|
have_constant "curlopt_ssl_verifypeer"
|
335
345
|
have_constant "curlopt_cainfo"
|
@@ -364,6 +374,16 @@ have_constant "curle_not_built_in"
|
|
364
374
|
|
365
375
|
have_constant "curle_obsolete" # removed in 7.24 ?
|
366
376
|
|
377
|
+
have_constant "curle_ftp_pret_failed"
|
378
|
+
have_constant "curle_rtsp_cseq_error"
|
379
|
+
have_constant "curle_rtsp_session_error"
|
380
|
+
have_constant "curle_ftp_bad_file_list"
|
381
|
+
have_constant "curle_chunk_failed"
|
382
|
+
have_constant "curle_no_connection_available"
|
383
|
+
have_constant "curle_ssl_pinnedpubkeynotmatch"
|
384
|
+
have_constant "curle_ssl_invalidcertstatus"
|
385
|
+
have_constant "curle_http2_stream"
|
386
|
+
|
367
387
|
# gssapi/spnego delegation related constants
|
368
388
|
have_constant "curlopt_gssapi_delegation"
|
369
389
|
have_constant "curlgssapi_delegation_policy_flag"
|
@@ -377,6 +397,9 @@ have_constant "curlopt_unix_socket_path"
|
|
377
397
|
# added in 7.42.0
|
378
398
|
have_constant "curlopt_path_as_is"
|
379
399
|
|
400
|
+
# added in 7.43.0
|
401
|
+
have_constant "curlopt_pipewait"
|
402
|
+
|
380
403
|
if try_compile('int main() { return 0; }','-Wall')
|
381
404
|
$CFLAGS << ' -Wall'
|
382
405
|
end
|
@@ -404,6 +427,8 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
|
|
404
427
|
|
405
428
|
have_func('rb_thread_blocking_region')
|
406
429
|
have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
|
430
|
+
have_header('ruby/io.h')
|
431
|
+
have_func('rb_io_stdio_file')
|
407
432
|
|
408
433
|
create_header('curb_config.h')
|
409
434
|
create_makefile('curb_core')
|
data/lib/curb.rb
CHANGED
data/lib/curl.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'curb_core'
|
2
3
|
require 'curl/easy'
|
3
4
|
require 'curl/multi'
|
@@ -6,7 +7,7 @@ require 'cgi'
|
|
6
7
|
|
7
8
|
# expose shortcut methods
|
8
9
|
module Curl
|
9
|
-
|
10
|
+
|
10
11
|
def self.http(verb, url, post_body=nil, put_data=nil, &block)
|
11
12
|
handle = Thread.current[:curb_curl] ||= Curl::Easy.new
|
12
13
|
handle.reset
|
@@ -48,7 +49,11 @@ module Curl
|
|
48
49
|
|
49
50
|
def self.urlalize(url, params={})
|
50
51
|
uri = URI(url)
|
51
|
-
|
52
|
+
# early return if we didn't specify any extra params
|
53
|
+
return uri.to_s if (params || {}).empty?
|
54
|
+
|
55
|
+
params_query = URI.encode_www_form(params || {})
|
56
|
+
uri.query = [uri.query.to_s, params_query].reject(&:empty?).join('&')
|
52
57
|
uri.to_s
|
53
58
|
end
|
54
59
|
|
data/lib/curl/easy.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Curl
|
2
3
|
class Easy
|
3
4
|
|
@@ -19,9 +20,9 @@ module Curl
|
|
19
20
|
# easy.status => String
|
20
21
|
#
|
21
22
|
def status
|
22
|
-
# Matches the last HTTP Status - following the HTTP protocol specification 'Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF'
|
23
|
-
statuses = self.header_str.scan(/HTTP\/\d\.\d
|
24
|
-
statuses.last.strip
|
23
|
+
# Matches the last HTTP Status - following the HTTP protocol specification 'Status-Line = HTTP-Version SP Status-Code SP (Opt:)Reason-Phrase CRLF'
|
24
|
+
statuses = self.header_str.scan(/HTTP\/\d(\.\d)?\s(\d+\s.*)\r\n/).map{ |match| match[1] }
|
25
|
+
statuses.last.strip if statuses.length > 0
|
25
26
|
end
|
26
27
|
|
27
28
|
#
|
@@ -68,9 +69,14 @@ module Curl
|
|
68
69
|
ret = self.multi.perform
|
69
70
|
self.multi.remove self
|
70
71
|
|
72
|
+
if Curl::Multi.autoclose
|
73
|
+
self.multi.close
|
74
|
+
self.multi = nil
|
75
|
+
end
|
76
|
+
|
71
77
|
if self.last_result != 0 && self.on_failure.nil?
|
72
78
|
error = Curl::Easy.error(self.last_result)
|
73
|
-
raise error.first.new(
|
79
|
+
raise error.first.new(self.head)
|
74
80
|
end
|
75
81
|
|
76
82
|
ret
|
data/lib/curl/multi.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Curl
|
2
|
-
|
3
3
|
class Multi
|
4
4
|
class << self
|
5
5
|
# call-seq:
|
6
6
|
# Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy|
|
7
7
|
# easy
|
8
8
|
# end
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# Blocking call to fetch multiple url's in parallel.
|
11
11
|
def get(urls, easy_options={}, multi_options={}, &blk)
|
12
12
|
url_confs = []
|
@@ -25,10 +25,10 @@ module Curl
|
|
25
25
|
# {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
|
26
26
|
# easy_handle_on_request_complete
|
27
27
|
# end
|
28
|
-
#
|
28
|
+
#
|
29
29
|
# Blocking call to POST multiple form's in parallel.
|
30
|
-
#
|
31
|
-
# urls_with_config: is a hash of url's pointing to the postfields to send
|
30
|
+
#
|
31
|
+
# urls_with_config: is a hash of url's pointing to the postfields to send
|
32
32
|
# easy_options: are a set of common options to set on all easy handles
|
33
33
|
# multi_options: options to set on the Curl::Multi handle
|
34
34
|
#
|
@@ -49,10 +49,10 @@ module Curl
|
|
49
49
|
# {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
|
50
50
|
# easy_handle_on_request_complete
|
51
51
|
# end
|
52
|
-
#
|
52
|
+
#
|
53
53
|
# Blocking call to POST multiple form's in parallel.
|
54
|
-
#
|
55
|
-
# urls_with_config: is a hash of url's pointing to the postfields to send
|
54
|
+
#
|
55
|
+
# urls_with_config: is a hash of url's pointing to the postfields to send
|
56
56
|
# easy_options: are a set of common options to set on all easy handles
|
57
57
|
# multi_options: options to set on the Curl::Multi handle
|
58
58
|
#
|
@@ -79,7 +79,7 @@ module Curl
|
|
79
79
|
# Blocking call to issue multiple HTTP requests with varying verb's.
|
80
80
|
#
|
81
81
|
# urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used
|
82
|
-
# multi_options: options for the multi handle
|
82
|
+
# multi_options: options for the multi handle
|
83
83
|
# blk: a callback, that yeilds when a handle is completed
|
84
84
|
#
|
85
85
|
def http(urls_with_config, multi_options={}, &blk)
|
@@ -128,7 +128,7 @@ module Curl
|
|
128
128
|
|
129
129
|
# headers is a special key
|
130
130
|
headers.each {|k,v| easy.headers[k] = v } if headers
|
131
|
-
|
131
|
+
|
132
132
|
#
|
133
133
|
# use the remaining options as specific configuration to the easy handle
|
134
134
|
# bad options should raise an undefined method error
|
@@ -168,6 +168,7 @@ module Curl
|
|
168
168
|
end
|
169
169
|
free_handles = nil
|
170
170
|
end
|
171
|
+
|
171
172
|
end
|
172
173
|
|
173
174
|
# call-seq:
|
@@ -175,7 +176,7 @@ module Curl
|
|
175
176
|
# Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
|
176
177
|
#
|
177
178
|
# will create 2 new files file1.txt and file2.txt
|
178
|
-
#
|
179
|
+
#
|
179
180
|
# 2 files will be opened, and remain open until the call completes
|
180
181
|
#
|
181
182
|
# when using the :post or :put method, urls should be a hash, including the individual post fields per post
|
@@ -242,7 +243,45 @@ module Curl
|
|
242
243
|
}
|
243
244
|
raise errors unless errors.empty?
|
244
245
|
end
|
246
|
+
end
|
245
247
|
|
248
|
+
def cancel!
|
249
|
+
requests.each do |_,easy|
|
250
|
+
remove(easy)
|
251
|
+
end
|
246
252
|
end
|
253
|
+
|
254
|
+
def idle?
|
255
|
+
requests.empty?
|
256
|
+
end
|
257
|
+
|
258
|
+
def requests
|
259
|
+
@requests ||= {}
|
260
|
+
end
|
261
|
+
|
262
|
+
def add(easy)
|
263
|
+
return self if requests[easy.object_id]
|
264
|
+
requests[easy.object_id] = easy
|
265
|
+
_add(easy)
|
266
|
+
self
|
267
|
+
end
|
268
|
+
|
269
|
+
def remove(easy)
|
270
|
+
return self if !requests[easy.object_id]
|
271
|
+
requests.delete(easy.object_id)
|
272
|
+
_remove(easy)
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
def close
|
277
|
+
requests.values.each {|easy|
|
278
|
+
_remove(easy)
|
279
|
+
}
|
280
|
+
@requests = {}
|
281
|
+
_close
|
282
|
+
self
|
283
|
+
end
|
284
|
+
|
285
|
+
|
247
286
|
end
|
248
287
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
|
4
|
+
require 'curb'
|
5
|
+
|
6
|
+
class BugIssue102 < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_gc_closewait
|
9
|
+
100.times do
|
10
|
+
responses = {}
|
11
|
+
requests = ["http://www.google.co.uk/", "http://www.ruby-lang.org/"]
|
12
|
+
m = Curl::Multi.new
|
13
|
+
# add a few easy handles
|
14
|
+
requests.each do |url|
|
15
|
+
responses[url] = ""
|
16
|
+
c = Curl::Easy.new(url) do|curl|
|
17
|
+
curl.follow_location = true
|
18
|
+
curl.on_body{|data| responses[url] << data; data.size }
|
19
|
+
curl.on_success {|easy| #puts "success, add more easy handles"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
m.add(c)
|
23
|
+
end
|
24
|
+
|
25
|
+
m.perform do
|
26
|
+
#puts "idling... can do some work here"
|
27
|
+
end
|
28
|
+
GC.start
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/tests/helper.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# Copyright (c)2006 Ross Bamford. See LICENSE.
|
3
3
|
$CURB_TESTING = true
|
4
4
|
require 'uri'
|
5
|
+
require 'stringio'
|
5
6
|
|
6
7
|
$TOPDIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
7
8
|
$EXTDIR = File.join($TOPDIR, 'ext')
|
@@ -18,7 +19,7 @@ rescue LoadError
|
|
18
19
|
end
|
19
20
|
require 'fileutils'
|
20
21
|
|
21
|
-
$TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{
|
22
|
+
$TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{File.expand_path(__FILE__).tr('\\','/')}"
|
22
23
|
|
23
24
|
require 'thread'
|
24
25
|
require 'webrick'
|
@@ -30,19 +31,22 @@ require 'webrick'
|
|
30
31
|
TEST_SINGLE_THREADED=false
|
31
32
|
|
32
33
|
# keep webrick quiet
|
33
|
-
|
34
|
+
::WEBrick::HTTPServer.send(:remove_method,:access_log) if ::WEBrick::HTTPServer.instance_methods.include?(:access_log)
|
35
|
+
::WEBrick::BasicLog.send(:remove_method,:log) if ::WEBrick::BasicLog.instance_methods.include?(:log)
|
36
|
+
|
37
|
+
::WEBrick::HTTPServer.class_eval do
|
34
38
|
def access_log(config, req, res)
|
35
39
|
# nop
|
36
40
|
end
|
37
41
|
end
|
38
|
-
|
42
|
+
::WEBrick::BasicLog.class_eval do
|
39
43
|
def log(level, data)
|
40
44
|
# nop
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
44
48
|
#
|
45
|
-
# Simple test server to record number of times a request is sent/recieved of a specific
|
49
|
+
# Simple test server to record number of times a request is sent/recieved of a specific
|
46
50
|
# request type, e.g. GET,POST,PUT,DELETE
|
47
51
|
#
|
48
52
|
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
@@ -94,7 +98,7 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
94
98
|
if params and params['s'] == '500'
|
95
99
|
res.status = 500
|
96
100
|
elsif params and params['c']
|
97
|
-
cookie = URI.
|
101
|
+
cookie = URI.decode_www_form_component(params['c']).split('=')
|
98
102
|
res.cookies << WEBrick::Cookie.new(*cookie)
|
99
103
|
else
|
100
104
|
respond_with("POST\n#{req.body}",req,res)
|
@@ -147,7 +151,7 @@ module TestServerMethods
|
|
147
151
|
rd.close
|
148
152
|
rd = nil
|
149
153
|
|
150
|
-
# start up a webrick server for testing delete
|
154
|
+
# start up a webrick server for testing delete
|
151
155
|
server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
152
156
|
|
153
157
|
server.mount(servlet.path, servlet)
|
@@ -163,7 +167,7 @@ module TestServerMethods
|
|
163
167
|
rd.read
|
164
168
|
rd.close
|
165
169
|
else
|
166
|
-
# start up a webrick server for testing delete
|
170
|
+
# start up a webrick server for testing delete
|
167
171
|
@server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
168
172
|
|
169
173
|
@server.mount(servlet.path, servlet)
|
@@ -204,3 +208,81 @@ module TestServerMethods
|
|
204
208
|
rescue Errno::EADDRINUSE
|
205
209
|
end
|
206
210
|
end
|
211
|
+
|
212
|
+
|
213
|
+
|
214
|
+
# Backport for Ruby 1.8
|
215
|
+
module Backports
|
216
|
+
module Ruby18
|
217
|
+
module URIFormEncoding
|
218
|
+
TBLENCWWWCOMP_ = {}
|
219
|
+
TBLDECWWWCOMP_ = {}
|
220
|
+
|
221
|
+
def encode_www_form_component(str)
|
222
|
+
if TBLENCWWWCOMP_.empty?
|
223
|
+
256.times do |i|
|
224
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
225
|
+
end
|
226
|
+
TBLENCWWWCOMP_[' '] = '+'
|
227
|
+
TBLENCWWWCOMP_.freeze
|
228
|
+
end
|
229
|
+
str.to_s.gsub( /([^*\-.0-9A-Z_a-z])/ ) {|*| TBLENCWWWCOMP_[$1] }
|
230
|
+
end
|
231
|
+
|
232
|
+
def decode_www_form_component(str)
|
233
|
+
if TBLDECWWWCOMP_.empty?
|
234
|
+
256.times do |i|
|
235
|
+
h, l = i>>4, i&15
|
236
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
237
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
238
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
239
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
240
|
+
end
|
241
|
+
TBLDECWWWCOMP_['+'] = ' '
|
242
|
+
TBLDECWWWCOMP_.freeze
|
243
|
+
end
|
244
|
+
|
245
|
+
raise ArgumentError, "invalid %-encoding (#{str.dump})" unless /\A(?:%[[:xdigit:]]{2}|[^%]+)*\z/ =~ str
|
246
|
+
str.gsub( /(\+|%[[:xdigit:]]{2})/ ) {|*| TBLDECWWWCOMP_[$1] }
|
247
|
+
end
|
248
|
+
|
249
|
+
def encode_www_form( enum )
|
250
|
+
enum.map do |k,v|
|
251
|
+
if v.nil?
|
252
|
+
encode_www_form_component(k)
|
253
|
+
elsif v.respond_to?(:to_ary)
|
254
|
+
v.to_ary.map do |w|
|
255
|
+
str = encode_www_form_component(k)
|
256
|
+
unless w.nil?
|
257
|
+
str << '='
|
258
|
+
str << encode_www_form_component(w)
|
259
|
+
end
|
260
|
+
end.join('&')
|
261
|
+
else
|
262
|
+
str = encode_www_form_component(k)
|
263
|
+
str << '='
|
264
|
+
str << encode_www_form_component(v)
|
265
|
+
end
|
266
|
+
end.join('&')
|
267
|
+
end
|
268
|
+
|
269
|
+
WFKV_ = '(?:%\h\h|[^%#=;&])'
|
270
|
+
def decode_www_form(str, _)
|
271
|
+
return [] if str.to_s == ''
|
272
|
+
|
273
|
+
unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/ =~ str
|
274
|
+
raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
|
275
|
+
end
|
276
|
+
ary = []
|
277
|
+
$&.scan(/([^=;&]+)=([^;&]*)/) do
|
278
|
+
ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
|
279
|
+
end
|
280
|
+
ary
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
unless URI.methods.include?(:encode_www_form)
|
287
|
+
URI.extend(Backports::Ruby18::URIFormEncoding)
|
288
|
+
end
|