curb 0.9.3 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/extconf.rb CHANGED
@@ -18,6 +18,8 @@ elsif !have_library('curl') or !have_header('curl/curl.h')
18
18
  fail <<-EOM
19
19
  Can't find libcurl or curl/curl.h
20
20
 
21
+ Make sure development libs (ie libcurl4-openssl-dev) are installed on the system.
22
+
21
23
  Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
22
24
  options to extconf.
23
25
  EOM
@@ -59,6 +61,9 @@ def have_constant(name)
59
61
  end
60
62
  end
61
63
 
64
+ have_constant "curlopt_tcp_keepalive"
65
+ have_constant "curlopt_tcp_keepidle"
66
+ have_constant "curlopt_tcp_keepintvl"
62
67
  have_constant "curlinfo_appconnect_time"
63
68
  have_constant "curlinfo_redirect_time"
64
69
  have_constant "curlinfo_response_code"
@@ -66,6 +71,7 @@ have_constant "curlinfo_filetime"
66
71
  have_constant "curlinfo_redirect_count"
67
72
  have_constant "curlinfo_os_errno"
68
73
  have_constant "curlinfo_num_connects"
74
+ have_constant "curlinfo_cookielist"
69
75
  have_constant "curlinfo_ftp_entry_path"
70
76
  have_constant "curl_version_ssl"
71
77
  have_constant "curl_version_libz"
@@ -83,6 +89,7 @@ have_constant "curlproxy_http"
83
89
  have_constant "curlproxy_socks4"
84
90
  have_constant "curlproxy_socks4a"
85
91
  have_constant "curlproxy_socks5"
92
+ have_constant "curlproxy_socks5_hostname"
86
93
  have_constant "curlauth_basic"
87
94
  have_constant "curlauth_digest"
88
95
  have_constant "curlauth_gssnegotiate"
@@ -101,6 +108,12 @@ have_constant "curle_send_fail_rewind"
101
108
  have_constant "curle_ssl_engine_initfailed"
102
109
  have_constant "curle_login_denied"
103
110
 
111
+ # older than 7.10.0
112
+ have_constant "curlopt_nosignal"
113
+
114
+ # older than 7.16.0
115
+ have_constant "curlmopt_pipelining"
116
+
104
117
  # older than 7.16.3
105
118
  have_constant "curlmopt_maxconnects"
106
119
 
@@ -145,6 +158,8 @@ have_func("curl_multi_timeout")
145
158
  have_func("curl_multi_fdset")
146
159
  have_func("curl_multi_perform")
147
160
 
161
+ have_constant "curlopt_haproxyprotocol"
162
+
148
163
  # constants
149
164
  have_constant "curlopt_interleavefunction"
150
165
  have_constant "curlopt_interleavedata"
@@ -209,6 +224,7 @@ have_constant "curlopt_httppost"
209
224
  have_constant "curlopt_referer"
210
225
  have_constant "curlopt_useragent"
211
226
  have_constant "curlopt_httpheader"
227
+ have_constant "curlopt_proxyheader"
212
228
  have_constant "curlopt_http200aliases"
213
229
  have_constant "curlopt_cookie"
214
230
  have_constant "curlopt_cookiefile"
@@ -318,14 +334,17 @@ have_constant "curlopt_sslengine"
318
334
  have_constant "curlopt_sslengine_default"
319
335
  have_constant "curlopt_sslversion"
320
336
  have_constant "curl_sslversion_default"
321
- have_constant "curl_sslversion_tlsv1"
322
- have_constant "curl_sslversion_sslv2"
323
- have_constant "curl_sslversion_sslv3"
337
+ have_constant :CURL_SSLVERSION_TLSv1
338
+ have_constant :CURL_SSLVERSION_SSLv2
339
+ have_constant :CURL_SSLVERSION_SSLv3
324
340
 
325
341
  # Added in 7.34.0
