ethon 0.18.0 → 0.19.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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +705 -2
  3. data/README.md +183 -40
  4. data/lib/ethon/curl.rb +22 -0
  5. data/lib/ethon/curls/classes.rb +284 -12
  6. data/lib/ethon/curls/codes.rb +28 -9
  7. data/lib/ethon/curls/constants.rb +61 -4
  8. data/lib/ethon/curls/form_options.rb +2 -1
  9. data/lib/ethon/curls/functions.rb +60 -11
  10. data/lib/ethon/curls/infos.rb +104 -5
  11. data/lib/ethon/curls/introspection.rb +139 -0
  12. data/lib/ethon/curls/introspection_constants.rb +42 -0
  13. data/lib/ethon/curls/mime.rb +187 -0
  14. data/lib/ethon/curls/optional.rb +14 -0
  15. data/lib/ethon/curls/options.rb +298 -71
  16. data/lib/ethon/curls/settings.rb +12 -3
  17. data/lib/ethon/curls/share.rb +165 -0
  18. data/lib/ethon/curls/share_constants.rb +47 -0
  19. data/lib/ethon/curls/url.rb +171 -0
  20. data/lib/ethon/curls/url_constants.rb +78 -0
  21. data/lib/ethon/easy/callbacks.rb +177 -51
  22. data/lib/ethon/easy/features.rb +4 -1
  23. data/lib/ethon/easy/files.rb +113 -0
  24. data/lib/ethon/easy/form.rb +65 -6
  25. data/lib/ethon/easy/header.rb +57 -15
  26. data/lib/ethon/easy/headers_api.rb +79 -0
  27. data/lib/ethon/easy/http/actionable.rb +25 -3
  28. data/lib/ethon/easy/http/get.rb +5 -1
  29. data/lib/ethon/easy/http/post.rb +1 -0
  30. data/lib/ethon/easy/http/postable.rb +9 -2
  31. data/lib/ethon/easy/http/putable.rb +19 -4
  32. data/lib/ethon/easy/http.rb +16 -4
  33. data/lib/ethon/easy/informations.rb +51 -2
  34. data/lib/ethon/easy/mime.rb +29 -0
  35. data/lib/ethon/easy/mirror.rb +17 -4
  36. data/lib/ethon/easy/operations.rb +264 -11
  37. data/lib/ethon/easy/options.rb +327 -8
  38. data/lib/ethon/easy/queryable.rb +59 -14
  39. data/lib/ethon/easy/response_callbacks.rb +33 -12
  40. data/lib/ethon/easy/share.rb +28 -0
  41. data/lib/ethon/easy/url.rb +32 -0
  42. data/lib/ethon/easy/util.rb +19 -0
  43. data/lib/ethon/easy.rb +185 -10
  44. data/lib/ethon/errors/fork_safety.rb +10 -0
  45. data/lib/ethon/errors/form_add.rb +12 -0
  46. data/lib/ethon/errors/global_init.rb +1 -1
  47. data/lib/ethon/errors/header.rb +12 -0
  48. data/lib/ethon/errors/invalid_option.rb +3 -1
  49. data/lib/ethon/errors/invalid_value.rb +3 -1
  50. data/lib/ethon/errors/mime.rb +17 -0
  51. data/lib/ethon/errors/multi_add.rb +1 -1
  52. data/lib/ethon/errors/multi_assign.rb +12 -0
  53. data/lib/ethon/errors/multi_fdset.rb +1 -1
  54. data/lib/ethon/errors/multi_perform.rb +12 -0
  55. data/lib/ethon/errors/multi_poll.rb +12 -0
  56. data/lib/ethon/errors/multi_remove.rb +1 -1
  57. data/lib/ethon/errors/multi_timeout.rb +2 -2
  58. data/lib/ethon/errors/multi_wait.rb +12 -0
  59. data/lib/ethon/errors/multi_wakeup.rb +12 -0
  60. data/lib/ethon/errors/select.rb +1 -1
  61. data/lib/ethon/errors/set_option.rb +27 -0
  62. data/lib/ethon/errors/share.rb +17 -0
  63. data/lib/ethon/errors/unsupported_option.rb +23 -0
  64. data/lib/ethon/errors/url.rb +17 -0
  65. data/lib/ethon/errors.rb +13 -0
  66. data/lib/ethon/libc.rb +30 -1
  67. data/lib/ethon/loggable.rb +1 -1
  68. data/lib/ethon/multi/operations.rb +269 -25
  69. data/lib/ethon/multi/options.rb +114 -12
  70. data/lib/ethon/multi/stack.rb +181 -10
  71. data/lib/ethon/multi.rb +51 -15
  72. data/lib/ethon/version.rb +1 -1
  73. data/lib/ethon.rb +8 -9
  74. metadata +35 -6
@@ -7,7 +7,7 @@ module Ethon
7
7
  module Codes
8
8
 
9
9
  # Libcurl error codes, refer
