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
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,36 +43,210 @@ end
35
43
  # puts "Selected arch: #{archs.first}"
36
44
  #end
37
45
 
38
- def define(s, v=1)
39
- $defs.push( format("-D HAVE_%s=%d", s.to_s.upcase, v) )
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
- def have_constant(name)
43
- sname = name.is_a?(Symbol) ? name.to_s : name.upcase
44
- checking_for name do
45
- src = %{
46
- #include <curl/curl.h>
47
- int main() {
48
- int test = (int)#{sname};
49
- return 0;
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
- if try_compile(src,"#{$CFLAGS} #{$LIBS}")
53
- define name
54
- true
55
- else
56
- #define name, 0
57
- false
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
58
235
  end
59
236
  end
60
237
  end
61
238
 
239
+ have_constant "curlopt_tcp_keepalive"
240
+ have_constant "curlopt_tcp_keepidle"
241
+ have_constant "curlopt_tcp_keepintvl"
242
+ have_constant "curlinfo_appconnect_time"
62
243
  have_constant "curlinfo_redirect_time"
63
244
  have_constant "curlinfo_response_code"
64
245
  have_constant "curlinfo_filetime"
65
246
  have_constant "curlinfo_redirect_count"
66
247
  have_constant "curlinfo_os_errno"
67
248
  have_constant "curlinfo_num_connects"
249
+ have_constant "curlinfo_cookielist"
68
250
  have_constant "curlinfo_ftp_entry_path"
69
251
  have_constant "curl_version_ssl"
70
252
  have_constant "curl_version_libz"
@@ -77,9 +259,12 @@ have_constant "curl_version_largefile"
77
259
  have_constant "curl_version_idn"
78
260
  have_constant "curl_version_sspi"
79
261
  have_constant "curl_version_conv"
262
+ have_constant "curl_version_http2"
80
263
  have_constant "curlproxy_http"
81
264
  have_constant "curlproxy_socks4"
265
+ have_constant "curlproxy_socks4a"
82
266
  have_constant "curlproxy_socks5"
267
+ have_constant "curlproxy_socks5_hostname"
83
268
  have_constant "curlauth_basic"
84
269
  have_constant "curlauth_digest"
85
270
  have_constant "curlauth_gssnegotiate"
@@ -98,6 +283,12 @@ have_constant "curle_send_fail_rewind"
98
283
  have_constant "curle_ssl_engine_initfailed"
99
284
  have_constant "curle_login_denied"
100
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
+
101
292
  # older than 7.16.3
102
293
  have_constant "curlmopt_maxconnects"
103
294
 
@@ -108,6 +299,38 @@ have_constant "curlopt_sockoptdata"
108
299
  have_constant "curlopt_opensocketfunction"
109
300
  have_constant "curlopt_opensocketdata"
110
301
 
302
+ # Deprecated constants (still check for them for backward compat)
303
+ have_constant "curlopt_ioctlfunction"
304
+ have_constant "curlopt_ioctldata"
305
+ have_constant "curlopt_progressfunction"
306
+ have_constant "curlopt_progressdata"
307
+ have_constant "curlopt_conv_to_network_function"
308
+ have_constant "curlopt_conv_from_network_function"
309
+ have_constant "curlopt_conv_from_utf8_function"
310
+
311
+ # Replacements for deprecated constants
312
+ # CURLOPT_PROGRESSFUNCTION -> CURLOPT_XFERINFOFUNCTION (since 7.32.0)
313
+ have_constant "curlopt_xferinfofunction"
314
+ have_constant "curlopt_xferinfodata"
315
+
316
+ # CURLOPT_PROTOCOLS -> CURLOPT_PROTOCOLS_STR (since 7.85.0)
317
+ have_constant "curlopt_protocols_str"
318
+ have_constant "curlopt_redir_protocols_str"
319
+
320
+ # CURLOPT_SOCKS5_GSSAPI_SERVICE -> CURLOPT_PROXY_SERVICE_NAME (since 7.49.0)
321
+ have_constant "curlopt_proxy_service_name"
322
+
323
+ # CURLOPT_HTTPPOST -> CURLOPT_MIMEPOST (since 7.56.0)
324
+ have_constant "curlopt_mimepost"
325
+
326
+ # CURLINFO_* -> CURLINFO_*_T (since 7.55.0)
327
+ have_constant "curlinfo_size_upload_t"
328
+ have_constant "curlinfo_size_download_t"
329
+ have_constant "curlinfo_speed_upload_t"
330
+ have_constant "curlinfo_speed_download_t"
331
+ have_constant "curlinfo_content_length_download_t"
332
+ have_constant "curlinfo_content_length_upload_t"
333
+
111
334
  # additional consts
112
335
  have_constant "curle_conv_failed"
113
336
  have_constant "curle_conv_reqd"
@@ -142,6 +365,8 @@ have_func("curl_multi_timeout")
142
365
  have_func("curl_multi_fdset")
143
366
  have_func("curl_multi_perform")
144
367
 
368
+ have_constant "curlopt_haproxyprotocol"
369
+
145
370
  # constants
146
371
  have_constant "curlopt_interleavefunction"
147
372
  have_constant "curlopt_interleavedata"
@@ -206,6 +431,7 @@ have_constant "curlopt_httppost"
206
431
  have_constant "curlopt_referer"
207
432
  have_constant "curlopt_useragent"
208
433
  have_constant "curlopt_httpheader"
434
+ have_constant "curlopt_proxyheader"
209
435
  have_constant "curlopt_http200aliases"
210
436
  have_constant "curlopt_cookie"
211
437
  have_constant "curlopt_cookiefile"
@@ -306,6 +532,7 @@ have_constant "curlusessl_try"
306
532
  have_constant "curlusessl_control"
307
533
  have_constant "curlusessl_all"
308
534
  have_constant "curlopt_resolve"
535
+ have_constant "curlopt_request_target"
309
536
  have_constant "curlopt_sslcert"
310
537
  have_constant "curlopt_sslcerttype"
311
538
  have_constant "curlopt_sslkey"
@@ -318,6 +545,18 @@ have_constant "curl_sslversion_default"
318
545
  have_constant :CURL_SSLVERSION_TLSv1
319
546
  have_constant :CURL_SSLVERSION_SSLv2
320
547
  have_constant :CURL_SSLVERSION_SSLv3
548
+
549
+ # Added in 7.30.0
550
+ have_constant "curlmopt_max_host_connections"
551
+
552
+ # Added in 7.34.0
553
+ have_constant :CURL_SSLVERSION_TLSv1_0
554
+ have_constant :CURL_SSLVERSION_TLSv1_1
555
+ have_constant :CURL_SSLVERSION_TLSv1_2
556
+
557
+ # Added in 7.52.0
558
+ have_constant :CURL_SSLVERSION_TLSv1_3
559
+
321
560
  have_constant "curlopt_ssl_verifypeer"
322
561
  have_constant "curlopt_cainfo"
323
562
  have_constant "curlopt_issuercert"
@@ -351,10 +590,77 @@ have_constant "curle_not_built_in"
351
590
 
352
591
  have_constant "curle_obsolete" # removed in 7.24 ?
353
592
 
593
+ have_constant "curle_ftp_pret_failed"
594
+ have_constant "curle_rtsp_cseq_error"
595
+ have_constant "curle_rtsp_session_error"
596
+ have_constant "curle_ftp_bad_file_list"
597
+ have_constant "curle_chunk_failed"
598
+ have_constant "curle_no_connection_available"
599
+ have_constant "curle_ssl_pinnedpubkeynotmatch"
600
+ have_constant "curle_ssl_invalidcertstatus"
601
+ have_constant "curle_http2_stream"
602
+
603
+ # gssapi/spnego delegation related constants
604
+ have_constant "curlopt_gssapi_delegation"
605
+ have_constant "curlgssapi_delegation_policy_flag"
606
+ have_constant "curlgssapi_delegation_flag"
607
+
608
+ have_constant "CURLM_ADDED_ALREADY"
609
+
610
+ # added in 7.40.0
611
+ have_constant "curlopt_unix_socket_path"
612
+
613
+ # added in 7.42.0
614
+ have_constant "curlopt_path_as_is"
615
+
616
+ # added in 7.43.0
617
+ have_constant "curlopt_pipewait"
618
+
619
+ have_constant "curlopt_proxy_ssl_verifyhost"
620
+
621
+ # protocol constants
622
+ have_constant "curlproto_all"
623
+ have_constant "curlproto_dict"
624
+ have_constant "curlproto_file"
625
+ have_constant "curlproto_ftp"
626
+ have_constant "curlproto_ftps"
627
+ have_constant "curlproto_gopher"
628
+ have_constant "curlproto_http"
629
+ have_constant "curlproto_https"
630
+ have_constant "curlproto_imap"
631
+ have_constant "curlproto_imaps"
632
+ have_constant "curlproto_ldap"
633
+ have_constant "curlproto_ldaps"
634
+ have_constant "curlproto_pop3"
635
+ have_constant "curlproto_pop3s"
636
+ have_constant "curlproto_rtmp"
637
+ have_constant "curlproto_rtmpe"
638
+ have_constant "curlproto_rtmps"
639
+ have_constant "curlproto_rtmpt"
640
+ have_constant "curlproto_rtmpte"
641
+ have_constant "curlproto_rtmpts"
642
+ have_constant "curlproto_rtsp"
643
+ have_constant "curlproto_scp"
644
+ have_constant "curlproto_sftp"
645
+ have_constant "curlproto_smb"
646
+ have_constant "curlproto_smbs"
647
+ have_constant "curlproto_smtp"
648
+ have_constant "curlproto_smtps"
649
+ have_constant "curlproto_telnet"
650
+ have_constant "curlproto_tftp"
651
+
354
652
  if try_compile('int main() { return 0; }','-Wall')
355
653
  $CFLAGS << ' -Wall'
356
654
  end
357
655
 
656
+ # Clang-specific warning suppressions (not recognized by GCC)
657
+ # These are used to suppress warnings in Ruby header macros
658
+ %w[-Wno-self-assign -Wno-parentheses-equality -Wno-constant-logical-operand].each do |flag|
659
+ if try_compile('int main() { return 0; }', flag)
660
+ $CFLAGS << " #{flag}"
661
+ end
662
+ end
663
+
358
664
  # do some checking to detect ruby 1.8 hash.c vs ruby 1.9 hash.c
359
665
  def test_for(name, const, src)
360
666
  checking_for name do
@@ -366,23 +672,6 @@ def test_for(name, const, src)
366
672
  end
367
673
  end
368
674
  end
369
- test_for("Ruby 1.9 Hash", "RUBY19_HASH", %{
370
- #include <ruby.h>
371
- int main() {
372
- VALUE hash = rb_hash_new();
373
- if( RHASH(hash)->ntbl->num_entries ) {
374
- return 0;
375
- }
376
- return 1;
377
- }
378
- })
379
- test_for("Ruby 1.9 st.h", "RUBY19_ST_H", %{
380
- #include <ruby.h>
381
- #include <ruby/st.h>
382
- int main() {
383
- return 0;
384
- }
385
- })
386
675
 
387
676
  test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
388
677
  #include <curl/curl.h>
@@ -394,6 +683,35 @@ test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
394
683
  })