326
- have_constant "curl_sslversion_tlsv1_0"
327
- have_constant "curl_sslversion_tlsv1_1"
328
- have_constant "curl_sslversion_tlsv1_2"
342
+ have_constant :CURL_SSLVERSION_TLSv1_0
343
+ have_constant :CURL_SSLVERSION_TLSv1_1
344
+ have_constant :CURL_SSLVERSION_TLSv1_2
345
+
346
+ # Added in 7.52.0
347
+ have_constant :CURL_SSLVERSION_TLSv1_3
329
348
 
330
349
  have_constant "curlopt_ssl_verifypeer"
331
350
  have_constant "curlopt_cainfo"
@@ -360,6 +379,16 @@ have_constant "curle_not_built_in"
360
379
 
361
380
  have_constant "curle_obsolete" # removed in 7.24 ?
362
381
 
382
+ have_constant "curle_ftp_pret_failed"
383
+ have_constant "curle_rtsp_cseq_error"
384
+ have_constant "curle_rtsp_session_error"
385
+ have_constant "curle_ftp_bad_file_list"
386
+ have_constant "curle_chunk_failed"
387
+ have_constant "curle_no_connection_available"
388
+ have_constant "curle_ssl_pinnedpubkeynotmatch"
389
+ have_constant "curle_ssl_invalidcertstatus"
390
+ have_constant "curle_http2_stream"
391
+
363
392
  # gssapi/spnego delegation related constants
364
393
  have_constant "curlopt_gssapi_delegation"
365
394
  have_constant "curlgssapi_delegation_policy_flag"
@@ -370,6 +399,43 @@ have_constant "CURLM_ADDED_ALREADY"
370
399
  # added in 7.40.0
371
400
  have_constant "curlopt_unix_socket_path"
372
401
 
402
+ # added in 7.42.0
403
+ have_constant "curlopt_path_as_is"
404
+
405
+ # added in 7.43.0
406
+ have_constant "curlopt_pipewait"
407
+
408
+ # protocol constants
409
+ have_constant "curlproto_all"
410
+ have_constant "curlproto_dict"
411
+ have_constant "curlproto_file"
412
+ have_constant "curlproto_ftp"
413
+ have_constant "curlproto_ftps"
414
+ have_constant "curlproto_gopher"
415
+ have_constant "curlproto_http"
416
+ have_constant "curlproto_https"
417
+ have_constant "curlproto_imap"
418
+ have_constant "curlproto_imaps"
419
+ have_constant "curlproto_ldap"
420
+ have_constant "curlproto_ldaps"
421
+ have_constant "curlproto_pop3"
422
+ have_constant "curlproto_pop3s"
423
+ have_constant "curlproto_rtmp"
424
+ have_constant "curlproto_rtmpe"
425
+ have_constant "curlproto_rtmps"
426
+ have_constant "curlproto_rtmpt"
427
+ have_constant "curlproto_rtmpte"
428
+ have_constant "curlproto_rtmpts"
429
+ have_constant "curlproto_rtsp"
430
+ have_constant "curlproto_scp"
431
+ have_constant "curlproto_sftp"
432
+ have_constant "curlproto_smb"
433
+ have_constant "curlproto_smbs"
434
+ have_constant "curlproto_smtp"
435
+ have_constant "curlproto_smtps"
436
+ have_constant "curlproto_telnet"
437
+ have_constant "curlproto_tftp"
438
+
373
439
  if try_compile('int main() { return 0; }','-Wall')
374
440
  $CFLAGS << ' -Wall'
375
441
  end
@@ -397,6 +463,8 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
397
463
 
398
464
  have_func('rb_thread_blocking_region')
399
465
  have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
466
+ have_header('ruby/io.h')
467
+ have_func('rb_io_stdio_file')
400
468
 
401
469
  create_header('curb_config.h')
402
470
  create_makefile('curb_core')
