1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-04-02 10:50:07 +03:00

Feature #1935: Package novnc libraries

This commit is contained in:
Jaime Melis 2013-04-26 12:29:06 +02:00
parent cee934b77d
commit f64437c894
30 changed files with 9659 additions and 67 deletions

View File

@ -190,7 +190,8 @@ else
fi
SHARE_DIRS="$SHARE_LOCATION/examples \
$SHARE_LOCATION/tgt"
$SHARE_LOCATION/tgt \
$SHARE_LOCATION/websockify"
ETC_DIRS="$ETC_LOCATION/im_ec2 \
$ETC_LOCATION/vmm_ec2 \
@ -293,6 +294,8 @@ SUNSTONE_DIRS="$SUNSTONE_LOCATION/routes \
$SUNSTONE_LOCATION/public/vendor/explorercanvas \
$SUNSTONE_LOCATION/public/vendor/flot \
$SUNSTONE_LOCATION/public/vendor/fileuploader \
$SUNSTONE_LOCATION/public/vendor/noVNC \
$SUNSTONE_LOCATION/public/vendor/noVNC/web-socket-js \
$SUNSTONE_LOCATION/public/vendor/4.0 \
$SUNSTONE_LOCATION/public/vendor/4.0/datatables \
$SUNSTONE_LOCATION/public/vendor/4.0/foundation_datatables \
@ -431,7 +434,7 @@ INSTALL_FILES=(
NETWORK_VMWARE_FILES:$VAR_LOCATION/remotes/vnm/vmware
EXAMPLE_SHARE_FILES:$SHARE_LOCATION/examples
TGT_SHARE_FILES:$SHARE_LOCATION/tgt
INSTALL_NOVNC_SHARE_FILE:$SHARE_LOCATION
WEBSOCKIFY_SHARE_FILES:$SHARE_LOCATION/websockify
INSTALL_GEMS_SHARE_FILE:$SHARE_LOCATION
HOOK_FT_FILES:$VAR_LOCATION/remotes/hooks/ft
COMMON_CLOUD_LIB_FILES:$LIB_LOCATION/ruby/cloud
@ -490,6 +493,8 @@ INSTALL_SUNSTONE_FILES=(
SUNSTONE_PUBLIC_VENDOR_EXPLORERCANVAS:$SUNSTONE_LOCATION/public/vendor/explorercanvas
SUNSTONE_PUBLIC_VENDOR_FLOT:$SUNSTONE_LOCATION/public/vendor/flot
SUNSTONE_PUBLIC_VENDOR_FILEUPLOADER:$SUNSTONE_LOCATION/public/vendor/fileuploader
SUNSTONE_PUBLIC_VENDOR_NOVNC:$SUNSTONE_LOCATION/public/vendor/noVNC
SUNSTONE_PUBLIC_VENDOR_NOVNC_WEBSOCKET:$SUNSTONE_LOCATION/public/vendor/noVNC/web-socket-js
SUNSTONE_PUBLIC_NEW_VENDOR_DATATABLES:$SUNSTONE_LOCATION/public/vendor/4.0/datatables
SUNSTONE_PUBLIC_NEW_VENDOR_FOUNDATION_DATATABLES:$SUNSTONE_LOCATION/public/vendor/4.0/foundation_datatables
SUNSTONE_PUBLIC_NEW_VENDOR_JGROWL:$SUNSTONE_LOCATION/public/vendor/4.0/jgrowl
@ -1096,6 +1101,14 @@ EXAMPLE_SHARE_FILES="share/examples/vm.template \
TGT_SHARE_FILES="share/scripts/tgt/tgt-setup-lun-one"
#-------------------------------------------------------------------------------
# Files required to interact with the websockify server
#-------------------------------------------------------------------------------
WEBSOCKIFY_SHARE_FILES="share/websockify/websocketproxy.py \
share/websockify/websocket.py \
share/websockify/websockify"
#-------------------------------------------------------------------------------
# HOOK scripts, to be installed under $VAR_LOCATION/remotes/hooks
#-------------------------------------------------------------------------------
@ -1106,7 +1119,6 @@ HOOK_FT_FILES="share/hooks/host_error.rb"
# Installation scripts, to be installed under $SHARE_LOCATION
#-------------------------------------------------------------------------------
INSTALL_NOVNC_SHARE_FILE="share/install_novnc.sh"
INSTALL_GEMS_SHARE_FILE="share/install_gems/install_gems"
#-------------------------------------------------------------------------------
@ -1446,6 +1458,33 @@ SUNSTONE_PUBLIC_VENDOR_FILEUPLOADER="\
src/sunstone/public/vendor/fileuploader/NOTICE \
src/sunstone/public/vendor/fileuploader/fileuploader.js"
SUNSTONE_PUBLIC_VENDOR_NOVNC="\
src/sunstone/public/vendor/noVNC/LICENSE.txt \
src/sunstone/public/vendor/noVNC/black.css \
src/sunstone/public/vendor/noVNC/playback.js \
src/sunstone/public/vendor/noVNC/websock.js \
src/sunstone/public/vendor/noVNC/util.js \
src/sunstone/public/vendor/noVNC/des.js \
src/sunstone/public/vendor/noVNC/jsunzip.js \
src/sunstone/public/vendor/noVNC/Orbitron700.ttf \
src/sunstone/public/vendor/noVNC/display.js \
src/sunstone/public/vendor/noVNC/input.js \
src/sunstone/public/vendor/noVNC/rfb.js \
src/sunstone/public/vendor/noVNC/base64.js \
src/sunstone/public/vendor/noVNC/Orbitron700.woff \
src/sunstone/public/vendor/noVNC/logo.js \
src/sunstone/public/vendor/noVNC/blue.css \
src/sunstone/public/vendor/noVNC/ui.js \
src/sunstone/public/vendor/noVNC/vnc.js \
src/sunstone/public/vendor/noVNC/base.css \
src/sunstone/public/vendor/noVNC/webutil.js"
SUNSTONE_PUBLIC_VENDOR_NOVNC_WEBSOCKET="\
src/sunstone/public/vendor/noVNC/web-socket-js/web_socket.js \
src/sunstone/public/vendor/noVNC/web-socket-js/README.txt \
src/sunstone/public/vendor/noVNC/web-socket-js/swfobject.js \
src/sunstone/public/vendor/noVNC/web-socket-js/WebSocketMain.swf"
SUNSTONE_PUBLIC_VENDOR_XML2JSON="\
src/sunstone/public/vendor/xml2json/NOTICE \
src/sunstone/public/vendor/xml2json/jquery.xml2json.pack.js"

View File

@ -1,63 +0,0 @@
#!/bin/bash
NOVNC_TMP=/tmp/one/novnc-$(date "+%Y%m%d%H%M%S")
PROXY_PATH=websockify/websocketproxy.py
NOVNC_TAR=https://github.com/downloads/kanaka/noVNC/novnc-0.4.tar.gz
WEBSOCKIFY_RAW_URL=https://raw.github.com/kanaka/websockify/ee2f269c067c27ef49d63ad11d9efec499423500/websockify
if [ -z "$ONE_LOCATION" ]; then
ONE_SHARE=/usr/share/one
ONE_PUBLIC_SUNSTONE=/usr/lib/one/sunstone/public
SUNSTONE_CONF=/etc/one/sunstone-server.conf
else
ONE_SHARE=$ONE_LOCATION/share
ONE_PUBLIC_SUNSTONE=$ONE_LOCATION/lib/sunstone/public
SUNSTONE_CONF=$ONE_LOCATION/etc/sunstone-server.conf
fi
echo "Downloading noVNC latest version..."
mkdir -p $NOVNC_TMP
cd $NOVNC_TMP
curl -O -# -L $NOVNC_TAR
if [ $? -ne 0 ]; then
echo "\nError downloading noVNC"
exit 1
fi
echo "Extracting files to temporary folder..."
tar=`ls -rt $NOVNC_TMP|tail -n1`
tar -mxzf $NOVNC_TMP/$tar
if [ $? -ne 0 ]; then
echo "Error untaring noVNC"
exit 1
fi
echo "Installing Sunstone client libraries in $ONE_PUBLIC_SUNSTONE..."
rm -rf $ONE_PUBLIC_SUNSTONE/vendor/noVNC/
mkdir -p $ONE_PUBLIC_SUNSTONE/vendor/noVNC
cp -r $NOVNC_TMP/*novnc*/include/* $ONE_PUBLIC_SUNSTONE/vendor/noVNC/
cd $ONE_SHARE
rm -rf $NOVNC_TMP
echo "Downloading Websockify VNC proxy files"
rm -rf $ONE_SHARE/websockify
mkdir -p $ONE_SHARE/websockify
cd $ONE_SHARE/websockify
curl -O -# -L $WEBSOCKIFY_RAW_URL/websocket.py
if [ $? -ne 0 ]; then
echo "\nError downloading websockify"
exit 1
fi
curl -O -# -L $WEBSOCKIFY_RAW_URL/websocketproxy.py
if [ $? -ne 0 ]; then
echo "\nError downloading websocket.py"
exit 1
fi
ln -s websocketproxy.py websockify
echo "Backing up and updating $SUNSTONE_CONF with new VNC proxy path..."
sed -i.bck "s%^\(:vnc_proxy_path:\).*$%\1 $ONE_SHARE/$PROXY_PATH%" $SUNSTONE_CONF
echo "Installation successful"

View File

@ -0,0 +1,977 @@
#!/usr/bin/env python
'''
Python WebSocket library with support for "wss://" encryption.
Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
Supports following protocol versions:
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
- http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
- http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import os, sys, time, errno, signal, socket, traceback, select
import array, struct
from base64 import b64encode, b64decode
# Imports that vary by python version
# python 3.0 differences
if sys.hexversion > 0x3000000:
b2s = lambda buf: buf.decode('latin_1')
s2b = lambda s: s.encode('latin_1')
s2a = lambda s: s
else:
b2s = lambda buf: buf # No-op
s2b = lambda s: s # No-op
s2a = lambda s: [ord(c) for c in s]
try: from io import StringIO
except: from cStringIO import StringIO
try: from http.server import SimpleHTTPRequestHandler
except: from SimpleHTTPServer import SimpleHTTPRequestHandler
# python 2.6 differences
try: from hashlib import md5, sha1
except: from md5 import md5; from sha import sha as sha1
# python 2.5 differences
try:
from struct import pack, unpack_from
except:
from struct import pack
def unpack_from(fmt, buf, offset=0):
slice = buffer(buf, offset, struct.calcsize(fmt))
return struct.unpack(fmt, slice)
# Degraded functionality if these imports are missing
for mod, sup in [('numpy', 'HyBi protocol'), ('ssl', 'TLS/SSL/wss'),
('multiprocessing', 'Multi-Processing'),
('resource', 'daemonizing')]:
try:
globals()[mod] = __import__(mod)
except ImportError:
globals()[mod] = None
print("WARNING: no '%s' module, %s is slower or disabled" % (
mod, sup))
if multiprocessing and sys.platform == 'win32':
# make sockets pickle-able/inheritable
import multiprocessing.reduction
class WebSocketServer(object):
"""
WebSockets server class.
Must be sub-classed with new_client method definition.
"""
buffer_size = 65536
server_handshake_hixie = """HTTP/1.1 101 Web Socket Protocol Handshake\r
Upgrade: WebSocket\r
Connection: Upgrade\r
%sWebSocket-Origin: %s\r
%sWebSocket-Location: %s://%s%s\r
"""
server_handshake_hybi = """HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-WebSocket-Accept: %s\r
"""
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>\n"""
# An exception before the WebSocket connection was established
class EClose(Exception):
pass
# An exception while the WebSocket client was connected
class CClose(Exception):
pass
def __init__(self, listen_host='', listen_port=None, source_is_ipv6=False,
verbose=False, cert='', key='', ssl_only=None,
daemon=False, record='', web='',
run_once=False, timeout=0, idle_timeout=0):
# settings
self.verbose = verbose
self.listen_host = listen_host
self.listen_port = listen_port
self.prefer_ipv6 = source_is_ipv6
self.ssl_only = ssl_only
self.daemon = daemon
self.run_once = run_once
self.timeout = timeout
self.idle_timeout = idle_timeout
self.launch_time = time.time()
self.ws_connection = False
self.handler_id = 1
# Make paths settings absolute
self.cert = os.path.abspath(cert)
self.key = self.web = self.record = ''
if key:
self.key = os.path.abspath(key)
if web:
self.web = os.path.abspath(web)
if record:
self.record = os.path.abspath(record)
if self.web:
os.chdir(self.web)
# Sanity checks
if not ssl and self.ssl_only:
raise Exception("No 'ssl' module and SSL-only specified")
if self.daemon and not resource:
raise Exception("Module 'resource' required to daemonize")
# Show configuration
print("WebSocket server settings:")
print(" - Listen on %s:%s" % (
self.listen_host, self.listen_port))
print(" - Flash security policy server")
if self.web:
print(" - Web server. Web root: %s" % self.web)
if ssl:
if os.path.exists(self.cert):
print(" - SSL/TLS support")
if self.ssl_only:
print(" - Deny non-SSL/TLS connections")
else:
print(" - No SSL/TLS support (no cert file)")
else:
print(" - No SSL/TLS support (no 'ssl' module)")
if self.daemon:
print(" - Backgrounding (daemon)")
if self.record:
print(" - Recording to '%s.*'" % self.record)
#
# WebSocketServer static methods
#
@staticmethod
def socket(host, port=None, connect=False, prefer_ipv6=False, unix_socket=None, use_ssl=False):
""" Resolve a host (and optional port) to an IPv4 or IPv6
address. Create a socket. Bind to it if listen is set,
otherwise connect to it. Return the socket.
"""
flags = 0
if host == '':
host = None
if connect and not (port or unix_socket):
raise Exception("Connect mode requires a port")
if use_ssl and not ssl:
raise Exception("SSL socket requested but Python SSL module not loaded.");
if not connect and use_ssl:
raise Exception("SSL only supported in connect mode (for now)")
if not connect:
flags = flags | socket.AI_PASSIVE
if not unix_socket:
addrs = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM,
socket.IPPROTO_TCP, flags)
if not addrs:
raise Exception("Could not resolve host '%s'" % host)
addrs.sort(key=lambda x: x[0])
if prefer_ipv6:
addrs.reverse()
sock = socket.socket(addrs[0][0], addrs[0][1])
if connect:
sock.connect(addrs[0][4])
if use_ssl:
sock = ssl.wrap_socket(sock)
else:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addrs[0][4])
sock.listen(100)
else:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(unix_socket)
return sock
@staticmethod
def daemonize(keepfd=None, chdir='/'):
os.umask(0)
if chdir:
os.chdir(chdir)
else:
os.chdir('/')
os.setgid(os.getgid()) # relinquish elevations
os.setuid(os.getuid()) # relinquish elevations
# Double fork to daemonize
if os.fork() > 0: os._exit(0) # Parent exits
os.setsid() # Obtain new process group
if os.fork() > 0: os._exit(0) # Parent exits
# Signal handling
def terminate(a,b): os._exit(0)
signal.signal(signal.SIGTERM, terminate)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Close open files
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if maxfd == resource.RLIM_INFINITY: maxfd = 256
for fd in reversed(range(maxfd)):
try:
if fd != keepfd:
os.close(fd)
except OSError:
_, exc, _ = sys.exc_info()
if exc.errno != errno.EBADF: raise
# Redirect I/O to /dev/null
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdin.fileno())
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stdout.fileno())
os.dup2(os.open(os.devnull, os.O_RDWR), sys.stderr.fileno())
@staticmethod
def unmask(buf, hlen, plen):
pstart = hlen + 4
pend = pstart + plen
if numpy:
b = c = s2b('')
if plen >= 4:
mask = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=hlen, count=1)
data = numpy.frombuffer(buf, dtype=numpy.dtype('<u4'),
offset=pstart, count=int(plen / 4))
#b = numpy.bitwise_xor(data, mask).data
b = numpy.bitwise_xor(data, mask).tostring()
if plen % 4:
#print("Partial unmask")
mask = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=hlen, count=(plen % 4))
data = numpy.frombuffer(buf, dtype=numpy.dtype('B'),
offset=pend - (plen % 4),
count=(plen % 4))
c = numpy.bitwise_xor(data, mask).tostring()
return b + c
else:
# Slower fallback
mask = buf[hlen:hlen+4]
data = array.array('B')
mask = s2a(mask)
data.fromstring(buf[pstart:pend])
for i in range(len(data)):
data[i] ^= mask[i % 4]
return data.tostring()
@staticmethod
def encode_hybi(buf, opcode, base64=False):
""" Encode a HyBi style WebSocket frame.
Optional opcode:
0x0 - continuation
0x1 - text frame (base64 encode buf)
0x2 - binary frame (use raw buf)
0x8 - connection close
0x9 - ping
0xA - pong
"""
if base64:
buf = b64encode(buf)
b1 = 0x80 | (opcode & 0x0f) # FIN + opcode
payload_len = len(buf)
if payload_len <= 125:
header = pack('>BB', b1, payload_len)
elif payload_len > 125 and payload_len < 65536:
header = pack('>BBH', b1, 126, payload_len)
elif payload_len >= 65536:
header = pack('>BBQ', b1, 127, payload_len)
#print("Encoded: %s" % repr(header + buf))
return header + buf, len(header), 0
@staticmethod
def decode_hybi(buf, base64=False):
""" Decode HyBi style WebSocket packets.
Returns:
{'fin' : 0_or_1,
'opcode' : number,
'masked' : boolean,
'hlen' : header_bytes_number,
'length' : payload_bytes_number,
'payload' : decoded_buffer,
'left' : bytes_left_number,
'close_code' : number,
'close_reason' : string}
"""
f = {'fin' : 0,
'opcode' : 0,
'masked' : False,
'hlen' : 2,
'length' : 0,
'payload' : None,
'left' : 0,
'close_code' : 1000,
'close_reason' : ''}
blen = len(buf)
f['left'] = blen
if blen < f['hlen']:
return f # Incomplete frame header
b1, b2 = unpack_from(">BB", buf)
f['opcode'] = b1 & 0x0f
f['fin'] = (b1 & 0x80) >> 7
f['masked'] = (b2 & 0x80) >> 7
f['length'] = b2 & 0x7f
if f['length'] == 126:
f['hlen'] = 4
if blen < f['hlen']:
return f # Incomplete frame header
(f['length'],) = unpack_from('>xxH', buf)
elif f['length'] == 127:
f['hlen'] = 10
if blen < f['hlen']:
return f # Incomplete frame header
(f['length'],) = unpack_from('>xxQ', buf)
full_len = f['hlen'] + f['masked'] * 4 + f['length']
if blen < full_len: # Incomplete frame
return f # Incomplete frame header
# Number of bytes that are part of the next frame(s)
f['left'] = blen - full_len
# Process 1 frame
if f['masked']:
# unmask payload
f['payload'] = WebSocketServer.unmask(buf, f['hlen'],
f['length'])
else:
print("Unmasked frame: %s" % repr(buf))
f['payload'] = buf[(f['hlen'] + f['masked'] * 4):full_len]
if base64 and f['opcode'] in [1, 2]:
try:
f['payload'] = b64decode(f['payload'])
except:
print("Exception while b64decoding buffer: %s" %
repr(buf))
raise
if f['opcode'] == 0x08:
if f['length'] >= 2:
f['close_code'] = unpack_from(">H", f['payload'])[0]
if f['length'] > 3:
f['close_reason'] = f['payload'][2:]
return f
@staticmethod
def encode_hixie(buf):
return s2b("\x00" + b2s(b64encode(buf)) + "\xff"), 1, 1
@staticmethod
def decode_hixie(buf):
end = buf.find(s2b('\xff'))
return {'payload': b64decode(buf[1:end]),
'hlen': 1,
'masked': False,
'length': end - 1,
'left': len(buf) - (end + 1)}
@staticmethod
def gen_md5(keys):
""" Generate hash value for WebSockets hixie-76. """
key1 = keys['Sec-WebSocket-Key1']
key2 = keys['Sec-WebSocket-Key2']
key3 = keys['key3']
spaces1 = key1.count(" ")
spaces2 = key2.count(" ")
num1 = int("".join([c for c in key1 if c.isdigit()])) / spaces1
num2 = int("".join([c for c in key2 if c.isdigit()])) / spaces2
return b2s(md5(pack('>II8s',
int(num1), int(num2), key3)).digest())
#
# WebSocketServer logging/output functions
#
def traffic(self, token="."):
""" Show traffic flow in verbose mode. """
if self.verbose and not self.daemon:
sys.stdout.write(token)
sys.stdout.flush()
def msg(self, msg):
""" Output message with handler_id prefix. """
if not self.daemon:
print("% 3d: %s" % (self.handler_id, msg))
def vmsg(self, msg):
""" Same as msg() but only if verbose. """
if self.verbose:
self.msg(msg)
#
# Main WebSocketServer methods
#
def send_frames(self, bufs=None):
""" Encode and send WebSocket frames. Any frames already
queued will be sent first. If buf is not set then only queued
frames will be sent. Returns the number of pending frames that
could not be fully sent. If returned pending frames is greater
than 0, then the caller should call again when the socket is
ready. """
tdelta = int(time.time()*1000) - self.start_time
if bufs:
for buf in bufs:
if self.version.startswith("hybi"):
if self.base64:
encbuf, lenhead, lentail = self.encode_hybi(
buf, opcode=1, base64=True)
else:
encbuf, lenhead, lentail = self.encode_hybi(
buf, opcode=2, base64=False)
else:
encbuf, lenhead, lentail = self.encode_hixie(buf)
if self.rec:
self.rec.write("%s,\n" %
repr("{%s{" % tdelta
+ encbuf[lenhead:len(encbuf)-lentail]))
self.send_parts.append(encbuf)
while self.send_parts:
# Send pending frames
buf = self.send_parts.pop(0)
sent = self.client.send(buf)
if sent == len(buf):
self.traffic("<")
else:
self.traffic("<.")
self.send_parts.insert(0, buf[sent:])
break
return len(self.send_parts)
def recv_frames(self):
""" Receive and decode WebSocket frames.
Returns:
(bufs_list, closed_string)
"""
closed = False
bufs = []
tdelta = int(time.time()*1000) - self.start_time
buf = self.client.recv(self.buffer_size)
if len(buf) == 0:
closed = {'code': 1000, 'reason': "Client closed abruptly"}
return bufs, closed
if self.recv_part:
# Add partially received frames to current read buffer
buf = self.recv_part + buf
self.recv_part = None
while buf:
if self.version.startswith("hybi"):
frame = self.decode_hybi(buf, base64=self.base64)
#print("Received buf: %s, frame: %s" % (repr(buf), frame))
if frame['payload'] == None:
# Incomplete/partial frame
self.traffic("}.")
if frame['left'] > 0:
self.recv_part = buf[-frame['left']:]
break
else:
if frame['opcode'] == 0x8: # connection close
closed = {'code': frame['close_code'],
'reason': frame['close_reason']}
break
else:
if buf[0:2] == s2b('\xff\x00'):
closed = {'code': 1000,
'reason': "Client sent orderly close frame"}
break
elif buf[0:2] == s2b('\x00\xff'):
buf = buf[2:]
continue # No-op
elif buf.count(s2b('\xff')) == 0:
# Partial frame
self.traffic("}.")
self.recv_part = buf
break
frame = self.decode_hixie(buf)
self.traffic("}")
if self.rec:
start = frame['hlen']
end = frame['hlen'] + frame['length']
if frame['masked']:
recbuf = WebSocketServer.unmask(buf, frame['hlen'],
frame['length'])
else:
recbuf = buf[frame['hlen']:frame['hlen'] +
frame['length']]
self.rec.write("%s,\n" %
repr("}%s}" % tdelta + recbuf))
bufs.append(frame['payload'])
if frame['left']:
buf = buf[-frame['left']:]
else:
buf = ''
return bufs, closed
def send_close(self, code=1000, reason=''):
""" Send a WebSocket orderly close frame. """
if self.version.startswith("hybi"):
msg = pack(">H%ds" % len(reason), code, reason)
buf, h, t = self.encode_hybi(msg, opcode=0x08, base64=False)
self.client.send(buf)
elif self.version == "hixie-76":
buf = s2b('\xff\x00')
self.client.send(buf)
# No orderly close for 75
def do_websocket_handshake(self, headers, path):
h = self.headers = headers
self.path = path
prot = 'WebSocket-Protocol'
protocols = h.get('Sec-'+prot, h.get(prot, '')).split(',')
ver = h.get('Sec-WebSocket-Version')
if ver:
# HyBi/IETF version of the protocol
# HyBi-07 report version 7
# HyBi-08 - HyBi-12 report version 8
# HyBi-13 reports version 13
if ver in ['7', '8', '13']:
self.version = "hybi-%02d" % int(ver)
else:
raise self.EClose('Unsupported protocol version %s' % ver)
key = h['Sec-WebSocket-Key']
# Choose binary if client supports it
if 'binary' in protocols:
self.base64 = False
elif 'base64' in protocols:
self.base64 = True
else:
raise self.EClose("Client must support 'binary' or 'base64' protocol")
# Generate the hash value for the accept header
accept = b64encode(sha1(s2b(key + self.GUID)).digest())
response = self.server_handshake_hybi % b2s(accept)
if self.base64:
response += "Sec-WebSocket-Protocol: base64\r\n"
else:
response += "Sec-WebSocket-Protocol: binary\r\n"
response += "\r\n"
else:
# Hixie version of the protocol (75 or 76)
if h.get('key3'):
trailer = self.gen_md5(h)
pre = "Sec-"
self.version = "hixie-76"
else:
trailer = ""
pre = ""
self.version = "hixie-75"
# We only support base64 in Hixie era
self.base64 = True
response = self.server_handshake_hixie % (pre,
h['Origin'], pre, self.scheme, h['Host'], path)
if 'base64' in protocols:
response += "%sWebSocket-Protocol: base64\r\n" % pre
else:
self.msg("Warning: client does not report 'base64' protocol support")
response += "\r\n" + trailer
return response
def do_handshake(self, sock, address):
"""
do_handshake does the following:
- Peek at the first few bytes from the socket.
- If the connection is Flash policy request then answer it,
close the socket and return.
- If the connection is an HTTPS/SSL/TLS connection then SSL
wrap the socket.
- Read from the (possibly wrapped) socket.
- If we have received a HTTP GET request and the webserver
functionality is enabled, answer it, close the socket and
return.
- Assume we have a WebSockets connection, parse the client
handshake data.
- Send a WebSockets handshake server response.
- Return the socket for this WebSocket client.
"""
stype = ""
ready = select.select([sock], [], [], 3)[0]
if not ready:
raise self.EClose("ignoring socket not ready")
# Peek, but do not read the data so that we have a opportunity
# to SSL wrap the socket first
handshake = sock.recv(1024, socket.MSG_PEEK)
#self.msg("Handshake [%s]" % handshake)
if handshake == "":
raise self.EClose("ignoring empty handshake")
elif handshake.startswith(s2b("<policy-file-request/>")):
# Answer Flash policy request
handshake = sock.recv(1024)
sock.send(s2b(self.policy_response))
raise self.EClose("Sending flash policy response")
elif handshake[0] in ("\x16", "\x80", 22, 128):
# SSL wrap the connection
if not ssl:
raise self.EClose("SSL connection but no 'ssl' module")
if not os.path.exists(self.cert):
raise self.EClose("SSL connection but '%s' not found"
% self.cert)
retsock = None
try:
retsock = ssl.wrap_socket(
sock,
server_side=True,
certfile=self.cert,
keyfile=self.key)
except ssl.SSLError:
_, x, _ = sys.exc_info()
if x.args[0] == ssl.SSL_ERROR_EOF:
if len(x.args) > 1:
raise self.EClose(x.args[1])
else:
raise self.EClose("Got SSL_ERROR_EOF")
else:
raise
self.scheme = "wss"
stype = "SSL/TLS (wss://)"
elif self.ssl_only:
raise self.EClose("non-SSL connection received but disallowed")
else:
retsock = sock
self.scheme = "ws"
stype = "Plain non-SSL (ws://)"
wsh = WSRequestHandler(retsock, address, not self.web)
if wsh.last_code == 101:
# Continue on to handle WebSocket upgrade
pass
elif wsh.last_code == 405:
raise self.EClose("Normal web request received but disallowed")
elif wsh.last_code < 200 or wsh.last_code >= 300:
raise self.EClose(wsh.last_message)
elif self.verbose:
raise self.EClose(wsh.last_message)
else:
raise self.EClose("")
response = self.do_websocket_handshake(wsh.headers, wsh.path)
self.msg("%s: %s WebSocket connection" % (address[0], stype))
self.msg("%s: Version %s, base64: '%s'" % (address[0],
self.version, self.base64))
if self.path != '/':
self.msg("%s: Path: '%s'" % (address[0], self.path))
# Send server WebSockets handshake response
#self.msg("sending response [%s]" % response)
retsock.send(s2b(response))
# Return the WebSockets socket which may be SSL wrapped
return retsock
#
# Events that can/should be overridden in sub-classes
#
def started(self):
""" Called after WebSockets startup """
self.vmsg("WebSockets server started")
def poll(self):
""" Run periodically while waiting for connections. """
#self.vmsg("Running poll()")
pass
def fallback_SIGCHLD(self, sig, stack):
# Reap zombies when using os.fork() (python 2.4)
self.vmsg("Got SIGCHLD, reaping zombies")
try:
result = os.waitpid(-1, os.WNOHANG)
while result[0]:
self.vmsg("Reaped child process %s" % result[0])
result = os.waitpid(-1, os.WNOHANG)
except (OSError):
pass
def do_SIGINT(self, sig, stack):
self.msg("Got SIGINT, exiting")
sys.exit(0)
def top_new_client(self, startsock, address):
""" Do something with a WebSockets client connection. """
# Initialize per client settings
self.send_parts = []
self.recv_part = None
self.base64 = False
self.rec = None
self.start_time = int(time.time()*1000)
# handler process
try:
try:
self.client = self.do_handshake(startsock, address)
if self.record:
# Record raw frame data as JavaScript array
fname = "%s.%s" % (self.record,
self.handler_id)
self.msg("opening record file: %s" % fname)
self.rec = open(fname, 'w+')
encoding = "binary"
if self.base64: encoding = "base64"
self.rec.write("var VNC_frame_encoding = '%s';\n"
% encoding)
self.rec.write("var VNC_frame_data = [\n")
self.ws_connection = True
self.new_client()
except self.CClose:
# Close the client
_, exc, _ = sys.exc_info()
if self.client:
self.send_close(exc.args[0], exc.args[1])
except self.EClose:
_, exc, _ = sys.exc_info()
# Connection was not a WebSockets connection
if exc.args[0]:
self.msg("%s: %s" % (address[0], exc.args[0]))
except Exception:
_, exc, _ = sys.exc_info()
self.msg("handler exception: %s" % str(exc))
if self.verbose:
self.msg(traceback.format_exc())
finally:
if self.rec:
self.rec.write("'EOF'];\n")
self.rec.close()
if self.client and self.client != startsock:
# Close the SSL wrapped socket
# Original socket closed by caller
self.client.close()
def new_client(self):
""" Do something with a WebSockets client connection. """
raise("WebSocketServer.new_client() must be overloaded")
def start_server(self):
"""
Daemonize if requested. Listen for for connections. Run
do_handshake() method for each connection. If the connection
is a WebSockets client then call new_client() method (which must
be overridden) for each new client connection.
"""
lsock = self.socket(self.listen_host, self.listen_port, False, self.prefer_ipv6)
if self.daemon:
self.daemonize(keepfd=lsock.fileno(), chdir=self.web)
self.started() # Some things need to happen after daemonizing
# Allow override of SIGINT
signal.signal(signal.SIGINT, self.do_SIGINT)
if not multiprocessing:
# os.fork() (python 2.4) child reaper
signal.signal(signal.SIGCHLD, self.fallback_SIGCHLD)
last_active_time = self.launch_time
while True:
try:
try:
self.client = None
startsock = None
pid = err = 0
child_count = 0
if multiprocessing and self.idle_timeout:
child_count = len(multiprocessing.active_children())
time_elapsed = time.time() - self.launch_time
if self.timeout and time_elapsed > self.timeout:
self.msg('listener exit due to --timeout %s'
% self.timeout)
break
if self.idle_timeout:
idle_time = 0
if child_count == 0:
idle_time = time.time() - last_active_time
else:
idle_time = 0
last_active_time = time.time()
if idle_time > self.idle_timeout and child_count == 0:
self.msg('listener exit due to --idle-timeout %s'
% self.idle_timeout)
break
try:
self.poll()
ready = select.select([lsock], [], [], 1)[0]
if lsock in ready:
startsock, address = lsock.accept()
else:
continue
except Exception:
_, exc, _ = sys.exc_info()
if hasattr(exc, 'errno'):
err = exc.errno
elif hasattr(exc, 'args'):
err = exc.args[0]
else:
err = exc[0]
if err == errno.EINTR:
self.vmsg("Ignoring interrupted syscall")
continue
else:
raise
if self.run_once:
# Run in same process if run_once
self.top_new_client(startsock, address)
if self.ws_connection :
self.msg('%s: exiting due to --run-once'
% address[0])
break
elif multiprocessing:
self.vmsg('%s: new handler Process' % address[0])
p = multiprocessing.Process(
target=self.top_new_client,
args=(startsock, address))
p.start()
# child will not return
else:
# python 2.4
self.vmsg('%s: forking handler' % address[0])
pid = os.fork()
if pid == 0:
# child handler process
self.top_new_client(startsock, address)
break # child process exits
# parent process
self.handler_id += 1
except KeyboardInterrupt:
_, exc, _ = sys.exc_info()
print("In KeyboardInterrupt")
pass
except SystemExit:
_, exc, _ = sys.exc_info()
print("In SystemExit")
break
except Exception:
_, exc, _ = sys.exc_info()
self.msg("handler exception: %s" % str(exc))
if self.verbose:
self.msg(traceback.format_exc())
finally:
if startsock:
startsock.close()
# HTTP handler with WebSocket upgrade support
class WSRequestHandler(SimpleHTTPRequestHandler):
def __init__(self, req, addr, only_upgrade=False):
self.only_upgrade = only_upgrade # only allow upgrades
SimpleHTTPRequestHandler.__init__(self, req, addr, object())
def do_GET(self):
if (self.headers.get('upgrade') and
self.headers.get('upgrade').lower() == 'websocket'):
if (self.headers.get('sec-websocket-key1') or
self.headers.get('websocket-key1')):
# For Hixie-76 read out the key hash
self.headers.__setitem__('key3', self.rfile.read(8))
# Just indicate that an WebSocket upgrade is needed
self.last_code = 101
self.last_message = "101 Switching Protocols"
elif self.only_upgrade:
# Normal web request responses are disabled
self.last_code = 405
self.last_message = "405 Method Not Allowed"
else:
SimpleHTTPRequestHandler.do_GET(self)
def send_response(self, code, message=None):
# Save the status code
self.last_code = code
SimpleHTTPRequestHandler.send_response(self, code, message)
def log_message(self, f, *args):
# Save instead of printing
self.last_message = f % args

Binary file not shown.

View File

@ -0,0 +1,390 @@
#!/usr/bin/env python
'''
A WebSocket to TCP socket proxy with support for "wss://" encryption.
Copyright 2011 Joel Martin
Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
You can make a cert/key with openssl using:
openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem
as taken from http://docs.python.org/dev/library/ssl.html#certificates
'''
import signal, socket, optparse, time, os, sys, subprocess
from select import select
import websocket
try: from urllib.parse import parse_qs, urlparse
except: from urlparse import parse_qs, urlparse
class WebSocketProxy(websocket.WebSocketServer):
"""
Proxy traffic to and from a WebSockets client to a normal TCP
socket server target. All traffic to/from the client is base64
encoded/decoded to allow binary data to be sent/received to/from
the target.
"""
buffer_size = 65536
traffic_legend = """
Traffic Legend:
} - Client receive
}. - Client receive partial
{ - Target receive
> - Target send
>. - Target send partial
< - Client send
<. - Client send partial
"""
def __init__(self, *args, **kwargs):
# Save off proxy specific options
self.target_host = kwargs.pop('target_host', None)
self.target_port = kwargs.pop('target_port', None)
self.wrap_cmd = kwargs.pop('wrap_cmd', None)
self.wrap_mode = kwargs.pop('wrap_mode', None)
self.unix_target = kwargs.pop('unix_target', None)
self.ssl_target = kwargs.pop('ssl_target', None)
self.target_cfg = kwargs.pop('target_cfg', None)
# Last 3 timestamps command was run
self.wrap_times = [0, 0, 0]
if self.wrap_cmd:
rebinder_path = ['./', os.path.dirname(sys.argv[0])]
self.rebinder = None
for rdir in rebinder_path:
rpath = os.path.join(rdir, "rebind.so")
if os.path.exists(rpath):
self.rebinder = rpath
break
if not self.rebinder:
raise Exception("rebind.so not found, perhaps you need to run make")
self.rebinder = os.path.abspath(self.rebinder)
self.target_host = "127.0.0.1" # Loopback
# Find a free high port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
self.target_port = sock.getsockname()[1]
sock.close()
os.environ.update({
"LD_PRELOAD": self.rebinder,
"REBIND_OLD_PORT": str(kwargs['listen_port']),
"REBIND_NEW_PORT": str(self.target_port)})
if self.target_cfg:
self.target_cfg = os.path.abspath(self.target_cfg)
websocket.WebSocketServer.__init__(self, *args, **kwargs)
def run_wrap_cmd(self):
print("Starting '%s'" % " ".join(self.wrap_cmd))
self.wrap_times.append(time.time())
self.wrap_times.pop(0)
self.cmd = subprocess.Popen(
self.wrap_cmd, env=os.environ, preexec_fn=_subprocess_setup)
self.spawn_message = True
def started(self):
"""
Called after Websockets server startup (i.e. after daemonize)
"""
# Need to call wrapped command after daemonization so we can
# know when the wrapped command exits
if self.wrap_cmd:
dst_string = "'%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
dst_string = self.unix_target
else:
dst_string = "%s:%s" % (self.target_host, self.target_port)
if self.target_cfg:
msg = " - proxying from %s:%s to targets in %s" % (
self.listen_host, self.listen_port, self.target_cfg)
else:
msg = " - proxying from %s:%s to %s" % (
self.listen_host, self.listen_port, dst_string)
if self.ssl_target:
msg += " (using SSL)"
print(msg + "\n")
if self.wrap_cmd:
self.run_wrap_cmd()
def poll(self):
# If we are wrapping a command, check it's status
if self.wrap_cmd and self.cmd:
ret = self.cmd.poll()
if ret != None:
self.vmsg("Wrapped command exited (or daemon). Returned %s" % ret)
self.cmd = None
if self.wrap_cmd and self.cmd == None:
# Response to wrapped command being gone
if self.wrap_mode == "ignore":
pass
elif self.wrap_mode == "exit":
sys.exit(ret)
elif self.wrap_mode == "respawn":
now = time.time()
avg = sum(self.wrap_times)/len(self.wrap_times)
if (now - avg) < 10:
# 3 times in the last 10 seconds
if self.spawn_message:
print("Command respawning too fast")
self.spawn_message = False
else:
self.run_wrap_cmd()
#
# Routines above this point are run in the master listener
# process.
#
#
# Routines below this point are connection handler routines and
# will be run in a separate forked process for each connection.
#
def new_client(self):
"""
Called after a new WebSocket connection has been established.
"""
# Checks if we receive a token, and look
# for a valid target for it then
if self.target_cfg:
(self.target_host, self.target_port) = self.get_target(self.target_cfg, self.path)
# Connect to the target
if self.wrap_cmd:
msg = "connecting to command: '%s' (port %s)" % (" ".join(self.wrap_cmd), self.target_port)
elif self.unix_target:
msg = "connecting to unix socket: %s" % self.unix_target
else:
msg = "connecting to: %s:%s" % (
self.target_host, self.target_port)
if self.ssl_target:
msg += " (using SSL)"
self.msg(msg)
tsock = self.socket(self.target_host, self.target_port,
connect=True, use_ssl=self.ssl_target, unix_socket=self.unix_target)
if self.verbose and not self.daemon:
print(self.traffic_legend)
# Start proxying
try:
self.do_proxy(tsock)
except:
if tsock:
tsock.shutdown(socket.SHUT_RDWR)
tsock.close()
self.vmsg("%s:%s: Closed target" %(
self.target_host, self.target_port))
raise
def get_target(self, target_cfg, path):
"""
Parses the path, extracts a token, and looks for a valid
target for that token in the configuration file(s). Sets
target_host and target_port if successful
"""
# The files in targets contain the lines
# in the form of token: host:port
# Extract the token parameter from url
args = parse_qs(urlparse(path)[4]) # 4 is the query from url
if not len(args['token']):
raise self.EClose("Token not present")
token = args['token'][0].rstrip('\n')
# target_cfg can be a single config file or directory of
# config files
if os.path.isdir(target_cfg):
cfg_files = [os.path.join(target_cfg, f)
for f in os.listdir(target_cfg)]
else:
cfg_files = [target_cfg]
targets = {}
for f in cfg_files:
for line in [l.strip() for l in file(f).readlines()]:
if line and not line.startswith('#'):
ttoken, target = line.split(': ')
targets[ttoken] = target.strip()
self.vmsg("Target config: %s" % repr(targets))
if targets.has_key(token):
return targets[token].split(':')
else:
raise self.EClose("Token '%s' not found" % token)
def do_proxy(self, target):
"""
Proxy client WebSocket to normal target socket.
"""
cqueue = []
c_pend = 0
tqueue = []
rlist = [self.client, target]
while True:
wlist = []
if tqueue: wlist.append(target)
if cqueue or c_pend: wlist.append(self.client)
ins, outs, excepts = select(rlist, wlist, [], 1)
if excepts: raise Exception("Socket exception")
if target in outs:
# Send queued client data to the target
dat = tqueue.pop(0)
sent = target.send(dat)
if sent == len(dat):
self.traffic(">")
else:
# requeue the remaining data
tqueue.insert(0, dat[sent:])
self.traffic(".>")
if target in ins:
# Receive target data, encode it and queue for client
buf = target.recv(self.buffer_size)
if len(buf) == 0:
self.vmsg("%s:%s: Target closed connection" %(
self.target_host, self.target_port))
raise self.CClose(1000, "Target closed")
cqueue.append(buf)
self.traffic("{")
if self.client in outs:
# Send queued target data to the client
c_pend = self.send_frames(cqueue)
cqueue = []
if self.client in ins:
# Receive client data, decode it, and queue for target
bufs, closed = self.recv_frames()
tqueue.extend(bufs)
if closed:
# TODO: What about blocking on client socket?
self.vmsg("%s:%s: Client closed connection" %(
self.target_host, self.target_port))
raise self.CClose(closed['code'], closed['reason'])
def _subprocess_setup():
# Python installs a SIGPIPE handler by default. This is usually not what
# non-Python successfulbprocesses expect.
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
def websockify_init():
usage = "\n %prog [options]"
usage += " [source_addr:]source_port [target_addr:target_port]"
usage += "\n %prog [options]"
usage += " [source_addr:]source_port -- WRAP_COMMAND_LINE"
parser = optparse.OptionParser(usage=usage)
parser.add_option("--verbose", "-v", action="store_true",
help="verbose messages and per frame traffic")
parser.add_option("--record",
help="record sessions to FILE.[session_number]", metavar="FILE")
parser.add_option("--daemon", "-D",
dest="daemon", action="store_true",
help="become a daemon (background process)")
parser.add_option("--run-once", action="store_true",
help="handle a single WebSocket connection and exit")
parser.add_option("--timeout", type=int, default=0,
help="after TIMEOUT seconds exit when not connected")
parser.add_option("--idle-timeout", type=int, default=0,
help="server exits after TIMEOUT seconds if there are no "
"active connections")
parser.add_option("--cert", default="self.pem",
help="SSL certificate file")
parser.add_option("--key", default=None,
help="SSL key file (if separate from cert)")
parser.add_option("--ssl-only", action="store_true",
help="disallow non-encrypted client connections")
parser.add_option("--ssl-target", action="store_true",
help="connect to SSL target as SSL client")
parser.add_option("--unix-target",
help="connect to unix socket target", metavar="FILE")
parser.add_option("--web", default=None, metavar="DIR",
help="run webserver on same port. Serve files from DIR.")
parser.add_option("--wrap-mode", default="exit", metavar="MODE",
choices=["exit", "ignore", "respawn"],
help="action to take when the wrapped program exits "
"or daemonizes: exit (default), ignore, respawn")
parser.add_option("--prefer-ipv6", "-6",
action="store_true", dest="source_is_ipv6",
help="prefer IPv6 when resolving source_addr")
parser.add_option("--target-config", metavar="FILE",
dest="target_cfg",
help="Configuration file containing valid targets "
"in the form 'token: host:port' or, alternatively, a "
"directory containing configuration files of this form")
(opts, args) = parser.parse_args()
# Sanity checks
if len(args) < 2 and not (opts.target_cfg or opts.unix_target):
parser.error("Too few arguments")
if sys.argv.count('--'):
opts.wrap_cmd = args[1:]
else:
opts.wrap_cmd = None
if len(args) > 2:
parser.error("Too many arguments")
if not websocket.ssl and opts.ssl_target:
parser.error("SSL target requested and Python SSL module not loaded.");
if opts.ssl_only and not os.path.exists(opts.cert):
parser.error("SSL only and %s not found" % opts.cert)
# Parse host:port and convert ports to numbers
if args[0].count(':') > 0:
opts.listen_host, opts.listen_port = args[0].rsplit(':', 1)
opts.listen_host = opts.listen_host.strip('[]')
else:
opts.listen_host, opts.listen_port = '', args[0]
try: opts.listen_port = int(opts.listen_port)
except: parser.error("Error parsing listen port")
if opts.wrap_cmd or opts.unix_target or opts.target_cfg:
opts.target_host = None
opts.target_port = None
else:
if args[1].count(':') > 0:
opts.target_host, opts.target_port = args[1].rsplit(':', 1)
opts.target_host = opts.target_host.strip('[]')
else:
parser.error("Error parsing target")
try: opts.target_port = int(opts.target_port)
except: parser.error("Error parsing target port")
# Create and start the WebSockets proxy
server = WebSocketProxy(**opts.__dict__)
server.start_server()
if __name__ == '__main__':
websockify_init()

1
share/websockify/websockify Symbolic link
View File

@ -0,0 +1 @@
websocketproxy.py

View File

@ -83,7 +83,7 @@
# vnc_proxy_cert: Certificate to encrypt wss connections.
# vnc_proxy_key: Key for wss connections. Only necessary if not included in cert.
:vnc_proxy_port: 29876
:vnc_proxy_path:
:vnc_proxy_path: /usr/share/one/websockify/websocketproxy.py
:vnc_proxy_support_wss: no
:vnc_proxy_cert:
:vnc_proxy_key:

View File

@ -0,0 +1,78 @@
noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
The noVNC core library is licensed under the LGPLv3 (GNU Lesser
General Public License). The noVNC core library is composed of the
Javascript code necessary for full noVNC operation. This includes (but
is not limited to):
include/base64.js
include/des.js
include/display.js
include/input.js
include/jsunzip.js
include/logo.js
include/rfb.js
include/ui.js
include/util.js
include/vnc.js
include/websock.js
include/webutil.js
The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the
noVNC core library and are licensed under more permissive licenses.
The intent is to allow easy integration of noVNC into existing web
sites and web applications.
The HTML, CSS, font and image files are licensed as follows:
*.html : 2-Clause BSD license
include/*.css : 2-Clause BSD license
include/Orbitron* : SIL Open Font License 1.1
(Copyright 2009 Matt McInerney)
images/ : Creative Commons Attribution-ShareAlike
http://creativecommons.org/licenses/by-sa/3.0/
In addition the following file, which is part of the noVNC core
library, may be licensed under either the LGPL-2, LGPL-3 or MPL 2.0
when it used separately from the noVNC core library.
include/input.js : LGPL-2 or any later version
Some portions of noVNC are copyright to their individual authors.
Please refer to the individual source files and/or to the noVNC commit
history: https://github.com/kanaka/noVNC/commits/master
The are several files and projects that have been incorporated into
the noVNC core library. Here is a list of those files and the original
licenses (all LGPL-3 compatible):
include/base64.js : MPL 1.1, GPL-2 or LGPL-2.1
include/des.js : Various BSD style licenses
include/jsunzip.js : zlib/libpng license
include/web-socket-js/ : New BSD license (3-clause). Source code at
http://github.com/gimite/web-socket-js
The following license texts are included:
docs/LICENSE.LGPL-3 and
docs/LICENSE.GPL-3
docs/LICENSE.OFL-1.1
docs/LICENSE.BSD-3-Clause (New BSD)
docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD)
docs/LICENSE.zlib
docs/LICENSE.MPL-2.0
Or alternatively the license texts may be found here:
http://www.gnu.org/licenses/lgpl.html and
http://www.gnu.org/licenses/gpl.html
http://scripts.sil.org/OFL
http://www.mozilla.org/MPL/1.1/
http://www.mozilla.org/MPL/2.0/

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,405 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
body {
margin:0;
padding:0;
font-family: Helvetica;
/*Background image with light grey curve.*/
background-color:#494949;
background-repeat:no-repeat;
background-position:right bottom;
height:100%;
}
html {
height:100%;
}
#noVNC_controls ul {
list-style: none;
margin: 0px;
padding: 0px;
}
#noVNC_controls li {
padding-bottom:8px;
}
#noVNC_host {
width:150px;
}
#noVNC_port {
width: 80px;
}
#noVNC_password {
width: 150px;
}
#noVNC_encrypt {
}
#noVNC_connectTimeout {
width: 30px;
}
#noVNC_path {
width: 100px;
}
#noVNC_connect_button {
width: 110px;
float:right;
}
#noVNC_view_drag_button {
display: none;
}
#sendCtrlAltDelButton {
display: none;
}
#noVNC_mobile_buttons {
display: none;
}
.noVNC-buttons-left {
float: left;
padding-left:10px;
padding-top:4px;
}
.noVNC-buttons-right {
float:right;
right: 0px;
padding-right:10px;
padding-top:4px;
}
#noVNC_status_bar {
margin-top: 0px;
padding: 0px;
}
#noVNC_status_bar div {
font-size: 12px;
padding-top: 4px;
width:100%;
}
#noVNC_status {
height:20px;
text-align: center;
}
#noVNC_settings_menu {
margin: 3px;
text-align: left;
}
#noVNC_settings_menu ul {
list-style: none;
margin: 0px;
padding: 0px;
}
#noVNC_apply {
float:right;
}
.noVNC_status_normal {
background: #eee;
}
.noVNC_status_error {
background: #f44;
}
.noVNC_status_warn {
background: #ff4;
}
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */
#noVNC_screen_pad {
margin: 0px;
padding: 0px;
height: 44px;
}
#noVNC_screen {
text-align: center;
display: table;
width:100%;
height:100%;
background-color:#313131;
border-bottom-right-radius: 800px 600px;
/*border-top-left-radius: 800px 600px;*/
}
#noVNC_container, #noVNC_canvas {
margin: 0px;
padding: 0px;
}
#noVNC_canvas {
left: 0px;
}
#VNC_clipboard_clear_button {
float:right;
}
#VNC_clipboard_text {
font-size: 11px;
}
#noVNC_clipboard_clear_button {
float:right;
}
/*Bubble contents divs*/
#noVNC_settings {
display:none;
margin-top:77px;
right:20px;
position:fixed;
}
#noVNC_controls {
display:none;
margin-top:77px;
right:12px;
position:fixed;
}
#noVNC_controls.top:after {
right:15px;
}
#noVNC_description {
display:none;
position:fixed;
margin-top:77px;
right:20px;
left:20px;
padding:15px;
color:#000;
background:#eee; /* default background for browsers without gradient support */
border:2px solid #E0E0E0;
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
}
#noVNC_clipboard {
display:none;
margin-top:77px;
right:30px;
position:fixed;
}
#noVNC_clipboard.top:after {
right:85px;
}
#keyboardinput {
width:1px;
height:1px;
background-color:#fff;
color:#fff;
border:0;
position: relative;
left: -40px;
z-index: -1;
}
.noVNC_status_warn {
background-color:yellow;
}
/*
* Advanced Styling
*/
/* Control bar */
#noVNC-control-bar {
position:fixed;
background: #b2bdcd; /* Old browsers */
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
display:block;
height:44px;
left:0;
top:0;
width:100%;
z-index:200;
}
.noVNC_status_button {
padding: 4px 4px;
vertical-align: middle;
border:1px solid #869dbc;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
background: #b2bdcd; /* Old browsers */
background: -moz-linear-gradient(top, #b2bdcd 0%, #899cb3 49%, #7e93af 51%, #6e84a3 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2bdcd), color-stop(49%,#899cb3), color-stop(51%,#7e93af), color-stop(100%,#6e84a3)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b2bdcd', endColorstr='#6e84a3',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #b2bdcd 0%,#899cb3 49%,#7e93af 51%,#6e84a3 100%); /* W3C */
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
.noVNC_status_button_selected {
padding: 4px 4px;
vertical-align: middle;
border:1px solid #4366a9;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
background: #779ced; /* Old browsers */
background: -moz-linear-gradient(top, #779ced 0%, #3970e0 49%, #2160dd 51%, #2463df 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#779ced), color-stop(49%,#3970e0), color-stop(51%,#2160dd), color-stop(100%,#2463df)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#779ced', endColorstr='#2463df',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #779ced 0%,#3970e0 49%,#2160dd 51%,#2463df 100%); /* W3C */
/*box-shadow:inset 0.4px 0.4px 0.4px #000000;*/
}
/*Settings Bubble*/
.triangle-right {
position:relative;
padding:15px;
margin:1em 0 3em;
color:#fff;
background:#fff; /* default background for browsers without gradient support */
/* css3 */
/*background:-webkit-gradient(linear, 0 0, 0 100%, from(#2e88c4), to(#075698));
background:-moz-linear-gradient(#2e88c4, #075698);
background:-o-linear-gradient(#2e88c4, #075698);
background:linear-gradient(#2e88c4, #075698);*/
-webkit-border-radius:10px;
-moz-border-radius:10px;
border-radius:10px;
color:#000;
border:2px solid #E0E0E0;
}
.triangle-right.top:after {
border-color: transparent #E0E0E0;
border-width: 20px 20px 0 0;
bottom: auto;
left: auto;
right: 50px;
top: -20px;
}
.triangle-right:after {
content:"";
position:absolute;
bottom:-20px; /* value = - border-top-width - border-bottom-width */
left:50px; /* controls horizontal position */
border-width:20px 0 0 20px; /* vary these values to change the angle of the vertex */
border-style:solid;
border-color:#E0E0E0 transparent;
/* reduce the damage in FF3.0 */
display:block;
width:0;
}
.triangle-right.top:after {
top:-40px; /* value = - border-top-width - border-bottom-width */
right:50px; /* controls horizontal position */
bottom:auto;
left:auto;
border-width:40px 40px 0 0; /* vary these values to change the angle of the vertex */
border-color:transparent #E0E0E0;
}
/*Default noVNC logo.*/
/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */
@font-face {
font-family: 'Orbitron';
font-style: normal;
font-weight: 700;
src: local('?'), url('Orbitron700.woff') format('woff'),
url('Orbitron700.ttf') format('truetype');
}
#noVNC_logo {
margin-top: 170px;
margin-left: 10px;
color:yellow;
text-align:left;
font-family: 'Orbitron', 'OrbitronTTF', sans-serif;
line-height:90%;
text-shadow:
5px 5px 0 #000,
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
#noVNC_logo span{
color:green;
}
/* ----------------------------------------
* Media sizing
* ----------------------------------------
*/
.noVNC_status_button {
font-size: 12px;
}
#noVNC_clipboard_text {
width: 500px;
}
#noVNC_logo {
font-size: 180px;
}
@media screen and (min-width: 481px) and (max-width: 640px) {
.noVNC_status_button {
font-size: 10px;
}
#noVNC_clipboard_text {
width: 410px;
}
#noVNC_logo {
font-size: 150px;
}
}
@media screen and (min-width: 321px) and (max-width: 480px) {
.noVNC_status_button {
font-size: 10px;
}
#noVNC_clipboard_text {
width: 250px;
}
#noVNC_logo {
font-size: 110px;
}
}
@media screen and (max-width: 320px) {
.noVNC_status_button {
font-size: 9px;
}
#noVNC_clipboard_text {
width: 220px;
}
#noVNC_logo {
font-size: 90px;
}
}

