ethon-impersonate 0.17.4-x86_64-linux

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +379 -0
  3. data/LICENSE +20 -0
  4. data/README.md +116 -0
  5. data/config/puma.rb +1 -0
  6. data/ethon-impersonate.gemspec +28 -0
  7. data/ext/libcurl-impersonate.4.dylib +0 -0
  8. data/lib/ethon/impersonate.rb +9 -0
  9. data/lib/ethon-impersonate.rb +1 -0
  10. data/lib/ethon_impersonate/curl.rb +90 -0
  11. data/lib/ethon_impersonate/curls/classes.rb +65 -0
  12. data/lib/ethon_impersonate/curls/codes.rb +122 -0
  13. data/lib/ethon_impersonate/curls/constants.rb +81 -0
  14. data/lib/ethon_impersonate/curls/form_options.rb +37 -0
  15. data/lib/ethon_impersonate/curls/functions.rb +59 -0
  16. data/lib/ethon_impersonate/curls/infos.rb +151 -0
  17. data/lib/ethon_impersonate/curls/messages.rb +19 -0
  18. data/lib/ethon_impersonate/curls/options.rb +503 -0
  19. data/lib/ethon_impersonate/curls/settings.rb +13 -0
  20. data/lib/ethon_impersonate/easy/callbacks.rb +149 -0
  21. data/lib/ethon_impersonate/easy/debug_info.rb +49 -0
  22. data/lib/ethon_impersonate/easy/features.rb +31 -0
  23. data/lib/ethon_impersonate/easy/form.rb +107 -0
  24. data/lib/ethon_impersonate/easy/header.rb +61 -0
  25. data/lib/ethon_impersonate/easy/http/actionable.rb +157 -0
  26. data/lib/ethon_impersonate/easy/http/custom.rb +29 -0
  27. data/lib/ethon_impersonate/easy/http/delete.rb +25 -0
  28. data/lib/ethon_impersonate/easy/http/get.rb +24 -0
  29. data/lib/ethon_impersonate/easy/http/head.rb +24 -0
  30. data/lib/ethon_impersonate/easy/http/options.rb +24 -0
  31. data/lib/ethon_impersonate/easy/http/patch.rb +24 -0
  32. data/lib/ethon_impersonate/easy/http/post.rb +26 -0
  33. data/lib/ethon_impersonate/easy/http/postable.rb +32 -0
  34. data/lib/ethon_impersonate/easy/http/put.rb +27 -0
  35. data/lib/ethon_impersonate/easy/http/putable.rb +25 -0
  36. data/lib/ethon_impersonate/easy/http.rb +68 -0
  37. data/lib/ethon_impersonate/easy/informations.rb +118 -0
  38. data/lib/ethon_impersonate/easy/mirror.rb +38 -0
  39. data/lib/ethon_impersonate/easy/operations.rb +64 -0
  40. data/lib/ethon_impersonate/easy/options.rb +50 -0
  41. data/lib/ethon_impersonate/easy/params.rb +29 -0
  42. data/lib/ethon_impersonate/easy/queryable.rb +154 -0
  43. data/lib/ethon_impersonate/easy/response_callbacks.rb +136 -0
  44. data/lib/ethon_impersonate/easy/util.rb +28 -0
  45. data/lib/ethon_impersonate/easy.rb +332 -0
  46. data/lib/ethon_impersonate/errors/ethon_error.rb +9 -0
  47. data/lib/ethon_impersonate/errors/global_init.rb +13 -0
  48. data/lib/ethon_impersonate/errors/invalid_option.rb +13 -0
  49. data/lib/ethon_impersonate/errors/invalid_value.rb +13 -0
  50. data/lib/ethon_impersonate/errors/multi_add.rb +12 -0
  51. data/lib/ethon_impersonate/errors/multi_fdset.rb +12 -0
  52. data/lib/ethon_impersonate/errors/multi_remove.rb +12 -0
  53. data/lib/ethon_impersonate/errors/multi_timeout.rb +13 -0
  54. data/lib/ethon_impersonate/errors/select.rb +13 -0
  55. data/lib/ethon_impersonate/errors.rb +17 -0
  56. data/lib/ethon_impersonate/impersonate/fingerprints.rb +16 -0
  57. data/lib/ethon_impersonate/impersonate/settings.rb +89 -0
  58. data/lib/ethon_impersonate/impersonate/targets.rb +89 -0
  59. data/lib/ethon_impersonate/impersonate/tls.rb +189 -0
  60. data/lib/ethon_impersonate/impersonate.rb +10 -0
  61. data/lib/ethon_impersonate/libc.rb +21 -0
  62. data/lib/ethon_impersonate/loggable.rb +59 -0
  63. data/lib/ethon_impersonate/multi/operations.rb +228 -0
  64. data/lib/ethon_impersonate/multi/options.rb +117 -0
  65. data/lib/ethon_impersonate/multi/stack.rb +49 -0
  66. data/lib/ethon_impersonate/multi.rb +126 -0
  67. data/lib/ethon_impersonate/version.rb +6 -0
  68. data/lib/ethon_impersonate.rb +28 -0
  69. metadata +123 -0
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+ module EthonImpersonate
3
+ module Impersonate
4
+ module Tls
5
+ # TLS version are in the format of 0xAABB, where AA is major version and BB is minor
6
+ # version. As of today, the major version is always 03.
7
+ VERSION_MAP = {
8
+ 0x0301 => :tlsv1_0, # TLS 1.0
9
+ 0x0302 => :tlsv1_1, # TLS 1.1
10
+ 0x0303 => :tlsv1_2, # TLS 1.2
11
+ 0x0304 => :tlsv1_3, # TLS 1.3
12
+ }.freeze
13
+
14
+ MAX_DEFAULT_VERSION = :tlsv1_3
15
+
16
+ # A list of the possible cipher suite ids. Taken from
17
+ # http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
18
+ # via BoringSSL
19
+ CIPHER_NAME_MAP = {
20
+ 0x000A => "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
21
+ 0x002F => "TLS_RSA_WITH_AES_128_CBC_SHA",
22
+ 0x0035 => "TLS_RSA_WITH_AES_256_CBC_SHA",
23
+ 0x003C => "TLS_RSA_WITH_AES_128_CBC_SHA256",
24
+ 0x003D => "TLS_RSA_WITH_AES_256_CBC_SHA256",
25
+ 0x008C => "TLS_PSK_WITH_AES_128_CBC_SHA",
26
+ 0x008D => "TLS_PSK_WITH_AES_256_CBC_SHA",
27
+ 0x009C => "TLS_RSA_WITH_AES_128_GCM_SHA256",
28
+ 0x009D => "TLS_RSA_WITH_AES_256_GCM_SHA384",
29
+ 0xC008 => "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
30
+ 0xC009 => "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
31
+ 0xC00A => "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
32
+ 0xC012 => "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
33
+ 0xC013 => "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
34
+ 0xC014 => "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
35
+ 0xC023 => "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
36
+ 0xC024 => "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
37
+ 0xC027 => "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
38
+ 0xC028 => "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
39
+ 0xC02B => "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
40
+ 0xC02C => "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
41
+ 0xC02F => "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
42
+ 0xC030 => "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
43
+ 0xC035 => "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
44
+ 0xC036 => "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
45
+ 0xCCA8 => "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
46
+ 0xCCA9 => "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
47
+ 0xCCAC => "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
48
+ 0x1301 => "TLS_AES_128_GCM_SHA256",
49
+ 0x1302 => "TLS_AES_256_GCM_SHA384",
50
+ 0x1303 => "TLS_CHACHA20_POLY1305_SHA256",
51
+ }.freeze
52
+
53
+ # RFC tls extensions: https://datatracker.ietf.org/doc/html/rfc6066
54
+ # IANA list: https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
55
+ EXTENSION_NAME_MAP = {
56
+ 0 => "server_name", # default enabled
57
+ 1 => "max_fragment_length",
58
+ 2 => "client_certificate_url",
59
+ 3 => "trusted_ca_keys",
60
+ 4 => "truncated_hmac",
61
+ 5 => "status_request", # default enabled
62
+ 6 => "user_mapping",
63
+ 7 => "client_authz",
64
+ 8 => "server_authz",
65
+ 9 => "cert_type",
66
+ 10 => "supported_groups", # default enabled (renamed from "elliptic_curves")
67
+ 11 => "ec_point_formats", # default enabled
68
+ 12 => "srp",
69
+ 13 => "signature_algorithms", # default enabled
70
+ 14 => "use_srtp",
71
+ 15 => "heartbeat",
72
+ 16 => "application_layer_protocol_negotiation", # default enabled
73
+ 17 => "status_request_v2",
74
+ 18 => "signed_certificate_timestamp",
75
+ 19 => "client_certificate_type",
76
+ 20 => "server_certificate_type",
77
+ 21 => "padding",
78
+ 22 => "encrypt_then_mac",
79
+ 23 => "extended_master_secret",
80
+ 24 => "token_binding",
81
+ 25 => "cached_info",
82
+ 26 => "tls_lts",
83
+ 27 => "compress_certificate",
84
+ 28 => "record_size_limit",
85
+ 29 => "pwd_protect",
86
+ 30 => "pwd_clear",
87
+ 31 => "password_salt",
88
+ 32 => "ticket_pinning",
89
+ 33 => "tls_cert_with_extern_psk",
90
+ 34 => "delegated_credential",
91
+ 35 => "session_ticket", # default enabled (renamed from "SessionTicket TLS")
92
+ 36 => "TLMSP",
93
+ 37 => "TLMSP_proxying",
94
+ 38 => "TLMSP_delegate",
95
+ 39 => "supported_ekt_ciphers",
96
+ # 40 => "Reserved",
97
+ 41 => "pre_shared_key",
98
+ 42 => "early_data",
99
+ 43 => "supported_versions", # default enabled
100
+ 44 => "cookie",
101
+ 45 => "psk_key_exchange_modes", # default enabled
102
+ # 46 => "Reserved",
103
+ 47 => "certificate_authorities",
104
+ 48 => "oid_filters",
105
+ 49 => "post_handshake_auth",
106
+ 50 => "signature_algorithms_cert",
107
+ 51 => "key_share", # default enabled
108
+ 52 => "transparency_info",
109
+ # 53 => "connection_id", # (deprecated)
110
+ 54 => "connection_id",
111
+ 55 => "external_id_hash",
112
+ 56 => "external_session_id",
113
+ 57 => "quic_transport_parameters",
114
+ 58 => "ticket_request",
115
+ 59 => "dnssec_chain",
116
+ 60 => "sequence_number_encryption_algorithms",
117
+ 61 => "rrc",
118
+ 17513 => "application_settings", # BoringSSL private usage
119
+ # 62-2569 => "Unassigned",
120
+ # 2570 => "Reserved",
121
+ # 2571-6681 => "Unassigned",
122
+ # 6682 => "Reserved",
123
+ # 6683-10793 => "Unassigned",
124
+ # 10794 => "Reserved",
125
+ # 10795-14905 => "Unassigned",
126
+ # 14906 => "Reserved",
127
+ # 14907-19017 => "Unassigned",
128
+ # 19018 => "Reserved",
129
+ # 19019-23129 => "Unassigned",
130
+ # 23130 => "Reserved",
131
+ # 23131-27241 => "Unassigned",
132
+ # 27242 => "Reserved",
133
+ # 27243-31353 => "Unassigned",
134
+ # 31354 => "Reserved",
135
+ # 31355-35465 => "Unassigned",
136
+ # 35466 => "Reserved",
137
+ # 35467-39577 => "Unassigned",
138
+ # 39578 => "Reserved",
139
+ # 39579-43689 => "Unassigned",
140
+ # 43690 => "Reserved",
141
+ # 43691-47801 => "Unassigned",
142
+ # 47802 => "Reserved",
143
+ # 47803-51913 => "Unassigned",
144
+ # 51914 => "Reserved",
145
+ # 51915-56025 => "Unassigned",
146
+ # 56026 => "Reserved",
147
+ # 56027-60137 => "Unassigned",
148
+ # 60138 => "Reserved",
149
+ # 60139-64249 => "Unassigned",
150
+ # 64250 => "Reserved",
151
+ # 64251-64767 => "Unassigned",
152
+ 64768 => "ech_outer_extensions",
153
+ # 64769-65036 => "Unassigned",
154
+ 65037 => "encrypted_client_hello",
155
+ # 65038-65279 => "Unassigned",
156
+ # 65280 => "Reserved for Private Use",
157
+ 65281 => "renegotiation_info", # default enabled
158
+ # 65282-65535 => "Reserved for Private Use",
159
+ }.freeze
160
+
161
+ EXTENSION_ID_MAP = EXTENSION_NAME_MAP.map { |k, v| [v.downcase.to_sym, k] }.to_h
162
+
163
+ EXTENSIONS_DEFAULT_ENABLED = [
164
+ :server_name, # 0
165
+ :status_request, # 5
166
+ :signature_algorithms, # 13
167
+ :supported_groups, # 10
168
+ :renegotiation_info, # 65281
169
+ :session_ticket, # 35
170
+ :supported_versions, # 43
171
+ :psk_key_exchange_modes, # 45
172
+ :key_share, # 51
173
+ :ec_point_formats, # 11
174
+ :application_layer_protocol_negotiation, # 16
175
+ ].freeze
176
+
177
+ EC_CURVES_MAP = {
178
+ 19 => "P-192",
179
+ 21 => "P-224",
180
+ 23 => "P-256",
181
+ 24 => "P-384",
182
+ 25 => "P-521",
183
+ 29 => "X25519",
184
+ 4588 => "X25519MLKEM768",
185
+ 25497 => "X25519Kyber768Draft00",
186
+ }.freeze
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ require 'ethon_impersonate/impersonate/settings'
3
+ require 'ethon_impersonate/impersonate/fingerprints'
4
+ require 'ethon_impersonate/impersonate/targets'
5
+ require 'ethon_impersonate/impersonate/tls'
6
+
7
+ module EthonImpersonate
8
+ module Impersonate
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module EthonImpersonate
3
+
4
+ # FFI Wrapper module for Libc.
5
+ #
6
+ # @api private
7
+ module Libc
8
+ extend FFI::Library
9
+ ffi_lib 'c'
10
+
11
+ # :nodoc:
12
+ def self.windows?
13
+ Gem.win_platform?
14
+ end
15
+
16
+ unless windows?
17
+ attach_function :getdtablesize, [], :int
18
+ attach_function :free, [:pointer], :void
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ module EthonImpersonate
4
+
5
+ # Contains logging behaviour.
6
+ module Loggable
7
+
8
+ # Get the logger.
9
+ #
10
+ # @note Will try to grab Rails' logger first before creating a new logger
11
+ # with stdout.
12
+ #
13
+ # @example Get the logger.
14
+ # Loggable.logger
15
+ #
16
+ # @return [ Logger ] The logger.
17
+ def logger
18
+ return @logger if defined?(@logger)
19
+ @logger = rails_logger || default_logger
20
+ end
21
+
22
+ # Set the logger.
23
+ #
24
+ # @example Set the logger.
25
+ # Loggable.logger = Logger.new($stdout)
26
+ #
27
+ # @param [ Logger ] logger The logger to set.
28
+ #
29
+ # @return [ Logger ] The new logger.
30
+ def logger=(logger)
31
+ @logger = logger
32
+ end
33
+
34
+ private
35
+
36
+ # Gets the default EthonImpersonate logger - stdout.
37
+ #
38
+ # @example Get the default logger.
39
+ # Loggable.default_logger
40
+ #
41
+ # @return [ Logger ] The default logger.
42
+ def default_logger
43
+ logger = Logger.new($stdout)
44
+ logger.level = Logger::INFO
45
+ logger
46
+ end
47
+
48
+ # Get the Rails logger if it's defined.
49
+ #
50
+ # @example Get Rails' logger.
51
+ # Loggable.rails_logger
52
+ #
53
+ # @return [ Logger ] The Rails logger.
54
+ def rails_logger
55
+ defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
56
+ end
57
+ end
58
+ extend Loggable
59
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+ module EthonImpersonate
3
+ class Multi # :nodoc
4
+ # This module contains logic to run a multi.
5
+ module Operations
6
+ STARTED_MULTI = "ETHON: started MULTI"
7
+ PERFORMED_MULTI = "ETHON: performed MULTI"
8
+
9
+ # Return the multi handle. Inititialize multi handle,
10
+ # in case it didn't happened already.
11
+ #
12
+ # @example Return multi handle.
13
+ # multi.handle
14
+ #
15
+ # @return [ FFI::Pointer ] The multi handle.
16
+ def handle
17
+ @handle ||= FFI::AutoPointer.new(Curl.multi_init, Curl.method(:multi_cleanup))
18
+ end
19
+
20
+ # Initialize variables.
21
+ #
22
+ # @example Initialize variables.
23
+ # multi.init_vars
24
+ #
25
+ # @return [ void ]
26
+ def init_vars
27
+ if @execution_mode == :perform
28
+ @timeout = ::FFI::MemoryPointer.new(:long)
29
+ @timeval = Curl::Timeval.new
30
+ @fd_read = Curl::FDSet.new
31
+ @fd_write = Curl::FDSet.new
32
+ @fd_excep = Curl::FDSet.new
33
+ @max_fd = ::FFI::MemoryPointer.new(:int)
34
+ elsif @execution_mode == :socket_action
35
+ @running_count_pointer = FFI::MemoryPointer.new(:int)
36
+ end
37
+ end
38
+
39
+ # Perform multi.
40
+ #
41
+ # @return [ nil ]
42
+ #
43
+ # @example Perform multi.
44
+ # multi.perform
45
+ def perform
46
+ ensure_execution_mode(:perform)
47
+
48
+ EthonImpersonate.logger.debug(STARTED_MULTI)
49
+ while ongoing?
50
+ run
51
+ timeout = get_timeout
52
+ next if timeout == 0
53
+ reset_fds
54
+ set_fds(timeout)
55
+ end
56
+ EthonImpersonate.logger.debug(PERFORMED_MULTI)
57
+ nil
58
+ end
59
+
60
+ # Prepare multi.
61
+ #
62
+ # @return [ nil ]
63
+ #
64
+ # @example Prepare multi.
65
+ # multi.prepare
66
+ #
67
+ # @deprecated It is no longer necessary to call prepare.
68
+ def prepare
69
+ EthonImpersonate.logger.warn(
70
+ "ETHON: It is no longer necessay to call "+
71
+ "Multi#prepare. Its going to be removed "+
72
+ "in future versions."
73
+ )
74
+ end
75
+
76
+ # Continue execution with an external IO loop.
77
+ #
78
+ # @example When no sockets are ready yet, or to begin.
79
+ # multi.socket_action
80
+ #
81
+ # @example When a socket is readable
82
+ # multi.socket_action(io_object, [:in])
83
+ #
84
+ # @example When a socket is readable and writable
85
+ # multi.socket_action(io_object, [:in, :out])
86
+ #
87
+ # @return [ Symbol ] The Curl.multi_socket_action return code.
88
+ def socket_action(io = nil, readiness = 0)
89
+ ensure_execution_mode(:socket_action)
90
+
91
+ fd = if io.nil?
92
+ ::EthonImpersonate::Curl::SOCKET_TIMEOUT
93
+ elsif io.is_a?(Integer)
94
+ io
95
+ else
96
+ io.fileno
97
+ end
98
+
99
+ code = Curl.multi_socket_action(handle, fd, readiness, @running_count_pointer)
100
+ @running_count = @running_count_pointer.read_int
101
+
102
+ check
103
+
104
+ code
105
+ end
106
+
107
+ # Return whether the multi still contains requests or not.
108
+ #
109
+ # @example Return if ongoing.
110
+ # multi.ongoing?
111
+ #
112
+ # @return [ Boolean ] True if ongoing, else false.
113
+ def ongoing?
114
+ easy_handles.size > 0 || (!defined?(@running_count) || running_count > 0)
115
+ end
116
+
117
+ private
118
+
119
+ # Get timeout.
120
+ #
121
+ # @example Get timeout.
122
+ # multi.get_timeout
123
+ #
124
+ # @return [ Integer ] The timeout.
125
+ #
126
+ # @raise [ EthonImpersonate::Errors::MultiTimeout ] If getting the timeout fails.
127
+ def get_timeout
128
+ code = Curl.multi_timeout(handle, @timeout)
129
+ raise Errors::MultiTimeout.new(code) unless code == :ok
130
+ timeout = @timeout.read_long
131
+ timeout = 1 if timeout < 0
132
+ timeout
133
+ end
134
+
135
+ # Reset file describtors.
136
+ #
137
+ # @example Reset fds.
138
+ # multi.reset_fds
139
+ #
140
+ # @return [ void ]
141
+ def reset_fds
142
+ @fd_read.clear
143
+ @fd_write.clear
144
+ @fd_excep.clear
145
+ end
146
+
147
+ # Set fds.
148
+ #
149
+ # @example Set fds.
150
+ # multi.set_fds
151
+ #
152
+ # @return [ void ]
153
+ #
154
+ # @raise [ EthonImpersonate::Errors::MultiFdset ] If setting the file descriptors fails.
155
+ # @raise [ EthonImpersonate::Errors::Select ] If select fails.
156
+ def set_fds(timeout)
157
+ code = Curl.multi_fdset(handle, @fd_read, @fd_write, @fd_excep, @max_fd)
158
+ raise Errors::MultiFdset.new(code) unless code == :ok
159
+ max_fd = @max_fd.read_int
160
+ if max_fd == -1
161
+ sleep(0.001)
162
+ else
163
+ @timeval[:sec] = timeout / 1000
164
+ @timeval[:usec] = (timeout * 1000) % 1000000
165
+ loop do
166
+ code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval)
167
+ break unless code < 0 && ::FFI.errno == Errno::EINTR::Errno
168
+ end
169
+ raise Errors::Select.new(::FFI.errno) if code < 0
170
+ end
171
+ end
172
+
173
+ # Check.
174
+ #
175
+ # @example Check.
176
+ # multi.check
177
+ #
178
+ # @return [ void ]
179
+ def check
180
+ msgs_left = ::FFI::MemoryPointer.new(:int)
181
+ while true
182
+ msg = Curl.multi_info_read(handle, msgs_left)
183
+ break if msg.null?
184
+ next if msg[:code] != :done
185
+ easy = easy_handles.find{ |e| e.handle == msg[:easy_handle] }
186
+ easy.return_code = msg[:data][:code]
187
+ EthonImpersonate.logger.debug { "ETHON: performed #{easy.log_inspect}" }
188
+ delete(easy)
189
+ easy.complete
190
+ end
191
+ end
192
+
193
+ # Run.
194
+ #
195
+ # @example Run
196
+ # multi.run
197
+ #
198
+ # @return [ void ]
199
+ def run
200
+ running_count_pointer = FFI::MemoryPointer.new(:int)
201
+ begin code = trigger(running_count_pointer) end while code == :call_multi_perform
202
+ check
203
+ end
204
+
205
+ # Trigger.
206
+ #
207
+ # @example Trigger.
208
+ # multi.trigger
209
+ #
210
+ # @return [ Symbol ] The Curl.multi_perform return code.
211
+ def trigger(running_count_pointer)
212
+ code = Curl.multi_perform(handle, running_count_pointer)
213
+ @running_count = running_count_pointer.read_int
214
+ code
215
+ end
216
+
217
+ # Return number of running requests.
218
+ #
219
+ # @example Return count.
220
+ # multi.running_count
221
+ #
222
+ # @return [ Integer ] Number running requests.
223
+ def running_count
224
+ @running_count ||= nil
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+ module EthonImpersonate
3
+ class Multi
4
+
5
+ # This module contains the logic and knowledge about the
6
+ # available options on multi.
7
+ module Options
8
+
9
+ # Sets max_total_connections option.
10
+ #
11
+ # @example Set max_total_connections option.
12
+ # multi.max_total_conections = $value
13
+ #
14
+ # @param [ String ] value The value to set.
15
+ #
16
+ # @return [ void ]
17
+ def max_total_connections=(value)
18
+ Curl.set_option(:max_total_connections, value_for(value, :int), handle, :multi)
19
+ end
20
+
21
+ # Sets maxconnects option.
22
+ #
23
+ # @example Set maxconnects option.
24
+ # multi.maxconnects = $value
25
+ #
26
+ # @param [ String ] value The value to set.
27
+ #
28
+ # @return [ void ]
29
+ def maxconnects=(value)
30
+ Curl.set_option(:maxconnects, value_for(value, :int), handle, :multi)
31
+ end
32
+
33
+ # Sets pipelining option.
34
+ #
35
+ # @example Set pipelining option.
36
+ # multi.pipelining = $value
37
+ #
38
+ # @param [ String ] value The value to set.
39
+ #
40
+ # @return [ void ]
41
+ def pipelining=(value)
42
+ Curl.set_option(:pipelining, value_for(value, :int), handle, :multi)
43
+ end
44
+
45
+ # Sets socketdata option.
46
+ #
47
+ # @example Set socketdata option.
48
+ # multi.socketdata = $value
49
+ #
50
+ # @param [ String ] value The value to set.
51
+ #
52
+ # @return [ void ]
53
+ def socketdata=(value)
54
+ Curl.set_option(:socketdata, value_for(value, :string), handle, :multi)
55
+ end
56
+
57
+ # Sets socketfunction option.
58
+ #
59
+ # @example Set socketfunction option.
60
+ # multi.socketfunction = $value
61
+ #
62
+ # @param [ String ] value The value to set.
63
+ #
64
+ # @return [ void ]
65
+ def socketfunction=(value)
66
+ Curl.set_option(:socketfunction, value_for(value, :string), handle, :multi)
67
+ end
68
+
69
+ # Sets timerdata option.
70
+ #
71
+ # @example Set timerdata option.
72
+ # multi.timerdata = $value
73
+ #
74
+ # @param [ String ] value The value to set.
75
+ #
76
+ # @return [ void ]
77
+ def timerdata=(value)
78
+ Curl.set_option(:timerdata, value_for(value, :string), handle, :multi)
79
+ end
80
+
81
+ # Sets timerfunction option.
82
+ #
83
+ # @example Set timerfunction option.
84
+ # multi.timerfunction = $value
85
+ #
86
+ # @param [ String ] value The value to set.
87
+ #
88
+ # @return [ void ]
89
+ def timerfunction=(value)
90
+ Curl.set_option(:timerfunction, value_for(value, :string), handle, :multi)
91
+ end
92
+
93
+ private
94
+
95
+ # Return the value to set to multi handle. It is converted with the help
96
+ # of bool_options, enum_options and int_options.
97
+ #
98
+ # @example Return casted the value.
99
+ # multi.value_for(:verbose)
100
+ #
101
+ # @return [ Object ] The casted value.
102
+ def value_for(value, type, option = nil)
103
+ return nil if value.nil?
104
+
105
+ if type == :bool
106
+ value ? 1 : 0
107
+ elsif type == :int
108
+ value.to_i
109
+ elsif value.is_a?(String)
110
+ EthonImpersonate::Easy::Util.escape_zero_byte(value)
111
+ else
112
+ value
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ module EthonImpersonate
3
+ class Multi
4
+
5
+ # This module provides the multi stack behaviour.
6
+ module Stack
7
+
8
+ # Return easy handles.
9
+ #
10
+ # @example Return easy handles.
11
+ # multi.easy_handles
12
+ #
13
+ # @return [ Array ] The easy handles.
14
+ def easy_handles
15
+ @easy_handles ||= []
16
+ end
17
+
18
+ # Add an easy to the stack.
19
+ #
20
+ # @example Add easy.
21
+ # multi.add(easy)
22
+ #
23
+ # @param [ Easy ] easy The easy to add.
24
+ #
25
+ # @raise [ EthonImpersonate::Errors::MultiAdd ] If adding an easy failed.
26
+ def add(easy)
27
+ return nil if easy_handles.include?(easy)
28
+
29
+ code = Curl.multi_add_handle(handle, easy.handle)
30
+ raise Errors::MultiAdd.new(code, easy) unless code == :ok
31
+ easy_handles << easy
32
+ end
33
+
34
+ # Delete an easy from stack.
35
+ #
36
+ # @example Delete easy from stack.
37
+ #
38
+ # @param [ Easy ] easy The easy to delete.
39
+ #
40
+ # @raise [ EthonImpersonate::Errors::MultiRemove ] If removing an easy failed.
41
+ def delete(easy)
42
+ if easy_handles.delete(easy)
43
+ code = Curl.multi_remove_handle(handle, easy.handle)
44
+ raise Errors::MultiRemove.new(code, handle) unless code == :ok
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end