10
- # https://github.com/bagder/curl/blob/master/include/curl/curl.h for details
10
+ # https://github.com/curl/curl/blob/master/include/curl/curl.h for details
11
11
  def easy_codes
12
12
  [
13
13
  :ok,
@@ -18,7 +18,7 @@ module Ethon
18
18
  :couldnt_resolve_proxy,
19
19
  :couldnt_resolve_host,
20
20
  :couldnt_connect,
21
- :ftp_weird_server_reply,
21
+ :ftp_weird_server_reply, # CURLE_WEIRD_SERVER_REPLY since 7.51.0
22
22
  :remote_access_denied,
23
23
  :ftp_accept_failed,
24
24
  :ftp_weird_pass_reply,
@@ -44,14 +44,14 @@ module Ethon
44
44
  :ftp_couldnt_use_rest,
45
45
  :obsolete32,
46
46
  :range_error,
47
- :http_post_error,
47
+ :http_post_error, # CURLE_OBSOLETE34 since 7.56.0
48
48
  :ssl_connect_error,
49
49
  :bad_download_resume,
50
50
  :file_couldnt_read_file,
51
51
  :ldap_cannot_bind,
52
52
  :ldap_search_failed,
53
53
  :obsolete40,
54
- :function_not_found,
54
+ :function_not_found, # CURLE_OBSOLETE41 since 7.53.0
55
55
  :aborted_by_callback,
56
56
  :bad_function_argument,
57
57
  :obsolete44,
@@ -59,7 +59,7 @@ module Ethon
59
59
  :obsolete46,
60
60
  :too_many_redirects ,
61
61
  :unknown_option,
62
- :telnet_option_syntax ,
62
+ :telnet_option_syntax, # CURLE_SETOPT_OPTION_SYNTAX since 7.78.0
63
63
  :obsolete50,
64
64
  :obsolete51,
65
65
  :got_nothing,
@@ -72,7 +72,7 @@ module Ethon
72
72
  :ssl_cipher,
73
73
  :peer_failed_verification,
74
74
  :bad_content_encoding,
75
- :ldap_invalid_url,
75
+ :ldap_invalid_url, # CURLE_OBSOLETE62 since 7.82.0
76
76
  :filesize_exceeded,
77
77
  :use_ssl_failed,
78
78
  :send_fail_rewind,
@@ -85,8 +85,8 @@ module Ethon
85
85
  :tftp_unknownid,
86
86
  :remote_file_exists,
87
87
  :tftp_nosuchuser,
88
- :conv_failed,
89
- :conv_reqd,
88
+ :conv_failed, # CURLE_OBSOLETE75 since 7.82.0
89
+ :conv_reqd, # CURLE_OBSOLETE76 since 7.82.0
90
90
  :ssl_cacert_badfile,
91
91
  :remote_file_not_found,
92
92
  :ssh,
@@ -99,12 +99,25 @@ module Ethon
99
99
  :rtsp_session_error,
100
100
  :ftp_bad_file_list,
101
101
  :chunk_failed,
102
+ :no_connection_available,
103
+ :ssl_pinnedpubkeynotmatch,
104
+ :ssl_invalidcertstatus,
105
+ :http2_stream,
106
+ :recursive_api_call,
107
+ :auth_error,
108
+ :http3,
109
+ :quic_connect_error,
110
+ :proxy,
111
+ :ssl_clientcert,
112
+ :unrecoverable_poll,
113
+ :too_large,
114
+ :ech_required,
102
115
  :last
103
116
  ]
104
117
  end
105
118
 
106
119
  # Curl-Multi socket error codes, refer
107
- # https://github.com/bagder/curl/blob/master/include/curl/multi.h for details
120
+ # https://github.com/curl/curl/blob/master/include/curl/multi.h for details
108
121
  def multi_codes
109
122
  [
110
123
  :call_multi_perform, -1,
@@ -115,6 +128,12 @@ module Ethon
115
128
  :internal_error,
116
129
  :bad_socket,
117
130
  :unknown_option,
131
+ :added_already,
132
+ :recursive_api_call,
133
+ :wakeup_failure,
134
+ :bad_function_argument,
135
+ :aborted_by_callback,
136
+ :unrecoverable_poll,
118
137
  :last
119
138
  ]
120
139
  end
@@ -4,6 +4,34 @@ module Ethon
4
4
  # :nodoc:
5
5
  VERSION_NOW = 3
6
6
 
7
+ # curl_sslbackend values used by curl_global_sslset.
8
+ SslBackend = enum(:ssl_backend, [
9
+ :none,
10
+ :openssl,
11
+ :gnutls,
12
+ :nss,
13
+ :obsolete4,
14
+ :gskit,
15
+ :polarssl,
16
+ :wolfssl,
17
+ :schannel,
18
+ :securetransport,
19
+ :axtls,
20
+ :mbedtls,
21
+ :mesalink,
22
+ :bearssl,
23
+ :rustls
24
+ ])
25
+
26
+ # CURLsslset values returned by curl_global_sslset. These are not CURLcode
27
+ # values, even though both start at zero.
28
+ SslSetCode = enum(:ssl_set_code, [
29
+ :ok,
30
+ :unknown_backend,
31
+ :too_late,
32
+ :no_backends
33
+ ])
34
+
7
35
  # Flag. Initialize SSL.
