websocket-server 1.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +185 -0
  4. data/Rakefile +56 -0
  5. data/exe/websocket +41 -0
  6. data/lib/log.rb +244 -0
  7. data/lib/server/mime_types.rb +38 -0
  8. data/lib/websocket/arguments_parser.rb +142 -0
  9. data/lib/websocket/channel_initializer.rb +73 -0
  10. data/lib/websocket/config.rb +59 -0
  11. data/lib/websocket/encoding.rb +21 -0
  12. data/lib/websocket/file_server_channel_progressive_future_listener.rb +32 -0
  13. data/lib/websocket/frame_handler.rb +71 -0
  14. data/lib/websocket/header_helpers.rb +70 -0
  15. data/lib/websocket/http_static_file_server_handler.rb +50 -0
  16. data/lib/websocket/http_static_file_server_handler_instance_methods.rb +160 -0
  17. data/lib/websocket/idle_handler.rb +41 -0
  18. data/lib/websocket/idle_state_user_event_handler.rb +47 -0
  19. data/lib/websocket/instance_methods.rb +127 -0
  20. data/lib/websocket/listenable.rb +41 -0
  21. data/lib/websocket/message_handler.rb +47 -0
  22. data/lib/websocket/response_helpers.rb +83 -0
  23. data/lib/websocket/server.rb +26 -0
  24. data/lib/websocket/shutdown_hook.rb +36 -0
  25. data/lib/websocket/ssl_cipher_inspector.rb +44 -0
  26. data/lib/websocket/ssl_context_initialization.rb +106 -0
  27. data/lib/websocket/telnet_proxy.rb +22 -0
  28. data/lib/websocket/validation_helpers.rb +51 -0
  29. data/lib/websocket/version.rb +16 -0
  30. data/lib/websocket-server.rb +13 -0
  31. data/lib/websocket_client.rb +478 -0
  32. data/lib/websocket_server.rb +50 -0
  33. data/web/client.html +43 -0
  34. data/web/css/client/console.css +167 -0
  35. data/web/css/client/parchment.css +112 -0
  36. data/web/favicon.ico +0 -0
  37. data/web/fonts/droidsansmono.v4.woff +0 -0
  38. data/web/js/client/ansispan.js +103 -0
  39. data/web/js/client/client.js +144 -0
  40. data/web/js/client/console.js +393 -0
  41. data/web/js/client/websocket.js +76 -0
  42. data/web/js/jquery.min.js +2 -0
  43. metadata +145 -0
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ require_relative 'idle_state_user_event_handler'
17
+
18
+ # The WebSocket module
19
+ module WebSocket
20
+ java_import Java::io.netty.handler.codec.http.websocketx.TextWebSocketFrame
21
+
22
+ # The IdleHandler class handles idle channels detected by the
23
+ # server pipeline.
24
+ class IdleHandler < WebSocket::IdleStateUserEventHandler
25
+ def initialize
26
+ super()
27
+ end
28
+
29
+ def handle_idle_channel(ctx, _evt)
30
+ klass = self.class.name
31
+ log.debug "#{klass} < IdleStateUserEventHandler ##{__method__}"
32
+ message = TextWebSocketFrame.new("\nDisconnecting idle session\n")
33
+ # TODO: Test
34
+ ctx.writeAndFlush(message).sync
35
+ ctx.close
36
+ # ctx.channel.writeAndFlush(message).sync
37
+ # ctx.channel.disconnect().awaitUninterruptibly()
38
+ # ctx.channel.close().awaitUninterruptibly()
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ # The WebSocket module
17
+ module WebSocket
18
+ java_import Java::io.netty.channel.ChannelDuplexHandler
19
+ java_import Java::io.netty.handler.codec.http.websocketx.PingWebSocketFrame
20
+ java_import Java::io.netty.handler.timeout.IdleState
21
+
22
+ # The PingMessage class is just a convenient alias
23
+ # for the PingWebSocketFrame class.
24
+ PingMessage = Class.new(PingWebSocketFrame)
25
+
26
+ # The IdleStateUserEventHandler class specifies methods implementing
27
+ # what to do when the pipeline detects an idle channel.
28
+ class IdleStateUserEventHandler < ChannelDuplexHandler
29
+ def initialize
30
+ super()
31
+ end
32
+
33
+ # java_signature 'public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception'
34
+ def userEventTriggered(ctx, evt)
35
+ return unless evt.respond_to?(:state)
36
+ case evt.state
37
+ when IdleState::READER_IDLE
38
+ return handle_idle_channel(ctx, evt) if respond_to?(:handle_idle_channel)
39
+ message = TextWebSocketFrame.new("\nDisconnecting idle session\n")
40
+ ctx.writeAndFlush(message).sync
41
+ ctx.close
42
+ when IdleState::WRITER_IDLE
43
+ ctx.writeAndFlush(PingMessage.new)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ require_relative 'channel_initializer'
17
+ require_relative 'shutdown_hook'
18
+
19
+ # The WebSocket module
20
+ module WebSocket
21
+ java_import Java::io.netty.bootstrap.ServerBootstrap
22
+ java_import Java::io.netty.channel.group.DefaultChannelGroup
23
+ java_import Java::io.netty.channel.nio.NioEventLoopGroup
24
+ java_import Java::io.netty.handler.logging.LoggingHandler
25
+ java_import Java::io.netty.handler.logging.LogLevel
26
+ java_import Java::io.netty.util.concurrent.GlobalEventExecutor
27
+
28
+ # The InstanceMethods module
29
+ module InstanceMethods
30
+ def configure_handlers(&block)
31
+ add_listener(self)
32
+ channel_initializer << block if block_given?
33
+ end
34
+
35
+ def bootstrap
36
+ @bootstrap = ServerBootstrap.new
37
+ @bootstrap.group(boss_group, worker_group)
38
+ @bootstrap.channel(Server::CHANNEL_TYPE)
39
+ @bootstrap.handler(logging_handler) if @options[:log_requests]
40
+ @bootstrap.childHandler(channel_initializer)
41
+ end
42
+
43
+ def channel_initializer
44
+ @channel_initializer ||= ::WebSocket::ChannelInitializer.new(channel_group, @options)
45
+ end
46
+
47
+ def boss_group
48
+ @boss_group ||= NioEventLoopGroup.new(2)
49
+ end
50
+
51
+ def worker_group
52
+ @worker_group ||= NioEventLoopGroup.new
53
+ end
54
+
55
+ def channel_group
56
+ @channel_group ||= DefaultChannelGroup.new('server_channels', GlobalEventExecutor::INSTANCE)
57
+ end
58
+
59
+ def logging_handler
60
+ LoggingHandler.new(LogLevel::INFO)
61
+ end
62
+
63
+ # rubocop: disable Metrics/AbcSize
64
+ # rubocop: disable Metrics/MethodLength
65
+ def run(params = {})
66
+ options.merge!(params)
67
+ channel = bootstrap.bind(port).sync().channel()
68
+ channel_group.add(channel)
69
+ ::WebSocket::ShutdownHook.new(self)
70
+ log.info "Listening on #{channel.local_address}"
71
+ channel.closeFuture().sync()
72
+ rescue java.net.BindException => e
73
+ raise "Bind error: #{e.message}: #{options[:host]}:#{port}"
74
+ rescue java.net.SocketException => e
75
+ raise "Socket error: #{e.message}: #{options[:host]}:#{port}"
76
+ ensure
77
+ stop
78
+ end
79
+ # rubocop: enable Metrics/AbcSize
80
+ # rubocop: enable Metrics/MethodLength
81
+
82
+ IntegerPattern = /^\d+$/.freeze
83
+
84
+ def valid_port?(val)
85
+ IntegerPattern.match?(val.to_s) && val.positive? && val < 65_536
86
+ end
87
+
88
+ def given_port
89
+ value = (options[:ssl] ? options[:ssl_port] : options[:port]).to_i
90
+ raise 'Given port parameter is invalid' unless valid_port?(value)
91
+ value
92
+ end
93
+
94
+ def port
95
+ @port ||= given_port
96
+ end
97
+
98
+ def shutdown
99
+ channel_group.disconnect().awaitUninterruptibly()
100
+ channel_group.close().awaitUninterruptibly()
101
+ end
102
+
103
+ def stop
104
+ boss_group&.shutdownGracefully()
105
+ worker_group&.shutdownGracefully()
106
+ end
107
+
108
+ def <<(handler)
109
+ channel_initializer.handlers << handler
110
+ end
111
+
112
+ def add_listener(listener)
113
+ channel_initializer.add_listener(listener)
114
+ end
115
+
116
+ # def all_exist?(*files)
117
+ # files.all? { |f| File.exist?(f) }
118
+ # end
119
+
120
+ # def init_ssl_context(certificate, private_key)
121
+ # return certificate_key_pair(certificate, private_key) if all_exist?(certificate, private_key)
122
+ # options[:use_jdk_ssl_provider] ? jdk_ssl_provider : self_signed_certificate
123
+ # end
124
+ end
125
+ # module ServerInstanceMethods
126
+ end
127
+ # module WebSocket
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+
15
+ # The WebSocket module
16
+ module WebSocket
17
+ # The Listenable module
18
+ module Listenable
19
+ def listeners
20
+ @listeners ||= java.util.concurrent.CopyOnWriteArrayList.new
21
+ end
22
+
23
+ def add_listener(listener)
24
+ listeners << listener
25
+ end
26
+
27
+ def remove_listener(listener)
28
+ listeners.delete(listener)
29
+ end
30
+
31
+ def notify(message, *args)
32
+ return if listeners.empty?
33
+ log.trace "Notifying listeners (#{listeners}) of message: #{message}"
34
+ listeners.each do |listener|
35
+ listener.send(message.to_sym, *args) if listener.respond_to?(message.to_sym)
36
+ end
37
+ end
38
+ end
39
+ # module Listenable
40
+ end
41
+ # module WebSocket
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ require_relative 'frame_handler'
17
+
18
+ # The WebSocket module
19
+ module WebSocket
20
+ java_import Java::io.netty.channel.ChannelHandler
21
+ java_import Java::io.netty.channel.ChannelFutureListener
22
+ java_import Java::io.netty.handler.codec.http.websocketx.TextWebSocketFrame
23
+
24
+ # The MessageHandler class provides a method for specifying code
25
+ # to handle incoming messages and respond with the results from
26
+ # the given code.
27
+ class MessageHandler < WebSocket::FrameHandler
28
+ include ChannelHandler
29
+ include ChannelFutureListener
30
+
31
+ def initialize(&handler)
32
+ super()
33
+ @handler = handler
34
+ end
35
+
36
+ def handle_message(ctx, message)
37
+ request = message&.to_s&.strip
38
+ return if request.nil?
39
+ response = @handler.call(ctx, request)
40
+ return if response.nil?
41
+ log.debug "#{self.class}#handle_message response: #{response.chomp}"
42
+ ctx.channel.writeAndFlush(TextWebSocketFrame.new(response))
43
+ response
44
+ end
45
+ end
46
+ end
47
+ # module WebSocket
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ require_relative 'encoding'
17
+
18
+ # The WebSocket module
19
+ module WebSocket
20
+ java_import Java::io.netty.channel.ChannelFutureListener
21
+ java_import Java::io.netty.buffer.Unpooled
22
+ java_import Java::io.netty.handler.codec.http.DefaultFullHttpResponse
23
+ java_import Java::io.netty.handler.codec.http.HttpHeaderNames
24
+ java_import Java::io.netty.handler.codec.http.HttpVersion
25
+ java_import Java::io.netty.util.CharsetUtil
26
+
27
+ # The ResponseHelpers helpers
28
+ module ResponseHelpers
29
+ NonSpacesBeforeEOLPattern = %r{([^\s]+)$}.freeze
30
+
31
+ def index_listing(path)
32
+ results = `ls -la #{path}`.strip.split("\n")
33
+ results.shift
34
+ index = results.collect do |s|
35
+ s.gsub(NonSpacesBeforeEOLPattern) do |resource_name|
36
+ %(<a href="#{resource_name}">#{resource_name}</a>)
37
+ end
38
+ end.join '<br />'
39
+ "<html><pre>#{index}</pre></html>"
40
+ end
41
+
42
+ def send_listing(ctx, path)
43
+ response = DefaultFullHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::OK)
44
+ response.headers().set(HttpHeaderNames::CONTENT_TYPE, WebSocket::HtmlContentType)
45
+
46
+ html = index_listing(path)
47
+
48
+ buffer = Unpooled.copiedBuffer(html, WebSocket::Encoding)
49
+ response.content().writeBytes(buffer)
50
+ buffer.release()
51
+
52
+ # Close the connection as soon as the error message is sent.
53
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener::CLOSE)
54
+ end
55
+
56
+ def send_redirect(ctx, redirect_to_uri)
57
+ response = DefaultFullHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::FOUND)
58
+ response.headers().set(HttpHeaderNames::LOCATION, redirect_to_uri)
59
+
60
+ # Close the connection as soon as the error message is sent.
61
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener::CLOSE)
62
+ end
63
+
64
+ def send_error(ctx, status)
65
+ message = Unpooled.copiedBuffer("#{status}\r\n", CharsetUtil::UTF_8)
66
+ response = DefaultFullHttpResponse.new(HttpVersion::HTTP_1_1, status, message)
67
+ response.headers().set(HttpHeaderNames::CONTENT_TYPE, WebSocket::HtmlContentType)
68
+
69
+ # Close the connection as soon as the error message is sent.
70
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener::CLOSE)
71
+ end
72
+
73
+ def send_not_modified(ctx, date)
74
+ response = DefaultFullHttpResponse.new(HttpVersion::HTTP_1_1, HttpResponseStatus::NOT_MODIFIED)
75
+ date_header(response, date)
76
+
77
+ # Close the connection as soon as the error message is sent.
78
+ ctx.writeAndFlush(response).addListener(ChannelFutureListener::CLOSE)
79
+ end
80
+ end
81
+ # module ResponseHelpers
82
+ end
83
+ # module WebSocket
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+ require 'tcp-server'
16
+
17
+ require_relative 'instance_methods'
18
+
19
+ # The WebSocket module
20
+ module WebSocket
21
+ # The Server class sets up the netty server
22
+ class Server < ::Server::Server
23
+ include ::WebSocket::InstanceMethods
24
+ end
25
+ end
26
+ # module WebSocket
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ # The WebSocket module
17
+ module WebSocket
18
+ # The ShutdownHook class specifies a routine to be invoked when the
19
+ # java runtime is shutdown.
20
+ class ShutdownHook < java.lang.Thread
21
+ attr_reader :server
22
+
23
+ def initialize(server)
24
+ super()
25
+ @server = server
26
+ java.lang.Runtime.runtime.add_shutdown_hook(self)
27
+ end
28
+
29
+ def run
30
+ $stdout.write "\r\e[0K"
31
+ $stdout.flush
32
+ ::WebSocket::Server.log.info 'Shutting down'
33
+ server.shutdown if server.respond_to?(:shutdown)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ # The WebSocket module
17
+ module WebSocket
18
+ java_import Java::io.netty.channel.SimpleChannelInboundHandler
19
+ java_import Java::io.netty.handler.ssl.SslHandler
20
+
21
+ # The SslCipherInspector class enables debugging the details around
22
+ # cipher configuration and pipeline handling of SSL handshakes.
23
+ class SslCipherInspector < SimpleChannelInboundHandler
24
+ def initialize
25
+ super()
26
+ end
27
+
28
+ def channelRead(ctx, msg)
29
+ cipher_suite = cipher_suite ctx
30
+ log.info "Server communications are secured by #{cipher_suite}"
31
+ ctx.fireChannelRead(msg)
32
+ end
33
+
34
+ private
35
+
36
+ def cipher_suite(ctx)
37
+ # Confirm the SSL context has been loaded into the channel pipeline
38
+ ssl_handler = ctx.pipeline.get(SslHandler.java_class)
39
+ ssl_engine = ssl_handler.engine()
40
+ ssl_session = ssl_engine.getSession()
41
+ ssl_session.getCipherSuite()
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ # The WebSocket module
17
+ module WebSocket
18
+ # This module implements methods responsible for the
19
+ # initialization of an SSL context
20
+ module SslContextInitializationMethods
21
+ java_import Java::io.netty.handler.ssl.SslContextBuilder
22
+ java_import Java::io.netty.handler.ssl.SslProvider
23
+ java_import Java::io.netty.handler.ssl.util.SelfSignedCertificate
24
+ java_import java.io.FileInputStream
25
+ java_import java.io.BufferedInputStream
26
+ java_import java.io.ByteArrayOutputStream
27
+ java_import java.security.KeyFactory
28
+ java_import java.security.cert.CertificateFactory
29
+ java_import java.security.spec.PKCS8EncodedKeySpec
30
+
31
+ DefaultCertificateType = 'X.509'.freeze
32
+
33
+ def init_ssl(channel_initializer)
34
+ return unless options[:ssl] && channel_initializer.respond_to?(:ssl_context)
35
+ context = ssl_context
36
+ channel_initializer.ssl_context = context unless context.nil?
37
+ end
38
+
39
+ def ssl_context
40
+ @ssl_context ||= init_ssl_context(options[:ssl_certificate_file_path], options[:ssl_private_key_file_path])
41
+ end
42
+
43
+ def certificate_key_pair(certificate, private_key)
44
+ log.info "Securing socket layer using #{certificate} and #{private_key}"
45
+ certificate = read_certificate(certificate)
46
+ private_key = read_private_key(private_key)
47
+ SslContextBuilder.forServer(private_key, certificate).build()
48
+ end
49
+
50
+ def jdk_ssl_provider
51
+ log.info 'Securing socket layer using JDK self-signed certificate'
52
+ ssc = SelfSignedCertificate.new
53
+ context_builder = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
54
+ context_builder.sslProvider(SslProvider::JDK).build()
55
+ end
56
+
57
+ def self_signed_certificate
58
+ log.info 'Securing socket layer using self-signed certificate'
59
+ ssc = SelfSignedCertificate.new
60
+ SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build()
61
+ end
62
+
63
+ def read_certificate(file_path)
64
+ file_input_stream = FileInputStream.new(file_path)
65
+ buffered_input_stream = BufferedInputStream.new(file_input_stream)
66
+ CertificateFactory.getInstance(DefaultCertificateType).generateCertificate(buffered_input_stream)
67
+ end
68
+
69
+ def read_private_key(file_path)
70
+ file_input_stream = FileInputStream.new(file_path)
71
+ buffered_input_stream = BufferedInputStream.new(file_input_stream)
72
+ decode_private_key(stream_data(buffered_input_stream))
73
+ ensure
74
+ close_streams(buffered_input_stream, file_input_stream)
75
+ end
76
+
77
+ def stream_data(input_stream, output_stream = ByteArrayOutputStream.new)
78
+ buffer = Java::byte[1024 * 4].new
79
+ loop do
80
+ n = input_stream.read(buffer)
81
+ break if n == -1
82
+ output_stream.write(buffer, 0, n)
83
+ end
84
+ output_stream.toByteArray()
85
+ ensure
86
+ close_streams(output_stream)
87
+ end
88
+
89
+ def close_streams(*streams)
90
+ streams.each { |stream| safely_close_stream(stream) }
91
+ end
92
+
93
+ def safely_close_stream(stream)
94
+ stream.close()
95
+ rescue StandardError => e
96
+ log.warn "Failed to close stream: #{e.message}"
97
+ end
98
+
99
+ def decode_private_key(encoded_private_key)
100
+ private_key_spec = PKCS8EncodedKeySpec.new(encoded_private_key)
101
+ KeyFactory.getInstance('RSA').generatePrivate(private_key_spec)
102
+ end
103
+ end
104
+ # module SslContextInitializationMethods
105
+ end
106
+ # module WebSocket
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ # The WebSocket module
14
+ module WebSocket
15
+ # The TelnetProxy class adds a handler to the server channel
16
+ # pipeline which will proxy incoming requests to a telnet server.
17
+ class TelnetProxy
18
+ def initialize(host, port)
19
+ pipeline << TelnetProxyFrontendHandler.new(host, port)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ require 'java'
14
+ require 'netty'
15
+
16
+ require_relative 'encoding'
17
+
18
+ # The WebSocket module
19
+ module WebSocket
20
+ # The ValidationHelpers module
21
+ module ValidationHelpers
22
+ FileSeparatorDotPattern = %r{#{File::SEPARATOR}\.}.freeze
23
+ DotFileSeparatorPattern = %r{\.#{File::SEPARATOR}}.freeze
24
+
25
+ # Simplistic dumb security check.
26
+ # Something more serious is required in a production environment.
27
+ def insecure_uri?(uri)
28
+ FileSeparatorDotPattern.match?(uri) ||
29
+ DotFileSeparatorPattern.match?(uri) ||
30
+ uri.start_with?('.') ||
31
+ uri.end_with?('.') ||
32
+ options[:insecure_uri_pattern].match?(uri)
33
+ end
34
+
35
+ QueryStringPattern = %r{\?.*}.freeze
36
+ ForwardSlashPattern = %r{/}.freeze
37
+
38
+ def sanitize_uri(uri)
39
+ # Decode the path.
40
+ uri = CGI.unescape(uri.gsub(QueryStringPattern, ''), WebSocket::Encoding.name)
41
+ return nil if uri.empty? || !uri.start_with?('/')
42
+ # Convert file separators.
43
+ uri = uri.gsub(ForwardSlashPattern, File::SEPARATOR)
44
+ return nil if insecure_uri?(uri)
45
+ # Convert to absolute path.
46
+ File.join(options[:web_root], uri)
47
+ end
48
+ end
49
+ # module ValidationHelpers
50
+ end
51
+ # module WebSocket
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: false
3
+
4
+ # -*- mode: ruby -*-
5
+ # vi: set ft=ruby :
6
+
7
+ # =begin
8
+ #
9
+ # Copyright Nels Nelson 2016-2022 but freely usable (see license)
10
+ #
11
+ # =end
12
+
13
+ # The WebSocket module
14
+ module WebSocket
15
+ VERSION = '1.0.1'.freeze
16
+ end