View File

@ -0,0 +1,147 @@
/*
* Modified from:
* http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla XML-RPC Client component.
*
* The Initial Developer of the Original Code is
* Digital Creations 2, Inc.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Martijn Pieters <mj@digicool.com> (original author)
* Samuel Sieb <samuel@sieb.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*jslint white: false, bitwise: false, plusplus: false */
/*global console */
var Base64 = {
/* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
base64Pad : '=',
encode: function (data) {
"use strict";
var result = '',
chrTable = Base64.toBase64Table.split(''),
pad = Base64.base64Pad,
length = data.length,
i;
// Convert every three bytes to 4 ascii characters.
for (i = 0; i < (length - 2); i += 3) {
result += chrTable[data[i] >> 2];
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += chrTable[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
result += chrTable[data[i+2] & 0x3f];
}
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
if (length%3) {
i = length - (length%3);
result += chrTable[data[i] >> 2];
if ((length%3) === 2) {
result += chrTable[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
result += chrTable[(data[i+1] & 0x0f) << 2];
result += pad;
} else {
result += chrTable[(data[i] & 0x03) << 4];
result += pad + pad;
}
}
return result;
},
/* Convert Base64 data to a string */
toBinaryTable : [
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
],
decode: function (data, offset) {
"use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0;
var binTable = Base64.toBinaryTable,
pad = Base64.base64Pad,
result, result_length, idx, i, c, padding,
leftbits = 0, // number of bits decoded, but yet to be appended
leftdata = 0, // bits decoded, but yet to be appended
data_length = data.indexOf('=') - offset;
if (data_length < 0) { data_length = data.length - offset; }
/* Every four characters is 3 resulting numbers */
result_length = (data_length >> 2) * 3 + Math.floor((data_length%4)/1.5);
result = new Array(result_length);
// Convert one by one.
for (idx = 0, i = offset; i < data.length; i++) {
c = binTable[data.charCodeAt(i) & 0x7f];
padding = (data.charAt(i) === pad);
// Skip illegal characters and whitespace
if (c === -1) {
console.error("Illegal character code " + data.charCodeAt(i) + " at position " + i);
continue;
}
// Collect data into leftdata, update bitcount
leftdata = (leftdata << 6) | c;
leftbits += 6;
// If we have 8 or more bits, append 8 bits to the result
if (leftbits >= 8) {
leftbits -= 8;
// Append if not padding.
if (!padding) {
result[idx++] = (leftdata >> leftbits) & 0xff;
}
leftdata &= (1 << leftbits) - 1;
}
}
// If there are any bits left, the base64 string was corrupted
if (leftbits) {
throw {name: 'Base64-Error',
message: 'Corrupted base64 string'};
}
return result;
}
}; /* End of Base64 namespace */

