ronin-support-web 0.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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