Mini Shell
# Phusion Passenger - https://www.phusionpassenger.com/
# Copyright (c) 2010-2017 Phusion Holding B.V.
#
# "Passenger", "Phusion Passenger" and "Union Station" are registered
# trademarks of Phusion Holding B.V.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
PhusionPassenger.require_passenger_lib 'utils' # So that we can know whether #writev is supported.
# https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
def ruby2_keywords(*)
end if RUBY_VERSION < "2.7"
module PhusionPassenger
module Utils
# Some frameworks (e.g. Merb) call `seek` and `rewind` on the input stream
# if it responds to these methods. In case of Phusion Passenger, the input
# stream is a socket, and altough socket objects respond to `seek` and
# `rewind`, calling these methods will raise an exception. We don't want
# this to happen so in AbstractRequestHandler we wrap the client socket
# into an UnseekableSocket wrapper, which doesn't respond to these methods.
#
# We used to dynamically undef `seek` and `rewind` on sockets, but this
# blows the Ruby interpreter's method cache and made things slower.
# Wrapping a socket is faster despite extra method calls.
#
# Furthermore, all exceptions originating from the wrapped socket will
# be annotated. One can check whether a certain exception originates
# from the wrapped socket by calling #source_of_exception?
class UnseekableSocket
def self.wrap(socket)
return new.wrap(socket)
end
def wrap(socket)
# Some people report that sometimes their Ruby (MRI/REE)
# processes get stuck with 100% CPU usage. Upon further
# inspection with strace, it turns out that these Ruby
# processes are continuously calling lseek() on a socket,
# which of course returns ESPIPE as error. gdb reveals
# lseek() is called by fwrite(), which in turn is called
# by rb_fwrite(). The affected socket is the
# AbstractRequestHandler client socket.
#
# I inspected the MRI source code and didn't find
# anything that would explain this behavior. This makes
# me think that it's a glibc bug, but that's very
# unlikely.
#
# The rb_fwrite() implementation takes an entirely
# different code path if I set 'sync' to true: it will
# skip fwrite() and use write() instead. So here we set
# 'sync' to true in the hope that this will work around
# the problem.
socket.sync = true
# There's no need to set the encoding for Ruby 1.9 because
# abstract_request_handler.rb is tagged with 'encoding: binary'.
@socket = socket
return self
end
# Don't allow disabling of sync.
def sync=(value)
end
# Socket is sync'ed so flushing shouldn't do anything.
def flush
end
# Already set to binary mode.
def binmode
end
# This makes select() work.
def to_io
@socket
end
def simulate_eof!
@simulate_eof = true
end
def stop_simulating_eof!
@simulate_eof = false
end
def fileno
@socket.fileno
end
def addr
@socket.addr
rescue => e
raise annotate(e)
end
def write(string)
@socket.write(string)
rescue => e
raise annotate(e)
end
ruby2_keywords def write_nonblock(string, *args)
@socket.write_nonblock(string, *args)
rescue => e
raise annotate(e)
end
def writev(components)
@socket.writev(components)
rescue => e
raise annotate(e)
end if IO.method_defined?(:writev)
def writev2(components, components2)
@socket.writev2(components, components2)
rescue => e
raise annotate(e)
end if IO.method_defined?(:writev2)
def writev3(components, components2, components3)
@socket.writev3(components, components2, components3)
rescue => e
raise annotate(e)
end if IO.method_defined?(:writev3)
ruby2_keywords def send(*args)
@socket.send(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def sendmsg(*args)
@socket.sendmsg(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def sendmsg_nonblock(*args)
@socket.sendmsg_nonblock(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def puts(*args)
@socket.puts(*args)
rescue => e
raise annotate(e)
end
def gets
return nil if @simulate_eof
@socket.gets
rescue => e
raise annotate(e)
end
ruby2_keywords def read(*args)
if @simulate_eof
length, buffer = args
if buffer
buffer.replace(binary_string(""))
else
buffer = binary_string("")
end
if length
return nil
else
return buffer
end
end
@socket.read(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def read_nonblock(*args)
raise EOFError, "end of file reached" if @simulate_eof
@socket.read_nonblock(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def readpartial(*args)
raise EOFError, "end of file reached" if @simulate_eof
@socket.readpartial(*args)
rescue => e
raise annotate(e)
end
def readline
raise EOFError, "end of file reached" if @simulate_eof
@socket.readline
rescue => e
raise annotate(e)
end
ruby2_keywords def recv(*args)
raise EOFError, "end of file reached" if @simulate_eof
@socket.recv(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def recvfrom(*args)
raise EOFError, "end of file reached" if @simulate_eof
@socket.recvfrom(*args)
rescue => e
raise annotate(e)
end
ruby2_keywords def recvfrom_nonblock(*args)
raise EOFError, "end of file reached" if @simulate_eof
@socket.recvfrom_nonblock(*args)
rescue => e
raise annotate(e)
end
def each(&block)
return if @simulate_eof
@socket.each(&block)
rescue => e
raise annotate(e)
end
def eof?
return true if @simulate_eof
@socket.eof?
rescue => e
raise annotate(e)
end
def closed?
@socket.closed?
rescue => e
raise annotate(e)
end
def close
@socket.close
rescue => e
raise annotate(e)
end
def close_read
@socket.close_read
rescue => e
raise annotate(e)
end
def close_write
@socket.close_write
rescue => e
raise annotate(e)
end
def source_of_exception?(exception)
return exception.instance_variable_get(:"@from_unseekable_socket") == @socket.object_id
end
def to_hash
{ :socket => "Not JSON Encodable", :eof => @simulate_eof }
end
private
def annotate(exception)
exception.instance_variable_set(:"@from_unseekable_socket", @socket.object_id)
return exception
end
def raise_error_because_activity_disallowed!
raise IOError, "It is not possible to read or write from the client socket because the current."
end
if ''.respond_to?(:force_encoding)
def binary_string(str)
return ''.force_encoding('binary')
end
else
def binary_string(str)
return ''
end
end
end
end # module Utils
end # module PhusionPassenger
Zerion Mini Shell 1.0