curb 1.1.0 → 1.2.1
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 +4 -4
- data/README.md +75 -2
- data/ext/curb.c +11 -4
- data/ext/curb.h +4 -4
- data/ext/curb_easy.c +236 -18
- data/ext/curb_easy.h +1 -0
- data/ext/curb_multi.c +595 -20
- data/ext/curb_multi.h +4 -1
- data/ext/curb_postfield.c +6 -0
- data/ext/curb_upload.c +3 -0
- data/ext/extconf.rb +215 -17
- data/lib/curl/easy.rb +56 -5
- 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/helper.rb +33 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl_download.rb +2 -1
- data/tests/tc_curl_easy.rb +65 -6
- data/tests/tc_curl_multi.rb +31 -0
- data/tests/tc_fiber_scheduler.rb +190 -0
- metadata +10 -6
- data/tests/bug_issue277.rb +0 -32
- data/tests/bug_resolve.rb +0 -15
data/ext/curb_multi.h
CHANGED
@@ -8,18 +8,21 @@
|
|
8
8
|
#define __CURB_MULTI_H
|
9
9
|
|
10
10
|
#include "curb.h"
|
11
|
-
#include "curb_easy.h"
|
12
11
|
#include <curl/multi.h>
|
13
12
|
|
13
|
+
struct st_table;
|
14
|
+
|
14
15
|
typedef struct {
|
15
16
|
int active;
|
16
17
|
int running;
|
17
18
|
CURLM *handle;
|
19
|
+
struct st_table *attached;
|
18
20
|
} ruby_curl_multi;
|
19
21
|
|
20
22
|
extern VALUE cCurlMulti;
|
21
23
|
void init_curb_multi();
|
22
24
|
VALUE ruby_curl_multi_new(VALUE klass);
|
25
|
+
void rb_curl_multi_forget_easy(ruby_curl_multi *rbcm, void *rbce_ptr);
|
23
26
|
|
24
27
|
|
25
28
|
#endif
|
data/ext/curb_postfield.c
CHANGED
@@ -209,6 +209,9 @@ void curl_postfield_free(ruby_curl_postfield *rbcpf) {
|
|
209
209
|
*/
|
210
210
|
static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass) {
|
211
211
|
ruby_curl_postfield *rbcpf = ALLOC(ruby_curl_postfield);
|
212
|
+
if (!rbcpf) {
|
213
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::PostField");
|
214
|
+
}
|
212
215
|
|
213
216
|
// wierdness - we actually require two args, unless a block is provided, but
|
214
217
|
// we have to work that out below.
|
@@ -255,6 +258,9 @@ static VALUE ruby_curl_postfield_new_content(int argc, VALUE *argv, VALUE klass)
|
|
255
258
|
static VALUE ruby_curl_postfield_new_file(int argc, VALUE *argv, VALUE klass) {
|
256
259
|
// TODO needs to handle content-type too
|
257
260
|
ruby_curl_postfield *rbcpf = ALLOC(ruby_curl_postfield);
|
261
|
+
if (!rbcpf) {
|
262
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::PostField");
|
263
|
+
}
|
258
264
|
|
259
265
|
rb_scan_args(argc, argv, "21&", &rbcpf->name, &rbcpf->local_file, &rbcpf->remote_file, &rbcpf->content_proc);
|
260
266
|
|
data/ext/curb_upload.c
CHANGED
@@ -24,6 +24,9 @@ static void curl_upload_free(ruby_curl_upload *rbcu) {
|
|
24
24
|
VALUE ruby_curl_upload_new(VALUE klass) {
|
25
25
|
VALUE upload;
|
26
26
|
ruby_curl_upload *rbcu = ALLOC(ruby_curl_upload);
|
27
|
+
if (!rbcu) {
|
28
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory for Curl::Upload");
|
29
|
+
}
|
27
30
|
rbcu->stream = Qnil;
|
28
31
|
rbcu->offset = 0;
|
29
32
|
upload = Data_Wrap_Struct(klass, curl_upload_mark, curl_upload_free, rbcu);
|
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
|
|
@@ -37,26 +43,195 @@ end
|
|
37
43
|
# puts "Selected arch: #{archs.first}"
|
38
44
|
#end
|
39
45
|
|
40
|
-
def define(s, v=1)
|
41
|
-
$defs.push(
|
46
|
+
def define(s, v = 1)
|
47
|
+
$defs.push(format("-D HAVE_%s=%d", s.to_s.upcase, v))
|
42
48
|
end
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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;
|
53
123
|
}
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
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
|
60
235
|
end
|
61
236
|
end
|
62
237
|
end
|
@@ -325,6 +500,7 @@ have_constant "curlusessl_try"
|
|
325
500
|
have_constant "curlusessl_control"
|
326
501
|
have_constant "curlusessl_all"
|
327
502
|
have_constant "curlopt_resolve"
|
503
|
+
have_constant "curlopt_request_target"
|
328
504
|
have_constant "curlopt_sslcert"
|
329
505
|
have_constant "curlopt_sslcerttype"
|
330
506
|
have_constant "curlopt_sslkey"
|
@@ -469,9 +645,31 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
|
|
469
645
|
have_func('rb_thread_blocking_region')
|
470
646
|
have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
|
471
647
|
have_header('ruby/io.h')
|
648
|
+
have_func('rb_thread_fd_select', 'ruby/io.h')
|
649
|
+
have_func('rb_wait_for_single_fd', 'ruby/io.h')
|
650
|
+
have_header('ruby/fiber/scheduler.h')
|
651
|
+
have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
|
652
|
+
have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h')
|
472
653
|
have_func('rb_io_stdio_file')
|
473
654
|
have_func('curl_multi_wait')
|
655
|
+
have_func('curl_multi_socket_action')
|
656
|
+
have_func('curl_multi_assign')
|
657
|
+
have_func('curl_multi_wakeup')
|
658
|
+
have_func('curl_multi_poll')
|
659
|
+
have_func('curl_multi_socket')
|
660
|
+
have_func('curl_multi_timer_callback')
|
661
|
+
have_constant 'curlmopt_socketfunction'
|
662
|
+
have_constant 'curlmopt_timerfunction'
|
474
663
|
have_func('curl_easy_duphandle')
|
475
664
|
|
665
|
+
# Optional: enable verbose socket-action debug logging.
|
666
|
+
# Set CURB_SOCKET_DEBUG=1 in the environment before running extconf to enable.
|
667
|
+
if ENV['CURB_SOCKET_DEBUG'] == '1'
|
668
|
+
$defs << '-DCURB_SOCKET_DEBUG=1'
|
669
|
+
end
|
670
|
+
|
671
|
+
# Run any queued constant checks (in parallel if enabled) before header generation.
|
672
|
+
flush_constant_checks
|
673
|
+
|
476
674
|
create_header('curb_config.h')
|
477
675
|
create_makefile('curb_core')
|
data/lib/curl/easy.rb
CHANGED
@@ -158,6 +158,22 @@ module Curl
|
|
158
158
|
set :proxy, url
|
159
159
|
end
|
160
160
|
|
161
|
+
#
|
162
|
+
# call-seq:
|
163
|
+
# easy.request_target = string => string
|
164
|
+
#
|
165
|
+
# Set the request-target used in the HTTP request line (libcurl CURLOPT_REQUEST_TARGET).
|
166
|
+
# Useful for absolute-form request targets (e.g., when speaking to proxies) or
|
167
|
+
# special targets like "*" (OPTIONS *). Requires libcurl with CURLOPT_REQUEST_TARGET support.
|
168
|
+
#
|
169
|
+
def request_target=(value)
|
170
|
+
if Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
|
171
|
+
set :request_target, value
|
172
|
+
else
|
173
|
+
raise NotImplementedError, "CURLOPT_REQUEST_TARGET is not supported by this libcurl"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
161
177
|
def ssl_verify_host=(value)
|
162
178
|
value = 1 if value.class == TrueClass
|
163
179
|
value = 0 if value.class == FalseClass
|
@@ -216,9 +232,18 @@ module Curl
|
|
216
232
|
# call-seq:
|
217
233
|
# easy.cookies = "name1=content1; name2=content2;" => string
|
218
234
|
#
|
219
|
-
# Set
|
220
|
-
# be NAME=CONTENTS, where NAME is the cookie name and
|
221
|
-
# Set multiple cookies in one string like this:
|
235
|
+
# Set the manual Cookie request header for this Curl::Easy instance.
|
236
|
+
# The format of the string should be NAME=CONTENTS, where NAME is the cookie name and
|
237
|
+
# CONTENTS is what the cookie should contain. Set multiple cookies in one string like this:
|
238
|
+
# "name1=content1; name2=content2;".
|
239
|
+
#
|
240
|
+
# Notes:
|
241
|
+
# - This only affects the outgoing Cookie header (libcurl CURLOPT_COOKIE) and does NOT
|
242
|
+
# alter the internal libcurl cookie engine (which stores cookies from Set-Cookie).
|
243
|
+
# - To change cookies stored in the engine, use {#cookielist} / {#cookielist=} or
|
244
|
+
# {#set} with :cookielist.
|
245
|
+
# - To clear a previously set manual Cookie header, assign an empty string ('').
|
246
|
+
# Assigning +nil+ has no effect in current curb versions.
|
222
247
|
#
|
223
248
|
def cookies=(value)
|
224
249
|
set :cookie, value
|
@@ -233,6 +258,8 @@ module Curl
|
|
233
258
|
# *Note* that you must set enable_cookies true to enable the cookie
|
234
259
|
# engine, or this option will be ignored.
|
235
260
|
#
|
261
|
+
# Note: assigning +nil+ has no effect; pass a path string to use a cookie file.
|
262
|
+
#
|
236
263
|
def cookiefile=(value)
|
237
264
|
set :cookiefile, value
|
238
265
|
end
|
@@ -241,16 +268,40 @@ module Curl
|
|
241
268
|
# call-seq:
|
242
269
|
# easy.cookiejar = string => string
|
243
270
|
#
|
244
|
-
# Set a cookiejar file to use for this Curl::Easy instance.
|
245
|
-
#
|
271
|
+
# Set a cookiejar file to use for this Curl::Easy instance. Cookies from the response
|
272
|
+
# will be written into this file.
|
246
273
|
#
|
247
274
|
# *Note* that you must set enable_cookies true to enable the cookie
|
248
275
|
# engine, or this option will be ignored.
|
249
276
|
#
|
277
|
+
# Note: assigning +nil+ has no effect; pass a path string to persist cookies to a file.
|
278
|
+
#
|
250
279
|
def cookiejar=(value)
|
251
280
|
set :cookiejar, value
|
252
281
|
end
|
253
282
|
|
283
|
+
#
|
284
|
+
# call-seq:
|
285
|
+
# easy.cookielist = string => string
|
286
|
+
#
|
287
|
+
# Modify cookies in libcurl's internal cookie engine (CURLOPT_COOKIELIST).
|
288
|
+
# Accepts a Set-Cookie style string, one or more lines in Netscape cookie file format,
|
289
|
+
# or one of the special commands: "ALL" (clear), "SESS" (remove session cookies),
|
290
|
+
# "FLUSH" (write to jar), "RELOAD" (reload from file).
|
291
|
+
#
|
292
|
+
# Examples:
|
293
|
+
# easy.cookielist = "Set-Cookie: session=42; Domain=example.com; Path=/;"
|
294
|
+
# easy.cookielist = [
|
295
|
+
# ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c1', 'v1'].join("\t"),
|
296
|
+
# ['.example.com', 'TRUE', '/', 'FALSE', 0, 'c2', 'v2'].join("\t"),
|
297
|
+
# ''
|
298
|
+
# ].join("\n")
|
299
|
+
# easy.cookielist = 'ALL' # clear all cookies in the engine
|
300
|
+
#
|
301
|
+
def cookielist=(value)
|
302
|
+
set :cookielist, value
|
303
|
+
end
|
304
|
+
|
254
305
|
#
|
255
306
|
# call-seq:
|
256
307
|
# easy = Curl::Easy.new("url") do|c|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class BugIssueNoproxy < Test::Unit::TestCase
|
4
|
+
def test_noproxy_option_support
|
5
|
+
# Test that CURLOPT_NOPROXY constant is defined
|
6
|
+
assert Curl.const_defined?(:CURLOPT_NOPROXY), "CURLOPT_NOPROXY constant should be defined"
|
7
|
+
|
8
|
+
# Test basic noproxy setting using setopt
|
9
|
+
c = Curl::Easy.new('https://google.com')
|
10
|
+
|
11
|
+
# This should not raise an error
|
12
|
+
assert_nothing_raised do
|
13
|
+
c.setopt(Curl::CURLOPT_NOPROXY, "localhost,127.0.0.1")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Test using the convenience method if it exists
|
17
|
+
if c.respond_to?(:noproxy=)
|
18
|
+
assert_nothing_raised do
|
19
|
+
c.noproxy = "localhost,127.0.0.1"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Test getter if it exists
|
23
|
+
if c.respond_to?(:noproxy)
|
24
|
+
assert_equal "localhost,127.0.0.1", c.noproxy
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_noproxy_with_set_method
|
30
|
+
c = Curl::Easy.new('https://google.com')
|
31
|
+
|
32
|
+
# The issue specifically mentions using the set method
|
33
|
+
# This currently raises TypeError as reported in the issue
|
34
|
+
assert_nothing_raised(TypeError) do
|
35
|
+
c.set(:noproxy, "localhost,127.0.0.1")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_noproxy_empty_string
|
40
|
+
c = Curl::Easy.new('https://google.com')
|
41
|
+
|
42
|
+
# Test setting empty string to override environment variables
|
43
|
+
assert_nothing_raised do
|
44
|
+
c.setopt(Curl::CURLOPT_NOPROXY, "")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_noproxy_nil_value
|
49
|
+
c = Curl::Easy.new('https://google.com')
|
50
|
+
|
51
|
+
# Test setting nil to reset
|
52
|
+
assert_nothing_raised do
|
53
|
+
c.setopt(Curl::CURLOPT_NOPROXY, nil)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class BugIssuePostRedirect < Test::Unit::TestCase
|
5
|
+
include BugTestServerSetupTeardown
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@port = 9998
|
9
|
+
super
|
10
|
+
# Mount a POST endpoint that returns a redirect
|
11
|
+
@server.mount_proc("/post_redirect") do |req, res|
|
12
|
+
if req.request_method == "POST"
|
13
|
+
res.status = 302
|
14
|
+
res['Location'] = "http://127.0.0.1:#{@port}/redirected"
|
15
|
+
res.body = "Redirecting..."
|
16
|
+
else
|
17
|
+
res.status = 405
|
18
|
+
res.body = "Method not allowed"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Mount the redirect target
|
23
|
+
@server.mount_proc("/redirected") do |req, res|
|
24
|
+
res.status = 200
|
25
|
+
res['Content-Type'] = "text/plain"
|
26
|
+
res.body = "You have been redirected"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_post_with_max_redirects_zero_should_not_follow_redirect
|
31
|
+
# Test case replicating the issue: POST with max_redirects=0 and follow_location=false
|
32
|
+
# should NOT trigger on_redirect callback or follow the redirect
|
33
|
+
|
34
|
+
redirect_called = false
|
35
|
+
|
36
|
+
handle = Curl::Easy.new("http://127.0.0.1:#{@port}/post_redirect") do |curl|
|
37
|
+
curl.max_redirects = 0
|
38
|
+
curl.follow_location = false
|
39
|
+
curl.on_redirect do |easy|
|
40
|
+
redirect_called = true
|
41
|
+
end
|
42
|
+
curl.headers['Content-Type'] = 'application/json'
|
43
|
+
curl.post_body = {test: "data"}.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
handle.http(:POST)
|
47
|
+
|
48
|
+
# The response should be the redirect response (302)
|
49
|
+
assert_equal 302, handle.response_code
|
50
|
+
assert_match(/Redirecting/, handle.body_str)
|
51
|
+
|
52
|
+
# on_redirect should NOT be called when follow_location is false
|
53
|
+
assert !redirect_called, "on_redirect callback should not be called when follow_location is false"
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_post_with_follow_location_true_triggers_redirect
|
57
|
+
# Test that on_redirect IS called when follow_location is true
|
58
|
+
redirect_called = false
|
59
|
+
|
60
|
+
handle = Curl::Easy.new("http://127.0.0.1:#{@port}/post_redirect") do |curl|
|
61
|
+
curl.follow_location = true
|
62
|
+
curl.on_redirect do |easy|
|
63
|
+
redirect_called = true
|
64
|
+
end
|
65
|
+
curl.headers['Content-Type'] = 'application/json'
|
66
|
+
curl.post_body = {test: "data"}.to_json
|
67
|
+
end
|
68
|
+
|
69
|
+
handle.http(:POST)
|
70
|
+
|
71
|
+
# Should follow the redirect and get the final response
|
72
|
+
assert_equal 200, handle.response_code
|
73
|
+
assert_match(/You have been redirected/, handle.body_str)
|
74
|
+
|
75
|
+
# on_redirect SHOULD be called when follow_location is true
|
76
|
+
assert redirect_called, "on_redirect callback should be called when follow_location is true"
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_curl_post_class_method_respects_redirect_settings
|
80
|
+
# Test that Curl.post (class method) respects redirect settings
|
81
|
+
# According to the issue, this works correctly
|
82
|
+
|
83
|
+
response = Curl.post("http://127.0.0.1:#{@port}/post_redirect", {test: "data"}.to_json) do |curl|
|
84
|
+
curl.max_redirects = 0
|
85
|
+
curl.follow_location = false
|
86
|
+
curl.headers['Content-Type'] = 'application/json'
|
87
|
+
end
|
88
|
+
|
89
|
+
# Should get the redirect response, not follow it
|
90
|
+
assert_equal 302, response.response_code
|
91
|
+
assert_match(/Redirecting/, response.body_str)
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
2
|
+
|
3
|
+
class BugIssueSpnego < Test::Unit::TestCase
|
4
|
+
def test_spnego_detection_works
|
5
|
+
# The fix for issue #227 ensures that Curl.spnego? checks for both
|
6
|
+
# CURL_VERSION_SPNEGO (newer libcurl) and CURL_VERSION_GSSNEGOTIATE (older libcurl)
|
7
|
+
#
|
8
|
+
# We can't test that it returns true because that depends on how libcurl
|
9
|
+
# was compiled on the system. We can only test that:
|
10
|
+
# 1. The method exists and works
|
11
|
+
# 2. If CURLAUTH_GSSNEGOTIATE is available, we can use it
|
12
|
+
|
13
|
+
# Check if CURLAUTH_GSSNEGOTIATE is available
|
14
|
+
if Curl.const_defined?(:CURLAUTH_GSSNEGOTIATE) && Curl.const_get(:CURLAUTH_GSSNEGOTIATE) != 0
|
15
|
+
# Test that we can use GSSNEGOTIATE auth type
|
16
|
+
c = Curl::Easy.new('http://example.com')
|
17
|
+
assert_nothing_raised do
|
18
|
+
c.http_auth_types = Curl::CURLAUTH_GSSNEGOTIATE
|
19
|
+
end
|
20
|
+
|
21
|
+
# The fix ensures spnego? won't incorrectly return false when
|
22
|
+
# older libcurl versions have GSSNEGOTIATE support
|
23
|
+
# (The actual return value depends on system libcurl compilation)
|
24
|
+
end
|
25
|
+
|
26
|
+
# The important fix is that the method now checks both constants
|
27
|
+
# This test passes if no exceptions are raised
|
28
|
+
assert true, "SPNEGO detection is working correctly"
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_spnego_method_exists
|
32
|
+
# The method should always exist
|
33
|
+
assert Curl.respond_to?(:spnego?), "Curl should respond to spnego?"
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_spnego_returns_boolean
|
37
|
+
# The method should return a boolean
|
38
|
+
result = Curl.spnego?
|
39
|
+
assert [true, false].include?(result), "Curl.spnego? should return true or false, got #{result.inspect}"
|
40
|
+
end
|
41
|
+
end
|
data/tests/helper.rb
CHANGED
@@ -11,6 +11,34 @@ $LIBDIR = File.join($TOPDIR, 'lib')
|
|
11
11
|
$:.unshift($LIBDIR)
|
12
12
|
$:.unshift($EXTDIR)
|
13
13
|
|
14
|
+
# Setup SimpleCov for Ruby code coverage if COVERAGE env var is set
|
15
|
+
if ENV['COVERAGE']
|
16
|
+
begin
|
17
|
+
require 'simplecov'
|
18
|
+
require 'simplecov-lcov'
|
19
|
+
|
20
|
+
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
21
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
22
|
+
SimpleCov::Formatter::HTMLFormatter,
|
23
|
+
SimpleCov::Formatter::LcovFormatter
|
24
|
+
])
|
25
|
+
|
26
|
+
SimpleCov.start do
|
27
|
+
add_filter '/tests/'
|
28
|
+
add_filter '/spec/'
|
29
|
+
add_filter '/vendor/'
|
30
|
+
add_filter '/.bundle/'
|
31
|
+
add_group 'Library', 'lib'
|
32
|
+
add_group 'Extensions', 'ext'
|
33
|
+
|
34
|
+
# Track branch coverage if available
|
35
|
+
enable_coverage :branch if respond_to?(:enable_coverage)
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
puts "SimpleCov not available. Install it with: gem install simplecov simplecov-lcov"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
14
42
|
require 'curb'
|
15
43
|
begin
|
16
44
|
require 'test/unit'
|
@@ -19,6 +47,11 @@ rescue LoadError
|
|
19
47
|
require 'test/unit'
|
20
48
|
end
|
21
49
|
require 'fileutils'
|
50
|
+
require 'rbconfig'
|
51
|
+
|
52
|
+
# Platform helpers
|
53
|
+
WINDOWS = /mswin|msys|mingw|cygwin|bccwin|wince|emc|windows/i.match?(RbConfig::CONFIG['host_os'])
|
54
|
+
NO_FORK = !Process.respond_to?(:fork)
|
22
55
|
|
23
56
|
$TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{File.expand_path(__FILE__).tr('\\','/')}"
|
24
57
|
|
data/tests/mem_check.rb
CHANGED
@@ -6,6 +6,9 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
6
6
|
# Run some tests to measure the memory usage of curb, these tests require fork and ps
|
7
7
|
#
|
8
8
|
class TestCurbMemory < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
omit('Memory fork/ps tests not supported on Windows') if WINDOWS || NO_FORK
|
11
|
+
end
|
9
12
|
|
10
13
|
def test_easy_memory
|
11
14
|
easy_avg, easy_std = measure_object_memory(Curl::Easy)
|
data/tests/tc_curl_download.rb
CHANGED
@@ -32,6 +32,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def test_download_url_to_file_via_io
|
35
|
+
omit('fork not available on this platform') if NO_FORK || WINDOWS
|
35
36
|
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
36
37
|
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
37
38
|
reader, writer = IO.pipe
|
@@ -58,7 +59,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
|
|
58
59
|
assert File.exist?(dl_path)
|
59
60
|
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
60
61
|
ensure
|
61
|
-
File.unlink(dl_path) if File.exist?(dl_path)
|
62
|
+
File.unlink(dl_path) if dl_path && File.exist?(dl_path)
|
62
63
|
end
|
63
64
|
|
64
65
|
def test_download_bad_url_gives_404
|