View File

@ -0,0 +1,52 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
#keyboardinput {
background-color:#000;
}
#noVNC-control-bar {
background: #4c4c4c; /* Old browsers */
background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
}
.triangle-right {
border:2px solid #fff;
background:#000;
color:#fff;
}
.noVNC_status_button {
font-size: 12px;
vertical-align: middle;
border:1px solid #4c4c4c;
background: #4c4c4c; /* Old browsers */
background: -moz-linear-gradient(top, #4c4c4c 0%, #2c2c2c 50%, #000000 51%, #131313 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(100%,#131313)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #4c4c4c 0%,#2c2c2c 50%,#000000 51%,#131313 100%); /* W3C */
}
.noVNC_status_button_selected {
background: #9dd53a; /* Old browsers */
background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
}

View File

@ -0,0 +1,33 @@
/*
* noVNC base CSS
* Copyright (C) 2012 Joel Martin
* noVNC is licensed under the LGPL-3 (see LICENSE.txt)
* This file is licensed under the 2-Clause BSD license (see LICENSE.txt).
*/
#noVNC-control-bar {
background-color:#04073d;
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0.54, rgb(10,15,79)),
color-stop(0.5, rgb(4,7,61))
);
background-image: -moz-linear-gradient(
center bottom,
rgb(10,15,79) 54%,
rgb(4,7,61) 50%
);
}
.triangle-right {
border:2px solid #fff;
background:#04073d;
color:#fff;
}
#keyboardinput {
background-color:#04073d;
}

