diff options
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em')
39 files changed, 7871 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb new file mode 100644 index 0000000..caf4f77 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/buftok.rb @@ -0,0 +1,59 @@ +# BufferedTokenizer takes a delimiter upon instantiation, or acts line-based +# by default. It allows input to be spoon-fed from some outside source which +# receives arbitrary length datagrams which may-or-may-not contain the token +# by which entities are delimited. In this respect it's ideally paired with +# something like EventMachine (http://rubyeventmachine.com/). +class BufferedTokenizer + # New BufferedTokenizers will operate on lines delimited by a delimiter, + # which is by default the global input delimiter $/ ("\n"). + # + # The input buffer is stored as an array. This is by far the most efficient + # approach given language constraints (in C a linked list would be a more + # appropriate data structure). Segments of input data are stored in a list + # which is only joined when a token is reached, substantially reducing the + # number of objects required for the operation. + def initialize(delimiter = $/) + @delimiter = delimiter + @input = [] + @tail = '' + @trim = @delimiter.length - 1 + end + + # Extract takes an arbitrary string of input data and returns an array of + # tokenized entities, provided there were any available to extract. This + # makes for easy processing of datagrams using a pattern like: + # + # tokenizer.extract(data).map { |entity| Decode(entity) }.each do ... + # + # Using -1 makes split to return "" if the token is at the end of + # the string, meaning the last element is the start of the next chunk. + def extract(data) + if @trim > 0 + tail_end = @tail.slice!(-@trim, @trim) # returns nil if string is too short + data = tail_end + data if tail_end + end + + @input << @tail + entities = data.split(@delimiter, -1) + @tail = entities.shift + + unless entities.empty? + @input << @tail + entities.unshift @input.join + @input.clear + @tail = entities.pop + end + + entities + end + + # Flush the contents of the input buffer, i.e. return the input buffer even though + # a token has not yet been encountered + def flush + @input << @tail + buffer = @input.join + @input.clear + @tail = "" # @tail.clear is slightly faster, but not supported on 1.8.7 + buffer + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb new file mode 100644 index 0000000..4928feb --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/callback.rb @@ -0,0 +1,58 @@ +module EventMachine + # Utility method for coercing arguments to an object that responds to :call. + # Accepts an object and a method name to send to, or a block, or an object + # that responds to :call. + # + # @example EventMachine.Callback used with a block. Returns that block. + # + # cb = EventMachine.Callback do |msg| + # puts(msg) + # end + # # returned object is a callable + # cb.call('hello world') + # + # + # @example EventMachine.Callback used with an object (to be more specific, class object) and a method name, returns an object that responds to #call + # + # cb = EventMachine.Callback(Object, :puts) + # # returned object is a callable that delegates to Kernel#puts (in this case Object.puts) + # cb.call('hello world') + # + # + # @example EventMachine.Callback used with an object that responds to #call. Returns the argument. + # + # cb = EventMachine.Callback(proc{ |msg| puts(msg) }) + # # returned object is a callable + # cb.call('hello world') + # + # + # @overload Callback(object, method) + # Wraps `method` invocation on `object` into an object that responds to #call that proxies all the arguments to that method + # @param [Object] Object to invoke method on + # @param [Symbol] Method name + # @return [<#call>] An object that responds to #call that takes any number of arguments and invokes method on object with those arguments + # + # @overload Callback(object) + # Returns callable object as is, without any coercion + # @param [<#call>] An object that responds to #call + # @return [<#call>] Its argument + # + # @overload Callback(&block) + # Returns block passed to it without any coercion + # @return [<#call>] Block passed to this method + # + # @raise [ArgumentError] When argument doesn't respond to #call, method name is missing or when invoked without arguments and block isn't given + # + # @return [<#call>] + def self.Callback(object = nil, method = nil, &blk) + if object && method + lambda { |*args| object.__send__ method, *args } + else + if object.respond_to? :call + object + else + blk || raise(ArgumentError) + end # if + end # if + end # self.Callback +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb new file mode 100644 index 0000000..a919adf --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/channel.rb @@ -0,0 +1,69 @@ +module EventMachine + # Provides a simple thread-safe way to transfer data between (typically) long running + # tasks in {EventMachine.defer} and event loop thread. + # + # @example + # + # channel = EventMachine::Channel.new + # sid = channel.subscribe { |msg| p [:got, msg] } + # + # channel.push('hello world') + # channel.unsubscribe(sid) + # + # + class Channel + def initialize + @subs = {} + @uid = 0 + end + + # Return the number of current subscribers. + def num_subscribers + return @subs.size + end + + # Takes any arguments suitable for EM::Callback() and returns a subscriber + # id for use when unsubscribing. + # + # @return [Integer] Subscribe identifier + # @see #unsubscribe + def subscribe(*a, &b) + name = gen_id + EM.schedule { @subs[name] = EM::Callback(*a, &b) } + + name + end + + # Removes subscriber from the list. + # + # @param [Integer] Subscriber identifier + # @see #subscribe + def unsubscribe(name) + EM.schedule { @subs.delete name } + end + + # Add items to the channel, which are pushed out to all subscribers. + def push(*items) + items = items.dup + EM.schedule { items.each { |i| @subs.values.each { |s| s.call i } } } + end + alias << push + + # Fetches one message from the channel. + def pop(*a, &b) + EM.schedule { + name = subscribe do |*args| + unsubscribe(name) + EM::Callback(*a, &b).call(*args) + end + } + end + + private + + # @private + def gen_id + @uid += 1 + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb new file mode 100644 index 0000000..926535f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb @@ -0,0 +1,304 @@ +# = EM::Completion +# +# A completion is a callback container for various states of completion. In +# its most basic form it has a start state and a finish state. +# +# This implementation includes some hold-back from the EM::Deferrable +# interface in order to be compatible - but it has a much cleaner +# implementation. +# +# In general it is preferred that this implementation be used as a state +# callback container than EM::DefaultDeferrable or other classes including +# EM::Deferrable. This is because it is generally more sane to keep this level +# of state in a dedicated state-back container. This generally leads to more +# malleable interfaces and software designs, as well as eradicating nasty bugs +# that result from abstraction leakage. +# +# == Basic Usage +# +# As already mentioned, the basic usage of a Completion is simply for its two +# final states, :succeeded and :failed. +# +# An asynchronous operation will complete at some future point in time, and +# users often want to react to this event. API authors will want to expose +# some common interface to react to these events. +# +# In the following example, the user wants to know when a short lived +# connection has completed its exchange with the remote server. The simple +# protocol just waits for an ack to its message. +# +# class Protocol < EM::Connection +# include EM::P::LineText2 +# +# def initialize(message, completion) +# @message, @completion = message, completion +# @completion.completion { close_connection } +# @completion.timeout(1, :timeout) +# end +# +# def post_init +# send_data(@message) +# end +# +# def receive_line(line) +# case line +# when /ACK/i +# @completion.succeed line +# when /ERR/i +# @completion.fail :error, line +# else +# @completion.fail :unknown, line +# end +# end +# +# def unbind +# @completion.fail :disconnected unless @completion.completed? +# end +# end +# +# class API +# attr_reader :host, :port +# +# def initialize(host = 'example.org', port = 8000) +# @host, @port = host, port +# end +# +# def request(message) +# completion = EM::Deferrable::Completion.new +# EM.connect(host, port, Protocol, message, completion) +# completion +# end +# end +# +# api = API.new +# completion = api.request('stuff') +# completion.callback do |line| +# puts "API responded with: #{line}" +# end +# completion.errback do |type, line| +# case type +# when :error +# puts "API error: #{line}" +# when :unknown +# puts "API returned unknown response: #{line}" +# when :disconnected +# puts "API server disconnected prematurely" +# when :timeout +# puts "API server did not respond in a timely fashion" +# end +# end +# +# == Advanced Usage +# +# This completion implementation also supports more state callbacks and +# arbitrary states (unlike the original Deferrable API). This allows for basic +# stateful process encapsulation. One might use this to setup state callbacks +# for various states in an exchange like in the basic usage example, except +# where the applicaiton could be made to react to "connected" and +# "disconnected" states additionally. +# +# class Protocol < EM::Connection +# def initialize(completion) +# @response = [] +# @completion = completion +# @completion.stateback(:disconnected) do +# @completion.succeed @response.join +# end +# end +# +# def connection_completed +# @host, @port = Socket.unpack_sockaddr_in get_peername +# @completion.change_state(:connected, @host, @port) +# send_data("GET http://example.org/ HTTP/1.0\r\n\r\n") +# end +# +# def receive_data(data) +# @response << data +# end +# +# def unbind +# @completion.change_state(:disconnected, @host, @port) +# end +# end +# +# completion = EM::Deferrable::Completion.new +# completion.stateback(:connected) do |host, port| +# puts "Connected to #{host}:#{port}" +# end +# completion.stateback(:disconnected) do |host, port| +# puts "Disconnected from #{host}:#{port}" +# end +# completion.callback do |response| +# puts response +# end +# +# EM.connect('example.org', 80, Protocol, completion) +# +# == Timeout +# +# The Completion also has a timeout. The timeout is global and is not aware of +# states apart from completion states. The timeout is only engaged if #timeout +# is called, and it will call fail if it is reached. +# +# == Completion states +# +# By default there are two completion states, :succeeded and :failed. These +# states can be modified by subclassing and overrding the #completion_states +# method. Completion states are special, in that callbacks for all completion +# states are explcitly cleared when a completion state is entered. This +# prevents errors that could arise from accidental unterminated timeouts, and +# other such user errors. +# +# == Other notes +# +# Several APIs have been carried over from EM::Deferrable for compatibility +# reasons during a transitionary period. Specifically cancel_errback and +# cancel_callback are implemented, but their usage is to be strongly +# discouraged. Due to the already complex nature of reaction systems, dynamic +# callback deletion only makes the problem much worse. It is always better to +# add correct conditionals to the callback code, or use more states, than to +# address such implementaiton issues with conditional callbacks. + +module EventMachine + + class Completion + # This is totally not used (re-implemented), it's here in case people check + # for kind_of? + include EventMachine::Deferrable + + attr_reader :state, :value + + def initialize + @state = :unknown + @callbacks = Hash.new { |h,k| h[k] = [] } + @value = [] + @timeout_timer = nil + end + + # Enter the :succeeded state, setting the result value if given. + def succeed(*args) + change_state(:succeeded, *args) + end + # The old EM method: + alias set_deferred_success succeed + + # Enter the :failed state, setting the result value if given. + def fail(*args) + change_state(:failed, *args) + end + # The old EM method: + alias set_deferred_failure fail + + # Statebacks are called when you enter (or are in) the named state. + def stateback(state, *a, &b) + # The following is quite unfortunate special casing for :completed + # statebacks, but it's a necessary evil for latent completion + # definitions. + + if :completed == state || !completed? || @state == state + @callbacks[state] << EM::Callback(*a, &b) + end + execute_callbacks + self + end + + # Callbacks are called when you enter (or are in) a :succeeded state. + def callback(*a, &b) + stateback(:succeeded, *a, &b) + end + + # Errbacks are called when you enter (or are in) a :failed state. + def errback(*a, &b) + stateback(:failed, *a, &b) + end + + # Completions are called when you enter (or are in) either a :failed or a + # :succeeded state. They are stored as a special (reserved) state called + # :completed. + def completion(*a, &b) + stateback(:completed, *a, &b) + end + + # Enter a new state, setting the result value if given. If the state is one + # of :succeeded or :failed, then :completed callbacks will also be called. + def change_state(state, *args) + @value = args + @state = state + + EM.schedule { execute_callbacks } + end + + # The old EM method: + alias set_deferred_status change_state + + # Indicates that we've reached some kind of completion state, by default + # this is :succeeded or :failed. Due to these semantics, the :completed + # state is reserved for internal use. + def completed? + completion_states.any? { |s| state == s } + end + + # Completion states simply returns a list of completion states, by default + # this is :succeeded and :failed. + def completion_states + [:succeeded, :failed] + end + + # Schedule a time which if passes before we enter a completion state, this + # deferrable will be failed with the given arguments. + def timeout(time, *args) + cancel_timeout + @timeout_timer = EM::Timer.new(time) do + fail(*args) unless completed? + end + end + + # Disable the timeout + def cancel_timeout + if @timeout_timer + @timeout_timer.cancel + @timeout_timer = nil + end + end + + # Remove an errback. N.B. Some errbacks cannot be deleted. Usage is NOT + # recommended, this is an anti-pattern. + def cancel_errback(*a, &b) + @callbacks[:failed].delete(EM::Callback(*a, &b)) + end + + # Remove a callback. N.B. Some callbacks cannot be deleted. Usage is NOT + # recommended, this is an anti-pattern. + def cancel_callback(*a, &b) + @callbacks[:succeeded].delete(EM::Callback(*a, &b)) + end + + private + # Execute all callbacks for the current state. If in a completed state, then + # call any statebacks associated with the completed state. + def execute_callbacks + execute_state_callbacks(state) + if completed? + execute_state_callbacks(:completed) + clear_dead_callbacks + cancel_timeout + end + end + + # Iterate all callbacks for a given state, and remove then call them. + def execute_state_callbacks(state) + while callback = @callbacks[state].shift + callback.call(*value) + end + end + + # If we enter a completion state, clear other completion states after all + # callback chains are completed. This means that operation specific + # callbacks can't be dual-called, which is most common user error. + def clear_dead_callbacks + completion_states.each do |state| + @callbacks[state].clear + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb new file mode 100644 index 0000000..267aec2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/connection.rb @@ -0,0 +1,770 @@ +module EventMachine + class FileNotFoundException < Exception + end + + # EventMachine::Connection is a class that is instantiated + # by EventMachine's processing loop whenever a new connection + # is created. (New connections can be either initiated locally + # to a remote server or accepted locally from a remote client.) + # When a Connection object is instantiated, it <i>mixes in</i> + # the functionality contained in the user-defined module + # specified in calls to {EventMachine.connect} or {EventMachine.start_server}. + # User-defined handler modules may redefine any or all of the standard + # methods defined here, as well as add arbitrary additional code + # that will also be mixed in. + # + # EventMachine manages one object inherited from EventMachine::Connection + # (and containing the mixed-in user code) for every network connection + # that is active at any given time. + # The event loop will automatically call methods on EventMachine::Connection + # objects whenever specific events occur on the corresponding connections, + # as described below. + # + # This class is never instantiated by user code, and does not publish an + # initialize method. The instance methods of EventMachine::Connection + # which may be called by the event loop are: + # + # * {#post_init} + # * {#connection_completed} + # * {#receive_data} + # * {#unbind} + # * {#ssl_verify_peer} (if TLS is used) + # * {#ssl_handshake_completed} + # + # All of the other instance methods defined here are called only by user code. + # + # @see file:docs/GettingStarted.md EventMachine tutorial + class Connection + # @private + attr_accessor :signature + + # @private + alias original_method method + + # Override .new so subclasses don't have to call super and can ignore + # connection-specific arguments + # + # @private + def self.new(sig, *args) + allocate.instance_eval do + # Store signature + @signature = sig + # associate_callback_target sig + + # Call a superclass's #initialize if it has one + initialize(*args) + + # post initialize callback + post_init + + self + end + end + + # Stubbed initialize so legacy superclasses can safely call super + # + # @private + def initialize(*args) + end + + # Called by the event loop immediately after the network connection has been established, + # and before resumption of the network loop. + # This method is generally not called by user code, but is called automatically + # by the event loop. The base-class implementation is a no-op. + # This is a very good place to initialize instance variables that will + # be used throughout the lifetime of the network connection. + # + # @see #connection_completed + # @see #unbind + # @see #send_data + # @see #receive_data + def post_init + end + + # Called by the event loop whenever data has been received by the network connection. + # It is never called by user code. {#receive_data} is called with a single parameter, a String containing + # the network protocol data, which may of course be binary. You will + # generally redefine this method to perform your own processing of the incoming data. + # + # Here's a key point which is essential to understanding the event-driven + # programming model: <i>EventMachine knows absolutely nothing about the protocol + # which your code implements.</i> You must not make any assumptions about + # the size of the incoming data packets, or about their alignment on any + # particular intra-message or PDU boundaries (such as line breaks). + # receive_data can and will send you arbitrary chunks of data, with the + # only guarantee being that the data is presented to your code in the order + # it was collected from the network. Don't even assume that the chunks of + # data will correspond to network packets, as EventMachine can and will coalesce + # several incoming packets into one, to improve performance. The implication for your + # code is that you generally will need to implement some kind of a state machine + # in your redefined implementation of receive_data. For a better understanding + # of this, read through the examples of specific protocol handlers in EventMachine::Protocols + # + # The base-class implementation (which will be invoked only if you didn't override it in your protocol handler) + # simply prints incoming data packet size to stdout. + # + # @param [String] data Opaque incoming data. + # @note Depending on the protocol, buffer sizes and OS networking stack configuration, incoming data may or may not be "a complete message". + # It is up to this handler to detect content boundaries to determine whether all the content (for example, full HTTP request) + # has been received and can be processed. + # + # @see #post_init + # @see #connection_completed + # @see #unbind + # @see #send_data + # @see file:docs/GettingStarted.md EventMachine tutorial + def receive_data data + puts "............>>>#{data.length}" + end + + # Called by EventMachine when the SSL/TLS handshake has + # been completed, as a result of calling #start_tls to initiate SSL/TLS on the connection. + # + # This callback exists because {#post_init} and {#connection_completed} are **not** reliable + # for indicating when an SSL/TLS connection is ready to have its certificate queried for. + # + # @see #get_peer_cert + def ssl_handshake_completed + end + + # Called by EventMachine when :verify_peer => true has been passed to {#start_tls}. + # It will be called with each certificate in the certificate chain provided by the remote peer. + # + # The cert will be passed as a String in PEM format, the same as in {#get_peer_cert}. It is up to user defined + # code to perform a check on the certificates. The return value from this callback is used to accept or deny the peer. + # A return value that is not nil or false triggers acceptance. If the peer is not accepted, the connection + # will be subsequently closed. + # + # @example This server always accepts all peers + # + # module AcceptServer + # def post_init + # start_tls(:verify_peer => true) + # end + # + # def ssl_verify_peer(cert) + # true + # end + # + # def ssl_handshake_completed + # $server_handshake_completed = true + # end + # end + # + # + # @example This server never accepts any peers + # + # module DenyServer + # def post_init + # start_tls(:verify_peer => true) + # end + # + # def ssl_verify_peer(cert) + # # Do not accept the peer. This should now cause the connection to shut down + # # without the SSL handshake being completed. + # false + # end + # + # def ssl_handshake_completed + # $server_handshake_completed = true + # end + # end + # + # @see #start_tls + def ssl_verify_peer(cert) + end + + # called by the framework whenever a connection (either a server or client connection) is closed. + # The close can occur because your code intentionally closes it (using {#close_connection} and {#close_connection_after_writing}), + # because the remote peer closed the connection, or because of a network error. + # You may not assume that the network connection is still open and able to send or + # receive data when the callback to unbind is made. This is intended only to give + # you a chance to clean up associations your code may have made to the connection + # object while it was open. + # + # If you want to detect which peer has closed the connection, you can override {#close_connection} in your protocol handler + # and set an @ivar. + # + # @example Overriding Connection#close_connection to distinguish connections closed on our side + # + # class MyProtocolHandler < EventMachine::Connection + # + # # ... + # + # def close_connection(*args) + # @intentionally_closed_connection = true + # super(*args) + # end + # + # def unbind + # if @intentionally_closed_connection + # # ... + # end + # end + # + # # ... + # + # end + # + # @see #post_init + # @see #connection_completed + # @see file:docs/GettingStarted.md EventMachine tutorial + def unbind + end + + # Called by the reactor after attempting to relay incoming data to a descriptor (set as a proxy target descriptor with + # {EventMachine.enable_proxy}) that has already been closed. + # + # @see EventMachine.enable_proxy + def proxy_target_unbound + end + + # called when the reactor finished proxying all + # of the requested bytes. + def proxy_completed + end + + # EventMachine::Connection#proxy_incoming_to is called only by user code. It sets up + # a low-level proxy relay for all data inbound for this connection, to the connection given + # as the argument. This is essentially just a helper method for enable_proxy. + # + # @see EventMachine.enable_proxy + def proxy_incoming_to(conn,bufsize=0) + EventMachine::enable_proxy(self, conn, bufsize) + end + + # A helper method for {EventMachine.disable_proxy} + def stop_proxying + EventMachine::disable_proxy(self) + end + + # The number of bytes proxied to another connection. Reset to zero when + # EventMachine::Connection#proxy_incoming_to is called, and incremented whenever data is proxied. + def get_proxied_bytes + EventMachine::get_proxied_bytes(@signature) + end + + # EventMachine::Connection#close_connection is called only by user code, and never + # by the event loop. You may call this method against a connection object in any + # callback handler, whether or not the callback was made against the connection + # you want to close. close_connection <i>schedules</i> the connection to be closed + # at the next available opportunity within the event loop. You may not assume that + # the connection is closed when close_connection returns. In particular, the framework + # will callback the unbind method for the particular connection at a point shortly + # after you call close_connection. You may assume that the unbind callback will + # take place sometime after your call to close_connection completes. In other words, + # the unbind callback will not re-enter your code "inside" of your call to close_connection. + # However, it's not guaranteed that a future version of EventMachine will not change + # this behavior. + # + # {#close_connection} will *silently discard* any outbound data which you have + # sent to the connection using {EventMachine::Connection#send_data} but which has not + # yet been sent across the network. If you want to avoid this behavior, use + # {EventMachine::Connection#close_connection_after_writing}. + # + def close_connection after_writing = false + EventMachine::close_connection @signature, after_writing + end + + # Removes given connection from the event loop. + # The connection's socket remains open and its file descriptor number is returned. + def detach + EventMachine::detach_fd @signature + end + + def get_sock_opt level, option + EventMachine::get_sock_opt @signature, level, option + end + + def set_sock_opt level, optname, optval + EventMachine::set_sock_opt @signature, level, optname, optval + end + + # A variant of {#close_connection}. + # All of the descriptive comments given for close_connection also apply to + # close_connection_after_writing, *with one exception*: if the connection has + # outbound data sent using send_dat but which has not yet been sent across the network, + # close_connection_after_writing will schedule the connection to be closed *after* + # all of the outbound data has been safely written to the remote peer. + # + # Depending on the amount of outgoing data and the speed of the network, + # considerable time may elapse between your call to close_connection_after_writing + # and the actual closing of the socket (at which time the unbind callback will be called + # by the event loop). During this time, you *may not* call send_data to transmit + # additional data (that is, the connection is closed for further writes). In very + # rare cases, you may experience a receive_data callback after your call to {#close_connection_after_writing}, + # depending on whether incoming data was in the process of being received on the connection + # at the moment when you called {#close_connection_after_writing}. Your protocol handler must + # be prepared to properly deal with such data (probably by ignoring it). + # + # @see #close_connection + # @see #send_data + def close_connection_after_writing + close_connection true + end + + # Call this method to send data to the remote end of the network connection. It takes a single String argument, + # which may contain binary data. Data is buffered to be sent at the end of this event loop tick (cycle). + # + # When used in a method that is event handler (for example, {#post_init} or {#connection_completed}, it will send + # data to the other end of the connection that generated the event. + # You can also call {#send_data} to write to other connections. For more information see The Chat Server Example in the + # {file:docs/GettingStarted.md EventMachine tutorial}. + # + # If you want to send some data and then immediately close the connection, make sure to use {#close_connection_after_writing} + # instead of {#close_connection}. + # + # + # @param [String] data Data to send asynchronously + # + # @see file:docs/GettingStarted.md EventMachine tutorial + # @see Connection#receive_data + # @see Connection#post_init + # @see Connection#unbind + def send_data data + data = data.to_s + size = data.bytesize if data.respond_to?(:bytesize) + size ||= data.size + EventMachine::send_data @signature, data, size + end + + # Returns true if the connection is in an error state, false otherwise. + # + # In general, you can detect the occurrence of communication errors or unexpected + # disconnection by the remote peer by handing the {#unbind} method. In some cases, however, + # it's useful to check the status of the connection using {#error?} before attempting to send data. + # This function is synchronous but it will return immediately without blocking. + # + # @return [Boolean] true if the connection is in an error state, false otherwise + def error? + errno = EventMachine::report_connection_error_status(@signature) + case errno + when 0 + false + when -1 + true + else + EventMachine::ERRNOS[errno] + end + end + + # Called by the event loop when a remote TCP connection attempt completes successfully. + # You can expect to get this notification after calls to {EventMachine.connect}. Remember that EventMachine makes remote connections + # asynchronously, just as with any other kind of network event. This method + # is intended primarily to assist with network diagnostics. For normal protocol + # handling, use #post_init to perform initial work on a new connection (such as sending initial set of data). + # {Connection#post_init} will always be called. This method will only be called in case of a successful completion. + # A connection attempt which fails will result a call to {Connection#unbind} after the failure. + # + # @see Connection#post_init + # @see Connection#unbind + # @see file:docs/GettingStarted.md EventMachine tutorial + def connection_completed + end + + # Call {#start_tls} at any point to initiate TLS encryption on connected streams. + # The method is smart enough to know whether it should perform a server-side + # or a client-side handshake. An appropriate place to call {#start_tls} is in + # your redefined {#post_init} method, or in the {#connection_completed} handler for + # an outbound connection. + # + # + # @option args [String] :cert_chain_file (nil) local path of a readable file that contants a chain of X509 certificates in + # the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail), + # with the most-resolved certificate at the top of the file, successive intermediate + # certs in the middle, and the root (or CA) cert at the bottom. + # + # @option args [String] :private_key_file (nil) local path of a readable file that must contain a private key in the [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail). + # + # @option args [Boolean] :verify_peer (false) indicates whether a server should request a certificate from a peer, to be verified by user code. + # If true, the {#ssl_verify_peer} callback on the {EventMachine::Connection} object is called with each certificate + # in the certificate chain provided by the peer. See documentation on {#ssl_verify_peer} for how to use this. + # + # @option args [Boolean] :fail_if_no_peer_cert (false) Used in conjunction with verify_peer. If set the SSL handshake will be terminated if the peer does not provide a certificate. + # + # + # @option args [String] :cipher_list ("ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH") indicates the available SSL cipher values. Default value is "ALL:!ADH:!LOW:!EXP:!DES-CBC3-SHA:@STRENGTH". Check the format of the OpenSSL cipher string at http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT. + # + # @option args [String] :ecdh_curve (nil) The curve for ECDHE ciphers. See available ciphers with 'openssl ecparam -list_curves' + # + # @option args [String] :dhparam (nil) The local path of a file containing DH parameters for EDH ciphers in [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail) See: 'openssl dhparam' + # + # @option args [Array] :ssl_version (TLSv1 TLSv1_1 TLSv1_2) indicates the allowed SSL/TLS versions. Possible values are: {SSLv2}, {SSLv3}, {TLSv1}, {TLSv1_1}, {TLSv1_2}. + # + # @example Using TLS with EventMachine + # + # require 'rubygems' + # require 'eventmachine' + # + # module Handler + # def post_init + # start_tls(:private_key_file => '/tmp/server.key', :cert_chain_file => '/tmp/server.crt', :verify_peer => false) + # end + # end + # + # EventMachine.run do + # EventMachine.start_server("127.0.0.1", 9999, Handler) + # end + # + # @param [Hash] args + # + # @todo support passing an encryption parameter, which can be string or Proc, to get a passphrase + # for encrypted private keys. + # @todo support passing key material via raw strings or Procs that return strings instead of + # just filenames. + # + # @see #ssl_verify_peer + def start_tls args={} + priv_key = args[:private_key_file] + cert_chain = args[:cert_chain_file] + verify_peer = args[:verify_peer] + sni_hostname = args[:sni_hostname] + cipher_list = args[:cipher_list] + ssl_version = args[:ssl_version] + ecdh_curve = args[:ecdh_curve] + dhparam = args[:dhparam] + fail_if_no_peer_cert = args[:fail_if_no_peer_cert] + + [priv_key, cert_chain].each do |file| + next if file.nil? or file.empty? + raise FileNotFoundException, + "Could not find #{file} for start_tls" unless File.exist? file + end + + protocols_bitmask = 0 + if ssl_version.nil? + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1 + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1 + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2 + else + [ssl_version].flatten.each do |p| + case p.to_s.downcase + when 'sslv2' + protocols_bitmask |= EventMachine::EM_PROTO_SSLv2 + when 'sslv3' + protocols_bitmask |= EventMachine::EM_PROTO_SSLv3 + when 'tlsv1' + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1 + when 'tlsv1_1' + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_1 + when 'tlsv1_2' + protocols_bitmask |= EventMachine::EM_PROTO_TLSv1_2 + else + raise("Unrecognized SSL/TLS Protocol: #{p}") + end + end + end + + EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer, fail_if_no_peer_cert, sni_hostname || '', cipher_list || '', ecdh_curve || '', dhparam || '', protocols_bitmask) + EventMachine::start_tls @signature + end + + # If [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is active on the connection, returns the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509) + # as a string, in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail). This can then be used for arbitrary validation + # of a peer's certificate in your code. + # + # This should be called in/after the {#ssl_handshake_completed} callback, which indicates + # that SSL/TLS is active. Using this callback is important, because the certificate may not + # be available until the time it is executed. Using #post_init or #connection_completed is + # not adequate, because the SSL handshake may still be taking place. + # + # This method will return `nil` if: + # + # * EventMachine is not built with [OpenSSL](http://www.openssl.org) support + # * [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) is not active on the connection + # * TLS handshake is not yet complete + # * Remote peer for any other reason has not presented a certificate + # + # + # @example Getting peer TLS certificate information in EventMachine + # + # module Handler + # def post_init + # puts "Starting TLS" + # start_tls + # end + # + # def ssl_handshake_completed + # puts get_peer_cert + # close_connection + # end + # + # def unbind + # EventMachine::stop_event_loop + # end + # end + # + # EventMachine.run do + # EventMachine.connect "mail.google.com", 443, Handler + # end + # + # # Will output: + # # -----BEGIN CERTIFICATE----- + # # MIIDIjCCAougAwIBAgIQbldpChBPqv+BdPg4iwgN8TANBgkqhkiG9w0BAQUFADBM + # # MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg + # # THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wODA1MDIxNjMyNTRaFw0w + # # OTA1MDIxNjMyNTRaMGkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh + # # MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMRgw + # # FgYDVQQDEw9tYWlsLmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ + # # AoGBALlkxdh2QXegdElukCSOV2+8PKiONIS+8Tu9K7MQsYpqtLNC860zwOPQ2NLI + # # 3Zp4jwuXVTrtzGuiqf5Jioh35Ig3CqDXtLyZoypjZUQcq4mlLzHlhIQ4EhSjDmA7 + # # Ffw9y3ckSOQgdBQWNLbquHh9AbEUjmhkrYxIqKXeCnRKhv6nAgMBAAGjgecwgeQw + # # KAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwNgYDVR0f + # # BC8wLTAroCmgJ4YlaHR0cDovL2NybC50aGF3dGUuY29tL1RoYXd0ZVNHQ0NBLmNy + # # bDByBggrBgEFBQcBAQRmMGQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnRoYXd0 + # # ZS5jb20wPgYIKwYBBQUHMAKGMmh0dHA6Ly93d3cudGhhd3RlLmNvbS9yZXBvc2l0 + # # b3J5L1RoYXd0ZV9TR0NfQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEF + # # BQADgYEAsRwpLg1dgCR1gYDK185MFGukXMeQFUvhGqF8eT/CjpdvezyKVuz84gSu + # # 6ccMXgcPQZGQN/F4Xug+Q01eccJjRSVfdvR5qwpqCj+6BFl5oiKDBsveSkrmL5dz + # # s2bn7TdTSYKcLeBkjXxDLHGBqLJ6TNCJ3c4/cbbG5JhGvoema94= + # # -----END CERTIFICATE----- + # + # You can do whatever you want with the certificate String, such as load it + # as a certificate object using the OpenSSL library, and check its fields. + # + # @return [String] the remote [X509 certificate](http://en.wikipedia.org/wiki/X.509), in the popular [PEM format](http://en.wikipedia.org/wiki/Privacy_Enhanced_Mail), + # if TLS is active on the connection + # + # @see Connection#start_tls + # @see Connection#ssl_handshake_completed + def get_peer_cert + EventMachine::get_peer_cert @signature + end + + def get_cipher_bits + EventMachine::get_cipher_bits @signature + end + + def get_cipher_name + EventMachine::get_cipher_name @signature + end + + def get_cipher_protocol + EventMachine::get_cipher_protocol @signature + end + + def get_sni_hostname + EventMachine::get_sni_hostname @signature + end + + # Sends UDP messages. + # + # This method may be called from any Connection object that refers + # to an open datagram socket (see EventMachine#open_datagram_socket). + # The method sends a UDP (datagram) packet containing the data you specify, + # to a remote peer specified by the IP address and port that you give + # as parameters to the method. + # Observe that you may send a zero-length packet (empty string). + # However, you may not send an arbitrarily-large data packet because + # your operating system will enforce a platform-specific limit on + # the size of the outbound packet. (Your kernel + # will respond in a platform-specific way if you send an overlarge + # packet: some will send a truncated packet, some will complain, and + # some will silently drop your request). + # On LANs, it's usually OK to send datagrams up to about 4000 bytes in length, + # but to be really safe, send messages smaller than the Ethernet-packet + # size (typically about 1400 bytes). Some very restrictive WANs + # will either drop or truncate packets larger than about 500 bytes. + # + # @param [String] data Data to send asynchronously + # @param [String] recipient_address IP address of the recipient + # @param [String] recipient_port Port of the recipient + def send_datagram data, recipient_address, recipient_port + data = data.to_s + size = data.bytesize if data.respond_to?(:bytesize) + size ||= data.size + EventMachine::send_datagram @signature, data, size, recipient_address, Integer(recipient_port) + end + + + # This method is used with stream-connections to obtain the identity + # of the remotely-connected peer. If a peername is available, this method + # returns a sockaddr structure. The method returns nil if no peername is available. + # You can use Socket.unpack_sockaddr_in and its variants to obtain the + # values contained in the peername structure returned from #get_peername. + # + # @example How to get peer IP address and port with EventMachine + # + # require 'socket' + # + # module Handler + # def receive_data data + # port, ip = Socket.unpack_sockaddr_in(get_peername) + # puts "got #{data.inspect} from #{ip}:#{port}" + # end + # end + def get_peername + EventMachine::get_peername @signature + end + + # Used with stream-connections to obtain the identity + # of the local side of the connection. If a local name is available, this method + # returns a sockaddr structure. The method returns nil if no local name is available. + # You can use {Socket.unpack_sockaddr_in} and its variants to obtain the + # values contained in the local-name structure returned from this method. + # + # @example + # + # require 'socket' + # + # module Handler + # def receive_data data + # port, ip = Socket.unpack_sockaddr_in(get_sockname) + # puts "got #{data.inspect}" + # end + # end + def get_sockname + EventMachine::get_sockname @signature + end + + # Returns the PID (kernel process identifier) of a subprocess + # associated with this Connection object. For use with {EventMachine.popen} + # and similar methods. Returns nil when there is no meaningful subprocess. + # + # @return [Integer] + def get_pid + EventMachine::get_subprocess_pid @signature + end + + # Returns a subprocess exit status. Only useful for {EventMachine.popen}. Call it in your + # {#unbind} handler. + # + # @return [Integer] + def get_status + EventMachine::get_subprocess_status @signature + end + + # The number of seconds since the last send/receive activity on this connection. + def get_idle_time + EventMachine::get_idle_time @signature + end + + # comm_inactivity_timeout returns the current value (float in seconds) of the inactivity-timeout + # property of network-connection and datagram-socket objects. A nonzero value + # indicates that the connection or socket will automatically be closed if no read or write + # activity takes place for at least that number of seconds. + # A zero value (the default) specifies that no automatic timeout will take place. + def comm_inactivity_timeout + EventMachine::get_comm_inactivity_timeout @signature + end + + # Allows you to set the inactivity-timeout property for + # a network connection or datagram socket. Specify a non-negative float value in seconds. + # If the value is greater than zero, the connection or socket will automatically be closed + # if no read or write activity takes place for at least that number of seconds. + # Specify a value of zero to indicate that no automatic timeout should take place. + # Zero is the default value. + def comm_inactivity_timeout= value + EventMachine::set_comm_inactivity_timeout @signature, value.to_f + end + alias set_comm_inactivity_timeout comm_inactivity_timeout= + + # The duration after which a TCP connection in the connecting state will fail. + # It is important to distinguish this value from {EventMachine::Connection#comm_inactivity_timeout}, + # which looks at how long since data was passed on an already established connection. + # The value is a float in seconds. + # + # @return [Float] The duration after which a TCP connection in the connecting state will fail, in seconds. + def pending_connect_timeout + EventMachine::get_pending_connect_timeout @signature + end + + # Sets the duration after which a TCP connection in a + # connecting state will fail. + # + # @param [Float, #to_f] value Connection timeout in seconds + def pending_connect_timeout= value + EventMachine::set_pending_connect_timeout @signature, value.to_f + end + alias set_pending_connect_timeout pending_connect_timeout= + + # Reconnect to a given host/port with the current instance + # + # @param [String] server Hostname or IP address + # @param [Integer] port Port to reconnect to + def reconnect server, port + EventMachine::reconnect server, port, self + end + + + # Like {EventMachine::Connection#send_data}, this sends data to the remote end of + # the network connection. {EventMachine::Connection#send_file_data} takes a + # filename as an argument, though, and sends the contents of the file, in one + # chunk. + # + # @param [String] filename Local path of the file to send + # + # @see #send_data + # @author Kirk Haines + def send_file_data filename + EventMachine::send_file_data @signature, filename + end + + # Open a file on the filesystem and send it to the remote peer. This returns an + # object of type {EventMachine::Deferrable}. The object's callbacks will be executed + # on the reactor main thread when the file has been completely scheduled for + # transmission to the remote peer. Its errbacks will be called in case of an error (such as file-not-found). + # This method employs various strategies to achieve the fastest possible performance, + # balanced against minimum consumption of memory. + # + # Warning: this feature has an implicit dependency on an outboard extension, + # evma_fastfilereader. You must install this extension in order to use {#stream_file_data} + # with files larger than a certain size (currently 8192 bytes). + # + # @option args [Boolean] :http_chunks (false) If true, this method will stream the file data in a format + # compatible with the HTTP chunked-transfer encoding + # + # @param [String] filename Local path of the file to stream + # @param [Hash] args Options + # + # @return [EventMachine::Deferrable] + def stream_file_data filename, args={} + EventMachine::FileStreamer.new( self, filename, args ) + end + + # Watches connection for readability. Only possible if the connection was created + # using {EventMachine.attach} and had {EventMachine.notify_readable}/{EventMachine.notify_writable} defined on the handler. + # + # @see #notify_readable? + def notify_readable= mode + EventMachine::set_notify_readable @signature, mode + end + + # @return [Boolean] true if the connection is being watched for readability. + def notify_readable? + EventMachine::is_notify_readable @signature + end + + # Watches connection for writeability. Only possible if the connection was created + # using {EventMachine.attach} and had {EventMachine.notify_readable}/{EventMachine.notify_writable} defined on the handler. + # + # @see #notify_writable? + def notify_writable= mode + EventMachine::set_notify_writable @signature, mode + end + + # Returns true if the connection is being watched for writability. + def notify_writable? + EventMachine::is_notify_writable @signature + end + + # Pause a connection so that {#send_data} and {#receive_data} events are not fired until {#resume} is called. + # @see #resume + def pause + EventMachine::pause_connection @signature + end + + # Resume a connection's {#send_data} and {#receive_data} events. + # @see #pause + def resume + EventMachine::resume_connection @signature + end + + # @return [Boolean] true if the connect was paused using {EventMachine::Connection#pause}. + # @see #pause + # @see #resume + def paused? + EventMachine::connection_paused? @signature + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb new file mode 100644 index 0000000..18a6d31 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable.rb @@ -0,0 +1,210 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 Jul 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Deferrable + autoload :Pool, 'em/deferrable/pool' + + # Specify a block to be executed if and when the Deferrable object receives + # a status of :succeeded. See #set_deferred_status for more information. + # + # Calling this method on a Deferrable object whose status is not yet known + # will cause the callback block to be stored on an internal list. + # If you call this method on a Deferrable whose status is :succeeded, the + # block will be executed immediately, receiving the parameters given to the + # prior #set_deferred_status call. + # + #-- + # If there is no status, add a callback to an internal list. + # If status is succeeded, execute the callback immediately. + # If status is failed, do nothing. + # + def callback &block + return unless block + @deferred_status ||= :unknown + if @deferred_status == :succeeded + block.call(*@deferred_args) + elsif @deferred_status != :failed + @callbacks ||= [] + @callbacks.unshift block # << block + end + self + end + + # Cancels an outstanding callback to &block if any. Undoes the action of #callback. + # + def cancel_callback block + @callbacks ||= [] + @callbacks.delete block + end + + # Specify a block to be executed if and when the Deferrable object receives + # a status of :failed. See #set_deferred_status for more information. + #-- + # If there is no status, add an errback to an internal list. + # If status is failed, execute the errback immediately. + # If status is succeeded, do nothing. + # + def errback &block + return unless block + @deferred_status ||= :unknown + if @deferred_status == :failed + block.call(*@deferred_args) + elsif @deferred_status != :succeeded + @errbacks ||= [] + @errbacks.unshift block # << block + end + self + end + + # Cancels an outstanding errback to &block if any. Undoes the action of #errback. + # + def cancel_errback block + @errbacks ||= [] + @errbacks.delete block + end + + # Sets the "disposition" (status) of the Deferrable object. See also the large set of + # sugarings for this method. + # Note that if you call this method without arguments, + # no arguments will be passed to the callback/errback. + # If the user has coded these with arguments, then the + # user code will throw an argument exception. + # Implementors of deferrable classes <b>must</b> + # document the arguments they will supply to user callbacks. + # + # OBSERVE SOMETHING VERY SPECIAL here: you may call this method even + # on the INSIDE of a callback. This is very useful when a previously-registered + # callback wants to change the parameters that will be passed to subsequently-registered + # ones. + # + # You may give either :succeeded or :failed as the status argument. + # + # If you pass :succeeded, then all of the blocks passed to the object using the #callback + # method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks + # passed to the object using #errback will be discarded. + # + # If you pass :failed, then all of the blocks passed to the object using the #errback + # method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks + # passed to the object using # callback will be discarded. + # + # If you pass any arguments to #set_deferred_status in addition to the status argument, + # they will be passed as arguments to any callbacks or errbacks that are executed. + # It's your responsibility to ensure that the argument lists specified in your callbacks and + # errbacks match the arguments given in calls to #set_deferred_status, otherwise Ruby will raise + # an ArgumentError. + # + #-- + # We're shifting callbacks off and discarding them as we execute them. + # This is valid because by definition callbacks are executed no more than + # once. It also has the magic effect of permitting recursive calls, which + # means that a callback can call #set_deferred_status and change the parameters + # that will be sent to subsequent callbacks down the chain. + # + # Changed @callbacks and @errbacks from push/shift to unshift/pop, per suggestion + # by Kirk Haines, to work around the memory leak bug that still exists in many Ruby + # versions. + # + # Changed 15Sep07: after processing callbacks or errbacks, CLEAR the other set of + # handlers. This gets us a little closer to the behavior of Twisted's "deferred," + # which only allows status to be set once. Prior to making this change, it was possible + # to "succeed" a Deferrable (triggering its callbacks), and then immediately "fail" it, + # triggering its errbacks! That is clearly undesirable, but it's just as undesirable + # to raise an exception is status is set more than once on a Deferrable. The latter + # behavior would invalidate the idiom of resetting arguments by setting status from + # within a callback or errback, but more seriously it would cause spurious errors + # if a Deferrable was timed out and then an attempt was made to succeed it. See the + # comments under the new method #timeout. + # + def set_deferred_status status, *args + cancel_timeout + @errbacks ||= nil + @callbacks ||= nil + @deferred_status = status + @deferred_args = args + case @deferred_status + when :succeeded + if @callbacks + while cb = @callbacks.pop + cb.call(*@deferred_args) + end + end + @errbacks.clear if @errbacks + when :failed + if @errbacks + while eb = @errbacks.pop + eb.call(*@deferred_args) + end + end + @callbacks.clear if @callbacks + end + end + + + # Setting a timeout on a Deferrable causes it to go into the failed state after + # the Timeout expires (passing no arguments to the object's errbacks). + # Setting the status at any time prior to a call to the expiration of the timeout + # will cause the timer to be cancelled. + def timeout seconds, *args + cancel_timeout + me = self + @deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)} + self + end + + # Cancels an outstanding timeout if any. Undoes the action of #timeout. + # + def cancel_timeout + @deferred_timeout ||= nil + if @deferred_timeout + @deferred_timeout.cancel + @deferred_timeout = nil + end + end + + + # Sugar for set_deferred_status(:succeeded, ...) + # + def succeed *args + set_deferred_status :succeeded, *args + end + alias set_deferred_success succeed + + # Sugar for set_deferred_status(:failed, ...) + # + def fail *args + set_deferred_status :failed, *args + end + alias set_deferred_failure fail + end + + + # DefaultDeferrable is an otherwise empty class that includes Deferrable. + # This is very useful when you just need to return a Deferrable object + # as a way of communicating deferred status to some other part of a program. + class DefaultDeferrable + include Deferrable + end +end
\ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb new file mode 100644 index 0000000..3c278ee --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/deferrable/pool.rb @@ -0,0 +1,2 @@ +warn "EM::Deferrable::Pool is deprecated, please use EM::Pool" +EM::Deferrable::Pool = EM::Pool
\ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb new file mode 100644 index 0000000..074ffed --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/file_watch.rb @@ -0,0 +1,73 @@ +module EventMachine + # Utility class that is useful for file monitoring. Supported events are + # + # * File is modified + # * File is deleted + # * File is moved + # + # @note On Mac OS X, file watching only works when kqueue is enabled + # + # @see EventMachine.watch_file + class FileWatch < Connection + # @private + Cmodified = 'modified'.freeze + # @private + Cdeleted = 'deleted'.freeze + # @private + Cmoved = 'moved'.freeze + + + # @private + def receive_data(data) + case data + when Cmodified + file_modified + when Cdeleted + file_deleted + when Cmoved + file_moved + end + end + + # Returns the path that is being monitored. + # + # @note Current implementation does not pick up on the new filename after a rename occurs. + # + # @return [String] + # @see EventMachine.watch_file + def path + @path + end + + # Will be called when the file is modified. Supposed to be redefined by subclasses. + # + # @abstract + def file_modified + end + + # Will be called when the file is deleted. Supposed to be redefined by subclasses. + # When the file is deleted, stop_watching will be called after this to make sure everything is + # cleaned up correctly. + # + # @note On Linux (with {http://en.wikipedia.org/wiki/Inotify inotify}), this method will not be called until *all* open file descriptors to + # the file have been closed. + # + # @abstract + def file_deleted + end + + # Will be called when the file is moved or renamed. Supposed to be redefined by subclasses. + # + # @abstract + def file_moved + end + + # Discontinue monitoring of the file. + # + # This involves cleaning up the underlying monitoring details with kqueue/inotify, and in turn firing {EventMachine::Connection#unbind}. + # This will be called automatically when a file is deleted. User code may call it as well. + def stop_watching + EventMachine::unwatch_filename(@signature) + end # stop_watching + end # FileWatch +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb new file mode 100644 index 0000000..4affbf5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/future.rb @@ -0,0 +1,61 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 Jul 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +#-- +# This defines EventMachine::Deferrable#future, which requires +# that the rest of EventMachine::Deferrable has already been seen. +# (It's in deferrable.rb.) + +module EventMachine + module Deferrable + + # A future is a sugaring of a typical deferrable usage. + #-- + # Evaluate arg (which may be an expression or a block). + # What's the class of arg? + # If arg is an ordinary expression, then return it. + # If arg is deferrable (responds to :set_deferred_status), + # then look at the arguments. If either callback or errback + # are defined, then use them. If neither are defined, then + # use the supplied block (if any) as the callback. + # Then return arg. + def self.future arg, cb=nil, eb=nil, &blk + arg = arg.call if arg.respond_to?(:call) + + if arg.respond_to?(:set_deferred_status) + if cb || eb + arg.callback(&cb) if cb + arg.errback(&eb) if eb + else + arg.callback(&blk) if blk + end + end + + arg + end + + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb new file mode 100644 index 0000000..a30b9dd --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/iterator.rb @@ -0,0 +1,252 @@ +module EventMachine + # A simple iterator for concurrent asynchronous work. + # + # Unlike ruby's built-in iterators, the end of the current iteration cycle is signaled manually, + # instead of happening automatically after the yielded block finishes executing. For example: + # + # (0..10).each{ |num| } + # + # becomes: + # + # EM::Iterator.new(0..10).each{ |num,iter| iter.next } + # + # This is especially useful when doing asynchronous work via reactor libraries and + # functions. For example, given a sync and async http api: + # + # response = sync_http_get(url); ... + # async_http_get(url){ |response| ... } + # + # a synchronous iterator such as: + # + # responses = urls.map{ |url| sync_http_get(url) } + # ... + # puts 'all done!' + # + # could be written as: + # + # EM::Iterator.new(urls).map(proc{ |url,iter| + # async_http_get(url){ |res| + # iter.return(res) + # } + # }, proc{ |responses| + # ... + # puts 'all done!' + # }) + # + # Now, you can take advantage of the asynchronous api to issue requests in parallel. For example, + # to fetch 10 urls at a time, simply pass in a concurrency of 10: + # + # EM::Iterator.new(urls, 10).each do |url,iter| + # async_http_get(url){ iter.next } + # end + # + class Iterator + Stop = "EM::Stop" + # Create a new parallel async iterator with specified concurrency. + # + # i = EM::Iterator.new(1..100, 10) + # + # will create an iterator over the range that processes 10 items at a time. Iteration + # is started via #each, #map or #inject + # + # The list may either be an array-like object, or a proc that returns a new object + # to be processed each time it is called. If a proc is used, it must return + # EventMachine::Iterator::Stop to signal the end of the iterations. + # + def initialize(list, concurrency = 1) + raise ArgumentError, 'concurrency must be bigger than zero' unless (concurrency > 0) + if list.respond_to?(:call) + @list = nil + @list_proc = list + elsif list.respond_to?(:to_a) + @list = list.to_a.dup + @list_proc = nil + else + raise ArgumentError, 'argument must be a proc or an array' + end + @concurrency = concurrency + + @started = false + @ended = false + end + + # Change the concurrency of this iterator. Workers will automatically be spawned or destroyed + # to accomodate the new concurrency level. + # + def concurrency=(val) + old = @concurrency + @concurrency = val + + spawn_workers if val > old and @started and !@ended + end + attr_reader :concurrency + + # Iterate over a set of items using the specified block or proc. + # + # EM::Iterator.new(1..100).each do |num, iter| + # puts num + # iter.next + # end + # + # An optional second proc is invoked after the iteration is complete. + # + # EM::Iterator.new(1..100).each( + # proc{ |num,iter| iter.next }, + # proc{ puts 'all done' } + # ) + # + def each(foreach=nil, after=nil, &blk) + raise ArgumentError, 'proc or block required for iteration' unless foreach ||= blk + raise RuntimeError, 'cannot iterate over an iterator more than once' if @started or @ended + + @started = true + @pending = 0 + @workers = 0 + + all_done = proc{ + after.call if after and @ended and @pending == 0 + } + + @process_next = proc{ + # p [:process_next, :pending=, @pending, :workers=, @workers, :ended=, @ended, :concurrency=, @concurrency, :list=, @list] + unless @ended or @workers > @concurrency + item = next_item() + if item.equal?(Stop) + @ended = true + @workers -= 1 + all_done.call + else + @pending += 1 + + is_done = false + on_done = proc{ + raise RuntimeError, 'already completed this iteration' if is_done + is_done = true + + @pending -= 1 + + if @ended + all_done.call + else + EM.next_tick(@process_next) + end + } + class << on_done + alias :next :call + end + + foreach.call(item, on_done) + end + else + @workers -= 1 + end + } + + spawn_workers + + self + end + + # Collect the results of an asynchronous iteration into an array. + # + # EM::Iterator.new(%w[ pwd uptime uname date ], 2).map(proc{ |cmd,iter| + # EM.system(cmd){ |output,status| + # iter.return(output) + # } + # }, proc{ |results| + # p results + # }) + # + def map(foreach, after) + index = 0 + + inject([], proc{ |results,item,iter| + i = index + index += 1 + + is_done = false + on_done = proc{ |res| + raise RuntimeError, 'already returned a value for this iteration' if is_done + is_done = true + + results[i] = res + iter.return(results) + } + class << on_done + alias :return :call + def next + raise NoMethodError, 'must call #return on a map iterator' + end + end + + foreach.call(item, on_done) + }, proc{ |results| + after.call(results) + }) + end + + # Inject the results of an asynchronous iteration onto a given object. + # + # EM::Iterator.new(%w[ pwd uptime uname date ], 2).inject({}, proc{ |hash,cmd,iter| + # EM.system(cmd){ |output,status| + # hash[cmd] = status.exitstatus == 0 ? output.strip : nil + # iter.return(hash) + # } + # }, proc{ |results| + # p results + # }) + # + def inject(obj, foreach, after) + each(proc{ |item,iter| + is_done = false + on_done = proc{ |res| + raise RuntimeError, 'already returned a value for this iteration' if is_done + is_done = true + + obj = res + iter.next + } + class << on_done + alias :return :call + def next + raise NoMethodError, 'must call #return on an inject iterator' + end + end + + foreach.call(obj, item, on_done) + }, proc{ + after.call(obj) + }) + end + + private + + # Spawn workers to consume items from the iterator's enumerator based on the current concurrency level. + # + def spawn_workers + EM.next_tick(start_worker = proc{ + if @workers < @concurrency and !@ended + # p [:spawning_worker, :workers=, @workers, :concurrency=, @concurrency, :ended=, @ended] + @workers += 1 + @process_next.call + EM.next_tick(start_worker) + end + }) + nil + end + + # Return the next item from @list or @list_proc. + # Once items have run out, will return EM::Iterator::Stop. Procs must supply this themselves + def next_item + if @list_proc + @list_proc.call + else + @list.empty? ? Stop : @list.shift + end + end + end +end + +# TODO: pass in one object instead of two? .each{ |iter| puts iter.current; iter.next } +# TODO: support iter.pause/resume/stop/break/continue? +# TODO: create some exceptions instead of using RuntimeError diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb new file mode 100644 index 0000000..9a51c39 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/messages.rb @@ -0,0 +1,66 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 Jul 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +=begin + +Message Routing in EventMachine. + +The goal here is to enable "routing points," objects that can send and receive +"messages," which are delimited streams of bytes. The boundaries of a message +are preserved as it passes through the reactor system. + +There will be several module methods defined in EventMachine to create route-point +objects (which will probably have a base class of EventMachine::MessageRouter +until someone suggests a better name). + +As with I/O objects, routing objects will receive events by having the router +core call methods on them. And of course user code can and will define handlers +to deal with events of interest. + +The message router base class only really needs a receive_message method. There will +be an EM module-method to send messages, in addition to the module methods to create +the various kinds of message receivers. + +The simplest kind of message receiver object can receive messages by being named +explicitly in a parameter to EM#send_message. More sophisticated receivers can define +pub-sub selectors and message-queue names. And they can also define channels for +route-points in other processes or even on other machines. + +A message is NOT a marshallable entity. Rather, it's a chunk of flat content more like +an Erlang message. Initially, all content submitted for transmission as a message will +have the to_s method called on it. Eventually, we'll be able to transmit certain structured +data types (XML and YAML documents, Structs within limits) and have them reconstructed +on the other end. + +A fundamental goal of the message-routing capability is to interoperate seamlessly with +external systems, including non-Ruby systems like ActiveMQ. We will define various protocol +handlers for things like Stomp and possibly AMQP, but these will be wrapped up and hidden +from the users of the basic routing capability. + +As with Erlang, a critical goal is for programs that are built to use message-passing to work +WITHOUT CHANGE when the code is re-based on a multi-process system. + +=end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb new file mode 100644 index 0000000..2cb3662 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pool.rb @@ -0,0 +1,151 @@ +module EventMachine + # A simple async resource pool based on a resource and work queue. Resources + # are enqueued and work waits for resources to become available. + # + # @example + # require 'em-http-request' + # + # EM.run do + # pool = EM::Pool.new + # spawn = lambda { pool.add EM::HttpRequest.new('http://example.org') } + # 10.times { spawn[] } + # done, scheduled = 0, 0 + # + # check = lambda do + # done += 1 + # if done >= scheduled + # EM.stop + # end + # end + # + # pool.on_error { |conn| spawn[] } + # + # 100.times do |i| + # scheduled += 1 + # pool.perform do |conn| + # req = conn.get :path => '/', :keepalive => true + # + # req.callback do + # p [:success, conn.object_id, i, req.response.size] + # check[] + # end + # + # req.errback { check[] } + # + # req + # end + # end + # end + # + # Resources are expected to be controlled by an object responding to a + # deferrable/completion style API with callback and errback blocks. + # + class Pool + + def initialize + @resources = EM::Queue.new + @removed = [] + @contents = [] + @on_error = nil + end + + def add resource + @contents << resource + requeue resource + end + + def remove resource + @contents.delete resource + @removed << resource + end + + # Returns a list for introspection purposes only. You should *NEVER* call + # modification or work oriented methods on objects in this list. A good + # example use case is periodic statistics collection against a set of + # connection resources. + # + # @example + # pool.contents.inject(0) { |sum, connection| connection.num_bytes } + def contents + @contents.dup + end + + # Define a default catch-all for when the deferrables returned by work + # blocks enter a failed state. By default all that happens is that the + # resource is returned to the pool. If on_error is defined, this block is + # responsible for re-adding the resource to the pool if it is still usable. + # In other words, it is generally assumed that on_error blocks explicitly + # handle the rest of the lifetime of the resource. + def on_error *a, &b + @on_error = EM::Callback(*a, &b) + end + + # Perform a given #call-able object or block. The callable object will be + # called with a resource from the pool as soon as one is available, and is + # expected to return a deferrable. + # + # The deferrable will have callback and errback added such that when the + # deferrable enters a finished state, the object is returned to the pool. + # + # If on_error is defined, then objects are not automatically returned to the + # pool. + def perform(*a, &b) + work = EM::Callback(*a, &b) + + @resources.pop do |resource| + if removed? resource + @removed.delete resource + reschedule work + else + process work, resource + end + end + end + alias reschedule perform + + # A peek at the number of enqueued jobs waiting for resources + def num_waiting + @resources.num_waiting + end + + # Removed will show resources in a partial pruned state. Resources in the + # removed list may not appear in the contents list if they are currently in + # use. + def removed? resource + @removed.include? resource + end + + protected + def requeue resource + @resources.push resource + end + + def failure resource + if @on_error + @contents.delete resource + @on_error.call resource + # Prevent users from calling a leak. + @removed.delete resource + else + requeue resource + end + end + + def completion deferrable, resource + deferrable.callback { requeue resource } + deferrable.errback { failure resource } + end + + def process work, resource + deferrable = work.call resource + if deferrable.kind_of?(EM::Deferrable) + completion deferrable, resource + else + raise ArgumentError, "deferrable expected from work" + end + rescue + failure resource + raise + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb new file mode 100644 index 0000000..66e8943 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/process_watch.rb @@ -0,0 +1,45 @@ +module EventMachine + + # This is subclassed from EventMachine::Connection for use with the process monitoring API. Read the + # documentation on the instance methods of this class, and for a full explanation see EventMachine.watch_process. + class ProcessWatch < Connection + # @private + Cfork = 'fork'.freeze + # @private + Cexit = 'exit'.freeze + + # @private + def receive_data(data) + case data + when Cfork + process_forked + when Cexit + process_exited + end + end + + # Returns the pid that EventMachine::watch_process was originally called with. + def pid + @pid + end + + # Should be redefined with the user's custom callback that will be fired when the prcess is forked. + # + # There is currently not an easy way to get the pid of the forked child. + def process_forked + end + + # Should be redefined with the user's custom callback that will be fired when the process exits. + # + # stop_watching is called automatically after this callback + def process_exited + end + + # Discontinue monitoring of the process. + # This will be called automatically when a process dies. User code may call it as well. + def stop_watching + EventMachine::unwatch_pid(@signature) + end + end + +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb new file mode 100644 index 0000000..4bbc14f --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/processes.rb @@ -0,0 +1,123 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 13 Dec 07 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-08 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + + +module EventMachine + + # EM::DeferrableChildProcess is a sugaring of a common use-case + # involving EM::popen. + # Call the #open method on EM::DeferrableChildProcess, passing + # a command-string. #open immediately returns an EM::Deferrable + # object. It also schedules the forking of a child process, which + # will execute the command passed to #open. + # When the forked child terminates, the Deferrable will be signalled + # and execute its callbacks, passing the data that the child process + # wrote to stdout. + # + class DeferrableChildProcess < EventMachine::Connection + include EventMachine::Deferrable + + # @private + def initialize + super + @data = [] + end + + # Sugars a common use-case involving forked child processes. + # #open takes a String argument containing an shell command + # string (including arguments if desired). #open immediately + # returns an EventMachine::Deferrable object, without blocking. + # + # It also invokes EventMachine#popen to run the passed-in + # command in a forked child process. + # + # When the forked child terminates, the Deferrable that + # #open calls its callbacks, passing the data returned + # from the child process. + # + def self.open cmd + EventMachine.popen( cmd, DeferrableChildProcess ) + end + + # @private + def receive_data data + @data << data + end + + # @private + def unbind + succeed( @data.join ) + end + end + + # @private + class SystemCmd < EventMachine::Connection + def initialize cb + @cb = cb + @output = [] + end + def receive_data data + @output << data + end + def unbind + @cb.call @output.join(''), get_status if @cb + end + end + + # EM::system is a simple wrapper for EM::popen. It is similar to Kernel::system, but requires a + # single string argument for the command and performs no shell expansion. + # + # The block or proc passed to EM::system is called with two arguments: the output generated by the command, + # and a Process::Status that contains information about the command's execution. + # + # EM.run{ + # EM.system('ls'){ |output,status| puts output if status.exitstatus == 0 } + # } + # + # You can also supply an additional proc to send some data to the process: + # + # EM.run{ + # EM.system('sh', proc{ |process| + # process.send_data("echo hello\n") + # process.send_data("exit\n") + # }, proc{ |out,status| + # puts(out) + # }) + # } + # + # Like EventMachine.popen, EventMachine.system currently does not work on windows. + # It returns the pid of the spawned process. + def EventMachine::system cmd, *args, &cb + cb ||= args.pop if args.last.is_a? Proc + init = args.pop if args.last.is_a? Proc + + # merge remaining arguments into the command + cmd = [cmd, *args] if args.any? + + EM.get_subprocess_pid(EM.popen(cmd, SystemCmd, cb) do |c| + init[c] if init + end.signature) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb new file mode 100644 index 0000000..0c17906 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols.rb @@ -0,0 +1,37 @@ +module EventMachine + # This module contains various protocol implementations, including: + # - HttpClient and HttpClient2 + # - Stomp + # - Memcache + # - SmtpClient and SmtpServer + # - SASLauth and SASLauthclient + # - LineProtocol, LineAndTextProtocol and LineText2 + # - HeaderAndContentProtocol + # - Postgres3 + # - ObjectProtocol + # + # The protocol implementations live in separate files in the protocols/ subdirectory, + # but are auto-loaded when they are first referenced in your application. + # + # EventMachine::Protocols is also aliased to EM::P for easier usage. + # + module Protocols + # TODO : various autotools are completely useless with the lack of naming + # convention, we need to correct that! + autoload :TcpConnectTester, 'em/protocols/tcptest' + autoload :HttpClient, 'em/protocols/httpclient' + autoload :HttpClient2, 'em/protocols/httpclient2' + autoload :LineAndTextProtocol, 'em/protocols/line_and_text' + autoload :HeaderAndContentProtocol, 'em/protocols/header_and_content' + autoload :LineText2, 'em/protocols/linetext2' + autoload :Stomp, 'em/protocols/stomp' + autoload :SmtpClient, 'em/protocols/smtpclient' + autoload :SmtpServer, 'em/protocols/smtpserver' + autoload :SASLauth, 'em/protocols/saslauth' + autoload :Memcache, 'em/protocols/memcache' + autoload :Postgres3, 'em/protocols/postgres3' + autoload :ObjectProtocol, 'em/protocols/object_protocol' + autoload :Socks4, 'em/protocols/socks4' + autoload :LineProtocol, 'em/protocols/line_protocol' + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb new file mode 100644 index 0000000..b5a465a --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/header_and_content.rb @@ -0,0 +1,138 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 Nov 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # === Usage + # + # class RequestHandler < EM::P::HeaderAndContentProtocol + # def receive_request headers, content + # p [:request, headers, content] + # end + # end + # + # EM.run{ + # EM.start_server 'localhost', 80, RequestHandler + # } + # + #-- + # Originally, this subclassed LineAndTextProtocol, which in + # turn relies on BufferedTokenizer, which doesn't gracefully + # handle the transitions between lines and binary text. + # Changed 13Sep08 by FCianfrocca. + class HeaderAndContentProtocol < Connection + include LineText2 + + ContentLengthPattern = /Content-length:\s*(\d+)/i + + def initialize *args + super + init_for_request + end + + def receive_line line + case @hc_mode + when :discard_blanks + unless line == "" + @hc_mode = :headers + receive_line line + end + when :headers + if line == "" + raise "unrecognized state" unless @hc_headers.length > 0 + if respond_to?(:receive_headers) + receive_headers @hc_headers + end + # @hc_content_length will be nil, not 0, if there was no content-length header. + if @hc_content_length.to_i > 0 + set_binary_mode @hc_content_length + else + dispatch_request + end + else + @hc_headers << line + if ContentLengthPattern =~ line + # There are some attacks that rely on sending multiple content-length + # headers. This is a crude protection, but needs to become tunable. + raise "extraneous content-length header" if @hc_content_length + @hc_content_length = $1.to_i + end + if @hc_headers.length == 1 and respond_to?(:receive_first_header_line) + receive_first_header_line line + end + end + else + raise "internal error, unsupported mode" + end + end + + def receive_binary_data text + @hc_content = text + dispatch_request + end + + def dispatch_request + if respond_to?(:receive_request) + receive_request @hc_headers, @hc_content + end + init_for_request + end + private :dispatch_request + + def init_for_request + @hc_mode = :discard_blanks + @hc_headers = [] + # originally was @hc_headers ||= []; @hc_headers.clear to get a performance + # boost, but it's counterproductive because a subclassed handler will have to + # call dup to use the header array we pass in receive_headers. + + @hc_content_length = nil + @hc_content = "" + end + private :init_for_request + + # Basically a convenience method. We might create a subclass that does this + # automatically. But it's such a performance killer. + def headers_2_hash hdrs + self.class.headers_2_hash hdrs + end + + class << self + def headers_2_hash hdrs + hash = {} + hdrs.each {|h| + if /\A([^\s:]+)\s*:\s*/ =~ h + tail = $'.dup + hash[ $1.downcase.gsub(/-/,"_").intern ] = tail + end + } + hash + end + end + + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb new file mode 100644 index 0000000..38b175c --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient.rb @@ -0,0 +1,300 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # <b>Note:</b> This class is deprecated and will be removed. Please use EM-HTTP-Request instead. + # + # @example + # EventMachine.run { + # http = EventMachine::Protocols::HttpClient.request( + # :host => server, + # :port => 80, + # :request => "/index.html", + # :query_string => "parm1=value1&parm2=value2" + # ) + # http.callback {|response| + # puts response[:status] + # puts response[:headers] + # puts response[:content] + # } + # } + #-- + # TODO: + # Add streaming so we can support enormous POSTs. Current max is 20meg. + # Timeout for connections that run too long or hang somewhere in the middle. + # Persistent connections (HTTP/1.1), may need a associated delegate object. + # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's + # DNS lookups are unbelievably slow. + # HEAD requests. + # Convenience methods for requests. get, post, url, etc. + # SSL. + # Handle status codes like 304, 100, etc. + # Refactor this code so that protocol errors all get handled one way (an exception?), + # instead of sprinkling set_deferred_status :failed calls everywhere. + class HttpClient < Connection + include EventMachine::Deferrable + + MaxPostContentLength = 20 * 1024 * 1024 + + def initialize + warn "HttpClient is deprecated and will be removed. EM-Http-Request should be used instead." + @connected = false + end + + # @param args [Hash] The request arguments + # @option args [String] :host The host IP/DNS name + # @option args [Integer] :port The port to connect too + # @option args [String] :verb The request type [GET | POST | DELETE | PUT] + # @option args [String] :request The request path + # @option args [Hash] :basic_auth The basic auth credentials (:username and :password) + # @option args [String] :content The request content + # @option args [String] :contenttype The content type (e.g. text/plain) + # @option args [String] :query_string The query string + # @option args [String] :host_header The host header to set + # @option args [String] :cookie Cookies to set + def self.request( args = {} ) + args[:port] ||= 80 + EventMachine.connect( args[:host], args[:port], self ) {|c| + # According to the docs, we will get here AFTER post_init is called. + c.instance_eval {@args = args} + } + end + + def post_init + @start_time = Time.now + @data = "" + @read_state = :base + end + + # We send the request when we get a connection. + # AND, we set an instance variable to indicate we passed through here. + # That allows #unbind to know whether there was a successful connection. + # NB: This naive technique won't work when we have to support multiple + # requests on a single connection. + def connection_completed + @connected = true + send_request @args + end + + def send_request args + args[:verb] ||= args[:method] # Support :method as an alternative to :verb. + args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified? + + verb = args[:verb].to_s.upcase + unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb) + set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type + return # NOTE THE EARLY RETURN, we're not sending any data. + end + + request = args[:request] || "/" + unless request[0,1] == "/" + request = "/" + request + end + + qs = args[:query_string] || "" + if qs.length > 0 and qs[0,1] != '?' + qs = "?" + qs + end + + version = args[:version] || "1.1" + + # Allow an override for the host header if it's not the connect-string. + host = args[:host_header] || args[:host] || "_" + # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default. + port = args[:port].to_i != 80 ? ":#{args[:port]}" : "" + + # POST items. + postcontenttype = args[:contenttype] || "application/octet-stream" + postcontent = args[:content] || "" + raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength + + # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise. + # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption. + req = [ + "#{verb} #{request}#{qs} HTTP/#{version}", + "Host: #{host}#{port}", + "User-agent: Ruby EventMachine", + ] + + if verb == "POST" || verb == "PUT" + req << "Content-type: #{postcontenttype}" + req << "Content-length: #{postcontent.length}" + end + + # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string. + # Eventually we will want to deal intelligently with arrays and hashes. + if args[:cookie] + req << "Cookie: #{args[:cookie]}" + end + + # Allow custom HTTP headers, e.g. SOAPAction + args[:custom_headers].each do |k,v| + req << "#{k}: #{v}" + end if args[:custom_headers] + + # Basic-auth stanza contributed by Matt Murphy. + if args[:basic_auth] + basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'') + req << "Authorization: Basic #{basic_auth_string}" + end + + req << "" + reqstring = req.map {|l| "#{l}\r\n"}.join + send_data reqstring + + if verb == "POST" || verb == "PUT" + send_data postcontent + end + end + + + def receive_data data + while data and data.length > 0 + case @read_state + when :base + # Perform any per-request initialization here and don't consume any data. + @data = "" + @headers = [] + @content_length = nil # not zero + @content = "" + @status = nil + @chunked = false + @chunk_length = nil + @read_state = :header + @connection_close = nil + when :header + ary = data.split( /\r?\n/m, 2 ) + if ary.length == 2 + data = ary.last + if ary.first == "" + if (@content_length and @content_length > 0) || @chunked || @connection_close + @read_state = :content + else + dispatch_response + @read_state = :base + end + else + @headers << ary.first + if @headers.length == 1 + parse_response_line + elsif ary.first =~ /\Acontent-length:\s*/i + # Only take the FIRST content-length header that appears, + # which we can distinguish because @content_length is nil. + # TODO, it's actually a fatal error if there is more than one + # content-length header, because the caller is presumptively + # a bad guy. (There is an exploit that depends on multiple + # content-length headers.) + @content_length ||= $'.to_i + elsif ary.first =~ /\Aconnection:\s*close/i + @connection_close = true + elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i + @chunked = true + end + end + else + @data << data + data = "" + end + when :content + if @chunked && @chunk_length + bytes_needed = @chunk_length - @chunk_read + new_data = data[0, bytes_needed] + @chunk_read += new_data.length + @content += new_data + data = data[bytes_needed..-1] || "" + if @chunk_length == @chunk_read && data[0,2] == "\r\n" + @chunk_length = nil + data = data[2..-1] + end + elsif @chunked + if (m = data.match(/\A(\S*)\r\n/m)) + data = data[m[0].length..-1] + @chunk_length = m[1].to_i(16) + @chunk_read = 0 + if @chunk_length == 0 + dispatch_response + @read_state = :base + end + end + elsif @content_length + # If there was no content-length header, we have to wait until the connection + # closes. Everything we get until that point is content. + # TODO: Must impose a content-size limit, and also must implement chunking. + # Also, must support either temporary files for large content, or calling + # a content-consumer block supplied by the user. + bytes_needed = @content_length - @content.length + @content += data[0, bytes_needed] + data = data[bytes_needed..-1] || "" + if @content_length == @content.length + dispatch_response + @read_state = :base + end + else + @content << data + data = "" + end + end + end + end + + + # We get called here when we have received an HTTP response line. + # It's an opportunity to throw an exception or trigger other exceptional + # handling. + def parse_response_line + if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/ + @status = $1.to_i + else + set_deferred_status :failed, { + :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this. + } + close_connection + end + end + private :parse_response_line + + def dispatch_response + @read_state = :base + set_deferred_status :succeeded, { + :content => @content, + :headers => @headers, + :status => @status + } + # TODO, we close the connection for now, but this is wrong for persistent clients. + close_connection + end + + def unbind + if !@connected + set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error. + elsif (@read_state == :content and @content_length == nil) + dispatch_response + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb new file mode 100644 index 0000000..0fb64e8 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/httpclient2.rb @@ -0,0 +1,600 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # <b>Note:</b> This class is deprecated and will be removed. Please use EM-HTTP-Request instead. + # + # === Usage + # + # EM.run{ + # conn = EM::Protocols::HttpClient2.connect 'google.com', 80 + # + # req = conn.get('/') + # req.callback{ |response| + # p(response.status) + # p(response.headers) + # p(response.content) + # } + # } + class HttpClient2 < Connection + include LineText2 + + def initialize + warn "HttpClient2 is deprecated and will be removed. EM-Http-Request should be used instead." + + @authorization = nil + @closed = nil + @requests = nil + end + + # @private + class Request + include Deferrable + + attr_reader :version + attr_reader :status + attr_reader :header_lines + attr_reader :headers + attr_reader :content + attr_reader :internal_error + + def initialize conn, args + @conn = conn + @args = args + @header_lines = [] + @headers = {} + @blanks = 0 + @chunk_trailer = nil + @chunking = nil + end + + def send_request + az = @args[:authorization] and az = "Authorization: #{az}\r\n" + + r = [ + "#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n", + "Host: #{@args[:host_header] || "_"}\r\n", + az || "", + "\r\n" + ] + @conn.send_data r.join + end + + + #-- + # + def receive_line ln + if @chunk_trailer + receive_chunk_trailer(ln) + elsif @chunking + receive_chunk_header(ln) + else + receive_header_line(ln) + end + end + + #-- + # + def receive_chunk_trailer ln + if ln.length == 0 + @conn.pop_request + succeed(self) + else + p "Received chunk trailer line" + end + end + + #-- + # Allow up to ten blank lines before we get a real response line. + # Allow no more than 100 lines in the header. + # + def receive_header_line ln + if ln.length == 0 + if @header_lines.length > 0 + process_header + else + @blanks += 1 + if @blanks > 10 + @conn.close_connection + end + end + else + @header_lines << ln + if @header_lines.length > 100 + @internal_error = :bad_header + @conn.close_connection + end + end + end + + #-- + # Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks. + # + def receive_chunk_header ln + if ln.length > 0 + chunksize = ln.to_i(16) + if chunksize > 0 + @conn.set_text_mode(ln.to_i(16)) + else + @content = @content ? @content.join : '' + @chunk_trailer = true + end + else + # We correctly come here after each chunk gets read. + # p "Got A BLANK chunk line" + end + + end + + + #-- + # We get a single chunk. Append it to the incoming content and switch back to line mode. + # + def receive_chunked_text text + # p "RECEIVED #{text.length} CHUNK" + (@content ||= []) << text + end + + + #-- + # TODO, inefficient how we're handling this. Part of it is done so as to + # make sure we don't have problems in detecting chunked-encoding, content-length, + # etc. + # + HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i + ClenRE = /\AContent-length:\s*(\d+)/i + ChunkedRE = /\ATransfer-encoding:\s*chunked/i + ColonRE = /\:\s*/ + + def process_header + unless @header_lines.first =~ HttpResponseRE + @conn.close_connection + @internal_error = :bad_request + end + @version = $1.dup + @status = $2.dup.to_i + + clen = nil + chunks = nil + @header_lines.each_with_index do |e,ix| + if ix > 0 + hdr,val = e.split(ColonRE,2) + (@headers[hdr.downcase] ||= []) << val + end + + if clen == nil and e =~ ClenRE + clen = $1.dup.to_i + end + if e =~ ChunkedRE + chunks = true + end + end + + if clen + # If the content length is zero we should not call set_text_mode, + # because a value of zero will make it wait forever, hanging the + # connection. Just return success instead, with empty content. + if clen == 0 then + @content = "" + @conn.pop_request + succeed(self) + else + @conn.set_text_mode clen + end + elsif chunks + @chunking = true + else + # Chunked transfer, multipart, or end-of-connection. + # For end-of-connection, we need to go the unbind + # method and suppress its desire to fail us. + p "NO CLEN" + p @args[:uri] + p @header_lines + @internal_error = :unsupported_clen + @conn.close_connection + end + end + private :process_header + + + def receive_text text + @chunking ? receive_chunked_text(text) : receive_sized_text(text) + end + + #-- + # At the present time, we only handle contents that have a length + # specified by the content-length header. + # + def receive_sized_text text + @content = text + @conn.pop_request + succeed(self) + end + end + + # Make a connection to a remote HTTP server. + # Can take either a pair of arguments (which will be interpreted as + # a hostname/ip-address and a port), or a hash. + # If the arguments are a hash, then supported values include: + # :host => a hostname or ip-address + # :port => a port number + # :ssl => true to enable ssl + def self.connect *args + if args.length == 2 + args = {:host=>args[0], :port=>args[1]} + else + args = args.first + end + + h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl]) + conn = EM.connect( h, prt, self ) + conn.start_tls if ssl + conn.set_default_host_header( h, prt, ssl ) + conn + end + + # Get a url + # + # req = conn.get(:uri => '/') + # req.callback{|response| puts response.content } + # + def get args + if args.is_a?(String) + args = {:uri=>args} + end + args[:verb] = "GET" + request args + end + + # Post to a url + # + # req = conn.post('/data') + # req.callback{|response| puts response.content } + #-- + # XXX there's no way to supply a POST body.. wtf? + def post args + if args.is_a?(String) + args = {:uri=>args} + end + args[:verb] = "POST" + request args + end + + + #-- + # Compute and remember a string to be used as the host header in HTTP requests + # unless the user overrides it with an argument to #request. + # + # @private + def set_default_host_header host, port, ssl + if (ssl and port != 443) or (!ssl and port != 80) + @host_header = "#{host}:#{port}" + else + @host_header = host + end + end + + + # @private + def post_init + super + @connected = EM::DefaultDeferrable.new + end + + # @private + def connection_completed + super + @connected.succeed + end + + #-- + # All pending requests, if any, must fail. + # We might come here without ever passing through connection_completed + # in case we can't connect to the server. We'll also get here when the + # connection closes (either because the server closes it, or we close it + # due to detecting an internal error or security violation). + # In either case, run down all pending requests, if any, and signal failure + # on them. + # + # Set and remember a flag (@closed) so we can immediately fail any + # subsequent requests. + # + # @private + def unbind + super + @closed = true + (@requests || []).each {|r| r.fail} + end + + # @private + def request args + args[:host_header] = @host_header unless args.has_key?(:host_header) + args[:authorization] = @authorization unless args.has_key?(:authorization) + r = Request.new self, args + if @closed + r.fail + else + (@requests ||= []).unshift r + @connected.callback {r.send_request} + end + r + end + + # @private + def receive_line ln + if req = @requests.last + req.receive_line ln + else + p "??????????" + p ln + end + end + + # @private + def receive_binary_data text + @requests.last.receive_text text + end + + #-- + # Called by a Request object when it completes. + # + # @private + def pop_request + @requests.pop + end + end + + +=begin + class HttpClient2x < Connection + include LineText2 + + # TODO: Make this behave appropriate in case a #connect fails. + # Currently, this produces no errors. + + # Make a connection to a remote HTTP server. + # Can take either a pair of arguments (which will be interpreted as + # a hostname/ip-address and a port), or a hash. + # If the arguments are a hash, then supported values include: + # :host => a hostname or ip-address; + # :port => a port number + #-- + # TODO, support optional encryption arguments like :ssl + def self.connect *args + if args.length == 2 + args = {:host=>args[0], :port=>args[1]} + else + args = args.first + end + + h,prt = args[:host],Integer(args[:port]) + EM.connect( h, prt, self, h, prt ) + end + + + #-- + # Sugars a connection that makes a single request and then + # closes the connection. Matches the behavior and the arguments + # of the original implementation of class HttpClient. + # + # Intended primarily for back compatibility, but the idiom + # is probably useful so it's not deprecated. + # We return a Deferrable, as did the original implementation. + # + # Because we're improving the way we deal with errors and exceptions + # (specifically, HTTP response codes other than 2xx will trigger the + # errback rather than the callback), this may break some existing code. + # + def self.request args + c = connect args + end + + #-- + # Requests can be pipelined. When we get a request, add it to the + # front of a queue as an array. The last element of the @requests + # array is always the oldest request received. Each element of the + # @requests array is a two-element array consisting of a hash with + # the original caller's arguments, and an initially-empty Ostruct + # containing the data we retrieve from the server's response. + # Maintain the instance variable @current_response, which is the response + # of the oldest pending request. That's just to make other code a little + # easier. If the variable doesn't exist when we come here, we're + # obviously the first request being made on the connection. + # + # The reason for keeping this method private (and requiring use of the + # convenience methods #get, #post, #head, etc) is to avoid the small + # performance penalty of canonicalizing the verb. + # + def request args + d = EventMachine::DefaultDeferrable.new + + if @closed + d.fail + return d + end + + o = OpenStruct.new + o.deferrable = d + (@requests ||= []).unshift [args, o] + @current_response ||= @requests.last.last + @connected.callback { + az = args[:authorization] and az = "Authorization: #{az}\r\n" + + r = [ + "#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n", + "Host: #{args[:host_header] || @host_header}\r\n", + az || "", + "\r\n" + ] + p r + send_data r.join + } + o.deferrable + end + private :request + + def get args + if args.is_a?(String) + args = {:uri=>args} + end + args[:verb] = "GET" + request args + end + + def initialize host, port + super + @host_header = "#{host}:#{port}" + end + def post_init + super + @connected = EM::DefaultDeferrable.new + end + + + def connection_completed + super + @connected.succeed + end + + #-- + # Make sure to throw away any leftover incoming data if we've + # been closed due to recognizing an error. + # + # Generate an internal error if we get an unreasonable number of + # header lines. It could be malicious. + # + def receive_line ln + p ln + return if @closed + + if ln.length > 0 + (@current_response.headers ||= []).push ln + abort_connection if @current_response.headers.length > 100 + else + process_received_headers + end + end + + #-- + # We come here when we've seen all the headers for a particular request. + # What we do next depends on the response line (which should be the + # first line in the header set), and whether there is content to read. + # We may transition into a text-reading state to read content, or + # we may abort the connection, or we may go right back into parsing + # responses for the next response in the chain. + # + # We make an ASSUMPTION that the first line is an HTTP response. + # Anything else produces an error that aborts the connection. + # This may not be enough, because it may be that responses to pipelined + # requests will come with a blank-line delimiter. + # + # Any non-2xx response will be treated as a fatal error, and abort the + # connection. We will set up the status and other response parameters. + # TODO: we will want to properly support 1xx responses, which some versions + # of IIS copiously generate. + # TODO: We need to give the option of not aborting the connection with certain + # non-200 responses, in order to work with NTLM and other authentication + # schemes that work at the level of individual connections. + # + # Some error responses will get sugarings. For example, we'll return the + # Location header in the response in case of a 301/302 response. + # + # Possible dispositions here: + # 1) No content to read (either content-length is zero or it's a HEAD request); + # 2) Switch to text mode to read a specific number of bytes; + # 3) Read a chunked or multipart response; + # 4) Read till the server closes the connection. + # + # Our reponse to the client can be either to wait till all the content + # has been read and then to signal caller's deferrable, or else to signal + # it when we finish the processing the headers and then expect the caller + # to have given us a block to call as the content comes in. And of course + # the latter gets stickier with chunks and multiparts. + # + HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i + ClenRE = /\AContent-length:\s*(\d+)/i + def process_received_headers + abort_connection unless @current_response.headers.first =~ HttpResponseRE + @current_response.version = $1.dup + st = $2.dup + @current_response.status = st.to_i + abort_connection unless st[0,1] == "2" + + clen = nil + @current_response.headers.each do |e| + if clen == nil and e =~ ClenRE + clen = $1.dup.to_i + end + end + + if clen + set_text_mode clen + end + end + private :process_received_headers + + + def receive_binary_data text + @current_response.content = text + @current_response.deferrable.succeed @current_response + @requests.pop + @current_response = (@requests.last || []).last + set_line_mode + end + + + + # We've received either a server error or an internal error. + # Close the connection and abort any pending requests. + #-- + # When should we call close_connection? It will cause #unbind + # to be fired. Should the user expect to see #unbind before + # we call #receive_http_error, or the other way around? + # + # Set instance variable @closed. That's used to inhibit further + # processing of any inbound data after an error has been recognized. + # + # We shouldn't have to worry about any leftover outbound data, + # because we call close_connection (not close_connection_after_writing). + # That ensures that any pipelined requests received after an error + # DO NOT get streamed out to the server on this connection. + # Very important. TODO, write a unit-test to establish that behavior. + # + def abort_connection + close_connection + @closed = true + @current_response.deferrable.fail( @current_response ) + end + + + #------------------------ + # Below here are user-overridable methods. + + end +=end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb new file mode 100644 index 0000000..784daf2 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_and_text.rb @@ -0,0 +1,125 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + # A protocol that handles line-oriented data with interspersed binary text. + # + # This version is optimized for performance. See EventMachine::Protocols::LineText2 + # for a version which is optimized for correctness with regard to binary text blocks + # that can switch back to line mode. + class LineAndTextProtocol < Connection + MaxBinaryLength = 32*1024*1024 + + def initialize *args + super + lbp_init_line_state + end + + def receive_data data + if @lbp_mode == :lines + begin + @lpb_buffer.extract(data).each do |line| + receive_line(line.chomp) if respond_to?(:receive_line) + end + rescue + receive_error('overlength line') if respond_to?(:receive_error) + close_connection + return + end + else + if @lbp_binary_limit > 0 + wanted = @lbp_binary_limit - @lbp_binary_bytes_received + chunk = nil + if data.length > wanted + chunk = data.slice!(0...wanted) + else + chunk = data + data = "" + end + @lbp_binary_buffer[@lbp_binary_bytes_received...(@lbp_binary_bytes_received+chunk.length)] = chunk + @lbp_binary_bytes_received += chunk.length + if @lbp_binary_bytes_received == @lbp_binary_limit + receive_binary_data(@lbp_binary_buffer) if respond_to?(:receive_binary_data) + lbp_init_line_state + end + receive_data(data) if data.length > 0 + else + receive_binary_data(data) if respond_to?(:receive_binary_data) + data = "" + end + end + end + + def unbind + if @lbp_mode == :binary and @lbp_binary_limit > 0 + if respond_to?(:receive_binary_data) + receive_binary_data( @lbp_binary_buffer[0...@lbp_binary_bytes_received] ) + end + end + end + + # Set up to read the supplied number of binary bytes. + # This recycles all the data currently waiting in the line buffer, if any. + # If the limit is nil, then ALL subsequent data will be treated as binary + # data and passed to the upstream protocol handler as we receive it. + # If a limit is given, we'll hold the incoming binary data and not + # pass it upstream until we've seen it all, or until there is an unbind + # (in which case we'll pass up a partial). + # Specifying nil for the limit (the default) means there is no limit. + # Specifiyng zero for the limit will cause an immediate transition back to line mode. + # + def set_binary_mode size = nil + if @lbp_mode == :lines + if size == 0 + receive_binary_data("") if respond_to?(:receive_binary_data) + # Do no more work here. Stay in line mode and keep consuming data. + else + @lbp_binary_limit = size.to_i # (nil will be stored as zero) + if @lbp_binary_limit > 0 + raise "Overlength" if @lbp_binary_limit > MaxBinaryLength # arbitrary sanity check + @lbp_binary_buffer = "\0" * @lbp_binary_limit + @lbp_binary_bytes_received = 0 + end + + @lbp_mode = :binary + receive_data @lpb_buffer.flush + end + else + raise "invalid operation" + end + end + + #-- + # For internal use, establish protocol baseline for handling lines. + def lbp_init_line_state + @lpb_buffer = BufferedTokenizer.new("\n") + @lbp_mode = :lines + end + private :lbp_init_line_state + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb new file mode 100644 index 0000000..dfddae8 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/line_protocol.rb @@ -0,0 +1,29 @@ +module EventMachine + module Protocols + # LineProtocol will parse out newline terminated strings from a receive_data stream + # + # module Server + # include EM::P::LineProtocol + # + # def receive_line(line) + # send_data("you said: #{line}") + # end + # end + # + module LineProtocol + # @private + def receive_data data + (@buf ||= '') << data + + while @buf.slice!(/(.*?)\r?\n/) + receive_line($1) + end + end + + # Invoked with lines received over the network + def receive_line(line) + # stub + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb new file mode 100644 index 0000000..9fdf28b --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/linetext2.rb @@ -0,0 +1,179 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + # In the grand, time-honored tradition of re-inventing the wheel, we offer + # here YET ANOTHER protocol that handles line-oriented data with interspersed + # binary text. This one trades away some of the performance optimizations of + # EventMachine::Protocols::LineAndTextProtocol in order to get better correctness + # with regard to binary text blocks that can switch back to line mode. It also + # permits the line-delimiter to change in midstream. + # This was originally written to support Stomp. + module LineText2 + # TODO! We're not enforcing the limits on header lengths and text-lengths. + # When we get around to that, call #receive_error if the user defined it, otherwise + # throw exceptions. + + MaxBinaryLength = 32*1024*1024 + + #-- + # Will loop internally until there's no data left to read. + # That way the user-defined handlers we call can modify the + # handling characteristics on a per-token basis. + # + def receive_data data + return unless (data and data.length > 0) + + # Do this stuff in lieu of a constructor. + @lt2_mode ||= :lines + @lt2_delimiter ||= "\n" + @lt2_linebuffer ||= [] + + remaining_data = data + + while remaining_data.length > 0 + if @lt2_mode == :lines + delimiter_string = case @lt2_delimiter + when Regexp + remaining_data.slice(@lt2_delimiter) + else + @lt2_delimiter + end + ix = remaining_data.index(delimiter_string) if delimiter_string + if ix + @lt2_linebuffer << remaining_data[0...ix] + ln = @lt2_linebuffer.join + @lt2_linebuffer.clear + if @lt2_delimiter == "\n" + ln.chomp! + end + receive_line ln + remaining_data = remaining_data[(ix+delimiter_string.length)..-1] + else + @lt2_linebuffer << remaining_data + remaining_data = "" + end + elsif @lt2_mode == :text + if @lt2_textsize + needed = @lt2_textsize - @lt2_textpos + will_take = if remaining_data.length > needed + needed + else + remaining_data.length + end + + @lt2_textbuffer << remaining_data[0...will_take] + tail = remaining_data[will_take..-1] + + @lt2_textpos += will_take + if @lt2_textpos >= @lt2_textsize + # Reset line mode (the default behavior) BEFORE calling the + # receive_binary_data. This makes it possible for user code + # to call set_text_mode, enabling chains of text blocks + # (which can possibly be of different sizes). + set_line_mode + receive_binary_data @lt2_textbuffer.join + receive_end_of_binary_data + end + + remaining_data = tail + else + receive_binary_data remaining_data + remaining_data = "" + end + end + end + end + + # The line delimiter may be a regular expression or a string. Anything + # passed to set_delimiter other than a regular expression will be + # converted to a string. + def set_delimiter delim + @lt2_delimiter = case delim + when Regexp + delim + else + delim.to_s + end + end + + # Called internally but also exposed to user code, for the case in which + # processing of binary data creates a need to transition back to line mode. + # We support an optional parameter to "throw back" some data, which might + # be an umprocessed chunk of the transmitted binary data, or something else + # entirely. + def set_line_mode data="" + @lt2_mode = :lines + (@lt2_linebuffer ||= []).clear + receive_data data.to_s + end + + def set_text_mode size=nil + if size == 0 + set_line_mode + else + @lt2_mode = :text + (@lt2_textbuffer ||= []).clear + @lt2_textsize = size # which can be nil, signifying no limit + @lt2_textpos = 0 + end + end + + # Alias for #set_text_mode, added for back-compatibility with LineAndTextProtocol. + def set_binary_mode size=nil + set_text_mode size + end + + # In case of a dropped connection, we'll send a partial buffer to user code + # when in sized text mode. User overrides of #receive_binary_data need to + # be aware that they may get a short buffer. + def unbind + @lt2_mode ||= nil + if @lt2_mode == :text and @lt2_textpos > 0 + receive_binary_data @lt2_textbuffer.join + end + end + + # Stub. Should be subclassed by user code. + def receive_line ln + # no-op + end + + # Stub. Should be subclassed by user code. + def receive_binary_data data + # no-op + end + + # Stub. Should be subclassed by user code. + # This is called when transitioning internally from text mode + # back to line mode. Useful when client code doesn't want + # to keep track of how much data it's received. + def receive_end_of_binary_data + # no-op + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb new file mode 100644 index 0000000..1f81aaf --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/memcache.rb @@ -0,0 +1,331 @@ +module EventMachine + module Protocols + # Implements the Memcache protocol (http://code.sixapart.com/svn/memcached/trunk/server/doc/protocol.txt). + # Requires memcached >= 1.2.4 w/ noreply support + # + # == Usage example + # + # EM.run{ + # cache = EM::P::Memcache.connect 'localhost', 11211 + # + # cache.set :a, 'hello' + # cache.set :b, 'hi' + # cache.set :c, 'how are you?' + # cache.set :d, '' + # + # cache.get(:a){ |v| p v } + # cache.get_hash(:a, :b, :c, :d){ |v| p v } + # cache.get(:a,:b,:c,:d){ |a,b,c,d| p [a,b,c,d] } + # + # cache.get(:a,:z,:b,:y,:d){ |a,z,b,y,d| p [a,z,b,y,d] } + # + # cache.get(:missing){ |m| p [:missing=, m] } + # cache.set(:missing, 'abc'){ p :stored } + # cache.get(:missing){ |m| p [:missing=, m] } + # cache.del(:missing){ p :deleted } + # cache.get(:missing){ |m| p [:missing=, m] } + # } + # + module Memcache + include EM::Deferrable + + ## + # constants + + unless defined? Cempty + # @private + Cstored = 'STORED'.freeze + # @private + Cend = 'END'.freeze + # @private + Cdeleted = 'DELETED'.freeze + # @private + Cunknown = 'NOT_FOUND'.freeze + # @private + Cerror = 'ERROR'.freeze + + # @private + Cempty = ''.freeze + # @private + Cdelimiter = "\r\n".freeze + end + + ## + # commands + + # Get the value associated with one or multiple keys + # + # cache.get(:a){ |v| p v } + # cache.get(:a,:b,:c,:d){ |a,b,c,d| p [a,b,c,d] } + # + def get *keys + raise ArgumentError unless block_given? + + callback{ + keys = keys.map{|k| k.to_s.gsub(/\s/,'_') } + send_data "get #{keys.join(' ')}\r\n" + @get_cbs << [keys, proc{ |values| + yield *keys.map{ |k| values[k] } + }] + } + end + + # Set the value for a given key + # + # cache.set :a, 'hello' + # cache.set(:missing, 'abc'){ puts "stored the value!" } + # + def set key, val, exptime = 0, &cb + callback{ + val = val.to_s + send_cmd :set, key, 0, exptime, val.respond_to?(:bytesize) ? val.bytesize : val.size, !block_given? + send_data val + send_data Cdelimiter + @set_cbs << cb if cb + } + end + + # Gets multiple values as a hash + # + # cache.get_hash(:a, :b, :c, :d){ |h| puts h[:a] } + # + def get_hash *keys + raise ArgumentError unless block_given? + + get *keys do |*values| + yield keys.inject({}){ |hash, k| hash.update k => values[keys.index(k)] } + end + end + + # Delete the value associated with a key + # + # cache.del :a + # cache.del(:b){ puts "deleted the value!" } + # + def delete key, expires = 0, &cb + callback{ + send_data "delete #{key} #{expires}#{cb ? '' : ' noreply'}\r\n" + @del_cbs << cb if cb + } + end + alias del delete + + # Connect to a memcached server (must support NOREPLY, memcached >= 1.2.4) + def self.connect host = 'localhost', port = 11211 + EM.connect host, port, self, host, port + end + + def send_cmd cmd, key, flags = 0, exptime = 0, bytes = 0, noreply = false + send_data "#{cmd} #{key} #{flags} #{exptime} #{bytes}#{noreply ? ' noreply' : ''}\r\n" + end + private :send_cmd + + ## + # errors + + # @private + class ParserError < StandardError + end + + ## + # em hooks + + # @private + def initialize host, port = 11211 + @host, @port = host, port + end + + # @private + def connection_completed + @get_cbs = [] + @set_cbs = [] + @del_cbs = [] + + @values = {} + + @reconnecting = false + @connected = true + succeed + # set_delimiter "\r\n" + # set_line_mode + end + + #-- + # 19Feb09 Switched to a custom parser, LineText2 is recursive and can cause + # stack overflows when there is too much data. + # include EM::P::LineText2 + # @private + def receive_data data + (@buffer||='') << data + + while index = @buffer.index(Cdelimiter) + begin + line = @buffer.slice!(0,index+2) + process_cmd line + rescue ParserError + @buffer[0...0] = line + break + end + end + end + + #-- + # def receive_line line + # @private + def process_cmd line + case line.strip + when /^VALUE\s+(.+?)\s+(\d+)\s+(\d+)/ # VALUE <key> <flags> <bytes> + bytes = Integer($3) + # set_binary_mode bytes+2 + # @cur_key = $1 + if @buffer.size >= bytes + 2 + @values[$1] = @buffer.slice!(0,bytes) + @buffer.slice!(0,2) # \r\n + else + raise ParserError + end + + when Cend # END + if entry = @get_cbs.shift + keys, cb = entry + cb.call(@values) + end + @values = {} + + when Cstored # STORED + if cb = @set_cbs.shift + cb.call(true) + end + + when Cdeleted # DELETED + if cb = @del_cbs.shift + cb.call(true) + end + + when Cunknown # NOT_FOUND + if cb = @del_cbs.shift + cb.call(false) + end + + else + p [:MEMCACHE_UNKNOWN, line] + end + end + + #-- + # def receive_binary_data data + # @values[@cur_key] = data[0..-3] + # end + + # @private + def unbind + if @connected or @reconnecting + EM.add_timer(1){ reconnect @host, @port } + @connected = false + @reconnecting = true + @deferred_status = nil + else + raise 'Unable to connect to memcached server' + end + end + end + end +end + +if __FILE__ == $0 + # ruby -I ext:lib -r eventmachine -rubygems lib/protocols/memcache.rb + require 'em/spec' + + # @private + class TestConnection + include EM::P::Memcache + def send_data data + sent_data << data + end + def sent_data + @sent_data ||= '' + end + + def initialize + connection_completed + end + end + + EM.describe EM::Protocols::Memcache do + + before{ + @c = TestConnection.new + } + + should 'send get requests' do + @c.get('a'){} + @c.sent_data.should == "get a\r\n" + done + end + + should 'send set requests' do + @c.set('a', 1){} + @c.sent_data.should == "set a 0 0 1\r\n1\r\n" + done + end + + should 'use noreply on set without block' do + @c.set('a', 1) + @c.sent_data.should == "set a 0 0 1 noreply\r\n1\r\n" + done + end + + should 'send delete requests' do + @c.del('a') + @c.sent_data.should == "delete a 0 noreply\r\n" + done + end + + should 'work when get returns no values' do + @c.get('a'){ |a| + a.should.be.nil + done + } + + @c.receive_data "END\r\n" + end + + should 'invoke block on set' do + @c.set('a', 1){ + done + } + + @c.receive_data "STORED\r\n" + end + + should 'invoke block on delete' do + @c.delete('a'){ |found| + found.should.be.false + } + @c.delete('b'){ |found| + found.should.be.true + done + } + + @c.receive_data "NOT_FOUND\r\n" + @c.receive_data "DELETED\r\n" + end + + should 'parse split responses' do + @c.get('a'){ |a| + a.should == 'abc' + done + } + + @c.receive_data "VAL" + @c.receive_data "UE a 0 " + @c.receive_data "3\r\n" + @c.receive_data "ab" + @c.receive_data "c" + @c.receive_data "\r\n" + @c.receive_data "EN" + @c.receive_data "D\r\n" + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb new file mode 100644 index 0000000..ec79cb4 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/object_protocol.rb @@ -0,0 +1,46 @@ +module EventMachine + module Protocols + # ObjectProtocol allows for easy communication using marshaled ruby objects + # + # module RubyServer + # include EM::P::ObjectProtocol + # + # def receive_object obj + # send_object({'you said' => obj}) + # end + # end + # + module ObjectProtocol + # By default returns Marshal, override to return JSON or YAML, or any + # other serializer/deserializer responding to #dump and #load. + def serializer + Marshal + end + + # @private + def receive_data data + (@buf ||= '') << data + + while @buf.size >= 4 + if @buf.size >= 4+(size=@buf.unpack('N').first) + @buf.slice!(0,4) + receive_object serializer.load(@buf.slice!(0,size)) + else + break + end + end + end + + # Invoked with ruby objects received over the network + def receive_object obj + # stub + end + + # Sends a ruby object over the network + def send_object obj + data = serializer.dump(obj) + send_data [data.respond_to?(:bytesize) ? data.bytesize : data.size, data].pack('Na*') + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb new file mode 100644 index 0000000..7d87505 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/postgres3.rb @@ -0,0 +1,246 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-08 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +require 'postgres-pr/message' +require 'postgres-pr/connection' +require 'stringio' + +# @private +class StringIO + # Reads exactly +n+ bytes. + # + # If the data read is nil an EOFError is raised. + # + # If the data read is too short an IOError is raised + def readbytes(n) + str = read(n) + if str == nil + raise EOFError, "End of file reached" + end + if str.size < n + raise IOError, "data truncated" + end + str + end + alias read_exactly_n_bytes readbytes +end + + +module EventMachine + module Protocols + # PROVISIONAL IMPLEMENTATION of an evented Postgres client. + # This implements version 3 of the Postgres wire protocol, which will work + # with any Postgres version from roughly 7.4 onward. + # + # Objective: we want to access Postgres databases without requiring threads. + # Until now this has been a problem because the Postgres client implementations + # have all made use of blocking I/O calls, which is incompatible with a + # thread-free evented model. + # + # But rather than re-implement the Postgres Wire3 protocol, we're taking advantage + # of the existing postgres-pr library, which was originally written by Michael + # Neumann but (at this writing) appears to be no longer maintained. Still, it's + # in basically a production-ready state, and the wire protocol isn't that complicated + # anyway. + # + # We're tucking in a bunch of require statements that may not be present in garden-variety + # EM installations. Until we find a good way to only require these if a program + # requires postgres, this file will need to be required explicitly. + # + # We need to monkeypatch StringIO because it lacks the #readbytes method needed + # by postgres-pr. + # The StringIO monkeypatch is lifted from the standard library readbytes.rb, + # which adds method #readbytes directly to class IO. But StringIO is not a subclass of IO. + # It is modified to raise an IOError instead of TruncatedDataException since the exception is unused. + # + # We cloned the handling of postgres messages from lib/postgres-pr/connection.rb + # in the postgres-pr library, and modified it for event-handling. + # + # TODO: The password handling in dispatch_conn_message is totally incomplete. + # + # + # We return Deferrables from the user-level operations surfaced by this interface. + # Experimentally, we're using the pattern of always returning a boolean value as the + # first argument of a deferrable callback to indicate success or failure. This is + # instead of the traditional pattern of calling Deferrable#succeed or #fail, and + # requiring the user to define both a callback and an errback function. + # + # === Usage + # EM.run { + # db = EM.connect_unix_domain( "/tmp/.s.PGSQL.5432", EM::P::Postgres3 ) + # db.connect( dbname, username, psw ).callback do |status| + # if status + # db.query( "select * from some_table" ).callback do |status, result, errors| + # if status + # result.rows.each do |row| + # p row + # end + # end + # end + # end + # end + # } + class Postgres3 < EventMachine::Connection + include PostgresPR + + def initialize + @data = "" + @params = {} + end + + def connect db, user, psw=nil + d = EM::DefaultDeferrable.new + d.timeout 15 + + if @pending_query || @pending_conn + d.succeed false, "Operation already in progress" + else + @pending_conn = d + prms = {"user"=>user, "database"=>db} + @user = user + if psw + @password = psw + #prms["password"] = psw + end + send_data PostgresPR::StartupMessage.new( 3 << 16, prms ).dump + end + + d + end + + def query sql + d = EM::DefaultDeferrable.new + d.timeout 15 + + if @pending_query || @pending_conn + d.succeed false, "Operation already in progress" + else + @r = PostgresPR::Connection::Result.new + @e = [] + @pending_query = d + send_data PostgresPR::Query.dump(sql) + end + + d + end + + + def receive_data data + @data << data + while @data.length >= 5 + pktlen = @data[1...5].unpack("N").first + if @data.length >= (1 + pktlen) + pkt = @data.slice!(0...(1+pktlen)) + m = StringIO.open( pkt, "r" ) {|io| PostgresPR::Message.read( io ) } + if @pending_conn + dispatch_conn_message m + elsif @pending_query + dispatch_query_message m + else + raise "Unexpected message from database" + end + else + break # very important, break out of the while + end + end + end + + + def unbind + if o = (@pending_query || @pending_conn) + o.succeed false, "lost connection" + end + end + + # Cloned and modified from the postgres-pr. + def dispatch_conn_message msg + case msg + when AuthentificationClearTextPassword + raise ArgumentError, "no password specified" if @password.nil? + send_data PasswordMessage.new(@password).dump + + when AuthentificationCryptPassword + raise ArgumentError, "no password specified" if @password.nil? + send_data PasswordMessage.new(@password.crypt(msg.salt)).dump + + when AuthentificationMD5Password + raise ArgumentError, "no password specified" if @password.nil? + require 'digest/md5' + + m = Digest::MD5.hexdigest(@password + @user) + m = Digest::MD5.hexdigest(m + msg.salt) + m = 'md5' + m + send_data PasswordMessage.new(m).dump + + when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential + raise "unsupported authentification" + + when AuthentificationOk + when ErrorResponse + raise msg.field_values.join("\t") + when NoticeResponse + @notice_processor.call(msg) if @notice_processor + when ParameterStatus + @params[msg.key] = msg.value + when BackendKeyData + # TODO + #p msg + when ReadyForQuery + # TODO: use transaction status + pc,@pending_conn = @pending_conn,nil + pc.succeed true + else + raise "unhandled message type" + end + end + + # Cloned and modified from the postgres-pr. + def dispatch_query_message msg + case msg + when DataRow + @r.rows << msg.columns + when CommandComplete + @r.cmd_tag = msg.cmd_tag + when ReadyForQuery + pq,@pending_query = @pending_query,nil + pq.succeed true, @r, @e + when RowDescription + @r.fields = msg.fields + when CopyInResponse + when CopyOutResponse + when EmptyQueryResponse + when ErrorResponse + # TODO + @e << msg + when NoticeResponse + @notice_processor.call(msg) if @notice_processor + else + # TODO + end + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb new file mode 100644 index 0000000..9cabc51 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/saslauth.rb @@ -0,0 +1,175 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + + # Implements SASL authd. + # This is a very, very simple protocol that mimics the one used + # by saslauthd and pwcheck, two outboard daemons included in the + # standard SASL library distro. + # The only thing this is really suitable for is SASL PLAIN + # (user+password) authentication, but the SASL libs that are + # linked into standard servers (like imapd and sendmail) implement + # the other ones. + # + # SASL-auth is intended for reasonably fast operation inside a + # single machine, so it has no transport-security (although there + # have been multi-machine extensions incorporating transport-layer + # encryption). + # + # The standard saslauthd module generally runs privileged and does + # its work by referring to the system-account files. + # + # This feature was added to EventMachine to enable the development + # of custom authentication/authorization engines for standard servers. + # + # To use SASLauth, include it in a class that subclasses EM::Connection, + # and reimplement the validate method. + # + # The typical way to incorporate this module into an authentication + # daemon would be to set it as the handler for a UNIX-domain socket. + # The code might look like this: + # + # EM.start_unix_domain_server( "/var/run/saslauthd/mux", MyHandler ) + # File.chmod( 0777, "/var/run/saslauthd/mux") + # + # The chmod is probably needed to ensure that unprivileged clients can + # access the UNIX-domain socket. + # + # It's also a very good idea to drop superuser privileges (if any), after + # the UNIX-domain socket has been opened. + #-- + # Implementation details: assume the client can send us pipelined requests, + # and that the client will close the connection. + # + # The client sends us four values, each encoded as a two-byte length field in + # network order followed by the specified number of octets. + # The fields specify the username, password, service name (such as imap), + # and the "realm" name. We send back the barest minimum reply, a single + # field also encoded as a two-octet length in network order, followed by + # either "NO" or "OK" - simplicity itself. + # + # We enforce a maximum field size just as a sanity check. + # We do NOT automatically time out the connection. + # + # The code we use to parse out the values is ugly and probably slow. + # Improvements welcome. + # + module SASLauth + + MaxFieldSize = 128*1024 + def post_init + super + @sasl_data = "" + @sasl_values = [] + end + + def receive_data data + @sasl_data << data + while @sasl_data.length >= 2 + len = (@sasl_data[0,2].unpack("n")).first + raise "SASL Max Field Length exceeded" if len > MaxFieldSize + if @sasl_data.length >= (len + 2) + @sasl_values << @sasl_data[2,len] + @sasl_data.slice!(0...(2+len)) + if @sasl_values.length == 4 + send_data( validate(*@sasl_values) ? "\0\002OK" : "\0\002NO" ) + @sasl_values.clear + end + else + break + end + end + end + + def validate username, psw, sysname, realm + p username + p psw + p sysname + p realm + true + end + end + + # Implements the SASL authd client protocol. + # This is a very, very simple protocol that mimics the one used + # by saslauthd and pwcheck, two outboard daemons included in the + # standard SASL library distro. + # The only thing this is really suitable for is SASL PLAIN + # (user+password) authentication, but the SASL libs that are + # linked into standard servers (like imapd and sendmail) implement + # the other ones. + # + # You can use this module directly as a handler for EM Connections, + # or include it in a module or handler class of your own. + # + # First connect to a SASL server (it's probably a TCP server, or more + # likely a Unix-domain socket). Then call the #validate? method, + # passing at least a username and a password. #validate? returns + # a Deferrable which will either succeed or fail, depending + # on the status of the authentication operation. + # + module SASLauthclient + MaxFieldSize = 128*1024 + + def validate? username, psw, sysname=nil, realm=nil + + str = [username, psw, sysname, realm].map {|m| + [(m || "").length, (m || "")] + }.flatten.pack( "nA*" * 4 ) + send_data str + + d = EM::DefaultDeferrable.new + @queries.unshift d + d + end + + def post_init + @sasl_data = "" + @queries = [] + end + + def receive_data data + @sasl_data << data + + while @sasl_data.length > 2 + len = (@sasl_data[0,2].unpack("n")).first + raise "SASL Max Field Length exceeded" if len > MaxFieldSize + if @sasl_data.length >= (len + 2) + val = @sasl_data[2,len] + @sasl_data.slice!(0...(2+len)) + q = @queries.pop + (val == "NO") ? q.fail : q.succeed + else + break + end + end + end + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb new file mode 100644 index 0000000..25ad17a --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpclient.rb @@ -0,0 +1,394 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +require 'ostruct' + +module EventMachine + module Protocols + + # Simple SMTP client + # + # @example + # email = EM::Protocols::SmtpClient.send( + # :domain=>"example.com", + # :host=>'localhost', + # :port=>25, # optional, defaults 25 + # :starttls=>true, # use ssl + # :from=>"sender@example.com", + # :to=> ["to_1@example.com", "to_2@example.com"], + # :header=> {"Subject" => "This is a subject line"}, + # :body=> "This is the body of the email" + # ) + # email.callback{ + # puts 'Email sent!' + # } + # email.errback{ |e| + # puts 'Email failed!' + # } + # + # Sending generated emails (using Mail) + # + # mail = Mail.new do + # from 'alice@example.com' + # to 'bob@example.com' + # subject 'This is a test email' + # body 'Hello, world!' + # end + # + # email = EM::P::SmtpClient.send( + # :domain=>'example.com', + # :from=>mail.from.first, + # :to=>mail.to, + # :message=>mail.to_s + # ) + # + class SmtpClient < Connection + include EventMachine::Deferrable + include EventMachine::Protocols::LineText2 + + def initialize + @succeeded = nil + @responder = nil + @code = nil + @msg = nil + end + + # :host => required String + # a string containing the IP address or host name of the SMTP server to connect to. + # :port => optional + # defaults to 25. + # :domain => required String + # This is passed as the argument to the EHLO command. + # :starttls => optional Boolean + # If it evaluates true, then the client will initiate STARTTLS with + # the server, and abort the connection if the negotiation doesn't succeed. + # TODO, need to be able to pass certificate parameters with this option. + # :auth => optional Hash of auth parameters + # If not given, then no auth will be attempted. + # (In that case, the connection will be aborted if the server requires auth.) + # Specify the hash value :type to determine the auth type, along with additional parameters + # depending on the type. + # Currently only :type => :plain is supported. Pass additional parameters :username (String), + # and :password (either a String or a Proc that will be called at auth-time). + # + # @example + # :auth => {:type=>:plain, :username=>"mickey@disney.com", :password=>"mouse"} + # + # :from => required String + # Specifies the sender of the message. Will be passed as the argument + # to the MAIL FROM. Do NOT enclose the argument in angle-bracket (<>) characters. + # The connection will abort if the server rejects the value. + # :to => required String or Array of Strings + # The recipient(s) of the message. Do NOT enclose + # any of the values in angle-brackets (<>) characters. It's NOT a fatal error if one or more + # recipients are rejected by the server. (Of course, if ALL of them are, the server will most + # likely trigger an error when we try to send data.) An array of codes containing the status + # of each requested recipient is available after the call completes. TODO, we should define + # an overridable stub that will be called on rejection of a recipient or a sender, giving + # user code the chance to try again or abort the connection. + # + # One of either :message, :content, or :header and :body is required: + # + # :message => String + # A valid RFC2822 Internet Message. + # :content => String + # Raw data which MUST be in correct SMTP body format, with escaped leading dots and a trailing + # dot line. + # :header => String or Hash of values to be transmitted in the header of the message. + # The hash keys are the names of the headers (do NOT append a trailing colon), and the values + # are strings containing the header values. TODO, support Arrays of header values, which would + # cause us to send that specific header line more than once. + # + # @example + # :header => {"Subject" => "Bogus", "CC" => "myboss@example.com"} + # + # :body => Optional String or Array of Strings, defaults blank. + # This will be passed as the body of the email message. + # TODO, this needs to be significantly beefed up. As currently written, this requires the caller + # to properly format the input into CRLF-delimited lines of 7-bit characters in the standard + # SMTP transmission format. We need to be able to automatically convert binary data, and add + # correct line-breaks to text data. + # + # :verbose => Optional. + # If true, will cause a lot of information (including the server-side of the + # conversation) to be dumped to $>. + # + def self.send args={} + args[:port] ||= 25 + args[:body] ||= "" + +=begin + (I don't think it's possible for EM#connect to throw an exception under normal + circumstances, so this original code is stubbed out. A connect-failure will result + in the #unbind method being called without calling #connection_completed.) + begin + EventMachine.connect( args[:host], args[:port], self) {|c| + # According to the EM docs, we will get here AFTER post_init is called. + c.args = args + c.set_comm_inactivity_timeout 60 + } + rescue + # We'll get here on a connect error. This code mimics the effect + # of a call to invoke_internal_error. Would be great to DRY this up. + # (Actually, it may be that we never get here, if EM#connect catches + # its errors internally.) + d = EM::DefaultDeferrable.new + d.set_deferred_status(:failed, {:error=>[:connect, 500, "unable to connect to server"]}) + d + end +=end + EventMachine.connect( args[:host], args[:port], self) {|c| + # According to the EM docs, we will get here AFTER post_init is called. + c.args = args + c.set_comm_inactivity_timeout 60 + } + end + + attr_writer :args + + # @private + def post_init + @return_values = OpenStruct.new + @return_values.start_time = Time.now + end + + # @private + def connection_completed + @responder = :receive_signon + @msg = [] + end + + # We can get here in a variety of ways, all of them being failures unless + # the @succeeded flag is set. If a protocol success was recorded, then don't + # set a deferred success because the caller will already have done it + # (no need to wait until the connection closes to invoke the callbacks). + # + # @private + def unbind + unless @succeeded + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = @code + @return_values.message = @msg + set_deferred_status(:failed, @return_values) + end + end + + # @private + def receive_line ln + $>.puts ln if @args[:verbose] + @range = ln[0...1].to_i + @code = ln[0...3].to_i + @msg << ln[4..-1] + unless ln[3...4] == '-' + $>.puts @responder if @args[:verbose] + send @responder + @msg.clear + end + end + + private + + # We encountered an error from the server and will close the connection. + # Use the error and message the server returned. + # + def invoke_error + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = @code + @return_values.message = @msg + set_deferred_status :failed, @return_values + send_data "QUIT\r\n" + close_connection_after_writing + end + + # We encountered an error on our side of the protocol and will close the connection. + # Use an extra-protocol error code (900) and use the message from the caller. + # + def invoke_internal_error msg = "???" + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = 900 + @return_values.message = msg + set_deferred_status :failed, @return_values + send_data "QUIT\r\n" + close_connection_after_writing + end + + def send_ehlo + send_data "EHLO #{@args[:domain]}\r\n" + end + + def receive_signon + return invoke_error unless @range == 2 + send_ehlo + @responder = :receive_ehlo_response + end + def receive_ehlo_response + return invoke_error unless @range == 2 + @server_caps = @msg + invoke_starttls + end + + def invoke_starttls + if @args[:starttls] + # It would be more sociable to first ask if @server_caps contains + # the string "STARTTLS" before we invoke it, but hey, life's too short. + send_data "STARTTLS\r\n" + @responder = :receive_starttls_response + else + invoke_auth + end + end + def receive_starttls_response + return invoke_error unless @range == 2 + start_tls + invoke_ehlo_over_tls + end + + def invoke_ehlo_over_tls + send_ehlo + @responder = :receive_ehlo_over_tls_response + end + def receive_ehlo_over_tls_response + return invoke_error unless @range == 2 + invoke_auth + end + + # Perform an authentication. If the caller didn't request one, then fall through + # to the mail-from state. + def invoke_auth + if @args[:auth] + if @args[:auth][:type] == :plain + psw = @args[:auth][:password] + if psw.respond_to?(:call) + psw = psw.call + end + #str = Base64::encode64("\0#{@args[:auth][:username]}\0#{psw}").chomp + str = ["\0#{@args[:auth][:username]}\0#{psw}"].pack("m").gsub(/\n/, '') + send_data "AUTH PLAIN #{str}\r\n" + @responder = :receive_auth_response + else + return invoke_internal_error("unsupported auth type") + end + else + invoke_mail_from + end + end + def receive_auth_response + return invoke_error unless @range == 2 + invoke_mail_from + end + + def invoke_mail_from + send_data "MAIL FROM: <#{@args[:from]}>\r\n" + @responder = :receive_mail_from_response + end + def receive_mail_from_response + return invoke_error unless @range == 2 + invoke_rcpt_to + end + + def invoke_rcpt_to + @rcpt_responses ||= [] + l = @rcpt_responses.length + to = @args[:to].is_a?(Array) ? @args[:to] : [@args[:to].to_s] + if l < to.length + send_data "RCPT TO: <#{to[l]}>\r\n" + @responder = :receive_rcpt_to_response + else + e = @rcpt_responses.select {|rr| rr.last == 2} + if e and e.length > 0 + invoke_data + else + invoke_error + end + end + end + def receive_rcpt_to_response + @rcpt_responses << [@code, @msg, @range] + invoke_rcpt_to + end + + def escape_leading_dots(s) + s.gsub(/^\./, '..') + end + + def invoke_data + send_data "DATA\r\n" + @responder = :receive_data_response + end + def receive_data_response + return invoke_error unless @range == 3 + + # The data to send can be given in either @args[:message], @args[:content], or the + # combination of @args[:header] and @args[:body]. + # + # - @args[:message] (String) MUST be a valid RFC2822 Internet Message + # + # - @args[:content] (String) MUST be in correct SMTP body format, with escaped + # leading dots and a trailing dot line + # + # - @args[:header] (Hash or String) + # - @args[:body] (Array or String) + if @args[:message] + send_data escape_leading_dots(@args[:message].to_s) + send_data "\r\n.\r\n" + elsif @args[:content] + send_data @args[:content].to_s + else + # The header can be a hash or an array. + if @args[:header].is_a?(Hash) + (@args[:header] || {}).each {|k,v| send_data escape_leading_dots("#{k}: #{v}\r\n") } + else + send_data escape_leading_dots(@args[:header].to_s) + end + send_data "\r\n" + + if @args[:body].is_a?(Array) + @args[:body].each {|e| send_data escape_leading_dots(e)} + else + send_data escape_leading_dots(@args[:body].to_s) + end + + send_data "\r\n.\r\n" + end + + @responder = :receive_message_response + end + def receive_message_response + return invoke_error unless @range == 2 + send_data "QUIT\r\n" + close_connection_after_writing + @succeeded = true + @return_values.elapsed_time = Time.now - @return_values.start_time + @return_values.responder = @responder + @return_values.code = @code + @return_values.message = @msg + set_deferred_status :succeeded, @return_values + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb new file mode 100644 index 0000000..e0f1f05 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/smtpserver.rb @@ -0,0 +1,666 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + module Protocols + + # This is a protocol handler for the server side of SMTP. + # It's NOT a complete SMTP server obeying all the semantics of servers conforming to + # RFC2821. Rather, it uses overridable method stubs to communicate protocol states + # and data to user code. User code is responsible for doing the right things with the + # data in order to get complete and correct SMTP server behavior. + # + # Simple SMTP server example: + # + # class EmailServer < EM::P::SmtpServer + # def receive_plain_auth(user, pass) + # true + # end + # + # def get_server_domain + # "mock.smtp.server.local" + # end + # + # def get_server_greeting + # "mock smtp server greets you with impunity" + # end + # + # def receive_sender(sender) + # current.sender = sender + # true + # end + # + # def receive_recipient(recipient) + # current.recipient = recipient + # true + # end + # + # def receive_message + # current.received = true + # current.completed_at = Time.now + # + # p [:received_email, current] + # @current = OpenStruct.new + # true + # end + # + # def receive_ehlo_domain(domain) + # @ehlo_domain = domain + # true + # end + # + # def receive_data_command + # current.data = "" + # true + # end + # + # def receive_data_chunk(data) + # current.data << data.join("\n") + # true + # end + # + # def receive_transaction + # if @ehlo_domain + # current.ehlo_domain = @ehlo_domain + # @ehlo_domain = nil + # end + # true + # end + # + # def current + # @current ||= OpenStruct.new + # end + # + # def self.start(host = 'localhost', port = 1025) + # require 'ostruct' + # @server = EM.start_server host, port, self + # end + # + # def self.stop + # if @server + # EM.stop_server @server + # @server = nil + # end + # end + # + # def self.running? + # !!@server + # end + # end + # + # EM.run{ EmailServer.start } + # + #-- + # Useful paragraphs in RFC-2821: + # 4.3.2: Concise list of command-reply sequences, in essence a text representation + # of the command state-machine. + # + # STARTTLS is defined in RFC2487. + # Observe that there are important rules governing whether a publicly-referenced server + # (meaning one whose Internet address appears in public MX records) may require the + # non-optional use of TLS. + # Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS. + class SmtpServer < EventMachine::Connection + include Protocols::LineText2 + + HeloRegex = /\AHELO\s*/i + EhloRegex = /\AEHLO\s*/i + QuitRegex = /\AQUIT/i + MailFromRegex = /\AMAIL FROM:\s*/i + RcptToRegex = /\ARCPT TO:\s*/i + DataRegex = /\ADATA/i + NoopRegex = /\ANOOP/i + RsetRegex = /\ARSET/i + VrfyRegex = /\AVRFY\s+/i + ExpnRegex = /\AEXPN\s+/i + HelpRegex = /\AHELP/i + StarttlsRegex = /\ASTARTTLS/i + AuthRegex = /\AAUTH\s+/i + + + # Class variable containing default parameters that can be overridden + # in application code. + # Individual objects of this class will make an instance-local copy of + # the class variable, so that they can be reconfigured on a per-instance + # basis. + # + # Chunksize is the number of data lines we'll buffer before + # sending them to the application. TODO, make this user-configurable. + # + @@parms = { + :chunksize => 4000, + :verbose => false + } + def self.parms= parms={} + @@parms.merge!(parms) + end + + + + def initialize *args + super + @parms = @@parms + init_protocol_state + end + + def parms= parms={} + @parms.merge!(parms) + end + + # In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM, + # #post_init will execute BEFORE the block passed to #start_server, for any + # given accepted connection. Since in this class we'll probably be getting + # a lot of initialization parameters, we want the guts of post_init to + # run AFTER the application has initialized the connection object. So we + # use a spawn to schedule the post_init to run later. + # It's a little weird, I admit. A reasonable alternative would be to set + # parameters as a class variable and to do that before accepting any connections. + # + # OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration. + # + def post_init + #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL) + #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self) + (EM.spawn {|x| x.send_server_greeting}).notify(self) + end + + def send_server_greeting + send_data "220 #{get_server_greeting}\r\n" + end + + def receive_line ln + @@parms[:verbose] and $>.puts ">>> #{ln}" + + return process_data_line(ln) if @state.include?(:data) + return process_auth_line(ln) if @state.include?(:auth_incomplete) + + case ln + when EhloRegex + process_ehlo $'.dup + when HeloRegex + process_helo $'.dup + when MailFromRegex + process_mail_from $'.dup + when RcptToRegex + process_rcpt_to $'.dup + when DataRegex + process_data + when RsetRegex + process_rset + when VrfyRegex + process_vrfy + when ExpnRegex + process_expn + when HelpRegex + process_help + when NoopRegex + process_noop + when QuitRegex + process_quit + when StarttlsRegex + process_starttls + when AuthRegex + process_auth $'.dup + else + process_unknown + end + end + + # TODO - implement this properly, the implementation is a stub! + def process_help + send_data "250 Ok, but unimplemented\r\n" + end + + # RFC2821, 3.5.3 Meaning of VRFY or EXPN Success Response: + # A server MUST NOT return a 250 code in response to a VRFY or EXPN + # command unless it has actually verified the address. In particular, + # a server MUST NOT return 250 if all it has done is to verify that the + # syntax given is valid. In that case, 502 (Command not implemented) + # or 500 (Syntax error, command unrecognized) SHOULD be returned. + # + # TODO - implement this properly, the implementation is a stub! + def process_vrfy + send_data "502 Command not implemented\r\n" + end + # TODO - implement this properly, the implementation is a stub! + def process_expn + send_data "502 Command not implemented\r\n" + end + + #-- + # This is called at several points to restore the protocol state + # to a pre-transaction state. In essence, we "forget" having seen + # any valid command except EHLO and STARTTLS. + # We also have to callback user code, in case they're keeping track + # of senders, recipients, and whatnot. + # + # We try to follow the convention of avoiding the verb "receive" for + # internal method names except receive_line (which we inherit), and + # using only receive_xxx for user-overridable stubs. + # + # init_protocol_state is called when we initialize the connection as + # well as during reset_protocol_state. It does NOT call the user + # override method. This enables us to promise the users that they + # won't see the overridable fire except after EHLO and RSET, and + # after a message has been received. Although the latter may be wrong. + # The standard may allow multiple DATA segments with the same set of + # senders and recipients. + # + def reset_protocol_state + init_protocol_state + s,@state = @state,[] + @state << :starttls if s.include?(:starttls) + @state << :ehlo if s.include?(:ehlo) + receive_transaction + end + def init_protocol_state + @state ||= [] + end + + + #-- + # EHLO/HELO is always legal, per the standard. On success + # it always clears buffers and initiates a mail "transaction." + # Which means that a MAIL FROM must follow. + # + # Per the standard, an EHLO/HELO or a RSET "initiates" an email + # transaction. Thereafter, MAIL FROM must be received before + # RCPT TO, before DATA. Not sure what this specific ordering + # achieves semantically, but it does make it easier to + # implement. We also support user-specified requirements for + # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM + # without fulfilling tls and/or auth, if the user specified either + # or both as required. We need to check the extension standard + # for auth to see if a credential is discarded after a RSET along + # with all the rest of the state. We'll behave as if it is. + # Now clearly, we can't discard tls after its been negotiated + # without dropping the connection, so that flag doesn't get cleared. + # + def process_ehlo domain + if receive_ehlo_domain domain + send_data "250-#{get_server_domain}\r\n" + if @@parms[:starttls] + send_data "250-STARTTLS\r\n" + end + if @@parms[:auth] + send_data "250-AUTH PLAIN\r\n" + end + send_data "250-NO-SOLICITING\r\n" + # TODO, size needs to be configurable. + send_data "250 SIZE 20000000\r\n" + reset_protocol_state + @state << :ehlo + else + send_data "550 Requested action not taken\r\n" + end + end + + def process_helo domain + if receive_ehlo_domain domain.dup + send_data "250 #{get_server_domain}\r\n" + reset_protocol_state + @state << :ehlo + else + send_data "550 Requested action not taken\r\n" + end + end + + def process_quit + send_data "221 Ok\r\n" + close_connection_after_writing + end + + def process_noop + send_data "250 Ok\r\n" + end + + def process_unknown + send_data "500 Unknown command\r\n" + end + + #-- + # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well. + # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx + # response and a continuation of the auth conversation. + # + def process_auth str + if @state.include?(:auth) + send_data "503 auth already issued\r\n" + elsif str =~ /\APLAIN\s?/i + if $'.length == 0 + # we got a partial response, so let the client know to send the rest + @state << :auth_incomplete + send_data("334 \r\n") + else + # we got the initial response, so go ahead & process it + process_auth_line($') + end + #elsif str =~ /\ALOGIN\s+/i + else + send_data "504 auth mechanism not available\r\n" + end + end + + def process_auth_line(line) + plain = line.unpack("m").first + _,user,psw = plain.split("\000") + + succeeded = proc { + send_data "235 authentication ok\r\n" + @state << :auth + } + failed = proc { + send_data "535 invalid authentication\r\n" + } + auth = receive_plain_auth user,psw + + if auth.respond_to?(:callback) + auth.callback(&succeeded) + auth.errback(&failed) + else + (auth ? succeeded : failed).call + end + + @state.delete :auth_incomplete + end + + #-- + # Unusually, we can deal with a Deferrable returned from the user application. + # This was added to deal with a special case in a particular application, but + # it would be a nice idea to add it to the other user-code callbacks. + # + def process_data + unless @state.include?(:rcpt) + send_data "503 Operation sequence error\r\n" + else + succeeded = proc { + send_data "354 Send it\r\n" + @state << :data + @databuffer = [] + } + failed = proc { + send_data "550 Operation failed\r\n" + } + + d = receive_data_command + + if d.respond_to?(:callback) + d.callback(&succeeded) + d.errback(&failed) + else + (d ? succeeded : failed).call + end + end + end + + def process_rset + reset_protocol_state + receive_reset + send_data "250 Ok\r\n" + end + + def unbind + connection_ended + end + + #-- + # STARTTLS may not be issued before EHLO, or unless the user has chosen + # to support it. + # + # If :starttls_options is present and :starttls is set in the parms + # pass the options in :starttls_options to start_tls. Do this if you want to use + # your own certificate + # e.g. {:cert_chain_file => "/etc/ssl/cert.pem", :private_key_file => "/etc/ssl/private/cert.key"} + + def process_starttls + if @@parms[:starttls] + if @state.include?(:starttls) + send_data "503 TLS Already negotiated\r\n" + elsif ! @state.include?(:ehlo) + send_data "503 EHLO required before STARTTLS\r\n" + else + send_data "220 Start TLS negotiation\r\n" + start_tls(@@parms[:starttls_options] || {}) + @state << :starttls + end + else + process_unknown + end + end + + + #-- + # Requiring TLS is touchy, cf RFC2784. + # Requiring AUTH seems to be much more reasonable. + # We don't currently support any notion of deriving an authentication from the TLS + # negotiation, although that would certainly be reasonable. + # We DON'T allow MAIL FROM to be given twice. + # We DON'T enforce all the various rules for validating the sender or + # the reverse-path (like whether it should be null), and notifying the reverse + # path in case of delivery problems. All of that is left to the calling application. + # + def process_mail_from sender + if (@@parms[:starttls]==:required and !@state.include?(:starttls)) + send_data "550 This server requires STARTTLS before MAIL FROM\r\n" + elsif (@@parms[:auth]==:required and !@state.include?(:auth)) + send_data "550 This server requires authentication before MAIL FROM\r\n" + elsif @state.include?(:mail_from) + send_data "503 MAIL already given\r\n" + else + unless receive_sender sender + send_data "550 sender is unacceptable\r\n" + else + send_data "250 Ok\r\n" + @state << :mail_from + end + end + end + + #-- + # Since we require :mail_from to have been seen before we process RCPT TO, + # we don't need to repeat the tests for TLS and AUTH. + # Note that we don't remember or do anything else with the recipients. + # All of that is on the user code. + # TODO: we should enforce user-definable limits on the total number of + # recipients per transaction. + # We might want to make sure that a given recipient is only seen once, but + # for now we'll let that be the user's problem. + # + # User-written code can return a deferrable from receive_recipient. + # + def process_rcpt_to rcpt + unless @state.include?(:mail_from) + send_data "503 MAIL is required before RCPT\r\n" + else + succeeded = proc { + send_data "250 Ok\r\n" + @state << :rcpt unless @state.include?(:rcpt) + } + failed = proc { + send_data "550 recipient is unacceptable\r\n" + } + + d = receive_recipient rcpt + + if d.respond_to?(:set_deferred_status) + d.callback(&succeeded) + d.errback(&failed) + else + (d ? succeeded : failed).call + end + +=begin + unless receive_recipient rcpt + send_data "550 recipient is unacceptable\r\n" + else + send_data "250 Ok\r\n" + @state << :rcpt unless @state.include?(:rcpt) + end +=end + end + end + + + # Send the incoming data to the application one chunk at a time, rather than + # one line at a time. That lets the application be a little more flexible about + # storing to disk, etc. + # Since we clear the chunk array every time we submit it, the caller needs to be + # aware to do things like dup it if he wants to keep it around across calls. + # + # Resets the transaction upon disposition of the incoming message. + # RFC5321 says this about the MAIL FROM command: + # "This command tells the SMTP-receiver that a new mail transaction is + # starting and to reset all its state tables and buffers, including any + # recipients or mail data." + # + # Equivalent behaviour is implemented by resetting after a completed transaction. + # + # User-written code can return a Deferrable as a response from receive_message. + # + def process_data_line ln + if ln == "." + if @databuffer.length > 0 + receive_data_chunk @databuffer + @databuffer.clear + end + + + succeeded = proc { + send_data "250 Message accepted\r\n" + reset_protocol_state + } + failed = proc { + send_data "550 Message rejected\r\n" + reset_protocol_state + } + d = receive_message + + if d.respond_to?(:set_deferred_status) + d.callback(&succeeded) + d.errback(&failed) + else + (d ? succeeded : failed).call + end + + @state.delete :data + else + # slice off leading . if any + ln.slice!(0...1) if ln[0] == ?. + @databuffer << ln + if @databuffer.length > @@parms[:chunksize] + receive_data_chunk @databuffer + @databuffer.clear + end + end + end + + + #------------------------------------------ + # Everything from here on can be overridden in user code. + + # The greeting returned in the initial connection message to the client. + def get_server_greeting + "EventMachine SMTP Server" + end + # The domain name returned in the first line of the response to a + # successful EHLO or HELO command. + def get_server_domain + "Ok EventMachine SMTP Server" + end + + # A false response from this user-overridable method will cause a + # 550 error to be returned to the remote client. + # + def receive_ehlo_domain domain + true + end + + # Return true or false to indicate that the authentication is acceptable. + def receive_plain_auth user, password + true + end + + # Receives the argument of the MAIL FROM command. Return false to + # indicate to the remote client that the sender is not accepted. + # This can only be successfully called once per transaction. + # + def receive_sender sender + true + end + + # Receives the argument of a RCPT TO command. Can be given multiple + # times per transaction. Return false to reject the recipient. + # + def receive_recipient rcpt + true + end + + # Sent when the remote peer issues the RSET command. + # Since RSET is not allowed to fail (according to the protocol), + # we ignore any return value from user overrides of this method. + # + def receive_reset + end + + # Sent when the remote peer has ended the connection. + # + def connection_ended + end + + # Called when the remote peer sends the DATA command. + # Returning false will cause us to send a 550 error to the peer. + # This can be useful for dealing with problems that arise from processing + # the whole set of sender and recipients. + # + def receive_data_command + true + end + + # Sent when data from the remote peer is available. The size can be controlled + # by setting the :chunksize parameter. This call can be made multiple times. + # The goal is to strike a balance between sending the data to the application one + # line at a time, and holding all of a very large message in memory. + # + def receive_data_chunk data + @smtps_msg_size ||= 0 + @smtps_msg_size += data.join.length + STDERR.write "<#{@smtps_msg_size}>" + end + + # Sent after a message has been completely received. User code + # must return true or false to indicate whether the message has + # been accepted for delivery. + def receive_message + @@parms[:verbose] and $>.puts "Received complete message" + true + end + + # This is called when the protocol state is reset. It happens + # when the remote client calls EHLO/HELO or RSET. + def receive_transaction + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb new file mode 100644 index 0000000..132f320 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/socks4.rb @@ -0,0 +1,66 @@ +module EventMachine + module Protocols + # Basic SOCKS v4 client implementation + # + # Use as you would any regular connection: + # + # class MyConn < EM::P::Socks4 + # def post_init + # send_data("sup") + # end + # + # def receive_data(data) + # send_data("you said: #{data}") + # end + # end + # + # EM.connect socks_host, socks_port, MyConn, host, port + # + class Socks4 < Connection + def initialize(host, port) + @host = Socket.gethostbyname(host).last + @port = port + @socks_error_code = nil + @buffer = '' + setup_methods + end + + def setup_methods + class << self + def post_init; socks_post_init; end + def receive_data(*a); socks_receive_data(*a); end + end + end + + def restore_methods + class << self + remove_method :post_init + remove_method :receive_data + end + end + + def socks_post_init + header = [4, 1, @port, @host, 0].flatten.pack("CCnA4C") + send_data(header) + end + + def socks_receive_data(data) + @buffer << data + return if @buffer.size < 8 + + header_resp = @buffer.slice! 0, 8 + _, r = header_resp.unpack("cc") + if r != 90 + @socks_error_code = r + close_connection + return + end + + restore_methods + + post_init + receive_data(@buffer) unless @buffer.empty? + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb new file mode 100644 index 0000000..ca6f078 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/stomp.rb @@ -0,0 +1,205 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 15 November 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + + # Implements Stomp (http://docs.codehaus.org/display/STOMP/Protocol). + # + # == Usage example + # + # module StompClient + # include EM::Protocols::Stomp + # + # def connection_completed + # connect :login => 'guest', :passcode => 'guest' + # end + # + # def receive_msg msg + # if msg.command == "CONNECTED" + # subscribe '/some/topic' + # else + # p ['got a message', msg] + # puts msg.body + # end + # end + # end + # + # EM.run{ + # EM.connect 'localhost', 61613, StompClient + # } + # + module Stomp + include LineText2 + + class Message + # The command associated with the message, usually 'CONNECTED' or 'MESSAGE' + attr_accessor :command + # Hash containing headers such as destination and message-id + attr_accessor :header + alias :headers :header + # Body of the message + attr_accessor :body + + # @private + def initialize + @header = {} + @state = :precommand + @content_length = nil + end + # @private + def consume_line line + if @state == :precommand + unless line =~ /\A\s*\Z/ + @command = line + @state = :headers + end + elsif @state == :headers + if line == "" + if @content_length + yield( [:sized_text, @content_length+1] ) + else + @state = :body + yield( [:unsized_text] ) + end + elsif line =~ /\A([^:]+):(.+)\Z/ + k = $1.dup.strip + v = $2.dup.strip + @header[k] = v + if k == "content-length" + @content_length = v.to_i + end + else + # This is a protocol error. How to signal it? + end + elsif @state == :body + @body = line + yield( [:dispatch] ) + end + end + end + + # @private + def send_frame verb, headers={}, body="" + body = body.to_s + ary = [verb, "\n"] + body_bytesize = body.bytesize if body.respond_to? :bytesize + body_bytesize ||= body.size + headers.each {|k,v| ary << "#{k}:#{v}\n" } + ary << "content-length: #{body_bytesize}\n" + ary << "content-type: text/plain; charset=UTF-8\n" unless headers.has_key? 'content-type' + ary << "\n" + ary << body + ary << "\0" + send_data ary.join + end + + # @private + def receive_line line + @stomp_initialized || init_message_reader + @stomp_message.consume_line(line) {|outcome| + if outcome.first == :sized_text + set_text_mode outcome[1] + elsif outcome.first == :unsized_text + set_delimiter "\0" + elsif outcome.first == :dispatch + receive_msg(@stomp_message) if respond_to?(:receive_msg) + init_message_reader + end + } + end + + # @private + def receive_binary_data data + @stomp_message.body = data[0..-2] + receive_msg(@stomp_message) if respond_to?(:receive_msg) + init_message_reader + end + + # @private + def init_message_reader + @stomp_initialized = true + set_delimiter "\n" + set_line_mode + @stomp_message = Message.new + end + + # Invoked with an incoming Stomp::Message received from the STOMP server + def receive_msg msg + # stub, overwrite this in your handler + end + + # CONNECT command, for authentication + # + # connect :login => 'guest', :passcode => 'guest' + # + def connect parms={} + send_frame "CONNECT", parms + end + + # SEND command, for publishing messages to a topic + # + # send '/topic/name', 'some message here' + # + def send destination, body, parms={} + send_frame "SEND", parms.merge( :destination=>destination ), body.to_s + end + + # SUBSCRIBE command, for subscribing to topics + # + # subscribe '/topic/name', false + # + def subscribe dest, ack=false + send_frame "SUBSCRIBE", {:destination=>dest, :ack=>(ack ? "client" : "auto")} + end + + # ACK command, for acknowledging receipt of messages + # + # module StompClient + # include EM::P::Stomp + # + # def connection_completed + # connect :login => 'guest', :passcode => 'guest' + # # subscribe with ack mode + # subscribe '/some/topic', true + # end + # + # def receive_msg msg + # if msg.command == "MESSAGE" + # ack msg.headers['message-id'] + # puts msg.body + # end + # end + # end + # + def ack msgid + send_frame "ACK", 'message-id'=> msgid + end + + end + end +end + diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb new file mode 100644 index 0000000..3187893 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/protocols/tcptest.rb @@ -0,0 +1,54 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 16 July 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# +# + +module EventMachine + module Protocols + + # @private + class TcpConnectTester < Connection + include EventMachine::Deferrable + + def self.test( host, port ) + EventMachine.connect( host, port, self ) + end + + def post_init + @start_time = Time.now + end + + def connection_completed + @completed = true + set_deferred_status :succeeded, (Time.now - @start_time) + close_connection + end + + def unbind + set_deferred_status :failed, (Time.now - @start_time) unless @completed + end + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb new file mode 100644 index 0000000..94fd175 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/pure_ruby.rb @@ -0,0 +1,1284 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 8 Apr 2006 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#------------------------------------------------------------------- +# +# + +# TODO List: +# TCP-connects currently assume non-blocking connect is available- need to +# degrade automatically on versions of Ruby prior to June 2006. +# + +require 'singleton' +require 'forwardable' +require 'socket' +require 'fcntl' +require 'set' +require 'openssl' + +module EventMachine + # @private + class Error < Exception; end + # @private + class UnknownTimerFired < RuntimeError; end + # @private + class Unsupported < RuntimeError; end + # @private + class ConnectionError < RuntimeError; end + # @private + class ConnectionNotBound < RuntimeError; end + + # Older versions of Ruby may not provide the SSLErrorWaitReadable + # OpenSSL class. Create an error class to act as a "proxy". + if defined?(OpenSSL::SSL::SSLErrorWaitReadable) + SSLConnectionWaitReadable = OpenSSL::SSL::SSLErrorWaitReadable + else + SSLConnectionWaitReadable = IO::WaitReadable + end + + # Older versions of Ruby may not provide the SSLErrorWaitWritable + # OpenSSL class. Create an error class to act as a "proxy". + if defined?(OpenSSL::SSL::SSLErrorWaitWritable) + SSLConnectionWaitWritable = OpenSSL::SSL::SSLErrorWaitWritable + else + SSLConnectionWaitWritable = IO::WaitWritable + end +end + +module EventMachine + class CertificateCreator + attr_reader :cert, :key + + def initialize + @key = OpenSSL::PKey::RSA.new(1024) + public_key = @key.public_key + subject = "/C=EventMachine/O=EventMachine/OU=EventMachine/CN=EventMachine" + @cert = OpenSSL::X509::Certificate.new + @cert.subject = @cert.issuer = OpenSSL::X509::Name.parse(subject) + @cert.not_before = Time.now + @cert.not_after = Time.now + 365 * 24 * 60 * 60 + @cert.public_key = public_key + @cert.serial = 0x0 + @cert.version = 2 + factory = OpenSSL::X509::ExtensionFactory.new + factory.subject_certificate = @cert + factory.issuer_certificate = @cert + @cert.extensions = [ + factory.create_extension("basicConstraints","CA:TRUE", true), + factory.create_extension("subjectKeyIdentifier", "hash") + ] + @cert.add_extension factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") + @cert.sign(@key, OpenSSL::Digest::SHA1.new) + end + end + + # @private + DefaultCertificate = CertificateCreator.new + + # @private + DefaultDHKey1024 = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MIGHAoGBAJ0lOVy0VIr/JebWn0zDwY2h+rqITFOpdNr6ugsgvkDXuucdcChhYExJ +AV/ZD2AWPbrTqV76mGRgJg4EddgT1zG0jq3rnFdMj2XzkBYx3BVvfR0Arnby0RHR +T4h7KZ/2zmjvV+eF8kBUHBJAojUlzxKj4QeO2x20FP9X5xmNUXeDAgEC +-----END DH PARAMETERS----- + _end_of_pem_ + + # @private + DefaultDHKey2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY +JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab +VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6 +YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3 +1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD +7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg== +-----END DH PARAMETERS----- + _end_of_pem_ +end + +# @private +module EventMachine + class << self + # This is mostly useful for automated tests. + # Return a distinctive symbol so the caller knows whether he's dealing + # with an extension or with a pure-Ruby library. + # @private + def library_type + :pure_ruby + end + + # @private + def initialize_event_machine + Reactor.instance.initialize_for_run + end + + # Changed 04Oct06: intervals from the caller are now in milliseconds, but our native-ruby + # processor still wants them in seconds. + # @private + def add_oneshot_timer interval + Reactor.instance.install_oneshot_timer(interval / 1000) + end + + # @private + def run_machine + Reactor.instance.run + end + + # @private + def release_machine + end + + + def stopping? + return Reactor.instance.stop_scheduled + end + + # @private + def stop + Reactor.instance.stop + end + + # @private + def connect_server host, port + bind_connect_server nil, nil, host, port + end + + # @private + def bind_connect_server bind_addr, bind_port, host, port + EvmaTCPClient.connect(bind_addr, bind_port, host, port).uuid + end + + # @private + def send_data target, data, datalength + selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target" + selectable.send_data data + end + + # @private + def close_connection target, after_writing + selectable = Reactor.instance.get_selectable( target ) + selectable.schedule_close after_writing if selectable + end + + # @private + def start_tcp_server host, port + (s = EvmaTCPServer.start_server host, port) or raise "no acceptor" + s.uuid + end + + # @private + def stop_tcp_server sig + s = Reactor.instance.get_selectable(sig) + s.schedule_close + end + + # @private + def start_unix_server chain + (s = EvmaUNIXServer.start_server chain) or raise "no acceptor" + s.uuid + end + + # @private + def connect_unix_server chain + EvmaUNIXClient.connect(chain).uuid + end + + # @private + def signal_loopbreak + Reactor.instance.signal_loopbreak + end + + # @private + def get_peername sig + selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_peername target" + selectable.get_peername + end + + # @private + def get_sockname sig + selectable = Reactor.instance.get_selectable( sig ) or raise "unknown get_sockname target" + selectable.get_sockname + end + + # @private + def open_udp_socket host, port + EvmaUDPSocket.create(host, port).uuid + end + + # This is currently only for UDP! + # We need to make it work with unix-domain sockets as well. + # @private + def send_datagram target, data, datalength, host, port + selectable = Reactor.instance.get_selectable( target ) or raise "unknown send_data target" + selectable.send_datagram data, Socket::pack_sockaddr_in(port, host) + end + + + # Sets reactor quantum in milliseconds. The underlying Reactor function wants a (possibly + # fractional) number of seconds. + # @private + def set_timer_quantum interval + Reactor.instance.set_timer_quantum(( 1.0 * interval) / 1000.0) + end + + # This method is a harmless no-op in the pure-Ruby implementation. This is intended to ensure + # that user code behaves properly across different EM implementations. + # @private + def epoll + end + + # @private + def ssl? + true + end + + def tls_parm_set?(parm) + !(parm.nil? || parm.empty?) + end + + # This method takes a series of positional arguments for specifying such + # things as private keys and certificate chains. It's expected that the + # parameter list will grow as we add more supported features. ALL of these + # parameters are optional, and can be specified as empty or nil strings. + # @private + def set_tls_parms signature, priv_key, cert_chain, verify_peer, fail_if_no_peer_cert, sni_hostname, cipher_list, ecdh_curve, dhparam, protocols_bitmask + bitmask = protocols_bitmask + ssl_options = OpenSSL::SSL::OP_ALL + ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2) && EM_PROTO_SSLv2 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3) && EM_PROTO_SSLv3 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1) && EM_PROTO_TLSv1 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1) && EM_PROTO_TLSv1_1 & bitmask == 0 + ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_2 if defined?(OpenSSL::SSL::OP_NO_TLSv1_2) && EM_PROTO_TLSv1_2 & bitmask == 0 + @tls_parms ||= {} + @tls_parms[signature] = { + :verify_peer => verify_peer, + :fail_if_no_peer_cert => fail_if_no_peer_cert, + :ssl_options => ssl_options + } + @tls_parms[signature][:priv_key] = File.read(priv_key) if tls_parm_set?(priv_key) + @tls_parms[signature][:cert_chain] = File.read(cert_chain) if tls_parm_set?(cert_chain) + @tls_parms[signature][:sni_hostname] = sni_hostname if tls_parm_set?(sni_hostname) + @tls_parms[signature][:cipher_list] = cipher_list.gsub(/,\s*/, ':') if tls_parm_set?(cipher_list) + @tls_parms[signature][:dhparam] = File.read(dhparam) if tls_parm_set?(dhparam) + @tls_parms[signature][:ecdh_curve] = ecdh_curve if tls_parm_set?(ecdh_curve) + end + + def start_tls signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown io selectable for start_tls" + tls_parms = @tls_parms[signature] + ctx = OpenSSL::SSL::SSLContext.new + ctx.options = tls_parms[:ssl_options] + ctx.cert = DefaultCertificate.cert + ctx.key = DefaultCertificate.key + ctx.cert_store = OpenSSL::X509::Store.new + ctx.cert_store.set_default_paths + ctx.cert = OpenSSL::X509::Certificate.new(tls_parms[:cert_chain]) if tls_parms[:cert_chain] + ctx.key = OpenSSL::PKey::RSA.new(tls_parms[:priv_key]) if tls_parms[:priv_key] + verify_mode = OpenSSL::SSL::VERIFY_NONE + if tls_parms[:verify_peer] + verify_mode |= OpenSSL::SSL::VERIFY_PEER + end + if tls_parms[:fail_if_no_peer_cert] + verify_mode |= OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + end + ctx.verify_mode = verify_mode + ctx.servername_cb = Proc.new do |_, server_name| + tls_parms[:server_name] = server_name + nil + end + ctx.ciphers = tls_parms[:cipher_list] if tls_parms[:cipher_list] + if selectable.is_server + ctx.tmp_dh_callback = Proc.new do |_, _, key_length| + if tls_parms[:dhparam] + OpenSSL::PKey::DH.new(tls_parms[:dhparam]) + else + case key_length + when 1024 then DefaultDHKey1024 + when 2048 then DefaultDHKey2048 + else + nil + end + end + end + if tls_parms[:ecdh_curve] && ctx.respond_to?(:tmp_ecdh_callback) + ctx.tmp_ecdh_callback = Proc.new do + OpenSSL::PKey::EC.new(tls_parms[:ecdh_curve]) + end + end + end + ssl_io = OpenSSL::SSL::SSLSocket.new(selectable, ctx) + ssl_io.sync_close = true + if tls_parms[:sni_hostname] + ssl_io.hostname = tls_parms[:sni_hostname] if ssl_io.respond_to?(:hostname=) + end + begin + selectable.is_server ? ssl_io.accept_nonblock : ssl_io.connect_nonblock + rescue; end + selectable.io = ssl_io + end + + def get_peer_cert signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_peer_cert target" + if selectable.io.respond_to?(:peer_cert) && selectable.io.peer_cert + selectable.io.peer_cert.to_pem + else + nil + end + end + + def get_cipher_name signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_name target" + selectable.io.respond_to?(:cipher) ? selectable.io.cipher[0] : nil + end + + def get_cipher_protocol signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_protocol target" + selectable.io.respond_to?(:cipher) ? selectable.io.cipher[1] : nil + end + + def get_cipher_bits signature + selectable = Reactor.instance.get_selectable(signature) or raise "unknown get_cipher_bits target" + selectable.io.respond_to?(:cipher) ? selectable.io.cipher[2] : nil + end + + def get_sni_hostname signature + @tls_parms ||= {} + if @tls_parms[signature] + @tls_parms[signature][:server_name] + else + nil + end + end + + # This method is a no-op in the pure-Ruby implementation. We simply return Ruby's built-in + # per-process file-descriptor limit. + # @private + def set_rlimit_nofile n + 1024 + end + + # This method is a harmless no-op in pure Ruby, which doesn't have a built-in limit + # on the number of available timers. + # @private + def set_max_timer_count n + end + + # @private + def get_sock_opt signature, level, optname + selectable = Reactor.instance.get_selectable( signature ) or raise "unknown get_sock_opt target" + selectable.getsockopt level, optname + end + + # @private + def set_sock_opt signature, level, optname, optval + selectable = Reactor.instance.get_selectable( signature ) or raise "unknown set_sock_opt target" + selectable.setsockopt level, optname, optval + end + + # @private + def send_file_data sig, filename + sz = File.size(filename) + raise "file too large" if sz > 32*1024 + data = + begin + File.read filename + rescue + "" + end + send_data sig, data, data.length + end + + # @private + def get_outbound_data_size sig + r = Reactor.instance.get_selectable( sig ) or raise "unknown get_outbound_data_size target" + r.get_outbound_data_size + end + + # @private + def read_keyboard + EvmaKeyboard.open.uuid + end + + # @private + def set_comm_inactivity_timeout sig, tm + r = Reactor.instance.get_selectable( sig ) or raise "unknown set_comm_inactivity_timeout target" + r.set_inactivity_timeout tm + end + + # @private + def set_pending_connect_timeout sig, tm + # Needs to be implemented. Currently a no-op stub to allow + # certain software to operate with the EM pure-ruby. + end + + # @private + def report_connection_error_status signature + get_sock_opt(signature, Socket::SOL_SOCKET, Socket::SO_ERROR).int + end + end +end + +module EventMachine + # @private + class Connection + # @private + def get_outbound_data_size + EventMachine::get_outbound_data_size @signature + end + end +end + +module EventMachine + + # Factored out so we can substitute other implementations + # here if desired, such as the one in ActiveRBAC. + # @private + module UuidGenerator + def self.generate + @ix ||= 0 + @ix += 1 + end + end +end + + +module EventMachine + # @private + TimerFired = 100 + # @private + ConnectionData = 101 + # @private + ConnectionUnbound = 102 + # @private + ConnectionAccepted = 103 + # @private + ConnectionCompleted = 104 + # @private + LoopbreakSignalled = 105 + # @private + ConnectionNotifyReadable = 106 + # @private + ConnectionNotifyWritable = 107 + # @private + SslHandshakeCompleted = 108 + # @private + SslVerify = 109 + # @private + EM_PROTO_SSLv2 = 2 + # @private + EM_PROTO_SSLv3 = 4 + # @private + EM_PROTO_TLSv1 = 8 + # @private + EM_PROTO_TLSv1_1 = 16 + # @private + EM_PROTO_TLSv1_2 = 32 +end + +module EventMachine + # @private + class Reactor + include Singleton + + HeartbeatInterval = 2 + + attr_reader :current_loop_time, :stop_scheduled + + def initialize + initialize_for_run + end + + def install_oneshot_timer interval + uuid = UuidGenerator::generate + #@timers << [Time.now + interval, uuid] + #@timers.sort! {|a,b| a.first <=> b.first} + @timers.add([Time.now + interval, uuid]) + uuid + end + + # Called before run, this is a good place to clear out arrays + # with cruft that may be left over from a previous run. + # @private + def initialize_for_run + @running = false + @stop_scheduled = false + @selectables ||= {}; @selectables.clear + @timers = SortedSet.new # [] + set_timer_quantum(0.1) + @current_loop_time = Time.now + @next_heartbeat = @current_loop_time + HeartbeatInterval + end + + def add_selectable io + @selectables[io.uuid] = io + end + + def get_selectable uuid + @selectables[uuid] + end + + def run + raise Error.new( "already running" ) if @running + @running = true + + begin + open_loopbreaker + + loop { + @current_loop_time = Time.now + + break if @stop_scheduled + run_timers + break if @stop_scheduled + crank_selectables + break if @stop_scheduled + run_heartbeats + } + ensure + close_loopbreaker + @selectables.each {|k, io| io.close} + @selectables.clear + + @running = false + end + + end + + def run_timers + @timers.each {|t| + if t.first <= @current_loop_time + @timers.delete t + EventMachine::event_callback "", TimerFired, t.last + else + break + end + } + #while @timers.length > 0 and @timers.first.first <= now + # t = @timers.shift + # EventMachine::event_callback "", TimerFired, t.last + #end + end + + def run_heartbeats + if @next_heartbeat <= @current_loop_time + @next_heartbeat = @current_loop_time + HeartbeatInterval + @selectables.each {|k,io| io.heartbeat} + end + end + + def crank_selectables + #$stderr.write 'R' + + readers = @selectables.values.select {|io| io.select_for_reading?} + writers = @selectables.values.select {|io| io.select_for_writing?} + + s = select( readers, writers, nil, @timer_quantum) + + s and s[1] and s[1].each {|w| w.eventable_write } + s and s[0] and s[0].each {|r| r.eventable_read } + + @selectables.delete_if {|k,io| + if io.close_scheduled? + io.close + begin + EventMachine::event_callback io.uuid, ConnectionUnbound, nil + rescue ConnectionNotBound; end + true + end + } + end + + # #stop + def stop + raise Error.new( "not running") unless @running + @stop_scheduled = true + end + + def open_loopbreaker + # Can't use an IO.pipe because they can't be set nonselectable in Windows. + # Pick a random localhost UDP port. + #@loopbreak_writer.close if @loopbreak_writer + #rd,@loopbreak_writer = IO.pipe + @loopbreak_reader = UDPSocket.new + @loopbreak_writer = UDPSocket.new + bound = false + 100.times { + @loopbreak_port = rand(10000) + 40000 + begin + @loopbreak_reader.bind "127.0.0.1", @loopbreak_port + bound = true + break + rescue + end + } + raise "Unable to bind Loopbreaker" unless bound + LoopbreakReader.new(@loopbreak_reader) + end + + def close_loopbreaker + @loopbreak_writer.close + @loopbreak_writer = nil + end + + def signal_loopbreak + begin + @loopbreak_writer.send('+',0,"127.0.0.1",@loopbreak_port) if @loopbreak_writer + rescue IOError; end + end + + def set_timer_quantum interval_in_seconds + @timer_quantum = interval_in_seconds + end + + end + +end + +# @private +class IO + extend Forwardable + def_delegator :@my_selectable, :close_scheduled? + def_delegator :@my_selectable, :select_for_reading? + def_delegator :@my_selectable, :select_for_writing? + def_delegator :@my_selectable, :eventable_read + def_delegator :@my_selectable, :eventable_write + def_delegator :@my_selectable, :uuid + def_delegator :@my_selectable, :is_server + def_delegator :@my_selectable, :is_server= + def_delegator :@my_selectable, :send_data + def_delegator :@my_selectable, :schedule_close + def_delegator :@my_selectable, :get_peername + def_delegator :@my_selectable, :get_sockname + def_delegator :@my_selectable, :send_datagram + def_delegator :@my_selectable, :get_outbound_data_size + def_delegator :@my_selectable, :set_inactivity_timeout + def_delegator :@my_selectable, :heartbeat + def_delegator :@my_selectable, :io + def_delegator :@my_selectable, :io= +end + +module EventMachine + # @private + class Selectable + + attr_accessor :io, :is_server + attr_reader :uuid + + def initialize io + @io = io + @uuid = UuidGenerator.generate + @is_server = false + @last_activity = Reactor.instance.current_loop_time + + if defined?(Fcntl::F_GETFL) + m = @io.fcntl(Fcntl::F_GETFL, 0) + @io.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK | m) + else + # Windows doesn't define F_GETFL. + # It's not very reliable about setting descriptors nonblocking either. + begin + s = Socket.for_fd(@io.fileno) + s.fcntl( Fcntl::F_SETFL, Fcntl::O_NONBLOCK ) + rescue Errno::EINVAL, Errno::EBADF + warn "Serious error: unable to set descriptor non-blocking" + end + end + # TODO, should set CLOEXEC on Unix? + + @close_scheduled = false + @close_requested = false + + se = self; @io.instance_eval { @my_selectable = se } + Reactor.instance.add_selectable @io + end + + def close_scheduled? + @close_scheduled + end + + def select_for_reading? + false + end + + def select_for_writing? + false + end + + def get_peername + nil + end + + def get_sockname + nil + end + + def set_inactivity_timeout tm + @inactivity_timeout = tm + end + + def heartbeat + end + + def schedule_close(after_writing=false) + if after_writing + @close_requested = true + else + @close_scheduled = true + end + end + end + +end + +module EventMachine + # @private + class StreamObject < Selectable + def initialize io + super io + @outbound_q = [] + end + + # If we have to close, or a close-after-writing has been requested, + # then don't read any more data. + def select_for_reading? + true unless (@close_scheduled || @close_requested) + end + + # If we have to close, don't select for writing. + # Otherwise, see if the protocol is ready to close. + # If not, see if he has data to send. + # If a close-after-writing has been requested and the outbound queue + # is empty, convert the status to close_scheduled. + def select_for_writing? + unless @close_scheduled + if @outbound_q.empty? + @close_scheduled = true if @close_requested + false + else + true + end + end + end + + # Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006. + # If we have it, then we can read multiple times safely to improve + # performance. + # The last-activity clock ASSUMES that we only come here when we + # have selected readable. + # TODO, coalesce multiple reads into a single event. + # TODO, do the function check somewhere else and cache it. + def eventable_read + @last_activity = Reactor.instance.current_loop_time + begin + if io.respond_to?(:read_nonblock) + 10.times { + data = io.read_nonblock(4096) + EventMachine::event_callback uuid, ConnectionData, data + } + else + data = io.sysread(4096) + EventMachine::event_callback uuid, ConnectionData, data + end + rescue Errno::EAGAIN, Errno::EWOULDBLOCK, SSLConnectionWaitReadable + # no-op + rescue Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError, Errno::EPIPE, OpenSSL::SSL::SSLError + @close_scheduled = true + EventMachine::event_callback uuid, ConnectionUnbound, nil + end + + end + + # Provisional implementation. Will be re-implemented in subclasses. + # TODO: Complete this implementation. As it stands, this only writes + # a single packet per cycle. Highly inefficient, but required unless + # we're running on a Ruby with proper nonblocking I/O (Ruby 1.8.4 + # built from sources from May 25, 2006 or newer). + # We need to improve the loop so it writes multiple times, however + # not more than a certain number of bytes per cycle, otherwise + # one busy connection could hog output buffers and slow down other + # connections. Also we should coalesce small writes. + # URGENT TODO: Coalesce small writes. They are a performance killer. + # The last-activity recorder ASSUMES we'll only come here if we've + # selected writable. + def eventable_write + # coalesce the outbound array here, perhaps + @last_activity = Reactor.instance.current_loop_time + while data = @outbound_q.shift do + begin + data = data.to_s + w = if io.respond_to?(:write_nonblock) + io.write_nonblock data + else + io.syswrite data + end + + if w < data.length + @outbound_q.unshift data[w..-1] + break + end + rescue Errno::EAGAIN, SSLConnectionWaitReadable, SSLConnectionWaitWritable + @outbound_q.unshift data + break + rescue EOFError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EPIPE, OpenSSL::SSL::SSLError + @close_scheduled = true + @outbound_q.clear + end + end + + end + + # #send_data + def send_data data + # TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last? + unless @close_scheduled or @close_requested or !data or data.length <= 0 + @outbound_q << data.to_s + end + end + + # #get_peername + # This is defined in the normal way on connected stream objects. + # Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants. + # We could also use a convenience method that did the unpacking automatically. + def get_peername + io.getpeername + end + + # #get_sockname + # This is defined in the normal way on connected stream objects. + # Return an object that is suitable for passing to Socket#unpack_sockaddr_in or variants. + # We could also use a convenience method that did the unpacking automatically. + def get_sockname + io.getsockname + end + + # #get_outbound_data_size + def get_outbound_data_size + @outbound_q.inject(0) {|memo,obj| memo += (obj || "").length} + end + + def heartbeat + if @inactivity_timeout and @inactivity_timeout > 0 and (@last_activity + @inactivity_timeout) < Reactor.instance.current_loop_time + schedule_close true + end + end + end + + +end + + +#-------------------------------------------------------------- + + + +module EventMachine + # @private + class EvmaTCPClient < StreamObject + + def self.connect bind_addr, bind_port, host, port + sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) + sd.bind( Socket.pack_sockaddr_in( bind_port, bind_addr )) if bind_addr + + begin + # TODO, this assumes a current Ruby snapshot. + # We need to degrade to a nonblocking connect otherwise. + sd.connect_nonblock( Socket.pack_sockaddr_in( port, host )) + rescue Errno::ECONNREFUSED, Errno::EINPROGRESS + end + EvmaTCPClient.new sd + end + + def initialize io + super + @pending = true + @handshake_complete = false + end + + def ready? + if RUBY_PLATFORM =~ /linux/ + io.getsockopt(Socket::SOL_TCP, Socket::TCP_INFO).unpack("i").first == 1 # TCP_ESTABLISHED + else + io.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).unpack("i").first == 0 # NO ERROR + end + end + + def handshake_complete? + if !@handshake_complete && io.respond_to?(:state) + if io.state =~ /^SSLOK/ + @handshake_complete = true + EventMachine::event_callback uuid, SslHandshakeCompleted, "" + EventMachine::event_callback uuid, SslVerify, io.peer_cert.to_pem if io.peer_cert + end + else + @handshake_complete = true + end + @handshake_complete + end + + def pending? + handshake_complete? + if @pending + if ready? + @pending = false + EventMachine::event_callback uuid, ConnectionCompleted, "" + end + end + @pending + end + + def select_for_writing? + pending? + super + end + + def select_for_reading? + pending? + super + end + end +end + + + +module EventMachine + # @private + class EvmaKeyboard < StreamObject + + def self.open + EvmaKeyboard.new STDIN + end + + + def initialize io + super + end + + + def select_for_writing? + false + end + + def select_for_reading? + true + end + + + end +end + + + +module EventMachine + # @private + class EvmaUNIXClient < StreamObject + + def self.connect chain + sd = Socket.new( Socket::AF_LOCAL, Socket::SOCK_STREAM, 0 ) + begin + # TODO, this assumes a current Ruby snapshot. + # We need to degrade to a nonblocking connect otherwise. + sd.connect_nonblock( Socket.pack_sockaddr_un( chain )) + rescue Errno::EINPROGRESS + end + EvmaUNIXClient.new sd + end + + + def initialize io + super + @pending = true + end + + + def select_for_writing? + @pending ? true : super + end + + def select_for_reading? + @pending ? false : super + end + + def eventable_write + if @pending + @pending = false + if 0 == io.getsockopt(Socket::SOL_SOCKET, Socket::SO_ERROR).unpack("i").first + EventMachine::event_callback uuid, ConnectionCompleted, "" + end + else + super + end + end + + + + end +end + + +#-------------------------------------------------------------- + +module EventMachine + # @private + class EvmaTCPServer < Selectable + + # TODO, refactor and unify with EvmaUNIXServer. + + class << self + # Versions of ruby 1.8.4 later than May 26 2006 will work properly + # with an object of type TCPServer. Prior versions won't so we + # play it safe and just build a socket. + # + def start_server host, port + sd = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 ) + sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true ) + sd.bind( Socket.pack_sockaddr_in( port, host )) + sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough. + EvmaTCPServer.new sd + end + end + + def initialize io + super io + end + + + def select_for_reading? + true + end + + #-- + # accept_nonblock returns an array consisting of the accepted + # socket and a sockaddr_in which names the peer. + # Don't accept more than 10 at a time. + def eventable_read + begin + 10.times { + descriptor,peername = io.accept_nonblock + sd = EvmaTCPClient.new descriptor + sd.is_server = true + EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid + } + rescue Errno::EWOULDBLOCK, Errno::EAGAIN + end + end + + #-- + # + def schedule_close + @close_scheduled = true + end + + end +end + + +#-------------------------------------------------------------- + +module EventMachine + # @private + class EvmaUNIXServer < Selectable + + # TODO, refactor and unify with EvmaTCPServer. + + class << self + # Versions of ruby 1.8.4 later than May 26 2006 will work properly + # with an object of type TCPServer. Prior versions won't so we + # play it safe and just build a socket. + # + def start_server chain + sd = Socket.new( Socket::AF_LOCAL, Socket::SOCK_STREAM, 0 ) + sd.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true ) + sd.bind( Socket.pack_sockaddr_un( chain )) + sd.listen( 50 ) # 5 is what you see in all the books. Ain't enough. + EvmaUNIXServer.new sd + end + end + + def initialize io + super io + end + + + def select_for_reading? + true + end + + #-- + # accept_nonblock returns an array consisting of the accepted + # socket and a sockaddr_in which names the peer. + # Don't accept more than 10 at a time. + def eventable_read + begin + 10.times { + descriptor,peername = io.accept_nonblock + sd = StreamObject.new descriptor + EventMachine::event_callback uuid, ConnectionAccepted, sd.uuid + } + rescue Errno::EWOULDBLOCK, Errno::EAGAIN + end + end + + #-- + # + def schedule_close + @close_scheduled = true + end + + end +end + + + +#-------------------------------------------------------------- + +module EventMachine + # @private + class LoopbreakReader < Selectable + + def select_for_reading? + true + end + + def eventable_read + io.sysread(128) + EventMachine::event_callback "", LoopbreakSignalled, "" + end + + end +end + + + +# @private +module EventMachine + # @private + class DatagramObject < Selectable + def initialize io + super io + @outbound_q = [] + end + + # #send_datagram + def send_datagram data, target + # TODO, coalesce here perhaps by being smarter about appending to @outbound_q.last? + unless @close_scheduled or @close_requested + @outbound_q << [data.to_s, target] + end + end + + # #select_for_writing? + def select_for_writing? + unless @close_scheduled + if @outbound_q.empty? + @close_scheduled = true if @close_requested + false + else + true + end + end + end + + # #select_for_reading? + def select_for_reading? + true + end + + # #get_outbound_data_size + def get_outbound_data_size + @outbound_q.inject(0) {|memo,obj| memo += (obj || "").length} + end + + + end + + +end + + +module EventMachine + # @private + class EvmaUDPSocket < DatagramObject + + class << self + def create host, port + sd = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 ) + sd.bind Socket::pack_sockaddr_in( port, host ) + EvmaUDPSocket.new sd + end + end + + # #eventable_write + # This really belongs in DatagramObject, but there is some UDP-specific stuff. + def eventable_write + 40.times { + break if @outbound_q.empty? + begin + data,target = @outbound_q.first + + # This damn better be nonblocking. + io.send data.to_s, 0, target + + @outbound_q.shift + rescue Errno::EAGAIN + # It's not been observed in testing that we ever get here. + # True to the definition, packets will be accepted and quietly dropped + # if the system is under pressure. + break + rescue EOFError, Errno::ECONNRESET + @close_scheduled = true + @outbound_q.clear + end + } + end + + # Proper nonblocking I/O was added to Ruby 1.8.4 in May 2006. + # If we have it, then we can read multiple times safely to improve + # performance. + def eventable_read + begin + if io.respond_to?(:recvfrom_nonblock) + 40.times { + data,@return_address = io.recvfrom_nonblock(16384) + EventMachine::event_callback uuid, ConnectionData, data + @return_address = nil + } + else + raise "unimplemented datagram-read operation on this Ruby" + end + rescue Errno::EAGAIN + # no-op + rescue Errno::ECONNRESET, EOFError + @close_scheduled = true + EventMachine::event_callback uuid, ConnectionUnbound, nil + end + end + + def send_data data + send_datagram data, @return_address + end + end +end + +# load base EM api on top, now that we have the underlying pure ruby +# implementation defined +require 'eventmachine' diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb new file mode 100644 index 0000000..9096ed0 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/queue.rb @@ -0,0 +1,80 @@ +module EventMachine + # A cross thread, reactor scheduled, linear queue. + # + # This class provides a simple queue abstraction on top of the reactor + # scheduler. It services two primary purposes: + # + # * API sugar for stateful protocols + # * Pushing processing onto the reactor thread + # + # @example + # + # q = EM::Queue.new + # q.push('one', 'two', 'three') + # 3.times do + # q.pop { |msg| puts(msg) } + # end + # + class Queue + def initialize + @sink = [] + @drain = [] + @popq = [] + end + + # Pop items off the queue, running the block on the reactor thread. The pop + # will not happen immediately, but at some point in the future, either in + # the next tick, if the queue has data, or when the queue is populated. + # + # @return [NilClass] nil + def pop(*a, &b) + cb = EM::Callback(*a, &b) + EM.schedule do + if @drain.empty? + @drain = @sink + @sink = [] + end + if @drain.empty? + @popq << cb + else + cb.call @drain.shift + end + end + nil # Always returns nil + end + + # Push items onto the queue in the reactor thread. The items will not appear + # in the queue immediately, but will be scheduled for addition during the + # next reactor tick. + def push(*items) + EM.schedule do + @sink.push(*items) + unless @popq.empty? + @drain = @sink + @sink = [] + @popq.shift.call @drain.shift until @drain.empty? || @popq.empty? + end + end + end + alias :<< :push + + # @return [Boolean] + # @note This is a peek, it's not thread safe, and may only tend toward accuracy. + def empty? + @drain.empty? && @sink.empty? + end + + # @return [Integer] Queue size + # @note This is a peek, it's not thread safe, and may only tend toward accuracy. + def size + @drain.size + @sink.size + end + + # @return [Integer] Waiting size + # @note This is a peek at the number of jobs that are currently waiting on the Queue + def num_waiting + @popq.size + end + + end # Queue +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb new file mode 100644 index 0000000..1d2d7aa --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/resolver.rb @@ -0,0 +1,232 @@ +module EventMachine + module DNS + class Resolver + + def self.windows? + if RUBY_PLATFORM =~ /mswin32|cygwin|mingw|bccwin/ + require 'win32/resolv' + true + else + false + end + end + + HOSTS_FILE = windows? ? Win32::Resolv.get_hosts_path : '/etc/hosts' + + @hosts = nil + @nameservers = nil + @socket = nil + + def self.resolve(hostname) + Request.new(socket, hostname) + end + + def self.socket + if @socket && @socket.error? + @socket = Socket.open + else + @socket ||= Socket.open + end + end + + def self.nameservers=(ns) + @nameservers = ns + end + + def self.nameservers + return @nameservers if @nameservers + + if windows? + _, ns = Win32::Resolv.get_resolv_info + return @nameservers = ns || [] + end + + @nameservers = [] + IO.readlines('/etc/resolv.conf').each do |line| + if line =~ /^nameserver (.+)$/ + @nameservers << $1.split(/\s+/).first + end + end + + @nameservers + rescue + @nameservers = [] + end + + def self.nameserver + nameservers.shuffle.first + end + + def self.hosts + return @hosts if @hosts + + @hosts = {} + IO.readlines(HOSTS_FILE).each do |line| + next if line =~ /^#/ + addr, host = line.split(/\s+/) + + next unless addr && host + @hosts[host] ||= [] + @hosts[host] << addr + end + + @hosts + rescue + @hosts = {} + end + end + + class RequestIdAlreadyUsed < RuntimeError; end + + class Socket < EventMachine::Connection + def self.open + EventMachine::open_datagram_socket('0.0.0.0', 0, self) + end + + def initialize + @nameserver = nil + end + + def post_init + @requests = {} + end + + def start_timer + @timer ||= EM.add_periodic_timer(0.1, &method(:tick)) + end + + def stop_timer + EM.cancel_timer(@timer) + @timer = nil + end + + def unbind + end + + def tick + @requests.each do |id,req| + req.tick + end + end + + def register_request(id, req) + if @requests.has_key?(id) + raise RequestIdAlreadyUsed + else + @requests[id] = req + end + + start_timer + end + + def deregister_request(id, req) + @requests.delete(id) + stop_timer if @requests.length == 0 + end + + def send_packet(pkt) + send_datagram(pkt, nameserver, 53) + end + + def nameserver=(ns) + @nameserver = ns + end + + def nameserver + @nameserver || Resolver.nameserver + end + + # Decodes the packet, looks for the request and passes the + # response over to the requester + def receive_data(data) + msg = nil + begin + msg = Resolv::DNS::Message.decode data + rescue + else + req = @requests[msg.id] + if req + @requests.delete(msg.id) + stop_timer if @requests.length == 0 + req.receive_answer(msg) + end + end + end + end + + class Request + include Deferrable + attr_accessor :retry_interval, :max_tries + + def initialize(socket, hostname) + @socket = socket + @hostname = hostname + @tries = 0 + @last_send = Time.at(0) + @retry_interval = 3 + @max_tries = 5 + + if addrs = Resolver.hosts[hostname] + succeed addrs + else + EM.next_tick { tick } + end + end + + def tick + # Break early if nothing to do + return if @last_send + @retry_interval > Time.now + if @tries < @max_tries + send + else + @socket.deregister_request(@id, self) + fail 'retries exceeded' + end + end + + def receive_answer(msg) + addrs = [] + msg.each_answer do |name,ttl,data| + if data.kind_of?(Resolv::DNS::Resource::IN::A) || + data.kind_of?(Resolv::DNS::Resource::IN::AAAA) + addrs << data.address.to_s + end + end + + if addrs.empty? + fail "rcode=#{msg.rcode}" + else + succeed addrs + end + end + + private + + def send + @tries += 1 + @last_send = Time.now + @socket.send_packet(packet.encode) + end + + def id + begin + @id = rand(65535) + @socket.register_request(@id, self) + rescue RequestIdAlreadyUsed + retry + end unless defined?(@id) + + @id + end + + def packet + msg = Resolv::DNS::Message.new + msg.id = id + msg.rd = 1 + msg.add_question @hostname, Resolv::DNS::Resource::IN::A + msg + end + + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb new file mode 100644 index 0000000..35c087d --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/spawnable.rb @@ -0,0 +1,84 @@ +#-- +# +# Author:: Francis Cianfrocca (gmail: blackhedd) +# Homepage:: http://rubyeventmachine.com +# Date:: 25 Aug 2007 +# +# See EventMachine and EventMachine::Connection for documentation and +# usage examples. +# +#---------------------------------------------------------------------------- +# +# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved. +# Gmail: blackhedd +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of either: 1) the GNU General Public License +# as published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version; or 2) Ruby's License. +# +# See the file COPYING for complete licensing information. +# +#--------------------------------------------------------------------------- +# +# + +module EventMachine + # Support for Erlang-style processes. + # + class SpawnedProcess + # Send a message to the spawned process + def notify *x + me = self + EM.next_tick { + # A notification executes in the context of this + # SpawnedProcess object. That makes self and notify + # work as one would expect. + # + y = me.call(*x) + if y and y.respond_to?(:pull_out_yield_block) + a,b = y.pull_out_yield_block + set_receiver a + self.notify if b + end + } + end + alias_method :resume, :notify + alias_method :run, :notify # for formulations like (EM.spawn {xxx}).run + + def set_receiver blk + (class << self ; self ; end).class_eval do + remove_method :call if method_defined? :call + define_method :call, blk + end + end + + end + + # @private + class YieldBlockFromSpawnedProcess + def initialize block, notify + @block = [block,notify] + end + def pull_out_yield_block + @block + end + end + + # Spawn an erlang-style process + def self.spawn &block + s = SpawnedProcess.new + s.set_receiver block + s + end + + # @private + def self.yield &block + return YieldBlockFromSpawnedProcess.new( block, false ) + end + + # @private + def self.yield_and_notify &block + return YieldBlockFromSpawnedProcess.new( block, true ) + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb new file mode 100644 index 0000000..cf49c88 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/streamer.rb @@ -0,0 +1,118 @@ +module EventMachine + # Streams a file over a given connection. Streaming begins once the object is + # instantiated. Typically FileStreamer instances are not reused. + # + # Streaming uses buffering for files larger than 16K and uses so-called fast file reader (a C++ extension) + # if available (it is part of eventmachine gem itself). + # + # @example + # + # module FileSender + # def post_init + # streamer = EventMachine::FileStreamer.new(self, '/tmp/bigfile.tar') + # streamer.callback{ + # # file was sent successfully + # close_connection_after_writing + # } + # end + # end + # + # + # @author Francis Cianfrocca + class FileStreamer + include Deferrable + + # Use mapped streamer for files bigger than 16k + MappingThreshold = 16384 + # Wait until next tick to send more data when 50k is still in the outgoing buffer + BackpressureLevel = 50000 + # Send 16k chunks at a time + ChunkSize = 16384 + + # @param [EventMachine::Connection] connection + # @param [String] filename File path + # + # @option args [Boolean] :http_chunks (false) Use HTTP 1.1 style chunked-encoding semantics. + def initialize connection, filename, args = {} + @connection = connection + @http_chunks = args[:http_chunks] + + if File.exist?(filename) + @size = File.size(filename) + if @size <= MappingThreshold + stream_without_mapping filename + else + stream_with_mapping filename + end + else + fail "file not found" + end + end + + # @private + def stream_without_mapping filename + if @http_chunks + @connection.send_data "#{@size.to_s(16)}\r\n" + @connection.send_file_data filename + @connection.send_data "\r\n0\r\n\r\n" + else + @connection.send_file_data filename + end + succeed + end + private :stream_without_mapping + + # @private + def stream_with_mapping filename + ensure_mapping_extension_is_present + + @position = 0 + @mapping = EventMachine::FastFileReader::Mapper.new filename + stream_one_chunk + end + private :stream_with_mapping + + # Used internally to stream one chunk at a time over multiple reactor ticks + # @private + def stream_one_chunk + loop { + if @position < @size + if @connection.get_outbound_data_size > BackpressureLevel + EventMachine::next_tick {stream_one_chunk} + break + else + len = @size - @position + len = ChunkSize if (len > ChunkSize) + + @connection.send_data( "#{len.to_s(16)}\r\n" ) if @http_chunks + @connection.send_data( @mapping.get_chunk( @position, len )) + @connection.send_data("\r\n") if @http_chunks + + @position += len + end + else + @connection.send_data "0\r\n\r\n" if @http_chunks + @mapping.close + succeed + break + end + } + end + + # + # We use an outboard extension class to get memory-mapped files. + # It's outboard to avoid polluting the core distro, but that means + # there's a "hidden" dependency on it. The first time we get here in + # any run, try to load up the dependency extension. User code will see + # a LoadError if it's not available, but code that doesn't require + # mapped files will work fine without it. This is a somewhat difficult + # compromise between usability and proper modularization. + # + # @private + def ensure_mapping_extension_is_present + @@fastfilereader ||= (require 'fastfilereaderext') + end + private :ensure_mapping_extension_is_present + + end # FileStreamer +end # EventMachine diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb new file mode 100644 index 0000000..87704d5 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/threaded_resource.rb @@ -0,0 +1,90 @@ +module EventMachine + # = EventMachine::ThreadedResource + # + # A threaded resource is a "quick and dirty" wrapper around the concept of + # wiring up synchronous code into a standard EM::Pool. This is useful to keep + # interfaces coherent and provide a simple approach at "making an interface + # async-ish". + # + # General usage is to wrap libraries that do not support EventMachine, or to + # have a specific number of dedicated high-cpu worker resources. + # + # == Basic Usage example + # + # This example requires the cassandra gem. The cassandra gem contains an + # EventMachine interface, but it's sadly Fiber based and thus only works on + # 1.9. It also requires (potentially) complex stack switching logic to reach + # completion of nested operations. By contrast this approach provides a block + # in which normal synchronous code can occur, but makes no attempt to wire the + # IO into EventMachines C++ IO implementations, instead relying on the reactor + # pattern in rb_thread_select. + # + # cassandra_dispatcher = ThreadedResource.new do + # Cassandra.new('allthethings', '127.0.0.1:9160') + # end + # + # pool = EM::Pool.new + # + # pool.add cassandra_dispatcher + # + # # If we don't care about the result: + # pool.perform do |dispatcher| + # # The following block executes inside a dedicated thread, and should not + # # access EventMachine things: + # dispatcher.dispatch do |cassandra| + # cassandra.insert(:Things, '10', 'stuff' => 'things') + # end + # end + # + # # Example where we care about the result: + # pool.perform do |dispatcher| + # # The dispatch block is executed in the resources thread. + # completion = dispatcher.dispatch do |cassandra| + # cassandra.get(:Things, '10', 'stuff') + # end + # + # # This block will be yielded on the EM thread: + # completion.callback do |result| + # EM.do_something_with(result) + # end + # + # completion + # end + class ThreadedResource + + # The block should return the resource that will be yielded in a dispatch. + def initialize + @resource = yield + + @running = true + @queue = ::Queue.new + @thread = Thread.new do + @queue.pop.call while @running + end + end + + # Called on the EM thread, generally in a perform block to return a + # completion for the work. + def dispatch + completion = EM::Completion.new + @queue << lambda do + begin + result = yield @resource + completion.succeed result + rescue => e + completion.fail e + end + end + completion + end + + # Kill the internal thread. should only be used to cleanup - generally + # only required for tests. + def shutdown + @running = false + @queue << lambda {} + @thread.join + end + + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb new file mode 100644 index 0000000..a95d516 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/tick_loop.rb @@ -0,0 +1,85 @@ +module EventMachine + # Creates and immediately starts an EventMachine::TickLoop + def self.tick_loop(*a, &b) + TickLoop.new(*a, &b).start + end + + # A TickLoop is useful when one needs to distribute amounts of work + # throughout ticks in order to maintain response times. It is also useful for + # simple repeated checks and metrics. + # @example + # # Here we run through an array one item per tick until it is empty, + # # printing each element. + # # When the array is empty, we return :stop from the callback, and the + # # loop will terminate. + # # When the loop terminates, the on_stop callbacks will be called. + # EM.run do + # array = (1..100).to_a + # + # tickloop = EM.tick_loop do + # if array.empty? + # :stop + # else + # puts array.shift + # end + # end + # + # tickloop.on_stop { EM.stop } + # end + # + class TickLoop + + # Arguments: A callback (EM::Callback) to call each tick. If the call + # returns +:stop+ then the loop will be stopped. Any other value is + # ignored. + def initialize(*a, &b) + @work = EM::Callback(*a, &b) + @stops = [] + @stopped = true + end + + # Arguments: A callback (EM::Callback) to call once on the next stop (or + # immediately if already stopped). + def on_stop(*a, &b) + if @stopped + EM::Callback(*a, &b).call + else + @stops << EM::Callback(*a, &b) + end + end + + # Stop the tick loop immediately, and call it's on_stop callbacks. + def stop + @stopped = true + until @stops.empty? + @stops.shift.call + end + end + + # Query if the loop is stopped. + def stopped? + @stopped + end + + # Start the tick loop, will raise argument error if the loop is already + # running. + def start + raise ArgumentError, "double start" unless @stopped + @stopped = false + schedule + end + + private + def schedule + EM.next_tick do + next if @stopped + if @work.call == :stop + stop + else + schedule + end + end + self + end + end +end
\ No newline at end of file diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb new file mode 100644 index 0000000..41cd959 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/timers.rb @@ -0,0 +1,61 @@ +module EventMachine + # Creates a one-time timer + # + # timer = EventMachine::Timer.new(5) do + # # this will never fire because we cancel it + # end + # timer.cancel + # + class Timer + # Create a new timer that fires after a given number of seconds + def initialize interval, callback=nil, &block + @signature = EventMachine::add_timer(interval, callback || block) + end + + # Cancel the timer + def cancel + EventMachine.send :cancel_timer, @signature + end + end + + # Creates a periodic timer + # + # @example + # n = 0 + # timer = EventMachine::PeriodicTimer.new(5) do + # puts "the time is #{Time.now}" + # timer.cancel if (n+=1) > 5 + # end + # + class PeriodicTimer + # Create a new periodic timer that executes every interval seconds + def initialize interval, callback=nil, &block + @interval = interval + @code = callback || block + @cancelled = false + @work = method(:fire) + schedule + end + + # Cancel the periodic timer + def cancel + @cancelled = true + end + + # Fire the timer every interval seconds + attr_accessor :interval + + # @private + def schedule + EventMachine::add_timer @interval, @work + end + + # @private + def fire + unless @cancelled + @code.call + schedule + end + end + end +end diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb new file mode 100644 index 0000000..ffc4310 --- /dev/null +++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/version.rb @@ -0,0 +1,3 @@ +module EventMachine + VERSION = "1.2.7" +end |
