diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b5cc2de --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ccdce42 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb new file mode 100644 index 0000000..841e6b6 --- /dev/null +++ b/.idea/sonarlint/issuestore/index.pb @@ -0,0 +1,3 @@ + +9 + README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d \ No newline at end of file diff --git a/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/index.pb b/.idea/sonarlint/securityhotspotstore/index.pb new file mode 100644 index 0000000..841e6b6 --- /dev/null +++ b/.idea/sonarlint/securityhotspotstore/index.pb @@ -0,0 +1,3 @@ + +9 + README.md,8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/webssh.iml b/.idea/webssh.iml new file mode 100644 index 0000000..53ff249 --- /dev/null +++ b/.idea/webssh.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..3e7f579 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + { + "associatedIndex": 7 +} + + + + + + + + + + + + + + + + + + + + + + + 1696093060393 + + + + + + + + + + \ No newline at end of file diff --git a/webssh/handler.py b/webssh/handler.py index 6cfc822..0d8677c 100644 --- a/webssh/handler.py +++ b/webssh/handler.py @@ -6,6 +6,7 @@ import struct import traceback import weakref import paramiko +import socks import tornado.web from concurrent.futures import ThreadPoolExecutor @@ -29,7 +30,6 @@ try: except ImportError: from urlparse import urlparse - DEFAULT_PORT = 22 swallow_http_errors = True @@ -101,7 +101,6 @@ class SSHClient(paramiko.SSHClient): class PrivateKey(object): - max_length = 16384 # rough number tag_to_name = { @@ -143,7 +142,7 @@ class PrivateKey(object): logging.debug('Reset offset to {}.'.format(offset)) logging.debug('Try parsing it as {} type key'.format(name)) - pkeycls = getattr(paramiko, name+'Key') + pkeycls = getattr(paramiko, name + 'Key') pkey = None try: @@ -179,12 +178,11 @@ class PrivateKey(object): msg = 'Invalid key' if self.password: msg += ' or wrong passphrase "{}" for decrypting it.'.format( - self.password) + self.password) raise InvalidValueError(msg) class MixinHandler(object): - custom_headers = { 'Server': 'TornadoServer' } @@ -313,8 +311,7 @@ class NotFoundHandler(MixinHandler, tornado.web.ErrorHandler): class IndexHandler(MixinHandler, tornado.web.RequestHandler): - - executor = ThreadPoolExecutor(max_workers=cpu_count()*5) + executor = ThreadPoolExecutor(max_workers=cpu_count() * 5) def initialize(self, loop, policy, host_keys_settings): super(IndexHandler, self).initialize(loop) @@ -383,9 +380,9 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler): if self.ssh_client._system_host_keys.lookup(key) is None: if self.ssh_client._host_keys.lookup(key) is None: raise tornado.web.HTTPError( - 403, 'Connection to {}:{} is not allowed.'.format( - hostname, port) - ) + 403, 'Connection to {}:{} is not allowed.'.format( + hostname, port) + ) def get_args(self): hostname = self.get_hostname() @@ -396,6 +393,19 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler): passphrase = self.get_argument('passphrase', u'') totp = self.get_argument('totp', u'') + # proxyType, proxyIp, proxyPort, proxyRdns = true, proxyUser, proxyPass + + proxyType = self.get_argument('proxytype', None) + if proxyType is not None and proxyType != '': + proxyType = int(proxyType) + proxyIp = self.get_argument('proxyip', u'') + proxyPort = self.get_argument('proxyport', None) + if proxyPort is not None and proxyPort != '': + proxyPort = int(proxyPort) + proxyRdns = self.get_argument('proxyrdns', u'') + proxyUser = self.get_argument('proxyuser', u'') + proxyPass = self.get_argument('proxypass', u'') + if isinstance(self.policy, paramiko.RejectPolicy): self.lookup_hostname(hostname, port) @@ -405,7 +415,8 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler): pkey = None self.ssh_client.totp = totp - args = (hostname, port, username, password, pkey) + args = ( + hostname, port, username, password, pkey, proxyType, proxyIp, proxyPort, proxyRdns, proxyUser, proxyPass) logging.debug(args) return args @@ -446,13 +457,33 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler): logging.warning('Could not detect the default encoding.') return 'utf-8' + def http_proxy_tunnel_connect(self, proxy, target, timeout=None): + logging.info('Connecting to proxy {}'.format(proxy)) + logging.info('Connecting to target {}'.format(target)) + sock = socks.socksocket() + sock.set_proxy(*proxy) + sock.connect(target) + sock.settimeout(timeout) + return sock + def ssh_connect(self, args): ssh = self.ssh_client dst_addr = args[:2] + logging.info(args) logging.info('Connecting to {}:{}'.format(*dst_addr)) + sock = None + if len(args) > 5: + if args[6] is not None and args[6] != '': + # 从args中获取代理信息 + sock = self.http_proxy_tunnel_connect( + proxy=args[5:], + target=dst_addr, + timeout=5000 + ) + try: - ssh.connect(*args, timeout=options.timeout) + ssh.connect(*args[:5], timeout=options.timeout, sock=sock) except socket.error: raise ValueError('Unable to connect to {}:{}'.format(*dst_addr)) except paramiko.BadAuthenticationType: @@ -503,6 +534,7 @@ class IndexHandler(MixinHandler, tornado.web.RequestHandler): self.check_origin() + logging.info('Connected from {}'.format(self.get_args())) try: args = self.get_args() except InvalidValueError as exc: diff --git a/webssh/static/js/main.js b/webssh/static/js/main.js index 6f79643..4228acc 100644 --- a/webssh/static/js/main.js +++ b/webssh/static/js/main.js @@ -4,856 +4,847 @@ var jQuery; var wssh = {}; -(function() { - // For FormData without getter and setter - var proto = FormData.prototype, - data = {}; +(function () { + // For FormData without getter and setter + var proto = FormData.prototype, data = {}; - if (!proto.get) { - proto.get = function (name) { - if (data[name] === undefined) { - var input = document.querySelector('input[name="' + name + '"]'), - value; - if (input) { - if (input.type === 'file') { - value = input.files[0]; - } else { - value = input.value; - } - data[name] = value; - } - } - return data[name]; - }; - } + if (!proto.get) { + proto.get = function (name) { + if (data[name] === undefined) { + var input = document.querySelector('input[name="' + name + '"]'), value; + if (input) { + if (input.type === 'file') { + value = input.files[0]; + } else { + value = input.value; + } + data[name] = value; + } + } + return data[name]; + }; + } - if (!proto.set) { - proto.set = function (name, value) { - data[name] = value; - }; - } + if (!proto.set) { + proto.set = function (name, value) { + data[name] = value; + }; + } }()); -jQuery(function($){ - var status = $('#status'), - button = $('.btn-primary'), - form_container = $('.form-container'), - waiter = $('#waiter'), - term_type = $('#term'), - style = {}, - default_title = 'WebSSH', - title_element = document.querySelector('title'), - form_id = '#connect', - debug = document.querySelector(form_id).noValidate, - custom_font = document.fonts ? document.fonts.values().next().value : undefined, - default_fonts, - DISCONNECTED = 0, - CONNECTING = 1, - CONNECTED = 2, - state = DISCONNECTED, - messages = {1: 'This client is connecting ...', 2: 'This client is already connnected.'}, - key_max_size = 16384, - fields = ['hostname', 'port', 'username'], - form_keys = fields.concat(['password', 'totp']), - opts_keys = ['bgcolor', 'title', 'encoding', 'command', 'term', 'fontsize', 'fontcolor', 'cursor'], - url_form_data = {}, - url_opts_data = {}, - validated_form_data, - event_origin, - hostname_tester = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/; +jQuery(function ($) { + var status = $('#status'), button = $('.btn-primary'), form_container = $('.form-container'), waiter = $('#waiter'), + term_type = $('#term'), style = {}, default_title = 'WebSSH', title_element = document.querySelector('title'), + form_id = '#connect', debug = document.querySelector(form_id).noValidate, + custom_font = document.fonts ? document.fonts.values().next().value : undefined, default_fonts, + DISCONNECTED = 0, CONNECTING = 1, CONNECTED = 2, state = DISCONNECTED, + messages = {1: 'This client is connecting ...', 2: 'This client is already connnected.'}, key_max_size = 16384, + fields = ['hostname', 'port', 'username'], + form_keys = fields.concat(['password', 'totp', 'proxytype', 'proxyip', 'proxyport', 'proxyrdns', 'proxyuser', 'proxypass']), + opts_keys = ['bgcolor', 'title', 'encoding', 'command', 'term', 'fontsize', 'fontcolor', 'cursor'], + url_form_data = {}, url_opts_data = {}, validated_form_data, event_origin, + hostname_tester = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/; - function store_items(names, data) { - var i, name, value; + function store_items(names, data) { + var i, name, value; - for (i = 0; i < names.length; i++) { - name = names[i]; - value = data.get(name); - if (value){ - window.localStorage.setItem(name, value); - } - } - } - - - function restore_items(names) { - var i, name, value; - - for (i=0; i < names.length; i++) { - name = names[i]; - value = window.localStorage.getItem(name); - if (value) { - $('#'+name).val(value); - } - } - } - - - function populate_form(data) { - var names = form_keys.concat(['passphrase']), - i, name; - - for (i=0; i < names.length; i++) { - name = names[i]; - $('#'+name).val(data.get(name)); - } - } - - - function get_object_length(object) { - return Object.keys(object).length; - } - - - function decode_uri_component(uri) { - try { - return decodeURIComponent(uri); - } catch(e) { - console.error(e); - } - return ''; - } - - - function decode_password(encoded) { - try { - return window.atob(encoded); - } catch (e) { - console.error(e); - } - return null; - } - - - function parse_url_data(string, form_keys, opts_keys, form_map, opts_map) { - var i, pair, key, val, - arr = string.split('&'); - - for (i = 0; i < arr.length; i++) { - pair = arr[i].split('='); - key = pair[0].trim().toLowerCase(); - val = pair.slice(1).join('=').trim(); - - if (form_keys.indexOf(key) >= 0) { - form_map[key] = val; - } else if (opts_keys.indexOf(key) >=0) { - opts_map[key] = val; - } - } - - if (form_map.password) { - form_map.password = decode_password(form_map.password); - } - } - - - function parse_xterm_style() { - var text = $('.xterm-helpers style').text(); - var arr = text.split('xterm-normal-char{width:'); - style.width = parseFloat(arr[1]); - arr = text.split('div{height:'); - style.height = parseFloat(arr[1]); - } - - - function get_cell_size(term) { - style.width = term._core._renderService._renderer.dimensions.actualCellWidth; - style.height = term._core._renderService._renderer.dimensions.actualCellHeight; - } - - - function toggle_fullscreen(term) { - $('#terminal .terminal').toggleClass('fullscreen'); - term.fitAddon.fit(); - } - - - function current_geometry(term) { - if (!style.width || !style.height) { - try { - get_cell_size(term); - } catch (TypeError) { - parse_xterm_style(); - } - } - - var cols = parseInt(window.innerWidth / style.width, 10) - 1; - var rows = parseInt(window.innerHeight / style.height, 10); - return {'cols': cols, 'rows': rows}; - } - - - function resize_terminal(term) { - var geometry = current_geometry(term); - term.on_resize(geometry.cols, geometry.rows); - } - - - function set_backgound_color(term, color) { - term.setOption('theme', { - background: color - }); - } - - function set_font_color(term, color) { - term.setOption('theme', { - foreground: color - }); - } - - function custom_font_is_loaded() { - if (!custom_font) { - console.log('No custom font specified.'); - } else { - console.log('Status of custom font ' + custom_font.family + ': ' + custom_font.status); - if (custom_font.status === 'loaded') { - return true; - } - if (custom_font.status === 'unloaded') { - return false; - } - } - } - - function update_font_family(term) { - if (term.font_family_updated) { - console.log('Already using custom font family'); - return; - } - - if (!default_fonts) { - default_fonts = term.getOption('fontFamily'); - } - - if (custom_font_is_loaded()) { - var new_fonts = custom_font.family + ', ' + default_fonts; - term.setOption('fontFamily', new_fonts); - term.font_family_updated = true; - console.log('Using custom font family ' + new_fonts); - } - } - - - function reset_font_family(term) { - if (!term.font_family_updated) { - console.log('Already using default font family'); - return; - } - - if (default_fonts) { - term.setOption('fontFamily', default_fonts); - term.font_family_updated = false; - console.log('Using default font family ' + default_fonts); - } - } - - - function format_geometry(cols, rows) { - return JSON.stringify({'cols': cols, 'rows': rows}); - } - - - function read_as_text_with_decoder(file, callback, decoder) { - var reader = new window.FileReader(); - - if (decoder === undefined) { - decoder = new window.TextDecoder('utf-8', {'fatal': true}); - } - - reader.onload = function() { - var text; - try { - text = decoder.decode(reader.result); - } catch (TypeError) { - console.log('Decoding error happened.'); - } finally { - if (callback) { - callback(text); + for (i = 0; i < names.length; i++) { + name = names[i]; + value = data.get(name); + if (value) { + window.localStorage.setItem(name, value); + } } - } - }; - - reader.onerror = function (e) { - console.error(e); - }; - - reader.readAsArrayBuffer(file); - } - - - function read_as_text_with_encoding(file, callback, encoding) { - var reader = new window.FileReader(); - - if (encoding === undefined) { - encoding = 'utf-8'; } - reader.onload = function() { - if (callback) { - callback(reader.result); - } - }; - reader.onerror = function (e) { - console.error(e); - }; + function restore_items(names) { + var i, name, value; - reader.readAsText(file, encoding); - } - - - function read_file_as_text(file, callback, decoder) { - if (!window.TextDecoder) { - read_as_text_with_encoding(file, callback, decoder); - } else { - read_as_text_with_decoder(file, callback, decoder); - } - } - - - function reset_wssh() { - var name; - - for (name in wssh) { - if (wssh.hasOwnProperty(name) && name !== 'connect') { - delete wssh[name]; - } - } - } - - - function log_status(text, to_populate) { - console.log(text); - status.html(text.split('\n').join('
')); - - if (to_populate && validated_form_data) { - populate_form(validated_form_data); - validated_form_data = undefined; - } - - if (waiter.css('display') !== 'none') { - waiter.hide(); - } - - if (form_container.css('display') === 'none') { - form_container.show(); - } - } - - - function ajax_complete_callback(resp) { - button.prop('disabled', false); - - if (resp.status !== 200) { - log_status(resp.status + ': ' + resp.statusText, true); - state = DISCONNECTED; - return; - } - - var msg = resp.responseJSON; - if (!msg.id) { - log_status(msg.status, true); - state = DISCONNECTED; - return; - } - - var ws_url = window.location.href.split(/\?|#/, 1)[0].replace('http', 'ws'), - join = (ws_url[ws_url.length-1] === '/' ? '' : '/'), - url = ws_url + join + 'ws?id=' + msg.id, - sock = new window.WebSocket(url), - encoding = 'utf-8', - decoder = window.TextDecoder ? new window.TextDecoder(encoding) : encoding, - terminal = document.getElementById('terminal'), - termOptions = { - cursorBlink: true, - theme: { - background: url_opts_data.bgcolor || 'black', - foreground: url_opts_data.fontcolor || 'white', - cursor: url_opts_data.cursor || url_opts_data.fontcolor || 'white' - } - }; - - if (url_opts_data.fontsize) { - var fontsize = window.parseInt(url_opts_data.fontsize); - if (fontsize && fontsize > 0) { - termOptions.fontSize = fontsize; - } - } - - var term = new window.Terminal(termOptions); - - term.fitAddon = new window.FitAddon.FitAddon(); - term.loadAddon(term.fitAddon); - - console.log(url); - if (!msg.encoding) { - console.log('Unable to detect the default encoding of your server'); - msg.encoding = encoding; - } else { - console.log('The deault encoding of your server is ' + msg.encoding); - } - - function term_write(text) { - if (term) { - term.write(text); - if (!term.resized) { - resize_terminal(term); - term.resized = true; + for (i = 0; i < names.length; i++) { + name = names[i]; + value = window.localStorage.getItem(name); + if (value) { + $('#' + name).val(value); + } } - } } - function set_encoding(new_encoding) { - // for console use - if (!new_encoding) { - console.log('An encoding is required'); - return; - } - if (!window.TextDecoder) { - decoder = new_encoding; - encoding = decoder; - console.log('Set encoding to ' + encoding); - } else { + function populate_form(data) { + var names = form_keys.concat(['passphrase']), i, name; + + for (i = 0; i < names.length; i++) { + name = names[i]; + $('#' + name).val(data.get(name)); + } + } + + + function get_object_length(object) { + return Object.keys(object).length; + } + + + function decode_uri_component(uri) { try { - decoder = new window.TextDecoder(new_encoding); - encoding = decoder.encoding; - console.log('Set encoding to ' + encoding); - } catch (RangeError) { - console.log('Unknown encoding ' + new_encoding); - return false; + return decodeURIComponent(uri); + } catch (e) { + console.error(e); } - } - } - - wssh.set_encoding = set_encoding; - - if (url_opts_data.encoding) { - if (set_encoding(url_opts_data.encoding) === false) { - set_encoding(msg.encoding); - } - } else { - set_encoding(msg.encoding); + return ''; } - wssh.geometry = function() { - // for console use - var geometry = current_geometry(term); - console.log('Current window geometry: ' + JSON.stringify(geometry)); - }; + function decode_password(encoded) { + try { + return window.atob(encoded); + } catch (e) { + console.error(e); + } + return null; + } - wssh.send = function(data) { - // for console use - if (!sock) { - console.log('Websocket was already closed'); - return; - } - if (typeof data !== 'string') { - console.log('Only string is allowed'); - return; - } + function parse_url_data(string, form_keys, opts_keys, form_map, opts_map) { + var i, pair, key, val, arr = string.split('&'); - try { - JSON.parse(data); - sock.send(data); - } catch (SyntaxError) { - data = data.trim() + '\r'; - sock.send(JSON.stringify({'data': data})); - } - }; + for (i = 0; i < arr.length; i++) { + pair = arr[i].split('='); + key = pair[0].trim().toLowerCase(); + val = pair.slice(1).join('=').trim(); - wssh.reset_encoding = function() { - // for console use - if (encoding === msg.encoding) { - console.log('Already reset to ' + msg.encoding); - } else { - set_encoding(msg.encoding); - } - }; + if (form_keys.indexOf(key) >= 0) { + form_map[key] = val; + } else if (opts_keys.indexOf(key) >= 0) { + console.log(key, val) + opts_map[key] = val; + } + } - wssh.resize = function(cols, rows) { - // for console use - if (term === undefined) { - console.log('Terminal was already destroryed'); - return; - } + if (form_map.password) { + form_map.password = decode_password(form_map.password); + } + } - var valid_args = false; - if (cols > 0 && rows > 0) { + function parse_xterm_style() { + var text = $('.xterm-helpers style').text(); + var arr = text.split('xterm-normal-char{width:'); + style.width = parseFloat(arr[1]); + arr = text.split('div{height:'); + style.height = parseFloat(arr[1]); + } + + + function get_cell_size(term) { + style.width = term._core._renderService._renderer.dimensions.actualCellWidth; + style.height = term._core._renderService._renderer.dimensions.actualCellHeight; + } + + + function toggle_fullscreen(term) { + $('#terminal .terminal').toggleClass('fullscreen'); + term.fitAddon.fit(); + } + + + function current_geometry(term) { + if (!style.width || !style.height) { + try { + get_cell_size(term); + } catch (TypeError) { + parse_xterm_style(); + } + } + + var cols = parseInt(window.innerWidth / style.width, 10) - 1; + var rows = parseInt(window.innerHeight / style.height, 10); + return {'cols': cols, 'rows': rows}; + } + + + function resize_terminal(term) { var geometry = current_geometry(term); - if (cols <= geometry.cols && rows <= geometry.rows) { - valid_args = true; - } - } - - if (!valid_args) { - console.log('Unable to resize terminal to geometry: ' + format_geometry(cols, rows)); - } else { - term.on_resize(cols, rows); - } - }; - - wssh.set_bgcolor = function(color) { - set_backgound_color(term, color); - }; - - wssh.set_fontcolor = function(color) { - set_font_color(term, color); - }; - - wssh.custom_font = function() { - update_font_family(term); - }; - - wssh.default_font = function() { - reset_font_family(term); - }; - - term.on_resize = function(cols, rows) { - if (cols !== this.cols || rows !== this.rows) { - console.log('Resizing terminal to geometry: ' + format_geometry(cols, rows)); - this.resize(cols, rows); - sock.send(JSON.stringify({'resize': [cols, rows]})); - } - }; - - term.onData(function(data) { - // console.log(data); - sock.send(JSON.stringify({'data': data})); - }); - - sock.onopen = function() { - term.open(terminal); - toggle_fullscreen(term); - update_font_family(term); - term.focus(); - state = CONNECTED; - title_element.text = url_opts_data.title || default_title; - if (url_opts_data.command) { - setTimeout(function () { - sock.send(JSON.stringify({'data': url_opts_data.command+'\r'})); - }, 500); - } - }; - - sock.onmessage = function(msg) { - read_file_as_text(msg.data, term_write, decoder); - }; - - sock.onerror = function(e) { - console.error(e); - }; - - sock.onclose = function(e) { - term.dispose(); - term = undefined; - sock = undefined; - reset_wssh(); - log_status(e.reason, true); - state = DISCONNECTED; - default_title = 'WebSSH'; - title_element.text = default_title; - }; - - $(window).resize(function(){ - if (term) { - resize_terminal(term); - } - }); - } - - - function wrap_object(opts) { - var obj = {}; - - obj.get = function(attr) { - return opts[attr] || ''; - }; - - obj.set = function(attr, val) { - opts[attr] = val; - }; - - return obj; - } - - - function clean_data(data) { - var i, attr, val; - var attrs = form_keys.concat(['privatekey', 'passphrase']); - - for (i = 0; i < attrs.length; i++) { - attr = attrs[i]; - val = data.get(attr); - if (typeof val === 'string') { - data.set(attr, val.trim()); - } - } - } - - - function validate_form_data(data) { - clean_data(data); - - var hostname = data.get('hostname'), - port = data.get('port'), - username = data.get('username'), - pk = data.get('privatekey'), - result = { - valid: false, - data: data, - title: '' - }, - errors = [], size; - - if (!hostname) { - errors.push('Value of hostname is required.'); - } else { - if (!hostname_tester.test(hostname)) { - errors.push('Invalid hostname: ' + hostname); - } + term.on_resize(geometry.cols, geometry.rows); } - if (!port) { - port = 22; - } else { - if (!(port > 0 && port <= 65535)) { - errors.push('Invalid port: ' + port); - } + + function set_backgound_color(term, color) { + term.setOption('theme', { + background: color + }); } - if (!username) { - errors.push('Value of username is required.'); + function set_font_color(term, color) { + term.setOption('theme', { + foreground: color + }); } - if (pk) { - size = pk.size || pk.length; - if (size > key_max_size) { - errors.push('Invalid private key: ' + pk.name || ''); - } - } - - if (!errors.length || debug) { - result.valid = true; - result.title = username + '@' + hostname + ':' + port; - } - result.errors = errors; - - return result; - } - - // Fix empty input file ajax submission error for safari 11.x - function disable_file_inputs(inputs) { - var i, input; - - for (i = 0; i < inputs.length; i++) { - input = inputs[i]; - if (input.files.length === 0) { - input.setAttribute('disabled', ''); - } - } - } - - - function enable_file_inputs(inputs) { - var i; - - for (i = 0; i < inputs.length; i++) { - inputs[i].removeAttribute('disabled'); - } - } - - - function connect_without_options() { - // use data from the form - var form = document.querySelector(form_id), - inputs = form.querySelectorAll('input[type="file"]'), - url = form.action, - data, pk; - - disable_file_inputs(inputs); - data = new FormData(form); - pk = data.get('privatekey'); - enable_file_inputs(inputs); - - function ajax_post() { - status.text(''); - button.prop('disabled', true); - - $.ajax({ - url: url, - type: 'post', - data: data, - complete: ajax_complete_callback, - cache: false, - contentType: false, - processData: false - }); - } - - var result = validate_form_data(data); - if (!result.valid) { - log_status(result.errors.join('\n')); - return; - } - - if (pk && pk.size && !debug) { - read_file_as_text(pk, function(text) { - if (text === undefined) { - log_status('Invalid private key: ' + pk.name); + function custom_font_is_loaded() { + if (!custom_font) { + console.log('No custom font specified.'); } else { - ajax_post(); + console.log('Status of custom font ' + custom_font.family + ': ' + custom_font.status); + if (custom_font.status === 'loaded') { + return true; + } + if (custom_font.status === 'unloaded') { + return false; + } } - }); - } else { - ajax_post(); } - return result; - } + function update_font_family(term) { + if (term.font_family_updated) { + console.log('Already using custom font family'); + return; + } + if (!default_fonts) { + default_fonts = term.getOption('fontFamily'); + } - function connect_with_options(data) { - // use data from the arguments - var form = document.querySelector(form_id), - url = data.url || form.action, - _xsrf = form.querySelector('input[name="_xsrf"]'); - - var result = validate_form_data(wrap_object(data)); - if (!result.valid) { - log_status(result.errors.join('\n')); - return; + if (custom_font_is_loaded()) { + var new_fonts = custom_font.family + ', ' + default_fonts; + term.setOption('fontFamily', new_fonts); + term.font_family_updated = true; + console.log('Using custom font family ' + new_fonts); + } } - data.term = term_type.val(); - data._xsrf = _xsrf.value; - if (event_origin) { - data._origin = event_origin; + + function reset_font_family(term) { + if (!term.font_family_updated) { + console.log('Already using default font family'); + return; + } + + if (default_fonts) { + term.setOption('fontFamily', default_fonts); + term.font_family_updated = false; + console.log('Using default font family ' + default_fonts); + } } - status.text(''); - button.prop('disabled', true); - $.ajax({ - url: url, - type: 'post', - data: data, - complete: ajax_complete_callback + function format_geometry(cols, rows) { + return JSON.stringify({'cols': cols, 'rows': rows}); + } + + + function read_as_text_with_decoder(file, callback, decoder) { + var reader = new window.FileReader(); + + if (decoder === undefined) { + decoder = new window.TextDecoder('utf-8', {'fatal': true}); + } + + reader.onload = function () { + var text; + try { + text = decoder.decode(reader.result); + } catch (TypeError) { + console.log('Decoding error happened.'); + } finally { + if (callback) { + callback(text); + } + } + }; + + reader.onerror = function (e) { + console.error(e); + }; + + reader.readAsArrayBuffer(file); + } + + + function read_as_text_with_encoding(file, callback, encoding) { + var reader = new window.FileReader(); + + if (encoding === undefined) { + encoding = 'utf-8'; + } + + reader.onload = function () { + if (callback) { + callback(reader.result); + } + }; + + reader.onerror = function (e) { + console.error(e); + }; + + reader.readAsText(file, encoding); + } + + + function read_file_as_text(file, callback, decoder) { + if (!window.TextDecoder) { + read_as_text_with_encoding(file, callback, decoder); + } else { + read_as_text_with_decoder(file, callback, decoder); + } + } + + + function reset_wssh() { + var name; + + for (name in wssh) { + if (wssh.hasOwnProperty(name) && name !== 'connect') { + delete wssh[name]; + } + } + } + + + function log_status(text, to_populate) { + console.log(text); + status.html(text.split('\n').join('
')); + + if (to_populate && validated_form_data) { + populate_form(validated_form_data); + validated_form_data = undefined; + } + + if (waiter.css('display') !== 'none') { + waiter.hide(); + } + + if (form_container.css('display') === 'none') { + form_container.show(); + } + } + + + function ajax_complete_callback(resp) { + button.prop('disabled', false); + + if (resp.status !== 200) { + log_status(resp.status + ': ' + resp.statusText, true); + state = DISCONNECTED; + return; + } + + var msg = resp.responseJSON; + if (!msg.id) { + log_status(msg.status, true); + state = DISCONNECTED; + return; + } + + var ws_url = window.location.href.split(/\?|#/, 1)[0].replace('http', 'ws'), + join = (ws_url[ws_url.length - 1] === '/' ? '' : '/'), url = ws_url + join + 'ws?id=' + msg.id, + sock = new window.WebSocket(url), encoding = 'utf-8', + decoder = window.TextDecoder ? new window.TextDecoder(encoding) : encoding, + terminal = document.getElementById('terminal'), termOptions = { + cursorBlink: true, + theme: { + background: url_opts_data.bgcolor || 'black', + foreground: url_opts_data.fontcolor || 'white', + cursor: url_opts_data.cursor || url_opts_data.fontcolor || 'white' + } + }; + + window.sock = sock; + + if (url_opts_data.fontsize) { + var fontsize = window.parseInt(url_opts_data.fontsize); + if (fontsize && fontsize > 0) { + termOptions.fontSize = fontsize; + } + } + + var term = new window.Terminal(termOptions); + + term.fitAddon = new window.FitAddon.FitAddon(); + term.loadAddon(term.fitAddon); + + console.log(url); + if (!msg.encoding) { + console.log('Unable to detect the default encoding of your server'); + msg.encoding = encoding; + } else { + console.log('The deault encoding of your server is ' + msg.encoding); + } + + function term_write(text) { + if (term) { + term.write(text); + window.parent.postMessage({event: 'command', data: text}, '*') + if (!term.resized) { + resize_terminal(term); + term.resized = true; + } + } + } + + function set_encoding(new_encoding) { + // for console use + if (!new_encoding) { + console.log('An encoding is required'); + return; + } + + if (!window.TextDecoder) { + decoder = new_encoding; + encoding = decoder; + console.log('Set encoding to ' + encoding); + } else { + try { + decoder = new window.TextDecoder(new_encoding); + encoding = decoder.encoding; + console.log('Set encoding to ' + encoding); + } catch (RangeError) { + console.log('Unknown encoding ' + new_encoding); + return false; + } + } + } + + wssh.set_encoding = set_encoding; + + if (url_opts_data.encoding) { + if (set_encoding(url_opts_data.encoding) === false) { + set_encoding(msg.encoding); + } + } else { + set_encoding(msg.encoding); + } + + + wssh.geometry = function () { + // for console use + var geometry = current_geometry(term); + console.log('Current window geometry: ' + JSON.stringify(geometry)); + }; + + wssh.send = function (data) { + // for console use + if (!sock) { + console.log('Websocket was already closed'); + return; + } + + if (typeof data !== 'string') { + console.log('Only string is allowed'); + return; + } + + try { + JSON.parse(data); + sock.send(data); + } catch (SyntaxError) { + data = data.trim() + '\r'; + sock.send(JSON.stringify({'data': data})); + } + }; + + wssh.reset_encoding = function () { + // for console use + if (encoding === msg.encoding) { + console.log('Already reset to ' + msg.encoding); + } else { + set_encoding(msg.encoding); + } + }; + + wssh.resize = function (cols, rows) { + // for console use + if (term === undefined) { + console.log('Terminal was already destroryed'); + return; + } + + var valid_args = false; + + if (cols > 0 && rows > 0) { + var geometry = current_geometry(term); + if (cols <= geometry.cols && rows <= geometry.rows) { + valid_args = true; + } + } + + if (!valid_args) { + console.log('Unable to resize terminal to geometry: ' + format_geometry(cols, rows)); + } else { + term.on_resize(cols, rows); + } + }; + + wssh.set_bgcolor = function (color) { + set_backgound_color(term, color); + }; + + wssh.set_fontcolor = function (color) { + set_font_color(term, color); + }; + + wssh.custom_font = function () { + update_font_family(term); + }; + + wssh.default_font = function () { + reset_font_family(term); + }; + + term.on_resize = function (cols, rows) { + if (cols !== this.cols || rows !== this.rows) { + console.log('Resizing terminal to geometry: ' + format_geometry(cols, rows)); + this.resize(cols, rows); + sock.send(JSON.stringify({'resize': [cols, rows]})); + } + }; + + term.onData(function (data) { + // console.log(data); + sock.send(JSON.stringify({'data': data})); + }); + + sock.onopen = function () { + term.open(terminal); + toggle_fullscreen(term); + update_font_family(term); + term.focus(); + state = CONNECTED; + title_element.text = url_opts_data.title || default_title; + if (url_opts_data.command) { + setTimeout(function () { + sock.send(JSON.stringify({'data': url_opts_data.command + '\r'})); + }, 500); + } + }; + + sock.onmessage = function (msg) { + read_file_as_text(msg.data, term_write, decoder); + }; + + sock.onerror = function (e) { + console.error(e); + window.parent.postMessage({event: 'disconnected'}, '*') + }; + + sock.onclose = function (e) { + term.dispose(); + term = undefined; + sock = undefined; + reset_wssh(); + log_status(e.reason, true); + state = DISCONNECTED; + default_title = 'WebSSH'; + title_element.text = default_title; + window.parent.postMessage({event: 'disconnected'}, '*') + }; + + $(window).resize(function () { + if (term) { + resize_terminal(term); + } + }); + } + + + function wrap_object(opts) { + var obj = {}; + + obj.get = function (attr) { + return opts[attr] || ''; + }; + + obj.set = function (attr, val) { + opts[attr] = val; + }; + + return obj; + } + + + function clean_data(data) { + var i, attr, val; + var attrs = form_keys.concat(['privatekey', 'passphrase']); + + for (i = 0; i < attrs.length; i++) { + attr = attrs[i]; + val = data.get(attr); + if (typeof val === 'string') { + data.set(attr, val.trim()); + } + } + } + + + function validate_form_data(data) { + clean_data(data); + + var hostname = data.get('hostname'), port = data.get('port'), username = data.get('username'), + pk = data.get('privatekey'), result = { + valid: false, data: data, title: '' + }, errors = [], size; + + if (!hostname) { + errors.push('Value of hostname is required.'); + } else { + if (!hostname_tester.test(hostname)) { + errors.push('Invalid hostname: ' + hostname); + } + } + + if (!port) { + port = 22; + } else { + if (!(port > 0 && port <= 65535)) { + errors.push('Invalid port: ' + port); + } + } + + if (!username) { + errors.push('Value of username is required.'); + } + + if (pk) { + size = pk.size || pk.length; + if (size > key_max_size) { + errors.push('Invalid private key: ' + pk.name || ''); + } + } + + if (!errors.length || debug) { + result.valid = true; + result.title = username + '@' + hostname + ':' + port; + } + result.errors = errors; + + return result; + } + + // Fix empty input file ajax submission error for safari 11.x + function disable_file_inputs(inputs) { + var i, input; + + for (i = 0; i < inputs.length; i++) { + input = inputs[i]; + if (input.files.length === 0) { + input.setAttribute('disabled', ''); + } + } + } + + + function enable_file_inputs(inputs) { + var i; + + for (i = 0; i < inputs.length; i++) { + inputs[i].removeAttribute('disabled'); + } + } + + + function connect_without_options() { + // use data from the form + var form = document.querySelector(form_id), inputs = form.querySelectorAll('input[type="file"]'), + url = form.action, data, pk; + + disable_file_inputs(inputs); + data = new FormData(form); + pk = data.get('privatekey'); + enable_file_inputs(inputs); + + function ajax_post() { + status.text(''); + button.prop('disabled', true); + + $.ajax({ + url: url, + type: 'post', + data: data, + complete: ajax_complete_callback, + cache: false, + contentType: false, + processData: false + }); + } + + var result = validate_form_data(data); + if (!result.valid) { + log_status(result.errors.join('\n')); + window.parent.postMessage({event: 'connected'}, '*') + return; + } + + if (pk && pk.size && !debug) { + read_file_as_text(pk, function (text) { + if (text === undefined) { + log_status('Invalid private key: ' + pk.name); + } else { + ajax_post(); + } + }); + } else { + ajax_post(); + } + + return result; + } + + + function connect_with_options(data) { + // use data from the arguments + var form = document.querySelector(form_id), url = data.url || form.action, + _xsrf = form.querySelector('input[name="_xsrf"]'); + + var result = validate_form_data(wrap_object(data)); + if (!result.valid) { + log_status(result.errors.join('\n')); + window.parent.postMessage({event: 'connected'}, '*') + return; + } + + data.term = term_type.val(); + data._xsrf = _xsrf.value; + if (event_origin) { + data._origin = event_origin; + } + + status.text(''); + button.prop('disabled', true); + + $.ajax({ + url: url, type: 'post', data: data, complete: ajax_complete_callback, + }).then(() => { + window.parent.postMessage({event: 'connected'}, '*') + }).catch(() => { + window.parent.postMessage({event: 'connected'}, '*') + }) + + return result; + } + + + function connect(hostname, port, username, password, privatekey, passphrase, totp, proxyType, proxyIp, proxyPort, proxyRdns = true, proxyUser, proxyPass) { + var result, opts; + if (state !== DISCONNECTED) { + console.log(messages[state]); + return; + } + + if (hostname === undefined) { + result = connect_without_options(); + } else { + if (typeof hostname === 'string') { + opts = { + hostname: hostname, + port: port, + username: username, + password: password, + privatekey: privatekey, + passphrase: passphrase, + totp: totp, + proxyType: proxyType, + proxyIp: proxyIp, + proxyPort: proxyPort, + proxyRdns: proxyRdns, + proxyUser: proxyUser, + proxyPass: proxyPass + }; + } else { + opts = hostname; + } + + result = connect_with_options(opts); + } + + if (result) { + state = CONNECTING; + default_title = result.title; + if (hostname) { + validated_form_data = result.data; + } + store_items(fields, result.data); + } + + + } + + window.onmessage = function (event) { + //如果是command则执行 + if (event.data.command) { + console.log('command', event.data.command); + window.sock.send(JSON.stringify({'data': event.data.command + '\r'})); + } + } + + wssh.connect = connect; + + $(form_id).submit(function (event) { + event.preventDefault(); + connect(); }); - return result; - } + function cross_origin_connect(event) { + console.log(event.origin); + var prop = 'connect', args; - function connect(hostname, port, username, password, privatekey, passphrase, totp) { - // for console use - var result, opts; - - if (state !== DISCONNECTED) { - console.log(messages[state]); - return; - } - - if (hostname === undefined) { - result = connect_without_options(); - } else { - if (typeof hostname === 'string') { - opts = { - hostname: hostname, - port: port, - username: username, - password: password, - privatekey: privatekey, - passphrase: passphrase, - totp: totp - }; - } else { - opts = hostname; - } - - result = connect_with_options(opts); - } - - if (result) { - state = CONNECTING; - default_title = result.title; - if (hostname) { - validated_form_data = result.data; - } - store_items(fields, result.data); - } - } - - wssh.connect = connect; - - $(form_id).submit(function(event){ - event.preventDefault(); - connect(); - }); - - - function cross_origin_connect(event) - { - console.log(event.origin); - var prop = 'connect', - args; - - try { - args = JSON.parse(event.data); - } catch (SyntaxError) { - args = event.data.split('|'); - } - - if (!Array.isArray(args)) { - args = [args]; - } - - try { - event_origin = event.origin; - wssh[prop].apply(wssh, args); - } finally { - event_origin = undefined; - } - } - - window.addEventListener('message', cross_origin_connect, false); - - if (document.fonts) { - document.fonts.ready.then( - function () { - if (custom_font_is_loaded() === false) { - document.body.style.fontFamily = custom_font.family; + try { + args = JSON.parse(event.data); + } catch (SyntaxError) { + args = event.data.split('|'); } - } - ); - } + if (!Array.isArray(args)) { + args = [args]; + } - parse_url_data( - decode_uri_component(window.location.search.substring(1)) + '&' + decode_uri_component(window.location.hash.substring(1)), - form_keys, opts_keys, url_form_data, url_opts_data - ); - // console.log(url_form_data); - // console.log(url_opts_data); - - if (url_opts_data.term) { - term_type.val(url_opts_data.term); - } - - if (url_form_data.password === null) { - log_status('Password via url must be encoded in base64.'); - } else { - if (get_object_length(url_form_data)) { - waiter.show(); - connect(url_form_data); - } else { - restore_items(fields); - form_container.show(); + try { + event_origin = event.origin; + wssh[prop].apply(wssh, args); + } finally { + event_origin = undefined; + } + } + + window.addEventListener('message', cross_origin_connect, false); + + if (document.fonts) { + document.fonts.ready.then(function () { + if (custom_font_is_loaded() === false) { + document.body.style.fontFamily = custom_font.family; + } + }); + } + + document.addEventListener("dragenter", (e) => { + console.log("dragenter事件触发"); + e.target.style.backgroundColor = "green" + console.log("颜色改变"); + }) + + + parse_url_data(decode_uri_component(window.location.search.substring(1)) + '&' + decode_uri_component(window.location.hash.substring(1)), form_keys, opts_keys, url_form_data, url_opts_data); + console.log(url_form_data); + console.log("url_opts_data:", url_opts_data); + if (url_opts_data.term) { + term_type.val(url_opts_data.term); + } + + if (url_form_data.password === null) { + log_status('Password via url must be encoded in base64.'); + } else { + if (get_object_length(url_form_data)) { + waiter.show(); + connect(url_form_data); + console.log('Connecting ...', url_form_data) + } else { + restore_items(fields); + form_container.show(); + } } - } });