273
src/sunstone/public/vendor/noVNC/des.js vendored Normal file
View File

@ -0,0 +1,273 @@
/*
* Ported from Flashlight VNC ActionScript implementation:
* http://www.wizhelp.com/flashlight-vnc/
*
* Full attribution follows:
*
* -------------------------------------------------------------------------
*
* This DES class has been extracted from package Acme.Crypto for use in VNC.
* The unnecessary odd parity code has been removed.
*
* These changes are:
* Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* DesCipher - the DES encryption method
*
* The meat of this code is by Dave Zimmerman <dzimm@widget.com>, and is:
*
* Copyright (c) 1996 Widget Workshop, Inc. All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
* without fee is hereby granted, provided that this copyright notice is kept
* intact.
*
* WIDGET WORKSHOP MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY
* OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. WIDGET WORKSHOP SHALL NOT BE LIABLE
* FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*
* THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
* CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
* PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
* NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
* SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
* SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
* PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES"). WIDGET WORKSHOP
* SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
* HIGH RISK ACTIVITIES.
*
*
* The rest is:
*
* Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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.
*
* Visit the ACME Labs Java page for up-to-date versions of this and other
* fine Java utilities: http://www.acme.com/java/
*/
"use strict";
/*jslint white: false, bitwise: false, plusplus: false */
function DES(passwd) {
// Tables, permutations, S-boxes, etc.
var PC2 = [13,16,10,23, 0, 4, 2,27,14, 5,20, 9,22,18,11, 3,
25, 7,15, 6,26,19,12, 1,40,51,30,36,46,54,29,39,
50,44,32,47,43,48,38,55,33,52,45,41,49,35,28,31 ],
totrot = [ 1, 2, 4, 6, 8,10,12,14,15,17,19,21,23,25,27,28],
z = 0x0, a,b,c,d,e,f, SP1,SP2,SP3,SP4,SP5,SP6,SP7,SP8,
keys = [];
a=1<<16; b=1<<24; c=a|b; d=1<<2; e=1<<10; f=d|e;
SP1 = [c|e,z|z,a|z,c|f,c|d,a|f,z|d,a|z,z|e,c|e,c|f,z|e,b|f,c|d,b|z,z|d,
z|f,b|e,b|e,a|e,a|e,c|z,c|z,b|f,a|d,b|d,b|d,a|d,z|z,z|f,a|f,b|z,
a|z,c|f,z|d,c|z,c|e,b|z,b|z,z|e,c|d,a|z,a|e,b|d,z|e,z|d,b|f,a|f,
c|f,a|d,c|z,b|f,b|d,z|f,a|f,c|e,z|f,b|e,b|e,z|z,a|d,a|e,z|z,c|d];
a=1<<20; b=1<<31; c=a|b; d=1<<5; e=1<<15; f=d|e;
SP2 = [c|f,b|e,z|e,a|f,a|z,z|d,c|d,b|f,b|d,c|f,c|e,b|z,b|e,a|z,z|d,c|d,
a|e,a|d,b|f,z|z,b|z,z|e,a|f,c|z,a|d,b|d,z|z,a|e,z|f,c|e,c|z,z|f,
z|z,a|f,c|d,a|z,b|f,c|z,c|e,z|e,c|z,b|e,z|d,c|f,a|f,z|d,z|e,b|z,
z|f,c|e,a|z,b|d,a|d,b|f,b|d,a|d,a|e,z|z,b|e,z|f,b|z,c|d,c|f,a|e];
a=1<<17; b=1<<27; c=a|b; d=1<<3; e=1<<9; f=d|e;
SP3 = [z|f,c|e,z|z,c|d,b|e,z|z,a|f,b|e,a|d,b|d,b|d,a|z,c|f,a|d,c|z,z|f,
b|z,z|d,c|e,z|e,a|e,c|z,c|d,a|f,b|f,a|e,a|z,b|f,z|d,c|f,z|e,b|z,
c|e,b|z,a|d,z|f,a|z,c|e,b|e,z|z,z|e,a|d,c|f,b|e,b|d,z|e,z|z,c|d,
b|f,a|z,b|z,c|f,z|d,a|f,a|e,b|d,c|z,b|f,z|f,c|z,a|f,z|d,c|d,a|e];
a=1<<13; b=1<<23; c=a|b; d=1<<0; e=1<<7; f=d|e;
SP4 = [c|d,a|f,a|f,z|e,c|e,b|f,b|d,a|d,z|z,c|z,c|z,c|f,z|f,z|z,b|e,b|d,
z|d,a|z,b|z,c|d,z|e,b|z,a|d,a|e,b|f,z|d,a|e,b|e,a|z,c|e,c|f,z|f,
b|e,b|d,c|z,c|f,z|f,z|z,z|z,c|z,a|e,b|e,b|f,z|d,c|d,a|f,a|f,z|e,
c|f,z|f,z|d,a|z,b|d,a|d,c|e,b|f,a|d,a|e,b|z,c|d,z|e,b|z,a|z,c|e];
a=1<<25; b=1<<30; c=a|b; d=1<<8; e=1<<19; f=d|e;
SP5 = [z|d,a|f,a|e,c|d,z|e,z|d,b|z,a|e,b|f,z|e,a|d,b|f,c|d,c|e,z|f,b|z,
a|z,b|e,b|e,z|z,b|d,c|f,c|f,a|d,c|e,b|d,z|z,c|z,a|f,a|z,c|z,z|f,
z|e,c|d,z|d,a|z,b|z,a|e,c|d,b|f,a|d,b|z,c|e,a|f,b|f,z|d,a|z,c|e,
c|f,z|f,c|z,c|f,a|e,z|z,b|e,c|z,z|f,a|d,b|d,z|e,z|z,b|e,a|f,b|d];
a=1<<22; b=1<<29; c=a|b; d=1<<4; e=1<<14; f=d|e;
SP6 = [b|d,c|z,z|e,c|f,c|z,z|d,c|f,a|z,b|e,a|f,a|z,b|d,a|d,b|e,b|z,z|f,
z|z,a|d,b|f,z|e,a|e,b|f,z|d,c|d,c|d,z|z,a|f,c|e,z|f,a|e,c|e,b|z,
b|e,z|d,c|d,a|e,c|f,a|z,z|f,b|d,a|z,b|e,b|z,z|f,b|d,c|f,a|e,c|z,
a|f,c|e,z|z,c|d,z|d,z|e,c|z,a|f,z|e,a|d,b|f,z|z,c|e,b|z,a|d,b|f];
a=1<<21; b=1<<26; c=a|b; d=1<<1; e=1<<11; f=d|e;
SP7 = [a|z,c|d,b|f,z|z,z|e,b|f,a|f,c|e,c|f,a|z,z|z,b|d,z|d,b|z,c|d,z|f,
b|e,a|f,a|d,b|e,b|d,c|z,c|e,a|d,c|z,z|e,z|f,c|f,a|e,z|d,b|z,a|e,
b|z,a|e,a|z,b|f,b|f,c|d,c|d,z|d,a|d,b|z,b|e,a|z,c|e,z|f,a|f,c|e,
z|f,b|d,c|f,c|z,a|e,z|z,z|d,c|f,z|z,a|f,c|z,z|e,b|d,b|e,z|e,a|d];
a=1<<18; b=1<<28; c=a|b; d=1<<6; e=1<<12; f=d|e;
SP8 = [b|f,z|e,a|z,c|f,b|z,b|f,z|d,b|z,a|d,c|z,c|f,a|e,c|e,a|f,z|e,z|d,
c|z,b|d,b|e,z|f,a|e,a|d,c|d,c|e,z|f,z|z,z|z,c|d,b|d,b|e,a|f,a|z,
a|f,a|z,c|e,z|e,z|d,c|d,z|e,a|f,b|e,z|d,b|d,c|z,c|d,b|z,a|z,b|f,
z|z,c|f,a|d,b|d,c|z,b|e,b|f,z|z,c|f,a|e,a|e,z|f,z|f,a|d,b|z,c|e];
// Set the key.
function setKeys(keyBlock) {
var i, j, l, m, n, o, pc1m = [], pcr = [], kn = [],
raw0, raw1, rawi, KnLi;
for (j = 0, l = 56; j < 56; ++j, l-=8) {
l += l<-5 ? 65 : l<-3 ? 31 : l<-1 ? 63 : l===27 ? 35 : 0; // PC1
m = l & 0x7;
pc1m[j] = ((keyBlock[l >>> 3] & (1<<m)) !== 0) ? 1: 0;
}
for (i = 0; i < 16; ++i) {
m = i << 1;
n = m + 1;
kn[m] = kn[n] = 0;
for (o=28; o<59; o+=28) {
for (j = o-28; j < o; ++j) {
l = j + totrot[i];
if (l < o) {
pcr[j] = pc1m[l];
} else {
pcr[j] = pc1m[l - 28];
}
}
}
for (j = 0; j < 24; ++j) {
if (pcr[PC2[j]] !== 0) {
kn[m] |= 1<<(23-j);
}
if (pcr[PC2[j + 24]] !== 0) {
kn[n] |= 1<<(23-j);
}
}
}
// cookey
for (i = 0, rawi = 0, KnLi = 0; i < 16; ++i) {
raw0 = kn[rawi++];
raw1 = kn[rawi++];
keys[KnLi] = (raw0 & 0x00fc0000) << 6;
keys[KnLi] |= (raw0 & 0x00000fc0) << 10;
keys[KnLi] |= (raw1 & 0x00fc0000) >>> 10;
keys[KnLi] |= (raw1 & 0x00000fc0) >>> 6;
++KnLi;
keys[KnLi] = (raw0 & 0x0003f000) << 12;
keys[KnLi] |= (raw0 & 0x0000003f) << 16;
keys[KnLi] |= (raw1 & 0x0003f000) >>> 4;
keys[KnLi] |= (raw1 & 0x0000003f);
++KnLi;
}
}
// Encrypt 8 bytes of text
function enc8(text) {
var i = 0, b = text.slice(), fval, keysi = 0,
l, r, x; // left, right, accumulator
// Squash 8 bytes to 2 ints
l = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
r = b[i++]<<24 | b[i++]<<16 | b[i++]<<8 | b[i++];
x = ((l >>> 4) ^ r) & 0x0f0f0f0f;
r ^= x;
l ^= (x << 4);
x = ((l >>> 16) ^ r) & 0x0000ffff;
r ^= x;
l ^= (x << 16);
x = ((r >>> 2) ^ l) & 0x33333333;
l ^= x;
r ^= (x << 2);
x = ((r >>> 8) ^ l) & 0x00ff00ff;
l ^= x;
r ^= (x << 8);
r = (r << 1) | ((r >>> 31) & 1);
x = (l ^ r) & 0xaaaaaaaa;
l ^= x;
r ^= x;
l = (l << 1) | ((l >>> 31) & 1);
for (i = 0; i < 8; ++i) {
x = (r << 28) | (r >>> 4);
x ^= keys[keysi++];
fval = SP7[x & 0x3f];
fval |= SP5[(x >>> 8) & 0x3f];
fval |= SP3[(x >>> 16) & 0x3f];
fval |= SP1[(x >>> 24) & 0x3f];
x = r ^ keys[keysi++];
fval |= SP8[x & 0x3f];
fval |= SP6[(x >>> 8) & 0x3f];
fval |= SP4[(x >>> 16) & 0x3f];
fval |= SP2[(x >>> 24) & 0x3f];
l ^= fval;
x = (l << 28) | (l >>> 4);
x ^= keys[keysi++];
fval = SP7[x & 0x3f];
fval |= SP5[(x >>> 8) & 0x3f];
fval |= SP3[(x >>> 16) & 0x3f];
fval |= SP1[(x >>> 24) & 0x3f];
x = l ^ keys[keysi++];
fval |= SP8[x & 0x0000003f];
fval |= SP6[(x >>> 8) & 0x3f];
fval |= SP4[(x >>> 16) & 0x3f];
fval |= SP2[(x >>> 24) & 0x3f];
r ^= fval;
}
r = (r << 31) | (r >>> 1);
x = (l ^ r) & 0xaaaaaaaa;
l ^= x;
r ^= x;
l = (l << 31) | (l >>> 1);
x = ((l >>> 8) ^ r) & 0x00ff00ff;
r ^= x;
l ^= (x << 8);
x = ((l >>> 2) ^ r) & 0x33333333;
r ^= x;
l ^= (x << 2);
x = ((r >>> 16) ^ l) & 0x0000ffff;
l ^= x;
r ^= (x << 16);
x = ((r >>> 4) ^ l) & 0x0f0f0f0f;
l ^= x;
r ^= (x << 4);
// Spread ints to bytes
x = [r, l];
for (i = 0; i < 8; i++) {
b[i] = (x[i>>>2] >>> (8*(3 - (i%4)))) % 256;
if (b[i] < 0) { b[i] += 256; } // unsigned
}
return b;
}
// Encrypt 16 bytes of text using passwd as key
function encrypt(t) {
return enc8(t.slice(0,8)).concat(enc8(t.slice(8,16)));
}
setKeys(passwd); // Setup keys
return {'encrypt': encrypt}; // Public interface
} // function DES

View File

