curb 0.7.15 → 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.
- checksums.yaml +7 -0
- data/README.markdown +283 -0
- data/Rakefile +38 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +903 -46
- data/ext/curb.h +20 -11
- data/ext/curb_easy.c +1039 -565
- data/ext/curb_easy.h +12 -0
- data/ext/curb_errors.c +127 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +10 -6
- data/ext/curb_multi.c +245 -167
- data/ext/curb_multi.h +0 -1
- data/ext/curb_upload.c +2 -2
- data/ext/extconf.rb +314 -20
- data/lib/curb.rb +2 -308
- data/lib/curl/easy.rb +489 -0
- data/lib/curl/multi.rb +287 -0
- data/lib/curl.rb +68 -1
- data/tests/bug_crash_on_debug.rb +39 -0
- data/tests/bug_crash_on_progress.rb +73 -0
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +2 -2
- data/tests/bug_issue102.rb +17 -0
- data/tests/bug_require_last_or_segfault.rb +1 -1
- data/tests/helper.rb +120 -16
- data/tests/signals.rb +33 -0
- data/tests/tc_curl.rb +69 -0
- data/tests/tc_curl_download.rb +4 -4
- data/tests/tc_curl_easy.rb +327 -43
- data/tests/tc_curl_easy_resolve.rb +16 -0
- data/tests/tc_curl_easy_setopt.rb +31 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +141 -15
- data/tests/tc_curl_postfield.rb +29 -29
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/timeout.rb +30 -6
- metadata +61 -58
- data/README +0 -177
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')
|
|
@@ -10,10 +11,15 @@ $:.unshift($LIBDIR)
|
|
|
10
11
|
$:.unshift($EXTDIR)
|
|
11
12
|
|
|
12
13
|
require 'curb'
|
|
13
|
-
|
|
14
|
+
begin
|
|
15
|
+
require 'test/unit'
|
|
16
|
+
rescue LoadError
|
|
17
|
+
gem 'test/unit'
|
|
18
|
+
require 'test/unit'
|
|
19
|
+
end
|
|
14
20
|
require 'fileutils'
|
|
15
21
|
|
|
16
|
-
$TEST_URL = "file://#{
|
|
22
|
+
$TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{File.expand_path(__FILE__).tr('\\','/')}"
|
|
17
23
|
|
|
18
24
|
require 'thread'
|
|
19
25
|
require 'webrick'
|
|
@@ -25,19 +31,22 @@ require 'webrick'
|
|
|
25
31
|
TEST_SINGLE_THREADED=false
|
|
26
32
|
|
|
27
33
|
# keep webrick quiet
|
|
28
|
-
|
|
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
|
|
29
38
|
def access_log(config, req, res)
|
|
30
39
|
# nop
|
|
31
40
|
end
|
|
32
41
|
end
|
|
33
|
-
|
|
42
|
+
::WEBrick::BasicLog.class_eval do
|
|
34
43
|
def log(level, data)
|
|
35
44
|
# nop
|
|
36
45
|
end
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
#
|
|
40
|
-
# 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
|
|
41
50
|
# request type, e.g. GET,POST,PUT,DELETE
|
|
42
51
|
#
|
|
43
52
|
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
@@ -47,13 +56,12 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
47
56
|
end
|
|
48
57
|
|
|
49
58
|
def self.port
|
|
50
|
-
|
|
59
|
+
@port ||= 9129
|
|
51
60
|
end
|
|
52
61
|
|
|
53
62
|
def self.path
|
|
54
63
|
'/methods'
|
|
55
64
|
end
|
|
56
|
-
|
|
57
65
|
def self.url
|
|
58
66
|
"http://127.0.0.1:#{port}#{path}"
|
|
59
67
|
end
|
|
@@ -65,12 +73,20 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
65
73
|
end
|
|
66
74
|
|
|
67
75
|
def do_GET(req,res)
|
|
68
|
-
|
|
76
|
+
if req.path.match(/redirect$/)
|
|
77
|
+
res.status = 302
|
|
78
|
+
res['Location'] = '/foo'
|
|
79
|
+
elsif req.path.match(/not_here$/)
|
|
80
|
+
res.status = 404
|
|
81
|
+
elsif req.path.match(/error$/)
|
|
82
|
+
res.status = 500
|
|
83
|
+
end
|
|
84
|
+
respond_with("GET#{req.query_string}",req,res)
|
|
69
85
|
end
|
|
70
86
|
|
|
71
87
|
def do_HEAD(req,res)
|
|
72
88
|
res['Location'] = "/nonexistent"
|
|
73
|
-
respond_with(
|
|
89
|
+
respond_with("HEAD#{req.query_string}",req,res)
|
|
74
90
|
end
|
|
75
91
|
|
|
76
92
|
def do_POST(req,res)
|
|
@@ -81,6 +97,9 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
81
97
|
end
|
|
82
98
|
if params and params['s'] == '500'
|
|
83
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)
|
|
84
103
|
else
|
|
85
104
|
respond_with("POST\n#{req.body}",req,res)
|
|
86
105
|
end
|
|
@@ -95,15 +114,23 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
95
114
|
end
|
|
96
115
|
|
|
97
116
|
def do_DELETE(req,res)
|
|
98
|
-
respond_with(
|
|
117
|
+
respond_with("DELETE#{req.query_string}",req,res)
|
|
99
118
|
end
|
|
100
119
|
|
|
101
120
|
def do_PURGE(req,res)
|
|
102
|
-
respond_with(
|
|
121
|
+
respond_with("PURGE#{req.query_string}",req,res)
|
|
103
122
|
end
|
|
104
123
|
|
|
105
124
|
def do_COPY(req,res)
|
|
106
|
-
respond_with(
|
|
125
|
+
respond_with("COPY#{req.query_string}",req,res)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def do_PATCH(req,res)
|
|
129
|
+
respond_with("PATCH\n#{req.body}",req,res)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def do_OPTIONS(req,res)
|
|
133
|
+
respond_with("OPTIONS#{req.query_string}",req,res)
|
|
107
134
|
end
|
|
108
135
|
|
|
109
136
|
end
|
|
@@ -115,8 +142,7 @@ module TestServerMethods
|
|
|
115
142
|
|
|
116
143
|
def server_setup(port=9129,servlet=TestServlet)
|
|
117
144
|
@__port = port
|
|
118
|
-
if @server.nil? and !File.exist?(locked_file)
|
|
119
|
-
|
|
145
|
+
if (@server ||= nil).nil? and !File.exist?(locked_file)
|
|
120
146
|
File.open(locked_file,'w') {|f| f << 'locked' }
|
|
121
147
|
if TEST_SINGLE_THREADED
|
|
122
148
|
rd, wr = IO.pipe
|
|
@@ -124,7 +150,7 @@ module TestServerMethods
|
|
|
124
150
|
rd.close
|
|
125
151
|
rd = nil
|
|
126
152
|
|
|
127
|
-
# start up a webrick server for testing delete
|
|
153
|
+
# start up a webrick server for testing delete
|
|
128
154
|
server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
|
129
155
|
|
|
130
156
|
server.mount(servlet.path, servlet)
|
|
@@ -140,7 +166,7 @@ module TestServerMethods
|
|
|
140
166
|
rd.read
|
|
141
167
|
rd.close
|
|
142
168
|
else
|
|
143
|
-
# start up a webrick server for testing delete
|
|
169
|
+
# start up a webrick server for testing delete
|
|
144
170
|
@server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
|
145
171
|
|
|
146
172
|
@server.mount(servlet.path, servlet)
|
|
@@ -181,3 +207,81 @@ module TestServerMethods
|
|
|
181
207
|
rescue Errno::EADDRINUSE
|
|
182
208
|
end
|
|
183
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/signals.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
|
|
3
|
+
# This test suite requires the timeout server to be running
|
|
4
|
+
# See tests/timeout.rb for more info about the timeout server
|
|
5
|
+
class TestCurbSignals < Test::Unit::TestCase
|
|
6
|
+
|
|
7
|
+
# Testcase for https://github.com/taf2/curb/issues/117
|
|
8
|
+
def test_continue_after_signal
|
|
9
|
+
trap("SIGUSR1") { }
|
|
10
|
+
|
|
11
|
+
curl = Curl::Easy.new(wait_url(2))
|
|
12
|
+
pid = $$
|
|
13
|
+
Thread.new do
|
|
14
|
+
sleep 1
|
|
15
|
+
Process.kill("SIGUSR1", pid)
|
|
16
|
+
end
|
|
17
|
+
assert_equal true, curl.http_get
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def wait_url(time)
|
|
23
|
+
"#{server_base}/wait/#{time}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def serve_url(chunk_size, time, count)
|
|
27
|
+
"#{server_base}/serve/#{chunk_size}/every/#{time}/for/#{count}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def server_base
|
|
31
|
+
'http://127.0.0.1:9128'
|
|
32
|
+
end
|
|
33
|
+
end
|
data/tests/tc_curl.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
|
|
3
|
+
class TestCurl < Test::Unit::TestCase
|
|
4
|
+
def test_get
|
|
5
|
+
curl = Curl.get(TestServlet.url, {:foo => "bar"})
|
|
6
|
+
assert_equal "GETfoo=bar", curl.body_str
|
|
7
|
+
|
|
8
|
+
curl = Curl.options(TestServlet.url, {:foo => "bar"}) do|http|
|
|
9
|
+
http.headers['Cookie'] = 'foo=1;bar=2'
|
|
10
|
+
end
|
|
11
|
+
assert_equal "OPTIONSfoo=bar", curl.body_str
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_post
|
|
15
|
+
curl = Curl.post(TestServlet.url, {:foo => "bar"})
|
|
16
|
+
assert_equal "POST\nfoo=bar", curl.body_str
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_put
|
|
20
|
+
curl = Curl.put(TestServlet.url, {:foo => "bar"})
|
|
21
|
+
assert_equal "PUT\nfoo=bar", curl.body_str
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_patch
|
|
25
|
+
curl = Curl.patch(TestServlet.url, {:foo => "bar"})
|
|
26
|
+
assert_equal "PATCH\nfoo=bar", curl.body_str
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_options
|
|
30
|
+
curl = Curl.options(TestServlet.url, {:foo => "bar"})
|
|
31
|
+
assert_equal "OPTIONSfoo=bar", curl.body_str
|
|
32
|
+
end
|
|
33
|
+
|
|
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
|
|
65
|
+
|
|
66
|
+
def setup
|
|
67
|
+
server_setup
|
|
68
|
+
end
|
|
69
|
+
end
|
data/tests/tc_curl_download.rb
CHANGED
|
@@ -6,12 +6,12 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
6
6
|
def setup
|
|
7
7
|
server_setup
|
|
8
8
|
end
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
def test_download_url_to_file_via_string
|
|
11
11
|
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
12
12
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Curl::Easy.download(dl_url, dl_path)
|
|
15
15
|
assert File.exist?(dl_path)
|
|
16
16
|
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
17
17
|
ensure
|
|
@@ -23,7 +23,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
23
23
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
24
24
|
io = File.open(dl_path, 'wb')
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Curl::Easy.download(dl_url, io)
|
|
27
27
|
assert io.closed?
|
|
28
28
|
assert File.exist?(dl_path)
|
|
29
29
|
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
@@ -49,7 +49,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
|
49
49
|
# Download remote source
|
|
50
50
|
begin
|
|
51
51
|
reader.close
|
|
52
|
-
|
|
52
|
+
Curl::Easy.download(dl_url, writer)
|
|
53
53
|
Process.wait
|
|
54
54
|
ensure
|
|
55
55
|
writer.close rescue IOError # if the stream has already been closed, which occurs in Easy::download
|