ronin-support-web 0.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-support-web - A web support library for ronin-rb.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-support-web is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-support-web is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-support-web. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/support/web/websocket/socket'
22
+ require 'ronin/support/web/websocket/url_methods'
23
+ require 'ronin/support/network/tcp'
24
+ require 'ronin/support/network/ssl'
25
+
26
+ require 'websocket'
27
+
28
+ module Ronin
29
+ module Support
30
+ module Web
31
+ module WebSocket
32
+ #
33
+ # Represents a WebSocket server.
34
+ #
35
+ class Server
36
+
37
+ include URLMethods
38
+
39
+ # The underlying server socket.
40
+ #
41
+ # @return [TCPServer, OpenSSL::SSL::SSLServer]
42
+ attr_reader :socket
43
+
44
+ #
45
+ # Initializes the WebSocket server.
46
+ #
47
+ # @param [String, URI::WS, URI::WSS] url
48
+ # The `ws://` or `wss://` URL.
49
+ #
50
+ # @param [String, nil] bind_host
51
+ # The optional host to bind the server socket to.
52
+ #
53
+ # @param [Integer, nil] bind_port
54
+ # The optioanl port to bind the server socket to. If not specified,
55
+ # it will default to the port of the URL.
56
+ #
57
+ # @param [Integer] backlog
58
+ # The maximum backlog of pending connections.
59
+ #
60
+ # @param [Hash{Symbol => Object}] ssl
61
+ # Additional keyword arguments for
62
+ # `Ronin::Support::Network::SSL.server`.
63
+ #
64
+ # @option ssl [1, 1.1, 1.2, String, Symbol, nil] :version
65
+ # The SSL version to use.
66
+ #
67
+ # @option ssl [Symbol, Boolean] :verify
68
+ # Specifies whether to verify the SSL certificate.
69
+ # May be one of the following:
70
+ #
71
+ # * `:none`
72
+ # * `:peer`
73
+ # * `:fail_if_no_peer_cert`
74
+ # * `:client_once`
75
+ #
76
+ # @option ssl [Crypto::Key::RSA, OpenSSL::PKey::RSA, nil] :key
77
+ # The RSA key to use for the SSL context.
78
+ #
79
+ # @option ssl [String] :key_file
80
+ # The path to the SSL `.key` file.
81
+ #
82
+ # @option ssl [Crypto::Cert, OpenSSL::X509::Certificate, nil] :cert
83
+ # The X509 certificate to use for the SSL context.
84
+ #
85
+ # @option ssl [String] :cert_file
86
+ # The path to the SSL `.crt` file.
87
+ #
88
+ # @option ssl [String] :ca_bundle
89
+ # Path to the CA certificate file or directory.
90
+ #
91
+ def initialize(url, bind_host: nil,
92
+ bind_port: nil,
93
+ backlog: 5,
94
+ ssl: {})
95
+ super(url)
96
+
97
+ @bind_host = bind_host
98
+ @bind_port = bind_port || @port
99
+
100
+ @socket = case @url.scheme
101
+ when 'ws'
102
+ Support::Network::TCP.server(
103
+ host: @bind_host,
104
+ port: @bind_port,
105
+ backlog: backlog
106
+ )
107
+ when 'wss'
108
+ Support::Network::SSL.server(
109
+ host: @bind_host,
110
+ port: @bind_port,
111
+ backlog: backlog,
112
+ **ssl
113
+ )
114
+ else
115
+ raise(ArgumentError,"unsupported websocket scheme: #{url}")
116
+ end
117
+ end
118
+
119
+ #
120
+ # Sets the connection backlog for the server socket.
121
+ #
122
+ # @param [Integer] backlog
123
+ # The number of pending connection to allow.
124
+ #
125
+ def listen(backlog)
126
+ @socket.listen(backlog)
127
+ end
128
+
129
+ #
130
+ # Accepts a new WebSocket connection.
131
+ #
132
+ # @return [Client]
133
+ # The new WebSocket connection to the server.
134
+ #
135
+ def accept
136
+ Client.new(@url,@socket.accept)
137
+ end
138
+
139
+ #
140
+ # Closes the WebSocket server's socket.
141
+ #
142
+ # @api public
143
+ #
144
+ def close
145
+ @socket.close
146
+ end
147
+
148
+ #
149
+ # Determines if the WebSocket server is closed?
150
+ #
151
+ # @return [Boolean]
152
+ #
153
+ # @api public
154
+ #
155
+ def closed?
156
+ @socket.closed?
157
+ end
158
+
159
+ #
160
+ # Represents a WebSocket client connected to the Websocket server.
161
+ #
162
+ class Client < Socket
163
+
164
+ #
165
+ # Initializes a WebSocket server client.
166
+ #
167
+ # @param [URI::WS, URI::WSS] url
168
+ # The WebSocket server's `ws://` or `wss://` URL.
169
+ #
170
+ # @param [TCPSocket, OpenSSL::SSL::SSLSocket] socket
171
+ # The client's connection socket.
172
+ #
173
+ # @api private
174
+ #
175
+ def initialize(url,socket)
176
+ super()
177
+
178
+ @url = url
179
+ @socket = socket
180
+
181
+ receive_handshake!
182
+
183
+ set_frame_classes(
184
+ ::WebSocket::Frame::Incoming::Server,
185
+ ::WebSocket::Frame::Outgoing::Server
186
+ )
187
+ end
188
+
189
+ #
190
+ # Receives the WebSocket handshake.
191
+ #
192
+ # @api private
193
+ #
194
+ def receive_handshake!(**kwargs)
195
+ @handshake = ::WebSocket::Handshake::Server.new(
196
+ url: @url.to_s, **kwargs
197
+ )
198
+
199
+ @handshake << @socket.readpartial(1024) until @handshake.finished?
200
+
201
+ if @handshake.valid?
202
+ @socket.write(@handshake)
203
+ end
204
+ end
205
+
206
+ end
207
+
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-support-web - A web support library for ronin-rb.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-support-web is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-support-web is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-support-web. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'ronin/support/web/websocket/url_methods'
22
+
23
+ module Ronin
24
+ module Support
25
+ module Web
26
+ module WebSocket
27
+ #
28
+ # Base class for all WebSockets.
29
+ #
30
+ # @abstract
31
+ #
32
+ # @api private
33
+ #
34
+ class Socket
35
+
36
+ # The underlying socket.
37
+ #
38
+ # @return [TCPSocket, OpenSSL::SSL::SSLSocket]
39
+ attr_reader :socket
40
+
41
+ # The WebSocket handshake information.
42
+ #
43
+ # @return [::WebSocket::Handshake::Client, WebSocket::Handshake::Server]
44
+ attr_reader :handshake
45
+
46
+ #
47
+ # Indicates whether the handshake has finished.
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ # @api public
52
+ #
53
+ def handshake_finished?
54
+ @handshake.finished?
55
+ end
56
+
57
+ #
58
+ # Indicates whether the handshake was valid.
59
+ #
60
+ # @return [Boolean]
61
+ #
62
+ # @api public
63
+ #
64
+ def handshake_valid?
65
+ @handshake.valid?
66
+ end
67
+
68
+ #
69
+ # Sends a data frame.
70
+ #
71
+ # @param [#to_s] data
72
+ # The data to send.
73
+ #
74
+ # @param [:text, :binary, :ping, :pong, :close] type
75
+ # The data frame type.
76
+ #
77
+ # @api public
78
+ #
79
+ def send_frame(data, type: :text)
80
+ outgoing_frame = @outgoing_frame_class.new(
81
+ version: @handshake.version,
82
+ data: data,
83
+ type: type
84
+ )
85
+
86
+ @socket.write(outgoing_frame.to_s)
87
+ end
88
+
89
+ #
90
+ # Sends a data frame.
91
+ #
92
+ # @param [#to_s] data
93
+ # The data to send.
94
+ #
95
+ # @param [Hash{Symbol => Object}] kwargs
96
+ # Additional keyword arguments for {#send_frame}.
97
+ #
98
+ # @option kwargs [:text, :binary, :ping, :pong, :close] :type (:text)
99
+ # The data frame type.
100
+ #
101
+ # @api public
102
+ #
103
+ # @see #send_frame
104
+ #
105
+ def send(data,**kwargs)
106
+ send_frame(data,**kwargs)
107
+ end
108
+
109
+ #
110
+ # Receives a data frame from the WebSocket.
111
+ #
112
+ # @return [WebSocket::Frame::Incoming::Client,
113
+ # WebSocket::Frame::Incoming::Server]
114
+ # The received websocket data frame.
115
+ #
116
+ # @api public
117
+ #
118
+ def recv_frame
119
+ frame = @incoming_frame_class.new(version: @handshake.version)
120
+
121
+ begin
122
+ # read data into the input frame
123
+ frame << @socket.readpartial(1024)
124
+ rescue EOFError
125
+ return nil
126
+ end
127
+
128
+ return frame.next
129
+ end
130
+
131
+ #
132
+ # Receives a data frame.
133
+ #
134
+ # @return [String, nil]
135
+ #
136
+ # @api public
137
+ #
138
+ def recv
139
+ data = recv_frame.data
140
+ data unless data.empty?
141
+ end
142
+
143
+ #
144
+ # Closes the socket.
145
+ #
146
+ # @api public
147
+ #
148
+ def close
149
+ @socket.close
150
+ end
151
+
152
+ #
153
+ # Determines if the socket is closed.
154
+ #
155
+ # @return [Boolean]
156
+ #
157
+ # @api public
158
+ #
159
+ def closed?
160
+ @socket.closed?
161
+ end
162
+
163
+ private
164
+
165
+ #
166
+ # Sets the frame classes to use.
167
+ #
168
+ # @param [Class<WebSocket::Frame::Incoming::Client>,
169
+ # Class<WebSocket::Frame::Incoming::Server>] incoming_frame_class
170
+ #
171
+ # @param [Class<WebSocket::Frame::Outgoing::Client>,
172
+ # Class<WebSocket::Frame::Outgoing::Server>] outgoing_frame_class
173
+ #
174
+ # @api private
175
+ #
176
+ def set_frame_classes(incoming_frame_class,outgoing_frame_class)
177
+ @incoming_frame_class = incoming_frame_class
178
+ @outgoing_frame_class = outgoing_frame_class
179
+ end
180
+
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # ronin-support-web - A web support library for ronin-rb.
4
+ #
5
+ # Copyright (c) 2023-2024 Hal Brodigan (postmodern.mod3@gmail.com)
6
+ #
7
+ # ronin-support-web is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Lesser General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # ronin-support-web is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Lesser General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Lesser General Public License
18
+ # along with ronin-support-web. If not, see <https://www.gnu.org/licenses/>.
19
+ #
20
+
21
+ require 'uri'
22
+
23
+ module Ronin
24
+ module Support
25
+ module Web
26
+ module WebSocket
27
+ #
28
+ # Mixin which accepts and parses a `ws://` or `wss://` URL.
29
+ #
30
+ # @api private
31
+ #
32
+ module URLMethods
33
+ # The parsed `ws://` or `wss://` URI.
34
+ #
35
+ # @return [URI::WS, URI::WSS]
36
+ #
37
+ # @api public
38
+ attr_reader :url
39
+
40
+ # The websocket host name.
41
+ #
42
+ # @return [String]
43
+ #
44
+ # @api public
45
+ attr_reader :host
46
+
47
+ # The websocket port.
48
+ #
49
+ # @return [Integer]
50
+ #
51
+ # @api public
52
+ attr_reader :port
53
+
54
+ # The websocket port.
55
+ #
56
+ # @return [String]
57
+ #
58
+ # @api public
59
+ attr_reader :path
60
+
61
+ #
62
+ # Sets the {#url}.
63
+ #
64
+ # @param [String] url
65
+ # The `ws://` or `wss://` URL.
66
+ #
67
+ # @api public
68
+ #
69
+ def initialize(url)
70
+ @url = URI(url)
71
+ @host = @url.host
72
+ @port = @url.port
73
+ @path = @url.path
74
+ @ssl = (@url.scheme == 'wss')
75
+ end
76
+
77
+ #
78
+ # Determines whether the websocket uses SSL/TLS.
79
+ #
80
+ # @return [Boolean]
81
+ #
82
+ # @api public
83
+ #
84
+ def ssl?
85
+ @ssl
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end