@ -0,0 +1,757 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*jslint browser: true, white: false, bitwise: false */
/*global Util, Base64, changeCursor */
function Display(defaults) {
"use strict";
var that = {}, // Public API methods
conf = {}, // Configuration attributes
// Private Display namespace variables
c_ctx = null,
c_forceCanvas = false,
// Queued drawing actions for in-order rendering
renderQ = [],
// Predefine function variables (jslint)
imageDataGet, rgbImageData, bgrxImageData, cmapImageData,
setFillColor, rescale, scan_renderQ,
// The full frame buffer (logical canvas) size
fb_width = 0,
fb_height = 0,
// The visible "physical canvas" viewport
viewport = {'x': 0, 'y': 0, 'w' : 0, 'h' : 0 },
cleanRect = {'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1},
c_prevStyle = "",
tile = null,
tile16x16 = null,
tile_x = 0,
tile_y = 0;
// Configuration attributes
Util.conf_defaults(conf, that, defaults, [
['target', 'wo', 'dom', null, 'Canvas element for rendering'],
['context', 'ro', 'raw', null, 'Canvas 2D context for rendering (read-only)'],
['logo', 'rw', 'raw', null, 'Logo to display when cleared: {"width": width, "height": height, "data": data}'],
['true_color', 'rw', 'bool', true, 'Use true-color pixel data'],
['colourMap', 'rw', 'arr', [], 'Colour map array (when not true-color)'],
['scale', 'rw', 'float', 1.0, 'Display area scale factor 0.0 - 1.0'],
['viewport', 'rw', 'bool', false, 'Use a viewport set with viewportChange()'],
['width', 'rw', 'int', null, 'Display area width'],
['height', 'rw', 'int', null, 'Display area height'],
['render_mode', 'ro', 'str', '', 'Canvas rendering mode (read-only)'],
['prefer_js', 'rw', 'str', null, 'Prefer Javascript over canvas methods'],
['cursor_uri', 'rw', 'raw', null, 'Can we render cursor using data URI']
]);
// Override some specific getters/setters
that.get_context = function () { return c_ctx; };
that.set_scale = function(scale) { rescale(scale); };
that.set_width = function (val) { that.resize(val, fb_height); };
that.get_width = function() { return fb_width; };
that.set_height = function (val) { that.resize(fb_width, val); };
that.get_height = function() { return fb_height; };
//
// Private functions
//
// Create the public API interface
function constructor() {
Util.Debug(">> Display.constructor");
var c, func, i, curDat, curSave,
has_imageData = false, UE = Util.Engine;
if (! conf.target) { throw("target must be set"); }
if (typeof conf.target === 'string') {
throw("target must be a DOM element");
}
c = conf.target;
if (! c.getContext) { throw("no getContext method"); }
if (! c_ctx) { c_ctx = c.getContext('2d'); }
Util.Debug("User Agent: " + navigator.userAgent);
if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); }
that.clear();
// Check canvas features
if ('createImageData' in c_ctx) {
conf.render_mode = "canvas rendering";
} else {
throw("Canvas does not support createImageData");
}
if (conf.prefer_js === null) {
Util.Info("Prefering javascript operations");
conf.prefer_js = true;
}
// Initialize cached tile imageData
tile16x16 = c_ctx.createImageData(16, 16);
/*
* Determine browser support for setting the cursor via data URI
* scheme
*/
curDat = [];
for (i=0; i < 8 * 8 * 4; i += 1) {
curDat.push(255);
}
try {
curSave = c.style.cursor;
changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8);
if (c.style.cursor) {
if (conf.cursor_uri === null) {
conf.cursor_uri = true;
}
Util.Info("Data URI scheme cursor supported");
} else {
if (conf.cursor_uri === null) {
conf.cursor_uri = false;
}
Util.Warn("Data URI scheme cursor not supported");
}
c.style.cursor = curSave;
} catch (exc2) {
Util.Error("Data URI scheme cursor test exception: " + exc2);
conf.cursor_uri = false;
}
Util.Debug("<< Display.constructor");
return that ;
}
rescale = function(factor) {
var c, tp, x, y,
properties = ['transform', 'WebkitTransform', 'MozTransform', null];
c = conf.target;
tp = properties.shift();
while (tp) {
if (typeof c.style[tp] !== 'undefined') {
break;
}
tp = properties.shift();
}
if (tp === null) {
Util.Debug("No scaling support");
return;
}
if (typeof(factor) === "undefined") {
factor = conf.scale;
} else if (factor > 1.0) {
factor = 1.0;
} else if (factor < 0.1) {
factor = 0.1;
}
if (conf.scale === factor) {
//Util.Debug("Display already scaled to '" + factor + "'");
return;
}
conf.scale = factor;
x = c.width - c.width * factor;
y = c.height - c.height * factor;
c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
};
setFillColor = function(color) {
var bgr, newStyle;
if (conf.true_color) {
bgr = color;
} else {
bgr = conf.colourMap[color[0]];
}
newStyle = "rgb(" + bgr[2] + "," + bgr[1] + "," + bgr[0] + ")";
if (newStyle !== c_prevStyle) {
c_ctx.fillStyle = newStyle;
c_prevStyle = newStyle;
}
};
//
// Public API interface functions
//
// Shift and/or resize the visible viewport
that.viewportChange = function(deltaX, deltaY, width, height) {
var c = conf.target, v = viewport, cr = cleanRect,
saveImg = null, saveStyle, x1, y1, vx2, vy2, w, h;
if (!conf.viewport) {
Util.Debug("Setting viewport to full display region");
deltaX = -v.w; // Clamped later if out of bounds
deltaY = -v.h; // Clamped later if out of bounds
width = fb_width;
height = fb_height;
}
if (typeof(deltaX) === "undefined") { deltaX = 0; }
if (typeof(deltaY) === "undefined") { deltaY = 0; }
if (typeof(width) === "undefined") { width = v.w; }
if (typeof(height) === "undefined") { height = v.h; }
// Size change
if (width > fb_width) { width = fb_width; }
if (height > fb_height) { height = fb_height; }
if ((v.w !== width) || (v.h !== height)) {
// Change width
if ((width < v.w) && (cr.x2 > v.x + width -1)) {
cr.x2 = v.x + width - 1;
}
v.w = width;
// Change height
if ((height < v.h) && (cr.y2 > v.y + height -1)) {
cr.y2 = v.y + height - 1;
}
v.h = height;
if (v.w > 0 && v.h > 0 && c.width > 0 && c.height > 0) {
saveImg = c_ctx.getImageData(0, 0,
(c.width < v.w) ? c.width : v.w,
(c.height < v.h) ? c.height : v.h);
}
c.width = v.w;
c.height = v.h;
if (saveImg) {
c_ctx.putImageData(saveImg, 0, 0);
}
}
vx2 = v.x + v.w - 1;
vy2 = v.y + v.h - 1;
// Position change
if ((deltaX < 0) && ((v.x + deltaX) < 0)) {
deltaX = - v.x;
}
if ((vx2 + deltaX) >= fb_width) {
deltaX -= ((vx2 + deltaX) - fb_width + 1);
}
if ((v.y + deltaY) < 0) {
deltaY = - v.y;
}
if ((vy2 + deltaY) >= fb_height) {
deltaY -= ((vy2 + deltaY) - fb_height + 1);
}
if ((deltaX === 0) && (deltaY === 0)) {
//Util.Debug("skipping viewport change");
return;
}
Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
v.x += deltaX;
vx2 += deltaX;
v.y += deltaY;
vy2 += deltaY;
// Update the clean rectangle
if (v.x > cr.x1) {
cr.x1 = v.x;
}
if (vx2 < cr.x2) {
cr.x2 = vx2;
}
if (v.y > cr.y1) {
cr.y1 = v.y;
}
if (vy2 < cr.y2) {
cr.y2 = vy2;
}
if (deltaX < 0) {
// Shift viewport left, redraw left section
x1 = 0;
w = - deltaX;
} else {
// Shift viewport right, redraw right section
x1 = v.w - deltaX;
w = deltaX;
}
if (deltaY < 0) {
// Shift viewport up, redraw top section
y1 = 0;
h = - deltaY;
} else {
// Shift viewport down, redraw bottom section
y1 = v.h - deltaY;
h = deltaY;
}
// Copy the valid part of the viewport to the shifted location
saveStyle = c_ctx.fillStyle;
c_ctx.fillStyle = "rgb(255,255,255)";
if (deltaX !== 0) {
//that.copyImage(0, 0, -deltaX, 0, v.w, v.h);
//that.fillRect(x1, 0, w, v.h, [255,255,255]);
c_ctx.drawImage(c, 0, 0, v.w, v.h, -deltaX, 0, v.w, v.h);
c_ctx.fillRect(x1, 0, w, v.h);
}
if (deltaY !== 0) {
//that.copyImage(0, 0, 0, -deltaY, v.w, v.h);
//that.fillRect(0, y1, v.w, h, [255,255,255]);
c_ctx.drawImage(c, 0, 0, v.w, v.h, 0, -deltaY, v.w, v.h);
c_ctx.fillRect(0, y1, v.w, h);
}
c_ctx.fillStyle = saveStyle;
};
// Return a map of clean and dirty areas of the viewport and reset the
// tracking of clean and dirty areas.
//
// Returns: {'cleanBox': {'x': x, 'y': y, 'w': w, 'h': h},
// 'dirtyBoxes': [{'x': x, 'y': y, 'w': w, 'h': h}, ...]}
that.getCleanDirtyReset = function() {
var v = viewport, c = cleanRect, cleanBox, dirtyBoxes = [],
vx2 = v.x + v.w - 1, vy2 = v.y + v.h - 1;
// Copy the cleanRect
cleanBox = {'x': c.x1, 'y': c.y1,
'w': c.x2 - c.x1 + 1, 'h': c.y2 - c.y1 + 1};
if ((c.x1 >= c.x2) || (c.y1 >= c.y2)) {
// Whole viewport is dirty
dirtyBoxes.push({'x': v.x, 'y': v.y, 'w': v.w, 'h': v.h});
} else {
// Redraw dirty regions
if (v.x < c.x1) {
// left side dirty region
dirtyBoxes.push({'x': v.x, 'y': v.y,
'w': c.x1 - v.x + 1, 'h': v.h});
}
if (vx2 > c.x2) {
// right side dirty region
dirtyBoxes.push({'x': c.x2 + 1, 'y': v.y,
'w': vx2 - c.x2, 'h': v.h});
}
if (v.y < c.y1) {
// top/middle dirty region
dirtyBoxes.push({'x': c.x1, 'y': v.y,
'w': c.x2 - c.x1 + 1, 'h': c.y1 - v.y});
}
if (vy2 > c.y2) {
// bottom/middle dirty region
dirtyBoxes.push({'x': c.x1, 'y': c.y2 + 1,
'w': c.x2 - c.x1 + 1, 'h': vy2 - c.y2});
}
}
// Reset the cleanRect to the whole viewport
cleanRect = {'x1': v.x, 'y1': v.y,
'x2': v.x + v.w - 1, 'y2': v.y + v.h - 1};
return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes};
};
// Translate viewport coordinates to absolute coordinates
that.absX = function(x) {
return x + viewport.x;
};
that.absY = function(y) {
return y + viewport.y;
};
that.resize = function(width, height) {
c_prevStyle = "";
fb_width = width;
fb_height = height;
rescale(conf.scale);
that.viewportChange();
};
that.clear = function() {
if (conf.logo) {
that.resize(conf.logo.width, conf.logo.height);
that.blitStringImage(conf.logo.data, 0, 0);
} else {
that.resize(640, 20);
c_ctx.clearRect(0, 0, viewport.w, viewport.h);
}
renderQ = [];
// No benefit over default ("source-over") in Chrome and firefox
//c_ctx.globalCompositeOperation = "copy";
};
that.fillRect = function(x, y, width, height, color) {
setFillColor(color);
c_ctx.fillRect(x - viewport.x, y - viewport.y, width, height);
};
that.copyImage = function(old_x, old_y, new_x, new_y, w, h) {
var x1 = old_x - viewport.x, y1 = old_y - viewport.y,
x2 = new_x - viewport.x, y2 = new_y - viewport.y;
c_ctx.drawImage(conf.target, x1, y1, w, h, x2, y2, w, h);
};
// Start updating a tile
that.startTile = function(x, y, width, height, color) {
var data, bgr, red, green, blue, i;
tile_x = x;
tile_y = y;
if ((width === 16) && (height === 16)) {
tile = tile16x16;
} else {
tile = c_ctx.createImageData(width, height);
}
data = tile.data;
if (conf.prefer_js) {
if (conf.true_color) {
bgr = color;
} else {
bgr = conf.colourMap[color[0]];
}
red = bgr[2];
green = bgr[1];
blue = bgr[0];
for (i = 0; i < (width * height * 4); i+=4) {
data[i ] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
} else {
that.fillRect(x, y, width, height, color);
}
};
// Update sub-rectangle of the current tile
that.subTile = function(x, y, w, h, color) {
var data, p, bgr, red, green, blue, width, j, i, xend, yend;
if (conf.prefer_js) {
data = tile.data;
width = tile.width;
if (conf.true_color) {
bgr = color;
} else {
bgr = conf.colourMap[color[0]];
}
red = bgr[2];
green = bgr[1];
blue = bgr[0];
xend = x + w;
yend = y + h;
for (j = y; j < yend; j += 1) {
for (i = x; i < xend; i += 1) {
p = (i + (j * width) ) * 4;
data[p ] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
}
} else {
that.fillRect(tile_x + x, tile_y + y, w, h, color);
}
};
// Draw the current tile to the screen
that.finishTile = function() {
if (conf.prefer_js) {
c_ctx.putImageData(tile, tile_x - viewport.x, tile_y - viewport.y);
}
// else: No-op, if not prefer_js then already done by setSubTile
};
rgbImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, v = viewport;
/*
if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
(x - v.x + width < 0) || (y - v.y + height < 0)) {
// Skipping because outside of viewport
return;
}
*/
img = c_ctx.createImageData(width, height);
data = img.data;
for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+3) {
data[i ] = arr[j ];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j + 2];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - v.x, y - v.y);
};
bgrxImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, v = viewport;
/*
if ((x - v.x >= v.w) || (y - v.y >= v.h) ||
(x - v.x + width < 0) || (y - v.y + height < 0)) {
// Skipping because outside of viewport
return;
}
*/
img = c_ctx.createImageData(width, height);
data = img.data;
for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
data[i ] = arr[j + 2];
data[i + 1] = arr[j + 1];
data[i + 2] = arr[j ];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - v.x, y - v.y);
};
cmapImageData = function(x, y, width, height, arr, offset) {
var img, i, j, data, bgr, cmap;
img = c_ctx.createImageData(width, height);
data = img.data;
cmap = conf.colourMap;
for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
bgr = cmap[arr[j]];
data[i ] = bgr[2];
data[i + 1] = bgr[1];
data[i + 2] = bgr[0];
data[i + 3] = 255; // Set Alpha
}
c_ctx.putImageData(img, x - viewport.x, y - viewport.y);
};
that.blitImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) {
bgrxImageData(x, y, width, height, arr, offset);
} else {
cmapImageData(x, y, width, height, arr, offset);
}
};
that.blitRgbImage = function(x, y, width, height, arr, offset) {
if (conf.true_color) {
rgbImageData(x, y, width, height, arr, offset);
} else {
// prolly wrong...
cmapImageData(x, y, width, height, arr, offset);
}
};
that.blitStringImage = function(str, x, y) {
var img = new Image();
img.onload = function () {
c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
};
img.src = str;
};
// Wrap ctx.drawImage but relative to viewport
that.drawImage = function(img, x, y) {
c_ctx.drawImage(img, x - viewport.x, y - viewport.y);
};
that.renderQ_push = function(action) {
renderQ.push(action);
if (renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will start polling the queue (every
// requestAnimationFrame interval)
scan_renderQ();
}
};
scan_renderQ = function() {
var a, ready = true;
while (ready && renderQ.length > 0) {
a = renderQ[0];
switch (a.type) {
case 'copy':
that.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height);
break;
case 'fill':
that.fillRect(a.x, a.y, a.width, a.height, a.color);
break;
case 'blit':
that.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
break;
case 'blitRgb':
that.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
break;
case 'img':
if (a.img.complete) {
that.drawImage(a.img, a.x, a.y);
} else {
// We need to wait for this image to 'load'
// to keep things in-order
ready = false;
}
break;
}
if (ready) {
a = renderQ.shift();
}
}
if (renderQ.length > 0) {
requestAnimFrame(scan_renderQ);
}
};
that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
if (conf.cursor_uri === false) {
Util.Warn("changeCursor called but no cursor data URI support");
return;
}
if (conf.true_color) {
changeCursor(conf.target, pixels, mask, hotx, hoty, w, h);
} else {
changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap);
}
};
that.defaultCursor = function() {
conf.target.style.cursor = "default";
};
return constructor(); // Return the public API interface
} // End of Display()
/* Set CSS cursor property using data URI encoded cursor file */
function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) {
"use strict";
var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y;
//Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
// Push multi-byte little-endian values
cur.push16le = function (num) {
this.push((num ) & 0xFF,
(num >> 8) & 0xFF );
};
cur.push32le = function (num) {
this.push((num ) & 0xFF,
(num >> 8) & 0xFF,
(num >> 16) & 0xFF,
(num >> 24) & 0xFF );
};
IHDRsz = 40;
RGBsz = w * h * 4;
XORsz = Math.ceil( (w * h) / 8.0 );
ANDsz = Math.ceil( (w * h) / 8.0 );
// Main header
cur.push16le(0); // 0: Reserved
cur.push16le(2); // 2: .CUR type
cur.push16le(1); // 4: Number of images, 1 for non-animated ico
// Cursor #1 header (ICONDIRENTRY)
cur.push(w); // 6: width
cur.push(h); // 7: height
cur.push(0); // 8: colors, 0 -> true-color
cur.push(0); // 9: reserved
cur.push16le(hotx); // 10: hotspot x coordinate
cur.push16le(hoty); // 12: hotspot y coordinate
cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz);
// 14: cursor data byte size
cur.push32le(22); // 18: offset of cursor data in the file
// Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO)
cur.push32le(IHDRsz); // 22: Infoheader size
cur.push32le(w); // 26: Cursor width
cur.push32le(h*2); // 30: XOR+AND height
cur.push16le(1); // 34: number of planes
cur.push16le(32); // 36: bits per pixel
cur.push32le(0); // 38: Type of compression
cur.push32le(XORsz + ANDsz); // 43: Size of Image
// Gimp leaves this as 0
cur.push32le(0); // 46: reserved
cur.push32le(0); // 50: reserved
cur.push32le(0); // 54: reserved
cur.push32le(0); // 58: reserved
// 62: color data (RGBQUAD icColors[])
for (y = h-1; y >= 0; y -= 1) {
for (x = 0; x < w; x += 1) {
idx = y * Math.ceil(w / 8) + Math.floor(x/8);
alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
if (cmap) {
idx = (w * y) + x;
rgb = cmap[pixels[idx]];
cur.push(rgb[2]); // blue
cur.push(rgb[1]); // green
cur.push(rgb[0]); // red
cur.push(alpha); // alpha
} else {
idx = ((w * y) + x) * 4;
cur.push(pixels[idx + 2]); // blue
cur.push(pixels[idx + 1]); // green
cur.push(pixels[idx ]); // red
cur.push(alpha); // alpha
}
}
}
// XOR/bitmask data (BYTE icXOR[])
// (ignored, just needs to be right size)
for (y = 0; y < h; y += 1) {
for (x = 0; x < Math.ceil(w / 8); x += 1) {
cur.push(0x00);
}
}
// AND/bitmask data (BYTE icAND[])
// (ignored, just needs to be right size)
for (y = 0; y < h; y += 1) {
for (x = 0; x < Math.ceil(w / 8); x += 1) {
cur.push(0x00);
}
}
url = "data:image/x-icon;base64," + Base64.encode(cur);
target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
//Util.Debug("<< changeCursor, cur.length: " + cur.length);
}

1911
src/sunstone/public/vendor/noVNC/input.js vendored Normal file

File diff suppressed because it is too large Load Diff

668
src/sunstone/public/vendor/noVNC/jsunzip.js vendored Executable file
View File