8
36
  GLOBAL_SSL = 0x01
9
37
  # Flag. Initialize win32 socket libraries.
@@ -24,18 +52,18 @@ module Ethon
24
52
  MultiOption = enum(:multi_option, multi_options(:enum).to_a.flatten)
25
53
 
26
54
  # Used by curl_debug_callback when setting CURLOPT_DEBUGFUNCTION
27
- # https://github.com/bagder/curl/blob/master/include/curl/curl.h#L378 for details
55
+ # https://github.com/curl/curl/blob/master/include/curl/curl.h#L378 for details
28
56
  DebugInfoType = enum(:debug_info_type, debug_info_types)
29
57
 
30
58
  # :nodoc:
31
59
  InfoType = enum(info_types.to_a.flatten)
32
60
 
33
61
  # Info details, refer
34
- # https://github.com/bagder/curl/blob/master/src/tool_writeout.c#L66 for details
62
+ # https://github.com/curl/curl/blob/master/src/tool_writeout.c#L66 for details
35
63
  Info = enum(:info, infos.to_a.flatten)
36
64
 
37
65
  # Form options, used by FormAdd for temporary storage, refer
38
- # https://github.com/bagder/curl/blob/master/lib/formdata.h#L51 for details
66
+ # https://github.com/curl/curl/blob/master/lib/formdata.h#L51 for details
39
67
  FormOption = enum(:form_option, form_options)
40
68
 
41
69
  # :nodoc:
@@ -59,8 +87,26 @@ module Ethon
59
87
  VERSION_NTLM_WB = (1<<15) # NTLM delegating to winbind helper
60
88
  VERSION_HTTP2 = (1<<16) # HTTP2 support built
61
89
  VERSION_GSSAPI = (1<<17) # GSS-API is supported
90
+ VERSION_KERBEROS5 = (1<<18) # Kerberos V5 auth is supported
91
+ VERSION_UNIX_SOCKETS = (1<<19) # Unix domain sockets support
92
+ VERSION_PSL = (1<<20) # Public Suffix List support
93
+ VERSION_HTTPS_PROXY = (1<<21) # HTTPS-proxy support built-in
94
+ VERSION_MULTI_SSL = (1<<22) # Multiple SSL backends available
95
+ VERSION_BROTLI = (1<<23) # Brotli features are present
96
+ VERSION_ALTSVC = (1<<24) # Alt-Svc handling built-in
97
+ VERSION_HTTP3 = (1<<25) # HTTP3 support built-in
98
+ VERSION_ZSTD = (1<<26) # zstd features are present
99
+ VERSION_UNICODE = (1<<27) # Unicode support on Windows
100
+ VERSION_HSTS = (1<<28) # HSTS is supported
101
+ VERSION_GSASL = (1<<29) # libgsasl is supported
102
+ VERSION_THREADSAFE = (1<<30) # libcurl API is thread-safe
62
103
 
63
- SOCKET_BAD = -1
104
+ # curl_socket_t is int on Unix and SOCKET (UINT_PTR) on Windows.
105
+ SOCKET_BAD = if Curl.windows?
106
+ (1 << (::FFI.type_size(:size_t) * 8)) - 1
107
+ else
108
+ -1
109
+ end
64
110
  SOCKET_TIMEOUT = SOCKET_BAD
65
111
 
66
112
  PollAction = enum(:poll_action, [
@@ -76,5 +122,16 @@ module Ethon
76
122
  :out, # CURL_CSELECT_OUT - 0x02 (bit 1)
77
123
  :err, # CURL_CSELECT_ERR - 0x04 (bit 2)
78
124
  ])
125
+
126
+ # Bitmasks for curl_easy_pause.
127
+ PAUSE_RECV = (1<<0)
128
+ PAUSE_SEND = (1<<2)
129
+ PAUSE_ALL = (PAUSE_RECV | PAUSE_SEND)
130
+ PAUSE_CONT = 0
131
+
132
+ # Event bits for curl_multi_wait/curl_multi_poll.
133
+ WAIT_POLLIN = 0x0001
134
+ WAIT_POLLPRI = 0x0002
135
+ WAIT_POLLOUT = 0x0004
79
136
  end
80
137
  end
@@ -6,7 +6,7 @@ module Ethon
6
6
  module FormOptions
7
7
 
8
8
  # Form options, used by FormAdd for temporary storage, refer
9
- # https://github.com/bagder/curl/blob/master/lib/formdata.h#L51 for details
9
+ # https://github.com/curl/curl/blob/master/lib/formdata.h#L51 for details
10
10
  def form_options