395
684
 
396
685
  have_func('rb_thread_blocking_region')
686
+ have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
687
+ have_header('ruby/io.h')
688
+ # Ruby 4.x exports rb_thread_fd_select without declaring it in ruby/io.h.
689
+ have_func('rb_thread_fd_select')
690
+ have_func('rb_wait_for_single_fd', 'ruby/io.h')
691
+ have_header('ruby/fiber/scheduler.h')
692
+ have_func('rb_fiber_scheduler_current', 'ruby/fiber/scheduler.h')
693
+ have_func('rb_fiber_scheduler_io_wait', 'ruby/fiber/scheduler.h')
694
+ have_func('rb_fiber_scheduler_io_select', 'ruby/fiber/scheduler.h')
695
+ have_func('rb_io_stdio_file')
696
+ have_func('curl_multi_wait')
697
+ have_func('curl_multi_socket_action')
698
+ have_func('curl_multi_assign')
699
+ have_func('curl_multi_wakeup')
700
+ have_func('curl_multi_poll')
701
+ have_func('curl_multi_socket')
702
+ have_func('curl_multi_timer_callback')
703
+ have_constant 'curlmopt_socketfunction'
704
+ have_constant 'curlmopt_timerfunction'
705
+ have_func('curl_easy_duphandle')
706
+
707
+ # Optional: enable verbose socket-action debug logging.
708
+ # Set CURB_SOCKET_DEBUG=1 in the environment before running extconf to enable.
709
+ if ENV['CURB_SOCKET_DEBUG'] == '1'
710
+ $defs << '-DCURB_SOCKET_DEBUG=1'
711
+ end
712
+
713
+ # Run any queued constant checks (in parallel if enabled) before header generation.
714
+ flush_constant_checks
397
715
 
398
716
  create_header('curb_config.h')
399
717
  create_makefile('curb_core')
data/lib/curb.rb CHANGED
@@ -1 +1,2 @@
1
+ # frozen_string_literal: true
1
2
  require 'curl'