@ -0,0 +1,668 @@
/*
* JSUnzip
*
* Copyright (c) 2011 by Erik Moller
* All Rights Reserved
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
var tinf;
function JSUnzip() {
this.getInt = function(offset, size) {
switch (size) {
case 4:
return (this.data.charCodeAt(offset + 3) & 0xff) << 24 |
(this.data.charCodeAt(offset + 2) & 0xff) << 16 |
(this.data.charCodeAt(offset + 1) & 0xff) << 8 |
(this.data.charCodeAt(offset + 0) & 0xff);
break;
case 2:
return (this.data.charCodeAt(offset + 1) & 0xff) << 8 |
(this.data.charCodeAt(offset + 0) & 0xff);
break;
default:
return this.data.charCodeAt(offset) & 0xff;
break;
}
};
this.getDOSDate = function(dosdate, dostime) {
var day = dosdate & 0x1f;
var month = ((dosdate >> 5) & 0xf) - 1;
var year = 1980 + ((dosdate >> 9) & 0x7f)
var second = (dostime & 0x1f) * 2;
var minute = (dostime >> 5) & 0x3f;
hour = (dostime >> 11) & 0x1f;
return new Date(year, month, day, hour, minute, second);
}
this.open = function(data) {
this.data = data;
this.files = [];
if (this.data.length < 22)
return { 'status' : false, 'error' : 'Invalid data' };
var endOfCentralDirectory = this.data.length - 22;
while (endOfCentralDirectory >= 0 && this.getInt(endOfCentralDirectory, 4) != 0x06054b50)
--endOfCentralDirectory;
if (endOfCentralDirectory < 0)
return { 'status' : false, 'error' : 'Invalid data' };
if (this.getInt(endOfCentralDirectory + 4, 2) != 0 || this.getInt(endOfCentralDirectory + 6, 2) != 0)
return { 'status' : false, 'error' : 'No multidisk support' };
var entriesInThisDisk = this.getInt(endOfCentralDirectory + 8, 2);
var centralDirectoryOffset = this.getInt(endOfCentralDirectory + 16, 4);
var globalCommentLength = this.getInt(endOfCentralDirectory + 20, 2);
this.comment = this.data.slice(endOfCentralDirectory + 22, endOfCentralDirectory + 22 + globalCommentLength);
var fileOffset = centralDirectoryOffset;
for (var i = 0; i < entriesInThisDisk; ++i) {
if (this.getInt(fileOffset + 0, 4) != 0x02014b50)
return { 'status' : false, 'error' : 'Invalid data' };
if (this.getInt(fileOffset + 6, 2) > 20)
return { 'status' : false, 'error' : 'Unsupported version' };
if (this.getInt(fileOffset + 8, 2) & 1)
return { 'status' : false, 'error' : 'Encryption not implemented' };
var compressionMethod = this.getInt(fileOffset + 10, 2);
if (compressionMethod != 0 && compressionMethod != 8)
return { 'status' : false, 'error' : 'Unsupported compression method' };
var lastModFileTime = this.getInt(fileOffset + 12, 2);
var lastModFileDate = this.getInt(fileOffset + 14, 2);
var lastModifiedDate = this.getDOSDate(lastModFileDate, lastModFileTime);
var crc = this.getInt(fileOffset + 16, 4);
// TODO: crc
var compressedSize = this.getInt(fileOffset + 20, 4);
var uncompressedSize = this.getInt(fileOffset + 24, 4);
var fileNameLength = this.getInt(fileOffset + 28, 2);
var extraFieldLength = this.getInt(fileOffset + 30, 2);
var fileCommentLength = this.getInt(fileOffset + 32, 2);
var relativeOffsetOfLocalHeader = this.getInt(fileOffset + 42, 4);
var fileName = this.data.slice(fileOffset + 46, fileOffset + 46 + fileNameLength);
var fileComment = this.data.slice(fileOffset + 46 + fileNameLength + extraFieldLength, fileOffset + 46 + fileNameLength + extraFieldLength + fileCommentLength);
if (this.getInt(relativeOffsetOfLocalHeader + 0, 4) != 0x04034b50)
return { 'status' : false, 'error' : 'Invalid data' };
var localFileNameLength = this.getInt(relativeOffsetOfLocalHeader + 26, 2);
var localExtraFieldLength = this.getInt(relativeOffsetOfLocalHeader + 28, 2);
var localFileContent = relativeOffsetOfLocalHeader + 30 + localFileNameLength + localExtraFieldLength;
this.files[fileName] =
{
'fileComment' : fileComment,
'compressionMethod' : compressionMethod,
'compressedSize' : compressedSize,
'uncompressedSize' : uncompressedSize,
'localFileContent' : localFileContent,
'lastModifiedDate' : lastModifiedDate
};
fileOffset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
}
return { 'status' : true }
};
this.read = function(fileName) {
var fileInfo = this.files[fileName];
if (fileInfo) {
if (fileInfo.compressionMethod == 8) {
if (!tinf) {
tinf = new TINF();
tinf.init();
}
var result = tinf.uncompress(this.data, fileInfo.localFileContent);
if (result.status == tinf.OK)
return { 'status' : true, 'data' : result.data };
else
return { 'status' : false, 'error' : result.error };
} else {
return { 'status' : true, 'data' : this.data.slice(fileInfo.localFileContent, fileInfo.localFileContent + fileInfo.uncompressedSize) };
}
}
return { 'status' : false, 'error' : "File '" + fileName + "' doesn't exist in zip" };
};
};
/*
* tinflate - tiny inflate
*
* Copyright (c) 2003 by Joergen Ibsen / Jibz
* All Rights Reserved
*
* http://www.ibsensoftware.com/
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
/*
* tinflate javascript port by Erik Moller in May 2011.
* emoller@opera.com
*
* read_bits() patched by mike@imidio.com to allow
* reading more then 8 bits (needed in some zlib streams)
*/
"use strict";
function TINF() {
this.OK = 0;
this.DATA_ERROR = (-3);
this.WINDOW_SIZE = 32768;
/* ------------------------------ *
* -- internal data structures -- *
* ------------------------------ */
this.TREE = function() {
this.table = new Array(16); /* table of code length counts */
this.trans = new Array(288); /* code -> symbol translation table */
};
this.DATA = function(that) {
this.source = '';
this.sourceIndex = 0;
this.tag = 0;
this.bitcount = 0;
this.dest = [];
this.history = [];
this.ltree = new that.TREE(); /* dynamic length/symbol tree */
this.dtree = new that.TREE(); /* dynamic distance tree */
};
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
this.sltree = new this.TREE(); /* fixed length/symbol tree */
this.sdtree = new this.TREE(); /* fixed distance tree */
/* extra bits and base tables for length codes */
this.length_bits = new Array(30);
this.length_base = new Array(30);
/* extra bits and base tables for distance codes */
this.dist_bits = new Array(30);
this.dist_base = new Array(30);
/* special ordering of code length codes */
this.clcidx = [
16, 17, 18, 0, 8, 7, 9, 6,
10, 5, 11, 4, 12, 3, 13, 2,
14, 1, 15
];
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
/* build extra bits and base tables */
this.build_bits_base = function(bits, base, delta, first)
{
var i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = Math.floor(i / delta);
/* build base table */
for (sum = first, i = 0; i < 30; ++i)
{
base[i] = sum;
sum += 1 << bits[i];
}
}
/* build the fixed huffman trees */
this.build_fixed_trees = function(lt, dt)
{
var i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt.table[i] = 0;
lt.table[7] = 24;
lt.table[8] = 152;
lt.table[9] = 112;
for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt.table[i] = 0;
dt.table[5] = 32;
for (i = 0; i < 32; ++i) dt.trans[i] = i;
}
/* given an array of code lengths, build a tree */
this.build_tree = function(t, lengths, loffset, num)
{
var offs = new Array(16);
var i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i) t.table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i) t.table[lengths[loffset + i]]++;
t.table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i)
{
offs[i] = sum;
sum += t.table[i];
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i)
{
if (lengths[loffset + i]) t.trans[offs[lengths[loffset + i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
/* get one bit from source stream */
this.getbit = function(d)
{
var bit;
/* check if tag is empty */
if (!d.bitcount--)
{
/* load next tag */
d.tag = d.source[d.sourceIndex++] & 0xff;
d.bitcount = 7;
}
/* shift bit out of tag */
bit = d.tag & 0x01;
d.tag >>= 1;
return bit;
}
/* read a num bit value from a stream and add base */
this.read_bits = function(d, num, base)
{
if (!num)
return base;
var val = 0;
while (d.bitcount < 24) {
d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
d.bitcount += 8;
}
val = d.tag & (0xffff >> (16 - num));
d.tag >>= num;
d.bitcount -= num;
return val + base;
}
/* given a data stream and a tree, decode a symbol */
this.decode_symbol = function(d, t)
{
while (d.bitcount < 16) {
d.tag = d.tag | (d.source[d.sourceIndex++] & 0xff) << d.bitcount;
d.bitcount += 8;
}
var sum = 0, cur = 0, len = 0;
do {
cur = 2 * cur + ((d.tag & (1 << len)) >> len);
++len;
sum += t.table[len];
cur -= t.table[len];
} while (cur >= 0);
d.tag >>= len;
d.bitcount -= len;
return t.trans[sum + cur];
}
/* given a data stream, decode dynamic trees from it */
this.decode_trees = function(d, lt, dt)
{
var code_tree = new this.TREE();
var lengths = new Array(288+32);
var hlit, hdist, hclen;
var i, num, length;
/* get 5 bits HLIT (257-286) */
hlit = this.read_bits(d, 5, 257);
/* get 5 bits HDIST (1-32) */
hdist = this.read_bits(d, 5, 1);
/* get 4 bits HCLEN (4-19) */
hclen = this.read_bits(d, 4, 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i)
{
/* get 3 bits code length (0-7) */
var clen = this.read_bits(d, 3, 0);
lengths[this.clcidx[i]] = clen;
}
/* build code length tree */
this.build_tree(code_tree, lengths, 0, 19);
/* decode code lengths for the dynamic trees */
for (num = 0; num < hlit + hdist; )
{
var sym = this.decode_symbol(d, code_tree);
switch (sym)
{
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
{
var prev = lengths[num - 1];
for (length = this.read_bits(d, 2, 3); length; --length)
{
lengths[num++] = prev;
}
}
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
for (length = this.read_bits(d, 3, 3); length; --length)
{
lengths[num++] = 0;
}
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
for (length = this.read_bits(d, 7, 11); length; --length)
{
lengths[num++] = 0;
}
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
break;
}
}
/* build dynamic trees */
this.build_tree(lt, lengths, 0, hlit);
this.build_tree(dt, lengths, hlit, hdist);
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate a block of data */
this.inflate_block_data = function(d, lt, dt)
{
// js optimization.
var ddest = d.dest;
var ddestlength = ddest.length;
while (1)
{
var sym = this.decode_symbol(d, lt);
/* check for end of block */
if (sym == 256)
{
return this.OK;
}
if (sym < 256)
{
ddest[ddestlength++] = sym; // ? String.fromCharCode(sym);
d.history.push(sym);
} else {
var length, dist, offs;
var i;
sym -= 257;
/* possibly get more bits from length code */
length = this.read_bits(d, this.length_bits[sym], this.length_base[sym]);
dist = this.decode_symbol(d, dt);
/* possibly get more bits from distance code */
offs = d.history.length - this.read_bits(d, this.dist_bits[dist], this.dist_base[dist]);
if (offs < 0)
throw ("Invalid zlib offset " + offs);
/* copy match */
for (i = offs; i < offs + length; ++i) {
//ddest[ddestlength++] = ddest[i];
ddest[ddestlength++] = d.history[i];
d.history.push(d.history[i]);
}
}
}
}
/* inflate an uncompressed block of data */
this.inflate_uncompressed_block = function(d)
{
var length, invlength;
var i;
if (d.bitcount > 7) {
var overflow = Math.floor(d.bitcount / 8);
d.sourceIndex -= overflow;
d.bitcount = 0;
d.tag = 0;
}
/* get length */
length = d.source[d.sourceIndex+1];
length = 256*length + d.source[d.sourceIndex];
/* get one's complement of length */
invlength = d.source[d.sourceIndex+3];
invlength = 256*invlength + d.source[d.sourceIndex+2];
/* check length */
if (length != (~invlength & 0x0000ffff)) return this.DATA_ERROR;
d.sourceIndex += 4;
/* copy block */
for (i = length; i; --i) {
d.history.push(d.source[d.sourceIndex]);
d.dest[d.dest.length] = d.source[d.sourceIndex++];
}
/* make sure we start next block on a byte boundary */
d.bitcount = 0;
return this.OK;
}
/* inflate a block of data compressed with fixed huffman trees */
this.inflate_fixed_block = function(d)
{
/* decode block using fixed trees */
return this.inflate_block_data(d, this.sltree, this.sdtree);
}
/* inflate a block of data compressed with dynamic huffman trees */
this.inflate_dynamic_block = function(d)
{
/* decode trees from stream */
this.decode_trees(d, d.ltree, d.dtree);
/* decode block using decoded trees */
return this.inflate_block_data(d, d.ltree, d.dtree);
}
/* ---------------------- *
* -- public functions -- *
* ---------------------- */
/* initialize global (static) data */
this.init = function()
{
/* build fixed huffman trees */
this.build_fixed_trees(this.sltree, this.sdtree);
/* build extra bits and base tables */
this.build_bits_base(this.length_bits, this.length_base, 4, 3);
this.build_bits_base(this.dist_bits, this.dist_base, 2, 1);
/* fix a special case */
this.length_bits[28] = 0;
this.length_base[28] = 258;
this.reset();
}
this.reset = function()
{
this.d = new this.DATA(this);
delete this.header;
}
/* inflate stream from source to dest */
this.uncompress = function(source, offset)
{
var d = this.d;
var bfinal;
/* initialise data */
d.source = source;
d.sourceIndex = offset;
d.bitcount = 0;
d.dest = [];
// Skip zlib header at start of stream
if (typeof this.header == 'undefined') {
this.header = this.read_bits(d, 16, 0);
/* byte 0: 0x78, 7 = 32k window size, 8 = deflate */
/* byte 1: check bits for header and other flags */
}
var blocks = 0;
do {
var btype;
var res;
/* read final block flag */
bfinal = this.getbit(d);
/* read block type (2 bits) */
btype = this.read_bits(d, 2, 0);
/* decompress block */
switch (btype)
{
case 0:
/* decompress uncompressed block */
res = this.inflate_uncompressed_block(d);
break;
case 1:
/* decompress block with fixed huffman trees */
res = this.inflate_fixed_block(d);
break;
case 2:
/* decompress block with dynamic huffman trees */
res = this.inflate_dynamic_block(d);
break;
default:
return { 'status' : this.DATA_ERROR };
}
if (res != this.OK) return { 'status' : this.DATA_ERROR };
blocks++;
} while (!bfinal && d.sourceIndex < d.source.length);
d.history = d.history.slice(-this.WINDOW_SIZE);
return { 'status' : this.OK, 'data' : d.dest };
}
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,90 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.LGPL-3)
*/
"use strict";
/*jslint browser: true, white: false */
/*global Util, VNC_frame_data, finish */
var rfb, mode, test_state, frame_idx, frame_length,
iteration, iterations, istart_time,
// Pre-declarations for jslint
send_array, next_iteration, queue_next_packet, do_packet;
// Override send_array
send_array = function (arr) {
// Stub out send_array
};
next_iteration = function () {
if (iteration === 0) {
frame_length = VNC_frame_data.length;
test_state = 'running';
} else {
rfb.disconnect();
}
if (test_state !== 'running') { return; }
iteration += 1;
if (iteration > iterations) {
finish();
return;
}
frame_idx = 0;
istart_time = (new Date()).getTime();
rfb.connect('test', 0, "bogus");
queue_next_packet();
};
queue_next_packet = function () {
var frame, foffset, toffset, delay;
if (test_state !== 'running') { return; }
frame = VNC_frame_data[frame_idx];
while ((frame_idx < frame_length) && (frame.charAt(0) === "}")) {
//Util.Debug("Send frame " + frame_idx);
frame_idx += 1;
frame = VNC_frame_data[frame_idx];
}
if (frame === 'EOF') {
Util.Debug("Finished, found EOF");
next_iteration();
return;
}
if (frame_idx >= frame_length) {
Util.Debug("Finished, no more frames");
next_iteration();
return;
}
if (mode === 'realtime') {
foffset = frame.slice(1, frame.indexOf('{', 1));
toffset = (new Date()).getTime() - istart_time;
delay = foffset - toffset;
if (delay < 1) {
delay = 1;
}
setTimeout(do_packet, delay);
} else {
setTimeout(do_packet, 1);
}
};
do_packet = function () {
//Util.Debug("Processing frame: " + frame_idx);
var frame = VNC_frame_data[frame_idx];
rfb.recv_message({'data' : frame.slice(frame.indexOf('{', 1) + 1)});
frame_idx += 1;
queue_next_packet();
};

1849
src/sunstone/public/vendor/noVNC/rfb.js vendored Normal file

File diff suppressed because it is too large Load Diff

659
src/sunstone/public/vendor/noVNC/ui.js vendored Normal file
View File

@ -0,0 +1,659 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint white: false, browser: true */
/*global window, $D, Util, WebUtil, RFB, Display */
var UI = {
rfb_state : 'loaded',
settingsOpen : false,
connSettingsOpen : false,
clipboardOpen: false,
keyboardVisible: false,
// Render default UI and initialize settings menu
load: function() {
var html = '', i, sheet, sheets, llevels;
// Stylesheet selection dropdown
sheet = WebUtil.selectStylesheet();
sheets = WebUtil.getStylesheets();
for (i = 0; i < sheets.length; i += 1) {
UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
}
// Logging selection dropdown
llevels = ['error', 'warn', 'info', 'debug'];
for (i = 0; i < llevels.length; i += 1) {
UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
}
// Settings with immediate effects
UI.initSetting('logging', 'warn');
WebUtil.init_logging(UI.getSetting('logging'));
UI.initSetting('stylesheet', 'default');
WebUtil.selectStylesheet(null);
// call twice to get around webkit bug
WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
/* Populate the controls if defaults are provided in the URL */
UI.initSetting('host', window.location.hostname);
UI.initSetting('port', window.location.port);
UI.initSetting('password', '');
UI.initSetting('encrypt', (window.location.protocol === "https:"));
UI.initSetting('true_color', true);
UI.initSetting('cursor', false);
UI.initSetting('shared', true);
UI.initSetting('view_only', false);
UI.initSetting('connectTimeout', 2);
UI.initSetting('path', 'websockify');
UI.initSetting('repeaterID', '');
UI.rfb = RFB({'target': $D('noVNC_canvas'),
'onUpdateState': UI.updateState,
'onClipboard': UI.clipReceive});
UI.updateVisualState();
// Unfocus clipboard when over the VNC area
//$D('VNC_screen').onmousemove = function () {
// var keyboard = UI.rfb.get_keyboard();
// if ((! keyboard) || (! keyboard.get_focused())) {
// $D('VNC_clipboard_text').blur();
// }
// };
// Show mouse selector buttons on touch screen devices
if ('ontouchstart' in document.documentElement) {
// Show mobile buttons
$D('noVNC_mobile_buttons').style.display = "inline";
UI.setMouseButton();
// Remove the address bar
setTimeout(function() { window.scrollTo(0, 1); }, 100);
UI.forceSetting('clip', true);
$D('noVNC_clip').disabled = true;
} else {
UI.initSetting('clip', false);
}
//iOS Safari does not support CSS position:fixed.
//This detects iOS devices and enables javascript workaround.
if ((navigator.userAgent.match(/iPhone/i)) ||
(navigator.userAgent.match(/iPod/i)) ||
(navigator.userAgent.match(/iPad/i))) {
//UI.setOnscroll();
//UI.setResize();
}
$D('noVNC_host').focus();
UI.setViewClip();
Util.addEvent(window, 'resize', UI.setViewClip);
Util.addEvent(window, 'beforeunload', function () {
if (UI.rfb_state === 'normal') {
return "You are currently connected.";
}
} );
// Show description by default when hosted at for kanaka.github.com
if (location.host === "kanaka.github.com") {
// Open the description dialog
$D('noVNC_description').style.display = "block";
} else {
// Open the connect panel on first load
UI.toggleConnectPanel();
}
},
// Read form control compatible setting from cookie
getSetting: function(name) {
var val, ctrl = $D('noVNC_' + name);
val = WebUtil.readCookie(name);
if (ctrl.type === 'checkbox') {
if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
val = false;
} else {
val = true;
}
}
return val;
},
// Update cookie and form control setting. If value is not set, then
// updates from control to current cookie setting.
updateSetting: function(name, value) {
var i, ctrl = $D('noVNC_' + name);
// Save the cookie for this session
if (typeof value !== 'undefined') {
WebUtil.createCookie(name, value);
}
// Update the settings control
value = UI.getSetting(name);
if (ctrl.type === 'checkbox') {
ctrl.checked = value;
} else if (typeof ctrl.options !== 'undefined') {
for (i = 0; i < ctrl.options.length; i += 1) {
if (ctrl.options[i].value === value) {
ctrl.selectedIndex = i;
break;
}
}
} else {
/*Weird IE9 error leads to 'null' appearring
in textboxes instead of ''.*/
if (value === null) {
value = "";
}
ctrl.value = value;
}
},
// Save control setting to cookie
saveSetting: function(name) {
var val, ctrl = $D('noVNC_' + name);
if (ctrl.type === 'checkbox') {
val = ctrl.checked;
} else if (typeof ctrl.options !== 'undefined') {
val = ctrl.options[ctrl.selectedIndex].value;
} else {
val = ctrl.value;
}
WebUtil.createCookie(name, val);
//Util.Debug("Setting saved '" + name + "=" + val + "'");
return val;
},
// Initial page load read/initialization of settings
initSetting: function(name, defVal) {
var val;
// Check Query string followed by cookie
val = WebUtil.getQueryVar(name);
if (val === null) {
val = WebUtil.readCookie(name, defVal);
}
UI.updateSetting(name, val);
//Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
return val;
},
// Force a setting to be a certain value
forceSetting: function(name, val) {
UI.updateSetting(name, val);
return val;
},
// Show the clipboard panel
toggleClipboardPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
//Close settings if open
if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
}
//Close connection settings if open
if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
//Toggle Clipboard Panel
if (UI.clipboardOpen === true) {
$D('noVNC_clipboard').style.display = "none";
$D('clipboardButton').className = "noVNC_status_button";
UI.clipboardOpen = false;
} else {
$D('noVNC_clipboard').style.display = "block";
$D('clipboardButton').className = "noVNC_status_button_selected";
UI.clipboardOpen = true;
}
},
// Show the connection settings panel/menu
toggleConnectPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
//Close connection settings if open
if (UI.settingsOpen === true) {
UI.settingsApply();
UI.closeSettingsMenu();
$D('connectButton').className = "noVNC_status_button";
}
if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Toggle Connection Panel
if (UI.connSettingsOpen === true) {
$D('noVNC_controls').style.display = "none";
$D('connectButton').className = "noVNC_status_button";
UI.connSettingsOpen = false;
} else {
$D('noVNC_controls').style.display = "block";
$D('connectButton').className = "noVNC_status_button_selected";
UI.connSettingsOpen = true;
$D('noVNC_host').focus();
}
},
// Toggle the settings menu:
// On open, settings are refreshed from saved cookies.
// On close, settings are applied
toggleSettingsPanel: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.settingsOpen) {
UI.settingsApply();
UI.closeSettingsMenu();
} else {
UI.updateSetting('encrypt');
UI.updateSetting('true_color');
if (UI.rfb.get_display().get_cursor_uri()) {
UI.updateSetting('cursor');
} else {
UI.updateSetting('cursor', false);
$D('noVNC_cursor').disabled = true;
}
UI.updateSetting('clip');
UI.updateSetting('shared');
UI.updateSetting('view_only');
UI.updateSetting('connectTimeout');
UI.updateSetting('path');
UI.updateSetting('repeaterID');
UI.updateSetting('stylesheet');
UI.updateSetting('logging');
UI.openSettingsMenu();
}
},
// Open menu
openSettingsMenu: function() {
// Close the description panel
$D('noVNC_description').style.display = "none";
if (UI.clipboardOpen === true) {
UI.toggleClipboardPanel();
}
//Close connection settings if open
if (UI.connSettingsOpen === true) {
UI.toggleConnectPanel();
}
$D('noVNC_settings').style.display = "block";
$D('settingsButton').className = "noVNC_status_button_selected";
UI.settingsOpen = true;
},
// Close menu (without applying settings)
closeSettingsMenu: function() {
$D('noVNC_settings').style.display = "none";
$D('settingsButton').className = "noVNC_status_button";
UI.settingsOpen = false;
},
// Save/apply settings when 'Apply' button is pressed
settingsApply: function() {
//Util.Debug(">> settingsApply");
UI.saveSetting('encrypt');
UI.saveSetting('true_color');
if (UI.rfb.get_display().get_cursor_uri()) {
UI.saveSetting('cursor');
}
UI.saveSetting('clip');
UI.saveSetting('shared');
UI.saveSetting('view_only');
UI.saveSetting('connectTimeout');
UI.saveSetting('path');
UI.saveSetting('repeaterID');
UI.saveSetting('stylesheet');
UI.saveSetting('logging');
// Settings with immediate (non-connected related) effect
WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
WebUtil.init_logging(UI.getSetting('logging'));
UI.setViewClip();
UI.setViewDrag(UI.rfb.get_viewportDrag());
//Util.Debug("<< settingsApply");
},
setPassword: function() {
UI.rfb.sendPassword($D('noVNC_password').value);
//Reset connect button.
$D('noVNC_connect_button').value = "Connect";
$D('noVNC_connect_button').onclick = UI.Connect;
//Hide connection panel.
UI.toggleConnectPanel();
return false;
},
sendCtrlAltDel: function() {
UI.rfb.sendCtrlAltDel();
},
setMouseButton: function(num) {
var b, blist = [0, 1,2,4], button;
if (typeof num === 'undefined') {
// Disable mouse buttons
num = -1;
}
if (UI.rfb) {
UI.rfb.get_mouse().set_touchButton(num);
}
for (b = 0; b < blist.length; b++) {
button = $D('noVNC_mouse_button' + blist[b]);
if (blist[b] === num) {
button.style.display = "";
} else {
button.style.display = "none";
/*
button.style.backgroundColor = "black";
button.style.color = "lightgray";
button.style.backgroundColor = "";
button.style.color = "";
*/
}
}
},
updateState: function(rfb, state, oldstate, msg) {
var s, sb, c, d, cad, vd, klass;
UI.rfb_state = state;
s = $D('noVNC_status');
sb = $D('noVNC_status_bar');
switch (state) {
case 'failed':
case 'fatal':
klass = "noVNC_status_error";
break;
case 'normal':
klass = "noVNC_status_normal";
break;
case 'disconnected':
$D('noVNC_logo').style.display = "block";
// Fall through
case 'loaded':
klass = "noVNC_status_normal";
break;
case 'password':
UI.toggleConnectPanel();
$D('noVNC_connect_button').value = "Send Password";
$D('noVNC_connect_button').onclick = UI.setPassword;
$D('noVNC_password').focus();
klass = "noVNC_status_warn";
break;
default:
klass = "noVNC_status_warn";
break;
}
if (typeof(msg) !== 'undefined') {
s.setAttribute("class", klass);
sb.setAttribute("class", klass);
s.innerHTML = msg;
}
UI.updateVisualState();
},
// Disable/enable controls depending on connection state
updateVisualState: function() {
var connected = UI.rfb_state === 'normal' ? true : false;
//Util.Debug(">> updateVisualState");
$D('noVNC_encrypt').disabled = connected;
$D('noVNC_true_color').disabled = connected;
if (UI.rfb && UI.rfb.get_display() &&
UI.rfb.get_display().get_cursor_uri()) {
$D('noVNC_cursor').disabled = connected;
} else {
UI.updateSetting('cursor', false);
$D('noVNC_cursor').disabled = true;
}
$D('noVNC_shared').disabled = connected;
$D('noVNC_view_only').disabled = connected;
$D('noVNC_connectTimeout').disabled = connected;
$D('noVNC_path').disabled = connected;
$D('noVNC_repeaterID').disabled = connected;
if (connected) {
UI.setViewClip();
UI.setMouseButton(1);
$D('clipboardButton').style.display = "inline";
$D('showKeyboard').style.display = "inline";
$D('sendCtrlAltDelButton').style.display = "inline";
} else {
UI.setMouseButton();
$D('clipboardButton').style.display = "none";
$D('showKeyboard').style.display = "none";
$D('sendCtrlAltDelButton').style.display = "none";
}
// State change disables viewport dragging.
// It is enabled (toggled) by direct click on the button
UI.setViewDrag(false);
switch (UI.rfb_state) {
case 'fatal':
case 'failed':
case 'loaded':
case 'disconnected':
$D('connectButton').style.display = "";
$D('disconnectButton').style.display = "none";
break;
default:
$D('connectButton').style.display = "none";
$D('disconnectButton').style.display = "";
break;
}
//Util.Debug("<< updateVisualState");
},
clipReceive: function(rfb, text) {
Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
$D('noVNC_clipboard_text').value = text;
Util.Debug("<< UI.clipReceive");
},
connect: function() {
var host, port, password, path;
UI.closeSettingsMenu();
UI.toggleConnectPanel();
host = $D('noVNC_host').value;
port = $D('noVNC_port').value;
password = $D('noVNC_password').value;
path = $D('noVNC_path').value;
if ((!host) || (!port)) {
throw("Must set host and port");
}
UI.rfb.set_encrypt(UI.getSetting('encrypt'));
UI.rfb.set_true_color(UI.getSetting('true_color'));
UI.rfb.set_local_cursor(UI.getSetting('cursor'));
UI.rfb.set_shared(UI.getSetting('shared'));
UI.rfb.set_view_only(UI.getSetting('view_only'));
UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
UI.rfb.connect(host, port, password, path);
//Close dialog.
setTimeout(UI.setBarPosition, 100);
$D('noVNC_logo').style.display = "none";
},
disconnect: function() {
UI.closeSettingsMenu();
UI.rfb.disconnect();
$D('noVNC_logo').style.display = "block";
UI.connSettingsOpen = false;
UI.toggleConnectPanel();
},
displayBlur: function() {
UI.rfb.get_keyboard().set_focused(false);
UI.rfb.get_mouse().set_focused(false);
},
displayFocus: function() {
UI.rfb.get_keyboard().set_focused(true);
UI.rfb.get_mouse().set_focused(true);
},
clipClear: function() {
$D('noVNC_clipboard_text').value = "";
UI.rfb.clipboardPasteFrom("");
},
clipSend: function() {
var text = $D('noVNC_clipboard_text').value;
Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
UI.rfb.clipboardPasteFrom(text);
Util.Debug("<< UI.clipSend");
},
// Enable/disable and configure viewport clipping
setViewClip: function(clip) {
var display, cur_clip, pos, new_w, new_h;
if (UI.rfb) {
display = UI.rfb.get_display();
} else {
return;
}
cur_clip = display.get_viewport();
if (typeof(clip) !== 'boolean') {
// Use current setting
clip = UI.getSetting('clip');
}
if (clip && !cur_clip) {
// Turn clipping on
UI.updateSetting('clip', true);
} else if (!clip && cur_clip) {
// Turn clipping off
UI.updateSetting('clip', false);
display.set_viewport(false);
$D('noVNC_canvas').style.position = 'static';
display.viewportChange();
}
if (UI.getSetting('clip')) {
// If clipping, update clipping settings
$D('noVNC_canvas').style.position = 'absolute';
pos = Util.getPosition($D('noVNC_canvas'));
new_w = window.innerWidth - pos.x;
new_h = window.innerHeight - pos.y;
display.set_viewport(true);
display.viewportChange(0, 0, new_w, new_h);
}
},
// Toggle/set/unset the viewport drag/move button
setViewDrag: function(drag) {
var vmb = $D('noVNC_view_drag_button');
if (!UI.rfb) { return; }
if (UI.rfb_state === 'normal' &&
UI.rfb.get_display().get_viewport()) {
vmb.style.display = "inline";
} else {
vmb.style.display = "none";
}
if (typeof(drag) === "undefined") {
// If not specified, then toggle
drag = !UI.rfb.get_viewportDrag();
}
if (drag) {
vmb.className = "noVNC_status_button_selected";
UI.rfb.set_viewportDrag(true);
} else {
vmb.className = "noVNC_status_button";
UI.rfb.set_viewportDrag(false);
}
},
// On touch devices, show the OS keyboard
showKeyboard: function() {
if(UI.keyboardVisible === false) {
$D('keyboardinput').focus();
UI.keyboardVisible = true;
$D('showKeyboard').className = "noVNC_status_button_selected";
} else if(UI.keyboardVisible === true) {
$D('keyboardinput').blur();
$D('showKeyboard').className = "noVNC_status_button";
UI.keyboardVisible = false;
}
},
keyInputBlur: function() {
$D('showKeyboard').className = "noVNC_status_button";
//Weird bug in iOS if you change keyboardVisible
//here it does not actually occur so next time
//you click keyboard icon it doesnt work.
setTimeout(function() { UI.setKeyboard(); },100);
},
setKeyboard: function() {
UI.keyboardVisible = false;
},
// iOS < Version 5 does not support position fixed. Javascript workaround:
setOnscroll: function() {
window.onscroll = function() {
UI.setBarPosition();
};
},
setResize: function () {
window.onResize = function() {
UI.setBarPosition();
};
},
//Helper to add options to dropdown.
addOption: function(selectbox,text,value )
{
var optn = document.createElement("OPTION");
optn.text = text;
optn.value = value;
selectbox.options.add(optn);
},
setBarPosition: function() {
$D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
$D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
var vncwidth = $D('noVNC_screen').style.offsetWidth;
$D('noVNC-control-bar').style.width = vncwidth + 'px';
}
};