11
11
  [
12
12
  :none,
@@ -29,6 +29,7 @@ module Ethon
29
29
  :end,
30
30
  :obsolete2,
31
31
  :stream,
32
+ :contentlen,
32
33
  :last
33
34
  ]
34
35
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'ethon/curls/optional'
2
3
  module Ethon
3
4
  module Curls
4
5
 
@@ -12,8 +13,14 @@ module Ethon
12
13
  base.attach_function :global_cleanup, :curl_global_cleanup, [], :void
13
14
  base.attach_function :free, :curl_free, [:pointer], :void
14
15
 
16
+ Ethon::Curls::Optional.attach(base, :easy_header, :curl_easy_header, [:pointer, :string, :size_t, :uint, :int, :pointer], :header_code, blocking: false)
17
+ Ethon::Curls::Optional.attach(base, :easy_nextheader, :curl_easy_nextheader, [:pointer, :uint, :int, :pointer], Curl::HeaderData.ptr, blocking: false)
18
+ Ethon::Curls::Optional.attach(base, :easy_option_by_name, :curl_easy_option_by_name, [:string], Curl::EasyOptionInfo.ptr, blocking: false)
19
+ Ethon::Curls::Optional.attach(base, :easy_option_by_id, :curl_easy_option_by_id, [:easy_option], Curl::EasyOptionInfo.ptr, blocking: false)
20
+ Ethon::Curls::Optional.attach(base, :easy_option_next, :curl_easy_option_next, [:pointer], Curl::EasyOptionInfo.ptr, blocking: false)
21
+
15
22
  base.attach_function :easy_init, :curl_easy_init, [], :pointer
16
- base.attach_function :easy_cleanup, :curl_easy_cleanup, [:pointer], :void
23
+ base.attach_function :easy_cleanup, :curl_easy_cleanup, [:pointer], :void, blocking: true
17
24
  base.attach_function :easy_getinfo, :curl_easy_getinfo, [:pointer, :info, :varargs], :easy_code
18
25
  base.attach_function :easy_setopt, :curl_easy_setopt, [:pointer, :easy_option, :varargs], :easy_code
19
26
  base.instance_variable_set(:@blocking, true)
@@ -21,38 +28,80 @@ module Ethon
21
28
  base.attach_function :easy_strerror, :curl_easy_strerror, [:easy_code], :string
22
29
  base.attach_function :easy_escape, :curl_easy_escape, [:pointer, :pointer, :int], :pointer
23
30
  base.attach_function :easy_reset, :curl_easy_reset, [:pointer], :void
24
- base.attach_function :easy_duphandle, :curl_easy_duphandle, [:pointer], :pointer
31
+ Ethon::Curls::Optional.attach(base, :easy_duphandle, :curl_easy_duphandle, [:pointer], :pointer, blocking: false)
32
+
33
+ Ethon::Curls::Optional.attach(base, :url, :curl_url, [], :pointer, blocking: false)
34
+ Ethon::Curls::Optional.attach(base, :url_cleanup, :curl_url_cleanup, [:pointer], :void, blocking: false)
35
+ Ethon::Curls::Optional.attach(base, :url_dup, :curl_url_dup, [:pointer], :pointer, blocking: false)
36
+ Ethon::Curls::Optional.attach(base, :url_get, :curl_url_get, [:pointer, :url_part, :pointer, :url_flags], :url_code, blocking: false)
37
+ Ethon::Curls::Optional.attach(base, :url_set, :curl_url_set, [:pointer, :url_part, :string, :url_flags], :url_code, blocking: false)
38
+ Ethon::Curls::Optional.attach(base, :url_strerror, :curl_url_strerror, [:url_code], :string, blocking: false)
25
39
 
26
- base.attach_function :formadd, :curl_formadd, [:pointer, :pointer, :varargs], :int
27
- base.attach_function :formfree, :curl_formfree, [:pointer], :void
40
+ # The form API is deprecated and absent from builds configured with
41
+ # --disable-form-api. Attach it when present so Ethon still loads.
42
+ Ethon::Curls::Optional.attach(base, :formadd, :curl_formadd, [:pointer, :pointer, :varargs], :int)
43
+ Ethon::Curls::Optional.attach(base, :formfree, :curl_formfree, [:pointer], :void)
44
+
45
+ Ethon::Curls::Optional.attach(base, :share_init, :curl_share_init, [], :pointer, blocking: false)
46
+ Ethon::Curls::Optional.attach(base, :share_cleanup, :curl_share_cleanup, [:pointer], :share_code, blocking: false)
47
+ Ethon::Curls::Optional.attach(base, :share_setopt, :curl_share_setopt, [:pointer, :share_option, :varargs], :share_code, blocking: false)
48
+ Ethon::Curls::Optional.attach(base, :share_strerror, :curl_share_strerror, [:share_code], :string, blocking: false)
28
49
 
