curb 0.8.4 → 1.3.5

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +522 -0
  3. data/Rakefile +79 -26
  4. data/ext/banned.h +32 -0
  5. data/ext/curb.c +549 -230
  6. data/ext/curb.h +19 -10
  7. data/ext/curb_easy.c +1935 -366
  8. data/ext/curb_easy.h +16 -0
  9. data/ext/curb_errors.c +115 -18
  10. data/ext/curb_errors.h +8 -5
  11. data/ext/curb_macros.h +33 -21
  12. data/ext/curb_multi.c +1858 -272
  13. data/ext/curb_multi.h +10 -3
  14. data/ext/curb_postfield.c +149 -77
  15. data/ext/curb_postfield.h +1 -0
  16. data/ext/curb_upload.c +38 -11
  17. data/ext/curb_upload.h +2 -0
  18. data/ext/extconf.rb +353 -35
  19. data/lib/curb.rb +1 -0
  20. data/lib/curl/easy.rb +314 -78
  21. data/lib/curl/multi.rb +134 -28
  22. data/lib/curl.rb +217 -11
  23. data/tests/bug_crash_on_debug.rb +14 -28
  24. data/tests/bug_crash_on_progress.rb +32 -16
  25. data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
  26. data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
  27. data/tests/bug_follow_redirect_288.rb +83 -0
  28. data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
  29. data/tests/bug_issue102.rb +1 -1
  30. data/tests/bug_issue_noproxy.rb +56 -0
  31. data/tests/bug_issue_post_redirect.rb +93 -0
  32. data/tests/bug_issue_spnego.rb +41 -0
  33. data/tests/bug_multi_segfault.rb +1 -0
  34. data/tests/bug_raise_on_callback.rb +30 -0
  35. data/tests/helper.rb +400 -44
  36. data/tests/leak_trace.rb +237 -0
  37. data/tests/mem_check.rb +3 -0
  38. data/tests/tc_curl.rb +31 -1
  39. data/tests/tc_curl_download.rb +12 -7
  40. data/tests/tc_curl_easy.rb +911 -63
  41. data/tests/tc_curl_easy_cookielist.rb +277 -0
  42. data/tests/tc_curl_easy_request_target.rb +41 -0
  43. data/tests/tc_curl_easy_resolve.rb +48 -0
  44. data/tests/tc_curl_maxfilesize.rb +12 -0
  45. data/tests/tc_curl_multi.rb +855 -53
  46. data/tests/tc_curl_native_coverage.rb +145 -0
  47. data/tests/tc_curl_postfield.rb +207 -30
  48. data/tests/tc_curl_protocols.rb +37 -0
  49. data/tests/tc_fiber_scheduler.rb +543 -0
  50. data/tests/tc_ftp_options.rb +39 -0
  51. data/tests/tc_gc_compact.rb +223 -0
  52. data/tests/tc_test_server_methods.rb +110 -0
  53. data/tests/timeout.rb +30 -6
  54. metadata +59 -36
  55. data/README +0 -194