data/lib/curb.rb CHANGED
@@ -1 +1,2 @@
1
+ # frozen_string_literal: true
1
2
  require 'curl'
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\s(\d+\s.*)\r\n/).map{ |match| match[0] }
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.to_s.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,15 @@ 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
- error = Curl::Easy.error(self.last_result)
73
- raise error.first.new(error.last)
78
+ (err_class, err_summary) = Curl::Easy.error(self.last_result)
79
+ err_detail = self.last_error
80
+ raise err_class.new([err_summary, err_detail].compact.join(": "))
74
81
  end
75
82
 
76
83
  ret
@@ -314,7 +321,7 @@ module Curl
314
321
 
315
322
  #
316
323
  # call-seq:
317
- # Curl::Easy.perform(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
324
+ # Curl::Easy.perform(url) { |easy| ... } => #<Curl::Easy...>
318
325
  #
319
326
  # Convenience method that creates a new Curl::Easy instance with
320
327
  # the specified URL and calls the general +perform+ method, before returning
@@ -332,7 +339,7 @@ module Curl
332
339
 
333
340
  #
334
341
  # call-seq:
335
- # Curl::Easy.http_get(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
342
+ # Curl::Easy.http_get(url) { |easy| ... } => #<Curl::Easy...>
336
343
  #
337
344
  # Convenience method that creates a new Curl::Easy instance with
338
345
  # the specified URL and calls +http_get+, before returning the new instance.
@@ -349,7 +356,7 @@ module Curl
349
356
 
350
357
  #
351
358
  # call-seq:
352
- # Curl::Easy.http_head(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
359
+ # Curl::Easy.http_head(url) { |easy| ... } => #<Curl::Easy...>
353
360
  #
354
361
  # Convenience method that creates a new Curl::Easy instance with
355
362
  # the specified URL and calls +http_head+, before returning the new instance.
@@ -403,7 +410,7 @@ module Curl
403
410
 
404
411
  #
405
412
  # call-seq:
406
- # Curl::Easy.http_delete(url) { |easy| ... } => #&lt;Curl::Easy...&gt;
413
+ # Curl::Easy.http_delete(url) { |easy| ... } => #<Curl::Easy...>
407
414
  #
408
415
  # Convenience method that creates a new Curl::Easy instance with
409
416
  # the specified URL and calls +http_delete+, before returning the new instance.
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
@@ -144,7 +144,7 @@ module Curl
144
144
 
145
145
  max_connects.times do
146
146
  conf = urls_with_config.pop
147
- add_free_handle.call conf, nil
147
+ add_free_handle.call(conf, nil) if conf
148
148
  break if urls_with_config.empty?
149
149
  end
150
150
 
@@ -153,7 +153,7 @@ module Curl
153
153
  if urls_with_config.size > 0 && free_handles.size > 0
154
154
  easy = free_handles.pop
155
155
  conf = urls_with_config.pop
156
- add_free_handle.call conf, easy
156
+ add_free_handle.call(conf, easy) if conf
157
157
  end
158
158
  end
159
159
 
@@ -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
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,14 +7,22 @@ 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
- handle = Thread.current[:curb_curl] ||= Curl::Easy.new
12
- handle.reset
12
+ if Thread.current[:curb_curl_yielding]
13
+ handle = Curl::Easy.new # we can't reuse this
14
+ else
15
+ handle = Thread.current[:curb_curl] ||= Curl::Easy.new
16
+ handle.reset
17
+ end
13
18
  handle.url = url
14
19
  handle.post_body = post_body if post_body
15
20
  handle.put_data = put_data if put_data
16
- yield handle if block_given?
21
+ if block_given?
22
+ Thread.current[:curb_curl_yielding] = true
23
+ yield handle
24
+ Thread.current[:curb_curl_yielding] = false
25
+ end
17
26
  handle.http(verb)
18
27
  handle
19
28
  end
@@ -47,14 +56,13 @@ module Curl
47
56
  end
48
57
 
49
58
  def self.urlalize(url, params={})