320
src/sunstone/public/vendor/noVNC/util.js vendored Normal file
View File

@ -0,0 +1,320 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*global window, console, document, navigator, ActiveXObject */
// Globals defined here
var Util = {};
/*
* Make arrays quack
*/
Array.prototype.push8 = function (num) {
this.push(num & 0xFF);
};
Array.prototype.push16 = function (num) {
this.push((num >> 8) & 0xFF,
(num ) & 0xFF );
};
Array.prototype.push32 = function (num) {
this.push((num >> 24) & 0xFF,
(num >> 16) & 0xFF,
(num >> 8) & 0xFF,
(num ) & 0xFF );
};
// IE does not support map (even in IE9)
//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
//
// requestAnimationFrame shim with setTimeout fallback
//
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
/*
* ------------------------------------------------------
* Namespaced in Util
* ------------------------------------------------------
*/
/*
* Logging/debug routines
*/
Util._log_level = 'warn';
Util.init_logging = function (level) {
if (typeof level === 'undefined') {
level = Util._log_level;
} else {
Util._log_level = level;
}
if (typeof window.console === "undefined") {
if (typeof window.opera !== "undefined") {
window.console = {
'log' : window.opera.postError,
'warn' : window.opera.postError,
'error': window.opera.postError };
} else {
window.console = {
'log' : function(m) {},
'warn' : function(m) {},
'error': function(m) {}};
}
}
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
switch (level) {
case 'debug': Util.Debug = function (msg) { console.log(msg); };
case 'info': Util.Info = function (msg) { console.log(msg); };
case 'warn': Util.Warn = function (msg) { console.warn(msg); };
case 'error': Util.Error = function (msg) { console.error(msg); };
case 'none':
break;
default:
throw("invalid logging type '" + level + "'");
}
};
Util.get_logging = function () {
return Util._log_level;
};
// Initialize logging level
Util.init_logging();
// Set configuration default for Crockford style function namespaces
Util.conf_default = function(cfg, api, defaults, v, mode, type, defval, desc) {
var getter, setter;
// Default getter function
getter = function (idx) {
if ((type in {'arr':1, 'array':1}) &&
(typeof idx !== 'undefined')) {
return cfg[v][idx];
} else {
return cfg[v];
}
};
// Default setter function
setter = function (val, idx) {
if (type in {'boolean':1, 'bool':1}) {
if ((!val) || (val in {'0':1, 'no':1, 'false':1})) {
val = false;
} else {
val = true;
}
} else if (type in {'integer':1, 'int':1}) {
val = parseInt(val, 10);
} else if (type === 'str') {
val = String(val);
} else if (type === 'func') {
if (!val) {
val = function () {};
}
}
if (typeof idx !== 'undefined') {
cfg[v][idx] = val;
} else {
cfg[v] = val;
}
};
// Set the description
api[v + '_description'] = desc;
// Set the getter function
if (typeof api['get_' + v] === 'undefined') {
api['get_' + v] = getter;
}
// Set the setter function with extra sanity checks
if (typeof api['set_' + v] === 'undefined') {
api['set_' + v] = function (val, idx) {
if (mode in {'RO':1, 'ro':1}) {
throw(v + " is read-only");
} else if ((mode in {'WO':1, 'wo':1}) &&
(typeof cfg[v] !== 'undefined')) {
throw(v + " can only be set once");
}
setter(val, idx);
};
}
// Set the default value
if (typeof defaults[v] !== 'undefined') {
defval = defaults[v];
} else if ((type in {'arr':1, 'array':1}) &&
(! (defval instanceof Array))) {
defval = [];
}
// Coerce existing setting to the right type
//Util.Debug("v: " + v + ", defval: " + defval + ", defaults[v]: " + defaults[v]);
setter(defval);
};
// Set group of configuration defaults
Util.conf_defaults = function(cfg, api, defaults, arr) {
var i;
for (i = 0; i < arr.length; i++) {
Util.conf_default(cfg, api, defaults, arr[i][0], arr[i][1],
arr[i][2], arr[i][3], arr[i][4]);
}
};
/*
* Cross-browser routines
*/
// Get DOM element position on page
Util.getPosition = function (obj) {
var x = 0, y = 0;
if (obj.offsetParent) {
do {
x += obj.offsetLeft;
y += obj.offsetTop;
obj = obj.offsetParent;
} while (obj);
}
return {'x': x, 'y': y};
};
// Get mouse event position in DOM element
Util.getEventPosition = function (e, obj, scale) {
var evt, docX, docY, pos;
//if (!e) evt = window.event;
evt = (e ? e : window.event);
evt = (evt.changedTouches ? evt.changedTouches[0] : evt.touches ? evt.touches[0] : evt);
if (evt.pageX || evt.pageY) {
docX = evt.pageX;
docY = evt.pageY;
} else if (evt.clientX || evt.clientY) {
docX = evt.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
docY = evt.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
pos = Util.getPosition(obj);
if (typeof scale === "undefined") {
scale = 1;
}
return {'x': (docX - pos.x) / scale, 'y': (docY - pos.y) / scale};
};
// Event registration. Based on: http://www.scottandrew.com/weblog/articles/cbs-events
Util.addEvent = function (obj, evType, fn){
if (obj.attachEvent){
var r = obj.attachEvent("on"+evType, fn);
return r;
} else if (obj.addEventListener){
obj.addEventListener(evType, fn, false);
return true;
} else {
throw("Handler could not be attached");
}
};
Util.removeEvent = function(obj, evType, fn){
if (obj.detachEvent){
var r = obj.detachEvent("on"+evType, fn);
return r;
} else if (obj.removeEventListener){
obj.removeEventListener(evType, fn, false);
return true;
} else {
throw("Handler could not be removed");
}
};
Util.stopEvent = function(e) {
if (e.stopPropagation) { e.stopPropagation(); }
else { e.cancelBubble = true; }
if (e.preventDefault) { e.preventDefault(); }
else { e.returnValue = false; }
};
// Set browser engine versions. Based on mootools.
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
Util.Engine = {
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
//'presto': (function() {
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
'presto': (function() { return (!window.opera) ? false : true; }()),
'trident': (function() {
return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4); }()),
'webkit': (function() {
try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
//'webkit': (function() {
// return ((typeof navigator.taintEnabled !== "unknown") && navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); }()),
'gecko': (function() {
return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18); }())
};
if (Util.Engine.webkit) {
// Extract actual webkit version if available
Util.Engine.webkit = (function(v) {
var re = new RegExp('WebKit/([0-9\.]*) ');
v = (navigator.userAgent.match(re) || ['', v])[1];
return parseFloat(v, 10);
})(Util.Engine.webkit);
}
Util.Flash = (function(){
var v, version;
try {
v = navigator.plugins['Shockwave Flash'].description;
} catch(err1) {
try {
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
} catch(err2) {
v = '0 r0';
}
}
version = v.match(/\d+/g);
return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
}());

43
src/sunstone/public/vendor/noVNC/vnc.js vendored Normal file
View File

@ -0,0 +1,43 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
/*jslint evil: true */
/*global window, document, INCLUDE_URI */
/*
* Load supporting scripts
*/
function get_INCLUDE_URI() {
return (typeof INCLUDE_URI !== "undefined") ? INCLUDE_URI : "include/";
}
(function () {
"use strict";
var extra = "", start, end;
start = "<script src='" + get_INCLUDE_URI();
end = "'><\/script>";
// Uncomment to activate firebug lite
//extra += "<script src='http://getfirebug.com/releases/lite/1.2/" +
// "firebug-lite-compressed.js'><\/script>";
extra += start + "util.js" + end;
extra += start + "webutil.js" + end;
extra += start + "base64.js" + end;
extra += start + "websock.js" + end;
extra += start + "des.js" + end;
extra += start + "input.js" + end;
extra += start + "display.js" + end;
extra += start + "rfb.js" + end;
extra += start + "jsunzip.js" + end;
document.write(extra);
}());

View File

@ -0,0 +1,109 @@
* How to try
Assuming you have Web server (e.g. Apache) running at http://example.com/ .
- Download web_socket.rb from:
http://github.com/gimite/web-socket-ruby/tree/master
- Run sample Web Socket server (echo server) in example.com with: (#1)
$ ruby web-socket-ruby/samples/echo_server.rb example.com 10081
- If your server already provides socket policy file at port 843, modify the file to allow access to port 10081. Otherwise you can skip this step. See below for details.
- Publish the web-socket-js directory with your Web server (e.g. put it in ~/public_html).
- Change ws://localhost:10081 to ws://example.com:10081 in sample.html.
- Open sample.html in your browser.
- After "onopen" is shown, input something, click [Send] and confirm echo back.
#1: First argument of echo_server.rb means that it accepts Web Socket connection from HTML pages in example.com.
* Troubleshooting
If it doesn't work, try these:
1. Try Chrome and Firefox 3.x.
- It doesn't work on Chrome:
-- It's likely an issue of your code or the server. Debug your code as usual e.g. using console.log.
- It works on Chrome but it doesn't work on Firefox:
-- It's likely an issue of web-socket-js specific configuration (e.g. 3 and 4 below).
- It works on both Chrome and Firefox, but it doesn't work on your browser:
-- Check "Supported environment" section below. Your browser may not be supported by web-socket-js.
2. Add this line before your code:
WEB_SOCKET_DEBUG = true;
and use Developer Tools (Chrome/Safari) or Firebug (Firefox) to see if console.log outputs any errors.
3. Make sure you do NOT open your HTML page as local file e.g. file:///.../sample.html. web-socket-js doesn't work on local file. Open it via Web server e.g. http:///.../sample.html.
4. If you are NOT using web-socket-ruby as your WebSocket server, you need to place Flash socket policy file on your server. See "Flash socket policy file" section below for details.
5. Check if sample.html bundled with web-socket-js works.
6. Make sure the port used for WebSocket (10081 in example above) is not blocked by your server/client's firewall.
7. Install debugger version of Flash Player available here to see Flash errors:
http://www.adobe.com/support/flashplayer/downloads.html
* Supported environments
It should work on:
- Google Chrome 4 or later (just uses native implementation)
- Firefox 3.x, Internet Explorer 8 + Flash Player 9 or later
It may or may not work on other browsers such as Safari, Opera or IE 6. Patch for these browsers are appreciated, but I will not work on fixing issues specific to these browsers by myself.
* Flash socket policy file
This implementation uses Flash's socket, which means that your server must provide Flash socket policy file to declare the server accepts connections from Flash.
If you use web-socket-ruby available at
http://github.com/gimite/web-socket-ruby/tree/master
, you don't need anything special, because web-socket-ruby handles Flash socket policy file request. But if you already provide socket policy file at port 843, you need to modify the file to allow access to Web Socket port, because it precedes what web-socket-ruby provides.
If you use other Web Socket server implementation, you need to provide socket policy file yourself. See
http://www.lightsphere.com/dev/articles/flash_socket_policy.html
for details and sample script to run socket policy file server. node.js implementation is available here:
http://github.com/LearnBoost/Socket.IO-node/blob/master/lib/socket.io/transports/flashsocket.js
Actually, it's still better to provide socket policy file at port 843 even if you use web-socket-ruby. Flash always try to connect to port 843 first, so providing the file at port 843 makes startup faster.
* Cookie considerations
Cookie is sent if Web Socket host is the same as the origin of JavaScript. Otherwise it is not sent, because I don't know way to send right Cookie (which is Cookie of the host of Web Socket, I heard).
Note that it's technically possible that client sends arbitrary string as Cookie and any other headers (by modifying this library for example) once you place Flash socket policy file in your server. So don't trust Cookie and other headers if you allow connection from untrusted origin.
* Proxy considerations
The WebSocket spec (http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) specifies instructions for User Agents to support proxied connections by implementing the HTTP CONNECT method.
The AS3 Socket class doesn't implement this mechanism, which renders it useless for the scenarios where the user trying to open a socket is behind a proxy.
The class RFC2817Socket (by Christian Cantrell) effectively lets us implement this, as long as the proxy settings are known and provided by the interface that instantiates the WebSocket. As such, if you want to support proxied conncetions, you'll have to supply this information to the WebSocket constructor when Flash is being used. One way to go about it would be to ask the user for proxy settings information if the initial connection fails.
* How to host HTML file and SWF file in different domains
By default, HTML file and SWF file must be in the same domain. You can follow steps below to allow hosting them in different domain.
WARNING: If you use the method below, HTML files in ANY domains can send arbitrary TCP data to your WebSocket server, regardless of configuration in Flash socket policy file. Arbitrary TCP data means that they can even fake request headers including Origin and Cookie.
- Unzip WebSocketMainInsecure.zip to extract WebSocketMainInsecure.swf.
- Put WebSocketMainInsecure.swf on your server, instead of WebSocketMain.swf.
- In JavaScript, set WEB_SOCKET_SWF_LOCATION to URL of your WebSocketMainInsecure.swf.
* How to build WebSocketMain.swf
Install Flex 4 SDK:
http://opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4
$ cd flash-src
$ ./build.sh
* License
New BSD License.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,341 @@
// Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
// License: New BSD License
// Reference: http://dev.w3.org/html5/websockets/
// Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
(function() {
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) return;
var console = window.console;
if (!console || !console.log || !console.error) {
console = {log: function(){ }, error: function(){ }};
}
if (!swfobject.hasFlashPlayerVersion("10.0.0")) {
console.error("Flash Player >= 10.0.0 is required.");
return;
}
if (location.protocol == "file:") {
console.error(
"WARNING: web-socket-js doesn't work in file:///... URL " +
"unless you set Flash Security Settings properly. " +
"Open the page via Web server i.e. http://...");
}
/**
* This class represents a faux web socket.
* @param {string} url
* @param {string} protocol
* @param {string} proxyHost
* @param {int} proxyPort
* @param {string} headers
*/
WebSocket = function(url, protocol, proxyHost, proxyPort, headers) {
var self = this;
self.__id = WebSocket.__nextId++;
WebSocket.__instances[self.__id] = self;
self.readyState = WebSocket.CONNECTING;
self.bufferedAmount = 0;
self.__events = {};
// Uses setTimeout() to make sure __createFlash() runs after the caller sets ws.onopen etc.
// Otherwise, when onopen fires immediately, onopen is called before it is set.
setTimeout(function() {
WebSocket.__addTask(function() {
WebSocket.__flash.create(
self.__id, url, protocol, proxyHost || null, proxyPort || 0, headers || null);
});
}, 0);
};
/**
* Send data to the web socket.
* @param {string} data The data to send to the socket.
* @return {boolean} True for success, false for failure.
*/
WebSocket.prototype.send = function(data) {
if (this.readyState == WebSocket.CONNECTING) {
throw "INVALID_STATE_ERR: Web Socket connection has not been established";
}
// We use encodeURIComponent() here, because FABridge doesn't work if
// the argument includes some characters. We don't use escape() here
// because of this:
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Functions#escape_and_unescape_Functions
// But it looks decodeURIComponent(encodeURIComponent(s)) doesn't
// preserve all Unicode characters either e.g. "\uffff" in Firefox.
// Note by wtritch: Hopefully this will not be necessary using ExternalInterface. Will require
// additional testing.
var result = WebSocket.__flash.send(this.__id, encodeURIComponent(data));
if (result < 0) { // success
return true;
} else {
this.bufferedAmount += result;
return false;
}
};
/**
* Close this web socket gracefully.
*/
WebSocket.prototype.close = function() {
if (this.readyState == WebSocket.CLOSED || this.readyState == WebSocket.CLOSING) {
return;
}
this.readyState = WebSocket.CLOSING;
WebSocket.__flash.close(this.__id);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture
* @return void
*/
WebSocket.prototype.addEventListener = function(type, listener, useCapture) {
if (!(type in this.__events)) {
this.__events[type] = [];
}
this.__events[type].push(listener);
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {string} type
* @param {function} listener
* @param {boolean} useCapture
* @return void
*/
WebSocket.prototype.removeEventListener = function(type, listener, useCapture) {
if (!(type in this.__events)) return;
var events = this.__events[type];
for (var i = events.length - 1; i >= 0; --i) {
if (events[i] === listener) {
events.splice(i, 1);
break;
}
}
};
/**
* Implementation of {@link <a href="http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-registration">DOM 2 EventTarget Interface</a>}
*
* @param {Event} event
* @return void
*/
WebSocket.prototype.dispatchEvent = function(event) {
var events = this.__events[event.type] || [];
for (var i = 0; i < events.length; ++i) {
events[i](event);
}
var handler = this["on" + event.type];
if (handler) handler(event);
};
/**
* Handles an event from Flash.
* @param {Object} flashEvent
*/
WebSocket.prototype.__handleEvent = function(flashEvent) {
if ("readyState" in flashEvent) {
this.readyState = flashEvent.readyState;
}
var jsEvent;
if (flashEvent.type == "open" || flashEvent.type == "error") {
jsEvent = this.__createSimpleEvent(flashEvent.type);
} else if (flashEvent.type == "close") {
// TODO implement jsEvent.wasClean
jsEvent = this.__createSimpleEvent("close");
} else if (flashEvent.type == "message") {
var data = decodeURIComponent(flashEvent.message);
jsEvent = this.__createMessageEvent("message", data);
} else {
throw "unknown event type: " + flashEvent.type;
}
this.dispatchEvent(jsEvent);
};
WebSocket.prototype.__createSimpleEvent = function(type) {
if (document.createEvent && window.Event) {
var event = document.createEvent("Event");
event.initEvent(type, false, false);
return event;
} else {
return {type: type, bubbles: false, cancelable: false};
}
};
WebSocket.prototype.__createMessageEvent = function(type, data) {
if (document.createEvent && window.MessageEvent && !window.opera) {
var event = document.createEvent("MessageEvent");
event.initMessageEvent("message", false, false, data, null, null, window, null);
return event;
} else {
// IE and Opera, the latter one truncates the data parameter after any 0x00 bytes.
return {type: type, data: data, bubbles: false, cancelable: false};
}
};
/**
* Define the WebSocket readyState enumeration.
*/
WebSocket.CONNECTING = 0;
WebSocket.OPEN = 1;
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;
WebSocket.__flash = null;
WebSocket.__instances = {};
WebSocket.__tasks = [];
WebSocket.__nextId = 0;
/**
* Load a new flash security policy file.
* @param {string} url
*/
WebSocket.loadFlashPolicyFile = function(url){
WebSocket.__addTask(function() {
WebSocket.__flash.loadManualPolicyFile(url);
});
};
/**
* Loads WebSocketMain.swf and creates WebSocketMain object in Flash.
*/
WebSocket.__initialize = function() {
if (WebSocket.__flash) return;
if (WebSocket.__swfLocation) {
// For backword compatibility.
window.WEB_SOCKET_SWF_LOCATION = WebSocket.__swfLocation;
}
if (!window.WEB_SOCKET_SWF_LOCATION) {
console.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");
return;
}
var container = document.createElement("div");
container.id = "webSocketContainer";
// Hides Flash box. We cannot use display: none or visibility: hidden because it prevents
// Flash from loading at least in IE. So we move it out of the screen at (-100, -100).
// But this even doesn't work with Flash Lite (e.g. in Droid Incredible). So with Flash
// Lite, we put it at (0, 0). This shows 1x1 box visible at left-top corner but this is
// the best we can do as far as we know now.
container.style.position = "absolute";
if (WebSocket.__isFlashLite()) {
container.style.left = "0px";
container.style.top = "0px";
} else {
container.style.left = "-100px";
container.style.top = "-100px";
}
var holder = document.createElement("div");
holder.id = "webSocketFlash";
container.appendChild(holder);
document.body.appendChild(container);
// See this article for hasPriority:
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
swfobject.embedSWF(
WEB_SOCKET_SWF_LOCATION,
"webSocketFlash",
"1" /* width */,
"1" /* height */,
"10.0.0" /* SWF version */,
null,
null,
{hasPriority: true, swliveconnect : true, allowScriptAccess: "always"},
null,
function(e) {
if (!e.success) {
console.error("[WebSocket] swfobject.embedSWF failed");
}
});
};
/**
* Called by Flash to notify JS that it's fully loaded and ready
* for communication.
*/
WebSocket.__onFlashInitialized = function() {
// We need to set a timeout here to avoid round-trip calls
// to flash during the initialization process.
setTimeout(function() {
WebSocket.__flash = document.getElementById("webSocketFlash");
WebSocket.__flash.setCallerUrl(location.href);
WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);
for (var i = 0; i < WebSocket.__tasks.length; ++i) {
WebSocket.__tasks[i]();
}
WebSocket.__tasks = [];
}, 0);
};
/**
* Called by Flash to notify WebSockets events are fired.
*/
WebSocket.__onFlashEvent = function() {
setTimeout(function() {
try {
// Gets events using receiveEvents() instead of getting it from event object
// of Flash event. This is to make sure to keep message order.
// It seems sometimes Flash events don't arrive in the same order as they are sent.
var events = WebSocket.__flash.receiveEvents();
for (var i = 0; i < events.length; ++i) {
WebSocket.__instances[events[i].webSocketId].__handleEvent(events[i]);
}
} catch (e) {
console.error(e);
}
}, 0);
return true;
};
// Called by Flash.
WebSocket.__log = function(message) {
console.log(decodeURIComponent(message));
};
// Called by Flash.
WebSocket.__error = function(message) {
console.error(decodeURIComponent(message));
};
WebSocket.__addTask = function(task) {
if (WebSocket.__flash) {
task();
} else {
WebSocket.__tasks.push(task);
}
};
/**
* Test if the browser is running flash lite.
* @return {boolean} True if flash lite is running, false otherwise.
*/
WebSocket.__isFlashLite = function() {
if (!window.navigator || !window.navigator.mimeTypes) {
return false;
}
var mimeType = window.navigator.mimeTypes["application/x-shockwave-flash"];
if (!mimeType || !mimeType.enabledPlugin || !mimeType.enabledPlugin.filename) {
return false;
}
return mimeType.enabledPlugin.filename.match(/flashlite/i) ? true : false;
};
if (!window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION) {
if (window.addEventListener) {
window.addEventListener("load", function(){
WebSocket.__initialize();
}, false);
} else {
window.attachEvent("onload", function(){
WebSocket.__initialize();
});
}
}
})();

View File

@ -0,0 +1,360 @@
/*
* Websock: high-performance binary WebSockets
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* Websock is similar to the standard WebSocket object but Websock
* enables communication with raw TCP sockets (i.e. the binary stream)
* via websockify. This is accomplished by base64 encoding the data
* stream between Websock and websockify.
*
* Websock has built-in receive queue buffering; the message event
* does not contain actual data but is simply a notification that
* there is new data available. Several rQ* methods are available to
* read binary data off of the receive queue.
*/
/*jslint browser: true, bitwise: false, plusplus: false */
/*global Util, Base64 */
// Load Flash WebSocket emulator if needed
// To force WebSocket emulator even when native WebSocket available
//window.WEB_SOCKET_FORCE_FLASH = true;
// To enable WebSocket emulator debug:
//window.WEB_SOCKET_DEBUG=1;
if (window.WebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
} else if (window.MozWebSocket && !window.WEB_SOCKET_FORCE_FLASH) {
Websock_native = true;
window.WebSocket = window.MozWebSocket;
} else {
/* no builtin WebSocket so load web_socket.js */
Websock_native = false;
(function () {
function get_INCLUDE_URI() {
return (typeof INCLUDE_URI !== "undefined") ?
INCLUDE_URI : "include/";
}
var start = "<script src='" + get_INCLUDE_URI(),
end = "'><\/script>", extra = "";
window.WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
"web-socket-js/WebSocketMain.swf";
if (Util.Engine.trident) {
Util.Debug("Forcing uncached load of WebSocketMain.swf");
window.WEB_SOCKET_SWF_LOCATION += "?" + Math.random();
}
extra += start + "web-socket-js/swfobject.js" + end;
extra += start + "web-socket-js/web_socket.js" + end;
document.write(extra);
}());
}
function Websock() {
"use strict";
var api = {}, // Public API
websocket = null, // WebSocket object
rQ = [], // Receive queue
rQi = 0, // Receive queue index
rQmax = 10000, // Max receive queue size before compacting
sQ = [], // Send queue
eventHandlers = {
'message' : function() {},
'open' : function() {},
'close' : function() {},
'error' : function() {}
},
test_mode = false;
//
// Queue public functions
//
function get_sQ() {
return sQ;
}
function get_rQ() {
return rQ;
}
function get_rQi() {
return rQi;
}
function set_rQi(val) {
rQi = val;
}
function rQlen() {
return rQ.length - rQi;
}
function rQpeek8() {
return (rQ[rQi] );
}
function rQshift8() {
return (rQ[rQi++] );
}
function rQunshift8(num) {
if (rQi === 0) {
rQ.unshift(num);
} else {
rQi -= 1;
rQ[rQi] = num;
}
}
function rQshift16() {
return (rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQshift32() {
return (rQ[rQi++] << 24) +
(rQ[rQi++] << 16) +
(rQ[rQi++] << 8) +
(rQ[rQi++] );
}
function rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
var arr = rQ.slice(rQi, rQi + len);
rQi += len;
return arr.map(function (num) {
return String.fromCharCode(num); } ).join('');
}
function rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = rQlen(); }
rQi += len;
return rQ.slice(rQi-len, rQi);
}
function rQslice(start, end) {
if (end) {
return rQ.slice(rQi + start, rQi + end);
} else {
return rQ.slice(rQi + start);
}
}
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
function rQwait(msg, num, goback) {
var rQlen = rQ.length - rQi; // Skip rQlen() function call
if (rQlen < num) {
if (goback) {
if (rQi < goback) {
throw("rQwait cannot backup " + goback + " bytes");
}
rQi -= goback;
}
//Util.Debug(" waiting for " + (num-rQlen) +
// " " + msg + " byte(s)");
return true; // true means need more data
}
return false;
}
//
// Private utility routines
//
function encode_message() {
/* base64 encode */
return Base64.encode(sQ);
}
function decode_message(data) {
//Util.Debug(">> decode_message: " + data);
/* base64 decode */
rQ = rQ.concat(Base64.decode(data, 0));
//Util.Debug(">> decode_message, rQ: " + rQ);
}
//
// Public Send functions
//
function flush() {
if (websocket.bufferedAmount !== 0) {
Util.Debug("bufferedAmount: " + websocket.bufferedAmount);
}
if (websocket.bufferedAmount < api.maxBufferedAmount) {
//Util.Debug("arr: " + arr);
//Util.Debug("sQ: " + sQ);
if (sQ.length > 0) {
websocket.send(encode_message(sQ));
sQ = [];
}
return true;
} else {
Util.Info("Delaying send, bufferedAmount: " +
websocket.bufferedAmount);
return false;
}
}
// overridable for testing
function send(arr) {
//Util.Debug(">> send_array: " + arr);
sQ = sQ.concat(arr);
return flush();
}
function send_string(str) {
//Util.Debug(">> send_string: " + str);
api.send(str.split('').map(
function (chr) { return chr.charCodeAt(0); } ) );
}
//
// Other public functions
function recv_message(e) {
//Util.Debug(">> recv_message: " + e.data.length);
try {
decode_message(e.data);
if (rQlen() > 0) {
eventHandlers.message();
// Compact the receive queue
if (rQ.length > rQmax) {
//Util.Debug("Compacting receive queue");
rQ = rQ.slice(rQi);
rQi = 0;
}
} else {
Util.Debug("Ignoring empty message");
}
} catch (exc) {
if (typeof exc.stack !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.stack);
} else if (typeof exc.description !== 'undefined') {
Util.Warn("recv_message, caught exception: " + exc.description);
} else {
Util.Warn("recv_message, caught exception:" + exc);
}
if (typeof exc.name !== 'undefined') {
eventHandlers.error(exc.name + ": " + exc.message);
} else {
eventHandlers.error(exc);
}
}
//Util.Debug("<< recv_message");
}
// Set event handlers
function on(evt, handler) {
eventHandlers[evt] = handler;
}
function init() {
rQ = [];
rQi = 0;
sQ = [];
websocket = null;
}
function open(uri) {
init();
if (test_mode) {
websocket = {};
} else {
websocket = new WebSocket(uri, 'base64');
// TODO: future native binary support
//websocket = new WebSocket(uri, ['binary', 'base64']);
}
websocket.onmessage = recv_message;
websocket.onopen = function() {
Util.Debug(">> WebSock.onopen");
if (websocket.protocol) {
Util.Info("Server chose sub-protocol: " + websocket.protocol);
} else {
Util.Error("Server select no sub-protocol!: " + websocket.protocol);
}
eventHandlers.open();
Util.Debug("<< WebSock.onopen");
};
websocket.onclose = function(e) {
Util.Debug(">> WebSock.onclose");
eventHandlers.close(e);
Util.Debug("<< WebSock.onclose");
};
websocket.onerror = function(e) {
Util.Debug(">> WebSock.onerror: " + e);
eventHandlers.error(e);
Util.Debug("<< WebSock.onerror");
};
}
function close() {
if (websocket) {
if ((websocket.readyState === WebSocket.OPEN) ||
(websocket.readyState === WebSocket.CONNECTING)) {
Util.Info("Closing WebSocket connection");
websocket.close();
}
websocket.onmessage = function (e) { return; };
}
}
// Override internal functions for testing
// Takes a send function, returns reference to recv function
function testMode(override_send) {
test_mode = true;
api.send = override_send;
api.close = function () {};
return recv_message;
}
function constructor() {
// Configuration settings
api.maxBufferedAmount = 200;
// Direct access to send and receive queues
api.get_sQ = get_sQ;
api.get_rQ = get_rQ;
api.get_rQi = get_rQi;
api.set_rQi = set_rQi;
// Routines to read from the receive queue
api.rQlen = rQlen;
api.rQpeek8 = rQpeek8;
api.rQshift8 = rQshift8;
api.rQunshift8 = rQunshift8;
api.rQshift16 = rQshift16;
api.rQshift32 = rQshift32;
api.rQshiftStr = rQshiftStr;
api.rQshiftBytes = rQshiftBytes;
api.rQslice = rQslice;
api.rQwait = rQwait;
api.flush = flush;
api.send = send;
api.send_string = send_string;
api.on = on;
api.init = init;
api.open = open;
api.close = close;
api.testMode = testMode;
return api;
}
return constructor();
}

