From cc2f13d948d7605be5053e3c97313c29af001c86 Mon Sep 17 00:00:00 2001 From: Ebersold Date: Wed, 22 Dec 2021 21:08:12 +0100 Subject: [PATCH] Initial import from svn --- .gitmodules | 9 + CMakeLists.txt | 26 + data-models/CMakeLists.txt | 6 + data-models/reverse_proxy.xsd | 248 ++++++ rules | 1 + utils | 1 + wsproxy/CMakeLists.txt | 72 ++ wsproxy/TODO.txt | 49 ++ wsproxy/config/wsproxy.xml | 27 + wsproxy/config/wsproxy_plugin.xml | 54 ++ wsproxy/jsTerm-master/LICENSE | 19 + wsproxy/jsTerm-master/README | 0 wsproxy/jsTerm-master/example/index.html | 93 ++ wsproxy/jsTerm-master/example/palawan.html | 93 ++ .../fonts/ansilove_font_pc_80x25.png | Bin 0 -> 61132 bytes wsproxy/jsTerm-master/src/Term.js | 32 + .../jsTerm-master/src/parser/AnsiParser.js | 97 +++ wsproxy/jsTerm-master/src/parser/ByteArray.js | 36 + .../src/parser/CharacterCodes.js | 260 ++++++ .../src/parser/EscapeSequencer.js | 453 ++++++++++ wsproxy/jsTerm-master/src/parser/Keyboard.js | 11 + wsproxy/jsTerm-master/src/parser/NVTCodes.js | 79 ++ .../jsTerm-master/src/parser/SixteenColors.js | 20 + wsproxy/jsTerm-master/src/parser/Telnet.js | 796 ++++++++++++++++++ wsproxy/jsTerm-master/src/telnet/Session.js | 108 +++ wsproxy/jsTerm-master/src/telnet/Socket.js | 45 + .../jsTerm-master/src/viewer/AnsiViewer.js | 191 +++++ wsproxy/jsTerm-master/src/viewer/Cursor.js | 69 ++ wsproxy/jsTerm-master/src/viewer/Font.js | 11 + wsproxy/jsTerm-master/src/viewer/Point.js | 10 + wsproxy/main.cpp | 450 ++++++++++ wsproxy/plugins/CMakeLists.txt | 9 + wsproxy/plugins/plugin_auth.h | 3 + wsproxy/plugins/plugin_default.cpp | 66 ++ wsproxy/plugins/plugin_default.h | 25 + wsproxy/plugins/plugin_log.cpp | 69 ++ wsproxy/plugins/plugin_log.h | 25 + wsproxy/plugins/plugin_ssl.cpp | 69 ++ wsproxy/plugins/plugin_ssl.h | 22 + wsproxy/plugins/plugin_ws.cpp | 66 ++ wsproxy/plugins/plugin_ws.h | 25 + wsproxy/ressources/echo.html | 71 ++ wsproxy/ressources/oxo.html | 71 ++ wsproxy/sha1.cpp | 185 ++++ wsproxy/sha1.h | 49 ++ wsproxy/test.html | 70 ++ wsproxy/ws_acceptor.cpp | 78 ++ wsproxy/ws_acceptor.h | 32 + wsproxy/ws_allocator.h | 68 ++ wsproxy/ws_base64.cpp | 60 ++ wsproxy/ws_base64.h | 5 + wsproxy/ws_conn_handler.h | 77 ++ wsproxy/ws_constant.h | 53 ++ wsproxy/ws_frame.cpp | 142 ++++ wsproxy/ws_frame.h | 36 + wsproxy/ws_handler.h | 60 ++ wsproxy/ws_handler_factory.cpp | 138 +++ wsproxy/ws_handler_factory.h | 34 + wsproxy/ws_http_handler.h | 43 + wsproxy/ws_http_header.cpp | 97 +++ wsproxy/ws_http_header.h | 28 + wsproxy/ws_https_handler.h | 42 + wsproxy/ws_log.cpp | 81 ++ wsproxy/ws_log.h | 42 + wsproxy/ws_plugin.h | 60 ++ wsproxy/ws_plugin_manager.cpp | 115 +++ wsproxy/ws_plugin_manager.h | 41 + wsproxy/ws_plugin_request.h | 40 + wsproxy/ws_proxy_endpoint.cpp | 150 ++++ wsproxy/ws_proxy_endpoint.h | 62 ++ wsproxy/ws_proxy_handler.cpp | 669 +++++++++++++++ wsproxy/ws_proxy_handler.h | 180 ++++ xml-transform | 1 + 73 files changed, 6625 insertions(+) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 data-models/CMakeLists.txt create mode 100644 data-models/reverse_proxy.xsd create mode 160000 rules create mode 160000 utils create mode 100644 wsproxy/CMakeLists.txt create mode 100644 wsproxy/TODO.txt create mode 100644 wsproxy/config/wsproxy.xml create mode 100644 wsproxy/config/wsproxy_plugin.xml create mode 100644 wsproxy/jsTerm-master/LICENSE create mode 100644 wsproxy/jsTerm-master/README create mode 100755 wsproxy/jsTerm-master/example/index.html create mode 100755 wsproxy/jsTerm-master/example/palawan.html create mode 100644 wsproxy/jsTerm-master/fonts/ansilove_font_pc_80x25.png create mode 100644 wsproxy/jsTerm-master/src/Term.js create mode 100644 wsproxy/jsTerm-master/src/parser/AnsiParser.js create mode 100644 wsproxy/jsTerm-master/src/parser/ByteArray.js create mode 100644 wsproxy/jsTerm-master/src/parser/CharacterCodes.js create mode 100644 wsproxy/jsTerm-master/src/parser/EscapeSequencer.js create mode 100644 wsproxy/jsTerm-master/src/parser/Keyboard.js create mode 100644 wsproxy/jsTerm-master/src/parser/NVTCodes.js create mode 100644 wsproxy/jsTerm-master/src/parser/SixteenColors.js create mode 100644 wsproxy/jsTerm-master/src/parser/Telnet.js create mode 100644 wsproxy/jsTerm-master/src/telnet/Session.js create mode 100644 wsproxy/jsTerm-master/src/telnet/Socket.js create mode 100644 wsproxy/jsTerm-master/src/viewer/AnsiViewer.js create mode 100644 wsproxy/jsTerm-master/src/viewer/Cursor.js create mode 100644 wsproxy/jsTerm-master/src/viewer/Font.js create mode 100644 wsproxy/jsTerm-master/src/viewer/Point.js create mode 100644 wsproxy/main.cpp create mode 100644 wsproxy/plugins/CMakeLists.txt create mode 100644 wsproxy/plugins/plugin_auth.h create mode 100644 wsproxy/plugins/plugin_default.cpp create mode 100644 wsproxy/plugins/plugin_default.h create mode 100644 wsproxy/plugins/plugin_log.cpp create mode 100644 wsproxy/plugins/plugin_log.h create mode 100644 wsproxy/plugins/plugin_ssl.cpp create mode 100644 wsproxy/plugins/plugin_ssl.h create mode 100644 wsproxy/plugins/plugin_ws.cpp create mode 100644 wsproxy/plugins/plugin_ws.h create mode 100644 wsproxy/ressources/echo.html create mode 100644 wsproxy/ressources/oxo.html create mode 100755 wsproxy/sha1.cpp create mode 100755 wsproxy/sha1.h create mode 100644 wsproxy/test.html create mode 100644 wsproxy/ws_acceptor.cpp create mode 100644 wsproxy/ws_acceptor.h create mode 100644 wsproxy/ws_allocator.h create mode 100644 wsproxy/ws_base64.cpp create mode 100644 wsproxy/ws_base64.h create mode 100644 wsproxy/ws_conn_handler.h create mode 100644 wsproxy/ws_constant.h create mode 100644 wsproxy/ws_frame.cpp create mode 100644 wsproxy/ws_frame.h create mode 100644 wsproxy/ws_handler.h create mode 100644 wsproxy/ws_handler_factory.cpp create mode 100644 wsproxy/ws_handler_factory.h create mode 100644 wsproxy/ws_http_handler.h create mode 100644 wsproxy/ws_http_header.cpp create mode 100644 wsproxy/ws_http_header.h create mode 100644 wsproxy/ws_https_handler.h create mode 100644 wsproxy/ws_log.cpp create mode 100644 wsproxy/ws_log.h create mode 100644 wsproxy/ws_plugin.h create mode 100644 wsproxy/ws_plugin_manager.cpp create mode 100644 wsproxy/ws_plugin_manager.h create mode 100644 wsproxy/ws_plugin_request.h create mode 100644 wsproxy/ws_proxy_endpoint.cpp create mode 100644 wsproxy/ws_proxy_endpoint.h create mode 100644 wsproxy/ws_proxy_handler.cpp create mode 100644 wsproxy/ws_proxy_handler.h create mode 160000 xml-transform diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cc376c5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "rules"] + path = rules + url = https://git.ebersold.fr/repos/rules.git +[submodule "utils"] + path = utils + url = https://git.ebersold.fr/repos/aebutils.git +[submodule "xml-transform"] + path = xml-transform + url = https://git.ebersold.fr/repos/xml-transform.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..26bbef5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +PROJECT(wsproxy-root) +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.8) + +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/rules") + +SUBDIRS(rules utils xml-transform wsproxy data-models) + +INCLUDE_DIRECTORIES(${aebutils_SOURCE_DIR}) + +# +# Try generation of configuration reader +# +FIND_LIBRARY(EXPAT_LIB expat "/usr/local/lib:/usr/lib") +INCLUDE_DIRECTORIES(${EXPAT_INCLUDE_DIR}) +FIND_LIBRARY(DL_LIB dl "/lib:/usr/local/lib:/usr/lib") + +INCLUDE_DIRECTORIES("${xml-t_SOURCE_DIR}/libxsd/include") +INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}/include") +INCLUDE_DIRECTORIES(".") +# Does not work under ubuntu cmake 2.8.7 It says empty string !!! +# INCLUDE_DIRECTORIES("${aebutils_SOURCE_DIR}") +INCLUDE_DIRECTORIES("/usr/local/include") + +SET(SAXON CACHE STRING "~/Tools/saxon/saxon9.jar") +SET(XSD2CPP "${xml-t_SOURCE_DIR}/xsd2cpp.xsl") + diff --git a/data-models/CMakeLists.txt b/data-models/CMakeLists.txt new file mode 100644 index 0000000..1cd7385 --- /dev/null +++ b/data-models/CMakeLists.txt @@ -0,0 +1,6 @@ +PROJECT(wsproxy-dm) +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.8) + +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/rules") + + diff --git a/data-models/reverse_proxy.xsd b/data-models/reverse_proxy.xsd new file mode 100644 index 0000000..a0a0dd5 --- /dev/null +++ b/data-models/reverse_proxy.xsd @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Field that defines the path of the request. Ex: /app/toto + + + + + + + + + Field that defines the backend port and address. This parameter is in conflict with attribute host and port !!! + It would be good to see how we could define an unix socket name. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Defines the user name the process should have once running. + + + + + + + Defines the group name the process should have once running. + + + + + + + + + + + + + + + + + + diff --git a/rules b/rules new file mode 160000 index 0000000..aeca878 --- /dev/null +++ b/rules @@ -0,0 +1 @@ +Subproject commit aeca878a84169490996e427211493ac306f00c7c diff --git a/utils b/utils new file mode 160000 index 0000000..5f6e913 --- /dev/null +++ b/utils @@ -0,0 +1 @@ +Subproject commit 5f6e9134ab67b61537fc0bf557842221001f76b1 diff --git a/wsproxy/CMakeLists.txt b/wsproxy/CMakeLists.txt new file mode 100644 index 0000000..ca1adb1 --- /dev/null +++ b/wsproxy/CMakeLists.txt @@ -0,0 +1,72 @@ +PROJECT(wsproxy) + +INCLUDE_DIRECTORIES(${aebutils_SOURCE_DIR}) + +# +# Try generation of configuration reader +# +FIND_LIBRARY(EXPAT_LIB expat "/usr/local/lib:/usr/lib") +INCLUDE_DIRECTORIES(${EXPAT_INCLUDE_DIR}) +FIND_LIBRARY(DL_LIB dl "/lib:/usr/local/lib:/usr/lib") + +INCLUDE_DIRECTORIES("${xml-t_SOURCE_DIR}/libxsd/include") +INCLUDE_DIRECTORIES("${CMAKE_CURRENT_BINARY_DIR}/include") +INCLUDE_DIRECTORIES(".") +INCLUDE_DIRECTORIES("${aebutils_SOURCE_DIR}") +INCLUDE_DIRECTORIES(${aebutils_SOURCE_DIR}/aeb) +INCLUDE_DIRECTORIES(${libltdl_SOURCE_DIR}) +INCLUDE_DIRECTORIES("/usr/local/include") + +SUBDIRS(plugins) + +SET(SAXON CACHE STRING "~/Tools/saxon/saxon9.jar") +SET(XSD2CPP "${xml-t_SOURCE_DIR}/xsd2cpp.xsl") +SET(PROFILE + "${wsproxy-root_SOURCE_DIR}/data-models/reverse_proxy.xsd") + + +ADD_CUSTOM_COMMAND( + SOURCE ${PROFILE} + COMMAND java + ARGS -jar ${SAXON} -s ${PROFILE} -xsl:${XSD2CPP} + rootdir="./parser/" + target="release" root-element="CONFIGURATION" + shared-ptr="aeb" incdir="/../include" + srcdir="./" + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/parser/p_reverse_proxy.cpp + ${CMAKE_CURRENT_BINARY_DIR}/parser/reverse_proxy.cpp + COMMENT "Transform reverse_proxy.xsd" +) + + + + +ADD_EXECUTABLE(wsproxy + main.cpp + ws_proxy_handler.cpp + ws_proxy_endpoint.cpp + ws_handler_factory.cpp + ws_http_header.cpp + ws_acceptor.cpp + ws_base64.cpp + ws_frame.cpp + ws_log.cpp + sha1.cpp + ws_plugin_manager.cpp + ${CMAKE_CURRENT_BINARY_DIR}/parser/reverse_proxy.cpp + ${CMAKE_CURRENT_BINARY_DIR}/parser/p_reverse_proxy.cpp +) + +TARGET_LINK_LIBRARIES(wsproxy ltdl libxsd ${EXPAT_LIB}) + +IF( UNIX ) +ELSE (UNIX ) + TARGET_LINK_LIBRARIES(ip4_server ws2_32) +ENDIF (UNIX ) + + + +INSTALL(TARGETS wsproxy DESTINATION bin + COMPONENT wsproxy + ) diff --git a/wsproxy/TODO.txt b/wsproxy/TODO.txt new file mode 100644 index 0000000..80ea61a --- /dev/null +++ b/wsproxy/TODO.txt @@ -0,0 +1,49 @@ +2015/09/22: +----------- + - TODO create ws_handler abstract class + - TODO create ws_connection_handler class ( handles connection state, + CONNECTING,OPEN,CLOSING,CLOSED) decides if http or https according to + config + - TODO move ws logic into plugin_ws + - TODO plugin configuration management ... + - TODO backend class seems to be ws_proxy_endpoint ... + - TODO servlet support + - TODO fastcgi support + - TODO auth module support +2015/07/28: +----------- + - CMake installer +2014/12/04: +----------- + - DONE telnet seems to work pretty well. + - DONE log information in configuration file + - frame limit check must be finalized. + - Packaging ... + - Documentation on the web site would be of great help as well. + - add a factory for handlers ws_handler_factory. So that I can leave in + peace. + - improve aeb lib to support SSL / TLS + - improve http header analysis (Origin, Cookies, Host) + - Authentication mechanism SASL, Basic Auth .... + - Add statistics number of connections, inbound / outbound flow .... +2014/11/24: +----------- + - DONE include base64 functions + - DONE include sha-1 encoding functions + - DONE add configuration file + - I think a class ws_http_header would be good to handle WS HTTP initiation + - Improve error handling (exception or return code) when dealing with sockets + and streams + - integrate telnet example for proof of concept + - don't forget installer + - check if logging information are defined in configuration file log level, + log file, log syslog... + - check +2014/11/19: +----------- + - There is a need for a logger. + - remove the traces in aeb/net + - include base64 functions + - include sha-1 encoding functions + - add configuration file + - implement end point class diff --git a/wsproxy/config/wsproxy.xml b/wsproxy/config/wsproxy.xml new file mode 100644 index 0000000..b97f6f6 --- /dev/null +++ b/wsproxy/config/wsproxy.xml @@ -0,0 +1,27 @@ + + + + + + + + + + aebersol + users + + + localhost + 10323 + test.cert + + + + + + + + + + + diff --git a/wsproxy/config/wsproxy_plugin.xml b/wsproxy/config/wsproxy_plugin.xml new file mode 100644 index 0000000..509e324 --- /dev/null +++ b/wsproxy/config/wsproxy_plugin.xml @@ -0,0 +1,54 @@ + + + + + + + + + + aebersol + users + + + + + + + localhost + 8082 + test.cert + + + localhost + 11323 + test.cert + + + + + /services/ + + + + + + + /services/telnet + + + + /services/XmlPhone + + + + /services/XmlAdmin + + + + + + + + + diff --git a/wsproxy/jsTerm-master/LICENSE b/wsproxy/jsTerm-master/LICENSE new file mode 100644 index 0000000..ce2a40c --- /dev/null +++ b/wsproxy/jsTerm-master/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010 Peter Nitsch + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/wsproxy/jsTerm-master/README b/wsproxy/jsTerm-master/README new file mode 100644 index 0000000..e69de29 diff --git a/wsproxy/jsTerm-master/example/index.html b/wsproxy/jsTerm-master/example/index.html new file mode 100755 index 0000000..d4647e7 --- /dev/null +++ b/wsproxy/jsTerm-master/example/index.html @@ -0,0 +1,93 @@ + + + + jsTerm Example + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + diff --git a/wsproxy/jsTerm-master/example/palawan.html b/wsproxy/jsTerm-master/example/palawan.html new file mode 100755 index 0000000..d4647e7 --- /dev/null +++ b/wsproxy/jsTerm-master/example/palawan.html @@ -0,0 +1,93 @@ + + + + jsTerm Example + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + diff --git a/wsproxy/jsTerm-master/fonts/ansilove_font_pc_80x25.png b/wsproxy/jsTerm-master/fonts/ansilove_font_pc_80x25.png new file mode 100644 index 0000000000000000000000000000000000000000..98aa57244e159bcf9badb5e3093744c1abd5ff77 GIT binary patch literal 61132 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 literal 0 HcmV?d00001 diff --git a/wsproxy/jsTerm-master/src/Term.js b/wsproxy/jsTerm-master/src/Term.js new file mode 100644 index 0000000..b78bb67 --- /dev/null +++ b/wsproxy/jsTerm-master/src/Term.js @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010 Peter Nitsch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author Peter Nitsch + * http://jsterm.com + * http://www.peternitsch.net + */ + +var TERM = TERM || { + socket : null, + SERVER_URL : "172.25.16.146", // Your node.js server IP + SERVER_PORT : "10222", // Your node.js server port + VERSION : "0.1 alpha" +}; diff --git a/wsproxy/jsTerm-master/src/parser/AnsiParser.js b/wsproxy/jsTerm-master/src/parser/AnsiParser.js new file mode 100644 index 0000000..b008328 --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/AnsiParser.js @@ -0,0 +1,97 @@ +/** + * @author Peter Nitsch + */ + +TERM.AnsiParser = function (viewer){ + + var viewer = viewer; + var escapeCommand = []; + var bufferEscapeCommand = false; + + this.escapeCommands = new TERM.EscapeSequencer(viewer); + this._bytes; + + this.parse = function (bytes) { + + if( bytes != null ) + this._bytes = new TERM.ByteArray(bytes); + while( this._bytes.bytesAvailable > 0 ){ + var result = this._bytes.readUnsignedByte(); + + if( result == SUBSTITUTE) { + break; + } else { + this.readByte( result ); + } + } + + this._bytes.position = 0; + this._bytes = null; + }; + + this._exceptionsLib = []; + this._exceptions = []; + + this.hasException = function( code ) { + if( this._exceptions.indexOf(code) != -1 ) + return true; + return false; + }; + + this.writeException = function( code, callback ) { + if( !this.hasException(code) ){ + this._exceptionsLib[code] = callback; + this._exceptions.push(code); + } + }; + + this.readByte = function (b) { + if(b == ESCAPE) { + escapeCommand = []; + escapeCommand.push(b); + bufferEscapeCommand = true; + this.escapeCommands.onRead(0,b); + } else { + if(bufferEscapeCommand){ + escapeCommand.push(b); + this.escapeCommands.onRead(escapeCommand.length,b); + if( this.escapeCommands.complete && this.escapeCommands.checkCommandAction(escapeCommand.length, b) ) { + this.escapeCommands.executeCommand(escapeCommand); + bufferEscapeCommand = false; + this.escapeCommands.complete = false; + } + } else if( this.hasException(b) ) { + console.log("TERM.AnsiParser.readByte "+b+" Has exception"); + this._exceptionsLib[b]( b, this._bytes ); + } else if(b >= SPACE) { + viewer.drawCharacter(b); + } else { + console.log("TERM.AnsiParser.readByte switch: "+b+ " type="+typeof(b)); + switch(parseInt(b)) { + case BACKSPACE: + viewer.moveBackward(1, true); + break; + + case LINE_FEED: + viewer.carriageReturn(); + viewer.moveDown(1); + break; + + case CARRIAGE_RETURN: + viewer.carriageReturn(); + break; + + case FORM_FEED: + viewer.eraseScreen(); + viewer.reposition(0, 0); + break; + default: + console.log("TERM.AnsiParser.readByte in switch default:"); + //viewer.carriageReturn(); + ; + } + } + } + }; + +}; diff --git a/wsproxy/jsTerm-master/src/parser/ByteArray.js b/wsproxy/jsTerm-master/src/parser/ByteArray.js new file mode 100644 index 0000000..27f41ba --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/ByteArray.js @@ -0,0 +1,36 @@ +/** + * @author Peter Nitsch + */ + +TERM.ByteArray = function (bytes){ + + this.position = 0; + this.stringdata = bytes.toString(); + this.readview = new Int8Array(bytes); + this.buffer = bytes; + this.readUnsignedByte = function() { + // var b = this.stringdata.charCodeAt(this.position); + // alert(" readUnsignedByte enter at position :"+this.position +" / "+ this.bytesAvailable); + var b = this.readview[this.position].toString(10); + //console.log(" readUnsignedByte return ="+b+" at position :"+this.position ); + if(this.position!=this.buffer.byteLength) this.position++; + return b; + }; + + this.writeByte = function(){ + // TO DO + }; + +}; + +TERM.ByteArray.prototype = { + + get bytesAvailable (){ + return this.buffer.byteLength - this.position; + }, + get length (){ + return this.buffer.byteLength; + } + +}; + diff --git a/wsproxy/jsTerm-master/src/parser/CharacterCodes.js b/wsproxy/jsTerm-master/src/parser/CharacterCodes.js new file mode 100644 index 0000000..e1684af --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/CharacterCodes.js @@ -0,0 +1,260 @@ +/** + * @author Peter Nitsch + */ + +const NULL = 0 +const START_OF_HEADING = 1; +const START_OF_TEXT = 2; +const END_OF_TEXT = 3; +const END_OF_TRANSMISSION = 4; +const ENQUIRY = 5; +const ACKNOWLEDGE = 6; +const BELL = 7; +const BACKSPACE = 8; +const HORIZONTAL_TABULATION = 9; +const LINE_FEED = 10; +const VERTICAL_TABULATION = 11; +const FORM_FEED = 12; +const CARRIAGE_RETURN = 13; +const SHIFT_OUT = 14; +const SHIFT_IN = 15; +const DATA_LINK_ESCAPE = 16; +const DEVICE_CONTROL_ONE = 17; +const DEVICE_CONTROL_TWO = 18; +const DEVICE_CONTROL_THREE = 19; +const DEVICE_CONTROL_FOUR = 20; +const NEGATIVE_ACKNOWLEDGE = 21; +const SYNCHRONOUS_IDLE = 22; +const END_OF_TRANSMISSION_BLOCK = 23; +const CANCEL = 24; +const END_OF_MEDIUM = 25; +const SUBSTITUTE = 26; +const ESCAPE = 27; +const FILE_SEPARATOR = 28; +const GROUP_SEPARATOR = 29; +const RECORD_SEPARATOR = 30; +const UNIT_SEPARATOR = 31; +const SPACE = 32; +const EXCLAMATION_MARK = 33; +const QUOTATION_MARK = 34; +const uint_SIGN = 35; +const DOLLAR_SIGN = 36; +const PERCENT_SIGN = 37; +const AMPERSAND = 38; +const APOSTROPHE = 39; +const LEFT_PARENTHESIS = 40; +const RIGHT_PARENTHESIS = 41; +const ASTERISK = 42; +const PLUS_SIGN = 43; +const COMMA = 44; +const HYPHEN_MINUS = 45; +const FULL_STOP = 46; +const SOLIDUS = 47; +const DIGIT_ZERO = 48; +const DIGIT_ONE = 49; +const DIGIT_TWO = 50; +const DIGIT_THREE = 51; +const DIGIT_FOUR = 52; +const DIGIT_FIVE = 53; +const DIGIT_SIX = 54; +const DIGIT_SEVEN = 55; +const DIGIT_EIGHT = 56; +const DIGIT_NINE = 57; +const COLON = 58; +const SEMICOLON = 59; +const LESS_THAN_SIGN = 60; +const EQUALS_SIGN = 61; +const GREATER_THAN_SIGN = 62; +const QUESTION_MARK = 63; +const COMMERCIAL_AT = 64; +const LATIN_CAPITAL_LETTER_A = 65; +const LATIN_CAPITAL_LETTER_B = 66; +const LATIN_CAPITAL_LETTER_C = 67; +const LATIN_CAPITAL_LETTER_D = 68; +const LATIN_CAPITAL_LETTER_E = 69; +const LATIN_CAPITAL_LETTER_F = 70; +const LATIN_CAPITAL_LETTER_G = 71; +const LATIN_CAPITAL_LETTER_H = 72; +const LATIN_CAPITAL_LETTER_I = 73; +const LATIN_CAPITAL_LETTER_J = 74; +const LATIN_CAPITAL_LETTER_K = 75; +const LATIN_CAPITAL_LETTER_L = 76; +const LATIN_CAPITAL_LETTER_M = 77; +const LATIN_CAPITAL_LETTER_N = 78; +const LATIN_CAPITAL_LETTER_O = 79; +const LATIN_CAPITAL_LETTER_P = 80; +const LATIN_CAPITAL_LETTER_Q = 81; +const LATIN_CAPITAL_LETTER_R = 82; +const LATIN_CAPITAL_LETTER_S = 83; +const LATIN_CAPITAL_LETTER_T = 84; +const LATIN_CAPITAL_LETTER_U = 85; +const LATIN_CAPITAL_LETTER_V = 86; +const LATIN_CAPITAL_LETTER_W = 87; +const LATIN_CAPITAL_LETTER_X = 88; +const LATIN_CAPITAL_LETTER_Y = 89; +const LATIN_CAPITAL_LETTER_Z = 90; +const LEFT_SQUARE_BRACKET = 91; +const REVERSE_SOLIDUS = 92; +const RIGHT_SQUARE_BRACKET = 93; +const CIRCUMFLEX_ACCENT = 94; +const LOW_LINE = 95; +const GRAVE_ACCENT = 96; +const LATIN_SMALL_LETTER_A = 97; +const LATIN_SMALL_LETTER_B = 98; +const LATIN_SMALL_LETTER_C = 99; +const LATIN_SMALL_LETTER_D = 100; +const LATIN_SMALL_LETTER_E = 101; +const LATIN_SMALL_LETTER_F = 102; +const LATIN_SMALL_LETTER_G = 103; +const LATIN_SMALL_LETTER_H = 104; +const LATIN_SMALL_LETTER_I = 105; +const LATIN_SMALL_LETTER_J = 106; +const LATIN_SMALL_LETTER_K = 107; +const LATIN_SMALL_LETTER_L = 108; +const LATIN_SMALL_LETTER_M = 109; +const LATIN_SMALL_LETTER_N = 110; +const LATIN_SMALL_LETTER_O = 111; +const LATIN_SMALL_LETTER_P = 112; +const LATIN_SMALL_LETTER_Q = 113; +const LATIN_SMALL_LETTER_R = 114; +const LATIN_SMALL_LETTER_S = 115; +const LATIN_SMALL_LETTER_T = 116; +const LATIN_SMALL_LETTER_U = 117; +const LATIN_SMALL_LETTER_V = 118; +const LATIN_SMALL_LETTER_W = 119; +const LATIN_SMALL_LETTER_X = 120; +const LATIN_SMALL_LETTER_Y = 121; +const LATIN_SMALL_LETTER_Z = 122; +const LEFT_CURLY_BRACKET = 123; +const VERTICAL_LINE = 124; +const RIGHT_CURLY_BRACKET = 125; +const TILDE = 126; +const DELETE = 127; +const LATIN_CAPITAL_LETTER_C_WITH_CEDILLA = 128; +const LATIN_SMALL_LETTER_U_WITH_DIAERESIS = 129; +const LATIN_SMALL_LETTER_E_WITH_ACUTE = 130; +const LATIN_SMALL_LETTER_A_WITH_CIRCUMFLEX = 131; +const LATIN_SMALL_LETTER_A_WITH_DIAERESIS = 132; +const LATIN_SMALL_LETTER_A_WITH_GRAVE = 133; +const LATIN_SMALL_LETTER_A_WITH_RING_ABOVE = 134; +const LATIN_SMALL_LETTER_C_WITH_CEDILLA = 135; +const LATIN_SMALL_LETTER_E_WITH_CIRCUMFLEX = 136; +const LATIN_SMALL_LETTER_E_WITH_DIAERESIS = 137; +const LATIN_SMALL_LETTER_E_WITH_GRAVE = 138; +const LATIN_SMALL_LETTER_I_WITH_DIAERESIS = 139; +const LATIN_SMALL_LETTER_I_WITH_CIRCUMFLEX = 140; +const LATIN_SMALL_LETTER_I_WITH_GRAVE = 141; +const LATIN_CAPITAL_LETTER_A_WITH_DIAERESIS = 142; +const LATIN_CAPITAL_LETTER_A_WITH_RING_ABOVE = 143; +const LATIN_CAPITAL_LETTER_E_WITH_ACUTE = 144; +const LATIN_SMALL_LETTER_AE = 145; +const LATIN_CAPITAL_LETTER_AE = 146; +const LATIN_SMALL_LETTER_O_WITH_CIRCUMFLEX = 147; +const LATIN_SMALL_LETTER_O_WITH_DIAERESIS = 148; +const LATIN_SMALL_LETTER_O_WITH_GRAVE = 149; +const LATIN_SMALL_LETTER_U_WITH_CIRCUMFLEX = 150; +const LATIN_SMALL_LETTER_U_WITH_GRAVE = 151; +const LATIN_SMALL_LETTER_Y_WITH_DIAERESIS = 152; +const LATIN_CAPITAL_LETTER_O_WITH_DIAERESIS = 153; +const LATIN_CAPITAL_LETTER_U_WITH_DIAERESIS = 154; +const CENT_SIGN = 155; +const POUND_SIGN = 156; +const YEN_SIGN = 157; +const PESETA_SIGN = 158; +const LATIN_SMALL_LETTER_F_WITH_HOOK = 159; +const LATIN_SMALL_LETTER_A_WITH_ACUTE = 160; +const LATIN_SMALL_LETTER_I_WITH_ACUTE = 161; +const LATIN_SMALL_LETTER_O_WITH_ACUTE = 162; +const LATIN_SMALL_LETTER_U_WITH_ACUTE = 163; +const LATIN_SMALL_LETTER_N_WITH_TILDE = 164; +const LATIN_CAPITAL_LETTER_N_WITH_TILDE = 165; +const FEMININE_ORDINAL_INDICATOR = 166; +const MASCULINE_ORDINAL_INDICATOR = 167; +const INVERTED_QUESTION_MARK = 168; +const REVERSED_NOT_SIGN = 169; +const NOT_SIGN = 170; +const VULGAR_FRACTION_ONE_HALF = 171; +const VULGAR_FRACTION_ONE_QUARTER = 172; +const INVERTED_EXCLAMATION_MARK = 173; +const LEFT_POINTING_DOUBLE_ANGLE_QUOTATION_MARK = 174; +const RIGHT_POINTING_DOUBLE_ANGLE_QUOTATION_MARK = 175; +const LIGHT_SHADE = 176; +const MEDIUM_SHADE = 177; +const DARK_SHADE = 178; +const BOX_DRAWINGS_LIGHT_VERTICAL = 179; +const BOX_DRAWINGS_LIGHT_VERTICAL_AND_LEFT = 180; +const BOX_DRAWINGS_VERTICAL_SINGLE_AND_LEFT_DOUBLE = 181; +const BOX_DRAWINGS_VERTICAL_DOUBLE_AND_LEFT_SINGLE = 182; +const BOX_DRAWINGS_DOWN_DOUBLE_AND_LEFT_SINGLE = 183; +const BOX_DRAWINGS_DOWN_SINGLE_AND_LEFT_DOUBLE = 184; +const BOX_DRAWINGS_DOUBLE_VERTICAL_AND_LEFT = 185; +const BOX_DRAWINGS_DOUBLE_VERTICAL = 186; +const BOX_DRAWINGS_DOUBLE_DOWN_AND_LEFT = 187; +const BOX_DRAWINGS_DOUBLE_UP_AND_LEFT = 188; +const BOX_DRAWINGS_UP_DOUBLE_AND_LEFT_SINGLE = 189; +const BOX_DRAWINGS_UP_SINGLE_AND_LEFT_DOUBLE = 190; +const BOX_DRAWINGS_LIGHT_DOWN_AND_LEFT = 191; +const BOX_DRAWINGS_LIGHT_UP_AND_RIGHT = 192; +const BOX_DRAWINGS_LIGHT_UP_AND_HORIZONTAL = 193; +const BOX_DRAWINGS_LIGHT_DOWN_AND_HORIZONTAL = 194; +const BOX_DRAWINGS_LIGHT_VERTICAL_AND_RIGHT = 195; +const BOX_DRAWINGS_LIGHT_HORIZONTAL = 196; +const BOX_DRAWINGS_LIGHT_VERTICAL_AND_HORIZONTAL = 197; +const BOX_DRAWINGS_VERTICAL_SINGLE_AND_RIGHT_DOUBLE = 198; +const BOX_DRAWINGS_VERTICAL_DOUBLE_AND_RIGHT_SINGLE = 199; +const BOX_DRAWINGS_DOUBLE_UP_AND_RIGHT = 200; +const BOX_DRAWINGS_DOUBLE_DOWN_AND_RIGHT = 201; +const BOX_DRAWINGS_DOUBLE_UP_AND_HORIZONTAL = 202; +const BOX_DRAWINGS_DOUBLE_DOWN_AND_HORIZONTAL = 203; +const BOX_DRAWINGS_DOUBLE_VERTICAL_AND_RIGHT = 204; +const BOX_DRAWINGS_DOUBLE_HORIZONTAL = 205; +const BOX_DRAWINGS_DOUBLE_VERTICAL_AND_HORIZONTAL = 206; +const BOX_DRAWINGS_UP_SINGLE_AND_HORIZONTAL_DOUBLE = 207; +const BOX_DRAWINGS_UP_DOUBLE_AND_HORIZONTAL_SINGLE = 208; +const BOX_DRAWINGS_DOWN_SINGLE_AND_HORIZONTAL_DOUBLE = 209; +const BOX_DRAWINGS_DOWN_DOUBLE_AND_HORIZONTAL_SINGLE = 210; +const BOX_DRAWINGS_UP_DOUBLE_AND_RIGHT_SINGLE = 211; +const BOX_DRAWINGS_UP_SINGLE_AND_RIGHT_DOUBLE = 212; +const BOX_DRAWINGS_DOWN_SINGLE_AND_RIGHT_DOUBLE = 213; +const BOX_DRAWINGS_DOWN_DOUBLE_AND_RIGHT_SINGLE = 214; +const BOX_DRAWINGS_VERTICAL_DOUBLE_AND_HORIZONTAL_SINGLE = 215; +const BOX_DRAWINGS_VERTICAL_SINGLE_AND_HORIZONTAL_DOUBLE = 216; +const BOX_DRAWINGS_LIGHT_UP_AND_LEFT = 217; +const BOX_DRAWINGS_LIGHT_DOWN_AND_RIGHT = 218; +const FULL_BLOCK = 219; +const LOWER_HALF_BLOCK = 220; +const LEFT_HALF_BLOCK = 221; +const RIGHT_HALF_BLOCK = 222; +const UPPER_HALF_BLOCK = 223; +const GREEK_SMALL_LETTER_ALPHA = 224; +const LATIN_SMALL_LETTER_SHARP_S = 225; +const GREEK_CAPITAL_LETTER_GAMMA = 226; +const GREEK_SMALL_LETTER_PI = 227; +const GREEK_CAPITAL_LETTER_SIGMA = 228; +const GREEK_SMALL_LETTER_SIGMA = 229; +const MICRO_SIGN = 230; +const GREEK_SMALL_LETTER_TAU = 231; +const GREEK_CAPITAL_LETTER_PHI = 232; +const GREEK_CAPITAL_LETTER_THETA = 233; +const GREEK_CAPITAL_LETTER_OMEGA = 234; +const GREEK_SMALL_LETTER_DELTA = 235; +const INFINITY = 236; +const GREEK_SMALL_LETTER_PHI = 237; +const GREEK_SMALL_LETTER_EPSILON = 238; +const INTERSECTION = 239; +const IDENTICAL_TO = 240; +const PLUS_MINUS_SIGN = 241; +const GREATER_THAN_OR_EQUAL_TO = 242; +const LESS_THAN_OR_EQUAL_TO = 243; +const TOP_HALF_INTEGRAL = 244; +const BOTTOM_HALF_INTEGRAL = 245; +const DIVISION_SIGN = 246; +const ALMOST_EQUAL_TO = 247; +const DEGREE_SIGN = 248; +const BULLET_OPERATOR = 249; +const MIDDLE_DOT = 250; +const SQUARE_ROOT = 251; +const SUPERSCRIPT_LATIN_SMALL_LETTER_N = 252; +const SUPERSCRIPT_TWO = 253; +const BLACK_SQUARE = 254; +const NO_BREAK_SPACE = 255; \ No newline at end of file diff --git a/wsproxy/jsTerm-master/src/parser/EscapeSequencer.js b/wsproxy/jsTerm-master/src/parser/EscapeSequencer.js new file mode 100644 index 0000000..afbb831 --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/EscapeSequencer.js @@ -0,0 +1,453 @@ +/** + * @author Peter Nitsch + */ +/** +const LEFT_SQUARE_BRACKET = 91; +const REVERSE_SOLIDUS = 92; +const RIGHT_SQUARE_BRACKET = 93; +const CIRCUMFLEX_ACCENT = 94; +const LOW_LINE = 95; +const GRAVE_ACCENT = 96; +*/ +TERM.InitState = function () { + this.onRead = function (ctx,position,character) { + console.log("TERM.InitState.onRead "+character); + if (character == ESCAPE) + ctx.setState(new TERM.EscapeState()); + }; + console.log("TERM.InitState new called"); +}; + + +TERM.EscapeState = function () { + + this.onRead = function (ctx,position,character) { + console.log("TERM.EscapeState.onRead "+character); + if (character == LEFT_SQUARE_BRACKET) + ctx.setState(new TERM.CsiState()); + if (character == RIGHT_SQUARE_BRACKET) + ctx.setState(new TERM.OscState()); + }; +}; + +TERM.OscState = function () { + + this.onRead = function (ctx,position,character) { + console.log("TERM.OscState.onRead "+character); + if (character == LATIN_CAPITAL_LETTER_G || character == BELL) { + ctx.complete = true; + ctx.setState(new TERM.InitState()); + } + }; +}; + +TERM.CsiState = function () { + + this.onRead = function (ctx,position,character) { + console.log("TERM.CsiState.onRead "+character); + if (ctx.checkCommandAction(position,character)) { + ctx.complete = true; + ctx.setState(new TERM.InitState()); + } + }; +}; + + + +TERM.EscapeSequencer = function (viewer){ + + var viewer = viewer; + var telnet; + var state; + var complete = false; + var _customCommand; + var _currentCustomCommand = {}; + + this.actionCharacterLib = []; + + this.init = function() { + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_H ] = this.cursorPosition; + this.actionCharacterLib[ LATIN_SMALL_LETTER_F ] = this.cursorPosition; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_A ] = this.cursorUp; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_B ] = this.cursorDown; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_C] = this.cursorForward; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_D ] = this.cursorBackward; + this.actionCharacterLib[ LATIN_SMALL_LETTER_S ] = this.saveCursorPosition; + this.actionCharacterLib[ LATIN_SMALL_LETTER_U ] = this.restoreCursorPosition; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_K ] = this.eraseLine; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_J ] = this.eraseDisplay; + this.actionCharacterLib[ LATIN_SMALL_LETTER_N ] = this.deviceRequest; + this.actionCharacterLib[ LATIN_SMALL_LETTER_M ] = this.setGraphicsMode; + this.actionCharacterLib[ LATIN_SMALL_LETTER_H ] = this.setMode; + this.actionCharacterLib[ LATIN_SMALL_LETTER_L ] = this.resetMode; + this.actionCharacterLib[ LATIN_SMALL_LETTER_P ] = this.setKeyboardStrings; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_M ] = this.scrollUp; + this.actionCharacterLib[ LATIN_SMALL_LETTER_R ] = this.scrollScreen; + + // TO DO + this.actionCharacterLib[ LATIN_SMALL_LETTER_A ] = this.unused; + this.actionCharacterLib[ LATIN_SMALL_LETTER_D ] = this.unused; + this.actionCharacterLib[ LATIN_SMALL_LETTER_E ] = this.unused; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_L ] = this.unused; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_P ] = this.unused; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_E ] = this.unused; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_F ] = this.unused; + this.actionCharacterLib[ LATIN_CAPITAL_LETTER_X ] = this.unused; + this.actionCharacterLib[ BELL ] = this.title; + + this.state = new TERM.InitState(); + }; + + this.setState = function (_state) { + this.state = _state; + }; + + this.onRead = function (position,character) { + this.state.onRead(this,position,character); + }; + + this.executeCommand = function(command) { + try { + this.actionCharacterLib[ command[command.length-1] ]( command ); + } catch(error) { + console.log(error); + } + }; + + this.checkCommandAction = function(position, character) { + if( this.actionCharacterLib[character] != undefined ) + return true; + console.log("EscapeSequencer.checkCommandAction undefined "+character); + return false; + }; + + this.unused = function(params) { + // EMPTY + }; + + this.title = function(params) { + console.log("Title commande "); + }; + + this.deviceRequest = function(params) { + if( params[2]==DIGIT_FIVE ){ + TERM.socket.writeByte(ESCAPE); + TERM.socket.writeByte(LEFT_SQUARE_BRACKET); + TERM.socket.writeByte(DIGIT_ZERO); + TERM.socket.writeByte(LATIN_SMALL_LETTER_N); + } else if( params[2]==DIGIT_SIX ) { + var i; + var rows = "" + viewer.cursor.y; + var cols = "" + viewer.cursor.x; + + TERM.socket.writeByte(ESCAPE); + TERM.socket.writeByte(LEFT_SQUARE_BRACKET); + for(i=0;i 0){ + lineArray = params.slice(2, params.length-1); + for( i=0; i0) ? column-1 : 0; + line = (line>0) ? line-1 : 0; + + viewer.reposition(column, line); + } + }; + + this.cursorUp = function(params) { + var valueArray = params.slice(2, params.length-1); + var valueStr = ""; + for( i=0; i 0) ? parseInt(valueStr) : 1; + + viewer.moveUp(value); + }; + + this.cursorDown = function(params) { + var valueArray = params.slice(2, params.length-1); + var valueStr = ""; + for( i=0; i 0) ? parseInt(valueStr) : 1; + + viewer.moveDown(value); + }; + + this.cursorForward = function(params) { + var valueArray = params.slice(2, params.length-1); + var valueStr = ""; + for( i=0; i 0) ? parseInt(valueStr) : 1; + + viewer.moveForward(value); + }; + + this.cursorBackward = function(params) { + var valueArray = params.slice(2, params.length-1); + var valueStr = ""; + for( i=0; i 0) ? parseInt(valueStr) : 1; + + viewer.moveBackward(value); + }; + + this.saveCursorPosition = function(params) { + viewer.savePosition(); + }; + + this.restoreCursorPosition = function(params) { + viewer.restorePosition(); + }; + + // Set Graphic Mode functions + var _bold = false; + var _reverse = false; + + var _boldColors = [BLACK_BOLD, RED_BOLD, GREEN_BOLD, YELLOW_BOLD, BLUE_BOLD, MAGENTA_BOLD, CYAN_BOLD, WHITE_BOLD]; + var _normalColors = [BLACK_NORMAL, RED_NORMAL, GREEN_NORMAL, YELLOW_NORMAL, BLUE_NORMAL, MAGENTA_NORMAL, CYAN_NORMAL, WHITE_NORMAL]; + + var _currentForegroundColor = WHITE_NORMAL; + var _currentBackgroundColor = BLACK_NORMAL; + + this.setGraphicsMode = function(params) { + console.log("setGraphicsMode"); + for( i=2; i 48) + eStr += (eArray[i] - 48).toString(); + } + e = parseInt(eStr); + + viewer.scrollScreen(s, e); + } + }; + + this.scrollUp = function(params) { + viewer.scrollUp(1); + }; + + this.eraseLine = function(params) { + if( params[2]==DIGIT_ONE ){ + viewer.eraseStartOfLine(); + } else if( params[2]==DIGIT_TWO ) { + viewer.eraseLine(); + } else { + viewer.eraseEndOfLine(); + } + }; + + this.eraseDisplay = function(params) { + console.log("Escape::eraseDisaply"); + if( params[2]==DIGIT_ONE ){ + viewer.eraseUp(); + viewer.reposition(0, 0); + } else if( params[2]==DIGIT_TWO ) { + viewer.eraseScreen(); + viewer.reposition(0, 0); + } else { + viewer.eraseDown(); + } + }; + + // Terminal functions + this.setMode = function(){ + // TO DO + }; + + this.resetMode = function(){ + // TO DO + }; + + this.setKeyboardStrings = function(){ + // TO DO + }; + + this.init(); + +}; diff --git a/wsproxy/jsTerm-master/src/parser/Keyboard.js b/wsproxy/jsTerm-master/src/parser/Keyboard.js new file mode 100644 index 0000000..0854a13 --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/Keyboard.js @@ -0,0 +1,11 @@ +/** + * @author Peter Nitsch + */ + +const KEYBOARD_LEFT = 37; +const KEYBOARD_RIGHT = 39; +const KEYBOARD_UP = 38; +const KEYBOARD_DOWN = 40; +const KEYBOARD_PAGE_UP = 33; +const KEYBOARD_PAGE_DOWN = 34; +const KEYBOARD_HOME = 36; diff --git a/wsproxy/jsTerm-master/src/parser/NVTCodes.js b/wsproxy/jsTerm-master/src/parser/NVTCodes.js new file mode 100644 index 0000000..5e4f947 --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/NVTCodes.js @@ -0,0 +1,79 @@ +/** + * @author Peter Nitsch + */ + +const IAC = 255; +const GA = 249; +const WILL = 251; +const WONT = 252; +const DO = 253; +const DONT = 254; +const SB = 250; +const SE = 240; +const NOP = 241; +const DM = 242; +const BRK = 243; + +const IP = 244; +const AO = 245; +const AYT = 246; +const EC = 247; +const EL = 248; + +const ECHO = 1; +const SUPGA = 3; + +const NAWS = 31; +const TTYPE = 24; +const IS = 0; +const SEND = 1; +const LOGOUT = 18; + +const LINEMODE = 34; +const LM_MODE = 1; +const LM_EDIT = 1; +const LM_TRAPSIG = 2; +const LM_MODEACK = 4; +const LM_FORWARDMASK = 2; + +const LM_SLC = 3; +const LM_SLC_NOSUPPORT = 0; +const LM_SLC_DEFAULT = 3; +const LM_SLC_VALUE = 2; +const LM_SLC_CANTCHANGE = 1; +const LM_SLC_LEVELBITS = 3; +const LM_SLC_ACK = 128; +const LM_SLC_FLUSHIN = 64; +const LM_SLC_FLUSHOUT = 32; + +const LM_SLC_SYNCH = 1; +const LM_SLC_BRK = 2; +const LM_SLC_IP = 3; +const LM_SLC_AO = 4; +const LM_SLC_AYT = 5; +const LM_SLC_EOR = 6; +const LM_SLC_ABORT = 7; +const LM_SLC_EOF = 8; +const LM_SLC_SUSP = 9; + +const NEWENV = 39; +const NE_INFO = 2; +const NE_VAR = 0; +const NE_VALUE = 1; +const NE_ESC = 2; +const NE_USERVAR = 3; + +const NE_VAR_OK = 2; +const NE_VAR_DEFINED = 1; +const NE_VAR_DEFINED_EMPTY = 0; +const NE_VAR_UNDEFINED = -1; +const NE_IN_ERROR = -2; +const NE_IN_END = -3; +const NE_VAR_NAME_MAXLENGTH = 50; +const NE_VAR_VALUE_MAXLENGTH = 1000; + +// Unused +const EXT_ASCII = 17; +const SEND_LOC = 23; +const AUTHENTICATION = 37; +const ENCRYPT = 38; \ No newline at end of file diff --git a/wsproxy/jsTerm-master/src/parser/SixteenColors.js b/wsproxy/jsTerm-master/src/parser/SixteenColors.js new file mode 100644 index 0000000..fdf658a --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/SixteenColors.js @@ -0,0 +1,20 @@ +/** + * @author Peter Nitsch + */ + +const BLACK_NORMAL = "#000000"; +const BLACK_BOLD = "#545454"; +const RED_NORMAL = "#a80000"; +const RED_BOLD = "#fc5454"; +const GREEN_NORMAL = "#00a800"; +const GREEN_BOLD = "#54fc54"; +const YELLOW_NORMAL = "#a85400"; +const YELLOW_BOLD = "#fcfc54"; +const BLUE_NORMAL = "#0000a8"; +const BLUE_BOLD = "#5454fc"; +const MAGENTA_NORMAL = "#a800a8"; +const MAGENTA_BOLD = "#fc54fc"; +const CYAN_NORMAL = "#00a8a8"; +const CYAN_BOLD = "#54fcfc"; +const WHITE_NORMAL = "#a8a8a8"; +const WHITE_BOLD = "#fcfcfc"; \ No newline at end of file diff --git a/wsproxy/jsTerm-master/src/parser/Telnet.js b/wsproxy/jsTerm-master/src/parser/Telnet.js new file mode 100644 index 0000000..74d68f2 --- /dev/null +++ b/wsproxy/jsTerm-master/src/parser/Telnet.js @@ -0,0 +1,796 @@ +/** + * @author Peter Nitsch + */ + +TERM.Telnet = function (viewer){ + + var parser = viewer.parser; + var _actionCharacterLib = []; + + var bytes; + + this.init = function() { + _actionCharacterLib[ IAC ] = interpretCommand; + parser.writeException(IAC, interpretCommand); + }; + + function interpretCommand(code, b) { + bytes = b; + var command = bytes.readUnsignedByte(); + handleC(command); + }; + + function read16int () { + var c = 0; + try { + c = bytes.readUnsignedByte(); + return c; + } catch (e) { + console.log(e); + } + + return c; + }; + + function IamHere() { + try { + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(DO); + TERM.socket.writeByte(AYT); + } catch (error) { + console.log("IamHere() Error "+ error); + } + }; + + function nvtBreak() { + // TO DO + }; + + function setTerminalGeometry (width, height) { + // TO DO + }; + + function setEcho(b) { + // TO DO + }; + + var buffer = [0,0]; + + var DO_ECHO = false; + var DO_SUPGA = false; + var DO_NAWS = false; + var DO_TTYPE = false; + var DO_LINEMODE = false; + var DO_NEWENV = false; + + var WAIT_DO_REPLY_SUPGA = false; + var WAIT_DO_REPLY_ECHO = false; + var WAIT_DO_REPLY_NAWS = false; + var WAIT_DO_REPLY_TTYPE = false; + var WAIT_DO_REPLY_LINEMODE = false; + var WAIT_LM_MODE_ACK = false; + var WAIT_LM_DO_REPLY_FORWARDMASK = false; + var WAIT_DO_REPLY_NEWENV = false; + var WAIT_NE_SEND_REPLY = false; + + var WAIT_WILL_REPLY_SUPGA = false; + var WAIT_WILL_REPLY_ECHO = false; + var WAIT_WILL_REPLY_NAWS = false; + var WAIT_WILL_REPLY_TTYPE = false; + + function doCharacterModeInit() { + sendCommand(WILL, ECHO, true); + sendCommand(DONT, ECHO, true); + sendCommand(DO, NAWS, true); + sendCommand(WILL, SUPGA, true); + sendCommand(DO, SUPGA, true); + sendCommand(DO, TTYPE, true); + sendCommand(DO, NEWENV, true); + }; + + function doLineModeInit() { + sendCommand(DO, NAWS, true); + sendCommand(WILL, SUPGA, true); + sendCommand(DO, SUPGA, true); + sendCommand(DO, TTYPE, true); + sendCommand(DO, LINEMODE, true); + sendCommand(DO, NEWENV, true); + }; + + function handleC(i) { + buffer[0] = i; + if (!parseTWO(buffer)) { + try { + buffer[1] = bytes.readUnsignedByte(); + parse(buffer); + } catch(e) { + console.log(e); + } + } + + buffer[0] = 0; + buffer[1] = 0; + }; + + function parseTWO(buf) { + switch (buf[0]) { + case IAC: + break; + case AYT: + IamHere(); + break; + case AO: + case IP: + case EL: + case EC: + case NOP: + break; + case BRK: + nvtBreak(); + break; + default: + return false; + } + return true; + }; + + function parse(buf) { + switch (buf[0]) { + case WILL: + if (supported(buf[1]) && isEnabled(buf[1])) { + ; + } else { + if (waitDOreply(buf[1]) && supported(buf[1])) { + enable(buf[1]); + setWait(DO, buf[1], false); + } else { + if (supported(buf[1])) { + sendCommand(DO, buf[1], false); + enable(buf[1]); + } else { + sendCommand(DONT, buf[1], false); + } + } + } + break; + case WONT: + if (waitDOreply(buf[1]) && supported(buf[1])) { + setWait(DO, buf[1], false); + } else { + if (supported(buf[1]) && isEnabled(buf[1])) { + enable(buf[1]); + } + } + break; + case DO: + if (supported(buf[1]) && isEnabled(buf[1])) { + } else { + if (waitWILLreply(buf[1]) && supported(buf[1])) { + enable(buf[1]); + setWait(WILL, buf[1], false); + } else { + if (supported(buf[1])) { + sendCommand(WILL, buf[1], false); + enable(buf[1]); + } else { + sendCommand(WONT, buf[1], false); + } + } + } + break; + case DONT: + if (waitWILLreply(buf[1]) && supported(buf[1])) { + setWait(WILL, buf[1], false); + } else { + if (supported(buf[1]) && isEnabled(buf[1])) { + enable(buf[1]); + } + } + break; + + case DM: + break; + case SB: + if ((supported(buf[1])) && (isEnabled(buf[1]))) { + switch (buf[1]) { + case NAWS: + handleNAWS(); + break; + case TTYPE: + handleTTYPE(); + break; + case LINEMODE: + handleLINEMODE(); + break; + case NEWENV: + handleNEWENV(); + break; + default: + ; + } + } else { + + } + break; + default: + ; + } + }; + + + function handleNAWS() { + var width = read16int(); + if (width == 255) { + width = read16int(); + } + var height = read16int(); + if (height == 255) { + height = read16int(); + } + skipToSE(); + setTerminalGeometry(width, height); + }; + + function handleTTYPE() { + var tmpstr = ""; + var b = bytes.readUnsignedByte(); + + switch(b) { + case SEND: + var cont = true; + do { + var i; + i = bytes.readUnsignedByte(); + if (i == SE) { + cont = false; + } + + } while (cont); + + _session.flush(); + + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SB); + TERM.socket.writeByte(TTYPE); + TERM.socket.writeByte(IS); + writeMultiByte("ansi", "us-ascii"); + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SE); + break; + + case IS: + tmpstr = readIACSETerminatedString(40); + break; + } + }; + + function handleLINEMODE() { + var c = bytes.readUnsignedByte(); + switch (c) { + case LM_MODE: + handleLMMode(); + break; + case LM_SLC: + handleLMSLC(); + break; + case WONT: + case WILL: + handleLMForwardMask(c); + break; + default: + skipToSE(); + } + }; + + function handleLMMode() { + if (WAIT_LM_MODE_ACK) { + var mask = bytes.readUnsignedByte(); + if (mask != (LM_EDIT | LM_TRAPSIG | LM_MODEACK)) { + } + WAIT_LM_MODE_ACK = false; + } + skipToSE(); + }; + + function handleLMSLC() { + var triple = [0,0,0]; + if (!readTriple(triple)) return; + + if ((triple[0] == 0) && (triple[1] == LM_SLC_DEFAULT) && (triple[2] == 0)) { + skipToSE(); + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SB); + TERM.socket.writeByte(LINEMODE); + TERM.socket.writeByte(LM_SLC); + + for (var i = 1; i < 12; i++) { + TERM.socket.writeByte(i); + TERM.socket.writeByte(LM_SLC_DEFAULT); + TERM.socket.writeByte(0); + } + + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SE); + } else { + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SB); + TERM.socket.writeByte(LINEMODE); + TERM.socket.writeByte(LM_SLC); + TERM.socket.writeByte(triple[0]); + TERM.socket.writeByte(triple[1] | LM_SLC_ACK); + TERM.socket.writeByte(triple[2]); + while (readTriple(triple)) { + TERM.socket.writeByte(triple[0]); + TERM.socket.writeByte(triple[1] | LM_SLC_ACK); + TERM.socket.writeByte(triple[2]); + } + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SE); + } + }; + + function handleLMForwardMask(WHAT) { + switch (WHAT) { + case WONT: + if (WAIT_LM_DO_REPLY_FORWARDMASK) { + WAIT_LM_DO_REPLY_FORWARDMASK = false; + } + break; + } + skipToSE(); + }; + + function handleNEWENV() { + var c = bytes.readUnsignedByte(); + switch (c) { + case IS: + handleNEIs(); + break; + case NE_INFO: + handleNEInfo(); + break; + default: + skipToSE(); + } + }; + + function readNEVariableName(sbuf) { + var i = -1; + do { + i = bytes.readUnsignedByte(); + if (i == -1) { + return NE_IN_ERROR; + } else if (i == IAC) { + i = bytes.readUnsignedByte(); + if (i == IAC) { + sbuf.concat(i); + } else if (i == SE) { + return NE_IN_END; + } else { + return NE_IN_ERROR; + } + } else if (i == NE_ESC) { + i = bytes.readUnsignedByte(); + if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) { + sbuf.concat(i); + } else { + return NE_IN_ERROR; + } + } else if (i == NE_VAR || i == NE_USERVAR) { + return NE_VAR_UNDEFINED; + } else if (i == NE_VALUE) { + return NE_VAR_DEFINED; + } else { + if (sbuf.length >= NE_VAR_NAME_MAXLENGTH) { + return NE_IN_ERROR; + } else { + sbuf.concat(i); + } + } + } while (true); + + return i; + }; + + function readNEVariableValue(sbuf) { + var i = bytes.readUnsignedByte(); + if (i == -1) { + return NE_IN_ERROR; + } else if (i == IAC) { + i = bytes.readUnsignedByte(); + if (i == IAC) { + return NE_VAR_DEFINED_EMPTY; + } else if (i == SE) { + return NE_IN_END; + } else { + return NE_IN_ERROR; + } + } else if (i == NE_VAR || i == NE_USERVAR) { + return NE_VAR_DEFINED_EMPTY; + } else if (i == NE_ESC) { + i = bytes.readUnsignedByte(); + if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) { + sbuf.concat(i); + } else { + return NE_IN_ERROR; + } + } else { + sbuf.concat(i); + } + + do { + i = bytes.readUnsignedByte(); + if (i == -1) { + return NE_IN_ERROR; + } else if (i == IAC) { + i = bytes.readUnsignedByte(); + if (i == IAC) { + sbuf.concat(i); + } else if (i == SE) { + return NE_IN_END; + } else { + return NE_IN_ERROR; + } + } else if (i == NE_ESC) { + i = bytes.readUnsignedByte(); + if (i == NE_ESC || i == NE_VAR || i == NE_USERVAR || i == NE_VALUE) { + sbuf.concat(i); + } else { + return NE_IN_ERROR; + } + } else if (i == NE_VAR || i == NE_USERVAR) { + return NE_VAR_OK; + } else { + if (sbuf.length > NE_VAR_VALUE_MAXLENGTH) { + return NE_IN_ERROR; + } else { + sbuf.concat(i); + } + } + } while (true); + + return i; + }; + + + function readNEVariables() { + var sbuf = ""; + var i = bytes.readUnsignedByte(); + if (i == IAC) { + skipToSE(); + console.log("readNEVariables()::INVALID VARIABLE"); + return; + } + var cont = true; + if (i == NE_VAR || i == NE_USERVAR) { + do { + switch (readNEVariableName(sbuf)) { + case NE_IN_ERROR: + console.log("readNEVariables()::NE_IN_ERROR"); + return; + case NE_IN_END: + console.log("readNEVariables()::NE_IN_END"); + return; + case NE_VAR_DEFINED: + console.log("readNEVariables()::NE_VAR_DEFINED"); + var str = sbuf; + sbuf = ""; + switch (readNEVariableValue(sbuf)) { + case NE_IN_ERROR: + console.log("readNEVariables()::NE_IN_ERROR"); + return; + case NE_IN_END: + console.log("readNEVariables()::NE_IN_END"); + return; + case NE_VAR_DEFINED_EMPTY: + console.log("readNEVariables()::NE_VAR_DEFINED_EMPTY"); + break; + case NE_VAR_OK: + console.log("readNEVariables()::NE_VAR_OK:VAR=" + str + " VAL=" + sbuf.toString()); + sbuf = ""; + break; + } + break; + case NE_VAR_UNDEFINED: + console.log("readNEVariables()::NE_VAR_UNDEFINED"); + break; + } + } while (cont); + } + }; + + function handleNEIs() { + if (isEnabled(NEWENV)) { + readNEVariables(); + } + }; + + function handleNEInfo() { + if (isEnabled(NEWENV)) { + readNEVariables(); + } + }; + + function getTTYPE() { + if (isEnabled(TTYPE)) { + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SB); + TERM.socket.writeByte(TTYPE); + TERM.socket.writeByte(SEND); + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SE); + } + }; + + function negotiateLineMode() { + if (isEnabled(LINEMODE)) { + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SB); + TERM.socket.writeByte(LINEMODE); + TERM.socket.writeByte(LM_MODE); + TERM.socket.writeByte(LM_EDIT | LM_TRAPSIG); + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SE); + WAIT_LM_MODE_ACK = true; + + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SB); + TERM.socket.writeByte(LINEMODE); + TERM.socket.writeByte(DONT); + TERM.socket.writeByte(LM_FORWARDMASK); + TERM.socket.writeByte(IAC); + TERM.socket.writeByte(SE); + WAIT_LM_DO_REPLY_FORWARDMASK = true; + } + }; + + function negotiateEnvironment() { + if (isEnabled(NEWENV)) { + ba.TERM.socket.writeByte(IAC); + ba.TERM.socket.writeByte(SB); + ba.TERM.socket.writeByte(NEWENV); + ba.TERM.socket.writeByte(SEND); + ba.TERM.socket.writeByte(NE_VAR); + ba.TERM.socket.writeByte(NE_USERVAR); + ba.TERM.socket.writeByte(IAC); + ba.TERM.socket.writeByte(SE); + WAIT_NE_SEND_REPLY = true; + } + }; + + function skipToSE() { + while (bytes.readUnsignedByte() != SE) ; + }; + + function readTriple(triple) { + triple[0] = bytes.readUnsignedByte(); + triple[1] = bytes.readUnsignedByte(); + if ((triple[0] == IAC) && (triple[1] == SE)) { + return false; + } else { + triple[2] = bytes.readUnsignedByte(); + return true; + } + + return false; + }; + + + function readIACSETerminatedString(maxlength) { + var where = 0; + var cbuf = []; + var b = ' '; + var cont = true; + + do { + var i; + i = bytes.readUnsignedByte(); + switch (i) { + case IAC: + i = bytes.readUnsignedByte(); + if (i == SE) { + cont = false; + } + break; + case -1: + return ("default"); + default: + } + if (cont) { + b = i.toString(); + if (b == '\n' || b == '\r' || where == maxlength) { + cont = false; + } else { + cbuf[where++] = b; + } + } + } while (cont); + + var str = ""; + for(var j=0; j this.cursor.maxColumnWidth * this.cursor.columnWidth){ + this.moveDown(1); + this.cursor.carriageReturn(); + } + }; + + this.draw = function(charCode) { + //console.log(charCode +" "+ this.cursor.x +" "+ this.cursor.y) + + ctx.fillStyle = this.cursor.backgroundColor; + ctx.fillRect(this.cursor.x, this.cursor.y, font.width, font.height); + + ctx.drawImage(fontmap, + charCode*(font.width+1), this.colorTable(this.cursor.foregroundColor)*font.height, font.width, font.height, + this.cursor.x, this.cursor.y, font.width, font.height); + }; + + this.carriageReturn = function() { + this.cursor.carriageReturn(); + }; + + this.formFeed = function() { + this.cursor.x = 0; + this.cursor.y = 0; + }; + + this.moveBackward = function(val, erase) { + var movements = val; + + while( movements > 0 ) { + this.cursor.moveBackward(1); + if(erase) this.draw(SPACE); + movements--; + } + }; + + this.moveDown = function(val) { + console.log("Viewer.moveDown "+this.cursor.y+" max "+(this.cursor.lineHeight*(botMargin-1)) ); + if(this.cursor.y >= this.cursor.lineHeight*(botMargin-1) && scroll){ + this.scrollUp(1); + } else { + this.cursor.moveDown(val); + } + }; + + this.moveForward = function(val) { + this.cursor.moveForward(val); + }; + + this.moveUp = function(val) { + this.cursor.moveUp(val); + }; + + this.reposition = function(x, y) { + this.cursor.x = x * this.cursor.columnWidth; + this.cursor.y = y * this.cursor.lineHeight; + }; + + this.restorePosition = function() { + this.cursor.x = _savedPosition.x; + this.cursor.y = _savedPosition.y; + }; + + this.savePosition = function() { + _savedPosition.x = this.cursor.x; + _savedPosition.y = this.cursor.y; + }; + + this.displayCleared = function() { + ctx.fillStyle = BLACK_NORMAL; + ctx.fillRect(0, 0, this.cursor.maxColumnWidth * this.cursor.columnWidth, this.cursor.maxLineHeight * this.cursor.lineHeight); + }; + + this.eraseUp = function() { + ctx.fillStyle = BLACK_NORMAL; + ctx.fillRect(0, 0, this.cursor.maxColumnWidth * this.cursor.columnWidth, this.cursor.y); + }; + + this.eraseScreen = function() { + ctx.fillStyle = this.cursor.backgroundColor; + ctx.fillRect(0, 0, this.cursor.maxColumnwidth * this.cursor.columnWidth, this.cursor.maxLineHeight * this.cursor.lineHeight); + }; + + this.eraseDown = function() { + console.log("Viewer::eraseDown 0,"+this.cursor.y); + ctx.fillStyle = BLACK_NORMAL; + ctx.fillRect(0, this.cursor.y, this.cursor.maxColumnWidth * this.cursor.columnWidth, (this.cursor.maxLineHeight * this.cursor.lineHeight) - this.cursor.y); + }; + + this.eraseEndOfLine = function() { + ctx.fillStyle = BLACK_NORMAL; + var w = (this.cursor.maxColumnWidth * this.cursor.columnWidth) - (this.cursor.x - this.cursor.columnWidth); + ctx.fillRect(this.cursor.x, this.cursor.y, w, this.cursor.lineHeight); + }; + + this.eraseStartOfLine = function() { + ctx.fillStyle = BLACK_NORMAL; + ctx.fillRect(0, this.cursor.y, this.cursor.x, this.cursor.lineHeight); + }; + + this.eraseLine = function() { + ctx.fillStyle = BLACK_NORMAL; + ctx.fillRect(0, this.cursor.y, this.cursor.maxColumnWidth * this.cursor.columnWidth, this.cursor.lineHeight); + }; + + this.backgroundColorChanged = function(color) { + this.cursor.backgroundColor = color; + }; + + this.foregroundColorChanged = function(color) { + this.cursor.foregroundColor = color; + }; + + this.home = function() { + this.cursor.x = 0; + this.cursor.y = (topMargin-1) * this.cursor.maxLineHeight; + }; + + this.scrollScreen = function(start, end) { + console.log("Viewer::scrollScreen handle Home KO start="+start+" end="+end+" topM="+topMargin+" "+botMargin); + topMargin = start; + if (end >0 && end < 25) + botMargin = end; + this.scrollUp(start); + handleHome(); + }; + + this.scrollUp = function(val) { + var canvasData = ctx.getImageData(0, topMargin * this.cursor.lineHeight, this.cursor.maxColumnWidth*this.cursor.columnWidth, this.cursor.lineHeight * (botMargin-topMargin)); + this.displayCleared(); + ctx.putImageData(canvasData, 0, this.cursor.lineHeight*(topMargin-1)); + }; + + this.clearCanvas(); + +}; diff --git a/wsproxy/jsTerm-master/src/viewer/Cursor.js b/wsproxy/jsTerm-master/src/viewer/Cursor.js new file mode 100644 index 0000000..9287e68 --- /dev/null +++ b/wsproxy/jsTerm-master/src/viewer/Cursor.js @@ -0,0 +1,69 @@ +/** + * @author Peter Nitsch + */ + +TERM.Cursor = function (){ + + this.foregroundColor = WHITE_NORMAL; + this.backgroundColor = BLACK_NORMAL; + this.position = new TERM.Point(); + this.maxColumnWidth = 80; + this.maxLineHeight = 25; + this.columnWidth = 8; + this.lineHeight = 16; + this.maxColumns = 80; + this.infiniteWidth = false; + this.infiniteHeight = false; + + this.moveForward = function(columns) { + if( this.position.x + (columns*this.columnWidth) <= this.maxColumns * this.columnWidth ) + this.position.x = this.position.x + (columns*this.columnWidth); + else { + //this.position.x = (this.maxColumns * this.columnWidth) - this.columnWidth; + this.position.x = 0; + this.moveDown(1); + } + }; + + this.moveBackward = function(columns) { + if( this.position.x - (columns*this.columnWidth) >= 0 ) + this.position.x = this.position.x - (columns*this.columnWidth); + else + this.position.x = 0; + }; + + this.moveDown = function(lines) { + this.position.y = this.position.y + (lines*this.lineHeight); + }; + + this.moveUp = function(lines) { + if( this.position.y - (lines*this.lineHeight) >= 0 ) + this.position.y = this.position.y - (lines*this.lineHeight); + else + this.position.y = 0; + }; + + this.carriageReturn = function() { + this.position.x = 0; + // this.moveDown(1); + }; + +}; + +TERM.Cursor.prototype = { + + get x (){ + return this.position.x; + }, + set x (val){ + this.position.x = val; + }, + + get y (){ + return this.position.y; + }, + set y (val){ + this.position.y = val; + } + +}; diff --git a/wsproxy/jsTerm-master/src/viewer/Font.js b/wsproxy/jsTerm-master/src/viewer/Font.js new file mode 100644 index 0000000..794290e --- /dev/null +++ b/wsproxy/jsTerm-master/src/viewer/Font.js @@ -0,0 +1,11 @@ +/** + * @author Peter Nitsch + */ + +TERM.Font = function() { + + this.width = 8; + this.height = 16; + this.lineHeight = 23; + +}; \ No newline at end of file diff --git a/wsproxy/jsTerm-master/src/viewer/Point.js b/wsproxy/jsTerm-master/src/viewer/Point.js new file mode 100644 index 0000000..b31b4c9 --- /dev/null +++ b/wsproxy/jsTerm-master/src/viewer/Point.js @@ -0,0 +1,10 @@ +/** + * @author Peter Nitsch + */ + +TERM.Point = function() { + + this.x = 0; + this.y = 0; + +}; \ No newline at end of file diff --git a/wsproxy/main.cpp b/wsproxy/main.cpp new file mode 100644 index 0000000..2df0976 --- /dev/null +++ b/wsproxy/main.cpp @@ -0,0 +1,450 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#endif + +// Signal stuff +#include +#include +#include +using namespace aeb::net; +typedef aeb::net::io_dispatcher io_service; +typedef aeb::net::basic_socket tcp_socket; + +#include + +#include "ws_allocator.h" +#include "ws_log.h" +#include "ws_constant.h" +#include "ws_frame.h" + +#include "ws_proxy_endpoint.h" +#include "ws_proxy_handler.h" +#include "ws_acceptor.h" + +#include "ws_handler_factory.h" +#include +#include + +#include + +#include "optionparser.h" +/** + * plugin stuff + */ +#include "ws_plugin_request.h" +#include "ws_plugin.h" +#include "ws_plugin_manager.h" + + +// Local debug macro +#define LOG_MAIN_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0); +#define LOG_MAIN_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n" ,args);} while (0) + +#define LOG_MAIN_INFO(x) do {m_logger->log(eLOG_INFO,"%s\n",x);} while (0); +#define LOG_MAIN_INFO_FMT(format,args...) do { m_logger->log(eLOG_INFO,format "\n",args);} while (0) ; + +#define LOG_MAIN_NOTICE(x) do {m_logger->log(eLOG_NOTICE,"%s\n",x);} while (0); +#define LOG_MAIN_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args);} while (0) ; + +#define LOG_MAIN_WARN(x) do {m_logger->log(eLOG_WARN,"%s\n",x);} while (0); +#define LOG_MAIN_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args);} while (0) ; + +#define LOG_MAIN_ERROR(x) do {m_logger->log(eLOG_ERROR,"%s\n",x);} while (0); +#define LOG_MAIN_ERROR_FMT(format,args...) do {m_logger->log(eLOG_ERROR,format "\n",args);} while (0) ; + +static ws_log *m_logger = ws_log::getInstance("wsproxy"); + +using namespace wsproxy; + +typedef std::list::iterator acceptor_iterator; + +std::list g_acceptors; + + +// +// Load configuration +// +int load_configuration(const char *filename,ConfigurationType_skel &config) +{ + xsd::parser::SimpleElement se; + p_ConfigurationType_skel p; + p_GatewayType_skel p_gateway; + p_ReverseProxyType_skel p_rproxy; + p_ApplicationType_skel p_app; + p_WsApplicationType_skel p_wsapp; + p_WebSocketType_skel p_ws; + p_AccessFilterType_skel p_ar; + p_ListAuthorization_skel p_lauth; + p_ListGateway_skel p_lg; + p_LogType_skel p_log; + p_LogLevel_skel p_logl; + p_GlobalType_skel p_global; + p_ParamType_skel p_param; + p_PluginsType_skel p_plugins; + p_PluginType_skel p_plugin; + // set parser + p.listen_parser(p_gateway); + p.plugins_parser(p_plugins); + p.rproxy_parser(p_rproxy); + p.websocket_parser(p_ws); + p.global_parser(p_global); + // + p_global.log_parser(p_log); + // + //p_log.name_parser(se); + p_log.level_parser(p_logl); + //p_log.syslog_parser(se); + p_plugins.plugin_parser(p_plugin); + // + p_global.workers_parser(se); + p_global.user_parser(se); + p_global.group_parser(se); + // + p_gateway.host_parser(se); + p_gateway.port_parser(se); + p_gateway.certfile_parser(se); + + p_rproxy.application_parser(p_app); + p_ar.authorize_parser(p_lauth); + p_ar.gateway_parser(p_lg); + p_app.access_filter_parser(p_ar); + p_app.filter_parser(se); + p_app.param_parser(p_param); + + // + p_ws.application_parser(p_wsapp); + // p_app.name_parser(se); + // p_app.host_parser(se); + // p_app_port_parser(se); + // start parsing + std::ifstream iss1; + iss1.open(filename,std::ifstream::in); + xsd::parser::expat::Document doc(p,"CONFIGURATION"); + doc.parse(iss1); + p.post(config); + +} + +// +// Setup log levels +// +void setup_log( wsproxy::GlobalType_skel &g) +{ + if (g.log().size() == 0) + { + std::cerr<<"setup_log no log entries in configuration file"<attr_level().content())) + { + ws_log::getInstance((*it)->attr_name())->set_level(eLOG_DEBUG); + } + else if (! std::string("NOTICE").compare((*it)->attr_level().content())) + { + ws_log::getInstance((*it)->attr_name())->set_level(eLOG_NOTICE); + } + else if (! std::string("WARNING").compare((*it)->attr_level().content())) + { + ws_log::getInstance((*it)->attr_name())->set_level(eLOG_WARN); + } + else if (! std::string("ERROR").compare((*it)->attr_level().content())) + { + ws_log::getInstance((*it)->attr_name())->set_level(eLOG_ERROR); + } + else if (! std::string("INFO").compare((*it)->attr_level().content())) + { + ws_log::getInstance((*it)->attr_name())->set_level(eLOG_INFO); + } + // Set syslog + if ((*it)->attr_syslog()) + { + ws_log::getInstance((*it)->attr_name())->set_syslog() ; + } + // Check if log to file + if ( (*it)->isset_file()) + { + } + } +} + +// +// Signal Handlers +// +volatile sig_atomic_t main_running = true; + +void main_stop(int sig) +{ + LOG_MAIN_DEBUG_FMT("main_stop: signal %d",sig); + main_running = false; + return ; +} + +/** + * Option management + */ +enum optionIndex {UNKNOWN, HELP, DBG,FICHIER,DAEMON,OPT_END}; + +const option::Descriptor usage[] = { + {UNKNOWN, 0, "", "", option::Arg::None,"Usage wsproxy [options]\n\texample: wsproxy -D -c /users/toto/config.xml \n"}, + {HELP, 0, "h", "help", option::Arg::None,"--help,-h \tPrint help and quit."}, + {DBG, 0, "d", "debug", option::Arg::Optional,"--debug,-d \tDebug mode"}, + {FICHIER, 0, "c", "config", option::Arg::Required,"--config,-c \txml configuration file"}, + {DAEMON, 0, "D", "daemon", option::Arg::None,"--daemon,-D \tRun program as daemon"}, + {0,0,0,0,0,0} +}; + +typedef ws_allocator ws32_alloc; +typedef std::basic_string,ws32_alloc> ws32_string; + +// +// Main entry point +// +int main(int argc, char **argv) +{ + + ConfigurationType_skel config; + ip::basic_endpoint ep(ip::tcp::v4(),10222); + + std::vector endpoints; + + { + ws32_alloc alloc; + ws32_string str; + str = "DEDE"; + std::cout<arg) + { + load_configuration(opt->arg,config); + } else + { + return 1; + } + } else { + // Lookup for config file in several places before trying to load the config + load_configuration("wsproxy.xml",config); + } + // + if (config.isset_global() ) + { + setup_log(config.global()); + } else + { + LOG_MAIN_ERROR("main config.global not available in wsproxy.xml file"); + exit(1); + } + // Handle daemon feature .... + if ( options[DAEMON] ) + { + // Allright, fork .... + if ( pid_t pid = fork() ) + { + // Parent process continues here. + if ( pid > 0 ) { + // detach from main ... + exit(0); + } else { + // Failure + LOG_MAIN_ERROR("Failed fork"); + return 1; + } + } + // child process here (pid == 0) + setsid(); + } + + /** + * trying to Load Plugins + */ + LOG_MAIN_INFO("Loading plugins..."); + ws_plugin_manager *mgr = ws_plugin_manager::getInstance("plugins"); + + try + { + if (mgr && config.isset_plugins() ) + { + PluginsType_skel::plugin_sequence::iterator pit = config.plugins().plugin().begin(); + for (; pit != config.plugins().plugin().end() ; ++pit) + { + std::string pn = (*pit)->attr_load() ; + ws_plugin *p_default = mgr->load_plugin(pn); + if (p_default) + { + LOG_MAIN_INFO_FMT("Loaded plugin %s",pn.c_str()); + // p_default->register_hooks(); + } else { + LOG_MAIN_ERROR_FMT("Load Failed for plugin %s ",pn.c_str()); + } + } + + } + } + catch (std::exception &e) + { + LOG_MAIN_ERROR_FMT("EXCEPTION: %s",e.what()); + } + + // Loop over ports to listen to + ConfigurationType_skel::listen_sequence::iterator it = config.listen().begin(); + for ( ; it != config.listen().end() ; ++it) + { + ip::basic_endpoint eps(ip::tcp::v4(),(*it)->port()); + endpoints.push_back(eps); + LOG_MAIN_DEBUG_FMT("Listen on port: %d",(*it)->port()); + } + + // Loop over rporxy applications + ReverseProxyType_skel::application_sequence::iterator it_app = config.rproxy().application().begin(); + if (config.isset_rproxy() ) + { + for ( ; it_app != config.rproxy().application().end() ; ++it_app) + { + LOG_MAIN_INFO_FMT("Reverse Proxy Backend <%s>\tconnect to : %s on port : %d" + , (*it_app)->attr_name().c_str() + , (*it_app)->attr_host().c_str() + , (*it_app)->attr_port() + ); + if ( (*it_app)->isset_access_filter()) + { + AccessFilterType_skel access_filter((*it_app)->access_filter()); + ListGateway_skel gw(access_filter.attr_gateway()); + ListAuthorization_skel autorisations(access_filter.attr_authorize()); + LOG_MAIN_INFO_FMT("Access Filter autorize length <%d> gw<%d>",autorisations.size(),gw.size()); + } + // Alright, do the real job and register the appropriate acceptor + } + } + // Loop over websocket applications + if (config.isset_websocket() ) + { + WebSocketType_skel::application_sequence::iterator it_wsapp = config.websocket().application().begin(); + for ( ; it_wsapp != config.websocket().application().end() ; ++it_wsapp) + { + LOG_MAIN_INFO_FMT("WebSock Proxy Backend <%s>\tconnect to : %s on port : %d" + , (*it_wsapp)->attr_name().c_str() + , (*it_wsapp)->attr_host().c_str() + , (*it_wsapp)->attr_port() + ); + } + } else + { + LOG_MAIN_WARN("No Web Socket application backend defined"); + } + + io_service *io = aeb::singleton::get_instance(); + try + { + // Register all ws application with all acceptors + for ( std::vector::const_iterator ite = endpoints.begin() + ; ite != endpoints.end() + ; ++ite) + { + int *i = new int; + LOG_MAIN_DEBUG_FMT("Web Socket application start acceptor %d",endpoints.size()); + try + { + ws_acceptor::ptr ws_ac( new ws_acceptor( (*ite) + ,config.rproxy().application()) + ); + + ws_ac->listen(); + g_acceptors.push_back(ws_ac); + io->register_handler(2,(ws_acceptor *)ws_ac); + } + catch (std::exception &e) + { + LOG_MAIN_ERROR_FMT("EXCEPTION when creating acceptor %s",e.what()); + } + LOG_MAIN_DEBUG_FMT("Web Socket application end acceptor %d",endpoints.size()); + } + + do + { + sigprocmask(SIG_SETMASK,&sigset,&oldloopset); + io->run(true); + sigprocmask(SIG_SETMASK,&sigset,NULL); + } + while (main_running); + + } + catch (std::exception &e) + { + std::cout<close_handlers(); + LOG_MAIN_NOTICE("Shutdown listening sockets"); + acceptor_iterator ait = g_acceptors.begin(); + for( ; ait != g_acceptors.end() ; ++ait) + { + (*ait)->shutdown(SHUT_RDWR); + } + + LOG_MAIN_DEBUG("Shutdown Wait one more round to normal end"); + io->run(true); + // Unregister + ait = g_acceptors.begin(); + for( ; ait != g_acceptors.end() ; ++ait) + { + io->unregister_handler(2,(ws_acceptor *)(*ait)); + } + // destroy listener + g_acceptors.erase(g_acceptors.begin(),g_acceptors.end()); + LOG_MAIN_NOTICE_FMT("Exist %s",argv[0]); +} +/** + :vim:et:sw=2:ts=2: + */ diff --git a/wsproxy/plugins/CMakeLists.txt b/wsproxy/plugins/CMakeLists.txt new file mode 100644 index 0000000..562478b --- /dev/null +++ b/wsproxy/plugins/CMakeLists.txt @@ -0,0 +1,9 @@ +PROJECT(ws-plugins) + +INCLUDE_DIRECTORIES(".") + +ADD_LIBRARY(plugin_default SHARED plugin_default.cpp) +SET_TARGET_PROPERTIES(plugin_default PROPERTIES COMPILE_FLAGS -fPIC) +SET_TARGET_PROPERTIES(plugin_default PROPERTIES LINK_FLAGS -Wl) +ADD_LIBRARY(plugin_log SHARED plugin_log.cpp) +SET_TARGET_PROPERTIES(plugin_log PROPERTIES COMPILE_FLAGS -fPIC) diff --git a/wsproxy/plugins/plugin_auth.h b/wsproxy/plugins/plugin_auth.h new file mode 100644 index 0000000..67882f3 --- /dev/null +++ b/wsproxy/plugins/plugin_auth.h @@ -0,0 +1,3 @@ +#ifndef PLUGIN_AUTH_H +#define PLUGIN_AUTH_H +#endif /*PLUGIN_AUTH_H*/ diff --git a/wsproxy/plugins/plugin_default.cpp b/wsproxy/plugins/plugin_default.cpp new file mode 100644 index 0000000..5a8070c --- /dev/null +++ b/wsproxy/plugins/plugin_default.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "ws_plugin_request.h" +#include "ws_plugin.h" +#include "plugin_default.h" + +/** + * Default handler + */ +class default_handler : public ws_plugin_handler { + public: + default_handler(); + default_handler(const default_handler &r); + ~default_handler(); + + int handle_request(ws_plugin_request &r); +}; + +// default Contructor +default_handler::default_handler() +{ +} + +// +default_handler::default_handler(const default_handler &o) +{ +} + +// default Desctructor +default_handler::~default_handler() +{ +} + +int +default_handler::handle_request(ws_plugin_request &r) +{ + std::cout<<"default_handler::handle_request"< +#include +#include "ws_plugin_request.h" +#include "ws_plugin.h" +#include "plugin_log.h" + +/** + * Default handler + */ +class log_handler : public ws_plugin_handler { + public: + log_handler(); + log_handler(const log_handler &r); + ~log_handler(); + + int handle_request(ws_plugin_request &r); +}; + +// default Contructor +log_handler::log_handler() +{ +} + +// +log_handler::log_handler(const log_handler &o) +{ +} + +// default Desctructor +log_handler::~log_handler() +{ +} + +int +log_handler::handle_request(ws_plugin_request &r) +{ + std::cout<<"log_handler::handle_request"< +#include +#include "ws_plugin_request.h" +#include "ws_plugin.h" +#include "plugin_ssl.h" + +/** + * Default handler + */ +class ssl_handler : public ws_plugin_handler { + public: + ssl_handler(); + ssl_handler(const ssl_handler &r); + ~ssl_handler(); + + int handle_request(ws_plugin_request &r); +}; + +// default Contructor +ssl_handler::ssl_handler() +{ +} + +// +ssl_handler::ssl_handler(const ssl_handler &o) +{ +} + +// default Desctructor +ssl_handler::~ssl_handler() +{ +} + +int +ssl_handler::handle_request(ws_plugin_request &r) +{ + std::cout<<"ssl_handler::handle_request"< +#include +#include "ws_plugin_request.h" +#include "ws_plugin.h" +#include "plugin_ws.h" + +/** + * Default handler + */ +class ws_handler : public ws_plugin_handler { + public: + ws_handler(); + ws_handler(const ws_handler &r); + ~ws_handler(); + + int handle_request(ws_plugin_request &r); +}; + +// default Contructor +ws_handler::ws_handler() +{ +} + +// +ws_handler::ws_handler(const ws_handler &o) +{ +} + +// default Desctructor +ws_handler::~ws_handler() +{ +} + +int +ws_handler::handle_request(ws_plugin_request &r) +{ + std::cout<<"ws_handler::handle_request"< + + + +WebSocket Test + + + +