@@ -0,0 +1,277 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'json'
3
+
4
+ class TestCurbCurlEasyCookielist < Test::Unit::TestCase
5
+ def test_setopt_cookielist
6
+ easy = Curl::Easy.new
7
+ # DateTime handles time zone correctly
8
+ expires = (Date.today + 2).to_datetime
9
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost; expires=#{expires.httpdate};")
10
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c2=v2; domain=localhost')
11
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c3=v3; expires=#{expires.httpdate};")
12
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c4=v4;')
13
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c5=v5; domain=127.0.0.1; expires=#{expires.httpdate};")
14
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: c6=v6; domain=127.0.0.1;;')
15
+
16
+ # Since 7.43.0 cookies that were imported in the Set-Cookie format without a domain name are not exported by this option.
17
+ # So, before 7.43.0, c3 and c4 will be exported too; but that version is far too old for current curb version, so it's not handled here.
18
+ if Curl::CURL_VERSION.to_f > 8
19
+ expected_cookielist = [
20
+ ".127.0.0.1\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc5\tv5",
21
+ ".127.0.0.1\tTRUE\t/\tFALSE\t0\tc6\tv6",
22
+ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1",
23
+ ".localhost\tTRUE\t/\tFALSE\t0\tc2\tv2",
24
+ ]
25
+ else
26
+ expected_cookielist = [
27
+ "127.0.0.1\tFALSE\t/\tFALSE\t#{expires.to_time.to_i}\tc5\tv5",
28
+ "127.0.0.1\tFALSE\t/\tFALSE\t0\tc6\tv6",
29
+ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1",
30
+ ".localhost\tTRUE\t/\tFALSE\t0\tc2\tv2",
31
+ ]
32
+ end
33
+ assert_equal expected_cookielist, easy.cookielist
34
+
35
+ easy.url = "#{TestServlet.url}/get_cookies"
36
+ easy.perform
37
+ assert_equal 'c6=v6; c5=v5; c4=v4; c3=v3', easy.body_str
38
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
39
+ easy.perform
40
+ assert_equal 'c2=v2; c1=v1', easy.body_str
41
+ end
42
+
43
+ # libcurl documentation says: "This option also enables the cookie engine", but it's not tracked on the curb level
44
+ def test_setopt_cookielist_enables_cookie_engine
45
+ easy = Curl::Easy.new
46
+ expires = (Date.today + 2).to_datetime
47
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies"
48
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: c1=v1; domain=localhost; expires=#{expires.httpdate};")
49
+ easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }])
50
+ easy.perform
51
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
52
+ easy.post_body = nil
53
+ easy.perform
54
+
55
+ assert !easy.enable_cookies?
56
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc1\tv1", ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc2\tv2"], easy.cookielist
57
+ assert_equal 'c2=v2; c1=v1', easy.body_str
58
+ end
59
+
60
+ def test_setopt_cookielist_invalid_format
61
+ easy = Curl::Easy.new
62
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
63
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Not a cookie')
64
+ assert_nil easy.cookielist
65
+ easy.perform
66
+ assert_equal '', easy.body_str
67
+ end
68
+
69
+ def test_setopt_cookielist_netscape_format
70
+ easy = Curl::Easy.new
71
+ expires = (Date.today + 2).to_datetime
72
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
73
+ # Note domain changes for include subdomains
74
+ [
75
+ ['localhost', 'FALSE', '/', 'TRUE', 0, 'session_http_only', '42'].join("\t"),
76
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '43'].join("\t"),
77
+ ['localhost', 'TRUE', '/', 'FALSE', expires.to_time.to_i, 'permanent', '44'].join("\t"),
78
+ ['.localhost', 'FALSE', '/', 'TRUE', expires.to_time.to_i, 'permanent_http_only', '45'].join("\t"),
79
+ ].each { |cookie| easy.setopt(Curl::CURLOPT_COOKIELIST, cookie) }
80
+
81
+ expected_cookielist = [
82
+ ['localhost', 'FALSE', '/', 'TRUE', 0, 'session_http_only', '42'].join("\t"),
83
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '43'].join("\t"),
84
+ ['.localhost', 'TRUE', '/', 'FALSE', expires.to_time.to_i, 'permanent', '44'].join("\t"),
85
+ ['localhost', 'FALSE', '/', 'TRUE', expires.to_time.to_i, 'permanent_http_only', '45'].join("\t"),
86
+ ]
87
+ assert_equal expected_cookielist, easy.cookielist
88
+ easy.perform
89
+ assert_equal 'permanent_http_only=45; session_http_only=42; permanent=44; session=43', easy.body_str
90
+ end
91
+
92
+ # Multiple cookies and comments are not supported
93
+ def test_setopt_cookielist_netscape_format_mutliline
94
+ easy = Curl::Easy.new
95
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
96
+ easy.setopt(
97
+ Curl::CURLOPT_COOKIELIST,
98
+ [
99
+ '# Netscape HTTP Cookie File',
100
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '42'].join("\t"),
101
+ '',
102
+ ].join("\n"),
103
+ )
104
+ assert_nil easy.cookielist
105
+ easy.perform
106
+ assert_equal '', easy.body_str
107
+
108
+ easy.setopt(
109
+ Curl::CURLOPT_COOKIELIST,
110
+ [
111
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session', '42'].join("\t"),
112
+ ['.localhost', 'TRUE', '/', 'FALSE', 0, 'session2', '84'].join("\t"),
113
+ '',
114
+ ].join("\n"),
115
+ )
116
+ # Only first cookie is set
117
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t0\tsession\t42"], easy.cookielist
118
+ easy.perform
119
+ assert_equal 'session=42', easy.body_str
120
+ end
121
+
122
+ # ALL erases all cookies held in memory
123
+ # ALL was added in 7.14.1
124
+ def test_setopt_cookielist_command_all
125
+ expires = (Date.today + 2).to_datetime
126
+ with_permanent_and_session_cookies(expires) do |easy|
127
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'ALL')
128
+ assert_nil easy.cookielist
129
+ easy.perform
130
+ assert_equal '', easy.body_str
131
+ end
132
+ end
133
+
134
+ # SESS erases all session cookies held in memory
135
+ # SESS was added in 7.15.4
136
+ def test_setopt_cookielist_command_sess
137
+ expires = (Date.today + 2).to_datetime
138
+ with_permanent_and_session_cookies(expires) do |easy|
139
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'SESS')
140
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42"], easy.cookielist
141
+ easy.perform
142
+ assert_equal 'permanent=42', easy.body_str
143
+ end
144
+ end
145
+
146
+ # FLUSH writes all known cookies to the file specified by CURLOPT_COOKIEJAR
147
+ # FLUSH was added in 7.17.1
148
+ def test_setopt_cookielist_command_flush
149
+ expires = (Date.today + 2).to_datetime
150
+ with_permanent_and_session_cookies(expires) do |easy|
151
+ cookiejar = File.join(Dir.tmpdir, 'curl_test_cookiejar')
152
+ assert !File.exist?(cookiejar)
153
+ begin
154
+ easy.cookiejar = cookiejar
155
+ # trick to actually set CURLOPT_COOKIEJAR
156
+ easy.enable_cookies = true
157
+ easy.perform
158
+ assert !File.exist?(cookiejar)
159
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'FLUSH')
160
+ expected_cookiejar = <<~COOKIEJAR
161
+ # Netscape HTTP Cookie File
162
+ # https://curl.se/docs/http-cookies.html
163
+ # This file was generated by libcurl! Edit at your own risk.
164
+
165
+ .localhost TRUE / FALSE 0 session 420
166
+ .localhost TRUE / FALSE #{expires.to_time.to_i} permanent 42
167
+ COOKIEJAR
168
+ assert_equal expected_cookiejar, File.read(cookiejar)
169
+ ensure
170
+ # Otherwise it'll create this file again
171
+ easy.close
172
+ File.unlink(cookiejar) if File.exist?(cookiejar)
173
+ end
174
+ end
175
+ end
176
+
177
+ # RELOAD loads all cookies from the files specified by CURLOPT_COOKIEFILE
178
+ # RELOAD was added in 7.39.0
179
+ def test_setopt_cookielist_command_reload
180
+ expires = (Date.today + 2).to_datetime
181
+ expires_file = (Date.today + 4).to_datetime
182
+ with_permanent_and_session_cookies(expires) do |easy|
183
+ cookiefile = File.join(Dir.tmpdir, 'curl_test_cookiefile')
184
+ assert !File.exist?(cookiefile)
185
+ begin
186
+ cookielist = [
187
+ # Won't be updated, added instead
188
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent\t84",
189
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent_file\t84",
190
+ # Won't be updated, added instead
191
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession\t840",
192
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession_file\t840",
193
+ '',
194
+ ]
195
+ File.write(cookiefile, cookielist.join("\n"))
196
+ easy.cookiefile = cookiefile
197
+ # trick to actually set CURLOPT_COOKIEFILE
198
+ easy.enable_cookies = true
199
+ easy.perform
200
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'RELOAD')
201
+ expected_cookielist = [
202
+ ".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42",
203
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession\t420",
204
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent\t84",
205
+ ".localhost\tTRUE\t/\tFALSE\t#{expires_file.to_time.to_i}\tpermanent_file\t84",
206
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession\t840",
207
+ ".localhost\tTRUE\t/\tFALSE\t0\tsession_file\t840",
208
+ ]
209
+ assert_equal expected_cookielist, easy.cookielist
210
+ easy.perform
211
+ # Be careful, duplicates are not removed
212
+ assert_equal 'permanent_file=84; session_file=840; permanent=84; session=840; permanent=42; session=420', easy.body_str
213
+ ensure
214
+ File.unlink(cookiefile) if File.exist?(cookiefile)
215
+ end
216
+ end
217
+ end
218
+
219
+ def test_commands_do_not_enable_cookie_engine
220
+ %w[ALL SESS FLUSH RELOAD].each do |command|
221
+ easy = Curl::Easy.new
222
+ expires = (Date.today + 2).to_datetime
223
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies"
224
+ easy.setopt(Curl::CURLOPT_COOKIELIST, command)
225
+ easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }])
226
+ easy.perform
227
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
228
+ easy.post_body = nil
229
+ easy.perform
230
+
231
+ assert !easy.enable_cookies?
232
+ assert_nil easy.cookielist
233
+ assert_equal '', easy.body_str
234
+ end
235
+ end
236
+
237
+
238
+ def test_strings_without_cookie_enable_cookie_engine
239
+ [
240
+ '',
241
+ '# Netscape HTTP Cookie File',
242
+ 'no_a_cookie',
243
+ ].each do |command|
244
+ easy = Curl::Easy.new
245
+ expires = (Date.today + 2).to_datetime
246
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/set_cookies"
247
+ easy.setopt(Curl::CURLOPT_COOKIELIST, command)
248
+ easy.post_body = JSON.generate([{ name: 'c2', value: 'v2', domain: 'localhost', expires: expires.httpdate, path: '/' }])
249
+ easy.perform
250
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
251
+ easy.post_body = nil
252
+ easy.perform
253
+
254
+ assert !easy.enable_cookies?
255
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tc2\tv2"], easy.cookielist
256
+ assert_equal 'c2=v2', easy.body_str
257
+ end
258
+ end
259
+
260
+ def with_permanent_and_session_cookies(expires)
261
+ easy = Curl::Easy.new
262
+ easy.url = "http://localhost:#{TestServlet.port}#{TestServlet.path}/get_cookies"
263
+ easy.setopt(Curl::CURLOPT_COOKIELIST, "Set-Cookie: permanent=42; domain=localhost; expires=#{expires.httpdate};")
264
+ easy.setopt(Curl::CURLOPT_COOKIELIST, 'Set-Cookie: session=420; domain=localhost;')
265
+ assert_equal [".localhost\tTRUE\t/\tFALSE\t#{expires.to_time.to_i}\tpermanent\t42", ".localhost\tTRUE\t/\tFALSE\t0\tsession\t420"], easy.cookielist
266
+ easy.perform
267
+ assert_equal 'permanent=42; session=420', easy.body_str
268
+
269
+ yield easy
270
+ end
271
+
272
+ include TestServerMethods
273
+
274
+ def setup
275
+ server_setup
276
+ end
277
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class TestCurbCurlEasyRequestTarget < Test::Unit::TestCase
4
+ include TestServerMethods
5
+
6
+ def setup
7
+ server_setup
8
+ end
9
+
10
+ def test_request_target_absolute_form
11
+ unless Curl.const_defined?(:CURLOPT_REQUEST_TARGET)
12
+ omit('libcurl lacks CURLOPT_REQUEST_TARGET support')
13
+ end
14
+
15
+ tmp = Tempfile.new('curb_test_request_target')
16
+ path = tmp.path
17
+ fd = IO.sysopen(path, 'w')
18
+ io = IO.new(fd, 'w')
19
+ io.sync = true
20
+
21
+ easy = Curl::Easy.new(TestServlet.url)
22
+ easy.verbose = true
23
+ easy.setopt(Curl::CURLOPT_STDERR, io)
24
+
25
+ # Force absolute-form request target, different from the URL host
26
+ easy.request_target = "http://localhost:#{TestServlet.port}#{TestServlet.path}"
27
+ easy.headers = { 'Host' => "example.com" }
28
+
29
+ easy.perform
30
+
31
+ io.flush
32
+ io.close
33
+ output = File.read(path)
34
+
35
+ assert_match(/GET\s+http:\/\/localhost:#{TestServlet.port}#{Regexp.escape(TestServlet.path)}\s+HTTP\/1\.1/, output)
36
+ assert_match(/Host:\s+example\.com/, output)
37
+ ensure
38
+ tmp.close! if defined?(tmp) && tmp
39
+ end
40
+ end
41
+
@@ -0,0 +1,48 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class TestCurbCurlEasyResolve < Test::Unit::TestCase
4
+ include TestServerMethods
5
+
6
+ def setup
7
+ server_setup
8
+ @easy = Curl::Easy.new
9
+ end
10
+
11
+ def test_resolve
12
+ @easy.resolve = [ "example.com:80:127.0.0.1" ]
13
+ assert_equal @easy.resolve, [ "example.com:80:127.0.0.1" ]
14
+ end
15
+
16
+ def test_empty_resolve
17
+ assert_equal @easy.resolve, nil
18
+ end
19
+
20
+ def test_setopt_resolve_persists_across_performs
21
+ host = "curb-setopt-resolve.invalid"
22
+ mapping = "#{host}:#{TestServlet.port}:127.0.0.1"
23
+
24
+ @easy.url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}"
25
+ @easy.dns_cache_timeout = 0
26
+ @easy.set(:resolve, [mapping])
27
+
28
+ @easy.perform
29
+ assert_match(/GET/, @easy.body_str)
30
+
31
+ @easy.perform
32
+ assert_match(/GET/, @easy.body_str)
33
+ end
34
+
35
+ def test_resolve_entries_can_be_objects_that_convert_to_string
36
+ host = "curb-resolve-object.invalid"
37
+ mapping = "#{host}:#{TestServlet.port}:127.0.0.1"
38
+ entry = Object.new
39
+ entry.define_singleton_method(:to_s) { mapping }
40
+
41
+ @easy.url = "http://#{host}:#{TestServlet.port}#{TestServlet.path}"
42
+ @easy.dns_cache_timeout = 0
43
+ @easy.resolve = [entry]
44
+
45
+ @easy.perform
46
+ assert_match(/GET/, @easy.body_str)
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class TestCurbCurlMaxFileSize < Test::Unit::TestCase
4
+ def setup
5
+ @easy = Curl::Easy.new
6
+ end
7
+
8
+ def test_maxfilesize
9
+ @easy.set(Curl::CURLOPT_MAXFILESIZE, 5000000)
10
+ end
11
+
12
+ end