50
- query_str = params.map {|k,v| "#{URI.escape(k.to_s)}=#{CGI.escape(v.to_s)}" }.join('&')
51
- if url.match(/\?/) && query_str.size > 0
52
- "#{url}&#{query_str}"
53
- elsif query_str.size > 0
54
- "#{url}?#{query_str}"
55
- else
56
- url
57
- end
59
+ uri = URI(url)
60
+ # early return if we didn't specify any extra params
61
+ return uri.to_s if (params || {}).empty?
62
+
63
+ params_query = URI.encode_www_form(params || {})
64
+ uri.query = [uri.query.to_s, params_query].reject(&:empty?).join('&')
65
+ uri.to_s
58
66
  end
59
67
 
60
68
  def self.postalize(params={})
@@ -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://#{URI.escape(File.expand_path(__FILE__).tr('\\','/').tr(':','|'))}"
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
- class ::WEBrick::HTTPServer
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
- class ::WEBrick::BasicLog
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
@@ -52,13 +56,12 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
52
56
  end
53
57
 
54
58
  def self.port
55
- (@port or 9129)
59
+ @port ||= 9129
56
60
  end
57
61
 
58
62
  def self.path
59
63
  '/methods'
60
64
  end
61
-
62
65
  def self.url
63
66
  "http://127.0.0.1:#{port}#{path}"
64
67
  end
@@ -70,12 +73,12 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
70
73
  end
71
74
 
72
75
  def do_GET(req,res)
73
- if req.path.match /redirect$/
76
+ if req.path.match(/redirect$/)
74
77
  res.status = 302
75
78
  res['Location'] = '/foo'
76
- elsif req.path.match /not_here$/
79
+ elsif req.path.match(/not_here$/)
77
80
  res.status = 404
78
- elsif req.path.match /error$/
81
+ elsif req.path.match(/error$/)
79
82
  res.status = 500
80
83
  end
81
84
  respond_with("GET#{req.query_string}",req,res)
@@ -94,6 +97,9 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
94
97
  end
95
98
  if params and params['s'] == '500'
96
99
  res.status = 500
100
+ elsif params and params['c']
101
+ cookie = URI.decode_www_form_component(params['c']).split('=')
102
+ res.cookies << WEBrick::Cookie.new(*cookie)
97
103
  else
98
104
  respond_with("POST\n#{req.body}",req,res)
99
105
  end
@@ -136,8 +142,7 @@ module TestServerMethods
136
142
 
137
143
  def server_setup(port=9129,servlet=TestServlet)
138
144
  @__port = port
139
- if @server.nil? and !File.exist?(locked_file)
140
-
145
+ if (@server ||= nil).nil? and !File.exist?(locked_file)
141
146
  File.open(locked_file,'w') {|f| f << 'locked' }
142
147
  if TEST_SINGLE_THREADED
143
148
  rd, wr = IO.pipe
@@ -145,7 +150,7 @@ module TestServerMethods
145
150
  rd.close
146
151
  rd = nil
147
152
 
148
- # start up a webrick server for testing delete
153
+ # start up a webrick server for testing delete
149
154
  server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
150
155
 
151
156
  server.mount(servlet.path, servlet)
@@ -161,7 +166,7 @@ module TestServerMethods
161
166
  rd.read
162
167
  rd.close
163
168
  else
164
- # start up a webrick server for testing delete
169
+ # start up a webrick server for testing delete
165
170
  @server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
166
171
 
167
172
  @server.mount(servlet.path, servlet)
@@ -202,3 +207,81 @@ module TestServerMethods
202
207
  rescue Errno::EADDRINUSE
203
208
  end
204
209
  end