29
50
  base.attach_function :multi_init, :curl_multi_init, [], :pointer
30
- base.attach_function :multi_cleanup, :curl_multi_cleanup, [:pointer], :void
51
+ base.attach_function :multi_cleanup, :curl_multi_cleanup, [:pointer], :void, blocking: true
31
52
  base.attach_function :multi_add_handle, :curl_multi_add_handle, [:pointer, :pointer], :multi_code
32
- base.attach_function :multi_remove_handle, :curl_multi_remove_handle, [:pointer, :pointer], :multi_code
53
+ base.attach_function :multi_remove_handle, :curl_multi_remove_handle, [:pointer, :pointer], :multi_code, blocking: true
33
54
  base.attach_function :multi_info_read, :curl_multi_info_read, [:pointer, :pointer], Curl::Msg.ptr
34
- base.attach_function :multi_perform, :curl_multi_perform, [:pointer, :pointer], :multi_code
55
+ base.attach_function :multi_perform, :curl_multi_perform, [:pointer, :pointer], :multi_code, blocking: true
35
56
  base.attach_function :multi_timeout, :curl_multi_timeout, [:pointer, :pointer], :multi_code
36
57
  base.attach_function :multi_fdset, :curl_multi_fdset, [:pointer, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::FDSet.ptr, :pointer], :multi_code
37
- base.attach_function :multi_strerror, :curl_multi_strerror, [:int], :string
58
+ base.attach_function :multi_strerror, :curl_multi_strerror, [:multi_code], :string
38
59
  base.attach_function :multi_setopt, :curl_multi_setopt, [:pointer, :multi_option, :varargs], :multi_code
39
- base.attach_function :multi_socket_action, :curl_multi_socket_action, [:pointer, :int, :socket_readiness, :pointer], :multi_code
60
+ base.attach_function :multi_socket_action, :curl_multi_socket_action, [:pointer, :curl_socket_t, :socket_readiness, :pointer], :multi_code, blocking: true
61
+ base.attach_function :multi_assign, :curl_multi_assign, [:pointer, :curl_socket_t, :pointer], :multi_code
62
+
63
+ Ethon::Curls::Optional.attach(base, :multi_get_handles, :curl_multi_get_handles, [:pointer], :pointer, blocking: false)
64
+ Ethon::Curls::Optional.attach(base, :global_sslset, :curl_global_sslset, [:ssl_backend, :string, :pointer], :ssl_set_code, blocking: false)
65
+ Ethon::Curls::Optional.attach(base, :global_trace, :curl_global_trace, [:string], :easy_code, blocking: false)
40
66
 
41
67
  base.attach_function :version, :curl_version, [], :string
42
- base.attach_function :version_info, :curl_version_info, [], Curl::VersionInfoData.ptr
68
+ base.attach_function :curl_version_info, :curl_version_info, [:int], Curl::VersionInfoData.ptr
69
+
70
+ Ethon::Curls::Optional.attach(base, :mime_init, :curl_mime_init, [:pointer], :pointer, blocking: false)
71
+ Ethon::Curls::Optional.attach(base, :mime_free, :curl_mime_free, [:pointer], :void, blocking: false)
72
+ Ethon::Curls::Optional.attach(base, :mime_addpart, :curl_mime_addpart, [:pointer], :pointer, blocking: false)
73
+ Ethon::Curls::Optional.attach(base, :mime_name, :curl_mime_name, [:pointer, :string], :easy_code, blocking: false)
74
+ Ethon::Curls::Optional.attach(base, :mime_filename, :curl_mime_filename, [:pointer, :string], :easy_code, blocking: false)
75
+ Ethon::Curls::Optional.attach(base, :mime_type, :curl_mime_type, [:pointer, :string], :easy_code, blocking: false)
76
+ Ethon::Curls::Optional.attach(base, :mime_encoder, :curl_mime_encoder, [:pointer, :string], :easy_code, blocking: false)
77
+ Ethon::Curls::Optional.attach(base, :mime_data, :curl_mime_data, [:pointer, :pointer, :size_t], :easy_code, blocking: false)
78
+ Ethon::Curls::Optional.attach(base, :mime_filedata, :curl_mime_filedata, [:pointer, :string], :easy_code, blocking: false)
79
+ Ethon::Curls::Optional.attach(base, :mime_headers, :curl_mime_headers, [:pointer, :pointer, :int], :easy_code, blocking: false)
43
80
 
44
81
  base.attach_function :slist_append, :curl_slist_append, [:pointer, :string], :pointer
45
82
  base.attach_function :slist_free_all, :curl_slist_free_all, [:pointer], :void
46
83
  base.instance_variable_set(:@blocking, true)
47
84
 
