Class: Rex::Proto::Http::WebSocket::Interface::Channel

Inherits:
Object
  • Object
show all
Includes:
Rex::Post::Channel::StreamAbstraction
Defined in:
lib/rex/proto/http/web_socket.rb

Overview

A channel object that allows reading and writing either text or binary data directly to the remote peer.

Defined Under Namespace

Modules: SocketInterface

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Rex::Post::Channel::StreamAbstraction

#read

Constructor Details

#initialize(websocket, read_type: nil, write_type: :binary) ⇒ Channel

Returns a new instance of Channel.

Parameters:

  • websocket (WebSocket::Interface)

    the WebSocket that this channel is being opened on

  • read_type (nil, Symbol) (defaults to: nil)

    the data type(s) to read from the WebSocket, one of :binary, :text or nil (for both binary and text)

  • write_type (Symbol) (defaults to: :binary)

    the data type to write to the WebSocket

Raises:

  • (ArgumentError)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/rex/proto/http/web_socket.rb', line 46

def initialize(websocket, read_type: nil, write_type: :binary)
  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
  _, peerhost, peerport = Rex::Socket.from_sockaddr(websocket.getpeername)
  @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)
    end

    close
  end

  lsock.extend(SocketInterface)
  lsock.channel = self

  rsock.extend(SocketInterface)
  rsock.channel = self
end

Instance Attribute Details

#paramsObject (readonly)

The socket parameters describing the underlying connection.



40
41
42
# File 'lib/rex/proto/http/web_socket.rb', line 40

def params
  @params
end

Instance Method Details

#closeObject



93
94
95
96
97
98
99
100
101
102
# File 'lib/rex/proto/http/web_socket.rb', line 93

def close
  @mutex.synchronize do
    return if closed?

    @websocket.wsclose
    @websocket = nil
  end

  cleanup_abstraction
end

#close_writeObject

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.



108
109
110
111
112
113
114
# File 'lib/rex/proto/http/web_socket.rb', line 108

def close_write
  if closed?
    raise IOError, 'Channel has been closed.', caller
  end

  @websocket.put_wsframe(Frame.new(header: { opcode: Opcode::CONNECTION_CLOSE }))
end

#closed?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/rex/proto/http/web_socket.rb', line 89

def closed?
  @websocket.nil?
end

#on_data_read(data, _data_type) ⇒ String?

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.

Parameters:

  • data (String)

    the data that was read

  • data_type (Symbol)

    the type of data that was received, either :binary or :text

Returns:

  • (String, nil)

    if a string is returned, it's passed through the channel



149
150
151
# File 'lib/rex/proto/http/web_socket.rb', line 149

def on_data_read(data, _data_type)
  data
end

#on_data_write(data) ⇒ String?

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.

Parameters:

  • data (String)

    the data that is being written

Returns:

  • (String, nil)

    if a string is returned, it's passed through the channel



159
160
161
# File 'lib/rex/proto/http/web_socket.rb', line 159

def on_data_write(data)
  data
end

#write(buf, length = nil) ⇒ Object

Write buf to the channel, optionally truncating it to length bytes.

Parameters:

  • buf (String)

    The data to write to the channel.

  • length (Integer) (defaults to: nil)

    An optional length to truncate data to before sending it.



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/rex/proto/http/web_socket.rb', line 122

def write(buf, length = nil)
  if closed?
    raise IOError, 'Channel has been closed.', caller
  end

  if !length.nil? && buf.length >= length
    buf = buf[0..length]
  end

  length = buf.length
  buf = on_data_write(buf)
  if @write_type == :binary
    @websocket.put_wsbinary(buf)
  elsif @write_type == :text
    @websocket.put_wstext(buf)
  end

  length
end