diff --git a/library/server/ewsgi/connectors/standalone/standalone_websocket-safe.ecf b/library/server/ewsgi/connectors/standalone/standalone_websocket-safe.ecf
new file mode 100644
index 00000000..1955bb7a
--- /dev/null
+++ b/library/server/ewsgi/connectors/standalone/standalone_websocket-safe.ecf
@@ -0,0 +1,24 @@
+
+
+
+
+
+ /EIFGENs$
+ /\.git$
+ /\.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/server/wsf/connector/standalone_websocket-safe.ecf b/library/server/wsf/connector/standalone_websocket-safe.ecf
new file mode 100644
index 00000000..01484e5b
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket-safe.ecf
@@ -0,0 +1,25 @@
+
+
+
+
+
+ /EIFGENs$
+ /\.git$
+ /\.svn$
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e b/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e
new file mode 100644
index 00000000..b8e8994d
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_event_i.e
@@ -0,0 +1,183 @@
+note
+ description: "[
+ API to perform actions like opening and closing the connection, sending and receiving messages, and listening
+ for events.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ WEB_SOCKET_EVENT_I
+
+inherit
+ WEB_SOCKET_CONSTANTS
+
+ REFACTORING_HELPER
+
+feature -- Web Socket Interface
+
+ on_event (conn: HTTPD_STREAM_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
+ -- Called when a frame from the client has been receive
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ local
+ l_message: READABLE_STRING_8
+ do
+ debug ("ws")
+ print ("%Non_event (conn, a_message, " + opcode_name (a_opcode) + ")%N")
+ end
+ if a_message = Void then
+ create {STRING} l_message.make_empty
+ else
+ l_message := a_message
+ end
+
+ if a_opcode = Binary_frame then
+ on_binary (conn, l_message)
+ elseif a_opcode = Text_frame then
+ on_text (conn, l_message)
+ elseif a_opcode = Pong_frame then
+ on_pong (conn, l_message)
+ elseif a_opcode = Ping_frame then
+ on_ping (conn, l_message)
+ elseif a_opcode = Connection_close_frame then
+ on_connection_close (conn, "")
+ else
+ on_unsupported (conn, l_message, a_opcode)
+ end
+ end
+
+ on_open (conn: HTTPD_STREAM_SOCKET)
+ -- Called after handshake, indicates that a complete WebSocket connection has been established.
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ deferred
+ end
+
+ on_binary (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ deferred
+ end
+
+ on_pong (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ do
+ -- log ("Its a pong frame")
+ -- at first we ignore pong
+ -- FIXME: provide better explanation
+ end
+
+ on_ping (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ do
+ send (conn, Pong_frame, a_message)
+ end
+
+ on_text (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8)
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ deferred
+ end
+
+ on_unsupported (conn: HTTPD_STREAM_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ do
+ -- do nothing
+ end
+
+ on_connection_close (conn: HTTPD_STREAM_SOCKET; a_message: detachable READABLE_STRING_8)
+ require
+ conn_attached: conn /= Void
+ conn_valid: conn.is_open_read and then conn.is_open_write
+ do
+ send (conn, Connection_close_frame, "")
+ end
+
+ on_close (conn: detachable HTTPD_STREAM_SOCKET)
+ -- Called after the WebSocket connection is closed.
+ deferred
+ end
+
+feature {NONE} -- Implementation
+
+ send (conn: HTTPD_STREAM_SOCKET; a_opcode:INTEGER; a_message: READABLE_STRING_8)
+ local
+ i: INTEGER
+ l_chunk_size: INTEGER
+ l_chunk: READABLE_STRING_8
+ l_header_message: STRING
+ l_message_count: INTEGER
+ n: NATURAL_64
+ retried: BOOLEAN
+ do
+ print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
+ if not retried then
+ create l_header_message.make_empty
+ l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
+ l_message_count := a_message.count
+ n := l_message_count.to_natural_64
+ if l_message_count > 0xffff then
+ --! Improve. this code needs to be checked.
+ l_header_message.append_code ((0 | 127).to_natural_32)
+ l_header_message.append_character ((n |>> 56).to_character_8)
+ l_header_message.append_character ((n |>> 48).to_character_8)
+ l_header_message.append_character ((n |>> 40).to_character_8)
+ l_header_message.append_character ((n |>> 32).to_character_8)
+ l_header_message.append_character ((n |>> 24).to_character_8)
+ l_header_message.append_character ((n |>> 16).to_character_8)
+ l_header_message.append_character ((n |>> 8).to_character_8)
+ l_header_message.append_character ( n.to_character_8)
+ elseif l_message_count > 125 then
+ l_header_message.append_code ((0 | 126).to_natural_32)
+ l_header_message.append_code ((n |>> 8).as_natural_32)
+ l_header_message.append_character (n.to_character_8)
+ else
+ l_header_message.append_code (n.as_natural_32)
+ end
+ conn.put_string (l_header_message)
+
+
+ l_chunk_size := 16_384 -- 16K
+ if l_message_count < l_chunk_size then
+ conn.put_string (a_message)
+ else
+ from
+ i := 0
+ until
+ l_chunk_size = 0
+ loop
+ debug ("ws")
+ print ("Sending chunk " + (i + 1).out + " -> " + (i + l_chunk_size).out +" / " + l_message_count.out + "%N")
+ end
+ l_chunk := a_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
+ conn.put_string (l_chunk)
+ if l_chunk.count < l_chunk_size then
+ l_chunk_size := 0
+ end
+ i := i + l_chunk_size
+ end
+ debug ("ws")
+ print ("Sending chunk done%N")
+ end
+ end
+ else
+ -- FIXME: what should be done on rescue?
+ end
+ rescue
+ retried := True
+ io.put_string ("Internal error in " + generator + ".do_send (conn, a_opcode=" + a_opcode.out + ", a_message) !%N")
+ retry
+ end
+
+end
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_message_type.e b/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_message_type.e
new file mode 100644
index 00000000..cd48af00
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/event/web_socket_message_type.e
@@ -0,0 +1,39 @@
+note
+ description: "[
+ A web socket message has an opcode specifying the type of the message payload. The
+ opcode consists of the last four bits in the first byte of the frame header.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+ EIS: "name=Data Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.6", "protocol=uri"
+ EIS: "name=Control Frame", "src=http://tools.ietf.org/html/rfc6455#section-5.5", "protocol=uri"
+
+class
+ WEB_SOCKET_MESSAGE_TYPE
+
+feature -- Data Frames
+
+ Text: INTEGER = 0x1
+ -- The data type of the message is text.
+
+ Binary: INTEGER = 0x2
+ -- The data type of the message is binary.
+
+feature -- Control Frames
+
+ Close: INTEGER = 0x8
+ -- The client or server is sending a closing
+ -- handshake to the server or client.
+
+ Ping: INTEGER = 0x9
+ -- The client or server sends a ping to the server or client.
+
+ Pong: INTEGER = 0xA
+ -- The client or server sends a pong to the server or client.
+
+feature -- Reserverd
+
+ -- Opcodes 0x3-0x7 are reserved for further non-control frames yet to be
+ -- defined.
+
+end
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_constants.e b/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_constants.e
new file mode 100644
index 00000000..86e771df
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_constants.e
@@ -0,0 +1,203 @@
+note
+ description: "Constants for WebSockets"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ WEB_SOCKET_CONSTANTS
+
+feature -- Constants
+
+
+ HTTP_1_1: STRING = "HTTP/1.1 101 WebSocket Protocol Handshake"
+
+ Upgrade_ws: STRING = "Upgrade: websocket"
+
+ Connection_ws: STRING = "Connection: Upgrade"
+
+ Sec_WebSocket_Origin: STRING = "Sec-WebSocket-Origin: "
+
+ Sec_WebSocket_Protocol: STRING = "Sec-WebSocket-Protocol: "
+
+ Sec_WebSocket_Location: STRING = "Sec-WebSocket-Location: "
+
+ Sec_WebSocket_Version: STRING = "Sec-WebSocket-Version: "
+
+ Sec_WebSocket_Extensions: STRING = "Sec-WebSocket-Extensions: "
+
+ WebSocket_Origin: STRING = "WebSocket-Origin: "
+
+ WebSocket_Protocol: STRING = "WebSocket-Protocol: "
+
+ WebSocket_Location: STRING = "WebSocket-Location: "
+
+ Origin: STRING = "Origin"
+
+ Server: STRING = "EWSS"
+
+ Sec_WebSocket_Key: STRING = "Sec-WebSocket-Key"
+
+ Ws_scheme: STRING = "ws://"
+
+ Wss_scheme: STRING = "wss://"
+
+ Magic_guid: STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+ -- The handshake from the client looks as follows:
+
+ -- GET /chat HTTP/1.1
+ -- Host: server.example.com
+ -- Upgrade: websocket
+ -- Connection: Upgrade
+ -- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+ -- Origin: http://example.com
+ -- Sec-WebSocket-Protocol: chat, superchat
+ -- Sec-WebSocket-Version: 13
+
+ -- The handshake from the server looks as follows:
+
+ -- HTTP/1.1 101 Switching Protocols
+ -- Upgrade: websocket
+ -- Connection: Upgrade
+ -- Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
+ -- Sec-WebSocket-Protocol: chat
+
+feature -- Opcodes Standard actions
+
+ --| Maybe we need an enum STANDARD_ACTIONS_OPCODES?
+ -- |Opcode | Meaning | Reference |
+ -- -+--------+-------------------------------------+-----------|
+ -- | 0 | Continuation Frame | RFC 6455 |
+ -- -+--------+-------------------------------------+-----------|
+ -- | 1 | Text Frame | RFC 6455 |
+ -- -+--------+-------------------------------------+-----------|
+ -- | 2 | Binary Frame | RFC 6455 |
+ -- -+--------+-------------------------------------+-----------|
+ -- | 8 | Connection Close Frame | RFC 6455 |
+ -- -+--------+-------------------------------------+-----------|
+ -- | 9 | Ping Frame | RFC 6455 |
+ -- -+--------+-------------------------------------+-----------|
+ -- | 10 | Pong Frame | RFC 6455 |
+ -- -+--------+-------------------------------------+-----------|
+
+ Continuation_frame: INTEGER = 0
+
+ Text_frame: INTEGER = 1
+
+ Binary_frame: INTEGER = 2
+
+ Connection_close_frame: INTEGER = 8
+
+ Ping_frame: INTEGER = 9
+
+ Pong_frame: INTEGER = 10
+
+ is_control_frame (a_opcode: INTEGER): BOOLEAN
+ -- Is `a_opcode' a control frame?
+ do
+ inspect a_opcode
+ when Connection_close_frame, Ping_frame, Pong_frame then
+ Result := True
+ else
+ end
+ end
+
+ opcode_name (a_opcode: INTEGER): STRING
+ do
+ inspect a_opcode
+ when Continuation_frame then Result := "Continuation"
+ when Text_frame then Result := "Text"
+ when Binary_frame then Result := "Binary"
+ when Connection_close_frame then Result := "Connection Close"
+ when Ping_frame then Result := "Ping"
+ when Pong_frame then Result := "Pong"
+ else
+ Result := "Unknown-Opcode"
+ end
+ Result := "0x" + a_opcode.to_hex_string + " " + Result
+ end
+
+feature -- Close code numbers
+
+ -- Maybe an ENUM CLOSE_CODES
+
+ -- |Status Code | Meaning | Contact | Reference |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1000 | Normal Closure | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1001 | Going Away | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1002 | Protocol error | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1003 | Unsupported Data| hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1004 | ---Reserved---- | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1005 | No Status Rcvd | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1006 | Abnormal Closure| hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1007 | Invalid frame | hybi@ietf.org | RFC 6455 |
+ -- | | payload data | | |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1008 | Policy Violation| hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1009 | Message Too Big | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1010 | Mandatory Ext. | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1011 | Internal Server | hybi@ietf.org | RFC 6455 |
+ -- | | Error | | |
+ -- -+------------+-----------------+---------------+-----------|
+ -- | 1015 | TLS handshake | hybi@ietf.org | RFC 6455 |
+ -- -+------------+-----------------+---------------+-----------|
+
+ Normal_closure: INTEGER = 1000
+ -- Indicates a normal closure, meaning that the purpose for
+ -- which the connection was established has been fulfilled.
+
+ Going_away: INTEGER = 1001
+ -- Indicates that an endpoint is "going away", such as a server
+ -- going down or a browser having navigated away from a page.
+
+ Protocol_error: INTEGER = 1002
+ -- Indicates that an endpoint is terminating the connection due
+ -- to a protocol error.
+
+ Unsupported_data: INTEGER = 1003
+ -- Indicates that an endpoint is terminating the connection
+ -- because it has received a type of data it cannot accept (e.g., an
+ -- endpoint that understands only text data MAY send this if it
+ -- receives a binary message).
+
+ Invalid_data: INTEGER = 1007
+ -- Indicates that an endpoint is terminating the connection
+ -- because it has received data within a message that was not
+ -- consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
+ -- data within a text message).
+
+ Policy_violation: INTEGER = 1008
+ -- Indicates that an endpoint is terminating the connection
+ -- because it has received a message that violates its policy. This
+ -- is a generic status code that can be returned when there is no
+ -- other more suitable status code (e.g., 1003 or 1009) or if there
+ -- is a need to hide specific details about the policy.
+
+ Message_too_large: INTEGER = 1009
+ -- Indicates that an endpoint is terminating the connection
+ -- because it has received a message that is too big for it to
+ -- process.
+
+ Extension_required: INTEGER = 1010
+ -- Indicates that an endpoint (client) is terminating the
+ -- connection because it has expected the server to negotiate one or
+ -- more extension, but the server didn't return them in the response
+ -- message of the WebSocket handshake.
+
+ Internal_error: INTEGER = 1011
+ -- Indicates that a server is terminating the connection because
+ -- it encountered an unexpected condition that prevented it from
+ -- fulfilling the request.
+
+
+end
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_error_frame.e b/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_error_frame.e
new file mode 100644
index 00000000..af9aa833
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_error_frame.e
@@ -0,0 +1,35 @@
+note
+ description: "Summary description for {WEB_SOCKET_ERROR_FRAME}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ WEB_SOCKET_ERROR_FRAME
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (a_code: INTEGER; a_desc: like description)
+ do
+ code := a_code
+ description := a_desc
+ end
+
+feature -- Access
+
+ code: INTEGER
+
+ description: READABLE_STRING_8
+
+feature -- Conversion
+
+ string: STRING
+ do
+ create Result.make_from_string ("Error(" + code.out + "): " + description)
+ end
+
+
+end
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_frame.e b/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_frame.e
new file mode 100644
index 00000000..3f3350a0
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/protocol/web_socket_frame.e
@@ -0,0 +1,437 @@
+note
+ description: "[
+ Summary description for {WEB_SOCKET_FRAME}.
+ See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-------+-+-------------+-------------------------------+
+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ |I|S|S|S| (4) |A| (7) | (16/64) |
+ |N|V|V|V| |S| | (if payload len==126/127) |
+ | |1|2|3| |K| | |
+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ | Extended payload length continued, if payload len == 127 |
+ + - - - - - - - - - - - - - - - +-------------------------------+
+ | |Masking-key, if MASK set to 1 |
+ +-------------------------------+-------------------------------+
+ | Masking-key (continued) | Payload Data |
+ +-------------------------------- - - - - - - - - - - - - - - - +
+ : Payload Data continued ... :
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ | Payload Data continued ... |
+ +---------------------------------------------------------------+
+
+ Check the `check_utf_8_validity_on_chop' if there is performance issue
+ with bigger data.
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+ EIS: "name=Websocket RFC6455 section-5.2", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2", "tag=rfc"
+
+class
+ WEB_SOCKET_FRAME
+
+inherit
+ ANY
+
+ WEB_SOCKET_CONSTANTS
+
+create
+ make,
+ make_as_injected_control
+
+feature {NONE} -- Initialization
+
+ make (a_opcode: INTEGER; flag_is_fin: BOOLEAN)
+ -- Create current frame with opcode `a_opcode'
+ -- and `a_fin' to indicate if this is the final fragment.
+ do
+ is_incomplete := False
+ opcode := a_opcode
+ is_fin := flag_is_fin
+
+ inspect opcode
+ when
+ Continuation_frame, -- 0
+ Text_frame, -- 1
+ Binary_frame -- 2
+ then
+ --| Supported opcode
+ when
+ Connection_close_frame, -- 8
+ Ping_frame, -- 9
+ Pong_frame -- 10
+ then
+ --| Supported control opcode
+ -- All control frames MUST have a payload length of 125 bytes or less
+ -- and MUST NOT be fragmented.
+ if flag_is_fin then
+ -- So far it is valid.
+ else
+ report_error (Protocol_error, "Control frames MUST NOT be fragmented.")
+ end
+ else
+ report_error (Protocol_error, "Unknown opcode")
+ end
+ end
+
+ make_as_injected_control (a_opcode: INTEGER; a_parent: WEB_SOCKET_FRAME)
+ require
+ parent_is_not_control_frame: not a_parent.is_control
+ a_opcode_is_control_frame: is_control_frame (a_opcode)
+ do
+ make (a_opcode, True)
+ parent := a_parent
+ a_parent.add_injected_control_frame (Current)
+ end
+
+feature -- Access
+
+ opcode: INTEGER
+ -- CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING
+
+ is_fin: BOOLEAN
+ -- is the final fragment in a message?
+
+ fragment_count: INTEGER
+
+ payload_length: NATURAL_64
+ payload_data: detachable STRING_8
+ -- Maybe we need a buffer here.
+
+ uncoded_payload_data: detachable STRING_32
+ local
+ utf: UTF_CONVERTER
+ do
+ if attached payload_data as d then
+ Result := utf.utf_8_string_8_to_string_32 (d)
+ end
+ end
+
+ error: detachable WEB_SOCKET_ERROR_FRAME
+ -- Describe the type of error
+
+feature -- Access: injected control frames
+
+ injected_control_frames: detachable LIST [WEB_SOCKET_FRAME]
+
+ parent: detachable WEB_SOCKET_FRAME
+ -- If Current is injected, `parent' is the related fragmented frame
+
+ is_injected_control: BOOLEAN
+ do
+ Result := parent /= Void
+ ensure
+ Result implies (is_control_frame (opcode))
+ end
+
+feature -- Operation
+
+ update_fin (a_flag_is_fin: BOOLEAN)
+ do
+ is_fin := a_flag_is_fin
+ end
+
+feature {WEB_SOCKET_FRAME} -- Change: injected control frames
+
+ add_injected_control_frame (f: WEB_SOCKET_FRAME)
+ require
+ Current_is_not_control: not is_control
+ f_is_control_frame: f.is_control
+ parented_to_current: f.parent = Current
+ local
+ lst: like injected_control_frames
+ do
+ lst := injected_control_frames
+ if lst = Void then
+ create {ARRAYED_LIST [WEB_SOCKET_FRAME]} lst.make (1)
+ injected_control_frames := lst
+ end
+ lst.force (f)
+ ensure
+ parented_to_current: f.parent = Current
+ end
+
+ remove_injected_control_frame (f: WEB_SOCKET_FRAME)
+ require
+ Current_is_not_control: not is_control
+ f_is_control_frame: f.is_control
+ parented_to_current: f.parent = Current
+ local
+ lst: like injected_control_frames
+ do
+ lst := injected_control_frames
+ if lst /= Void then
+ lst.prune (f)
+ if lst.is_empty then
+ injected_control_frames := Void
+ end
+ end
+ end
+
+feature -- Query
+
+ is_binary: BOOLEAN
+ do
+ Result := opcode = binary_frame
+ end
+
+ is_text: BOOLEAN
+ do
+ Result := opcode = text_frame
+ end
+
+ is_continuation: BOOLEAN
+ do
+ Result := opcode = continuation_frame
+ end
+
+ is_connection_close: BOOLEAN
+ do
+ Result := opcode = connection_close_frame
+ end
+
+ is_control: BOOLEAN
+ do
+ inspect opcode
+ when connection_close_frame, Ping_frame, Pong_frame then
+ Result := True
+ else
+ end
+ end
+
+ is_ping: BOOLEAN
+ do
+ Result := opcode = ping_frame
+ end
+
+ is_pong: BOOLEAN
+ do
+ Result := opcode = pong_frame
+ end
+
+feature -- Status report
+
+ is_valid: BOOLEAN
+ do
+ Result := not has_error
+ end
+
+ is_incomplete: BOOLEAN
+
+ has_error: BOOLEAN
+ do
+ Result := error /= Void
+ end
+
+feature -- Change
+
+ increment_fragment_count
+ do
+ fragment_count := fragment_count + 1
+ end
+
+ check_utf_8_validity_on_chop: BOOLEAN = False
+ -- True: check for each chop
+ -- False: check only for each fragment
+ --| see autobahntestsuite #6.4.3 and #6.4.4
+
+ append_payload_data_chop (a_data: STRING_8; a_len: INTEGER; a_flag_chop_complete: BOOLEAN)
+ do
+ if a_flag_chop_complete then
+ increment_fragment_count
+ end
+ if attached payload_data as l_payload_data then
+ l_payload_data.append (a_data)
+ else
+ payload_data := a_data
+ end
+ payload_length := payload_length + a_len.to_natural_64
+
+ if is_text then
+ if is_fin and a_flag_chop_complete then
+ -- Check the whole message is a valid UTF-8 string
+ if attached payload_data as d then
+ if not is_valid_utf_8_string (d) then
+ report_error (invalid_data, "The text message is not a valid UTF-8 text!")
+ end
+ else
+ -- empty payload??
+ end
+ elseif check_utf_8_validity_on_chop or else a_flag_chop_complete then
+ -- Check the payload data as utf-8 stream (may be incomplete at this point)
+ if not is_valid_text_payload_stream then
+ report_error (invalid_data, "This is not a valid UTF-8 stream!")
+ -- is_valid implies the connection will be closed!
+ end
+ end
+ end
+ end
+
+ report_error (a_code: INTEGER; a_description: READABLE_STRING_8)
+ require
+ not has_error
+ do
+ create error.make (a_code, a_description)
+ ensure
+ has_error: has_error
+ is_not_valid: not is_valid
+ end
+
+feature {NONE} -- Helper
+
+ last_utf_8_stream_validation_position: INTEGER
+ -- In relation with `is_valid_utf_8 (.., a_is_stream=True)'
+
+ is_valid_text_payload_stream: BOOLEAN
+ require
+ is_text_frame: is_text
+ do
+ if attached payload_data as s then
+ Result := is_valid_utf_8 (s, not is_fin)
+ end
+ end
+
+ is_valid_utf_8_string (s: READABLE_STRING_8): BOOLEAN
+ do
+ Result := is_valid_utf_8 (s, False)
+-- and (create {UTF_CONVERTER}).is_valid_utf_8_string_8 (s)
+ end
+
+ is_valid_utf_8 (s: READABLE_STRING_8; a_is_stream: BOOLEAN): BOOLEAN
+ -- UTF-8 validity checker.
+ note
+ EIS: "name=UTF-8 RFC3629", "protocol=URI", "src=https://tools.ietf.org/html/rfc3629", "tag=rfc"
+ require
+ is_text_frame: is_text
+ local
+ i: like {STRING_8}.count
+ n: like {STRING_8}.count
+ c,c2,c3,c4,w: NATURAL_32
+ l_is_incomplete_stream: BOOLEAN
+ do
+ Result := True
+ -- Following code also check that codepoint is between 0 and 0x10FFFF
+ -- (as expected by spec, and tested by autobahn ws testsuite)
+ from
+ if a_is_stream then
+ i := last_utf_8_stream_validation_position -- to avoid recomputing from the beginning each time.
+ else
+ i := 0
+ end
+ n := s.count
+ until
+ i >= n or not Result
+ loop
+ i := i + 1
+ c := s.code (i)
+ if c <= 0x7F then
+ -- 0xxxxxxx
+ w := c
+ elseif c <= 0xC1 then
+ -- The octet values C0, C1, F5 to FF never appear.
+ --| case 0xC0 and 0xC1
+ Result := False
+ elseif (c & 0xE0) = 0xC0 then
+ -- 110xxxxx 10xxxxxx
+ i := i + 1
+ if i <= n then
+ c2 := s.code (i)
+ if
+ (c2 & 0xC0) = 0x80
+ then
+ w := ((c & 0x1F) |<< 6)
+ | (c2 & 0x3F)
+ Result := 0x80 <= w and w <= 0x7FF
+ else
+ Result := False
+ end
+ else
+ l_is_incomplete_stream := True
+ end
+ elseif (c & 0xF0) = 0xE0 then
+ -- 1110xxxx 10xxxxxx 10xxxxxx
+ i := i + 2
+ if i <= n then
+ c2 := s.code (i - 1)
+ c3 := s.code (i)
+ if
+ (c2 & 0xC0) = 0x80 and
+ (c3 & 0xC0) = 0x80
+ then
+ w := ((c & 0xF) |<< 12)
+ | ((c2 & 0x3F) |<< 6)
+ | (c3 & 0x3F)
+ if 0x800 <= w and w <= 0xFFFF then
+ if 0xD800 <= w and w <= 0xDFFF then
+ -- The definition of UTF-8 prohibits encoding character numbers between U+D800 and U+DFFF
+ Result := False
+ end
+ else
+ Result := False
+ end
+ else
+ Result := False
+ end
+ else
+ if i - 1 <= n then
+ Result := (s.code (i - 1) & 0xC0) = 0x80
+ end
+ l_is_incomplete_stream := True
+ end
+ elseif (c & 0xF8) = 0xF0 then -- 0001 0000-0010 FFFF
+ -- 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ if 0xF5 <= c and c <= 0xFF then
+ -- The octet values C0, C1, F5 to FF never appear.
+ Result := False
+ else
+ i := i + 3
+ if i <= n then
+ c2 := s.code (i - 2)
+ c3 := s.code (i - 1)
+ c4 := s.code (i)
+ if
+ (c2 & 0xC0) = 0x80 and
+ (c3 & 0xC0) = 0x80 and
+ (c4 & 0xC0) = 0x80
+ then
+ w := ((c & 0x7) |<< 18) |
+ ((c2 & 0x3F) |<< 12) |
+ ((c3 & 0x3F) |<< 6) |
+ (c4 & 0x3F)
+ Result := 0x1_0000 <= w and w <= 0x10_FFFF
+ else
+ Result := False
+ end
+ else
+ if i - 2 <= n then
+ c2 := s.code (i - 2)
+ Result := (c2 & 0xC0) = 0x80
+ if Result then
+ if c = 0xF4 and c2 >= 0x90 then
+ --| any byte 10xxxxxx (i.e >= 0x80) that would come after,
+ -- will result in out of range code point
+ -- indeed 0xF4 0x90 0x80 0x80 = 0x1100 0000 > 0x10_FFFF
+ Result := False
+ elseif i - 1 <= n then
+ Result := (s.code (i - 1) & 0xC0) = 0x80
+ end
+ end
+ end
+ l_is_incomplete_stream := True
+ end
+ end
+ else
+ -- Invalid byte in UTF-8
+ Result := False
+ end
+ if Result then
+ if l_is_incomplete_stream then
+ Result := a_is_stream
+ elseif a_is_stream then
+ last_utf_8_stream_validation_position := i
+ end
+ end
+ end
+ end
+end
diff --git a/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e b/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
new file mode 100644
index 00000000..b1c4b8d8
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/websocket/web_socket.e
@@ -0,0 +1,754 @@
+note
+ description: "Summary description for {WEB_SOCKET}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ WEB_SOCKET
+
+inherit
+ WGI_STANDALONE_CONNECTOR_ACCESS
+
+ HTTPD_LOGGER_CONSTANTS
+
+ WEB_SOCKET_CONSTANTS
+
+ SHARED_BASE64
+
+create
+ make
+
+feature {NONE} -- Initialization
+
+ make (req: WSF_REQUEST; res: WSF_RESPONSE)
+ do
+ request := req
+ response := res
+ is_verbose := True
+
+ if
+ attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input
+ then
+ socket := r_input.source
+ else
+ create socket.make_empty
+ check has_socket: False end
+ end
+ end
+
+feature -- Access
+
+ socket: HTTPD_STREAM_SOCKET
+
+ request: WSF_REQUEST
+
+ response: WSF_RESPONSE
+
+feature -- Access
+
+ is_websocket: BOOLEAN
+
+ has_error: BOOLEAN
+
+ is_verbose: BOOLEAN
+
+ socket_is_ready_for_reading: BOOLEAN
+ do
+ Result := socket.ready_for_reading
+ end
+
+feature -- Element change
+
+ log (m: READABLE_STRING_8; lev: INTEGER)
+ do
+ if is_verbose then
+ response.put_error (m)
+ end
+ end
+
+feature -- Basic Operation
+
+ open_ws_handshake
+ -- The opening handshake is intended to be compatible with HTTP-based
+ -- server-side software and intermediaries, so that a single port can be
+ -- used by both HTTP clients alking to that server and WebSocket
+ -- clients talking to that server. To this end, the WebSocket client's
+ -- handshake is an HTTP Upgrade request:
+
+ -- GET /chat HTTP/1.1
+ -- Host: server.example.com
+ -- Upgrade: websocket
+ -- Connection: Upgrade
+ -- Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+ -- Origin: http://example.com
+ -- Sec-WebSocket-Protocol: chat, superchat
+ -- Sec-WebSocket-Version: 13
+ local
+ l_sha1: SHA1
+ l_key : STRING
+ l_handshake: STRING
+ req: like request
+ res: like response
+ do
+ req := request
+ res := response
+ is_websocket := False
+ has_error := False
+
+ -- Reading client's opening GT
+
+ -- TODO extract to a validator handshake or something like that.
+ if is_verbose then
+ log ("%NReceive <====================", debug_level)
+ if attached req.raw_header_data as rhd then
+ log (rhd, debug_level)
+ end
+ end
+ if
+ req.is_get_request_method and then -- MUST be GET request!
+ attached req.meta_string_variable ("HTTP_UPGRADE") as l_upgrade_key and then
+ l_upgrade_key.is_case_insensitive_equal_general ("websocket") -- Upgrade header must be present with value websocket
+ then
+ is_websocket := True
+-- if
+-- attached {WGI_STANDALONE_INPUT_STREAM} req.input as r_input and then
+-- attached r_input.source as l_socket
+-- then
+-- l_socket.set_blocking
+-- end
+ socket.set_blocking
+ if
+ attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_KEY") as l_ws_key and then -- Sec-websocket-key must be present
+ attached req.meta_string_variable ("HTTP_CONNECTION") as l_connection_key and then -- Connection header must be present with value Upgrade
+ l_connection_key.has_substring ("Upgrade") and then
+ attached req.meta_string_variable ("HTTP_SEC_WEBSOCKET_VERSION") as l_version_key and then -- Version header must be present with value 13
+ l_version_key.is_case_insensitive_equal ("13") and then
+ attached req.http_host -- Host header must be present
+ then
+ if is_verbose then
+ log ("key " + l_ws_key, debug_level)
+ end
+ -- Sending the server's opening handshake
+ create l_sha1.make
+ l_sha1.update_from_string (l_ws_key + magic_guid)
+ l_key := Base64_encoder.encoded_string (digest (l_sha1))
+-- create l_handshake.make_from_string ("") --HTTP/1.1 101 Switching Protocols%R%N")
+ create l_handshake.make_from_string ("HTTP/1.1 101 Switching Protocols%R%N")
+ l_handshake.append_string ("Upgrade: websocket%R%N")
+ l_handshake.append_string ("Connection: Upgrade%R%N")
+ l_handshake.append_string ("Sec-WebSocket-Accept: ")
+ l_handshake.append_string (l_key)
+ l_handshake.append_string ("%R%N")
+ -- end of header empty line
+--not with WSF_RESPONSE l_handshake.append_string ("%R%N")
+ l_handshake.append_string ("%R%N")
+ if is_verbose then
+ log ("%N================> Send", debug_level)
+ log (l_handshake, debug_level)
+ end
+ socket.put_string (l_handshake)
+-- res.set_status_code_with_reason_phrase (101, "Switching Protocols")
+-- res.put_header_text (l_handshake)
+ else
+ has_error := True
+ if is_verbose then
+ log ("Error (opening_handshake)!!!", debug_level)
+ end
+ -- If we cannot complete the handshake, then the server MUST stop processing the client's handshake and return an HTTP response with an
+ -- appropriate error code (such as 400 Bad Request).
+ res.set_status_code_with_reason_phrase (400, "Bad Request")
+-- a_socket.put_string ("HTTP/1.1 400 Bad Request%N")
+ end
+ else
+ is_websocket := False
+ end
+ end
+
+feature -- Response!
+
+ send (a_opcode:INTEGER; a_message: READABLE_STRING_8)
+ local
+ i: INTEGER
+ l_chunk_size: INTEGER
+ l_chunk: READABLE_STRING_8
+ l_header_message: STRING
+ l_message_count: INTEGER
+ n: NATURAL_64
+ retried: BOOLEAN
+ do
+ print (">>do_send (..., "+ opcode_name (a_opcode) +", ..)%N")
+ if not retried then
+ create l_header_message.make_empty
+ l_header_message.append_code ((0x80 | a_opcode).to_natural_32)
+ l_message_count := a_message.count
+ n := l_message_count.to_natural_64
+ if l_message_count > 0xffff then
+ --! Improve. this code needs to be checked.
+ l_header_message.append_code ((0 | 127).to_natural_32)
+ l_header_message.append_character ((n |>> 56).to_character_8)
+ l_header_message.append_character ((n |>> 48).to_character_8)
+ l_header_message.append_character ((n |>> 40).to_character_8)
+ l_header_message.append_character ((n |>> 32).to_character_8)
+ l_header_message.append_character ((n |>> 24).to_character_8)
+ l_header_message.append_character ((n |>> 16).to_character_8)
+ l_header_message.append_character ((n |>> 8).to_character_8)
+ l_header_message.append_character ( n.to_character_8)
+ elseif l_message_count > 125 then
+ l_header_message.append_code ((0 | 126).to_natural_32)
+ l_header_message.append_code ((n |>> 8).as_natural_32)
+ l_header_message.append_character (n.to_character_8)
+ else
+ l_header_message.append_code (n.as_natural_32)
+ end
+ socket.put_string (l_header_message)
+
+ l_chunk_size := 16_384 -- 16K
+ if l_message_count < l_chunk_size then
+ socket.put_string (a_message)
+ else
+ from
+ i := 0
+ until
+ l_chunk_size = 0
+ loop
+ debug ("ws")
+ print ("Sending chunk " + (i + 1).out + " -> " + (i + l_chunk_size).out +" / " + l_message_count.out + "%N")
+ end
+ l_chunk := a_message.substring (i + 1, l_message_count.min (i + l_chunk_size))
+ socket.put_string (l_chunk)
+ if l_chunk.count < l_chunk_size then
+ l_chunk_size := 0
+ end
+ i := i + l_chunk_size
+ end
+ debug ("ws")
+ print ("Sending chunk done%N")
+ end
+ end
+ else
+ -- FIXME: what should be done on rescue?
+ end
+ rescue
+ retried := True
+ io.put_string ("Internal error in " + generator + ".do_send (conn, a_opcode=" + a_opcode.out + ", a_message) !%N")
+ retry
+ end
+
+ next_frame: detachable WEB_SOCKET_FRAME
+ -- TODO Binary messages
+ -- Handle error responses in a better way.
+ -- IDEA:
+ -- class FRAME
+ -- is_fin: BOOLEAN
+ -- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
+ -- data/payload
+ -- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
+ -- has_error
+ --
+ -- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
+ -- 0 1 2 3
+ -- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ -- +-+-+-+-+-------+-+-------------+-------------------------------+
+ -- |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ -- |I|S|S|S| (4) |A| (7) | (16/64) |
+ -- |N|V|V|V| |S| | (if payload len==126/127) |
+ -- | |1|2|3| |K| | |
+ -- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ -- | Extended payload length continued, if payload len == 127 |
+ -- + - - - - - - - - - - - - - - - +-------------------------------+
+ -- | |Masking-key, if MASK set to 1 |
+ -- +-------------------------------+-------------------------------+
+ -- | Masking-key (continued) | Payload Data |
+ -- +-------------------------------- - - - - - - - - - - - - - - - +
+ -- : Payload Data continued ... :
+ -- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ -- | Payload Data continued ... |
+ -- +---------------------------------------------------------------+
+ note
+ EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
+ require
+ socket_in_blocking_mode: socket.is_blocking
+ local
+ l_socket: like socket
+ l_opcode: INTEGER
+ l_len: INTEGER
+ l_remaining_len: INTEGER
+ l_payload_len: NATURAL_64
+ l_masking_key: detachable READABLE_STRING_8
+ l_chunk: STRING
+ l_rsv: BOOLEAN
+ l_fin: BOOLEAN
+ l_has_mask: BOOLEAN
+ l_chunk_size: INTEGER
+ l_byte: INTEGER
+ l_fetch_count: INTEGER
+ l_bytes_read: INTEGER
+ s: STRING
+ is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
+ retried: BOOLEAN
+ do
+ if not retried then
+-- l_input := request.input
+ l_socket := socket
+ debug ("ws")
+ print ("next_frame:%N")
+ end
+ from
+ is_data_frame_ok := True
+ until
+ l_fin or not is_data_frame_ok
+ loop
+ -- multi-frames or continue is only valid for Binary or Text
+ s := next_bytes (l_socket, 1)
+ if s.is_empty then
+ is_data_frame_ok := False
+ debug ("ws")
+ print ("[ERROR] incomplete_data!%N")
+ end
+ else
+ l_byte := s [1].code
+ debug ("ws")
+ print (" fin,rsv(3),opcode(4)=")
+ print (to_byte_representation (l_byte))
+ print ("%N")
+ end
+ l_fin := l_byte & (0b10000000) /= 0
+ l_rsv := l_byte & (0b01110000) = 0
+ l_opcode := l_byte & 0b00001111
+ if Result /= Void then
+ if l_opcode = Result.opcode then
+ -- should not occur in multi-fragment frame!
+ create Result.make (l_opcode, l_fin)
+ Result.report_error (protocol_error, "Unexpected injected frame")
+ elseif l_opcode = continuation_frame then
+ -- Expected
+ Result.update_fin (l_fin)
+ elseif is_control_frame (l_opcode) then
+ -- Control frames (see Section 5.5) MAY be injected in the middle of
+ -- a fragmented message. Control frames themselves MUST NOT be fragmented.
+ -- if the l_opcode is a control frame then there is an error!!!
+ -- CLOSE, PING, PONG
+ create Result.make_as_injected_control (l_opcode, Result)
+ else
+ -- should not occur in multi-fragment frame!
+ create Result.make (l_opcode, l_fin)
+ Result.report_error (protocol_error, "Unexpected frame")
+ end
+ else
+ create Result.make (l_opcode, l_fin)
+ if Result.is_continuation then
+ -- Continuation frame is not expected without parent frame!
+ Result.report_error (protocol_error, "There is no message to continue!")
+ end
+ end
+ if Result.is_valid then
+ --| valid frame/fragment
+ if is_verbose then
+ log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
+ end
+
+ -- rsv validation
+ if not l_rsv then
+ -- RSV1, RSV2, RSV3: 1 bit each
+
+ -- MUST be 0 unless an extension is negotiated that defines meanings
+ -- for non-zero values. If a nonzero value is received and none of
+ -- the negotiated extensions defines the meaning of such a nonzero
+ -- value, the receiving endpoint MUST _Fail the WebSocket
+ -- Connection_
+
+ -- FIXME: add support for extension ?
+ Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
+ end
+ else
+ if is_verbose then
+ log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")", debug_level)
+ end
+ end
+
+ -- At the moment only TEXT, (pending Binary)
+ if Result.is_valid then
+ if Result.is_text or Result.is_binary or Result.is_control then
+ -- Reading next byte (mask+payload_len)
+ s := next_bytes (l_socket, 1)
+ if s.is_empty then
+ Result.report_error (invalid_data, "Incomplete data for mask and payload len")
+ else
+ l_byte := s [1].code
+ debug ("ws")
+ print (" mask,payload_len(7)=")
+ print (to_byte_representation (l_byte))
+ io.put_new_line
+ end
+ l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
+ l_len := l_byte & 0b01111111 -- 7bits
+
+ debug ("ws")
+ print (" payload_len=" + l_len.out)
+ io.put_new_line
+ end
+ if Result.is_control and then l_len > 125 then
+ -- All control frames MUST have a payload length of 125 bytes or less
+ -- and MUST NOT be fragmented.
+ Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
+ elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
+ -- the following 8 bytes interpreted as a 64-bit unsigned integer
+ -- (the most significant bit MUST be 0) are the payload length.
+ -- Multibyte length quantities are expressed in network byte order.
+ s := next_bytes (l_socket, 8) -- 64 bits
+ debug ("ws")
+ print (" extended payload length=" + string_to_byte_representation (s))
+ io.put_new_line
+ end
+ if s.count < 8 then
+ Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
+ else
+ l_payload_len := s [8].natural_32_code.to_natural_64
+ l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
+ l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
+ l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
+ l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
+ l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
+ l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
+ l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
+ end
+ elseif l_len = 126 then
+ s := next_bytes (l_socket, 2) -- 16 bits
+ debug ("ws")
+ print (" extended payload length bits=" + string_to_byte_representation (s))
+ io.put_new_line
+ end
+ if s.count < 2 then
+ Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
+ else
+ l_payload_len := s [2].natural_32_code.to_natural_64
+ l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
+ end
+ else
+ l_payload_len := l_len.to_natural_64
+ end
+ debug ("ws")
+ print (" Full payload length=" + l_payload_len.out)
+ io.put_new_line
+ end
+ if Result.is_valid then
+ if l_has_mask then
+ l_masking_key := next_bytes (l_socket, 4) -- 32 bits
+ debug ("ws")
+ print (" Masking key bits=" + string_to_byte_representation (l_masking_key))
+ io.put_new_line
+ end
+ if l_masking_key.count < 4 then
+ debug ("ws")
+ print ("masking-key read stream -> " + l_socket.bytes_read.out + " bits%N")
+ end
+ Result.report_error (Invalid_data, "Incomplete data for Masking-key")
+ l_masking_key := Void
+ end
+ else
+ Result.report_error (protocol_error, "All frames sent from client to server are masked!")
+ end
+ if Result.is_valid then
+ l_chunk_size := 0x4000 -- 16 K
+ if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
+ -- Issue .. to big to store in STRING
+ -- FIXME !!!
+ Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
+ else
+ l_len := l_payload_len.to_integer_32
+ end
+ from
+ l_fetch_count := 0
+ l_remaining_len := l_len
+ until
+ l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
+ loop
+ if l_remaining_len < l_chunk_size then
+ l_chunk_size := l_remaining_len
+ end
+-- l_input.read_string (l_chunk_size)
+ l_socket.read_stream (l_chunk_size)
+-- l_bytes_read := l_input.last_string.count
+ l_bytes_read := l_socket.bytes_read
+ debug ("ws")
+ print ("read chunk size=" + l_chunk_size.out + " fetch_count=" + l_fetch_count.out + " l_len=" + l_len.out + " -> " + l_bytes_read.out + "bytes%N")
+ end
+ if l_bytes_read > 0 then
+ l_remaining_len := l_remaining_len - l_bytes_read
+ l_chunk := l_socket.last_string
+ if l_masking_key /= Void then
+ -- Masking
+ -- http://tools.ietf.org/html/rfc6455#section-5.3
+ unmask (l_chunk, l_fetch_count + 1, l_masking_key)
+ else
+ check
+ client_frame_should_always_be_encoded: False
+ end
+ end
+ l_fetch_count := l_fetch_count + l_bytes_read
+ Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
+ else
+ Result.report_error (internal_error, "Issue reading payload data...")
+ end
+ end
+ if is_verbose then
+ log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============", debug_level)
+ end
+ debug ("ws")
+ print (" -> ")
+ if attached Result.payload_data as l_payload_data then
+ s := l_payload_data.tail (l_fetch_count)
+ if s.count > 50 then
+ print (string_to_byte_hexa_representation (s.head (50) + ".."))
+ else
+ print (string_to_byte_hexa_representation (s))
+ end
+ print ("%N")
+ if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
+ print (" -> ")
+ if s.count > 50 then
+ print (s.head (50) + "..")
+ else
+ print (s)
+ end
+ print ("%N")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ if Result /= Void then
+ if attached Result.error as err then
+ if is_verbose then
+ log (" !Invalid frame: " + err.string, debug_level)
+ end
+ end
+ if Result.is_injected_control then
+ if attached Result.parent as l_parent then
+ if not Result.is_valid then
+ l_parent.report_error (protocol_error, "Invalid injected frame")
+ end
+ if Result.is_connection_close then
+ -- Return this and process the connection close right away!
+ l_parent.update_fin (True)
+ l_fin := Result.is_fin
+ else
+ Result := l_parent
+ l_fin := l_parent.is_fin
+ check
+ -- This is a control frame but occurs in fragmented frame.
+ inside_fragmented_frame: not l_fin
+ end
+ end
+ else
+ check
+ has_parent: False
+ end
+ l_fin := False -- This is a control frame but occurs in fragmented frame.
+ end
+ end
+ if not Result.is_valid then
+ is_data_frame_ok := False
+ end
+ else
+ is_data_frame_ok := False
+ end
+ end
+ end
+ has_error := Result = Void or else Result.has_error
+ rescue
+ retried := True
+ if Result /= Void then
+ Result.report_error (internal_error, "Internal error")
+ end
+ retry
+ end
+
+
+feature -- Encoding
+
+ digest (a_sha1: SHA1): STRING
+ -- Digest of `a_sha1'.
+ -- Should by in SHA1 class
+ local
+ l_digest: SPECIAL [NATURAL_8]
+ index, l_upper: INTEGER
+ do
+ l_digest := a_sha1.digest
+ create Result.make (l_digest.count // 2)
+ from
+ index := l_digest.Lower
+ l_upper := l_digest.upper
+ until
+ index > l_upper
+ loop
+ Result.append_character (l_digest [index].to_character_8)
+ index := index + 1
+ end
+ end
+
+feature {NONE} -- Socket helpers
+
+ next_bytes (a_socket: HTTPD_STREAM_SOCKET; nb: INTEGER): STRING
+ require
+ nb > 0
+ local
+ n, l_bytes_read: INTEGER
+ do
+ create Result.make (nb)
+ from
+ n := nb
+ until
+ n = 0
+ loop
+ a_socket.read_stream (nb)
+ l_bytes_read := a_socket.bytes_read
+ if l_bytes_read > 0 then
+ Result.append (a_socket.last_string)
+ n := n - l_bytes_read
+ else
+ n := 0
+ end
+ end
+ end
+
+feature -- Masking Data Client - Server
+
+ unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
+ local
+ i, n: INTEGER
+ do
+ from
+ i := 1
+ n := a_chunk.count
+ until
+ i > n
+ loop
+ a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
+ i := i + 1
+ end
+ end
+
+ append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
+ -- To convert masked data into unmasked data, or vice versa, the following
+ -- algorithm is applied. The same algorithm applies regardless of the
+ -- direction of the translation, e.g., the same steps are applied to
+ -- mask the data as to unmask the data.
+
+ -- Octet i of the transformed data ("transformed-octet-i") is the XOR of
+ -- octet i of the original data ("original-octet-i") with octet at index
+ -- i modulo 4 of the masking key ("masking-key-octet-j"):
+
+ -- j = i MOD 4
+ -- transformed-octet-i = original-octet-i XOR masking-key-octet-j
+
+ -- The payload length, indicated in the framing as frame-payload-length,
+ -- does NOT include the length of the masking key. It is the length of
+ -- the "Payload data", e.g., the number of bytes following the masking
+ -- key.
+ note
+ EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
+ local
+ i, n: INTEGER
+ do
+ -- debug ("ws")
+ -- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
+ -- end
+ from
+ i := 1
+ n := a_chunk.count
+ until
+ i > n
+ loop
+ a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
+ i := i + 1
+ end
+ end
+
+feature {NONE} -- Debug
+
+ to_byte_representation (a_integer: INTEGER): STRING
+ require
+ valid: a_integer >= 0 and then a_integer <= 255
+ local
+ l_val: INTEGER
+ do
+ create Result.make (8)
+ from
+ l_val := a_integer
+ until
+ l_val < 2
+ loop
+ Result.prepend_integer (l_val \\ 2)
+ l_val := l_val // 2
+ end
+ Result.prepend_integer (l_val)
+ end
+
+ string_to_byte_representation (s: STRING): STRING
+ require
+ valid: s.count > 0
+ local
+ i, n: INTEGER
+ do
+ n := s.count
+ create Result.make (8 * n)
+ if n > 0 then
+ from
+ i := 1
+ until
+ i > n
+ loop
+ if not Result.is_empty then
+ Result.append_character (':')
+ end
+ Result.append (to_byte_representation (s [i].code))
+ i := i + 1
+ end
+ end
+ end
+
+ string_to_byte_hexa_representation (s: STRING): STRING
+ local
+ i, n: INTEGER
+ c: INTEGER
+ do
+ n := s.count
+ create Result.make (8 * n)
+ if n > 0 then
+ from
+ i := 1
+ until
+ i > n
+ loop
+ if not Result.is_empty then
+ Result.append_character (':')
+ end
+ c := s [i].code
+ check
+ c <= 0xFF
+ end
+ Result.append_character (((c |>> 4) & 0xF).to_hex_character)
+ Result.append_character (((c) & 0xF).to_hex_character)
+ i := i + 1
+ end
+ end
+ end
+
+
+note
+ copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+ source: "[
+ Eiffel Software
+ 5949 Hollister Ave., Goleta, CA 93117 USA
+ Telephone 805-685-1006, Fax 805-685-6869
+ Website http://www.eiffel.com
+ Customer support http://support.eiffel.com
+ ]"
+end
diff --git a/library/server/wsf/connector/standalone_websocket/wgi_standalone_websocket_connector.e b/library/server/wsf/connector/standalone_websocket/wgi_standalone_websocket_connector.e
new file mode 100644
index 00000000..15675200
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/wgi_standalone_websocket_connector.e
@@ -0,0 +1,44 @@
+note
+ description: "Summary description for {WGI_STANDALONE_WEBSOCKET_CONNECTOR}."
+ author: ""
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ WGI_STANDALONE_WEBSOCKET_CONNECTOR [G -> WGI_EXECUTION create make end]
+
+inherit
+ WGI_STANDALONE_CONNECTOR [G]
+ redefine
+ name, version
+ end
+
+create
+ make,
+ make_with_base
+
+feature -- Access
+
+ name: STRING_8
+ -- Name of Current connector
+ once
+ Result := "ws_httpd"
+ end
+
+ version: STRING_8
+ -- Version of Current connector
+ once
+ Result := "1.0"
+ end
+
+note
+ copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+ source: "[
+ Eiffel Software
+ 5949 Hollister Ave., Goleta, CA 93117 USA
+ Telephone 805-685-1006, Fax 805-685-6869
+ Website http://www.eiffel.com
+ Customer support http://support.eiffel.com
+ ]"
+end
diff --git a/library/server/wsf/connector/standalone_websocket/wsf_standalone_websocket_service_launcher.e b/library/server/wsf/connector/standalone_websocket/wsf_standalone_websocket_service_launcher.e
new file mode 100644
index 00000000..0a334fb0
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/wsf_standalone_websocket_service_launcher.e
@@ -0,0 +1,49 @@
+note
+ description: "[
+ Component to launch the service using the default connector
+
+ Eiffel Web httpd for this class
+
+
+ The httpd default connector support options:
+ port: numeric such as 8099 (or equivalent string as "8099")
+ base: base_url (very specific to standalone server)
+ verbose: to display verbose output, useful for standalone connector
+ force_single_threaded: use only one thread, useful for standalone connector
+
+ check WSF_SERVICE_LAUNCHER for more documentation
+ ]"
+ date: "$Date$"
+ revision: "$Revision$"
+
+class
+ WSF_STANDALONE_WEBSOCKET_SERVICE_LAUNCHER [G -> WSF_WEBSOCKET_EXECUTION create make end]
+
+inherit
+ WSF_STANDALONE_SERVICE_LAUNCHER [G]
+ redefine
+ connector
+ end
+
+create
+ make,
+ make_and_launch
+
+feature -- Status report
+
+ connector: WGI_STANDALONE_WEBSOCKET_CONNECTOR [G]
+ -- Default connector
+
+;note
+ copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+ source: "[
+ Eiffel Software
+ 5949 Hollister Ave., Goleta, CA 93117 USA
+ Telephone 805-685-1006, Fax 805-685-6869
+ Website http://www.eiffel.com
+ Customer support http://support.eiffel.com
+ ]"
+
+end
+
diff --git a/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e b/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e
new file mode 100644
index 00000000..0505795d
--- /dev/null
+++ b/library/server/wsf/connector/standalone_websocket/wsf_websocket_execution.e
@@ -0,0 +1,240 @@
+note
+ description: "[
+ Objects that ...
+ ]"
+ author: "$Author$"
+ date: "$Date$"
+ revision: "$Revision$"
+
+deferred class
+ WSF_WEBSOCKET_EXECUTION
+
+inherit
+ WSF_EXECUTION
+ rename
+ execute as http_execute
+ end
+
+ WEB_SOCKET_CONSTANTS
+
+ REFACTORING_HELPER
+
+ HTTPD_LOGGER_CONSTANTS
+
+ WGI_STANDALONE_CONNECTOR_ACCESS
+
+--create
+-- make
+
+feature -- Execution
+
+ is_verbose: BOOLEAN
+
+ is_websocket: BOOLEAN
+
+ has_error: BOOLEAN
+
+ log (m: READABLE_STRING_8; lev: INTEGER)
+ do
+
+ end
+
+ http_execute
+ local
+ ws: WEB_SOCKET
+ do
+ has_error := False
+ is_websocket := False
+ create ws.make (request, response)
+ ws.open_ws_handshake
+ if ws.is_websocket then
+ has_error := ws.has_error
+ is_websocket := True
+ on_open (ws)
+ execute_websocket (ws)
+ else
+ execute
+ end
+ end
+
+ execute_websocket (ws: WEB_SOCKET)
+ require
+ is_websocket: is_websocket
+ local
+ exit: BOOLEAN
+ l_frame: detachable WEB_SOCKET_FRAME
+ l_client_message: detachable READABLE_STRING_8
+ l_utf: UTF_CONVERTER
+ do
+ from
+ -- loop until ws is closed or has error.
+ until
+ has_error or else exit
+ loop
+-- debug ("dbglog")
+-- dbglog (generator + ".LOOP WS_REQUEST_HANDLER.process_request {...}")
+-- end
+ if ws.socket_is_ready_for_reading then
+ l_frame := ws.next_frame
+ if l_frame /= Void and then l_frame.is_valid then
+ if attached l_frame.injected_control_frames as l_injections then
+ -- Process injected control frames now.
+ -- FIXME
+ across
+ l_injections as ic
+ loop
+ if ic.item.is_connection_close then
+ -- FIXME: we should probably send this event .. after the `l_frame.parent' frame event.
+ on_event (ws, ic.item.payload_data, ic.item.opcode)
+ exit := True
+ elseif ic.item.is_ping then
+ -- FIXME reply only to the most recent ping ...
+ on_event (ws, ic.item.payload_data, ic.item.opcode)
+ else
+ on_event (ws, ic.item.payload_data, ic.item.opcode)
+ end
+ end
+ end
+
+ l_client_message := l_frame.payload_data
+ if l_client_message = Void then
+ l_client_message := ""
+ end
+
+-- debug ("ws")
+ if is_verbose then
+ print("%NExecute: %N")
+ print (" [opcode: "+ opcode_name (l_frame.opcode) +"]%N")
+ if l_frame.is_text then
+ print (" [client message: %""+ l_client_message +"%"]%N")
+ elseif l_frame.is_binary then
+ print (" [client binary message length: %""+ l_client_message.count.out +"%"]%N")
+ end
+ print (" [is_control: " + l_frame.is_control.out + "]%N")
+ print (" [is_binary: " + l_frame.is_binary.out + "]%N")
+ print (" [is_text: " + l_frame.is_text.out + "]%N")
+ end
+
+ if l_frame.is_connection_close then
+ on_event (ws, l_client_message, l_frame.opcode)
+ exit := True
+ elseif l_frame.is_binary then
+ on_event (ws, l_client_message, l_frame.opcode)
+ elseif l_frame.is_text then
+ check is_valid_utf_8: l_utf.is_valid_utf_8_string_8 (l_client_message) end
+ on_event (ws, l_client_message, l_frame.opcode)
+ else
+ on_event (ws, l_client_message, l_frame.opcode)
+ end
+ else
+-- debug ("ws")
+ if is_verbose then
+ print("%NExecute: %N")
+ print (" [ERROR: invalid frame]%N")
+ if l_frame /= Void and then attached l_frame.error as err then
+ print (" [Code: "+ err.code.out +"]%N")
+ print (" [Description: "+ err.description +"]%N")
+ end
+ end
+ on_event (ws, "", connection_close_frame)
+ exit := True
+ end
+ else
+ if is_verbose then
+ log (generator + ".WAITING WS_REQUEST_HANDLER.process_request {..}", debug_level)
+ end
+ end
+ end
+ end
+
+ execute
+ -- Execute Current request,
+ -- getting data from `request'
+ -- and response to client via `response'.
+ deferred
+ end
+
+feature -- Web Socket Interface
+
+ on_event (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8; a_opcode: INTEGER)
+ -- Called when a frame from the client has been receive
+ local
+ l_message: READABLE_STRING_8
+ do
+ debug ("ws")
+ print ("%Non_event (conn, a_message, " + opcode_name (a_opcode) + ")%N")
+ end
+ if a_message = Void then
+ create {STRING} l_message.make_empty
+ else
+ l_message := a_message
+ end
+
+ if a_opcode = Binary_frame then
+ on_binary (ws, l_message)
+ elseif a_opcode = Text_frame then
+ on_text (ws, l_message)
+ elseif a_opcode = Pong_frame then
+ on_pong (ws, l_message)
+ elseif a_opcode = Ping_frame then
+ on_ping (ws, l_message)
+ elseif a_opcode = Connection_close_frame then
+ on_connection_close (ws, "")
+ else
+ on_unsupported (ws, l_message, a_opcode)
+ end
+ end
+
+ on_open (ws: WEB_SOCKET)
+ -- Called after handshake, indicates that a complete WebSocket connection has been established.
+ deferred
+ end
+
+ on_binary (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
+ deferred
+ end
+
+ on_pong (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
+ do
+ -- log ("Its a pong frame")
+ -- at first we ignore pong
+ -- FIXME: provide better explanation
+ end
+
+ on_ping (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
+ do
+ ws.send (Pong_frame, a_message)
+ end
+
+ on_text (ws: WEB_SOCKET; a_message: READABLE_STRING_8)
+ deferred
+ end
+
+ on_unsupported (ws: WEB_SOCKET; a_message: READABLE_STRING_8; a_opcode: INTEGER)
+ do
+ -- do nothing
+ fixme ("implement on_unsupported")
+ end
+
+ on_connection_close (ws: WEB_SOCKET; a_message: detachable READABLE_STRING_8)
+ do
+ ws.send (Connection_close_frame, "")
+ end
+
+ on_close (ws: WEB_SOCKET)
+ -- Called after the WebSocket connection is closed.
+ deferred
+ end
+
+
+note
+ copyright: "2011-2016, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
+ license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
+ source: "[
+ Eiffel Software
+ 5949 Hollister Ave., Goleta, CA 93117 USA
+ Telephone 805-685-1006, Fax 805-685-6869
+ Website http://www.eiffel.com
+ Customer support http://support.eiffel.com
+ ]"
+end