85
+ Ethon::Curls::Optional.attach(base, :easy_unescape, :curl_easy_unescape, [:pointer, :pointer, :int, :pointer], :pointer, blocking: false)
86
+ Ethon::Curls::Optional.attach(base, :easy_pause, :curl_easy_pause, [:pointer, :int], :easy_code, blocking: false)
87
+ Ethon::Curls::Optional.attach(base, :easy_send, :curl_easy_send, [:pointer, :pointer, :size_t, :pointer], :easy_code, blocking: false)
88
+ Ethon::Curls::Optional.attach(base, :easy_recv, :curl_easy_recv, [:pointer, :pointer, :size_t, :pointer], :easy_code, blocking: false)
89
+ Ethon::Curls::Optional.attach(base, :multi_wait, :curl_multi_wait, [:pointer, :pointer, :uint, :int, :pointer], :multi_code, blocking: true)
90
+ Ethon::Curls::Optional.attach(base, :easy_upkeep, :curl_easy_upkeep, [:pointer], :easy_code, blocking: false)
91
+ Ethon::Curls::Optional.attach(base, :multi_poll, :curl_multi_poll, [:pointer, :pointer, :uint, :int, :pointer], :multi_code, blocking: true)
92
+ Ethon::Curls::Optional.attach(base, :multi_wakeup, :curl_multi_wakeup, [:pointer], :multi_code, blocking: false)
93
+
48
94
  if Curl.windows?
49
95
  base.ffi_lib 'ws2_32'
50
96
  else
51
97
  base.ffi_lib ::FFI::Library::LIBC
52
98
  end
53
99
 
54
- base.attach_function :select, [:int, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::Timeval.ptr], :int
100
+ # select() blocks for the multi timeout. Release the GVL so other Ruby
101
+ # threads can run while the select fallback is waiting.
102
+ base.attach_function :select, [:int, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::FDSet.ptr, Curl::Timeval.ptr], :int, blocking: true
55
103
  end
104
+
56
105
  end
57
106
  end
58
107
  end
@@ -17,12 +17,15 @@ module Ethon
17
17
  :string =>0x100000,
18
18
  :long => 0x200000,
19
19
  :double =>0x300000,
20
- :slist => 0x400000
20
+ :slist => 0x400000,
21
+ :ptr => 0x400000,
22
+ :socket =>0x500000,
23
+ :off_t => 0x600000
21
24
  }
22
25
  end
23
26
 
24
- # http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTDEBUGFUNCTION
25
- # https://github.com/bagder/curl/blob/master/include/curl/curl.h#L378
27
+ # https://curl.se/libcurl/c/CURLOPT_DEBUGFUNCTION.html
28
+ # https://github.com/curl/curl/blob/master/include/curl/curl.h#L378
26
29
  #
27
30
  # @example Return debug info types.
28
31
  # Ethon::Curl.debug_info_types
@@ -41,7 +44,7 @@ module Ethon
41
44
  end
42
45
 
43
46
  # Return Info details, refer
44
- # https://github.com/bagder/curl/blob/master/src/tool_writeout.c#L66 for details
47
+ # https://github.com/curl/curl/blob/master/src/tool_writeout.c#L66 for details
45
48
  #
46
49
  # @example Return infos.
47
50
  # Ethon::Curl.infos
@@ -49,6 +52,40 @@ module Ethon
49
52
  # @return [ Hash ] The infos.
50
53
  def infos
51
54
  {
55
+ :size_upload_t => info_types[:off_t] + 7,
56
+ :size_download_t => info_types[:off_t] + 8,
57
+ :speed_download_t => info_types[:off_t] + 9,
58
+ :speed_upload_t => info_types[:off_t] + 10,
59
+ :filetime_t => info_types[:off_t] + 14,
60
+ :content_length_download_t =>info_types[:off_t] + 15,
61
+ :content_length_upload_t =>info_types[:off_t] + 16,
62
+ :http_version => info_types[:long] + 46,
63
+ :proxy_ssl_verifyresult => info_types[:long] + 47,
64
+ :scheme => info_types[:string] + 49,
65
+ :total_time_t => info_types[:off_t] + 50,
66
+ :namelookup_time_t => info_types[:off_t] + 51,
67
+ :connect_time_t => info_types[:off_t] + 52,
68
+ :pretransfer_time_t => info_types[:off_t] + 53,
69
+ :starttransfer_time_t => info_types[:off_t] + 54,
70
+ :redirect_time_t => info_types[:off_t] + 55,
71
+ :appconnect_time_t => info_types[:off_t] + 56,
72
+ :retry_after => info_types[:off_t] + 57,
73
+ :effective_method => info_types[:string] + 58,
74
+ :proxy_error => info_types[:long] + 59,
75
+ :referer => info_types[:string] + 60,
76
+ :cainfo => info_types[:string] + 61,
77
+ :capath => info_types[:string] + 62,
78
+ :xfer_id => info_types[:off_t] + 63,
79
+ :conn_id => info_types[:off_t] + 64,
80
+ :queue_time_t => info_types[:off_t] + 65,
81
+ :used_proxy => info_types[:long] + 66,
82
+ :posttransfer_time_t => info_types[:off_t] + 67,
83
+ :earlydata_sent_t => info_types[:off_t] + 68,
84
+ :httpauth_used => info_types[:long] + 69,
85
+ :proxyauth_used => info_types[:long] + 70,
86
+ :size_delivered => info_types[:off_t] + 71,
87
+ :activesocket => info_types[:socket] + 44,
88
+ :tls_ssl_ptr => info_types[:ptr] + 45,
52
89
  :effective_url => info_types[:string] + 1,
53
90
  :response_code => info_types[:long] + 2,
54
91
  :total_time => info_types[:double] + 3,
@@ -91,7 +128,7 @@ module Ethon
91
128
  :primary_port => info_types[:long] + 40,
92
129
  :local_ip => info_types[:string] + 41,
93
130
  :local_port => info_types[:long] + 42,
94
- :last =>42
131
+ :last =>71
95
132
  }
