2020-07-31 10:08:26 -04:00
|
|
|
# -*- coding: binary -*-
|
2021-11-02 16:03:45 -04:00
|
|
|
|
2020-07-31 10:08:26 -04:00
|
|
|
require 'bindata'
|
2023-02-23 14:27:44 +00:00
|
|
|
require 'rex/post/channel'
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
module Rex::Proto::Http::WebSocket
|
|
|
|
|
class WebSocketError < StandardError
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class ConnectionError < WebSocketError
|
|
|
|
|
def initialize(msg: 'The WebSocket connection failed', http_response: nil)
|
|
|
|
|
@message = msg
|
|
|
|
|
@http_response = http_response
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
attr_accessor :message, :http_response
|
|
|
|
|
alias to_s message
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# This defines the interface that the standard socket is extended with to provide WebSocket functionality. It should be
|
|
|
|
|
# used on a socket when the server has already successfully handled a WebSocket upgrade request.
|
|
|
|
|
module Interface
|
|
|
|
|
#
|
|
|
|
|
# A channel object that allows reading and writing either text or binary data directly to the remote peer.
|
|
|
|
|
#
|
|
|
|
|
class Channel
|
|
|
|
|
include Rex::Post::Channel::StreamAbstraction
|
|
|
|
|
|
|
|
|
|
module SocketInterface
|
|
|
|
|
include Rex::Post::Channel::SocketAbstraction::SocketInterface
|
|
|
|
|
|
|
|
|
|
def type?
|
|
|
|
|
'tcp'
|
2021-09-24 14:18:55 -04:00
|
|
|
end
|
2021-11-05 10:41:07 -04:00
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
# The socket parameters describing the underlying connection.
|
|
|
|
|
# @!attribute [r] params
|
|
|
|
|
# @return [Rex::Socket::Parameters]
|
|
|
|
|
attr_reader :params
|
|
|
|
|
|
|
|
|
|
# @param [WebSocket::Interface] websocket the WebSocket that this channel is being opened on
|
|
|
|
|
# @param [nil, Symbol] read_type the data type(s) to read from the WebSocket, one of :binary, :text or nil (for both
|
|
|
|
|
# binary and text)
|
|
|
|
|
# @param [Symbol] write_type the data type to write to the WebSocket
|
2023-01-21 08:29:28 -05:00
|
|
|
def initialize(websocket, read_type: nil, write_type: :binary)
|
2021-11-05 10:41:07 -04:00
|
|
|
initialize_abstraction
|
|
|
|
|
|
|
|
|
|
# a read type of nil will handle both binary and text frames that are received
|
|
|
|
|
raise ArgumentError, 'read_type must be nil, :binary or :text' unless [nil, :binary, :text].include?(read_type)
|
|
|
|
|
raise ArgumentError, 'write_type must be :binary or :text' unless %i[binary text].include?(write_type)
|
|
|
|
|
|
|
|
|
|
@websocket = websocket
|
|
|
|
|
@read_type = read_type
|
|
|
|
|
@write_type = write_type
|
|
|
|
|
@mutex = Mutex.new
|
|
|
|
|
|
|
|
|
|
# beware of: https://github.com/rapid7/rex-socket/issues/32
|
|
|
|
|
_, localhost, localport = websocket.getlocalname
|
2022-03-23 02:28:47 +00:00
|
|
|
_, peerhost, peerport = Rex::Socket.from_sockaddr(websocket.getpeername)
|
2021-11-05 10:41:07 -04:00
|
|
|
@params = Rex::Socket::Parameters.from_hash({
|
|
|
|
|
'LocalHost' => localhost,
|
|
|
|
|
'LocalPort' => localport,
|
|
|
|
|
'PeerHost' => peerhost,
|
|
|
|
|
'PeerPort' => peerport,
|
|
|
|
|
'SSL' => websocket.respond_to?(:sslctx) && !websocket.sslctx.nil?
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@thread = Rex::ThreadFactory.spawn("WebSocketChannel(#{localhost}->#{peerhost})", false) do
|
|
|
|
|
websocket.wsloop do |data, data_type|
|
|
|
|
|
next unless @read_type.nil? || data_type == @read_type
|
|
|
|
|
|
|
|
|
|
data = on_data_read(data, data_type)
|
|
|
|
|
next if data.nil?
|
|
|
|
|
|
|
|
|
|
rsock.syswrite(data)
|
2021-11-02 16:03:45 -04:00
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
close
|
2021-11-02 16:03:45 -04:00
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
lsock.extend(SocketInterface)
|
|
|
|
|
lsock.channel = self
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
rsock.extend(SocketInterface)
|
|
|
|
|
rsock.channel = self
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def closed?
|
|
|
|
|
@websocket.nil?
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def close
|
|
|
|
|
@mutex.synchronize do
|
|
|
|
|
return if closed?
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
@websocket.wsclose
|
|
|
|
|
@websocket = nil
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
cleanup_abstraction
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Close the channel for write operations. This sends a CONNECTION_CLOSE request, after which (per RFC 6455 section
|
|
|
|
|
# 5.5.1) this side must not send any more data frames.
|
|
|
|
|
#
|
|
|
|
|
def close_write
|
|
|
|
|
if closed?
|
|
|
|
|
raise IOError, 'Channel has been closed.', caller
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
@websocket.put_wsframe(Frame.new(header: { opcode: Opcode::CONNECTION_CLOSE }))
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Write *buf* to the channel, optionally truncating it to *length* bytes.
|
|
|
|
|
#
|
|
|
|
|
# @param [String] buf The data to write to the channel.
|
|
|
|
|
# @param [Integer] length An optional length to truncate *data* to before
|
|
|
|
|
# sending it.
|
|
|
|
|
def write(buf, length = nil)
|
|
|
|
|
if closed?
|
|
|
|
|
raise IOError, 'Channel has been closed.', caller
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
if !length.nil? && buf.length >= length
|
|
|
|
|
buf = buf[0..length]
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
length = buf.length
|
|
|
|
|
buf = on_data_write(buf)
|
|
|
|
|
if @write_type == :binary
|
2023-01-21 08:29:28 -05:00
|
|
|
@websocket.put_wsbinary(buf)
|
2021-11-05 10:41:07 -04:00
|
|
|
elsif @write_type == :text
|
2023-01-21 08:29:28 -05:00
|
|
|
@websocket.put_wstext(buf)
|
2021-11-05 10:41:07 -04:00
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
length
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# This provides a hook point that is called when data is read from the WebSocket peer. Subclasses can intercept and
|
|
|
|
|
# process the data. The default functionality does nothing.
|
|
|
|
|
#
|
|
|
|
|
# @param [String] data the data that was read
|
|
|
|
|
# @param [Symbol] data_type the type of data that was received, either :binary or :text
|
|
|
|
|
# @return [String, nil] if a string is returned, it's passed through the channel
|
|
|
|
|
def on_data_read(data, _data_type)
|
|
|
|
|
data
|
|
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# This provides a hook point that is called when data is written to the WebSocket peer. Subclasses can intercept and
|
|
|
|
|
# process the data. The default functionality does nothing.
|
|
|
|
|
#
|
|
|
|
|
# @param [String] data the data that is being written
|
|
|
|
|
# @return [String, nil] if a string is returned, it's passed through the channel
|
|
|
|
|
def on_data_write(data)
|
|
|
|
|
data
|
|
|
|
|
end
|
|
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Send a WebSocket::Frame to the peer.
|
|
|
|
|
#
|
|
|
|
|
# @param [WebSocket::Frame] frame the frame to send to the peer.
|
|
|
|
|
def put_wsframe(frame, opts = {})
|
|
|
|
|
put(frame.to_binary_s, opts = opts)
|
|
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Build a WebSocket::Frame representing the binary data and send it to the peer.
|
|
|
|
|
#
|
|
|
|
|
# @param [String] value the binary value to use as the frame payload.
|
|
|
|
|
def put_wsbinary(value, opts = {})
|
|
|
|
|
put_wsframe(Frame.from_binary(value), opts = opts)
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Build a WebSocket::Frame representing the text data and send it to the peer.
|
|
|
|
|
#
|
|
|
|
|
# @param [String] value the binary value to use as the frame payload.
|
|
|
|
|
def put_wstext(value, opts = {})
|
|
|
|
|
put_wsframe(Frame.from_text(value), opts = opts)
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Read a WebSocket::Frame from the peer.
|
|
|
|
|
#
|
2023-02-01 16:11:56 -05:00
|
|
|
# @return [Nil, WebSocket::Frame] the frame that was received from the peer.
|
2021-11-05 10:41:07 -04:00
|
|
|
def get_wsframe(_opts = {})
|
|
|
|
|
frame = Frame.new
|
|
|
|
|
frame.header.read(self)
|
|
|
|
|
payload_data = ''
|
|
|
|
|
while payload_data.length < frame.payload_len
|
|
|
|
|
chunk = read(frame.payload_len - payload_data.length)
|
|
|
|
|
if chunk.empty? # no partial reads!
|
|
|
|
|
elog('WebSocket::Interface#get_wsframe: received an empty websocket payload data chunk')
|
|
|
|
|
return nil
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
payload_data << chunk
|
|
|
|
|
end
|
|
|
|
|
frame.payload_data.assign(payload_data)
|
|
|
|
|
frame
|
|
|
|
|
rescue ::IOError
|
|
|
|
|
wlog('WebSocket::Interface#get_wsframe: encountered an IOError while reading a websocket frame')
|
|
|
|
|
nil
|
|
|
|
|
end
|
2021-09-24 14:18:55 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Build a channel to allow reading and writing from the WebSocket. This provides high level functionality so the
|
|
|
|
|
# caller needn't worry about individual frames.
|
|
|
|
|
#
|
|
|
|
|
# @return [WebSocket::Interface::Channel]
|
|
|
|
|
def to_wschannel(**kwargs)
|
|
|
|
|
Channel.new(self, **kwargs)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Close the WebSocket. If the underlying TCP socket is still active a WebSocket CONNECTION_CLOSE request will be sent
|
|
|
|
|
# and then it will wait for a CONNECTION_CLOSE response. Once completed the underlying TCP socket will be closed.
|
|
|
|
|
#
|
|
|
|
|
def wsclose(opts = {})
|
|
|
|
|
return if closed? # there's nothing to do if the underlying TCP socket has already been closed
|
|
|
|
|
|
|
|
|
|
# this implementation doesn't handle the optional close reasons at all
|
|
|
|
|
frame = Frame.new(header: { opcode: Opcode::CONNECTION_CLOSE })
|
|
|
|
|
# close frames must be masked
|
|
|
|
|
# see: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.1
|
|
|
|
|
frame.mask!
|
|
|
|
|
put_wsframe(frame, opts = opts)
|
|
|
|
|
while (frame = get_wsframe(opts))
|
2023-02-01 16:11:56 -05:00
|
|
|
break if frame.nil?
|
|
|
|
|
break if frame.header.opcode == Opcode::CONNECTION_CLOSE
|
2021-11-05 10:41:07 -04:00
|
|
|
# all other frames are dropped after our connection close request is sent
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
close # close the underlying TCP socket
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
# Run a loop to handle data from the remote end of the websocket. The loop will automatically handle fragmentation
|
|
|
|
|
# unmasking payload data and ping requests. When the remote connection is closed, the loop will exit. If specified the
|
|
|
|
|
# block will be passed data chunks and their data types.
|
|
|
|
|
#
|
|
|
|
|
def wsloop(opts = {}, &block)
|
|
|
|
|
buffer = ''
|
|
|
|
|
buffer_type = nil
|
|
|
|
|
|
|
|
|
|
# since web sockets have their own tear down exchange, use a synchronization lock to ensure we aren't closed until
|
|
|
|
|
# either the remote socket is closed or the teardown takes place
|
|
|
|
|
@wsstream_lock = Rex::ReadWriteLock.new
|
|
|
|
|
@wsstream_lock.synchronize_read do
|
|
|
|
|
while (frame = get_wsframe(opts))
|
|
|
|
|
frame.unmask! if frame.header.masked == 1
|
|
|
|
|
|
|
|
|
|
case frame.header.opcode
|
|
|
|
|
when Opcode::CONNECTION_CLOSE
|
|
|
|
|
put_wsframe(Frame.new(header: { opcode: Opcode::CONNECTION_CLOSE }).tap { |f| f.mask! }, opts = opts)
|
|
|
|
|
break
|
|
|
|
|
when Opcode::CONTINUATION
|
|
|
|
|
# a continuation frame can only be sent for a data frames
|
|
|
|
|
# see: https://datatracker.ietf.org/doc/html/rfc6455#section-5.4
|
|
|
|
|
raise WebSocketError, 'Received an unexpected continuation frame (uninitialized buffer)' if buffer_type.nil?
|
|
|
|
|
|
|
|
|
|
buffer << frame.payload_data
|
|
|
|
|
when Opcode::BINARY
|
|
|
|
|
raise WebSocketError, 'Received an unexpected binary frame (incomplete buffer)' unless buffer_type.nil?
|
|
|
|
|
|
|
|
|
|
buffer = frame.payload_data
|
|
|
|
|
buffer_type = :binary
|
|
|
|
|
when Opcode::TEXT
|
|
|
|
|
raise WebSocketError, 'Received an unexpected text frame (incomplete buffer)' unless buffer_type.nil?
|
|
|
|
|
|
|
|
|
|
buffer = frame.payload_data
|
|
|
|
|
buffer_type = :text
|
|
|
|
|
when Opcode::PING
|
|
|
|
|
# see: https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2
|
|
|
|
|
put_wsframe(frame.dup.tap { |f| f.header.opcode = Opcode::PONG }, opts = opts)
|
2021-11-02 16:03:45 -04:00
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
next unless frame.header.fin == 1
|
|
|
|
|
|
|
|
|
|
if block_given?
|
|
|
|
|
# text data is UTF-8 encoded
|
|
|
|
|
# see: https://datatracker.ietf.org/doc/html/rfc6455#section-5.6
|
|
|
|
|
buffer.force_encoding('UTF-8') if buffer_type == :text
|
|
|
|
|
# release the stream lock before entering the callback, allowing it to close the socket if desired
|
|
|
|
|
@wsstream_lock.unlock_read
|
2021-09-30 16:53:24 -04:00
|
|
|
begin
|
2021-11-05 10:41:07 -04:00
|
|
|
block.call(buffer, buffer_type)
|
2021-09-30 16:53:24 -04:00
|
|
|
ensure
|
2021-11-05 10:41:07 -04:00
|
|
|
@wsstream_lock.lock_read
|
2021-09-30 16:53:24 -04:00
|
|
|
end
|
|
|
|
|
end
|
2021-11-05 10:41:07 -04:00
|
|
|
|
|
|
|
|
buffer = ''
|
|
|
|
|
buffer_type = nil
|
2021-09-30 16:53:24 -04:00
|
|
|
end
|
2021-11-05 10:41:07 -04:00
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
close
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def close
|
|
|
|
|
# if #wsloop was ever called, a synchronization lock will have been initialized
|
|
|
|
|
@wsstream_lock.lock_write unless @wsstream_lock.nil?
|
|
|
|
|
begin
|
|
|
|
|
super
|
|
|
|
|
ensure
|
|
|
|
|
@wsstream_lock.unlock_write unless @wsstream_lock.nil?
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
class Opcode < BinData::Bit4
|
|
|
|
|
CONTINUATION = 0
|
|
|
|
|
TEXT = 1
|
|
|
|
|
BINARY = 2
|
|
|
|
|
CONNECTION_CLOSE = 8
|
|
|
|
|
PING = 9
|
|
|
|
|
PONG = 10
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
default_parameter assert: -> { !Opcode.name(value).nil? }
|
|
|
|
|
|
|
|
|
|
def self.name(value)
|
|
|
|
|
constants.select { |c| c.upcase == c }.find { |c| const_get(c) == value }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def to_sym
|
|
|
|
|
self.class.name(value)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
class Frame < BinData::Record
|
|
|
|
|
endian :big
|
|
|
|
|
|
|
|
|
|
struct :header do
|
|
|
|
|
endian :big
|
|
|
|
|
hide :rsv1, :rsv2, :rsv3
|
|
|
|
|
|
|
|
|
|
bit1 :fin, initial_value: 1
|
|
|
|
|
bit1 :rsv1
|
|
|
|
|
bit1 :rsv2
|
|
|
|
|
bit1 :rsv3
|
|
|
|
|
opcode :opcode
|
|
|
|
|
bit1 :masked
|
|
|
|
|
bit7 :payload_len_sm
|
|
|
|
|
uint16 :payload_len_md, onlyif: -> { payload_len_sm == 126 }
|
|
|
|
|
uint64 :payload_len_lg, onlyif: -> { payload_len_sm == 127 }
|
|
|
|
|
uint32 :masking_key, onlyif: -> { masked == 1 }
|
|
|
|
|
end
|
|
|
|
|
string :payload_data, read_length: -> { payload_len }
|
|
|
|
|
|
|
|
|
|
class << self
|
|
|
|
|
private
|
|
|
|
|
|
|
|
|
|
def from_opcode(opcode, payload, last: true, mask: true)
|
|
|
|
|
frame = Frame.new(header: { fin: (last ? 1 : 0), opcode: opcode })
|
|
|
|
|
frame.payload_len = payload.length
|
|
|
|
|
frame.payload_data = payload
|
|
|
|
|
|
|
|
|
|
case mask
|
|
|
|
|
when TrueClass
|
|
|
|
|
frame.mask!
|
|
|
|
|
when Integer
|
|
|
|
|
frame.mask!(mask)
|
|
|
|
|
when FalseClass
|
|
|
|
|
else
|
|
|
|
|
raise ArgumentError, 'mask must be true, false or an integer (literal masking key)'
|
2021-11-02 16:03:45 -04:00
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
frame
|
|
|
|
|
end
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def self.apply_masking_key(data, mask)
|
|
|
|
|
mask = [mask].pack('N').each_byte.to_a
|
|
|
|
|
xored = ''
|
|
|
|
|
data.each_byte.each_with_index do |byte, index|
|
|
|
|
|
xored << (byte ^ mask[index % 4]).chr
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
xored
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def self.from_binary(value, last: true, mask: true)
|
|
|
|
|
from_opcode(Opcode::BINARY, value, last: last, mask: mask)
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def self.from_text(value, last: true, mask: true)
|
|
|
|
|
from_opcode(Opcode::TEXT, value, last: last, mask: mask)
|
|
|
|
|
end
|
2021-09-23 15:58:25 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Update the frame instance in place to apply a masking key to the payload data as defined in RFC 6455 section 5.3.
|
|
|
|
|
#
|
|
|
|
|
# @param [nil, Integer] key either an explicit 32-bit masking key or nil to generate a random one
|
|
|
|
|
# @return [String] the masked payload data is returned
|
|
|
|
|
def mask!(key = nil)
|
|
|
|
|
header.masked.assign(1)
|
|
|
|
|
key = rand(1..0xffffffff) if key.nil?
|
|
|
|
|
header.masking_key.assign(key)
|
|
|
|
|
payload_data.assign(self.class.apply_masking_key(payload_data, header.masking_key))
|
|
|
|
|
payload_data.value
|
|
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
#
|
|
|
|
|
# Update the frame instance in place to apply a masking key to the payload data as defined in RFC 6455 section 5.3.
|
|
|
|
|
#
|
|
|
|
|
# @return [String] the unmasked payload data is returned
|
|
|
|
|
def unmask!
|
|
|
|
|
payload_data.assign(self.class.apply_masking_key(payload_data, header.masking_key))
|
|
|
|
|
header.masked.assign(0)
|
|
|
|
|
payload_data.value
|
|
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def payload_len
|
|
|
|
|
case header.payload_len_sm
|
|
|
|
|
when 127
|
|
|
|
|
header.payload_len_lg
|
|
|
|
|
when 126
|
|
|
|
|
header.payload_len_md
|
|
|
|
|
else
|
|
|
|
|
header.payload_len_sm
|
|
|
|
|
end
|
|
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
|
2021-11-05 10:41:07 -04:00
|
|
|
def payload_len=(value)
|
|
|
|
|
if value < 126
|
|
|
|
|
header.payload_len_sm.assign(value)
|
|
|
|
|
elsif value < 0xffff
|
|
|
|
|
header.payload_len_sm.assign(126)
|
|
|
|
|
header.payload_len_md.assign(value)
|
|
|
|
|
elsif value < 0x7fffffffffffffff
|
|
|
|
|
header.payload_len_sm.assign(127)
|
|
|
|
|
header.payload_len_lg.assign(value)
|
|
|
|
|
else
|
|
|
|
|
raise ArgumentError, 'payload length is outside the acceptable range'
|
2021-11-02 16:03:45 -04:00
|
|
|
end
|
2020-07-31 10:08:26 -04:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|