View File

@ -0,0 +1,148 @@
/*
* noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin
* Licensed under LGPL-3 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
"use strict";
/*jslint bitwise: false, white: false */
/*global Util, window, document */
// Globals defined here
var WebUtil = {}, $D;
/*
* Simple DOM selector by ID
*/
if (!window.$D) {
window.$D = function (id) {
if (document.getElementById) {
return document.getElementById(id);
} else if (document.all) {
return document.all[id];
} else if (document.layers) {
return document.layers[id];
}
return undefined;
};
}
/*
* ------------------------------------------------------
* Namespaced in WebUtil
* ------------------------------------------------------
*/
// init log level reading the logging HTTP param
WebUtil.init_logging = function() {
Util._log_level = (document.location.href.match(
/logging=([A-Za-z0-9\._\-]*)/) ||
['', Util._log_level])[1];
Util.init_logging();
};
WebUtil.init_logging();
WebUtil.dirObj = function (obj, depth, parent) {
var i, msg = "", val = "";
if (! depth) { depth=2; }
if (! parent) { parent= ""; }
// Print the properties of the passed-in object
for (i in obj) {
if ((depth > 1) && (typeof obj[i] === "object")) {
// Recurse attributes that are objects
msg += WebUtil.dirObj(obj[i], depth-1, parent + "." + i);
} else {
//val = new String(obj[i]).replace("\n", " ");
if (typeof(obj[i]) === "undefined") {
val = "undefined";
} else {
val = obj[i].toString().replace("\n", " ");
}
if (val.length > 30) {
val = val.substr(0,30) + "...";
}
msg += parent + "." + i + ": " + val + "\n";
}
}
return msg;
};
// Read a query string variable
WebUtil.getQueryVar = function(name, defVal) {
var re = new RegExp('[?][^#]*' + name + '=([^&#]*)');
if (typeof defVal === 'undefined') { defVal = null; }
return (document.location.href.match(re) || ['',defVal])[1];
};
/*
* Cookie handling. Dervied from: http://www.quirksmode.org/js/cookies.html
*/
// No days means only for this browser session
WebUtil.createCookie = function(name,value,days) {
var date, expires;
if (days) {
date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
expires = "; expires="+date.toGMTString();
}
else {
expires = "";
}
document.cookie = name+"="+value+expires+"; path=/";
};
WebUtil.readCookie = function(name, defaultValue) {
var i, c, nameEQ = name + "=", ca = document.cookie.split(';');
for(i=0; i < ca.length; i += 1) {
c = ca[i];
while (c.charAt(0) === ' ') { c = c.substring(1,c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
}
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
};
WebUtil.eraseCookie = function(name) {
WebUtil.createCookie(name,"",-1);
};
/*
* Alternate stylesheet selection
*/
WebUtil.getStylesheets = function() { var i, links, sheets = [];
links = document.getElementsByTagName("link");
for (i = 0; i < links.length; i += 1) {
if (links[i].title &&
links[i].rel.toUpperCase().indexOf("STYLESHEET") > -1) {
sheets.push(links[i]);
}
}
return sheets;
};
// No sheet means try and use value from cookie, null sheet used to
// clear all alternates.
WebUtil.selectStylesheet = function(sheet) {
var i, link, sheets = WebUtil.getStylesheets();
if (typeof sheet === 'undefined') {
sheet = 'default';
}
for (i=0; i < sheets.length; i += 1) {
link = sheets[i];
if (link.title === sheet) {
Util.Debug("Using stylesheet " + sheet);
link.disabled = false;
} else {
//Util.Debug("Skipping stylesheet " + link.title);
link.disabled = true;
}
}
return sheet;
};