96
133
  end
97
134
 
@@ -146,6 +183,68 @@ module Ethon
146
183
  double_ptr.read_double
147
184
  end
148
185
  end
186
+
187
+ # Return info as a 64-bit integer (curl_off_t).
188
+ def get_info_off_t(option, handle)
189
+ int64_ptr = ::FFI::MemoryPointer.new(:int64)
190
+
191
+ if easy_getinfo(handle, option, :pointer, int64_ptr) == :ok
192
+ int64_ptr.read_int64
193
+ end
194
+ end
195
+
196
+ # Return info as a curl_socket_t.
197
+ def get_info_socket(option, handle)
198
+ if Curl.windows?
199
+ ptr = ::FFI::MemoryPointer.new(:size_t)
200
+ return unless easy_getinfo(handle, option, :pointer, ptr) == :ok
201
+ ::FFI.type_size(:size_t) == 8 ? ptr.read_uint64 : ptr.read_uint32
202
+ else
203
+ ptr = ::FFI::MemoryPointer.new(:int)
204
+ ptr.read_int if easy_getinfo(handle, option, :pointer, ptr) == :ok
205
+ end
206
+ end
207
+
208
+ # Return info as a raw pointer.
209
+ def get_info_ptr(option, handle)
210
+ ptr = ::FFI::MemoryPointer.new(:pointer)
211
+
212
+ if easy_getinfo(handle, option, :pointer, ptr) == :ok
213
+ value = ptr.read_pointer
214
+ value unless value.null?
215
+ end
216
+ end
217
+
218
+ # Return info as array of strings.
219
+ #
220
+ # @example Return info.
221
+ # Curl.get_info_slist(:ssl_engines, easy)
222
+ #
223
+ # @param [ Symbol ] option The option name.
224
+ # @param [ ::FFI::Pointer ] handle The easy handle.
225
+ #
226
+ # @return [ Array ] The info.
227
+ def get_info_slist(option, handle)
228
+ result = ::FFI::MemoryPointer.new(:pointer)
229
+ return unless easy_getinfo(handle, option, :pointer, result) == :ok
230
+
231
+ pointer = result.read_pointer
232
+ return [] if pointer.null?
233
+
234
+ list = Curl::Slist.new(pointer)
235
+ list.to_a
236
+ ensure
237
+ list.free if list
238
+ end
239
+
240
+ # Return certificate information copied from libcurl-owned memory.
241
+ def get_info_certinfo(option, handle)
242
+ result = ::FFI::MemoryPointer.new(:pointer)
243
+ return unless easy_getinfo(handle, option, :pointer, result) == :ok
244
+
245
+ pointer = result.read_pointer
246
+ pointer.null? ? [] : Curl::Certinfo.new(pointer).to_a
247
+ end
149
248
  end
150
249
  end
151
250
  end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+ module Ethon
