curb 0.8.5 → 1.3.6
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.md +579 -0
- data/Rakefile +85 -27
- data/doc.rb +48 -8
- data/ext/banned.h +32 -0
- data/ext/curb.c +563 -233
- data/ext/curb.h +19 -10
- data/ext/curb_easy.c +3229 -368
- data/ext/curb_easy.h +42 -0
- data/ext/curb_errors.c +117 -18
- data/ext/curb_errors.h +9 -5
- data/ext/curb_macros.h +33 -21
- data/ext/curb_multi.c +1904 -272
- data/ext/curb_multi.h +11 -3
- data/ext/curb_postfield.c +149 -77
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +38 -11
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +355 -35
- data/lib/curb.rb +1 -0
- data/lib/curl/download.rb +160 -0
- data/lib/curl/easy.rb +422 -88
- data/lib/curl/multi.rb +295 -56
- data/lib/curl.rb +676 -11
- data/tests/bug_crash_on_debug.rb +14 -28
- data/tests/bug_crash_on_progress.rb +32 -16
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
- data/tests/bug_follow_redirect_288.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
- data/tests/bug_issue102.rb +17 -0
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/bug_multi_segfault.rb +1 -0
- data/tests/bug_poison.rb +29 -0
- data/tests/bug_raise_on_callback.rb +30 -0
- data/tests/helper.rb +400 -44
- data/tests/leak_trace.rb +237 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_download.rb +98 -7
- data/tests/tc_curl_easy.rb +985 -66
- data/tests/tc_curl_easy_cookielist.rb +277 -0
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_easy_resolve.rb +48 -0
- data/tests/tc_curl_maxfilesize.rb +212 -0
- data/tests/tc_curl_multi.rb +1111 -51
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_network_policy.rb +1475 -0
- data/tests/tc_curl_postfield.rb +207 -30
- data/tests/tc_curl_protocols.rb +388 -0
- data/tests/tc_fiber_scheduler.rb +584 -0
- data/tests/tc_ftp_options.rb +39 -0
- data/tests/tc_gc_compact.rb +223 -0
- data/tests/tc_test_server_methods.rb +110 -0
- data/tests/timeout.rb +30 -6
- metadata +66 -31
- data/README +0 -194
data/ext/extconf.rb
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
require 'mkmf'
|
|
2
|
+
require 'tmpdir'
|
|
3
|
+
begin
|
|
4
|
+
require 'etc'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# Etc may not be available on all Ruby builds (very rare). Fallback later.
|
|
7
|
+
end
|
|
2
8
|
|
|
3
9
|
dir_config('curl')
|
|
4
10
|
|
|
@@ -9,7 +15,7 @@ if find_executable('curl-config')
|
|
|
9
15
|
else
|
|
10
16
|
$LIBS << " #{`curl-config --libs`.strip}"
|
|
11
17
|
end
|
|
12
|
-
ca_bundle_path=`curl-config --ca`.strip
|
|
18
|
+
ca_bundle_path=`curl-config --ca`.strip.gsub(/^"([^"]+)"$/,'\1')
|
|
13
19
|
if !ca_bundle_path.nil? and ca_bundle_path != ''
|
|
14
20
|
$defs.push( %{-D HAVE_CURL_CONFIG_CA} )
|
|
15
21
|
$defs.push( %{-D CURL_CONFIG_CA='#{ca_bundle_path.inspect}'} )
|
|
@@ -18,6 +24,8 @@ elsif !have_library('curl') or !have_header('curl/curl.h')
|
|
|
18
24
|
fail <<-EOM
|
|
19
25
|
Can't find libcurl or curl/curl.h
|
|
20
26
|
|
|
27
|
+
Make sure development libs (ie libcurl4-openssl-dev) are installed on the system.
|
|
28
|
+
|
|
21
29
|
Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
|
|
22
30
|
options to extconf.
|
|
23
31
|
EOM
|
|
@@ -35,30 +43,202 @@ end
|
|
|
35
43
|
# puts "Selected arch: #{archs.first}"
|
|
36
44
|
#end
|
|
37
45
|
|
|
38
|
-
def define(s, v=1)
|
|
39
|
-
$defs.push(
|
|
46
|
+
def define(s, v = 1)
|
|
47
|
+
$defs.push(format("-D HAVE_%s=%d", s.to_s.upcase, v))
|
|
40
48
|
end
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
# Optional parallelization support for constant checks only.
|
|
51
|
+
# Enable automatically when job hints are present (JOBS, BUNDLE_JOBS, MAKEFLAGS -jN),
|
|
52
|
+
# or explicitly via EXTCONF_JOBS/EXTCONF_PARALLEL.
|
|
53
|
+
|
|
54
|
+
def parse_jobs_from_makeflags(flags)
|
|
55
|
+
return nil if flags.nil? || flags.empty?
|
|
56
|
+
tokens = flags.to_s.split(/\s+/)
|
|
57
|
+
jobs = nil
|
|
58
|
+
tokens.each_with_index do |tok, i|
|
|
59
|
+
case tok
|
|
60
|
+
when /\A-j(\d+)\z/
|
|
61
|
+
jobs = $1.to_i
|
|
62
|
+
when '-j'
|
|
63
|
+
nxt = tokens[i + 1]
|
|
64
|
+
jobs = nxt.to_i if nxt && nxt =~ /\A\d+\z/
|
|
65
|
+
when /\A--jobs(?:=(\d+)|\s+(\d+))\z/
|
|
66
|
+
jobs = ($1 || $2).to_i
|
|
67
|
+
when /\Aj(\d+)\z/ # sometimes make condenses flags
|
|
68
|
+
jobs = $1.to_i
|
|
69
|
+
end
|
|
70
|
+
break if jobs && jobs > 0
|
|
71
|
+
end
|
|
72
|
+
jobs
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def detect_job_hints
|
|
76
|
+
# Priority: explicit extconf hint, then common envs used by bundler/rubygems
|
|
77
|
+
[
|
|
78
|
+
ENV['EXTCONF_JOBS'],
|
|
79
|
+
ENV['JOBS'],
|
|
80
|
+
ENV['BUNDLE_JOBS'],
|
|
81
|
+
parse_jobs_from_makeflags(ENV['MAKEFLAGS'])
|
|
82
|
+
].each do |v|
|
|
83
|
+
n = v.to_i if v
|
|
84
|
+
return n if n && n > 0
|
|
85
|
+
end
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
explicit_parallel = ENV.key?('EXTCONF_PARALLEL') && ENV['EXTCONF_PARALLEL'] == '1'
|
|
90
|
+
job_hints = detect_job_hints
|
|
91
|
+
|
|
92
|
+
DEFAULT_PARALLEL_JOBS = begin
|
|
93
|
+
n = Etc.respond_to?(:nprocessors) ? Etc.nprocessors.to_i : 1
|
|
94
|
+
n = 1 if n <= 0
|
|
95
|
+
# Use half the CPUs by default (rounded up), but at least 1
|
|
96
|
+
jobs = [(n.to_f / 2).ceil, 1].max
|
|
97
|
+
jobs
|
|
98
|
+
rescue
|
|
99
|
+
1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
PARALLEL_JOBS = begin
|
|
103
|
+
if job_hints && job_hints > 0
|
|
104
|
+
job_hints
|
|
105
|
+
else
|
|
106
|
+
# If no hints provided, default to half the CPUs.
|
|
107
|
+
DEFAULT_PARALLEL_JOBS
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Only enable if the platform supports fork. On Windows this stays sequential.
|
|
112
|
+
PARALLEL_CONSTANT_CHECKS = PARALLEL_JOBS > 1 && Process.respond_to?(:fork)
|
|
113
|
+
|
|
114
|
+
$queued_constants = []
|
|
115
|
+
|
|
116
|
+
def try_constant_compile(sname)
|
|
117
|
+
src = %{
|
|
118
|
+
#include <curl/curl.h>
|
|
119
|
+
int main() {
|
|
120
|
+
int test = (int)#{sname};
|
|
121
|
+
(void)test;
|
|
122
|
+
return 0;
|
|
51
123
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
124
|
+
}
|
|
125
|
+
try_compile(src, "#{$CFLAGS} #{$LIBS}")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def have_constant(name)
|
|
129
|
+
# Queue constants for optional parallel probing. Falls back to sequential.
|
|
130
|
+
if PARALLEL_CONSTANT_CHECKS
|
|
131
|
+
$queued_constants << name
|
|
132
|
+
true
|
|
133
|
+
else
|
|
134
|
+
sname = name.is_a?(Symbol) ? name.to_s : name.upcase
|
|
135
|
+
checking_for name do
|
|
136
|
+
if try_constant_compile(sname)
|
|
137
|
+
define name
|
|
138
|
+
true
|
|
139
|
+
else
|
|
140
|
+
false
|
|
141
|
+
end
|
|
58
142
|
end
|
|
59
143
|
end
|
|
60
144
|
end
|
|
61
145
|
|
|
146
|
+
def flush_constant_checks
|
|
147
|
+
return if $queued_constants.empty?
|
|
148
|
+
|
|
149
|
+
constants = $queued_constants.uniq
|
|
150
|
+
$queued_constants.clear
|
|
151
|
+
|
|
152
|
+
# On platforms without fork (e.g., Windows), run sequentially.
|
|
153
|
+
unless PARALLEL_CONSTANT_CHECKS
|
|
154
|
+
constants.each { |name| have_constant(name) }
|
|
155
|
+
return
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Run constant probes in isolated child processes to avoid mkmf
|
|
159
|
+
# scratch file and logfile contention.
|
|
160
|
+
results = {}
|
|
161
|
+
pending = constants.dup
|
|
162
|
+
running = {}
|
|
163
|
+
max_jobs = PARALLEL_JOBS
|
|
164
|
+
|
|
165
|
+
spawn_probe = lambda do |const_name|
|
|
166
|
+
r, w = IO.pipe
|
|
167
|
+
pid = fork do
|
|
168
|
+
begin
|
|
169
|
+
r.close
|
|
170
|
+
Dir.mktmpdir('curb-mkmf-') do |dir|
|
|
171
|
+
Dir.chdir(dir) do
|
|
172
|
+
begin
|
|
173
|
+
# Avoid clobbering the main mkmf.log
|
|
174
|
+
Logging::logfile = File.open(File::NULL, 'w') rescue File.open(File.join(dir, 'mkmf.log'), 'w')
|
|
175
|
+
rescue
|
|
176
|
+
# best-effort
|
|
177
|
+
end
|
|
178
|
+
sname = const_name.is_a?(Symbol) ? const_name.to_s : const_name.upcase
|
|
179
|
+
ok = try_constant_compile(sname)
|
|
180
|
+
w.write([const_name, ok ? 1 : 0].join("\t"))
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
rescue
|
|
184
|
+
# Treat as failure if anything unexpected happens in child
|
|
185
|
+
begin
|
|
186
|
+
w.write([const_name, 0].join("\t"))
|
|
187
|
+
rescue
|
|
188
|
+
end
|
|
189
|
+
ensure
|
|
190
|
+
begin w.close rescue nil end
|
|
191
|
+
# Ensure the child exits without running at_exit handlers
|
|
192
|
+
exit! 0
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
w.close
|
|
196
|
+
running[pid] = r
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Start initial batch
|
|
200
|
+
while running.size < max_jobs && !pending.empty?
|
|
201
|
+
spawn_probe.call(pending.shift)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Collect results and keep spawning until done
|
|
205
|
+
until running.empty?
|
|
206
|
+
pid = Process.wait
|
|
207
|
+
io = running.delete(pid)
|
|
208
|
+
if io
|
|
209
|
+
begin
|
|
210
|
+
msg = io.read.to_s
|
|
211
|
+
name_str, ok_str = msg.split("\t", 2)
|
|
212
|
+
if name_str
|
|
213
|
+
# Map back to the original object if symbol-like
|
|
214
|
+
original = constants.find { |n| n.to_s == name_str }
|
|
215
|
+
results[original || name_str] = ok_str.to_i == 1
|
|
216
|
+
end
|
|
217
|
+
ensure
|
|
218
|
+
begin io.close rescue nil end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
# Fill next task slot
|
|
222
|
+
spawn_probe.call(pending.shift) unless pending.empty?
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Apply results to $defs and output summary via checking_for
|
|
226
|
+
results.each do |const_name, ok|
|
|
227
|
+
sname = const_name.is_a?(Symbol) ? const_name.to_s : const_name.upcase
|
|
228
|
+
checking_for const_name do
|
|
229
|
+
if ok
|
|
230
|
+
define const_name
|
|
231
|
+
true
|
|
232
|
+
else
|
|
233
|
+
false
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
have_constant "curlopt_tcp_keepalive"
|
|
240
|
+
have_constant "curlopt_tcp_keepidle"
|
|
241
|
+
have_constant "curlopt_tcp_keepintvl"
|
|
62
242
|
have_constant "curlinfo_appconnect_time"
|
|
63
243
|
have_constant "curlinfo_redirect_time"
|
|
64
244
|
have_constant "curlinfo_response_code"
|
|
@@ -66,6 +246,7 @@ have_constant "curlinfo_filetime"
|
|
|
66
246
|
have_constant "curlinfo_redirect_count"
|
|
67
247
|
have_constant "curlinfo_os_errno"
|
|
68
248
|
have_constant "curlinfo_num_connects"
|
|
249
|
+
have_constant "curlinfo_cookielist"
|
|
69
250
|
have_constant "curlinfo_ftp_entry_path"
|
|
70
251
|
have_constant "curl_version_ssl"
|
|
71
252
|
have_constant "curl_version_libz"
|
|
@@ -78,9 +259,12 @@ have_constant "curl_version_largefile"
|
|
|
78
259
|
have_constant "curl_version_idn"
|
|
79
260
|
have_constant "curl_version_sspi"
|
|
80
261
|
have_constant "curl_version_conv"
|
|
262
|
+
have_constant "curl_version_http2"
|
|
81
263
|
have_constant "curlproxy_http"
|
|
82
264
|
have_constant "curlproxy_socks4"
|
|
265
|
+
have_constant "curlproxy_socks4a"
|
|
83
266
|
have_constant "curlproxy_socks5"
|
|
267
|
+
have_constant "curlproxy_socks5_hostname"
|
|
84
268
|
have_constant "curlauth_basic"
|
|
85
269
|
have_constant "curlauth_digest"
|
|
86
270
|
have_constant "curlauth_gssnegotiate"
|
|
@@ -99,6 +283,12 @@ have_constant "curle_send_fail_rewind"
|
|
|
99
283
|
have_constant "curle_ssl_engine_initfailed"
|
|
100
284
|
have_constant "curle_login_denied"
|
|
101
285
|
|
|
286
|
+
# older than 7.10.0
|
|
287
|
+
have_constant "curlopt_nosignal"
|
|
288
|
+
|
|
289
|
+
# older than 7.16.0
|
|
290
|
+
have_constant "curlmopt_pipelining"
|
|
291
|
+
|
|
102
292
|
# older than 7.16.3
|
|
103
293
|
have_constant "curlmopt_maxconnects"
|
|
104
294
|
|
|
@@ -108,6 +298,40 @@ have_constant "curlopt_sockoptfunction"
|
|
|
108
298
|
have_constant "curlopt_sockoptdata"
|
|
109
299
|
have_constant "curlopt_opensocketfunction"
|
|
110
300
|
have_constant "curlopt_opensocketdata"
|
|
301
|
+
have_constant "curlopt_prereqfunction"
|
|
302
|
+
have_constant "curlopt_prereqdata"
|
|
303
|
+
|
|
304
|
+
# Deprecated constants (still check for them for backward compat)
|
|
305
|
+
have_constant "curlopt_ioctlfunction"
|
|
306
|
+
have_constant "curlopt_ioctldata"
|
|
307
|
+
have_constant "curlopt_progressfunction"
|
|
308
|
+
have_constant "curlopt_progressdata"
|
|
309
|
+
have_constant "curlopt_conv_to_network_function"
|
|
310
|
+
have_constant "curlopt_conv_from_network_function"
|
|
311
|
+
have_constant "curlopt_conv_from_utf8_function"
|
|
312
|
+
|
|
313
|
+
# Replacements for deprecated constants
|
|
314
|
+
# CURLOPT_PROGRESSFUNCTION -> CURLOPT_XFERINFOFUNCTION (since 7.32.0)
|
|
315
|
+
have_constant "curlopt_xferinfofunction"
|
|
316
|
+
have_constant "curlopt_xferinfodata"
|
|
317
|
+
|
|
318
|
+
# CURLOPT_PROTOCOLS -> CURLOPT_PROTOCOLS_STR (since 7.85.0)
|
|
319
|
+
have_constant "curlopt_protocols_str"
|
|
320
|
+
have_constant "curlopt_redir_protocols_str"
|
|
321
|
+
|
|
322
|
+
# CURLOPT_SOCKS5_GSSAPI_SERVICE -> CURLOPT_PROXY_SERVICE_NAME (since 7.49.0)
|
|
323
|
+
have_constant "curlopt_proxy_service_name"
|
|
324
|
+
|
|
325
|
+
# CURLOPT_HTTPPOST -> CURLOPT_MIMEPOST (since 7.56.0)
|
|
326
|
+
have_constant "curlopt_mimepost"
|
|
327
|
+
|
|
328
|
+
# CURLINFO_* -> CURLINFO_*_T (since 7.55.0)
|
|
329
|
+
have_constant "curlinfo_size_upload_t"
|
|
330
|
+
have_constant "curlinfo_size_download_t"
|
|
331
|
+
have_constant "curlinfo_speed_upload_t"
|
|
332
|
+
have_constant "curlinfo_speed_download_t"
|
|
333
|
+
have_constant "curlinfo_content_length_download_t"
|
|
334
|
+
have_constant "curlinfo_content_length_upload_t"
|
|
111
335
|
|
|
112
336
|
# additional consts
|
|
113
337
|
have_constant "curle_conv_failed"
|
|
@@ -143,6 +367,8 @@ have_func("curl_multi_timeout")
|
|
|
143
367
|
have_func("curl_multi_fdset")
|
|
144
368
|
have_func("curl_multi_perform")
|
|
145
369
|
|
|
370
|
+
have_constant "curlopt_haproxyprotocol"
|
|
371
|
+
|
|
146
372
|
# constants
|
|
147
373
|
have_constant "curlopt_interleavefunction"
|
|
148
374
|
have_constant "curlopt_interleavedata"
|
|
@@ -167,6 +393,7 @@ have_constant "curlopt_socks5_gssapi_nec"
|
|
|
167
393
|
have_constant "curlopt_interface"
|
|
168
394
|
have_constant "curlopt_localport"
|
|
169
395
|
have_constant "curlopt_dns_cache_timeout"
|
|
396
|
+
have_constant "curlopt_dns_servers"
|
|
170
397
|
have_constant "curlopt_dns_use_global_cache"
|
|
171
398
|
have_constant "curlopt_buffersize"
|
|
172
399
|
have_constant "curlopt_port"
|
|
@@ -207,6 +434,7 @@ have_constant "curlopt_httppost"
|
|
|
207
434
|
have_constant "curlopt_referer"
|
|
208
435
|
have_constant "curlopt_useragent"
|
|
209
436
|
have_constant "curlopt_httpheader"
|
|
437
|
+
have_constant "curlopt_proxyheader"
|
|
210
438
|
have_constant "curlopt_http200aliases"
|
|
211
439
|
have_constant "curlopt_cookie"
|
|
212
440
|
have_constant "curlopt_cookiefile"
|
|
@@ -306,7 +534,9 @@ have_constant "curlusessl_none"
|
|
|
306
534
|
have_constant "curlusessl_try"
|
|
307
535
|
have_constant "curlusessl_control"
|
|
308
536
|
have_constant "curlusessl_all"
|
|
537
|
+
have_constant "curlopt_connect_to"
|
|
309
538
|
have_constant "curlopt_resolve"
|
|
539
|
+
have_constant "curlopt_request_target"
|
|
310
540
|
have_constant "curlopt_sslcert"
|
|
311
541
|
have_constant "curlopt_sslcerttype"
|
|
312
542
|
have_constant "curlopt_sslkey"
|
|
@@ -319,7 +549,23 @@ have_constant "curl_sslversion_default"
|
|
|
319
549
|
have_constant :CURL_SSLVERSION_TLSv1
|
|
320
550
|
have_constant :CURL_SSLVERSION_SSLv2
|
|
321
551
|
have_constant :CURL_SSLVERSION_SSLv3
|
|
552
|
+
|
|
553
|
+
# Added in 7.30.0
|
|
554
|
+
have_constant "curlmopt_max_host_connections"
|
|
555
|
+
|
|
556
|
+
# Added in 7.34.0
|
|
557
|
+
have_constant :CURL_SSLVERSION_TLSv1_0
|
|
558
|
+
have_constant :CURL_SSLVERSION_TLSv1_1
|
|
559
|
+
have_constant :CURL_SSLVERSION_TLSv1_2
|
|
560
|
+
|
|
561
|
+
# Added in 7.52.0
|
|
562
|
+
have_constant :CURL_SSLVERSION_TLSv1_3
|
|
563
|
+
|
|
322
564
|
have_constant "curlopt_ssl_verifypeer"
|
|
565
|
+
have_constant "curlopt_doh_url"
|
|
566
|
+
have_constant "curlopt_doh_ssl_verifypeer"
|
|
567
|
+
have_constant "curlopt_doh_ssl_verifyhost"
|
|
568
|
+
have_constant "curlopt_doh_ssl_verifystatus"
|
|
323
569
|
have_constant "curlopt_cainfo"
|
|
324
570
|
have_constant "curlopt_issuercert"
|
|
325
571
|
have_constant "curlopt_capath"
|
|
@@ -352,15 +598,77 @@ have_constant "curle_not_built_in"
|
|
|
352
598
|
|
|
353
599
|
have_constant "curle_obsolete" # removed in 7.24 ?
|
|
354
600
|
|
|
601
|
+
have_constant "curle_ftp_pret_failed"
|
|
602
|
+
have_constant "curle_rtsp_cseq_error"
|
|
603
|
+
have_constant "curle_rtsp_session_error"
|
|
604
|
+
have_constant "curle_ftp_bad_file_list"
|
|
605
|
+
have_constant "curle_chunk_failed"
|
|
606
|
+
have_constant "curle_no_connection_available"
|
|
607
|
+
have_constant "curle_ssl_pinnedpubkeynotmatch"
|
|
608
|
+
have_constant "curle_ssl_invalidcertstatus"
|
|
609
|
+
have_constant "curle_http2_stream"
|
|
610
|
+
|
|
355
611
|
# gssapi/spnego delegation related constants
|
|
356
612
|
have_constant "curlopt_gssapi_delegation"
|
|
357
613
|
have_constant "curlgssapi_delegation_policy_flag"
|
|
358
614
|
have_constant "curlgssapi_delegation_flag"
|
|
359
615
|
|
|
616
|
+
have_constant "CURLM_ADDED_ALREADY"
|
|
617
|
+
|
|
618
|
+
# added in 7.40.0
|
|
619
|
+
have_constant "curlopt_unix_socket_path"
|
|
620
|
+
|
|
621
|
+
# added in 7.42.0
|
|
622
|
+
have_constant "curlopt_path_as_is"
|
|
623
|
+
|
|
624
|
+
# added in 7.43.0
|
|
625
|
+
have_constant "curlopt_pipewait"
|
|
626
|
+
|
|
627
|
+
have_constant "curlopt_proxy_ssl_verifyhost"
|
|
628
|
+
|
|
629
|
+
# protocol constants
|
|
630
|
+
have_constant "curlproto_all"
|
|
631
|
+
have_constant "curlproto_dict"
|
|
632
|
+
have_constant "curlproto_file"
|
|
633
|
+
have_constant "curlproto_ftp"
|
|
634
|
+
have_constant "curlproto_ftps"
|
|
635
|
+
have_constant "curlproto_gopher"
|
|
636
|
+
have_constant "curlproto_http"
|
|
637
|
+
have_constant "curlproto_https"
|
|
638
|
+
have_constant "curlproto_imap"
|
|
639
|
+
have_constant "curlproto_imaps"
|
|
640
|
+
have_constant "curlproto_ldap"
|
|
641
|
+
have_constant "curlproto_ldaps"
|
|
642
|
+
have_constant "curlproto_pop3"
|
|
643
|
+
have_constant "curlproto_pop3s"
|
|
644
|
+
have_constant "curlproto_rtmp"
|
|
645
|
+
have_constant "curlproto_rtmpe"
|
|
646
|
+
have_constant "curlproto_rtmps"
|
|
647
|
+
have_constant "curlproto_rtmpt"
|
|
648
|
+
have_constant "curlproto_rtmpte"
|
|
649
|
+
have_constant "curlproto_rtmpts"
|
|
650
|
+
have_constant "curlproto_rtsp"
|
|
651
|
+
have_constant "curlproto_scp"
|
|
652
|
+
have_constant "curlproto_sftp"
|
|
653
|
+
have_constant "curlproto_smb"
|
|
654
|
+
have_constant "curlproto_smbs"
|
|
655
|
+
have_constant "curlproto_smtp"
|
|
656
|
+
have_constant "curlproto_smtps"
|
|
657
|
+
have_constant "curlproto_telnet"
|
|
658
|
+
have_constant "curlproto_tftp"
|
|
659
|
+
|
|
360
660
|
if try_compile('int main() { return 0; }','-Wall')
|
|
361
661
|
$CFLAGS << ' -Wall'
|
|
362
662
|
end
|
|
363
663
|
|
|
664
|
+
# Clang-specific warning suppressions (not recognized by GCC)
|
|
665
|
+
# These are used to suppress warnings in Ruby header macros
|
|
666
|
+
%w[-Wno-self-assign -Wno-parentheses-equality -Wno-constant-logical-operand].each do |flag|
|
|
667
|
+
if try_compile('int main() { return 0; }', flag)
|
|
668
|
+
$CFLAGS << " #{flag}"
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
364
672
|
# do some checking to detect ruby 1.8 hash.c vs ruby 1.9 hash.c
|
|
365
673
|
def test_for(name, const, src)
|
|
366
674
|
checking_for name do
|
|
@@ -372,23 +680,6 @@ def test_for(name, const, src)
|
|
|
372
680
|
end
|
|
373
681
|
end
|
|
374
682
|
end
|
|
375
|
-
test_for("Ruby 1.9 Hash", "RUBY19_HASH", %{
|
|
376
|
-
#include <ruby.h>
|
|
377
|
-
int main() {
|
|
378
|
-
VALUE hash = rb_hash_new();
|
|
379
|
-
if( RHASH(hash)->ntbl->num_entries ) {
|
|
380
|
-
return 0;
|
|
381
|
-
}
|
|
382
|
-
return 1;
|
|
383
|
-
}
|
|
384
|
-
})
|
|
385
|
-
test_for("Ruby 1.9 st.h", "RUBY19_ST_H", %{
|
|
386
|
-
#include <ruby.h>
|
|
387
|
-
#include <ruby/st.h>
|
|
388
|
-
int main() {
|
|
389
|
-
return 0;
|
|
390
|
-
}
|
|
391
|
-
})
|
|
392
683
|
|
|
393
684
|
test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
|
|
394
685
|
#include <curl/curl.h>
|
|
@@ -400,6 +691,35 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
|
|
|
400
691
|
})
|
|
401
692
|
|
|
402
693
|
have_func('rb_thread_blocking_region')
|
|
694
|
+
have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
|
|
695
|
+
have_header('ruby/io.h')
|
|
696
|
+
# Ruby 4.x exports rb_thread_fd_select without declaring it in ruby/io.h.
|
|
697
|
+
have_func('rb_thread_fd_select')
|
|
698
|
+
have_func('rb_wait_for_single_fd', 'ruby/io.h')
|
|
699
|
+
have_header('ruby/fiber/scheduler.h')
|
|
700
|
+
have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
|
|
701
|
+
have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h')
|
|
702
|
+
have_func('rb_fiber_scheduler_io_select', 'ruby/fiber/scheduler.h')
|
|
703
|
+
have_func('rb_io_stdio_file')
|
|
704
|
+
have_func('curl_multi_wait')
|
|
705
|
+
have_func('curl_multi_socket_action')
|
|
706
|
+
have_func('curl_multi_assign')
|
|
707
|
+
have_func('curl_multi_wakeup')
|
|
708
|
+
have_func('curl_multi_poll')
|
|
709
|
+
have_func('curl_multi_socket')
|
|
710
|
+
have_func('curl_multi_timer_callback')
|
|
711
|
+
have_constant 'curlmopt_socketfunction'
|
|
712
|
+
have_constant 'curlmopt_timerfunction'
|
|
713
|
+
have_func('curl_easy_duphandle')
|
|
714
|
+
|
|
715
|
+
# Optional: enable verbose socket-action debug logging.
|
|
716
|
+
# Set CURB_SOCKET_DEBUG=1 in the environment before running extconf to enable.
|
|
717
|
+
if ENV['CURB_SOCKET_DEBUG'] == '1'
|
|
718
|
+
$defs << '-DCURB_SOCKET_DEBUG=1'
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# Run any queued constant checks (in parallel if enabled) before header generation.
|
|
722
|
+
flush_constant_checks
|
|
403
723
|
|
|
404
724
|
create_header('curb_config.h')
|
|
405
725
|
create_makefile('curb_core')
|
data/lib/curb.rb
CHANGED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
module Curl
|
|
7
|
+
class DownloadTargetExistsError < Errno::EEXIST; end
|
|
8
|
+
DOWNLOAD_OPTION_KEYS = [:download_dir, :overwrite].freeze
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def download_options_hash?(value)
|
|
12
|
+
value.is_a?(Hash) && value.keys.all? { |key| DOWNLOAD_OPTION_KEYS.include?(key) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def normalize_download_arguments(filename, options)
|
|
16
|
+
if download_options_hash?(filename) && options.empty?
|
|
17
|
+
options = filename
|
|
18
|
+
filename = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
[filename, options || {}]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse_download_options(options)
|
|
25
|
+
raise ArgumentError, "download options must be a Hash" unless options.is_a?(Hash)
|
|
26
|
+
|
|
27
|
+
options = options.dup
|
|
28
|
+
download_dir = options.delete(:download_dir)
|
|
29
|
+
overwrite = options.key?(:overwrite) ? !!options.delete(:overwrite) : false
|
|
30
|
+
raise ArgumentError, "unsupported download option(s): #{options.keys.join(', ')}" unless options.empty?
|
|
31
|
+
|
|
32
|
+
[download_dir, overwrite]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_download_output(url, filename = nil, options = {})
|
|
36
|
+
filename, options = normalize_download_arguments(filename, options)
|
|
37
|
+
download_dir, overwrite = parse_download_options(options)
|
|
38
|
+
|
|
39
|
+
if filename.is_a?(IO)
|
|
40
|
+
filename.binmode if filename.respond_to?(:binmode)
|
|
41
|
+
return [nil, filename, false, overwrite]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
path = if download_dir
|
|
45
|
+
safe_download_path(url, download_dir, filename: filename)
|
|
46
|
+
elsif filename.nil?
|
|
47
|
+
safe_download_path(url)
|
|
48
|
+
else
|
|
49
|
+
filename.to_s
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
[path, nil, true, overwrite]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def prepare_download_output(url, filename = nil, options = {})
|
|
56
|
+
path, io, safe_output, overwrite = resolve_download_output(url, filename, options)
|
|
57
|
+
return [path, io, safe_output] unless safe_output
|
|
58
|
+
|
|
59
|
+
[path, open_safe_download_output(path, overwrite: overwrite), true]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def download_filename_from_url(url)
|
|
63
|
+
path = begin
|
|
64
|
+
URI.parse(url.to_s).path
|
|
65
|
+
rescue URI::InvalidURIError
|
|
66
|
+
url.to_s.split(/\?/, 2).first
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
basename = File.basename(path.to_s)
|
|
70
|
+
validate_download_filename!(basename)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def safe_download_path(url, destination_dir = Dir.pwd, filename: nil)
|
|
74
|
+
dir = File.expand_path(destination_dir.to_s)
|
|
75
|
+
raise ArgumentError, "download destination directory does not exist: #{destination_dir}" unless File.directory?(dir)
|
|
76
|
+
|
|
77
|
+
File.join(dir, filename.nil? ? download_filename_from_url(url) : validate_download_filename!(filename))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def validate_download_filename!(filename)
|
|
81
|
+
filename = filename.to_s
|
|
82
|
+
invalid = filename.empty? ||
|
|
83
|
+
filename == '.' ||
|
|
84
|
+
filename == '..' ||
|
|
85
|
+
filename == File::SEPARATOR ||
|
|
86
|
+
filename.start_with?('.') ||
|
|
87
|
+
filename.include?("\0") ||
|
|
88
|
+
filename.include?(File::SEPARATOR) ||
|
|
89
|
+
filename.include?('\\') ||
|
|
90
|
+
filename.match?(/\A[A-Za-z]:/) ||
|
|
91
|
+
(File::ALT_SEPARATOR && filename.include?(File::ALT_SEPARATOR))
|
|
92
|
+
|
|
93
|
+
raise ArgumentError, "unsafe download filename derived from URL: #{filename.inspect}" if invalid
|
|
94
|
+
|
|
95
|
+
filename
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def open_safe_download_output(path, overwrite: false)
|
|
99
|
+
SafeDownloadOutput.new(path, overwrite: overwrite)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
class SafeDownloadOutput
|
|
104
|
+
def initialize(path, overwrite: false)
|
|
105
|
+
@path = File.expand_path(path.to_s)
|
|
106
|
+
@overwrite = overwrite
|
|
107
|
+
raise DownloadTargetExistsError, @path if !@overwrite && File.exist?(@path)
|
|
108
|
+
|
|
109
|
+
@tmp = Tempfile.new(['.curb-download-', '.tmp'], File.dirname(@path))
|
|
110
|
+
@tmp.binmode
|
|
111
|
+
@closed = false
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
attr_reader :path
|
|
115
|
+
|
|
116
|
+
def write(data)
|
|
117
|
+
@tmp.write(data)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def <<(data)
|
|
121
|
+
write(data)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def close(success = false)
|
|
125
|
+
return if @closed
|
|
126
|
+
|
|
127
|
+
@tmp.flush unless @tmp.closed?
|
|
128
|
+
@tmp.close unless @tmp.closed?
|
|
129
|
+
install_tmp_file if success
|
|
130
|
+
ensure
|
|
131
|
+
@tmp.close! if @tmp
|
|
132
|
+
@closed = true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def install_tmp_file
|
|
138
|
+
if @overwrite
|
|
139
|
+
File.rename(@tmp.path, @path)
|
|
140
|
+
else
|
|
141
|
+
File.link(@tmp.path, @path)
|
|
142
|
+
end
|
|
143
|
+
rescue Errno::EEXIST
|
|
144
|
+
raise DownloadTargetExistsError, @path
|
|
145
|
+
rescue NotImplementedError
|
|
146
|
+
install_tmp_file_by_copy
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def install_tmp_file_by_copy
|
|
150
|
+
flags = File::WRONLY | File::CREAT | File::EXCL
|
|
151
|
+
flags |= File::BINARY if File.const_defined?(:BINARY)
|
|
152
|
+
|
|
153
|
+
File.open(@path, flags) do |dest|
|
|
154
|
+
File.open(@tmp.path, 'rb') { |src| IO.copy_stream(src, dest) }
|
|
155
|
+
end
|
|
156
|
+
rescue Errno::EEXIST
|
|
157
|
+
raise DownloadTargetExistsError, @path
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|