210
+
211
+
212
+
213
+ # Backport for Ruby 1.8
214
+ module Backports
215
+ module Ruby18
216
+ module URIFormEncoding
217
+ TBLENCWWWCOMP_ = {}
218
+ TBLDECWWWCOMP_ = {}
219
+
220
+ def encode_www_form_component(str)
221
+ if TBLENCWWWCOMP_.empty?
222
+ 256.times do |i|
223
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
224
+ end
225
+ TBLENCWWWCOMP_[' '] = '+'
226
+ TBLENCWWWCOMP_.freeze
227
+ end
228
+ str.to_s.gsub( /([^*\-.0-9A-Z_a-z])/ ) {|*| TBLENCWWWCOMP_[$1] }
229
+ end
230
+
231
+ def decode_www_form_component(str)
232
+ if TBLDECWWWCOMP_.empty?
233
+ 256.times do |i|
234
+ h, l = i>>4, i&15
235
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
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
+ end
240
+ TBLDECWWWCOMP_['+'] = ' '
241
+ TBLDECWWWCOMP_.freeze
242
+ end
243
+
244
+ raise ArgumentError, "invalid %-encoding (#{str.dump})" unless /\A(?:%[[:xdigit:]]{2}|[^%]+)*\z/ =~ str
245
+ str.gsub( /(\+|%[[:xdigit:]]{2})/ ) {|*| TBLDECWWWCOMP_[$1] }
246
+ end
247
+
248
+ def encode_www_form( enum )
249
+ enum.map do |k,v|
250
+ if v.nil?
251
+ encode_www_form_component(k)
252
+ elsif v.respond_to?(:to_ary)
253
+ v.to_ary.map do |w|
254
+ str = encode_www_form_component(k)
255
+ unless w.nil?
256
+ str << '='
257
+ str << encode_www_form_component(w)
258
+ end
259
+ end.join('&')
260
+ else
261
+ str = encode_www_form_component(k)
262
+ str << '='
263
+ str << encode_www_form_component(v)
264
+ end
265
+ end.join('&')
266
+ end
267
+
268
+ WFKV_ = '(?:%\h\h|[^%#=;&])'
269
+ def decode_www_form(str, _)
270
+ return [] if str.to_s == ''
271
+
272
+ unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/ =~ str
273
+ raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
274
+ end
275
+ ary = []
276
+ $&.scan(/([^=;&]+)=([^;&]*)/) do
277
+ ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
278
+ end
279
+ ary
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ unless URI.methods.include?(:encode_www_form)
286
+ URI.extend(Backports::Ruby18::URIFormEncoding)
287
+ end
data/tests/tc_curl.rb CHANGED
@@ -31,7 +31,37 @@ class TestCurl < Test::Unit::TestCase
31
31
  assert_equal "OPTIONSfoo=bar", curl.body_str
32
32
  end
33
33
 
34
- include TestServerMethods
34
+ def test_urlalize_without_extra_params
35
+ url_no_params = 'http://localhost/test'
36
+ url_with_params = 'http://localhost/test?a=1'
37
+
38
+ assert_equal(url_no_params, Curl.urlalize(url_no_params))
39
+ assert_equal(url_with_params, Curl.urlalize(url_with_params))
40
+ end
41
+
42
+ def test_urlalize_with_nil_as_params
43
+ url = 'http://localhost/test'
44
+ assert_equal(url, Curl.urlalize(url, nil))
45
+ end
46
+
47
+ def test_urlalize_with_extra_params
48
+ url_no_params = 'http://localhost/test'
49
+ url_with_params = 'http://localhost/test?a=1'
50
+ extra_params = { :b => 2 }
51
+
52
+ expected_url_no_params = 'http://localhost/test?b=2'
53
+ expected_url_with_params = 'http://localhost/test?a=1&b=2'
54
+
55
+ assert_equal(expected_url_no_params, Curl.urlalize(url_no_params, extra_params))
56
+ assert_equal(expected_url_with_params, Curl.urlalize(url_with_params, extra_params))
57
+ end
58
+
59
+ def test_urlalize_does_not_strip_trailing_?
60
+ url_empty_params = 'http://localhost/test?'
61
+ assert_equal(url_empty_params, Curl.urlalize(url_empty_params))
62
+ end
63
+
64
+ include TestServerMethods
35
65
 
36
66
  def setup
37
67
  server_setup