summaryrefslogtreecommitdiffstats
path: root/webclients
diff options
context:
space:
mode:
authorChristian Beier <dontmind@freeshell.org>2011-11-09 20:00:16 +0100
committerChristian Beier <dontmind@freeshell.org>2011-11-09 20:00:16 +0100
commitfaadd484489efddd182d6d0df43d8ecfdbff29ff (patch)
tree9c098fca3bd1280a2166259650695d172d049aee /webclients
parent7cb0e4a9a95886300268e1bff1e72b4c18f1b762 (diff)
downloadlibtdevnc-faadd484489efddd182d6d0df43d8ecfdbff29ff.tar.gz
libtdevnc-faadd484489efddd182d6d0df43d8ecfdbff29ff.zip
Rename 'classes' dir to 'webclients'.
Diffstat (limited to 'webclients')
-rw-r--r--webclients/Makefile.am5
-rw-r--r--webclients/VncViewer.jarbin0 -> 35462 bytes
-rw-r--r--webclients/index.vnc37
-rw-r--r--webclients/javaviewer.pseudo_proxy.patch141
-rw-r--r--webclients/novnc/LICENSE.txt33
-rw-r--r--webclients/novnc/README.md93
l---------webclients/novnc/favicon.ico1
-rw-r--r--webclients/novnc/images/clipboard.pngbin0 -> 501 bytes
-rw-r--r--webclients/novnc/images/connect.pngbin0 -> 404 bytes
-rw-r--r--webclients/novnc/images/ctrlaltdel.pngbin0 -> 317 bytes
-rw-r--r--webclients/novnc/images/disconnect.pngbin0 -> 1378 bytes
-rw-r--r--webclients/novnc/images/drag.pngbin0 -> 963 bytes
-rw-r--r--webclients/novnc/images/favicon.icobin0 -> 1150 bytes
-rw-r--r--webclients/novnc/images/favicon.pngbin0 -> 453 bytes
-rw-r--r--webclients/novnc/images/keyboard.pngbin0 -> 1283 bytes
-rw-r--r--webclients/novnc/images/mouse_left.pngbin0 -> 511 bytes
-rw-r--r--webclients/novnc/images/mouse_middle.pngbin0 -> 517 bytes
-rw-r--r--webclients/novnc/images/mouse_none.pngbin0 -> 497 bytes
-rw-r--r--webclients/novnc/images/mouse_right.pngbin0 -> 513 bytes
-rw-r--r--webclients/novnc/images/screen_320x460.pngbin0 -> 12778 bytes
-rw-r--r--webclients/novnc/images/screen_57x57.pngbin0 -> 1807 bytes
-rw-r--r--webclients/novnc/images/screen_700x700.pngbin0 -> 17930 bytes
-rw-r--r--webclients/novnc/images/settings.pngbin0 -> 2495 bytes
-rw-r--r--webclients/novnc/include/Orbitron700.ttfbin0 -> 38580 bytes
-rw-r--r--webclients/novnc/include/Orbitron700.woffbin0 -> 17472 bytes
-rw-r--r--webclients/novnc/include/base.css380
-rw-r--r--webclients/novnc/include/base64.js147
-rw-r--r--webclients/novnc/include/black.css45
-rw-r--r--webclients/novnc/include/blue.css27
-rw-r--r--webclients/novnc/include/des.js273
-rw-r--r--webclients/novnc/include/display.js671
-rw-r--r--webclients/novnc/include/input.js1884
-rw-r--r--webclients/novnc/include/logo.js1
-rw-r--r--webclients/novnc/include/playback.js90
-rw-r--r--webclients/novnc/include/rfb.js1613
-rw-r--r--webclients/novnc/include/ui.js629
-rw-r--r--webclients/novnc/include/util.js276
-rw-r--r--webclients/novnc/include/vnc.js42
-rw-r--r--webclients/novnc/include/web-socket-js/README.txt109
-rw-r--r--webclients/novnc/include/web-socket-js/WebSocketMain.swfbin0 -> 175746 bytes
-rw-r--r--webclients/novnc/include/web-socket-js/swfobject.js4
-rw-r--r--webclients/novnc/include/web-socket-js/web_socket.js341
-rw-r--r--webclients/novnc/include/websock.js347
-rw-r--r--webclients/novnc/include/webutil.js148
-rw-r--r--webclients/novnc/vnc.html180
-rw-r--r--webclients/novnc/vnc_auto.html116
-rw-r--r--webclients/ssl/Makefile.am2
-rw-r--r--webclients/ssl/README338
-rw-r--r--webclients/ssl/SignedUltraViewerSSL.jarbin0 -> 113117 bytes
-rw-r--r--webclients/ssl/SignedVncViewer.jarbin0 -> 89208 bytes
-rw-r--r--webclients/ssl/UltraViewerSSL.jarbin0 -> 110040 bytes
-rw-r--r--webclients/ssl/VncViewer.jarbin0 -> 86228 bytes
-rw-r--r--webclients/ssl/index.vnc26
-rwxr-xr-xwebclients/ssl/onetimekey65
-rw-r--r--webclients/ssl/proxy.vnc73
-rwxr-xr-xwebclients/ssl/ss_vncviewer3676
-rw-r--r--webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch111
-rw-r--r--webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch2600
-rw-r--r--webclients/ssl/ultra.vnc28
-rw-r--r--webclients/ssl/ultraproxy.vnc28
-rw-r--r--webclients/ssl/ultrasigned.vnc28
-rw-r--r--webclients/ssl/ultravnc-102-JavaViewer-ssl-etc.patch5494
62 files changed, 20102 insertions, 0 deletions
diff --git a/webclients/Makefile.am b/webclients/Makefile.am
new file mode 100644
index 0000000..c5497a8
--- /dev/null
+++ b/webclients/Makefile.am
@@ -0,0 +1,5 @@
+EXTRA_DIST=VncViewer.jar index.vnc javaviewer.pseudo_proxy.patch
+
+SUBDIRS = ssl
+DIST_SUBDIRS = ssl
+
diff --git a/webclients/VncViewer.jar b/webclients/VncViewer.jar
new file mode 100644
index 0000000..602fdb9
--- /dev/null
+++ b/webclients/VncViewer.jar
Binary files differ
diff --git a/webclients/index.vnc b/webclients/index.vnc
new file mode 100644
index 0000000..34ad25d
--- /dev/null
+++ b/webclients/index.vnc
@@ -0,0 +1,37 @@
+<!-- index.vnc - default html page for Java VNC viewer applet. On any file
+ ending in .vnc, the HTTP server embedded in Xvnc will substitute the
+ following variables when preceded by a dollar: USER, DESKTOP, DISPLAY,
+ APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT, PARAMS. Use two dollar
+ signs ($$) to get a dollar sign in the generated html. -->
+
+<HTML>
+<head>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+</head>
+<APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=PORT value=$PORT>
+<param name="Open New Window" value=yes>
+</APPLET>
+<br/>
+<br/>
+
+If the above Java applet does not work, you can also try the new JavaScript-only <a href="http://kanaka.github.com/noVNC/">noVNC</a> viewer. You will need a HTML5-capable browser though.
+<script language="JavaScript">
+ <!--
+ function start_novnc(){
+ open("novnc/vnc_auto.html?host=" + document.location.hostname + "&port=$PORT&true_color=1");
+ }
+ -->
+</script>
+<form name="novnc_button_form">
+ <input type="button" name="novnc_button" value="Click here to connect using noVNC" onClick='start_novnc()'>
+</form>
+
+<br/>
+<br/>
+<br/>
+<A href="http://libvncserver.sf.net/">LibVNCServer/LibVNCClient Homepage</A>
+</HTML>
diff --git a/webclients/javaviewer.pseudo_proxy.patch b/webclients/javaviewer.pseudo_proxy.patch
new file mode 100644
index 0000000..4d2f36e
--- /dev/null
+++ b/webclients/javaviewer.pseudo_proxy.patch
@@ -0,0 +1,141 @@
+diff -ru vnc_javasrc/OptionsFrame.java proxy_vnc_javasrc/OptionsFrame.java
+--- vnc_javasrc/OptionsFrame.java Fri Jul 5 08:17:23 2002
++++ proxy_vnc_javasrc/OptionsFrame.java Thu Aug 22 23:24:44 2002
+@@ -70,6 +70,12 @@
+
+ Label[] labels = new Label[names.length];
+ Choice[] choices = new Choice[names.length];
++
++ Label proxyHostLabel;
++ TextField proxyHostEdit;
++ Label proxyPortLabel;
++ TextField proxyPortEdit;
++
+ Button closeButton;
+ VncViewer viewer;
+
+@@ -93,6 +99,9 @@
+ boolean shareDesktop;
+ boolean viewOnly;
+
++ String proxyHost;
++ int proxyPort;
++
+ //
+ // Constructor. Set up the labels and choices from the names and values
+ // arrays.
+@@ -126,6 +135,32 @@
+ }
+ }
+
++ // TODO: find a way to set these to defaults from browser
++ proxyPort = viewer.readIntParameter("Use Proxy Port", -1);
++ if(proxyPort>-1) {
++ proxyHost = viewer.readParameter("Use Proxy Host", false);
++ if(proxyHost == null)
++ proxyHost = viewer.host;
++
++ proxyHostLabel = new Label("Proxy Host");
++ gbc.gridwidth = 1;
++ gridbag.setConstraints(proxyHostLabel,gbc);
++ add(proxyHostLabel);
++ proxyHostEdit = new TextField();
++ gbc.gridwidth = GridBagConstraints.REMAINDER;
++ gridbag.setConstraints(proxyHostEdit,gbc);
++ add(proxyHostEdit);
++
++ proxyPortLabel = new Label("Proxy Port");
++ gbc.gridwidth = 1;
++ gridbag.setConstraints(proxyPortLabel,gbc);
++ add(proxyPortLabel);
++ proxyPortEdit = new TextField();
++ gbc.gridwidth = GridBagConstraints.REMAINDER;
++ gridbag.setConstraints(proxyPortEdit,gbc);
++ add(proxyPortEdit);
++ }
++
+ closeButton = new Button("Close");
+ gbc.gridwidth = GridBagConstraints.REMAINDER;
+ gridbag.setConstraints(closeButton, gbc);
+@@ -161,6 +196,11 @@
+ }
+ }
+
++ if(proxyPort>-1) {
++ proxyPortEdit.setText(Integer.toString(proxyPort));
++ proxyHostEdit.setText(proxyHost);
++ }
++
+ // Make the booleans and encodings array correspond to the state of the GUI
+
+ setEncodings();
+@@ -361,8 +401,12 @@
+ //
+
+ public void actionPerformed(ActionEvent evt) {
+- if (evt.getSource() == closeButton)
++ if (evt.getSource() == closeButton) {
+ setVisible(false);
++ proxyHost = proxyHostEdit.getText();
++ proxyPort = Integer.parseInt(proxyPortEdit.getText());
++ System.err.println("proxy is " + proxyHost + ":" + proxyPort);
++ }
+ }
+
+ //
+diff -ru vnc_javasrc/RfbProto.java proxy_vnc_javasrc/RfbProto.java
+--- vnc_javasrc/RfbProto.java Sun Aug 4 18:39:35 2002
++++ proxy_vnc_javasrc/RfbProto.java Thu Aug 22 22:53:53 2002
+@@ -119,12 +119,51 @@
+ viewer = v;
+ host = h;
+ port = p;
+- sock = new Socket(host, port);
++ if(viewer.options.proxyPort>-1)
++ sock = new Socket(viewer.options.proxyHost, viewer.options.proxyPort);
++ else
++ sock = new Socket(host, port);
+ is = new DataInputStream(new BufferedInputStream(sock.getInputStream(),
+ 16384));
+ os = sock.getOutputStream();
++ if(viewer.options.proxyPort>-1)
++ negotiateProxy(host,port);
+ }
+
++ // this is inefficient as hell, but only used once per connection
++ String readLine() {
++ byte[] ba = new byte[1];
++ String s = new String("");
++
++ ba[0]=0;
++ try {
++ while(ba[0] != 0xa) {
++ ba[0] = (byte)is.readUnsignedByte();
++ s += new String(ba);
++ }
++ } catch(Exception e) {
++ e.printStackTrace();
++ }
++ return s;
++ }
++
++ void negotiateProxy(String realHost,int realPort) throws IOException {
++ String line;
++
++ // this would be the correct way, but we want to trick strict proxies.
++ // line = "CONNECT " + realHost + ":" + realPort + " HTTP/1.1\r\nHost: " + realHost + ":" + realPort + "\r\n\r\n";
++ line = "GET " + realHost + ":" + realPort + "/proxied.connection HTTP/1.0\r\nPragma: No-Cache\r\nProxy-Connection: Keep-Alive\r\n\r\n";
++ os.write(line.getBytes());
++
++ line = readLine();
++ System.err.println("Proxy said: " + line);
++ if(!(line.substring(0,7)+line.substring(8,12)).equalsIgnoreCase("HTTP/1. 200")) {
++ IOException e = new IOException(line);
++ throw e;
++ }
++ while(!line.equals("\r\n") && !line.equals("\n"))
++ line = readLine();
++ }
+
+ void close() {
+ try {
diff --git a/webclients/novnc/LICENSE.txt b/webclients/novnc/LICENSE.txt
new file mode 100644
index 0000000..755ace3
--- /dev/null
+++ b/webclients/novnc/LICENSE.txt
@@ -0,0 +1,33 @@
+noVNC is Copyright (C) 2011 Joel Martin <github@martintribe.org>
+
+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
+
+noVNC is licensed under the LGPL (GNU Lesser General Public License)
+version 3 with the following exceptions (all LGPL-3 compatible):
+
+ include/input.js : LGPL-2 or any later version
+
+ include/base64.js : Dual GPL-2 or LGPL-2.1
+
+ include/des.js : Various BSD style licenses
+
+ include/web-socket-js/ : New BSD license. Source code at
+ http://github.com/gimite/web-socket-js
+
+ 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/
+
+The license texts are included at:
+ docs/LICENSE.LGPL-3 and
+ docs/LICENSE.GPL-3
+ docs/LICENSE.OFL-1.1
+
+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
diff --git a/webclients/novnc/README.md b/webclients/novnc/README.md
new file mode 100644
index 0000000..4672969
--- /dev/null
+++ b/webclients/novnc/README.md
@@ -0,0 +1,93 @@
+## noVNC: HTML5 VNC Client
+
+
+### Description
+
+noVNC is a VNC client implemented using HTML5 technologies,
+specifically Canvas and WebSockets (supports 'wss://' encryption).
+noVNC is licensed under the
+[LGPLv3](http://www.gnu.org/licenses/lgpl.html).
+
+Special thanks to [Sentry Data Systems](http://www.sentryds.com) for
+sponsoring ongoing development of this project (and for employing me).
+
+There are many companies/projects that have integrated noVNC into
+their products including: [Sentry Data Systems](http://www.sentryds.com), [Ganeti Web Manager](http://code.osuosl.org/projects/ganeti-webmgr), [Archipel](http://archipelproject.org), [openQRM](http://www.openqrm.com/), [OpenNode](http://www.opennodecloud.com/), [OpenStack](http://www.openstack.org), [Broadway (HTML5 GDK/GTK+ backend)](http://blogs.gnome.org/alexl/2011/03/15/gtk-html-backend-update/), [OpenNebula](http://opennebula.org/), [CloudSigma](http://www.cloudsigma.com/), [Zentyal (formerly eBox)](http://www.zentyal.org/), and [SlapOS](http://www.slapos.org). See [this wiki page](https://github.com/kanaka/noVNC/wiki/ProjectsCompanies-using-noVNC) for more info and links.
+
+Notable commits, announcements and news are posted to
+@<a href="http://www.twitter.com/noVNC">noVNC</a>
+
+
+### Screenshots
+
+Running in Chrome before and after connecting:
+
+<img src="http://kanaka.github.com/noVNC/img/noVNC-5.png" width=400>&nbsp;<img src="http://kanaka.github.com/noVNC/img/noVNC-7.jpg" width=400>
+
+See more screenshots <a href="http://kanaka.github.com/noVNC/screenshots.html">here</a>.
+
+
+### Browser Requirements
+
+* HTML5 Canvas (with createImageData): Chrome, Firefox 3.6+, iOS
+ Safari, Opera 11+, Internet Explorer 9+, etc.
+
+* HTML5 WebSockets: For browsers that do not have builtin
+ WebSockets support, the project includes
+ <a href="http://github.com/gimite/web-socket-js">web-socket-js</a>,
+ a WebSockets emulator using Adobe Flash. iOS 4.2+ has built-in
+ WebSocket support.
+
+* Fast Javascript Engine: noVNC avoids using new Javascript
+ functionality so it will run on older browsers, but decode and
+ rendering happen in Javascript, so a slow Javascript engine will
+ mean noVNC is painfully slow.
+
+* I maintain a more detailed browser compatibility list <a
+ href="https://github.com/kanaka/noVNC/wiki/Browser-support">here</a>.
+
+
+### Server Requirements
+
+Unless you are using a VNC server with support for WebSockets
+connections (only my [fork of libvncserver](http://github.com/kanaka/libvncserver)
+currently), you need to use a WebSockets to TCP socket proxy. There is
+a python proxy included ('websockify'). One advantage of using the
+proxy is that it has builtin support for SSL/TLS encryption (i.e.
+"wss://").
+
+There a few reasons why a proxy is required:
+
+ 1. WebSockets is not a pure socket protocol. There is an initial HTTP
+ like handshake to allow easy hand-off by web servers and allow
+ some origin policy exchange. Also, each WebSockets frame begins
+ with 0 ('\x00') and ends with 255 ('\xff').
+
+ 2. Javascript itself does not have the ability to handle pure byte
+ arrays. The python proxy encodes the data as base64 so that the
+ Javascript client can decode the data as an integer array.
+
+
+### Quick Start
+
+* Use the launch script to start a mini-webserver and the WebSockets
+ proxy (websockify). The `--vnc` option is used to specify the location of
+ a running VNC server:
+
+ `./utils/launch.sh --vnc localhost:5901`
+
+* Point your browser to the cut-and-paste URL that is output by the
+ launch script. Enter a password if the VNC server has one
+ configured. Hit the Connect button and enjoy!
+
+
+### Other Pages
+
+* [Advanced Usage](https://github.com/kanaka/noVNC/wiki/Advanced-usage). Generating an SSL
+ certificate, starting a VNC server, advanced websockify usage, etc.
+
+* [Integrating noVNC](https://github.com/kanaka/noVNC/wiki/Integration) into existing projects.
+
+* [Troubleshooting noVNC](https://github.com/kanaka/noVNC/wiki/Troubleshooting) problems.
+
+
diff --git a/webclients/novnc/favicon.ico b/webclients/novnc/favicon.ico
new file mode 120000
index 0000000..45399c8
--- /dev/null
+++ b/webclients/novnc/favicon.ico
@@ -0,0 +1 @@
+images/favicon.ico \ No newline at end of file
diff --git a/webclients/novnc/images/clipboard.png b/webclients/novnc/images/clipboard.png
new file mode 100644
index 0000000..24df33c
--- /dev/null
+++ b/webclients/novnc/images/clipboard.png
Binary files differ
diff --git a/webclients/novnc/images/connect.png b/webclients/novnc/images/connect.png
new file mode 100644
index 0000000..79e71ad
--- /dev/null
+++ b/webclients/novnc/images/connect.png
Binary files differ
diff --git a/webclients/novnc/images/ctrlaltdel.png b/webclients/novnc/images/ctrlaltdel.png
new file mode 100644
index 0000000..31922e5
--- /dev/null
+++ b/webclients/novnc/images/ctrlaltdel.png
Binary files differ
diff --git a/webclients/novnc/images/disconnect.png b/webclients/novnc/images/disconnect.png
new file mode 100644
index 0000000..8832f5e
--- /dev/null
+++ b/webclients/novnc/images/disconnect.png
Binary files differ
diff --git a/webclients/novnc/images/drag.png b/webclients/novnc/images/drag.png
new file mode 100644
index 0000000..433f896
--- /dev/null
+++ b/webclients/novnc/images/drag.png
Binary files differ
diff --git a/webclients/novnc/images/favicon.ico b/webclients/novnc/images/favicon.ico
new file mode 100644
index 0000000..c999634
--- /dev/null
+++ b/webclients/novnc/images/favicon.ico
Binary files differ
diff --git a/webclients/novnc/images/favicon.png b/webclients/novnc/images/favicon.png
new file mode 100644
index 0000000..e2bdb19
--- /dev/null
+++ b/webclients/novnc/images/favicon.png
Binary files differ
diff --git a/webclients/novnc/images/keyboard.png b/webclients/novnc/images/keyboard.png
new file mode 100644
index 0000000..f797952
--- /dev/null
+++ b/webclients/novnc/images/keyboard.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_left.png b/webclients/novnc/images/mouse_left.png
new file mode 100644
index 0000000..1de7a48
--- /dev/null
+++ b/webclients/novnc/images/mouse_left.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_middle.png b/webclients/novnc/images/mouse_middle.png
new file mode 100644
index 0000000..81fbd9b
--- /dev/null
+++ b/webclients/novnc/images/mouse_middle.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_none.png b/webclients/novnc/images/mouse_none.png
new file mode 100644
index 0000000..93dbf57
--- /dev/null
+++ b/webclients/novnc/images/mouse_none.png
Binary files differ
diff --git a/webclients/novnc/images/mouse_right.png b/webclients/novnc/images/mouse_right.png
new file mode 100644
index 0000000..355b25d
--- /dev/null
+++ b/webclients/novnc/images/mouse_right.png
Binary files differ
diff --git a/webclients/novnc/images/screen_320x460.png b/webclients/novnc/images/screen_320x460.png
new file mode 100644
index 0000000..172ec55
--- /dev/null
+++ b/webclients/novnc/images/screen_320x460.png
Binary files differ
diff --git a/webclients/novnc/images/screen_57x57.png b/webclients/novnc/images/screen_57x57.png
new file mode 100644
index 0000000..e2085f2
--- /dev/null
+++ b/webclients/novnc/images/screen_57x57.png
Binary files differ
diff --git a/webclients/novnc/images/screen_700x700.png b/webclients/novnc/images/screen_700x700.png
new file mode 100644
index 0000000..ae67768
--- /dev/null
+++ b/webclients/novnc/images/screen_700x700.png
Binary files differ
diff --git a/webclients/novnc/images/settings.png b/webclients/novnc/images/settings.png
new file mode 100644
index 0000000..a43f5e1
--- /dev/null
+++ b/webclients/novnc/images/settings.png
Binary files differ
diff --git a/webclients/novnc/include/Orbitron700.ttf b/webclients/novnc/include/Orbitron700.ttf
new file mode 100644
index 0000000..e28729d
--- /dev/null
+++ b/webclients/novnc/include/Orbitron700.ttf
Binary files differ
diff --git a/webclients/novnc/include/Orbitron700.woff b/webclients/novnc/include/Orbitron700.woff
new file mode 100644
index 0000000..61db630
--- /dev/null
+++ b/webclients/novnc/include/Orbitron700.woff
Binary files differ
diff --git a/webclients/novnc/include/base.css b/webclients/novnc/include/base.css
new file mode 100644
index 0000000..0a62a1b
--- /dev/null
+++ b/webclients/novnc/include/base.css
@@ -0,0 +1,380 @@
+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 {
+ margin-top:77px;
+ right:12px;
+ position:fixed;
+}
+#noVNC_controls.top:after {
+ right:15px;
+}
+
+#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;
+ }
+}
diff --git a/webclients/novnc/include/base64.js b/webclients/novnc/include/base64.js
new file mode 100644
index 0000000..c68b33a
--- /dev/null
+++ b/webclients/novnc/include/base64.js
@@ -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 '" + data.charCodeAt(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 */
diff --git a/webclients/novnc/include/black.css b/webclients/novnc/include/black.css
new file mode 100644
index 0000000..8f80f66
--- /dev/null
+++ b/webclients/novnc/include/black.css
@@ -0,0 +1,45 @@
+#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 */
+}
diff --git a/webclients/novnc/include/blue.css b/webclients/novnc/include/blue.css
new file mode 100644
index 0000000..a8baf70
--- /dev/null
+++ b/webclients/novnc/include/blue.css
@@ -0,0 +1,27 @@
+
+#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;
+}
+
diff --git a/webclients/novnc/include/des.js b/webclients/novnc/include/des.js
new file mode 100644
index 0000000..1f95285
--- /dev/null
+++ b/webclients/novnc/include/des.js
@@ -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
diff --git a/webclients/novnc/include/display.js b/webclients/novnc/include/display.js
new file mode 100644
index 0000000..2cf262d
--- /dev/null
+++ b/webclients/novnc/include/display.js
@@ -0,0 +1,671 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 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,
+
+ // Predefine function variables (jslint)
+ imageDataGet, rgbxImageData, cmapImageData,
+ setFillColor, rescale,
+
+ // 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 rgb, newStyle;
+ if (conf.true_color) {
+ rgb = color;
+ } else {
+ rgb = conf.colourMap[color[0]];
+ }
+ newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
+ 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);
+ }
+
+ // 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, rgb, 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) {
+ rgb = color;
+ } else {
+ rgb = conf.colourMap[color[0]];
+ }
+ red = rgb[0];
+ green = rgb[1];
+ blue = rgb[2];
+ 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, rgb, red, green, blue, width, j, i, xend, yend;
+ if (conf.prefer_js) {
+ data = tile.data;
+ width = tile.width;
+ if (conf.true_color) {
+ rgb = color;
+ } else {
+ rgb = conf.colourMap[color[0]];
+ }
+ red = rgb[0];
+ green = rgb[1];
+ blue = rgb[2];
+ 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
+};
+
+rgbxImageData = 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 ];
+ 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);
+};
+
+cmapImageData = function(x, y, width, height, arr, offset) {
+ var img, i, j, data, rgb, 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) {
+ rgb = cmap[arr[j]];
+ data[i ] = rgb[0];
+ data[i + 1] = rgb[1];
+ data[i + 2] = rgb[2];
+ 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) {
+ rgbxImageData(x, y, width, height, arr, offset);
+ } else {
+ 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;
+};
+
+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);
+}
diff --git a/webclients/novnc/include/input.js b/webclients/novnc/include/input.js
new file mode 100644
index 0000000..3124d08
--- /dev/null
+++ b/webclients/novnc/include/input.js
@@ -0,0 +1,1884 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-2 or any later version (see LICENSE.txt)
+ */
+
+/*jslint browser: true, white: false, bitwise: false */
+/*global window, Util */
+
+
+//
+// Keyboard event handler
+//
+
+function Keyboard(defaults) {
+"use strict";
+
+var that = {}, // Public API methods
+ conf = {}, // Configuration attributes
+
+ keyDownList = []; // List of depressed keys
+ // (even if they are happy)
+
+// Configuration attributes
+Util.conf_defaults(conf, that, defaults, [
+ ['target', 'wo', 'dom', document, 'DOM element that captures keyboard input'],
+ ['focused', 'rw', 'bool', true, 'Capture and send key events'],
+
+ ['onKeyPress', 'rw', 'func', null, 'Handler for key press/release']
+ ]);
+
+
+//
+// Private functions
+//
+
+// From the event keyCode return the keysym value for keys that need
+// to be suppressed otherwise they may trigger unintended browser
+// actions
+function getKeysymSpecial(evt) {
+ var keysym = null;
+
+ switch ( evt.keyCode ) {
+ // These generate a keyDown and keyPress in Firefox and Opera
+ case 8 : keysym = 0xFF08; break; // BACKSPACE
+ case 13 : keysym = 0xFF0D; break; // ENTER
+
+ // This generates a keyDown and keyPress in Opera
+ case 9 : keysym = 0xFF09; break; // TAB
+ default : break;
+ }
+
+ if (evt.type === 'keydown') {
+ switch ( evt.keyCode ) {
+ case 27 : keysym = 0xFF1B; break; // ESCAPE
+ case 46 : keysym = 0xFFFF; break; // DELETE
+
+ case 36 : keysym = 0xFF50; break; // HOME
+ case 35 : keysym = 0xFF57; break; // END
+ case 33 : keysym = 0xFF55; break; // PAGE_UP
+ case 34 : keysym = 0xFF56; break; // PAGE_DOWN
+ case 45 : keysym = 0xFF63; break; // INSERT
+ // '-' during keyPress
+ case 37 : keysym = 0xFF51; break; // LEFT
+ case 38 : keysym = 0xFF52; break; // UP
+ case 39 : keysym = 0xFF53; break; // RIGHT
+ case 40 : keysym = 0xFF54; break; // DOWN
+ case 16 : keysym = 0xFFE1; break; // SHIFT
+ case 17 : keysym = 0xFFE3; break; // CONTROL
+ //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
+ case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
+
+ case 112 : keysym = 0xFFBE; break; // F1
+ case 113 : keysym = 0xFFBF; break; // F2
+ case 114 : keysym = 0xFFC0; break; // F3
+ case 115 : keysym = 0xFFC1; break; // F4
+ case 116 : keysym = 0xFFC2; break; // F5
+ case 117 : keysym = 0xFFC3; break; // F6
+ case 118 : keysym = 0xFFC4; break; // F7
+ case 119 : keysym = 0xFFC5; break; // F8
+ case 120 : keysym = 0xFFC6; break; // F9
+ case 121 : keysym = 0xFFC7; break; // F10
+ case 122 : keysym = 0xFFC8; break; // F11
+ case 123 : keysym = 0xFFC9; break; // F12
+
+ default : break;
+ }
+ }
+
+ if ((!keysym) && (evt.ctrlKey || evt.altKey)) {
+ if ((typeof(evt.which) !== "undefined") && (evt.which > 0)) {
+ keysym = evt.which;
+ } else {
+ // IE9 always
+ // Firefox and Opera when ctrl/alt + special
+ Util.Warn("which not set, using keyCode");
+ keysym = evt.keyCode;
+ }
+
+ /* Remap symbols */
+ switch (keysym) {
+ case 186 : keysym = 59; break; // ; (IE)
+ case 187 : keysym = 61; break; // = (IE)
+ case 188 : keysym = 44; break; // , (Mozilla, IE)
+ case 109 : // - (Mozilla, Opera)
+ if (Util.Engine.gecko || Util.Engine.presto) {
+ keysym = 45; }
+ break;
+ case 189 : keysym = 45; break; // - (IE)
+ case 190 : keysym = 46; break; // . (Mozilla, IE)
+ case 191 : keysym = 47; break; // / (Mozilla, IE)
+ case 192 : keysym = 96; break; // ` (Mozilla, IE)
+ case 219 : keysym = 91; break; // [ (Mozilla, IE)
+ case 220 : keysym = 92; break; // \ (Mozilla, IE)
+ case 221 : keysym = 93; break; // ] (Mozilla, IE)
+ case 222 : keysym = 39; break; // ' (Mozilla, IE)
+ }
+
+ /* Remap shifted and unshifted keys */
+ if (!!evt.shiftKey) {
+ switch (keysym) {
+ case 48 : keysym = 41 ; break; // ) (shifted 0)
+ case 49 : keysym = 33 ; break; // ! (shifted 1)
+ case 50 : keysym = 64 ; break; // @ (shifted 2)
+ case 51 : keysym = 35 ; break; // # (shifted 3)
+ case 52 : keysym = 36 ; break; // $ (shifted 4)
+ case 53 : keysym = 37 ; break; // % (shifted 5)
+ case 54 : keysym = 94 ; break; // ^ (shifted 6)
+ case 55 : keysym = 38 ; break; // & (shifted 7)
+ case 56 : keysym = 42 ; break; // * (shifted 8)
+ case 57 : keysym = 40 ; break; // ( (shifted 9)
+
+ case 59 : keysym = 58 ; break; // : (shifted `)
+ case 61 : keysym = 43 ; break; // + (shifted ;)
+ case 44 : keysym = 60 ; break; // < (shifted ,)
+ case 45 : keysym = 95 ; break; // _ (shifted -)
+ case 46 : keysym = 62 ; break; // > (shifted .)
+ case 47 : keysym = 63 ; break; // ? (shifted /)
+ case 96 : keysym = 126; break; // ~ (shifted `)
+ case 91 : keysym = 123; break; // { (shifted [)
+ case 92 : keysym = 124; break; // | (shifted \)
+ case 93 : keysym = 125; break; // } (shifted ])
+ case 39 : keysym = 34 ; break; // " (shifted ')
+ }
+ } else if ((keysym >= 65) && (keysym <=90)) {
+ /* Remap unshifted A-Z */
+ keysym += 32;
+ } else if (evt.keyLocation === 3) {
+ // numpad keys
+ switch (keysym) {
+ case 96 : keysym = 48; break; // 0
+ case 97 : keysym = 49; break; // 1
+ case 98 : keysym = 50; break; // 2
+ case 99 : keysym = 51; break; // 3
+ case 100: keysym = 52; break; // 4
+ case 101: keysym = 53; break; // 5
+ case 102: keysym = 54; break; // 6
+ case 103: keysym = 55; break; // 7
+ case 104: keysym = 56; break; // 8
+ case 105: keysym = 57; break; // 9
+ case 109: keysym = 45; break; // -
+ case 110: keysym = 46; break; // .
+ case 111: keysym = 47; break; // /
+ }
+ }
+ }
+
+ return keysym;
+}
+
+/* Translate DOM keyPress event to keysym value */
+function getKeysym(evt) {
+ var keysym, msg;
+
+ if (typeof(evt.which) !== "undefined") {
+ // WebKit, Firefox, Opera
+ keysym = evt.which;
+ } else {
+ // IE9
+ Util.Warn("which not set, using keyCode");
+ keysym = evt.keyCode;
+ }
+
+ if ((keysym > 255) && (keysym < 0xFF00)) {
+ msg = "Mapping character code " + keysym;
+ // Map Unicode outside Latin 1 to X11 keysyms
+ keysym = unicodeTable[keysym];
+ if (typeof(keysym) === 'undefined') {
+ keysym = 0;
+ }
+ Util.Debug(msg + " to " + keysym);
+ }
+
+ return keysym;
+}
+
+function show_keyDownList(kind) {
+ var c;
+ var msg = "keyDownList (" + kind + "):\n";
+ for (c = 0; c < keyDownList.length; c++) {
+ msg = msg + " " + c + " - keyCode: " + keyDownList[c].keyCode +
+ " - which: " + keyDownList[c].which + "\n";
+ }
+ Util.Debug(msg);
+}
+
+function copyKeyEvent(evt) {
+ var members = ['type', 'keyCode', 'charCode', 'which',
+ 'altKey', 'ctrlKey', 'shiftKey',
+ 'keyLocation', 'keyIdentifier'], i, obj = {};
+ for (i = 0; i < members.length; i++) {
+ if (typeof(evt[members[i]]) !== "undefined") {
+ obj[members[i]] = evt[members[i]];
+ }
+ }
+ return obj;
+}
+
+function pushKeyEvent(fevt) {
+ keyDownList.push(fevt);
+}
+
+function getKeyEvent(keyCode, pop) {
+ var i, fevt = null;
+ for (i = keyDownList.length-1; i >= 0; i--) {
+ if (keyDownList[i].keyCode === keyCode) {
+ if ((typeof(pop) !== "undefined") && (pop)) {
+ fevt = keyDownList.splice(i, 1)[0];
+ } else {
+ fevt = keyDownList[i];
+ }
+ break;
+ }
+ }
+ return fevt;
+}
+
+function ignoreKeyEvent(evt) {
+ // Blarg. Some keys have a different keyCode on keyDown vs keyUp
+ if (evt.keyCode === 229) {
+ // French AZERTY keyboard dead key.
+ // Lame thing is that the respective keyUp is 219 so we can't
+ // properly ignore the keyUp event
+ return true;
+ }
+ return false;
+}
+
+
+//
+// Key Event Handling:
+//
+// There are several challenges when dealing with key events:
+// - The meaning and use of keyCode, charCode and which depends on
+// both the browser and the event type (keyDown/Up vs keyPress).
+// - We cannot automatically determine the keyboard layout
+// - The keyDown and keyUp events have a keyCode value that has not
+// been translated by modifier keys.
+// - The keyPress event has a translated (for layout and modifiers)
+// character code but the attribute containing it differs. keyCode
+// contains the translated value in WebKit (Chrome/Safari), Opera
+// 11 and IE9. charCode contains the value in WebKit and Firefox.
+// The which attribute contains the value on WebKit, Firefox and
+// Opera 11.
+// - The keyDown/Up keyCode value indicates (sort of) the physical
+// key was pressed but only for standard US layout. On a US
+// keyboard, the '-' and '_' characters are on the same key and
+// generate a keyCode value of 189. But on an AZERTY keyboard even
+// though they are different physical keys they both still
+// generate a keyCode of 189!
+// - To prevent a key event from propagating to the browser and
+// causing unwanted default actions (such as closing a tab,
+// opening a menu, shifting focus, etc) we must suppress this
+// event in both keyDown and keyPress because not all key strokes
+// generate on a keyPress event. Also, in WebKit and IE9
+// suppressing the keyDown prevents a keyPress but other browsers
+// still generated a keyPress even if keyDown is suppressed.
+//
+// For safe key events, we wait until the keyPress event before
+// reporting a key down event. For unsafe key events, we report a key
+// down event when the keyDown event fires and we suppress any further
+// actions (including keyPress).
+//
+// In order to report a key up event that matches what we reported
+// for the key down event, we keep a list of keys that are currently
+// down. When the keyDown event happens, we add the key event to the
+// list. If it is a safe key event, then we update the which attribute
+// in the most recent item on the list when we received a keyPress
+// event (keyPress should immediately follow keyDown). When we
+// received a keyUp event we search for the event on the list with
+// a matching keyCode and we report the character code using the value
+// in the 'which' attribute that was stored with that key.
+//
+
+function onKeyDown(e) {
+ if (! conf.focused) {
+ return true;
+ }
+ var fevt = null, evt = (e ? e : window.event),
+ keysym = null, suppress = false;
+ //Util.Debug("onKeyDown kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
+
+ fevt = copyKeyEvent(evt);
+
+ keysym = getKeysymSpecial(evt);
+ // Save keysym decoding for use in keyUp
+ fevt.keysym = keysym;
+ if (keysym) {
+ // If it is a key or key combination that might trigger
+ // browser behaviors or it has no corresponding keyPress
+ // event, then send it immediately
+ if (conf.onKeyPress && !ignoreKeyEvent(evt)) {
+ Util.Debug("onKeyPress down, keysym: " + keysym +
+ " (onKeyDown key: " + evt.keyCode +
+ ", which: " + evt.which + ")");
+ conf.onKeyPress(keysym, 1, evt);
+ }
+ suppress = true;
+ }
+
+ if (! ignoreKeyEvent(evt)) {
+ // Add it to the list of depressed keys
+ pushKeyEvent(fevt);
+ //show_keyDownList('down');
+ }
+
+ if (suppress) {
+ // Suppress bubbling/default actions
+ Util.stopEvent(e);
+ return false;
+ } else {
+ // Allow the event to bubble and become a keyPress event which
+ // will have the character code translated
+ return true;
+ }
+}
+
+function onKeyPress(e) {
+ if (! conf.focused) {
+ return true;
+ }
+ var evt = (e ? e : window.event),
+ kdlen = keyDownList.length, keysym = null;
+ //Util.Debug("onKeyPress kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
+
+ if (((evt.which !== "undefined") && (evt.which === 0)) ||
+ (getKeysymSpecial(evt))) {
+ // Firefox and Opera generate a keyPress event even if keyDown
+ // is suppressed. But the keys we want to suppress will have
+ // either:
+ // - the which attribute set to 0
+ // - getKeysymSpecial() will identify it
+ Util.Debug("Ignoring special key in keyPress");
+ Util.stopEvent(e);
+ return false;
+ }
+
+ keysym = getKeysym(evt);
+
+ // Modify the the which attribute in the depressed keys list so
+ // that the keyUp event will be able to have the character code
+ // translation available.
+ if (kdlen > 0) {
+ keyDownList[kdlen-1].keysym = keysym;
+ } else {
+ Util.Warn("keyDownList empty when keyPress triggered");
+ }
+
+ //show_keyDownList('press');
+
+ // Send the translated keysym
+ if (conf.onKeyPress && (keysym > 0)) {
+ Util.Debug("onKeyPress down, keysym: " + keysym +
+ " (onKeyPress key: " + evt.keyCode +
+ ", which: " + evt.which + ")");
+ conf.onKeyPress(keysym, 1, evt);
+ }
+
+ // Stop keypress events just in case
+ Util.stopEvent(e);
+ return false;
+}
+
+function onKeyUp(e) {
+ if (! conf.focused) {
+ return true;
+ }
+ var fevt = null, evt = (e ? e : window.event), keysym;
+ //Util.Debug("onKeyUp kC:" + evt.keyCode + " cC:" + evt.charCode + " w:" + evt.which);
+
+ fevt = getKeyEvent(evt.keyCode, true);
+
+ if (fevt) {
+ keysym = fevt.keysym;
+ } else {
+ Util.Warn("Key event (keyCode = " + evt.keyCode +
+ ") not found on keyDownList");
+ keysym = 0;
+ }
+
+ //show_keyDownList('up');
+
+ if (conf.onKeyPress && (keysym > 0)) {
+ //Util.Debug("keyPress up, keysym: " + keysym +
+ // " (key: " + evt.keyCode + ", which: " + evt.which + ")");
+ Util.Debug("onKeyPress up, keysym: " + keysym +
+ " (onKeyPress key: " + evt.keyCode +
+ ", which: " + evt.which + ")");
+ conf.onKeyPress(keysym, 0, evt);
+ }
+ Util.stopEvent(e);
+ return false;
+}
+
+//
+// Public API interface functions
+//
+
+that.grab = function() {
+ //Util.Debug(">> Keyboard.grab");
+ var c = conf.target;
+
+ Util.addEvent(c, 'keydown', onKeyDown);
+ Util.addEvent(c, 'keyup', onKeyUp);
+ Util.addEvent(c, 'keypress', onKeyPress);
+
+ //Util.Debug("<< Keyboard.grab");
+};
+
+that.ungrab = function() {
+ //Util.Debug(">> Keyboard.ungrab");
+ var c = conf.target;
+
+ Util.removeEvent(c, 'keydown', onKeyDown);
+ Util.removeEvent(c, 'keyup', onKeyUp);
+ Util.removeEvent(c, 'keypress', onKeyPress);
+
+ //Util.Debug(">> Keyboard.ungrab");
+};
+
+return that; // Return the public API interface
+
+} // End of Keyboard()
+
+
+//
+// Mouse event handler
+//
+
+function Mouse(defaults) {
+"use strict";
+
+var that = {}, // Public API methods
+ conf = {}; // Configuration attributes
+
+// Configuration attributes
+Util.conf_defaults(conf, that, defaults, [
+ ['target', 'ro', 'dom', document, 'DOM element that captures mouse input'],
+ ['focused', 'rw', 'bool', true, 'Capture and send mouse clicks/movement'],
+ ['scale', 'rw', 'float', 1.0, 'Viewport scale factor 0.0 - 1.0'],
+
+ ['onMouseButton', 'rw', 'func', null, 'Handler for mouse button click/release'],
+ ['onMouseMove', 'rw', 'func', null, 'Handler for mouse movement'],
+ ['touchButton', 'rw', 'int', 1, 'Button mask (1, 2, 4) for touch devices (0 means ignore clicks)']
+ ]);
+
+
+//
+// Private functions
+//
+
+function onMouseButton(e, down) {
+ var evt, pos, bmask;
+ if (! conf.focused) {
+ return true;
+ }
+ evt = (e ? e : window.event);
+ pos = Util.getEventPosition(e, conf.target, conf.scale);
+ if (e.touches || e.changedTouches) {
+ // Touch device
+ bmask = conf.touchButton;
+ // If bmask is set
+ } else if (evt.which) {
+ /* everything except IE */
+ bmask = 1 << evt.button;
+ } else {
+ /* IE including 9 */
+ bmask = (evt.button & 0x1) + // Left
+ (evt.button & 0x2) * 2 + // Right
+ (evt.button & 0x4) / 2; // Middle
+ }
+ //Util.Debug("mouse " + pos.x + "," + pos.y + " down: " + down +
+ // " bmask: " + bmask + "(evt.button: " + evt.button + ")");
+ if (bmask > 0 && conf.onMouseButton) {
+ Util.Debug("onMouseButton " + (down ? "down" : "up") +
+ ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
+ conf.onMouseButton(pos.x, pos.y, down, bmask);
+ }
+ Util.stopEvent(e);
+ return false;
+}
+
+function onMouseDown(e) {
+ onMouseButton(e, 1);
+}
+
+function onMouseUp(e) {
+ onMouseButton(e, 0);
+}
+
+function onMouseWheel(e) {
+ var evt, pos, bmask, wheelData;
+ if (! conf.focused) {
+ return true;
+ }
+ evt = (e ? e : window.event);
+ pos = Util.getEventPosition(e, conf.target, conf.scale);
+ wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
+ if (wheelData > 0) {
+ bmask = 1 << 3;
+ } else {
+ bmask = 1 << 4;
+ }
+ //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
+ if (conf.onMouseButton) {
+ conf.onMouseButton(pos.x, pos.y, 1, bmask);
+ conf.onMouseButton(pos.x, pos.y, 0, bmask);
+ }
+ Util.stopEvent(e);
+ return false;
+}
+
+function onMouseMove(e) {
+ var evt, pos;
+ if (! conf.focused) {
+ return true;
+ }
+ evt = (e ? e : window.event);
+ pos = Util.getEventPosition(e, conf.target, conf.scale);
+ //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
+ if (conf.onMouseMove) {
+ conf.onMouseMove(pos.x, pos.y);
+ }
+ Util.stopEvent(e);
+ return false;
+}
+
+function onMouseDisable(e) {
+ var evt, pos;
+ if (! conf.focused) {
+ return true;
+ }
+ evt = (e ? e : window.event);
+ pos = Util.getEventPosition(e, conf.target, conf.scale);
+ /* Stop propagation if inside canvas area */
+ if ((pos.x >= 0) && (pos.y >= 0) &&
+ (pos.x < conf.target.offsetWidth) &&
+ (pos.y < conf.target.offsetHeight)) {
+ //Util.Debug("mouse event disabled");
+ Util.stopEvent(e);
+ return false;
+ }
+ //Util.Debug("mouse event not disabled");
+ return true;
+}
+
+//
+// Public API interface functions
+//
+
+that.grab = function() {
+ //Util.Debug(">> Mouse.grab");
+ var c = conf.target;
+
+ if ('ontouchstart' in document.documentElement) {
+ Util.addEvent(c, 'touchstart', onMouseDown);
+ Util.addEvent(c, 'touchend', onMouseUp);
+ Util.addEvent(c, 'touchmove', onMouseMove);
+ } else {
+ Util.addEvent(c, 'mousedown', onMouseDown);
+ Util.addEvent(c, 'mouseup', onMouseUp);
+ Util.addEvent(c, 'mousemove', onMouseMove);
+ Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+ onMouseWheel);
+ }
+
+ /* Work around right and middle click browser behaviors */
+ Util.addEvent(document, 'click', onMouseDisable);
+ Util.addEvent(document.body, 'contextmenu', onMouseDisable);
+
+ //Util.Debug("<< Mouse.grab");
+};
+
+that.ungrab = function() {
+ //Util.Debug(">> Mouse.ungrab");
+ var c = conf.target;
+
+ if ('ontouchstart' in document.documentElement) {
+ Util.removeEvent(c, 'touchstart', onMouseDown);
+ Util.removeEvent(c, 'touchend', onMouseUp);
+ Util.removeEvent(c, 'touchmove', onMouseMove);
+ } else {
+ Util.removeEvent(c, 'mousedown', onMouseDown);
+ Util.removeEvent(c, 'mouseup', onMouseUp);
+ Util.removeEvent(c, 'mousemove', onMouseMove);
+ Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
+ onMouseWheel);
+ }
+
+ /* Work around right and middle click browser behaviors */
+ Util.removeEvent(document, 'click', onMouseDisable);
+ Util.removeEvent(document.body, 'contextmenu', onMouseDisable);
+
+ //Util.Debug(">> Mouse.ungrab");
+};
+
+return that; // Return the public API interface
+
+} // End of Mouse()
+
+
+/*
+ * Browser keypress to X11 keysym for Unicode characters > U+00FF
+ */
+unicodeTable = {
+ 0x0104 : 0x01a1,
+ 0x02D8 : 0x01a2,
+ 0x0141 : 0x01a3,
+ 0x013D : 0x01a5,
+ 0x015A : 0x01a6,
+ 0x0160 : 0x01a9,
+ 0x015E : 0x01aa,
+ 0x0164 : 0x01ab,
+ 0x0179 : 0x01ac,
+ 0x017D : 0x01ae,
+ 0x017B : 0x01af,
+ 0x0105 : 0x01b1,
+ 0x02DB : 0x01b2,
+ 0x0142 : 0x01b3,
+ 0x013E : 0x01b5,
+ 0x015B : 0x01b6,
+ 0x02C7 : 0x01b7,
+ 0x0161 : 0x01b9,
+ 0x015F : 0x01ba,
+ 0x0165 : 0x01bb,
+ 0x017A : 0x01bc,
+ 0x02DD : 0x01bd,
+ 0x017E : 0x01be,
+ 0x017C : 0x01bf,
+ 0x0154 : 0x01c0,
+ 0x0102 : 0x01c3,
+ 0x0139 : 0x01c5,
+ 0x0106 : 0x01c6,
+ 0x010C : 0x01c8,
+ 0x0118 : 0x01ca,
+ 0x011A : 0x01cc,
+ 0x010E : 0x01cf,
+ 0x0110 : 0x01d0,
+ 0x0143 : 0x01d1,
+ 0x0147 : 0x01d2,
+ 0x0150 : 0x01d5,
+ 0x0158 : 0x01d8,
+ 0x016E : 0x01d9,
+ 0x0170 : 0x01db,
+ 0x0162 : 0x01de,
+ 0x0155 : 0x01e0,
+ 0x0103 : 0x01e3,
+ 0x013A : 0x01e5,
+ 0x0107 : 0x01e6,
+ 0x010D : 0x01e8,
+ 0x0119 : 0x01ea,
+ 0x011B : 0x01ec,
+ 0x010F : 0x01ef,
+ 0x0111 : 0x01f0,
+ 0x0144 : 0x01f1,
+ 0x0148 : 0x01f2,
+ 0x0151 : 0x01f5,
+ 0x0171 : 0x01fb,
+ 0x0159 : 0x01f8,
+ 0x016F : 0x01f9,
+ 0x0163 : 0x01fe,
+ 0x02D9 : 0x01ff,
+ 0x0126 : 0x02a1,
+ 0x0124 : 0x02a6,
+ 0x0130 : 0x02a9,
+ 0x011E : 0x02ab,
+ 0x0134 : 0x02ac,
+ 0x0127 : 0x02b1,
+ 0x0125 : 0x02b6,
+ 0x0131 : 0x02b9,
+ 0x011F : 0x02bb,
+ 0x0135 : 0x02bc,
+ 0x010A : 0x02c5,
+ 0x0108 : 0x02c6,
+ 0x0120 : 0x02d5,
+ 0x011C : 0x02d8,
+ 0x016C : 0x02dd,
+ 0x015C : 0x02de,
+ 0x010B : 0x02e5,
+ 0x0109 : 0x02e6,
+ 0x0121 : 0x02f5,
+ 0x011D : 0x02f8,
+ 0x016D : 0x02fd,
+ 0x015D : 0x02fe,
+ 0x0138 : 0x03a2,
+ 0x0156 : 0x03a3,
+ 0x0128 : 0x03a5,
+ 0x013B : 0x03a6,
+ 0x0112 : 0x03aa,
+ 0x0122 : 0x03ab,
+ 0x0166 : 0x03ac,
+ 0x0157 : 0x03b3,
+ 0x0129 : 0x03b5,
+ 0x013C : 0x03b6,
+ 0x0113 : 0x03ba,
+ 0x0123 : 0x03bb,
+ 0x0167 : 0x03bc,
+ 0x014A : 0x03bd,
+ 0x014B : 0x03bf,
+ 0x0100 : 0x03c0,
+ 0x012E : 0x03c7,
+ 0x0116 : 0x03cc,
+ 0x012A : 0x03cf,
+ 0x0145 : 0x03d1,
+ 0x014C : 0x03d2,
+ 0x0136 : 0x03d3,
+ 0x0172 : 0x03d9,
+ 0x0168 : 0x03dd,
+ 0x016A : 0x03de,
+ 0x0101 : 0x03e0,
+ 0x012F : 0x03e7,
+ 0x0117 : 0x03ec,
+ 0x012B : 0x03ef,
+ 0x0146 : 0x03f1,
+ 0x014D : 0x03f2,
+ 0x0137 : 0x03f3,
+ 0x0173 : 0x03f9,
+ 0x0169 : 0x03fd,
+ 0x016B : 0x03fe,
+ 0x1E02 : 0x1001e02,
+ 0x1E03 : 0x1001e03,
+ 0x1E0A : 0x1001e0a,
+ 0x1E80 : 0x1001e80,
+ 0x1E82 : 0x1001e82,
+ 0x1E0B : 0x1001e0b,
+ 0x1EF2 : 0x1001ef2,
+ 0x1E1E : 0x1001e1e,
+ 0x1E1F : 0x1001e1f,
+ 0x1E40 : 0x1001e40,
+ 0x1E41 : 0x1001e41,
+ 0x1E56 : 0x1001e56,
+ 0x1E81 : 0x1001e81,
+ 0x1E57 : 0x1001e57,
+ 0x1E83 : 0x1001e83,
+ 0x1E60 : 0x1001e60,
+ 0x1EF3 : 0x1001ef3,
+ 0x1E84 : 0x1001e84,
+ 0x1E85 : 0x1001e85,
+ 0x1E61 : 0x1001e61,
+ 0x0174 : 0x1000174,
+ 0x1E6A : 0x1001e6a,
+ 0x0176 : 0x1000176,
+ 0x0175 : 0x1000175,
+ 0x1E6B : 0x1001e6b,
+ 0x0177 : 0x1000177,
+ 0x0152 : 0x13bc,
+ 0x0153 : 0x13bd,
+ 0x0178 : 0x13be,
+ 0x203E : 0x047e,
+ 0x3002 : 0x04a1,
+ 0x300C : 0x04a2,
+ 0x300D : 0x04a3,
+ 0x3001 : 0x04a4,
+ 0x30FB : 0x04a5,
+ 0x30F2 : 0x04a6,
+ 0x30A1 : 0x04a7,
+ 0x30A3 : 0x04a8,
+ 0x30A5 : 0x04a9,
+ 0x30A7 : 0x04aa,
+ 0x30A9 : 0x04ab,
+ 0x30E3 : 0x04ac,
+ 0x30E5 : 0x04ad,
+ 0x30E7 : 0x04ae,
+ 0x30C3 : 0x04af,
+ 0x30FC : 0x04b0,
+ 0x30A2 : 0x04b1,
+ 0x30A4 : 0x04b2,
+ 0x30A6 : 0x04b3,
+ 0x30A8 : 0x04b4,
+ 0x30AA : 0x04b5,
+ 0x30AB : 0x04b6,
+ 0x30AD : 0x04b7,
+ 0x30AF : 0x04b8,
+ 0x30B1 : 0x04b9,
+ 0x30B3 : 0x04ba,
+ 0x30B5 : 0x04bb,
+ 0x30B7 : 0x04bc,
+ 0x30B9 : 0x04bd,
+ 0x30BB : 0x04be,
+ 0x30BD : 0x04bf,
+ 0x30BF : 0x04c0,
+ 0x30C1 : 0x04c1,
+ 0x30C4 : 0x04c2,
+ 0x30C6 : 0x04c3,
+ 0x30C8 : 0x04c4,
+ 0x30CA : 0x04c5,
+ 0x30CB : 0x04c6,
+ 0x30CC : 0x04c7,
+ 0x30CD : 0x04c8,
+ 0x30CE : 0x04c9,
+ 0x30CF : 0x04ca,
+ 0x30D2 : 0x04cb,
+ 0x30D5 : 0x04cc,
+ 0x30D8 : 0x04cd,
+ 0x30DB : 0x04ce,
+ 0x30DE : 0x04cf,
+ 0x30DF : 0x04d0,
+ 0x30E0 : 0x04d1,
+ 0x30E1 : 0x04d2,
+ 0x30E2 : 0x04d3,
+ 0x30E4 : 0x04d4,
+ 0x30E6 : 0x04d5,
+ 0x30E8 : 0x04d6,
+ 0x30E9 : 0x04d7,
+ 0x30EA : 0x04d8,
+ 0x30EB : 0x04d9,
+ 0x30EC : 0x04da,
+ 0x30ED : 0x04db,
+ 0x30EF : 0x04dc,
+ 0x30F3 : 0x04dd,
+ 0x309B : 0x04de,
+ 0x309C : 0x04df,
+ 0x06F0 : 0x10006f0,
+ 0x06F1 : 0x10006f1,
+ 0x06F2 : 0x10006f2,
+ 0x06F3 : 0x10006f3,
+ 0x06F4 : 0x10006f4,
+ 0x06F5 : 0x10006f5,
+ 0x06F6 : 0x10006f6,
+ 0x06F7 : 0x10006f7,
+ 0x06F8 : 0x10006f8,
+ 0x06F9 : 0x10006f9,
+ 0x066A : 0x100066a,
+ 0x0670 : 0x1000670,
+ 0x0679 : 0x1000679,
+ 0x067E : 0x100067e,
+ 0x0686 : 0x1000686,
+ 0x0688 : 0x1000688,
+ 0x0691 : 0x1000691,
+ 0x060C : 0x05ac,
+ 0x06D4 : 0x10006d4,
+ 0x0660 : 0x1000660,
+ 0x0661 : 0x1000661,
+ 0x0662 : 0x1000662,
+ 0x0663 : 0x1000663,
+ 0x0664 : 0x1000664,
+ 0x0665 : 0x1000665,
+ 0x0666 : 0x1000666,
+ 0x0667 : 0x1000667,
+ 0x0668 : 0x1000668,
+ 0x0669 : 0x1000669,
+ 0x061B : 0x05bb,
+ 0x061F : 0x05bf,
+ 0x0621 : 0x05c1,
+ 0x0622 : 0x05c2,
+ 0x0623 : 0x05c3,
+ 0x0624 : 0x05c4,
+ 0x0625 : 0x05c5,
+ 0x0626 : 0x05c6,
+ 0x0627 : 0x05c7,
+ 0x0628 : 0x05c8,
+ 0x0629 : 0x05c9,
+ 0x062A : 0x05ca,
+ 0x062B : 0x05cb,
+ 0x062C : 0x05cc,
+ 0x062D : 0x05cd,
+ 0x062E : 0x05ce,
+ 0x062F : 0x05cf,
+ 0x0630 : 0x05d0,
+ 0x0631 : 0x05d1,
+ 0x0632 : 0x05d2,
+ 0x0633 : 0x05d3,
+ 0x0634 : 0x05d4,
+ 0x0635 : 0x05d5,
+ 0x0636 : 0x05d6,
+ 0x0637 : 0x05d7,
+ 0x0638 : 0x05d8,
+ 0x0639 : 0x05d9,
+ 0x063A : 0x05da,
+ 0x0640 : 0x05e0,
+ 0x0641 : 0x05e1,
+ 0x0642 : 0x05e2,
+ 0x0643 : 0x05e3,
+ 0x0644 : 0x05e4,
+ 0x0645 : 0x05e5,
+ 0x0646 : 0x05e6,
+ 0x0647 : 0x05e7,
+ 0x0648 : 0x05e8,
+ 0x0649 : 0x05e9,
+ 0x064A : 0x05ea,
+ 0x064B : 0x05eb,
+ 0x064C : 0x05ec,
+ 0x064D : 0x05ed,
+ 0x064E : 0x05ee,
+ 0x064F : 0x05ef,
+ 0x0650 : 0x05f0,
+ 0x0651 : 0x05f1,
+ 0x0652 : 0x05f2,
+ 0x0653 : 0x1000653,
+ 0x0654 : 0x1000654,
+ 0x0655 : 0x1000655,
+ 0x0698 : 0x1000698,
+ 0x06A4 : 0x10006a4,
+ 0x06A9 : 0x10006a9,
+ 0x06AF : 0x10006af,
+ 0x06BA : 0x10006ba,
+ 0x06BE : 0x10006be,
+ 0x06CC : 0x10006cc,
+ 0x06D2 : 0x10006d2,
+ 0x06C1 : 0x10006c1,
+ 0x0492 : 0x1000492,
+ 0x0493 : 0x1000493,
+ 0x0496 : 0x1000496,
+ 0x0497 : 0x1000497,
+ 0x049A : 0x100049a,
+ 0x049B : 0x100049b,
+ 0x049C : 0x100049c,
+ 0x049D : 0x100049d,
+ 0x04A2 : 0x10004a2,
+ 0x04A3 : 0x10004a3,
+ 0x04AE : 0x10004ae,
+ 0x04AF : 0x10004af,
+ 0x04B0 : 0x10004b0,
+ 0x04B1 : 0x10004b1,
+ 0x04B2 : 0x10004b2,
+ 0x04B3 : 0x10004b3,
+ 0x04B6 : 0x10004b6,
+ 0x04B7 : 0x10004b7,
+ 0x04B8 : 0x10004b8,
+ 0x04B9 : 0x10004b9,
+ 0x04BA : 0x10004ba,
+ 0x04BB : 0x10004bb,
+ 0x04D8 : 0x10004d8,
+ 0x04D9 : 0x10004d9,
+ 0x04E2 : 0x10004e2,
+ 0x04E3 : 0x10004e3,
+ 0x04E8 : 0x10004e8,
+ 0x04E9 : 0x10004e9,
+ 0x04EE : 0x10004ee,
+ 0x04EF : 0x10004ef,
+ 0x0452 : 0x06a1,
+ 0x0453 : 0x06a2,
+ 0x0451 : 0x06a3,
+ 0x0454 : 0x06a4,
+ 0x0455 : 0x06a5,
+ 0x0456 : 0x06a6,
+ 0x0457 : 0x06a7,
+ 0x0458 : 0x06a8,
+ 0x0459 : 0x06a9,
+ 0x045A : 0x06aa,
+ 0x045B : 0x06ab,
+ 0x045C : 0x06ac,
+ 0x0491 : 0x06ad,
+ 0x045E : 0x06ae,
+ 0x045F : 0x06af,
+ 0x2116 : 0x06b0,
+ 0x0402 : 0x06b1,
+ 0x0403 : 0x06b2,
+ 0x0401 : 0x06b3,
+ 0x0404 : 0x06b4,
+ 0x0405 : 0x06b5,
+ 0x0406 : 0x06b6,
+ 0x0407 : 0x06b7,
+ 0x0408 : 0x06b8,
+ 0x0409 : 0x06b9,
+ 0x040A : 0x06ba,
+ 0x040B : 0x06bb,
+ 0x040C : 0x06bc,
+ 0x0490 : 0x06bd,
+ 0x040E : 0x06be,
+ 0x040F : 0x06bf,
+ 0x044E : 0x06c0,
+ 0x0430 : 0x06c1,
+ 0x0431 : 0x06c2,
+ 0x0446 : 0x06c3,
+ 0x0434 : 0x06c4,
+ 0x0435 : 0x06c5,
+ 0x0444 : 0x06c6,
+ 0x0433 : 0x06c7,
+ 0x0445 : 0x06c8,
+ 0x0438 : 0x06c9,
+ 0x0439 : 0x06ca,
+ 0x043A : 0x06cb,
+ 0x043B : 0x06cc,
+ 0x043C : 0x06cd,
+ 0x043D : 0x06ce,
+ 0x043E : 0x06cf,
+ 0x043F : 0x06d0,
+ 0x044F : 0x06d1,
+ 0x0440 : 0x06d2,
+ 0x0441 : 0x06d3,
+ 0x0442 : 0x06d4,
+ 0x0443 : 0x06d5,
+ 0x0436 : 0x06d6,
+ 0x0432 : 0x06d7,
+ 0x044C : 0x06d8,
+ 0x044B : 0x06d9,
+ 0x0437 : 0x06da,
+ 0x0448 : 0x06db,
+ 0x044D : 0x06dc,
+ 0x0449 : 0x06dd,
+ 0x0447 : 0x06de,
+ 0x044A : 0x06df,
+ 0x042E : 0x06e0,
+ 0x0410 : 0x06e1,
+ 0x0411 : 0x06e2,
+ 0x0426 : 0x06e3,
+ 0x0414 : 0x06e4,
+ 0x0415 : 0x06e5,
+ 0x0424 : 0x06e6,
+ 0x0413 : 0x06e7,
+ 0x0425 : 0x06e8,
+ 0x0418 : 0x06e9,
+ 0x0419 : 0x06ea,
+ 0x041A : 0x06eb,
+ 0x041B : 0x06ec,
+ 0x041C : 0x06ed,
+ 0x041D : 0x06ee,
+ 0x041E : 0x06ef,
+ 0x041F : 0x06f0,
+ 0x042F : 0x06f1,
+ 0x0420 : 0x06f2,
+ 0x0421 : 0x06f3,
+ 0x0422 : 0x06f4,
+ 0x0423 : 0x06f5,
+ 0x0416 : 0x06f6,
+ 0x0412 : 0x06f7,
+ 0x042C : 0x06f8,
+ 0x042B : 0x06f9,
+ 0x0417 : 0x06fa,
+ 0x0428 : 0x06fb,
+ 0x042D : 0x06fc,
+ 0x0429 : 0x06fd,
+ 0x0427 : 0x06fe,
+ 0x042A : 0x06ff,
+ 0x0386 : 0x07a1,
+ 0x0388 : 0x07a2,
+ 0x0389 : 0x07a3,
+ 0x038A : 0x07a4,
+ 0x03AA : 0x07a5,
+ 0x038C : 0x07a7,
+ 0x038E : 0x07a8,
+ 0x03AB : 0x07a9,
+ 0x038F : 0x07ab,
+ 0x0385 : 0x07ae,
+ 0x2015 : 0x07af,
+ 0x03AC : 0x07b1,
+ 0x03AD : 0x07b2,
+ 0x03AE : 0x07b3,
+ 0x03AF : 0x07b4,
+ 0x03CA : 0x07b5,
+ 0x0390 : 0x07b6,
+ 0x03CC : 0x07b7,
+ 0x03CD : 0x07b8,
+ 0x03CB : 0x07b9,
+ 0x03B0 : 0x07ba,
+ 0x03CE : 0x07bb,
+ 0x0391 : 0x07c1,
+ 0x0392 : 0x07c2,
+ 0x0393 : 0x07c3,
+ 0x0394 : 0x07c4,
+ 0x0395 : 0x07c5,
+ 0x0396 : 0x07c6,
+ 0x0397 : 0x07c7,
+ 0x0398 : 0x07c8,
+ 0x0399 : 0x07c9,
+ 0x039A : 0x07ca,
+ 0x039B : 0x07cb,
+ 0x039C : 0x07cc,
+ 0x039D : 0x07cd,
+ 0x039E : 0x07ce,
+ 0x039F : 0x07cf,
+ 0x03A0 : 0x07d0,
+ 0x03A1 : 0x07d1,
+ 0x03A3 : 0x07d2,
+ 0x03A4 : 0x07d4,
+ 0x03A5 : 0x07d5,
+ 0x03A6 : 0x07d6,
+ 0x03A7 : 0x07d7,
+ 0x03A8 : 0x07d8,
+ 0x03A9 : 0x07d9,
+ 0x03B1 : 0x07e1,
+ 0x03B2 : 0x07e2,
+ 0x03B3 : 0x07e3,
+ 0x03B4 : 0x07e4,
+ 0x03B5 : 0x07e5,
+ 0x03B6 : 0x07e6,
+ 0x03B7 : 0x07e7,
+ 0x03B8 : 0x07e8,
+ 0x03B9 : 0x07e9,
+ 0x03BA : 0x07ea,
+ 0x03BB : 0x07eb,
+ 0x03BC : 0x07ec,
+ 0x03BD : 0x07ed,
+ 0x03BE : 0x07ee,
+ 0x03BF : 0x07ef,
+ 0x03C0 : 0x07f0,
+ 0x03C1 : 0x07f1,
+ 0x03C3 : 0x07f2,
+ 0x03C2 : 0x07f3,
+ 0x03C4 : 0x07f4,
+ 0x03C5 : 0x07f5,
+ 0x03C6 : 0x07f6,
+ 0x03C7 : 0x07f7,
+ 0x03C8 : 0x07f8,
+ 0x03C9 : 0x07f9,
+ 0x23B7 : 0x08a1,
+ 0x2320 : 0x08a4,
+ 0x2321 : 0x08a5,
+ 0x23A1 : 0x08a7,
+ 0x23A3 : 0x08a8,
+ 0x23A4 : 0x08a9,
+ 0x23A6 : 0x08aa,
+ 0x239B : 0x08ab,
+ 0x239D : 0x08ac,
+ 0x239E : 0x08ad,
+ 0x23A0 : 0x08ae,
+ 0x23A8 : 0x08af,
+ 0x23AC : 0x08b0,
+ 0x2264 : 0x08bc,
+ 0x2260 : 0x08bd,
+ 0x2265 : 0x08be,
+ 0x222B : 0x08bf,
+ 0x2234 : 0x08c0,
+ 0x221D : 0x08c1,
+ 0x221E : 0x08c2,
+ 0x2207 : 0x08c5,
+ 0x223C : 0x08c8,
+ 0x2243 : 0x08c9,
+ 0x21D4 : 0x08cd,
+ 0x21D2 : 0x08ce,
+ 0x2261 : 0x08cf,
+ 0x221A : 0x08d6,
+ 0x2282 : 0x08da,
+ 0x2283 : 0x08db,
+ 0x2229 : 0x08dc,
+ 0x222A : 0x08dd,
+ 0x2227 : 0x08de,
+ 0x2228 : 0x08df,
+ 0x2202 : 0x08ef,
+ 0x0192 : 0x08f6,
+ 0x2190 : 0x08fb,
+ 0x2191 : 0x08fc,
+ 0x2192 : 0x08fd,
+ 0x2193 : 0x08fe,
+ 0x25C6 : 0x09e0,
+ 0x2592 : 0x09e1,
+ 0x2409 : 0x09e2,
+ 0x240C : 0x09e3,
+ 0x240D : 0x09e4,
+ 0x240A : 0x09e5,
+ 0x2424 : 0x09e8,
+ 0x240B : 0x09e9,
+ 0x2518 : 0x09ea,
+ 0x2510 : 0x09eb,
+ 0x250C : 0x09ec,
+ 0x2514 : 0x09ed,
+ 0x253C : 0x09ee,
+ 0x23BA : 0x09ef,
+ 0x23BB : 0x09f0,
+ 0x2500 : 0x09f1,
+ 0x23BC : 0x09f2,
+ 0x23BD : 0x09f3,
+ 0x251C : 0x09f4,
+ 0x2524 : 0x09f5,
+ 0x2534 : 0x09f6,
+ 0x252C : 0x09f7,
+ 0x2502 : 0x09f8,
+ 0x2003 : 0x0aa1,
+ 0x2002 : 0x0aa2,
+ 0x2004 : 0x0aa3,
+ 0x2005 : 0x0aa4,
+ 0x2007 : 0x0aa5,
+ 0x2008 : 0x0aa6,
+ 0x2009 : 0x0aa7,
+ 0x200A : 0x0aa8,
+ 0x2014 : 0x0aa9,
+ 0x2013 : 0x0aaa,
+ 0x2026 : 0x0aae,
+ 0x2025 : 0x0aaf,
+ 0x2153 : 0x0ab0,
+ 0x2154 : 0x0ab1,
+ 0x2155 : 0x0ab2,
+ 0x2156 : 0x0ab3,
+ 0x2157 : 0x0ab4,
+ 0x2158 : 0x0ab5,
+ 0x2159 : 0x0ab6,
+ 0x215A : 0x0ab7,
+ 0x2105 : 0x0ab8,
+ 0x2012 : 0x0abb,
+ 0x215B : 0x0ac3,
+ 0x215C : 0x0ac4,
+ 0x215D : 0x0ac5,
+ 0x215E : 0x0ac6,
+ 0x2122 : 0x0ac9,
+ 0x2018 : 0x0ad0,
+ 0x2019 : 0x0ad1,
+ 0x201C : 0x0ad2,
+ 0x201D : 0x0ad3,
+ 0x211E : 0x0ad4,
+ 0x2032 : 0x0ad6,
+ 0x2033 : 0x0ad7,
+ 0x271D : 0x0ad9,
+ 0x2663 : 0x0aec,
+ 0x2666 : 0x0aed,
+ 0x2665 : 0x0aee,
+ 0x2720 : 0x0af0,
+ 0x2020 : 0x0af1,
+ 0x2021 : 0x0af2,
+ 0x2713 : 0x0af3,
+ 0x2717 : 0x0af4,
+ 0x266F : 0x0af5,
+ 0x266D : 0x0af6,
+ 0x2642 : 0x0af7,
+ 0x2640 : 0x0af8,
+ 0x260E : 0x0af9,
+ 0x2315 : 0x0afa,
+ 0x2117 : 0x0afb,
+ 0x2038 : 0x0afc,
+ 0x201A : 0x0afd,
+ 0x201E : 0x0afe,
+ 0x22A4 : 0x0bc2,
+ 0x230A : 0x0bc4,
+ 0x2218 : 0x0bca,
+ 0x2395 : 0x0bcc,
+ 0x22A5 : 0x0bce,
+ 0x25CB : 0x0bcf,
+ 0x2308 : 0x0bd3,
+ 0x22A3 : 0x0bdc,
+ 0x22A2 : 0x0bfc,
+ 0x2017 : 0x0cdf,
+ 0x05D0 : 0x0ce0,
+ 0x05D1 : 0x0ce1,
+ 0x05D2 : 0x0ce2,
+ 0x05D3 : 0x0ce3,
+ 0x05D4 : 0x0ce4,
+ 0x05D5 : 0x0ce5,
+ 0x05D6 : 0x0ce6,
+ 0x05D7 : 0x0ce7,
+ 0x05D8 : 0x0ce8,
+ 0x05D9 : 0x0ce9,
+ 0x05DA : 0x0cea,
+ 0x05DB : 0x0ceb,
+ 0x05DC : 0x0cec,
+ 0x05DD : 0x0ced,
+ 0x05DE : 0x0cee,
+ 0x05DF : 0x0cef,
+ 0x05E0 : 0x0cf0,
+ 0x05E1 : 0x0cf1,
+ 0x05E2 : 0x0cf2,
+ 0x05E3 : 0x0cf3,
+ 0x05E4 : 0x0cf4,
+ 0x05E5 : 0x0cf5,
+ 0x05E6 : 0x0cf6,
+ 0x05E7 : 0x0cf7,
+ 0x05E8 : 0x0cf8,
+ 0x05E9 : 0x0cf9,
+ 0x05EA : 0x0cfa,
+ 0x0E01 : 0x0da1,
+ 0x0E02 : 0x0da2,
+ 0x0E03 : 0x0da3,
+ 0x0E04 : 0x0da4,
+ 0x0E05 : 0x0da5,
+ 0x0E06 : 0x0da6,
+ 0x0E07 : 0x0da7,
+ 0x0E08 : 0x0da8,
+ 0x0E09 : 0x0da9,
+ 0x0E0A : 0x0daa,
+ 0x0E0B : 0x0dab,
+ 0x0E0C : 0x0dac,
+ 0x0E0D : 0x0dad,
+ 0x0E0E : 0x0dae,
+ 0x0E0F : 0x0daf,
+ 0x0E10 : 0x0db0,
+ 0x0E11 : 0x0db1,
+ 0x0E12 : 0x0db2,
+ 0x0E13 : 0x0db3,
+ 0x0E14 : 0x0db4,
+ 0x0E15 : 0x0db5,
+ 0x0E16 : 0x0db6,
+ 0x0E17 : 0x0db7,
+ 0x0E18 : 0x0db8,
+ 0x0E19 : 0x0db9,
+ 0x0E1A : 0x0dba,
+ 0x0E1B : 0x0dbb,
+ 0x0E1C : 0x0dbc,
+ 0x0E1D : 0x0dbd,
+ 0x0E1E : 0x0dbe,
+ 0x0E1F : 0x0dbf,
+ 0x0E20 : 0x0dc0,
+ 0x0E21 : 0x0dc1,
+ 0x0E22 : 0x0dc2,
+ 0x0E23 : 0x0dc3,
+ 0x0E24 : 0x0dc4,
+ 0x0E25 : 0x0dc5,
+ 0x0E26 : 0x0dc6,
+ 0x0E27 : 0x0dc7,
+ 0x0E28 : 0x0dc8,
+ 0x0E29 : 0x0dc9,
+ 0x0E2A : 0x0dca,
+ 0x0E2B : 0x0dcb,
+ 0x0E2C : 0x0dcc,
+ 0x0E2D : 0x0dcd,
+ 0x0E2E : 0x0dce,
+ 0x0E2F : 0x0dcf,
+ 0x0E30 : 0x0dd0,
+ 0x0E31 : 0x0dd1,
+ 0x0E32 : 0x0dd2,
+ 0x0E33 : 0x0dd3,
+ 0x0E34 : 0x0dd4,
+ 0x0E35 : 0x0dd5,
+ 0x0E36 : 0x0dd6,
+ 0x0E37 : 0x0dd7,
+ 0x0E38 : 0x0dd8,
+ 0x0E39 : 0x0dd9,
+ 0x0E3A : 0x0dda,
+ 0x0E3F : 0x0ddf,
+ 0x0E40 : 0x0de0,
+ 0x0E41 : 0x0de1,
+ 0x0E42 : 0x0de2,
+ 0x0E43 : 0x0de3,
+ 0x0E44 : 0x0de4,
+ 0x0E45 : 0x0de5,
+ 0x0E46 : 0x0de6,
+ 0x0E47 : 0x0de7,
+ 0x0E48 : 0x0de8,
+ 0x0E49 : 0x0de9,
+ 0x0E4A : 0x0dea,
+ 0x0E4B : 0x0deb,
+ 0x0E4C : 0x0dec,
+ 0x0E4D : 0x0ded,
+ 0x0E50 : 0x0df0,
+ 0x0E51 : 0x0df1,
+ 0x0E52 : 0x0df2,
+ 0x0E53 : 0x0df3,
+ 0x0E54 : 0x0df4,
+ 0x0E55 : 0x0df5,
+ 0x0E56 : 0x0df6,
+ 0x0E57 : 0x0df7,
+ 0x0E58 : 0x0df8,
+ 0x0E59 : 0x0df9,
+ 0x0587 : 0x1000587,
+ 0x0589 : 0x1000589,
+ 0x055D : 0x100055d,
+ 0x058A : 0x100058a,
+ 0x055C : 0x100055c,
+ 0x055B : 0x100055b,
+ 0x055E : 0x100055e,
+ 0x0531 : 0x1000531,
+ 0x0561 : 0x1000561,
+ 0x0532 : 0x1000532,
+ 0x0562 : 0x1000562,
+ 0x0533 : 0x1000533,
+ 0x0563 : 0x1000563,
+ 0x0534 : 0x1000534,
+ 0x0564 : 0x1000564,
+ 0x0535 : 0x1000535,
+ 0x0565 : 0x1000565,
+ 0x0536 : 0x1000536,
+ 0x0566 : 0x1000566,
+ 0x0537 : 0x1000537,
+ 0x0567 : 0x1000567,
+ 0x0538 : 0x1000538,
+ 0x0568 : 0x1000568,
+ 0x0539 : 0x1000539,
+ 0x0569 : 0x1000569,
+ 0x053A : 0x100053a,
+ 0x056A : 0x100056a,
+ 0x053B : 0x100053b,
+ 0x056B : 0x100056b,
+ 0x053C : 0x100053c,
+ 0x056C : 0x100056c,
+ 0x053D : 0x100053d,
+ 0x056D : 0x100056d,
+ 0x053E : 0x100053e,
+ 0x056E : 0x100056e,
+ 0x053F : 0x100053f,
+ 0x056F : 0x100056f,
+ 0x0540 : 0x1000540,
+ 0x0570 : 0x1000570,
+ 0x0541 : 0x1000541,
+ 0x0571 : 0x1000571,
+ 0x0542 : 0x1000542,
+ 0x0572 : 0x1000572,
+ 0x0543 : 0x1000543,
+ 0x0573 : 0x1000573,
+ 0x0544 : 0x1000544,
+ 0x0574 : 0x1000574,
+ 0x0545 : 0x1000545,
+ 0x0575 : 0x1000575,
+ 0x0546 : 0x1000546,
+ 0x0576 : 0x1000576,
+ 0x0547 : 0x1000547,
+ 0x0577 : 0x1000577,
+ 0x0548 : 0x1000548,
+ 0x0578 : 0x1000578,
+ 0x0549 : 0x1000549,
+ 0x0579 : 0x1000579,
+ 0x054A : 0x100054a,
+ 0x057A : 0x100057a,
+ 0x054B : 0x100054b,
+ 0x057B : 0x100057b,
+ 0x054C : 0x100054c,
+ 0x057C : 0x100057c,
+ 0x054D : 0x100054d,
+ 0x057D : 0x100057d,
+ 0x054E : 0x100054e,
+ 0x057E : 0x100057e,
+ 0x054F : 0x100054f,
+ 0x057F : 0x100057f,
+ 0x0550 : 0x1000550,
+ 0x0580 : 0x1000580,
+ 0x0551 : 0x1000551,
+ 0x0581 : 0x1000581,
+ 0x0552 : 0x1000552,
+ 0x0582 : 0x1000582,
+ 0x0553 : 0x1000553,
+ 0x0583 : 0x1000583,
+ 0x0554 : 0x1000554,
+ 0x0584 : 0x1000584,
+ 0x0555 : 0x1000555,
+ 0x0585 : 0x1000585,
+ 0x0556 : 0x1000556,
+ 0x0586 : 0x1000586,
+ 0x055A : 0x100055a,
+ 0x10D0 : 0x10010d0,
+ 0x10D1 : 0x10010d1,
+ 0x10D2 : 0x10010d2,
+ 0x10D3 : 0x10010d3,
+ 0x10D4 : 0x10010d4,
+ 0x10D5 : 0x10010d5,
+ 0x10D6 : 0x10010d6,
+ 0x10D7 : 0x10010d7,
+ 0x10D8 : 0x10010d8,
+ 0x10D9 : 0x10010d9,
+ 0x10DA : 0x10010da,
+ 0x10DB : 0x10010db,
+ 0x10DC : 0x10010dc,
+ 0x10DD : 0x10010dd,
+ 0x10DE : 0x10010de,
+ 0x10DF : 0x10010df,
+ 0x10E0 : 0x10010e0,
+ 0x10E1 : 0x10010e1,
+ 0x10E2 : 0x10010e2,
+ 0x10E3 : 0x10010e3,
+ 0x10E4 : 0x10010e4,
+ 0x10E5 : 0x10010e5,
+ 0x10E6 : 0x10010e6,
+ 0x10E7 : 0x10010e7,
+ 0x10E8 : 0x10010e8,
+ 0x10E9 : 0x10010e9,
+ 0x10EA : 0x10010ea,
+ 0x10EB : 0x10010eb,
+ 0x10EC : 0x10010ec,
+ 0x10ED : 0x10010ed,
+ 0x10EE : 0x10010ee,
+ 0x10EF : 0x10010ef,
+ 0x10F0 : 0x10010f0,
+ 0x10F1 : 0x10010f1,
+ 0x10F2 : 0x10010f2,
+ 0x10F3 : 0x10010f3,
+ 0x10F4 : 0x10010f4,
+ 0x10F5 : 0x10010f5,
+ 0x10F6 : 0x10010f6,
+ 0x1E8A : 0x1001e8a,
+ 0x012C : 0x100012c,
+ 0x01B5 : 0x10001b5,
+ 0x01E6 : 0x10001e6,
+ 0x01D2 : 0x10001d1,
+ 0x019F : 0x100019f,
+ 0x1E8B : 0x1001e8b,
+ 0x012D : 0x100012d,
+ 0x01B6 : 0x10001b6,
+ 0x01E7 : 0x10001e7,
+ 0x01D2 : 0x10001d2,
+ 0x0275 : 0x1000275,
+ 0x018F : 0x100018f,
+ 0x0259 : 0x1000259,
+ 0x1E36 : 0x1001e36,
+ 0x1E37 : 0x1001e37,
+ 0x1EA0 : 0x1001ea0,
+ 0x1EA1 : 0x1001ea1,
+ 0x1EA2 : 0x1001ea2,
+ 0x1EA3 : 0x1001ea3,
+ 0x1EA4 : 0x1001ea4,
+ 0x1EA5 : 0x1001ea5,
+ 0x1EA6 : 0x1001ea6,
+ 0x1EA7 : 0x1001ea7,
+ 0x1EA8 : 0x1001ea8,
+ 0x1EA9 : 0x1001ea9,
+ 0x1EAA : 0x1001eaa,
+ 0x1EAB : 0x1001eab,
+ 0x1EAC : 0x1001eac,
+ 0x1EAD : 0x1001ead,
+ 0x1EAE : 0x1001eae,
+ 0x1EAF : 0x1001eaf,
+ 0x1EB0 : 0x1001eb0,
+ 0x1EB1 : 0x1001eb1,
+ 0x1EB2 : 0x1001eb2,
+ 0x1EB3 : 0x1001eb3,
+ 0x1EB4 : 0x1001eb4,
+ 0x1EB5 : 0x1001eb5,
+ 0x1EB6 : 0x1001eb6,
+ 0x1EB7 : 0x1001eb7,
+ 0x1EB8 : 0x1001eb8,
+ 0x1EB9 : 0x1001eb9,
+ 0x1EBA : 0x1001eba,
+ 0x1EBB : 0x1001ebb,
+ 0x1EBC : 0x1001ebc,
+ 0x1EBD : 0x1001ebd,
+ 0x1EBE : 0x1001ebe,
+ 0x1EBF : 0x1001ebf,
+ 0x1EC0 : 0x1001ec0,
+ 0x1EC1 : 0x1001ec1,
+ 0x1EC2 : 0x1001ec2,
+ 0x1EC3 : 0x1001ec3,
+ 0x1EC4 : 0x1001ec4,
+ 0x1EC5 : 0x1001ec5,
+ 0x1EC6 : 0x1001ec6,
+ 0x1EC7 : 0x1001ec7,
+ 0x1EC8 : 0x1001ec8,
+ 0x1EC9 : 0x1001ec9,
+ 0x1ECA : 0x1001eca,
+ 0x1ECB : 0x1001ecb,
+ 0x1ECC : 0x1001ecc,
+ 0x1ECD : 0x1001ecd,
+ 0x1ECE : 0x1001ece,
+ 0x1ECF : 0x1001ecf,
+ 0x1ED0 : 0x1001ed0,
+ 0x1ED1 : 0x1001ed1,
+ 0x1ED2 : 0x1001ed2,
+ 0x1ED3 : 0x1001ed3,
+ 0x1ED4 : 0x1001ed4,
+ 0x1ED5 : 0x1001ed5,
+ 0x1ED6 : 0x1001ed6,
+ 0x1ED7 : 0x1001ed7,
+ 0x1ED8 : 0x1001ed8,
+ 0x1ED9 : 0x1001ed9,
+ 0x1EDA : 0x1001eda,
+ 0x1EDB : 0x1001edb,
+ 0x1EDC : 0x1001edc,
+ 0x1EDD : 0x1001edd,
+ 0x1EDE : 0x1001ede,
+ 0x1EDF : 0x1001edf,
+ 0x1EE0 : 0x1001ee0,
+ 0x1EE1 : 0x1001ee1,
+ 0x1EE2 : 0x1001ee2,
+ 0x1EE3 : 0x1001ee3,
+ 0x1EE4 : 0x1001ee4,
+ 0x1EE5 : 0x1001ee5,
+ 0x1EE6 : 0x1001ee6,
+ 0x1EE7 : 0x1001ee7,
+ 0x1EE8 : 0x1001ee8,
+ 0x1EE9 : 0x1001ee9,
+ 0x1EEA : 0x1001eea,
+ 0x1EEB : 0x1001eeb,
+ 0x1EEC : 0x1001eec,
+ 0x1EED : 0x1001eed,
+ 0x1EEE : 0x1001eee,
+ 0x1EEF : 0x1001eef,
+ 0x1EF0 : 0x1001ef0,
+ 0x1EF1 : 0x1001ef1,
+ 0x1EF4 : 0x1001ef4,
+ 0x1EF5 : 0x1001ef5,
+ 0x1EF6 : 0x1001ef6,
+ 0x1EF7 : 0x1001ef7,
+ 0x1EF8 : 0x1001ef8,
+ 0x1EF9 : 0x1001ef9,
+ 0x01A0 : 0x10001a0,
+ 0x01A1 : 0x10001a1,
+ 0x01AF : 0x10001af,
+ 0x01B0 : 0x10001b0,
+ 0x20A0 : 0x10020a0,
+ 0x20A1 : 0x10020a1,
+ 0x20A2 : 0x10020a2,
+ 0x20A3 : 0x10020a3,
+ 0x20A4 : 0x10020a4,
+ 0x20A5 : 0x10020a5,
+ 0x20A6 : 0x10020a6,
+ 0x20A7 : 0x10020a7,
+ 0x20A8 : 0x10020a8,
+ 0x20A9 : 0x10020a9,
+ 0x20AA : 0x10020aa,
+ 0x20AB : 0x10020ab,
+ 0x20AC : 0x20ac,
+ 0x2070 : 0x1002070,
+ 0x2074 : 0x1002074,
+ 0x2075 : 0x1002075,
+ 0x2076 : 0x1002076,
+ 0x2077 : 0x1002077,
+ 0x2078 : 0x1002078,
+ 0x2079 : 0x1002079,
+ 0x2080 : 0x1002080,
+ 0x2081 : 0x1002081,
+ 0x2082 : 0x1002082,
+ 0x2083 : 0x1002083,
+ 0x2084 : 0x1002084,
+ 0x2085 : 0x1002085,
+ 0x2086 : 0x1002086,
+ 0x2087 : 0x1002087,
+ 0x2088 : 0x1002088,
+ 0x2089 : 0x1002089,
+ 0x2202 : 0x1002202,
+ 0x2205 : 0x1002205,
+ 0x2208 : 0x1002208,
+ 0x2209 : 0x1002209,
+ 0x220B : 0x100220B,
+ 0x221A : 0x100221A,
+ 0x221B : 0x100221B,
+ 0x221C : 0x100221C,
+ 0x222C : 0x100222C,
+ 0x222D : 0x100222D,
+ 0x2235 : 0x1002235,
+ 0x2245 : 0x1002248,
+ 0x2247 : 0x1002247,
+ 0x2262 : 0x1002262,
+ 0x2263 : 0x1002263,
+ 0x2800 : 0x1002800,
+ 0x2801 : 0x1002801,
+ 0x2802 : 0x1002802,
+ 0x2803 : 0x1002803,
+ 0x2804 : 0x1002804,
+ 0x2805 : 0x1002805,
+ 0x2806 : 0x1002806,
+ 0x2807 : 0x1002807,
+ 0x2808 : 0x1002808,
+ 0x2809 : 0x1002809,
+ 0x280a : 0x100280a,
+ 0x280b : 0x100280b,
+ 0x280c : 0x100280c,
+ 0x280d : 0x100280d,
+ 0x280e : 0x100280e,
+ 0x280f : 0x100280f,
+ 0x2810 : 0x1002810,
+ 0x2811 : 0x1002811,
+ 0x2812 : 0x1002812,
+ 0x2813 : 0x1002813,
+ 0x2814 : 0x1002814,
+ 0x2815 : 0x1002815,
+ 0x2816 : 0x1002816,
+ 0x2817 : 0x1002817,
+ 0x2818 : 0x1002818,
+ 0x2819 : 0x1002819,
+ 0x281a : 0x100281a,
+ 0x281b : 0x100281b,
+ 0x281c : 0x100281c,
+ 0x281d : 0x100281d,
+ 0x281e : 0x100281e,
+ 0x281f : 0x100281f,
+ 0x2820 : 0x1002820,
+ 0x2821 : 0x1002821,
+ 0x2822 : 0x1002822,
+ 0x2823 : 0x1002823,
+ 0x2824 : 0x1002824,
+ 0x2825 : 0x1002825,
+ 0x2826 : 0x1002826,
+ 0x2827 : 0x1002827,
+ 0x2828 : 0x1002828,
+ 0x2829 : 0x1002829,
+ 0x282a : 0x100282a,
+ 0x282b : 0x100282b,
+ 0x282c : 0x100282c,
+ 0x282d : 0x100282d,
+ 0x282e : 0x100282e,
+ 0x282f : 0x100282f,
+ 0x2830 : 0x1002830,
+ 0x2831 : 0x1002831,
+ 0x2832 : 0x1002832,
+ 0x2833 : 0x1002833,
+ 0x2834 : 0x1002834,
+ 0x2835 : 0x1002835,
+ 0x2836 : 0x1002836,
+ 0x2837 : 0x1002837,
+ 0x2838 : 0x1002838,
+ 0x2839 : 0x1002839,
+ 0x283a : 0x100283a,
+ 0x283b : 0x100283b,
+ 0x283c : 0x100283c,
+ 0x283d : 0x100283d,
+ 0x283e : 0x100283e,
+ 0x283f : 0x100283f,
+ 0x2840 : 0x1002840,
+ 0x2841 : 0x1002841,
+ 0x2842 : 0x1002842,
+ 0x2843 : 0x1002843,
+ 0x2844 : 0x1002844,
+ 0x2845 : 0x1002845,
+ 0x2846 : 0x1002846,
+ 0x2847 : 0x1002847,
+ 0x2848 : 0x1002848,
+ 0x2849 : 0x1002849,
+ 0x284a : 0x100284a,
+ 0x284b : 0x100284b,
+ 0x284c : 0x100284c,
+ 0x284d : 0x100284d,
+ 0x284e : 0x100284e,
+ 0x284f : 0x100284f,
+ 0x2850 : 0x1002850,
+ 0x2851 : 0x1002851,
+ 0x2852 : 0x1002852,
+ 0x2853 : 0x1002853,
+ 0x2854 : 0x1002854,
+ 0x2855 : 0x1002855,
+ 0x2856 : 0x1002856,
+ 0x2857 : 0x1002857,
+ 0x2858 : 0x1002858,
+ 0x2859 : 0x1002859,
+ 0x285a : 0x100285a,
+ 0x285b : 0x100285b,
+ 0x285c : 0x100285c,
+ 0x285d : 0x100285d,
+ 0x285e : 0x100285e,
+ 0x285f : 0x100285f,
+ 0x2860 : 0x1002860,
+ 0x2861 : 0x1002861,
+ 0x2862 : 0x1002862,
+ 0x2863 : 0x1002863,
+ 0x2864 : 0x1002864,
+ 0x2865 : 0x1002865,
+ 0x2866 : 0x1002866,
+ 0x2867 : 0x1002867,
+ 0x2868 : 0x1002868,
+ 0x2869 : 0x1002869,
+ 0x286a : 0x100286a,
+ 0x286b : 0x100286b,
+ 0x286c : 0x100286c,
+ 0x286d : 0x100286d,
+ 0x286e : 0x100286e,
+ 0x286f : 0x100286f,
+ 0x2870 : 0x1002870,
+ 0x2871 : 0x1002871,
+ 0x2872 : 0x1002872,
+ 0x2873 : 0x1002873,
+ 0x2874 : 0x1002874,
+ 0x2875 : 0x1002875,
+ 0x2876 : 0x1002876,
+ 0x2877 : 0x1002877,
+ 0x2878 : 0x1002878,
+ 0x2879 : 0x1002879,
+ 0x287a : 0x100287a,
+ 0x287b : 0x100287b,
+ 0x287c : 0x100287c,
+ 0x287d : 0x100287d,
+ 0x287e : 0x100287e,
+ 0x287f : 0x100287f,
+ 0x2880 : 0x1002880,
+ 0x2881 : 0x1002881,
+ 0x2882 : 0x1002882,
+ 0x2883 : 0x1002883,
+ 0x2884 : 0x1002884,
+ 0x2885 : 0x1002885,
+ 0x2886 : 0x1002886,
+ 0x2887 : 0x1002887,
+ 0x2888 : 0x1002888,
+ 0x2889 : 0x1002889,
+ 0x288a : 0x100288a,
+ 0x288b : 0x100288b,
+ 0x288c : 0x100288c,
+ 0x288d : 0x100288d,
+ 0x288e : 0x100288e,
+ 0x288f : 0x100288f,
+ 0x2890 : 0x1002890,
+ 0x2891 : 0x1002891,
+ 0x2892 : 0x1002892,
+ 0x2893 : 0x1002893,
+ 0x2894 : 0x1002894,
+ 0x2895 : 0x1002895,
+ 0x2896 : 0x1002896,
+ 0x2897 : 0x1002897,
+ 0x2898 : 0x1002898,
+ 0x2899 : 0x1002899,
+ 0x289a : 0x100289a,
+ 0x289b : 0x100289b,
+ 0x289c : 0x100289c,
+ 0x289d : 0x100289d,
+ 0x289e : 0x100289e,
+ 0x289f : 0x100289f,
+ 0x28a0 : 0x10028a0,
+ 0x28a1 : 0x10028a1,
+ 0x28a2 : 0x10028a2,
+ 0x28a3 : 0x10028a3,
+ 0x28a4 : 0x10028a4,
+ 0x28a5 : 0x10028a5,
+ 0x28a6 : 0x10028a6,
+ 0x28a7 : 0x10028a7,
+ 0x28a8 : 0x10028a8,
+ 0x28a9 : 0x10028a9,
+ 0x28aa : 0x10028aa,
+ 0x28ab : 0x10028ab,
+ 0x28ac : 0x10028ac,
+ 0x28ad : 0x10028ad,
+ 0x28ae : 0x10028ae,
+ 0x28af : 0x10028af,
+ 0x28b0 : 0x10028b0,
+ 0x28b1 : 0x10028b1,
+ 0x28b2 : 0x10028b2,
+ 0x28b3 : 0x10028b3,
+ 0x28b4 : 0x10028b4,
+ 0x28b5 : 0x10028b5,
+ 0x28b6 : 0x10028b6,
+ 0x28b7 : 0x10028b7,
+ 0x28b8 : 0x10028b8,
+ 0x28b9 : 0x10028b9,
+ 0x28ba : 0x10028ba,
+ 0x28bb : 0x10028bb,
+ 0x28bc : 0x10028bc,
+ 0x28bd : 0x10028bd,
+ 0x28be : 0x10028be,
+ 0x28bf : 0x10028bf,
+ 0x28c0 : 0x10028c0,
+ 0x28c1 : 0x10028c1,
+ 0x28c2 : 0x10028c2,
+ 0x28c3 : 0x10028c3,
+ 0x28c4 : 0x10028c4,
+ 0x28c5 : 0x10028c5,
+ 0x28c6 : 0x10028c6,
+ 0x28c7 : 0x10028c7,
+ 0x28c8 : 0x10028c8,
+ 0x28c9 : 0x10028c9,
+ 0x28ca : 0x10028ca,
+ 0x28cb : 0x10028cb,
+ 0x28cc : 0x10028cc,
+ 0x28cd : 0x10028cd,
+ 0x28ce : 0x10028ce,
+ 0x28cf : 0x10028cf,
+ 0x28d0 : 0x10028d0,
+ 0x28d1 : 0x10028d1,
+ 0x28d2 : 0x10028d2,
+ 0x28d3 : 0x10028d3,
+ 0x28d4 : 0x10028d4,
+ 0x28d5 : 0x10028d5,
+ 0x28d6 : 0x10028d6,
+ 0x28d7 : 0x10028d7,
+ 0x28d8 : 0x10028d8,
+ 0x28d9 : 0x10028d9,
+ 0x28da : 0x10028da,
+ 0x28db : 0x10028db,
+ 0x28dc : 0x10028dc,
+ 0x28dd : 0x10028dd,
+ 0x28de : 0x10028de,
+ 0x28df : 0x10028df,
+ 0x28e0 : 0x10028e0,
+ 0x28e1 : 0x10028e1,
+ 0x28e2 : 0x10028e2,
+ 0x28e3 : 0x10028e3,
+ 0x28e4 : 0x10028e4,
+ 0x28e5 : 0x10028e5,
+ 0x28e6 : 0x10028e6,
+ 0x28e7 : 0x10028e7,
+ 0x28e8 : 0x10028e8,
+ 0x28e9 : 0x10028e9,
+ 0x28ea : 0x10028ea,
+ 0x28eb : 0x10028eb,
+ 0x28ec : 0x10028ec,
+ 0x28ed : 0x10028ed,
+ 0x28ee : 0x10028ee,
+ 0x28ef : 0x10028ef,
+ 0x28f0 : 0x10028f0,
+ 0x28f1 : 0x10028f1,
+ 0x28f2 : 0x10028f2,
+ 0x28f3 : 0x10028f3,
+ 0x28f4 : 0x10028f4,
+ 0x28f5 : 0x10028f5,
+ 0x28f6 : 0x10028f6,
+ 0x28f7 : 0x10028f7,
+ 0x28f8 : 0x10028f8,
+ 0x28f9 : 0x10028f9,
+ 0x28fa : 0x10028fa,
+ 0x28fb : 0x10028fb,
+ 0x28fc : 0x10028fc,
+ 0x28fd : 0x10028fd,
+ 0x28fe : 0x10028fe,
+ 0x28ff : 0x10028ff
+}; \ No newline at end of file
diff --git a/webclients/novnc/include/logo.js b/webclients/novnc/include/logo.js
new file mode 100644
index 0000000..befa598
--- /dev/null
+++ b/webclients/novnc/include/logo.js
@@ -0,0 +1 @@
+noVNC_logo = {"width": 640, "height": 435, "data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAGzCAYAAAC/y6a9AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAStAAAErQBBHTWggAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7N13fBvlwQfw3522ZMm2vPdIGCFkA4GyoYyGsCmjk+7dQksHL2/H2/dtC4W2tLTlfelu2VA2lEILFCgQIHEGJCQkdjzkLdmWZGvfvX8oOkmJEy/pNO73/Xz44DtLzz2RT7qfnnXC8uXLZUxDlqfdnUYQhIP+bjbPn+5xhypzrmUf6rGzOc5cjzVduXN9/nTPyfRrMt/jzOcY05U5n3L2f95s/34LPW4m/p6FbLp/73xe+5nKnWuZs/07ZOOcnusx5nucbJU727LneuxslDmdTBxn/2NmusyEuZS7kHMxG/XP5Gf3TOVmQzY+u/PhPMnkMcSsH5WIiIiI8goDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHGMAASERERaQwDIBEREZHG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"};
diff --git a/webclients/novnc/include/playback.js b/webclients/novnc/include/playback.js
new file mode 100644
index 0000000..22a00a3
--- /dev/null
+++ b/webclients/novnc/include/playback.js
@@ -0,0 +1,90 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 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();
+};
+
diff --git a/webclients/novnc/include/rfb.js b/webclients/novnc/include/rfb.js
new file mode 100644
index 0000000..b7aa3f6
--- /dev/null
+++ b/webclients/novnc/include/rfb.js
@@ -0,0 +1,1613 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 Joel Martin
+ * Licensed under LGPL-3 (see LICENSE.txt)
+ *
+ * See README.md for usage and integration instructions.
+ */
+
+/*jslint white: false, browser: true, bitwise: false, plusplus: false */
+/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES */
+
+
+function RFB(defaults) {
+"use strict";
+
+var that = {}, // Public API methods
+ conf = {}, // Configuration attributes
+
+ // Pre-declare private functions used before definitions (jslint)
+ init_vars, updateState, fail, handle_message,
+ init_msg, normal_msg, framebufferUpdate, print_stats,
+
+ pixelFormat, clientEncodings, fbUpdateRequest, fbUpdateRequests,
+ keyEvent, pointerEvent, clientCutText,
+
+ extract_data_uri, scan_tight_imgQ,
+ keyPress, mouseButton, mouseMove,
+
+ checkEvents, // Overridable for testing
+
+
+ //
+ // Private RFB namespace variables
+ //
+ rfb_host = '',
+ rfb_port = 5900,
+ rfb_password = '',
+ rfb_path = '',
+
+ rfb_state = 'disconnected',
+ rfb_version = 0,
+ rfb_max_version= 3.8,
+ rfb_auth_scheme= '',
+
+
+ // In preference order
+ encodings = [
+ ['COPYRECT', 0x01 ],
+ ['TIGHT_PNG', -260 ],
+ ['HEXTILE', 0x05 ],
+ ['RRE', 0x02 ],
+ ['RAW', 0x00 ],
+ ['DesktopSize', -223 ],
+ ['Cursor', -239 ],
+
+ // Psuedo-encoding settings
+ ['JPEG_quality_lo', -32 ],
+ //['JPEG_quality_hi', -23 ],
+ ['compress_lo', -255 ]
+ //['compress_hi', -247 ]
+ ],
+
+ encHandlers = {},
+ encNames = {},
+ encStats = {}, // [rectCnt, rectCntTot]
+
+ ws = null, // Websock object
+ display = null, // Display object
+ keyboard = null, // Keyboard input handler object
+ mouse = null, // Mouse input handler object
+ sendTimer = null, // Send Queue check timer
+ connTimer = null, // connection timer
+ disconnTimer = null, // disconnection timer
+ msgTimer = null, // queued handle_message timer
+
+ // Frame buffer update state
+ FBU = {
+ rects : 0,
+ subrects : 0, // RRE
+ lines : 0, // RAW
+ tiles : 0, // HEXTILE
+ bytes : 0,
+ x : 0,
+ y : 0,
+ width : 0,
+ height : 0,
+ encoding : 0,
+ subencoding : -1,
+ background : null,
+ imgQ : [] // TIGHT_PNG image queue
+ },
+
+ fb_Bpp = 4,
+ fb_depth = 3,
+ fb_width = 0,
+ fb_height = 0,
+ fb_name = "",
+
+ scan_imgQ_rate = 40, // 25 times per second or so
+ last_req_time = 0,
+ rre_chunk_sz = 100,
+
+ timing = {
+ last_fbu : 0,
+ fbu_total : 0,
+ fbu_total_cnt : 0,
+ full_fbu_total : 0,
+ full_fbu_cnt : 0,
+
+ fbu_rt_start : 0,
+ fbu_rt_total : 0,
+ fbu_rt_cnt : 0
+ },
+
+ test_mode = false,
+
+ def_con_timeout = Websock_native ? 2 : 5,
+
+ /* Mouse state */
+ mouse_buttonMask = 0,
+ mouse_arr = [],
+ viewportDragging = false,
+ viewportDragPos = {};
+
+// Configuration attributes
+Util.conf_defaults(conf, that, defaults, [
+ ['target', 'wo', 'dom', null, 'VNC display rendering Canvas object'],
+ ['focusContainer', 'wo', 'dom', document, 'DOM element that captures keyboard input'],
+
+ ['encrypt', 'rw', 'bool', false, 'Use TLS/SSL/wss encryption'],
+ ['true_color', 'rw', 'bool', true, 'Request true color pixel data'],
+ ['local_cursor', 'rw', 'bool', false, 'Request locally rendered cursor'],
+ ['shared', 'rw', 'bool', true, 'Request shared mode'],
+
+ ['connectTimeout', 'rw', 'int', def_con_timeout, 'Time (s) to wait for connection'],
+ ['disconnectTimeout', 'rw', 'int', 3, 'Time (s) to wait for disconnection'],
+
+ ['viewportDrag', 'rw', 'bool', false, 'Move the viewport on mouse drags'],
+
+ ['check_rate', 'rw', 'int', 217, 'Timing (ms) of send/receive check'],
+ ['fbu_req_rate', 'rw', 'int', 1413, 'Timing (ms) of frameBufferUpdate requests'],
+
+ // Callback functions
+ ['onUpdateState', 'rw', 'func', function() { },
+ 'onUpdateState(rfb, state, oldstate, statusMsg): RFB state update/change '],
+ ['onPasswordRequired', 'rw', 'func', function() { },
+ 'onPasswordRequired(rfb): VNC password is required '],
+ ['onClipboard', 'rw', 'func', function() { },
+ 'onClipboard(rfb, text): RFB clipboard contents received'],
+ ['onBell', 'rw', 'func', function() { },
+ 'onBell(rfb): RFB Bell message received '],
+ ['onFBUReceive', 'rw', 'func', function() { },
+ 'onFBUReceive(rfb, fbu): RFB FBU received but not yet processed '],
+ ['onFBUComplete', 'rw', 'func', function() { },
+ 'onFBUComplete(rfb, fbu): RFB FBU received and processed '],
+
+ // These callback names are deprecated
+ ['updateState', 'rw', 'func', function() { },
+ 'obsolete, use onUpdateState'],
+ ['clipboardReceive', 'rw', 'func', function() { },
+ 'obsolete, use onClipboard']
+ ]);
+
+
+// Override/add some specific configuration getters/setters
+that.set_local_cursor = function(cursor) {
+ if ((!cursor) || (cursor in {'0':1, 'no':1, 'false':1})) {
+ conf.local_cursor = false;
+ } else {
+ if (display.get_cursor_uri()) {
+ conf.local_cursor = true;
+ } else {
+ Util.Warn("Browser does not support local cursor");
+ }
+ }
+};
+
+// These are fake configuration getters
+that.get_display = function() { return display; };
+
+that.get_keyboard = function() { return keyboard; };
+
+that.get_mouse = function() { return mouse; };
+
+
+
+//
+// Setup routines
+//
+
+// Create the public API interface and initialize values that stay
+// constant across connect/disconnect
+function constructor() {
+ var i, rmode;
+ Util.Debug(">> RFB.constructor");
+
+ // Create lookup tables based encoding number
+ for (i=0; i < encodings.length; i+=1) {
+ encHandlers[encodings[i][1]] = encHandlers[encodings[i][0]];
+ encNames[encodings[i][1]] = encodings[i][0];
+ encStats[encodings[i][1]] = [0, 0];
+ }
+ // Initialize display, mouse, keyboard, and websock
+ try {
+ display = new Display({'target': conf.target});
+ } catch (exc) {
+ Util.Error("Display exception: " + exc);
+ updateState('fatal', "No working Display");
+ }
+ keyboard = new Keyboard({'target': conf.focusContainer,
+ 'onKeyPress': keyPress});
+ mouse = new Mouse({'target': conf.target,
+ 'onMouseButton': mouseButton,
+ 'onMouseMove': mouseMove});
+
+ rmode = display.get_render_mode();
+
+ ws = new Websock();
+ ws.on('message', handle_message);
+ ws.on('open', function() {
+ if (rfb_state === "connect") {
+ updateState('ProtocolVersion', "Starting VNC handshake");
+ } else {
+ fail("Got unexpected WebSockets connection");
+ }
+ });
+ ws.on('close', function() {
+ if (rfb_state === 'disconnect') {
+ updateState('disconnected', 'VNC disconnected');
+ } else if (rfb_state === 'ProtocolVersion') {
+ fail('Failed to connect to server');
+ } else if (rfb_state in {'failed':1, 'disconnected':1}) {
+ Util.Error("Received onclose while disconnected");
+ } else {
+ fail('Server disconnected');
+ }
+ });
+ ws.on('error', function(e) {
+ fail("WebSock error: " + e);
+ });
+
+
+ init_vars();
+
+ /* Check web-socket-js if no builtin WebSocket support */
+ if (Websock_native) {
+ Util.Info("Using native WebSockets");
+ updateState('loaded', 'noVNC ready: native WebSockets, ' + rmode);
+ } else {
+ Util.Warn("Using web-socket-js bridge. Flash version: " +
+ Util.Flash.version);
+ if ((! Util.Flash) ||
+ (Util.Flash.version < 9)) {
+ updateState('fatal', "WebSockets or <a href='http://get.adobe.com/flashplayer'>Adobe Flash<\/a> is required");
+ } else if (document.location.href.substr(0, 7) === "file://") {
+ updateState('fatal',
+ "'file://' URL is incompatible with Adobe Flash");
+ } else {
+ updateState('loaded', 'noVNC ready: WebSockets emulation, ' + rmode);
+ }
+ }
+
+ Util.Debug("<< RFB.constructor");
+ return that; // Return the public API interface
+}
+
+function connect() {
+ Util.Debug(">> RFB.connect");
+
+ var uri = "";
+ if (conf.encrypt) {
+ uri = "wss://";
+ } else {
+ uri = "ws://";
+ }
+ uri += rfb_host + ":" + rfb_port + "/" + rfb_path;
+ Util.Info("connecting to " + uri);
+ ws.open(uri);
+
+ Util.Debug("<< RFB.connect");
+}
+
+// Initialize variables that are reset before each connection
+init_vars = function() {
+ var i;
+
+ /* Reset state */
+ ws.init();
+
+ FBU.rects = 0;
+ FBU.subrects = 0; // RRE and HEXTILE
+ FBU.lines = 0; // RAW
+ FBU.tiles = 0; // HEXTILE
+ FBU.imgQ = []; // TIGHT_PNG image queue
+ mouse_buttonMask = 0;
+ mouse_arr = [];
+
+ // Clear the per connection encoding stats
+ for (i=0; i < encodings.length; i+=1) {
+ encStats[encodings[i][1]][0] = 0;
+ }
+};
+
+// Print statistics
+print_stats = function() {
+ var i, s;
+ Util.Info("Encoding stats for this connection:");
+ for (i=0; i < encodings.length; i+=1) {
+ s = encStats[encodings[i][1]];
+ if ((s[0] + s[1]) > 0) {
+ Util.Info(" " + encodings[i][0] + ": " +
+ s[0] + " rects");
+ }
+ }
+ Util.Info("Encoding stats since page load:");
+ for (i=0; i < encodings.length; i+=1) {
+ s = encStats[encodings[i][1]];
+ if ((s[0] + s[1]) > 0) {
+ Util.Info(" " + encodings[i][0] + ": " +
+ s[1] + " rects");
+ }
+ }
+};
+
+//
+// Utility routines
+//
+
+
+/*
+ * Page states:
+ * loaded - page load, equivalent to disconnected
+ * disconnected - idle state
+ * connect - starting to connect (to ProtocolVersion)
+ * normal - connected
+ * disconnect - starting to disconnect
+ * failed - abnormal disconnect
+ * fatal - failed to load page, or fatal error
+ *
+ * RFB protocol initialization states:
+ * ProtocolVersion
+ * Security
+ * Authentication
+ * password - waiting for password, not part of RFB
+ * SecurityResult
+ * ClientInitialization - not triggered by server message
+ * ServerInitialization (to normal)
+ */
+updateState = function(state, statusMsg) {
+ var func, cmsg, oldstate = rfb_state;
+
+ if (state === oldstate) {
+ /* Already here, ignore */
+ Util.Debug("Already in state '" + state + "', ignoring.");
+ return;
+ }
+
+ /*
+ * These are disconnected states. A previous connect may
+ * asynchronously cause a connection so make sure we are closed.
+ */
+ if (state in {'disconnected':1, 'loaded':1, 'connect':1,
+ 'disconnect':1, 'failed':1, 'fatal':1}) {
+ if (sendTimer) {
+ clearInterval(sendTimer);
+ sendTimer = null;
+ }
+
+ if (msgTimer) {
+ clearInterval(msgTimer);
+ msgTimer = null;
+ }
+
+ if (display && display.get_context()) {
+ keyboard.ungrab();
+ mouse.ungrab();
+ display.defaultCursor();
+ if ((Util.get_logging() !== 'debug') ||
+ (state === 'loaded')) {
+ // Show noVNC logo on load and when disconnected if
+ // debug is off
+ display.clear();
+ }
+ }
+
+ ws.close();
+ }
+
+ if (oldstate === 'fatal') {
+ Util.Error("Fatal error, cannot continue");
+ }
+
+ if ((state === 'failed') || (state === 'fatal')) {
+ func = Util.Error;
+ } else {
+ func = Util.Warn;
+ }
+
+ if ((oldstate === 'failed') && (state === 'disconnected')) {
+ // Do disconnect action, but stay in failed state.
+ rfb_state = 'failed';
+ } else {
+ rfb_state = state;
+ }
+
+ cmsg = typeof(statusMsg) !== 'undefined' ? (" Msg: " + statusMsg) : "";
+ func("New state '" + rfb_state + "', was '" + oldstate + "'." + cmsg);
+
+ if (connTimer && (rfb_state !== 'connect')) {
+ Util.Debug("Clearing connect timer");
+ clearInterval(connTimer);
+ connTimer = null;
+ }
+
+ if (disconnTimer && (rfb_state !== 'disconnect')) {
+ Util.Debug("Clearing disconnect timer");
+ clearInterval(disconnTimer);
+ disconnTimer = null;
+ }
+
+ switch (state) {
+ case 'normal':
+ if ((oldstate === 'disconnected') || (oldstate === 'failed')) {
+ Util.Error("Invalid transition from 'disconnected' or 'failed' to 'normal'");
+ }
+
+ break;
+
+
+ case 'connect':
+
+ connTimer = setTimeout(function () {
+ fail("Connect timeout");
+ }, conf.connectTimeout * 1000);
+
+ init_vars();
+ connect();
+
+ // WebSocket.onopen transitions to 'ProtocolVersion'
+ break;
+
+
+ case 'disconnect':
+
+ if (! test_mode) {
+ disconnTimer = setTimeout(function () {
+ fail("Disconnect timeout");
+ }, conf.disconnectTimeout * 1000);
+ }
+
+ print_stats();
+
+ // WebSocket.onclose transitions to 'disconnected'
+ break;
+
+
+ case 'failed':
+ if (oldstate === 'disconnected') {
+ Util.Error("Invalid transition from 'disconnected' to 'failed'");
+ }
+ if (oldstate === 'normal') {
+ Util.Error("Error while connected.");
+ }
+ if (oldstate === 'init') {
+ Util.Error("Error while initializing.");
+ }
+
+ // Make sure we transition to disconnected
+ setTimeout(function() { updateState('disconnected'); }, 50);
+
+ break;
+
+
+ default:
+ // No state change action to take
+
+ }
+
+ if ((oldstate === 'failed') && (state === 'disconnected')) {
+ // Leave the failed message
+ conf.updateState(that, state, oldstate); // Obsolete
+ conf.onUpdateState(that, state, oldstate);
+ } else {
+ conf.updateState(that, state, oldstate, statusMsg); // Obsolete
+ conf.onUpdateState(that, state, oldstate, statusMsg);
+ }
+};
+
+fail = function(msg) {
+ updateState('failed', msg);
+ return false;
+};
+
+handle_message = function() {
+ //Util.Debug(">> handle_message ws.rQlen(): " + ws.rQlen());
+ //Util.Debug("ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+ if (ws.rQlen() === 0) {
+ Util.Warn("handle_message called on empty receive queue");
+ return;
+ }
+ switch (rfb_state) {
+ case 'disconnected':
+ case 'failed':
+ Util.Error("Got data while disconnected");
+ break;
+ case 'normal':
+ if (normal_msg() && ws.rQlen() > 0) {
+ // true means we can continue processing
+ // Give other events a chance to run
+ if (msgTimer === null) {
+ Util.Debug("More data to process, creating timer");
+ msgTimer = setTimeout(function () {
+ msgTimer = null;
+ handle_message();
+ }, 10);
+ } else {
+ Util.Debug("More data to process, existing timer");
+ }
+ }
+ break;
+ default:
+ init_msg();
+ break;
+ }
+};
+
+
+function genDES(password, challenge) {
+ var i, passwd = [];
+ for (i=0; i < password.length; i += 1) {
+ passwd.push(password.charCodeAt(i));
+ }
+ return (new DES(passwd)).encrypt(challenge);
+}
+
+function flushClient() {
+ if (mouse_arr.length > 0) {
+ //send(mouse_arr.concat(fbUpdateRequests()));
+ ws.send(mouse_arr);
+ setTimeout(function() {
+ ws.send(fbUpdateRequests());
+ }, 50);
+
+ mouse_arr = [];
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// overridable for testing
+checkEvents = function() {
+ var now;
+ if (rfb_state === 'normal' && !viewportDragging) {
+ if (! flushClient()) {
+ now = new Date().getTime();
+ if (now > last_req_time + conf.fbu_req_rate) {
+ last_req_time = now;
+ ws.send(fbUpdateRequests());
+ }
+ }
+ }
+ setTimeout(checkEvents, conf.check_rate);
+};
+
+keyPress = function(keysym, down) {
+ var arr;
+ arr = keyEvent(keysym, down);
+ arr = arr.concat(fbUpdateRequests());
+ ws.send(arr);
+};
+
+mouseButton = function(x, y, down, bmask) {
+ if (down) {
+ mouse_buttonMask |= bmask;
+ } else {
+ mouse_buttonMask ^= bmask;
+ }
+
+ if (conf.viewportDrag) {
+ if (down && !viewportDragging) {
+ viewportDragging = true;
+ viewportDragPos = {'x': x, 'y': y};
+
+ // Skip sending mouse events
+ return;
+ } else {
+ viewportDragging = false;
+ }
+ }
+
+ mouse_arr = mouse_arr.concat(
+ pointerEvent(display.absX(x), display.absY(y)) );
+ flushClient();
+};
+
+mouseMove = function(x, y) {
+ //Util.Debug('>> mouseMove ' + x + "," + y);
+ var deltaX, deltaY;
+
+ if (viewportDragging) {
+ //deltaX = x - viewportDragPos.x; // drag viewport
+ deltaX = viewportDragPos.x - x; // drag frame buffer
+ //deltaY = y - viewportDragPos.y; // drag viewport
+ deltaY = viewportDragPos.y - y; // drag frame buffer
+ viewportDragPos = {'x': x, 'y': y};
+
+ display.viewportChange(deltaX, deltaY);
+
+ // Skip sending mouse events
+ return;
+ }
+
+ mouse_arr = mouse_arr.concat(
+ pointerEvent(display.absX(x), display.absY(y)) );
+};
+
+
+//
+// Server message handlers
+//
+
+// RFB/VNC initialisation message handler
+init_msg = function() {
+ //Util.Debug(">> init_msg [rfb_state '" + rfb_state + "']");
+
+ var strlen, reason, length, sversion, cversion,
+ i, types, num_types, challenge, response, bpp, depth,
+ big_endian, red_max, green_max, blue_max, red_shift,
+ green_shift, blue_shift, true_color, name_length;
+
+ //Util.Debug("ws.rQ (" + ws.rQlen() + ") " + ws.rQslice(0));
+ switch (rfb_state) {
+
+ case 'ProtocolVersion' :
+ if (ws.rQlen() < 12) {
+ return fail("Incomplete protocol version");
+ }
+ sversion = ws.rQshiftStr(12).substr(4,7);
+ Util.Info("Server ProtocolVersion: " + sversion);
+ switch (sversion) {
+ case "003.003": rfb_version = 3.3; break;
+ case "003.006": rfb_version = 3.3; break; // UltraVNC
+ case "003.007": rfb_version = 3.7; break;
+ case "003.008": rfb_version = 3.8; break;
+ default:
+ return fail("Invalid server version " + sversion);
+ }
+ if (rfb_version > rfb_max_version) {
+ rfb_version = rfb_max_version;
+ }
+
+ if (! test_mode) {
+ sendTimer = setInterval(function() {
+ // Send updates either at a rate of one update
+ // every 50ms, or whatever slower rate the network
+ // can handle.
+ ws.flush();
+ }, 50);
+ }
+
+ cversion = "00" + parseInt(rfb_version,10) +
+ ".00" + ((rfb_version * 10) % 10);
+ ws.send_string("RFB " + cversion + "\n");
+ updateState('Security', "Sent ProtocolVersion: " + cversion);
+ break;
+
+ case 'Security' :
+ if (rfb_version >= 3.7) {
+ // Server sends supported list, client decides
+ num_types = ws.rQshift8();
+ if (ws.rQwait("security type", num_types, 1)) { return false; }
+ if (num_types === 0) {
+ strlen = ws.rQshift32();
+ reason = ws.rQshiftStr(strlen);
+ return fail("Security failure: " + reason);
+ }
+ rfb_auth_scheme = 0;
+ types = ws.rQshiftBytes(num_types);
+ Util.Debug("Server security types: " + types);
+ for (i=0; i < types.length; i+=1) {
+ if ((types[i] > rfb_auth_scheme) && (types[i] < 3)) {
+ rfb_auth_scheme = types[i];
+ }
+ }
+ if (rfb_auth_scheme === 0) {
+ return fail("Unsupported security types: " + types);
+ }
+
+ ws.send([rfb_auth_scheme]);
+ } else {
+ // Server decides
+ if (ws.rQwait("security scheme", 4)) { return false; }
+ rfb_auth_scheme = ws.rQshift32();
+ }
+ updateState('Authentication',
+ "Authenticating using scheme: " + rfb_auth_scheme);
+ init_msg(); // Recursive fallthrough (workaround JSLint complaint)
+ break;
+
+ // Triggered by fallthough, not by server message
+ case 'Authentication' :
+ //Util.Debug("Security auth scheme: " + rfb_auth_scheme);
+ switch (rfb_auth_scheme) {
+ case 0: // connection failed
+ if (ws.rQwait("auth reason", 4)) { return false; }
+ strlen = ws.rQshift32();
+ reason = ws.rQshiftStr(strlen);
+ return fail("Auth failure: " + reason);
+ case 1: // no authentication
+ if (rfb_version >= 3.8) {
+ updateState('SecurityResult');
+ return;
+ }
+ // Fall through to ClientInitialisation
+ break;
+ case 2: // VNC authentication
+ if (rfb_password.length === 0) {
+ // Notify via both callbacks since it is kind of
+ // a RFB state change and a UI interface issue.
+ updateState('password', "Password Required");
+ conf.onPasswordRequired(that);
+ return;
+ }
+ if (ws.rQwait("auth challenge", 16)) { return false; }
+ challenge = ws.rQshiftBytes(16);
+ //Util.Debug("Password: " + rfb_password);
+ //Util.Debug("Challenge: " + challenge +
+ // " (" + challenge.length + ")");
+ response = genDES(rfb_password, challenge);
+ //Util.Debug("Response: " + response +
+ // " (" + response.length + ")");
+
+ //Util.Debug("Sending DES encrypted auth response");
+ ws.send(response);
+ updateState('SecurityResult');
+ return;
+ default:
+ fail("Unsupported auth scheme: " + rfb_auth_scheme);
+ return;
+ }
+ updateState('ClientInitialisation', "No auth required");
+ init_msg(); // Recursive fallthrough (workaround JSLint complaint)
+ break;
+
+ case 'SecurityResult' :
+ if (ws.rQwait("VNC auth response ", 4)) { return false; }
+ switch (ws.rQshift32()) {
+ case 0: // OK
+ // Fall through to ClientInitialisation
+ break;
+ case 1: // failed
+ if (rfb_version >= 3.8) {
+ length = ws.rQshift32();
+ if (ws.rQwait("SecurityResult reason", length, 8)) {
+ return false;
+ }
+ reason = ws.rQshiftStr(length);
+ fail(reason);
+ } else {
+ fail("Authentication failed");
+ }
+ return;
+ case 2: // too-many
+ return fail("Too many auth attempts");
+ }
+ updateState('ClientInitialisation', "Authentication OK");
+ init_msg(); // Recursive fallthrough (workaround JSLint complaint)
+ break;
+
+ // Triggered by fallthough, not by server message
+ case 'ClientInitialisation' :
+ ws.send([conf.shared ? 1 : 0]); // ClientInitialisation
+ updateState('ServerInitialisation', "Authentication OK");
+ break;
+
+ case 'ServerInitialisation' :
+ if (ws.rQwait("server initialization", 24)) { return false; }
+
+ /* Screen size */
+ fb_width = ws.rQshift16();
+ fb_height = ws.rQshift16();
+
+ /* PIXEL_FORMAT */
+ bpp = ws.rQshift8();
+ depth = ws.rQshift8();
+ big_endian = ws.rQshift8();
+ true_color = ws.rQshift8();
+
+ red_max = ws.rQshift16();
+ green_max = ws.rQshift16();
+ blue_max = ws.rQshift16();
+ red_shift = ws.rQshift8();
+ green_shift = ws.rQshift8();
+ blue_shift = ws.rQshift8();
+ ws.rQshiftStr(3); // padding
+
+ Util.Info("Screen: " + fb_width + "x" + fb_height +
+ ", bpp: " + bpp + ", depth: " + depth +
+ ", big_endian: " + big_endian +
+ ", true_color: " + true_color +
+ ", red_max: " + red_max +
+ ", green_max: " + green_max +
+ ", blue_max: " + blue_max +
+ ", red_shift: " + red_shift +
+ ", green_shift: " + green_shift +
+ ", blue_shift: " + blue_shift);
+
+ /* Connection name/title */
+ name_length = ws.rQshift32();
+ fb_name = ws.rQshiftStr(name_length);
+
+ display.set_true_color(conf.true_color);
+ display.resize(fb_width, fb_height);
+ keyboard.grab();
+ mouse.grab();
+
+ if (conf.true_color) {
+ fb_Bpp = 4;
+ fb_depth = 3;
+ } else {
+ fb_Bpp = 1;
+ fb_depth = 1;
+ }
+
+ response = pixelFormat();
+ response = response.concat(clientEncodings());
+ response = response.concat(fbUpdateRequests());
+ timing.fbu_rt_start = (new Date()).getTime();
+ ws.send(response);
+
+ /* Start pushing/polling */
+ setTimeout(checkEvents, conf.check_rate);
+ setTimeout(scan_tight_imgQ, scan_imgQ_rate);
+
+ if (conf.encrypt) {
+ updateState('normal', "Connected (encrypted) to: " + fb_name);
+ } else {
+ updateState('normal', "Connected (unencrypted) to: " + fb_name);
+ }
+ break;
+ }
+ //Util.Debug("<< init_msg");
+};
+
+
+/* Normal RFB/VNC server message handler */
+normal_msg = function() {
+ //Util.Debug(">> normal_msg");
+
+ var ret = true, msg_type, length, text,
+ c, first_colour, num_colours, red, green, blue;
+
+ if (FBU.rects > 0) {
+ msg_type = 0;
+ } else {
+ msg_type = ws.rQshift8();
+ }
+ switch (msg_type) {
+ case 0: // FramebufferUpdate
+ ret = framebufferUpdate(); // false means need more data
+ break;
+ case 1: // SetColourMapEntries
+ Util.Debug("SetColourMapEntries");
+ ws.rQshift8(); // Padding
+ first_colour = ws.rQshift16(); // First colour
+ num_colours = ws.rQshift16();
+ for (c=0; c < num_colours; c+=1) {
+ red = ws.rQshift16();
+ //Util.Debug("red before: " + red);
+ red = parseInt(red / 256, 10);
+ //Util.Debug("red after: " + red);
+ green = parseInt(ws.rQshift16() / 256, 10);
+ blue = parseInt(ws.rQshift16() / 256, 10);
+ display.set_colourMap([red, green, blue], first_colour + c);
+ }
+ Util.Debug("colourMap: " + display.get_colourMap());
+ Util.Info("Registered " + num_colours + " colourMap entries");
+ //Util.Debug("colourMap: " + display.get_colourMap());
+ break;
+ case 2: // Bell
+ Util.Debug("Bell");
+ conf.onBell(that);
+ break;
+ case 3: // ServerCutText
+ Util.Debug("ServerCutText");
+ if (ws.rQwait("ServerCutText header", 7, 1)) { return false; }
+ ws.rQshiftBytes(3); // Padding
+ length = ws.rQshift32();
+ if (ws.rQwait("ServerCutText", length, 8)) { return false; }
+
+ text = ws.rQshiftStr(length);
+ conf.clipboardReceive(that, text); // Obsolete
+ conf.onClipboard(that, text);
+ break;
+ default:
+ fail("Disconnected: illegal server message type " + msg_type);
+ Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
+ break;
+ }
+ //Util.Debug("<< normal_msg");
+ return ret;
+};
+
+framebufferUpdate = function() {
+ var now, hdr, fbu_rt_diff, ret = true;
+
+ if (FBU.rects === 0) {
+ //Util.Debug("New FBU: ws.rQslice(0,20): " + ws.rQslice(0,20));
+ if (ws.rQwait("FBU header", 3)) {
+ ws.rQunshift8(0); // FBU msg_type
+ return false;
+ }
+ ws.rQshift8(); // padding
+ FBU.rects = ws.rQshift16();
+ //Util.Debug("FramebufferUpdate, rects:" + FBU.rects);
+ FBU.bytes = 0;
+ timing.cur_fbu = 0;
+ if (timing.fbu_rt_start > 0) {
+ now = (new Date()).getTime();
+ Util.Info("First FBU latency: " + (now - timing.fbu_rt_start));
+ }
+ }
+
+ while (FBU.rects > 0) {
+ if (rfb_state !== "normal") {
+ return false;
+ }
+ if (ws.rQwait("FBU", FBU.bytes)) { return false; }
+ if (FBU.bytes === 0) {
+ if (ws.rQwait("rect header", 12)) { return false; }
+ /* New FramebufferUpdate */
+
+ hdr = ws.rQshiftBytes(12);
+ FBU.x = (hdr[0] << 8) + hdr[1];
+ FBU.y = (hdr[2] << 8) + hdr[3];
+ FBU.width = (hdr[4] << 8) + hdr[5];
+ FBU.height = (hdr[6] << 8) + hdr[7];
+ FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
+ (hdr[10] << 8) + hdr[11], 10);
+
+ conf.onFBUReceive(that,
+ {'x': FBU.x, 'y': FBU.y,
+ 'width': FBU.width, 'height': FBU.height,
+ 'encoding': FBU.encoding,
+ 'encodingName': encNames[FBU.encoding]});
+
+ if (encNames[FBU.encoding]) {
+ // Debug:
+ /*
+ var msg = "FramebufferUpdate rects:" + FBU.rects;
+ msg += " x: " + FBU.x + " y: " + FBU.y;
+ msg += " width: " + FBU.width + " height: " + FBU.height;
+ msg += " encoding:" + FBU.encoding;
+ msg += "(" + encNames[FBU.encoding] + ")";
+ msg += ", ws.rQlen(): " + ws.rQlen();
+ Util.Debug(msg);
+ */
+ } else {
+ fail("Disconnected: unsupported encoding " +
+ FBU.encoding);
+ return false;
+ }
+ }
+
+ timing.last_fbu = (new Date()).getTime();
+
+ ret = encHandlers[FBU.encoding]();
+
+ now = (new Date()).getTime();
+ timing.cur_fbu += (now - timing.last_fbu);
+
+ if (ret) {
+ encStats[FBU.encoding][0] += 1;
+ encStats[FBU.encoding][1] += 1;
+ }
+
+ if (FBU.rects === 0) {
+ if (((FBU.width === fb_width) &&
+ (FBU.height === fb_height)) ||
+ (timing.fbu_rt_start > 0)) {
+ timing.full_fbu_total += timing.cur_fbu;
+ timing.full_fbu_cnt += 1;
+ Util.Info("Timing of full FBU, cur: " +
+ timing.cur_fbu + ", total: " +
+ timing.full_fbu_total + ", cnt: " +
+ timing.full_fbu_cnt + ", avg: " +
+ (timing.full_fbu_total /
+ timing.full_fbu_cnt));
+ }
+ if (timing.fbu_rt_start > 0) {
+ fbu_rt_diff = now - timing.fbu_rt_start;
+ timing.fbu_rt_total += fbu_rt_diff;
+ timing.fbu_rt_cnt += 1;
+ Util.Info("full FBU round-trip, cur: " +
+ fbu_rt_diff + ", total: " +
+ timing.fbu_rt_total + ", cnt: " +
+ timing.fbu_rt_cnt + ", avg: " +
+ (timing.fbu_rt_total /
+ timing.fbu_rt_cnt));
+ timing.fbu_rt_start = 0;
+ }
+ }
+ if (! ret) {
+ return ret; // false ret means need more data
+ }
+ }
+
+ conf.onFBUComplete(that,
+ {'x': FBU.x, 'y': FBU.y,
+ 'width': FBU.width, 'height': FBU.height,
+ 'encoding': FBU.encoding,
+ 'encodingName': encNames[FBU.encoding]});
+
+ return true; // We finished this FBU
+};
+
+//
+// FramebufferUpdate encodings
+//
+
+encHandlers.RAW = function display_raw() {
+ //Util.Debug(">> display_raw (" + ws.rQlen() + " bytes)");
+
+ var cur_y, cur_height;
+
+ if (FBU.lines === 0) {
+ FBU.lines = FBU.height;
+ }
+ FBU.bytes = FBU.width * fb_Bpp; // At least a line
+ if (ws.rQwait("RAW", FBU.bytes)) { return false; }
+ cur_y = FBU.y + (FBU.height - FBU.lines);
+ cur_height = Math.min(FBU.lines,
+ Math.floor(ws.rQlen()/(FBU.width * fb_Bpp)));
+ display.blitImage(FBU.x, cur_y, FBU.width, cur_height,
+ ws.get_rQ(), ws.get_rQi());
+ ws.rQshiftBytes(FBU.width * cur_height * fb_Bpp);
+ FBU.lines -= cur_height;
+
+ if (FBU.lines > 0) {
+ FBU.bytes = FBU.width * fb_Bpp; // At least another line
+ } else {
+ FBU.rects -= 1;
+ FBU.bytes = 0;
+ }
+ //Util.Debug("<< display_raw (" + ws.rQlen() + " bytes)");
+ return true;
+};
+
+encHandlers.COPYRECT = function display_copy_rect() {
+ //Util.Debug(">> display_copy_rect");
+
+ var old_x, old_y;
+
+ if (ws.rQwait("COPYRECT", 4)) { return false; }
+ old_x = ws.rQshift16();
+ old_y = ws.rQshift16();
+ display.copyImage(old_x, old_y, FBU.x, FBU.y, FBU.width, FBU.height);
+ FBU.rects -= 1;
+ FBU.bytes = 0;
+ return true;
+};
+
+encHandlers.RRE = function display_rre() {
+ //Util.Debug(">> display_rre (" + ws.rQlen() + " bytes)");
+ var color, x, y, width, height, chunk;
+
+ if (FBU.subrects === 0) {
+ if (ws.rQwait("RRE", 4+fb_Bpp)) { return false; }
+ FBU.subrects = ws.rQshift32();
+ color = ws.rQshiftBytes(fb_Bpp); // Background
+ display.fillRect(FBU.x, FBU.y, FBU.width, FBU.height, color);
+ }
+ while ((FBU.subrects > 0) && (ws.rQlen() >= (fb_Bpp + 8))) {
+ color = ws.rQshiftBytes(fb_Bpp);
+ x = ws.rQshift16();
+ y = ws.rQshift16();
+ width = ws.rQshift16();
+ height = ws.rQshift16();
+ display.fillRect(FBU.x + x, FBU.y + y, width, height, color);
+ FBU.subrects -= 1;
+ }
+ //Util.Debug(" display_rre: rects: " + FBU.rects +
+ // ", FBU.subrects: " + FBU.subrects);
+
+ if (FBU.subrects > 0) {
+ chunk = Math.min(rre_chunk_sz, FBU.subrects);
+ FBU.bytes = (fb_Bpp + 8) * chunk;
+ } else {
+ FBU.rects -= 1;
+ FBU.bytes = 0;
+ }
+ //Util.Debug("<< display_rre, FBU.bytes: " + FBU.bytes);
+ return true;
+};
+
+encHandlers.HEXTILE = function display_hextile() {
+ //Util.Debug(">> display_hextile");
+ var subencoding, subrects, color, cur_tile,
+ tile_x, x, w, tile_y, y, h, xy, s, sx, sy, wh, sw, sh,
+ rQ = ws.get_rQ(), rQi = ws.get_rQi();
+
+ if (FBU.tiles === 0) {
+ FBU.tiles_x = Math.ceil(FBU.width/16);
+ FBU.tiles_y = Math.ceil(FBU.height/16);
+ FBU.total_tiles = FBU.tiles_x * FBU.tiles_y;
+ FBU.tiles = FBU.total_tiles;
+ }
+
+ /* FBU.bytes comes in as 1, ws.rQlen() at least 1 */
+ while (FBU.tiles > 0) {
+ FBU.bytes = 1;
+ if (ws.rQwait("HEXTILE subencoding", FBU.bytes)) { return false; }
+ subencoding = rQ[rQi]; // Peek
+ if (subencoding > 30) { // Raw
+ fail("Disconnected: illegal hextile subencoding " + subencoding);
+ //Util.Debug("ws.rQslice(0,30):" + ws.rQslice(0,30));
+ return false;
+ }
+ subrects = 0;
+ cur_tile = FBU.total_tiles - FBU.tiles;
+ tile_x = cur_tile % FBU.tiles_x;
+ tile_y = Math.floor(cur_tile / FBU.tiles_x);
+ x = FBU.x + tile_x * 16;
+ y = FBU.y + tile_y * 16;
+ w = Math.min(16, (FBU.x + FBU.width) - x);
+ h = Math.min(16, (FBU.y + FBU.height) - y);
+
+ /* Figure out how much we are expecting */
+ if (subencoding & 0x01) { // Raw
+ //Util.Debug(" Raw subencoding");
+ FBU.bytes += w * h * fb_Bpp;
+ } else {
+ if (subencoding & 0x02) { // Background
+ FBU.bytes += fb_Bpp;
+ }
+ if (subencoding & 0x04) { // Foreground
+ FBU.bytes += fb_Bpp;
+ }
+ if (subencoding & 0x08) { // AnySubrects
+ FBU.bytes += 1; // Since we aren't shifting it off
+ if (ws.rQwait("hextile subrects header", FBU.bytes)) { return false; }
+ subrects = rQ[rQi + FBU.bytes-1]; // Peek
+ if (subencoding & 0x10) { // SubrectsColoured
+ FBU.bytes += subrects * (fb_Bpp + 2);
+ } else {
+ FBU.bytes += subrects * 2;
+ }
+ }
+ }
+
+ /*
+ Util.Debug(" tile:" + cur_tile + "/" + (FBU.total_tiles - 1) +
+ " (" + tile_x + "," + tile_y + ")" +
+ " [" + x + "," + y + "]@" + w + "x" + h +
+ ", subenc:" + subencoding +
+ "(last: " + FBU.lastsubencoding + "), subrects:" +
+ subrects +
+ ", ws.rQlen():" + ws.rQlen() + ", FBU.bytes:" + FBU.bytes +
+ " last:" + ws.rQslice(FBU.bytes-10, FBU.bytes) +
+ " next:" + ws.rQslice(FBU.bytes-1, FBU.bytes+10));
+ */
+ if (ws.rQwait("hextile", FBU.bytes)) { return false; }
+
+ /* We know the encoding and have a whole tile */
+ FBU.subencoding = rQ[rQi];
+ rQi += 1;
+ if (FBU.subencoding === 0) {
+ if (FBU.lastsubencoding & 0x01) {
+ /* Weird: ignore blanks after RAW */
+ Util.Debug(" Ignoring blank after RAW");
+ } else {
+ display.fillRect(x, y, w, h, FBU.background);
+ }
+ } else if (FBU.subencoding & 0x01) { // Raw
+ display.blitImage(x, y, w, h, rQ, rQi);
+ rQi += FBU.bytes - 1;
+ } else {
+ if (FBU.subencoding & 0x02) { // Background
+ FBU.background = rQ.slice(rQi, rQi + fb_Bpp);
+ rQi += fb_Bpp;
+ }
+ if (FBU.subencoding & 0x04) { // Foreground
+ FBU.foreground = rQ.slice(rQi, rQi + fb_Bpp);
+ rQi += fb_Bpp;
+ }
+
+ display.startTile(x, y, w, h, FBU.background);
+ if (FBU.subencoding & 0x08) { // AnySubrects
+ subrects = rQ[rQi];
+ rQi += 1;
+ for (s = 0; s < subrects; s += 1) {
+ if (FBU.subencoding & 0x10) { // SubrectsColoured
+ color = rQ.slice(rQi, rQi + fb_Bpp);
+ rQi += fb_Bpp;
+ } else {
+ color = FBU.foreground;
+ }
+ xy = rQ[rQi];
+ rQi += 1;
+ sx = (xy >> 4);
+ sy = (xy & 0x0f);
+
+ wh = rQ[rQi];
+ rQi += 1;
+ sw = (wh >> 4) + 1;
+ sh = (wh & 0x0f) + 1;
+
+ display.subTile(sx, sy, sw, sh, color);
+ }
+ }
+ display.finishTile();
+ }
+ ws.set_rQi(rQi);
+ FBU.lastsubencoding = FBU.subencoding;
+ FBU.bytes = 0;
+ FBU.tiles -= 1;
+ }
+
+ if (FBU.tiles === 0) {
+ FBU.rects -= 1;
+ }
+
+ //Util.Debug("<< display_hextile");
+ return true;
+};
+
+
+encHandlers.TIGHT_PNG = function display_tight_png() {
+ //Util.Debug(">> display_tight_png");
+ var ctl, cmode, clength, getCLength, color, img;
+ //Util.Debug(" FBU.rects: " + FBU.rects);
+ //Util.Debug(" starting ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+
+ FBU.bytes = 1; // compression-control byte
+ if (ws.rQwait("TIGHT compression-control", FBU.bytes)) { return false; }
+
+ // Get 'compact length' header and data size
+ getCLength = function (arr) {
+ var header = 1, data = 0;
+ data += arr[0] & 0x7f;
+ if (arr[0] & 0x80) {
+ header += 1;
+ data += (arr[1] & 0x7f) << 7;
+ if (arr[1] & 0x80) {
+ header += 1;
+ data += arr[2] << 14;
+ }
+ }
+ return [header, data];
+ };
+
+ ctl = ws.rQpeek8();
+ switch (ctl >> 4) {
+ case 0x08: cmode = "fill"; break;
+ case 0x09: cmode = "jpeg"; break;
+ case 0x0A: cmode = "png"; break;
+ default: throw("Illegal basic compression received, ctl: " + ctl);
+ }
+ switch (cmode) {
+ // fill uses fb_depth because TPIXELs drop the padding byte
+ case "fill": FBU.bytes += fb_depth; break; // TPIXEL
+ case "jpeg": FBU.bytes += 3; break; // max clength
+ case "png": FBU.bytes += 3; break; // max clength
+ }
+
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ //Util.Debug(" ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+ //Util.Debug(" cmode: " + cmode);
+
+ // Determine FBU.bytes
+ switch (cmode) {
+ case "fill":
+ ws.rQshift8(); // shift off ctl
+ color = ws.rQshiftBytes(fb_depth);
+ FBU.imgQ.push({
+ 'type': 'fill',
+ 'img': {'complete': true},
+ 'x': FBU.x,
+ 'y': FBU.y,
+ 'width': FBU.width,
+ 'height': FBU.height,
+ 'color': color});
+ break;
+ case "jpeg":
+ case "png":
+ clength = getCLength(ws.rQslice(1, 4));
+ FBU.bytes = 1 + clength[0] + clength[1]; // ctl + clength size + jpeg-data
+ if (ws.rQwait("TIGHT " + cmode, FBU.bytes)) { return false; }
+
+ // We have everything, render it
+ //Util.Debug(" png, ws.rQlen(): " + ws.rQlen() + ", clength[0]: " + clength[0] + ", clength[1]: " + clength[1]);
+ ws.rQshiftBytes(1 + clength[0]); // shift off ctl + compact length
+ img = new Image();
+ //img.onload = scan_tight_imgQ;
+ FBU.imgQ.push({
+ 'type': 'img',
+ 'img': img,
+ 'x': FBU.x,
+ 'y': FBU.y});
+ img.src = "data:image/" + cmode +
+ extract_data_uri(ws.rQshiftBytes(clength[1]));
+ img = null;
+ break;
+ }
+ FBU.bytes = 0;
+ FBU.rects -= 1;
+ //Util.Debug(" ending ws.rQslice(0,20): " + ws.rQslice(0,20) + " (" + ws.rQlen() + ")");
+ //Util.Debug("<< display_tight_png");
+ return true;
+};
+
+extract_data_uri = function(arr) {
+ //var i, stra = [];
+ //for (i=0; i< arr.length; i += 1) {
+ // stra.push(String.fromCharCode(arr[i]));
+ //}
+ //return "," + escape(stra.join(''));
+ return ";base64," + Base64.encode(arr);
+};
+
+scan_tight_imgQ = function() {
+ var data, imgQ, ctx;
+ ctx = display.get_context();
+ if (rfb_state === 'normal') {
+ imgQ = FBU.imgQ;
+ while ((imgQ.length > 0) && (imgQ[0].img.complete)) {
+ data = imgQ.shift();
+ if (data['type'] === 'fill') {
+ display.fillRect(data.x, data.y, data.width, data.height, data.color);
+ } else {
+ ctx.drawImage(data.img, data.x, data.y);
+ }
+ }
+ setTimeout(scan_tight_imgQ, scan_imgQ_rate);
+ }
+};
+
+encHandlers.DesktopSize = function set_desktopsize() {
+ Util.Debug(">> set_desktopsize");
+ fb_width = FBU.width;
+ fb_height = FBU.height;
+ display.resize(fb_width, fb_height);
+ timing.fbu_rt_start = (new Date()).getTime();
+ // Send a new non-incremental request
+ ws.send(fbUpdateRequests());
+
+ FBU.bytes = 0;
+ FBU.rects -= 1;
+
+ Util.Debug("<< set_desktopsize");
+ return true;
+};
+
+encHandlers.Cursor = function set_cursor() {
+ var x, y, w, h, pixelslength, masklength;
+ //Util.Debug(">> set_cursor");
+ x = FBU.x; // hotspot-x
+ y = FBU.y; // hotspot-y
+ w = FBU.width;
+ h = FBU.height;
+
+ pixelslength = w * h * fb_Bpp;
+ masklength = Math.floor((w + 7) / 8) * h;
+
+ FBU.bytes = pixelslength + masklength;
+ if (ws.rQwait("cursor encoding", FBU.bytes)) { return false; }
+
+ //Util.Debug(" set_cursor, x: " + x + ", y: " + y + ", w: " + w + ", h: " + h);
+
+ display.changeCursor(ws.rQshiftBytes(pixelslength),
+ ws.rQshiftBytes(masklength),
+ x, y, w, h);
+
+ FBU.bytes = 0;
+ FBU.rects -= 1;
+
+ //Util.Debug("<< set_cursor");
+ return true;
+};
+
+encHandlers.JPEG_quality_lo = function set_jpeg_quality() {
+ Util.Error("Server sent jpeg_quality pseudo-encoding");
+};
+
+encHandlers.compress_lo = function set_compress_level() {
+ Util.Error("Server sent compress level pseudo-encoding");
+};
+
+/*
+ * Client message routines
+ */
+
+pixelFormat = function() {
+ //Util.Debug(">> pixelFormat");
+ var arr;
+ arr = [0]; // msg-type
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+
+ arr.push8(fb_Bpp * 8); // bits-per-pixel
+ arr.push8(fb_depth * 8); // depth
+ arr.push8(0); // little-endian
+ arr.push8(conf.true_color ? 1 : 0); // true-color
+
+ arr.push16(255); // red-max
+ arr.push16(255); // green-max
+ arr.push16(255); // blue-max
+ arr.push8(0); // red-shift
+ arr.push8(8); // green-shift
+ arr.push8(16); // blue-shift
+
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ //Util.Debug("<< pixelFormat");
+ return arr;
+};
+
+clientEncodings = function() {
+ //Util.Debug(">> clientEncodings");
+ var arr, i, encList = [];
+
+ for (i=0; i<encodings.length; i += 1) {
+ if ((encodings[i][0] === "Cursor") &&
+ (! conf.local_cursor)) {
+ Util.Debug("Skipping Cursor pseudo-encoding");
+ } else {
+ //Util.Debug("Adding encoding: " + encodings[i][0]);
+ encList.push(encodings[i][1]);
+ }
+ }
+
+ arr = [2]; // msg-type
+ arr.push8(0); // padding
+
+ arr.push16(encList.length); // encoding count
+ for (i=0; i < encList.length; i += 1) {
+ arr.push32(encList[i]);
+ }
+ //Util.Debug("<< clientEncodings: " + arr);
+ return arr;
+};
+
+fbUpdateRequest = function(incremental, x, y, xw, yw) {
+ //Util.Debug(">> fbUpdateRequest");
+ if (typeof(x) === "undefined") { x = 0; }
+ if (typeof(y) === "undefined") { y = 0; }
+ if (typeof(xw) === "undefined") { xw = fb_width; }
+ if (typeof(yw) === "undefined") { yw = fb_height; }
+ var arr;
+ arr = [3]; // msg-type
+ arr.push8(incremental);
+ arr.push16(x);
+ arr.push16(y);
+ arr.push16(xw);
+ arr.push16(yw);
+ //Util.Debug("<< fbUpdateRequest");
+ return arr;
+};
+
+// Based on clean/dirty areas, generate requests to send
+fbUpdateRequests = function() {
+ var cleanDirty = display.getCleanDirtyReset(),
+ arr = [], i, cb, db;
+
+ cb = cleanDirty.cleanBox;
+ if (cb.w > 0 && cb.h > 0) {
+ // Request incremental for clean box
+ arr = arr.concat(fbUpdateRequest(1, cb.x, cb.y, cb.w, cb.h));
+ }
+ for (i = 0; i < cleanDirty.dirtyBoxes.length; i++) {
+ db = cleanDirty.dirtyBoxes[i];
+ // Force all (non-incremental for dirty box
+ arr = arr.concat(fbUpdateRequest(0, db.x, db.y, db.w, db.h));
+ }
+ return arr;
+};
+
+
+
+keyEvent = function(keysym, down) {
+ //Util.Debug(">> keyEvent, keysym: " + keysym + ", down: " + down);
+ var arr;
+ arr = [4]; // msg-type
+ arr.push8(down);
+ arr.push16(0);
+ arr.push32(keysym);
+ //Util.Debug("<< keyEvent");
+ return arr;
+};
+
+pointerEvent = function(x, y) {
+ //Util.Debug(">> pointerEvent, x,y: " + x + "," + y +
+ // " , mask: " + mouse_buttonMask);
+ var arr;
+ arr = [5]; // msg-type
+ arr.push8(mouse_buttonMask);
+ arr.push16(x);
+ arr.push16(y);
+ //Util.Debug("<< pointerEvent");
+ return arr;
+};
+
+clientCutText = function(text) {
+ //Util.Debug(">> clientCutText");
+ var arr, i, n;
+ arr = [6]; // msg-type
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push8(0); // padding
+ arr.push32(text.length);
+ n = text.length;
+ for (i=0; i < n; i+=1) {
+ arr.push(text.charCodeAt(i));
+ }
+ //Util.Debug("<< clientCutText:" + arr);
+ return arr;
+};
+
+
+
+//
+// Public API interface functions
+//
+
+that.connect = function(host, port, password, path) {
+ //Util.Debug(">> connect");
+
+ rfb_host = host;
+ rfb_port = port;
+ rfb_password = (password !== undefined) ? password : "";
+ rfb_path = (path !== undefined) ? path : "";
+
+ if ((!rfb_host) || (!rfb_port)) {
+ return fail("Must set host and port");
+ }
+
+ updateState('connect');
+ //Util.Debug("<< connect");
+
+};
+
+that.disconnect = function() {
+ //Util.Debug(">> disconnect");
+ updateState('disconnect', 'Disconnecting');
+ //Util.Debug("<< disconnect");
+};
+
+that.sendPassword = function(passwd) {
+ rfb_password = passwd;
+ rfb_state = "Authentication";
+ setTimeout(init_msg, 1);
+};
+
+that.sendCtrlAltDel = function() {
+ if (rfb_state !== "normal") { return false; }
+ Util.Info("Sending Ctrl-Alt-Del");
+ var arr = [];
+ arr = arr.concat(keyEvent(0xFFE3, 1)); // Control
+ arr = arr.concat(keyEvent(0xFFE9, 1)); // Alt
+ arr = arr.concat(keyEvent(0xFFFF, 1)); // Delete
+ arr = arr.concat(keyEvent(0xFFFF, 0)); // Delete
+ arr = arr.concat(keyEvent(0xFFE9, 0)); // Alt
+ arr = arr.concat(keyEvent(0xFFE3, 0)); // Control
+ arr = arr.concat(fbUpdateRequests());
+ ws.send(arr);
+};
+
+// Send a key press. If 'down' is not specified then send a down key
+// followed by an up key.
+that.sendKey = function(code, down) {
+ if (rfb_state !== "normal") { return false; }
+ var arr = [];
+ if (typeof down !== 'undefined') {
+ Util.Info("Sending key code (" + (down ? "down" : "up") + "): " + code);
+ arr = arr.concat(keyEvent(code, down ? 1 : 0));
+ } else {
+ Util.Info("Sending key code (down + up): " + code);
+ arr = arr.concat(keyEvent(code, 1));
+ arr = arr.concat(keyEvent(code, 0));
+ }
+ arr = arr.concat(fbUpdateRequests());
+ ws.send(arr);
+};
+
+that.clipboardPasteFrom = function(text) {
+ if (rfb_state !== "normal") { return; }
+ //Util.Debug(">> clipboardPasteFrom: " + text.substr(0,40) + "...");
+ ws.send(clientCutText(text));
+ //Util.Debug("<< clipboardPasteFrom");
+};
+
+// Override internal functions for testing
+that.testMode = function(override_send) {
+ test_mode = true;
+ that.recv_message = ws.testMode(override_send);
+
+ checkEvents = function () { /* Stub Out */ };
+ that.connect = function(host, port, password) {
+ rfb_host = host;
+ rfb_port = port;
+ rfb_password = password;
+ updateState('ProtocolVersion', "Starting VNC handshake");
+ };
+};
+
+
+return constructor(); // Return the public API interface
+
+} // End of RFB()
diff --git a/webclients/novnc/include/ui.js b/webclients/novnc/include/ui.js
new file mode 100644
index 0000000..74a0005
--- /dev/null
+++ b/webclients/novnc/include/ui.js
@@ -0,0 +1,629 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 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 : true,
+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', '');
+ UI.initSetting('port', '');
+ UI.initSetting('password', '');
+ UI.initSetting('encrypt', false);
+ UI.initSetting('true_color', true);
+ UI.initSetting('cursor', false);
+ UI.initSetting('shared', true);
+ UI.initSetting('connectTimeout', 2);
+ UI.initSetting('path', '');
+
+ 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.";
+ }
+ } );
+
+},
+
+// 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 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 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() {
+ 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('connectTimeout');
+ UI.updateSetting('path');
+ UI.updateSetting('stylesheet');
+ UI.updateSetting('logging');
+
+ UI.openSettingsMenu();
+ }
+},
+
+// Open menu
+openSettingsMenu: function() {
+ 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('connectTimeout');
+ UI.saveSetting('path');
+ 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";
+ 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_connectTimeout').disabled = connected;
+ $D('noVNC_path').disabled = connected;
+
+ if (connected) {
+ UI.setViewClip();
+ UI.setMouseButton(1);
+ $D('showKeyboard').style.display = "inline";
+ $D('sendCtrlAltDelButton').style.display = "inline";
+ } else {
+ UI.setMouseButton();
+ $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_connectTimeout(UI.getSetting('connectTimeout'));
+
+ 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("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';
+}
+
+};
+
+
+
+
diff --git a/webclients/novnc/include/util.js b/webclients/novnc/include/util.js
new file mode 100644
index 0000000..0a9e0e0
--- /dev/null
+++ b/webclients/novnc/include/util.js
@@ -0,0 +1,276 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 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 );
+};
+
+/*
+ * ------------------------------------------------------
+ * 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 === '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 = {
+ 'presto': (function() {
+ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
+ '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};
+}());
diff --git a/webclients/novnc/include/vnc.js b/webclients/novnc/include/vnc.js
new file mode 100644
index 0000000..f938be7
--- /dev/null
+++ b/webclients/novnc/include/vnc.js
@@ -0,0 +1,42 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 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;
+
+ document.write(extra);
+}());
+
diff --git a/webclients/novnc/include/web-socket-js/README.txt b/webclients/novnc/include/web-socket-js/README.txt
new file mode 100644
index 0000000..2e32ea7
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/README.txt
@@ -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.
diff --git a/webclients/novnc/include/web-socket-js/WebSocketMain.swf b/webclients/novnc/include/web-socket-js/WebSocketMain.swf
new file mode 100644
index 0000000..244c445
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/WebSocketMain.swf
Binary files differ
diff --git a/webclients/novnc/include/web-socket-js/swfobject.js b/webclients/novnc/include/web-socket-js/swfobject.js
new file mode 100644
index 0000000..8eafe9d
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/swfobject.js
@@ -0,0 +1,4 @@
+/* SWFObject v2.2 <http://code.google.com/p/swfobject/>
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
+*/
+var swfobject=function(){var D="undefined",r="object",S="Shockwave Flash",W="ShockwaveFlash.ShockwaveFlash",q="application/x-shockwave-flash",R="SWFObjectExprInst",x="onreadystatechange",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,X=!+"\v1",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ag[0]=parseInt(ab.replace(/^(.*)\..*$/,"$1"),10);ag[1]=parseInt(ab.replace(/^.*\.(.*)\s.*$/,"$1"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable("$version");if(ab){X=true;ab=ab.split(" ")[1].split(",");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState=="complete")||(typeof j.readyState==D&&(j.getElementsByTagName("body")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener("DOMContentLoaded",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState=="complete"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName("body")[0].appendChild(C("span"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener("load",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener("load",Y,false)}else{if(typeof O.attachEvent!=D){i(O,"onload",Y)}else{if(typeof O.onload=="function"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName("body")[0];var aa=C(r);aa.setAttribute("type",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable("$version");if(ab){ab=ab.split(" ")[1].split(",");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute("width")||"0";ai.height=ae.getAttribute("height")||"0";if(ae.getAttribute("class")){ai.styleclass=ae.getAttribute("class")}if(ae.getAttribute("align")){ai.align=ae.getAttribute("align")}var ah={};var X=ae.getElementsByTagName("param");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute("name").toLowerCase()!="movie"){ah[X[ad].getAttribute("name")]=X[ad].getAttribute("value")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName=="OBJECT"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F("6.0.65")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName=="OBJECT"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width="310"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height="137"}j.title=j.title.slice(0,47)+" - Flash Player Installation";var ad=M.ie&&M.win?"ActiveX":"PlugIn",ac="MMredirectURL="+O.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ad+"&MMdoctitle="+j.title;if(typeof ab.flashvars!=D){ab.flashvars+="&"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C("div");X+="SWFObjectNew";Y.setAttribute("id",X);ae.parentNode.insertBefore(Y,ae);ae.style.display="none";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C("div");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display="none";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C("div");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName=="PARAM")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah="";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()=="data"){ag.movie=ai[ae]}else{if(ae.toLowerCase()=="styleclass"){ah+=' class="'+ai[ae]+'"'}else{if(ae.toLowerCase()!="classid"){ah+=" "+ae+'="'+ai[ae]+'"'}}}}}var af="";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name="'+ad+'" value="'+ag[ad]+'" />'}}aa.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ah+">"+af+"</object>";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute("type",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()=="styleclass"){Z.setAttribute("class",ai[ac])}else{if(ac.toLowerCase()!="classid"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!="movie"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C("param");aa.setAttribute("name",X);aa.setAttribute("value",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName=="OBJECT"){if(M.ie&&M.win){X.style.display="none";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]=="function"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(".");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName("head")[0];if(!aa){return}var X=(ad&&typeof ad=="string")?ad:"screen";if(ab){n=null;G=null}if(!n||G!=X){var Z=C("style");Z.setAttribute("type","text/css");Z.setAttribute("media",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+" {"+Y+"}"))}}}function w(Z,X){if(!m){return}var Y=X?"visible":"hidden";if(J&&c(Z)){c(Z).style.visibility=Y}else{v("#"+Z,"visibility:"+Y)}}function L(Y){var Z=/[\\\"<>\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent("onunload",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+="";ag+="";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+="&"+ai+"="+Z[ai]}else{am.flashvars=ai+"="+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\?/.test(Z)){Z=Z.split("?")[1]}if(aa==null){return L(Z)}var Y=Z.split("&");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf("="))==aa){return L(Y[X].substring((Y[X].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display="block"}}if(E){E(B)}}a=false}}}}(); \ No newline at end of file
diff --git a/webclients/novnc/include/web-socket-js/web_socket.js b/webclients/novnc/include/web-socket-js/web_socket.js
new file mode 100644
index 0000000..ec2a8b7
--- /dev/null
+++ b/webclients/novnc/include/web-socket-js/web_socket.js
@@ -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) 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();
+ });
+ }
+ }
+
+})();
diff --git a/webclients/novnc/include/websock.js b/webclients/novnc/include/websock.js
new file mode 100644
index 0000000..a688f76
--- /dev/null
+++ b/webclients/novnc/include/websock.js
@@ -0,0 +1,347 @@
+/*
+ * Websock: high-performance binary WebSockets
+ * Copyright (C) 2011 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.
+ */
+
+
+// Load Flash WebSocket emulator if needed
+
+if (window.WebSocket) {
+ Websock_native = true;
+} else if (window.MozWebSocket) {
+ 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 = "";
+
+ WEB_SOCKET_SWF_LOCATION = get_INCLUDE_URI() +
+ "web-socket-js/WebSocketMain.swf";
+ if (Util.Engine.trident) {
+ Util.Debug("Forcing uncached load of WebSocketMain.swf");
+ 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) {
+ var arr = rQ.slice(rQi, rQi + len);
+ rQi += len;
+ return arr.map(function (num) {
+ return String.fromCharCode(num); } ).join('');
+
+}
+function rQshiftBytes(len) {
+ 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);
+ }
+ 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();
+
+}
diff --git a/webclients/novnc/include/webutil.js b/webclients/novnc/include/webutil.js
new file mode 100644
index 0000000..95138f8
--- /dev/null
+++ b/webclients/novnc/include/webutil.js
@@ -0,0 +1,148 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2011 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, document */
+
+// Globals defined here
+var WebUtil = {}, $D;
+
+/*
+ * Simple DOM selector by ID
+ */
+if (!window.$D) {
+ $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;
+};
diff --git a/webclients/novnc/vnc.html b/webclients/novnc/vnc.html
new file mode 100644
index 0000000..281b4d3
--- /dev/null
+++ b/webclients/novnc/vnc.html
@@ -0,0 +1,180 @@
+<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">
+<html>
+<head>
+
+ <!--
+ noVNC example: simple example using default UI
+ Copyright (C) 2011 Joel Martin
+ Licensed under LGPL-3 (see LICENSE.txt)
+ -->
+ <title>noVNC</title>
+
+ <meta charset="utf-8">
+
+ <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
+ Remove this if you use the .htaccess -->
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+
+ <!-- Apple iOS Safari settings -->
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <meta names="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+ <!-- App Start Icon -->
+ <link rel="apple-touch-startup-image" href="images/screen_320x460.png" />
+ <!-- For iOS devices set the icon to use if user bookmarks app on their homescreen -->
+ <link rel="apple-touch-icon" href="images/screen_57x57.png">
+ <!--
+ <link rel="apple-touch-icon-precomposed" href="images/screen_57x57.png" />
+ -->
+
+
+ <!-- Stylesheets -->
+ <link rel="stylesheet" href="include/base.css" />
+ <link rel="alternate stylesheet" href="include/black.css" TITLE="Black" />
+ <link rel="alternate stylesheet" href="include/blue.css" TITLE="Blue" />
+
+ <!--
+ <script type='text/javascript'
+ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+ -->
+
+ <script src="include/vnc.js"></script>
+ <script src="include/ui.js"></script>
+
+</head>
+
+<body>
+ <div id="noVNC-control-bar">
+ <!--noVNC Mobile Device only Buttons-->
+ <div class="noVNC-buttons-left">
+ <input type="image" src="images/drag.png"
+ id="noVNC_view_drag_button" class="noVNC_status_button"
+ title="Move/Drag Viewport"
+ onclick="UI.setViewDrag();">
+ <div id="noVNC_mobile_buttons">
+ <input type="image" src="images/mouse_none.png"
+ id="noVNC_mouse_button0" class="noVNC_status_button"
+ onclick="UI.setMouseButton(1);">
+ <input type="image" src="images/mouse_left.png"
+ id="noVNC_mouse_button1" class="noVNC_status_button"
+ onclick="UI.setMouseButton(2);">
+ <input type="image" src="images/mouse_middle.png"
+ id="noVNC_mouse_button2" class="noVNC_status_button"
+ onclick="UI.setMouseButton(4);">
+ <input type="image" src="images/mouse_right.png"
+ id="noVNC_mouse_button4" class="noVNC_status_button"
+ onclick="UI.setMouseButton(0);">
+ <input type="image" src="images/keyboard.png"
+ id="showKeyboard" class="noVNC_status_button"
+ value="Keyboard" title="Show Keyboard"
+ onclick="UI.showKeyboard()"/>
+ <input type="email"
+ autocapitalize="off" autocorrect="off"
+ id="keyboardinput" class="noVNC_status_button"
+ onKeyDown="onKeyDown(event);" onblur="UI.keyInputBlur();"/>
+ </div>
+ </div>
+
+ <!--noVNC Buttons-->
+ <div class="noVNC-buttons-right">
+ <input type="image" src="images/ctrlaltdel.png"
+ id="sendCtrlAltDelButton" class="noVNC_status_button"
+ title="Send Ctrl-Alt-Del"
+ onclick="UI.sendCtrlAltDel();" />
+ <input type="image" src="images/clipboard.png"
+ id="clipboardButton" class="noVNC_status_button"
+ title="Clipboard"
+ onclick="UI.toggleClipboardPanel();" />
+ <input type="image" src="images/settings.png"
+ id="settingsButton" class="noVNC_status_button"
+ title="Settings"
+ onclick="UI.toggleSettingsPanel();" />
+ <input type="image" src="images/connect.png"
+ id="connectButton" class="noVNC_status_button_selected"
+ title="Connect"
+ onclick="UI.toggleConnectPanel()" />
+ <input type="image" src="images/disconnect.png"
+ id="disconnectButton" class="noVNC_status_button"
+ title="Disconnect"
+ onclick="UI.disconnect()" />
+ </div>
+
+ <!-- Clipboard Panel -->
+ <div id="noVNC_clipboard" class="triangle-right top">
+ <textarea id="noVNC_clipboard_text" rows=5
+ onfocus="UI.displayBlur();" onblur="UI.displayFocus();"
+ onchange="UI.clipSend();">
+ </textarea>
+ <br />
+ <input id="noVNC_clipboard_clear_button" type="button"
+ value="Clear" onclick="UI.clipClear();">
+ </div>
+
+ <!-- Settings Panel -->
+ <div id="noVNC_settings" class="triangle-right top">
+ <span id="noVNC_settings_menu" onmouseover="UI.displayBlur();"
+ onmouseout="UI.displayFocus();">
+ <ul>
+ <li><input id="noVNC_encrypt" type="checkbox"> Encrypt</li>
+ <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li>
+ <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li>
+ <li><input id="noVNC_clip" type="checkbox"> Clip to window</li>
+ <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li>
+ <li><input id="noVNC_connectTimeout" type="input"> Connect Timeout (s)</li>
+ <li><input id="noVNC_path" type="input"> Path</li>
+ <hr>
+ <!-- Stylesheet selection dropdown -->
+ <li><label><strong>Style: </strong>
+ <select id="noVNC_stylesheet" name="vncStyle">
+ <option value="default">default</option>
+ </select></label>
+ </li>
+
+ <!-- Logging selection dropdown -->
+ <li><label><strong>Logging: </strong>
+ <select id="noVNC_logging" name="vncLogging">
+ </select></label>
+ </li>
+ <hr>
+ <li><input type="button" id="noVNC_apply" value="Apply"
+ onclick="UI.settingsApply()"></li>
+ </ul>
+ </span>
+ </div>
+
+ <!-- Connection Panel -->
+ <div id="noVNC_controls" class="triangle-right top">
+ <ul>
+ <li><label><strong>Host: </strong><input id="noVNC_host" /></label></li>
+ <li><label><strong>Port: </strong><input id="noVNC_port" /></label></li>
+ <li><label><strong>Password: </strong><input id="noVNC_password" type="password" /></label></li>
+ <li><input id="noVNC_connect_button" type="button" value="Connect" onclick="UI.connect();"></li>
+ </ul>
+ </div>
+
+ </div> <!-- End of noVNC-control-bar -->
+
+
+ <div id="noVNC_screen">
+ <div id="noVNC_screen_pad"></div>
+
+ <div id="noVNC_status_bar" class="noVNC_status_bar">
+ <div id="noVNC_status">Loading</div>
+ </div>
+
+ <h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
+
+ <!-- HTML5 Canvas -->
+ <div id="noVNC_container">
+ <canvas id="noVNC_canvas" width="640px" height="20px">
+ Canvas not supported.
+ </canvas>
+ </div>
+
+ </div>
+
+ <script>
+ window.onload = UI.load;
+ </script>
+ </body>
+</html>
diff --git a/webclients/novnc/vnc_auto.html b/webclients/novnc/vnc_auto.html
new file mode 100644
index 0000000..a500b79
--- /dev/null
+++ b/webclients/novnc/vnc_auto.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+ <!--
+ noVNC Example: Automatically connect on page load.
+ Copyright (C) 2011 Joel Martin
+ Licensed under LGPL-3 (see LICENSE.txt)
+
+ Connect parameters are provided in query string:
+ http://example.com/?host=HOST&port=PORT&encrypt=1&true_color=1
+ -->
+ <head>
+ <title>noVNC</title>
+ <meta http-equiv="X-UA-Compatible" content="chrome=1">
+ <link rel="stylesheet" href="include/base.css" title="plain">
+ <!--
+ <script type='text/javascript'
+ src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
+ -->
+ <script src="include/vnc.js"></script>
+ </head>
+
+ <body style="margin: 0px;">
+ <div id="noVNC_screen">
+ <div id="noVNC_status_bar" class="noVNC_status_bar" style="margin-top: 0px;">
+ <table border=0 width="100%"><tr>
+ <td><div id="noVNC_status">Loading</div></td>
+ <td width="1%"><div id="noVNC_buttons">
+ <input type=button value="Send CtrlAltDel"
+ id="sendCtrlAltDelButton">
+ </div></td>
+ </tr></table>
+ </div>
+ <canvas id="noVNC_canvas" width="640px" height="20px">
+ Canvas not supported.
+ </canvas>
+ </div>
+
+ <script>
+ /*jslint white: false */
+ /*global window, $, Util, RFB, */
+ "use strict";
+
+ var rfb;
+
+ function passwordRequired(rfb) {
+ var msg;
+ msg = '<form onsubmit="return setPassword();"';
+ msg += ' style="margin-bottom: 0px">';
+ msg += 'Password Required: ';
+ msg += '<input type=password size=10 id="password_input" class="noVNC_status">';
+ msg += '<\/form>';
+ $D('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
+ $D('noVNC_status').innerHTML = msg;
+ }
+ function setPassword() {
+ rfb.sendPassword($D('password_input').value);
+ return false;
+ }
+ function sendCtrlAltDel() {
+ rfb.sendCtrlAltDel();
+ return false;
+ }
+ function updateState(rfb, state, oldstate, msg) {
+ var s, sb, cad, level;
+ s = $D('noVNC_status');
+ sb = $D('noVNC_status_bar');
+ cad = $D('sendCtrlAltDelButton');
+ switch (state) {
+ case 'failed': level = "error"; break;
+ case 'fatal': level = "error"; break;
+ case 'normal': level = "normal"; break;
+ case 'disconnected': level = "normal"; break;
+ case 'loaded': level = "normal"; break;
+ default: level = "warn"; break;
+ }
+
+ if (state === "normal") { cad.disabled = false; }
+ else { cad.disabled = true; }
+
+ if (typeof(msg) !== 'undefined') {
+ sb.setAttribute("class", "noVNC_status_" + level);
+ s.innerHTML = msg;
+ }
+ }
+
+ window.onload = function () {
+ var host, port, password, path;
+
+ $D('sendCtrlAltDelButton').style.display = "inline";
+ $D('sendCtrlAltDelButton').onclick = sendCtrlAltDel;
+
+ document.title = unescape(WebUtil.getQueryVar('title', 'noVNC'));
+ host = WebUtil.getQueryVar('host', null);
+ port = WebUtil.getQueryVar('port', null);
+ password = WebUtil.getQueryVar('password', '');
+ path = WebUtil.getQueryVar('path', '');
+ if ((!host) || (!port)) {
+ updateState('failed',
+ "Must specify host and port in URL");
+ return;
+ }
+
+ rfb = new RFB({'target': $D('noVNC_canvas'),
+ 'encrypt': WebUtil.getQueryVar('encrypt', false),
+ 'true_color': WebUtil.getQueryVar('true_color', true),
+ 'local_cursor': WebUtil.getQueryVar('cursor', true),
+ 'shared': WebUtil.getQueryVar('shared', true),
+ 'updateState': updateState,
+ 'onPasswordRequired': passwordRequired});
+ rfb.connect(host, port, password, path);
+ };
+ </script>
+
+ </body>
+</html>
+
diff --git a/webclients/ssl/Makefile.am b/webclients/ssl/Makefile.am
new file mode 100644
index 0000000..fd1c201
--- /dev/null
+++ b/webclients/ssl/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST=VncViewer.jar index.vnc SignedVncViewer.jar proxy.vnc README ss_vncviewer onetimekey UltraViewerSSL.jar SignedUltraViewerSSL.jar ultra.vnc ultrasigned.vnc ultraproxy.vnc
+
diff --git a/webclients/ssl/README b/webclients/ssl/README
new file mode 100644
index 0000000..b244cf1
--- /dev/null
+++ b/webclients/ssl/README
@@ -0,0 +1,338 @@
+This directory contains a patched Java applet VNC viewer that is SSL
+enabled.
+
+The patches in the *.patch files are relative to the source tarball:
+
+ tightvnc-1.3dev7_javasrc.tar.gz
+
+currently (4/06) available here:
+
+ http://prdownloads.sourceforge.net/vnc-tight/tightvnc-1.3dev7_javasrc.tar.gz?download
+
+It also includes some simple patches to:
+
+ - fix richcursor colors
+
+ - make the Java Applet cursor (not the cursor drawn to the canvas
+ framebuffer) invisible when it is inside the canvas.
+
+ - allow Tab (and some other) keystrokes to be sent to the vnc
+ server instead of doing widget traversal.
+
+
+This SSL applet should work with any VNC viewer that has an SSL tunnel in
+front of it. It has been tested on x11vnc and using the stunnel tunnel
+to other VNC servers.
+
+By default this Vnc Viewer will only do SSL. To do unencrypted traffic
+see the "DisableSSL" applet parameter (e.g. set it to Yes in index.vnc).
+
+Proxies: they are a general problem with java socket applets (a socket
+connection does not go through the proxy). See the info in the proxy.vnc
+file for a workaround. It uses SignedVncViewer.jar which is simply
+a signed version of VncViewer.jar. The basic idea is the user clicks
+"Yes" to trust the applet and then it can connect directly to the proxy
+and issue a CONNECT request.
+
+This applet has been tested on versions 1.4.2 and 1.5.0 of the Sun
+Java plugin. It may not work on older releases or different vendor VM's.
+Send full Java Console output for failures.
+
+---------------------------------------------------------------
+Tips:
+
+When doing single-port proxy connections (e.g. both VNC and HTTPS
+thru port 5900) it helps to move through the 'do you trust this site'
+dialogs quickly. x11vnc has to wait to see if the traffic is VNC or
+HTTP and this can cause timeouts if you don't move thru them quickly.
+
+You may have to restart your browser completely if it gets into a
+weird state. For one case we saw the JVM requesting VncViewer.class
+even when no such file exists.
+
+
+---------------------------------------------------------------
+Extras:
+
+ss_vncviewer (not Java):
+
+ Wrapper script for native VNC viewer to connect to x11vnc in
+ SSL mode. Script launches stunnel(8) and then connects to it
+ via localhost which in turn is then redirected to x11vnc via an
+ SSL tunnel. stunnel(8) must be installed and available in PATH.
+
+
+Running Java SSL VncViewer from the command line:
+
+ From this directory:
+
+ java -cp ./VncViewer.jar VncViewer HOST <thehost> PORT <theport>
+
+ substitute <thehost> and <theport> with the actual values.
+ You can add any other parameters, e.g.: ignoreProxy yes
+
+---------------------------------------------------------------
+UltraVNC:
+
+The UltraVNC java viewer has also been patched to support SSL. Various
+bugs in the UltraVNC java viewer were also fixed. This viewer can be
+useful because is support UltraVNC filetransfer, and so it works on
+Unix, etc.
+
+UltraViewerSSL.jar
+SignedUltraViewerSSL.jar
+ultra.vnc
+ultraproxy.vnc
+ultravnc-102-JavaViewer-ssl-etc.patch
+
+---------------------------------------------------------------
+Applet Parameters:
+
+Some additional applet parameters can be set via the URL, e.g.
+
+ http://host:5800/?param=value
+ http://host:5800/ultra.vnc?param=value
+ https://host:5900/ultra.vnc?param=value
+
+etc. If running java from command line as show above, it comes
+in as java ... VncViewer param value ...
+
+There is a limitation with libvncserver that param and value can
+only be alphanumeric, underscore, "+" (for space), or "."
+
+We have added some applet parameters to the stock VNC java
+viewers. Here are the applet parameters:
+
+Both TightVNC and UltraVNC Java viewers:
+
+ HOST
+ string, default: none.
+ The Hostname to connect to.
+
+ PORT
+ number, default: 0
+ The VNC server port to connect to.
+
+ Open New Window
+ yes/no, default: no
+ Run applet in separate frame.
+
+ Show Controls
+ yes/no, default: yes
+ Show Controls button panel.
+
+ Show Offline Desktop
+ yes/no, default: no
+ Do we continue showing desktop on remote disconnect?
+
+ Defer screen updates
+ number, default: 20
+ Milliseconds delay
+
+ Defer cursor updates
+ number, default: 10
+ Milliseconds delay
+
+ Defer update requests
+ number, default: 50
+ Milliseconds delay
+
+ PASSWORD
+ string, default: none
+ VNC session password in plain text.
+
+ ENCPASSWORD
+ string, default: none
+ VNC session password in encrypted in DES with KNOWN FIXED
+ key. It is a hex string. This is like the ~/.vnc/passwd format.
+
+
+ The following are added by x11vnc and/or ssvnc project
+
+ VNCSERVERPORT
+ number, default: 0
+ Like PORT, but if there is a firewall this is the Actual VNC
+ server port. PORT might be a redir port on the firewall.
+
+ DisableSSL
+ yes/no, default: no
+ Do unencrypted connection, no SSL.
+
+ httpsPort
+ number, default: none
+ When checking for proxy, use this at the url port number.
+
+ CONNECT
+ string, default: none
+ Sets to host:port for the CONNECT line to a Web proxy.
+ The Web proxy should connect us to it.
+
+ GET
+ yes/no, default: no
+ Set to do a special HTTP GET (/request.https.vnc.connection)
+ to the vnc server that will cause it to switch to VNC instead.
+ This is to speedup/make more robust, the single port HTTPS and VNC
+ mode of x11vnc (e.g. both services thru port 5900, etc)
+
+ urlPrefix
+ string, default: none
+ set to a string that will be prefixed to all URL's when contacting
+ the VNC server. Idea is a special proxy will use this to indicate
+ internal hostname, etc.
+
+ oneTimeKey
+ string, default: none
+ set a special hex "key" to correspond to an SSL X.509 cert+key.
+ See the 'onetimekey' helper script. Can also be PROMPT to prompt
+ the user to paste the hex key string in.
+
+ This provides a Client-Side cert+key that the client will use to
+ authenticate itself by SSL To the VNC Server.
+
+ This is to try to work around the problem that the Java applet
+ cannot keep an SSL keystore on disk, etc. E.g. if they log
+ into an HTTPS website via password they are authenticated and
+ encrypted, then the website can safely put oneTimeKey=... on the
+ URL. The Vncviewer authenticates the VNC server with this key.
+
+ Note that there is currently a problem in that if x11vnc requires
+ Client Certificates the user cannot download the index.vnc HTML
+ and VncViewer.jar from the same x11vnc. Those need to come from
+ a different x11vnc or from a web server.
+
+ Note that the HTTPS website can also put the VNC Password
+ (e.g. a temporary/one-time one) in the parameter PASSWORD.
+ The Java Applet will automatically supply this VNC password
+ instead of prompting.
+
+ serverCert
+ string, default: none
+ set a special hex "cert" to correspond to an SSL X.509 cert
+ See the 'onetimekey -certonly' helper script.
+
+ This provides a Server-Side cert that the client will authenticate
+ the VNC Server against by SSL.
+
+ This is to try to work around the problem that the Java applet
+ cannot keep an SSL keystore on disk, etc. E.g. if they log
+ into an HTTPS website via password they are authenticated and
+ encrypted, then the website can safely put serverCert=... on the
+ URL.
+
+ Of course the VNC Server is sending this string to the Java
+ Applet, so this is only reasonable security if the VNC Viewer
+ already trusts the HTTPS retrieval of the URL + serverCert param
+ that it gets. This should be done over HTTPS not HTTP.
+
+ proxyHost
+ string, default: none
+ Do not try to guess the proxy's hostname, use the value in
+ proxyHost. Does not imply forceProxy (below.)
+
+ proxyPort
+ string, default: none
+ Do not try to guess the proxy's port number, use the value in
+ proxyPort. Does not imply forceProxy (below.)
+
+ forceProxy
+ yes/no, default: no
+ Assume there is a proxy and force its use.
+
+ If a string other than "yes" or "no" is given, it implies "yes"
+ and uses the string for proxyHost and proxyPort (see above).
+ In this case the string must be of the form "hostname+port".
+ Note that it is "+" and not ":" before the port number.
+
+ ignoreProxy
+ yes/no, default: no
+ Don't check for a proxy, assume there is none.
+
+ trustAllVncCerts
+ yes/no, default: no
+ Automatically trust any cert received from the VNC server
+ (obviously this could be dangerous and lead to man in the
+ middle attack). Do not ask the user to verify any of these
+ certs from the VNC server.
+
+ trustUrlVncCert
+ yes/no, default: no
+ Automatically trust any cert that the web browsers has accepted.
+ E.g. the user said "Yes" or "Continue" to a web browser dialog
+ regarding a certificate. If we get the same cert (chain) from
+ the VNC server we trust it without prompting the user.
+
+ debugCerts
+ yes/no, default: no
+ Print out every cert in the Server, TrustUrl, TrustAll chains.
+
+
+TightVNC Java viewer only:
+
+ Offer Relogin
+ yes/no, default: yes
+ "Offer Relogin" set to "No" disables "Login again"
+
+ SocketFactory
+ string, default: none
+ set Java Socket class factory.
+
+UltraVNC Java viewer only:
+
+ None.
+
+ The following are added by x11vnc and/or ssvnc project
+
+ ftpDropDown
+ string, default: none
+ Sets the file transfer "drives" dropdown to the "." separated
+ list. Use "+" for space. The default is
+
+ My+Documents.Desktop.Home
+
+ for 3 entries in the dropdown in addition to the "drives"
+ (e.g. C:\) These items should be expanded properly by the VNC
+ Server. x11vnc will prepend $HOME to them, which is normally
+ what one wants. To include a "/" use "_2F_". Another example:
+
+ Home.Desktop.bin_2F_linux
+
+ If an item is prefixed with "TOP_" then the item is inserted at
+ the top of the drop down rather than being appended to the end.
+ E.g. to try to initially load the user homedir instead of /:
+
+ TOP_Home.My+Documents.Desktop
+
+ If ftpDropDown is set to the empty string, "", then no special
+ locations, [Desktop] etc., are placed in the drop down. Only the
+ ultravnc "drives" will appear.
+
+ ftpOnly
+ yes/no, default: no
+ The VNC viewer only shows the filetransfer panel, no desktop
+ is displayed.
+
+ graftFtp
+ yes/no, default: no
+ As ftpOnly, the VNC viewer only shows the filetransfer panel,
+ no desktop is displayed, however it is "grafted" onto an existing
+ SSVNC unix vncviewer. The special SSVNC vncviewer merges the two
+ channels.
+
+ dsmActive
+ yes/no, default: no
+ Special usage mode with the SSVNC unix vncviewer. The UltraVNC
+ DSM encryption is active. Foolishly, UltraVNC DSM encryption
+ *MODIFIES* the VNC protocol when active (it is not a pure tunnel).
+ This option indicates to modify the VNC protocol to make this work.
+ Usually only used with graftFtp and SSVNC unix vncviewer.
+
+ delayAuthPanel
+ yes/no, default: no
+ This is another special usage mode with the SSVNC unix vncviewer.
+ A login panel is delayed (not shown at startup.) Could be useful
+ for non SSVNC usage too.
+
+ ignoreMSLogonCheck
+ yes/no, default: no
+ Similar to delayAuthPanel, do not put up a popup asking for
+ Windows username, etc.
diff --git a/webclients/ssl/SignedUltraViewerSSL.jar b/webclients/ssl/SignedUltraViewerSSL.jar
new file mode 100644
index 0000000..6c18737
--- /dev/null
+++ b/webclients/ssl/SignedUltraViewerSSL.jar
Binary files differ
diff --git a/webclients/ssl/SignedVncViewer.jar b/webclients/ssl/SignedVncViewer.jar
new file mode 100644
index 0000000..95c0b0b
--- /dev/null
+++ b/webclients/ssl/SignedVncViewer.jar
Binary files differ
diff --git a/webclients/ssl/UltraViewerSSL.jar b/webclients/ssl/UltraViewerSSL.jar
new file mode 100644
index 0000000..45259fd
--- /dev/null
+++ b/webclients/ssl/UltraViewerSSL.jar
Binary files differ
diff --git a/webclients/ssl/VncViewer.jar b/webclients/ssl/VncViewer.jar
new file mode 100644
index 0000000..9453c6f
--- /dev/null
+++ b/webclients/ssl/VncViewer.jar
Binary files differ
diff --git a/webclients/ssl/index.vnc b/webclients/ssl/index.vnc
new file mode 100644
index 0000000..ec520dc
--- /dev/null
+++ b/webclients/ssl/index.vnc
@@ -0,0 +1,26 @@
+<!--
+ index.vnc - default HTML page for TightVNC Java viewer applet, to be
+ used with Xvnc. On any file ending in .vnc, the HTTP server embedded in
+ Xvnc will substitute the following variables when preceded by a dollar:
+ USER, DESKTOP, DISPLAY, APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT,
+ PARAMS. Use two dollar signs ($$) to get a dollar sign in the generated
+ HTML page.
+
+ NOTE: the $PARAMS variable is not supported by the standard VNC, so
+ make sure you have TightVNC on the server side, if you're using this
+ variable.
+-->
+
+<HTML>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+<APPLET CODE=VncViewer.class ARCHIVE=VncViewer.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=PORT value=$PORT>
+<param name="Open New Window" value=yes>
+$PARAMS
+</APPLET>
+<BR>
+<A href="http://www.karlrunge.com/x11vnc">x11vnc site</A>
+</HTML>
diff --git a/webclients/ssl/onetimekey b/webclients/ssl/onetimekey
new file mode 100755
index 0000000..bf57c8f
--- /dev/null
+++ b/webclients/ssl/onetimekey
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# usage: onetimekey path/to/mycert.pem
+# onetimekey -certonly path/to/mycert.pem
+#
+# Takes an openssl cert+key pem file and turns into a long string
+# for the x11vnc SSL VNC Java Viewer.
+#
+# The Java applet URL parameter can be oneTimeKey=<str> where str is
+# the output of this program, or can be oneTimeKey=PROMPT in which
+# case the applet will ask you to paste in the string.
+#
+# The problem trying to be solved here is it is difficult to get
+# the Java applet to have or use a keystore with the key saved
+# in it. Also, as the name implies, an HTTPS server can create
+# a one time key to send to the applet (the user has already
+# logged in via password to the HTTPS server).
+#
+# Note oneTimeKey is to provide a CLIENT Certificate for the viewer
+# to authenticate itself to the VNC Server.
+#
+# There is also the serverCert=<str> Applet parameter. This is
+# a cert to authenticate the VNC server against. To create that
+# string with this tool specify -certonly as the first argument.
+
+certonly=""
+if [ "X$1" = "X-certonly" ]; then
+ shift
+ certonly=1
+fi
+
+in=$1
+der=/tmp/1time$$.der
+touch $der
+chmod 600 $der
+
+openssl pkcs8 -topk8 -nocrypt -in "$in" -out "$der" -outform der
+
+pbinhex=/tmp/pbinhex.$$
+cat > $pbinhex <<END
+#!/usr/bin/perl
+
+\$str = '';
+while (1) {
+ \$c = getc(STDIN);
+ last if \$c eq '';
+ \$str .= sprintf("%02x", unpack("C", \$c));
+}
+
+print "\$str\n";
+END
+
+chmod 700 $pbinhex
+
+str1=`$pbinhex < "$der"`
+rm -f "$der"
+
+n=`grep -n 'BEGIN CERTIFICATE' $in | awk -F: '{print $1}' | head -1`
+str2=`tail +$n $in | $pbinhex`
+if [ "X$certonly" = "X1" ]; then
+ echo "$str2"
+else
+ echo "$str1,$str2"
+fi
+rm -f $pbinhex
diff --git a/webclients/ssl/proxy.vnc b/webclients/ssl/proxy.vnc
new file mode 100644
index 0000000..6d3ab3d
--- /dev/null
+++ b/webclients/ssl/proxy.vnc
@@ -0,0 +1,73 @@
+<!--
+ index.vnc - default HTML page for TightVNC Java viewer applet, to be
+ used with Xvnc. On any file ending in .vnc, the HTTP server embedded in
+ Xvnc will substitute the following variables when preceded by a dollar:
+ USER, DESKTOP, DISPLAY, APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT,
+ PARAMS. Use two dollar signs ($$) to get a dollar sign in the generated
+ HTML page.
+
+ NOTE: the $PARAMS variable is not supported by the standard VNC, so
+ make sure you have TightVNC on the server side, if you're using this
+ variable.
+-->
+
+<!--
+The idea behind using the signed applet in SignedVncViewer.jar for
+firewall proxies:
+
+Java socket applets and http proxies do not get along well.
+
+Java security allows the applet to connect back via a socket to the
+originating host, but the browser/plugin Proxy settings are not used for
+socket connections (only http and the like). So the socket connection
+fails in the proxy environment.
+
+The applet is not allowed to open a socket connection to the proxy (since
+that would let it connect to just about any host, e.g. CONNECT method).
+
+This is indpendent of SSL but of course fails for that socket connection
+as well. I.e. this is a problem for non-SSL VNC Viewers as well.
+
+Solution? Sign the applet and have the user click on "Yes" that they
+fully trust the applet. Then the applet can connect to any host via
+sockets, in particular the proxy. It next issues the request
+
+ CONNECT host:port HTTP/1.1
+ Host: host:port
+
+and if the proxy supports the CONNECT method we are finally connected to
+the VNC server.
+
+For SSL connections, SSL is layered on top of this socket. However note
+this scheme will work for non-SSL applet proxy tunnelling as well.
+
+It should be able to get non-SSL VNC connections to work via GET
+command but that has not been done yet.
+
+Note that some proxies only allow CONNECT to only these the ports 443
+(HTTPS) and 563 (SNEWS). So you would have to run the VNC server on
+those ports.
+
+SignedVncViewer.jar is just a signed version of VncViewer.jar
+
+The URL to use for this file: https://host:port/proxy.vnc
+
+Note VNCSERVERPORT, we assume $PARAMS will have the correct PORT setting
+(e.g. 563), not the one libvncserver puts in....
+
+-->
+
+
+<HTML>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+<APPLET CODE=VncViewer.class ARCHIVE=SignedVncViewer.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=VNCSERVERPORT value=$PORT>
+<param name="Open New Window" value=yes>
+$PARAMS
+</APPLET>
+<BR>
+<A href="http://www.karlrunge.com/x11vnc">x11vnc site</A>
+</HTML>
diff --git a/webclients/ssl/ss_vncviewer b/webclients/ssl/ss_vncviewer
new file mode 100755
index 0000000..7e793ff
--- /dev/null
+++ b/webclients/ssl/ss_vncviewer
@@ -0,0 +1,3676 @@
+#!/bin/sh
+#
+# ss_vncviewer: wrapper for vncviewer to use an stunnel SSL tunnel
+# or an SSH tunnel.
+#
+# Copyright (c) 2006-2009 by Karl J. Runge <runge@karlrunge.com>
+#
+# ss_vncviewer is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or (at
+# your option) any later version.
+#
+# ss_vncviewer 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. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with ss_vncviewer; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
+# or see <http://www.gnu.org/licenses/>.
+#
+#
+# You must have stunnel(8) installed on the system and in your PATH
+# (however, see the -ssh option below, in which case you will need ssh(1)
+# installed) Note: stunnel is usually installed in an "sbin" subdirectory.
+#
+# You should have "x11vnc -ssl ..." or "x11vnc -stunnel ..."
+# already running as the VNC server on the remote machine.
+# (or use stunnel on the server side for any other VNC server)
+#
+#
+# Usage: ss_vncviewer [cert-args] host:display <vncviewer-args>
+#
+# e.g.: ss_vncviewer snoopy:0
+# ss_vncviewer snoopy:0 -encodings "copyrect tight zrle hextile"
+#
+# [cert-args] can be:
+#
+# -verify /path/to/cacert.pem
+# -mycert /path/to/mycert.pem
+# -crl /path/to/my_crl.pem (or directory)
+# -proxy host:port
+#
+# -verify specifies a CA cert PEM file (or a self-signed one) for
+# authenticating the VNC server.
+#
+# -mycert specifies this client's cert+key PEM file for the VNC server to
+# authenticate this client.
+#
+# -proxy try host:port as a Web proxy to use the CONNECT method
+# to reach the VNC server (e.g. your firewall requires a proxy).
+#
+# For the "double proxy" case use -proxy host1:port1,host2:port2
+# (the first CONNECT is done through host1:port1 to host2:port2
+# and then a 2nd CONNECT to the destination VNC server.)
+#
+# Use socks://host:port, socks4://host:port, or socks5://host,port
+# to force usage of a SOCKS proxy. Also repeater://host:port and
+# sslrepeater://host:port.
+#
+# -showcert Only fetch the certificate using the 'openssl s_client'
+# command (openssl(1) must in installed). On ssvnc 1.0.27 and
+# later the bundled command 'ultravnc_dsm_helper' is used.
+#
+# See http://www.karlrunge.com/x11vnc/faq.html#faq-ssl-ca for details on
+# SSL certificates with VNC.
+#
+# A few other args (not related to SSL and certs):
+#
+# -2nd Run the vncviewer a 2nd time if the first connections fails.
+#
+# -ssh Use ssh instead of stunnel SSL. ssh(1) must be installed and you
+# must be able to log into the remote machine via ssh.
+#
+# In this case "host:display" may be of the form "user@host:display"
+# where "user@host" is used for the ssh login (see ssh(1) manpage).
+#
+# If -proxy is supplied it can be of the forms: "gwhost" "gwhost:port"
+# "user@gwhost" or "user@gwhost:port". "gwhost" is an incoming ssh
+# gateway machine (the VNC server is not running there), an ssh -L
+# redir is used to "host" in "host:display" from "gwhost". Any "user@"
+# part must be in the -proxy string (not in "host:display").
+#
+# Under -proxy use "gwhost:port" if connecting to any ssh port
+# other than the default (22). (even for the non-gateway case,
+# -proxy must be used to specify a non-standard ssh port)
+#
+# A "double ssh" can be specified via a -proxy string with the two
+# hosts separated by a comma:
+#
+# [user1@]host1[:port1],[user2@]host2[:port2]
+#
+# in which case a ssh to host1 and thru it via a -L redir a 2nd
+# ssh is established to host2.
+#
+# Examples:
+#
+# ss_vncviewer -ssh bob@bobs-home.net:0
+# ss_vncviewer -ssh -sshcmd 'x11vnc -localhost' bob@bobs-home.net:0
+#
+# ss_vncviewer -ssh -proxy fred@mygate.com:2022 mymachine:0
+# ss_vncviewer -ssh -proxy bob@bobs-home.net:2222 localhost:0
+#
+# ss_vncviewer -ssh -proxy fred@gw-host,fred@peecee localhost:0
+#
+# -sshcmd cmd Run "cmd" via ssh instead of the default "sleep 15"
+# e.g. -sshcmd 'x11vnc -display :0 -localhost -rfbport 5900'
+#
+# -sshargs "args" pass "args" to the ssh process, e.g. -L/-R port redirs.
+#
+# -sshssl Tunnel the SSL connection thru a SSH connection. The tunnel as
+# under -ssh is set up and the SSL connection goes thru it. Use
+# this if you want to have and end-to-end SSL connection but must
+# go thru a SSH gateway host (e.g. not the vnc server). Or use
+# this if you need to tunnel additional services via -R and -L
+# (see -sshargs above).
+#
+# ss_vncviewer -sshssl -proxy fred@mygate.com mymachine:0
+#
+# -listen (or -reverse) set up a reverse connection.
+#
+# -alpha turn on cursor alphablending hack if you are using the
+# enhanced tightvnc vncviewer.
+#
+# -grab turn on XGrabServer hack if you are using the enhanced tightvnc
+# vncviewer (e.g. for fullscreen mode in some windowmanagers like
+# fvwm that do not otherwise work in fullscreen mode)
+#
+#
+# set VNCVIEWERCMD to whatever vncviewer command you want to use.
+#
+VNCIPCMD=${VNCVIEWERCMD:-vncip}
+VNCVIEWERCMD=${VNCVIEWERCMD:-vncviewer}
+if [ "X$SSVNC_TURBOVNC" != "X" ]; then
+ if echo "$VNCVIEWERCMD" | grep '\.turbovnc' > /dev/null; then
+ :
+ else
+ if type "$VNCVIEWERCMD.turbovnc" > /dev/null 2>/dev/null; then
+ VNCVIEWERCMD="$VNCVIEWERCMD.turbovnc"
+ fi
+ fi
+fi
+#
+# Same for STUNNEL, e.g. set it to /path/to/stunnel or stunnel4, etc.
+#
+
+# turn on verbose debugging output
+if [ "X$SS_DEBUG" != "X" -a "X$SS_DEBUG" != "X0" ]; then
+ set -xv
+fi
+
+PATH=$PATH:/usr/sbin:/usr/local/sbin:/dist/sbin; export PATH
+
+localhost="localhost"
+if uname | grep Darwin >/dev/null; then
+ localhost="127.0.0.1"
+fi
+
+# work out which stunnel to use (debian installs as stunnel4)
+stunnel_set_here=""
+if [ "X$STUNNEL" = "X" ]; then
+ check_stunnel=1
+ if [ "X$SSVNC_BASEDIRNAME" != "X" ]; then
+ if [ -x "$SSVNC_BASEDIRNAME/stunnel" ]; then
+ type stunnel > /dev/null 2>&1
+ if [ $? = 0 ]; then
+ # found ours
+ STUNNEL=stunnel
+ check_stunnel=0
+ fi
+ fi
+ fi
+ if [ "X$check_stunnel" = "X1" ]; then
+ type stunnel4 > /dev/null 2>&1
+ if [ $? = 0 ]; then
+ STUNNEL=stunnel4
+ else
+ STUNNEL=stunnel
+ fi
+ fi
+ stunnel_set_here=1
+fi
+
+help() {
+ tail -n +2 "$0" | sed -e '/^$/ q'
+}
+
+secondtry=""
+gotalpha=""
+use_ssh=""
+use_sshssl=""
+direct_connect=""
+ssh_sleep=15
+
+# sleep longer in -listen mode:
+if echo "$*" | grep '.*-listen' > /dev/null; then
+ ssh_sleep=1800
+fi
+
+
+ssh_cmd=""
+# env override of ssh_cmd:
+if [ "X$SS_VNCVIEWER_SSH_CMD" != "X" ]; then
+ ssh_cmd="$SS_VNCVIEWER_SSH_CMD"
+fi
+
+ssh_args=""
+showcert=""
+reverse=""
+
+ciphers=""
+anondh="ALL:RC4+RSA:+SSLv2:@STRENGTH"
+anondh_set=""
+stunnel_debug="6"
+if [ "X$SS_DEBUG" != "X" -o "X$SSVNC_VENCRYPT_DEBUG" != "X" -o "X$SSVNC_STUNNEL_DEBUG" != "X" ]; then
+ stunnel_debug="7"
+fi
+
+if [ "X$1" = "X-viewerflavor" ]; then
+ # special case, try to guess which viewer:
+ #
+ if echo "$VNCVIEWERCMD" | egrep -i '^(xmessage|sleep )' > /dev/null; then
+ echo "unknown"
+ exit 0
+ fi
+ if echo "$VNCVIEWERCMD" | grep -i chicken.of > /dev/null; then
+ echo "cotvnc"
+ exit 0
+ fi
+ if echo "$VNCVIEWERCMD" | grep -i ultra > /dev/null; then
+ echo "ultravnc"
+ exit 0
+ fi
+ # OK, run it for help output...
+ str=`$VNCVIEWERCMD -h 2>&1 | head -n 5`
+ if echo "$str" | grep -i 'TightVNC.viewer' > /dev/null; then
+ echo "tightvnc"
+ elif echo "$str" | grep -i 'VNC viewer version 3' > /dev/null; then
+ echo "realvnc3"
+ elif echo "$str" | grep -i 'VNC viewer .*Edition 4' > /dev/null; then
+ echo "realvnc4"
+ elif echo "$str" | grep -i 'RealVNC.Ltd' > /dev/null; then
+ echo "realvnc4"
+ else
+ echo "unknown"
+ fi
+ exit 0
+fi
+if [ "X$1" = "X-viewerhelp" ]; then
+ $VNCVIEWERCMD -h 2>&1
+ exit 0
+fi
+
+# grab our cmdline options:
+while [ "X$1" != "X" ]
+do
+ case $1 in
+ "-verify") shift; verify="$1"
+ ;;
+ "-mycert") shift; mycert="$1"
+ ;;
+ "-crl") shift; crl="$1"
+ ;;
+ "-proxy") shift; proxy="$1"
+ ;;
+ "-ssh") use_ssh=1
+ ;;
+ "-sshssl") use_ssh=1
+ use_sshssl=1
+ ;;
+ "-sshcmd") shift; ssh_cmd="$1"
+ ;;
+ "-sshargs") shift; ssh_args="$1"
+ ;;
+ "-anondh") ciphers="ciphers=$anondh"
+ ULTRAVNC_DSM_HELPER_SHOWCERT_ADH=1
+ export ULTRAVNC_DSM_HELPER_SHOWCERT_ADH
+ anondh_set=1
+ ;;
+ "-ciphers") shift; ciphers="ciphers=$1"
+ ;;
+ "-alpha") gotalpha=1
+ ;;
+ "-showcert") showcert=1
+ ;;
+ "-listen") reverse=1
+ ;;
+ "-reverse") reverse=1
+ ;;
+ "-2nd") secondtry=1
+ ;;
+ "-grab") VNCVIEWER_GRAB_SERVER=1; export VNCVIEWER_GRAB_SERVER
+ ;;
+ "-x11cursor") VNCVIEWER_X11CURSOR=1; export VNCVIEWER_X11CURSOR
+ ;;
+ "-rawlocal") VNCVIEWER_RAWLOCAL=1; export VNCVIEWER_RAWLOCAL
+ ;;
+ "-scale") shift; SSVNC_SCALE="$1"; export SSVNC_SCALE
+ ;;
+ "-onelisten") SSVNC_LISTEN_ONCE=1; export SSVNC_LISTEN_ONCE
+ ;;
+ "-sendclipboard") VNCVIEWER_SEND_CLIPBOARD=1; export VNCVIEWER_SEND_CLIPBOARD
+ ;;
+ "-sendalways") VNCVIEWER_SEND_ALWAYS=1; export VNCVIEWER_SEND_ALWAYS
+ ;;
+ "-recvtext") shift; VNCVIEWER_RECV_TEXT="$1"; export VNCVIEWER_RECV_TEXT
+ ;;
+ "-escape") shift; VNCVIEWER_ESCAPE="$1"; export VNCVIEWER_ESCAPE
+ ;;
+ "-ssvnc_encodings") shift; VNCVIEWER_ENCODINGS="$1"; export VNCVIEWER_ENCODINGS
+ ;;
+ "-ssvnc_extra_opts") shift; VNCVIEWERCMD_EXTRA_OPTS="$1"; export VNCVIEWERCMD_EXTRA_OPTS
+ ;;
+ "-rfbversion") shift; VNCVIEWER_RFBVERSION="$1"; export VNCVIEWER_RFBVERSION
+ ;;
+ "-nobell") VNCVIEWER_NOBELL=1; export VNCVIEWER_NOBELL
+ ;;
+ "-popupfix") VNCVIEWER_POPUP_FIX=1; export VNCVIEWER_POPUP_FIX
+ ;;
+ "-realvnc4") VNCVIEWER_IS_REALVNC4=1; export VNCVIEWER_IS_REALVNC4
+ ;;
+ "-h"*) help; exit 0
+ ;;
+ "--h"*) help; exit 0
+ ;;
+ *) break
+ ;;
+ esac
+ shift
+done
+
+# maxconn is something we added to stunnel, this disables it:
+if [ "X$SS_VNCVIEWER_NO_MAXCONN" != "X" ]; then
+ STUNNEL_EXTRA_OPTS=`echo "$STUNNEL_EXTRA_OPTS" | sed -e 's/maxconn/#maxconn/'`
+elif echo "$VNCVIEWERCMD" | egrep -i '^(xmessage|sleep )' > /dev/null; then
+ STUNNEL_EXTRA_OPTS=`echo "$STUNNEL_EXTRA_OPTS" | sed -e 's/maxconn/#maxconn/'`
+elif [ "X$reverse" != "X" ]; then
+ STUNNEL_EXTRA_OPTS=`echo "$STUNNEL_EXTRA_OPTS" | sed -e 's/maxconn/#maxconn/'`
+else
+ # new way (our patches). other than the above, we set these:
+ if [ "X$SKIP_STUNNEL_ONCE" = "X" ]; then
+ STUNNEL_ONCE=1; export STUNNEL_ONCE
+ fi
+ if [ "X$SKIP_STUNNEL_MAX_CLIENTS" = "X" ]; then
+ STUNNEL_MAX_CLIENTS=1; export STUNNEL_MAX_CLIENTS
+ fi
+fi
+# always set this one:
+if [ "X$SKIP_STUNNEL_NO_SYSLOG" = "X" ]; then
+ STUNNEL_NO_SYSLOG=1; export STUNNEL_NO_SYSLOG
+fi
+
+# this is the -t ssh option (gives better keyboard response thru SSH tunnel)
+targ="-t"
+if [ "X$SS_VNCVIEWER_NO_T" != "X" ]; then
+ targ=""
+fi
+
+# set the alpha blending env. hack:
+if [ "X$gotalpha" = "X1" ]; then
+ VNCVIEWER_ALPHABLEND=1
+ export VNCVIEWER_ALPHABLEND
+else
+ NO_ALPHABLEND=1
+ export NO_ALPHABLEND
+fi
+
+if [ "X$reverse" != "X" ]; then
+ ssh_sleep=1800
+ if [ "X$proxy" != "X" ]; then
+ # check proxy usage under reverse connection:
+ if [ "X$use_ssh" = "X" -a "X$use_sshssl" = "X" ]; then
+ echo ""
+ if echo "$proxy" | egrep -i "(repeater|vencrypt)://" > /dev/null; then
+ :
+ else
+ echo "*Warning*: SSL -listen and a Web proxy does not make sense."
+ sleep 2
+ fi
+ elif echo "$proxy" | grep "," > /dev/null; then
+ :
+ else
+ echo ""
+ echo "*Warning*: -listen and a single proxy/gateway does not make sense."
+ sleep 2
+ fi
+
+ # we now try to PPROXY_LOOP_THYSELF, set this var to disable that.
+ #SSVNC_LISTEN_ONCE=1; export SSVNC_LISTEN_ONCE
+ fi
+fi
+if [ "X$ssh_cmd" = "X" ]; then
+ # if no remote ssh cmd, sleep a bit:
+ ssh_cmd="sleep $ssh_sleep"
+fi
+
+# this should be a host:display:
+#
+orig="$1"
+shift
+
+dL="-L"
+if uname -sr | egrep 'SunOS 5\.[5-8]' > /dev/null; then
+ dL="-h"
+fi
+
+have_uvnc_dsm_helper_showcert=""
+if [ "X$showcert" = "X1" -a "X$SSVNC_USE_S_CLIENT" = "X" -a "X$reverse" = "X" ]; then
+ if type ultravnc_dsm_helper >/dev/null 2>&1; then
+ if ultravnc_dsm_helper -help 2>&1 | grep -w showcert >/dev/null; then
+ have_uvnc_dsm_helper_showcert=1
+ fi
+ fi
+fi
+have_uvnc_dsm_helper_ipv6=""
+if [ "X$SSVNC_ULTRA_DSM" != "X" ]; then
+ if type ultravnc_dsm_helper >/dev/null 2>&1; then
+ if ultravnc_dsm_helper -help 2>&1 | grep -iw ipv6 >/dev/null; then
+ have_uvnc_dsm_helper_ipv6=1
+ fi
+ fi
+fi
+
+rchk() {
+ # a kludge to set $RANDOM if we are not bash:
+ if [ "X$BASH_VERSION" = "X" ]; then
+ RANDOM=`date +%S``sh -c 'echo $$'``ps -elf 2>&1 | sum 2>&1 | awk '{print $1}'`
+ fi
+}
+rchk
+
+# a portable, but not absolutely safe, tmp file creator
+mytmp() {
+ tf=$1
+ if type mktemp > /dev/null 2>&1; then
+ # if we have mktemp(1), use it:
+ tf2="$tf.XXXXXX"
+ tf2=`mktemp "$tf2"`
+ if [ "X$tf2" != "X" -a -f "$tf2" ]; then
+ if [ "X$DEBUG_MKTEMP" != "X" ]; then
+ echo "mytmp-mktemp: $tf2" 1>&2
+ fi
+ echo "$tf2"
+ return
+ fi
+ fi
+ # fallback to multiple cmds:
+ rm -rf "$tf" || exit 1
+ if [ -d "$tf" ]; then
+ echo "tmp file $tf still exists as a directory."
+ exit 1
+ elif [ $dL "$tf" ]; then
+ echo "tmp file $tf still exists as a symlink."
+ exit 1
+ elif [ -f "$tf" ]; then
+ echo "tmp file $tf still exists."
+ exit 1
+ fi
+ touch "$tf" || exit 1
+ chmod 600 "$tf" || exit 1
+ rchk
+ if [ "X$DEBUG_MKTEMP" != "X" ]; then
+ echo "mytmp-touch: $tf" 1>&2
+ fi
+ echo "$tf"
+}
+
+# set up special case of ultravnc single click III mode:
+if echo "$proxy" | egrep "^sslrepeater://" > /dev/null; then
+ pstr=`echo "$proxy" | sed -e 's,sslrepeater://,,'`
+ pstr1=`echo "$pstr" | sed -e 's/+.*$//'`
+ pstr2=`echo "$pstr" | sed -e 's/^[^+]*+//'`
+ SSVNC_REPEATER="SCIII=$pstr2"; export SSVNC_REPEATER
+ orig=$pstr1
+ echo
+ echo "reset: SSVNC_REPEATER=$SSVNC_REPEATER orig=$orig proxy=''"
+ proxy=""
+fi
+if echo "$proxy" | egrep "vencrypt://" > /dev/null; then
+ vtmp="/tmp/ss_handshake${RANDOM}.$$.txt"
+ vtmp=`mytmp "$vtmp"`
+ SSVNC_PREDIGESTED_HANDSHAKE="$vtmp"
+ export SSVNC_PREDIGESTED_HANDSHAKE
+ if [ "X$SSVNC_USE_OURS" = "X" ]; then
+ NEED_VENCRYPT_VIEWER_BRIDGE=1
+ fi
+fi
+if [ "X$SSVNC_USE_OURS" = "X" ]; then
+ VNCVIEWERCMD_EXTRA_OPTS=""
+fi
+
+
+# check -ssh and -mycert/-verify conflict:
+if [ "X$use_ssh" = "X1" -a "X$use_sshssl" = "X" ]; then
+ if [ "X$mycert" != "X" -o "X$verify" != "X" ]; then
+ echo "-mycert and -verify cannot be used in -ssh mode"
+ exit 1
+ fi
+fi
+
+# direct mode Vnc:// means show no warnings.
+# direct mode vnc:// will show warnings.
+if echo "$orig" | grep '^V[Nn][Cc]://' > /dev/null; then
+ SSVNC_NO_ENC_WARN=1
+ export SSVNC_NO_ENC_WARN
+ orig=`echo "$orig" | sed -e 's/^...:/vnc:/'`
+fi
+
+# interprest the pseudo URL proto:// strings:
+if echo "$orig" | grep '^vnc://' > /dev/null; then
+ orig=`echo "$orig" | sed -e 's,vnc://,,'`
+ verify=""
+ mycert=""
+ crl=""
+ use_ssh=""
+ use_sshssl=""
+ direct_connect=1
+elif echo "$orig" | grep '^vncs://' > /dev/null; then
+ orig=`echo "$orig" | sed -e 's,vncs://,,'`
+elif echo "$orig" | grep '^vncssl://' > /dev/null; then
+ orig=`echo "$orig" | sed -e 's,vncssl://,,'`
+elif echo "$orig" | grep '^vnc+ssl://' > /dev/null; then
+ orig=`echo "$orig" | sed -e 's,vnc.ssl://,,'`
+elif echo "$orig" | grep '^vncssh://' > /dev/null; then
+ orig=`echo "$orig" | sed -e 's,vncssh://,,'`
+ use_ssh=1
+elif echo "$orig" | grep '^vnc+ssh://' > /dev/null; then
+ orig=`echo "$orig" | sed -e 's,vnc.ssh://,,'`
+ use_ssh=1
+fi
+
+if [ "X$SSVNC_ULTRA_DSM" != "X" ]; then
+ verify=""
+ mycert=""
+ crl=""
+ use_ssh=""
+ use_sshssl=""
+ direct_connect=1
+ if echo "$SSVNC_ULTRA_DSM" | grep 'noultra:' > /dev/null; then
+ SSVNC_NO_ULTRA_DSM=1; export SSVNC_NO_ULTRA_DSM
+ fi
+fi
+
+# rsh mode is an internal/secret thing only I use.
+rsh=""
+if echo "$orig" | grep '^rsh://' > /dev/null; then
+ use_ssh=1
+ rsh=1
+ orig=`echo "$orig" | sed -e 's,rsh://,,'`
+elif echo "$orig" | grep '^rsh:' > /dev/null; then
+ use_ssh=1
+ rsh=1
+ orig=`echo "$orig" | sed -e 's,rsh:,,'`
+fi
+
+# play around with host:display port:
+if echo "$orig" | grep ':[0-9][0-9]*$' > /dev/null; then
+ :
+else
+ # add or assume :0 if no ':'
+ if [ "X$reverse" = "X" ]; then
+ orig="$orig:0"
+ elif [ "X$orig" = "X" ]; then
+ orig=":0"
+ fi
+fi
+
+# extract host and disp number:
+
+# try to see if it is ipv6 address:
+ipv6=0
+if echo "$orig" | grep '\[' > /dev/null; then
+ # ipv6 [fe80::219:dbff:fee5:3f92%eth1]:5900
+ host=`echo "$orig" | sed -e 's/\].*$//' -e 's/\[//'`
+ disp=`echo "$orig" | sed -e 's/^.*\]://'`
+ ipv6=1
+elif echo "$orig" | grep ':..*:' > /dev/null; then
+ # ipv6 fe80::219:dbff:fee5:3f92%eth1:5900
+ host=`echo "$orig" | sed -e 's/:[^:]*$//'`
+ disp=`echo "$orig" | sed -e 's/^.*://'`
+ ipv6=1
+else
+ # regular host:port
+ host=`echo "$orig" | awk -F: '{print $1}'`
+ disp=`echo "$orig" | awk -F: '{print $2}'`
+fi
+
+if [ "X$reverse" != "X" -a "X$STUNNEL_LISTEN" = "X" -a "X$host" != "X" ]; then
+ STUNNEL_LISTEN=$host
+ echo "set STUNNEL_LISTEN=$STUNNEL_LISTEN"
+fi
+
+if [ "X$host" = "X" ]; then
+ host=$localhost
+fi
+
+if [ "X$SSVNC_IPV6" = "X0" ]; then
+ # disable checking for it.
+ ipv6=0
+#elif [ "X$reverse" != "X" -a "X$ipv6" = "X1" ]; then
+# ipv6=0
+elif [ "X$ipv6" = "X1" ]; then
+ :
+elif echo "$host" | grep '^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' > /dev/null; then
+ :
+else
+ # regular hostname, can't be sure...
+ gout=""
+ if type getent > /dev/null 2>/dev/null; then
+ gout=`getent hosts "$host" 2>/dev/null`
+ fi
+ if echo "$gout" | grep ':.*:' > /dev/null; then
+ if echo "$gout" | grep '^[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' > /dev/null; then
+ :
+ else
+ echo "ipv6: "`echo "$gout" | grep ':.*:' | head -n 1`
+ ipv6=1
+ fi
+ fi
+ if [ "X$ipv6" = "X0" ]; then
+ hout=""
+ if type host > /dev/null 2>/dev/null; then
+ host "$host" >/dev/null 2>&1
+ host "$host" >/dev/null 2>&1
+ hout=`host "$host" 2>/dev/null`
+ fi
+ if echo "$hout" | grep -i 'has ipv6 address' > /dev/null; then
+ if echo "$hout" | grep -i 'has address' > /dev/null; then
+ :
+ else
+ echo "ipv6: "`echo "$hout" | grep -i 'has ipv6 address' | head -n 1`
+ ipv6=1
+ fi
+ fi
+ fi
+ if [ "X$ipv6" = "X0" ]; then
+ dout=""
+ if type dig > /dev/null 2>/dev/null; then
+ dout=`dig -t any "$host" 2>/dev/null`
+ fi
+ if echo "$dout" | grep -i "^$host" | grep '[ ]AAAA[ ]' > /dev/null; then
+ if echo "$dout" | grep -i "^$host" | grep '[ ]A[ ]' > /dev/null; then
+ :
+ else
+ echo "ipv6: "`echo "$dout" | grep -i '[ ]AAAA[ ]' | head -n 1`
+ ipv6=1
+ fi
+ fi
+ fi
+ if [ "X$ipv6" = "X0" ]; then
+ sout=`env LOOKUP="$host" \
+ perl -e ' eval {use Socket}; exit 0 if $@;
+ eval {use Socket6}; exit 0 if $@;
+ @res = getaddrinfo($ENV{LOOKUP}, "daytime", AF_UNSPEC, SOCK_STREAM);
+ $ipv4 = 0;
+ $ipv6 = 0;
+ $ip6 = "";
+ while (scalar(@res) >= 5) {
+ ($family, $socktype, $proto, $saddr, $canon, @res) = @res;
+ $ipv4 = 1 if $family == AF_INET;
+ $ipv6 = 1 if $family == AF_INET6;
+ if ($family == AF_INET6 && $ip6 eq "") {
+ my ($host, $port) = getnameinfo($saddr, NI_NUMERICHOST | NI_NUMERICSERV);
+ $ip6 = $host;
+ }
+ }
+ if (! $ipv4 && $ipv6) {
+ print "AF_INET6_ONLY: $ENV{LOOKUP}: $ip6\n";
+ }
+ exit 0;
+ ' 2>/dev/null`
+ if echo "$sout" | grep AF_INET6_ONLY > /dev/null; then
+ echo "$sout"
+ ipv6=1
+ fi
+ fi
+fi
+if [ "X$ipv6" = "X1" ]; then
+ echo "ipv6: addr=$host disp=$disp"
+fi
+if [ "X$disp" = "X" ]; then
+ port="" # probably -listen mode.
+elif [ $disp -lt 0 ]; then
+ # negative means use |n| without question:
+ port=`expr 0 - $disp`
+elif [ $disp -lt 200 ]; then
+ # less than 200 means 5900+n
+ if [ "X$reverse" = "X" ]; then
+ port=`expr $disp + 5900`
+ else
+ port=`expr $disp + 5500`
+ fi
+else
+ # otherwise use the number directly, e.g. 443, 2345
+ port=$disp
+fi
+
+if [ "X$ipv6" = "X1" -a "X$direct_connect" = "X1" ]; then
+ if [ "X$proxy" = "X" -a "X$reverse" = "X" ]; then
+ if [ "X$SSVNC_ULTRA_DSM" != "X" -a "X$have_uvnc_dsm_helper_ipv6" = "X1" ]; then
+ :
+ elif [ "X$SSVNC_NO_IPV6_PROXY" != "X" ]; then
+ :
+ elif [ "X$SSVNC_NO_IPV6_PROXY_DIRECT" != "X" ]; then
+ :
+ elif [ "X$SSVNC_USE_OURS" = "X1" ]; then
+ # requires 1.0.27 and later ssvncviewer binary
+ :
+ else
+ proxy="ipv6://$host:$port"
+ echo "direct connect: set proxy=$proxy"
+ fi
+ fi
+fi
+
+# (possibly) tell the vncviewer to only listen on lo:
+if [ "X$reverse" != "X" ]; then
+ if [ "X$direct_connect" = "X" -o "X$proxy" != "X" -o "X$STUNNEL_LISTEN" != "X" ]; then
+ VNCVIEWER_LISTEN_LOCALHOST=1
+ export VNCVIEWER_LISTEN_LOCALHOST
+ fi
+fi
+
+# try to find an open listening port via netstat(1):
+inuse=""
+if uname | grep Linux > /dev/null; then
+ inuse=`netstat -ant | egrep 'LISTEN|WAIT|ESTABLISH|CLOSE' | awk '{print $4}' | sed 's/^.*://'`
+elif uname | grep SunOS > /dev/null; then
+ inuse=`netstat -an -f inet -P tcp | egrep 'LISTEN|WAIT|ESTABLISH|CLOSE' | awk '{print $1}' | sed 's/^.*\.//'`
+elif uname | egrep -i 'bsd|darwin' > /dev/null; then
+ inuse=`netstat -ant -f inet | egrep 'LISTEN|WAIT|ESTABLISH|CLOSE' | awk '{print $4}' | sed 's/^.*\.//'`
+# add others...
+fi
+
+# this is a crude attempt for unique ports tags, etc.
+date_sec=`date +%S`
+
+# these are special cases of no vnc, e.g. sleep or xmessage.
+# these are for using ssvnc as a general port redirector.
+if echo "$VNCVIEWERCMD" | grep '^sleep[ ][ ]*[0-9][0-9]*' > /dev/null; then
+ if [ "X$SS_VNCVIEWER_LISTEN_PORT" = "X" ]; then
+ p=`echo "$VNCVIEWERCMD" | awk '{print $3}'`
+ if [ "X$p" != "X" ]; then
+ SS_VNCVIEWER_LISTEN_PORT=$p
+ fi
+ fi
+ p2=`echo "$VNCVIEWERCMD" | awk '{print $2}'`
+ VNCVIEWERCMD="eval sleep $p2; echo Local "
+elif echo "$VNCVIEWERCMD" | grep '^xmessage[ ][ ]*[0-9][0-9]*' > /dev/null; then
+ if [ "X$SS_VNCVIEWER_LISTEN_PORT" = "X" ]; then
+ p=`echo "$VNCVIEWERCMD" | awk '{print $2}'`
+ SS_VNCVIEWER_LISTEN_PORT=$p
+ fi
+fi
+
+# utility to find a free port to listen on.
+findfree() {
+ try0=$1
+ try=$try0
+ use0=""
+
+ if [ "X$SS_VNCVIEWER_LISTEN_PORT" != "X" ]; then
+ echo "$SS_VNCVIEWER_LISTEN_PORT"
+ return
+ fi
+ if [ $try -ge 6000 ]; then
+ fmax=`expr $try + 1000`
+ else
+ fmax=6000
+ fi
+
+ while [ $try -lt $fmax ]
+ do
+ if [ "X$inuse" = "X" ]; then
+ break
+ fi
+ if echo "$inuse" | grep -w $try > /dev/null; then
+ :
+ else
+ use0=$try
+ break
+ fi
+ try=`expr $try + 1`
+ done
+ if [ "X$use0" = "X" ]; then
+ use0=`expr $date_sec + $try0`
+ fi
+
+ echo $use0
+}
+
+# utility for exiting; kills some helper processes,
+# removes files, etc.
+final() {
+ echo ""
+ if [ "X$tmp_cfg" != "X" ]; then
+ rm -f $tmp_cfg
+ fi
+ if [ "X$SS_VNCVIEWER_RM" != "X" ]; then
+ rm -f $SS_VNCVIEWER_RM 2>/dev/null
+ fi
+ if [ "X$tcert" != "X" ]; then
+ rm -f $tcert
+ fi
+ if [ "X$pssh" != "X" ]; then
+ echo "Terminating background ssh process"
+ echo kill -TERM "$pssh"
+ kill -TERM "$pssh" 2>/dev/null
+ sleep 1
+ kill -KILL "$pssh" 2>/dev/null
+ pssh=""
+ fi
+ if [ "X$stunnel_pid" != "X" ]; then
+ echo "Terminating background stunnel process"
+ echo kill -TERM "$stunnel_pid"
+ kill -TERM "$stunnel_pid" 2>/dev/null
+ sleep 1
+ kill -KILL "$stunnel_pid" 2>/dev/null
+ stunnel_pid=""
+ fi
+ if [ "X$dsm_pid" != "X" ]; then
+ echo "Terminating background ultravnc_dsm_helper process"
+ echo kill -TERM "$dsm_pid"
+ kill -TERM "$dsm_pid" 2>/dev/null
+ sleep 1
+ kill -KILL "$dsm_pid" 2>/dev/null
+ stunnel_pid=""
+ fi
+ if [ "X$tail_pid" != "X" ]; then
+ kill -TERM $tail_pid
+ fi
+ if [ "X$tail_pid2" != "X" ]; then
+ kill -TERM $tail_pid2
+ fi
+}
+
+if [ "X$reverse" = "X" ]; then
+ # normal connections try 5930-5999:
+ if [ "X$showcert" = "X" ]; then
+ use=`findfree 5930`
+ else
+ # move away from normal place for (possibly many) -showcert
+ pstart=`date +%S`
+ pstart=`expr 6130 + $pstart + $pstart`
+ use=`findfree $pstart`
+ fi
+ if [ $use -ge 5900 ]; then
+ N=`expr $use - 5900`
+ else
+ N=$use
+ fi
+else
+ # reverse connections:
+ p2=`expr $port + 30`
+ use=`findfree $p2`
+ if [ $use -ge 5500 ]; then
+ N=`expr $use - 5500`
+ else
+ N=$use
+ fi
+fi
+
+# this is for my special use of ss_vncip -> vncip viewer.
+if echo "$0" | grep vncip > /dev/null; then
+ VNCVIEWERCMD="$VNCIPCMD"
+fi
+
+if echo "$VNCVIEWERCMD" | egrep -i '^(xmessage|sleep )' > /dev/null; then
+ :
+elif [ "X$VNCVIEWERCMD_EXTRA_OPTS" != "X" ]; then
+ VNCVIEWERCMD="$VNCVIEWERCMD $VNCVIEWERCMD_EXTRA_OPTS"
+fi
+
+# trick for the undocumented rsh://host:port method.
+rsh_setup() {
+ if echo "$ssh_host" | grep '@' > /dev/null; then
+ ul=`echo "$ssh_host" | awk -F@ '{print $1}'`
+ ul="-l $ul"
+ ssh_host=`echo "$ssh_host" | awk -F@ '{print $2}'`
+ else
+ ul=""
+ fi
+ ssh_cmd=`echo "$ssh_cmd" | sed -e 's/ -localhost/ /g'`
+}
+
+# trick for the undocumented rsh://host:port method.
+rsh_viewer() {
+ trap "final" 0 2 15
+ if [ "X$PORT" = "X" ]; then
+ exit 1
+ elif [ $PORT -ge 5900 ]; then
+ vdpy=`expr $PORT - 5900`
+ else
+ vdpy=":$PORT"
+ fi
+ stty sane
+ echo "$VNCVIEWERCMD" "$@" $ssh_host:$vdpy
+ echo ""
+ $VNCVIEWERCMD "$@" $ssh_host:$vdpy
+ if [ $? != 0 ]; then
+ sleep 2
+ $VNCVIEWERCMD "$@" $ssh_host:$vdpy
+ fi
+}
+
+check_perl() {
+ if type "$1" > /dev/null 2>&1; then
+ :
+ elif [ ! -x "$1" ]; then
+ echo ""
+ echo "*******************************************************"
+ echo "** Problem finding the Perl command '$1': **"
+ echo ""
+ type "perl"
+ echo ""
+ echo "** Perhaps you need to install the Perl package. **"
+ echo "*******************************************************"
+ echo ""
+ sleep 5
+ fi
+}
+
+# this is the PPROXY tool. used only here for now...
+pcode() {
+ tf=$1
+ PPROXY_PROXY=$proxy; export PPROXY_PROXY
+ PPROXY_DEST="$host:$port"; export PPROXY_DEST
+ check_perl /usr/bin/perl
+
+ cod='#!/usr/bin/perl
+
+# A hack to glue stunnel to a Web or SOCKS proxy, UltraVNC repeater for
+# client connections.
+# Also acts as a VeNCrypt bridge (by redirecting to stunnel.)
+
+use IO::Socket::INET;
+
+my $have_inet6 = "";
+eval "use IO::Socket::INET6;";
+$have_inet6 = 1 if $@ eq "";
+
+#my $have_sock6 = "";
+#eval "use Socket; use Socket6;";
+#$have_sock6 = 1 if $@ eq "";
+
+if (exists $ENV{PPROXY_LOOP_THYSELF}) {
+ # used for reverse vnc, run a repeating outer loop.
+ print STDERR "PPROXY_LOOP: $ENV{PPROXY_LOOP_THYSELF}\n";
+ my $rm = $ENV{PPROXY_REMOVE};
+ my $lp = $ENV{PPROXY_LOOP_THYSELF};
+ delete $ENV{PPROXY_REMOVE};
+ delete $ENV{PPROXY_LOOP_THYSELF};
+ $ENV{PPROXY_LOOP_THYSELF_MASTER} = $$;
+ my $pid = $$;
+ my $dbg = 0;
+ my $c = 0;
+ use POSIX ":sys_wait_h";
+ while (1) {
+ $pid = fork();
+ last if ! defined $pid;
+ if ($pid eq "0") {
+ last;
+ }
+ $c++;
+ print STDERR "\nPPROXY_LOOP: pid=$$ child=$pid count=$c\n";
+ while (1) {
+ waitpid(-1, WNOHANG);
+ fsleep(0.25);
+ if (! kill 0, $pid) {
+ print STDERR "PPROXY_LOOP: child=$pid gone.\n";
+ last;
+ }
+ print STDERR "PPROXY_LOOP: child=$pid alive.\n" if $dbg;
+ if (! -f $lp) {
+ print STDERR "PPROXY_LOOP: flag file $lp gone, killing $pid\n";
+ kill TERM, $pid;
+ fsleep(0.1);
+ wait;
+ last;
+ }
+ print STDERR "PPROXY_LOOP: file exists $lp\n" if $dbg;
+ }
+ last if ! -f $lp;
+ fsleep(0.25);
+ }
+ if ($pid ne "0") {
+ unlink($0) if $rm;
+ exit 0;
+ }
+}
+
+if (exists $ENV{PPROXY_SLEEP} && $ENV{PPROXY_SLEEP} > 0) {
+ print STDERR "PPROXY_PID: $$\n";
+ sleep $ENV{PPROXY_SLEEP};
+}
+
+foreach my $var (qw(
+ PPROXY_DEST
+ PPROXY_KILLPID
+ PPROXY_LISTEN
+ PPROXY_PROXY
+ PPROXY_REMOVE
+ PPROXY_REPEATER
+ PPROXY_REVERSE
+ PPROXY_SLEEP
+ PPROXY_SOCKS
+ PPROXY_VENCRYPT
+ PPROXY_VENCRYPT_VIEWER_BRIDGE
+ )) {
+ if (0 || $ENV{SS_DEBUG} || $ENV{SSVNC_VENCRYPT_DEBUG}) {
+ print STDERR "$var: $ENV{$var}\n";
+ }
+}
+
+if ($ENV{PPROXY_SOCKS} ne "" && $ENV{PPROXY_PROXY} !~ m,^socks5?://,i) {
+ if ($ENV{PPROXY_SOCKS} eq "5") {
+ $ENV{PPROXY_PROXY} = "socks5://$ENV{PPROXY_PROXY}";
+ } else {
+ $ENV{PPROXY_PROXY} = "socks://$ENV{PPROXY_PROXY}";
+ }
+}
+
+my $rfbSecTypeAnonTls = 18;
+my $rfbSecTypeVencrypt = 19;
+
+my $rfbVencryptPlain = 256;
+my $rfbVencryptTlsNone = 257;
+my $rfbVencryptTlsVnc = 258;
+my $rfbVencryptTlsPlain = 259;
+my $rfbVencryptX509None = 260;
+my $rfbVencryptX509Vnc = 261;
+my $rfbVencryptX509Plain = 262;
+
+my $handshake_file = "";
+if (exists $ENV{SSVNC_PREDIGESTED_HANDSHAKE}) {
+ $handshake_file = $ENV{SSVNC_PREDIGESTED_HANDSHAKE};
+}
+
+my $have_gettimeofday = 0;
+eval "use Time::HiRes;";
+if ($@ eq "") {
+ $have_gettimeofday = 1;
+}
+sub gettime {
+ my $t = "0.0";
+ if ($have_gettimeofday) {
+ $t = Time::HiRes::gettimeofday();
+ }
+ return $t;
+}
+
+my $listen_handle = "";
+my $sock = "";
+my $parent = $$;
+
+my $initial_data = "";
+
+if ($ENV{PPROXY_VENCRYPT_VIEWER_BRIDGE}) {
+ my ($from, $to) = split(/,/, $ENV{PPROXY_VENCRYPT_VIEWER_BRIDGE});
+ do_vencrypt_viewer_bridge($from, $to);
+ exit 0;
+}
+
+my ($first, $second, $third) = split(/,/, $ENV{PPROXY_PROXY}, 3);
+my ($mode_1st, $mode_2nd, $mode_3rd) = ("", "", "");
+
+($first, $mode_1st) = url_parse($first);
+
+my ($proxy_host, $proxy_port) = ($first, "");
+if ($proxy_host =~ /^(.*):(\d+)$/) {
+ $proxy_host = $1;
+ $proxy_port = $2;
+}
+my $connect = $ENV{PPROXY_DEST};
+
+if ($second ne "") {
+ ($second, $mode_2nd) = url_parse($second);
+}
+
+if ($third ne "") {
+ ($third, $mode_3rd) = url_parse($third);
+}
+
+
+print STDERR "\n";
+print STDERR "PPROXY v0.4: a tool for Web, SOCKS, and UltraVNC proxies and for\n";
+print STDERR "PPROXY v0.4: IPv6 and VNC VeNCrypt bridging.\n";
+print STDERR "proxy_host: $proxy_host\n";
+print STDERR "proxy_port: $proxy_port\n";
+print STDERR "proxy_connect: $connect\n";
+print STDERR "pproxy_params: $ENV{PPROXY_PROXY}\n";
+print STDERR "pproxy_listen: $ENV{PPROXY_LISTEN}\n";
+print STDERR "pproxy_reverse: $ENV{PPROXY_REVERSE}\n";
+print STDERR "io_socket_inet6: $have_inet6\n";
+print STDERR "\n";
+if (! $have_inet6) {
+ print STDERR "PPROXY: To enable IPv6 connections, install the IO::Socket::INET6 perl module.\n\n";
+}
+
+if (1) {
+ print STDERR "pproxy 1st: $first\t- $mode_1st\n";
+ print STDERR "pproxy 2nd: $second\t- $mode_2nd\n";
+ print STDERR "pproxy 3rd: $third\t- $mode_3rd\n";
+ print STDERR "\n";
+}
+
+sub pdie {
+ my $msg = shift;
+ kill_proxy_pids();
+ die "$msg";
+}
+
+if ($ENV{PPROXY_REVERSE} ne "") {
+ my ($rhost, $rport) = ($ENV{PPROXY_REVERSE}, "");
+ if ($rhost =~ /^(.*):(\d+)$/) {
+ $rhost = $1;
+ $rport = $2;
+ }
+ $rport = 5900 unless $rport;
+ my $emsg = "";
+ $listen_handle = IO::Socket::INET->new(
+ PeerAddr => $rhost,
+ PeerPort => $rport,
+ Proto => "tcp"
+ );
+ $emsg = $!;
+ if (! $listen_handle && $have_inet6) {
+ eval {$listen_handle = IO::Socket::INET6->new(
+ PeerAddr => $rhost,
+ PeerPort => $rport,
+ Proto => "tcp"
+ );};
+ $emsg .= " / $!";
+ }
+ if (! $listen_handle) {
+ pdie "pproxy: $emsg -- PPROXY_REVERSE\n";
+ }
+ print STDERR "PPROXY_REVERSE: connected to $rhost $rport\n";
+
+} elsif ($ENV{PPROXY_LISTEN} ne "") {
+ my $listen_sock = "";
+ my $maxtry = 12;
+ my $sleep = 5;
+ my $p2 = "";
+ my $emsg = "";
+ for (my $i=0; $i < $maxtry; $i++) {
+ my ($if, $p) = ("", $ENV{PPROXY_LISTEN});
+ if ($p =~ /^(.*):(\d+)$/) {
+ $if = $1;
+ $p = $2;
+ }
+ $p2 = "*:$p";
+ if ($if eq "") {
+ $if = "localhost";
+ }
+ print STDERR "pproxy interface: $if\n";
+
+ $emsg = "";
+ if (($if eq "INADDR_ANY6" || $if eq "::") && $have_inet6) {
+ eval {$listen_sock = IO::Socket::INET6->new(
+ Listen => 2,
+ ReuseAddr => 1,
+ Domain => AF_INET6,
+ LocalAddr => "::",
+ LocalPort => $p,
+ Proto => "tcp"
+ );};
+ $p2 = ":::$p";
+ } elsif ($if =~ /^INADDR_ANY/) {
+ $listen_sock = IO::Socket::INET->new(
+ Listen => 2,
+ ReuseAddr => 1,
+ LocalPort => $p,
+ Proto => "tcp"
+ );
+ } elsif (($if eq "INADDR_LOOPBACK6" || $if eq "::1") && $have_inet6) {
+ $p2 = "::1:$p";
+ eval {$listen_sock = IO::Socket::INET6->new(
+ Listen => 2,
+ ReuseAddr => 1,
+ Domain => AF_INET6,
+ LocalAddr => "::1",
+ LocalPort => $p,
+ Proto => "tcp"
+ );};
+ $p2 = "::1:$p";
+ } else {
+ $p2 = "$if:$p";
+ $listen_sock = IO::Socket::INET->new(
+ Listen => 2,
+ ReuseAddr => 1,
+ LocalAddr => $if,
+ LocalPort => $p,
+ Proto => "tcp"
+ );
+ $emsg = $!;
+
+ if (! $listen_sock && $have_inet6) {
+ print STDERR "PPROXY_LISTEN: retry with INET6\n";
+ eval {$listen_sock = IO::Socket::INET6->new(
+ Listen => 2,
+ ReuseAddr => 1,
+ Domain => AF_INET6,
+ LocalAddr => $if,
+ LocalPort => $p,
+ Proto => "tcp"
+ );};
+ $emsg .= " / $!";
+ }
+ }
+ if (! $listen_sock) {
+ if ($i < $maxtry - 1) {
+ warn "pproxy: $emsg $!\n";
+ warn "Could not listen on port $p2, retrying in $sleep seconds... (Ctrl-C to quit)\n";
+ sleep $sleep;
+ }
+ } else {
+ last;
+ }
+ }
+ if (! $listen_sock) {
+ pdie "pproxy: $emsg -- PPROXY_LISTEN\n";
+ }
+ print STDERR "pproxy: listening on $p2\n";
+ my $ip;
+ ($listen_handle, $ip) = $listen_sock->accept();
+ my $err = $!;
+ close $listen_sock;
+ if (! $listen_handle) {
+ pdie "pproxy: $err\n";
+ }
+
+ if ($ENV{PPROXY_LOOP_THYSELF_MASTER}) {
+ my $sml = $ENV{SSVNC_MULTIPLE_LISTEN};
+ if ($sml ne "" && $sml ne "0") {
+ setpgrp(0, 0);
+ if (fork()) {
+ close $viewer_sock;
+ wait;
+ exit 0;
+ }
+ if (fork()) {
+ close $viewer_sock;
+ exit 0;
+ }
+ setpgrp(0, 0);
+ $parent = $$;
+ }
+ }
+}
+
+$sock = IO::Socket::INET->new(
+ PeerAddr => $proxy_host,
+ PeerPort => $proxy_port,
+ Proto => "tcp"
+);
+
+my $err = "";
+
+if (! $sock && $have_inet6) {
+ $err = $!;
+
+ print STDERR "pproxy: $!\n";
+
+ eval {$sock = IO::Socket::INET6->new(
+ PeerAddr => $proxy_host,
+ PeerPort => $proxy_port,
+ Proto => "tcp"
+ );};
+ $err .= " / $!";
+}
+
+if (! $sock && ($proxy_host =~ /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i || $proxy_host =~ /^::ffff:([\da-f]+:[\da-f]+)$/i)) {
+ print STDERR "pproxy: $!\n";
+ my $ipv4_addr = $1;
+ if ($ipv4_addr =~ /:/) {
+ my ($a, $b) = split(/:/, $ipv4_addr);
+ $a = hex($a);
+ $b = hex($b);
+ $ipv4_addr = sprintf("%d.", ($a & 0xff00) >> 8);
+ $ipv4_addr .= sprintf("%d.", ($a & 0x00ff));
+ $ipv4_addr .= sprintf("%d.", ($b & 0xff00) >> 8);
+ $ipv4_addr .= sprintf("%d", ($b & 0x00ff));
+ }
+
+ print STDERR "pproxy: re-trying with ipv4 addr: $ipv4_addr\n";
+
+ eval {$sock = IO::Socket::INET->new(
+ PeerAddr => $ipv4_addr,
+ PeerPort => $proxy_port,
+ Proto => "tcp"
+ );};
+ $err .= " / $!";
+}
+
+if (! $sock) {
+ unlink($0) if $ENV{PPROXY_REMOVE};
+ pdie "pproxy: $err\n";
+}
+
+unlink($0) if $ENV{PPROXY_REMOVE};
+
+if ($ENV{PPROXY_PROXY} =~ /^vencrypt:/ && $ENV{PPROXY_VENCRYPT_REVERSE}) {
+ print STDERR "\nPPROXY: vencrypt+reverse: swapping listen socket with connect socket.\n";
+ my $tmp_swap = $sock;
+ $sock = $listen_handle;
+ $listen_handle = $tmp_swap;
+}
+
+$cur_proxy = $first;
+setmode($mode_1st);
+
+if ($second ne "") {
+ connection($second, 1);
+
+ setmode($mode_2nd);
+ $cur_proxy = $second;
+
+ if ($third ne "") {
+ connection($third, 2);
+ setmode($mode_3rd);
+ $cur_proxy = $third;
+ connection($connect, 3);
+ } else {
+ connection($connect, 2);
+ }
+} else {
+ connection($connect, 1);
+}
+
+sub kill_proxy_pids() {
+ if ($ENV{PPROXY_VENCRYPT_VIEWER_BRIDGE}) {
+ return;
+ }
+ if ($ENV{PPROXY_KILLPID}) {
+ foreach my $p (split(/,/, $ENV{PPROXY_KILLPID})) {
+ if ($p =~ /^(\+|-)/) {
+ $p = $parent + $p;
+ }
+ print STDERR "kill TERM, $p (PPROXY_KILLPID)\n";
+ kill "TERM", $p;
+ }
+ }
+}
+
+sub xfer {
+ my($in, $out) = @_;
+ $RIN = $WIN = $EIN = "";
+ $ROUT = "";
+ vec($RIN, fileno($in), 1) = 1;
+ vec($WIN, fileno($in), 1) = 1;
+ $EIN = $RIN | $WIN;
+
+ while (1) {
+ my $nf = 0;
+ while (! $nf) {
+ $nf = select($ROUT=$RIN, undef, undef, undef);
+ }
+ my $len = sysread($in, $buf, 8192);
+ if (! defined($len)) {
+ next if $! =~ /^Interrupted/;
+ print STDERR "pproxy[$$]: $!\n";
+ last;
+ } elsif ($len == 0) {
+ print STDERR "pproxy[$$]: Input is EOF.\n";
+ last;
+ }
+ my $offset = 0;
+ my $quit = 0;
+ while ($len) {
+ my $written = syswrite($out, $buf, $len, $offset);
+ if (! defined $written) {
+ print STDERR "pproxy[$$]: Output is EOF. $!\n";
+ $quit = 1;
+ last;
+ }
+ $len -= $written;
+ $offset += $written;
+ }
+ last if $quit;
+ }
+ close($out);
+ close($in);
+ print STDERR "pproxy[$$]: finished xfer.\n";
+}
+
+sub handler {
+ print STDERR "pproxy[$$]: got SIGTERM.\n";
+ close $listen_handle if $listen_handle;
+ close $sock if $sock;
+ exit;
+}
+
+sub xfer_both {
+ $child = fork;
+
+ if (! defined $child) {
+ kill_proxy_pids();
+ exit 1;
+ }
+
+ $SIG{TERM} = "handler";
+
+ if ($child) {
+ if ($listen_handle) {
+ print STDERR "pproxy parent[$$] listen_handle -> socket\n";
+ xfer($listen_handle, $sock);
+ } else {
+ print STDERR "pproxy parent[$$] STDIN -> socket\n";
+ xfer(STDIN, $sock);
+ }
+ select(undef, undef, undef, 0.25);
+ if (kill 0, $child) {
+ select(undef, undef, undef, 0.9);
+ if (kill 0, $child) {
+ print STDERR "pproxy[$$]: kill TERM child $child\n";
+ kill "TERM", $child;
+ } else {
+ print STDERR "pproxy[$$]: child $child gone.\n";
+ }
+ }
+ } else {
+ select(undef, undef, undef, 0.05);
+ if ($listen_handle) {
+ print STDERR "pproxy child [$$] socket -> listen_handle\n";
+ if ($initial_data ne "") {
+ my $len = length $initial_data;
+ print STDERR "pproxy child [$$] sending initial_data, length $len\n\n";
+ syswrite($listen_handle, $initial_data, $len);
+ } else {
+ print STDERR "\n";
+ }
+ xfer($sock, $listen_handle);
+ } else {
+ print STDERR "pproxy child [$$] socket -> STDOUT\n";
+ if ($initial_data ne "") {
+ my $len = length $initial_data;
+ print STDERR "pproxy child [$$] sending initial_data, length $len\n\n";
+ syswrite(STDOUT, $initial_data, $len);
+ } else {
+ print STDERR "\n";
+ }
+ xfer($sock, STDOUT);
+ }
+ select(undef, undef, undef, 0.25);
+ if (kill 0, $parent) {
+ select(undef, undef, undef, 0.8);
+ if (kill 0, $parent) {
+ print STDERR "pproxy[$$]: kill TERM parent $parent\n";
+ kill "TERM", $parent;
+ } else {
+ print STDERR "pproxy[$$]: parent $parent gone.\n";
+ }
+ }
+ }
+
+ kill_proxy_pids();
+}
+
+xfer_both();
+
+exit;
+
+sub fsleep {
+ select(undef, undef, undef, shift);
+}
+
+sub url_parse {
+ my $hostport = shift;
+ my $mode = "http";
+ if ($hostport =~ m,^socks4?://(\S*)$,i) {
+ $mode = "socks4";
+ $hostport = $1;
+ } elsif ($hostport =~ m,^socks5://(\S*)$,i) {
+ $mode = "socks5";
+ $hostport = $1;
+ } elsif ($hostport =~ m,^https?://(\S*)$,i) {
+ $mode = "http";
+ $hostport = $1;
+ } elsif ($hostport =~ m,^ipv6://(\S*)$,i) {
+ $mode = "ipv6";
+ $hostport = $1;
+ } elsif ($hostport =~ m,^repeater://(\S*)\+(\S*)$,i) {
+ # ultravnc repeater proxy.
+ $hostport = $1;
+ $mode = "repeater:$2";
+ if ($hostport !~ /:\d+$/) {
+ $hostport .= ":5900";
+ }
+ } elsif ($hostport =~ m,^vencrypt://(\S*)$,i) {
+ # vencrypt handshake.
+ $hostport = $1;
+ my $m = "connect";
+ if ($hostpost =~ /^(\S+)\+(\S+)$/) {
+ $hostport = $1;
+ $mode = $2;
+ }
+ $mode = "vencrypt:$m";
+ if ($hostport !~ /:\d+$/) {
+ $hostport .= ":5900";
+ }
+ }
+ return ($hostport, $mode);
+}
+
+sub setmode {
+ my $mode = shift;
+ $ENV{PPROXY_REPEATER} = "";
+ $ENV{PPROXY_VENCRYPT} = "";
+ if ($mode =~ /^socks/) {
+ if ($mode =~ /^socks5/) {
+ $ENV{PPROXY_SOCKS} = 5;
+ } else {
+ $ENV{PPROXY_SOCKS} = 1;
+ }
+ } elsif ($mode =~ /^ipv6/i) {
+ $ENV{PPROXY_SOCKS} = 0;
+ } elsif ($mode =~ /^repeater:(.*)/) {
+ $ENV{PPROXY_REPEATER} = $1;
+ $ENV{PPROXY_SOCKS} = "";
+ } elsif ($mode =~ /^vencrypt:(.*)/) {
+ $ENV{PPROXY_VENCRYPT} = $1;
+ $ENV{PPROXY_SOCKS} = "";
+ } else {
+ $ENV{PPROXY_SOCKS} = "";
+ }
+}
+
+sub connection {
+ my ($CONNECT, $w) = @_;
+
+ my $con = "";
+ my $msg = "";
+
+ if ($ENV{PPROXY_SOCKS} eq "5") {
+ # SOCKS5
+ my ($h, $p) = ($CONNECT, "");
+ if ($h =~ /^(.*):(\d+)$/) {
+ $h = $1;
+ $p = $2;
+ }
+ $con .= pack("C", 0x05);
+ $con .= pack("C", 0x01);
+ $con .= pack("C", 0x00);
+
+ $msg = "SOCKS5 via $cur_proxy to $h:$p\n\n";
+ print STDERR "proxy_request$w: $msg";
+
+ syswrite($sock, $con, length($con));
+
+ my ($n1, $n2, $n3, $n4, $n5, $n6);
+ my ($r1, $r2, $r3, $r4, $r5, $r6);
+ my ($s1, $s2, $s3, $s4, $s5, $s6);
+
+ $n1 = sysread($sock, $r1, 1);
+ $n2 = sysread($sock, $r2, 1);
+
+ $s1 = unpack("C", $r1);
+ $s2 = unpack("C", $r2);
+ if ($s1 != 0x05 || $s2 != 0x00) {
+ print STDERR "SOCKS5 fail s1=$s1 s2=$s2 n1=$n1 n2=$n2\n";
+ close $sock;
+ exit(1);
+ }
+
+ $con = "";
+ $con .= pack("C", 0x05);
+ $con .= pack("C", 0x01);
+ $con .= pack("C", 0x00);
+ $con .= pack("C", 0x03);
+ $con .= pack("C", length($h));
+ $con .= $h;
+ $con .= pack("C", $p >> 8);
+ $con .= pack("C", $p & 0xff);
+
+ syswrite($sock, $con, length($con));
+
+ $n1 = sysread($sock, $r1, 1);
+ $n2 = sysread($sock, $r2, 1);
+ $n3 = sysread($sock, $r3, 1);
+ $n4 = sysread($sock, $r4, 1);
+ $s1 = unpack("C", $r1);
+ $s2 = unpack("C", $r2);
+ $s3 = unpack("C", $r3);
+ $s4 = unpack("C", $r4);
+
+ if ($s4 == 0x1) {
+ sysread($sock, $r5, 4 + 2);
+ } elsif ($s4 == 0x3) {
+ sysread($sock, $r5, 1);
+ $s5 = unpack("C", $r5);
+ sysread($sock, $r6, $s5 + 2);
+ } elsif ($s4 == 0x4) {
+ sysread($sock, $r5, 16 + 2);
+ }
+
+ if ($s1 != 0x5 || $s2 != 0x0 || $s3 != 0x0) {
+ print STDERR "SOCKS5 failed: s1=$s1 s2=$s2 s3=$s3 s4=$s4 n1=$n1 n2=$n2 n3=$n3 n4=$n4\n";
+ close $sock;
+ exit(1);
+ }
+
+ } elsif ($ENV{PPROXY_SOCKS} eq "1") {
+ # SOCKS4 SOCKS4a
+ my ($h, $p) = ($CONNECT, "");
+ if ($h =~ /^(.*):(\d+)$/) {
+ $h = $1;
+ $p = $2;
+ }
+ $con .= pack("C", 0x04);
+ $con .= pack("C", 0x01);
+ $con .= pack("n", $p);
+
+ my $SOCKS_4a = 0;
+ if ($h eq "localhost" || $h eq "127.0.0.1") {
+ $con .= pack("C", 127);
+ $con .= pack("C", 0);
+ $con .= pack("C", 0);
+ $con .= pack("C", 1);
+ } elsif ($h =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) {
+ $con .= pack("C", $1);
+ $con .= pack("C", $2);
+ $con .= pack("C", $3);
+ $con .= pack("C", $4);
+ } else {
+ $con .= pack("C", 0);
+ $con .= pack("C", 0);
+ $con .= pack("C", 0);
+ $con .= pack("C", 3);
+ $SOCKS_4a = 1;
+ }
+
+ $con .= "nobody";
+ $con .= pack("C", 0);
+
+ $msg = "SOCKS4 via $cur_proxy to $h:$p\n\n";
+ if ($SOCKS_4a) {
+ $con .= $h;
+ $con .= pack("C", 0);
+ $msg =~ s/SOCKS4/SOCKS4a/;
+ }
+ print STDERR "proxy_request$w: $msg";
+ syswrite($sock, $con, length($con));
+
+ my $ok = 1;
+ for (my $i = 0; $i < 8; $i++) {
+ my $c;
+ sysread($sock, $c, 1);
+ my $s = unpack("C", $c);
+ if ($i == 0) {
+ $ok = 0 if $s != 0x0;
+ } elsif ($i == 1) {
+ $ok = 0 if $s != 0x5a;
+ }
+ }
+ if (! $ok) {
+ print STDERR "SOCKS4 failed.\n";
+ close $sock;
+ exit(1);
+ }
+ } elsif ($ENV{PPROXY_SOCKS} eq "0") {
+ # hack for ipv6 "proxy", nothing to do, assume INET6 call worked.
+ ;
+ } elsif ($ENV{PPROXY_REPEATER} ne "") {
+ my $rep = $ENV{PPROXY_REPEATER};
+ print STDERR "repeater: $rep\n";
+ $rep .= pack("x") x 250;
+ syswrite($sock, $rep, 250);
+
+ my $rfb = "";
+
+ my $ok = 1;
+ for (my $i = 0; $i < 12; $i++) {
+ my $c;
+ last if $ENV{PPROXY_GENERIC_REPEATER};
+ sysread($sock, $c, 1);
+ print STDERR $c;
+ $rfb .= $c;
+ }
+ if ($rfb ne "" && $rfb !~ /^RFB 000\.000/) {
+ $initial_data = $rfb;
+ $rfb =~ s/\n//g;
+ print STDERR "detected non-UltraVNC repeater; forwarding \"$rfb\"\nlength: ", length($initial_data), "\n";
+ }
+ } elsif ($ENV{PPROXY_VENCRYPT} ne "") {
+ my $vencrypt = $ENV{PPROXY_VENCRYPT};
+ vencrypt_dialog($vencrypt);
+
+ } else {
+ # Web Proxy:
+ $con = "CONNECT $CONNECT HTTP/1.1\r\n";
+ $con .= "Host: $CONNECT\r\n";
+ $con .= "Connection: close\r\n\r\n";
+ $msg = $con;
+
+ print STDERR "proxy_request$w: via $cur_proxy:\n$msg";
+ syswrite($sock, $con, length($con));
+
+ my $rep = "";
+ my $n = 0;
+ while ($rep !~ /\r\n\r\n/ && $n < 30000) {
+ my $c;
+ sysread($sock, $c, 1);
+ print STDERR $c;
+ $rep .= $c;
+ $n++;
+ }
+ if ($rep !~ m,HTTP/.* 200,) {
+ print STDERR "HTTP CONNECT failed.\n";
+ close $sock;
+ exit(1);
+ }
+ }
+}
+
+sub vdie {
+ append_handshake("done\n");
+ close $sock;
+ kill_proxy_pids();
+ exit(1);
+}
+
+sub anontls_handshake {
+ my ($vmode, $db) = @_;
+
+ print STDERR "\nPPROXY: Doing ANONTLS Handshake\n";
+
+ my $psec = pack("C", $rfbSecTypeAnonTls);
+ syswrite($sock, $psec, 1);
+
+ append_handshake("done\n");
+}
+
+sub vencrypt_handshake {
+
+ my ($vmode, $db) = @_;
+
+ print STDERR "\nPPROXY: Doing VeNCrypt Handshake\n";
+
+ my $psec = pack("C", $rfbSecTypeVencrypt);
+
+ if (exists $ENV{SSVNC_TEST_SEC_TYPE}) {
+ my $fake = $ENV{SSVNC_TEST_SEC_TYPE};
+ print STDERR "PPROXY: sending sec-type: $fake\n";
+ $psec = pack("C", $fake);
+ }
+
+ syswrite($sock, $psec, 1);
+
+ my $vmajor;
+ my $vminor;
+ sysread($sock, $vmajor, 1);
+ sysread($sock, $vminor, 1);
+
+ vdie if $vmajor eq "" || $vminor eq "";
+
+ $vmajor = unpack("C", $vmajor);
+ $vminor = unpack("C", $vminor);
+ print STDERR "server vencrypt version $vmajor.$vminor\n" if $db;
+
+ if (exists $ENV{SSVNC_TEST_SEC_TYPE}) {
+ print STDERR "PPROXY: continuing on in test mode.\n";
+ } else {
+ vdie if $vmajor ne 0;
+ vdie if $vminor < 2;
+ }
+
+ $vmajor = pack("C", 0);
+ $vminor = pack("C", 2);
+ append_handshake("subversion=0.2\n");
+
+ syswrite($sock, $vmajor, 1);
+ syswrite($sock, $vminor, 1);
+
+ my $result;
+ sysread($sock, $result, 1);
+ print STDERR "result empty\n" if $db && $result eq "";
+
+ vdie if $result eq "";
+ $result = unpack("C", $result);
+ print STDERR "result=$result\n" if $db;
+
+ vdie if $result ne 0;
+
+ my $nsubtypes;
+ sysread($sock, $nsubtypes, 1);
+
+ vdie if $nsubtypes eq "";
+ $nsubtypes = unpack("C", $nsubtypes);
+ print STDERR "nsubtypes=$nsubtypes\n" if $db;
+
+ my %subtypes;
+
+ for (my $i = 0; $i < $nsubtypes; $i++) {
+ my $subtype = "";
+ sysread($sock, $subtype, 4);
+ vdie if length($subtype) != 4;
+
+ # XXX fix 64bit.
+ $subtype = unpack("N", $subtype);
+ print STDERR "subtype: $subtype\n" if $db;
+ $subtypes{$subtype} = 1;
+ append_handshake("sst$i=$subtype\n");
+ }
+
+ my $subtype = 0;
+ if (exists $subtypes{$rfbVencryptX509None}) {
+ $subtype = $rfbVencryptX509None;
+ print STDERR "selected rfbVencryptX509None\n" if $db;
+ } elsif (exists $subtypes{$rfbVencryptX509Vnc}) {
+ $subtype = $rfbVencryptX509Vnc;
+ print STDERR "selected rfbVencryptX509Vnc\n" if $db;
+ } elsif (exists $subtypes{$rfbVencryptX509Plain}) {
+ $subtype = $rfbVencryptX509Plain;
+ print STDERR "selected rfbVencryptX509Plain\n" if $db;
+ } elsif (exists $subtypes{$rfbVencryptTlsNone}) {
+ $subtype = $rfbVencryptTlsNone;
+ print STDERR "selected rfbVencryptTlsNone\n" if $db;
+ } elsif (exists $subtypes{$rfbVencryptTlsVnc}) {
+ $subtype = $rfbVencryptTlsVnc;
+ print STDERR "selected rfbVencryptTlsVnc\n" if $db;
+ } elsif (exists $subtypes{$rfbVencryptTlsPlain}) {
+ $subtype = $rfbVencryptTlsPlain;
+ print STDERR "selected rfbVencryptTlsPlain\n" if $db;
+ }
+
+ if (exists $ENV{SSVNC_TEST_SEC_SUBTYPE}) {
+ my $fake = $ENV{SSVNC_TEST_SEC_SUBTYPE};
+ print STDERR "PPROXY: sending sec-subtype: $fake\n";
+ $subtype = $fake;
+ }
+
+ append_handshake("subtype=$subtype\n");
+
+ my $pst = pack("N", $subtype);
+ syswrite($sock, $pst, 4);
+
+ if (exists $ENV{SSVNC_TEST_SEC_SUBTYPE}) {
+ print STDERR "PPROXY: continuing on in test mode.\n";
+ } else {
+ vdie if $subtype == 0;
+ }
+
+ my $ok;
+ sysread($sock, $ok, 1);
+ $ok = unpack("C", $ok);
+ print STDERR "ok=$ok\n" if $db;
+
+ append_handshake("done\n");
+
+ vdie if $ok == 0;
+}
+
+sub vencrypt_dialog {
+ my $vmode = shift;
+ my $db = 0;
+
+ $db = 1 if exists $ENV{SS_DEBUG};
+ $db = 1 if exists $ENV{SSVNC_VENCRYPT_DEBUG};
+
+ append_handshake("mode=$vmode\n");
+
+ my $server_rfb = "";
+ #syswrite($sock, $rep, 250);
+ for (my $i = 0; $i < 12; $i++) {
+ my $c;
+ sysread($sock, $c, 1);
+ $server_rfb .= $c;
+ print STDERR $c;
+ }
+ print STDERR "server_rfb: $server_rfb\n" if $db;
+ append_handshake("server=$server_rfb");
+
+ my $minor = "";
+ if ($server_rfb =~ /^RFB 003\.(\d+)/) {
+ $minor = $1;
+ } else {
+ vdie;
+ }
+ my $viewer_rfb = "RFB 003.008\n";
+ if ($minor < 7) {
+ vdie;
+ } elsif ($minor == 7) {
+ $viewer_rfb = "RFB 003.007\n";
+ }
+ my $nsec;
+ my $t1 = gettime();
+ my $t0 = gettime();
+
+ syswrite($sock, $viewer_rfb, 12);
+ sysread($sock, $nsec, 1);
+
+ $t1 = gettime();
+ $t1 = sprintf("%.6f", $t1 - $t0);
+
+ append_handshake("viewer=$viewer_rfb");
+ append_handshake("latency=$t1\n");
+
+ vdie if $nsec eq "";
+
+ $nsec = unpack("C", $nsec);
+
+ print STDERR "nsec: $nsec\n" if $db;
+ vdie if $nsec eq 0 || $nsec > 100;
+
+ my %sectypes = ();
+
+ for (my $i = 0; $i < $nsec; $i++) {
+ my $sec;
+ sysread($sock, $sec, 1);
+ vdie if $sec eq "";
+ $sec = unpack("C", $sec);
+ print STDERR "sec: $sec\n" if $db;
+ $sectypes{$sec} = 1;
+ }
+
+ if (exists $sectypes{$rfbSecTypeVencrypt}) {
+ print STDERR "found rfbSecTypeVencrypt\n" if $db;
+ append_handshake("sectype=$rfbSecTypeVencrypt\n");
+ vencrypt_handshake($vmode, $db);
+ } elsif (exists $sectypes{$rfbSecTypeAnonTls}) {
+ print STDERR "found rfbSecTypeAnonTls\n" if $db;
+ append_handshake("sectype=$rfbSecTypeAnonTls\n");
+ anontls_handshake($vmode, $db);
+ } else {
+ print STDERR "No supported sec-type found\n" if $db;
+ vdie;
+ }
+}
+
+sub append_handshake {
+ my $str = shift;
+ if ($handshake_file) {
+ if (open(HSF, ">>$handshake_file")) {
+ print HSF $str;
+ close HSF;
+ }
+ }
+}
+
+sub do_vencrypt_viewer_bridge {
+ my ($listen, $connect) = @_;
+ print STDERR "\npproxy: starting vencrypt_viewer_bridge[$$]: $listen \-> $connect\n";
+ my $db = 0;
+ my $backwards = 0;
+ if ($listen < 0) {
+ $backwards = 1;
+ $listen = -$listen;
+ }
+ if ($handshake_file eq "") {
+ die "pproxy: vencrypt_viewer_bridge[$$]: no SSVNC_PREDIGESTED_HANDSHAKE\n";
+ }
+ my $listen_sock;
+ my $maxtry = 12;
+ my $sleep = 5;
+ for (my $i=0; $i < $maxtry; $i++) {
+ $listen_sock = IO::Socket::INET->new(
+ Listen => 2,
+ ReuseAddr => 1,
+ LocalAddr => "127.0.0.1",
+ LocalPort => $listen,
+ Proto => "tcp"
+ );
+ if (! $listen_sock) {
+ if ($i < $maxtry - 1) {
+ warn "pproxy: vencrypt_viewer_bridge[$$]: $!\n";
+ warn "Could not listen on port $listen, retrying in $sleep seconds... (Ctrl-C to quit)\n";
+ sleep $sleep;
+ }
+ } else {
+ last;
+ }
+ }
+ if (! $listen_sock) {
+ die "pproxy: vencrypt_viewer_bridge[$$]: $!\n";
+ }
+ print STDERR "pproxy: vencrypt_viewer_bridge[$$]: listening on port $listen\n\n";
+ my ($viewer_sock, $ip) = $listen_sock->accept();
+ my $err = $!;
+ close $listen_sock;
+ if (! $viewer_sock) {
+ die "pproxy: vencrypt_viewer_bridge[$$]: $err\n";
+ }
+ if ($ENV{PPROXY_LOOP_THYSELF_MASTER}) {
+ my $sml = $ENV{SSVNC_MULTIPLE_LISTEN};
+ if ($sml ne "" && $sml ne "0") {
+ setpgrp(0, 0);
+ if (fork()) {
+ close $viewer_sock;
+ wait;
+ exit 0;
+ }
+ if (fork()) {
+ close $viewer_sock;
+ exit 0;
+ }
+ setpgrp(0, 0);
+ $parent = $$;
+ }
+ }
+ print STDERR "vencrypt_viewer_bridge[$$]: viewer_sock $viewer_sock\n" if $db;
+
+ print STDERR "pproxy: vencrypt_viewer_bridge[$$]: connecting to 127.0.0.1:$connect\n";
+ my $server_sock = IO::Socket::INET->new(
+ PeerAddr => "127.0.0.1",
+ PeerPort => $connect,
+ Proto => "tcp"
+ );
+ print STDERR "vencrypt_viewer_bridge[$$]: server_sock $server_sock\n" if $db;
+ if (! $server_sock) {
+ my $err = $!;
+ die "pproxy: vencrypt_viewer_bridge[$$]: $err\n";
+ }
+
+ if ($backwards) {
+ print STDERR "vencrypt_viewer_bridge[$$]: reversing roles of viewer and server.\n";
+ my $t = $viewer_sock;
+ $viewer_sock = $server_sock;
+ $server_sock = $t;
+ }
+
+ my %hs = ();
+ my $dt = 0.2;
+ my $slept = 0.0;
+ while ($slept < 20.0) {
+ select(undef, undef, undef, $dt);
+ $slept += $dt;
+ if (-f $handshake_file && open(HSF, "<$handshake_file")) {
+ my $done = 0;
+ %hs = ();
+ my $str = "";
+ while (<HSF>) {
+ print STDERR "vencrypt_viewer_bridge[$$]: $_" if $ENV{VENCRYPT_VIEWER_BRIDGE_DEBUG};
+ $str .= "vencrypt_viewer_bridge[$$]: $_";
+ chomp;
+ if ($_ eq "done") {
+ $done = 1;
+ } else {
+ my ($k, $v) = split(/=/, $_, 2);
+ if ($k ne "" && $v ne "") {
+ $hs{$k} = $v;
+ }
+ }
+ }
+ close HSF;
+ if ($done) {
+ print STDERR "\n" . $str;
+ last;
+ }
+ }
+ }
+ if (! exists $hs{server}) {
+ $hs{server} = "RFB 003.008";
+ }
+ if (! exists $hs{sectype}) {
+ unlink($handshake_file);
+ die "pproxy: vencrypt_viewer_bridge[$$]: no sectype.\n";
+ }
+ syswrite($viewer_sock, "$hs{server}\n", length($hs{server}) + 1);
+ my $viewer_rfb = "";
+ for (my $i = 0; $i < 12; $i++) {
+ my $c;
+ sysread($viewer_sock, $c, 1);
+ $viewer_rfb .= $c;
+ print STDERR $c;
+ }
+ my $viewer_major = 3;
+ my $viewer_minor = 8;
+ if ($viewer_rfb =~ /RFB (\d+)\.(\d+)/) {
+ $viewer_major = $1;
+ $viewer_minor = $2;
+ }
+ my $u0 = pack("C", 0);
+ my $u1 = pack("C", 1);
+ my $u2 = pack("C", 2);
+ if ($hs{sectype} == $rfbSecTypeAnonTls) {
+ unlink($handshake_file);
+ print STDERR "\npproxy: vencrypt_viewer_bridge[$$]: rfbSecTypeAnonTls\n";
+ if ($viewer_major > 3 || $viewer_minor >= 7) {
+ ; # setup ok, proceed to xfer.
+ } else {
+ print STDERR "pproxy: vencrypt_viewer_bridge[$$]: faking RFB version 3.3 to viewer.\n";
+ my $n;
+ sysread($server_sock, $n, 1);
+ $n = unpack("C", $n);
+ if ($n == 0) {
+ die "pproxy: vencrypt_viewer_bridge[$$]: nsectypes == $n.\n";
+ }
+ my %types;
+ for (my $i = 0; $i < $n; $i++) {
+ my $t;
+ sysread($server_sock, $t, 1);
+ $t = unpack("C", $t);
+ $types{$t} = 1;
+ }
+ my $use = 1; # None
+ if (exists $types{1}) {
+ $use = 1; # None
+ } elsif (exists $types{2}) {
+ $use = 2; # VncAuth
+ } else {
+ die "pproxy: vencrypt_viewer_bridge[$$]: no valid sectypes" . join(",", keys %types) . "\n";
+ }
+
+ # send 4 bytes sectype to viewer:
+ # (note this should be MSB, network byte order...)
+ my $up = pack("C", $use);
+ syswrite($viewer_sock, $u0, 1);
+ syswrite($viewer_sock, $u0, 1);
+ syswrite($viewer_sock, $u0, 1);
+ syswrite($viewer_sock, $up, 1);
+ # and tell server the one we selected:
+ syswrite($server_sock, $up, 1);
+ if ($use == 1) {
+ # even None has security result, so read it here and discard it.
+ my $sr = "";
+ sysread($server_sock, $sr, 4);
+ }
+ }
+ } elsif ($hs{sectype} == $rfbSecTypeVencrypt) {
+ print STDERR "\npproxy: vencrypt_viewer_bridge[$$]: rfbSecTypeVencrypt\n";
+ if (! exists $hs{subtype}) {
+ unlink($handshake_file);
+ die "pproxy: vencrypt_viewer_bridge[$$]: no subtype.\n";
+ }
+ my $fake_type = "None";
+ my $plain = 0;
+ my $sub_type = $hs{subtype};
+ if ($sub_type == $rfbVencryptTlsNone) {
+ $fake_type = "None";
+ } elsif ($sub_type == $rfbVencryptTlsVnc) {
+ $fake_type = "VncAuth";
+ } elsif ($sub_type == $rfbVencryptTlsPlain) {
+ $fake_type = "None";
+ $plain = 1;
+ } elsif ($sub_type == $rfbVencryptX509None) {
+ $fake_type = "None";
+ } elsif ($sub_type == $rfbVencryptX509Vnc) {
+ $fake_type = "VncAuth";
+ } elsif ($sub_type == $rfbVencryptX509Plain) {
+ $fake_type = "None";
+ $plain = 1;
+ }
+ if ($plain) {
+ if (!open(W, ">$handshake_file")) {
+ unlink($handshake_file);
+ die "pproxy: vencrypt_viewer_bridge[$$]: $handshake_file $!\n";
+ }
+ print W <<"END";
+
+ proc print_out {} {
+ global user pass env
+
+ if [info exists env(SSVNC_UP_DEBUG)] {
+ toplevel .b
+ button .b.b -text "user=\$user pass=\$pass" -command {destroy .b}
+ pack .b.b
+ update
+ tkwait window .b
+ }
+
+ if [info exists env(SSVNC_UP_FILE)] {
+ set fh ""
+ catch {set fh [open \$env(SSVNC_UP_FILE) w]}
+ if {\$fh != ""} {
+ puts \$fh user=\$user\\npass=\$pass
+ flush \$fh
+ close \$fh
+ return
+ }
+ }
+ puts stdout user=\$user\\npass=\$pass
+ flush stdout
+ }
+
+ proc center_win {w} {
+ update
+ set W [winfo screenwidth \$w]
+ set W [expr \$W + 1]
+ wm geometry \$w +\$W+0
+ update
+ set x [expr [winfo screenwidth \$w]/2 - [winfo width \$w]/2]
+ set y [expr [winfo screenheight \$w]/2 - [winfo height \$w]/2]
+
+ wm geometry \$w +\$x+\$y
+ wm deiconify \$w
+ update
+ }
+
+ wm withdraw .
+
+ global env
+ set up {}
+ if [info exists env(SSVNC_UNIXPW)] {
+ set rm 0
+ set up \$env(SSVNC_UNIXPW)
+ if [regexp {^rm:} \$up] {
+ set rm 1
+ regsub {^rm:} \$up {} up
+ }
+ if [file exists \$up] {
+ set fh ""
+ set f \$up
+ catch {set fh [open \$up r]}
+ if {\$fh != ""} {
+ gets \$fh u
+ gets \$fh p
+ close \$fh
+ set up "\$u@\$p"
+ }
+ if {\$rm} {
+ catch {file delete \$f}
+ }
+ }
+ } elseif [info exists env(SSVNC_VENCRYPT_USERPASS)] {
+ set up \$env(SSVNC_VENCRYPT_USERPASS)
+ }
+ #puts stderr up=\$up
+ if {\$up != ""} {
+ if [regexp {@} \$up] {
+ global user pass
+ set user \$up
+ set pass \$up
+ regsub {@.*\$} \$user "" user
+ regsub {^[^@]*@} \$pass "" pass
+ print_out
+ exit
+ }
+ }
+
+ wm title . {VeNCrypt Viewer Bridge User/Pass}
+
+ set user {}
+ set pass {}
+
+ label .l -text {SSVNC VeNCrypt Viewer Bridge}
+
+ frame .f0
+ frame .f0.fL
+ label .f0.fL.la -text {Username: }
+ label .f0.fL.lb -text {Password: }
+
+ pack .f0.fL.la .f0.fL.lb -side top
+
+ frame .f0.fR
+ entry .f0.fR.ea -width 24 -textvariable user
+ entry .f0.fR.eb -width 24 -textvariable pass -show *
+
+ pack .f0.fR.ea .f0.fR.eb -side top -fill x
+
+ pack .f0.fL -side left
+ pack .f0.fR -side right -expand 1 -fill x
+
+ button .no -text Cancel -command {destroy .}
+ button .ok -text Done -command {print_out; destroy .}
+
+ center_win .
+ pack .l .f0 .no .ok -side top -fill x
+ update
+ wm deiconify .
+
+ bind .f0.fR.ea <Return> {focus .f0.fR.eb}
+ bind .f0.fR.eb <Return> {print_out; destroy .}
+ focus .f0.fR.ea
+
+ wm resizable . 1 0
+ wm minsize . [winfo reqwidth .] [winfo reqheight .]
+END
+ close W;
+
+ #system("cat $handshake_file");
+ my $w = "wish";
+ if ($ENV{WISH}) {
+ $w = $ENV{WISH};
+ }
+ print STDERR "pproxy: vencrypt_viewer_bridge[$$]: prompt VencryptPlain user and passwd.\n";
+ my $res = "";
+ if (`uname` =~ /Darwin/) {
+ my $mtmp = `mktemp /tmp/hsup.XXXXXX`;
+ chomp $mtmp;
+ system("env SSVNC_UP_FILE=$mtmp $w $handshake_file");
+ $res = `cat $mtmp`;
+ unlink $mtmp;
+ } else {
+ $res = `$w $handshake_file`;
+ }
+ my $user = "";
+ my $pass = "";
+ if ($res =~ /user=(\S*)/) {
+ $user = $1;
+ }
+ if ($res =~ /pass=(\S*)/) {
+ $pass = $1;
+ }
+ print STDERR "pproxy: vencrypt_viewer_bridge[$$]: sending VencryptPlain user and passwd.\n";
+ my $ulen = pack("C", length($user));
+ my $plen = pack("C", length($pass));
+ # (note this should be MSB, network byte order...)
+ syswrite($server_sock, $u0, 1);
+ syswrite($server_sock, $u0, 1);
+ syswrite($server_sock, $u0, 1);
+ syswrite($server_sock, $ulen, 1);
+ syswrite($server_sock, $u0, 1);
+ syswrite($server_sock, $u0, 1);
+ syswrite($server_sock, $u0, 1);
+ syswrite($server_sock, $plen, 1);
+ syswrite($server_sock, $user, length($user));
+ syswrite($server_sock, $pass, length($pass));
+ }
+ unlink($handshake_file);
+
+ my $ft = 0;
+ if ($fake_type eq "None") {
+ $ft = 1;
+ } elsif ($fake_type eq "VncAuth") {
+ $ft = 2;
+ } else {
+ die "pproxy: vencrypt_viewer_bridge[$$]: unknown fake type: $fake_type\n";
+ }
+ my $fp = pack("C", $ft);
+ if ($viewer_major > 3 || $viewer_minor >= 7) {
+ syswrite($viewer_sock, $u1, 1);
+ syswrite($viewer_sock, $fp, 1);
+ my $cr;
+ sysread($viewer_sock, $cr, 1);
+ $cr = unpack("C", $cr);
+ if ($cr != $ft) {
+ die "pproxy: vencrypt_viewer_bridge[$$]: client selected wrong type: $cr / $ft\n";
+ }
+ } else {
+ print STDERR "pproxy: vencrypt_viewer_bridge[$$]: faking RFB version 3.3 to viewer.\n";
+ # send 4 bytes sect type to viewer:
+ # (note this should be MSB, network byte order...)
+ syswrite($viewer_sock, $u0, 1);
+ syswrite($viewer_sock, $u0, 1);
+ syswrite($viewer_sock, $u0, 1);
+ syswrite($viewer_sock, $fp, 1);
+ if ($ft == 1) {
+ # even None has security result, so read it here and discard it.
+ my $sr = "";
+ sysread($server_sock, $sr, 4);
+ }
+ }
+ }
+
+ $listen_handle = $viewer_sock;
+ $sock = $server_sock;
+
+ xfer_both();
+}
+'
+ # '
+ # xpg_echo will expand \n \r, etc.
+ # try to unset and then test for it.
+ if type shopt > /dev/null 2>&1; then
+ shopt -u xpg_echo >/dev/null 2>&1
+ fi
+ v='print STDOUT "abc\n";'
+ echo "$v" > $tf
+ chmod 700 $tf
+
+ lc=`wc -l $tf | awk '{print $1}'`
+ if [ "X$lc" = "X1" ]; then
+ echo "$cod" > $tf
+ else
+ printf "%s" "$cod" > $tf
+ echo "" >> $tf
+ fi
+ # prime perl
+ perl -e 'use IO::Socket::INET; select(undef, undef, undef, 0.01)' >/dev/null 2>&1
+}
+
+# make_tcert is no longer invoked via the ssvnc gui (Listen mode).
+# make_tcert is for testing only now via -mycert BUILTIN
+make_tcert() {
+ tcert="/tmp/ss_vnc_viewer_tcert${RANDOM}.$$"
+ tcert=`mytmp "$tcert"`
+ cat > $tcert <<END
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAvkfXxb0wcxgrjV2ziFikjII+ze8iKcTBt47L0GM/c21efelN
++zZpJUUXLu4zz8Ryq8Q+sQgfNy7uTOpN9bUUaOk1TnD7gaDQnQWiNHmqbW2kL+DS
+OKngJVPo9dETAS8hf7+D1e1DBZxjTc1a4RQqWJixwpYj99ixWzu8VC2m/xXsjvOs
+jp4+DLBB490nbkwvstmhmiWm1CmI5O5xOkgioVNQqHvQMdVKOSz9PpbjvZiRX1Uo
+qoMrk+2NOqwP90TB35yPASXb9zXKpO7DLhkube+yYGf+yk46aD707L07Eb7cosFP
+S84vNZ9gX7rQ0UOwm5rYA/oZTBskgaqhtIzkLwIDAQABAoIBAD4ot/sXt5kRn0Ca
+CIkU9AQWlC+v28grR2EQW9JiaZrqcoDNUzUqbCTJsi4ZkIFh2lf0TsqELbZYNW6Y
+6AjJM7al4E0UqYSKJTv2WCuuRxdiRs2BMwthqyBmjeanev7bB6V0ybt7u3Y8xU/o
+MrTuYnr4vrEjXPKdLirwk7AoDbKsRXHSIiHEIBOq1+dUQ32t36ukdnnza4wKDLZc
+PKHiCdCk/wOGhuDlxD6RspqUAlRnJ8/aEhrgWxadFXw1hRhRsf/v1shtB0T3DmTe
+Jchjwyiw9mryb9JZAcKxW+fUc4EVvj6VdQGqYInQJY5Yxm5JAlVQUJicuuJEvn6A
+rj5osQECgYEA552CaHpUiFlB4HGkjaH00kL+f0+gRF4PANCPk6X3UPDVYzKnzmuu
+yDvIdEETGFWBwoztUrOOKqVvPEQ+kBa2+DWWYaERZLtg2cI5byfDJxQ3ldzilS3J
+1S3WgCojqcsG/hlxoQJ1dZFanUy/QhUZ0B+wlC+Zp1Q8AyuGQvhHp68CgYEA0lBI
+eqq2GGCdJuNHMPFbi8Q0BnX55LW5C1hWjhuYiEkb3hOaIJuJrqvayBlhcQa2cGqp
+uP34e9UCfoeLgmoCQ0b4KpL2NGov/mL4i8bMgog4hcoYuIi3qxN18vVR14VKEh4U
+RLk0igAYPU+IK2QByaQlBo9OSaKkcfm7U1/pK4ECgYAxr6VpGk0GDvfF2Tsusv6d
+GIgV8ZP09qSLTTJvvxvF/lQYeqZq7sjI5aJD5i3de4JhpO/IXQJzfZfWOuGc8XKA
+3qYK/Y2IqXXGYRcHFGWV/Y1LFd55mCADHlk0l1WdOBOg8P5iRu/Br9PbiLpCx9oI
+vrOXpnp03eod1/luZmqguwKBgQCWFRSj9Q7ddpSvG6HCG3ro0qsNsUMTI1tZ7UBX
+SPogx4tLf1GN03D9ZUZLZVFUByZKMtPLX/Hi7K9K/A9ikaPrvsl6GEX6QYzeTGJx
+3Pw0amFrmDzr8ySewNR6/PXahxPEuhJcuI31rPufRRI3ZLah3rFNbRbBFX+klkJH
+zTnoAQKBgDbUK/aQFGduSy7WUT7LlM3UlGxJ2sA90TQh4JRQwzur0ACN5GdYZkqM
+YBts4sBJVwwJoxD9OpbvKu3uKCt41BSj0/KyoBzjT44S2io2tj1syujtlVUsyyBy
+/ca0A7WBB8lD1D7QMIhYUm2O9kYtSCLlUTHt5leqGaRG38DqlX36
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDzDCCArQCCQDSzxzxqhyqLzANBgkqhkiG9w0BAQQFADCBpzELMAkGA1UEBhMC
+VVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxDzANBgNVBAcTBkJvc3RvbjETMBEG
+A1UEChMKTXkgQ29tcGFueTEcMBoGA1UECxMTUHJvZHVjdCBEZXZlbG9wbWVudDEZ
+MBcGA1UEAxMQd3d3Lm5vd2hlcmUubm9uZTEhMB8GCSqGSIb3DQEJARYSYWRtaW5A
+bm93aGVyZS5ub25lMB4XDTA3MDMyMzE4MDc0NVoXDTI2MDUyMjE4MDc0NVowgacx
+CzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMQ8wDQYDVQQHEwZC
+b3N0b24xEzARBgNVBAoTCk15IENvbXBhbnkxHDAaBgNVBAsTE1Byb2R1Y3QgRGV2
+ZWxvcG1lbnQxGTAXBgNVBAMTEHd3dy5ub3doZXJlLm5vbmUxITAfBgkqhkiG9w0B
+CQEWEmFkbWluQG5vd2hlcmUubm9uZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAL5H18W9MHMYK41ds4hYpIyCPs3vIinEwbeOy9BjP3NtXn3pTfs2aSVF
+Fy7uM8/EcqvEPrEIHzcu7kzqTfW1FGjpNU5w+4Gg0J0FojR5qm1tpC/g0jip4CVT
+6PXREwEvIX+/g9XtQwWcY03NWuEUKliYscKWI/fYsVs7vFQtpv8V7I7zrI6ePgyw
+QePdJ25ML7LZoZolptQpiOTucTpIIqFTUKh70DHVSjks/T6W472YkV9VKKqDK5Pt
+jTqsD/dEwd+cjwEl2/c1yqTuwy4ZLm3vsmBn/spOOmg+9Oy9OxG+3KLBT0vOLzWf
+YF+60NFDsJua2AP6GUwbJIGqobSM5C8CAwEAATANBgkqhkiG9w0BAQQFAAOCAQEA
+vGomHEp6TVU83X2EBUgnbOhzKJ9u3fOI/Uf5L7p//Vxqow7OR1cguzh/YEzmXOIL
+ilMVnzX9nj/bvcLAuqEP7MR1A8f4+E807p/L/Sf49BiCcwQq5I966sGKYXjkve+T
+2GTBNwMSq+5kLSf6QY8VZI+qnrAudEQMeJByQhTZZ0dH8Njeq8EGl9KUio+VWaiW
+CQK6xJuAvAHqa06OjLmwu1fYD4GLGSrOIiRVkSXV8qLIUmzxdJaIRznkFWsrCEKR
+wAH966SAOvd2s6yOHMvyDRIL7WHxfESB6rDHsdIW/yny1fBePjv473KrxyXtbz7I
+dMw1yW09l+eEo4A7GzwOdw==
+-----END CERTIFICATE-----
+END
+ chmod 600 $tcert
+ echo "$tcert"
+}
+
+Kecho() {
+ NO_KECHO=1
+ if [ "X$USER" = "Xrunge" -a "X$NO_KECHO" = "X" ]; then
+ echo "dbg: $*"
+ fi
+}
+
+NHAFL_warning() {
+ echo ""
+ echo "** Warning: For the proxy: $proxy"
+ echo "** Warning: the ssh(1) option: $ssh_NHAFL"
+ echo "** Warning: will be used to avoid frequent 'ssh key has changed for localhost'"
+ echo "** Warning: dialogs and connection failures (for example, ssh will exit asking"
+ echo "** Warning: you to manually remove a key from ~/.ssh/known_hosts.)"
+ echo "** Warning: "
+ echo "** Warning: This decreases security: a Man-In-The-Middle attack is possible."
+ echo "** Warning: For chained ssh connections the first ssh leg is secure but the"
+ echo "** Warning: 2nd ssh leg is vulnerable. For an ssh connection going through"
+ echo "** Warning: a HTTP or SOCKS proxy the ssh connection is vulnerable."
+ echo "** Warning: "
+ echo "** Warning: You can set the SSVNC_SSH_LOCALHOST_AUTH=1 env. var. to disable"
+ echo "** Warning: using the NoHostAuthenticationForLocalhost=yes ssh option."
+ echo "** Warning: "
+ echo "** Warning: A better solution is to configure (in the SSVNC GUI) the setting:"
+ echo "** Warning: 'Options -> Advanced -> Private SSH KnownHosts file' (or set"
+ echo "** Warning: SSVNC_KNOWN_HOSTS_FILE directly) to a per-connection known hosts"
+ echo "** Warning: file. That file holds the 'localhost' cert for this specific"
+ echo "** Warning: connection. This yields a both secure and convenient solution."
+ echo ""
+}
+
+space_expand() {
+ str=`echo "$1" | sed -e 's/%SPACE/ /g' -e 's/%TAB/\t/g'`
+ echo "$str"
+}
+
+# handle ssh case:
+#
+if [ "X$use_ssh" = "X1" ]; then
+ #
+ # USING SSH
+ #
+ ssh_port="22"
+ ssh_host="$host"
+ vnc_host="$localhost"
+ ssh_UKHF=""
+ localhost_extra=""
+ # let user override ssh via $SSH
+ ssh=${SSH:-"ssh -x"}
+
+ sshword=`echo "$ssh" | awk '{print $1}'`
+ if [ "X$sshword" != "X" ]; then
+ if [ -x "$sshword" ]; then
+ :
+ elif type "$sshword" > /dev/null 2>&1; then
+ :
+ else
+ echo ""
+ echo "*********************************************************"
+ echo "** Problem finding the SSH command '$sshword': **"
+ echo ""
+ type "$sshword"
+ echo ""
+ echo "** Perhaps you need to install the SSH client package. **"
+ echo "*********************************************************"
+ echo ""
+ sleep 5
+ fi
+ fi
+
+ ssh_NHAFL="-o NoHostAuthenticationForLocalhost=yes"
+ if [ "X$SSVNC_SSH_LOCALHOST_AUTH" = "X1" ]; then
+ ssh_NHAFL=""
+ fi
+ if [ "X$SSVNC_KNOWN_HOSTS_FILE" != "X" ]; then
+ ssh_NHAFL=""
+
+ ssh_UKHF="-o UserKnownHostsFile=$SSVNC_KNOWN_HOSTS_FILE"
+ ssh_args="$ssh_args $ssh_UKHF"
+ if [ ! -f "$SSVNC_KNOWN_HOSTS_FILE" ]; then
+ touch "$SSVNC_KNOWN_HOSTS_FILE" >/dev/null 2>&1
+ fi
+ chmod 600 "$SSVNC_KNOWN_HOSTS_FILE" >/dev/null 2>&1
+ fi
+ did_ssh_NHAFL=""
+
+ if [ "X$SSVNC_LIM_ACCEPT_PRELOAD" != "X" ]; then
+ SSVNC_LIM_ACCEPT_PRELOAD="$SSVNC_BASEDIR/$SSVNC_UNAME/$SSVNC_LIM_ACCEPT_PRELOAD"
+ fi
+ if [ "X$SSVNC_LIM_ACCEPT_PRELOAD" != "X" ]; then
+ echo ""
+ echo "SSVNC_LIM_ACCEPT_PRELOAD=$SSVNC_LIM_ACCEPT_PRELOAD"
+ fi
+
+ if [ "X$SSVNC_LIM_ACCEPT_PRELOAD" != "X" -a -f "$SSVNC_LIM_ACCEPT_PRELOAD" ]; then
+ plvar=LD_PRELOAD
+ if uname | grep Darwin >/dev/null; then
+ plvar="DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES"
+ fi
+ ssh="env $plvar=$SSVNC_LIM_ACCEPT_PRELOAD $ssh"
+ else
+ SSVNC_LIM_ACCEPT_PRELOAD=""
+ fi
+
+ ssh_vencrypt_proxy=""
+ # We handle vencrypt for SSH+SSL mode.
+ if echo "$proxy" | grep 'vencrypt://' > /dev/null; then
+ proxynew=""
+ for part in `echo "$proxy" | tr ',' ' '`
+ do
+ if echo "$part" | egrep -i '^vencrypt://' > /dev/null; then
+ ssh_vencrypt_proxy=$part
+ else
+ if [ "X$proxynew" = "X" ]; then
+ proxynew="$part"
+ else
+ proxynew="$proxynew,$part"
+ fi
+ fi
+ done
+ proxy=$proxynew
+ fi
+ Kecho ssh_vencrypt_proxy=$ssh_vencrypt_proxy
+
+ # note that user must supply http:// for web proxy in SSH and SSH+SSL.
+ # No xxxx:// implies ssh server+port.
+ #
+ if echo "$proxy" | egrep '(http|https|socks|socks4|socks5)://' > /dev/null; then
+ # Handle Web or SOCKS proxy(ies) for the initial connect.
+ Kecho host=$host
+ Kecho port=$port
+ pproxy=""
+ sproxy1=""
+ sproxy_rest=""
+ for part in `echo "$proxy" | tr ',' ' '`
+ do
+ Kecho proxy_part=$part
+ if [ "X$part" = "X" ]; then
+ continue
+ elif echo "$part" | egrep -i '^(http|https|socks|socks4|socks5)://' > /dev/null; then
+ pproxy="$pproxy,$part"
+ else
+ if [ "X$sproxy1" = "X" ]; then
+ sproxy1="$part"
+ else
+ sproxy_rest="$sproxy_rest,$part"
+ fi
+ fi
+ done
+ pproxy=`echo "$pproxy" | sed -e 's/^,,*//' -e 's/,,*/,/g'`
+ sproxy_rest=`echo "$sproxy_rest" | sed -e 's/^,,*//' -e 's/,,*/,/g'`
+
+ Kecho pproxy=$pproxy
+ Kecho sproxy1=$sproxy1
+ Kecho sproxy_rest=$sproxy_rest
+
+ sproxy1_host=""
+ sproxy1_port=""
+ sproxy1_user=""
+
+ if [ "X$sproxy1" != "X" ]; then
+ # XXX fix ipv6 ip adder here and below.
+ sproxy1_host=`echo "$sproxy1" | awk -F: '{print $1}'`
+ sproxy1_user=`echo "$sproxy1_host" | awk -F@ '{print $1}'`
+ sproxy1_host=`echo "$sproxy1_host" | awk -F@ '{print $2}'`
+ if [ "X$sproxy1_host" = "X" ]; then
+ sproxy1_host=$sproxy1_user
+ sproxy1_user=""
+ else
+ sproxy1_user="${sproxy1_user}@"
+ fi
+ sproxy1_port=`echo "$sproxy1" | awk -F: '{print $2}'`
+ if [ "X$sproxy1_port" = "X" ]; then
+ sproxy1_port="22"
+ fi
+ else
+ sproxy1_host=`echo "$host" | awk -F: '{print $1}'`
+ sproxy1_user=`echo "$sproxy1_host" | awk -F@ '{print $1}'`
+ sproxy1_host=`echo "$sproxy1_host" | awk -F@ '{print $2}'`
+ if [ "X$sproxy1_host" = "X" ]; then
+ sproxy1_host=$sproxy1_user
+ sproxy1_user=""
+ else
+ sproxy1_user="${sproxy1_user}@"
+ fi
+ sproxy1_port=`echo "$host" | awk -F: '{print $2}'`
+ if [ "X$sproxy1_port" = "X" ]; then
+ sproxy1_port="22"
+ fi
+ fi
+
+ Kecho sproxy1_host=$sproxy1_host
+ Kecho sproxy1_port=$sproxy1_port
+ Kecho sproxy1_user=$sproxy1_user
+
+ ptmp="/tmp/ss_vncviewer_ssh${RANDOM}.$$.pl"
+ ptmp=`mytmp "$ptmp"`
+ PPROXY_REMOVE=1; export PPROXY_REMOVE
+ proxy=$pproxy
+ port_save=$port
+ host_save=$host
+ if [ "X$sproxy1_host" != "X" ]; then
+ host=$sproxy1_host
+ fi
+ if [ "X$sproxy1_port" != "X" ]; then
+ port=$sproxy1_port
+ fi
+ host=`echo "$host" | sed -e 's/^.*@//'`
+ port=`echo "$port" | sed -e 's/^.*://'`
+ pcode "$ptmp"
+ port=$port_save
+ host=$host_save
+
+ nd=`findfree 6600`
+ PPROXY_LISTEN=$nd; export PPROXY_LISTEN
+ # XXX no reverse forever PPROXY_LOOP_THYSELF ...
+ $ptmp &
+ sleep 1
+ if [ "X$ssh_NHAFL" != "X" -a "X$did_ssh_NHAFL" != "X1" ]; then
+ NHAFL_warning
+ ssh_args="$ssh_args $ssh_NHAFL"
+ did_ssh_NHAFL=1
+ fi
+ sleep 1
+ if [ "X$sproxy1" = "X" ]; then
+ u=""
+ if echo "$host" | grep '@' > /dev/null; then
+ u=`echo "$host" | sed -e 's/@.*$/@/'`
+ fi
+
+ proxy="${u}$localhost:$nd"
+ else
+ proxy="${sproxy1_user}$localhost:$nd"
+ fi
+ localhost_extra=".2"
+ if [ "X$sproxy_rest" != "X" ]; then
+ proxy="$proxy,$sproxy_rest"
+ fi
+ Kecho proxy=$proxy
+ fi
+
+ if echo "$proxy" | grep "," > /dev/null; then
+
+ proxy1=`echo "$proxy" | awk -F, '{print $1}'`
+ proxy2=`echo "$proxy" | awk -F, '{print $2}'`
+
+ # user1@gw1.com:port1,user2@ws2:port2
+ ssh_host1=`echo "$proxy1" | awk -F: '{print $1}'`
+ ssh_port1=`echo "$proxy1" | awk -F: '{print $2}'`
+ if [ "X$ssh_port1" != "X" ]; then
+ ssh_port1="-p $ssh_port1"
+ fi
+ ssh_host2=`echo "$proxy2" | awk -F: '{print $1}'`
+ ssh_user2=`echo "$ssh_host2" | awk -F@ '{print $1}'`
+ ssh_host2=`echo "$ssh_host2" | awk -F@ '{print $2}'`
+ if [ "X$ssh_host2" = "X" ]; then
+ ssh_host2=$ssh_user2
+ ssh_user2=""
+ else
+ ssh_user2="${ssh_user2}@"
+ fi
+ ssh_port2=`echo "$proxy2" | awk -F: '{print $2}'`
+ if [ "X$ssh_port2" = "X" ]; then
+ ssh_port2="22"
+ fi
+ proxport=`findfree 3500`
+ if [ "X$ssh_NHAFL" != "X" -a "X$did_ssh_NHAFL" != "X1" ]; then
+ NHAFL_warning
+ did_ssh_NHAFL=1
+ sleep 1
+ fi
+ echo
+ echo "Running 1st ssh proxy:"
+ ukhf=""
+ if [ "X$ssh_UKHF" != "X" ]; then
+ ukhf="$ssh_UKHF$localhost_extra"
+ fi
+ if echo "$ssh_host1" | grep '%' > /dev/null; then
+ uath=`space_expand "$ssh_host1"`
+ else
+ uath="$ssh_host1"
+ fi
+ echo "$ssh -f -x $ssh_port1 $targ -e none $ssh_NHAFL $ukhf -L $proxport:$ssh_host2:$ssh_port2 \"$uath\" \"sleep 30\""
+ echo ""
+ $ssh -f -x $ssh_port1 $targ -e none $ssh_NHAFL $ukhf -L $proxport:$ssh_host2:$ssh_port2 "$uath" "sleep 30"
+ ssh_args="$ssh_args $ssh_NHAFL"
+ sleep 1
+ stty sane
+ proxy="${ssh_user2}$localhost:$proxport"
+ fi
+
+ if [ "X$proxy" != "X" ]; then
+ ssh_port=`echo "$proxy" | awk -F: '{print $2}'`
+ if [ "X$ssh_port" = "X" ]; then
+ ssh_port="22"
+ fi
+ ssh_host=`echo "$proxy" | awk -F: '{print $1}'`
+ vnc_host="$host"
+ fi
+
+ echo ""
+ echo "Running ssh:"
+ sz=`echo "$ssh_cmd" | wc -c`
+ if [ "$sz" -gt 300 ]; then
+ info="..."
+ else
+ info="$ssh_cmd"
+ fi
+
+ C=""
+ if [ "X$SS_VNCVIEWER_USE_C" != "X" ]; then
+ C="-C"
+ fi
+
+ getport=""
+ teeport=""
+ if echo "$ssh_cmd" | egrep "(PORT=|P=) " > /dev/null; then
+ getport=1
+ if echo "$ssh_cmd" | egrep "P= " > /dev/null; then
+ teeport=1
+ fi
+
+ PORT=""
+ ssh_cmd=`echo "$ssh_cmd" | sed -e 's/PORT=[ ]*//' -e 's/P=//'`
+ SSVNC_NO_ENC_WARN=1
+ if [ "X$use_sshssl" = "X" ]; then
+ direct_connect=1
+ fi
+ fi
+ if [ "X$getport" != "X" ]; then
+ ssh_redir="-D ${use}"
+ elif [ "X$reverse" = "X" ]; then
+ ssh_redir="-L ${use}:${vnc_host}:${port}"
+ else
+ ssh_redir="-R ${port}:${vnc_host}:${use}"
+ fi
+ pmark=`sh -c 'echo $$'`
+
+ # the -t option actually speeds up typing response via VNC!!
+ if [ "X$ssh_port" = "X22" ]; then
+ ssh_port=""
+ else
+ ssh_port="-p $ssh_port"
+ fi
+
+ if echo "$ssh_host" | grep '%' > /dev/null; then
+ uath=`space_expand "$ssh_host"`
+ else
+ uath="$ssh_host"
+ fi
+ if [ "X$SS_VNCVIEWER_SSH_ONLY" != "X" ]; then
+ echo "$ssh -x $ssh_port $targ $C $ssh_args \"$uath\" \"$info\""
+ echo ""
+ $ssh -x $ssh_port $targ $C $ssh_args "$uath" "$ssh_cmd"
+ exit $?
+
+ elif [ "X$SS_VNCVIEWER_NO_F" != "X" ]; then
+ echo "$ssh -x $ssh_port $targ $C $ssh_redir $ssh_args \"$uath\" \"$info\""
+ echo ""
+ $ssh -x $ssh_port $targ $C $ssh_redir $ssh_args "$uath" "$ssh_cmd"
+ rc=$?
+
+ elif [ "X$getport" != "X" ]; then
+ tport=/tmp/ss_vncviewer_tport${RANDOM}.$$
+ tport=`mytmp "$tport"`
+ tport2=/tmp/ss_vncviewer_tport2${RANDOM}.$$
+ tport2=`mytmp "$tport2"`
+
+ if [ "X$rsh" != "X1" ]; then
+ if echo "$ssh_cmd" | grep "sudo " > /dev/null; then
+ echo ""
+ echo "Initial ssh with 'sudo id' to prime sudo so hopefully the next one"
+ echo "will require no password..."
+ echo ""
+ targ="-t"
+ $ssh -x $ssh_port $targ $ssh_args "$uath" "sudo id; tty"
+ echo ""
+ fi
+ echo "$ssh -x -f $ssh_port $targ $C $ssh_redir $ssh_args \"$uath\" \"$info\""
+ echo ""
+ $ssh -x -f $ssh_port $targ $C $ssh_redir $ssh_args "$uath" "$ssh_cmd" > $tport 2> $tport2
+ if [ "X$teeport" = "X1" ]; then
+ tail -f $tport 1>&2 &
+ tail_pid=$!
+ tail -f $tport2 1>&2 &
+ tail_pid2=$!
+ fi
+ rc=$?
+ else
+ rsh_setup
+ echo "rsh $ul \"$ssh_host\" \"$ssh_cmd\""
+ echo ""
+ rsh $ul "$ssh_host" "$ssh_cmd" > $tport &
+ sleep 1
+ rc=0
+ fi
+
+ if [ "X$SSVNC_EXTRA_SLEEP" != "X" ]; then
+ echo "sleep $SSVNC_EXTRA_SLEEP"
+ sleep $SSVNC_EXTRA_SLEEP
+ fi
+
+ stty sane
+ i=0
+ if type perl > /dev/null 2>&1; then
+ imax=50
+ sleepit="perl -e 'select(undef, undef, undef, 0.20)'"
+ else
+ imax=10
+ sleepit="sleep 1"
+ fi
+ while [ $i -lt $imax ]; do
+ #echo $sleepit
+ eval $sleepit
+ PORT=`grep "^PORT=" $tport | tr '\r' ' ' | head -n 1 | sed -e 's/PORT=//' -e 's/\r//g' -e 's/ *$//'`
+ if echo "$PORT" | grep '^[0-9][0-9]*$' > /dev/null; then
+ break
+ fi
+ vnss=`sed -e 's/\r//g' $tport $tport2 | egrep -i '^(New.* desktop is|A VNC server is already running).*:[0-9[0-9]*$' | head -n 1 | awk '{print $NF}'`
+ if [ "X$vnss" != "X" ]; then
+ PORT=`echo "$vnss" | awk -F: '{print $2}'`
+ if echo "$PORT" | grep '^[0-9][0-9]*$' > /dev/null; then
+ if [ $PORT -lt 100 ]; then
+ PORT=`expr $PORT + 5900`
+ fi
+ fi
+ if echo "$PORT" | grep '^[0-9][0-9]*$' > /dev/null; then
+ vnss=`sed -e 's/\r//g' $tport | egrep -i '^(New.* desktop is|A VNC server is already running).*:[0-9[0-9]*$' | head -n 1`
+ echo "vncserver string: $vnss" 1>&2
+ break
+ fi
+ fi
+ i=`expr $i + 1`
+ done
+
+ echo "found: PORT='$PORT'" 1>&2
+ lh6=""
+ if [ "X$SSVNC_PORT_IPV6" != "X" ]; then
+ lh6=1
+ elif egrep 'Info: listening on IPv6 only|Info: listening only on IPv6' $tport > /dev/null; then
+ lh6=1
+ fi
+ if [ "X$lh6" = "X1" ]; then
+ echo "set SOCKS5 localhost to ::1" 1>&2
+ fi
+ rm -f $tport $tport2
+ if [ "X$rsh" = "X1" ]; then
+ rsh_viewer "$@"
+ exit $?
+ fi
+ PPROXY_SOCKS=5
+ if [ "X$SSVNC_SOCKS5" != "X" ]; then
+ PPROXY_SOCKS=5
+ elif [ "X$SSVNC_SOCKS4" != "X" ]; then
+ PPROXY_SOCKS=1
+ fi
+ export PPROXY_SOCKS
+ if [ "X$lh6" = "X" ]; then
+ host="$localhost"
+ else
+ host="::1"
+ fi
+ port="$PORT"
+ proxy="$localhost:$use"
+
+ else
+ if [ "X$rsh" != "X1" ]; then
+ echo "$ssh -x -f $ssh_port $targ $C $ssh_redir $ssh_args \"$uath\" \"$info\""
+ echo ""
+ $ssh -x -f $ssh_port $targ $C $ssh_redir $ssh_args "$uath" "$ssh_cmd"
+ rc=$?
+ else
+ rsh_setup
+ echo "rsh $ul \"$ssh_host\" \"$ssh_cmd\""
+ echo ""
+ rsh $ul "$ssh_host" "$ssh_cmd" &
+ sleep 1
+ PORT=$port
+ rsh_viewer "$@"
+ exit $?
+ fi
+ fi
+
+ if [ "$rc" != "0" ]; then
+ echo ""
+ echo "ssh to \"$uath\" failed."
+ exit 1
+ fi
+ stty sane
+
+ c=0
+ pssh=""
+ while [ $c -lt 40 ]
+ do
+ p=`expr $pmark + $c`
+ pout=`ps -p "$p" 2>/dev/null | grep -v '^[ ]*PID' | sed -e 's/-L.*$//' -e 's/-x .*$//'`
+ if echo "$pout" | grep "ssh" > /dev/null; then
+ if echo "$pout" | egrep -i 'ssh.*(-add|-agent|-ask|-keygen|-argv0|vnc)' >/dev/null; then
+ :
+ elif echo "$pout" | egrep -i 'scp|sshd' >/dev/null; then
+ :
+ else
+ pssh=$p
+ break
+ fi
+ fi
+ c=`expr $c + 1`
+ done
+ if [ "X$getport" != "X" ]; then
+ :
+ elif [ "X$SSVNC_LIM_ACCEPT_PRELOAD" != "X" ] ; then
+ sleep 2
+ elif [ "X$ssh_cmd" = "Xsleep $ssh_sleep" ] ; then
+ #echo T sleep 1
+ sleep 1
+ elif echo "$ssh_cmd" | grep '^sleep ' >/dev/null; then
+ #echo T sleep 2
+ sleep 2
+ else
+ # let any command get started a bit.
+ #echo T sleep 5
+ sleep 5
+ fi
+ echo ""
+ #reset
+ stty sane
+ if [ "X$SSVNC_EXTRA_SLEEP" != "X" ]; then
+ echo "sleep $SSVNC_EXTRA_SLEEP"
+ sleep $SSVNC_EXTRA_SLEEP
+ fi
+ echo "ssh_pid='$pssh'"; echo
+ if [ "X$use_sshssl" = "X" -a "X$getport" = "X" ]; then
+ if [ "X$SSVNC_EXTRA_COMMAND" != "X" ]; then
+ (sh -c "$SSVNC_EXTRA_COMMAND") &
+ echo "($SSVNC_EXTRA_COMMAND) &"; echo
+ fi
+ echo "Running viewer:"
+
+ trap "final" 0 2 15
+ if [ "X$reverse" = "X" ]; then
+ echo "$VNCVIEWERCMD" "$@" $localhost:$N
+ echo ""
+ $VNCVIEWERCMD "$@" $localhost:$N
+ if [ $? != 0 ]; then
+ echo "vncviewer command failed: $?"
+ if [ "X$secondtry" = "X1" ]; then
+ sleep 2
+ $VNCVIEWERCMD "$@" $localhost:$N
+ fi
+ fi
+ else
+ echo ""
+ echo "NOTE: Press Ctrl-C to terminate viewer LISTEN mode."
+ echo ""
+ N2=$N
+ if [ "X$VNCVIEWER_IS_REALVNC4" = "X1" ]; then
+ N2=`echo "$N2" | sed -e 's/://g'`
+ if [ $N2 -le 200 ]; then
+ N2=`expr $N2 + 5500`
+ fi
+ fi
+ echo "$VNCVIEWERCMD" "$@" -listen $N2
+ echo ""
+ $VNCVIEWERCMD "$@" -listen $N2
+ fi
+
+ exit $?
+ else
+ use2=`findfree 5960`
+ host0=$host
+ port0=$port
+ host=$localhost
+ port=$use
+ use=$use2
+ N=`expr $use - 5900`
+ if [ "X$getport" != "X" ]; then
+ host="$host0"
+ port="$port0"
+ else
+ proxy=""
+ fi
+ if [ "X$ssh_vencrypt_proxy" != "X" ]; then
+ ssh_vencrypt_proxy="vencrypt://$host:$port"
+ if [ "X$proxy" = "X" ]; then
+ proxy=$ssh_vencrypt_proxy
+ else
+ proxy="$proxy,$ssh_vencrypt_proxy"
+ fi
+ Kecho "proxy_now=$proxy"
+ unset PPROXY_LISTEN
+ fi
+ fi
+fi
+
+if [ "X$stunnel_set_here" = "X1" -a "X$showcert" = "X" ]; then
+ if type $STUNNEL > /dev/null 2>&1; then
+ :
+ else
+ echo ""
+ echo "***************************************************************"
+ echo "** Problem finding the Stunnel command '$STUNNEL': **"
+ echo ""
+ type $STUNNEL
+ echo ""
+ echo "** Perhaps you need to install the stunnel/stunnel4 package. **"
+ echo "***************************************************************"
+ echo ""
+ sleep 5
+ fi
+fi
+
+# create the stunnel config file:
+if [ "X$verify" != "X" ]; then
+ if [ -d $verify ]; then
+ verify="CApath = $verify"
+ else
+ verify="CAfile = $verify"
+ fi
+ verify="$verify
+verify = 2"
+fi
+if [ "X$SSVNC_STUNNEL_VERIFY3" != "X" ]; then
+ verify=`echo "$verify" | sed -e 's/verify = 2/verify = 3/'`
+fi
+if [ "X$mycert" != "X" ]; then
+ cert="cert = $mycert"
+fi
+if [ "X$crl" != "X" ]; then
+ if [ -d $crl ]; then
+ crl="CRLpath = $crl"
+ else
+ crl="CRLfile = $crl"
+ fi
+fi
+
+if [ "X$showcert" = "X1" ]; then
+ if [ "X$have_uvnc_dsm_helper_showcert" = "X1" ]; then
+ :
+ elif [ "X$SSVNC_NO_IPV6_PROXY" != "X" ]; then
+ :
+ elif [ "X$ipv6" = "X1" -a "X$proxy" = "X" ]; then
+ proxy="ipv6://$host:$port"
+ fi
+fi
+
+if [ "X$direct_connect" != "X" -a "X$STUNNEL_LISTEN" != "X" ]; then
+ proxy=reverse_direct
+fi
+
+ptmp=""
+if [ "X$proxy" != "X" ]; then
+ ptmp="/tmp/ss_vncviewer${RANDOM}.$$.pl"
+ ptmp=`mytmp "$ptmp"`
+ PPROXY_REMOVE=1; export PPROXY_REMOVE
+ pcode "$ptmp"
+ if [ "X$showcert" != "X1" -a "X$direct_connect" = "X" ]; then
+ if uname | egrep 'Darwin|SunOS' >/dev/null; then
+ vout=`echo "$proxy" | grep -i vencrypt`
+ if [ "X$vout" != "X" -a "X$reverse" = "X1" ]; then
+ # need to exec for reverse vencrypt
+ connect="exec = $ptmp"
+ else
+ # on mac and solaris we need to listen on socket instead of stdio:
+ nd=`findfree 6700`
+ PPROXY_LISTEN=$nd
+ export PPROXY_LISTEN
+ if [ "X$reverse" = "X" ]; then
+ $ptmp &
+ fi
+ sleep 2
+ host="$localhost"
+ port="$nd"
+ connect="connect = $localhost:$nd"
+ fi
+ else
+ # otherwise on unix we can exec it:
+ connect="exec = $ptmp"
+ fi
+ else
+ connect="exec = $ptmp"
+ fi
+else
+ connect="connect = $host:$port"
+fi
+
+# handle showcert case:
+#
+if [ "X$showcert" = "X1" ]; then
+ if [ "X$proxy" != "X" ]; then
+ PPROXY_LISTEN=$use
+ export PPROXY_LISTEN
+ if [ "X$SS_DEBUG" != "X" ]; then
+ $ptmp &
+ else
+ $ptmp 2>/dev/null &
+ fi
+ sleep 1
+ more_sleep=1
+ if uname | grep Linux > /dev/null; then
+ if netstat -ant | grep LISTEN | grep "127.0.0.1:$use" > /dev/null; then
+ more_sleep=""
+ fi
+ elif uname | grep SunOS > /dev/null; then
+ if netstat -an -f inet -P tcp | grep LISTEN | grep "127.0.0.1.$use" > /dev/null; then
+ more_sleep=""
+ fi
+ elif uname | egrep -i 'bsd|darwin' > /dev/null; then
+ if netstat -ant -f inet | grep LISTEN | grep "127.0.0.1.$use" > /dev/null; then
+ more_sleep=""
+ fi
+ fi
+ if [ "X$more_sleep" = "X1" ]; then
+ sleep 1
+ fi
+ host="$localhost"
+ port="$use"
+ fi
+ cipher_args=""
+ if [ "X$ciphers" != "X" ]; then
+ cipher_args=`echo "$ciphers" | sed -e 's/ciphers=/-cipher /'`
+ fi
+ if [ "X$have_uvnc_dsm_helper_showcert" = "X1" ]; then
+ :
+ elif type openssl > /dev/null 2>&1; then
+ :
+ else
+ echo ""
+ echo "********************************************************"
+ echo "** Problem finding the OpenSSL command 'openssl': **"
+ echo ""
+ type openssl 2>&1
+ echo ""
+ echo "** Perhaps you need to install the 'openssl' package. **"
+ echo "********************************************************"
+ echo ""
+ fi
+ #echo "openssl s_client $cipher_args -connect $host:$port"
+ if [ "X$reverse" = "X" ]; then
+ if type host > /dev/null 2>/dev/null; then
+ host $host >/dev/null 2>&1
+ host $host >/dev/null 2>&1
+ fi
+ timeout=15
+ if [ "X$SSVNC_FETCH_TIMEOUT" != "X" ]; then
+ timeout=$SSVNC_FETCH_TIMEOUT
+ fi
+ if [ "X$have_uvnc_dsm_helper_showcert" = "X1" ]; then
+ if type pkill >/dev/null 2>&1; then
+ (sleep $timeout; if kill -0 $$; then pkill -TERM -f "ultravnc_dsm_helper.*$host.*$port"; fi) >/dev/null 2>&1 &
+ fi
+ ultravnc_dsm_helper showcert $host:$port 2>&1
+ else
+ if type pkill >/dev/null 2>&1; then
+ (sleep $timeout; if kill -0 $$; then pkill -TERM -f "openssl.*s_client.*$host.*$port"; fi) >/dev/null 2>&1 &
+ fi
+ openssl s_client $cipher_args -prexit -connect $host:$port 2>&1 < /dev/null
+ fi
+ rc=$?
+ else
+ tcert=""
+ if [ "X$mycert" = "X" ]; then
+ tcert=`make_tcert`
+ cert_args="-cert $tcert -CAfile $tcert"
+ else
+ cert_args="-cert $mycert -CAfile $mycert"
+ fi
+ tmp_out=/tmp/showcert_out${RANDOM}.$$
+ tmp_out=`mytmp "$tmp_out"`
+ tmp_err=/tmp/showcert_err${RANDOM}.$$
+ tmp_err=`mytmp "$tmp_err"`
+
+ #echo "openssl s_server $cipher_args $cert_args -accept $port -verify 2 > $tmp_out 2> $tmp_err" 1>&2
+
+ # assume we have perl:
+ check_perl perl
+
+ perl -e "
+ \$p = open(O, \"|openssl s_server $cipher_args $cert_args -accept $port -verify 2 1>$tmp_out 2> $tmp_err\");
+ exit 1 unless \$p;
+ while (1) {
+ sleep 1;
+ if (!open(F, \"<$tmp_out\")) {
+ kill \$p;
+ exit 1;
+ }
+ while (<F>) {
+ if (/RFB 00/) {
+ fsleep(0.25);
+ print O \"RFB 000.000\\n\";
+ fsleep(1.00);
+ kill \$p;
+ fsleep(0.25);
+ exit 0;
+ }
+ }
+ close F;
+ }
+ sub fsleep {
+ select(undef, undef, undef, shift);
+ }
+ ";
+
+ echo ""
+ cat $tmp_out
+ echo ""
+ echo "----2----"
+ cat $tmp_err
+ if grep BEGIN.CERTIFICATE $tmp_out >/dev/null; then
+ rc=0
+ else
+ rc=1
+ fi
+
+ rm -f $tmp_out $tmp_err
+ fi
+ if [ "X$SSVNC_PREDIGESTED_HANDSHAKE" != "X" ]; then
+ rm -f $SSVNC_PREDIGESTED_HANDSHAKE
+ fi
+ if [ "X$SSVNC_SHOWCERT_EXIT_0" = "X1" ]; then
+ exit 0
+ else
+ exit $rc
+ fi
+fi
+
+# handle direct connect case:
+#
+if [ "X$direct_connect" != "X" ]; then
+ if [ "X$SSVNC_ULTRA_DSM" != "X" ]; then
+ SSVNC_NO_ENC_WARN=1
+ echo ""
+ echo "Using UltraVNC DSM Plugin key for encryption:"
+ echo ""
+ ustr=`echo "$SSVNC_ULTRA_DSM" | sed -e 's/pw=[^ ]*/pw=******/g'`
+ echo " $ustr PORT HOST:PORT"
+ echo ""
+ elif [ "X$getport" = "X" ]; then
+ echo ""
+ echo "Running viewer for direct connection:"
+ if echo X"$@" | grep chatonly > /dev/null; then
+ :
+ else
+ echo ""
+ echo "** WARNING: THERE WILL BE NO SSL OR SSH ENCRYPTION **"
+ echo ""
+ fi
+ fi
+ x=""
+ if [ "X$SSVNC_NO_ENC_WARN" != "X" ]; then
+ if [ "X$getport" = "X" ]; then
+ sleep 1
+ fi
+ elif type printf > /dev/null 2>&1; then
+ printf "Are you sure you want to continue? [y]/n "
+ read x
+ else
+ echo -n "Are you sure you want to continue? [y]/n "
+ read x
+ fi
+ if [ "X$x" = "Xn" ]; then
+ exit 1
+ fi
+ echo ""
+ if [ "X$ptmp" != "X" ]; then
+ if [ "X$reverse" = "X" ]; then
+ PPROXY_LISTEN=$use
+ export PPROXY_LISTEN
+ else
+ if [ "X$proxy" = "Xreverse_direct" ]; then
+ PPROXY_LISTEN="$STUNNEL_LISTEN:`expr 5500 + $disp`"
+ PPROXY_DEST="$localhost:$use"
+ PPROXY_PROXY="ipv6://$localhost:$use" # not always ipv6..
+ export PPROXY_LISTEN PPROXY_DEST PPROXY_PROXY
+ pps=1
+ else
+ PPROXY_REVERSE="$localhost:$use"
+ export PPROXY_LISTEN
+ pps=3
+ fi
+ if [ "X$SSVNC_LISTEN_ONCE" != "X1" ]; then
+ PPROXY_LOOP_THYSELF=`mytmp "/tmp/pproxy_loop_thyself.${RANDOM}.$$"`
+ export PPROXY_LOOP_THYSELF
+ pps=2
+ fi
+ if [ "X$SSVNC_EXTRA_SLEEP" != "X" ]; then
+ pps=`expr $pps + $SSVNC_EXTRA_SLEEP`
+ fi
+ PPROXY_SLEEP=$pps; export PPROXY_SLEEP;
+ PPROXY_KILLPID=+1; export PPROXY_KILLPID;
+ fi
+
+ $ptmp &
+
+ if [ "X$reverse" = "X" ]; then
+ #sleep 2
+ #echo T sleep 1
+ sleep 1
+ fi
+ host="$localhost"
+ disp="$N"
+ port=`expr $disp + 5900`
+ fi
+ if [ "X$SSVNC_EXTRA_SLEEP" != "X" ]; then
+ echo "T sleep $SSVNC_EXTRA_SLEEP"
+ sleep $SSVNC_EXTRA_SLEEP
+ fi
+ if [ "X$SSVNC_EXTRA_COMMAND" != "X" ]; then
+ (sh -c "$SSVNC_EXTRA_COMMAND") &
+ echo "($SSVNC_EXTRA_COMMAND) &"; echo
+ fi
+ if [ "X$reverse" = "X" ]; then
+ hostdisp="$host:$disp"
+ if [ "X$SSVNC_ULTRA_DSM" != "X" ]; then
+ if [ "X$SSVNC_USE_OURS" = "X1" ]; then
+ hostdisp="exec=$SSVNC_ULTRA_DSM 0 $host:$port"
+ else
+ pf=`findfree 5970`
+ cmd="$SSVNC_ULTRA_DSM -$pf $host:$port"
+ pf=`expr $pf - 5900`
+ hostdisp="$localhost:$pf"
+ ustr=`echo "$cmd" | sed -e 's/pw=[^ ]*/pw=******/g'`
+ echo "Running:"
+ echo
+ echo "$ustr &"
+ echo
+ $cmd &
+ dsm_pid=$!
+ sleep 2
+ fi
+ fi
+ hostdisp2=`echo "$hostdisp" | sed -e 's/pw=[^ ]*/pw=******/g'`
+ echo "$VNCVIEWERCMD" "$@" "$hostdisp2"
+ trap "final" 0 2 15
+ echo ""
+ $VNCVIEWERCMD "$@" "$hostdisp"
+ if [ $? != 0 ]; then
+ echo "vncviewer command failed: $?"
+ if [ "X$secondtry" = "X1" ]; then
+ sleep 2
+ $VNCVIEWERCMD "$@" "$hostdisp"
+ fi
+ fi
+ else
+ echo ""
+ echo "NOTE: Press Ctrl-C to terminate viewer LISTEN mode."
+ echo ""
+ trap "final" 0 2 15
+ if [ "X$SSVNC_ULTRA_DSM" != "X" ]; then
+ if [ "X$SSVNC_LISTEN_ONCE" = "X1" ]; then
+ echo "NOTE: The ultravnc_dsm_helper only runs once. So after the first LISTEN"
+ echo " ends you must restart the Listening mode. You may also need to"
+ echo " Press Ctrl-C to stop the viewer and restart for another connection."
+ echo ""
+ fi
+ #SSVNC_LISTEN_ONCE=1; export SSVNC_LISTEN_ONCE
+ VNCVIEWER_LISTEN_LOCALHOST=1
+ export VNCVIEWER_LISTEN_LOCALHOST
+ dport=`expr 5500 + $disp`
+ cmd="$SSVNC_ULTRA_DSM $dport $localhost:$use"
+ ustr=`echo "$cmd" | sed -e 's/pw=[^ ]*/pw=******/g'`
+ echo "Running:"
+ echo
+ echo "$ustr &"
+ echo
+ if [ "X$SSVNC_LISTEN_ONCE" = "X1" ]; then
+ $cmd &
+ dsm_pid=$!
+ else
+ while [ 1 ]; do $cmd; sleep 1; done &
+ dsm_pid=$!
+ fi
+ sleep 2
+ disp=$use
+ if [ $disp -ge 5500 ]; then
+ disp=`expr $disp - 5500`
+ fi
+ fi
+ disp2=$disp
+ if [ "X$VNCVIEWER_IS_REALVNC4" = "X1" ]; then
+ disp2=`echo "$disp2" | sed -e 's/://g'`
+ if [ $disp2 -le 200 ]; then
+ disp2=`expr $disp2 + 5500`
+ fi
+ fi
+ echo "$VNCVIEWERCMD" "$@" -listen $disp2
+ echo ""
+ $VNCVIEWERCMD "$@" -listen $disp2
+ if [ "X$PPROXY_LOOP_THYSELF" != "X" ]; then
+ rm -f $PPROXY_LOOP_THYSELF
+ fi
+ fi
+ exit $?
+fi
+
+tmp_cfg=/tmp/ss_vncviewer${RANDOM}.$$
+tmp_cfg=`mytmp "$tmp_cfg"`
+
+stunnel_exec=""
+if [ "X$SSVNC_USE_OURS" != "X1" ]; then
+ :
+elif echo $STUNNEL_EXTRA_SVC_OPTS | grep '#stunnel-exec' > /dev/null; then
+ stunnel_exec="#"
+fi
+
+if [ "X$reverse" = "X" ]; then
+
+ if echo "$proxy" | grep "^repeater://" > /dev/null; then
+ if [ "X$cert" = "XBUILTIN" ]; then
+ ttcert=`make_tcert`
+ cert="cert = $ttcert"
+ fi
+ # Note for listen mode, an empty cert will cause stunnel to fail.
+ # The ssvnc gui will have already taken care of this.
+ fi
+
+ cat > "$tmp_cfg" <<END
+foreground = yes
+pid =
+client = yes
+debug = $stunnel_debug
+$ciphers
+$STUNNEL_EXTRA_OPTS
+$STUNNEL_EXTRA_OPTS_USER
+$cert
+$crl
+$verify
+
+${stunnel_exec}[vnc_stunnel]
+${stunnel_exec}accept = $localhost:$use
+$connect
+$STUNNEL_EXTRA_SVC_OPTS
+$STUNNEL_EXTRA_SVC_OPTS_USER
+
+END
+
+else
+ # REVERSE case:
+
+ stunnel_exec="" # doesn't work for listening.
+
+ p2=`expr 5500 + $N`
+ connect="connect = $localhost:$p2"
+ if [ "X$cert" = "XBUILTIN" ]; then
+ ttcert=`make_tcert`
+ cert="cert = $ttcert"
+ fi
+ # Note for listen mode, an empty cert will cause stunnel to fail.
+ # The ssvnc gui will have already taken care of this.
+
+
+ hloc=""
+ if [ "X$use_ssh" = "X1" ]; then
+ hloc="$localhost:"
+ elif [ "X$STUNNEL_LISTEN" != "X" ]; then
+ hloc="$STUNNEL_LISTEN:"
+ fi
+ if echo "$proxy" | grep -i '^vencrypt:' > /dev/null; then
+ hloc="$localhost:"
+ pv=`findfree 5570`
+ proxy="vencrypt:$pv:$port"
+ port=$pv
+ if [ "X$anondh_set" = "X1" ]; then
+ # not needed for ANONDH in this mode
+ #ciphers="ciphers = ADH:@STRENGTH"
+ :
+ fi
+ fi
+ cat > "$tmp_cfg" <<END
+foreground = yes
+pid =
+client = no
+debug = $stunnel_debug
+$ciphers
+$STUNNEL_EXTRA_OPTS
+$STUNNEL_EXTRA_OPTS_USER
+$cert
+$crl
+$verify
+
+[vnc_stunnel]
+accept = $hloc$port
+$connect
+$STUNNEL_EXTRA_SVC_OPTS
+$STUNNEL_EXTRA_SVC_OPTS_USER
+
+END
+
+fi
+
+echo ""
+echo "Using this stunnel configuration:"
+echo ""
+cat "$tmp_cfg" | uniq
+echo ""
+if egrep -i '^[ ]*(CApath|CAfile) =' "$tmp_cfg" > /dev/null ; then
+ :
+else
+ echo "** WARNING: THE STUNNEL CONFIG HAS NO SERVER CERTIFICATE SPECIFIED **"
+ echo "** WARNING: (the CApath or CAfile stunnel option) THE VNC SERVER WILL **"
+ echo "** WARNING: NOT BE AUTHENTICATED. A MAN-IN-THE-MIDDLE ATTACK IS POSSIBLE **"
+ echo ""
+fi
+sleep 1
+
+if [ "X$stunnel_exec" = "X" ]; then
+ echo ""
+ echo "Running stunnel:"
+ echo "$STUNNEL $tmp_cfg"
+ st=`echo "$STUNNEL" | awk '{print $1}'`
+ $st -help > /dev/null 2>&1
+ $STUNNEL "$tmp_cfg" < /dev/tty > /dev/tty &
+ stunnel_pid=$!
+ echo ""
+
+ # pause here to let the user supply a possible passphrase for the
+ # mycert key:
+ if [ "X$mycert" != "X" ]; then
+ nsl=10
+ dsl=0
+ if [ ! -f $mycert ]; then
+ dsl=0
+ elif grep -i 'Proc-Type.*ENCRYPTED' "$mycert" > /dev/null 2>/dev/null; then
+ dsl=1
+ fi
+ if [ "X$dsl" = "X1" ]; then
+ echo ""
+ echo "(** pausing $nsl secs for possible certificate passphrase dialog **)"
+ echo ""
+ sleep $nsl
+ echo "(** done pausing for passphrase **)"
+ echo ""
+ fi
+ fi
+ #echo T sleep 1
+ sleep 1
+ rm -f "$tmp_cfg"
+fi
+
+
+echo ""
+if [ "X$SSVNC_EXTRA_SLEEP" != "X" ]; then
+ echo "sleep $SSVNC_EXTRA_SLEEP"
+ sleep $SSVNC_EXTRA_SLEEP
+fi
+if [ "X$SSVNC_EXTRA_COMMAND" != "X" ]; then
+ (sh -c "$SSVNC_EXTRA_COMMAND") &
+ echo "($SSVNC_EXTRA_COMMAND) &"; echo
+fi
+
+if [ "X$reverse" = "X" ]; then
+ if [ "X$NEED_VENCRYPT_VIEWER_BRIDGE" = "X1" -a "X$ptmp" != "X" ] ; then
+ port1=`expr 5900 + $N` # stunnel port
+ port2=`findfree 5970` # bridge port (viewer connects to it.)
+ N=`expr $port2 - 5900`
+ env PPROXY_REMOVE=0 PPROXY_SLEEP=0 PPROXY_VENCRYPT_VIEWER_BRIDGE="$port2,$port1" $ptmp &
+ sleep 1
+ fi
+ echo "Running viewer:"
+ vnc_hp=$localhost:$N
+ if [ "X$stunnel_exec" != "X" ]; then
+ vnc_hp="exec=$STUNNEL $tmp_cfg"
+ fi
+ echo "$VNCVIEWERCMD" "$@" "$vnc_hp"
+ trap "final" 0 2 15
+ echo ""
+ $VNCVIEWERCMD "$@" "$vnc_hp"
+ if [ $? != 0 ]; then
+ echo "vncviewer command failed: $?"
+ if [ "X$secondtry" = "X1" ]; then
+ sleep 2
+ $VNCVIEWERCMD "$@" "$vnc_hp"
+ fi
+ fi
+else
+ echo "Running viewer:"
+ echo ""
+ echo "NOTE: Press Ctrl-C to terminate viewer LISTEN mode."
+ echo ""
+ trap "final" 0 2 15
+ N2=$N
+ N2_trim=`echo "$N2" | sed -e 's/://g'`
+ if [ $N2_trim -le 200 ]; then
+ N2_trim=`expr $N2_trim + 5500`
+ fi
+ if [ "X$proxy" != "X" ]; then
+ if echo "$proxy" | grep -i '^vencrypt:' > /dev/null; then
+ pstunnel=`echo "$proxy" | awk -F: '{print $2}'`
+ plisten=`echo "$proxy" | awk -F: '{print $3}'`
+ IF=INADDR_ANY
+ if [ "X$STUNNEL_LISTEN" != "X" ]; then
+ IF=$STUNNEL_LISTEN
+ fi
+ PPROXY_VENCRYPT_REVERSE=1; export PPROXY_VENCRYPT_REVERSE
+ PPROXY_LISTEN="$IF:$plisten"; export PPROXY_LISTEN
+ PPROXY_PROXY="vencrypt://$localhost:$pstunnel"; export PPROXY_PROXY
+ PPROXY_DEST="$localhost:$pstunnel"; export PPROXY_DEST
+ STUNNEL_ONCE=1; export STUNNEL_ONCE
+ STUNNEL_MAX_CLIENTS=1; export STUNNEL_MAX_CLIENTS
+ if [ "X$NEED_VENCRYPT_VIEWER_BRIDGE" = "X1" -a "X$ptmp" != "X" ] ; then
+ port1=`expr 5500 + $N2`
+ port2=`findfree 5580`
+ N2=`expr $port2 - 5500`
+ N2_trim=`echo "$N2" | sed -e 's/://g'`
+ if [ $N2_trim -le 200 ]; then
+ N2_trim=`expr $N2_trim + 5500`
+ fi
+ if [ "X$SSVNC_LISTEN_ONCE" != "X1" ]; then
+ PPROXY_LOOP_THYSELF=`mytmp "/tmp/pproxy_loop_thyself1.${RANDOM}.$$"`
+ export PPROXY_LOOP_THYSELF
+ PPROXY_LOOP_THYSELF0=$PPROXY_LOOP_THYSELF
+ fi
+ env PPROXY_REMOVE=0 PPROXY_SLEEP=0 PPROXY_VENCRYPT_VIEWER_BRIDGE="-$port1,$port2" $ptmp &
+ sleep 1
+ fi
+ else
+ PPROXY_REVERSE="$localhost:$port"; export PPROXY_REVERSE
+ PPROXY_SLEEP=1; export PPROXY_SLEEP;
+ fi
+ PPROXY_KILLPID=+1; export PPROXY_KILLPID;
+ if [ "X$SSVNC_LISTEN_ONCE" != "X1" ]; then
+ PPROXY_LOOP_THYSELF=`mytmp "/tmp/pproxy_loop_thyself2.${RANDOM}.$$"`
+ export PPROXY_LOOP_THYSELF
+ fi
+ $ptmp &
+ # Important to have no extra pids generated between here and VNCVIEWERCMD
+ fi
+ if [ "X$VNCVIEWER_IS_REALVNC4" = "X1" ]; then
+ N2=$N2_trim
+ fi
+ echo "$VNCVIEWERCMD" "$@" -listen $N2
+ echo ""
+ $VNCVIEWERCMD "$@" -listen $N2
+
+ if [ "X$PPROXY_LOOP_THYSELF" != "X" ]; then
+ rm -f $PPROXY_LOOP_THYSELF
+ fi
+ if [ "X$PPROXY_LOOP_THYSELF0" != "X" ]; then
+ rm -f $PPROXY_LOOP_THYSELF0
+ fi
+fi
+
+sleep 1
diff --git a/webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch b/webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch
new file mode 100644
index 0000000..bc10f3c
--- /dev/null
+++ b/webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-cursor-colors+no-tab-traversal.patch
@@ -0,0 +1,111 @@
+--- vnc_javasrc.orig/VncCanvas.java 2004-10-10 02:15:54.000000000 -0400
++++ vnc_javasrc/VncCanvas.java 2010-11-30 21:01:15.000000000 -0500
+@@ -28,13 +28,14 @@
+ import java.lang.*;
+ import java.util.zip.*;
+
++import java.util.Collections;
+
+ //
+ // VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
+ //
+
+ class VncCanvas extends Canvas
+- implements KeyListener, MouseListener, MouseMotionListener {
++ implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
+
+ VncViewer viewer;
+ RfbProto rfb;
+@@ -81,6 +82,20 @@
+ cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
+ cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
+
++ // kludge to not show any Java cursor in the canvas since we are
++ // showing the soft cursor (should be a user setting...)
++ Cursor dot = Toolkit.getDefaultToolkit().createCustomCursor(
++ Toolkit.getDefaultToolkit().createImage(new byte[4]), new Point(0,0),
++ "dot");
++ this.setCursor(dot);
++
++ // while we are at it... get rid of the keyboard traversals that
++ // make it so we can't type a Tab character:
++ this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
++ Collections.EMPTY_SET);
++ this.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
++ Collections.EMPTY_SET);
++
+ colors = new Color[256];
+ for (int i = 0; i < 256; i++)
+ colors[i] = new Color(cm8.getRGB(i));
+@@ -169,6 +184,7 @@
+ inputEnabled = true;
+ addMouseListener(this);
+ addMouseMotionListener(this);
++ addMouseWheelListener(this);
+ if (viewer.showControls) {
+ viewer.buttonPanel.enableRemoteAccessControls(true);
+ }
+@@ -177,6 +193,7 @@
+ inputEnabled = false;
+ removeMouseListener(this);
+ removeMouseMotionListener(this);
++ removeMouseWheelListener(this);
+ if (viewer.showControls) {
+ viewer.buttonPanel.enableRemoteAccessControls(false);
+ }
+@@ -1190,6 +1207,9 @@
+ public void mouseDragged(MouseEvent evt) {
+ processLocalMouseEvent(evt, true);
+ }
++ public void mouseWheelMoved(MouseWheelEvent evt) {
++ processLocalMouseWheelEvent(evt);
++ }
+
+ public void processLocalKeyEvent(KeyEvent evt) {
+ if (viewer.rfb != null && rfb.inNormalProtocol) {
+@@ -1221,6 +1241,19 @@
+ evt.consume();
+ }
+
++ public void processLocalMouseWheelEvent(MouseWheelEvent evt) {
++ if (viewer.rfb != null && rfb.inNormalProtocol) {
++ synchronized(rfb) {
++ try {
++ rfb.writeWheelEvent(evt);
++ } catch (Exception e) {
++ e.printStackTrace();
++ }
++ rfb.notify();
++ }
++ }
++ }
++
+ public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
+ if (viewer.rfb != null && rfb.inNormalProtocol) {
+ if (moved) {
+@@ -1387,9 +1420,9 @@
+ result = cm8.getRGB(pixBuf[i]);
+ } else {
+ result = 0xFF000000 |
+- (pixBuf[i * 4 + 1] & 0xFF) << 16 |
+- (pixBuf[i * 4 + 2] & 0xFF) << 8 |
+- (pixBuf[i * 4 + 3] & 0xFF);
++ (pixBuf[i * 4 + 2] & 0xFF) << 16 |
++ (pixBuf[i * 4 + 1] & 0xFF) << 8 |
++ (pixBuf[i * 4 + 0] & 0xFF);
+ }
+ } else {
+ result = 0; // Transparent pixel
+@@ -1403,9 +1436,9 @@
+ result = cm8.getRGB(pixBuf[i]);
+ } else {
+ result = 0xFF000000 |
+- (pixBuf[i * 4 + 1] & 0xFF) << 16 |
+- (pixBuf[i * 4 + 2] & 0xFF) << 8 |
+- (pixBuf[i * 4 + 3] & 0xFF);
++ (pixBuf[i * 4 + 2] & 0xFF) << 16 |
++ (pixBuf[i * 4 + 1] & 0xFF) << 8 |
++ (pixBuf[i * 4 + 0] & 0xFF);
+ }
+ } else {
+ result = 0; // Transparent pixel
diff --git a/webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch b/webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
new file mode 100644
index 0000000..801234a
--- /dev/null
+++ b/webclients/ssl/tightvnc-1.3dev7_javasrc-vncviewer-ssl.patch
@@ -0,0 +1,2600 @@
+diff -Naur vnc_javasrc.orig/Makefile vnc_javasrc/Makefile
+--- vnc_javasrc.orig/Makefile 2004-03-04 08:34:25.000000000 -0500
++++ vnc_javasrc/Makefile 2010-05-18 20:56:26.000000000 -0400
+@@ -4,6 +4,7 @@
+
+ CP = cp
+ JC = javac
++JC_ARGS = -target 1.4 -source 1.4
+ JAR = jar
+ ARCHIVE = VncViewer.jar
+ MANIFEST = MANIFEST.MF
+@@ -15,25 +16,29 @@
+ DesCipher.class CapabilityInfo.class CapsContainer.class \
+ RecordingFrame.class SessionRecorder.class AuthUnixLoginPanel.class \
+ SocketFactory.class HTTPConnectSocketFactory.class \
+- HTTPConnectSocket.class ReloginPanel.class
++ HTTPConnectSocket.class ReloginPanel.class \
++ SSLSocketToMe.class
++
++SSL_CLASSES = SSLSocketToMe*.class TrustDialog.class
+
+ SOURCES = VncViewer.java RfbProto.java AuthPanel.java VncCanvas.java \
+ OptionsFrame.java ClipboardFrame.java ButtonPanel.java \
+ DesCipher.java CapabilityInfo.java CapsContainer.java \
+ RecordingFrame.java SessionRecorder.java AuthUnixLoginPanel.java \
+ SocketFactory.java HTTPConnectSocketFactory.java \
+- HTTPConnectSocket.java ReloginPanel.java
++ HTTPConnectSocket.java ReloginPanel.java \
++ SSLSocketToMe.java
+
+ all: $(CLASSES) $(ARCHIVE)
+
+ $(CLASSES): $(SOURCES)
+- $(JC) -target 1.1 -O $(SOURCES)
++ $(JC) $(JC_ARGS) -O $(SOURCES)
+
+ $(ARCHIVE): $(CLASSES) $(MANIFEST)
+- $(JAR) cfm $(ARCHIVE) $(MANIFEST) $(CLASSES)
++ $(JAR) cfm $(ARCHIVE) $(MANIFEST) $(CLASSES) $(SSL_CLASSES)
+
+ install: $(CLASSES) $(ARCHIVE)
+- $(CP) $(CLASSES) $(ARCHIVE) $(PAGES) $(INSTALL_DIR)
++ $(CP) $(CLASSES) $(SSL_CLASSES) $(ARCHIVE) $(PAGES) $(INSTALL_DIR)
+
+ export:: $(CLASSES) $(ARCHIVE) $(PAGES)
+ @$(ExportJavaClasses)
+diff -Naur vnc_javasrc.orig/RfbProto.java vnc_javasrc/RfbProto.java
+--- vnc_javasrc.orig/RfbProto.java 2004-03-04 08:34:25.000000000 -0500
++++ vnc_javasrc/RfbProto.java 2010-11-30 22:05:12.000000000 -0500
+@@ -199,7 +199,21 @@
+ host = h;
+ port = p;
+
+- if (viewer.socketFactory == null) {
++ if (! viewer.disableSSL) {
++ System.out.println("new SSLSocketToMe");
++ SSLSocketToMe ssl;
++ try {
++ ssl = new SSLSocketToMe(host, port, v);
++ } catch (Exception e) {
++ throw new IOException(e.getMessage());
++ }
++
++ try {
++ sock = ssl.connectSock();
++ } catch (Exception es) {
++ throw new IOException(es.getMessage());
++ }
++ } else if (viewer.socketFactory == null) {
+ sock = new Socket(host, port);
+ } else {
+ try {
+@@ -255,7 +269,7 @@
+ || (b[10] < '0') || (b[10] > '9') || (b[11] != '\n'))
+ {
+ throw new Exception("Host " + host + " port " + port +
+- " is not an RFB server");
++ " is not an RFB server: " + b);
+ }
+
+ serverMajor = (b[4] - '0') * 100 + (b[5] - '0') * 10 + (b[6] - '0');
+@@ -892,6 +906,38 @@
+ final static int ALT_MASK = InputEvent.ALT_MASK;
+
+
++ void writeWheelEvent(MouseWheelEvent evt) throws IOException {
++
++ eventBufLen = 0;
++
++ int x = evt.getX();
++ int y = evt.getY();
++
++ if (x < 0) x = 0;
++ if (y < 0) y = 0;
++
++ int ptrmask;
++
++ int clicks = evt.getWheelRotation();
++ System.out.println("writeWheelEvent: clicks: " + clicks);
++ if (clicks > 0) {
++ ptrmask = 16;
++ } else if (clicks < 0) {
++ ptrmask = 8;
++ } else {
++ return;
++ }
++
++ eventBuf[eventBufLen++] = (byte) PointerEvent;
++ eventBuf[eventBufLen++] = (byte) ptrmask;
++ eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
++ eventBuf[eventBufLen++] = (byte) (x & 0xff);
++ eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
++ eventBuf[eventBufLen++] = (byte) (y & 0xff);
++
++ os.write(eventBuf, 0, eventBufLen);
++ }
++
+ //
+ // Write a pointer event message. We may need to send modifier key events
+ // around it to set the correct modifier state.
+@@ -992,6 +1038,19 @@
+ boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
+
+ int key;
++ if (viewer.debugKeyboard) {
++ System.out.println("----------------------------------------");
++ System.out.println("evt.getKeyChar: " + evt.getKeyChar());
++ System.out.println("getKeyText: " + KeyEvent.getKeyText(evt.getKeyCode()));
++ System.out.println("evt.getKeyCode: " + evt.getKeyCode());
++ System.out.println("evt.getID: " + evt.getID());
++ System.out.println("evt.getKeyLocation: " + evt.getKeyLocation());
++ System.out.println("evt.isActionKey: " + evt.isActionKey());
++ System.out.println("evt.isControlDown: " + evt.isControlDown());
++ System.out.println("evt.getModifiers: " + evt.getModifiers());
++ System.out.println("getKeyModifiersText: " + KeyEvent.getKeyModifiersText(evt.getModifiers()));
++ System.out.println("evt.paramString: " + evt.paramString());
++ }
+ if (evt.isActionKey()) {
+
+ //
+@@ -1025,6 +1084,13 @@
+ return;
+ }
+
++ if(key == 0xffc2 && viewer.mapF5_to_atsign) {
++ if (viewer.debugKeyboard) {
++ System.out.println("Mapping: F5 -> AT ");
++ }
++ key = 0x40;
++ }
++
+ } else {
+
+ //
+@@ -1036,6 +1102,7 @@
+
+ key = keyChar;
+
++
+ if (key < 0x20) {
+ if (evt.isControlDown()) {
+ key += 0x60;
+@@ -1121,6 +1188,16 @@
+ int oldModifiers = 0;
+
+ void writeModifierKeyEvents(int newModifiers) {
++ if(viewer.forbid_Ctrl_Alt) {
++ if ((newModifiers & CTRL_MASK) != 0 && (newModifiers & ALT_MASK) != 0) {
++ int orig = newModifiers;
++ newModifiers &= ~ALT_MASK;
++ newModifiers &= ~CTRL_MASK;
++ if (viewer.debugKeyboard) {
++ System.out.println("Ctrl+Alt modifiers: " + orig + " -> " + newModifiers);
++ }
++ }
++ }
+ if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
+ writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
+
+diff -Naur vnc_javasrc.orig/SSLSocketToMe.java vnc_javasrc/SSLSocketToMe.java
+--- vnc_javasrc.orig/SSLSocketToMe.java 1969-12-31 19:00:00.000000000 -0500
++++ vnc_javasrc/SSLSocketToMe.java 2010-07-10 19:18:06.000000000 -0400
+@@ -0,0 +1,2067 @@
++/*
++ * SSLSocketToMe.java: add SSL encryption to Java VNC Viewer.
++ *
++ * Copyright (c) 2006 Karl J. Runge <runge@karlrunge.com>
++ * All rights reserved.
++ *
++ * This is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * 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. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this software; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
++ * USA.
++ *
++ */
++
++import java.net.*;
++import java.io.*;
++import javax.net.ssl.*;
++import java.util.*;
++
++import java.security.*;
++import java.security.cert.*;
++import java.security.spec.*;
++import java.security.cert.Certificate;
++import java.security.cert.CertificateFactory;
++
++import java.awt.*;
++import java.awt.event.*;
++
++public class SSLSocketToMe {
++
++ /* basic member data: */
++ String host;
++ int port;
++ VncViewer viewer;
++
++ boolean debug = true;
++ boolean debug_certs = false;
++
++ /* sockets */
++ SSLSocket socket = null;
++ SSLSocketFactory factory;
++
++ /* fallback for Proxy connection */
++ boolean proxy_in_use = false;
++ boolean proxy_failure = false;
++ public DataInputStream is = null;
++ public OutputStream os = null;
++
++ /* strings from user WRT proxy: */
++ String proxy_auth_string = null;
++ String proxy_dialog_host = null;
++ int proxy_dialog_port = 0;
++
++ Socket proxySock;
++ DataInputStream proxy_is;
++ OutputStream proxy_os;
++
++ /* trust contexts */
++ SSLContext trustloc_ctx;
++ SSLContext trustall_ctx;
++ SSLContext trustsrv_ctx;
++ SSLContext trusturl_ctx;
++ SSLContext trustone_ctx;
++
++ /* corresponding trust managers */
++ TrustManager[] trustAllCerts;
++ TrustManager[] trustSrvCert;
++ TrustManager[] trustUrlCert;
++ TrustManager[] trustOneCert;
++
++ /* client-side SSL auth key (oneTimeKey=...) */
++ KeyManager[] mykey = null;
++
++ boolean user_wants_to_see_cert = true;
++ String cert_fail = null;
++
++ /* cert(s) we retrieve from Web server, VNC server, or serverCert param: */
++ java.security.cert.Certificate[] trustallCerts = null;
++ java.security.cert.Certificate[] trustsrvCerts = null;
++ java.security.cert.Certificate[] trusturlCerts = null;
++
++ /* utility to decode hex oneTimeKey=... and serverCert=... */
++ byte[] hex2bytes(String s) {
++ byte[] bytes = new byte[s.length()/2];
++ for (int i=0; i<s.length()/2; i++) {
++ int j = 2*i;
++ try {
++ int val = Integer.parseInt(s.substring(j, j+2), 16);
++ if (val > 127) {
++ val -= 256;
++ }
++ Integer I = new Integer(val);
++ bytes[i] = Byte.decode(I.toString()).byteValue();
++
++ } catch (Exception e) {
++ ;
++ }
++ }
++ return bytes;
++ }
++
++ SSLSocketToMe(String h, int p, VncViewer v) throws Exception {
++ host = h;
++ port = p;
++ viewer = v;
++
++ debug_certs = v.debugCerts;
++
++ /* we will first try default factory for certification: */
++
++ factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
++
++ dbg("SSL startup: " + host + " " + port);
++
++
++ /* create trust managers to be used if initial handshake fails: */
++
++ trustAllCerts = new TrustManager[] {
++ /*
++ * this one accepts everything. Only used if user
++ * has disabled checking (trustAllVncCerts=yes)
++ * or when we grab the cert to show it to them in
++ * a dialog and ask them to manually verify/accept it.
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) {
++ /* empty */
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) {
++ /* empty */
++ dbg("ALL: an untrusted connect to grab cert.");
++ }
++ }
++ };
++
++ trustUrlCert = new TrustManager[] {
++ /*
++ * this one accepts only the retrieved server
++ * cert by SSLSocket by this applet and stored in
++ * trusturlCerts.
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ throw new CertificateException("No Clients (URL)");
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ /* we want to check 'certs' against 'trusturlCerts' */
++ if (trusturlCerts == null) {
++ throw new CertificateException(
++ "No Trust url Certs array.");
++ }
++ if (trusturlCerts.length < 1) {
++ throw new CertificateException(
++ "No Trust url Certs.");
++ }
++ if (certs == null) {
++ throw new CertificateException(
++ "No this-certs array.");
++ }
++ if (certs.length < 1) {
++ throw new CertificateException(
++ "No this-certs Certs.");
++ }
++ if (certs.length != trusturlCerts.length) {
++ throw new CertificateException(
++ "certs.length != trusturlCerts.length " + certs.length + " " + trusturlCerts.length);
++ }
++ boolean ok = true;
++ for (int i = 0; i < certs.length; i++) {
++ if (! trusturlCerts[i].equals(certs[i])) {
++ ok = false;
++ dbg("URL: cert mismatch at i=" + i);
++ dbg("URL: cert mismatch cert" + certs[i]);
++ dbg("URL: cert mismatch url" + trusturlCerts[i]);
++ if (cert_fail == null) {
++ cert_fail = "cert-mismatch";
++ }
++ }
++ if (debug_certs) {
++ dbg("\n***********************************************");
++ dbg("URL: cert info at i=" + i);
++ dbg("URL: cert info cert" + certs[i]);
++ dbg("===============================================");
++ dbg("URL: cert info url" + trusturlCerts[i]);
++ dbg("***********************************************");
++ }
++ }
++ if (!ok) {
++ throw new CertificateException(
++ "Server Cert Chain != URL Cert Chain.");
++ }
++ dbg("URL: trusturlCerts[i] matches certs[i] i=0:" + (certs.length-1));
++ }
++ }
++ };
++
++ trustSrvCert = new TrustManager[] {
++ /*
++ * this one accepts cert given to us in the serverCert
++ * Applet Parameter we were started with. It is
++ * currently a fatal error if the VNC Server's cert
++ * doesn't match it.
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ throw new CertificateException("No Clients (SRV)");
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ /* we want to check 'certs' against 'trustsrvCerts' */
++ if (trustsrvCerts == null) {
++ throw new CertificateException(
++ "No Trust srv Certs array.");
++ }
++ if (trustsrvCerts.length < 1) {
++ throw new CertificateException(
++ "No Trust srv Certs.");
++ }
++ if (certs == null) {
++ throw new CertificateException(
++ "No this-certs array.");
++ }
++ if (certs.length < 1) {
++ throw new CertificateException(
++ "No this-certs Certs.");
++ }
++ if (certs.length != trustsrvCerts.length) {
++ throw new CertificateException(
++ "certs.length != trustsrvCerts.length " + certs.length + " " + trustsrvCerts.length);
++ }
++ boolean ok = true;
++ for (int i = 0; i < certs.length; i++) {
++ if (! trustsrvCerts[i].equals(certs[i])) {
++ ok = false;
++ dbg("SRV: cert mismatch at i=" + i);
++ dbg("SRV: cert mismatch cert" + certs[i]);
++ dbg("SRV: cert mismatch srv" + trustsrvCerts[i]);
++ if (cert_fail == null) {
++ cert_fail = "server-cert-mismatch";
++ }
++ }
++ if (debug_certs) {
++ dbg("\n***********************************************");
++ dbg("SRV: cert info at i=" + i);
++ dbg("SRV: cert info cert" + certs[i]);
++ dbg("===============================================");
++ dbg("SRV: cert info srv" + trustsrvCerts[i]);
++ dbg("***********************************************");
++ }
++ }
++ if (!ok) {
++ throw new CertificateException(
++ "Server Cert Chain != serverCert Applet Parameter Cert Chain.");
++ }
++ dbg("SRV: trustsrvCerts[i] matches certs[i] i=0:" + (certs.length-1));
++ }
++ }
++ };
++
++ trustOneCert = new TrustManager[] {
++ /*
++ * this one accepts only the retrieved server
++ * cert by SSLSocket by this applet we stored in
++ * trustallCerts that user has accepted or applet
++ * parameter trustAllVncCerts=yes is set. This is
++ * for when we reconnect after the user has manually
++ * accepted the trustall cert in the dialog (or set
++ * trustAllVncCerts=yes applet param.)
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ throw new CertificateException("No Clients (ONE)");
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ /* we want to check 'certs' against 'trustallCerts' */
++ if (trustallCerts == null) {
++ throw new CertificateException(
++ "No Trust All Server Certs array.");
++ }
++ if (trustallCerts.length < 1) {
++ throw new CertificateException(
++ "No Trust All Server Certs.");
++ }
++ if (certs == null) {
++ throw new CertificateException(
++ "No this-certs array.");
++ }
++ if (certs.length < 1) {
++ throw new CertificateException(
++ "No this-certs Certs.");
++ }
++ if (certs.length != trustallCerts.length) {
++ throw new CertificateException(
++ "certs.length != trustallCerts.length " + certs.length + " " + trustallCerts.length);
++ }
++ boolean ok = true;
++ for (int i = 0; i < certs.length; i++) {
++ if (! trustallCerts[i].equals(certs[i])) {
++ ok = false;
++ dbg("ONE: cert mismatch at i=" + i);
++ dbg("ONE: cert mismatch cert" + certs[i]);
++ dbg("ONE: cert mismatch all" + trustallCerts[i]);
++ }
++ if (debug_certs) {
++ dbg("\n***********************************************");
++ dbg("ONE: cert info at i=" + i);
++ dbg("ONE: cert info cert" + certs[i]);
++ dbg("===============================================");
++ dbg("ONE: cert info all" + trustallCerts[i]);
++ dbg("***********************************************");
++ }
++ }
++ if (!ok) {
++ throw new CertificateException(
++ "Server Cert Chain != TRUSTALL Cert Chain.");
++ }
++ dbg("ONE: trustallCerts[i] matches certs[i] i=0:" + (certs.length-1));
++ }
++ }
++ };
++
++ /*
++ * The above TrustManagers are used:
++ *
++ * 1) to retrieve the server cert in case of failure to
++ * display it to the user in a dialog.
++ * 2) to subsequently connect to the server if user agrees.
++ */
++
++ /*
++ * build oneTimeKey cert+key if supplied in applet parameter:
++ */
++ if (viewer.oneTimeKey != null && viewer.oneTimeKey.equals("PROMPT")) {
++ ClientCertDialog d = new ClientCertDialog();
++ viewer.oneTimeKey = d.queryUser();
++ }
++ if (viewer.oneTimeKey != null && viewer.oneTimeKey.indexOf(",") > 0) {
++ int idx = viewer.oneTimeKey.indexOf(",");
++
++ String onetimekey = viewer.oneTimeKey.substring(0, idx);
++ byte[] key = hex2bytes(onetimekey);
++ String onetimecert = viewer.oneTimeKey.substring(idx+1);
++ byte[] cert = hex2bytes(onetimecert);
++
++ KeyFactory kf = KeyFactory.getInstance("RSA");
++ PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec ( key );
++ PrivateKey ff = kf.generatePrivate (keysp);
++ if (debug_certs) {
++ dbg("one time key " + ff);
++ }
++
++ CertificateFactory cf = CertificateFactory.getInstance("X.509");
++ Collection c = cf.generateCertificates(new ByteArrayInputStream(cert));
++ Certificate[] certs = new Certificate[c.toArray().length];
++ if (c.size() == 1) {
++ Certificate tmpcert = cf.generateCertificate(new ByteArrayInputStream(cert));
++ if (debug_certs) {
++ dbg("one time cert" + tmpcert);
++ }
++ certs[0] = tmpcert;
++ } else {
++ certs = (Certificate[]) c.toArray();
++ }
++
++ KeyStore ks = KeyStore.getInstance("JKS");
++ ks.load(null, null);
++ ks.setKeyEntry("onetimekey", ff, "".toCharArray(), certs);
++ String da = KeyManagerFactory.getDefaultAlgorithm();
++ KeyManagerFactory kmf = KeyManagerFactory.getInstance(da);
++ kmf.init(ks, "".toCharArray());
++
++ mykey = kmf.getKeyManagers();
++ }
++
++ /*
++ * build serverCert cert if supplied in applet parameter:
++ */
++ if (viewer.serverCert != null) {
++ CertificateFactory cf = CertificateFactory.getInstance("X.509");
++ byte[] cert = hex2bytes(viewer.serverCert);
++ Collection c = cf.generateCertificates(new ByteArrayInputStream(cert));
++ trustsrvCerts = new Certificate[c.toArray().length];
++ if (c.size() == 1) {
++ Certificate tmpcert = cf.generateCertificate(new ByteArrayInputStream(cert));
++ trustsrvCerts[0] = tmpcert;
++ } else {
++ trustsrvCerts = (Certificate[]) c.toArray();
++ }
++ }
++
++ /* the trust loc certs context: */
++ try {
++ trustloc_ctx = SSLContext.getInstance("SSL");
++
++ /*
++ * below is a failed attempt to get jvm's default
++ * trust manager using null (below) makes it so
++ * for HttpsURLConnection the server cannot be
++ * verified (no prompting.)
++ */
++ if (false) {
++ boolean didit = false;
++ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
++ tmf.init((KeyStore) null);
++ TrustManager [] tml = tmf.getTrustManagers();
++ for (int i = 0; i < tml.length; i++) {
++ TrustManager tm = tml[i];
++ if (tm instanceof X509TrustManager) {
++ TrustManager tm1[] = new TrustManager[1];
++ tm1[0] = tm;
++ trustloc_ctx.init(mykey, tm1, null);
++ didit = true;
++ break;
++ }
++ }
++ if (!didit) {
++ trustloc_ctx.init(mykey, null, null);
++ }
++ } else {
++ /* we have to set trust manager to null */
++ trustloc_ctx.init(mykey, null, null);
++ }
++
++ } catch (Exception e) {
++ String msg = "SSL trustloc_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust all certs context: */
++ try {
++ trustall_ctx = SSLContext.getInstance("SSL");
++ trustall_ctx.init(mykey, trustAllCerts, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trustall_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust url certs context: */
++ try {
++ trusturl_ctx = SSLContext.getInstance("SSL");
++ trusturl_ctx.init(mykey, trustUrlCert, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trusturl_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust srv certs context: */
++ try {
++ trustsrv_ctx = SSLContext.getInstance("SSL");
++ trustsrv_ctx.init(mykey, trustSrvCert, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trustsrv_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust the one cert from server context: */
++ try {
++ trustone_ctx = SSLContext.getInstance("SSL");
++ trustone_ctx.init(mykey, trustOneCert, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trustone_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++ }
++
++ /*
++ * we call this early on to 1) check for a proxy, 2) grab
++ * Browser/JVM accepted HTTPS cert.
++ */
++ public void check_for_proxy_and_grab_vnc_server_cert() {
++
++ trusturlCerts = null;
++ proxy_in_use = false;
++
++ if (viewer.ignoreProxy) {
++ /* applet param says skip it. */
++ /* the downside is we do not set trusturlCerts for comparison later... */
++ /* nor do we autodetect x11vnc for GET=1. */
++ return;
++ }
++
++ dbg("------------------------------------------------");
++ dbg("Into check_for_proxy_and_grab_vnc_server_cert():");
++
++ dbg("TRYING HTTPS:");
++ String ustr = "https://" + host + ":";
++ if (viewer.httpsPort != null) {
++ ustr += viewer.httpsPort;
++ } else {
++ ustr += port;
++ }
++ ustr += viewer.urlPrefix + "/check.https.proxy.connection";
++ dbg("ustr is: " + ustr);
++
++ try {
++ /* prepare for an HTTPS URL connection to host:port */
++ URL url = new URL(ustr);
++ HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
++
++ if (mykey != null) {
++ /* with oneTimeKey (mykey) we can't use the default SSL context */
++ if (trustsrvCerts != null) {
++ dbg("passing trustsrv_ctx to HttpsURLConnection to provide client cert.");
++ https.setSSLSocketFactory(trustsrv_ctx.getSocketFactory());
++ } else if (trustloc_ctx != null) {
++ dbg("passing trustloc_ctx to HttpsURLConnection to provide client cert.");
++ https.setSSLSocketFactory(trustloc_ctx.getSocketFactory());
++ }
++ }
++
++ https.setUseCaches(false);
++ https.setRequestMethod("GET");
++ https.setRequestProperty("Pragma", "No-Cache");
++ https.setRequestProperty("Proxy-Connection", "Keep-Alive");
++ https.setDoInput(true);
++
++ dbg("trying https.connect()");
++ https.connect();
++
++ dbg("trying https.getServerCertificates()");
++ trusturlCerts = https.getServerCertificates();
++
++ if (trusturlCerts == null) {
++ dbg("set trusturlCerts to null!");
++ } else {
++ dbg("set trusturlCerts to non-null");
++ }
++
++ if (https.usingProxy()) {
++ proxy_in_use = true;
++ dbg("An HTTPS proxy is in use. There may be connection problems.");
++ }
++
++ dbg("trying https.getContent()");
++ Object output = https.getContent();
++ dbg("trying https.disconnect()");
++ https.disconnect();
++ if (! viewer.GET) {
++ String header = https.getHeaderField("VNC-Server");
++ if (header != null && header.startsWith("x11vnc")) {
++ dbg("detected x11vnc server (1), setting GET=1");
++ viewer.GET = true;
++ }
++ }
++
++ } catch(Exception e) {
++ dbg("HttpsURLConnection: " + e.getMessage());
++ }
++
++ if (proxy_in_use) {
++ dbg("exit check_for_proxy_and_grab_vnc_server_cert():");
++ dbg("------------------------------------------------");
++ return;
++ } else if (trusturlCerts != null && !viewer.forceProxy) {
++ /* Allow user to require HTTP check? use forceProxy for now. */
++ dbg("SKIPPING HTTP PROXY CHECK: got trusturlCerts, assuming proxy info is correct.");
++ dbg("exit check_for_proxy_and_grab_vnc_server_cert():");
++ dbg("------------------------------------------------");
++ return;
++ }
++
++ /*
++ * XXX need to remember scenario where this extra check
++ * gives useful info. User's Browser proxy settings?
++ */
++ dbg("TRYING HTTP:");
++ ustr = "http://" + host + ":" + port;
++ ustr += viewer.urlPrefix + "/index.vnc";
++ dbg("ustr is: " + ustr);
++
++ try {
++ /* prepare for an HTTP URL connection to the same host:port (but not httpsPort) */
++ URL url = new URL(ustr);
++ HttpURLConnection http = (HttpURLConnection)
++ url.openConnection();
++
++ http.setUseCaches(false);
++ http.setRequestMethod("GET");
++ http.setRequestProperty("Pragma", "No-Cache");
++ http.setRequestProperty("Proxy-Connection", "Keep-Alive");
++ http.setDoInput(true);
++
++ dbg("trying http.connect()");
++ http.connect();
++
++ if (http.usingProxy()) {
++ proxy_in_use = true;
++ dbg("An HTTP proxy is in use. There may be connection problems.");
++ }
++ dbg("trying http.getContent()");
++ Object output = http.getContent();
++ dbg("trying http.disconnect()");
++ http.disconnect();
++ if (! viewer.GET) {
++ String header = http.getHeaderField("VNC-Server");
++ if (header != null && header.startsWith("x11vnc")) {
++ dbg("detected x11vnc server (2), setting GET=1");
++ viewer.GET = true;
++ }
++ }
++ } catch(Exception e) {
++ dbg("HttpURLConnection: " + e.getMessage());
++ }
++ dbg("exit check_for_proxy_and_grab_vnc_server_cert():");
++ dbg("------------------------------------------------");
++ }
++
++ public Socket connectSock() throws IOException {
++ /*
++ * first try a https connection to detect a proxy, and
++ * grab the VNC server cert at the same time:
++ */
++ check_for_proxy_and_grab_vnc_server_cert();
++
++ boolean srv_cert = false;
++
++ if (trustsrvCerts != null) {
++ /* applet parameter suppled serverCert */
++ dbg("viewer.trustSrvCert-0 using trustsrv_ctx");
++ factory = trustsrv_ctx.getSocketFactory();
++ srv_cert = true;
++ } else if (viewer.trustAllVncCerts) {
++ /* trust all certs (no checking) */
++ dbg("viewer.trustAllVncCerts-0 using trustall_ctx");
++ factory = trustall_ctx.getSocketFactory();
++ } else if (trusturlCerts != null) {
++ /* trust certs the Browser/JVM accepted in check_for_proxy... */
++ dbg("using trusturl_ctx");
++ factory = trusturl_ctx.getSocketFactory();
++ } else {
++ /* trust the local defaults */
++ dbg("using trustloc_ctx");
++ factory = trustloc_ctx.getSocketFactory();
++ }
++
++ socket = null;
++
++ try {
++ if (proxy_in_use && viewer.forceProxy) {
++ throw new Exception("forcing proxy (forceProxy)");
++ } else if (viewer.CONNECT != null) {
++ throw new Exception("forcing CONNECT");
++ }
++
++ int timeout = 6;
++ if (timeout > 0) {
++ socket = (SSLSocket) factory.createSocket();
++ InetSocketAddress inetaddr = new InetSocketAddress(host, port);
++ dbg("Using timeout of " + timeout + " secs to: " + host + ":" + port);
++ socket.connect(inetaddr, timeout * 1000);
++ } else {
++ socket = (SSLSocket) factory.createSocket(host, port);
++ }
++
++ } catch (Exception esock) {
++ dbg("socket error: " + esock.getMessage());
++ if (proxy_in_use || viewer.CONNECT != null) {
++ proxy_failure = true;
++ if (proxy_in_use) {
++ dbg("HTTPS proxy in use. Trying to go with it.");
++ } else {
++ dbg("viewer.CONNECT reverse proxy in use. Trying to go with it.");
++ }
++ try {
++ socket = proxy_socket(factory);
++ } catch (Exception e) {
++ dbg("proxy_socket error: " + e.getMessage());
++ }
++ } else {
++ /* n.b. socket is left in error state to cause ex. below. */
++ }
++ }
++
++ try {
++ socket.startHandshake();
++
++ dbg("The Server Connection Verified OK on 1st try.");
++
++ java.security.cert.Certificate[] currentTrustedCerts;
++ BrowserCertsDialog bcd;
++
++ SSLSession sess = socket.getSession();
++ currentTrustedCerts = sess.getPeerCertificates();
++
++ if (viewer.trustAllVncCerts) {
++ dbg("viewer.trustAllVncCerts-1 keeping socket.");
++ } else if (currentTrustedCerts == null || currentTrustedCerts.length < 1) {
++ try {
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy.");
++ }
++ socket = null;
++ throw new SSLHandshakeException("no current certs");
++ }
++
++ String serv = "";
++ try {
++ CertInfo ci = new CertInfo(currentTrustedCerts[0]);
++ serv = ci.get_certinfo("CN");
++ } catch (Exception e) {
++ ;
++ }
++
++ if (viewer.trustAllVncCerts) {
++ dbg("viewer.trustAllVncCerts-2 skipping browser certs dialog");
++ user_wants_to_see_cert = false;
++ } else if (viewer.serverCert != null && trustsrvCerts != null) {
++ dbg("viewer.serverCert-1 skipping browser certs dialog");
++ user_wants_to_see_cert = false;
++ } else if (viewer.trustUrlVncCert) {
++ dbg("viewer.trustUrlVncCert-1 skipping browser certs dialog");
++ user_wants_to_see_cert = false;
++ } else {
++ /* have a dialog with the user: */
++ bcd = new BrowserCertsDialog(serv, host + ":" + port);
++ dbg("browser certs dialog begin.");
++ bcd.queryUser();
++ dbg("browser certs dialog finished.");
++
++ if (bcd.showCertDialog) {
++ String msg = "user wants to see cert";
++ dbg(msg);
++ user_wants_to_see_cert = true;
++ if (cert_fail == null) {
++ cert_fail = "user-view";
++ }
++ throw new SSLHandshakeException(msg);
++ } else {
++ user_wants_to_see_cert = false;
++ dbg("browser certs dialog: user said yes, accept it");
++ }
++ }
++
++ } catch (SSLHandshakeException eh) {
++ dbg("SSLHandshakeException: could not automatically verify Server.");
++ dbg("msg: " + eh.getMessage());
++
++
++ /* send a cleanup string just in case: */
++ String getoutstr = "GET /index.vnc HTTP/1.0\r\nConnection: close\r\n\r\n";
++
++ try {
++ OutputStream os = socket.getOutputStream();
++ os.write(getoutstr.getBytes());
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy!");
++ }
++
++ /* reload */
++
++ socket = null;
++
++ String reason = null;
++
++ if (srv_cert) {
++ /* for serverCert usage we make this a fatal error. */
++ throw new IOException("Fatal: VNC Server's Cert does not match Applet Parameter 'serverCert=...'");
++ /* see below in TrustDialog were we describe this case to user anyway */
++ }
++
++ /*
++ * Reconnect, trusting any cert, so we can grab
++ * the cert to show it to the user in a dialog
++ * for him to manually accept. This connection
++ * is not used for anything else.
++ */
++ factory = trustall_ctx.getSocketFactory();
++ if (proxy_failure) {
++ socket = proxy_socket(factory);
++ } else {
++ socket = (SSLSocket) factory.createSocket(host, port);
++ }
++
++ if (debug_certs) {
++ dbg("trusturlCerts: " + trusturlCerts);
++ dbg("trustsrvCerts: " + trustsrvCerts);
++ }
++ if (trusturlCerts == null && cert_fail == null) {
++ cert_fail = "missing-certs";
++ }
++
++ try {
++ socket.startHandshake();
++
++ dbg("The TrustAll Server Cert-grab Connection (trivially) Verified OK.");
++
++ /* grab the cert: */
++ try {
++ SSLSession sess = socket.getSession();
++ trustallCerts = sess.getPeerCertificates();
++ } catch (Exception e) {
++ throw new Exception("Could not get " +
++ "Peer Certificate");
++ }
++ if (debug_certs) {
++ dbg("trustallCerts: " + trustallCerts);
++ }
++
++ if (viewer.trustAllVncCerts) {
++ dbg("viewer.trustAllVncCerts-3. skipping dialog, trusting everything.");
++ } else if (! browser_cert_match()) {
++ /*
++ * close socket now, we will reopen after
++ * dialog if user agrees to use the cert.
++ */
++ try {
++ OutputStream os = socket.getOutputStream();
++ os.write(getoutstr.getBytes());
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy!!");
++ }
++ socket = null;
++
++ /* dialog with user to accept cert or not: */
++
++ TrustDialog td= new TrustDialog(host, port,
++ trustallCerts);
++
++ if (cert_fail == null) {
++ ;
++ } else if (cert_fail.equals("user-view")) {
++ reason = "Reason for this Dialog:\n\n"
++ + " You Asked to View the Certificate.";
++ } else if (cert_fail.equals("server-cert-mismatch")) {
++ /* this is now fatal error, see above. */
++ reason = "Reason for this Dialog:\n\n"
++ + " The VNC Server's Certificate does not match the Certificate\n"
++ + " specified in the supplied 'serverCert' Applet Parameter.";
++ } else if (cert_fail.equals("cert-mismatch")) {
++ reason = "Reason for this Dialog:\n\n"
++ + " The VNC Server's Certificate does not match the Website's\n"
++ + " HTTPS Certificate (that you previously accepted; either\n"
++ + " manually or automatically via Certificate Authority.)";
++ } else if (cert_fail.equals("missing-certs")) {
++ reason = "Reason for this Dialog:\n\n"
++ + " Not all Certificates could be obtained to check.";
++ }
++
++ if (! td.queryUser(reason)) {
++ String msg = "User decided against it.";
++ dbg(msg);
++ throw new IOException(msg);
++ }
++ }
++
++ } catch (Exception ehand2) {
++ dbg("** Could not TrustAll Verify Server!");
++
++ throw new IOException(ehand2.getMessage());
++ }
++
++ /* reload again: */
++
++ if (socket != null) {
++ try {
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy!!!");
++ }
++ socket = null;
++ }
++
++ /*
++ * Now connect a 3rd time, using the cert
++ * retrieved during connection 2 (sadly, that
++ * the user likely blindly agreed to...)
++ */
++
++ factory = trustone_ctx.getSocketFactory();
++ if (proxy_failure) {
++ socket = proxy_socket(factory);
++ } else {
++ socket = (SSLSocket) factory.createSocket(host, port);
++ }
++
++ try {
++ socket.startHandshake();
++ dbg("TrustAll/TrustOne Server Connection Verified #3.");
++
++ } catch (Exception ehand3) {
++ dbg("** Could not TrustAll/TrustOne Verify Server #3.");
++
++ throw new IOException(ehand3.getMessage());
++ }
++ }
++
++ /* we have socket (possibly null) at this point, so proceed: */
++
++ /* handle x11vnc GET=1, if applicable: */
++ if (socket != null && viewer.GET) {
++ String str = "GET ";
++ str += viewer.urlPrefix;
++ str += "/request.https.vnc.connection";
++ str += " HTTP/1.0\r\n";
++ str += "Pragma: No-Cache\r\n";
++ str += "\r\n";
++
++ System.out.println("sending: " + str);
++ OutputStream os = socket.getOutputStream();
++ String type = "os";
++
++ if (type == "os") {
++ os.write(str.getBytes());
++ os.flush();
++ System.out.println("used OutputStream");
++ } else if (type == "bs") {
++ BufferedOutputStream bs = new BufferedOutputStream(os);
++ bs.write(str.getBytes());
++ bs.flush();
++ System.out.println("used BufferedOutputStream");
++ } else if (type == "ds") {
++ DataOutputStream ds = new DataOutputStream(os);
++ ds.write(str.getBytes());
++ ds.flush();
++ System.out.println("used DataOutputStream");
++ }
++ if (false) {
++ String rep = "";
++ DataInputStream is = new DataInputStream(
++ new BufferedInputStream(socket.getInputStream(), 16384));
++ while (true) {
++ rep += readline(is);
++ if (rep.indexOf("\r\n\r\n") >= 0) {
++ break;
++ }
++ }
++ System.out.println("rep: " + rep);
++ }
++ }
++
++ dbg("SSL returning socket to caller.");
++ dbg("");
++
++ /* could be null, let caller handle that. */
++ return (Socket) socket;
++ }
++
++ boolean browser_cert_match() {
++ String msg = "Browser URL accept previously accepted cert";
++
++ if (user_wants_to_see_cert) {
++ return false;
++ }
++
++ if (viewer.serverCert != null || trustsrvCerts != null) {
++ if (cert_fail == null) {
++ cert_fail = "server-cert-mismatch";
++ }
++ }
++ if (trustallCerts != null && trusturlCerts != null) {
++ if (trustallCerts.length == trusturlCerts.length) {
++ boolean ok = true;
++ /* check toath trustallCerts (socket) equals trusturlCerts (browser) */
++ for (int i = 0; i < trusturlCerts.length; i++) {
++ if (! trustallCerts[i].equals(trusturlCerts[i])) {
++ dbg("BCM: cert mismatch at i=" + i);
++ dbg("BCM: cert mismatch url" + trusturlCerts[i]);
++ dbg("BCM: cert mismatch all" + trustallCerts[i]);
++ ok = false;
++ }
++ }
++ if (ok) {
++ System.out.println(msg);
++ if (cert_fail == null) {
++ cert_fail = "did-not-fail";
++ }
++ return true;
++ } else {
++ if (cert_fail == null) {
++ cert_fail = "cert-mismatch";
++ }
++ return false;
++ }
++ }
++ }
++ if (cert_fail == null) {
++ cert_fail = "missing-certs";
++ }
++ return false;
++ }
++
++ private void dbg(String s) {
++ if (debug) {
++ System.out.println(s);
++ }
++ }
++
++ private int gint(String s) {
++ int n = -1;
++ try {
++ Integer I = new Integer(s);
++ n = I.intValue();
++ } catch (Exception ex) {
++ return -1;
++ }
++ return n;
++ }
++
++ /* this will do the proxy CONNECT negotiation and hook us up. */
++
++ private void proxy_helper(String proxyHost, int proxyPort) {
++
++ boolean proxy_auth = false;
++ String proxy_auth_basic_realm = "";
++ String hp = host + ":" + port;
++ dbg("proxy_helper: " + proxyHost + ":" + proxyPort + " hp: " + hp);
++
++ /* we loop here a few times trying for the password case */
++ for (int k=0; k < 2; k++) {
++ dbg("proxy_in_use psocket: " + k);
++
++ if (proxySock != null) {
++ try {
++ proxySock.close();
++ } catch (Exception e) {
++ dbg("proxy socket is grumpy.");
++ }
++ }
++
++ proxySock = psocket(proxyHost, proxyPort);
++ if (proxySock == null) {
++ dbg("1-a sadly, returning a null socket");
++ return;
++ }
++
++ String req1 = "CONNECT " + hp + " HTTP/1.1\r\n"
++ + "Host: " + hp + "\r\n";
++
++ dbg("requesting via proxy: " + req1);
++
++ if (proxy_auth) {
++ if (proxy_auth_string == null) {
++ ProxyPasswdDialog pp = new ProxyPasswdDialog(proxyHost, proxyPort, proxy_auth_basic_realm);
++ pp.queryUser();
++ proxy_auth_string = pp.getAuth();
++ }
++ //dbg("auth1: " + proxy_auth_string);
++
++ String auth2 = Base64Coder.encodeString(proxy_auth_string);
++ //dbg("auth2: " + auth2);
++
++ req1 += "Proxy-Authorization: Basic " + auth2 + "\r\n";
++ //dbg("req1: " + req1);
++
++ dbg("added Proxy-Authorization: Basic ... to request");
++ }
++ req1 += "\r\n";
++
++ try {
++ proxy_os.write(req1.getBytes());
++ String reply = readline(proxy_is);
++
++ dbg("proxy replied: " + reply.trim());
++
++ if (reply.indexOf("HTTP/1.") == 0 && reply.indexOf(" 407 ") > 0) {
++ proxy_auth = true;
++ proxySock.close();
++ } else if (reply.indexOf("HTTP/1.") < 0 && reply.indexOf(" 200") < 0) {
++ proxySock.close();
++ proxySock = psocket(proxyHost, proxyPort);
++ if (proxySock == null) {
++ dbg("2-a sadly, returning a null socket");
++ return;
++ }
++ }
++ } catch(Exception e) {
++ dbg("some proxy socket problem: " + e.getMessage());
++ }
++
++ /* read the rest of the HTTP headers */
++ while (true) {
++ String line = readline(proxy_is);
++ dbg("proxy line: " + line.trim());
++ if (proxy_auth) {
++ String uc = line.toLowerCase();
++ if (uc.indexOf("proxy-authenticate:") == 0) {
++ if (uc.indexOf(" basic ") >= 0) {
++ int idx = uc.indexOf(" realm");
++ if (idx >= 0) {
++ proxy_auth_basic_realm = uc.substring(idx+1);
++ }
++ }
++ }
++ }
++ if (line.equals("\r\n") || line.equals("\n")) {
++ break;
++ }
++ }
++ if (!proxy_auth || proxy_auth_basic_realm.equals("")) {
++ /* we only try once for the non-password case: */
++ break;
++ }
++ }
++ }
++
++ public SSLSocket proxy_socket(SSLSocketFactory factory) {
++ Properties props = null;
++ String proxyHost = null;
++ int proxyPort = 0;
++ String proxyHost_nossl = null;
++ int proxyPort_nossl = 0;
++ String str;
++
++ /* see if we can guess the proxy info from Properties: */
++ try {
++ props = System.getProperties();
++ } catch (Exception e) {
++ /* sandboxed applet might not be able to read it. */
++ dbg("props failed: " + e.getMessage());
++ }
++ if (viewer.proxyHost != null) {
++ dbg("Using supplied proxy " + viewer.proxyHost + " " + viewer.proxyPort + " applet parameters.");
++ proxyHost = viewer.proxyHost;
++ if (viewer.proxyPort != null) {
++ proxyPort = gint(viewer.proxyPort);
++ } else {
++ proxyPort = 8080;
++ }
++
++ } else if (props != null) {
++ dbg("\n---------------\nAll props:");
++ props.list(System.out);
++ dbg("\n---------------\n\n");
++
++ /* scrape throught properties looking for proxy info: */
++
++ for (Enumeration e = props.propertyNames(); e.hasMoreElements(); ) {
++ String s = (String) e.nextElement();
++ String v = System.getProperty(s);
++ String s2 = s.toLowerCase();
++ String v2 = v.toLowerCase();
++
++ if (s2.indexOf("proxy.https.host") >= 0) {
++ proxyHost = v2;
++ continue;
++ }
++ if (s2.indexOf("proxy.https.port") >= 0) {
++ proxyPort = gint(v2);
++ continue;
++ }
++ if (s2.indexOf("proxy.http.host") >= 0) {
++ proxyHost_nossl = v2;
++ continue;
++ }
++ if (s2.indexOf("proxy.http.port") >= 0) {
++ proxyPort_nossl = gint(v2);
++ continue;
++ }
++ }
++
++ for (Enumeration e = props.propertyNames(); e.hasMoreElements(); ) {
++ String s = (String) e.nextElement();
++ String v = System.getProperty(s);
++ String s2 = s.toLowerCase();
++ String v2 = v.toLowerCase();
++
++ if (proxyHost != null && proxyPort > 0) {
++ break;
++ }
++
++ // look for something like: javaplugin.proxy.config.list = http=10.0.2.1:8082
++ if (s2.indexOf("proxy") < 0 && v2.indexOf("proxy") < 0) {
++ continue;
++ }
++ if (v2.indexOf("http") < 0) {
++ continue;
++ }
++
++ String[] pieces = v.split("[,;]");
++ for (int i = 0; i < pieces.length; i++) {
++ String p = pieces[i];
++ int j = p.indexOf("https");
++ if (j < 0) {
++ j = p.indexOf("http");
++ if (j < 0) {
++ continue;
++ }
++ }
++ j = p.indexOf("=", j);
++ if (j < 0) {
++ continue;
++ }
++ p = p.substring(j+1);
++ String [] hp = p.split(":");
++ if (hp.length != 2) {
++ continue;
++ }
++ if (hp[0].length() > 1 && hp[1].length() > 1) {
++
++ proxyPort = gint(hp[1]);
++ if (proxyPort < 0) {
++ continue;
++ }
++ proxyHost = new String(hp[0]);
++ break;
++ }
++ }
++ }
++ }
++ if (proxyHost != null) {
++ if (proxyHost_nossl != null && proxyPort_nossl > 0) {
++ dbg("Using http proxy info instead of https.");
++ proxyHost = proxyHost_nossl;
++ proxyPort = proxyPort_nossl;
++ }
++ }
++
++ if (proxy_in_use) {
++ if (proxy_dialog_host != null && proxy_dialog_port > 0) {
++ proxyHost = proxy_dialog_host;
++ proxyPort = proxy_dialog_port;
++ }
++ if (proxyHost != null) {
++ dbg("Lucky us! we figured out the Proxy parameters: " + proxyHost + " " + proxyPort);
++ } else {
++ /* ask user to help us: */
++ ProxyDialog pd = new ProxyDialog(proxyHost, proxyPort);
++ pd.queryUser();
++ proxyHost = pd.getHost();
++ proxyPort = pd.getPort();
++ proxy_dialog_host = new String(proxyHost);
++ proxy_dialog_port = proxyPort;
++ dbg("User said host: " + pd.getHost() + " port: " + pd.getPort());
++ }
++
++ proxy_helper(proxyHost, proxyPort);
++ if (proxySock == null) {
++ return null;
++ }
++ } else if (viewer.CONNECT != null) {
++ dbg("viewer.CONNECT psocket:");
++ proxySock = psocket(host, port);
++ if (proxySock == null) {
++ dbg("1-b sadly, returning a null socket");
++ return null;
++ }
++ }
++
++ if (viewer.CONNECT != null) {
++ String hp = viewer.CONNECT;
++ String req2 = "CONNECT " + hp + " HTTP/1.1\r\n"
++ + "Host: " + hp + "\r\n\r\n";
++
++ dbg("requesting2: " + req2);
++
++ try {
++ proxy_os.write(req2.getBytes());
++ String reply = readline(proxy_is);
++
++ dbg("proxy replied2: " + reply.trim());
++
++ if (reply.indexOf("HTTP/1.") < 0 && reply.indexOf(" 200") < 0) {
++ proxySock.close();
++ proxySock = psocket(proxyHost, proxyPort);
++ if (proxySock == null) {
++ dbg("2-b sadly, returning a null socket");
++ return null;
++ }
++ }
++ } catch(Exception e) {
++ dbg("proxy socket problem-2: " + e.getMessage());
++ }
++
++ while (true) {
++ String line = readline(proxy_is);
++ dbg("proxy line2: " + line.trim());
++ if (line.equals("\r\n") || line.equals("\n")) {
++ break;
++ }
++ }
++ }
++
++ Socket sslsock = null;
++ try {
++ sslsock = factory.createSocket(proxySock, host, port, true);
++ } catch(Exception e) {
++ dbg("sslsock prob: " + e.getMessage());
++ dbg("3 sadly, returning a null socket");
++ }
++
++ return (SSLSocket) sslsock;
++ }
++
++ Socket psocket(String h, int p) {
++ Socket psock = null;
++ try {
++ psock = new Socket(h, p);
++ proxy_is = new DataInputStream(new BufferedInputStream(
++ psock.getInputStream(), 16384));
++ proxy_os = psock.getOutputStream();
++ } catch(Exception e) {
++ dbg("psocket prob: " + e.getMessage());
++ return null;
++ }
++
++ return psock;
++ }
++
++ String readline(DataInputStream i) {
++ byte[] ba = new byte[1];
++ String s = new String("");
++ ba[0] = 0;
++ try {
++ while (ba[0] != 0xa) {
++ ba[0] = (byte) i.readUnsignedByte();
++ s += new String(ba);
++ }
++ } catch (Exception e) {
++ ;
++ }
++ return s;
++ }
++}
++
++class TrustDialog implements ActionListener {
++ String msg, host, text;
++ int port;
++ java.security.cert.Certificate[] trustallCerts = null;
++ boolean viewing_cert = false;
++ boolean trust_this_session = false;
++
++ /*
++ * this is the gui to show the user the cert and info and ask
++ * them if they want to continue using this cert.
++ */
++
++ Button ok, cancel, viewcert;
++ TextArea textarea;
++ Checkbox accept, deny;
++ Dialog dialog;
++
++ String s1 = "Accept this certificate temporarily for this session";
++ String s2 = "Do not accept this certificate and do not connect to"
++ + " this VNC server";
++ String ln = "\n---------------------------------------------------\n\n";
++
++ TrustDialog (String h, int p, java.security.cert.Certificate[] s) {
++ host = h;
++ port = p;
++ trustallCerts = s;
++
++ msg = "VNC Server " + host + ":" + port + " Not Verified";
++ }
++
++ public boolean queryUser(String reason) {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame(msg);
++
++ dialog = new Dialog(frame, true);
++
++ String infostr = "";
++ if (trustallCerts.length == 1) {
++ CertInfo ci = new CertInfo(trustallCerts[0]);
++ infostr = ci.get_certinfo("all");
++ }
++ if (reason != null) {
++ reason += "\n\n";
++ }
++
++ text = "\n"
+++ "Unable to verify the identity of\n"
+++ "\n"
+++ " " + host + ":" + port + "\n"
+++ "\n"
+++ infostr
+++ "\n"
+++ "as a trusted VNC server.\n"
+++ "\n"
+++ reason
+++ "In General not being able to verify the VNC Server and/or your seeing this Dialog\n"
+++ "is due to one of the following:\n"
+++ "\n"
+++ " - Your requesting to View the Certificate before accepting.\n"
+++ "\n"
+++ " - The VNC server is using a Self-Signed Certificate or a Certificate\n"
+++ " Authority not recognized by your Web Browser or Java Plugin runtime.\n"
+++ "\n"
+++ " - The use of an Apache SSL portal scheme employing CONNECT proxying AND\n"
+++ " the Apache Web server has a certificate *different* from the VNC server's.\n"
+++ "\n"
+++ " - No previously accepted Certificate (via Web Broswer/Java Plugin) could be\n"
+++ " obtained by this applet to compare the VNC Server Certificate against.\n"
+++ "\n"
+++ " - The VNC Server's Certificate does not match the one specified in the\n"
+++ " supplied 'serverCert' Java Applet Parameter.\n"
+++ "\n"
+++ " - A Man-In-The-Middle attack impersonating as the VNC server that you wish\n"
+++ " to connect to. (Wouldn't that be exciting!!)\n"
+++ "\n"
+++ "By safely copying the VNC server's Certificate (or using a common Certificate\n"
+++ "Authority certificate) you can configure your Web Browser and Java Plugin to\n"
+++ "automatically authenticate this VNC Server.\n"
+++ "\n"
+++ "If you do so, then you will only have to click \"Yes\" when this VNC Viewer\n"
+++ "applet asks you whether to trust your Browser/Java Plugin's acceptance of the\n"
+++ "certificate (except for the Apache portal case above where they don't match.)\n"
+++ "\n"
+++ "You can also set the applet parameter 'trustUrlVncCert=yes' to automatically\n"
+++ "accept certificates already accepted/trusted by your Web Browser/Java Plugin,\n"
+++ "and thereby see no dialog from this VNC Viewer applet.\n"
++;
++
++ /* the accept / do-not-accept radio buttons: */
++ CheckboxGroup checkbox = new CheckboxGroup();
++ accept = new Checkbox(s1, true, checkbox);
++ deny = new Checkbox(s2, false, checkbox);
++
++ /* put the checkboxes in a panel: */
++ Panel check = new Panel();
++ check.setLayout(new GridLayout(2, 1));
++
++ check.add(accept);
++ check.add(deny);
++
++ /* make the 3 buttons: */
++ ok = new Button("OK");
++ cancel = new Button("Cancel");
++ viewcert = new Button("View Certificate");
++
++ ok.addActionListener(this);
++ cancel.addActionListener(this);
++ viewcert.addActionListener(this);
++
++ /* put the buttons in their own panel: */
++ Panel buttonrow = new Panel();
++ buttonrow.setLayout(new FlowLayout(FlowLayout.LEFT));
++ buttonrow.add(viewcert);
++ buttonrow.add(ok);
++ buttonrow.add(cancel);
++
++ /* label at the top: */
++ Label label = new Label(msg, Label.CENTER);
++ label.setFont(new Font("Helvetica", Font.BOLD, 16));
++
++ /* textarea in the middle */
++ textarea = new TextArea(text, 38, 64,
++ TextArea.SCROLLBARS_VERTICAL_ONLY);
++ textarea.setEditable(false);
++
++ /* put the two panels in their own panel at bottom: */
++ Panel bot = new Panel();
++ bot.setLayout(new GridLayout(2, 1));
++ bot.add(check);
++ bot.add(buttonrow);
++
++ /* now arrange things inside the dialog: */
++ dialog.setLayout(new BorderLayout());
++
++ dialog.add("North", label);
++ dialog.add("South", bot);
++ dialog.add("Center", textarea);
++
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++
++ return trust_this_session;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++
++ if (evt.getSource() == viewcert) {
++ /* View Certificate button clicked */
++ if (viewing_cert) {
++ /* show the original info text: */
++ textarea.setText(text);
++ viewcert.setLabel("View Certificate");
++ viewing_cert = false;
++ } else {
++ int i;
++ /* show all (likely just one) certs: */
++ textarea.setText("");
++ for (i=0; i < trustallCerts.length; i++) {
++ int j = i + 1;
++ textarea.append("Certificate[" +
++ j + "]\n\n");
++ textarea.append(
++ trustallCerts[i].toString());
++ textarea.append(ln);
++ }
++ viewcert.setLabel("View Info");
++ viewing_cert = true;
++
++ textarea.setCaretPosition(0);
++ }
++
++ } else if (evt.getSource() == ok) {
++ /* OK button clicked */
++ if (accept.getState()) {
++ trust_this_session = true;
++ } else {
++ trust_this_session = false;
++ }
++ //dialog.dispose();
++ dialog.hide();
++
++ } else if (evt.getSource() == cancel) {
++ /* Cancel button clicked */
++ trust_this_session = false;
++
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++
++ String get_certinfo() {
++ String all = "";
++ String fields[] = {"CN", "OU", "O", "L", "C"};
++ int i;
++ if (trustallCerts.length < 1) {
++ all = "";
++ return all;
++ }
++ String cert = trustallCerts[0].toString();
++
++ /*
++ * For now we simply scrape the cert string, there must
++ * be an API for this... perhaps optionValue?
++ */
++
++ for (i=0; i < fields.length; i++) {
++ int f, t, t1, t2;
++ String sub, mat = fields[i] + "=";
++
++ f = cert.indexOf(mat, 0);
++ if (f > 0) {
++ t1 = cert.indexOf(", ", f);
++ t2 = cert.indexOf("\n", f);
++ if (t1 < 0 && t2 < 0) {
++ continue;
++ } else if (t1 < 0) {
++ t = t2;
++ } else if (t2 < 0) {
++ t = t1;
++ } else if (t1 < t2) {
++ t = t1;
++ } else {
++ t = t2;
++ }
++ if (t > f) {
++ sub = cert.substring(f, t);
++ all = all + " " + sub + "\n";
++ }
++ }
++ }
++ return all;
++ }
++}
++
++class ProxyDialog implements ActionListener {
++ String guessedHost = null;
++ String guessedPort = null;
++ /*
++ * this is the gui to show the user the cert and info and ask
++ * them if they want to continue using this cert.
++ */
++
++ Button ok;
++ Dialog dialog;
++ TextField entry;
++ String reply = "";
++
++ ProxyDialog (String h, int p) {
++ guessedHost = h;
++ try {
++ guessedPort = Integer.toString(p);
++ } catch (Exception e) {
++ guessedPort = "8080";
++ }
++ }
++
++ public void queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Need Proxy host:port");
++
++ dialog = new Dialog(frame, true);
++
++
++ Label label = new Label("Please Enter your https Proxy info as host:port", Label.CENTER);
++ //label.setFont(new Font("Helvetica", Font.BOLD, 16));
++ entry = new TextField(30);
++ ok = new Button("OK");
++ ok.addActionListener(this);
++
++ String guess = "";
++ if (guessedHost != null) {
++ guess = guessedHost + ":" + guessedPort;
++ }
++ entry.setText(guess);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", label);
++ dialog.add("Center", entry);
++ dialog.add("South", ok);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++ return;
++ }
++
++ public String getHost() {
++ int i = reply.indexOf(":");
++ if (i < 0) {
++ return "unknown";
++ }
++ String h = reply.substring(0, i);
++ return h;
++ }
++
++ public int getPort() {
++ int i = reply.indexOf(":");
++ int p = 8080;
++ if (i < 0) {
++ return p;
++ }
++ i++;
++ String ps = reply.substring(i);
++ try {
++ Integer I = new Integer(ps);
++ p = I.intValue();
++ } catch (Exception e) {
++ ;
++ }
++ return p;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == ok) {
++ reply = entry.getText();
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++}
++
++class ProxyPasswdDialog implements ActionListener {
++ String guessedHost = null;
++ String guessedPort = null;
++ String guessedUser = null;
++ String guessedPasswd = null;
++ String realm = null;
++ /*
++ * this is the gui to show the user the cert and info and ask
++ * them if they want to continue using this cert.
++ */
++
++ Button ok;
++ Dialog dialog;
++ TextField entry1;
++ TextField entry2;
++ String reply1 = "";
++ String reply2 = "";
++
++ ProxyPasswdDialog (String h, int p, String realm) {
++ guessedHost = h;
++ try {
++ guessedPort = Integer.toString(p);
++ } catch (Exception e) {
++ guessedPort = "8080";
++ }
++ this.realm = realm;
++ }
++
++ public void queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Proxy Requires Username and Password");
++
++ dialog = new Dialog(frame, true);
++
++ //Label label = new Label("Please Enter your Web Proxy Username in the top Entry and Password in the bottom Entry", Label.CENTER);
++ TextArea label = new TextArea("Please Enter your Web Proxy\nUsername in the Top Entry and\nPassword in the Bottom Entry,\nand then press OK.", 4, 20, TextArea.SCROLLBARS_NONE);
++ entry1 = new TextField(30);
++ entry2 = new TextField(30);
++ entry2.setEchoChar('*');
++ ok = new Button("OK");
++ ok.addActionListener(this);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", label);
++ dialog.add("Center", entry1);
++ dialog.add("South", entry2);
++ dialog.add("East", ok);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++ return;
++ }
++
++ public String getAuth() {
++ return reply1 + ":" + reply2;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == ok) {
++ reply1 = entry1.getText();
++ reply2 = entry2.getText();
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++}
++
++class ClientCertDialog implements ActionListener {
++
++ Button ok;
++ Dialog dialog;
++ TextField entry;
++ String reply = "";
++
++ ClientCertDialog() {
++ ;
++ }
++
++ public String queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Enter SSL Client Cert+Key String");
++
++ dialog = new Dialog(frame, true);
++
++
++ Label label = new Label("Please Enter the SSL Client Cert+Key String 308204c0...,...522d2d0a", Label.CENTER);
++ entry = new TextField(30);
++ ok = new Button("OK");
++ ok.addActionListener(this);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", label);
++ dialog.add("Center", entry);
++ dialog.add("South", ok);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++ return reply;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == ok) {
++ reply = entry.getText();
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++}
++
++class BrowserCertsDialog implements ActionListener {
++ Button yes, no;
++ Dialog dialog;
++ String vncServer;
++ String hostport;
++ public boolean showCertDialog = true;
++
++ BrowserCertsDialog(String serv, String hp) {
++ vncServer = serv;
++ hostport = hp;
++ }
++
++ public void queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Use Browser/JVM Certs?");
++
++ dialog = new Dialog(frame, true);
++
++ String m = "";
++m += "\n";
++m += "This VNC Viewer applet does not have its own keystore to track\n";
++m += "SSL certificates, and so cannot authenticate the certificate\n";
++m += "of the VNC Server:\n";
++m += "\n";
++m += " " + hostport + "\n\n " + vncServer + "\n";
++m += "\n";
++m += "on its own.\n";
++m += "\n";
++m += "However, it has noticed that your Web Browser and/or Java VM Plugin\n";
++m += "has previously accepted the same certificate. You may have set\n";
++m += "this up permanently or just for this session, or the server\n";
++m += "certificate was signed by a CA cert that your Web Browser or\n";
++m += "Java VM Plugin has.\n";
++m += "\n";
++m += "If the VNC Server connection times out while you are reading this\n";
++m += "dialog, then restart the connection and try again.\n";
++m += "\n";
++m += "Should this VNC Viewer applet now connect to the above VNC server?\n";
++m += "\n";
++
++ TextArea textarea = new TextArea(m, 22, 64,
++ TextArea.SCROLLBARS_VERTICAL_ONLY);
++ textarea.setEditable(false);
++ yes = new Button("Yes");
++ yes.addActionListener(this);
++ no = new Button("No, Let Me See the Certificate.");
++ no.addActionListener(this);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", textarea);
++ dialog.add("Center", yes);
++ dialog.add("South", no);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til Yes or No pressed. */
++ System.out.println("done show()");
++ return;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == yes) {
++ showCertDialog = false;
++ //dialog.dispose();
++ dialog.hide();
++ } else if (evt.getSource() == no) {
++ showCertDialog = true;
++ //dialog.dispose();
++ dialog.hide();
++ }
++ System.out.println("done actionPerformed()");
++ }
++}
++
++class CertInfo {
++ String fields[] = {"CN", "OU", "O", "L", "C"};
++ java.security.cert.Certificate cert;
++ String certString = "";
++
++ CertInfo(java.security.cert.Certificate c) {
++ cert = c;
++ certString = cert.toString();
++ }
++
++ String get_certinfo(String which) {
++ int i;
++ String cs = new String(certString);
++ String all = "";
++
++ /*
++ * For now we simply scrape the cert string, there must
++ * be an API for this... perhaps optionValue?
++ */
++ for (i=0; i < fields.length; i++) {
++ int f, t, t1, t2;
++ String sub, mat = fields[i] + "=";
++
++ f = cs.indexOf(mat, 0);
++ if (f > 0) {
++ t1 = cs.indexOf(", ", f);
++ t2 = cs.indexOf("\n", f);
++ if (t1 < 0 && t2 < 0) {
++ continue;
++ } else if (t1 < 0) {
++ t = t2;
++ } else if (t2 < 0) {
++ t = t1;
++ } else if (t1 < t2) {
++ t = t1;
++ } else {
++ t = t2;
++ }
++ if (t > f) {
++ sub = cs.substring(f, t);
++ all = all + " " + sub + "\n";
++ if (which.equals(fields[i])) {
++ return sub;
++ }
++ }
++ }
++ }
++ if (which.equals("all")) {
++ return all;
++ } else {
++ return "";
++ }
++ }
++}
++
++class Base64Coder {
++
++ // Mapping table from 6-bit nibbles to Base64 characters.
++ private static char[] map1 = new char[64];
++ static {
++ int i=0;
++ for (char c='A'; c<='Z'; c++) map1[i++] = c;
++ for (char c='a'; c<='z'; c++) map1[i++] = c;
++ for (char c='0'; c<='9'; c++) map1[i++] = c;
++ map1[i++] = '+'; map1[i++] = '/'; }
++
++ // Mapping table from Base64 characters to 6-bit nibbles.
++ private static byte[] map2 = new byte[128];
++ static {
++ for (int i=0; i<map2.length; i++) map2[i] = -1;
++ for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }
++
++ /**
++ * Encodes a string into Base64 format.
++ * No blanks or line breaks are inserted.
++ * @param s a String to be encoded.
++ * @return A String with the Base64 encoded data.
++ */
++ public static String encodeString (String s) {
++ return new String(encode(s.getBytes())); }
++
++ /**
++ * Encodes a byte array into Base64 format.
++ * No blanks or line breaks are inserted.
++ * @param in an array containing the data bytes to be encoded.
++ * @return A character array with the Base64 encoded data.
++ */
++ public static char[] encode (byte[] in) {
++ return encode(in,in.length); }
++
++ /**
++ * Encodes a byte array into Base64 format.
++ * No blanks or line breaks are inserted.
++ * @param in an array containing the data bytes to be encoded.
++ * @param iLen number of bytes to process in <code>in</code>.
++ * @return A character array with the Base64 encoded data.
++ */
++ public static char[] encode (byte[] in, int iLen) {
++ int oDataLen = (iLen*4+2)/3; // output length without padding
++ int oLen = ((iLen+2)/3)*4; // output length including padding
++ char[] out = new char[oLen];
++ int ip = 0;
++ int op = 0;
++ while (ip < iLen) {
++ int i0 = in[ip++] & 0xff;
++ int i1 = ip < iLen ? in[ip++] & 0xff : 0;
++ int i2 = ip < iLen ? in[ip++] & 0xff : 0;
++ int o0 = i0 >>> 2;
++ int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
++ int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
++ int o3 = i2 & 0x3F;
++ out[op++] = map1[o0];
++ out[op++] = map1[o1];
++ out[op] = op < oDataLen ? map1[o2] : '='; op++;
++ out[op] = op < oDataLen ? map1[o3] : '='; op++; }
++ return out; }
++
++ /**
++ * Decodes a string from Base64 format.
++ * @param s a Base64 String to be decoded.
++ * @return A String containing the decoded data.
++ * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
++ */
++ public static String decodeString (String s) {
++ return new String(decode(s)); }
++
++ /**
++ * Decodes a byte array from Base64 format.
++ * @param s a Base64 String to be decoded.
++ * @return An array containing the decoded data bytes.
++ * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
++ */
++ public static byte[] decode (String s) {
++ return decode(s.toCharArray()); }
++
++ /**
++ * Decodes a byte array from Base64 format.
++ * No blanks or line breaks are allowed within the Base64 encoded data.
++ * @param in a character array containing the Base64 encoded data.
++ * @return An array containing the decoded data bytes.
++ * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
++ */
++ public static byte[] decode (char[] in) {
++ int iLen = in.length;
++ if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
++ while (iLen > 0 && in[iLen-1] == '=') iLen--;
++ int oLen = (iLen*3) / 4;
++ byte[] out = new byte[oLen];
++ int ip = 0;
++ int op = 0;
++ while (ip < iLen) {
++ int i0 = in[ip++];
++ int i1 = in[ip++];
++ int i2 = ip < iLen ? in[ip++] : 'A';
++ int i3 = ip < iLen ? in[ip++] : 'A';
++ if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
++ throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
++ int b0 = map2[i0];
++ int b1 = map2[i1];
++ int b2 = map2[i2];
++ int b3 = map2[i3];
++ if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
++ throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
++ int o0 = ( b0 <<2) | (b1>>>4);
++ int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
++ int o2 = ((b2 & 3)<<6) | b3;
++ out[op++] = (byte)o0;
++ if (op<oLen) out[op++] = (byte)o1;
++ if (op<oLen) out[op++] = (byte)o2; }
++ return out; }
++
++ // Dummy constructor.
++ private Base64Coder() {}
++
++}
+diff -Naur vnc_javasrc.orig/VncCanvas.java vnc_javasrc/VncCanvas.java
+--- vnc_javasrc.orig/VncCanvas.java 2004-10-10 02:15:54.000000000 -0400
++++ vnc_javasrc/VncCanvas.java 2010-11-30 21:01:15.000000000 -0500
+@@ -28,13 +28,14 @@
+ import java.lang.*;
+ import java.util.zip.*;
+
++import java.util.Collections;
+
+ //
+ // VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
+ //
+
+ class VncCanvas extends Canvas
+- implements KeyListener, MouseListener, MouseMotionListener {
++ implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
+
+ VncViewer viewer;
+ RfbProto rfb;
+@@ -81,6 +82,20 @@
+ cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
+ cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
+
++ // kludge to not show any Java cursor in the canvas since we are
++ // showing the soft cursor (should be a user setting...)
++ Cursor dot = Toolkit.getDefaultToolkit().createCustomCursor(
++ Toolkit.getDefaultToolkit().createImage(new byte[4]), new Point(0,0),
++ "dot");
++ this.setCursor(dot);
++
++ // while we are at it... get rid of the keyboard traversals that
++ // make it so we can't type a Tab character:
++ this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
++ Collections.EMPTY_SET);
++ this.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
++ Collections.EMPTY_SET);
++
+ colors = new Color[256];
+ for (int i = 0; i < 256; i++)
+ colors[i] = new Color(cm8.getRGB(i));
+@@ -169,6 +184,7 @@
+ inputEnabled = true;
+ addMouseListener(this);
+ addMouseMotionListener(this);
++ addMouseWheelListener(this);
+ if (viewer.showControls) {
+ viewer.buttonPanel.enableRemoteAccessControls(true);
+ }
+@@ -177,6 +193,7 @@
+ inputEnabled = false;
+ removeMouseListener(this);
+ removeMouseMotionListener(this);
++ removeMouseWheelListener(this);
+ if (viewer.showControls) {
+ viewer.buttonPanel.enableRemoteAccessControls(false);
+ }
+@@ -1190,6 +1207,9 @@
+ public void mouseDragged(MouseEvent evt) {
+ processLocalMouseEvent(evt, true);
+ }
++ public void mouseWheelMoved(MouseWheelEvent evt) {
++ processLocalMouseWheelEvent(evt);
++ }
+
+ public void processLocalKeyEvent(KeyEvent evt) {
+ if (viewer.rfb != null && rfb.inNormalProtocol) {
+@@ -1221,6 +1241,19 @@
+ evt.consume();
+ }
+
++ public void processLocalMouseWheelEvent(MouseWheelEvent evt) {
++ if (viewer.rfb != null && rfb.inNormalProtocol) {
++ synchronized(rfb) {
++ try {
++ rfb.writeWheelEvent(evt);
++ } catch (Exception e) {
++ e.printStackTrace();
++ }
++ rfb.notify();
++ }
++ }
++ }
++
+ public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
+ if (viewer.rfb != null && rfb.inNormalProtocol) {
+ if (moved) {
+@@ -1387,9 +1420,9 @@
+ result = cm8.getRGB(pixBuf[i]);
+ } else {
+ result = 0xFF000000 |
+- (pixBuf[i * 4 + 1] & 0xFF) << 16 |
+- (pixBuf[i * 4 + 2] & 0xFF) << 8 |
+- (pixBuf[i * 4 + 3] & 0xFF);
++ (pixBuf[i * 4 + 2] & 0xFF) << 16 |
++ (pixBuf[i * 4 + 1] & 0xFF) << 8 |
++ (pixBuf[i * 4 + 0] & 0xFF);
+ }
+ } else {
+ result = 0; // Transparent pixel
+@@ -1403,9 +1436,9 @@
+ result = cm8.getRGB(pixBuf[i]);
+ } else {
+ result = 0xFF000000 |
+- (pixBuf[i * 4 + 1] & 0xFF) << 16 |
+- (pixBuf[i * 4 + 2] & 0xFF) << 8 |
+- (pixBuf[i * 4 + 3] & 0xFF);
++ (pixBuf[i * 4 + 2] & 0xFF) << 16 |
++ (pixBuf[i * 4 + 1] & 0xFF) << 8 |
++ (pixBuf[i * 4 + 0] & 0xFF);
+ }
+ } else {
+ result = 0; // Transparent pixel
+diff -Naur vnc_javasrc.orig/VncViewer.java vnc_javasrc/VncViewer.java
+--- vnc_javasrc.orig/VncViewer.java 2004-03-04 08:34:25.000000000 -0500
++++ vnc_javasrc/VncViewer.java 2010-03-27 17:57:04.000000000 -0400
+@@ -29,6 +29,7 @@
+ import java.awt.event.*;
+ import java.io.*;
+ import java.net.*;
++import java.util.*;
+
+ public class VncViewer extends java.applet.Applet
+ implements java.lang.Runnable, WindowListener {
+@@ -80,7 +81,7 @@
+ // Variables read from parameter values.
+ String socketFactory;
+ String host;
+- int port;
++ int port, vncserverport;
+ boolean showControls;
+ boolean offerRelogin;
+ boolean showOfflineDesktop;
+@@ -88,6 +89,24 @@
+ int deferCursorUpdates;
+ int deferUpdateRequests;
+
++ boolean disableSSL;
++ boolean GET;
++ String CONNECT;
++ String urlPrefix;
++ String httpsPort;
++ String oneTimeKey;
++ String serverCert;
++ String proxyHost;
++ String proxyPort;
++ boolean forceProxy;
++ boolean ignoreProxy;
++ boolean trustAllVncCerts;
++ boolean trustUrlVncCert;
++ boolean debugCerts;
++ boolean debugKeyboard;
++ boolean mapF5_to_atsign;
++ boolean forbid_Ctrl_Alt;
++
+ // Reference to this applet for inter-applet communication.
+ public static java.applet.Applet refApplet;
+
+@@ -282,10 +301,24 @@
+ validate();
+ }
+
+- while (!tryAuthenticate()) {
+- authenticator.retry();
+- authenticatorUnixLogin.retry();
+- }
++ if (false) {
++ /* a bug on retries: 'Error: bad position: 8' sun.awt.X11.XTextFieldPeer.setCaretPosition(XTextFieldPeer.java:215) */
++ while (!tryAuthenticate()) {
++ authenticator.retry();
++ authenticatorUnixLogin.retry();
++ }
++ } else {
++ /* just try once and not forever... */
++ if (!tryAuthenticate()) {
++ showConnectionStatus("Authentication Failed.");
++ showMessage("Authentication Failed.");
++ if (!offerRelogin) {
++ fatalError("auth failed.");
++ }
++ } else {
++ //showConnectionStatus("Authentication OK.");
++ }
++ }
+ }
+
+
+@@ -428,7 +461,10 @@
+ gbc.ipadx = 100;
+ gbc.ipady = 50;
+ gridbag.setConstraints(authPanel, gbc);
++ try {
+ vncContainer.add(authPanel);
++ } catch (Exception e) {
++ }
+
+ if (inSeparateFrame) {
+ vncFrame.pack();
+@@ -590,9 +626,28 @@
+ fatalError("HOST parameter not specified");
+ }
+ }
++ Date d = new Date();
++ System.out.println("-\nSSL VNC Java Applet starting. " + d);
+
+- String str = readParameter("PORT", true);
+- port = Integer.parseInt(str);
++ port = 0;
++ String str = readParameter("PORT", false);
++ if (str != null) {
++ port = Integer.parseInt(str);
++ }
++ // When there is a proxy VNCSERVERPORT may be inaccessible (inside firewall).
++ vncserverport = 0;
++ str = readParameter("VNCSERVERPORT", false);
++ if (str != null) {
++ vncserverport = Integer.parseInt(str);
++ }
++ if (port == 0 && vncserverport == 0) {
++ fatalError("Neither PORT nor VNCSERVERPORT parameters specified");
++ }
++ if (port == 0) {
++ // Nevertheless, fall back to vncserverport if we have to.
++ System.out.println("using vncserverport: '" + vncserverport + "' for PORT.");
++ port = vncserverport;
++ }
+
+ if (inAnApplet) {
+ str = readParameter("Open New Window", false);
+@@ -626,6 +681,121 @@
+
+ // SocketFactory.
+ socketFactory = readParameter("SocketFactory", false);
++
++ // SSL
++ disableSSL = false;
++ str = readParameter("DisableSSL", false);
++ if (str != null && str.equalsIgnoreCase("Yes"))
++ disableSSL = true;
++
++ httpsPort = readParameter("httpsPort", false);
++
++ // Extra GET, CONNECT string:
++ CONNECT = readParameter("CONNECT", false);
++ if (CONNECT != null) {
++ CONNECT = CONNECT.replaceAll(" ", ":");
++ }
++
++ GET = false;
++ str = readParameter("GET", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ GET = true;
++ }
++ if (str != null && str.equalsIgnoreCase("1")) {
++ GET = true;
++ }
++
++ urlPrefix = readParameter("urlPrefix", false);
++ if (urlPrefix != null) {
++ urlPrefix = urlPrefix.replaceAll("%2F", "/");
++ urlPrefix = urlPrefix.replaceAll("%2f", "/");
++ urlPrefix = urlPrefix.replaceAll("_2F_", "/");
++ if (urlPrefix.indexOf("/") != 0) {
++ urlPrefix = "/" + urlPrefix;
++ }
++ } else {
++ urlPrefix = "";
++ }
++ System.out.println("urlPrefix: '" + urlPrefix + "'");
++
++ oneTimeKey = readParameter("oneTimeKey", false);
++ if (oneTimeKey != null) {
++ System.out.println("oneTimeKey is set.");
++ }
++
++ serverCert = readParameter("serverCert", false);
++ if (serverCert != null) {
++ System.out.println("serverCert is set.");
++ }
++
++ forceProxy = false;
++ proxyHost = null;
++ proxyPort = null;
++ str = readParameter("forceProxy", false);
++ if (str != null) {
++ if (str.equalsIgnoreCase("Yes")) {
++ forceProxy = true;
++ } else if (str.equalsIgnoreCase("No")) {
++ forceProxy = false;
++ } else {
++ forceProxy = true;
++ String[] pieces = str.split(" ");
++ proxyHost = new String(pieces[0]);
++ if (pieces.length >= 2) {
++ proxyPort = new String(pieces[1]);
++ } else {
++ proxyPort = new String("8080");
++ }
++ }
++ }
++ str = readParameter("proxyHost", false);
++ if (str != null) {
++ proxyHost = new String(str);
++ }
++ str = readParameter("proxyPort", false);
++ if (str != null) {
++ proxyPort = new String(str);
++ }
++ if (proxyHost != null && proxyPort == null) {
++ proxyPort = new String("8080");
++ }
++
++ ignoreProxy = false;
++ str = readParameter("ignoreProxy", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ ignoreProxy = true;
++ }
++
++ trustAllVncCerts = false;
++ str = readParameter("trustAllVncCerts", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ trustAllVncCerts = true;
++ }
++ trustUrlVncCert = false;
++ str = readParameter("trustUrlVncCert", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ trustUrlVncCert = true;
++ }
++ debugCerts = false;
++ str = readParameter("debugCerts", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ debugCerts = true;
++ }
++ debugKeyboard = false;
++ str = readParameter("debugKeyboard", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ debugKeyboard = true;
++ }
++ mapF5_to_atsign = false;
++ str = readParameter("mapF5_to_atsign", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ mapF5_to_atsign = true;
++ }
++ forbid_Ctrl_Alt = false;
++ str = readParameter("forbid_Ctrl_Alt", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ forbid_Ctrl_Alt = true;
++ }
+ }
+
+ public String readParameter(String name, boolean required) {
diff --git a/webclients/ssl/ultra.vnc b/webclients/ssl/ultra.vnc
new file mode 100644
index 0000000..3c57445
--- /dev/null
+++ b/webclients/ssl/ultra.vnc
@@ -0,0 +1,28 @@
+<!--
+ index.vnc - default HTML page for TightVNC Java viewer applet, to be
+ used with Xvnc. On any file ending in .vnc, the HTTP server embedded in
+ Xvnc will substitute the following variables when preceded by a dollar:
+ USER, DESKTOP, DISPLAY, APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT,
+ PARAMS. Use two dollar signs ($$) to get a dollar sign in the generated
+ HTML page.
+
+ NOTE: the $PARAMS variable is not supported by the standard VNC, so
+ make sure you have TightVNC on the server side, if you're using this
+ variable.
+-->
+
+<HTML>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+<APPLET CODE=VncViewer.class ARCHIVE=UltraViewerSSL.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=PORT value=$PORT>
+<param name="Open New Window" value=yes>
+<param name="ignoreMSLogonCheck" value=yes>
+<param name="delayAuthPanel" value=yes>
+$PARAMS
+</APPLET>
+<BR>
+<A href="http://www.karlrunge.com/x11vnc">x11vnc site</A>
+</HTML>
diff --git a/webclients/ssl/ultraproxy.vnc b/webclients/ssl/ultraproxy.vnc
new file mode 100644
index 0000000..fd842c4
--- /dev/null
+++ b/webclients/ssl/ultraproxy.vnc
@@ -0,0 +1,28 @@
+<!--
+ index.vnc - default HTML page for TightVNC Java viewer applet, to be
+ used with Xvnc. On any file ending in .vnc, the HTTP server embedded in
+ Xvnc will substitute the following variables when preceded by a dollar:
+ USER, DESKTOP, DISPLAY, APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT,
+ PARAMS. Use two dollar signs ($$) to get a dollar sign in the generated
+ HTML page.
+
+ NOTE: the $PARAMS variable is not supported by the standard VNC, so
+ make sure you have TightVNC on the server side, if you're using this
+ variable.
+-->
+
+<HTML>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+<APPLET CODE=VncViewer.class ARCHIVE=SignedUltraViewerSSL.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name="Open New Window" value=yes>
+<param name="ignoreMSLogonCheck" value=yes>
+<param name="delayAuthPanel" value=yes>
+<param name=VNCSERVERPORT value=$PORT>
+$PARAMS
+</APPLET>
+<BR>
+<A href="http://www.karlrunge.com/x11vnc">x11vnc site</A>
+</HTML>
diff --git a/webclients/ssl/ultrasigned.vnc b/webclients/ssl/ultrasigned.vnc
new file mode 100644
index 0000000..a711655
--- /dev/null
+++ b/webclients/ssl/ultrasigned.vnc
@@ -0,0 +1,28 @@
+<!--
+ index.vnc - default HTML page for TightVNC Java viewer applet, to be
+ used with Xvnc. On any file ending in .vnc, the HTTP server embedded in
+ Xvnc will substitute the following variables when preceded by a dollar:
+ USER, DESKTOP, DISPLAY, APPLETWIDTH, APPLETHEIGHT, WIDTH, HEIGHT, PORT,
+ PARAMS. Use two dollar signs ($$) to get a dollar sign in the generated
+ HTML page.
+
+ NOTE: the $PARAMS variable is not supported by the standard VNC, so
+ make sure you have TightVNC on the server side, if you're using this
+ variable.
+-->
+
+<HTML>
+<TITLE>
+$USER's $DESKTOP desktop ($DISPLAY)
+</TITLE>
+<APPLET CODE=VncViewer.class ARCHIVE=SignedUltraViewerSSL.jar
+ WIDTH=$APPLETWIDTH HEIGHT=$APPLETHEIGHT>
+<param name=PORT value=$PORT>
+<param name="Open New Window" value=yes>
+<param name="ignoreMSLogonCheck" value=yes>
+<param name="delayAuthPanel" value=yes>
+$PARAMS
+</APPLET>
+<BR>
+<A href="http://www.karlrunge.com/x11vnc">x11vnc site</A>
+</HTML>
diff --git a/webclients/ssl/ultravnc-102-JavaViewer-ssl-etc.patch b/webclients/ssl/ultravnc-102-JavaViewer-ssl-etc.patch
new file mode 100644
index 0000000..3309860
--- /dev/null
+++ b/webclients/ssl/ultravnc-102-JavaViewer-ssl-etc.patch
@@ -0,0 +1,5494 @@
+diff -Naur JavaViewer.orig/ButtonPanel.java JavaViewer/ButtonPanel.java
+--- JavaViewer.orig/ButtonPanel.java 2004-12-12 20:51:02.000000000 -0500
++++ JavaViewer/ButtonPanel.java 2007-05-31 15:40:45.000000000 -0400
+@@ -43,30 +43,36 @@
+ viewer = v;
+
+ setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+- disconnectButton = new Button("Disconnect");
++ if (v.ftpOnly) {
++ disconnectButton = new Button("Quit");
++ } else {
++ disconnectButton = new Button("Close");
++ }
+ disconnectButton.setEnabled(false);
+ add(disconnectButton);
+ disconnectButton.addActionListener(this);
+- optionsButton = new Button("Options");
+- add(optionsButton);
+- optionsButton.addActionListener(this);
+- clipboardButton = new Button("Clipboard");
+- clipboardButton.setEnabled(false);
+- add(clipboardButton);
+- clipboardButton.addActionListener(this);
+- if (viewer.rec != null) {
+- recordButton = new Button("Record");
+- add(recordButton);
+- recordButton.addActionListener(this);
+- }
+- ctrlAltDelButton = new Button("Send Ctrl-Alt-Del");
+- ctrlAltDelButton.setEnabled(false);
+- add(ctrlAltDelButton);
+- ctrlAltDelButton.addActionListener(this);
+- refreshButton = new Button("Refresh");
+- refreshButton.setEnabled(false);
+- add(refreshButton);
+- refreshButton.addActionListener(this);
++ if (!v.ftpOnly) {
++ optionsButton = new Button("Options");
++ add(optionsButton);
++ optionsButton.addActionListener(this);
++ clipboardButton = new Button("Clipboard");
++ clipboardButton.setEnabled(false);
++ add(clipboardButton);
++ clipboardButton.addActionListener(this);
++ if (viewer.rec != null) {
++ recordButton = new Button("Record");
++ add(recordButton);
++ recordButton.addActionListener(this);
++ }
++ ctrlAltDelButton = new Button("Send Ctrl-Alt-Del");
++ ctrlAltDelButton.setEnabled(false);
++ add(ctrlAltDelButton);
++ ctrlAltDelButton.addActionListener(this);
++ refreshButton = new Button("Refresh");
++ refreshButton.setEnabled(false);
++ add(refreshButton);
++ refreshButton.addActionListener(this);
++ }
+ ftpButton = new Button("File Transfer");
+ ftpButton.setEnabled(false);
+ add(ftpButton);
+@@ -79,9 +85,10 @@
+
+ public void enableButtons() {
+ disconnectButton.setEnabled(true);
++ ftpButton.setEnabled(true);
++ if (viewer.ftpOnly) {return;}
+ clipboardButton.setEnabled(true);
+ refreshButton.setEnabled(true);
+- ftpButton.setEnabled(true);
+ }
+
+ //
+@@ -89,6 +96,9 @@
+ //
+
+ public void disableButtonsOnDisconnect() {
++ ftpButton.setEnabled(false);
++ if (viewer.ftpOnly) {return;}
++
+ remove(disconnectButton);
+ disconnectButton = new Button("Hide desktop");
+ disconnectButton.setEnabled(true);
+@@ -99,7 +109,6 @@
+ clipboardButton.setEnabled(false);
+ ctrlAltDelButton.setEnabled(false);
+ refreshButton.setEnabled(false);
+- ftpButton.setEnabled(false);
+
+ validate();
+ }
+@@ -110,6 +119,7 @@
+ //
+
+ public void enableRemoteAccessControls(boolean enable) {
++ if (viewer.ftpOnly) {return;}
+ ctrlAltDelButton.setEnabled(enable);
+ }
+
+@@ -163,9 +173,19 @@
+ }
+ else if (evt.getSource() == ftpButton)
+ {
+- viewer.ftp.setVisible(!viewer.ftp.isVisible());
++// begin runge/x11vnc
++ if (viewer.ftpOnly) {
++ viewer.vncFrame.setVisible(false);
++ }
++ viewer.ftp.setSavedLocations();
++ if (viewer.ftp.isVisible()) {
++ viewer.ftp.doClose();
++ } else {
++ viewer.ftp.doOpen();
++ }
++// end runge/x11vnc
+ viewer.rfb.readServerDriveList();
+-
++
+ }
+ }
+ }
+diff -Naur JavaViewer.orig/FTPFrame.java JavaViewer/FTPFrame.java
+--- JavaViewer.orig/FTPFrame.java 2005-03-15 23:53:14.000000000 -0500
++++ JavaViewer/FTPFrame.java 2009-01-13 09:48:30.000000000 -0500
+@@ -24,8 +24,17 @@
+ import java.io.*;
+ import java.util.ArrayList;
+ import java.util.Vector;
++import java.util.Date;
+ import javax.swing.*;
+
++import java.nio.ByteBuffer;
++import java.nio.CharBuffer;
++import java.nio.charset.*;
++
++// begin runge/x11vnc
++import java.util.Arrays;
++// end runge/x11vnc
++
+
+ /*
+ * Created on Feb 25, 2004
+@@ -74,12 +83,31 @@
+ public javax.swing.JTextField connectionStatus = null;
+ public boolean updateDriveList;
+ private Vector remoteList = null;
++ private Vector remoteListInfo = null;
+ private Vector localList = null;
++ private Vector localListInfo = null;
+ private File currentLocalDirectory = null; // Holds the current local Directory
+ private File currentRemoteDirectory = null; // Holds the current remote Directory
+ private File localSelection = null; // Holds the currently selected local file
+ private String remoteSelection = null; // Holds the currently selected remote file
+ public String selectedTable = null;
++
++// begin runge/x11vnc
++ private javax.swing.JButton viewButton = null;
++ private javax.swing.JButton refreshButton = null;
++ public File saveLocalDirectory = null;
++ public long saveLocalDirectoryTime = 0;
++ public int saveLocalDirectoryCount = 0;
++ public String saveRemoteDirectory = null;
++ public long saveRemoteDirectoryTime = 0;
++ public int saveRemoteDirectoryCount = 0;
++ private boolean localCurrentIsDir = true;
++ private int lastRemoteIndex = -1;
++ private int lastLocalIndex = -1;
++ private boolean doingShortcutDir = false;
++ private boolean gotShortcutDir = false;
++ private boolean ignore_events = false;
++// end runge/x11vnc
+
+ // sf@2004 - Separate directories and files for better lisibility
+ private ArrayList DirsList;
+@@ -125,11 +153,61 @@
+
+ void refreshRemoteLocation()
+ {
++
++//System.out.println("refreshRemoteLocation1");
+ remoteList.clear();
++ remoteListInfo.clear();
+ remoteFileTable.setListData(remoteList);
++System.out.println("refreshRemoteLocation '" + remoteLocation.getText() + "'"); // runge/x11vnc
+ viewer.rfb.readServerDirectory(remoteLocation.getText());
+ }
+
++// begin runge/x11vnc
++ public void setSavedLocations() {
++ saveLocalDirectory = currentLocalDirectory;
++ saveLocalDirectoryTime = System.currentTimeMillis();
++ saveLocalDirectoryCount = 0;
++
++ if (remoteLocation != null) {
++ saveRemoteDirectory = remoteLocation.getText();
++System.out.println("RemoteSave '" + saveRemoteDirectory + "'");
++ }
++ saveRemoteDirectoryTime = System.currentTimeMillis();
++ saveRemoteDirectoryCount = 0;
++ }
++
++ private File saveLocalHack(File dir) {
++ saveLocalDirectoryCount++;
++//System.out.println("L " + saveLocalDirectoryCount + " dt: " + (System.currentTimeMillis() - saveLocalDirectoryTime) + " - " + saveLocalDirectory);
++ if (System.currentTimeMillis() > saveLocalDirectoryTime + 2000 || saveLocalDirectoryCount > 2) {
++ saveLocalDirectory = null;
++ }
++ if (saveLocalDirectory != null) {
++ currentLocalDirectory = saveLocalDirectory;
++ localLocation.setText(saveLocalDirectory.toString());
++ return saveLocalDirectory;
++ } else {
++ return dir;
++ }
++ }
++
++ private String saveRemoteHack(String indrive) {
++ saveRemoteDirectoryCount++;
++//System.out.println("R " + saveRemoteDirectoryCount + " - " + saveRemoteDirectory);
++ if (saveRemoteDirectory != null && saveRemoteDirectoryCount > 1) {
++ saveRemoteDirectory = null;
++ }
++ if (saveRemoteDirectory != null) {
++ if (! saveRemoteDirectory.equals("")) {
++System.out.println("saveRemoteHack setText + refreshRemoteLocation '" + saveRemoteDirectory + "'");
++ return saveRemoteDirectory;
++ }
++ }
++ return indrive;
++ }
++// end runge/x11vnc
++
++
+ /*
+ * Prints the list of drives on the remote directory and returns a String[].
+ * str takes as string like A:fC:lD:lE:lF:lG:cH:c
+@@ -143,6 +221,9 @@
+ int size = str.length();
+ String driveType = null;
+ String[] drive = new String[str.length() / 3];
++ int idx = 0, C_drive = -1, O_drive = -1;
++
++System.out.println("ComboBox: Str '" + str + "'");
+
+ // Loop through the string to create a String[]
+ for (int i = 0; i < size; i = i + 3) {
+@@ -150,26 +231,68 @@
+ driveType = str.substring(i + 2, i + 3);
+ if (driveType.compareTo("f") == 0)
+ drive[i / 3] += "\\ Floppy";
+- if (driveType.compareTo("l") == 0)
++ if (driveType.compareTo("l") == 0) {
+ drive[i / 3] += "\\ Local Disk";
++ if (drive[i/3].substring(0,1).toUpperCase().equals("C")) {
++ C_drive = idx;
++ } else if (O_drive < 0) {
++ O_drive = idx;
++ }
++ }
+ if (driveType.compareTo("c") == 0)
+ drive[i / 3] += "\\ CD-ROM";
+ if (driveType.compareTo("n") == 0)
+ drive[i / 3] += "\\ Network";
+
+ remoteDrivesComboBox.addItem(drive[i / 3]);
++System.out.println("ComboBox: Add " + idx + " '" + drive[i/3] + "'");
++ idx++;
++ }
++
++ // runge
++ if (viewer.ftpDropDown != null) {
++ String[] dd = viewer.ftpDropDown.split("\\.");
++ for (int i=0; i < dd.length; i++) {
++ if (!dd[i].equals("")) {
++ String s = dd[i];
++ if (s.startsWith("TOP_")) {
++ s = s.substring(4);
++ remoteDrivesComboBox.insertItemAt(" [" + s + "]", 0);
++ } else {
++ remoteDrivesComboBox.addItem(" [" + s + "]");
++ }
++ }
++ }
++ } else {
++ remoteDrivesComboBox.addItem(" [My Documents]");
++ remoteDrivesComboBox.addItem(" [Desktop]");
++ remoteDrivesComboBox.addItem(" [Home]");
+ }
++
+ //sf@ - Select Drive C:as default if possible
+ boolean bFound = false;
+- for(int i = 0; i < remoteDrivesComboBox.getItemCount() ; i++)
+- {
+- if(remoteDrivesComboBox.getItemAt(i).toString().substring(0,1).toUpperCase().equals("C"))
+- {
+- remoteDrivesComboBox.setSelectedIndex(i);
++
++ if (false) {
++ for(int i = 0; i < remoteDrivesComboBox.getItemCount() ; i++) {
++ if(remoteDrivesComboBox.getItemAt(i).toString().substring(0,1).toUpperCase().equals("C")) {
++ remoteDrivesComboBox.setSelectedIndex(i);
++ bFound = true;
++ }
++ }
++ } else {
++ if (C_drive >= 0) {
++ remoteDrivesComboBox.setSelectedIndex(C_drive);
++ bFound = true;
++System.out.println("ComboBox: C_drive index: " + C_drive);
++ } else if (O_drive >= 0) {
++ remoteDrivesComboBox.setSelectedIndex(O_drive);
+ bFound = true;
++System.out.println("ComboBox: Other_drive index: " + O_drive);
+ }
+ }
++
+ if (!bFound) remoteDrivesComboBox.setSelectedIndex(0);
++
+ updateDriveList = false;
+ return drive;
+ }
+@@ -185,6 +308,8 @@
+ stopButton.setVisible(true);
+ stopButton.setEnabled(true);
+ receiveButton.setEnabled(false);
++ viewButton.setEnabled(false); // runge/x11vnc
++ refreshButton.setEnabled(false);
+ remoteTopButton.setEnabled(false);
+ sendButton.setEnabled(false);
+ remoteFileTable.setEnabled(false);
+@@ -207,6 +332,8 @@
+ stopButton.setVisible(false);
+ stopButton.setEnabled(false);
+ receiveButton.setEnabled(true);
++ viewButton.setEnabled(true); // runge/x11vnc
++ refreshButton.setEnabled(true);
+ remoteTopButton.setEnabled(true);
+ sendButton.setEnabled(true);
+ remoteFileTable.setEnabled(true);
+@@ -221,10 +348,11 @@
+ /*
+ * Print Directory prints out all the contents of a directory
+ */
+- void printDirectory(ArrayList a) {
++ void printDirectory(ArrayList a, ArrayList b) {
+
+ for (int i = 0; i < a.size(); i++) {
+ remoteList.addElement(a.get(i));
++ remoteListInfo.addElement(b.get(i));
+ }
+ remoteFileTable.setListData(remoteList);
+ }
+@@ -235,10 +363,12 @@
+ * @return void
+ */
+ private void initialize() {
++ ignore_events = true;
+ this.setSize(794, 500);
+ this.setContentPane(getJContentPane());
++ ignore_events = false;
+ updateDriveList = true;
+- }
++ }
+ /**
+ * This method initializes jContentPane. This is the main content pane
+ *
+@@ -253,6 +383,33 @@
+ jContentPane.add(getRemotePanel(), java.awt.BorderLayout.EAST);
+ jContentPane.add(getLocalPanel(), java.awt.BorderLayout.WEST);
+ jContentPane.add(getButtonPanel(), java.awt.BorderLayout.CENTER);
++
++ KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
++ AbstractAction escapeAction = new AbstractAction() {
++ public void actionPerformed(ActionEvent actionEvent) {
++ System.out.println("Escape Pressed");
++ if (viewer.ftpOnly) {
++ System.out.println("exiting...");
++ System.exit(0);
++ } else {
++ doClose();
++ }
++ }
++ };
++ jContentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(stroke, "escapeAction");
++ jContentPane.getInputMap().put(stroke, "escapeAction");
++ jContentPane.getActionMap().put("escapeAction", escapeAction);
++
++ stroke = KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_MASK);
++ AbstractAction resetAction = new AbstractAction() {
++ public void actionPerformed(ActionEvent actionEvent) {
++ System.out.println("Ctrl-R Pressed");
++ doReset();
++ }
++ };
++ jContentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(stroke, "resetAction");
++ jContentPane.getInputMap().put(stroke, "resetAction");
++ jContentPane.getActionMap().put("resetAction", resetAction);
+ }
+ return jContentPane;
+ }
+@@ -270,6 +427,7 @@
+ topPanelLocal.add(getLocalMachineLabel(), java.awt.BorderLayout.CENTER);
+ topPanelLocal.add(getLocalTopButton(), java.awt.BorderLayout.EAST);
+ topPanelLocal.setBackground(java.awt.Color.lightGray);
++//System.out.println("getTopPanelLocal");
+ }
+ return topPanelLocal;
+ }
+@@ -288,6 +446,7 @@
+ topPanelRemote.add(getRemoteMachineLabel(), java.awt.BorderLayout.CENTER);
+ topPanelRemote.add(getRemoteTopButton(), java.awt.BorderLayout.EAST);
+ topPanelRemote.setBackground(java.awt.Color.lightGray);
++//System.out.println("getTopPanelRemote");
+ }
+ return topPanelRemote;
+ }
+@@ -301,6 +460,7 @@
+ if (topPanelCenter == null) {
+ topPanelCenter = new javax.swing.JPanel();
+ topPanelCenter.add(getDummyButton(), null);
++//System.out.println("getTopPanelCenter");
+ }
+ return topPanelCenter;
+ }
+@@ -328,6 +488,7 @@
+ topPanel.add(getRemoteTopButton(), null);
+ topPanel.setBackground(java.awt.Color.lightGray);
+ */
++//System.out.println("getTopPanel");
+ }
+ return topPanel;
+ }
+@@ -348,6 +509,7 @@
+ statusPanel.add(getJProgressBar(), null);
+ statusPanel.add(getConnectionStatus(), null);
+ statusPanel.setBackground(java.awt.Color.lightGray);
++//System.out.println("getStatusPanel");
+
+ }
+ return statusPanel;
+@@ -368,6 +530,7 @@
+ remotePanel.add(getRemoteScrollPane(), null);
+ remotePanel.add(getRemoteStatus(), null);
+ remotePanel.setBackground(java.awt.Color.lightGray);
++//System.out.println("getRemotePanel");
+ }
+ return remotePanel;
+ }
+@@ -390,6 +553,7 @@
+ localPanel.setComponentOrientation(
+ java.awt.ComponentOrientation.UNKNOWN);
+ localPanel.setName("localPanel");
++//System.out.println("getLocalPanel");
+ }
+ return localPanel;
+ }
+@@ -405,12 +569,15 @@
+ buttonPanel = new javax.swing.JPanel();
+ buttonPanel.setLayout(null);
+ buttonPanel.add(getReceiveButton(), null);
++ buttonPanel.add(getRefreshButton(), null); // runge/x11vnc
++ buttonPanel.add(getViewButton(), null); // runge/x11vnc
+ buttonPanel.add(getNewFolderButton(), null);
+ buttonPanel.add(getCloseButton(), null);
+ buttonPanel.add(getDeleteButton(), null);
+ buttonPanel.add(getSendButton(), null);
+ buttonPanel.add(getStopButton(), null);
+ buttonPanel.setBackground(java.awt.Color.lightGray);
++//System.out.println("getButtonPanel");
+ }
+ return buttonPanel;
+ }
+@@ -422,10 +589,11 @@
+ private javax.swing.JButton getSendButton() {
+ if (sendButton == null) {
+ sendButton = new javax.swing.JButton();
+- sendButton.setBounds(20, 30, 97, 25);
++ sendButton.setBounds(15, 30, 107, 25); // runge/x11vnc
+ sendButton.setText("Send >>");
+ sendButton.setName("sendButton");
+ sendButton.addActionListener(this);
++//System.out.println("getSendButton");
+
+ }
+ return sendButton;
+@@ -438,7 +606,7 @@
+ private javax.swing.JButton getReceiveButton() {
+ if (receiveButton == null) {
+ receiveButton = new javax.swing.JButton();
+- receiveButton.setBounds(20, 60, 97, 25);
++ receiveButton.setBounds(15, 60, 107, 25); // runge/x11vnc
+ receiveButton.setText("<< Receive");
+ receiveButton.setName("receiveButton");
+ receiveButton.addActionListener(this);
+@@ -453,7 +621,7 @@
+ private javax.swing.JButton getDeleteButton() {
+ if (deleteButton == null) {
+ deleteButton = new javax.swing.JButton();
+- deleteButton.setBounds(20, 110, 97, 25);
++ deleteButton.setBounds(15, 110, 107, 25); // runge/x11vnc
+ deleteButton.setText("Delete File");
+ deleteButton.setName("deleteButton");
+ deleteButton.addActionListener(this);
+@@ -468,7 +636,7 @@
+ private javax.swing.JButton getNewFolderButton() {
+ if (newFolderButton == null) {
+ newFolderButton = new javax.swing.JButton();
+- newFolderButton.setBounds(20, 140, 97, 25);
++ newFolderButton.setBounds(15, 140, 107, 25); // runge/x11vnc
+ newFolderButton.setText("New Folder");
+ newFolderButton.setName("newFolderButton");
+ newFolderButton.addActionListener(this);
+@@ -476,6 +644,39 @@
+ return newFolderButton;
+ }
+
++// begin runge/x11vnc
++ /**
++ * This method initializes refreshButton
++ *
++ * @return javax.swing.JButton
++ */
++ private javax.swing.JButton getRefreshButton() {
++ if (refreshButton == null) {
++ refreshButton = new javax.swing.JButton();
++ refreshButton.setBounds(15, 170, 107, 25);
++ refreshButton.setText("Refresh");
++ refreshButton.setName("refreshButton");
++ refreshButton.addActionListener(this);
++ }
++ return refreshButton;
++ }
++ /**
++ * This method initializes viewButton
++ *
++ * @return javax.swing.JButton
++ */
++ private javax.swing.JButton getViewButton() {
++ if (viewButton == null) {
++ viewButton = new javax.swing.JButton();
++ viewButton.setBounds(15, 200, 107, 25);
++ viewButton.setText("View File");
++ viewButton.setName("viewButton");
++ viewButton.addActionListener(this);
++ }
++ return viewButton;
++ }
++// end runge/x11vnc
++
+ /**
+ * This method initializes stopButton
+ *
+@@ -486,7 +687,7 @@
+ if (stopButton == null)
+ {
+ stopButton = new javax.swing.JButton();
+- stopButton.setBounds(20, 200, 97, 25);
++ stopButton.setBounds(15, 230, 107, 25); // runge/x11vnc
+ stopButton.setText("Stop");
+ stopButton.setName("stopButton");
+ stopButton.addActionListener(this);
+@@ -503,8 +704,12 @@
+ private javax.swing.JButton getCloseButton() {
+ if (closeButton == null) {
+ closeButton = new javax.swing.JButton();
+- closeButton.setBounds(20, 325, 97, 25);
+- closeButton.setText("Close");
++ closeButton.setBounds(15, 325, 107, 25); // runge/x11vnc
++ if (viewer.ftpOnly) {
++ closeButton.setText("Quit");
++ } else {
++ closeButton.setText("Close");
++ }
+ closeButton.setName("closeButton");
+ closeButton.addActionListener(this);
+ }
+@@ -551,6 +756,7 @@
+ //Select the second entry (e.g. C:\)
+ // localDrivesComboBox.setSelectedIndex(1);
+ localDrivesComboBox.addActionListener(this);
++//System.out.println("getLocalDrivesComboBox");
+ }
+ updateDriveList = false;
+ return localDrivesComboBox;
+@@ -567,6 +773,7 @@
+ remoteDrivesComboBox.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
+ remoteDrivesComboBox.addActionListener(this);
++//System.out.println("getRemoteDrivesComboBox");
+
+ }
+ return remoteDrivesComboBox;
+@@ -587,6 +794,7 @@
+ localMachineLabel.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.BOLD, 11));
+ localMachineLabel.setEditable(false);
++//System.out.println("getLocalMachineLabel");
+ }
+ return localMachineLabel;
+ }
+@@ -622,6 +830,7 @@
+ localTopButton.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.BOLD, 10));
+ localTopButton.addActionListener(this);
++//System.out.println("getLocalTopButton");
+ }
+ return localTopButton;
+ }
+@@ -638,6 +847,7 @@
+ remoteTopButton.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.BOLD, 10));
+ remoteTopButton.addActionListener(this);
++//System.out.println("getRemoteTopButton");
+ }
+ return remoteTopButton;
+ }
+@@ -650,9 +860,24 @@
+ private javax.swing.JList getLocalFileTable() {
+ if (localFileTable == null) {
+ localList = new Vector(0);
++ localListInfo = new Vector(0);
+ localFileTable = new JList(localList);
++ MouseMotionListener mlisten = new MouseMotionAdapter() {
++ public void mouseMoved(MouseEvent e) {
++ int index = localFileTable.locationToIndex(e.getPoint());
++ if (index == lastLocalIndex) {
++ return;
++ } else if (index < 0) {
++ return;
++ }
++ lastLocalIndex = index;
++ connectionStatus.setText((String) localListInfo.get(index));
++ }
++ };
+ localFileTable.addMouseListener(this);
++ localFileTable.addMouseMotionListener(mlisten);
+ localFileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
++//System.out.println("getLocalFileTable");
+ }
+ return localFileTable;
+ }
+@@ -669,6 +894,7 @@
+ localScrollPane.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
+ localScrollPane.setName("localFileList");
++//System.out.println("getLocalScrollPane");
+ }
+ return localScrollPane;
+ }
+@@ -680,10 +906,25 @@
+ private javax.swing.JList getRemoteFileTable() {
+ if (remoteFileTable == null) {
+ remoteList = new Vector(0);
++ remoteListInfo = new Vector(0);
+ remoteFileTable = new JList(remoteList);
++ MouseMotionListener mlisten = new MouseMotionAdapter() {
++ public void mouseMoved(MouseEvent e) {
++ int index = remoteFileTable.locationToIndex(e.getPoint());
++ if (index == lastRemoteIndex) {
++ return;
++ } else if (index < 0) {
++ return;
++ }
++ lastRemoteIndex = index;
++ connectionStatus.setText((String) remoteListInfo.get(index));
++ }
++ };
+ remoteFileTable.addMouseListener(this);
++ remoteFileTable.addMouseMotionListener(mlisten);
+ remoteFileTable.setSelectedValue("C:\\", false);
+ remoteFileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
++//System.out.println("getRemoteFileTable");
+
+ }
+ return remoteFileTable;
+@@ -698,6 +939,7 @@
+ remoteScrollPane = new javax.swing.JScrollPane();
+ remoteScrollPane.setViewportView(getRemoteFileTable());
+ remoteScrollPane.setPreferredSize(new java.awt.Dimension(325, 418));
++//System.out.println("getRemoteScrollPane");
+ }
+ return remoteScrollPane;
+ }
+@@ -716,6 +958,7 @@
+ remoteLocation.setBackground(new Color(255,255,238));
+ remoteLocation.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
++//System.out.println("getRemoteLocation");
+ }
+ return remoteLocation;
+ }
+@@ -732,6 +975,7 @@
+ localLocation.setBackground( new Color(255,255,238));
+ localLocation.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
++//System.out.println("getLocalLocation");
+ }
+ return localLocation;
+ }
+@@ -748,6 +992,7 @@
+ localStatus.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
+ localStatus.setEditable(false);
++//System.out.println("getLocalStatus");
+ }
+ return localStatus;
+ }
+@@ -764,6 +1009,7 @@
+ remoteStatus.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
+ remoteStatus.setEditable(false);
++//System.out.println("getRemoteStatus");
+ }
+ return remoteStatus;
+ }
+@@ -777,9 +1023,10 @@
+ historyComboBox = new javax.swing.JComboBox();
+ historyComboBox.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.BOLD, 10));
+- historyComboBox.insertItemAt(new String("Pulldown to view history ..."),0);
++ historyComboBox.insertItemAt(new String("Pulldown to view history; Press Escape to Close/Quit; Press Ctrl-R to Reset Panel."),0);
+ historyComboBox.setSelectedIndex(0);
+ historyComboBox.addActionListener(this);
++//System.out.println("getHistoryComboBox");
+ }
+ return historyComboBox;
+ }
+@@ -791,6 +1038,7 @@
+ private javax.swing.JProgressBar getJProgressBar() {
+ if (jProgressBar == null) {
+ jProgressBar = new javax.swing.JProgressBar();
++//System.out.println("getJProgressBar");
+ }
+ return jProgressBar;
+ }
+@@ -806,6 +1054,7 @@
+ connectionStatus.setBackground(java.awt.Color.lightGray);
+ connectionStatus.setFont(
+ new java.awt.Font("Dialog", java.awt.Font.PLAIN, 10));
++//System.out.println("getConnectionStatus");
+ }
+ connectionStatus.setEditable(false);
+ return connectionStatus;
+@@ -815,7 +1064,12 @@
+ * Implements Action listener.
+ */
+ public void actionPerformed(ActionEvent evt) {
+- System.out.println(evt.getSource());
++// System.out.println(evt.getSource());
++
++ if (ignore_events) {
++ System.out.println("ignore_events: " + evt.getSource());
++ return;
++ }
+
+ if (evt.getSource() == closeButton)
+ { // Close Button
+@@ -829,15 +1083,27 @@
+ {
+ doReceive();
+ }
++// begin runge/x11vnc
++ else if (evt.getSource() == viewButton)
++ {
++ doView();
++ }
++// end runge/x11vnc
+ else if (evt.getSource() == localDrivesComboBox)
+ {
+ changeLocalDrive();
+ }
+ else if (evt.getSource() == remoteDrivesComboBox)
+ {
++//System.out.println("remoteDrivesComboBox"); // runge/x11vnc
+ changeRemoteDrive();
+- remoteList.clear();
+- remoteFileTable.setListData(remoteList);
++
++ // are these really needed? changeRemoteDrive() does them at the end.
++ if (false) {
++ remoteList.clear();
++ remoteListInfo.clear();
++ remoteFileTable.setListData(remoteList);
++ }
+ }
+ else if (evt.getSource() == localTopButton)
+ {
+@@ -845,12 +1111,17 @@
+ }
+ else if (evt.getSource() == remoteTopButton)
+ {
++//System.out.println("remoteTopButton"); // runge/x11vnc
+ changeRemoteDrive();
+ }
+ else if(evt.getSource() == deleteButton)
+ {
+ doDelete();
+ }
++ else if(evt.getSource() == refreshButton)
++ {
++ doRefresh();
++ }
+ else if(evt.getSource()==newFolderButton)
+ {
+ doNewFolder();
+@@ -864,7 +1135,7 @@
+
+ private void doNewFolder()
+ {
+- String name = JOptionPane.showInputDialog(null,"Enter new directory name", "Create New Directory", JOptionPane.QUESTION_MESSAGE);
++ String name = JOptionPane.showInputDialog(jContentPane,"Enter new directory name", "Create New Directory", JOptionPane.QUESTION_MESSAGE);
+ if(selectedTable.equals("remote"))
+ {
+ name = remoteLocation.getText()+name;
+@@ -880,34 +1151,106 @@
+ historyComboBox.setSelectedIndex(0);
+ }
+ }
+- private void doClose()
++ public void doClose()
+ {
++ if (viewer.ftpOnly) {
++ viewer.disconnect();
++ return;
++ }
+ try {
+ this.setVisible(false);
+- viewer.rfb.writeFramebufferUpdateRequest(
+- 0,
+- 0,
+- viewer.rfb.framebufferWidth,
+- viewer.rfb.framebufferHeight,
+- true);
++ viewer.rfb.writeFramebufferUpdateRequest(0, 0, viewer.rfb.framebufferWidth,
++ viewer.rfb.framebufferHeight, true);
++
++ if (false) {
++ this.dispose();
++ jContentPane = null;
++ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
++ private void unSwing() {
++ jContentPane = null;
++ topPanel = null;
++ topPanelLocal = null;
++ topPanelRemote = null;
++ topPanelCenter = null;
++ statusPanel = null;
++ remotePanel = null;
++ localPanel = null;
++ buttonPanel = null;
++ sendButton = null;
++ receiveButton = null;
++ deleteButton = null;
++ newFolderButton = null;
++ stopButton = null;
++ closeButton = null;
++ dummyButton = null;
++ localDrivesComboBox = null;
++ remoteDrivesComboBox = null;
++ localMachineLabel = null;
++ remoteMachineLabel = null;
++ localTopButton = null;
++ remoteTopButton = null;
++ localScrollPane = null;
++ localFileTable = null;
++ remoteScrollPane = null;
++ remoteFileTable = null;
++ remoteLocation = null;
++ localLocation = null;
++ localStatus = null;
++ remoteStatus = null;
++ historyComboBox = null;
++ jProgressBar = null;
++ connectionStatus = null;
++ viewButton = null;
++ refreshButton = null;
++ }
++
++ public void doReset()
++ {
++ try {
++ this.setVisible(false);
++ this.dispose();
++ jContentPane = null;
++ try {Thread.sleep(500);} catch (InterruptedException e) {}
++ viewer.ftp_init();
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++ }
+
++ public void doOpen()
++ {
++ try {
++ this.setVisible(true);
++ if (false) {
++ this.initialize();
++ }
++ } catch (Exception e) {
++ // TODO Auto-generated catch block
++ e.printStackTrace();
++ }
++ }
+ private void doDelete()
+ {
+- System.out.println("Delete Button Pressed");
++// System.out.println("Delete Button Pressed");
+ //Call this method to delete a file at server
+ if(selectedTable.equals("remote"))
+ {
+- String sFileName = ((String) this.remoteFileTable.getSelectedValue());
++ Object selected = this.remoteFileTable.getSelectedValue();
++ if (selected == null) {
++ return;
++ }
++ String sFileName = ((String) selected);
+
+ // sf@2004 - Directory can't be deleted
+ if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]"))
+ {
+- JOptionPane.showMessageDialog(null, (String)"Directory Deletion is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
++ JOptionPane.showMessageDialog(jContentPane, (String)"Directory Deletion is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+
+@@ -916,7 +1259,7 @@
+ // sf@2004 - Delete prompt
+ if (remoteList.contains(sFileName))
+ {
+- int r = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete the file \n< " + sFileName + " >\n on Remote Machine ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
++ int r = JOptionPane.showConfirmDialog(jContentPane, "Are you sure you want to delete the file \n< " + sFileName + " >\n on Remote Machine ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
+ if (r == JOptionPane.NO_OPTION)
+ return;
+ }
+@@ -926,18 +1269,22 @@
+ }
+ else
+ {
+- String sFileName = ((String) this.localFileTable.getSelectedValue());
++ Object selected = this.localFileTable.getSelectedValue();
++ if (selected == null) {
++ return;
++ }
++ String sFileName = ((String) selected);
+
+ // sf@2004 - Directory can't be deleted
+ if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]"))
+ {
+- JOptionPane.showMessageDialog(null, (String)"Directory Deletion is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
++ JOptionPane.showMessageDialog(jContentPane, (String)"Directory Deletion is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+ // sf@2004 - Delete prompt
+ if (localList.contains(sFileName))
+ {
+- int r = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete the file \n< " + sFileName + " >\n on Local Machine ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
++ int r = JOptionPane.showConfirmDialog(jContentPane, "Are you sure you want to delete the file \n< " + sFileName + " >\n on Local Machine ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
+ if (r == JOptionPane.NO_OPTION)
+ return;
+ }
+@@ -952,21 +1299,25 @@
+
+ private void doReceive()
+ {
+- System.out.println("Received Button Pressed");
++// System.out.println("Received Button Pressed");
+
+- String sFileName = ((String) this.remoteFileTable.getSelectedValue());
++ Object selected = this.remoteFileTable.getSelectedValue();
++ if (selected == null) {
++ return;
++ }
++ String sFileName = ((String) selected);
+
+ // sf@2004 - Directory can't be transfered
+ if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]"))
+ {
+- JOptionPane.showMessageDialog(null, (String)"Directory Transfer is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
++ JOptionPane.showMessageDialog(jContentPane, (String)"Directory Transfer is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+
+ // sf@2004 - Overwrite prompt
+ if (localList.contains(sFileName))
+ {
+- int r = JOptionPane.showConfirmDialog(null, "The file < " + sFileName + " >\n already exists on Local Machine\n Are you sure you want to overwrite it ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
++ int r = JOptionPane.showConfirmDialog(jContentPane, "The file < " + sFileName + " >\n already exists on Local Machine\n Are you sure you want to overwrite it ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
+ if (r == JOptionPane.NO_OPTION)
+ return;
+ }
+@@ -979,23 +1330,101 @@
+ viewer.rfb.requestRemoteFile(remoteFileName,localDestinationPath);
+ }
+
++// begin runge/x11vnc
++ private void doRefresh()
++ {
++ System.out.println("Refreshing Local and Remote.");
++ refreshLocalLocation();
++ refreshRemoteLocation();
++ }
++
++ private void doView()
++ {
++// System.out.println("View Button Pressed");
++
++ if (selectedTable == null) {
++ return;
++ }
++ if (selectedTable.equals("remote")) {
++ viewRemote();
++ } else if (selectedTable.equals("local")) {
++ viewLocal();
++ }
++ }
++
++ private File doReceiveTmp()
++ {
++
++ if (remoteFileTable == null) {
++ return null;
++ }
++ Object selected = this.remoteFileTable.getSelectedValue();
++ if (selected == null) {
++ return null;
++ }
++ String sFileName = ((String) selected);
++
++ if (sFileName == null) {
++ return null;
++ }
++
++ // sf@2004 - Directory can't be transfered
++ if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]"))
++ {
++ return null;
++ }
++
++ File tmp = null;
++ try {
++ tmp = File.createTempFile("ULTRAFTP", ".txt");
++ } catch (Exception e) {
++ return null;
++ }
++
++ //updateHistory("Downloaded " + localSelection.toString());
++ String remoteFileName = this.remoteLocation.getText();
++ remoteFileName+= ((String) this.remoteFileTable.getSelectedValue()).substring(1);
++ System.out.println("remoteFileName: " + remoteFileName);
++if (false) {
++ char[] b = remoteFileName.toCharArray();
++ for (int n = 0; n < b.length; n++) {
++ System.out.print(Integer.toHexString(b[n]) + " ");
++ }
++ System.out.println("");
++ for (int n = 0; n < b.length; n++) {
++ System.out.print(b[n]);
++ }
++ System.out.println("");
++}
++
++ String localDestinationPath = tmp.getAbsolutePath();
++ viewer.rfb.requestRemoteFile(remoteFileName,localDestinationPath);
++ System.out.println("ReceiveTmp: " + localDestinationPath);
++ return tmp;
++ }
++// end runge/x11vnc
++
+ private void doSend()
+ {
+- System.out.println("Send Button Pressed");
++// System.out.println("Send Button Pressed");
+
+- String sFileName = ((String) this.localFileTable.getSelectedValue());
++ Object selected = this.localFileTable.getSelectedValue();
++ if (selected == null) {
++ return;
++ }
++ String sFileName = ((String) selected);
+
+ // sf@2004 - Directory can't be transfered
+ if (sFileName.substring(0, 2).equals(" [") && sFileName.substring((sFileName.length() - 1), sFileName.length()).equals("]"))
+ {
+- JOptionPane.showMessageDialog(null, (String)"Directory Transfer is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
++ JOptionPane.showMessageDialog(jContentPane, (String)"Directory Transfer is not yet available in this version...", "FileTransfer Info", JOptionPane.INFORMATION_MESSAGE);
+ return;
+ }
+
+ // sf@2004 - Overwrite prompt
+ if (remoteList.contains(sFileName))
+ {
+- int r = JOptionPane.showConfirmDialog(null, "The file < " + sFileName + " >\n already exists on Remote Machine\n Are you sure you want to overwrite it ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
++ int r = JOptionPane.showConfirmDialog(jContentPane, "The file < " + sFileName + " >\n already exists on Remote Machine\n Are you sure you want to overwrite it ?", "File Transfer Warning", JOptionPane.YES_NO_OPTION);
+ if (r == JOptionPane.NO_OPTION)
+ return;
+ }
+@@ -1013,6 +1442,7 @@
+ //
+ private void doStop()
+ {
++ System.out.println("** Current Transfer Aborted **");
+ viewer.rfb.fAbort = true;
+ }
+ /**
+@@ -1024,6 +1454,14 @@
+ System.out.println("History: " + message);
+ historyComboBox.insertItemAt(new String(message), 0);
+ }
++
++ public void receivedRemoteDirectoryName(String str) {
++ if (doingShortcutDir) {
++ if (str.length() > 1) {
++ remoteLocation.setText(str);
++ }
++ }
++ }
+
+ /**
+ * This method updates the file table to the current selection of the remoteComboBox
+@@ -1034,11 +1472,44 @@
+ remoteSelection = null;
+
+ if (!updateDriveList) {
+- String drive = remoteDrivesComboBox.getSelectedItem().toString().substring(0,1)+ ":\\";
+- viewer.rfb.readServerDirectory(drive);
+- remoteLocation.setText(drive);
++//System.out.println("changeRemoteDrive-A " + drive); // begin runge/x11vnc
++ Object selected = remoteDrivesComboBox.getSelectedItem();
++ if (selected != null) {
++ String instr = selected.toString();
++ if (instr != null) {
++System.out.println("changeRemoteDrive: instr='" + instr + "'");
++ String drive = instr.substring(0,1)+ ":\\";
++ if (instr.startsWith(" [")) {
++ int idx = instr.lastIndexOf(']');
++ if (idx > 2) {
++ drive = instr.substring(2, idx);
++ } else {
++ drive = instr.substring(2);
++ }
++ if (drive.equals("Home")) {
++ drive = "";
++ }
++ drive += "\\";
++ doingShortcutDir = true;
++ } else {
++ doingShortcutDir = false;
++ drive = saveRemoteHack(drive);
++ }
++ gotShortcutDir = false;
++ viewer.rfb.readServerDirectory(drive);
++ if (!gotShortcutDir) {
++ remoteLocation.setText(drive);
++ }
++ } else {
++System.out.println("changeRemoteDrive: instr null");
++ }
++ } else {
++System.out.println("changeRemoteDrive: selection null");
++ }
++//System.out.println("changeRemoteDrive-B " + drive); // end runge/x11vnc
+ }
+ remoteList.clear();
++ remoteListInfo.clear();
+ remoteFileTable.setListData(remoteList);
+ }
+ /**
+@@ -1048,6 +1519,7 @@
+ private void changeLocalDrive()
+ {
+ File currentDrive = new File(localDrivesComboBox.getSelectedItem().toString());
++System.out.println("changeLocalDrive " + currentDrive.toString()); // runge/x11vnc
+ if(currentDrive.canRead())
+ {
+ localSelection = null;
+@@ -1057,9 +1529,11 @@
+ else
+ {
+ localList.clear();
++ localListInfo.clear();
+ localStatus.setText("WARNING: Drive " + localDrivesComboBox.getSelectedItem().toString());
+ connectionStatus.setText(" > WARNING - Local Drive unavailable (possibly restricted access or media not present)");
+ }
++
+ }
+ /**
+ * Determines which FileTable was double-clicked and updates the table
+@@ -1098,10 +1572,18 @@
+ selectedTable = "remote";
+ localFileTable.setBackground(new Color(238, 238, 238));
+ remoteFileTable.setBackground(new Color(255, 255, 255));
+- String name = (remoteFileTable.getSelectedValue().toString()).substring(1);
++ Object selected = remoteFileTable.getSelectedValue();
++ if (selected == null) {
++ return;
++ }
++ String selstr = selected.toString();
++ if (selstr == null) {
++ return;
++ }
++ String name = selstr.substring(1);
+ if( !name.substring(0, 2).equals(" ["))
+ remoteSelection = remoteLocation.getText() + name.substring(0, name.length());
+-
++
+ }
+
+ /*
+@@ -1115,10 +1597,38 @@
+ localFileTable.setBackground(new Color(255, 255, 255));
+ File currentSelection = new File(currentLocalDirectory, getTrimmedSelection());
+
+- if(currentSelection.isFile())
++// begin runge/x11vnc
++ // localSelection = currentSelection.getAbsoluteFile();
++ if(currentSelection.isFile()) {
+ localSelection = currentSelection.getAbsoluteFile();
++ localCurrentIsDir = false;
++ } else {
++ localCurrentIsDir = true;
++ }
++// end runge/x11vnc
+
+ }
++
++// begin runge/x11vnc
++ private void viewRemote() {
++ File tmp = doReceiveTmp();
++ if (tmp == null) {
++ return;
++ }
++ TextViewer tv = new TextViewer("Remote: " + remoteSelection, tmp, true);
++ }
++ private void viewLocal() {
++ if (localSelection == null) {
++ return;
++ }
++ if (localCurrentIsDir) {
++ return;
++ }
++ File loc = new File(localSelection.toString());
++ TextViewer tv = new TextViewer("Local: " + localSelection.toString(), loc, false);
++ }
++// end runge/x11vnc
++
+ /**
+ * Updates the Remote File Table based on selection. Called from mouseClicked handler
+ */
+@@ -1126,20 +1636,29 @@
+ String name = null;
+ String action = null;
+ String drive = null;
+- name = (remoteFileTable.getSelectedValue().toString()).substring(1);
++ Object selected = remoteFileTable.getSelectedValue();
++ if (selected == null) {
++ return;
++ }
++ String sname = selected.toString();
++ if (sname == null) {
++ return;
++ }
++ name = sname.substring(1);
+
+ if (name.equals("[..]"))
+ {
+ action = "up";
+ remoteSelection = null;
+ drive = remoteLocation.getText().substring(0, remoteLocation.getText().length() - 1);
+- // JOptionPane.showMessageDialog(null, (String)drive, "FileTransfer DEBUG", JOptionPane.INFORMATION_MESSAGE);
++ // JOptionPane.showMessageDialog(jContentPane, (String)drive, "FileTransfer DEBUG", JOptionPane.INFORMATION_MESSAGE);
+ int index = drive.lastIndexOf("\\");
+ drive = drive.substring(0, index + 1);
+
+ remoteLocation.setText(drive);
+ viewer.rfb.readServerDirectory(drive);
+ remoteList.clear();
++ remoteListInfo.clear();
+ remoteFileTable.setListData(remoteList);
+ }
+ else if (!name.substring(0, 2).equals(" [") && !name.substring((name.length() - 1), name.length()).equals("]"))
+@@ -1149,6 +1668,7 @@
+ remoteSelection = remoteLocation.getText() + name.substring(0, name.length());
+ drive = remoteLocation.getText();
+ // ??
++ viewRemote(); // runge/x11vnc
+ }
+ else
+ {
+@@ -1159,10 +1679,12 @@
+ remoteLocation.setText(drive);
+ viewer.rfb.readServerDirectory(drive);
+ remoteList.clear();
++ remoteListInfo.clear();
+ remoteFileTable.setListData(remoteList);
+ }
+ //remoteLocation.setText(drive);
+ }
++
+ /**
+ * Updates the Local File Table based on selection. Called from MouseClicked handler
+ */
+@@ -1188,6 +1710,7 @@
+ else if (currentSelection.isFile())
+ {
+ localSelection = currentSelection.getAbsoluteFile();
++ viewLocal(); // runge/x11vnc
+ }
+ else if (currentSelection.isDirectory())
+ {
+@@ -1201,13 +1724,22 @@
+ *
+ */
+ private String getTrimmedSelection(){
+- String currentSelection = (localFileTable.getSelectedValue().toString()).substring(1);
+- if(currentSelection.substring(0,1).equals("[") &&
+- currentSelection.substring(currentSelection.length()-1,currentSelection.length()).equals("]")){
+- return currentSelection.substring(1,currentSelection.length()-1);
+- } else {
+- return currentSelection;
+- }
++ String currentSelection = "";
++ Object selected = localFileTable.getSelectedValue();
++ if (selected == null) {
++ return currentSelection;
++ }
++ String selstr = selected.toString();
++ if (selstr == null) {
++ return currentSelection;
++ }
++ currentSelection = selstr.substring(1);
++ if(currentSelection.substring(0,1).equals("[") &&
++ currentSelection.substring(currentSelection.length()-1,currentSelection.length()).equals("]")){
++ return currentSelection.substring(1,currentSelection.length()-1);
++ } else {
++ return currentSelection;
++ }
+ }
+
+ /*
+@@ -1241,36 +1773,148 @@
+ return null;
+ }
+
++ String timeStr(long t) {
++ Date date = new Date(t);
++ return date.toString();
++ }
++ String dotPast(double f, int n) {
++ String fs = "" + f;
++ int i = fs.lastIndexOf(".") + n;
++ if (i >= 0) {
++ int len = fs.length();
++ if (i >= len) {
++ i = len-1;
++ }
++ fs = fs.substring(0, i);
++ }
++ return fs;
++ }
++ String sizeStr(int s) {
++ if (s < 0) {
++ return s + "? B";
++ } else if (s < 1024) {
++ return s + " B";
++ } else if (s < 1024 * 1024) {
++ double k = s / 1024.0;
++ String ks = dotPast(k, 3);
++
++ return s + " (" + ks + " KB)";
++ } else {
++ double m = s / (1024.0*1024.0);
++ String ms = dotPast(m, 3);
++ return s + " (" + ms + " MB)";
++ }
++ }
++
++ int max_char(String text) {
++ int maxc = 0;
++ char chars[] = text.toCharArray();
++ for (int n = 0; n < chars.length; n++) {
++ if ((int) chars[n] > maxc) {
++ maxc = (int) chars[n];
++ }
++ }
++ return maxc;
++ }
+
+ /*
+ * Navigates the local file structure up or down one directory
+ */
+ public void changeLocalDirectory(File dir)
+ {
+- currentLocalDirectory = dir; // Updates Global
++ dir = saveLocalHack(dir); // runge/x11vnc
++
++ if (dir == null) {
++ connectionStatus.setText("Error changing local directory.");
++ historyComboBox.insertItemAt(new String("> Error changing local directory."), 0);
++ historyComboBox.setSelectedIndex(0);
++ return;
++ }
++
+ File allFiles[] = dir.listFiles(); // Reads files
+ String[] contents = dir.list();
+
++ if (contents == null || allFiles == null) {
++ connectionStatus.setText("Error changing local directory.");
++ historyComboBox.insertItemAt(new String("> Error changing local directory."), 0);
++ historyComboBox.setSelectedIndex(0);
++ return;
++ }
++
++ currentLocalDirectory = dir; // Updates Global
++// begin runge/x11vnc
++System.out.println("changeLocalDirectory: " + dir.toString());
++ if (contents != null) {
++ java.util.Arrays.sort(contents, String.CASE_INSENSITIVE_ORDER);
++ for (int i = 0; i < contents.length; i++) {
++ allFiles[i] = new File(dir, contents[i]);
++ }
++ } else {
++ return;
++ }
++// end runge/x11vnc
++
+ localList.clear();
++ localListInfo.clear();
+ localList.addElement(" [..]");
++ localListInfo.addElement(" [..]");
++
++ ArrayList DirInfo = new ArrayList();
++ ArrayList FilInfo = new ArrayList();
++
++ Charset charset = Charset.forName("ISO-8859-1");
++ CharsetDecoder decoder = charset.newDecoder();
++ CharsetEncoder encoder = charset.newEncoder();
+
+ // Populate the Lists
+ for (int i = 0; i < contents.length; i++)
+ {
+- if (allFiles[i].isDirectory())
++ String f1 = contents[i];
++
++if (false) {
++
++System.out.println("max_char: " + max_char(f1) + " " + f1);
++ if (max_char(f1) > 255) {
++ try {
++System.out.println("bbuf1");
++ ByteBuffer bbuf = encoder.encode(CharBuffer.wrap(f1.toCharArray()));
++System.out.println("bbuf2");
++ CharBuffer cbuf = decoder.decode(bbuf);
++System.out.println("bbuf3");
++ f1 = cbuf.toString();
++System.out.println("did bbuf: " + f1);
++ } catch (Exception e) {
++ ;
++ }
++ }
++}
++
++ String f2 = f1;
++ if (f2.length() < 24) {
++ for (int ik = f2.length(); ik < 24; ik++) {
++ f2 = f2 + " ";
++ }
++ }
++ String s = f2 + " \tLastmod: " + timeStr(allFiles[i].lastModified()) + " \t\tSize: " + sizeStr((int) allFiles[i].length());
++ if (allFiles[i].isDirectory()) {
+ // localList.addElement("[" + contents[i] + "]");
+- DirsList.add(" [" + contents[i] + "]"); // sf@2004
+- else
+- {
++ DirsList.add(" [" + f1 + "]"); // sf@2004
++ DirInfo.add(s);
++ } else {
+ // localList.addElement(contents[i]);
+- FilesList.add(" " + contents[i]); // sf@2004
++ FilesList.add(" " + f1); // sf@2004
++ FilInfo.add(s);
+ }
+ }
+ // sf@2004
+- for (int i = 0; i < DirsList.size(); i++)
++ for (int i = 0; i < DirsList.size(); i++) {
+ localList.addElement(DirsList.get(i));
+- for (int i = 0; i < FilesList.size(); i++)
++ localListInfo.addElement(DirInfo.get(i));
++ }
++ for (int i = 0; i < FilesList.size(); i++) {
+ localList.addElement(FilesList.get(i));
++ localListInfo.addElement(FilInfo.get(i));
++ }
+
+ FilesList.clear();
+ DirsList.clear();
+@@ -1296,3 +1940,147 @@
+ }
+
+ } // @jve:visual-info decl-index=0 visual-constraint="10,10"
++
++// begin runge/x11vnc
++class TextViewer extends JFrame implements ActionListener {
++
++ JTextArea textArea = new JTextArea(35, 80);
++ File file = null;
++ JButton refreshButton;
++ JButton dismissButton;
++ Timer tim = null;
++ int rcnt = 0;
++ int tms = 250;
++ boolean delete_it = false;
++ TextViewer me;
++
++ public TextViewer(String s, File f, boolean d) {
++
++ delete_it = d;
++ file = f;
++ me = this;
++
++ JScrollPane scrollPane = new JScrollPane(textArea,
++ JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
++ JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
++
++ textArea.setEditable(false);
++ textArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
++
++ KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, InputEvent.SHIFT_MASK);
++ AbstractAction escapeAction = new AbstractAction() {
++ public void actionPerformed(ActionEvent actionEvent) {
++ cleanse();
++ me.dispose();
++ }
++ };
++ textArea.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(stroke, "escapeAction");
++ textArea.getInputMap().put(stroke, "escapeAction");
++ textArea.getActionMap().put("escapeAction", escapeAction);
++
++ refreshButton = new JButton();
++ refreshButton.setText("Reload");
++ refreshButton.setName("refreshButton");
++ refreshButton.addActionListener(this);
++
++ dismissButton = new JButton();
++ dismissButton.setText("Dismiss");
++ dismissButton.setName("dismissButton");
++ dismissButton.addActionListener(this);
++
++ JPanel buttons = new JPanel();
++ buttons.setLayout(new BorderLayout());
++ buttons.add(refreshButton, BorderLayout.WEST);
++ buttons.add(dismissButton, BorderLayout.EAST);
++
++ JPanel content = new JPanel();
++ content.setLayout(new BorderLayout());
++ content.add(scrollPane, BorderLayout.CENTER);
++ content.add(buttons, BorderLayout.SOUTH);
++
++ ActionListener tsk = new ActionListener() {
++ public void actionPerformed(ActionEvent evt) {
++ // System.out.println("tsk");
++ refresh();
++ }
++ };
++ tim = new Timer(tms, tsk);
++ tim.start();
++
++ this.setContentPane(content);
++ this.setTitle("TextViewer - " + s);
++ this.pack();
++ this.setVisible(true);
++ }
++
++ private void refresh() {
++
++ rcnt++;
++ if (rcnt * tms > 3000 && tim != null) {
++ tim.stop();
++ tim = null;
++ }
++ BufferedReader input = null;
++ StringBuffer contents = new StringBuffer();
++ try {
++ if (input == null) {
++ input = new BufferedReader(new FileReader(file));
++ }
++ String line = null;
++ int i = 0;
++ while (( line = input.readLine()) != null) {
++ if (i == 0) {
++ // System.out.println("read");
++ }
++ i++;
++ contents.append(line);
++ contents.append(System.getProperty("line.separator"));
++ }
++ } catch (Exception e) {
++ ;
++ } finally {
++ try {
++ if (input != null) {
++ input.close();
++ input = null;
++ }
++ } catch (Exception e) {
++ ;
++ }
++ }
++
++ textArea.setText(contents.toString());
++ textArea.setCaretPosition(0);
++ }
++
++ public void actionPerformed(ActionEvent evt) {
++
++ if (evt.getSource() == refreshButton) {
++ refresh();
++ }
++ if (evt.getSource() == dismissButton) {
++ cleanse();
++ this.dispose();
++ }
++ }
++
++ private void cleanse() {
++ if (delete_it && file != null) {
++ try {
++ file.delete();
++ file = null;
++ } catch (Exception e) {
++ ;
++ }
++ }
++ }
++
++ protected void finalize() throws Throwable {
++ try {
++ cleanse();
++ } finally {
++ super.finalize();
++ }
++ }
++}
++// end runge/x11vnc
+diff -Naur JavaViewer.orig/Makefile JavaViewer/Makefile
+--- JavaViewer.orig/Makefile 2006-05-29 09:06:32.000000000 -0400
++++ JavaViewer/Makefile 2010-05-18 20:53:32.000000000 -0400
+@@ -4,6 +4,7 @@
+
+ CP = cp
+ JC = javac
++JC_ARGS = -target 1.4 -source 1.4
+ JAR = jar
+ ARCHIVE = VncViewer.jar
+ PAGES = index.vnc shared.vnc noshared.vnc hextile.vnc zlib.vnc tight.vnc
+@@ -20,7 +21,7 @@
+ all: $(CLASSES) $(ARCHIVE)
+
+ $(CLASSES): $(SOURCES)
+- $(JC) -O $(SOURCES)
++ $(JC) $(JC_ARGS) -O $(SOURCES)
+
+ $(ARCHIVE): $(CLASSES)
+ $(JAR) cf $(ARCHIVE) $(CLASSES)
+diff -Naur JavaViewer.orig/OptionsFrame.java JavaViewer/OptionsFrame.java
+--- JavaViewer.orig/OptionsFrame.java 2005-11-21 18:50:16.000000000 -0500
++++ JavaViewer/OptionsFrame.java 2007-05-13 22:18:30.000000000 -0400
+@@ -144,7 +144,10 @@
+ choices[jpegQualityIndex].select("6");
+ choices[cursorUpdatesIndex].select("Enable");
+ choices[useCopyRectIndex].select("Yes");
+- choices[eightBitColorsIndex].select("64");
++// begin runge/x11vnc
++// choices[eightBitColorsIndex].select("64");
++ choices[eightBitColorsIndex].select("Full");
++// end runge/x11vnc
+ choices[mouseButtonIndex].select("Normal");
+ choices[viewOnlyIndex].select("No");
+ choices[shareDesktopIndex].select("Yes");
+diff -Naur JavaViewer.orig/RfbProto.java JavaViewer/RfbProto.java
+--- JavaViewer.orig/RfbProto.java 2006-05-24 15:14:40.000000000 -0400
++++ JavaViewer/RfbProto.java 2010-11-30 22:13:58.000000000 -0500
+@@ -31,6 +31,7 @@
+ import java.net.Socket;
+ import java.util.*;
+ import java.util.zip.*;
++import java.text.DateFormat;
+
+
+ class RfbProto {
+@@ -86,8 +87,11 @@
+
+ // sf@2004 - FileTransfer part
+ ArrayList remoteDirsList;
++ ArrayList remoteDirsListInfo;
+ ArrayList remoteFilesList;
++ ArrayList remoteFilesListInfo;
+ ArrayList a;
++ ArrayList b;
+ boolean fFTInit = true; // sf@2004
+ boolean fFTAllowed = true;
+ boolean fAbort = false;
+@@ -199,6 +203,10 @@
+ // playback.
+ int numUpdatesInSession;
+
++// begin runge/x11vnc
++ int readServerDriveListCnt = -1;
++ long readServerDriveListTime = 0;
++// end runge/x11vnc
+ //
+ // Constructor. Make TCP connection to RFB server.
+ //
+@@ -207,7 +215,27 @@
+ viewer = v;
+ host = h;
+ port = p;
+- sock = new Socket(host, port);
++// begin runge/x11vnc
++// sock = new Socket(host, port);
++ if (! viewer.disableSSL) {
++ System.out.println("new SSLSocketToMe");
++ SSLSocketToMe ssl;
++ try {
++ ssl = new SSLSocketToMe(host, port, v);
++ } catch (Exception e) {
++ throw new IOException(e.getMessage());
++ }
++
++ try {
++ sock = ssl.connectSock();
++ } catch (Exception es) {
++ throw new IOException(es.getMessage());
++ }
++ } else {
++ sock = new Socket(host, port);
++ }
++// end runge/x11vnc
++
+ is =
+ new DataInputStream(
+ new BufferedInputStream(sock.getInputStream(), 16384));
+@@ -215,9 +243,12 @@
+ osw = new OutputStreamWriter(sock.getOutputStream());
+ inDirectory2 = false;
+ a = new ArrayList();
++ b = new ArrayList();
+ // sf@2004
+ remoteDirsList = new ArrayList();
++ remoteDirsListInfo = new ArrayList();
+ remoteFilesList = new ArrayList();
++ remoteFilesListInfo = new ArrayList();
+
+ sendFileSource = "";
+ }
+@@ -420,7 +451,13 @@
+ //
+
+ int readServerMessageType() throws IOException {
+- int msgType = is.readUnsignedByte();
++ int msgType;
++ try {
++ msgType = is.readUnsignedByte();
++ } catch (Exception e) {
++ viewer.disconnect();
++ return -1;
++ }
+
+ // If the session is being recorded:
+ if (rec != null) {
+@@ -600,6 +637,7 @@
+ contentParamT = is.readUnsignedByte();
+ contentParamT = contentParamT << 8;
+ contentParam = contentParam | contentParamT;
++//System.out.println("FTM: contentType " + contentType + " contentParam " + contentParam);
+ if (contentType == rfbRDrivesList || contentType == rfbDirPacket)
+ {
+ readDriveOrDirectory(contentParam);
+@@ -610,7 +648,7 @@
+ }
+ else if (contentType == rfbFilePacket)
+ {
+- receiveFileChunk();
++ receiveFileChunk();
+ }
+ else if (contentType == rfbEndOfFile)
+ {
+@@ -618,6 +656,10 @@
+ }
+ else if (contentType == rfbAbortFileTransfer)
+ {
++ System.out.println("rfbAbortFileTransfer: fFileReceptionRunning="
++ + fFileReceptionRunning + " fAbort="
++ + fAbort + " fFileReceptionError="
++ + fFileReceptionError);
+ if (fFileReceptionRunning)
+ {
+ endOfReceiveFile(false); // Error
+@@ -626,6 +668,11 @@
+ {
+ // sf@2004 - Todo: Add TestPermission
+ // System.out.println("File Transfer Aborted!");
++
++ // runge: seems like we must at least read the remaining
++ // 8 bytes of the header, right?
++ int size = is.readInt();
++ int length = is.readInt();
+ }
+
+ }
+@@ -645,6 +692,7 @@
+ {
+ System.out.println("ContentType: " + contentType);
+ }
++//System.out.println("FTM: done");
+ }
+
+ //Refactored from readRfbFileTransferMsg()
+@@ -662,6 +710,7 @@
+
+ //Refactored from readRfbFileTransferMsg()
+ public void readDriveOrDirectory(int contentParam) throws IOException {
++//System.out.println("RDOD: " + contentParam + " " + inDirectory2);
+ if (contentParam == rfbADrivesList)
+ {
+ readFTPMsgDriveList();
+@@ -688,13 +737,21 @@
+
+ // Internally used. Write an Rfb message to the server
+ void writeRfbFileTransferMsg(
+- int contentType,
+- int contentParam,
+- long size, // 0 : compression not supported - 1 : compression supported
+- long length,
+- String text) throws IOException
++ int contentType,
++ int contentParam,
++ long size, // 0 : compression not supported - 1 : compression supported
++ long length,
++ String text) throws IOException
+ {
+ byte b[] = new byte[12];
++ byte byteArray[];
++
++ if (viewer.dsmActive) {
++ // need to send the rfbFileTransfer msg type twice for the plugin...
++ byte b2[] = new byte[1];
++ b2[0] = (byte) rfbFileTransfer;
++ os.write(b2);
++ }
+
+ b[0] = (byte) rfbFileTransfer;
+ b[1] = (byte) contentType;
+@@ -702,7 +759,7 @@
+
+ byte by = 0;
+ long c = 0;
+- length++;
++
+ c = size & 0xFF000000;
+ by = (byte) (c >>> 24);
+ b[4] = by;
+@@ -716,6 +773,32 @@
+ by = (byte) c;
+ b[7] = by;
+
++ if (text != null) {
++ byte byteArray0[] = text.getBytes();
++ int maxc = max_char(text);
++ if (maxc > 255) {
++ System.out.println("writeRfbFileTransferMsg: using getBytes(\"UTF-8\")");
++ byteArray0 = text.getBytes("UTF-8");
++ } else if (maxc > 127) {
++ System.out.println("writeRfbFileTransferMsg: using getBytes(\"ISO-8859-1\")");
++ byteArray0 = text.getBytes("ISO-8859-1");
++ }
++ byteArray = new byte[byteArray0.length + 1];
++ for (int i = 0; i < byteArray0.length; i++) {
++ byteArray[i] = byteArray0[i];
++ }
++ byteArray[byteArray.length - 1] = 0;
++System.out.println("writeRfbFileTransferMsg: length: " + length + " -> byteArray.length: " + byteArray.length);
++
++ // will equal length for ascii, ISO-8859-1, more for UTF-8
++ length = byteArray.length;
++
++ //length++; // used to not include null byte at end.
++ } else {
++ String moo = "moo";
++ byteArray = moo.getBytes();
++ }
++
+ c = length & 0xFF000000;
+ by = (byte) (c >>> 24);
+ b[8] = by;
+@@ -729,29 +812,91 @@
+ by = (byte) c;
+ b[11] = by;
+ os.write(b);
++
++//System.out.println("size: " + size + " length: " + length + " text: " + text);
+
+
+ if (text != null)
+ {
+- byte byteArray[] = text.getBytes();
+- byte byteArray2[] = new byte[byteArray.length + 1];
+- for (int i = 0; i < byteArray.length; i++) {
+- byteArray2[i] = byteArray[i];
++ os.write(byteArray);
++ }
++ }
++
++ int max_char(String text) {
++ int maxc = 0;
++ char chars[] = text.toCharArray();
++ for (int n = 0; n < chars.length; n++) {
++ if ((int) chars[n] > maxc) {
++ maxc = (int) chars[n];
+ }
+- byteArray2[byteArray2.length - 1] = 0;
+- os.write(byteArray2);
+ }
+-
++ return maxc;
+ }
+
++ String guess_encoding(char[] chars) {
++ boolean saw_high_char = false;
++
++ for (int i = 0; i < chars.length; i++) {
++ if (chars[i] == '\0') {
++ break;
++ }
++ if (chars[i] >= 128) {
++ saw_high_char = true;
++ break;
++ }
++ }
++ if (!saw_high_char) {
++ return "ASCII";
++ }
++ char prev = 1;
++ boolean valid_utf8 = true;
++ int n = 0;
++ for (int i = 0; i < chars.length; i++) {
++ if (chars[i] == '\0') {
++ break;
++ }
++ char c = chars[i];
++ if (prev < 128 && c >= 128) {
++ if (c >> 5 == 0x6) {
++ n = 1;
++ } else if (c >> 4 == 0xe) {
++ n = 2;
++ } else if (c >> 3 == 0x1e) {
++ n = 3;
++ } else if (c >> 2 == 0x3e) {
++ n = 4;
++ } else {
++ valid_utf8 = false;
++ break;
++ }
++ } else {
++ if (n > 0) {
++ if (c < 128) {
++ valid_utf8 = false;
++ break;
++ }
++ n--;
++ }
++ }
++
++ prev = c;
++ }
++ if (valid_utf8) {
++ return "UTF-8";
++ } else {
++ return "ISO-8859-1";
++ }
++ }
++
++
+ //Internally used. Write an rfb message to the server for sending files ONLY
+ int writeRfbFileTransferMsgForSendFile(
+- int contentType,
+- int contentParam,
+- long size,
+- long length,
+- String source
+- ) throws IOException
++ int contentType,
++ int contentParam,
++ long size,
++ long length,
++ String source
++ ) throws IOException
+ {
+ File f = new File(source);
+ fis = new FileInputStream(f);
+@@ -768,50 +913,47 @@
+
+ while (bytesRead!=-1)
+ {
+- counter += bytesRead;
+- myDeflater.setInput(byteBuffer, 0, bytesRead);
+- myDeflater.finish();
+- compressedSize = myDeflater.deflate(CompressionBuffer);
+- myDeflater.reset();
+- // If the compressed data is larger than the original one, we're dealing with
+- // already compressed data
+- if (compressedSize > bytesRead)
+- fCompress = false;
+- this.writeRfbFileTransferMsg(
+- contentType,
+- contentParam,
+- (fCompress ? 1 : 0),
+- (fCompress ? compressedSize-1 : bytesRead-1),
+- null
+- );
+- // Todo: Test write error !
+- os.write(
+- fCompress ? CompressionBuffer : byteBuffer,
+- 0,
+- fCompress ? compressedSize : bytesRead
+- );
+-
+- // Todo: test read error !
+- bytesRead = fis.read(byteBuffer);
+-
+- // viewer.ftp.connectionStatus.setText("Sent: "+ counter + " bytes of "+ f.length() + " bytes");
+- viewer.ftp.jProgressBar.setValue((int)((counter * 100) / f.length()));
+- viewer.ftp.connectionStatus.setText(">>> Sending File: " + source + " - Size: " + f.length() + " bytes - Progress: " + ((counter * 100) / f.length()) + "%");
+-
+- if (fAbort == true)
+- {
+- fAbort = false;
+- fError = true;
+- break;
+- }
+- try
+- {
+- Thread.sleep(5);
+- }
+- catch(InterruptedException e)
+- {
+- System.err.println("Interrupted");
+- }
++ counter += bytesRead;
++ myDeflater.setInput(byteBuffer, 0, bytesRead);
++ myDeflater.finish();
++ compressedSize = myDeflater.deflate(CompressionBuffer);
++ myDeflater.reset();
++ // If the compressed data is larger than the original one, we're dealing with
++ // already compressed data
++ if (compressedSize > bytesRead)
++ fCompress = false;
++ this.writeRfbFileTransferMsg(
++ contentType,
++ contentParam,
++ (fCompress ? 1 : 0),
++// RUNGE (fCompress ? compressedSize-1 : bytesRead-1),
++ (fCompress ? compressedSize : bytesRead),
++ null
++ );
++ // Todo: Test write error !
++ os.write(fCompress ? CompressionBuffer : byteBuffer, 0, fCompress ? compressedSize : bytesRead);
++
++ // Todo: test read error !
++ bytesRead = fis.read(byteBuffer);
++
++ // viewer.ftp.connectionStatus.setText("Sent: "+ counter + " bytes of "+ f.length() + " bytes");
++ viewer.ftp.jProgressBar.setValue((int)((counter * 100) / f.length()));
++ viewer.ftp.connectionStatus.setText(">>> Sending File: " + source + " - Size: " + f.length() + " bytes - Progress: " + ((counter * 100) / f.length()) + "%");
++
++ if (fAbort == true)
++ {
++ fAbort = false;
++ fError = true;
++ break;
++ }
++ try
++ {
++ Thread.sleep(5);
++ }
++ catch(InterruptedException e)
++ {
++ System.err.println("Interrupted");
++ }
+ }
+
+ writeRfbFileTransferMsg(fError ? rfbAbortFileTransfer : rfbEndOfFile, 0, 0, 0, null);
+@@ -831,24 +973,30 @@
+ {
+ System.out.print((char) is.readUnsignedByte());
+ }
++ System.out.println("");
++
++ if (size == rfbRErrorCmd || size == -1) {
++ viewer.ftp.enableButtons();
++ viewer.ftp.connectionStatus.setText("Remote file not available for writing.");
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - Remote file not available for writing."), 0);
++ viewer.ftp.historyComboBox.setSelectedIndex(0);
++ return;
++ }
+
+- int ret = writeRfbFileTransferMsgForSendFile(
+- rfbFilePacket,
+- 0,
+- 0,
+- 0,
+- sendFileSource);
++ int ret = writeRfbFileTransferMsgForSendFile(rfbFilePacket, 0, 0, 0, sendFileSource);
+
+ viewer.ftp.refreshRemoteLocation();
+ if (ret != 1)
+ {
+ viewer.ftp.connectionStatus.setText(" > Error - File NOT sent");
+- viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - File: <" + sendFileSource) + "> was not correctly sent (aborted by user or error)",0);
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - File: <" + sendFileSource)
++ + "> was not correctly sent (aborted or error). Data may still be buffered/in transit. Wait for remote listing...",0);
+ }
+ else
+ {
+ viewer.ftp.connectionStatus.setText(" > File sent");
+- viewer.ftp.historyComboBox.insertItemAt(new String(" > File: <" + sendFileSource) + "> was sent to Remote Machine",0);
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > File: <" + sendFileSource)
++ + "> was sent to Remote Machine. Note: data may still be buffered/in transit. Wait for remote listing...",0);
+ }
+ viewer.ftp.historyComboBox.setSelectedIndex(0);
+ viewer.ftp.enableButtons();
+@@ -907,7 +1055,7 @@
+ //Handles acknowledgement that the file has been deleted on the server
+ void deleteRemoteFileFeedback() throws IOException
+ {
+- is.readInt();
++ int ret = is.readInt();
+ int length = is.readInt();
+ String f = "";
+ for (int i = 0; i < length; i++)
+@@ -916,7 +1064,11 @@
+ }
+
+ viewer.ftp.refreshRemoteLocation();
+- viewer.ftp.historyComboBox.insertItemAt(new String(" > Deleted File On Remote Machine: " + f.substring(0, f.length()-1)),0);
++ if (ret == -1) {
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > ERROR Could not Delete File On Remote Machine: "),0);
++ } else {
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > Deleted File On Remote Machine: " + f.substring(0, f.length()-1)),0);
++ }
+ viewer.ftp.historyComboBox.setSelectedIndex(0);
+ }
+
+@@ -926,12 +1078,7 @@
+ try
+ {
+ String temp = text;
+- writeRfbFileTransferMsg(
+- rfbCommand,
+- rfbCFileDelete,
+- 0,
+- temp.length(),
+- temp);
++ writeRfbFileTransferMsg(rfbCommand, rfbCFileDelete, 0, temp.length(), temp);
+ }
+ catch (IOException e)
+ {
+@@ -943,7 +1090,7 @@
+ // Handles acknowledgement that the directory has been created on the server
+ void createRemoteDirectoryFeedback() throws IOException
+ {
+- is.readInt();
++ int ret = is.readInt();
+ int length = is.readInt();
+ String f="";
+ for (int i = 0; i < length; i++)
+@@ -951,7 +1098,11 @@
+ f += (char)is.readUnsignedByte();
+ }
+ viewer.ftp.refreshRemoteLocation();
+- viewer.ftp.historyComboBox.insertItemAt(new String(" > Created Directory on Remote Machine: " + f.substring(0, f.length()-1)),0);
++ if (ret == -1) {
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > ERROR Could not Create Directory on Remote Machine."),0);
++ } else {
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > Created Directory on Remote Machine: " + f.substring(0, f.length()-1)),0);
++ }
+ viewer.ftp.historyComboBox.setSelectedIndex(0);
+ }
+
+@@ -961,12 +1112,7 @@
+ try
+ {
+ String temp = text;
+- writeRfbFileTransferMsg(
+- rfbCommand,
+- rfbCDirCreate,
+- 0,
+- temp.length(),
+- temp);
++ writeRfbFileTransferMsg(rfbCommand, rfbCDirCreate, 0, temp.length(), temp);
+ }
+ catch (IOException e)
+ {
+@@ -979,15 +1125,13 @@
+ {
+ try
+ {
++//System.out.println("requestRemoteFile text: " + text);
++//System.out.println("requestRemoteFile leng: " + text.length());
+ String temp = text;
+ receivePath = localPath;
+
+- writeRfbFileTransferMsg(
+- rfbFileTransferRequest,
+- 0,
+- 1, // 0 : compression not supported - 1 : compression supported
+- temp.length(),
+- temp);
++ // 0 : compression not supported - 1 : compression supported
++ writeRfbFileTransferMsg(rfbFileTransferRequest, 0, 1, temp.length(), temp);
+ }
+ catch (IOException e)
+ {
+@@ -1004,6 +1148,9 @@
+ viewer.ftp.disableButtons();
+ int size = is.readInt();
+ int length = is.readInt();
++
++//System.out.println("receiveFileHeader size: " + size);
++//System.out.println("receiveFileHeader leng: " + length);
+
+ String tempName = "";
+ for (int i = 0; i < length; i++)
+@@ -1011,6 +1158,15 @@
+ tempName += (char) is.readUnsignedByte();
+ }
+
++ if (size == rfbRErrorCmd || size == -1) {
++ fFileReceptionRunning = false;
++ viewer.ftp.enableButtons();
++ viewer.ftp.connectionStatus.setText("Remote file not available for reading.");
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - Remote file not available for reading."), 0);
++ viewer.ftp.historyComboBox.setSelectedIndex(0);
++ return;
++ }
++
+ // sf@2004 - Read the high part of file size (not yet in rfbFileTransferMsg for
+ // backward compatibility reasons...)
+ int sizeH = is.readInt();
+@@ -1021,7 +1177,16 @@
+ fileSize=0;
+ fileChunkCounter = 0;
+ String fileName = receivePath;
+- fos = new FileOutputStream(fileName);
++ try {
++ fos = new FileOutputStream(fileName);
++ } catch (Exception e) {
++ fFileReceptionRunning = false;
++ writeRfbFileTransferMsg(rfbAbortFileTransfer, 0, 0, 0, null);
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > ERROR opening Local File: <" + fileName ),0);
++ viewer.ftp.historyComboBox.setSelectedIndex(0);
++ viewer.ftp.enableButtons();
++ return;
++ }
+ writeRfbFileTransferMsg(rfbFileHeader, 0, 0, 0, null);
+ }
+
+@@ -1085,7 +1250,13 @@
+ fAbort = false;
+ fFileReceptionError = true;
+ writeRfbFileTransferMsg(rfbAbortFileTransfer, 0, 0, 0, null);
+-
++
++ //runge for use with x11vnc/libvncserver, no rfbAbortFileTransfer reply sent.
++ try {Thread.sleep(500);} catch (InterruptedException e) {}
++ viewer.ftp.enableButtons();
++ viewer.ftp.refreshLocalLocation();
++ viewer.ftp.connectionStatus.setText(" > Error - File NOT received");
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > Error - File: <" + receivePath + "> not correctly received from Remote Machine (aborted by user or error)") ,0);
+ }
+ // sf@2004 - For old FT protocole only
+ /*
+@@ -1104,7 +1275,7 @@
+ int length = is.readInt();
+ fileSize=0;
+ fos.close();
+-
++
+ viewer.ftp.refreshLocalLocation();
+ if (fReceptionOk && !fFileReceptionError)
+ {
+@@ -1132,12 +1303,7 @@
+ try
+ {
+ String temp = text;
+- writeRfbFileTransferMsg(
+- rfbDirContentRequest,
+- rfbRDirContent,
+- 0,
+- temp.length(),
+- temp);
++ writeRfbFileTransferMsg(rfbDirContentRequest, rfbRDirContent, 0, temp.length(), temp);
+ }
+ catch (IOException e)
+ {
+@@ -1197,11 +1363,80 @@
+ str += temp;
+ }
+ }
++ // runge
++ viewer.ftp.receivedRemoteDirectoryName(str);
+ // viewer.ftp.changeRemoteDirectory(str);
+
+ }
+ }
+
++ int zogswap(int n) {
++ long l = n;
++ if (l < 0) {
++ l += 0x100000000L;
++ }
++ l = l & 0xFFFFFFFF;
++ l = (l >> 24) | ((l & 0x00ff0000) >> 8) | ((l & 0x0000ff00) << 8) | (l << 24);
++ return (int) l;
++ }
++
++ int windozeToUnix(int L, int H) {
++ long L2 = zogswap(L);
++ long H2 = zogswap(H);
++ long unix = (H2 << 32) + L2;
++ unix -= 11644473600L * 10000000L;
++ unix /= 10000000L;
++ //System.out.println("unix time: " + unix + " H2: " + H2 + " L2: " + L2);
++ return (int) unix;
++ }
++
++ String timeStr(int t, int h) {
++ if (h == 0) {
++ // x11vnc/libvncserver unix
++ t = zogswap(t);
++ } else {
++ // ultra (except if h==0 by chance)
++ t = windozeToUnix(t, h);
++ }
++ long tl = (long) t;
++ Date date = new Date(tl * 1000);
++ if (true) {
++ return date.toString();
++ } else {
++ return DateFormat.getDateTimeInstance().format(date);
++ }
++ }
++
++ String dotPast(double f, int n) {
++ String fs = "" + f;
++ int i = fs.lastIndexOf(".") + n;
++ if (i >= 0) {
++ int len = fs.length();
++ if (i >= len) {
++ i = len-1;
++ }
++ fs = fs.substring(0, i);
++ }
++ return fs;
++ }
++ String sizeStr(int s) {
++ s = zogswap(s);
++ if (s < 0) {
++ return s + "? B";
++ } else if (s < 1024) {
++ return s + " B";
++ } else if (s < 1024 * 1024) {
++ double k = s / 1024.0;
++ String ks = dotPast(k, 3);
++
++ return s + " (" + ks + " KB)";
++ } else {
++ double m = s / (1024.0*1024.0);
++ String ms = dotPast(m, 3);
++ return s + " (" + ms + " MB)";
++ }
++ }
++
+ //Internally used to receive directory content from server
+ //Here, the server sends one file/directory with it's attributes
+ void readFTPMsgDirectoryListContent() throws IOException
+@@ -1217,17 +1452,32 @@
+ dwReserved0,
+ dwReserved1;
+ long ftCreationTime, ftLastAccessTime, ftLastWriteTime;
++ int ftCreationTimeL, ftLastAccessTimeL, ftLastWriteTimeL;
++ int ftCreationTimeH, ftLastAccessTimeH, ftLastWriteTimeH;
+ char cFileName, cAlternateFileName;
+ int length = 0;
+ is.readInt();
+ length = is.readInt();
++
++ char[] chars = new char[4*length];
++ int char_cnt = 0;
++ for (int i = 0; i < chars.length; i++) {
++ chars[i] = '\0';
++ }
++
+ dwFileAttributes = is.readInt();
+ length -= 4;
+- ftCreationTime = is.readLong();
++ //ftCreationTime = is.readLong();
++ ftCreationTimeL = is.readInt();
++ ftCreationTimeH = is.readInt();
+ length -= 8;
+- ftLastAccessTime = is.readLong();
++ //ftLastAccessTime = is.readLong();
++ ftLastAccessTimeL = is.readInt();
++ ftLastAccessTimeH = is.readInt();
+ length -= 8;
+- ftLastWriteTime = is.readLong();
++ //ftLastWriteTime = is.readLong();
++ ftLastWriteTimeL = is.readInt();
++ ftLastWriteTimeH = is.readInt();
+ length -= 8;
+ nFileSizeHigh = is.readInt();
+ length -= 4;
+@@ -1239,10 +1489,12 @@
+ length -= 4;
+ cFileName = (char) is.readUnsignedByte();
+ length--;
++ chars[char_cnt++] = cFileName;
+ while (cFileName != '\0')
+ {
+ fileName += cFileName;
+ cFileName = (char) is.readUnsignedByte();
++ chars[char_cnt++] = cFileName;
+ length--;
+ }
+ cAlternateFileName = (char) is.readByte();
+@@ -1253,7 +1505,28 @@
+ cAlternateFileName = (char) is.readUnsignedByte();
+ length--;
+ }
+- if (dwFileAttributes == 268435456
++ String guessed = guess_encoding(chars);
++ if (!guessed.equals("ASCII")) {
++ System.out.println("guess: " + guessed + "\t" + fileName);
++ }
++ if (guessed.equals("UTF-8")) {
++ try {
++ byte[] bytes = new byte[char_cnt-1];
++ for (int i=0; i < char_cnt-1; i++) {
++ bytes[i] = (byte) chars[i];
++ }
++ String newstr = new String(bytes, "UTF-8");
++ fileName = newstr;
++ } catch (Exception e) {
++ System.out.println("failed to convert bytes to UTF-8 based string");
++ }
++ }
++ for (int i = 0; i < char_cnt; i++) {
++ //System.out.println("char[" + i + "]\t" + (int) chars[i]);
++ }
++ if (fileName.length() <= 0) {
++ ;
++ } else if (dwFileAttributes == 268435456
+ || dwFileAttributes == 369098752
+ || dwFileAttributes == 285212672
+ || dwFileAttributes == 271056896
+@@ -1263,11 +1536,74 @@
+ || dwFileAttributes == 369623040)
+ {
+ fileName = " [" + fileName + "]";
+- remoteDirsList.add(fileName); // sf@2004
+- }
+- else
+- {
+- remoteFilesList.add(" " + fileName); // sf@2004
++// begin runge/x11vnc
++// remoteDirsList.add(fileName); // sf@2004
++ int i = -1;
++ String t1 = fileName.toLowerCase();
++ for (int j = 0; j < remoteDirsList.size(); j++) {
++ String t = (String) remoteDirsList.get(j);
++ String t2 = t.toLowerCase();
++ if (t1.compareTo(t2) < 0) {
++ i = j;
++ break;
++ }
++ }
++ //String s = "Lastmod: " + timeStr(ftLastWriteTimeL, ftLastWriteTimeH) + " " + fileName;
++ String f2 = fileName;
++ if (f2.length() < 24) {
++ for (int ik = f2.length(); ik < 24; ik++) {
++ f2 = f2 + " ";
++ }
++ }
++ String s = f2 + " \tLastmod: " + timeStr(ftLastWriteTimeL, ftLastWriteTimeH) + " \t\tSize: " + sizeStr(nFileSizeLow);
++ //s = fileName + " Lastmod: " + zogswap(ftLastWriteTimeL);
++ if (i >= 0) {
++ remoteDirsList.add(i, fileName);
++ remoteDirsListInfo.add(i, s);
++ } else {
++ remoteDirsList.add(fileName);
++ remoteDirsListInfo.add(s);
++ }
++// end runge/x11vnc
++ } else {
++// begin runge/x11vnc
++// remoteFilesList.add(" " + fileName); // sf@2004
++
++ fileName = " " + fileName;
++ int i = -1;
++ String t1 = fileName.toLowerCase();
++ for (int j = 0; j < remoteFilesList.size(); j++) {
++ String t = (String) remoteFilesList.get(j);
++ String t2 = t.toLowerCase();
++ if (t1.compareTo(t2) < 0) {
++ i = j;
++ break;
++ }
++ }
++ String f2 = fileName;
++ if (f2.length() < 24) {
++ for (int ik = f2.length(); ik < 24; ik++) {
++ f2 = f2 + " ";
++ }
++ }
++
++if (false) {
++System.out.println("fileName: " + f2);
++System.out.println("ftLastWriteTimeL: " + ftLastWriteTimeL);
++System.out.println("ftLastWriteTimeH: " + ftLastWriteTimeH);
++System.out.println("nFileSizeLow: " + nFileSizeLow);
++}
++
++ String s = f2 + " \tLastmod: " + timeStr(ftLastWriteTimeL, ftLastWriteTimeH) + " \t\tSize: " + sizeStr(nFileSizeLow);
++ //s = fileName + " Lastmod: " + ftLastWriteTimeL + "/" + zogswap(ftLastWriteTimeL) + " Size: " + nFileSizeLow + "/" + zogswap(nFileSizeLow);
++ if (i >= 0) {
++ remoteFilesList.add(i, fileName);
++ remoteFilesListInfo.add(i, s);
++ } else {
++ remoteFilesList.add(fileName);
++ remoteFilesListInfo.add(s);
++ }
++// end runge/x11vnc
+ }
+
+ // a.add(fileName);
+@@ -1282,14 +1618,32 @@
+
+ // sf@2004
+ a.clear();
+- for (int i = 0; i < remoteDirsList.size(); i++)
++ b.clear();
++ for (int i = 0; i < remoteDirsList.size(); i++) {
+ a.add(remoteDirsList.get(i));
+- for (int i = 0; i < remoteFilesList.size(); i++)
++ b.add(remoteDirsListInfo.get(i));
++ }
++ for (int i = 0; i < remoteFilesList.size(); i++) {
+ a.add(remoteFilesList.get(i));
++
++ b.add(remoteFilesListInfo.get(i));
++ }
+ remoteDirsList.clear();
++ remoteDirsListInfo.clear();
+ remoteFilesList.clear();
++ remoteFilesListInfo.clear();
+
+- viewer.ftp.printDirectory(a);
++// begin runge/x11vnc
++ // Hack for double listing at startup... probably libvncserver bug..
++ readServerDriveListCnt++;
++ if (readServerDriveListCnt == 2) {
++ if (System.currentTimeMillis() - readServerDriveListTime < 2000) {
++//System.out.println("readServerDriveListCnt skip " + readServerDriveListCnt);
++ return;
++ }
++ }
++// end runge/x11vnc
++ viewer.ftp.printDirectory(a, b);
+ }
+
+ //Internally used to signify the drive requested is not ready
+@@ -1299,6 +1653,8 @@
+ System.out.println("Remote Drive unavailable");
+ viewer.ftp.connectionStatus.setText(" > WARNING - Remote Drive unavailable (possibly restricted access or media not present)");
+ viewer.ftp.remoteStatus.setText("WARNING: Remote Drive unavailable");
++ viewer.ftp.historyComboBox.insertItemAt(new String(" > WARNING: Remote Drive unavailable."), 0);
++ viewer.ftp.historyComboBox.setSelectedIndex(0);
+ }
+
+ //Call this method to request the list of drives on the server.
+@@ -1306,12 +1662,11 @@
+ {
+ try
+ {
+- viewer.rfb.writeRfbFileTransferMsg(
+- RfbProto.rfbDirContentRequest,
+- RfbProto.rfbRDrivesList,
+- 0,
+- 0,
+- null);
++ viewer.rfb.writeRfbFileTransferMsg(RfbProto.rfbDirContentRequest, RfbProto.rfbRDrivesList, 0, 0, null);
++// begin runge/x11vnc
++ readServerDriveListCnt = 0;
++ readServerDriveListTime = System.currentTimeMillis();
++// end runge/x11vnc
+ }
+ catch (IOException e)
+ {
+@@ -1355,21 +1710,21 @@
+ int h,
+ boolean incremental)
+ throws IOException {
+- if (!viewer.ftp.isVisible()) {
+- byte[] b = new byte[10];
++ if (!viewer.ftp.isVisible()) {
++ byte[] b = new byte[10];
+
+- b[0] = (byte) FramebufferUpdateRequest;
+- b[1] = (byte) (incremental ? 1 : 0);
+- b[2] = (byte) ((x >> 8) & 0xff);
+- b[3] = (byte) (x & 0xff);
+- b[4] = (byte) ((y >> 8) & 0xff);
+- b[5] = (byte) (y & 0xff);
+- b[6] = (byte) ((w >> 8) & 0xff);
+- b[7] = (byte) (w & 0xff);
+- b[8] = (byte) ((h >> 8) & 0xff);
+- b[9] = (byte) (h & 0xff);
++ b[0] = (byte) FramebufferUpdateRequest;
++ b[1] = (byte) (incremental ? 1 : 0);
++ b[2] = (byte) ((x >> 8) & 0xff);
++ b[3] = (byte) (x & 0xff);
++ b[4] = (byte) ((y >> 8) & 0xff);
++ b[5] = (byte) (y & 0xff);
++ b[6] = (byte) ((w >> 8) & 0xff);
++ b[7] = (byte) (w & 0xff);
++ b[8] = (byte) ((h >> 8) & 0xff);
++ b[9] = (byte) (h & 0xff);
+
+- os.write(b);
++ os.write(b);
+ }
+ }
+
+@@ -1482,7 +1837,13 @@
+ b[6] = (byte) ((text.length() >> 8) & 0xff);
+ b[7] = (byte) (text.length() & 0xff);
+
+- System.arraycopy(text.getBytes(), 0, b, 8, text.length());
++ if (false && max_char(text) > 255) {
++ System.arraycopy(text.getBytes("UTF-8"), 0, b, 8, text.length());
++ } else if (max_char(text) > 127) {
++ System.arraycopy(text.getBytes("ISO-8859-1"), 0, b, 8, text.length());
++ } else {
++ System.arraycopy(text.getBytes(), 0, b, 8, text.length());
++ }
+
+ os.write(b);
+ // }
+@@ -1506,6 +1867,37 @@
+ final static int META_MASK = InputEvent.META_MASK;
+ final static int ALT_MASK = InputEvent.ALT_MASK;
+
++ void writeWheelEvent(MouseWheelEvent evt) throws IOException {
++ eventBufLen = 0;
++
++ int x = evt.getX();
++ int y = evt.getY();
++
++ if (x < 0) x = 0;
++ if (y < 0) y = 0;
++
++ int ptrmask;
++
++ int clicks = evt.getWheelRotation();
++ System.out.println("writeWheelEvent: clicks: " + clicks);
++ if (clicks > 0) {
++ ptrmask = 16;
++ } else if (clicks < 0) {
++ ptrmask = 8;
++ } else {
++ return;
++ }
++
++ eventBuf[eventBufLen++] = (byte) PointerEvent;
++ eventBuf[eventBufLen++] = (byte) ptrmask;
++ eventBuf[eventBufLen++] = (byte) ((x >> 8) & 0xff);
++ eventBuf[eventBufLen++] = (byte) (x & 0xff);
++ eventBuf[eventBufLen++] = (byte) ((y >> 8) & 0xff);
++ eventBuf[eventBufLen++] = (byte) (y & 0xff);
++
++ os.write(eventBuf, 0, eventBufLen);
++ }
++
+ //
+ // Write a pointer event message. We may need to send modifier key events
+ // around it to set the correct modifier state.
+@@ -1610,6 +2002,21 @@
+
+ boolean down = (evt.getID() == KeyEvent.KEY_PRESSED);
+
++ if (viewer.debugKeyboard) {
++ System.out.println("----------------------------------------");
++ System.out.println("evt.getKeyChar: " + evt.getKeyChar());
++ System.out.println("getKeyText: " + KeyEvent.getKeyText(evt.getKeyCode()));
++ System.out.println("evt.getKeyCode: " + evt.getKeyCode());
++ System.out.println("evt.getID: " + evt.getID());
++ System.out.println("evt.getKeyLocation: " + evt.getKeyLocation());
++ System.out.println("evt.isActionKey: " + evt.isActionKey());
++ System.out.println("evt.isControlDown: " + evt.isControlDown());
++ System.out.println("evt.getModifiers: " + evt.getModifiers());
++ System.out.println("getKeyModifiersText: " + KeyEvent.getKeyModifiersText(evt.getModifiers()));
++ System.out.println("evt.paramString: " + evt.paramString());
++ }
++
++
+ int key;
+ if (evt.isActionKey()) {
+
+@@ -1685,6 +2092,9 @@
+ default :
+ return;
+ }
++ if (key == 0xffc2 && viewer.mapF5_to_atsign) {
++ key = 0x40;
++ }
+
+ } else {
+
+@@ -1794,6 +2204,16 @@
+ int oldModifiers = 0;
+
+ void writeModifierKeyEvents(int newModifiers) {
++ if(viewer.forbid_Ctrl_Alt) {
++ if ((newModifiers & CTRL_MASK) != 0 && (newModifiers & ALT_MASK) != 0) {
++ int orig = newModifiers;
++ newModifiers &= ~ALT_MASK;
++ newModifiers &= ~CTRL_MASK;
++ if (viewer.debugKeyboard) {
++ System.out.println("Ctrl+Alt modifiers: " + orig + " -> " + newModifiers);
++ }
++ }
++ }
+ if ((newModifiers & CTRL_MASK) != (oldModifiers & CTRL_MASK))
+ writeKeyEvent(0xffe3, (newModifiers & CTRL_MASK) != 0);
+
+diff -Naur JavaViewer.orig/SSLSocketToMe.java JavaViewer/SSLSocketToMe.java
+--- JavaViewer.orig/SSLSocketToMe.java 1969-12-31 19:00:00.000000000 -0500
++++ JavaViewer/SSLSocketToMe.java 2010-07-10 19:18:06.000000000 -0400
+@@ -0,0 +1,2067 @@
++/*
++ * SSLSocketToMe.java: add SSL encryption to Java VNC Viewer.
++ *
++ * Copyright (c) 2006 Karl J. Runge <runge@karlrunge.com>
++ * All rights reserved.
++ *
++ * This is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * 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. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this software; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
++ * USA.
++ *
++ */
++
++import java.net.*;
++import java.io.*;
++import javax.net.ssl.*;
++import java.util.*;
++
++import java.security.*;
++import java.security.cert.*;
++import java.security.spec.*;
++import java.security.cert.Certificate;
++import java.security.cert.CertificateFactory;
++
++import java.awt.*;
++import java.awt.event.*;
++
++public class SSLSocketToMe {
++
++ /* basic member data: */
++ String host;
++ int port;
++ VncViewer viewer;
++
++ boolean debug = true;
++ boolean debug_certs = false;
++
++ /* sockets */
++ SSLSocket socket = null;
++ SSLSocketFactory factory;
++
++ /* fallback for Proxy connection */
++ boolean proxy_in_use = false;
++ boolean proxy_failure = false;
++ public DataInputStream is = null;
++ public OutputStream os = null;
++
++ /* strings from user WRT proxy: */
++ String proxy_auth_string = null;
++ String proxy_dialog_host = null;
++ int proxy_dialog_port = 0;
++
++ Socket proxySock;
++ DataInputStream proxy_is;
++ OutputStream proxy_os;
++
++ /* trust contexts */
++ SSLContext trustloc_ctx;
++ SSLContext trustall_ctx;
++ SSLContext trustsrv_ctx;
++ SSLContext trusturl_ctx;
++ SSLContext trustone_ctx;
++
++ /* corresponding trust managers */
++ TrustManager[] trustAllCerts;
++ TrustManager[] trustSrvCert;
++ TrustManager[] trustUrlCert;
++ TrustManager[] trustOneCert;
++
++ /* client-side SSL auth key (oneTimeKey=...) */
++ KeyManager[] mykey = null;
++
++ boolean user_wants_to_see_cert = true;
++ String cert_fail = null;
++
++ /* cert(s) we retrieve from Web server, VNC server, or serverCert param: */
++ java.security.cert.Certificate[] trustallCerts = null;
++ java.security.cert.Certificate[] trustsrvCerts = null;
++ java.security.cert.Certificate[] trusturlCerts = null;
++
++ /* utility to decode hex oneTimeKey=... and serverCert=... */
++ byte[] hex2bytes(String s) {
++ byte[] bytes = new byte[s.length()/2];
++ for (int i=0; i<s.length()/2; i++) {
++ int j = 2*i;
++ try {
++ int val = Integer.parseInt(s.substring(j, j+2), 16);
++ if (val > 127) {
++ val -= 256;
++ }
++ Integer I = new Integer(val);
++ bytes[i] = Byte.decode(I.toString()).byteValue();
++
++ } catch (Exception e) {
++ ;
++ }
++ }
++ return bytes;
++ }
++
++ SSLSocketToMe(String h, int p, VncViewer v) throws Exception {
++ host = h;
++ port = p;
++ viewer = v;
++
++ debug_certs = v.debugCerts;
++
++ /* we will first try default factory for certification: */
++
++ factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
++
++ dbg("SSL startup: " + host + " " + port);
++
++
++ /* create trust managers to be used if initial handshake fails: */
++
++ trustAllCerts = new TrustManager[] {
++ /*
++ * this one accepts everything. Only used if user
++ * has disabled checking (trustAllVncCerts=yes)
++ * or when we grab the cert to show it to them in
++ * a dialog and ask them to manually verify/accept it.
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) {
++ /* empty */
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) {
++ /* empty */
++ dbg("ALL: an untrusted connect to grab cert.");
++ }
++ }
++ };
++
++ trustUrlCert = new TrustManager[] {
++ /*
++ * this one accepts only the retrieved server
++ * cert by SSLSocket by this applet and stored in
++ * trusturlCerts.
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ throw new CertificateException("No Clients (URL)");
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ /* we want to check 'certs' against 'trusturlCerts' */
++ if (trusturlCerts == null) {
++ throw new CertificateException(
++ "No Trust url Certs array.");
++ }
++ if (trusturlCerts.length < 1) {
++ throw new CertificateException(
++ "No Trust url Certs.");
++ }
++ if (certs == null) {
++ throw new CertificateException(
++ "No this-certs array.");
++ }
++ if (certs.length < 1) {
++ throw new CertificateException(
++ "No this-certs Certs.");
++ }
++ if (certs.length != trusturlCerts.length) {
++ throw new CertificateException(
++ "certs.length != trusturlCerts.length " + certs.length + " " + trusturlCerts.length);
++ }
++ boolean ok = true;
++ for (int i = 0; i < certs.length; i++) {
++ if (! trusturlCerts[i].equals(certs[i])) {
++ ok = false;
++ dbg("URL: cert mismatch at i=" + i);
++ dbg("URL: cert mismatch cert" + certs[i]);
++ dbg("URL: cert mismatch url" + trusturlCerts[i]);
++ if (cert_fail == null) {
++ cert_fail = "cert-mismatch";
++ }
++ }
++ if (debug_certs) {
++ dbg("\n***********************************************");
++ dbg("URL: cert info at i=" + i);
++ dbg("URL: cert info cert" + certs[i]);
++ dbg("===============================================");
++ dbg("URL: cert info url" + trusturlCerts[i]);
++ dbg("***********************************************");
++ }
++ }
++ if (!ok) {
++ throw new CertificateException(
++ "Server Cert Chain != URL Cert Chain.");
++ }
++ dbg("URL: trusturlCerts[i] matches certs[i] i=0:" + (certs.length-1));
++ }
++ }
++ };
++
++ trustSrvCert = new TrustManager[] {
++ /*
++ * this one accepts cert given to us in the serverCert
++ * Applet Parameter we were started with. It is
++ * currently a fatal error if the VNC Server's cert
++ * doesn't match it.
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ throw new CertificateException("No Clients (SRV)");
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ /* we want to check 'certs' against 'trustsrvCerts' */
++ if (trustsrvCerts == null) {
++ throw new CertificateException(
++ "No Trust srv Certs array.");
++ }
++ if (trustsrvCerts.length < 1) {
++ throw new CertificateException(
++ "No Trust srv Certs.");
++ }
++ if (certs == null) {
++ throw new CertificateException(
++ "No this-certs array.");
++ }
++ if (certs.length < 1) {
++ throw new CertificateException(
++ "No this-certs Certs.");
++ }
++ if (certs.length != trustsrvCerts.length) {
++ throw new CertificateException(
++ "certs.length != trustsrvCerts.length " + certs.length + " " + trustsrvCerts.length);
++ }
++ boolean ok = true;
++ for (int i = 0; i < certs.length; i++) {
++ if (! trustsrvCerts[i].equals(certs[i])) {
++ ok = false;
++ dbg("SRV: cert mismatch at i=" + i);
++ dbg("SRV: cert mismatch cert" + certs[i]);
++ dbg("SRV: cert mismatch srv" + trustsrvCerts[i]);
++ if (cert_fail == null) {
++ cert_fail = "server-cert-mismatch";
++ }
++ }
++ if (debug_certs) {
++ dbg("\n***********************************************");
++ dbg("SRV: cert info at i=" + i);
++ dbg("SRV: cert info cert" + certs[i]);
++ dbg("===============================================");
++ dbg("SRV: cert info srv" + trustsrvCerts[i]);
++ dbg("***********************************************");
++ }
++ }
++ if (!ok) {
++ throw new CertificateException(
++ "Server Cert Chain != serverCert Applet Parameter Cert Chain.");
++ }
++ dbg("SRV: trustsrvCerts[i] matches certs[i] i=0:" + (certs.length-1));
++ }
++ }
++ };
++
++ trustOneCert = new TrustManager[] {
++ /*
++ * this one accepts only the retrieved server
++ * cert by SSLSocket by this applet we stored in
++ * trustallCerts that user has accepted or applet
++ * parameter trustAllVncCerts=yes is set. This is
++ * for when we reconnect after the user has manually
++ * accepted the trustall cert in the dialog (or set
++ * trustAllVncCerts=yes applet param.)
++ */
++ new X509TrustManager() {
++ public java.security.cert.X509Certificate[]
++ getAcceptedIssuers() {
++ return null;
++ }
++ public void checkClientTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ throw new CertificateException("No Clients (ONE)");
++ }
++ public void checkServerTrusted(
++ java.security.cert.X509Certificate[] certs,
++ String authType) throws CertificateException {
++ /* we want to check 'certs' against 'trustallCerts' */
++ if (trustallCerts == null) {
++ throw new CertificateException(
++ "No Trust All Server Certs array.");
++ }
++ if (trustallCerts.length < 1) {
++ throw new CertificateException(
++ "No Trust All Server Certs.");
++ }
++ if (certs == null) {
++ throw new CertificateException(
++ "No this-certs array.");
++ }
++ if (certs.length < 1) {
++ throw new CertificateException(
++ "No this-certs Certs.");
++ }
++ if (certs.length != trustallCerts.length) {
++ throw new CertificateException(
++ "certs.length != trustallCerts.length " + certs.length + " " + trustallCerts.length);
++ }
++ boolean ok = true;
++ for (int i = 0; i < certs.length; i++) {
++ if (! trustallCerts[i].equals(certs[i])) {
++ ok = false;
++ dbg("ONE: cert mismatch at i=" + i);
++ dbg("ONE: cert mismatch cert" + certs[i]);
++ dbg("ONE: cert mismatch all" + trustallCerts[i]);
++ }
++ if (debug_certs) {
++ dbg("\n***********************************************");
++ dbg("ONE: cert info at i=" + i);
++ dbg("ONE: cert info cert" + certs[i]);
++ dbg("===============================================");
++ dbg("ONE: cert info all" + trustallCerts[i]);
++ dbg("***********************************************");
++ }
++ }
++ if (!ok) {
++ throw new CertificateException(
++ "Server Cert Chain != TRUSTALL Cert Chain.");
++ }
++ dbg("ONE: trustallCerts[i] matches certs[i] i=0:" + (certs.length-1));
++ }
++ }
++ };
++
++ /*
++ * The above TrustManagers are used:
++ *
++ * 1) to retrieve the server cert in case of failure to
++ * display it to the user in a dialog.
++ * 2) to subsequently connect to the server if user agrees.
++ */
++
++ /*
++ * build oneTimeKey cert+key if supplied in applet parameter:
++ */
++ if (viewer.oneTimeKey != null && viewer.oneTimeKey.equals("PROMPT")) {
++ ClientCertDialog d = new ClientCertDialog();
++ viewer.oneTimeKey = d.queryUser();
++ }
++ if (viewer.oneTimeKey != null && viewer.oneTimeKey.indexOf(",") > 0) {
++ int idx = viewer.oneTimeKey.indexOf(",");
++
++ String onetimekey = viewer.oneTimeKey.substring(0, idx);
++ byte[] key = hex2bytes(onetimekey);
++ String onetimecert = viewer.oneTimeKey.substring(idx+1);
++ byte[] cert = hex2bytes(onetimecert);
++
++ KeyFactory kf = KeyFactory.getInstance("RSA");
++ PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec ( key );
++ PrivateKey ff = kf.generatePrivate (keysp);
++ if (debug_certs) {
++ dbg("one time key " + ff);
++ }
++
++ CertificateFactory cf = CertificateFactory.getInstance("X.509");
++ Collection c = cf.generateCertificates(new ByteArrayInputStream(cert));
++ Certificate[] certs = new Certificate[c.toArray().length];
++ if (c.size() == 1) {
++ Certificate tmpcert = cf.generateCertificate(new ByteArrayInputStream(cert));
++ if (debug_certs) {
++ dbg("one time cert" + tmpcert);
++ }
++ certs[0] = tmpcert;
++ } else {
++ certs = (Certificate[]) c.toArray();
++ }
++
++ KeyStore ks = KeyStore.getInstance("JKS");
++ ks.load(null, null);
++ ks.setKeyEntry("onetimekey", ff, "".toCharArray(), certs);
++ String da = KeyManagerFactory.getDefaultAlgorithm();
++ KeyManagerFactory kmf = KeyManagerFactory.getInstance(da);
++ kmf.init(ks, "".toCharArray());
++
++ mykey = kmf.getKeyManagers();
++ }
++
++ /*
++ * build serverCert cert if supplied in applet parameter:
++ */
++ if (viewer.serverCert != null) {
++ CertificateFactory cf = CertificateFactory.getInstance("X.509");
++ byte[] cert = hex2bytes(viewer.serverCert);
++ Collection c = cf.generateCertificates(new ByteArrayInputStream(cert));
++ trustsrvCerts = new Certificate[c.toArray().length];
++ if (c.size() == 1) {
++ Certificate tmpcert = cf.generateCertificate(new ByteArrayInputStream(cert));
++ trustsrvCerts[0] = tmpcert;
++ } else {
++ trustsrvCerts = (Certificate[]) c.toArray();
++ }
++ }
++
++ /* the trust loc certs context: */
++ try {
++ trustloc_ctx = SSLContext.getInstance("SSL");
++
++ /*
++ * below is a failed attempt to get jvm's default
++ * trust manager using null (below) makes it so
++ * for HttpsURLConnection the server cannot be
++ * verified (no prompting.)
++ */
++ if (false) {
++ boolean didit = false;
++ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
++ tmf.init((KeyStore) null);
++ TrustManager [] tml = tmf.getTrustManagers();
++ for (int i = 0; i < tml.length; i++) {
++ TrustManager tm = tml[i];
++ if (tm instanceof X509TrustManager) {
++ TrustManager tm1[] = new TrustManager[1];
++ tm1[0] = tm;
++ trustloc_ctx.init(mykey, tm1, null);
++ didit = true;
++ break;
++ }
++ }
++ if (!didit) {
++ trustloc_ctx.init(mykey, null, null);
++ }
++ } else {
++ /* we have to set trust manager to null */
++ trustloc_ctx.init(mykey, null, null);
++ }
++
++ } catch (Exception e) {
++ String msg = "SSL trustloc_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust all certs context: */
++ try {
++ trustall_ctx = SSLContext.getInstance("SSL");
++ trustall_ctx.init(mykey, trustAllCerts, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trustall_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust url certs context: */
++ try {
++ trusturl_ctx = SSLContext.getInstance("SSL");
++ trusturl_ctx.init(mykey, trustUrlCert, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trusturl_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust srv certs context: */
++ try {
++ trustsrv_ctx = SSLContext.getInstance("SSL");
++ trustsrv_ctx.init(mykey, trustSrvCert, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trustsrv_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++
++ /* the trust the one cert from server context: */
++ try {
++ trustone_ctx = SSLContext.getInstance("SSL");
++ trustone_ctx.init(mykey, trustOneCert, new
++ java.security.SecureRandom());
++
++ } catch (Exception e) {
++ String msg = "SSL trustone_ctx FAILED.";
++ dbg(msg);
++ throw new Exception(msg);
++ }
++ }
++
++ /*
++ * we call this early on to 1) check for a proxy, 2) grab
++ * Browser/JVM accepted HTTPS cert.
++ */
++ public void check_for_proxy_and_grab_vnc_server_cert() {
++
++ trusturlCerts = null;
++ proxy_in_use = false;
++
++ if (viewer.ignoreProxy) {
++ /* applet param says skip it. */
++ /* the downside is we do not set trusturlCerts for comparison later... */
++ /* nor do we autodetect x11vnc for GET=1. */
++ return;
++ }
++
++ dbg("------------------------------------------------");
++ dbg("Into check_for_proxy_and_grab_vnc_server_cert():");
++
++ dbg("TRYING HTTPS:");
++ String ustr = "https://" + host + ":";
++ if (viewer.httpsPort != null) {
++ ustr += viewer.httpsPort;
++ } else {
++ ustr += port;
++ }
++ ustr += viewer.urlPrefix + "/check.https.proxy.connection";
++ dbg("ustr is: " + ustr);
++
++ try {
++ /* prepare for an HTTPS URL connection to host:port */
++ URL url = new URL(ustr);
++ HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
++
++ if (mykey != null) {
++ /* with oneTimeKey (mykey) we can't use the default SSL context */
++ if (trustsrvCerts != null) {
++ dbg("passing trustsrv_ctx to HttpsURLConnection to provide client cert.");
++ https.setSSLSocketFactory(trustsrv_ctx.getSocketFactory());
++ } else if (trustloc_ctx != null) {
++ dbg("passing trustloc_ctx to HttpsURLConnection to provide client cert.");
++ https.setSSLSocketFactory(trustloc_ctx.getSocketFactory());
++ }
++ }
++
++ https.setUseCaches(false);
++ https.setRequestMethod("GET");
++ https.setRequestProperty("Pragma", "No-Cache");
++ https.setRequestProperty("Proxy-Connection", "Keep-Alive");
++ https.setDoInput(true);
++
++ dbg("trying https.connect()");
++ https.connect();
++
++ dbg("trying https.getServerCertificates()");
++ trusturlCerts = https.getServerCertificates();
++
++ if (trusturlCerts == null) {
++ dbg("set trusturlCerts to null!");
++ } else {
++ dbg("set trusturlCerts to non-null");
++ }
++
++ if (https.usingProxy()) {
++ proxy_in_use = true;
++ dbg("An HTTPS proxy is in use. There may be connection problems.");
++ }
++
++ dbg("trying https.getContent()");
++ Object output = https.getContent();
++ dbg("trying https.disconnect()");
++ https.disconnect();
++ if (! viewer.GET) {
++ String header = https.getHeaderField("VNC-Server");
++ if (header != null && header.startsWith("x11vnc")) {
++ dbg("detected x11vnc server (1), setting GET=1");
++ viewer.GET = true;
++ }
++ }
++
++ } catch(Exception e) {
++ dbg("HttpsURLConnection: " + e.getMessage());
++ }
++
++ if (proxy_in_use) {
++ dbg("exit check_for_proxy_and_grab_vnc_server_cert():");
++ dbg("------------------------------------------------");
++ return;
++ } else if (trusturlCerts != null && !viewer.forceProxy) {
++ /* Allow user to require HTTP check? use forceProxy for now. */
++ dbg("SKIPPING HTTP PROXY CHECK: got trusturlCerts, assuming proxy info is correct.");
++ dbg("exit check_for_proxy_and_grab_vnc_server_cert():");
++ dbg("------------------------------------------------");
++ return;
++ }
++
++ /*
++ * XXX need to remember scenario where this extra check
++ * gives useful info. User's Browser proxy settings?
++ */
++ dbg("TRYING HTTP:");
++ ustr = "http://" + host + ":" + port;
++ ustr += viewer.urlPrefix + "/index.vnc";
++ dbg("ustr is: " + ustr);
++
++ try {
++ /* prepare for an HTTP URL connection to the same host:port (but not httpsPort) */
++ URL url = new URL(ustr);
++ HttpURLConnection http = (HttpURLConnection)
++ url.openConnection();
++
++ http.setUseCaches(false);
++ http.setRequestMethod("GET");
++ http.setRequestProperty("Pragma", "No-Cache");
++ http.setRequestProperty("Proxy-Connection", "Keep-Alive");
++ http.setDoInput(true);
++
++ dbg("trying http.connect()");
++ http.connect();
++
++ if (http.usingProxy()) {
++ proxy_in_use = true;
++ dbg("An HTTP proxy is in use. There may be connection problems.");
++ }
++ dbg("trying http.getContent()");
++ Object output = http.getContent();
++ dbg("trying http.disconnect()");
++ http.disconnect();
++ if (! viewer.GET) {
++ String header = http.getHeaderField("VNC-Server");
++ if (header != null && header.startsWith("x11vnc")) {
++ dbg("detected x11vnc server (2), setting GET=1");
++ viewer.GET = true;
++ }
++ }
++ } catch(Exception e) {
++ dbg("HttpURLConnection: " + e.getMessage());
++ }
++ dbg("exit check_for_proxy_and_grab_vnc_server_cert():");
++ dbg("------------------------------------------------");
++ }
++
++ public Socket connectSock() throws IOException {
++ /*
++ * first try a https connection to detect a proxy, and
++ * grab the VNC server cert at the same time:
++ */
++ check_for_proxy_and_grab_vnc_server_cert();
++
++ boolean srv_cert = false;
++
++ if (trustsrvCerts != null) {
++ /* applet parameter suppled serverCert */
++ dbg("viewer.trustSrvCert-0 using trustsrv_ctx");
++ factory = trustsrv_ctx.getSocketFactory();
++ srv_cert = true;
++ } else if (viewer.trustAllVncCerts) {
++ /* trust all certs (no checking) */
++ dbg("viewer.trustAllVncCerts-0 using trustall_ctx");
++ factory = trustall_ctx.getSocketFactory();
++ } else if (trusturlCerts != null) {
++ /* trust certs the Browser/JVM accepted in check_for_proxy... */
++ dbg("using trusturl_ctx");
++ factory = trusturl_ctx.getSocketFactory();
++ } else {
++ /* trust the local defaults */
++ dbg("using trustloc_ctx");
++ factory = trustloc_ctx.getSocketFactory();
++ }
++
++ socket = null;
++
++ try {
++ if (proxy_in_use && viewer.forceProxy) {
++ throw new Exception("forcing proxy (forceProxy)");
++ } else if (viewer.CONNECT != null) {
++ throw new Exception("forcing CONNECT");
++ }
++
++ int timeout = 6;
++ if (timeout > 0) {
++ socket = (SSLSocket) factory.createSocket();
++ InetSocketAddress inetaddr = new InetSocketAddress(host, port);
++ dbg("Using timeout of " + timeout + " secs to: " + host + ":" + port);
++ socket.connect(inetaddr, timeout * 1000);
++ } else {
++ socket = (SSLSocket) factory.createSocket(host, port);
++ }
++
++ } catch (Exception esock) {
++ dbg("socket error: " + esock.getMessage());
++ if (proxy_in_use || viewer.CONNECT != null) {
++ proxy_failure = true;
++ if (proxy_in_use) {
++ dbg("HTTPS proxy in use. Trying to go with it.");
++ } else {
++ dbg("viewer.CONNECT reverse proxy in use. Trying to go with it.");
++ }
++ try {
++ socket = proxy_socket(factory);
++ } catch (Exception e) {
++ dbg("proxy_socket error: " + e.getMessage());
++ }
++ } else {
++ /* n.b. socket is left in error state to cause ex. below. */
++ }
++ }
++
++ try {
++ socket.startHandshake();
++
++ dbg("The Server Connection Verified OK on 1st try.");
++
++ java.security.cert.Certificate[] currentTrustedCerts;
++ BrowserCertsDialog bcd;
++
++ SSLSession sess = socket.getSession();
++ currentTrustedCerts = sess.getPeerCertificates();
++
++ if (viewer.trustAllVncCerts) {
++ dbg("viewer.trustAllVncCerts-1 keeping socket.");
++ } else if (currentTrustedCerts == null || currentTrustedCerts.length < 1) {
++ try {
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy.");
++ }
++ socket = null;
++ throw new SSLHandshakeException("no current certs");
++ }
++
++ String serv = "";
++ try {
++ CertInfo ci = new CertInfo(currentTrustedCerts[0]);
++ serv = ci.get_certinfo("CN");
++ } catch (Exception e) {
++ ;
++ }
++
++ if (viewer.trustAllVncCerts) {
++ dbg("viewer.trustAllVncCerts-2 skipping browser certs dialog");
++ user_wants_to_see_cert = false;
++ } else if (viewer.serverCert != null && trustsrvCerts != null) {
++ dbg("viewer.serverCert-1 skipping browser certs dialog");
++ user_wants_to_see_cert = false;
++ } else if (viewer.trustUrlVncCert) {
++ dbg("viewer.trustUrlVncCert-1 skipping browser certs dialog");
++ user_wants_to_see_cert = false;
++ } else {
++ /* have a dialog with the user: */
++ bcd = new BrowserCertsDialog(serv, host + ":" + port);
++ dbg("browser certs dialog begin.");
++ bcd.queryUser();
++ dbg("browser certs dialog finished.");
++
++ if (bcd.showCertDialog) {
++ String msg = "user wants to see cert";
++ dbg(msg);
++ user_wants_to_see_cert = true;
++ if (cert_fail == null) {
++ cert_fail = "user-view";
++ }
++ throw new SSLHandshakeException(msg);
++ } else {
++ user_wants_to_see_cert = false;
++ dbg("browser certs dialog: user said yes, accept it");
++ }
++ }
++
++ } catch (SSLHandshakeException eh) {
++ dbg("SSLHandshakeException: could not automatically verify Server.");
++ dbg("msg: " + eh.getMessage());
++
++
++ /* send a cleanup string just in case: */
++ String getoutstr = "GET /index.vnc HTTP/1.0\r\nConnection: close\r\n\r\n";
++
++ try {
++ OutputStream os = socket.getOutputStream();
++ os.write(getoutstr.getBytes());
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy!");
++ }
++
++ /* reload */
++
++ socket = null;
++
++ String reason = null;
++
++ if (srv_cert) {
++ /* for serverCert usage we make this a fatal error. */
++ throw new IOException("Fatal: VNC Server's Cert does not match Applet Parameter 'serverCert=...'");
++ /* see below in TrustDialog were we describe this case to user anyway */
++ }
++
++ /*
++ * Reconnect, trusting any cert, so we can grab
++ * the cert to show it to the user in a dialog
++ * for him to manually accept. This connection
++ * is not used for anything else.
++ */
++ factory = trustall_ctx.getSocketFactory();
++ if (proxy_failure) {
++ socket = proxy_socket(factory);
++ } else {
++ socket = (SSLSocket) factory.createSocket(host, port);
++ }
++
++ if (debug_certs) {
++ dbg("trusturlCerts: " + trusturlCerts);
++ dbg("trustsrvCerts: " + trustsrvCerts);
++ }
++ if (trusturlCerts == null && cert_fail == null) {
++ cert_fail = "missing-certs";
++ }
++
++ try {
++ socket.startHandshake();
++
++ dbg("The TrustAll Server Cert-grab Connection (trivially) Verified OK.");
++
++ /* grab the cert: */
++ try {
++ SSLSession sess = socket.getSession();
++ trustallCerts = sess.getPeerCertificates();
++ } catch (Exception e) {
++ throw new Exception("Could not get " +
++ "Peer Certificate");
++ }
++ if (debug_certs) {
++ dbg("trustallCerts: " + trustallCerts);
++ }
++
++ if (viewer.trustAllVncCerts) {
++ dbg("viewer.trustAllVncCerts-3. skipping dialog, trusting everything.");
++ } else if (! browser_cert_match()) {
++ /*
++ * close socket now, we will reopen after
++ * dialog if user agrees to use the cert.
++ */
++ try {
++ OutputStream os = socket.getOutputStream();
++ os.write(getoutstr.getBytes());
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy!!");
++ }
++ socket = null;
++
++ /* dialog with user to accept cert or not: */
++
++ TrustDialog td= new TrustDialog(host, port,
++ trustallCerts);
++
++ if (cert_fail == null) {
++ ;
++ } else if (cert_fail.equals("user-view")) {
++ reason = "Reason for this Dialog:\n\n"
++ + " You Asked to View the Certificate.";
++ } else if (cert_fail.equals("server-cert-mismatch")) {
++ /* this is now fatal error, see above. */
++ reason = "Reason for this Dialog:\n\n"
++ + " The VNC Server's Certificate does not match the Certificate\n"
++ + " specified in the supplied 'serverCert' Applet Parameter.";
++ } else if (cert_fail.equals("cert-mismatch")) {
++ reason = "Reason for this Dialog:\n\n"
++ + " The VNC Server's Certificate does not match the Website's\n"
++ + " HTTPS Certificate (that you previously accepted; either\n"
++ + " manually or automatically via Certificate Authority.)";
++ } else if (cert_fail.equals("missing-certs")) {
++ reason = "Reason for this Dialog:\n\n"
++ + " Not all Certificates could be obtained to check.";
++ }
++
++ if (! td.queryUser(reason)) {
++ String msg = "User decided against it.";
++ dbg(msg);
++ throw new IOException(msg);
++ }
++ }
++
++ } catch (Exception ehand2) {
++ dbg("** Could not TrustAll Verify Server!");
++
++ throw new IOException(ehand2.getMessage());
++ }
++
++ /* reload again: */
++
++ if (socket != null) {
++ try {
++ socket.close();
++ } catch (Exception e) {
++ dbg("socket is grumpy!!!");
++ }
++ socket = null;
++ }
++
++ /*
++ * Now connect a 3rd time, using the cert
++ * retrieved during connection 2 (sadly, that
++ * the user likely blindly agreed to...)
++ */
++
++ factory = trustone_ctx.getSocketFactory();
++ if (proxy_failure) {
++ socket = proxy_socket(factory);
++ } else {
++ socket = (SSLSocket) factory.createSocket(host, port);
++ }
++
++ try {
++ socket.startHandshake();
++ dbg("TrustAll/TrustOne Server Connection Verified #3.");
++
++ } catch (Exception ehand3) {
++ dbg("** Could not TrustAll/TrustOne Verify Server #3.");
++
++ throw new IOException(ehand3.getMessage());
++ }
++ }
++
++ /* we have socket (possibly null) at this point, so proceed: */
++
++ /* handle x11vnc GET=1, if applicable: */
++ if (socket != null && viewer.GET) {
++ String str = "GET ";
++ str += viewer.urlPrefix;
++ str += "/request.https.vnc.connection";
++ str += " HTTP/1.0\r\n";
++ str += "Pragma: No-Cache\r\n";
++ str += "\r\n";
++
++ System.out.println("sending: " + str);
++ OutputStream os = socket.getOutputStream();
++ String type = "os";
++
++ if (type == "os") {
++ os.write(str.getBytes());
++ os.flush();
++ System.out.println("used OutputStream");
++ } else if (type == "bs") {
++ BufferedOutputStream bs = new BufferedOutputStream(os);
++ bs.write(str.getBytes());
++ bs.flush();
++ System.out.println("used BufferedOutputStream");
++ } else if (type == "ds") {
++ DataOutputStream ds = new DataOutputStream(os);
++ ds.write(str.getBytes());
++ ds.flush();
++ System.out.println("used DataOutputStream");
++ }
++ if (false) {
++ String rep = "";
++ DataInputStream is = new DataInputStream(
++ new BufferedInputStream(socket.getInputStream(), 16384));
++ while (true) {
++ rep += readline(is);
++ if (rep.indexOf("\r\n\r\n") >= 0) {
++ break;
++ }
++ }
++ System.out.println("rep: " + rep);
++ }
++ }
++
++ dbg("SSL returning socket to caller.");
++ dbg("");
++
++ /* could be null, let caller handle that. */
++ return (Socket) socket;
++ }
++
++ boolean browser_cert_match() {
++ String msg = "Browser URL accept previously accepted cert";
++
++ if (user_wants_to_see_cert) {
++ return false;
++ }
++
++ if (viewer.serverCert != null || trustsrvCerts != null) {
++ if (cert_fail == null) {
++ cert_fail = "server-cert-mismatch";
++ }
++ }
++ if (trustallCerts != null && trusturlCerts != null) {
++ if (trustallCerts.length == trusturlCerts.length) {
++ boolean ok = true;
++ /* check toath trustallCerts (socket) equals trusturlCerts (browser) */
++ for (int i = 0; i < trusturlCerts.length; i++) {
++ if (! trustallCerts[i].equals(trusturlCerts[i])) {
++ dbg("BCM: cert mismatch at i=" + i);
++ dbg("BCM: cert mismatch url" + trusturlCerts[i]);
++ dbg("BCM: cert mismatch all" + trustallCerts[i]);
++ ok = false;
++ }
++ }
++ if (ok) {
++ System.out.println(msg);
++ if (cert_fail == null) {
++ cert_fail = "did-not-fail";
++ }
++ return true;
++ } else {
++ if (cert_fail == null) {
++ cert_fail = "cert-mismatch";
++ }
++ return false;
++ }
++ }
++ }
++ if (cert_fail == null) {
++ cert_fail = "missing-certs";
++ }
++ return false;
++ }
++
++ private void dbg(String s) {
++ if (debug) {
++ System.out.println(s);
++ }
++ }
++
++ private int gint(String s) {
++ int n = -1;
++ try {
++ Integer I = new Integer(s);
++ n = I.intValue();
++ } catch (Exception ex) {
++ return -1;
++ }
++ return n;
++ }
++
++ /* this will do the proxy CONNECT negotiation and hook us up. */
++
++ private void proxy_helper(String proxyHost, int proxyPort) {
++
++ boolean proxy_auth = false;
++ String proxy_auth_basic_realm = "";
++ String hp = host + ":" + port;
++ dbg("proxy_helper: " + proxyHost + ":" + proxyPort + " hp: " + hp);
++
++ /* we loop here a few times trying for the password case */
++ for (int k=0; k < 2; k++) {
++ dbg("proxy_in_use psocket: " + k);
++
++ if (proxySock != null) {
++ try {
++ proxySock.close();
++ } catch (Exception e) {
++ dbg("proxy socket is grumpy.");
++ }
++ }
++
++ proxySock = psocket(proxyHost, proxyPort);
++ if (proxySock == null) {
++ dbg("1-a sadly, returning a null socket");
++ return;
++ }
++
++ String req1 = "CONNECT " + hp + " HTTP/1.1\r\n"
++ + "Host: " + hp + "\r\n";
++
++ dbg("requesting via proxy: " + req1);
++
++ if (proxy_auth) {
++ if (proxy_auth_string == null) {
++ ProxyPasswdDialog pp = new ProxyPasswdDialog(proxyHost, proxyPort, proxy_auth_basic_realm);
++ pp.queryUser();
++ proxy_auth_string = pp.getAuth();
++ }
++ //dbg("auth1: " + proxy_auth_string);
++
++ String auth2 = Base64Coder.encodeString(proxy_auth_string);
++ //dbg("auth2: " + auth2);
++
++ req1 += "Proxy-Authorization: Basic " + auth2 + "\r\n";
++ //dbg("req1: " + req1);
++
++ dbg("added Proxy-Authorization: Basic ... to request");
++ }
++ req1 += "\r\n";
++
++ try {
++ proxy_os.write(req1.getBytes());
++ String reply = readline(proxy_is);
++
++ dbg("proxy replied: " + reply.trim());
++
++ if (reply.indexOf("HTTP/1.") == 0 && reply.indexOf(" 407 ") > 0) {
++ proxy_auth = true;
++ proxySock.close();
++ } else if (reply.indexOf("HTTP/1.") < 0 && reply.indexOf(" 200") < 0) {
++ proxySock.close();
++ proxySock = psocket(proxyHost, proxyPort);
++ if (proxySock == null) {
++ dbg("2-a sadly, returning a null socket");
++ return;
++ }
++ }
++ } catch(Exception e) {
++ dbg("some proxy socket problem: " + e.getMessage());
++ }
++
++ /* read the rest of the HTTP headers */
++ while (true) {
++ String line = readline(proxy_is);
++ dbg("proxy line: " + line.trim());
++ if (proxy_auth) {
++ String uc = line.toLowerCase();
++ if (uc.indexOf("proxy-authenticate:") == 0) {
++ if (uc.indexOf(" basic ") >= 0) {
++ int idx = uc.indexOf(" realm");
++ if (idx >= 0) {
++ proxy_auth_basic_realm = uc.substring(idx+1);
++ }
++ }
++ }
++ }
++ if (line.equals("\r\n") || line.equals("\n")) {
++ break;
++ }
++ }
++ if (!proxy_auth || proxy_auth_basic_realm.equals("")) {
++ /* we only try once for the non-password case: */
++ break;
++ }
++ }
++ }
++
++ public SSLSocket proxy_socket(SSLSocketFactory factory) {
++ Properties props = null;
++ String proxyHost = null;
++ int proxyPort = 0;
++ String proxyHost_nossl = null;
++ int proxyPort_nossl = 0;
++ String str;
++
++ /* see if we can guess the proxy info from Properties: */
++ try {
++ props = System.getProperties();
++ } catch (Exception e) {
++ /* sandboxed applet might not be able to read it. */
++ dbg("props failed: " + e.getMessage());
++ }
++ if (viewer.proxyHost != null) {
++ dbg("Using supplied proxy " + viewer.proxyHost + " " + viewer.proxyPort + " applet parameters.");
++ proxyHost = viewer.proxyHost;
++ if (viewer.proxyPort != null) {
++ proxyPort = gint(viewer.proxyPort);
++ } else {
++ proxyPort = 8080;
++ }
++
++ } else if (props != null) {
++ dbg("\n---------------\nAll props:");
++ props.list(System.out);
++ dbg("\n---------------\n\n");
++
++ /* scrape throught properties looking for proxy info: */
++
++ for (Enumeration e = props.propertyNames(); e.hasMoreElements(); ) {
++ String s = (String) e.nextElement();
++ String v = System.getProperty(s);
++ String s2 = s.toLowerCase();
++ String v2 = v.toLowerCase();
++
++ if (s2.indexOf("proxy.https.host") >= 0) {
++ proxyHost = v2;
++ continue;
++ }
++ if (s2.indexOf("proxy.https.port") >= 0) {
++ proxyPort = gint(v2);
++ continue;
++ }
++ if (s2.indexOf("proxy.http.host") >= 0) {
++ proxyHost_nossl = v2;
++ continue;
++ }
++ if (s2.indexOf("proxy.http.port") >= 0) {
++ proxyPort_nossl = gint(v2);
++ continue;
++ }
++ }
++
++ for (Enumeration e = props.propertyNames(); e.hasMoreElements(); ) {
++ String s = (String) e.nextElement();
++ String v = System.getProperty(s);
++ String s2 = s.toLowerCase();
++ String v2 = v.toLowerCase();
++
++ if (proxyHost != null && proxyPort > 0) {
++ break;
++ }
++
++ // look for something like: javaplugin.proxy.config.list = http=10.0.2.1:8082
++ if (s2.indexOf("proxy") < 0 && v2.indexOf("proxy") < 0) {
++ continue;
++ }
++ if (v2.indexOf("http") < 0) {
++ continue;
++ }
++
++ String[] pieces = v.split("[,;]");
++ for (int i = 0; i < pieces.length; i++) {
++ String p = pieces[i];
++ int j = p.indexOf("https");
++ if (j < 0) {
++ j = p.indexOf("http");
++ if (j < 0) {
++ continue;
++ }
++ }
++ j = p.indexOf("=", j);
++ if (j < 0) {
++ continue;
++ }
++ p = p.substring(j+1);
++ String [] hp = p.split(":");
++ if (hp.length != 2) {
++ continue;
++ }
++ if (hp[0].length() > 1 && hp[1].length() > 1) {
++
++ proxyPort = gint(hp[1]);
++ if (proxyPort < 0) {
++ continue;
++ }
++ proxyHost = new String(hp[0]);
++ break;
++ }
++ }
++ }
++ }
++ if (proxyHost != null) {
++ if (proxyHost_nossl != null && proxyPort_nossl > 0) {
++ dbg("Using http proxy info instead of https.");
++ proxyHost = proxyHost_nossl;
++ proxyPort = proxyPort_nossl;
++ }
++ }
++
++ if (proxy_in_use) {
++ if (proxy_dialog_host != null && proxy_dialog_port > 0) {
++ proxyHost = proxy_dialog_host;
++ proxyPort = proxy_dialog_port;
++ }
++ if (proxyHost != null) {
++ dbg("Lucky us! we figured out the Proxy parameters: " + proxyHost + " " + proxyPort);
++ } else {
++ /* ask user to help us: */
++ ProxyDialog pd = new ProxyDialog(proxyHost, proxyPort);
++ pd.queryUser();
++ proxyHost = pd.getHost();
++ proxyPort = pd.getPort();
++ proxy_dialog_host = new String(proxyHost);
++ proxy_dialog_port = proxyPort;
++ dbg("User said host: " + pd.getHost() + " port: " + pd.getPort());
++ }
++
++ proxy_helper(proxyHost, proxyPort);
++ if (proxySock == null) {
++ return null;
++ }
++ } else if (viewer.CONNECT != null) {
++ dbg("viewer.CONNECT psocket:");
++ proxySock = psocket(host, port);
++ if (proxySock == null) {
++ dbg("1-b sadly, returning a null socket");
++ return null;
++ }
++ }
++
++ if (viewer.CONNECT != null) {
++ String hp = viewer.CONNECT;
++ String req2 = "CONNECT " + hp + " HTTP/1.1\r\n"
++ + "Host: " + hp + "\r\n\r\n";
++
++ dbg("requesting2: " + req2);
++
++ try {
++ proxy_os.write(req2.getBytes());
++ String reply = readline(proxy_is);
++
++ dbg("proxy replied2: " + reply.trim());
++
++ if (reply.indexOf("HTTP/1.") < 0 && reply.indexOf(" 200") < 0) {
++ proxySock.close();
++ proxySock = psocket(proxyHost, proxyPort);
++ if (proxySock == null) {
++ dbg("2-b sadly, returning a null socket");
++ return null;
++ }
++ }
++ } catch(Exception e) {
++ dbg("proxy socket problem-2: " + e.getMessage());
++ }
++
++ while (true) {
++ String line = readline(proxy_is);
++ dbg("proxy line2: " + line.trim());
++ if (line.equals("\r\n") || line.equals("\n")) {
++ break;
++ }
++ }
++ }
++
++ Socket sslsock = null;
++ try {
++ sslsock = factory.createSocket(proxySock, host, port, true);
++ } catch(Exception e) {
++ dbg("sslsock prob: " + e.getMessage());
++ dbg("3 sadly, returning a null socket");
++ }
++
++ return (SSLSocket) sslsock;
++ }
++
++ Socket psocket(String h, int p) {
++ Socket psock = null;
++ try {
++ psock = new Socket(h, p);
++ proxy_is = new DataInputStream(new BufferedInputStream(
++ psock.getInputStream(), 16384));
++ proxy_os = psock.getOutputStream();
++ } catch(Exception e) {
++ dbg("psocket prob: " + e.getMessage());
++ return null;
++ }
++
++ return psock;
++ }
++
++ String readline(DataInputStream i) {
++ byte[] ba = new byte[1];
++ String s = new String("");
++ ba[0] = 0;
++ try {
++ while (ba[0] != 0xa) {
++ ba[0] = (byte) i.readUnsignedByte();
++ s += new String(ba);
++ }
++ } catch (Exception e) {
++ ;
++ }
++ return s;
++ }
++}
++
++class TrustDialog implements ActionListener {
++ String msg, host, text;
++ int port;
++ java.security.cert.Certificate[] trustallCerts = null;
++ boolean viewing_cert = false;
++ boolean trust_this_session = false;
++
++ /*
++ * this is the gui to show the user the cert and info and ask
++ * them if they want to continue using this cert.
++ */
++
++ Button ok, cancel, viewcert;
++ TextArea textarea;
++ Checkbox accept, deny;
++ Dialog dialog;
++
++ String s1 = "Accept this certificate temporarily for this session";
++ String s2 = "Do not accept this certificate and do not connect to"
++ + " this VNC server";
++ String ln = "\n---------------------------------------------------\n\n";
++
++ TrustDialog (String h, int p, java.security.cert.Certificate[] s) {
++ host = h;
++ port = p;
++ trustallCerts = s;
++
++ msg = "VNC Server " + host + ":" + port + " Not Verified";
++ }
++
++ public boolean queryUser(String reason) {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame(msg);
++
++ dialog = new Dialog(frame, true);
++
++ String infostr = "";
++ if (trustallCerts.length == 1) {
++ CertInfo ci = new CertInfo(trustallCerts[0]);
++ infostr = ci.get_certinfo("all");
++ }
++ if (reason != null) {
++ reason += "\n\n";
++ }
++
++ text = "\n"
+++ "Unable to verify the identity of\n"
+++ "\n"
+++ " " + host + ":" + port + "\n"
+++ "\n"
+++ infostr
+++ "\n"
+++ "as a trusted VNC server.\n"
+++ "\n"
+++ reason
+++ "In General not being able to verify the VNC Server and/or your seeing this Dialog\n"
+++ "is due to one of the following:\n"
+++ "\n"
+++ " - Your requesting to View the Certificate before accepting.\n"
+++ "\n"
+++ " - The VNC server is using a Self-Signed Certificate or a Certificate\n"
+++ " Authority not recognized by your Web Browser or Java Plugin runtime.\n"
+++ "\n"
+++ " - The use of an Apache SSL portal scheme employing CONNECT proxying AND\n"
+++ " the Apache Web server has a certificate *different* from the VNC server's.\n"
+++ "\n"
+++ " - No previously accepted Certificate (via Web Broswer/Java Plugin) could be\n"
+++ " obtained by this applet to compare the VNC Server Certificate against.\n"
+++ "\n"
+++ " - The VNC Server's Certificate does not match the one specified in the\n"
+++ " supplied 'serverCert' Java Applet Parameter.\n"
+++ "\n"
+++ " - A Man-In-The-Middle attack impersonating as the VNC server that you wish\n"
+++ " to connect to. (Wouldn't that be exciting!!)\n"
+++ "\n"
+++ "By safely copying the VNC server's Certificate (or using a common Certificate\n"
+++ "Authority certificate) you can configure your Web Browser and Java Plugin to\n"
+++ "automatically authenticate this VNC Server.\n"
+++ "\n"
+++ "If you do so, then you will only have to click \"Yes\" when this VNC Viewer\n"
+++ "applet asks you whether to trust your Browser/Java Plugin's acceptance of the\n"
+++ "certificate (except for the Apache portal case above where they don't match.)\n"
+++ "\n"
+++ "You can also set the applet parameter 'trustUrlVncCert=yes' to automatically\n"
+++ "accept certificates already accepted/trusted by your Web Browser/Java Plugin,\n"
+++ "and thereby see no dialog from this VNC Viewer applet.\n"
++;
++
++ /* the accept / do-not-accept radio buttons: */
++ CheckboxGroup checkbox = new CheckboxGroup();
++ accept = new Checkbox(s1, true, checkbox);
++ deny = new Checkbox(s2, false, checkbox);
++
++ /* put the checkboxes in a panel: */
++ Panel check = new Panel();
++ check.setLayout(new GridLayout(2, 1));
++
++ check.add(accept);
++ check.add(deny);
++
++ /* make the 3 buttons: */
++ ok = new Button("OK");
++ cancel = new Button("Cancel");
++ viewcert = new Button("View Certificate");
++
++ ok.addActionListener(this);
++ cancel.addActionListener(this);
++ viewcert.addActionListener(this);
++
++ /* put the buttons in their own panel: */
++ Panel buttonrow = new Panel();
++ buttonrow.setLayout(new FlowLayout(FlowLayout.LEFT));
++ buttonrow.add(viewcert);
++ buttonrow.add(ok);
++ buttonrow.add(cancel);
++
++ /* label at the top: */
++ Label label = new Label(msg, Label.CENTER);
++ label.setFont(new Font("Helvetica", Font.BOLD, 16));
++
++ /* textarea in the middle */
++ textarea = new TextArea(text, 38, 64,
++ TextArea.SCROLLBARS_VERTICAL_ONLY);
++ textarea.setEditable(false);
++
++ /* put the two panels in their own panel at bottom: */
++ Panel bot = new Panel();
++ bot.setLayout(new GridLayout(2, 1));
++ bot.add(check);
++ bot.add(buttonrow);
++
++ /* now arrange things inside the dialog: */
++ dialog.setLayout(new BorderLayout());
++
++ dialog.add("North", label);
++ dialog.add("South", bot);
++ dialog.add("Center", textarea);
++
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++
++ return trust_this_session;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++
++ if (evt.getSource() == viewcert) {
++ /* View Certificate button clicked */
++ if (viewing_cert) {
++ /* show the original info text: */
++ textarea.setText(text);
++ viewcert.setLabel("View Certificate");
++ viewing_cert = false;
++ } else {
++ int i;
++ /* show all (likely just one) certs: */
++ textarea.setText("");
++ for (i=0; i < trustallCerts.length; i++) {
++ int j = i + 1;
++ textarea.append("Certificate[" +
++ j + "]\n\n");
++ textarea.append(
++ trustallCerts[i].toString());
++ textarea.append(ln);
++ }
++ viewcert.setLabel("View Info");
++ viewing_cert = true;
++
++ textarea.setCaretPosition(0);
++ }
++
++ } else if (evt.getSource() == ok) {
++ /* OK button clicked */
++ if (accept.getState()) {
++ trust_this_session = true;
++ } else {
++ trust_this_session = false;
++ }
++ //dialog.dispose();
++ dialog.hide();
++
++ } else if (evt.getSource() == cancel) {
++ /* Cancel button clicked */
++ trust_this_session = false;
++
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++
++ String get_certinfo() {
++ String all = "";
++ String fields[] = {"CN", "OU", "O", "L", "C"};
++ int i;
++ if (trustallCerts.length < 1) {
++ all = "";
++ return all;
++ }
++ String cert = trustallCerts[0].toString();
++
++ /*
++ * For now we simply scrape the cert string, there must
++ * be an API for this... perhaps optionValue?
++ */
++
++ for (i=0; i < fields.length; i++) {
++ int f, t, t1, t2;
++ String sub, mat = fields[i] + "=";
++
++ f = cert.indexOf(mat, 0);
++ if (f > 0) {
++ t1 = cert.indexOf(", ", f);
++ t2 = cert.indexOf("\n", f);
++ if (t1 < 0 && t2 < 0) {
++ continue;
++ } else if (t1 < 0) {
++ t = t2;
++ } else if (t2 < 0) {
++ t = t1;
++ } else if (t1 < t2) {
++ t = t1;
++ } else {
++ t = t2;
++ }
++ if (t > f) {
++ sub = cert.substring(f, t);
++ all = all + " " + sub + "\n";
++ }
++ }
++ }
++ return all;
++ }
++}
++
++class ProxyDialog implements ActionListener {
++ String guessedHost = null;
++ String guessedPort = null;
++ /*
++ * this is the gui to show the user the cert and info and ask
++ * them if they want to continue using this cert.
++ */
++
++ Button ok;
++ Dialog dialog;
++ TextField entry;
++ String reply = "";
++
++ ProxyDialog (String h, int p) {
++ guessedHost = h;
++ try {
++ guessedPort = Integer.toString(p);
++ } catch (Exception e) {
++ guessedPort = "8080";
++ }
++ }
++
++ public void queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Need Proxy host:port");
++
++ dialog = new Dialog(frame, true);
++
++
++ Label label = new Label("Please Enter your https Proxy info as host:port", Label.CENTER);
++ //label.setFont(new Font("Helvetica", Font.BOLD, 16));
++ entry = new TextField(30);
++ ok = new Button("OK");
++ ok.addActionListener(this);
++
++ String guess = "";
++ if (guessedHost != null) {
++ guess = guessedHost + ":" + guessedPort;
++ }
++ entry.setText(guess);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", label);
++ dialog.add("Center", entry);
++ dialog.add("South", ok);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++ return;
++ }
++
++ public String getHost() {
++ int i = reply.indexOf(":");
++ if (i < 0) {
++ return "unknown";
++ }
++ String h = reply.substring(0, i);
++ return h;
++ }
++
++ public int getPort() {
++ int i = reply.indexOf(":");
++ int p = 8080;
++ if (i < 0) {
++ return p;
++ }
++ i++;
++ String ps = reply.substring(i);
++ try {
++ Integer I = new Integer(ps);
++ p = I.intValue();
++ } catch (Exception e) {
++ ;
++ }
++ return p;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == ok) {
++ reply = entry.getText();
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++}
++
++class ProxyPasswdDialog implements ActionListener {
++ String guessedHost = null;
++ String guessedPort = null;
++ String guessedUser = null;
++ String guessedPasswd = null;
++ String realm = null;
++ /*
++ * this is the gui to show the user the cert and info and ask
++ * them if they want to continue using this cert.
++ */
++
++ Button ok;
++ Dialog dialog;
++ TextField entry1;
++ TextField entry2;
++ String reply1 = "";
++ String reply2 = "";
++
++ ProxyPasswdDialog (String h, int p, String realm) {
++ guessedHost = h;
++ try {
++ guessedPort = Integer.toString(p);
++ } catch (Exception e) {
++ guessedPort = "8080";
++ }
++ this.realm = realm;
++ }
++
++ public void queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Proxy Requires Username and Password");
++
++ dialog = new Dialog(frame, true);
++
++ //Label label = new Label("Please Enter your Web Proxy Username in the top Entry and Password in the bottom Entry", Label.CENTER);
++ TextArea label = new TextArea("Please Enter your Web Proxy\nUsername in the Top Entry and\nPassword in the Bottom Entry,\nand then press OK.", 4, 20, TextArea.SCROLLBARS_NONE);
++ entry1 = new TextField(30);
++ entry2 = new TextField(30);
++ entry2.setEchoChar('*');
++ ok = new Button("OK");
++ ok.addActionListener(this);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", label);
++ dialog.add("Center", entry1);
++ dialog.add("South", entry2);
++ dialog.add("East", ok);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++ return;
++ }
++
++ public String getAuth() {
++ return reply1 + ":" + reply2;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == ok) {
++ reply1 = entry1.getText();
++ reply2 = entry2.getText();
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++}
++
++class ClientCertDialog implements ActionListener {
++
++ Button ok;
++ Dialog dialog;
++ TextField entry;
++ String reply = "";
++
++ ClientCertDialog() {
++ ;
++ }
++
++ public String queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Enter SSL Client Cert+Key String");
++
++ dialog = new Dialog(frame, true);
++
++
++ Label label = new Label("Please Enter the SSL Client Cert+Key String 308204c0...,...522d2d0a", Label.CENTER);
++ entry = new TextField(30);
++ ok = new Button("OK");
++ ok.addActionListener(this);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", label);
++ dialog.add("Center", entry);
++ dialog.add("South", ok);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til OK or Cancel pressed. */
++ return reply;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == ok) {
++ reply = entry.getText();
++ //dialog.dispose();
++ dialog.hide();
++ }
++ }
++}
++
++class BrowserCertsDialog implements ActionListener {
++ Button yes, no;
++ Dialog dialog;
++ String vncServer;
++ String hostport;
++ public boolean showCertDialog = true;
++
++ BrowserCertsDialog(String serv, String hp) {
++ vncServer = serv;
++ hostport = hp;
++ }
++
++ public void queryUser() {
++
++ /* create and display the dialog for unverified cert. */
++
++ Frame frame = new Frame("Use Browser/JVM Certs?");
++
++ dialog = new Dialog(frame, true);
++
++ String m = "";
++m += "\n";
++m += "This VNC Viewer applet does not have its own keystore to track\n";
++m += "SSL certificates, and so cannot authenticate the certificate\n";
++m += "of the VNC Server:\n";
++m += "\n";
++m += " " + hostport + "\n\n " + vncServer + "\n";
++m += "\n";
++m += "on its own.\n";
++m += "\n";
++m += "However, it has noticed that your Web Browser and/or Java VM Plugin\n";
++m += "has previously accepted the same certificate. You may have set\n";
++m += "this up permanently or just for this session, or the server\n";
++m += "certificate was signed by a CA cert that your Web Browser or\n";
++m += "Java VM Plugin has.\n";
++m += "\n";
++m += "If the VNC Server connection times out while you are reading this\n";
++m += "dialog, then restart the connection and try again.\n";
++m += "\n";
++m += "Should this VNC Viewer applet now connect to the above VNC server?\n";
++m += "\n";
++
++ TextArea textarea = new TextArea(m, 22, 64,
++ TextArea.SCROLLBARS_VERTICAL_ONLY);
++ textarea.setEditable(false);
++ yes = new Button("Yes");
++ yes.addActionListener(this);
++ no = new Button("No, Let Me See the Certificate.");
++ no.addActionListener(this);
++
++ dialog.setLayout(new BorderLayout());
++ dialog.add("North", textarea);
++ dialog.add("Center", yes);
++ dialog.add("South", no);
++ dialog.pack();
++ dialog.resize(dialog.preferredSize());
++
++ dialog.show(); /* block here til Yes or No pressed. */
++ System.out.println("done show()");
++ return;
++ }
++
++ public synchronized void actionPerformed(ActionEvent evt) {
++ System.out.println(evt.getActionCommand());
++ if (evt.getSource() == yes) {
++ showCertDialog = false;
++ //dialog.dispose();
++ dialog.hide();
++ } else if (evt.getSource() == no) {
++ showCertDialog = true;
++ //dialog.dispose();
++ dialog.hide();
++ }
++ System.out.println("done actionPerformed()");
++ }
++}
++
++class CertInfo {
++ String fields[] = {"CN", "OU", "O", "L", "C"};
++ java.security.cert.Certificate cert;
++ String certString = "";
++
++ CertInfo(java.security.cert.Certificate c) {
++ cert = c;
++ certString = cert.toString();
++ }
++
++ String get_certinfo(String which) {
++ int i;
++ String cs = new String(certString);
++ String all = "";
++
++ /*
++ * For now we simply scrape the cert string, there must
++ * be an API for this... perhaps optionValue?
++ */
++ for (i=0; i < fields.length; i++) {
++ int f, t, t1, t2;
++ String sub, mat = fields[i] + "=";
++
++ f = cs.indexOf(mat, 0);
++ if (f > 0) {
++ t1 = cs.indexOf(", ", f);
++ t2 = cs.indexOf("\n", f);
++ if (t1 < 0 && t2 < 0) {
++ continue;
++ } else if (t1 < 0) {
++ t = t2;
++ } else if (t2 < 0) {
++ t = t1;
++ } else if (t1 < t2) {
++ t = t1;
++ } else {
++ t = t2;
++ }
++ if (t > f) {
++ sub = cs.substring(f, t);
++ all = all + " " + sub + "\n";
++ if (which.equals(fields[i])) {
++ return sub;
++ }
++ }
++ }
++ }
++ if (which.equals("all")) {
++ return all;
++ } else {
++ return "";
++ }
++ }
++}
++
++class Base64Coder {
++
++ // Mapping table from 6-bit nibbles to Base64 characters.
++ private static char[] map1 = new char[64];
++ static {
++ int i=0;
++ for (char c='A'; c<='Z'; c++) map1[i++] = c;
++ for (char c='a'; c<='z'; c++) map1[i++] = c;
++ for (char c='0'; c<='9'; c++) map1[i++] = c;
++ map1[i++] = '+'; map1[i++] = '/'; }
++
++ // Mapping table from Base64 characters to 6-bit nibbles.
++ private static byte[] map2 = new byte[128];
++ static {
++ for (int i=0; i<map2.length; i++) map2[i] = -1;
++ for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }
++
++ /**
++ * Encodes a string into Base64 format.
++ * No blanks or line breaks are inserted.
++ * @param s a String to be encoded.
++ * @return A String with the Base64 encoded data.
++ */
++ public static String encodeString (String s) {
++ return new String(encode(s.getBytes())); }
++
++ /**
++ * Encodes a byte array into Base64 format.
++ * No blanks or line breaks are inserted.
++ * @param in an array containing the data bytes to be encoded.
++ * @return A character array with the Base64 encoded data.
++ */
++ public static char[] encode (byte[] in) {
++ return encode(in,in.length); }
++
++ /**
++ * Encodes a byte array into Base64 format.
++ * No blanks or line breaks are inserted.
++ * @param in an array containing the data bytes to be encoded.
++ * @param iLen number of bytes to process in <code>in</code>.
++ * @return A character array with the Base64 encoded data.
++ */
++ public static char[] encode (byte[] in, int iLen) {
++ int oDataLen = (iLen*4+2)/3; // output length without padding
++ int oLen = ((iLen+2)/3)*4; // output length including padding
++ char[] out = new char[oLen];
++ int ip = 0;
++ int op = 0;
++ while (ip < iLen) {
++ int i0 = in[ip++] & 0xff;
++ int i1 = ip < iLen ? in[ip++] & 0xff : 0;
++ int i2 = ip < iLen ? in[ip++] & 0xff : 0;
++ int o0 = i0 >>> 2;
++ int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
++ int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
++ int o3 = i2 & 0x3F;
++ out[op++] = map1[o0];
++ out[op++] = map1[o1];
++ out[op] = op < oDataLen ? map1[o2] : '='; op++;
++ out[op] = op < oDataLen ? map1[o3] : '='; op++; }
++ return out; }
++
++ /**
++ * Decodes a string from Base64 format.
++ * @param s a Base64 String to be decoded.
++ * @return A String containing the decoded data.
++ * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
++ */
++ public static String decodeString (String s) {
++ return new String(decode(s)); }
++
++ /**
++ * Decodes a byte array from Base64 format.
++ * @param s a Base64 String to be decoded.
++ * @return An array containing the decoded data bytes.
++ * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
++ */
++ public static byte[] decode (String s) {
++ return decode(s.toCharArray()); }
++
++ /**
++ * Decodes a byte array from Base64 format.
++ * No blanks or line breaks are allowed within the Base64 encoded data.
++ * @param in a character array containing the Base64 encoded data.
++ * @return An array containing the decoded data bytes.
++ * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
++ */
++ public static byte[] decode (char[] in) {
++ int iLen = in.length;
++ if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
++ while (iLen > 0 && in[iLen-1] == '=') iLen--;
++ int oLen = (iLen*3) / 4;
++ byte[] out = new byte[oLen];
++ int ip = 0;
++ int op = 0;
++ while (ip < iLen) {
++ int i0 = in[ip++];
++ int i1 = in[ip++];
++ int i2 = ip < iLen ? in[ip++] : 'A';
++ int i3 = ip < iLen ? in[ip++] : 'A';
++ if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
++ throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
++ int b0 = map2[i0];
++ int b1 = map2[i1];
++ int b2 = map2[i2];
++ int b3 = map2[i3];
++ if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
++ throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
++ int o0 = ( b0 <<2) | (b1>>>4);
++ int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
++ int o2 = ((b2 & 3)<<6) | b3;
++ out[op++] = (byte)o0;
++ if (op<oLen) out[op++] = (byte)o1;
++ if (op<oLen) out[op++] = (byte)o2; }
++ return out; }
++
++ // Dummy constructor.
++ private Base64Coder() {}
++
++}
+diff -Naur JavaViewer.orig/VncCanvas.java JavaViewer/VncCanvas.java
+--- JavaViewer.orig/VncCanvas.java 2005-11-21 18:50:18.000000000 -0500
++++ JavaViewer/VncCanvas.java 2010-11-30 22:57:50.000000000 -0500
+@@ -27,6 +27,13 @@
+ import java.lang.*;
+ import java.util.zip.*;
+
++// begin runge/x11vnc
++import java.util.Collections;
++// end runge/x11vnc
++
++// begin runge/x11vnc
++// all the MouseWheel stuff below.
++// end runge/x11vnc
+
+ //
+ // VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
+@@ -34,7 +41,7 @@
+
+ class VncCanvas
+ extends Canvas
+- implements KeyListener, MouseListener, MouseMotionListener {
++ implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener {
+
+ VncViewer viewer;
+ RfbProto rfb;
+@@ -85,6 +92,22 @@
+
+ cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);
+
++// begin runge/x11vnc
++// kludge to not show any Java cursor in the canvas since we are
++// showing the soft cursor (should be a user setting...)
++Cursor dot = Toolkit.getDefaultToolkit().createCustomCursor(
++ Toolkit.getDefaultToolkit().createImage(new byte[4]), new Point(0,0),
++ "dot");
++this.setCursor(dot);
++
++// while we are at it... get rid of the keyboard traversals that
++// make it so we can't type a Tab character:
++this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
++ Collections.EMPTY_SET);
++this.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
++ Collections.EMPTY_SET);
++// end runge/x11vnc
++
+ colors = new Color[256];
+ // sf@2005 - Now Default
+ for (int i = 0; i < 256; i++)
+@@ -186,6 +209,7 @@
+ inputEnabled = true;
+ addMouseListener(this);
+ addMouseMotionListener(this);
++ addMouseWheelListener(this);
+ if (viewer.showControls) {
+ viewer.buttonPanel.enableRemoteAccessControls(true);
+ }
+@@ -193,6 +217,7 @@
+ inputEnabled = false;
+ removeMouseListener(this);
+ removeMouseMotionListener(this);
++ removeMouseWheelListener(this);
+ if (viewer.showControls) {
+ viewer.buttonPanel.enableRemoteAccessControls(false);
+ }
+@@ -202,6 +227,9 @@
+
+ public void setPixelFormat() throws IOException {
+ // sf@2005 - Adding more color modes
++ if (viewer.graftFtp) {
++ return;
++ }
+ if (viewer.options.eightBitColors > 0)
+ {
+ viewer.options.oldEightBitColors = viewer.options.eightBitColors;
+@@ -237,6 +265,9 @@
+ }
+ else
+ {
++// begin runge/x11vnc
++ viewer.options.oldEightBitColors = viewer.options.eightBitColors;
++// end runge/x11vnc
+ rfb.writeSetPixelFormat(
+ 32,
+ 24,
+@@ -376,12 +407,14 @@
+ // Start/stop session recording if necessary.
+ viewer.checkRecordingStatus();
+
+- rfb.writeFramebufferUpdateRequest(
+- 0,
+- 0,
+- rfb.framebufferWidth,
+- rfb.framebufferHeight,
+- false);
++ if (!viewer.graftFtp) {
++ rfb.writeFramebufferUpdateRequest(
++ 0,
++ 0,
++ rfb.framebufferWidth,
++ rfb.framebufferHeight,
++ false);
++ }
+
+ //
+ // main dispatch loop
+@@ -390,6 +423,9 @@
+ while (true) {
+ // Read message type from the server.
+ int msgType = rfb.readServerMessageType();
++ if (viewer.ftpOnly && msgType != RfbProto.rfbFileTransfer) {
++ System.out.println("msgType:" + msgType);
++ }
+
+ // Process the message depending on its type.
+ switch (msgType) {
+@@ -1332,6 +1368,9 @@
+ public void mouseDragged(MouseEvent evt) {
+ processLocalMouseEvent(evt, true);
+ }
++ public void mouseWheelMoved(MouseWheelEvent evt) {
++ processLocalMouseWheelEvent(evt);
++ }
+
+ public void processLocalKeyEvent(KeyEvent evt) {
+ if (viewer.rfb != null && rfb.inNormalProtocol) {
+@@ -1367,6 +1406,19 @@
+ evt.consume();
+ }
+
++ public void processLocalMouseWheelEvent(MouseWheelEvent evt) {
++ if (viewer.rfb != null && rfb.inNormalProtocol) {
++ synchronized(rfb) {
++ try {
++ rfb.writeWheelEvent(evt);
++ } catch (Exception e) {
++ e.printStackTrace();
++ }
++ rfb.notify();
++ }
++ }
++ }
++
+ public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
+ if (viewer.rfb != null && rfb.inNormalProtocol) {
+ if (moved) {
+@@ -1532,9 +1584,14 @@
+ else
+ {
+ result =
+- 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF)
+- << 16 | (pixBuf[i * 4 + 2] & 0xFF)
+- << 8 | (pixBuf[i * 4 + 3] & 0xFF);
++// begin runge/x11vnc
++// 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF)
++// << 16 | (pixBuf[i * 4 + 2] & 0xFF)
++// << 8 | (pixBuf[i * 4 + 3] & 0xFF);
++ 0xFF000000 | (pixBuf[i * 4 + 2] & 0xFF)
++ << 16 | (pixBuf[i * 4 + 1] & 0xFF)
++ << 8 | (pixBuf[i * 4 + 0] & 0xFF);
++// end runge/x11vnc
+ }
+ } else {
+ result = 0; // Transparent pixel
+@@ -1565,9 +1622,14 @@
+ else
+ {
+ result =
+- 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF)
+- << 16 | (pixBuf[i * 4 + 2] & 0xFF)
+- << 8 | (pixBuf[i * 4 + 3] & 0xFF);
++// begin runge/x11vnc
++// 0xFF000000 | (pixBuf[i * 4 + 1] & 0xFF)
++// << 16 | (pixBuf[i * 4 + 2] & 0xFF)
++// << 8 | (pixBuf[i * 4 + 3] & 0xFF);
++ 0xFF000000 | (pixBuf[i * 4 + 2] & 0xFF)
++ << 16 | (pixBuf[i * 4 + 1] & 0xFF)
++ << 8 | (pixBuf[i * 4 + 0] & 0xFF);
++// end runge/x11vnc
+ }
+ } else {
+ result = 0; // Transparent pixel
+diff -Naur JavaViewer.orig/VncViewer.java JavaViewer/VncViewer.java
+--- JavaViewer.orig/VncViewer.java 2006-05-24 15:14:40.000000000 -0400
++++ JavaViewer/VncViewer.java 2010-03-27 18:00:28.000000000 -0400
+@@ -41,6 +41,7 @@
+ import java.io.*;
+ import java.net.*;
+ import javax.swing.*;
++import java.util.Date;
+
+ public class VncViewer extends java.applet.Applet
+ implements java.lang.Runnable, WindowListener {
+@@ -80,11 +81,11 @@
+ GridBagLayout gridbag;
+ ButtonPanel buttonPanel;
+ AuthPanel authenticator;
+- VncCanvas vc;
++ VncCanvas vc = null;
+ OptionsFrame options;
+ ClipboardFrame clipboard;
+ RecordingFrame rec;
+- FTPFrame ftp; // KMC: FTP Frame declaration
++ FTPFrame ftp = null; // KMC: FTP Frame declaration
+
+ // Control session recording.
+ Object recordingSync;
+@@ -96,7 +97,7 @@
+
+ // Variables read from parameter values.
+ String host;
+- int port;
++ int port, vncserverport;
+ String passwordParam;
+ String encPasswordParam;
+ boolean showControls;
+@@ -115,28 +116,75 @@
+ int i;
+ // mslogon support 2 end
+
++// begin runge/x11vnc
++boolean disableSSL;
++boolean GET;
++String CONNECT;
++String urlPrefix;
++String httpsPort;
++String oneTimeKey;
++String serverCert;
++String ftpDropDown;
++String proxyHost;
++String proxyPort;
++boolean forceProxy;
++boolean ignoreProxy;
++boolean trustAllVncCerts;
++boolean trustUrlVncCert;
++boolean debugCerts;
++boolean debugKeyboard;
++boolean mapF5_to_atsign;
++boolean forbid_Ctrl_Alt;
++
++boolean ignoreMSLogonCheck;
++boolean delayAuthPanel;
++boolean ftpOnly;
++boolean graftFtp;
++boolean dsmActive;
++
++boolean gotAuth;
++int authGot;
++// end runge/x11vnc
++
++
+ //
+ // init()
+ //
+
++public void ftp_init() {
++ boolean show = false;
++ if (ftp != null) {
++ show = true;
++ }
++ ftp = null;
++
++ ftp = new FTPFrame(this); // KMC: FTPFrame creation
++
++ if (show) {
++ ftp.doOpen();
++ rfb.readServerDriveList();
++ }
++}
++
+ public void init() {
+
+ readParameters();
+
+ if (inSeparateFrame) {
+- vncFrame = new Frame("Ultr@VNC");
+- if (!inAnApplet) {
+- vncFrame.add("Center", this);
+- }
+- vncContainer = vncFrame;
++ vncFrame = new Frame("Ultr@VNC");
++ if (!inAnApplet) {
++ vncFrame.add("Center", this);
++ }
++ vncContainer = vncFrame;
+ } else {
+- vncContainer = this;
++ vncContainer = this;
+ }
+
+ recordingSync = new Object();
+
+ options = new OptionsFrame(this);
+ clipboard = new ClipboardFrame(this);
++
+ // authenticator = new AuthPanel(false); // mslogon support : go to connectAndAuthenticate()
+ if (RecordingFrame.checkSecurity())
+ rec = new RecordingFrame(this);
+@@ -147,10 +195,11 @@
+ cursorUpdatesDef = null;
+ eightBitColorsDef = null;
+
+- if (inSeparateFrame)
++ if (inSeparateFrame && vncFrame != null)
+ vncFrame.addWindowListener(this);
+
+- ftp = new FTPFrame(this); // KMC: FTPFrame creation
++ ftp_init();
++
+ rfbThread = new Thread(this);
+ rfbThread.start();
+ }
+@@ -186,6 +235,30 @@
+ gbc.weightx = 1.0;
+ gbc.weighty = 1.0;
+
++ if (ftpOnly) {
++ if (showControls) {
++ buttonPanel.enableButtons();
++ }
++ ActionListener taskPerformer = new ActionListener() {
++ public void actionPerformed(ActionEvent evt) {
++ vncFrame.setVisible(false);
++ ftp.setSavedLocations();
++ if (ftp.isVisible()) {
++ ftp.doClose();
++ } else {
++ ftp.doOpen();
++ }
++ rfb.readServerDriveList();
++ }
++ };
++ Timer t = new Timer(300, taskPerformer);
++ t.setRepeats(false);
++ t.start();
++
++ vc.processNormalProtocol();
++ return;
++ }
++
+ // Add ScrollPanel to applet mode
+
+ // Create a panel which itself is resizeable and can hold
+@@ -286,6 +359,24 @@
+
+ void connectAndAuthenticate() throws Exception {
+
++ if (graftFtp) {
++ rfb = new RfbProto(host, port, this);
++ rfb.desktopName = "ftponly";
++ rfb.framebufferWidth = 12;
++ rfb.framebufferHeight = 12;
++ rfb.bitsPerPixel = 32;
++ rfb.depth = 24;
++ rfb.trueColour = true;
++ rfb.redMax = 255;
++ rfb.greenMax = 255;
++ rfb.blueMax = 255;
++ rfb.redShift = 16;
++ rfb.greenShift = 8;
++ rfb.blueShift = 0;
++ rfb.inNormalProtocol = true;
++ return;
++ }
++
+ // If "ENCPASSWORD" parameter is set, decrypt the password into
+ // the passwordParam string.
+
+@@ -336,7 +427,22 @@
+ //
+
+
+- prologueDetectAuthProtocol() ;
++// begin runge/x11vnc
++ gotAuth = false;
++ if (delayAuthPanel) {
++ if (tryAuthenticate(null, null)) {
++ if (inSeparateFrame) {
++ vncFrame.pack();
++ vncFrame.show();
++ }
++ return;
++ }
++ }
++// prologueDetectAuthProtocol() ;
++ if (ignoreMSLogonCheck == false) {
++ prologueDetectAuthProtocol() ;
++ }
++// end runge/x11vnc
+
+ authenticator = new AuthPanel(mslogon);
+
+@@ -371,6 +477,7 @@
+ //mslogon support end
+ }
+
++ int tries = 0;
+ while (true) {
+ // Wait for user entering a password, or a username and a password
+ synchronized(authenticator) {
+@@ -390,6 +497,13 @@
+ break;
+ //mslogon support end
+
++// begin runge/x11vnc
++ gotAuth = false;
++ if (++tries > 2) {
++ throw new Exception("Incorrect password entered " + tries + " times.");
++ }
++// end runge/x11vnc
++
+ // Retry on authentication failure.
+ authenticator.retry();
+ }
+@@ -405,9 +519,11 @@
+
+ void prologueDetectAuthProtocol() throws Exception {
+
+- rfb = new RfbProto(host, port, this);
++ if (!gotAuth) {
++ rfb = new RfbProto(host, port, this);
+
+- rfb.readVersionMsg();
++ rfb.readVersionMsg();
++ }
+
+ System.out.println("RFB server supports protocol version " +
+ rfb.serverMajor + "." + rfb.serverMinor);
+@@ -431,16 +547,36 @@
+
+ boolean tryAuthenticate(String us, String pw) throws Exception {
+
+- rfb = new RfbProto(host, port, this);
++ int authScheme;
+
+- rfb.readVersionMsg();
++ if (!gotAuth) {
++ rfb = new RfbProto(host, port, this);
+
+- System.out.println("RFB server supports protocol version " +
+- rfb.serverMajor + "." + rfb.serverMinor);
++ rfb.readVersionMsg();
+
+- rfb.writeVersionMsg();
++ System.out.println("RFB server supports protocol version: " +
++ rfb.serverMajor + "." + rfb.serverMinor);
+
+- int authScheme = rfb.readAuthScheme();
++ rfb.writeVersionMsg();
++
++ authScheme = rfb.readAuthScheme();
++
++ gotAuth = true;
++ authGot = authScheme;
++ } else {
++ authScheme = authGot;
++ }
++// begin runge/x11vnc
++ if (delayAuthPanel && pw == null) {
++ if (authScheme == RfbProto.NoAuth) {
++ System.out.println("No authentication needed");
++ return true;
++ } else {
++ return false;
++ }
++ }
++System.out.println("as: " + authScheme);
++// end runge/x11vnc
+
+ switch (authScheme) {
+
+@@ -629,6 +765,10 @@
+
+ void doProtocolInitialisation() throws IOException {
+
++ if (graftFtp) {
++ return;
++ }
++
+ rfb.writeClientInit();
+
+ rfb.readServerInit();
+@@ -774,9 +914,28 @@
+ fatalError("HOST parameter not specified");
+ }
+ }
++ Date d = new Date();
++ System.out.println("-\nSSL VNC Java Applet starting. " + d);
+
+- String str = readParameter("PORT", true);
+- port = Integer.parseInt(str);
++ port = 0;
++ String str = readParameter("PORT", false);
++ if (str != null) {
++ port = Integer.parseInt(str);
++ }
++ // When there is a proxy VNCSERVERPORT may be inaccessible (inside firewall).
++ vncserverport = 0;
++ str = readParameter("VNCSERVERPORT", false);
++ if (str != null) {
++ vncserverport = Integer.parseInt(str);
++ }
++ if (port == 0 && vncserverport == 0) {
++ fatalError("Neither PORT nor VNCSERVERPORT parameters specified");
++ }
++ if (port == 0) {
++ // Nevertheless, fall back to vncserverport if we have to.
++ System.out.println("using vncserverport: '" + vncserverport + "' for PORT.");
++ port = vncserverport;
++ }
+
+ if (inAnApplet) {
+ str = readParameter("Open New Window", false);
+@@ -804,6 +963,158 @@
+ deferScreenUpdates = readIntParameter("Defer screen updates", 20);
+ deferCursorUpdates = readIntParameter("Defer cursor updates", 10);
+ deferUpdateRequests = readIntParameter("Defer update requests", 50);
++
++// begin runge/x11vnc
++ // SSL
++ disableSSL = false;
++ str = readParameter("DisableSSL", false);
++ if (str != null && str.equalsIgnoreCase("Yes"))
++ disableSSL = true;
++
++ httpsPort = readParameter("httpsPort", false);
++
++ // Extra GET, CONNECT string:
++ CONNECT = readParameter("CONNECT", false);
++ if (CONNECT != null) {
++ CONNECT = CONNECT.replaceAll(" ", ":");
++ }
++
++ GET = false;
++ str = readParameter("GET", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ GET = true;
++ }
++ if (str != null && str.equalsIgnoreCase("1")) {
++ GET = true;
++ }
++
++ urlPrefix = readParameter("urlPrefix", false);
++ if (urlPrefix != null) {
++ urlPrefix = urlPrefix.replaceAll("%2F", "/");
++ urlPrefix = urlPrefix.replaceAll("%2f", "/");
++ urlPrefix = urlPrefix.replaceAll("_2F_", "/");
++ if (urlPrefix.indexOf("/") != 0) {
++ urlPrefix = "/" + urlPrefix;
++ }
++ } else {
++ urlPrefix = "";
++ }
++ System.out.println("urlPrefix: '" + urlPrefix + "'");
++
++ ftpDropDown = readParameter("ftpDropDown", false);
++ if (ftpDropDown != null) {
++ ftpDropDown = ftpDropDown.replaceAll("%2F", "/");
++ ftpDropDown = ftpDropDown.replaceAll("%2f", "/");
++ ftpDropDown = ftpDropDown.replaceAll("_2F_", "/");
++ ftpDropDown = ftpDropDown.replaceAll("%20", " ");
++ System.out.println("ftpDropDown: '" + ftpDropDown + "'");
++ }
++
++
++ oneTimeKey = readParameter("oneTimeKey", false);
++ if (oneTimeKey != null) {
++ System.out.println("oneTimeKey is set.");
++ }
++
++ serverCert = readParameter("serverCert", false);
++ if (serverCert != null) {
++ System.out.println("serverCert is set.");
++ }
++
++ forceProxy = false;
++ proxyHost = null;
++ proxyPort = null;
++ str = readParameter("forceProxy", false);
++ if (str != null) {
++ if (str.equalsIgnoreCase("Yes")) {
++ forceProxy = true;
++ } else if (str.equalsIgnoreCase("No")) {
++ forceProxy = false;
++ } else {
++ forceProxy = true;
++ String[] pieces = str.split(" ");
++ proxyHost = new String(pieces[0]);
++ if (pieces.length >= 2) {
++ proxyPort = new String(pieces[1]);
++ } else {
++ proxyPort = new String("8080");
++ }
++ }
++ }
++ str = readParameter("proxyHost", false);
++ if (str != null) {
++ proxyHost = new String(str);
++ }
++ str = readParameter("proxyPort", false);
++ if (str != null) {
++ proxyPort = new String(str);
++ }
++ if (proxyHost != null && proxyPort == null) {
++ proxyPort = new String("8080");
++ }
++
++ ignoreProxy = false;
++ str = readParameter("ignoreProxy", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ ignoreProxy = true;
++ }
++
++ trustAllVncCerts = false;
++ str = readParameter("trustAllVncCerts", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ trustAllVncCerts = true;
++ }
++ trustUrlVncCert = false;
++ str = readParameter("trustUrlVncCert", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ trustUrlVncCert = true;
++ }
++ debugCerts = false;
++ str = readParameter("debugCerts", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ debugCerts = true;
++ }
++ debugKeyboard = false;
++ str = readParameter("debugKeyboard", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ debugKeyboard = true;
++ }
++ mapF5_to_atsign = false;
++ str = readParameter("mapF5_to_atsign", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ mapF5_to_atsign = true;
++ }
++ forbid_Ctrl_Alt = false;
++ str = readParameter("forbid_Ctrl_Alt", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ forbid_Ctrl_Alt = true;
++ }
++ ignoreMSLogonCheck = false;
++ str = readParameter("ignoreMSLogonCheck", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ ignoreMSLogonCheck = true;
++ }
++ ftpOnly = false;
++ str = readParameter("ftpOnly", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ ftpOnly = true;
++ }
++ graftFtp = false;
++ str = readParameter("graftFtp", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ graftFtp = true;
++ }
++ dsmActive = false;
++ str = readParameter("dsmActive", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ dsmActive = true;
++ }
++ delayAuthPanel = false;
++ str = readParameter("delayAuthPanel", false);
++ if (str != null && str.equalsIgnoreCase("Yes")) {
++ delayAuthPanel = true;
++ }
++// end runge/x11vnc
+ }
+
+ public String readParameter(String name, boolean required) {