ghazel-curb 0.5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +51 -0
- data/README +157 -0
- data/Rakefile +297 -0
- data/doc.rb +42 -0
- data/ext/curb.c +339 -0
- data/ext/curb.h +50 -0
- data/ext/curb_easy.c +2932 -0
- data/ext/curb_easy.h +103 -0
- data/ext/curb_errors.c +630 -0
- data/ext/curb_errors.h +128 -0
- data/ext/curb_macros.h +114 -0
- data/ext/curb_multi.c +487 -0
- data/ext/curb_multi.h +26 -0
- data/ext/curb_postfield.c +511 -0
- data/ext/curb_postfield.h +40 -0
- data/ext/curb_upload.c +80 -0
- data/ext/curb_upload.h +30 -0
- data/ext/extconf.rb +162 -0
- data/lib/curb.rb +184 -0
- data/lib/curl.rb +2 -0
- data/tests/alltests.rb +3 -0
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +52 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +53 -0
- data/tests/bug_multi_segfault.rb +10 -0
- data/tests/bug_require_last_or_segfault.rb +40 -0
- data/tests/helper.rb +168 -0
- data/tests/require_last_or_segfault_script.rb +36 -0
- data/tests/tc_curl_download.rb +32 -0
- data/tests/tc_curl_easy.rb +664 -0
- data/tests/tc_curl_multi.rb +421 -0
- data/tests/tc_curl_postfield.rb +141 -0
- data/tests/unittests.rb +2 -0
- metadata +89 -0
data/ext/extconf.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
dir_config('curl')
|
4
|
+
|
5
|
+
if find_executable('curl-config')
|
6
|
+
$CFLAGS << " #{`curl-config --cflags`.strip}"
|
7
|
+
if ENV['STATIC_BUILD']
|
8
|
+
$LIBS << " #{`curl-config --static-libs`.strip}"
|
9
|
+
else
|
10
|
+
$LIBS << " #{`curl-config --libs`.strip}"
|
11
|
+
end
|
12
|
+
ca_bundle_path=`curl-config --ca`.strip
|
13
|
+
if !ca_bundle_path.nil? and ca_bundle_path != ''
|
14
|
+
$defs.push( %{-D HAVE_CURL_CONFIG_CA} )
|
15
|
+
$defs.push( %{-D CURL_CONFIG_CA='#{ca_bundle_path.inspect}'} )
|
16
|
+
end
|
17
|
+
elsif !have_library('curl') or !have_header('curl/curl.h')
|
18
|
+
fail <<-EOM
|
19
|
+
Can't find libcurl or curl/curl.h
|
20
|
+
|
21
|
+
Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
|
22
|
+
options to extconf.
|
23
|
+
EOM
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check arch flags
|
27
|
+
archs = $CFLAGS.scan(/-arch\s(.*?)\s/).first # get the first arch flag
|
28
|
+
if archs and archs.size >= 1
|
29
|
+
# need to reduce the number of archs...
|
30
|
+
# guess the first one is correct... at least the first one is probably the ruby installed arch...
|
31
|
+
# this could lead to compiled binaries that crash at runtime...
|
32
|
+
$CFLAGS.gsub!(/-arch\s(.*?)\s/,' ')
|
33
|
+
$CFLAGS << " -arch #{archs.first}"
|
34
|
+
puts "Selected arch: #{archs.first}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def define(s)
|
38
|
+
$defs.push( format("-D HAVE_%s", s.to_s.upcase) )
|
39
|
+
end
|
40
|
+
|
41
|
+
def have_constant(name)
|
42
|
+
checking_for name do
|
43
|
+
src = %{
|
44
|
+
#include <curl/curl.h>
|
45
|
+
int main() {
|
46
|
+
int test = (int)#{name.upcase};
|
47
|
+
return 0;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
if try_compile(src,"#{$CFLAGS} #{$LIBS}")
|
51
|
+
define name
|
52
|
+
true
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
have_constant "curlinfo_redirect_time"
|
60
|
+
have_constant "curlinfo_response_code"
|
61
|
+
have_constant "curlinfo_filetime"
|
62
|
+
have_constant "curlinfo_redirect_count"
|
63
|
+
have_constant "curlinfo_os_errno"
|
64
|
+
have_constant "curlinfo_num_connects"
|
65
|
+
have_constant "curlinfo_ftp_entry_path"
|
66
|
+
have_constant "curl_version_ssl"
|
67
|
+
have_constant "curl_version_libz"
|
68
|
+
have_constant "curl_version_ntlm"
|
69
|
+
have_constant "curl_version_gssnegotiate"
|
70
|
+
have_constant "curl_version_debug"
|
71
|
+
have_constant "curl_version_asynchdns"
|
72
|
+
have_constant "curl_version_spnego"
|
73
|
+
have_constant "curl_version_largefile"
|
74
|
+
have_constant "curl_version_idn"
|
75
|
+
have_constant "curl_version_sspi"
|
76
|
+
have_constant "curl_version_conv"
|
77
|
+
have_constant "curlproxy_http"
|
78
|
+
have_constant "curlproxy_socks4"
|
79
|
+
have_constant "curlproxy_socks5"
|
80
|
+
have_constant "curlauth_basic"
|
81
|
+
have_constant "curlauth_digest"
|
82
|
+
have_constant "curlauth_gssnegotiate"
|
83
|
+
have_constant "curlauth_ntlm"
|
84
|
+
have_constant "curlauth_anysafe"
|
85
|
+
have_constant "curlauth_any"
|
86
|
+
have_constant "curle_tftp_notfound"
|
87
|
+
have_constant "curle_tftp_perm"
|
88
|
+
have_constant "curle_tftp_diskfull"
|
89
|
+
have_constant "curle_tftp_illegal"
|
90
|
+
have_constant "curle_tftp_unknownid"
|
91
|
+
have_constant "curle_tftp_exists"
|
92
|
+
have_constant "curle_tftp_nosuchuser"
|
93
|
+
# older versions of libcurl 7.12
|
94
|
+
have_constant "curle_send_fail_rewind"
|
95
|
+
have_constant "curle_ssl_engine_initfailed"
|
96
|
+
have_constant "curle_login_denied"
|
97
|
+
|
98
|
+
# older than 7.16.3
|
99
|
+
have_constant "curlmopt_maxconnects"
|
100
|
+
|
101
|
+
# additional consts
|
102
|
+
have_constant "curle_conv_failed"
|
103
|
+
have_constant "curle_conv_reqd"
|
104
|
+
have_constant "curle_ssl_cacert_badfile"
|
105
|
+
have_constant "curle_remote_file_not_found"
|
106
|
+
have_constant "curle_ssh"
|
107
|
+
have_constant "curle_ssl_shutdown_failed"
|
108
|
+
have_constant "curle_again"
|
109
|
+
have_constant "curle_ssl_crl_badfile"
|
110
|
+
have_constant "curle_ssl_issuer_error"
|
111
|
+
|
112
|
+
# centos 4.5 build of libcurl
|
113
|
+
have_constant "curlm_bad_socket"
|
114
|
+
have_constant "curlm_unknown_option"
|
115
|
+
have_func("curl_multi_timeout")
|
116
|
+
have_func("curl_multi_fdset")
|
117
|
+
have_func("curl_multi_perform")
|
118
|
+
|
119
|
+
if try_compile('int main() { return 0; }','-Wall')
|
120
|
+
$CFLAGS << ' -Wall'
|
121
|
+
end
|
122
|
+
|
123
|
+
# do some checking to detect ruby 1.8 hash.c vs ruby 1.9 hash.c
|
124
|
+
def test_for(name, const, src)
|
125
|
+
checking_for name do
|
126
|
+
if try_compile(src,"#{$CFLAGS} #{$LIBS}")
|
127
|
+
define const
|
128
|
+
true
|
129
|
+
else
|
130
|
+
false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
test_for("Ruby 1.9 Hash", "RUBY19_HASH", %{
|
135
|
+
#include <ruby.h>
|
136
|
+
int main() {
|
137
|
+
VALUE hash = rb_hash_new();
|
138
|
+
if( RHASH(hash)->ntbl->num_entries ) {
|
139
|
+
return 0;
|
140
|
+
}
|
141
|
+
return 1;
|
142
|
+
}
|
143
|
+
})
|
144
|
+
test_for("Ruby 1.9 st.h", "RUBY19_ST_H", %{
|
145
|
+
#include <ruby.h>
|
146
|
+
#include <ruby/st.h>
|
147
|
+
int main() {
|
148
|
+
return 0;
|
149
|
+
}
|
150
|
+
})
|
151
|
+
|
152
|
+
test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
|
153
|
+
#include <curl/curl.h>
|
154
|
+
int main() {
|
155
|
+
CURL *easy = curl_easy_init();
|
156
|
+
curl_easy_escape(easy,"hello",5);
|
157
|
+
return 0;
|
158
|
+
}
|
159
|
+
})
|
160
|
+
|
161
|
+
create_header('curb_config.h')
|
162
|
+
create_makefile('curb_core')
|
data/lib/curb.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'curb_core'
|
2
|
+
|
3
|
+
module Curl
|
4
|
+
class Easy
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# call-seq:
|
8
|
+
# Curl::Easy.download(url, filename = url.split(/\?/).first.split(/\//).last) { |curl| ... }
|
9
|
+
#
|
10
|
+
# Stream the specified url (via perform) and save the data directly to the
|
11
|
+
# supplied filename (defaults to the last component of the URL path, which will
|
12
|
+
# usually be the filename most simple urls).
|
13
|
+
#
|
14
|
+
# If a block is supplied, it will be passed the curl instance prior to the
|
15
|
+
# perform call.
|
16
|
+
#
|
17
|
+
# *Note* that the semantics of the on_body handler are subtly changed when using
|
18
|
+
# download, to account for the automatic routing of data to the specified file: The
|
19
|
+
# data string is passed to the handler *before* it is written
|
20
|
+
# to the file, allowing the handler to perform mutative operations where
|
21
|
+
# necessary. As usual, the transfer will be aborted if the on_body handler
|
22
|
+
# returns a size that differs from the data chunk size - in this case, the
|
23
|
+
# offending chunk will *not* be written to the file, the file will be closed,
|
24
|
+
# and a Curl::Err::AbortedByCallbackError will be raised.
|
25
|
+
def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk)
|
26
|
+
curl = Curl::Easy.new(url, &blk)
|
27
|
+
|
28
|
+
File.open(filename, "wb") do |output|
|
29
|
+
old_on_body = curl.on_body do |data|
|
30
|
+
result = old_on_body ? old_on_body.call(data) : data.length
|
31
|
+
output << data if result == data.length
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
curl.perform
|
36
|
+
end
|
37
|
+
|
38
|
+
return curl
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Allow the incoming cert string to be file:password
|
43
|
+
# but be careful to not use a colon from a windows file path
|
44
|
+
# as the split point. Mimic what curl's main does
|
45
|
+
alias_method :native_cert=, :cert=
|
46
|
+
def cert=(cert_file)
|
47
|
+
pos = cert_file.rindex(':')
|
48
|
+
if pos && pos > 1
|
49
|
+
self.native_cert= cert_file[0..pos-1]
|
50
|
+
self.certpassword= cert_file[pos+1..-1]
|
51
|
+
else
|
52
|
+
self.native_cert= cert_file
|
53
|
+
end
|
54
|
+
self.cert
|
55
|
+
end
|
56
|
+
end
|
57
|
+
class Multi
|
58
|
+
class << self
|
59
|
+
# call-seq:
|
60
|
+
# Curl::Multi.get('url1','url2','url3','url4','url5', :follow_location => true) do|easy|
|
61
|
+
# easy
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# Blocking call to fetch multiple url's in parallel.
|
65
|
+
def get(urls, easy_options={}, multi_options={}, &blk)
|
66
|
+
url_confs = []
|
67
|
+
urls.each do|url|
|
68
|
+
url_confs << {:url => url, :method => :get}.merge(easy_options)
|
69
|
+
end
|
70
|
+
self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
|
71
|
+
end
|
72
|
+
|
73
|
+
# call-seq:
|
74
|
+
#
|
75
|
+
# Curl::Multi.post([{:url => 'url1', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
|
76
|
+
# {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
|
77
|
+
# {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
|
78
|
+
# { :follow_location => true, :multipart_form_post => true },
|
79
|
+
# {:pipeline => true }) do|easy|
|
80
|
+
# easy_handle_on_request_complete
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# Blocking call to POST multiple form's in parallel.
|
84
|
+
#
|
85
|
+
# urls_with_config: is a hash of url's pointing to the postfields to send
|
86
|
+
# easy_options: are a set of common options to set on all easy handles
|
87
|
+
# multi_options: options to set on the Curl::Multi handle
|
88
|
+
#
|
89
|
+
def post(urls_with_config, easy_options={}, multi_options={}, &blk)
|
90
|
+
url_confs = []
|
91
|
+
urls_with_config.each do|uconf|
|
92
|
+
url_confs << uconf.merge(:method => :post).merge(easy_options)
|
93
|
+
end
|
94
|
+
self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# call-seq:
|
98
|
+
#
|
99
|
+
# Curl::Multi.put([{:url => 'url1', :put_data => "some message"},
|
100
|
+
# {:url => 'url2', :put_data => IO.read('filepath')},
|
101
|
+
# {:url => 'url3', :put_data => "maybe another string or socket?"],
|
102
|
+
# {:follow_location => true},
|
103
|
+
# {:pipeline => true }) do|easy|
|
104
|
+
# easy_handle_on_request_complete
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# Blocking call to POST multiple form's in parallel.
|
108
|
+
#
|
109
|
+
# urls_with_config: is a hash of url's pointing to the postfields to send
|
110
|
+
# easy_options: are a set of common options to set on all easy handles
|
111
|
+
# multi_options: options to set on the Curl::Multi handle
|
112
|
+
#
|
113
|
+
def put(urls_with_config, easy_options={}, multi_options={}, &blk)
|
114
|
+
url_confs = []
|
115
|
+
urls_with_config.each do|uconf|
|
116
|
+
url_confs << uconf.merge(:method => :put).merge(easy_options)
|
117
|
+
end
|
118
|
+
self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# call-seq:
|
123
|
+
#
|
124
|
+
# Curl::Multi.http( [
|
125
|
+
# { :url => 'url1', :method => :post,
|
126
|
+
# :post_fields => {'field1' => 'value1', 'field2' => 'value2'} },
|
127
|
+
# { :url => 'url2', :method => :get,
|
128
|
+
# :follow_location => true, :max_redirects => 3 },
|
129
|
+
# { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
|
130
|
+
# { :url => 'url4', :method => :head }
|
131
|
+
# ], {:pipeline => true})
|
132
|
+
#
|
133
|
+
# Blocking call to issue multiple HTTP requests with varying verb's.
|
134
|
+
#
|
135
|
+
# 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
|
136
|
+
# multi_options: options for the multi handle
|
137
|
+
# blk: a callback, that yeilds when a handle is completed
|
138
|
+
#
|
139
|
+
def http(urls_with_config, multi_options={}, &blk)
|
140
|
+
m = Curl::Multi.new
|
141
|
+
# configure the multi handle
|
142
|
+
multi_options.each { |k,v| m.send("#{k}=", v) }
|
143
|
+
|
144
|
+
urls_with_config.each do|conf|
|
145
|
+
c = conf.dup # avoid being destructive to input
|
146
|
+
url = c.delete(:url)
|
147
|
+
method = c.delete(:method)
|
148
|
+
headers = c.delete(:headers)
|
149
|
+
|
150
|
+
easy = Curl::Easy.new(url)
|
151
|
+
|
152
|
+
case method
|
153
|
+
when :post
|
154
|
+
fields = c.delete(:post_fields)
|
155
|
+
# set the post post using the url fields
|
156
|
+
easy.post_body = fields.map{|f,k| "#{easy.escape(f)}=#{easy.escape(k)}"}.join('&')
|
157
|
+
when :put
|
158
|
+
easy.put_data = c.delete(:put_data)
|
159
|
+
when :head
|
160
|
+
easy.head = true
|
161
|
+
when :delete
|
162
|
+
easy.delete = true
|
163
|
+
when :get
|
164
|
+
else
|
165
|
+
# XXX: nil is treated like a GET
|
166
|
+
end
|
167
|
+
|
168
|
+
# headers is a special key
|
169
|
+
headers.each {|k,v| easy.headers[k] = v } if headers
|
170
|
+
|
171
|
+
#
|
172
|
+
# use the remaining options as specific configuration to the easy handle
|
173
|
+
# bad options should raise an undefined method error
|
174
|
+
#
|
175
|
+
c.each { |k,v| easy.send("#{k}=",v) }
|
176
|
+
|
177
|
+
easy.on_complete {|curl,code| blk.call(curl,code,method) } if blk
|
178
|
+
m.add(easy)
|
179
|
+
end
|
180
|
+
m.perform
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/curl.rb
ADDED
data/tests/alltests.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
require 'webrick'
|
3
|
+
class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
|
4
|
+
class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
|
5
|
+
|
6
|
+
class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
7
|
+
def test_bug
|
8
|
+
server = WEBrick::HTTPServer.new( :Port => 9999 )
|
9
|
+
server.mount_proc("/test") do|req,res|
|
10
|
+
sleep 0.5
|
11
|
+
res.body = "hi"
|
12
|
+
res['Content-Type'] = "text/html"
|
13
|
+
end
|
14
|
+
|
15
|
+
thread = Thread.new(server) do|srv|
|
16
|
+
srv.start
|
17
|
+
end
|
18
|
+
|
19
|
+
threads = []
|
20
|
+
timer = Time.now
|
21
|
+
|
22
|
+
5.times do |i|
|
23
|
+
t = Thread.new do
|
24
|
+
c = Curl::Easy.perform('http://localhost:9999/test')
|
25
|
+
c.header_str
|
26
|
+
end
|
27
|
+
threads << t
|
28
|
+
end
|
29
|
+
|
30
|
+
multi_responses = threads.collect do|t|
|
31
|
+
t.value
|
32
|
+
end
|
33
|
+
|
34
|
+
multi_time = (Time.now - timer)
|
35
|
+
puts "requested in #{multi_time}"
|
36
|
+
|
37
|
+
timer = Time.now
|
38
|
+
single_responses = []
|
39
|
+
5.times do |i|
|
40
|
+
c = Curl::Easy.perform('http://localhost:9999/test')
|
41
|
+
single_responses << c.header_str
|
42
|
+
end
|
43
|
+
|
44
|
+
single_time = (Time.now - timer)
|
45
|
+
puts "requested in #{single_time}"
|
46
|
+
|
47
|
+
assert single_time > multi_time
|
48
|
+
|
49
|
+
server.shutdown
|
50
|
+
thread.join
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# From Vlad Jebelev:
|
2
|
+
#
|
3
|
+
# - Second thing - I think you just probably didn't have the time to update
|
4
|
+
# instance methods yet but when I POST with a reusal of a previous curl
|
5
|
+
# instance, it doesnt' work for me, e.g. when I create a curl previously and
|
6
|
+
# then issue:
|
7
|
+
#
|
8
|
+
# c.http_post(login_url, *fields)
|
9
|
+
#
|
10
|
+
# instead of:
|
11
|
+
#
|
12
|
+
# c = Curl::Easy.http_post(login_url, *fields) do |curl|
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# then the result I am getting is quite different.
|
17
|
+
#
|
18
|
+
# ================
|
19
|
+
#
|
20
|
+
# Update:
|
21
|
+
#
|
22
|
+
# It seems that class httpost is incorrectly passing arguments down to
|
23
|
+
# instance httppost. This bug is intermittent, but results in an
|
24
|
+
# exception from the first post when it occurs.
|
25
|
+
#
|
26
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
27
|
+
|
28
|
+
class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
29
|
+
def test_bug
|
30
|
+
5.times do |i|
|
31
|
+
puts "Test ##{i}"
|
32
|
+
do_test
|
33
|
+
sleep 2
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def do_test
|
38
|
+
c = Curl::Easy.http_post('https://www.google.com/accounts/ServiceLoginAuth',
|
39
|
+
Curl::PostField.content('ltmpl','m_blanco'))
|
40
|
+
body_c, header_c = c.body_str, c.header_str
|
41
|
+
|
42
|
+
sleep 2
|
43
|
+
|
44
|
+
c.http_post('https://www.google.com/accounts/ServiceLoginAuth',
|
45
|
+
Curl::PostField.content('ltmpl','m_blanco'))
|
46
|
+
body_i, header_i = c.body_str, c.header_str
|
47
|
+
|
48
|
+
# timestamps will differ, just check first bit. We wont get here if
|
49
|
+
# the bug bites anyway...
|
50
|
+
assert_equal header_c[0..50], header_i[0..50]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# From safis http://github.com/taf2/curb/issues#issue/5
|
2
|
+
# irb: require 'curb'
|
3
|
+
# irb: multi = Curl::Multi.new
|
4
|
+
# irb: exit
|
5
|
+
# <main>:47140: [BUG] Bus Error
|
6
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','ext'))
|
7
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
|
8
|
+
require 'curb'
|
9
|
+
multi = Curl::Multi.new
|
10
|
+
exit
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# From Vlad Jebelev:
|
2
|
+
#
|
3
|
+
# - if I have a require statement after "require 'curb'" and there is a
|
4
|
+
# POST with at least 1 field, the script will fail with a segmentation
|
5
|
+
# fault, e.g. the following sequence fails every time for me (Ruby 1.8.5):
|
6
|
+
# -----------------------------------------------------------------
|
7
|
+
# require 'curb'
|
8
|
+
# require 'uri'
|
9
|
+
#
|
10
|
+
# url = 'https://www.google.com/accounts/ServiceLoginAuth'
|
11
|
+
#
|
12
|
+
# c = Curl::Easy.http_post(
|
13
|
+
# 'https://www.google.com/accounts/ServiceLoginAuth',
|
14
|
+
# [Curl:: PostField.content('ltmpl','m_blanco')] ) do |curl|
|
15
|
+
# end
|
16
|
+
# ------------------------------------------------------------------
|
17
|
+
# :..dev/util$ ruby seg.rb
|
18
|
+
# seg.rb:6: [BUG] Segmentation fault
|
19
|
+
# ruby 1.8.5 (2006-08-25) [i686-linux]
|
20
|
+
#
|
21
|
+
# Aborted
|
22
|
+
# ------------------------------------------------------------------
|
23
|
+
#
|
24
|
+
require 'test/unit'
|
25
|
+
require 'rbconfig'
|
26
|
+
|
27
|
+
$rubycmd = Config::CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
|
28
|
+
|
29
|
+
class BugTestRequireLastOrSegfault < Test::Unit::TestCase
|
30
|
+
def test_bug
|
31
|
+
5.times do |i|
|
32
|
+
puts "Test ##{i}"
|
33
|
+
|
34
|
+
# will be empty string if it segfaults...
|
35
|
+
assert_equal 'success', `#$rubycmd #{File.dirname(__FILE__)}/require_last_or_segfault_script.rb`.chomp
|
36
|
+
sleep 5
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
data/tests/helper.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# DO NOT REMOVE THIS COMMENT - PART OF TESTMODEL.
|
2
|
+
# Copyright (c)2006 Ross Bamford. See LICENSE.
|
3
|
+
$CURB_TESTING = true
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
$TOPDIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
7
|
+
$EXTDIR = File.join($TOPDIR, 'ext')
|
8
|
+
$LIBDIR = File.join($TOPDIR, 'lib')
|
9
|
+
$:.unshift($LIBDIR)
|
10
|
+
$:.unshift($EXTDIR)
|
11
|
+
|
12
|
+
require 'curb'
|
13
|
+
require 'test/unit'
|
14
|
+
|
15
|
+
$TEST_URL = "file://#{URI.escape(File.expand_path(__FILE__).tr('\\','/').tr(':','|'))}"
|
16
|
+
|
17
|
+
require 'thread'
|
18
|
+
require 'webrick'
|
19
|
+
|
20
|
+
# set this to true to avoid testing with multiple threads
|
21
|
+
# or to test with multiple threads set it to false
|
22
|
+
# this is important since, some code paths will change depending
|
23
|
+
# on the presence of multiple threads
|
24
|
+
TEST_SINGLE_THREADED=false
|
25
|
+
|
26
|
+
# keep webrick quiet
|
27
|
+
class ::WEBrick::HTTPServer
|
28
|
+
def access_log(config, req, res)
|
29
|
+
# nop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
class ::WEBrick::BasicLog
|
33
|
+
def log(level, data)
|
34
|
+
# nop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Simple test server to record number of times a request is sent/recieved of a specific
|
40
|
+
# request type, e.g. GET,POST,PUT,DELETE
|
41
|
+
#
|
42
|
+
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
43
|
+
|
44
|
+
def self.port=(p)
|
45
|
+
@port = p
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.port
|
49
|
+
(@port or 9129)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.path
|
53
|
+
'/methods'
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.url
|
57
|
+
"http://127.0.0.1:#{port}#{path}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def respond_with(method,req,res)
|
61
|
+
res.body = method.to_s
|
62
|
+
res['Content-Type'] = "text/plain"
|
63
|
+
end
|
64
|
+
|
65
|
+
def do_GET(req,res)
|
66
|
+
respond_with(:GET,req,res)
|
67
|
+
end
|
68
|
+
|
69
|
+
def do_HEAD(req,res)
|
70
|
+
res['Location'] = "/nonexistent"
|
71
|
+
respond_with(:HEAD, req, res)
|
72
|
+
end
|
73
|
+
|
74
|
+
def do_POST(req,res)
|
75
|
+
if req.body
|
76
|
+
params = {}
|
77
|
+
req.body.split('&').map{|s| k,v=s.split('='); params[k] = v }
|
78
|
+
end
|
79
|
+
if params and params['s'] == '500'
|
80
|
+
res.status = 500
|
81
|
+
else
|
82
|
+
respond_with("POST\n#{req.body}",req,res)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def do_PUT(req,res)
|
87
|
+
res['X-Requested-Content-Type'] = req.content_type
|
88
|
+
respond_with("PUT\n#{req.body}",req,res)
|
89
|
+
end
|
90
|
+
|
91
|
+
def do_DELETE(req,res)
|
92
|
+
respond_with(:DELETE,req,res)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
module TestServerMethods
|
98
|
+
def locked_file
|
99
|
+
File.join(File.dirname(__FILE__),"server_lock-#{@__port}")
|
100
|
+
end
|
101
|
+
|
102
|
+
def server_setup(port=9129,servlet=TestServlet)
|
103
|
+
@__port = port
|
104
|
+
if @server.nil? and !File.exist?(locked_file)
|
105
|
+
|
106
|
+
File.open(locked_file,'w') {|f| f << 'locked' }
|
107
|
+
if TEST_SINGLE_THREADED
|
108
|
+
rd, wr = IO.pipe
|
109
|
+
@__pid = fork do
|
110
|
+
rd.close
|
111
|
+
rd = nil
|
112
|
+
|
113
|
+
# start up a webrick server for testing delete
|
114
|
+
server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
115
|
+
|
116
|
+
server.mount(servlet.path, servlet)
|
117
|
+
server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
118
|
+
|
119
|
+
trap("INT") { server.shutdown }
|
120
|
+
GC.start
|
121
|
+
wr.flush
|
122
|
+
wr.close
|
123
|
+
server.start
|
124
|
+
end
|
125
|
+
wr.close
|
126
|
+
rd.read
|
127
|
+
rd.close
|
128
|
+
else
|
129
|
+
# start up a webrick server for testing delete
|
130
|
+
@server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
131
|
+
|
132
|
+
@server.mount(servlet.path, servlet)
|
133
|
+
@server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
134
|
+
queue = Queue.new # synchronize the thread startup to the main thread
|
135
|
+
|
136
|
+
@test_thread = Thread.new { queue << 1; @server.start }
|
137
|
+
|
138
|
+
# wait for the queue
|
139
|
+
value = queue.pop
|
140
|
+
if !value
|
141
|
+
STDERR.puts "Failed to startup test server!"
|
142
|
+
exit(1)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
exit_code = lambda do
|
148
|
+
begin
|
149
|
+
if File.exist?(locked_file)
|
150
|
+
File.unlink locked_file
|
151
|
+
if TEST_SINGLE_THREADED
|
152
|
+
Process.kill 'INT', @__pid
|
153
|
+
else
|
154
|
+
@server.shutdown unless @server.nil?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
#@server.shutdown unless @server.nil?
|
158
|
+
rescue Object => e
|
159
|
+
puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
trap("INT"){exit_code.call}
|
164
|
+
at_exit{exit_code.call}
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|