WebSocket Test

+ +
diff --git a/wsproxy/ressources/oxo.html b/wsproxy/ressources/oxo.html new file mode 100644 index 0000000..12c7c3c --- /dev/null +++ b/wsproxy/ressources/oxo.html @@ -0,0 +1,71 @@ + + + + +WebSocket Test + + + +

WebSocket Test

+ +
diff --git a/wsproxy/sha1.cpp b/wsproxy/sha1.cpp new file mode 100755 index 0000000..49c5045 --- /dev/null +++ b/wsproxy/sha1.cpp @@ -0,0 +1,185 @@ +/* + Copyright (c) 2011, Micael Hildenborg + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Micael Hildenborg nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + Contributors: + Gustav + Several members in the gamedev.se forum. + Gregory Petrosyan + */ + +#include "sha1.h" + +namespace sha1 +{ + namespace // local + { + // Rotate an integer value to left. + inline const unsigned int rol(const unsigned int value, + const unsigned int steps) + { + return ((value << steps) | (value >> (32 - steps))); + } + + // Sets the first 16 integers in the buffert to zero. + // Used for clearing the W buffert. + inline void clearWBuffert(unsigned int* buffert) + { + for (int pos = 16; --pos >= 0;) + { + buffert[pos] = 0; + } + } + + void innerHash(unsigned int* result, unsigned int* w) + { + unsigned int a = result[0]; + unsigned int b = result[1]; + unsigned int c = result[2]; + unsigned int d = result[3]; + unsigned int e = result[4]; + + int round = 0; + + #define sha1macro(func,val) \ + { \ + const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \ + e = d; \ + d = c; \ + c = rol(b, 30); \ + b = a; \ + a = t; \ + } + + while (round < 16) + { + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 20) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (~b & d), 0x5a827999) + ++round; + } + while (round < 40) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0x6ed9eba1) + ++round; + } + while (round < 60) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc) + ++round; + } + while (round < 80) + { + w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1); + sha1macro(b ^ c ^ d, 0xca62c1d6) + ++round; + } + + #undef sha1macro + + result[0] += a; + result[1] += b; + result[2] += c; + result[3] += d; + result[4] += e; + } + } // namespace + + void calc(const void* src, const int bytelength, unsigned char* hash) + { + // Init the result array. + unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; + + // Cast the void src pointer to be the byte array we can work with. + const unsigned char* sarray = (const unsigned char*) src; + + // The reusable round buffer + unsigned int w[80]; + + // Loop through all complete 64byte blocks. + const int endOfFullBlocks = bytelength - 64; + int endCurrentBlock; + int currentBlock = 0; + + while (currentBlock <= endOfFullBlocks) + { + endCurrentBlock = currentBlock + 64; + + // Init the round buffer with the 64 byte block data. + for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) + { + // This line will swap endian on big endian and keep endian on little endian. + w[roundPos++] = (unsigned int) sarray[currentBlock + 3] + | (((unsigned int) sarray[currentBlock + 2]) << 8) + | (((unsigned int) sarray[currentBlock + 1]) << 16) + | (((unsigned int) sarray[currentBlock]) << 24); + } + innerHash(result, w); + } + + // Handle the last and not full 64 byte block if existing. + endCurrentBlock = bytelength - currentBlock; + clearWBuffert(w); + int lastBlockBytes = 0; + for (;lastBlockBytes < endCurrentBlock; ++lastBlockBytes) + { + w[lastBlockBytes >> 2] |= (unsigned int) sarray[lastBlockBytes + currentBlock] << ((3 - (lastBlockBytes & 3)) << 3); + } + w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3); + if (endCurrentBlock >= 56) + { + innerHash(result, w); + clearWBuffert(w); + } + w[15] = bytelength << 3; + innerHash(result, w); + + // Store hash in result pointer, and make sure we get in in the correct order on both endian models. + for (int hashByte = 20; --hashByte >= 0;) + { + hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff; + } + } + + void toHexString(const unsigned char* hash, char* hexstring) + { + const char hexDigits[] = { "0123456789abcdef" }; + + for (int hashByte = 20; --hashByte >= 0;) + { + hexstring[hashByte << 1] = hexDigits[(hash[hashByte] >> 4) & 0xf]; + hexstring[(hashByte << 1) + 1] = hexDigits[hash[hashByte] & 0xf]; + } + hexstring[40] = 0; + } +} // namespace sha1 diff --git a/wsproxy/sha1.h b/wsproxy/sha1.h new file mode 100755 index 0000000..540c156 --- /dev/null +++ b/wsproxy/sha1.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2011, Micael Hildenborg + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Micael Hildenborg nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SHA1_DEFINED +#define SHA1_DEFINED + +namespace sha1 +{ + + /** + @param src points to any kind of data to be hashed. + @param bytelength the number of bytes to hash from the src pointer. + @param hash should point to a buffer of at least 20 bytes of size for storing the sha1 result in. + */ + void calc(const void* src, const int bytelength, unsigned char* hash); + + /** + @param hash is 20 bytes of sha1 hash. This is the same data that is the result from the calc function. + @param hexstring should point to a buffer of at least 41 bytes of size for storing the hexadecimal representation of the hash. A zero will be written at position 40, so the buffer will be a valid zero ended string. + */ + void toHexString(const unsigned char* hash, char* hexstring); + +} // namespace sha1 + +#endif // SHA1_DEFINED diff --git a/wsproxy/test.html b/wsproxy/test.html new file mode 100644 index 0000000..061997c --- /dev/null +++ b/wsproxy/test.html @@ -0,0 +1,70 @@ + + + + +WebSocket Test + + + +