3
+ module Curl
4
+ class << self
5
+ # Look up a libcurl option by name.
6
+ #
7
+ # @param [ String, Symbol ] name The CURLOPT name.
8
+ #
9
+ # @return [ EasyOptionInfo, nil ] The option metadata.
10
+ def easy_option(name)
11
+ return unless respond_to?(:easy_option_by_name)
12
+
13
+ info = easy_option_by_name(name.to_s)
14
+ info.null? ? nil : info
15
+ end
16
+
17
+ # Look up a libcurl option by numeric id.
18
+ #
19
+ # @param [ Integer, Symbol ] id The CURLoption id or name.
20
+ #
21
+ # @return [ EasyOptionInfo, nil ] The option metadata.
22
+ def option_by_id(id)
23
+ return unless respond_to?(:easy_option_by_id)
24
+
25
+ info = easy_option_by_id(id)
26
+ info.null? ? nil : info
27
+ end
28
+
29
+ # Enumerate all libcurl option metadata.
30
+ #
31
+ # @return [ Enumerator, Array ] The metadata.
32
+ def each_easy_option
33
+ unless respond_to?(:easy_option_next)
34
+ raise NotImplementedError, "curl_easy_option_next is not supported by this libcurl"
35
+ end
36
+ return enum_for(:each_easy_option) unless block_given?
37
+
38
+ info = easy_option_next(::FFI::Pointer::NULL)
39
+ until info.null?
40
+ yield info
41
+ info = easy_option_next(info)
42
+ end
43
+ self
44
+ end
45
+
46
+ def easy_option_list
47
+ each_easy_option.to_a
48
+ end
49
+
50
+ # The full version info struct when the runtime supports it.
51
+ #
52
+ # @return [ VersionInfoDataFull, nil ] The full version metadata.
53
+ def version_info_full
54
+ info = version_info
55
+ return unless info && info[:curl_version] >= CURLVERSION_ELEVENTH
56
+
57
+ VersionInfoDataFull.new(info.to_ptr)
58
+ end
59
+
60
+ # Protocols supported by the loaded libcurl.
61
+ #
62
+ # @return [ Array<String> ] Protocol names.
63
+ def protocols
64
+ string_array(version_info[:protocols])
65
+ end
66
+
67
+ # Return whether libcurl knows an option.
68
+ #
69
+ # Easy options are checked against libcurl's own option table on 7.73.0
70
+ # and newer. Older versions cannot answer that question, so it returns
71
+ # `true` for every option Ethon knows. Multi options are only checked
72
+ # against Ethon's table, because libcurl does not expose multi option
73
+ # metadata.
74
+ #
75
+ # @param [ String, Symbol ] name The CURLOPT_* name without the prefix.
76
+ # @param [ Symbol ] type :easy or :multi.
77
+ #
78
+ # @return [ Boolean ] True when the option is available.
79
+ def supports_option?(name, type = :easy)
80
+ name = name.to_sym if name.is_a?(String)
81
+ options = type == :multi ? multi_options(nil) : easy_options(nil)
82
+ option = options[name]
83
+ return false unless option
84
+
85
+ if type == :easy && respond_to?(:easy_option_by_id)
86
+ # Resolve by id so Ethon aliases such as :unix_socket are reported
87
+ # correctly; libcurl's by-name lookup only knows canonical names.
88
+ !easy_option_by_id(option[:opt]).null?
89
+ else
90
+ true
91
+ end
92
+ end
93
+
94
+ # Check the loaded libcurl version against a dotted version number.
95
+ #
96
+ # @example
97
+ # Ethon::Curl.version_at_least?("7.66.0")
98
+ #
99
+ # @param [ String ] version The version to compare, for example "7.66.0".
100
+ #
101
+ # @return [ Boolean ] True when libcurl is at least that version.
102
+ def version_at_least?(version)
103
+ parts = version.to_s.split(".").map(&:to_i)
104
+ required = (parts[0].to_i << 16) | (parts[1].to_i << 8) | parts[2].to_i
105
+ version_info[:version_num] >= required
106
+ end
107
+
108
+ # Feature names supported by the loaded libcurl.
109
+ #
110
+ # @return [ Array<String> ] Feature names.
111
+ def feature_names
112
+ info = version_info_full
113
+ return [] unless info
114
+
115
+ string_array(info[:feature_names])
116
+ end
117
+
118
+ # Read a NULL-terminated array of C strings.
119
+ #
120
+ # @param [ FFI::Pointer, nil ] pointer The array pointer.
121
+ #
122
+ # @return [ Array<String> ] The strings.
123
+ def string_array(pointer)
124
+ return [] if pointer.nil? || pointer.null?
125
+
126
+ strings = []
127
+ offset = 0
128
+ loop do
129
+ string = pointer.get_pointer(offset)
130
+ break if string.null?
131
+
132
+ strings << string.read_string
133
+ offset += ::FFI.type_size(:pointer)
134
+ end
135
+ strings
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ module Ethon
3
+ module Curl
4
+ # The struct age at which curl_version_info_data exposes feature_names.
5
+ CURLVERSION_ELEVENTH = 10
6
+
7
+ # CURLHcode values.
8
+ HeaderCode = enum(:header_code, [
9
+ :ok,
10
+ :badindex,
11
+ :missing,
12
+ :noheaders,
13
+ :norequest,
14
+ :out_of_memory,
15
+ :bad_argument,
16
+ :not_built_in
17
+ ])
18
+
19
+ # CURLH_* origin bits.
20
+ HeaderOrigin = bitmask(:header_origin, [
21
+ nil,
22
+ :header,
23
+ :trailer,
24
+ :connect,
25
+ :"1xx",
26
+ :pseudo
27
+ ])
28
+
29
+ # curl_easytype values used by curl_easy_option_*.
30
+ EasyType = enum(:easy_type, [
31
+ :long,
32
+ :values,
33
+ :off_t,
34
+ :object,
35
+ :string,
36
+ :slist,
37
+ :cbptr,
38
+ :blob,
39
+ :function
40
+ ])
41
+ end
42
+ end