WebSocket Test

+ +
diff --git a/wsproxy/ws_acceptor.cpp b/wsproxy/ws_acceptor.cpp new file mode 100644 index 0000000..3d4427c --- /dev/null +++ b/wsproxy/ws_acceptor.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#endif + +using namespace aeb::net; +typedef aeb::net::io_dispatcher io_service; +typedef aeb::net::basic_socket tcp_socket; + +#include "reverse_proxy.h" +#include "ws_constant.h" +#include "ws_log.h" +#include "ws_frame.h" +#include "ws_proxy_endpoint.h" +#include "ws_proxy_handler.h" + +#include "ws_acceptor.h" +#include "ws_handler_factory.h" + +// Local debug macro +#define LOG_WSAC_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0) +#define LOG_WSAC_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n",args);} while (0) + +#define LOG_WSAC_NOTICE(x) do { m_logger->log(eLOG_NOTICE,"%s\n",x); } while (0) +#define LOG_WSAC_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args); } while (0) + +#define LOG_WSAC_WARN(x) do { m_logger->log(eLOG_WARN,"%s\n",x); } while (0) +#define LOG_WSAC_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args); } while (0) + +#define LOG_WSAC_ERROR(x) do { m_logger->log(eLOG_ERROR,"%s\n",x); } while (0) +#define LOG_WSAC_ERROR_FMT(format,args...) do { m_logger->log(eLOG_ERROR,format "\n",args); } while (0) + +static ws_log *m_logger = ws_log::getInstance("ws_acceptor"); + + + +ws_acceptor::ws_acceptor(const ip::basic_endpoint &e, const wsproxy::ReverseProxyType_skel::application_sequence &apps) + : basic_socket_acceptor(e) + , m_applications(apps) + , m_ref(1) +{ + LOG_WSAC_DEBUG_FMT("ws_acceptor::ws_acceptor %d apps.size=%d",m_ref,apps.size()); + int val = 1; + m_socket.set_option(SO_REUSEADDR,val); +} + +ws_acceptor::~ws_acceptor() +{ + LOG_WSAC_DEBUG_FMT("ws_acceptor::~ws_acceptor %d",m_ref); +} + +void +ws_acceptor::on_accept() +{ + endpoint_type tmp; + int len = tmp.size(); + detail::socket_type s; + aeb::sys::error ec; + LOG_WSAC_DEBUG("ws_acceptor::on_accept"); + s = aeb::net::accept(this->m_descriptor,tmp.data(),&len,ec); + + LOG_WSAC_NOTICE_FMT("ws_acceptor::on_accept on(%d) new socket %d error code is %d str(%s)",this->m_descriptor,s,ec.code(), ec.c_str()); + + if (s != aeb::net::detail::invalid_socket) + { + ws_handler_factory *f = ws_handler_factory::get_instance(); + ws_proxy_handler::ptr handler = f->get_handler(s,m_applications); + } +} diff --git a/wsproxy/ws_acceptor.h b/wsproxy/ws_acceptor.h new file mode 100644 index 0000000..fb2ac6e --- /dev/null +++ b/wsproxy/ws_acceptor.h @@ -0,0 +1,32 @@ +#ifndef WS_ACCEPTOR_H +#define WS_ACCEPTOR_H + + +class ws_acceptor : public basic_socket_acceptor +{ + private: + int m_ref; + + ws_acceptor(ws_acceptor &a) { + std::cout<<"ws_acceptor:: copy"< ptr; + + inline void add_ref() {m_ref++;}; + + inline void release() {if (--m_ref == 0) delete this;}; + + public: + typedef aeb::net::detail::socket_type socket_type; + + ws_acceptor(const ip::basic_endpoint &e, const wsproxy::ReverseProxyType_skel::application_sequence &); + + void on_accept(); + protected: + ws_proxy_handler::ptr m_handler; + wsproxy::ReverseProxyType_skel::application_sequence m_applications; +}; + +#endif diff --git a/wsproxy/ws_allocator.h b/wsproxy/ws_allocator.h new file mode 100644 index 0000000..4720f7f --- /dev/null +++ b/wsproxy/ws_allocator.h @@ -0,0 +1,68 @@ +#ifndef WS_ALLOCATOR_H +#define WS_ALLOCATOR_H + +/** + * The purpose of the allocator is to + * align memory allocation to 8 bytes borders. This should help + * optimize the memory management. + * An maybe pool management as well to collect statistics. + * + * To be used for strings, vectors, maps, list, queues + */ +template +struct ws_allocator +{ + public: + // traits + typedef T * pointer; + typedef const T * const_pointer; + typedef T & reference; + typedef const T & const_reference; + typedef T value_type; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + public: + + ws_allocator() {} + + ws_allocator(const ws_allocator &a) {} + + ~ws_allocator() {} + + pointer allocate(std::size_t n) + { + //check max size ... + if ( (n < maxSize) && (n <= std::numeric_limits::max() / sizeof(T)) ) + { + int l = (n * sizeof(T) / Align + 1) * Align; + if (void * ptr = std::malloc( ( n * sizeof(T)/Align + 1) * Align ) ) { + return static_cast(ptr); + } + } + throw std::bad_alloc(); + } + + void deallocate(pointer ptr, std::size_t n) { + std::free(ptr); + } + + void construct(pointer p, const_reference val) { new(p) T(val); } + + void destroy(pointer p) + { + p->~T(); + } + + size_t max_size() const { return maxSize;} + + template + struct rebind { typedef ws_allocator other; } ; + protected: +}; + +template +inline bool operator ==(ws_allocator const &,ws_allocator const &) { + return true; +} + +#endif /*WS_ALLOCATOR_H*/ diff --git a/wsproxy/ws_base64.cpp b/wsproxy/ws_base64.cpp new file mode 100644 index 0000000..82c9439 --- /dev/null +++ b/wsproxy/ws_base64.cpp @@ -0,0 +1,60 @@ +#include +#include "ws_base64.h" + +/* + * Translation Table as described in RFC1113 + **/ +static const unsigned char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * Translation Table to decode (created by author) + **/ +static const unsigned char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; + + + +void +encode_base64_block(unsigned char *in,unsigned char *out,int len) +{ + out[0] = (unsigned char) cb64[ (int)(in[0] >> 2) & 0x3F ]; + out[1] = (unsigned char) cb64[ (int)(((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4)) ]; + out[2] = (unsigned char) (len > 1 ? cb64[ (int)(((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6)) ] : '='); + out[3] = (unsigned char) (len > 2 ? cb64[ (int)(in[2] & 0x3f) ] : '='); + ; +} + + +void +decode_base64_block(unsigned char *in,unsigned char *out,int len) +{ + out[ 0 ] = (unsigned char ) (in[0] << 2 | in[1] >> 4); + out[ 1 ] = (unsigned char ) (in[1] << 4 | in[2] >> 2); + out[ 2 ] = (unsigned char ) (((in[2] << 6) & 0xc0) | in[3]); +} + + +void encode_base64(const unsigned char *str,int len,std::string &output ) +{ + int count = 0; + unsigned char *p = ( unsigned char *)(str); + const unsigned char *end =(unsigned char *)( str + len); + + long size = ( (len - 1) /3) * 4 + 4; + + output.resize(size); + unsigned char *out = (unsigned char *)(const_cast(output.c_str())); + while (p < end) + { + if (p+3 < end) { + encode_base64_block(p,out,end-p); + p+=3; out+=4; + } else + { + encode_base64_block(p,out,end-p); + p+= (end-p); + } + } + + +} + diff --git a/wsproxy/ws_base64.h b/wsproxy/ws_base64.h new file mode 100644 index 0000000..6b698e4 --- /dev/null +++ b/wsproxy/ws_base64.h @@ -0,0 +1,5 @@ +#ifndef WS_BASE64_H__ +#define WS_BASE64_H__ +void encode_base64(const unsigned char *,int len,std::string &output ); + +#endif diff --git a/wsproxy/ws_conn_handler.h b/wsproxy/ws_conn_handler.h new file mode 100644 index 0000000..08e58d1 --- /dev/null +++ b/wsproxy/ws_conn_handler.h @@ -0,0 +1,77 @@ +#ifndef WS_CONN_HANDLER_H +#define WS_CONN_HANDLER_H + +class ws_conn_handler; + +/** + * \brief + * Possible state. IDLE, CONNECTING, SSL HANDSHACK, OPEN, CLOSING, CLOSED + * when ssl, conn goes through SSL HANDSHACK, otherwise direcly to OPEN + * Should I handle chunk and mime types here or at a higher level? + * + */ +class ws_conn_state +{ + ws_conn_state(); + ws_conn_state(const ws_conn_state &con); + public: + virtual ~ws_conn_state(); + + virtual void onRead(ws_conn_handler &p,const ws_frame &buffer); + + virtual void onClose(unsigned short _code) ; + + virtual void doActivity(ws_conn_handler &p); + +}; + + + +// +// ws_conn_handler +// \bried Actually, this is probably the lowest layer I should use. +// Once connected, the flow should travel through upper layers +// that know better what to do. Plugins should come in action +// specifically conn hooks +// - decides if the connection is accepted +// - according to config, decides if its ssl or not +// - conn_handler has a stated. IDLE CONNECTING OPEN CLOSING CLOSED +// - several https/http/ws or what ever can be processed. +// - if ssl do ssl auth stuff and so on +// - if plain do plain read and so on +// +class ws_conn_handler : public ws_handler +{ + public: + + bool available () const; + + public: + ws_conn_handler(detail::socket_type s); + + virtual ~ws_conn_handler() ; + + virtual void onRead(ws_conn_handler &p,const ws_frame &buffer); + + virtual void onClose(unsigned short _code) ; + + void on_write() ; + + void on_read() ; + + void on_accept(); + + void io_register(); + + void io_unregister(); + // reuse handler with a new socket + void reuse(aeb::net::detail::socket_type s); + + protected: + ws_conn_handler(const ws_conn_handler &p); + ws_conn_state *m_state; + // Do I own stream objects or not +}; + + +#endif /*WS_CONN_HANDLER_H*/ diff --git a/wsproxy/ws_constant.h b/wsproxy/ws_constant.h new file mode 100644 index 0000000..49ae5df --- /dev/null +++ b/wsproxy/ws_constant.h @@ -0,0 +1,53 @@ +#ifndef WS_CONSTANT_H__ +#define WS_CONSTANT_H__ + +#define SEC_WEBSOCKET_VERSION "Sec-WebSocket-Version" +#define SEC_WEBSOCKET_KEY "Sec-WebSocket-Key" +#define SEC_WEBSOCKET_ACCEPT "Sec-WebSocket-Accept" +#define SEC_WEBSOCKET_PROTOCOL "Sec-WebSocket-Protocol" +#define WS_UPGRADE "Upgrade" +#define WS_CONNECTION "Connection" +#define WS_COOKIES "Cookies" +#define WS_HOST "Host" +#define WS_GET "GET" +#define WS_ORIGIN "Origin" + +#define WS_HANDSHACK "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +// Structures as well + +enum ws_opcodes { + ews_continue = 0x0 + ,ews_text = 0x1 + ,ews_binary = 0x2 + ,ews_close = 0x8 + ,ews_ping = 0x9 + ,ews_pong = 0xA +}; + +enum ws_exit_code { + ews_exit_normal = 1000 + ,ews_exit_server = 1001 + ,ews_exit_protocol = 1001 + ,ews_exit_bad_frame = 1003 +}; + +typedef struct ws_header_ { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // For Big endian + unsigned char fin :1; + unsigned char rsv1:1; + unsigned char rsv2:1; + unsigned char rsv3:1; + unsigned char opcode:4; +#else + unsigned char opcode:4; + unsigned char rsv3:1; + unsigned char rsv1:1; + unsigned char rsv2:1; + unsigned char fin :1; +#endif +} ws_header; + + +#endif diff --git a/wsproxy/ws_frame.cpp b/wsproxy/ws_frame.cpp new file mode 100644 index 0000000..f2dbd18 --- /dev/null +++ b/wsproxy/ws_frame.cpp @@ -0,0 +1,142 @@ +#include +#include +#include +#include + +#include // for calloc +#include + +#include "ws_log.h" +#include "ws_constant.h" +#include "ws_frame.h" + +// Local debug macro +#define LOG_WSFR_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0) +#define LOG_WSFR_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n",args);} while (0) + +#define LOG_WSFR_NOTICE(x) do { m_logger->log(eLOG_NOTICE,"%s\n",x); } while (0) +#define LOG_WSFR_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args); printf(format "\n",args);} while (0) + +#define LOG_WSFR_WARN(x) do { m_logger->log(eLOG_WARN,"%s\n",x); } while (0) +#define LOG_WSFR_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args); printf(format "\n",args);} while (0) + +#define LOG_WSFR_ERROR(x) do { m_logger->log(eLOG_ERROR,"%s\n",x); } while (0) +#define LOG_WSFR_ERROR_FMT(format,args...) do { m_logger->log(eLOG_ERROR,format "\n",args); } while (0) + +static ws_log *m_logger = ws_log::getInstance("ws_frame"); + + + +ws_frame::ws_frame(unsigned char *_d,long l) + : m_length(l) , m_buffer(_d) , m_data(_d) , m_allocated(false) +{ +} + +ws_frame::~ws_frame() +{ + if (m_allocated) + free(m_buffer); +} + +int +ws_frame::decode( unsigned char *_d,size_t len) +{ + bool have_key = false; + m_buffer = _d; + m_data = _d; + m_header = *(ws_header *)_d; + m_data++; + LOG_WSFR_DEBUG_FMT("ws_frame::decode start len=%d",len); + if ( *m_data & 0x80) + have_key = true; + if ( (*m_data & 0x7F) == 0x7E) { + // 16 bits length header + m_data++; + unsigned short l = *(unsigned short *) m_data ; + m_data+=2; + m_length = l; + } else { + m_length = *m_data & 0x7F; + m_data++; + } + if (have_key) + { + int i = 0; + unsigned char *d = NULL; + for ( ; i < 4 ; m_key[i++] = *m_data, m_data++) ; + // Now if mask decode the buffer; + d = m_data; + len -= (d - _d); + i = 0; + LOG_WSFR_DEBUG_FMT("ws_frame::decode loop pos=%d len=%d",(int)(m_data-_d),len); + while (len--) + { + *d = *d ^ m_key[i % 4]; + d++; + i++; + } + } else { + LOG_WSFR_ERROR("ws_frame::decode un encoded frame should quit"); + } + LOG_WSFR_DEBUG("End Decode Frame"); + LOG_WSFR_DEBUG_FMT("ws_frame::decode end m_length=%d data=%s",m_length,m_data); +} + +int +ws_frame::encode(const unsigned char *_b,size_t len,int mode) +{ + m_length = len; + m_allocated = true; + + std::ostringstream ss; + std::ostringstream str; + + int al = ( len / 8 + 2) * 8; + m_buffer = (unsigned char *)calloc( al , sizeof(char) ); + LOG_WSFR_DEBUG_FMT("ws_frame::encode( len=%d) allocated %d",len,al); +#if 0 + // For debug purpose. + for (int i = 0 ; i < len ; i++ ) + { + ss< 31 && _b[i] < 127) { + str<<_b[i]; + } else { + str<<"."; + } + if ( ! (i % 16 ) ) { + LOG_WSPH_DEBUG_FMT("ws_frame::encode(buffer=<%s %s> size=%lu)",ss.str().c_str(),str.str().c_str(),len); + ss.str(""); str.str(""); + } + } +#endif + if (len < 126) + { + ws_header *header = (ws_header *)m_buffer; + //header->opcode = ews_binary; + header->opcode = mode; + header->fin = 1; + m_buffer[1] = (char) len; + memcpy(&m_buffer[2],_b,len); + //m_stream.send(buffer,len+2); + m_buffer_length = len + 2; + } else if (len < 65535) + { + unsigned short *l = (unsigned short *)&m_buffer[2]; + ws_header *header = (ws_header *)m_buffer; + + //header->opcode = ews_binary; + header->opcode = mode; + header->fin = 1; + m_buffer[1] = (char) 126; + *l = (unsigned short)len; + memcpy(&m_buffer[4],_b,len); + //m_stream.send(buffer,len+4); + m_buffer_length = len + 4; + } else { + LOG_WSFR_ERROR("ws_frame::encode( frame_size Too big > 65535 not supported"); + return 1; + } + return 0; + +} diff --git a/wsproxy/ws_frame.h b/wsproxy/ws_frame.h new file mode 100644 index 0000000..0424ed2 --- /dev/null +++ b/wsproxy/ws_frame.h @@ -0,0 +1,36 @@ +#ifndef WS_BUFFER_H__ +#define WS_BUFFER_H__ + +class ws_frame +{ + public: + ws_frame(unsigned char *_d=NULL,long len=0); + + ~ws_frame(); + + inline long length() const { return m_length; }; + + inline unsigned char * data() const { return m_data; }; + + inline const unsigned char * buffer() const { return m_buffer; }; + + inline long buffer_length() const { return m_buffer_length;}; + + inline int opcode() const { return m_header.opcode; }; + + int decode( unsigned char *b,size_t length); + + int encode(const unsigned char *_b,size_t length , int mode = ews_binary); + + private: + ws_frame(const ws_frame &) {} ; + protected: + unsigned char m_key[4]; + unsigned char *m_buffer; + unsigned char *m_data; + long m_length; + long m_buffer_length; + ws_header m_header; + bool m_allocated; +}; +#endif diff --git a/wsproxy/ws_handler.h b/wsproxy/ws_handler.h new file mode 100644 index 0000000..c288aec --- /dev/null +++ b/wsproxy/ws_handler.h @@ -0,0 +1,60 @@ +#ifndef WS_HANDLER_H +#define WS_HANDLER_H + + +// +// ws_proxy_handler +// +class ws_handler : public event_handler +{ + private: + int m_ref; + public: + + typedef basic_socket_stream Stream; + /// aeb::ssl::stream ? or detail::socket_type + typedef basic_socket_stream SSLStream; + + typedef aeb::intrusive_ptr ptr; + + inline void add_ref() {m_ref++;}; + + inline void release() { + if (--m_ref == 0) delete this; + }; + bool available () const; + + public: + ws_handler(detail::socket_type s); + + virtual ~ws_handler() ; + + + virtual void onRead(ws_proxy_handler &p,const ws_frame &buffer); + + virtual void onClose(unsigned short _code) ; + + void on_write() ; + + void on_read() ; + + void on_accept(); + + void io_register(); + + void io_unregister(); + // reuse handler with a new socket + void reuse(aeb::net::detail::socket_type s); + + protected: + ws_handler(const ws_handler &p) ; + // Should own an endpoint + long m_max_frame_size; +}; + +/** +vim:et:sw=2:ts=2 + */ + + +#endif /*WS_HANDLER_H*/ diff --git a/wsproxy/ws_handler_factory.cpp b/wsproxy/ws_handler_factory.cpp new file mode 100644 index 0000000..1f656ad --- /dev/null +++ b/wsproxy/ws_handler_factory.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include // find_if + +#include +#include +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#endif + + +#include "time.h" +#include "reverse_proxy.h" +#include "ws_constant.h" +#include "ws_log.h" +#include "ws_frame.h" +#include "sha1.h" +#include "ws_base64.h" + +using namespace aeb::net; +typedef aeb::net::io_dispatcher io_service; +#include "ws_proxy_endpoint.h" +#include "ws_proxy_handler.h" +#include "ws_handler_factory.h" + +// Log feature +#define LOG_WSHF_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0); +#define LOG_WSHF_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n" ,args);} while (0) + +#define LOG_WSHF_NOTICE(x) do {m_logger->log(eLOG_NOTICE,"%s\n",x);} while (0); +#define LOG_WSHF_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args);} while (0) + +#define LOG_WSHF_WARN(x) do {m_logger->log(eLOG_WARN,"%s\n",x);} while (0) +#define LOG_WSHF_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args);} while (0) + +#define LOG_WSHF_ERROR(x) do {m_logger->log(eLOG_ERROR,"%s\n",x);} while (0) +#define LOG_WSHF_ERROR_FMT(format,args...) do {m_logger->log(eLOG_ERROR,format "\n",args);} while (0) + +static ws_log *m_logger = ws_log::getInstance("ws_handler_factory"); + +// Begin Of Code + +// +ws_handler_factory::ws_handler_factory() +{ + LOG_WSHF_DEBUG("ws_handler_factory::ws_handler_factory"); +} + +// +ws_handler_factory::ws_handler_factory(const ws_handler_factory &_f) +{ + LOG_WSHF_DEBUG("ws_handler_factory::ws_handler_factory copy"); +} + +// I need a finder object to find +struct find_free { + find_free() {}; + bool operator ()(ws_handler_factory::handler &h) { + return h->available(); + }; +}; + +// I need a finder object to find +struct find_pointer { + find_pointer(ws_proxy_handler *_p) : m_p(_p) {}; + bool operator ()(ws_handler_factory::handler &h) { + return m_p == (ws_proxy_handler *)h; + }; + ws_proxy_handler *m_p; +}; +// handler get_handler(aeb::net::detail::socket_type fd,const wsproxy::ReverseProxyType_skel::application_sequence &apps); +// +ws_handler_factory::handler +ws_handler_factory::get_handler(aeb::net::detail::socket_type fd,const wsproxy::ReverseProxyType_skel::application_sequence &apps) +{ + LOG_WSHF_DEBUG_FMT("ws_handler_factory::get_handler active handlers %d", m_handlers.size()); + + handler_iterator it = std::find_if(m_handlers.begin(),m_handlers.end(),find_free()); + + if ( it == m_handlers.end() ) + { + LOG_WSHF_DEBUG("ws_handler_factory::get_handler create new"); + handler h( new ws_proxy_handler(fd,apps)); + m_handlers.push_back(h); + return h; + } + else + { + handler h(*it); + LOG_WSHF_DEBUG("ws_handler_factory::get_handler recycle unused handler"); + (*it)->reuse(fd); + return h; + } +} + +bool ws_handler_factory::find(ws_proxy_handler *ptr,handler _rp) +{ + handler_iterator it = std::find_if(m_handlers.begin(),m_handlers.end(),find_pointer(ptr)); + if (it != m_handlers.end()) + { + _rp = *it; + return true; + } else { + return false; + } +} +// +ws_handler_factory * ws_handler_factory::m_factory = NULL; + +ws_handler_factory * +ws_handler_factory::get_instance() { + if (m_factory == NULL) + m_factory = new ws_handler_factory(); + return m_factory; + +} + +void +ws_handler_factory::close_handlers() +{ + LOG_WSHF_DEBUG_FMT("ws_handler_factory::close_handlers handlers size: %d",m_handlers.size()); + handler_iterator it = m_handlers.begin(); + for ( ; it != m_handlers.end() ; ++it) + { + if (!(*it)->available()) + (*it)->onClose(ews_exit_server); + // (*it)->release(); + } + //m_handlers.erase(m_handlers.begin(),m_handlers.end()); + + LOG_WSHF_DEBUG_FMT("ws_handler_factory::close_handlers handlers end size : %d",m_handlers.size()); +} diff --git a/wsproxy/ws_handler_factory.h b/wsproxy/ws_handler_factory.h new file mode 100644 index 0000000..f6d58c6 --- /dev/null +++ b/wsproxy/ws_handler_factory.h @@ -0,0 +1,34 @@ +#ifndef WS_HANDLER_FACTORY_H__ +#define WS_HANDLER_FACTORY_H__ + + +class ws_handler_factory +{ + public: + typedef ws_proxy_handler::ptr handler; + //typedef std::map handler_map; + typedef std::list handler_map; + typedef handler_map::iterator handler_iterator; + + static ws_handler_factory *get_instance(); + // + handler get_handler(aeb::net::detail::socket_type fd,const wsproxy::ReverseProxyType_skel::application_sequence &apps); + // In case of end, close all sockets and exit properly + void close_handlers(); + + bool find(ws_proxy_handler *ptr,handler _rp); + protected: + handler_map m_handlers; + + private: + ws_handler_factory(); + + ws_handler_factory(const ws_handler_factory &_f); + + static ws_handler_factory *m_factory; +}; + +/* + vim:et:sw=2:ts=2 + */ +#endif diff --git a/wsproxy/ws_http_handler.h b/wsproxy/ws_http_handler.h new file mode 100644 index 0000000..7e2656f --- /dev/null +++ b/wsproxy/ws_http_handler.h @@ -0,0 +1,43 @@ +#ifndef WS_HTTP_HANDLER_H +#define WS_HTTP_HANDLER_H + +// +// ws_http_handler +// +class ws_http_handler : public ws_handler +{ + public: + + bool available () const; + + public: + ws_http_handler(detail::socket_type s); + + virtual ~ws_http_handler() ; + + + virtual void onRead(ws_handler &p,const ws_frame &buffer); + + virtual void onClose(unsigned short _code) ; + + void on_write() ; + + void on_read() ; + + void on_accept(); + + void io_register(); + + void io_unregister(); + // reuse handler with a new socket + void reuse(aeb::net::detail::socket_type s); + + protected: + ws_http_handler(const ws_http_handler &p) ; +}; + +/** +vim:et:sw=2:ts=2 + */ + +#endif /*WS_HTTP_HANDLER_H*/ diff --git a/wsproxy/ws_http_header.cpp b/wsproxy/ws_http_header.cpp new file mode 100644 index 0000000..360543e --- /dev/null +++ b/wsproxy/ws_http_header.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include + +#include "ws_constant.h" +#include "ws_log.h" +#include "ws_allocator.h" +#include "ws_http_header.h" + +// Local debug macro +#define LOG_WSHH_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0); +#define LOG_WSHH_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n" ,args);} while (0) + +#define LOG_WSHH_NOTICE(x) do {m_logger->log(eLOG_NOTICE,"%s\n",x);} while (0); +#define LOG_WSHH_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args);} while (0) ; + +#define LOG_WSHH_WARN(x) do {m_logger->log(eLOG_WARN,"%s\n",x);} while (0); +#define LOG_WSHH_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args);} while (0) ; + +#define LOG_WSHH_ERROR(x) do {m_logger->log(eLOG_ERROR,"%s\n",x);} while (0); +#define LOG_WSHH_ERROR_FMT(format,args...) do {m_logger->log(eLOG_ERROR,format "\n",args);} while (0) ; + +static ws_log *m_logger = ws_log::getInstance("ws_proxy_handler"); + +//using namespace aeb::net; +//typedef aeb::net::io_dispatcher io_service; + +//#include "ws_proxy_endpoint.h" + +// +ws_http_header::ws_http_header() +{ + LOG_WSHH_DEBUG("ws_http_header::ws_http_header"); +} + + +ws_http_header::~ws_http_header() +{ + LOG_WSHH_DEBUG("ws_http_header::~ws_http_header"); +} + +int +ws_http_header::parse(const std::string &_h) +{ + LOG_WSHH_DEBUG("ws_http_header::parse "); + std::istringstream stream(_h); + char result[256]; + + while( ! stream.getline(result,256).eof() ) { + int pos; + std::string s(result); + // std::cout<<"Process line:"< ws256_alloc; + typedef ws_allocator ws32_alloc; + typedef std::basic_string,ws256_alloc> string256; + typedef std::basic_string,ws32_alloc> string32; + + ws_http_header(); + + virtual ~ws_http_header(); + + int parse(const std::string &_h); + + void to_string(std::string &_h); + + void to_string(int code,std::string &result); + + void set_entry(const string32 &key,const std::string &_val); + protected: + // header entries + std::map m_header; + +}; + +#endif diff --git a/wsproxy/ws_https_handler.h b/wsproxy/ws_https_handler.h new file mode 100644 index 0000000..f7b4370 --- /dev/null +++ b/wsproxy/ws_https_handler.h @@ -0,0 +1,42 @@ +#ifndef WS_HTTPS_HANDLER_H +#define WS_HTTPS_HANDLER_H + +// +// ws_https_handler +// +class ws_https_handler : public ws_handler +{ + public: + + bool available () const; + + public: + ws_https_handler(detail::socket_type s); + + virtual ~ws_https_handler() ; + + + virtual void onRead(ws_handler &p,const ws_frame &buffer); + + virtual void onClose(unsigned short _code) ; + + void on_write() ; + + void on_read() ; + + void on_accept(); + + void io_register(); + + void io_unregister(); + // reuse handler with a new socket + void reuse(aeb::net::detail::socket_type s); + + protected: + ws_https_handler(const ws_http_handler &p); + +}; + + + +#endif /*WS_HTTPS_HANDLER_H*/ diff --git a/wsproxy/ws_log.cpp b/wsproxy/ws_log.cpp new file mode 100644 index 0000000..8396c95 --- /dev/null +++ b/wsproxy/ws_log.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include // for memset +#include + +#include "ws_log.h" + + +ws_log *ws_log::getInstance(const std::string &cls) +{ +// std::cout<<"ws_log::getInstance "<second; + } + ws_log *l = new ws_log(cls); + ws_log::m_loggers[cls] = l; + // std::cout<<"ws_log::getInstance before return "< ws_log::m_loggers = init_map(); +bool ws_log::m_syslog_open = false; + diff --git a/wsproxy/ws_log.h b/wsproxy/ws_log.h new file mode 100644 index 0000000..2d309c0 --- /dev/null +++ b/wsproxy/ws_log.h @@ -0,0 +1,42 @@ +#ifndef WS_LOG_H__ +#define WS_LOG_H__ + + +enum e_ws_log_level { + eLOG_EMERG = 0 + ,eLOG_ERROR = 1 + ,eLOG_AUTH = 2 + ,eLOG_WARN = 3 + ,eLOG_NOTICE = 4 + ,eLOG_INFO = 5 + ,eLOG_DEBUG = 6 + +}; + +class ws_log +{ + public: + typedef std::map logger_type; + typedef std::map::const_iterator logger_const_iterator; + typedef std::map::iterator logger_iterator; + + inline void set_level(int _l) { m_level = _l;}; + + inline void set_syslog() {m_syslog = true; }; + + static ws_log *getInstance(const std::string &cls) ; + + void log(int level,const char *format,...); + + protected: + int m_level; + bool m_syslog; // Says if trace should be reported in syslog. + private: + ws_log(const std::string &name); + + ws_log(const ws_log &name); + static logger_type init_map(); + static logger_type m_loggers; + static bool m_syslog_open; +}; +#endif diff --git a/wsproxy/ws_plugin.h b/wsproxy/ws_plugin.h new file mode 100644 index 0000000..6d7268a --- /dev/null +++ b/wsproxy/ws_plugin.h @@ -0,0 +1,60 @@ +#ifndef WS_PLUGING_H__ +#define WS_PLUGING_H__ + +#define PLUGIN_OK 0 +#define PLUGIN_DECLINE -1 + +/** + * Defines the kind of hool the plugin is going to register + */ +#define PLUGIN_HANDLER_FILTER_IN 0 +#define PLUGIN_HANDLER_CONTENT 1 +#define PLUGIN_HANDLER_FILTER_OUT 2 + + +/** + * class ws_plugin + * + * \brief Model class to implement a plugin + * \author + */ +class ws_plugin { + public: + // + ws_plugin(const std::string &_name) : m_name(_name) {} ; + // + ws_plugin(const ws_plugin &o) {} ; + // + ~ws_plugin() {} ; + + // Functions called my the + virtual void register_hooks() = 0; + protected: + // Plugin name + std::string m_name; +}; + + +/** + * class ws_plugin_handler + * + * \brief + * \author + */ +class ws_plugin_handler { + public: + // + ws_plugin_handler() {} ; + // + ws_plugin_handler(const ws_plugin_handler &o) {} ; + // + ~ws_plugin_handler() {}; + + virtual int handle_request(ws_plugin_request &r) = 0; + protected: +}; + + +extern "C" void *create_plugin() ; + +#endif diff --git a/wsproxy/ws_plugin_manager.cpp b/wsproxy/ws_plugin_manager.cpp new file mode 100644 index 0000000..f37effc --- /dev/null +++ b/wsproxy/ws_plugin_manager.cpp @@ -0,0 +1,115 @@ +#include +#include +#include + +#include "ltdl.h" + +#include "ws_log.h" + +#include "ws_plugin_request.h" +#include "ws_plugin.h" +#include "ws_plugin_manager.h" + +// Local debug macro +#define LOG_PMGR_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0); +#define LOG_PMGR_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n" ,args);} while (0) + +#define LOG_PMGR_INFO(x) do {m_logger->log(eLOG_INFO,"%s\n",x);} while (0); +#define LOG_PMGR_INFO_FMT(format,args...) do { m_logger->log(eLOG_INFO,format "\n",args);} while (0) ; + +#define LOG_PMGR_NOTICE(x) do {m_logger->log(eLOG_NOTICE,"%s\n",x);} while (0); +#define LOG_PMGR_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args);} while (0) ; + +#define LOG_PMGR_WARN(x) do {m_logger->log(eLOG_WARN,"%s\n",x);} while (0); +#define LOG_PMGR_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args);} while (0) ; + +#define LOG_PMGR_ERROR(x) do {m_logger->log(eLOG_ERROR,"%s\n",x);} while (0); +#define LOG_PMGR_ERROR_FMT(format,args...) do {m_logger->log(eLOG_ERROR,format "\n",args);} while (0) ; + +static ws_log *m_logger = ws_log::getInstance("ws_plugin_manager"); + + +typedef void * (*fct)(); + +class ltdl_manager : public ws_plugin_manager +{ + public: + ltdl_manager(const std::string &s ) : ws_plugin_manager(s) + { + int res = lt_dlinit(); + LOG_PMGR_DEBUG_FMT("ltdl_manager::ltdl_manager init res=%d",res); + } + + ~ltdl_manager() { + lt_dlclose(m_Handle); + } + ws_plugin *load_plugin(const std::string &s) + { + m_Handle = lt_dlopenext(s.c_str()); + LOG_PMGR_DEBUG_FMT("ltdl_manager::load_plugin %s m_Handle=%x",s.c_str(),m_Handle); + if (m_Handle) + { + fct _create = reinterpret_cast(reinterpret_cast(lt_dlsym(m_Handle,"create_plugin"))); + if ( _create ) + { + ws_plugin *p = static_cast(_create()); + if (p) + { + m_Plugins[s] = p; + } + return p; + } else + { + LOG_PMGR_ERROR_FMT("ltdl_manager::load_plugin Failed create_function lt_dlerror=%s",lt_dlerror()); + std::cout<<"Failed load function create_plugin"< PluginsMap; + public: + static ws_plugin_manager *getInstance(const std::string &_dir) ; + protected: + // + ws_plugin_manager(const std::string &dir) ; + // + ws_plugin_manager(const ws_plugin_manager &o) ; + // + ~ws_plugin_manager() ; + + public: + /** + * load plugin whose name is given in parameter + * \param _name: plugin name. + */ + virtual ws_plugin* load_plugin(const std::string &_name) ; + + + protected: + std::string m_Dir; + // Local storage for loaded plugins + PluginsMap m_Plugins; + // Instance to store the Manager + static ws_plugin_manager *m_PluginManager; +}; + + +#endif /*WS_PLUGIN_MANAGER_H*/ diff --git a/wsproxy/ws_plugin_request.h b/wsproxy/ws_plugin_request.h new file mode 100644 index 0000000..8d8c699 --- /dev/null +++ b/wsproxy/ws_plugin_request.h @@ -0,0 +1,40 @@ +#ifndef WS_PLUGIN_REQUEST_H +#define WS_PLUGIN_REQUEST_H + + + +/** + * class ws_plugin_request + * + * \brief this class contains all information required to process an http + * request. Among the known information are : + * - the uri + * - the method (GET,INFO,PUT,DELETE,POST,... + * - header_input + * - header_output + * - content + * - connection on which the handler operated (needed for ssl management + * - The plugin handler requests by the server + * - add missing fields here. + * \author + */ +class ws_plugin_request { + public: + // could be improved. + typedef std::map header; + public: + // + ws_plugin_request() ; + // + ws_plugin_request(const ws_plugin_request &o) ; + // + ~ws_plugin_request() ; + protected: + header m_HeaderIn; + header m_HeaderOut; + // Need the native socket so that I can decide what kind of handler to attach to the request. + // m_Conn; /* read write handler .... */ +}; + + +#endif /*WS_PLUGIN_REQUEST_H*/ diff --git a/wsproxy/ws_proxy_endpoint.cpp b/wsproxy/ws_proxy_endpoint.cpp new file mode 100644 index 0000000..5b07b29 --- /dev/null +++ b/wsproxy/ws_proxy_endpoint.cpp @@ -0,0 +1,150 @@ +#include + +#include // for memset in linux +#include +#include +#include +#include + +#include + +#include +#if defined(__MINGW32__) +#include +#endif +using namespace aeb::net; +#include "ws_constant.h" +#include "ws_log.h" +#include "ws_frame.h" +#include "reverse_proxy.h" +#include "ws_proxy_endpoint.h" +#include "ws_proxy_handler.h" + +// Local debug macro +#define LOG_WSEP_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0) +#define LOG_WSEP_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n",args);} while (0) + +#define LOG_WSEP_NOTICE(x) do { m_logger->log(eLOG_NOTICE,"%s\n",x); } while (0) +#define LOG_WSEP_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args); printf(format "\n",args);} while (0) + +#define LOG_WSEP_WARN(x) do { m_logger->log(eLOG_WARN,"%s\n",x); } while (0) +#define LOG_WSEP_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args); printf(format "\n",args);} while (0) + +#define LOG_WSEP_ERROR(x) do { m_logger->log(eLOG_ERROR,"%s\n",x); } while (0) +#define LOG_WSEP_ERROR_FMT(format,args...) do { m_logger->log(eLOG_ERROR,format "\n",args); } while (0) + +static ws_log *m_logger = ws_log::getInstance("ws_proxy_endpoint"); + +using namespace aeb::net; +typedef aeb::net::io_dispatcher io_service; +typedef aeb::net::basic_socket tcp_socket; + +ws_proxy_endpoint::ws_proxy_endpoint(const std::string &ws_proxy,const std::string &host,unsigned int port) + : m_endpoint(host.c_str(),port), m_stream(),m_ref(0), event_handler(-1) +{ + LOG_WSEP_DEBUG_FMT("ws_proxy_endpoint::ws_proxy_endpoint host=%s port=%d",host.c_str(),port); +} + +ws_proxy_endpoint::ws_proxy_endpoint(const wsproxy::ReverseProxyType_skel::application_sptr &app,const handler_ptr &h) + : m_application(app) + , m_endpoint(app->attr_host().c_str(),app->attr_port()) + , m_stream() + , m_handler(h) + , m_ref(0), event_handler(-1) +{ + LOG_WSEP_DEBUG_FMT("ws_proxy_endpoint::ws_proxy_endpoint host=%s port=%d",app->attr_host().c_str(),app->attr_port()); +} + +ws_proxy_endpoint::~ws_proxy_endpoint() +{ + LOG_WSEP_DEBUG("ws_proxy_endpoint::~ws_proxy_endpoint"); + m_stream.close(); +} + +int ws_proxy_endpoint::open() +{ + LOG_WSEP_DEBUG("ws_proxy_endpoint::open"); + aeb::net::ip::tcp p = m_endpoint.protocol(); + + m_stream.open(p); + + m_stream.connect(m_endpoint); + + this->m_descriptor = m_stream.native(); + // Register to io_service + register_io(); + + return 0; +} + +void ws_proxy_endpoint::close() +{ + LOG_WSEP_DEBUG("ws_proxy_endpoint::close"); + + // Register to io_service + unregister_io(); +} + + + +int ws_proxy_endpoint::send(unsigned char *_b,size_t len) +{ + LOG_WSEP_DEBUG_FMT("ws_proxy_endpoint::send len=%d",len); + if ( ! m_stream.is_open()) + { + // Do not even try to send anything is the backend is closed + LOG_WSEP_ERROR("ws_proxy_endpoint::send bakend is closed try to ropen send"); + open(); + } + int l = m_stream.send((const char *)_b,len); + + if ( l != len) { + LOG_WSEP_ERROR_FMT("ws_proxy_endpoint::send failed: len=%d result=%d",len,l); + } + return l; +} + + +#define BUF_SIZE (1024*4) +void ws_proxy_endpoint::on_read() +{ + char buf[BUF_SIZE]; + LOG_WSEP_DEBUG("ws_proxy_endpoint::on_read"); + // write data back into proxy_handler (send_frame here, flow attribute is important) + memset(buf,0x00,BUF_SIZE); + int result = m_stream.receive(buf,BUF_SIZE); + if (result < 0 ) + { // An error ahs occured + LOG_WSEP_ERROR_FMT("ws_proxy_endpoint::on_read read %d disconnect %d",result,m_stream.native()); + } else if (result == 0) + { // The socket has been closed. Do the same on my side. + LOG_WSEP_WARN_FMT("ws_proxy_endpoint::on_read read 0 disconnect %d",m_stream.native()); + unregister_io(); + } else + { // Have got the data + if (! std::string("text").compare(m_application->attr_flow())) + { + //if (*m_handler) + m_handler->send_frame_text(buf); + } else + { + //if (*m_handler) + m_handler->send_frame_binary((unsigned char *)buf,result); + } + } +} + +void ws_proxy_endpoint::register_io() +{ + LOG_WSEP_DEBUG("ws_proxy_endpoint::register_io"); + io_service *io = aeb::singleton::get_instance(); + + io->register_handler(0,this); +} +void ws_proxy_endpoint::unregister_io() +{ + LOG_WSEP_DEBUG("ws_proxy_endpoint::unregister_io"); + io_service *io = aeb::singleton::get_instance(); + io->unregister_handler(0,this); + m_stream.close(); +} diff --git a/wsproxy/ws_proxy_endpoint.h b/wsproxy/ws_proxy_endpoint.h new file mode 100644 index 0000000..885835f --- /dev/null +++ b/wsproxy/ws_proxy_endpoint.h @@ -0,0 +1,62 @@ +#ifndef WS_PROXY_ENDPOINT_H +#define WS_PROXY_ENDPOINT_H + +// the endpoint needs to send data to the handler. +// the handler sends data to the endpoint +// an endpoint is a handler as well +class ws_proxy_handler; + +/** + * helper class to find the appropriate application in an array + */ +struct find_application { + find_application(const std::string &n) : m_name(n) {}; + bool operator ()(const wsproxy::ReverseProxyType_skel::application_sptr &s) { + return ! m_name.compare(s->attr_name()); + }; + std::string m_name; +}; + +/** + * + */ +class ws_proxy_endpoint : public event_handler +{ + private: + int m_ref; + public: + typedef aeb::intrusive_ptr ptr; + typedef aeb::intrusive_ptr handler_ptr; + + inline void add_ref() {m_ref++;}; + inline void release() {if (--m_ref == 0) delete this;}; + + public: + typedef aeb::net::ip::basic_endpoint endpoint_type; + typedef aeb::net::basic_socket_stream stream_type; + + ws_proxy_endpoint(const std::string &ws_protocol, + const std::string &host,unsigned int port); + + ws_proxy_endpoint(const wsproxy::ReverseProxyType_skel::application_sptr &app,const handler_ptr &); + + virtual ~ws_proxy_endpoint(); + // handler functions + void on_read() ; + void on_write() {}; + void on_accept() {} ; + // Possible actions.... + int open(); + void close(); + int send(unsigned char *,size_t len); + protected: + void unregister_io(); + void register_io(); + protected: + wsproxy::ReverseProxyType_skel::application_sptr m_application; + handler_ptr m_handler; + endpoint_type m_endpoint; + stream_type m_stream; +}; + +#endif diff --git a/wsproxy/ws_proxy_handler.cpp b/wsproxy/ws_proxy_handler.cpp new file mode 100644 index 0000000..783863c --- /dev/null +++ b/wsproxy/ws_proxy_handler.cpp @@ -0,0 +1,669 @@ +#include +#include +#include +#include +#include +#include // find_if +#include +#include +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#endif + +#include "time.h" +#include "reverse_proxy.h" +#include "ws_constant.h" +#include "ws_log.h" +#include "ws_frame.h" +#include "sha1.h" +#include "ws_base64.h" + +// Local debug macro +#define LOG_WSPH_DEBUG(x) do { m_logger->log(eLOG_DEBUG,"%s\n",x); } while (0); +#define LOG_WSPH_DEBUG_FMT(format,args...) do {m_logger->log(eLOG_DEBUG,format "\n" ,args);} while (0) + +#define LOG_WSPH_NOTICE(x) do {m_logger->log(eLOG_NOTICE,"%s\n",x);} while (0); +#define LOG_WSPH_NOTICE_FMT(format,args...) do { m_logger->log(eLOG_NOTICE,format "\n",args);} while (0) ; + +#define LOG_WSPH_WARN(x) do {m_logger->log(eLOG_WARN,"%s\n",x);} while (0); +#define LOG_WSPH_WARN_FMT(format,args...) do { m_logger->log(eLOG_WARN,format "\n",args);} while (0) ; + +#define LOG_WSPH_ERROR(x) do {m_logger->log(eLOG_ERROR,"%s\n",x);} while (0); +#define LOG_WSPH_ERROR_FMT(format,args...) do {m_logger->log(eLOG_ERROR,format "\n",args);} while (0) ; + +static ws_log *m_logger = ws_log::getInstance("ws_proxy_handler"); +using namespace aeb::net; +typedef aeb::net::io_dispatcher io_service; +#include "ws_proxy_endpoint.h" +#include "ws_proxy_handler.h" + +void proxy_state::onRead(ws_proxy_handler &p,const ws_frame &buffer) +{ + ws_frame frame; + + frame.decode((unsigned char *)buffer.data(),buffer.length()); + //frame.decode() + switch (frame.opcode()) + { + case ews_continue: + break; + case ews_text: + this->onTextFrame(p,frame); + break; + case ews_binary: + this->onBinaryFrame(p,frame); + break; + case ews_close: + LOG_WSPH_WARN_FMT("proxy_state::onRead unkown opcode close: %d , value=%d",frame.opcode(),*(short *)frame.data()); + this->onClose(p,ews_exit_normal); + break; + case ews_ping: + this->onPing(p); + break; + case ews_pong: + this->onPong(p); + break; + default: + LOG_WSPH_WARN_FMT("proxy_state::onRead unkown opcode : %d",frame.opcode()); + ; + } +} + +void proxy_state::doActivity(ws_proxy_handler &p) +{ + ; +} + +// +// CONNECTING +// +CONNECTING::CONNECTING() + : proxy_state() +{ +} + +CONNECTING * CONNECTING::m_Instance = NULL; + +CONNECTING * CONNECTING::getInstance() +{ + if (m_Instance == NULL) + m_Instance = new CONNECTING(); + return m_Instance; +} + +void CONNECTING::onRead(ws_proxy_handler &p,const ws_frame &buffer) +{ + std::istringstream stream((char *)buffer.data()); + char result[256]; + + LOG_WSPH_DEBUG("CONNECTING::onRead"); + + while( ! stream.getline(result,256).eof() ) { + int pos; + std::string s(result); + if ( (pos = s.find(SEC_WEBSOCKET_KEY)) != std::string::npos && (pos == 0)) { + p.set_header(std::string(SEC_WEBSOCKET_KEY),s.substr(s.find(":")+1)); + p.set_challenge(s.substr(s.find(":")+1)); + } + else if ( ((pos = s.find(WS_UPGRADE)) != std::string::npos ) && (pos == 0 )) { + p.set_header(WS_UPGRADE,s.substr(s.find(":")+1)); + } + else if ( ((pos = s.find(WS_ORIGIN)) != std::string::npos ) && (pos == 0 )) { + p.set_header(WS_ORIGIN,s.substr(s.find(":")+1)); + } + else if ( (pos = s.find(SEC_WEBSOCKET_VERSION)) != std::string::npos && (pos == 0)) { + p.set_header(SEC_WEBSOCKET_VERSION,s.substr(s.find(":")+1)); + } + else if ( (pos = s.find(WS_CONNECTION)) != std::string::npos) { + p.set_header(WS_CONNECTION,s.substr(s.find(":")+1)); + } + else if ( ((pos = s.find(SEC_WEBSOCKET_PROTOCOL)) != std::string::npos) && (pos == 0)) { + p.set_header(SEC_WEBSOCKET_PROTOCOL,s.substr(s.find(":")+1)); + } else { + LOG_WSPH_NOTICE_FMT("CONNECTING::onRead Unknown header: %s",s.c_str()); + } + + } + + p.send_response(101); + p.set_state(OPEN::getInstance()); + OPEN::getInstance()->doActivity(p); +} + +void CONNECTING::onClose(ws_proxy_handler &p,unsigned short _code) +{ +} + +void CONNECTING::onPing(ws_proxy_handler &p) +{ +} + +void CONNECTING::onPong(ws_proxy_handler &p) +{ +} + +void CONNECTING::onBinaryFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void CONNECTING::onTextFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void CONNECTING::doActivity(ws_proxy_handler &p) +{ + ; +} + + +//OPEN +OPEN::OPEN() +{ +} + +OPEN * OPEN::m_Instance = NULL; + +OPEN * OPEN::getInstance() +{ + if (m_Instance == NULL) + m_Instance = new OPEN(); + return m_Instance; +} + +void OPEN::onRead(ws_proxy_handler &p,const ws_frame &buffer) +{ + LOG_WSPH_DEBUG("OPEN::onRead"); + proxy_state::onRead(p,buffer); +} + +void OPEN::onClose(ws_proxy_handler &p,unsigned short _code) +{ + std::string s; + LOG_WSPH_DEBUG("OPEN::onClose"); + + //p.send_close(ews_exit_normal,s); + p.send_close(_code,s); + + p.set_state(CLOSING::getInstance()); + CLOSING::getInstance()->doActivity(p); +} + +void OPEN::onPing(ws_proxy_handler &p) +{ +} + +void OPEN::onPong(ws_proxy_handler &p) +{ +} + +void OPEN::onBinaryFrame(ws_proxy_handler &p,const ws_frame &f) +{ + LOG_WSPH_DEBUG_FMT("OPEN::onBinaryFrame got (%s)",f.data()); +} + +void OPEN::onTextFrame(ws_proxy_handler &p,const ws_frame &f) +{ + std::string proto; + LOG_WSPH_DEBUG_FMT("OPEN::onTextFrame got (%s)",f.data()); + if ( ! p.protocol(proto)) { + p.send_frame_text(std::string("Hello")); + } else { + p.send_backend(f); + } +} + +void OPEN::doActivity(ws_proxy_handler &p) +{ + // Try to connect to endpoint, ... + std::string proto; + wsproxy::ReverseProxyType_skel::application_sequence::iterator it; + wsproxy::ReverseProxyType_skel::application_sptr appli; + if (p.protocol(proto)) + { + if (p.support_application(proto,appli)) { + LOG_WSPH_DEBUG_FMT("OPEN::doActivity found protocol %s open host %s port %ld",proto.c_str(),appli->attr_host().c_str(),appli->attr_port()); + if (appli->attr_max_frame_size() > 0) { + long m_max_frame_size = appli->attr_max_frame_size(); + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::support_application max_frame_size =%d",m_max_frame_size); + } + p.open_backend(appli); + } else + { + LOG_WSPH_WARN_FMT("OPEN::doActivity Given protocol unsupported %s",proto.c_str()); + } + } else { + LOG_WSPH_DEBUG_FMT("OPEN::doActivity (%s)",proto.c_str()); + } +} + + +// +// CLOSING +// +CLOSING::CLOSING() +{ +} + +CLOSING * CLOSING::m_Instance = NULL; + +CLOSING * CLOSING::getInstance() +{ + if (m_Instance == NULL) + m_Instance = new CLOSING(); + return m_Instance; +} + +void CLOSING::onRead(ws_proxy_handler &p,const ws_frame &buffer) +{ + LOG_WSPH_DEBUG("CLOSING::onRead"); + proxy_state::onRead(p,buffer); +} + +void CLOSING::onClose(ws_proxy_handler &p,unsigned short _code) +{ + LOG_WSPH_DEBUG("CLOSING::onClose"); + p.set_state(CLOSED::getInstance()); + CLOSED::getInstance()->doActivity(p); +} + +void CLOSING::onPing(ws_proxy_handler &p) +{ +} + +void CLOSING::onPong(ws_proxy_handler &p) +{ +} + +void CLOSING::onBinaryFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void CLOSING::onTextFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void CLOSING::doActivity(ws_proxy_handler &p) +{ + std::string s(""); + LOG_WSPH_DEBUG("CLOSING::doActivity"); + p.close(); + p.set_state(CLOSED::getInstance()); +} + + +// +// CLOSED +// +CLOSED::CLOSED() +{ +} + +CLOSED * CLOSED::m_Instance = NULL; + +CLOSED * CLOSED::getInstance() +{ + if (m_Instance == NULL) + m_Instance = new CLOSED(); + return m_Instance; +} + +void CLOSED::onRead(ws_proxy_handler &p,const ws_frame &buffer) +{ + proxy_state::onRead(p,buffer); +} + +void CLOSED::onClose(ws_proxy_handler &p,unsigned short _code) +{ + LOG_WSPH_DEBUG("CLOSED::onClose"); + doActivity(p); +} + +void CLOSED::onPing(ws_proxy_handler &p) +{ +} + +void CLOSED::onPong(ws_proxy_handler &p) +{ +} + +void CLOSED::onBinaryFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void CLOSED::onTextFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void CLOSED::doActivity(ws_proxy_handler &p) +{ + LOG_WSPH_DEBUG("CLOSED::doActivity"); + LOG_WSPH_DEBUG("------------------\n"); + p.close(); + p.io_unregister(); + // Here was release +} + + +// +// Proxy Handler implementation +// +ws_proxy_handler::ws_proxy_handler(detail::socket_type s,const wsproxy::ReverseProxyType_skel::application_sequence &apps) +//ws_proxy_handler::ws_proxy_handler(detail::socket_type s) + : event_handler(s), m_ref(0), m_stream(s),m_state(NULL) + ,m_applications(apps) + ,m_max_frame_size(1024*4) +{ + LOG_WSPH_DEBUG("ws_proxy_handler::ws_proxy_handler"); + io_service *io = aeb::singleton::get_instance(); + io->register_handler(0,this); + set_state(CONNECTING::getInstance()); +} + +ws_proxy_handler::~ws_proxy_handler() +{ + LOG_WSPH_DEBUG("ws_proxy_handler::~ws_proxy_handler"); + io_unregister(); +} + +// +void ws_proxy_handler::io_register() +{ + LOG_WSPH_DEBUG("ws_proxy_handler::io_register "); + io_service *io = aeb::singleton::get_instance(); + io->register_handler(0,this); +} + +// +void ws_proxy_handler::io_unregister() +{ + LOG_WSPH_DEBUG("ws_proxy_handler::io_unregister "); + io_service *io = aeb::singleton::get_instance(); + io->unregister_handler(0,this); + m_stream.close(); +} + +bool ws_proxy_handler::available () const +{ + LOG_WSPH_DEBUG("ws_proxy_handler::available"); + return ! m_stream.is_open(); +} + +// reuse handler with a new socket +void ws_proxy_handler::reuse(aeb::net::detail::socket_type s) +{ + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::reuse handler with s=%d",s); + aeb::net::ip::tcp p = m_stream.local_endpoint().protocol(); + m_stream.assign(p,s); + + set_state(CONNECTING::getInstance()); + + io_register(); +} + +void +ws_proxy_handler::on_write() +{ +} + +#define BS (1024*4) +void +ws_proxy_handler::on_read() +{ + char buf[BS]; + memset(buf,0x00,BS); + + int res = m_stream.receive(buf,BS); + if ( res== 0) + { + LOG_WSPH_DEBUG("ws_proxy_handler::on_read read 0 close"); + if (m_state) { + onClose(ews_exit_normal); + //m_state->onClose(*this); + //m_state->doActivity(*this); + // release(); + } + } else + { + ws_frame frame((unsigned char *)buf,res); + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::on_read size=%d dispatch",res); + if (m_state) + m_state->onRead(*this,frame); + } +} + +void ws_proxy_handler::on_accept() +{ +} + +void ws_proxy_handler::onRead(ws_proxy_handler &p,const ws_frame &buffer) +{ +} + +void ws_proxy_handler::onClose(unsigned short _code) +{ + if (m_state) + m_state->onClose(*this,_code); +} + +void ws_proxy_handler::onPing(ws_proxy_handler &p) +{ +} + +void ws_proxy_handler::onPong(ws_proxy_handler &p) +{ +} + +void ws_proxy_handler::onBinaryFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void ws_proxy_handler::onTextFrame(ws_proxy_handler &p,const ws_frame &f) +{ +} + +void +ws_proxy_handler::set_header(const std::string &key,const std::string &v) +{ + std::string s(v.substr(1,v.size()-2)); + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::set_header key=%s Value(%s)",key.c_str(),s.c_str()); + m_header[key] = s; +} + +void +ws_proxy_handler::set_challenge(const std::string &v) +{ + unsigned char sh[60]; + char hexstr[60]; + std::string complete; + std::string vs(v.substr(1,v.size()-2)); + std::string result; + // That's important because you never now the content of uninitialized arrays. Sometimes it works + // some times it does not + memset(sh,0x00,60); + memset(hexstr,0x00,60); + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::set_challenge String(%s)",vs.c_str()); + complete = vs + WS_HANDSHACK; + + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::set_challenge complete<%s>",complete.c_str()); + sha1::calc(complete.c_str(),complete.size(),sh); + sha1::toHexString(sh,hexstr); + + encode_base64((const unsigned char *)sh,20,result); + m_header[std::string(SEC_WEBSOCKET_ACCEPT)] = result; + LOG_WSPH_NOTICE_FMT("ws_proxy_handler::set_challenge String(%s) Hash=<%s>",vs.c_str(),result.c_str()); +} + + +void ws_proxy_handler::send_response(int status) +{ + std::ostringstream os; + std::string response; + char date[125]; + time_t t; + struct tm *tmp; + t = time(NULL); + tmp = localtime(&t); + os<<"HTTP/1.1 "; + switch (status) + { + case 101: + { + os<<"101 Web Socket Protocol Handshake\r\n"; + std::map::iterator it = m_header.find(SEC_WEBSOCKET_PROTOCOL); + if (it != m_header.end()) + os<second.substr(0,it->second.size()-1)<<"\r\n"; + +#endif + //os< size=%lu)",s.c_str(),s.size()); + + ws_frame frame; + if (! frame.encode((unsigned const char *)s.c_str(),s.size(),ews_text) ) { + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::send_frame_binary send buffer length %d",frame.buffer_length()); + m_stream.send((const char *)frame.buffer(),frame.buffer_length()); + } else { + LOG_WSPH_ERROR("ws_proxy_handler::send_frame_binary Failed encode frame"); + + } +} + + +void +ws_proxy_handler::send_frame_binary(const unsigned char *_b,int len) +{ + ws_frame frame; + if (! frame.encode(_b,len) ) { + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::send_frame_binary send buffer length %d",frame.buffer_length()); + m_stream.send((const char *)frame.buffer(),frame.buffer_length()); + } else { + LOG_WSPH_ERROR("ws_proxy_handler::send_frame_binary Failed encode frame"); + + } +} + +void ws_proxy_handler::send_close(unsigned short code,const std::string &cause) +{ + char buffer[10]; + memset(buffer,0x00,10); + ws_header *h = (ws_header *)buffer; + unsigned char *len = (unsigned char *)&buffer[1]; + unsigned short *c = (unsigned short *)&buffer[2]; + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::send_close(%d)",code); + + *len = 2; +#if 0 + *c = htons(code); +#else + *c = code; +#endif + h->opcode = ews_close; + h->fin = 1; + m_stream.send(buffer,4); +} + +// +void +ws_proxy_handler::close() +{ + LOG_WSPH_DEBUG_FMT("ws_proxy_handler::close(%d)",m_stream.native()); + m_stream.shutdown(SHUT_WR); + + if ( ! ! m_backend) + { + ws_proxy_endpoint::ptr release_backend(NULL); + LOG_WSPH_DEBUG("ws_proxy_handler::close backend"); + m_backend->close(); + m_backend = release_backend; + } +} + + +bool ws_proxy_handler::support_application(const std::string n,wsproxy::ReverseProxyType_skel::application_sptr &app) const +{ + // Try to connect to endpoint, ... + wsproxy::ReverseProxyType_skel::application_sequence::const_iterator it; + it = std::find_if(m_applications.begin(),m_applications.end(),find_application(n)); + if (it != m_applications.end()) { + app = *it; + return true; + } + return false; +} + +/** + * Tells if ws_protocol was set. If so, set s to the selected protocol + */ +bool ws_proxy_handler::protocol(std::string &s) +{ + std::map::iterator it = m_header.find(SEC_WEBSOCKET_PROTOCOL); + if (it != m_header.end()) + { + s = m_header[SEC_WEBSOCKET_PROTOCOL]; + return true; + } else + { + return false; + } +} + + +void ws_proxy_handler::open_backend(const wsproxy::ReverseProxyType_skel::application_sptr &app) +{ + ptr p(this); // I have a problem with this. + + m_backend = ws_proxy_endpoint::ptr( new ws_proxy_endpoint(app,p)); + m_backend->open(); +} + +void ws_proxy_handler::send_backend(const ws_frame &f) +{ + if ( ! m_backend ) { + LOG_WSPH_ERROR("ws_proxy_handler::send_backend not available reconnect?"); +#if 0 + ptr p(this); // I have a problem with this. + + m_backend = ws_proxy_endpoint::ptr( new ws_proxy_endpoint(app,p)); + m_backend->open(); +#endif + } else + m_backend->send(f.data(),f.length()); +} diff --git a/wsproxy/ws_proxy_handler.h b/wsproxy/ws_proxy_handler.h new file mode 100644 index 0000000..76d549d --- /dev/null +++ b/wsproxy/ws_proxy_handler.h @@ -0,0 +1,180 @@ +#ifndef WS_PROXY_HANDLER +#define WS_PROXY_HANDLER + + +class ws_proxy_handler; + +class proxy_state { + public: + proxy_state() {}; + // Depending on state data from socket have a different meaning + virtual void onRead(ws_proxy_handler &p,const ws_frame &buffer); + // has a meaning when state is OPEN + virtual void onClose(ws_proxy_handler &p,unsigned short _code) {std::cout<<"proxy_state::onClose"< +{ + private: + int m_ref; + public: + typedef aeb::intrusive_ptr ptr; + + inline void add_ref() {m_ref++;}; + inline void release() { +#if 0 + std::cout<<"ws_proy_handler::release ref="< m_stream; + std::map m_header; + ws_proxy_endpoint::ptr m_backend; + wsproxy::ReverseProxyType_skel::application_sequence m_applications; + long m_max_frame_size; +}; + +/** +vim:et:sw=2:ts=2 + */ +#endif diff --git a/xml-transform b/xml-transform new file mode 160000 index 0000000..9bb0eda --- /dev/null +++ b/xml-transform @@ -0,0 +1 @@ +Subproject commit 9bb0eda673f41d6e5125c1606e69f77ec1198198 -- 2.30.2