summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorEwan Mellor <ewan.mellor@citrix.com>2011-01-12 11:43:29 +0000
committerEwan Mellor <ewan.mellor@citrix.com>2011-01-12 11:43:29 +0000
commit6cdea8c9b024194d81ae724245b2f595d99606fe (patch)
tree8912c69eb1d8d99114441564bd40d3c7e4e3619e /tools
parent3461b9cf49201eb88ed65473eccf395382c25611 (diff)
parent3d57735caf78fd421da6e660c4d56c635706fa7d (diff)
downloadnova-6cdea8c9b024194d81ae724245b2f595d99606fe.tar.gz
Merged with trunk.
Diffstat (limited to 'tools')
-rw-r--r--tools/ajaxterm/README.txt120
-rw-r--r--tools/ajaxterm/ajaxterm.135
-rw-r--r--tools/ajaxterm/ajaxterm.css64
-rw-r--r--tools/ajaxterm/ajaxterm.html25
-rw-r--r--tools/ajaxterm/ajaxterm.js279
-rwxr-xr-xtools/ajaxterm/ajaxterm.py586
-rwxr-xr-xtools/ajaxterm/configure32
-rw-r--r--tools/ajaxterm/configure.ajaxterm.bin2
-rw-r--r--tools/ajaxterm/configure.initd.debian33
-rw-r--r--tools/ajaxterm/configure.initd.gentoo27
-rw-r--r--tools/ajaxterm/configure.initd.redhat75
-rw-r--r--tools/ajaxterm/configure.makefile20
-rw-r--r--tools/ajaxterm/qweb.py1356
-rw-r--r--tools/ajaxterm/sarissa.js647
-rw-r--r--tools/ajaxterm/sarissa_dhtml.js105
-rwxr-xr-xtools/euca-get-ajax-console164
-rw-r--r--tools/install_venv.py3
17 files changed, 3572 insertions, 1 deletions
diff --git a/tools/ajaxterm/README.txt b/tools/ajaxterm/README.txt
new file mode 100644
index 0000000000..4b0ae99afa
--- /dev/null
+++ b/tools/ajaxterm/README.txt
@@ -0,0 +1,120 @@
+= [http://antony.lesuisse.org/qweb/trac/wiki/AjaxTerm Ajaxterm] =
+
+Ajaxterm is a web based terminal. It was totally inspired and works almost
+exactly like http://anyterm.org/ except it's much easier to install (see
+comparaison with anyterm below).
+
+Ajaxterm written in python (and some AJAX javascript for client side) and depends only on python2.3 or better.[[BR]]
+Ajaxterm is '''very simple to install''' on Linux, MacOS X, FreeBSD, Solaris, cygwin and any Unix that runs python2.3.[[BR]]
+Ajaxterm was written by Antony Lesuisse (email: al AT udev.org), License Public Domain.
+
+Use the [/qweb/forum/viewforum.php?id=2 Forum], if you have any question or remark.
+
+== News ==
+
+ * 2006-10-29: v0.10 allow space in login, cgi launch fix, redhat init
+ * 2006-07-12: v0.9 change uid, daemon fix (Daniel Fischer)
+ * 2006-07-04: v0.8 add login support to ssh (Sven Geggus), change max width to 256
+ * 2006-05-31: v0.7 minor fixes, daemon option
+ * 2006-05-23: v0.6 Applied debian and gentoo patches, renamed to Ajaxterm, default port 8022
+
+== Download and Install ==
+
+ * Release: [/qweb/files/Ajaxterm-0.10.tar.gz Ajaxterm-0.10.tar.gz]
+ * Browse src: [/qweb/trac/browser/trunk/ajaxterm/ ajaxterm/]
+
+To install Ajaxterm issue the following commands:
+{{{
+wget http://antony.lesuisse.org/qweb/files/Ajaxterm-0.10.tar.gz
+tar zxvf Ajaxterm-0.10.tar.gz
+cd Ajaxterm-0.10
+./ajaxterm.py
+}}}
+Then point your browser to this URL : http://localhost:8022/
+
+== Screenshot ==
+
+{{{
+#!html
+<center><img src="/qweb/trac/attachment/wiki/AjaxTerm/scr.png?format=raw" alt="ajaxterm screenshot" style=""/></center>
+}}}
+
+== Documentation and Caveats ==
+
+ * Ajaxterm only support latin1, if you use Ubuntu or any LANG==en_US.UTF-8 distribution don't forget to "unset LANG".
+
+ * If run as root ajaxterm will run /bin/login, otherwise it will run ssh
+ localhost. To use an other command use the -c option.
+
+ * By default Ajaxterm only listen at 127.0.0.1:8022. For remote access, it is
+ strongly recommended to use '''https SSL/TLS''', and that is simple to
+ configure if you use the apache web server using mod_proxy.[[BR]][[BR]]
+ Using ssl will also speed up ajaxterm (probably because of keepalive).[[BR]][[BR]]
+ Here is an configuration example:
+
+{{{
+ Listen 443
+ NameVirtualHost *:443
+
+ <VirtualHost *:443>
+ ServerName localhost
+ SSLEngine On
+ SSLCertificateKeyFile ssl/apache.pem
+ SSLCertificateFile ssl/apache.pem
+
+ ProxyRequests Off
+ <Proxy *>
+ Order deny,allow
+ Allow from all
+ </Proxy>
+ ProxyPass /ajaxterm/ http://localhost:8022/
+ ProxyPassReverse /ajaxterm/ http://localhost:8022/
+ </VirtualHost>
+}}}
+
+ * Using GET HTTP request seems to speed up ajaxterm, just click on GET in the
+ interface, but be warned that your keystrokes might be loggued (by apache or
+ any proxy). I usually enable it after the login.
+
+ * Ajaxterm commandline usage:
+
+{{{
+usage: ajaxterm.py [options]
+
+options:
+ -h, --help show this help message and exit
+ -pPORT, --port=PORT Set the TCP port (default: 8022)
+ -cCMD, --command=CMD set the command (default: /bin/login or ssh localhost)
+ -l, --log log requests to stderr (default: quiet mode)
+ -d, --daemon run as daemon in the background
+ -PPIDFILE, --pidfile=PIDFILE
+ set the pidfile (default: /var/run/ajaxterm.pid)
+ -iINDEX_FILE, --index=INDEX_FILE
+ default index file (default: ajaxterm.html)
+ -uUID, --uid=UID Set the daemon's user id
+}}}
+
+ * Ajaxterm was first written as a demo for qweb (my web framework), but
+ actually doesn't use many features of qweb.
+
+ * Compared to anyterm:
+ * There are no partial updates, ajaxterm updates either all the screen or
+ nothing. That make the code simpler and I also think it's faster. HTTP
+ replies are always gzencoded. When used in 80x25 mode, almost all of
+ them are below the 1500 bytes (size of an ethernet frame) and we just
+ replace the screen with the reply (no javascript string handling).
+ * Ajaxterm polls the server for updates with an exponentially growing
+ timeout when the screen hasn't changed. The timeout is also resetted as
+ soon as a key is pressed. Anyterm blocks on a pending request and use a
+ parallel connection for keypresses. The anyterm approch is better
+ when there aren't any keypress.
+
+ * Ajaxterm files are released in the Public Domain, (except [http://sarissa.sourceforge.net/doc/ sarissa*] which are LGPL).
+
+== TODO ==
+
+ * insert mode ESC [ 4 h
+ * change size x,y from gui (sending signal)
+ * vt102 graphic codepage
+ * use innerHTML or prototype instead of sarissa
+
diff --git a/tools/ajaxterm/ajaxterm.1 b/tools/ajaxterm/ajaxterm.1
new file mode 100644
index 0000000000..46f2acb339
--- /dev/null
+++ b/tools/ajaxterm/ajaxterm.1
@@ -0,0 +1,35 @@
+.TH ajaxterm "1" "May 2006" "ajaxterm 0.5" "User commands"
+.SH NAME
+ajaxterm \- Web based terminal written in python
+
+.SH DESCRITPION
+\fBajaxterm\fR is a web based terminal written in python and some AJAX
+javascript for client side.
+It can use almost any web browser and even works through firewalls.
+
+.SH USAGE
+\fBajaxterm.py\fR [options]
+
+.SH OPTIONS
+A summary of the options supported by \fBajaxterm\fR is included below.
+ \fB-h, --help\fR show this help message and exit
+ \fB-pPORT, --port=PORT\fR Set the TCP port (default: 8022)
+ \fB-cCMD, --command=CMD\fR set the command (default: /bin/login or ssh localhost)
+ \fB-l, --log\fR log requests to stderr (default: quiet mode)
+
+.SH AUTHOR
+Antony Lesuisse <al@udev.org>
+
+This manual page was written for the Debian system by
+Julien Valroff <julien@kirya.net> (but may be used by others).
+
+.SH "REPORTING BUGS"
+Report any bugs to the author: Antony Lesuisse <al@udev.org>
+
+.SH COPYRIGHT
+Copyright Antony Lesuisse <al@udev.org>
+
+.SH SEE ALSO
+- \fBajaxterm\fR wiki page: http://antony.lesuisse.org/qweb/trac/wiki/AjaxTerm
+.br
+- \fBajaxterm\fR forum: http://antony.lesuisse.org/qweb/forum/viewforum.php?id=2
diff --git a/tools/ajaxterm/ajaxterm.css b/tools/ajaxterm/ajaxterm.css
new file mode 100644
index 0000000000..b9a5f87716
--- /dev/null
+++ b/tools/ajaxterm/ajaxterm.css
@@ -0,0 +1,64 @@
+pre.stat {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background-color: black;
+ border-top: 1px solid black;
+ color: white;
+}
+pre.stat span {
+ padding: 0px;
+}
+pre.stat .on {
+ background-color: #080;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.stat .off {
+ background-color: #888;
+ font-weight: bold;
+ color: white;
+ cursor: pointer;
+}
+pre.term {
+ margin: 0px;
+ padding: 4px;
+ display: block;
+ font-family: monospace;
+ white-space: pre;
+ background-color: black;
+ border-top: 1px solid white;
+ color: #eee;
+}
+pre.term span.f0 { color: #000; }
+pre.term span.f1 { color: #b00; }
+pre.term span.f2 { color: #0b0; }
+pre.term span.f3 { color: #bb0; }
+pre.term span.f4 { color: #00b; }
+pre.term span.f5 { color: #b0b; }
+pre.term span.f6 { color: #0bb; }
+pre.term span.f7 { color: #bbb; }
+pre.term span.f8 { color: #666; }
+pre.term span.f9 { color: #f00; }
+pre.term span.f10 { color: #0f0; }
+pre.term span.f11 { color: #ff0; }
+pre.term span.f12 { color: #00f; }
+pre.term span.f13 { color: #f0f; }
+pre.term span.f14 { color: #0ff; }
+pre.term span.f15 { color: #fff; }
+pre.term span.b0 { background-color: #000; }
+pre.term span.b1 { background-color: #b00; }
+pre.term span.b2 { background-color: #0b0; }
+pre.term span.b3 { background-color: #bb0; }
+pre.term span.b4 { background-color: #00b; }
+pre.term span.b5 { background-color: #b0b; }
+pre.term span.b6 { background-color: #0bb; }
+pre.term span.b7 { background-color: #bbb; }
+
+body { background-color: #888; }
+#term {
+ float: left;
+}
diff --git a/tools/ajaxterm/ajaxterm.html b/tools/ajaxterm/ajaxterm.html
new file mode 100644
index 0000000000..7fdef5e941
--- /dev/null
+++ b/tools/ajaxterm/ajaxterm.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+ <title>Ajaxterm</title>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
+ <link rel="stylesheet" type="text/css" href="ajaxterm.css"/>
+ <script type="text/javascript" src="sarissa.js"></script>
+ <script type="text/javascript" src="sarissa_dhtml.js"></script>
+ <script type="text/javascript" src="ajaxterm.js"></script>
+ <script type="text/javascript">
+ /*
+ ajaxterm.py creates a random session_id to demultiplex multiple connections,
+ and to add a layer of security - in its shipping form, ajaxterm accepted any session_id
+ and was susceptible to an easy exploit
+ */
+ SESSION_ID = '$session_id';
+ window.onload=function() {
+ t=ajaxterm.Terminal("term",80,25);
+ };
+ </script>
+</head>
+<body>
+<div id="term"></div>
+</body>
+</html>
diff --git a/tools/ajaxterm/ajaxterm.js b/tools/ajaxterm/ajaxterm.js
new file mode 100644
index 0000000000..32b401930c
--- /dev/null
+++ b/tools/ajaxterm/ajaxterm.js
@@ -0,0 +1,279 @@
+ajaxterm={};
+ajaxterm.Terminal_ctor=function(id,width,height) {
+ var ie=0;
+ if(window.ActiveXObject)
+ ie=1;
+ var sid=""+SESSION_ID;
+ var query0="s="+sid+"&w="+width+"&h="+height;
+ var query1=query0+"&c=1&k=";
+ var buf="";
+ var timeout;
+ var error_timeout;
+ var keybuf=[];
+ var sending=0;
+ var rmax=1;
+
+ var div=document.getElementById(id);
+ var dstat=document.createElement('pre');
+ var sled=document.createElement('span');
+ var opt_get=document.createElement('a');
+ var opt_color=document.createElement('a');
+ var opt_paste=document.createElement('a');
+ var sdebug=document.createElement('span');
+ var dterm=document.createElement('div');
+
+ function debug(s) {
+ sdebug.innerHTML=s;
+ }
+ function error() {
+ sled.className='off';
+ debug("Connection lost timeout ts:"+((new Date).getTime()));
+ }
+ function opt_add(opt,name) {
+ opt.className='off';
+ opt.innerHTML=' '+name+' ';
+ dstat.appendChild(opt);
+ dstat.appendChild(document.createTextNode(' '));
+ }
+ function do_get(event) {
+ opt_get.className=(opt_get.className=='off')?'on':'off';
+ debug('GET '+opt_get.className);
+ }
+ function do_color(event) {
+ var o=opt_color.className=(opt_color.className=='off')?'on':'off';
+ if(o=='on')
+ query1=query0+"&c=1&k=";
+ else
+ query1=query0+"&k=";
+ debug('Color '+opt_color.className);
+ }
+ function mozilla_clipboard() {
+ // mozilla sucks
+ try {
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ } catch (err) {
+ debug('Access denied, <a href="http://kb.mozillazine.org/Granting_JavaScript_access_to_the_clipboard" target="_blank">more info</a>');
+ return undefined;
+ }
+ var clip = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard);
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
+ if (!clip || !trans) {
+ return undefined;
+ }
+ trans.addDataFlavor("text/unicode");
+ clip.getData(trans,clip.kGlobalClipboard);
+ var str=new Object();
+ var strLength=new Object();
+ try {
+ trans.getTransferData("text/unicode",str,strLength);
+ } catch(err) {
+ return "";
+ }
+ if (str) {
+ str=str.value.QueryInterface(Components.interfaces.nsISupportsString);
+ }
+ if (str) {
+ return str.data.substring(0,strLength.value / 2);
+ } else {
+ return "";
+ }
+ }
+ function do_paste(event) {
+ var p=undefined;
+ if (window.clipboardData) {
+ p=window.clipboardData.getData("Text");
+ } else if(window.netscape) {
+ p=mozilla_clipboard();
+ }
+ if (p) {
+ debug('Pasted');
+ queue(encodeURIComponent(p));
+ } else {
+ }
+ }
+ function update() {
+// debug("ts: "+((new Date).getTime())+" rmax:"+rmax);
+ if(sending==0) {
+ sending=1;
+ sled.className='on';
+ var r=new XMLHttpRequest();
+ var send="";
+ while(keybuf.length>0) {
+ send+=keybuf.pop();
+ }
+ var query=query1+send;
+ if(opt_get.className=='on') {
+ r.open("GET","u?"+query,true);
+ if(ie) {
+ r.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
+ }
+ } else {
+ r.open("POST","u",true);
+ }
+ r.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
+ r.onreadystatechange = function () {
+// debug("xhr:"+((new Date).getTime())+" state:"+r.readyState+" status:"+r.status+" statusText:"+r.statusText);
+ if (r.readyState==4) {
+ if(r.status==200) {
+ window.clearTimeout(error_timeout);
+ de=r.responseXML.documentElement;
+ if(de.tagName=="pre") {
+ if(ie) {
+ Sarissa.updateContentFromNode(de, dterm);
+ } else {
+ Sarissa.updateContentFromNode(de, dterm);
+// old=div.firstChild;
+// div.replaceChild(de,old);
+ }
+ rmax=100;
+ } else {
+ rmax*=2;
+ if(rmax>2000)
+ rmax=2000;
+ }
+ sending=0;
+ sled.className='off';
+ timeout=window.setTimeout(update,rmax);
+ } else {
+ debug("Connection error status:"+r.status);
+ }
+ }
+ }
+ error_timeout=window.setTimeout(error,5000);
+ if(opt_get.className=='on') {
+ r.send(null);
+ } else {
+ r.send(query);
+ }
+ }
+ }
+ function queue(s) {
+ keybuf.unshift(s);
+ if(sending==0) {
+ window.clearTimeout(timeout);
+ timeout=window.setTimeout(update,1);
+ }
+ }
+ function keypress(ev) {
+ if (!ev) var ev=window.event;
+// s="kp keyCode="+ev.keyCode+" which="+ev.which+" shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey;
+// debug(s);
+// return false;
+// else { if (!ev.ctrlKey || ev.keyCode==17) { return; }
+ var kc;
+ var k="";
+ if (ev.keyCode)
+ kc=ev.keyCode;
+ if (ev.which)
+ kc=ev.which;
+ if (ev.altKey) {
+ if (kc>=65 && kc<=90)
+ kc+=32;
+ if (kc>=97 && kc<=122) {
+ k=String.fromCharCode(27)+String.fromCharCode(kc);
+ }
+ } else if (ev.ctrlKey) {
+ if (kc>=65 && kc<=90) k=String.fromCharCode(kc-64); // Ctrl-A..Z
+ else if (kc>=97 && kc<=122) k=String.fromCharCode(kc-96); // Ctrl-A..Z
+ else if (kc==54) k=String.fromCharCode(30); // Ctrl-^
+ else if (kc==109) k=String.fromCharCode(31); // Ctrl-_
+ else if (kc==219) k=String.fromCharCode(27); // Ctrl-[
+ else if (kc==220) k=String.fromCharCode(28); // Ctrl-\
+ else if (kc==221) k=String.fromCharCode(29); // Ctrl-]
+ else if (kc==219) k=String.fromCharCode(29); // Ctrl-]
+ else if (kc==219) k=String.fromCharCode(0); // Ctrl-@
+ } else if (ev.which==0) {
+ if (kc==9) k=String.fromCharCode(9); // Tab
+ else if (kc==8) k=String.fromCharCode(127); // Backspace
+ else if (kc==27) k=String.fromCharCode(27); // Escape
+ else {
+ if (kc==33) k="[5~"; // PgUp
+ else if (kc==34) k="[6~"; // PgDn
+ else if (kc==35) k="[4~"; // End
+ else if (kc==36) k="[1~"; // Home
+ else if (kc==37) k="[D"; // Left
+ else if (kc==38) k="[A"; // Up
+ else if (kc==39) k="[C"; // Right
+ else if (kc==40) k="[B"; // Down
+ else if (kc==45) k="[2~"; // Ins
+ else if (kc==46) k="[3~"; // Del
+ else if (kc==112) k="[[A"; // F1
+ else if (kc==113) k="[[B"; // F2
+ else if (kc==114) k="[[C"; // F3
+ else if (kc==115) k="[[D"; // F4
+ else if (kc==116) k="[[E"; // F5
+ else if (kc==117) k="[17~"; // F6
+ else if (kc==118) k="[18~"; // F7
+ else if (kc==119) k="[19~"; // F8
+ else if (kc==120) k="[20~"; // F9
+ else if (kc==121) k="[21~"; // F10
+ else if (kc==122) k="[23~"; // F11
+ else if (kc==123) k="[24~"; // F12
+ if (k.length) {
+ k=String.fromCharCode(27)+k;
+ }
+ }
+ } else {
+ if (kc==8)
+ k=String.fromCharCode(127); // Backspace
+ else
+ k=String.fromCharCode(kc);
+ }
+ if(k.length) {
+// queue(encodeURIComponent(k));
+ if(k=="+") {
+ queue("%2B");
+ } else {
+ queue(escape(k));
+ }
+ }
+ ev.cancelBubble=true;
+ if (ev.stopPropagation) ev.stopPropagation();
+ if (ev.preventDefault) ev.preventDefault();
+ return false;
+ }
+ function keydown(ev) {
+ if (!ev) var ev=window.event;
+ if (ie) {
+// s="kd keyCode="+ev.keyCode+" which="+ev.which+" shiftKey="+ev.shiftKey+" ctrlKey="+ev.ctrlKey+" altKey="+ev.altKey;
+// debug(s);
+ o={9:1,8:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1,45:1,46:1,112:1,
+ 113:1,114:1,115:1,116:1,117:1,118:1,119:1,120:1,121:1,122:1,123:1};
+ if (o[ev.keyCode] || ev.ctrlKey || ev.altKey) {
+ ev.which=0;
+ return keypress(ev);
+ }
+ }
+ }
+ function init() {
+ sled.appendChild(document.createTextNode('\xb7'));
+ sled.className='off';
+ dstat.appendChild(sled);
+ dstat.appendChild(document.createTextNode(' '));
+ opt_add(opt_color,'Colors');
+ opt_color.className='on';
+ opt_add(opt_get,'GET');
+ opt_add(opt_paste,'Paste');
+ dstat.appendChild(sdebug);
+ dstat.className='stat';
+ div.appendChild(dstat);
+ div.appendChild(dterm);
+ if(opt_color.addEventListener) {
+ opt_get.addEventListener('click',do_get,true);
+ opt_color.addEventListener('click',do_color,true);
+ opt_paste.addEventListener('click',do_paste,true);
+ } else {
+ opt_get.attachEvent("onclick", do_get);
+ opt_color.attachEvent("onclick", do_color);
+ opt_paste.attachEvent("onclick", do_paste);
+ }
+ document.onkeypress=keypress;
+ document.onkeydown=keydown;
+ timeout=window.setTimeout(update,100);
+ }
+ init();
+}
+ajaxterm.Terminal=function(id,width,height) {
+ return new this.Terminal_ctor(id,width,height);
+}
+
diff --git a/tools/ajaxterm/ajaxterm.py b/tools/ajaxterm/ajaxterm.py
new file mode 100755
index 0000000000..bf27b264a4
--- /dev/null
+++ b/tools/ajaxterm/ajaxterm.py
@@ -0,0 +1,586 @@
+#!/usr/bin/env python
+
+""" Ajaxterm """
+
+import array,cgi,fcntl,glob,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd
+
+os.chdir(os.path.normpath(os.path.dirname(__file__)))
+# Optional: Add QWeb in sys path
+sys.path[0:0]=glob.glob('../../python')
+
+import qweb
+import string, subprocess, uuid
+
+global g_server
+TIMEOUT=300
+
+class Terminal:
+ def __init__(self,width=80,height=24):
+ self.width=width
+ self.height=height
+ self.init()
+ self.reset()
+ def init(self):
+ self.esc_seq={
+ "\x00": None,
+ "\x05": self.esc_da,
+ "\x07": None,
+ "\x08": self.esc_0x08,
+ "\x09": self.esc_0x09,
+ "\x0a": self.esc_0x0a,
+ "\x0b": self.esc_0x0a,
+ "\x0c": self.esc_0x0a,
+ "\x0d": self.esc_0x0d,
+ "\x0e": None,
+ "\x0f": None,
+ "\x1b#8": None,
+ "\x1b=": None,
+ "\x1b>": None,
+ "\x1b(0": None,
+ "\x1b(A": None,
+ "\x1b(B": None,
+ "\x1b[c": self.esc_da,
+ "\x1b[0c": self.esc_da,
+ "\x1b]R": None,
+ "\x1b7": self.esc_save,
+ "\x1b8": self.esc_restore,
+ "\x1bD": None,
+ "\x1bE": None,
+ "\x1bH": None,
+ "\x1bM": self.esc_ri,
+ "\x1bN": None,
+ "\x1bO": None,
+ "\x1bZ": self.esc_da,
+ "\x1ba": None,
+ "\x1bc": self.reset,
+ "\x1bn": None,
+ "\x1bo": None,
+ }
+ for k,v in self.esc_seq.items():
+ if v==None:
+ self.esc_seq[k]=self.esc_ignore
+ # regex
+ d={
+ r'\[\??([0-9;]*)([@ABCDEFGHJKLMPXacdefghlmnqrstu`])' : self.csi_dispatch,
+ r'\]([^\x07]+)\x07' : self.esc_ignore,
+ }
+ self.esc_re=[]
+ for k,v in d.items():
+ self.esc_re.append((re.compile('\x1b'+k),v))
+ # define csi sequences
+ self.csi_seq={
+ '@': (self.csi_at,[1]),
+ '`': (self.csi_G,[1]),
+ 'J': (self.csi_J,[0]),
+ 'K': (self.csi_K,[0]),
+ }
+ for i in [i[4] for i in dir(self) if i.startswith('csi_') and len(i)==5]:
+ if not self.csi_seq.has_key(i):
+ self.csi_seq[i]=(getattr(self,'csi_'+i),[1])
+ # Init 0-256 to latin1 and html translation table
+ self.trl1=""
+ for i in range(256):
+ if i<32:
+ self.trl1+=" "
+ elif i<127 or i>160:
+ self.trl1+=chr(i)
+ else:
+ self.trl1+="?"
+ self.trhtml=""
+ for i in range(256):
+ if i==0x0a or (i>32 and i<127) or i>160:
+ self.trhtml+=chr(i)
+ elif i<=32:
+ self.trhtml+="\xa0"
+ else:
+ self.trhtml+="?"
+ def reset(self,s=""):
+ self.scr=array.array('i',[0x000700]*(self.width*self.height))
+ self.st=0
+ self.sb=self.height-1
+ self.cx_bak=self.cx=0
+ self.cy_bak=self.cy=0
+ self.cl=0
+ self.sgr=0x000700
+ self.buf=""
+ self.outbuf=""
+ self.last_html=""
+ def peek(self,y1,x1,y2,x2):
+ return self.scr[self.width*y1+x1:self.width*y2+x2]
+ def poke(self,y,x,s):
+ pos=self.width*y+x
+ self.scr[pos:pos+len(s)]=s
+ def zero(self,y1,x1,y2,x2):
+ w=self.width*(y2-y1)+x2-x1+1
+ z=array.array('i',[0x000700]*w)
+ self.scr[self.width*y1+x1:self.width*y2+x2+1]=z
+ def scroll_up(self,y1,y2):
+ self.poke(y1,0,self.peek(y1+1,0,y2,self.width))
+ self.zero(y2,0,y2,self.width-1)
+ def scroll_down(self,y1,y2):
+ self.poke(y1+1,0,self.peek(y1,0,y2-1,self.width))
+ self.zero(y1,0,y1,self.width-1)
+ def scroll_right(self,y,x):
+ self.poke(y,x+1,self.peek(y,x,y,self.width))
+ self.zero(y,x,y,x)
+ def cursor_down(self):
+ if self.cy>=self.st and self.cy<=self.sb:
+ self.cl=0
+ q,r=divmod(self.cy+1,self.sb+1)
+ if q:
+ self.scroll_up(self.st,self.sb)
+ self.cy=self.sb
+ else:
+ self.cy=r
+ def cursor_right(self):
+ q,r=divmod(self.cx+1,self.width)
+ if q:
+ self.cl=1
+ else:
+ self.cx=r
+ def echo(self,c):
+ if self.cl:
+ self.cursor_down()
+ self.cx=0
+ self.scr[(self.cy*self.width)+self.cx]=self.sgr|ord(c)
+ self.cursor_right()
+ def esc_0x08(self,s):
+ self.cx=max(0,self.cx-1)
+ def esc_0x09(self,s):
+ x=self.cx+8
+ q,r=divmod(x,8)
+ self.cx=(q*8)%self.width
+ def esc_0x0a(self,s):
+ self.cursor_down()
+ def esc_0x0d(self,s):
+ self.cl=0
+ self.cx=0
+ def esc_save(self,s):
+ self.cx_bak=self.cx
+ self.cy_bak=self.cy
+ def esc_restore(self,s):
+ self.cx=self.cx_bak
+ self.cy=self.cy_bak
+ self.cl=0
+ def esc_da(self,s):
+ self.outbuf="\x1b[?6c"
+ def esc_ri(self,s):
+ self.cy=max(self.st,self.cy-1)
+ if self.cy==self.st:
+ self.scroll_down(self.st,self.sb)
+ def esc_ignore(self,*s):
+ pass
+# print "term:ignore: %s"%repr(s)
+ def csi_dispatch(self,seq,mo):
+ # CSI sequences
+ s=mo.group(1)
+ c=mo.group(2)
+ f=self.csi_seq.get(c,None)
+ if f:
+ try:
+ l=[min(int(i),1024) for i in s.split(';') if len(i)<4]
+ except ValueError:
+ l=[]
+ if len(l)==0:
+ l=f[1]
+ f[0](l)
+# else:
+# print 'csi ignore',c,l
+ def csi_at(self,l):
+ for i in range(l[0]):
+ self.scroll_right(self.cy,self.cx)
+ def csi_A(self,l):
+ self.cy=max(self.st,self.cy-l[0])
+ def csi_B(self,l):
+ self.cy=min(self.sb,self.cy+l[0])
+ def csi_C(self,l):
+ self.cx=min(self.width-1,self.cx+l[0])
+ self.cl=0
+ def csi_D(self,l):
+ self.cx=max(0,self.cx-l[0])
+ self.cl=0
+ def csi_E(self,l):
+ self.csi_B(l)
+ self.cx=0
+ self.cl=0
+ def csi_F(self,l):
+ self.csi_A(l)
+ self.cx=0
+ self.cl=0
+ def csi_G(self,l):
+ self.cx=min(self.width,l[0])-1
+ def csi_H(self,l):
+ if len(l)<2: l=[1,1]
+ self.cx=min(self.width,l[1])-1
+ self.cy=min(self.height,l[0])-1
+ self.cl=0
+ def csi_J(self,l):
+ if l[0]==0:
+ self.zero(self.cy,self.cx,self.height-1,self.width-1)
+ elif l[0]==1:
+ self.zero(0,0,self.cy,self.cx)
+ elif l[0]==2:
+ self.zero(0,0,self.height-1,self.width-1)
+ def csi_K(self,l):
+ if l[0]==0:
+ self.zero(self.cy,self.cx,self.cy,self.width-1)
+ elif l[0]==1:
+ self.zero(self.cy,0,self.cy,self.cx)
+ elif l[0]==2:
+ self.zero(self.cy,0,self.cy,self.width-1)
+ def csi_L(self,l):
+ for i in range(l[0]):
+ if self.cy<self.sb:
+ self.scroll_down(self.cy,self.sb)
+ def csi_M(self,l):
+ if self.cy>=self.st and self.cy<=self.sb:
+ for i in range(l[0]):
+ self.scroll_up(self.cy,self.sb)
+ def csi_P(self,l):
+ w,cx,cy=self.width,self.cx,self.cy
+ end=self.peek(cy,cx,cy,w)
+ self.csi_K([0])
+ self.poke(cy,cx,end[l[0]:])
+ def csi_X(self,l):
+ self.zero(self.cy,self.cx,self.cy,self.cx+l[0])
+ def csi_a(self,l):
+ self.csi_C(l)
+ def csi_c(self,l):
+ #'\x1b[?0c' 0-8 cursor size
+ pass
+ def csi_d(self,l):
+ self.cy=min(self.height,l[0])-1
+ def csi_e(self,l):
+ self.csi_B(l)
+ def csi_f(self,l):
+ self.csi_H(l)
+ def csi_h(self,l):
+ if l[0]==4:
+ pass
+# print "insert on"
+ def csi_l(self,l):
+ if l[0]==4:
+ pass
+# print "insert off"
+ def csi_m(self,l):
+ for i in l:
+ if i==0 or i==39 or i==49 or i==27:
+ self.sgr=0x000700
+ elif i==1:
+ self.sgr=(self.sgr|0x000800)
+ elif i==7:
+ self.sgr=0x070000
+ elif i>=30 and i<=37:
+ c=i-30
+ self.sgr=(self.sgr&0xff08ff)|(c<<8)
+ elif i>=40 and i<=47:
+ c=i-40
+ self.sgr=(self.sgr&0x00ffff)|(c<<16)
+# else:
+# print "CSI sgr ignore",l,i
+# print 'sgr: %r %x'%(l,self.sgr)
+ def csi_r(self,l):
+ if len(l)<2: l=[0,self.height]
+ self.st=min(self.height-1,l[0]-1)
+ self.sb=min(self.height-1,l[1]-1)
+ self.sb=max(self.st,self.sb)
+ def csi_s(self,l):
+ self.esc_save(0)
+ def csi_u(self,l):
+ self.esc_restore(0)
+ def escape(self):
+ e=self.buf
+ if len(e)>32:
+# print "error %r"%e
+ self.buf=""
+ elif e in self.esc_seq:
+ self.esc_seq[e](e)
+ self.buf=""
+ else:
+ for r,f in self.esc_re:
+ mo=r.match(e)
+ if mo:
+ f(e,mo)
+ self.buf=""
+ break
+# if self.buf=='': print "ESC %r\n"%e
+ def write(self,s):
+ for i in s:
+ if len(self.buf) or (i in self.esc_seq):
+ self.buf+=i
+ self.escape()
+ elif i == '\x1b':
+ self.buf+=i
+ else:
+ self.echo(i)
+ def read(self):
+ b=self.outbuf
+ self.outbuf=""
+ return b
+ def dump(self):
+ r=''
+ for i in self.scr:
+ r+=chr(i&255)
+ return r
+ def dumplatin1(self):
+ return self.dump().translate(self.trl1)
+ def dumphtml(self,color=1):
+ h=self.height
+ w=self.width
+ r=""
+ span=""
+ span_bg,span_fg=-1,-1
+ for i in range(h*w):
+ q,c=divmod(self.scr[i],256)
+ if color:
+ bg,fg=divmod(q,256)
+ else:
+ bg,fg=0,7
+ if i==self.cy*w+self.cx:
+ bg,fg=1,7
+ if (bg!=span_bg or fg!=span_fg or i==h*w-1):
+ if len(span):
+ r+='<span class="f%d b%d">%s</span>'%(span_fg,span_bg,cgi.escape(span.translate(self.trhtml)))
+ span=""
+ span_bg,span_fg=bg,fg
+ span+=chr(c)
+ if i%w==w-1:
+ span+='\n'
+ r='<?xml version="1.0" encoding="ISO-8859-1"?><pre class="term">%s</pre>'%r
+ if self.last_html==r:
+ return '<?xml version="1.0"?><idem></idem>'
+ else:
+ self.last_html=r
+# print self
+ return r
+ def __repr__(self):
+ d=self.dumplatin1()
+ r=""
+ for i in range(self.height):
+ r+="|%s|\n"%d[self.width*i:self.width*(i+1)]
+ return r
+
+class SynchronizedMethod:
+ def __init__(self,lock,orig):
+ self.lock=lock
+ self.orig=orig
+ def __call__(self,*l):
+ self.lock.acquire()
+ r=self.orig(*l)
+ self.lock.release()
+ return r
+
+class Multiplex:
+ def __init__(self,cmd=None):
+ signal.signal(signal.SIGCHLD, signal.SIG_IGN)
+ self.cmd=cmd
+ self.proc={}
+ self.lock=threading.RLock()
+ self.thread=threading.Thread(target=self.loop)
+ self.alive=1
+ self.lastActivity=time.time()
+ # synchronize methods
+ for name in ['create','fds','proc_read','proc_write','dump','die','run']:
+ orig=getattr(self,name)
+ setattr(self,name,SynchronizedMethod(self.lock,orig))
+ self.thread.start()
+ def create(self,w=80,h=25):
+ pid,fd=pty.fork()
+ if pid==0:
+ try:
+ fdl=[int(i) for i in os.listdir('/proc/self/fd')]
+ except OSError:
+ fdl=range(256)
+ for i in [i for i in fdl if i>2]:
+ try:
+ os.close(i)
+ except OSError:
+ pass
+ if self.cmd:
+ cmd=['/bin/sh','-c',self.cmd]
+ elif os.getuid()==0:
+ cmd=['/bin/login']
+ else:
+ sys.stdout.write("Login: ")
+ login=sys.stdin.readline().strip()
+ if re.match('^[0-9A-Za-z-_. ]+$',login):
+ cmd=['ssh']
+ cmd+=['-oPreferredAuthentications=keyboard-interactive,password']
+ cmd+=['-oNoHostAuthenticationForLocalhost=yes']
+ cmd+=['-oLogLevel=FATAL']
+ cmd+=['-F/dev/null','-l',login,'localhost']
+ else:
+ os._exit(0)
+ env={}
+ env["COLUMNS"]=str(w)
+ env["LINES"]=str(h)
+ env["TERM"]="linux"
+ env["PATH"]=os.environ['PATH']
+ os.execvpe(cmd[0],cmd,env)
+ else:
+ fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
+ # python bug http://python.org/sf/1112949 on amd64
+ fcntl.ioctl(fd, struct.unpack('i',struct.pack('I',termios.TIOCSWINSZ))[0], struct.pack("HHHH",h,w,0,0))
+ self.proc[fd]={'pid':pid,'term':Terminal(w,h),'buf':'','time':time.time()}
+ return fd
+ def die(self):
+ self.alive=0
+ def run(self):
+ return self.alive
+ def fds(self):
+ return self.proc.keys()
+ def proc_kill(self,fd):
+ if fd in self.proc:
+ self.proc[fd]['time']=0
+ t=time.time()
+ for i in self.proc.keys():
+ t0=self.proc[i]['time']
+ if (t-t0)>TIMEOUT:
+ try:
+ os.close(i)
+ os.kill(self.proc[i]['pid'],signal.SIGTERM)
+ except (IOError,OSError):
+ pass
+ del self.proc[i]
+ def proc_read(self,fd):
+ try:
+ t=self.proc[fd]['term']
+ t.write(os.read(fd,65536))
+ reply=t.read()
+ if reply:
+ os.write(fd,reply)
+ self.proc[fd]['time']=time.time()
+ except (KeyError,IOError,OSError):
+ self.proc_kill(fd)
+ def proc_write(self,fd,s):
+ try:
+ os.write(fd,s)
+ except (IOError,OSError):
+ self.proc_kill(fd)
+ def dump(self,fd,color=1):
+ try:
+ return self.proc[fd]['term'].dumphtml(color)
+ except KeyError:
+ return False
+ def loop(self):
+ while self.run():
+ fds=self.fds()
+ i,o,e=select.select(fds, [], [], 1.0)
+ if time.time() - self.lastActivity > TIMEOUT:
+ global g_server
+ g_server.shutdown()
+ for fd in i:
+ self.proc_read(fd)
+ if len(i):
+ time.sleep(0.002)
+ for i in self.proc.keys():
+ try:
+ os.close(i)
+ os.kill(self.proc[i]['pid'],signal.SIGTERM)
+ except (IOError,OSError):
+ pass
+
+class AjaxTerm:
+ def __init__(self,cmd=None,index_file='ajaxterm.html',token=None):
+ self.files={}
+ self.token=token
+ for i in ['css','html','js']:
+ for j in glob.glob('*.%s'%i):
+ self.files[j]=file(j).read()
+ self.files['index']=file(index_file).read()
+ self.mime = mimetypes.types_map.copy()
+ self.mime['.html']= 'text/html; charset=UTF-8'
+ self.multi = Multiplex(cmd)
+ self.session = {}
+ def __call__(self, environ, start_response):
+ req = qweb.QWebRequest(environ, start_response,session=None)
+ if req.PATH_INFO.endswith('/u'):
+ s=req.REQUEST["s"]
+ k=req.REQUEST["k"]
+ c=req.REQUEST["c"]
+ w=req.REQUEST.int("w")
+ h=req.REQUEST.int("h")
+ if s in self.session:
+ term=self.session[s]
+ else:
+ raise Exception('Not Authorized')
+ # The original code below was insecure, because it allowed unauthorized sessions to be created
+ # if not (w>2 and w<256 and h>2 and h<100):
+ # w,h=80,25
+ # term=self.session[s]=self.multi.create(w,h)
+ if k:
+ self.multi.proc_write(term,k)
+ time.sleep(0.002)
+ self.multi.lastActivity = time.time();
+ dump=self.multi.dump(term,c)
+ req.response_headers['Content-Type']='text/xml'
+ if isinstance(dump,str):
+ req.write(dump)
+ req.response_gzencode=1
+ else:
+ del self.session[s]
+ req.write('<?xml version="1.0"?><idem></idem>')
+# print "sessions %r"%self.session
+ else:
+ n=os.path.basename(req.PATH_INFO)
+ if n in self.files:
+ req.response_headers['Content-Type'] = self.mime.get(os.path.splitext(n)[1].lower(), 'application/octet-stream')
+ req.write(self.files[n])
+ elif req.REQUEST['token'] == self.token:
+ req.response_headers['Content-Type'] = 'text/html; charset=UTF-8'
+ session_id = str(uuid.uuid4())
+ req.write(string.Template(self.files['index']).substitute(session_id=session_id))
+ term=self.session[session_id]=self.multi.create(80,25)
+ else:
+ raise Exception("Not Authorized")
+ return req
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option("-p", "--port", dest="port", default="8022", help="Set the TCP port (default: 8022)")
+ parser.add_option("-c", "--command", dest="cmd", default=None,help="set the command (default: /bin/login or ssh 0.0.0.0)")
+ parser.add_option("-l", "--log", action="store_true", dest="log",default=0,help="log requests to stderr (default: quiet mode)")
+ parser.add_option("-d", "--daemon", action="store_true", dest="daemon", default=0, help="run as daemon in the background")
+ parser.add_option("-P", "--pidfile",dest="pidfile",default="/var/run/ajaxterm.pid",help="set the pidfile (default: /var/run/ajaxterm.pid)")
+ parser.add_option("-i", "--index", dest="index_file", default="ajaxterm.html",help="default index file (default: ajaxterm.html)")
+ parser.add_option("-u", "--uid", dest="uid", help="Set the daemon's user id")
+ parser.add_option("-t", "--token", dest="token", help="Set authorization token")
+ (o, a) = parser.parse_args()
+ if o.daemon:
+ pid=os.fork()
+ if pid == 0:
+ #os.setsid() ?
+ os.setpgrp()
+ nullin = file('/dev/null', 'r')
+ nullout = file('/dev/null', 'w')
+ os.dup2(nullin.fileno(), sys.stdin.fileno())
+ os.dup2(nullout.fileno(), sys.stdout.fileno())
+ os.dup2(nullout.fileno(), sys.stderr.fileno())
+ if os.getuid()==0 and o.uid:
+ try:
+ os.setuid(int(o.uid))
+ except:
+ os.setuid(pwd.getpwnam(o.uid).pw_uid)
+ else:
+ try:
+ file(o.pidfile,'w+').write(str(pid)+'\n')
+ except:
+ pass
+ print 'AjaxTerm at http://0.0.0.0:%s/ pid: %d' % (o.port,pid)
+ sys.exit(0)
+ else:
+ print 'AjaxTerm at http://0.0.0.0:%s/' % o.port
+ at=AjaxTerm(o.cmd,o.index_file,o.token)
+# f=lambda:os.system('firefox http://localhost:%s/&'%o.port)
+# qweb.qweb_wsgi_autorun(at,ip='localhost',port=int(o.port),threaded=0,log=o.log,callback_ready=None)
+ try:
+ global g_server
+ g_server = qweb.QWebWSGIServer(at,ip='0.0.0.0',port=int(o.port),threaded=0,log=o.log)
+ g_server.serve_forever()
+ except KeyboardInterrupt,e:
+ sys.excepthook(*sys.exc_info())
+ at.multi.die()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/tools/ajaxterm/configure b/tools/ajaxterm/configure
new file mode 100755
index 0000000000..45391f4847
--- /dev/null
+++ b/tools/ajaxterm/configure
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+import optparse,os
+
+parser = optparse.OptionParser()
+parser.add_option("", "--prefix", dest="prefix",default="/usr/local",help="installation prefix (default: /usr/local)")
+parser.add_option("", "--confdir", dest="confdir", default="/etc",help="configuration files directory prefix (default: /etc)")
+parser.add_option("", "--port", dest="port", default="8022", help="set the listening TCP port (default: 8022)")
+parser.add_option("", "--command", dest="cmd", default=None,help="set the command (default: /bin/login or ssh localhost)")
+(o, a) = parser.parse_args()
+
+print "Configuring prefix=",o.prefix," port=",o.port
+
+etc=o.confdir
+port=o.port
+cmd=o.cmd
+bin=os.path.join(o.prefix,"bin")
+lib=os.path.join(o.prefix,"share/ajaxterm")
+man=os.path.join(o.prefix,"share/man/man1")
+
+file("ajaxterm.bin","w").write(file("configure.ajaxterm.bin").read()%locals())
+file("Makefile","w").write(file("configure.makefile").read()%locals())
+
+if os.path.isfile("/etc/gentoo-release"):
+ file("ajaxterm.initd","w").write(file("configure.initd.gentoo").read()%locals())
+elif os.path.isfile("/etc/fedora-release") or os.path.isfile("/etc/redhat-release"):
+ file("ajaxterm.initd","w").write(file("configure.initd.redhat").read()%locals())
+else:
+ file("ajaxterm.initd","w").write(file("configure.initd.debian").read()%locals())
+
+os.system("chmod a+x ajaxterm.bin")
+os.system("chmod a+x ajaxterm.initd")
diff --git a/tools/ajaxterm/configure.ajaxterm.bin b/tools/ajaxterm/configure.ajaxterm.bin
new file mode 100644
index 0000000000..4d1f5a98f0
--- /dev/null
+++ b/tools/ajaxterm/configure.ajaxterm.bin
@@ -0,0 +1,2 @@
+#!/bin/sh
+PYTHONPATH=%(lib)s exec %(lib)s/ajaxterm.py $@
diff --git a/tools/ajaxterm/configure.initd.debian b/tools/ajaxterm/configure.initd.debian
new file mode 100644
index 0000000000..9010827072
--- /dev/null
+++ b/tools/ajaxterm/configure.initd.debian
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
+DAEMON=%(bin)s/ajaxterm
+PORT=%(port)s
+PIDFILE=/var/run/ajaxterm.pid
+
+[ -x "$DAEMON" ] || exit 0
+
+#. /lib/lsb/init-functions
+
+case "$1" in
+ start)
+ echo "Starting ajaxterm on port $PORT"
+ start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- --daemon --port=$PORT --uid=nobody || return 2
+ ;;
+ stop)
+ echo "Stopping ajaxterm"
+ start-stop-daemon --stop --pidfile $PIDFILE
+ rm -f $PIDFILE
+ ;;
+ restart|force-reload)
+ $0 stop
+ sleep 1
+ $0 start
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
+
+:
diff --git a/tools/ajaxterm/configure.initd.gentoo b/tools/ajaxterm/configure.initd.gentoo
new file mode 100644
index 0000000000..ac28ef0b66
--- /dev/null
+++ b/tools/ajaxterm/configure.initd.gentoo
@@ -0,0 +1,27 @@
+#!/sbin/runscript
+
+# AjaxTerm Gentoo script, 08 May 2006 Mark Gillespie
+
+DAEMON=%(bin)s/ajaxterm
+PORT=%(port)s
+PIDFILE=/var/run/ajaxterm.pid
+
+depend()
+{
+ need net
+}
+
+start()
+{
+ ebegin "Starting AjaxTerm on port $PORT"
+ start-stop-daemon --start --pidfile $PIDFILE --exec $DAEMON -- --daemon --port=$PORT --uid=nobody
+ eend $?
+}
+
+stop()
+{
+ ebegin "Stopping AjaxTerm"
+ start-stop-daemon --stop --pidfile $PIDFILE
+ rm -f $PIDFILE
+ eend $?
+}
diff --git a/tools/ajaxterm/configure.initd.redhat b/tools/ajaxterm/configure.initd.redhat
new file mode 100644
index 0000000000..5c9788574f
--- /dev/null
+++ b/tools/ajaxterm/configure.initd.redhat
@@ -0,0 +1,75 @@
+#
+# ajaxterm Startup script for ajaxterm
+#
+# chkconfig: - 99 99
+# description: Ajaxterm is a yadda yadda yadda
+# processname: ajaxterm
+# pidfile: /var/run/ajaxterm.pid
+# version: 1.0 Kevin Reichhart - ajaxterminit at lastname dot org
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+if [ -f /etc/sysconfig/ajaxterm ]; then
+ . /etc/sysconfig/ajaxterm
+fi
+
+ajaxterm=/usr/local/bin/ajaxterm
+prog=ajaxterm
+pidfile=${PIDFILE-/var/run/ajaxterm.pid}
+lockfile=${LOCKFILE-/var/lock/subsys/ajaxterm}
+port=${PORT-8022}
+user=${xUSER-nobody}
+RETVAL=0
+
+
+start() {
+ echo -n $"Starting $prog: "
+ daemon $ajaxterm --daemon --port=$port --uid=$user $OPTIONS
+ RETVAL=$?
+ echo
+ [ $RETVAL = 0 ] && touch ${lockfile}
+ return $RETVAL
+}
+stop() {
+ echo -n $"Stopping $prog: "
+ killproc $ajaxterm
+ RETVAL=$?
+ echo
+ [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
+}
+reload() {
+ echo -n $"Reloading $prog: "
+ killproc $ajaxterm -HUP
+ RETVAL=$?
+ echo
+}
+
+# See how we were called.
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ status)
+ status python ajaxterm
+ RETVAL=$?
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ condrestart)
+ if [ -f ${pidfile} ] ; then
+ stop
+ start
+ fi
+ ;;
+ *)
+ echo $"Usage: $prog {start|stop|restart|condrestart}"
+ exit 1
+esac
+
+exit $RETVAL
diff --git a/tools/ajaxterm/configure.makefile b/tools/ajaxterm/configure.makefile
new file mode 100644
index 0000000000..6bd80853dd
--- /dev/null
+++ b/tools/ajaxterm/configure.makefile
@@ -0,0 +1,20 @@
+build:
+ true
+
+install:
+ install -d "%(bin)s"
+ install -d "%(lib)s"
+ install ajaxterm.bin "%(bin)s/ajaxterm"
+ install ajaxterm.initd "%(etc)s/init.d/ajaxterm"
+ install -m 644 ajaxterm.css ajaxterm.html ajaxterm.js qweb.py sarissa.js sarissa_dhtml.js "%(lib)s"
+ install -m 755 ajaxterm.py "%(lib)s"
+ gzip --best -c ajaxterm.1 > ajaxterm.1.gz
+ install -d "%(man)s"
+ install ajaxterm.1.gz "%(man)s"
+
+clean:
+ rm ajaxterm.bin
+ rm ajaxterm.initd
+ rm ajaxterm.1.gz
+ rm Makefile
+
diff --git a/tools/ajaxterm/qweb.py b/tools/ajaxterm/qweb.py
new file mode 100644
index 0000000000..20c5092300
--- /dev/null
+++ b/tools/ajaxterm/qweb.py
@@ -0,0 +1,1356 @@
+#!/usr/bin/python2.3
+#
+# vim:set et ts=4 fdc=0 fdn=2 fdl=0:
+#
+# There are no blank lines between blocks beacause i use folding from:
+# http://www.vim.org/scripts/script.php?script_id=515
+#
+
+"""= QWeb Framework =
+
+== What is QWeb ? ==
+
+QWeb is a python based [http://www.python.org/doc/peps/pep-0333/ WSGI]
+compatible web framework, it provides an infratructure to quickly build web
+applications consisting of:
+
+ * A lightweight request handler (QWebRequest)
+ * An xml templating engine (QWebXml and QWebHtml)
+ * A simple name based controler (qweb_control)
+ * A standalone WSGI Server (QWebWSGIServer)
+ * A cgi and fastcgi WSGI wrapper (taken from flup)
+ * A startup function that starts cgi, factgi or standalone according to the
+ evironement (qweb_autorun).
+
+QWeb applications are runnable in standalone mode (from commandline), via
+FastCGI, Regular CGI or by any python WSGI compliant server.
+
+QWeb doesn't provide any database access but it integrates nicely with ORMs
+such as SQLObject, SQLAlchemy or plain DB-API.
+
+Written by Antony Lesuisse (email al AT udev.org)
+
+Homepage: http://antony.lesuisse.org/qweb/trac/
+
+Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
+
+== Quick Start (for Linux, MacOS X and cygwin) ==
+
+Make sure you have at least python 2.3 installed and run the following commands:
+
+{{{
+$ wget http://antony.lesuisse.org/qweb/files/QWeb-0.7.tar.gz
+$ tar zxvf QWeb-0.7.tar.gz
+$ cd QWeb-0.7/examples/blog
+$ ./blog.py
+}}}
+
+And point your browser to http://localhost:8080/
+
+You may also try AjaxTerm which uses qweb request handler.
+
+== Download ==
+
+ * Version 0.7:
+ * Source [/qweb/files/QWeb-0.7.tar.gz QWeb-0.7.tar.gz]
+ * Python 2.3 Egg [/qweb/files/QWeb-0.7-py2.3.egg QWeb-0.7-py2.3.egg]
+ * Python 2.4 Egg [/qweb/files/QWeb-0.7-py2.4.egg QWeb-0.7-py2.4.egg]
+
+ * [/qweb/trac/browser Browse the source repository]
+
+== Documentation ==
+
+ * [/qweb/trac/browser/trunk/README.txt?format=raw Read the included documentation]
+ * QwebTemplating
+
+== Mailin-list ==
+
+ * Forum: [http://antony.lesuisse.org/qweb/forum/viewforum.php?id=1 Forum]
+ * No mailing-list exists yet, discussion should happen on: [http://mail.python.org/mailman/listinfo/web-sig web-sig] [http://mail.python.org/pipermail/web-sig/ archives]
+
+QWeb Components:
+----------------
+
+QWeb also feature a simple components api, that enables developers to easily
+produces reusable components.
+
+Default qweb components:
+
+ - qweb_static:
+ A qweb component to serve static content from the filesystem or from
+ zipfiles.
+
+ - qweb_dbadmin:
+ scaffolding for sqlobject
+
+License
+-------
+qweb/fcgi.py wich is BSD-like from saddi.com.
+Everything else is put in the public domain.
+
+
+TODO
+----
+ Announce QWeb to python-announce-list@python.org web-sig@python.org
+ qweb_core
+ rename request methods into
+ request_save_files
+ response_404
+ response_redirect
+ response_download
+ request callback_generator, callback_function ?
+ wsgi callback_server_local
+ xml tags explicitly call render_attributes(t_att)?
+ priority form-checkbox over t-value (for t-option)
+
+"""
+
+import BaseHTTPServer,SocketServer,Cookie
+import cgi,datetime,email,email.Message,errno,gzip,os,random,re,socket,sys,tempfile,time,types,urllib,urlparse,xml.dom
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
+
+#----------------------------------------------------------
+# Qweb Xml t-raw t-esc t-if t-foreach t-set t-call t-trim
+#----------------------------------------------------------
+class QWebEval:
+ def __init__(self,data):
+ self.data=data
+ def __getitem__(self,expr):
+ if self.data.has_key(expr):
+ return self.data[expr]
+ r=None
+ try:
+ r=eval(expr,self.data)
+ except NameError,e:
+ pass
+ except AttributeError,e:
+ pass
+ except Exception,e:
+ print "qweb: expression error '%s' "%expr,e
+ if self.data.has_key("__builtins__"):
+ del self.data["__builtins__"]
+ return r
+ def eval_object(self,expr):
+ return self[expr]
+ def eval_str(self,expr):
+ if expr=="0":
+ return self.data[0]
+ if isinstance(self[expr],unicode):
+ return self[expr].encode("utf8")
+ return str(self[expr])
+ def eval_format(self,expr):
+ try:
+ return str(expr%self)
+ except:
+ return "qweb: format error '%s' "%expr
+# if isinstance(r,unicode):
+# return r.encode("utf8")
+ def eval_bool(self,expr):
+ if self.eval_object(expr):
+ return 1
+ else:
+ return 0
+class QWebXml:
+ """QWeb Xml templating engine
+
+ The templating engine use a very simple syntax, "magic" xml attributes, to
+ produce any kind of texutal output (even non-xml).
+
+ QWebXml:
+ the template engine core implements the basic magic attributes:
+
+ t-att t-raw t-esc t-if t-foreach t-set t-call t-trim
+
+ """
+ def __init__(self,x=None,zipname=None):
+ self.node=xml.dom.Node
+ self._t={}
+ self._render_tag={}
+ prefix='render_tag_'
+ for i in [j for j in dir(self) if j.startswith(prefix)]:
+ name=i[len(prefix):].replace('_','-')
+ self._render_tag[name]=getattr(self.__class__,i)
+
+ self._render_att={}
+ prefix='render_att_'
+ for i in [j for j in dir(self) if j.startswith(prefix)]:
+ name=i[len(prefix):].replace('_','-')
+ self._render_att[name]=getattr(self.__class__,i)
+
+ if x!=None:
+ if zipname!=None:
+ import zipfile
+ zf=zipfile.ZipFile(zipname, 'r')
+ self.add_template(zf.read(x))
+ else:
+ self.add_template(x)
+ def register_tag(self,tag,func):
+ self._render_tag[tag]=func
+ def add_template(self,x):
+ if hasattr(x,'documentElement'):
+ dom=x
+ elif x.startswith("<?xml"):
+ import xml.dom.minidom
+ dom=xml.dom.minidom.parseString(x)
+ else:
+ import xml.dom.minidom
+ dom=xml.dom.minidom.parse(x)
+ for n in dom.documentElement.childNodes:
+ if n.nodeName=="t":
+ self._t[str(n.getAttribute("t-name"))]=n
+ def get_template(self,name):
+ return self._t[name]
+
+ def eval_object(self,expr,v):
+ return QWebEval(v).eval_object(expr)
+ def eval_str(self,expr,v):
+ return QWebEval(v).eval_str(expr)
+ def eval_format(self,expr,v):
+ return QWebEval(v).eval_format(expr)
+ def eval_bool(self,expr,v):
+ return QWebEval(v).eval_bool(expr)
+
+ def render(self,tname,v={},out=None):
+ if self._t.has_key(tname):
+ return self.render_node(self._t[tname],v)
+ else:
+ return 'qweb: template "%s" not found'%tname
+ def render_node(self,e,v):
+ r=""
+ if e.nodeType==self.node.TEXT_NODE or e.nodeType==self.node.CDATA_SECTION_NODE:
+ r=e.data.encode("utf8")
+ elif e.nodeType==self.node.ELEMENT_NODE:
+ pre=""
+ g_att=""
+ t_render=None
+ t_att={}
+ for (an,av) in e.attributes.items():
+ an=str(an)
+ if isinstance(av,types.UnicodeType):
+ av=av.encode("utf8")
+ else:
+ av=av.nodeValue.encode("utf8")
+ if an.startswith("t-"):
+ for i in self._render_att:
+ if an[2:].startswith(i):
+ g_att+=self._render_att[i](self,e,an,av,v)
+ break
+ else:
+ if self._render_tag.has_key(an[2:]):
+ t_render=an[2:]
+ t_att[an[2:]]=av
+ else:
+ g_att+=' %s="%s"'%(an,cgi.escape(av,1));
+ if t_render:
+ if self._render_tag.has_key(t_render):
+ r=self._render_tag[t_render](self,e,t_att,g_att,v)
+ else:
+ r=self.render_element(e,g_att,v,pre,t_att.get("trim",0))
+ return r
+ def render_element(self,e,g_att,v,pre="",trim=0):
+ g_inner=[]
+ for n in e.childNodes:
+ g_inner.append(self.render_node(n,v))
+ name=str(e.nodeName)
+ inner="".join(g_inner)
+ if trim==0:
+ pass
+ elif trim=='left':
+ inner=inner.lstrip()
+ elif trim=='right':
+ inner=inner.rstrip()
+ elif trim=='both':
+ inner=inner.strip()
+ if name=="t":
+ return inner
+ elif len(inner):
+ return "<%s%s>%s%s</%s>"%(name,g_att,pre,inner,name)
+ else:
+ return "<%s%s/>"%(name,g_att)
+
+ # Attributes
+ def render_att_att(self,e,an,av,v):
+ if an.startswith("t-attf-"):
+ att,val=an[7:],self.eval_format(av,v)
+ elif an.startswith("t-att-"):
+ att,val=(an[6:],self.eval_str(av,v))
+ else:
+ att,val=self.eval_object(av,v)
+ return ' %s="%s"'%(att,cgi.escape(val,1))
+
+ # Tags
+ def render_tag_raw(self,e,t_att,g_att,v):
+ return self.eval_str(t_att["raw"],v)
+ def render_tag_rawf(self,e,t_att,g_att,v):
+ return self.eval_format(t_att["rawf"],v)
+ def render_tag_esc(self,e,t_att,g_att,v):
+ return cgi.escape(self.eval_str(t_att["esc"],v))
+ def render_tag_escf(self,e,t_att,g_att,v):
+ return cgi.escape(self.eval_format(t_att["escf"],v))
+ def render_tag_foreach(self,e,t_att,g_att,v):
+ expr=t_att["foreach"]
+ enum=self.eval_object(expr,v)
+ if enum!=None:
+ var=t_att.get('as',expr).replace('.','_')
+ d=v.copy()
+ size=-1
+ if isinstance(enum,types.ListType):
+ size=len(enum)
+ elif isinstance(enum,types.TupleType):
+ size=len(enum)
+ elif hasattr(enum,'count'):
+ size=enum.count()
+ d["%s_size"%var]=size
+ d["%s_all"%var]=enum
+ index=0
+ ru=[]
+ for i in enum:
+ d["%s_value"%var]=i
+ d["%s_index"%var]=index
+ d["%s_first"%var]=index==0
+ d["%s_even"%var]=index%2
+ d["%s_odd"%var]=(index+1)%2
+ d["%s_last"%var]=index+1==size
+ if index%2:
+ d["%s_parity"%var]='odd'
+ else:
+ d["%s_parity"%var]='even'
+ if isinstance(i,types.DictType):
+ d.update(i)
+ else:
+ d[var]=i
+ ru.append(self.render_element(e,g_att,d))
+ index+=1
+ return "".join(ru)
+ else:
+ return "qweb: t-foreach %s not found."%expr
+ def render_tag_if(self,e,t_att,g_att,v):
+ if self.eval_bool(t_att["if"],v):
+ return self.render_element(e,g_att,v)
+ else:
+ return ""
+ def render_tag_call(self,e,t_att,g_att,v):
+ # TODO t-prefix
+ if t_att.has_key("import"):
+ d=v
+ else:
+ d=v.copy()
+ d[0]=self.render_element(e,g_att,d)
+ return self.render(t_att["call"],d)
+ def render_tag_set(self,e,t_att,g_att,v):
+ if t_att.has_key("eval"):
+ v[t_att["set"]]=self.eval_object(t_att["eval"],v)
+ else:
+ v[t_att["set"]]=self.render_element(e,g_att,v)
+ return ""
+
+#----------------------------------------------------------
+# QWeb HTML (+deprecated QWebFORM and QWebOLD)
+#----------------------------------------------------------
+class QWebURL:
+ """ URL helper
+ assert req.PATH_INFO== "/site/admin/page_edit"
+ u = QWebURL(root_path="/site/",req_path=req.PATH_INFO)
+ s=u.url2_href("user/login",{'a':'1'})
+ assert s=="../user/login?a=1"
+
+ """
+ def __init__(self, root_path="/", req_path="/",defpath="",defparam={}):
+ self.defpath=defpath
+ self.defparam=defparam
+ self.root_path=root_path
+ self.req_path=req_path
+ self.req_list=req_path.split("/")[:-1]
+ self.req_len=len(self.req_list)
+ def decode(self,s):
+ h={}
+ for k,v in cgi.parse_qsl(s,1):
+ h[k]=v
+ return h
+ def encode(self,h):
+ return urllib.urlencode(h.items())
+ def request(self,req):
+ return req.REQUEST
+ def copy(self,path=None,param=None):
+ npath=self.defpath
+ if path:
+ npath=path
+ nparam=self.defparam.copy()
+ if param:
+ nparam.update(param)
+ return QWebURL(self.root_path,self.req_path,npath,nparam)
+ def path(self,path=''):
+ if not path:
+ path=self.defpath
+ pl=(self.root_path+path).split('/')
+ i=0
+ for i in range(min(len(pl), self.req_len)):
+ if pl[i]!=self.req_list[i]:
+ break
+ else:
+ i+=1
+ dd=self.req_len-i
+ if dd<0:
+ dd=0
+ return '/'.join(['..']*dd+pl[i:])
+ def href(self,path='',arg={}):
+ p=self.path(path)
+ tmp=self.defparam.copy()
+ tmp.update(arg)
+ s=self.encode(tmp)
+ if len(s):
+ return p+"?"+s
+ else:
+ return p
+ def form(self,path='',arg={}):
+ p=self.path(path)
+ tmp=self.defparam.copy()
+ tmp.update(arg)
+ r=''.join(['<input type="hidden" name="%s" value="%s"/>'%(k,cgi.escape(str(v),1)) for k,v in tmp.items()])
+ return (p,r)
+class QWebField:
+ def __init__(self,name=None,default="",check=None):
+ self.name=name
+ self.default=default
+ self.check=check
+ # optional attributes
+ self.type=None
+ self.trim=1
+ self.required=1
+ self.cssvalid="form_valid"
+ self.cssinvalid="form_invalid"
+ # set by addfield
+ self.form=None
+ # set by processing
+ self.input=None
+ self.css=None
+ self.value=None
+ self.valid=None
+ self.invalid=None
+ self.validate(1)
+ def validate(self,val=1,update=1):
+ if val:
+ self.valid=1
+ self.invalid=0
+ self.css=self.cssvalid
+ else:
+ self.valid=0
+ self.invalid=1
+ self.css=self.cssinvalid
+ if update and self.form:
+ self.form.update()
+ def invalidate(self,update=1):
+ self.validate(0,update)
+class QWebForm:
+ class QWebFormF:
+ pass
+ def __init__(self,e=None,arg=None,default=None):
+ self.fields={}
+ # all fields have been submitted
+ self.submitted=False
+ self.missing=[]
+ # at least one field is invalid or missing
+ self.invalid=False
+ self.error=[]
+ # all fields have been submitted and are valid
+ self.valid=False
+ # fields under self.f for convenience
+ self.f=self.QWebFormF()
+ if e:
+ self.add_template(e)
+ # assume that the fields are done with the template
+ if default:
+ self.set_default(default,e==None)
+ if arg!=None:
+ self.process_input(arg)
+ def __getitem__(self,k):
+ return self.fields[k]
+ def set_default(self,default,add_missing=1):
+ for k,v in default.items():
+ if self.fields.has_key(k):
+ self.fields[k].default=str(v)
+ elif add_missing:
+ self.add_field(QWebField(k,v))
+ def add_field(self,f):
+ self.fields[f.name]=f
+ f.form=self
+ setattr(self.f,f.name,f)
+ def add_template(self,e):
+ att={}
+ for (an,av) in e.attributes.items():
+ an=str(an)
+ if an.startswith("t-"):
+ att[an[2:]]=av.encode("utf8")
+ for i in ["form-text", "form-password", "form-radio", "form-checkbox", "form-select","form-textarea"]:
+ if att.has_key(i):
+ name=att[i].split(".")[-1]
+ default=att.get("default","")
+ check=att.get("check",None)
+ f=QWebField(name,default,check)
+ if i=="form-textarea":
+ f.type="textarea"
+ f.trim=0
+ if i=="form-checkbox":
+ f.type="checkbox"
+ f.required=0
+ self.add_field(f)
+ for n in e.childNodes:
+ if n.nodeType==n.ELEMENT_NODE:
+ self.add_template(n)
+ def process_input(self,arg):
+ for f in self.fields.values():
+ if arg.has_key(f.name):
+ f.input=arg[f.name]
+ f.value=f.input
+ if f.trim:
+ f.input=f.input.strip()
+ f.validate(1,False)
+ if f.check==None:
+ continue
+ elif callable(f.check):
+ pass
+ elif isinstance(f.check,str):
+ v=f.check
+ if f.check=="email":
+ v=r"/^[^@#!& ]+@[A-Za-z0-9-][.A-Za-z0-9-]{0,64}\.[A-Za-z]{2,5}$/"
+ if f.check=="date":
+ v=r"/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/"
+ if not re.match(v[1:-1],f.input):
+ f.validate(0,False)
+ else:
+ f.value=f.default
+ self.update()
+ def validate_all(self,val=1):
+ for f in self.fields.values():
+ f.validate(val,0)
+ self.update()
+ def invalidate_all(self):
+ self.validate_all(0)
+ def update(self):
+ self.submitted=True
+ self.valid=True
+ self.errors=[]
+ for f in self.fields.values():
+ if f.required and f.input==None:
+ self.submitted=False
+ self.valid=False
+ self.missing.append(f.name)
+ if f.invalid:
+ self.valid=False
+ self.error.append(f.name)
+ # invalid have been submitted and
+ self.invalid=self.submitted and self.valid==False
+ def collect(self):
+ d={}
+ for f in self.fields.values():
+ d[f.name]=f.value
+ return d
+class QWebURLEval(QWebEval):
+ def __init__(self,data):
+ QWebEval.__init__(self,data)
+ def __getitem__(self,expr):
+ r=QWebEval.__getitem__(self,expr)
+ if isinstance(r,str):
+ return urllib.quote_plus(r)
+ else:
+ return r
+class QWebHtml(QWebXml):
+ """QWebHtml
+ QWebURL:
+ QWebField:
+ QWebForm:
+ QWebHtml:
+ an extended template engine, with a few utility class to easily produce
+ HTML, handle URLs and process forms, it adds the following magic attributes:
+
+ t-href t-action t-form-text t-form-password t-form-textarea t-form-radio
+ t-form-checkbox t-form-select t-option t-selected t-checked t-pager
+
+ # explication URL:
+ # v['tableurl']=QWebUrl({p=afdmin,saar=,orderby=,des=,mlink;meta_active=})
+ # t-href="tableurl?desc=1"
+ #
+ # explication FORM: t-if="form.valid()"
+ # Foreach i
+ # email: <input type="text" t-esc-name="i" t-esc-value="form[i].value" t-esc-class="form[i].css"/>
+ # <input type="radio" name="spamtype" t-esc-value="i" t-selected="i==form.f.spamtype.value"/>
+ # <option t-esc-value="cc" t-selected="cc==form.f.country.value"><t t-esc="cname"></option>
+ # Simple forms:
+ # <input t-form-text="form.email" t-check="email"/>
+ # <input t-form-password="form.email" t-check="email"/>
+ # <input t-form-radio="form.email" />
+ # <input t-form-checkbox="form.email" />
+ # <textarea t-form-textarea="form.email" t-check="email"/>
+ # <select t-form-select="form.email"/>
+ # <option t-value="1">
+ # <input t-form-radio="form.spamtype" t-value="1"/> Cars
+ # <input t-form-radio="form.spamtype" t-value="2"/> Sprt
+ """
+ # QWebForm from a template
+ def form(self,tname,arg=None,default=None):
+ form=QWebForm(self._t[tname],arg,default)
+ return form
+
+ # HTML Att
+ def eval_url(self,av,v):
+ s=QWebURLEval(v).eval_format(av)
+ a=s.split('?',1)
+ arg={}
+ if len(a)>1:
+ for k,v in cgi.parse_qsl(a[1],1):
+ arg[k]=v
+ b=a[0].split('/',1)
+ path=''
+ if len(b)>1:
+ path=b[1]
+ u=b[0]
+ return u,path,arg
+ def render_att_url_(self,e,an,av,v):
+ u,path,arg=self.eval_url(av,v)
+ if not isinstance(v.get(u,0),QWebURL):
+ out='qweb: missing url %r %r %r'%(u,path,arg)
+ else:
+ out=v[u].href(path,arg)
+ return ' %s="%s"'%(an[6:],cgi.escape(out,1))
+ def render_att_href(self,e,an,av,v):
+ return self.render_att_url_(e,"t-url-href",av,v)
+ def render_att_checked(self,e,an,av,v):
+ if self.eval_bool(av,v):
+ return ' %s="%s"'%(an[2:],an[2:])
+ else:
+ return ''
+ def render_att_selected(self,e,an,av,v):
+ return self.render_att_checked(e,an,av,v)
+
+ # HTML Tags forms
+ def render_tag_rawurl(self,e,t_att,g_att,v):
+ u,path,arg=self.eval_url(t_att["rawurl"],v)
+ return v[u].href(path,arg)
+ def render_tag_escurl(self,e,t_att,g_att,v):
+ u,path,arg=self.eval_url(t_att["escurl"],v)
+ return cgi.escape(v[u].href(path,arg))
+ def render_tag_action(self,e,t_att,g_att,v):
+ u,path,arg=self.eval_url(t_att["action"],v)
+ if not isinstance(v.get(u,0),QWebURL):
+ action,input=('qweb: missing url %r %r %r'%(u,path,arg),'')
+ else:
+ action,input=v[u].form(path,arg)
+ g_att+=' action="%s"'%action
+ return self.render_element(e,g_att,v,input)
+ def render_tag_form_text(self,e,t_att,g_att,v):
+ f=self.eval_object(t_att["form-text"],v)
+ g_att+=' type="text" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css)
+ return self.render_element(e,g_att,v)
+ def render_tag_form_password(self,e,t_att,g_att,v):
+ f=self.eval_object(t_att["form-password"],v)
+ g_att+=' type="password" name="%s" value="%s" class="%s"'%(f.name,cgi.escape(f.value,1),f.css)
+ return self.render_element(e,g_att,v)
+ def render_tag_form_textarea(self,e,t_att,g_att,v):
+ type="textarea"
+ f=self.eval_object(t_att["form-textarea"],v)
+ g_att+=' name="%s" class="%s"'%(f.name,f.css)
+ r="<%s%s>%s</%s>"%(type,g_att,cgi.escape(f.value,1),type)
+ return r
+ def render_tag_form_radio(self,e,t_att,g_att,v):
+ f=self.eval_object(t_att["form-radio"],v)
+ val=t_att["value"]
+ g_att+=' type="radio" name="%s" value="%s"'%(f.name,val)
+ if f.value==val:
+ g_att+=' checked="checked"'
+ return self.render_element(e,g_att,v)
+ def render_tag_form_checkbox(self,e,t_att,g_att,v):
+ f=self.eval_object(t_att["form-checkbox"],v)
+ val=t_att["value"]
+ g_att+=' type="checkbox" name="%s" value="%s"'%(f.name,val)
+ if f.value==val:
+ g_att+=' checked="checked"'
+ return self.render_element(e,g_att,v)
+ def render_tag_form_select(self,e,t_att,g_att,v):
+ f=self.eval_object(t_att["form-select"],v)
+ g_att+=' name="%s" class="%s"'%(f.name,f.css)
+ return self.render_element(e,g_att,v)
+ def render_tag_option(self,e,t_att,g_att,v):
+ f=self.eval_object(e.parentNode.getAttribute("t-form-select"),v)
+ val=t_att["option"]
+ g_att+=' value="%s"'%(val)
+ if f.value==val:
+ g_att+=' selected="selected"'
+ return self.render_element(e,g_att,v)
+
+ # HTML Tags others
+ def render_tag_pager(self,e,t_att,g_att,v):
+ pre=t_att["pager"]
+ total=int(self.eval_str(t_att["total"],v))
+ start=int(self.eval_str(t_att["start"],v))
+ step=int(self.eval_str(t_att.get("step","100"),v))
+ scope=int(self.eval_str(t_att.get("scope","5"),v))
+ # Compute Pager
+ p=pre+"_"
+ d={}
+ d[p+"tot_size"]=total
+ d[p+"tot_page"]=tot_page=total/step
+ d[p+"win_start0"]=total and start
+ d[p+"win_start1"]=total and start+1
+ d[p+"win_end0"]=max(0,min(start+step-1,total-1))
+ d[p+"win_end1"]=min(start+step,total)
+ d[p+"win_page0"]=win_page=start/step
+ d[p+"win_page1"]=win_page+1
+ d[p+"prev"]=(win_page!=0)
+ d[p+"prev_start"]=(win_page-1)*step
+ d[p+"next"]=(tot_page>=win_page+1)
+ d[p+"next_start"]=(win_page+1)*step
+ l=[]
+ begin=win_page-scope
+ end=win_page+scope
+ if begin<0:
+ end-=begin
+ if end>tot_page:
+ begin-=(end-tot_page)
+ i=max(0,begin)
+ while i<=min(end,tot_page) and total!=step:
+ l.append( { p+"page0":i, p+"page1":i+1, p+"start":i*step, p+"sel":(win_page==i) })
+ i+=1
+ d[p+"active"]=len(l)>1
+ d[p+"list"]=l
+ # Update v
+ v.update(d)
+ return ""
+
+#----------------------------------------------------------
+# QWeb Simple Controller
+#----------------------------------------------------------
+def qweb_control(self,jump='main',p=[]):
+ """ qweb_control(self,jump='main',p=[]):
+ A simple function to handle the controler part of your application. It
+ dispatch the control to the jump argument, while ensuring that prefix
+ function have been called.
+
+ qweb_control replace '/' to '_' and strip '_' from the jump argument.
+
+ name1
+ name1_name2
+ name1_name2_name3
+
+ """
+ jump=jump.replace('/','_').strip('_')
+ if not hasattr(self,jump):
+ return 0
+ done={}
+ todo=[]
+ while 1:
+ if jump!=None:
+ tmp=""
+ todo=[]
+ for i in jump.split("_"):
+ tmp+=i+"_";
+ if not done.has_key(tmp[:-1]):
+ todo.append(tmp[:-1])
+ jump=None
+ elif len(todo):
+ i=todo.pop(0)
+ done[i]=1
+ if hasattr(self,i):
+ f=getattr(self,i)
+ r=f(*p)
+ if isinstance(r,types.StringType):
+ jump=r
+ else:
+ break
+ return 1
+
+#----------------------------------------------------------
+# QWeb WSGI Request handler
+#----------------------------------------------------------
+class QWebSession(dict):
+ def __init__(self,environ,**kw):
+ dict.__init__(self)
+ default={
+ "path" : tempfile.gettempdir(),
+ "cookie_name" : "QWEBSID",
+ "cookie_lifetime" : 0,
+ "cookie_path" : '/',
+ "cookie_domain" : '',
+ "limit_cache" : 1,
+ "probability" : 0.01,
+ "maxlifetime" : 3600,
+ "disable" : 0,
+ }
+ for k,v in default.items():
+ setattr(self,'session_%s'%k,kw.get(k,v))
+ # Try to find session
+ self.session_found_cookie=0
+ self.session_found_url=0
+ self.session_found=0
+ self.session_orig=""
+ # Try cookie
+ c=Cookie.SimpleCookie()
+ c.load(environ.get('HTTP_COOKIE', ''))
+ if c.has_key(self.session_cookie_name):
+ sid=c[self.session_cookie_name].value[:64]
+ if re.match('[a-f0-9]+$',sid) and self.session_load(sid):
+ self.session_id=sid
+ self.session_found_cookie=1
+ self.session_found=1
+ # Try URL
+ if not self.session_found_cookie:
+ mo=re.search('&%s=([a-f0-9]+)'%self.session_cookie_name,environ.get('QUERY_STRING',''))
+ if mo and self.session_load(mo.group(1)):
+ self.session_id=mo.group(1)
+ self.session_found_url=1
+ self.session_found=1
+ # New session
+ if not self.session_found:
+ self.session_id='%032x'%random.randint(1,2**128)
+ self.session_trans_sid="&amp;%s=%s"%(self.session_cookie_name,self.session_id)
+ # Clean old session
+ if random.random() < self.session_probability:
+ self.session_clean()
+ def session_get_headers(self):
+ h=[]
+ if (not self.session_disable) and (len(self) or len(self.session_orig)):
+ self.session_save()
+ if not self.session_found_cookie:
+ c=Cookie.SimpleCookie()
+ c[self.session_cookie_name] = self.session_id
+ c[self.session_cookie_name]['path'] = self.session_cookie_path
+ if self.session_cookie_domain:
+ c[self.session_cookie_name]['domain'] = self.session_cookie_domain
+# if self.session_cookie_lifetime:
+# c[self.session_cookie_name]['expires'] = TODO date localtime or not, datetime.datetime(1970, 1, 1)
+ h.append(("Set-Cookie", c[self.session_cookie_name].OutputString()))
+ if self.session_limit_cache:
+ h.append(('Cache-Control','no-store, no-cache, must-revalidate, post-check=0, pre-check=0'))
+ h.append(('Expires','Thu, 19 Nov 1981 08:52:00 GMT'))
+ h.append(('Pragma','no-cache'))
+ return h
+ def session_load(self,sid):
+ fname=os.path.join(self.session_path,'qweb_sess_%s'%sid)
+ try:
+ orig=file(fname).read()
+ d=pickle.loads(orig)
+ except:
+ return
+ self.session_orig=orig
+ self.update(d)
+ return 1
+ def session_save(self):
+ if not os.path.isdir(self.session_path):
+ os.makedirs(self.session_path)
+ fname=os.path.join(self.session_path,'qweb_sess_%s'%self.session_id)
+ try:
+ oldtime=os.path.getmtime(fname)
+ except OSError,IOError:
+ oldtime=0
+ dump=pickle.dumps(self.copy())
+ if (dump != self.session_orig) or (time.time() > oldtime+self.session_maxlifetime/4):
+ tmpname=os.path.join(self.session_path,'qweb_sess_%s_%x'%(self.session_id,random.randint(1,2**32)))
+ f=file(tmpname,'wb')
+ f.write(dump)
+ f.close()
+ if sys.platform=='win32' and os.path.isfile(fname):
+ os.remove(fname)
+ os.rename(tmpname,fname)
+ def session_clean(self):
+ t=time.time()
+ try:
+ for i in [os.path.join(self.session_path,i) for i in os.listdir(self.session_path) if i.startswith('qweb_sess_')]:
+ if (t > os.path.getmtime(i)+self.session_maxlifetime):
+ os.unlink(i)
+ except OSError,IOError:
+ pass
+class QWebSessionMem(QWebSession):
+ def session_load(self,sid):
+ global _qweb_sessions
+ if not "_qweb_sessions" in globals():
+ _qweb_sessions={}
+ if _qweb_sessions.has_key(sid):
+ self.session_orig=_qweb_sessions[sid]
+ self.update(self.session_orig)
+ return 1
+ def session_save(self):
+ global _qweb_sessions
+ if not "_qweb_sessions" in globals():
+ _qweb_sessions={}
+ _qweb_sessions[self.session_id]=self.copy()
+class QWebSessionService:
+ def __init__(self, wsgiapp, url_rewrite=0):
+ self.wsgiapp=wsgiapp
+ self.url_rewrite_tags="a=href,area=href,frame=src,form=,fieldset="
+ def __call__(self, environ, start_response):
+ # TODO
+ # use QWebSession to provide environ["qweb.session"]
+ return self.wsgiapp(environ,start_response)
+class QWebDict(dict):
+ def __init__(self,*p):
+ dict.__init__(self,*p)
+ def __getitem__(self,key):
+ return self.get(key,"")
+ def int(self,key):
+ try:
+ return int(self.get(key,"0"))
+ except ValueError:
+ return 0
+class QWebListDict(dict):
+ def __init__(self,*p):
+ dict.__init__(self,*p)
+ def __getitem__(self,key):
+ return self.get(key,[])
+ def appendlist(self,key,val):
+ if self.has_key(key):
+ self[key].append(val)
+ else:
+ self[key]=[val]
+ def get_qwebdict(self):
+ d=QWebDict()
+ for k,v in self.items():
+ d[k]=v[-1]
+ return d
+class QWebRequest:
+ """QWebRequest a WSGI request handler.
+
+ QWebRequest is a WSGI request handler that feature GET, POST and POST
+ multipart methods, handles cookies and headers and provide a dict-like
+ SESSION Object (either on the filesystem or in memory).
+
+ It is constructed with the environ and start_response WSGI arguments:
+
+ req=qweb.QWebRequest(environ, start_response)
+
+ req has the folowing attributes :
+
+ req.environ standard WSGI dict (CGI and wsgi ones)
+
+ Some CGI vars as attributes from environ for convenience:
+
+ req.SCRIPT_NAME
+ req.PATH_INFO
+ req.REQUEST_URI
+
+ Some computed value (also for convenience)
+
+ req.FULL_URL full URL recontructed (http://host/query)
+ req.FULL_PATH (URL path before ?querystring)
+
+ Dict constructed from querystring and POST datas, PHP-like.
+
+ req.GET contains GET vars
+ req.POST contains POST vars
+ req.REQUEST contains merge of GET and POST
+ req.FILES contains uploaded files
+ req.GET_LIST req.POST_LIST req.REQUEST_LIST req.FILES_LIST multiple arguments versions
+ req.debug() returns an HTML dump of those vars
+
+ A dict-like session object.
+
+ req.SESSION the session start when the dict is not empty.
+
+ Attribute for handling the response
+
+ req.response_headers dict-like to set headers
+ req.response_cookies a SimpleCookie to set cookies
+ req.response_status a string to set the status like '200 OK'
+
+ req.write() to write to the buffer
+
+ req itselfs is an iterable object with the buffer, it will also also call
+ start_response automatically before returning anything via the iterator.
+
+ To make it short, it means that you may use
+
+ return req
+
+ at the end of your request handling to return the reponse to any WSGI
+ application server.
+ """
+ #
+ # This class contains part ripped from colubrid (with the permission of
+ # mitsuhiko) see http://wsgiarea.pocoo.org/colubrid/
+ #
+ # - the class HttpHeaders
+ # - the method load_post_data (tuned version)
+ #
+ class HttpHeaders(object):
+ def __init__(self):
+ self.data = [('Content-Type', 'text/html')]
+ def __setitem__(self, key, value):
+ self.set(key, value)
+ def __delitem__(self, key):
+ self.remove(key)
+ def __contains__(self, key):
+ key = key.lower()
+ for k, v in self.data:
+ if k.lower() == key:
+ return True
+ return False
+ def add(self, key, value):
+ self.data.append((key, value))
+ def remove(self, key, count=-1):
+ removed = 0
+ data = []
+ for _key, _value in self.data:
+ if _key.lower() != key.lower():
+ if count > -1:
+ if removed >= count:
+ break
+ else:
+ removed += 1
+ data.append((_key, _value))
+ self.data = data
+ def clear(self):
+ self.data = []
+ def set(self, key, value):
+ self.remove(key)
+ self.add(key, value)
+ def get(self, key=False, httpformat=False):
+ if not key:
+ result = self.data
+ else:
+ result = []
+ for _key, _value in self.data:
+ if _key.lower() == key.lower():
+ result.append((_key, _value))
+ if httpformat:
+ return '\n'.join(['%s: %s' % item for item in result])
+ return result
+ def load_post_data(self,environ,POST,FILES):
+ length = int(environ['CONTENT_LENGTH'])
+ DATA = environ['wsgi.input'].read(length)
+ if environ.get('CONTENT_TYPE', '').startswith('multipart'):
+ lines = ['Content-Type: %s' % environ.get('CONTENT_TYPE', '')]
+ for key, value in environ.items():
+ if key.startswith('HTTP_'):
+ lines.append('%s: %s' % (key, value))
+ raw = '\r\n'.join(lines) + '\r\n\r\n' + DATA
+ msg = email.message_from_string(raw)
+ for sub in msg.get_payload():
+ if not isinstance(sub, email.Message.Message):
+ continue
+ name_dict = cgi.parse_header(sub['Content-Disposition'])[1]
+ if 'filename' in name_dict:
+ # Nested MIME Messages are not supported'
+ if type([]) == type(sub.get_payload()):
+ continue
+ if not name_dict['filename'].strip():
+ continue
+ filename = name_dict['filename']
+ # why not keep all the filename? because IE always send 'C:\documents and settings\blub\blub.png'
+ filename = filename[filename.rfind('\\') + 1:]
+ if 'Content-Type' in sub:
+ content_type = sub['Content-Type']
+ else:
+ content_type = None
+ s = { "name":filename, "type":content_type, "data":sub.get_payload() }
+ FILES.appendlist(name_dict['name'], s)
+ else:
+ POST.appendlist(name_dict['name'], sub.get_payload())
+ else:
+ POST.update(cgi.parse_qs(DATA,keep_blank_values=1))
+ return DATA
+
+ def __init__(self,environ,start_response,session=QWebSession):
+ self.environ=environ
+ self.start_response=start_response
+ self.buffer=[]
+
+ self.SCRIPT_NAME = environ.get('SCRIPT_NAME', '')
+ self.PATH_INFO = environ.get('PATH_INFO', '')
+ # extensions:
+ self.FULL_URL = environ['FULL_URL'] = self.get_full_url(environ)
+ # REQUEST_URI is optional, fake it if absent
+ if not environ.has_key("REQUEST_URI"):
+ environ["REQUEST_URI"]=urllib.quote(self.SCRIPT_NAME+self.PATH_INFO)
+ if environ.get('QUERY_STRING'):
+ environ["REQUEST_URI"]+='?'+environ['QUERY_STRING']
+ self.REQUEST_URI = environ["REQUEST_URI"]
+ # full quote url path before the ?
+ self.FULL_PATH = environ['FULL_PATH'] = self.REQUEST_URI.split('?')[0]
+
+ self.request_cookies=Cookie.SimpleCookie()
+ self.request_cookies.load(environ.get('HTTP_COOKIE', ''))
+
+ self.response_started=False
+ self.response_gzencode=False
+ self.response_cookies=Cookie.SimpleCookie()
+ # to delete a cookie use: c[key]['expires'] = datetime.datetime(1970, 1, 1)
+ self.response_headers=self.HttpHeaders()
+ self.response_status="200 OK"
+
+ self.php=None
+ if self.environ.has_key("php"):
+ self.php=environ["php"]
+ self.SESSION=self.php._SESSION
+ self.GET=self.php._GET
+ self.POST=self.php._POST
+ self.REQUEST=self.php._ARG
+ self.FILES=self.php._FILES
+ else:
+ if isinstance(session,QWebSession):
+ self.SESSION=session
+ elif session:
+ self.SESSION=session(environ)
+ else:
+ self.SESSION=None
+ self.GET_LIST=QWebListDict(cgi.parse_qs(environ.get('QUERY_STRING', ''),keep_blank_values=1))
+ self.POST_LIST=QWebListDict()
+ self.FILES_LIST=QWebListDict()
+ self.REQUEST_LIST=QWebListDict(self.GET_LIST)
+ if environ['REQUEST_METHOD'] == 'POST':
+ self.DATA=self.load_post_data(environ,self.POST_LIST,self.FILES_LIST)
+ self.REQUEST_LIST.update(self.POST_LIST)
+ self.GET=self.GET_LIST.get_qwebdict()
+ self.POST=self.POST_LIST.get_qwebdict()
+ self.FILES=self.FILES_LIST.get_qwebdict()
+ self.REQUEST=self.REQUEST_LIST.get_qwebdict()
+ def get_full_url(environ):
+ # taken from PEP 333
+ if 'FULL_URL' in environ:
+ return environ['FULL_URL']
+ url = environ['wsgi.url_scheme']+'://'
+ if environ.get('HTTP_HOST'):
+ url += environ['HTTP_HOST']
+ else:
+ url += environ['SERVER_NAME']
+ if environ['wsgi.url_scheme'] == 'https':
+ if environ['SERVER_PORT'] != '443':
+ url += ':' + environ['SERVER_PORT']
+ else:
+ if environ['SERVER_PORT'] != '80':
+ url += ':' + environ['SERVER_PORT']
+ if environ.has_key('REQUEST_URI'):
+ url += environ['REQUEST_URI']
+ else:
+ url += urllib.quote(environ.get('SCRIPT_NAME', ''))
+ url += urllib.quote(environ.get('PATH_INFO', ''))
+ if environ.get('QUERY_STRING'):
+ url += '?' + environ['QUERY_STRING']
+ return url
+ get_full_url=staticmethod(get_full_url)
+ def save_files(self):
+ for k,v in self.FILES.items():
+ if not v.has_key("tmp_file"):
+ f=tempfile.NamedTemporaryFile()
+ f.write(v["data"])
+ f.flush()
+ v["tmp_file"]=f
+ v["tmp_name"]=f.name
+ def debug(self):
+ body=''
+ for name,d in [
+ ("GET",self.GET), ("POST",self.POST), ("REQUEST",self.REQUEST), ("FILES",self.FILES),
+ ("GET_LIST",self.GET_LIST), ("POST_LIST",self.POST_LIST), ("REQUEST_LIST",self.REQUEST_LIST), ("FILES_LIST",self.FILES_LIST),
+ ("SESSION",self.SESSION), ("environ",self.environ),
+ ]:
+ body+='<table border="1" width="100%" align="center">\n'
+ body+='<tr><th colspan="2" align="center">%s</th></tr>\n'%name
+ keys=d.keys()
+ keys.sort()
+ body+=''.join(['<tr><td>%s</td><td>%s</td></tr>\n'%(k,cgi.escape(repr(d[k]))) for k in keys])
+ body+='</table><br><br>\n\n'
+ return body
+ def write(self,s):
+ self.buffer.append(s)
+ def echo(self,*s):
+ self.buffer.extend([str(i) for i in s])
+ def response(self):
+ if not self.response_started:
+ if not self.php:
+ for k,v in self.FILES.items():
+ if v.has_key("tmp_file"):
+ try:
+ v["tmp_file"].close()
+ except OSError:
+ pass
+ if self.response_gzencode and self.environ.get('HTTP_ACCEPT_ENCODING','').find('gzip')!=-1:
+ zbuf=StringIO.StringIO()
+ zfile=gzip.GzipFile(mode='wb', fileobj=zbuf)
+ zfile.write(''.join(self.buffer))
+ zfile.close()
+ zbuf=zbuf.getvalue()
+ self.buffer=[zbuf]
+ self.response_headers['Content-Encoding']="gzip"
+ self.response_headers['Content-Length']=str(len(zbuf))
+ headers = self.response_headers.get()
+ if isinstance(self.SESSION, QWebSession):
+ headers.extend(self.SESSION.session_get_headers())
+ headers.extend([('Set-Cookie', self.response_cookies[i].OutputString()) for i in self.response_cookies])
+ self.start_response(self.response_status, headers)
+ self.response_started=True
+ return self.buffer
+ def __iter__(self):
+ return self.response().__iter__()
+ def http_redirect(self,url,permanent=1):
+ if permanent:
+ self.response_status="301 Moved Permanently"
+ else:
+ self.response_status="302 Found"
+ self.response_headers["Location"]=url
+ def http_404(self,msg="<h1>404 Not Found</h1>"):
+ self.response_status="404 Not Found"
+ if msg:
+ self.write(msg)
+ def http_download(self,fname,fstr,partial=0):
+# allow fstr to be a file-like object
+# if parital:
+# say accept ranages
+# parse range headers...
+# if range:
+# header("HTTP/1.1 206 Partial Content");
+# header("Content-Range: bytes $offset-".($fsize-1)."/".$fsize);
+# header("Content-Length: ".($fsize-$offset));
+# fseek($fd,$offset);
+# else:
+ self.response_headers["Content-Type"]="application/octet-stream"
+ self.response_headers["Content-Disposition"]="attachment; filename=\"%s\""%fname
+ self.response_headers["Content-Transfer-Encoding"]="binary"
+ self.response_headers["Content-Length"]="%d"%len(fstr)
+ self.write(fstr)
+
+#----------------------------------------------------------
+# QWeb WSGI HTTP Server to run any WSGI app
+# autorun, run an app as FCGI or CGI otherwise launch the server
+#----------------------------------------------------------
+class QWebWSGIHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+ def log_message(self,*p):
+ if self.server.log:
+ return BaseHTTPServer.BaseHTTPRequestHandler.log_message(self,*p)
+ def address_string(self):
+ return self.client_address[0]
+ def start_response(self,status,headers):
+ l=status.split(' ',1)
+ self.send_response(int(l[0]),l[1])
+ ctype_sent=0
+ for i in headers:
+ if i[0].lower()=="content-type":
+ ctype_sent=1
+ self.send_header(*i)
+ if not ctype_sent:
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+ return self.write
+ def write(self,data):
+ try:
+ self.wfile.write(data)
+ except (socket.error, socket.timeout),e:
+ print e
+ def bufferon(self):
+ if not getattr(self,'wfile_buf',0):
+ self.wfile_buf=1
+ self.wfile_bak=self.wfile
+ self.wfile=StringIO.StringIO()
+ def bufferoff(self):
+ if self.wfile_buf:
+ buf=self.wfile
+ self.wfile=self.wfile_bak
+ self.write(buf.getvalue())
+ self.wfile_buf=0
+ def serve(self,type):
+ path_info, parameters, query = urlparse.urlparse(self.path)[2:5]
+ environ = {
+ 'wsgi.version': (1,0),
+ 'wsgi.url_scheme': 'http',
+ 'wsgi.input': self.rfile,
+ 'wsgi.errors': sys.stderr,
+ 'wsgi.multithread': 0,
+ 'wsgi.multiprocess': 0,
+ 'wsgi.run_once': 0,
+ 'REQUEST_METHOD': self.command,
+ 'SCRIPT_NAME': '',
+ 'QUERY_STRING': query,
+ 'CONTENT_TYPE': self.headers.get('Content-Type', ''),
+ 'CONTENT_LENGTH': self.headers.get('Content-Length', ''),
+ 'REMOTE_ADDR': self.client_address[0],
+ 'REMOTE_PORT': str(self.client_address[1]),
+ 'SERVER_NAME': self.server.server_address[0],
+ 'SERVER_PORT': str(self.server.server_address[1]),
+ 'SERVER_PROTOCOL': self.request_version,
+ # extention
+ 'FULL_PATH': self.path,
+ 'qweb.mode': 'standalone',
+ }
+ if path_info:
+ environ['PATH_INFO'] = urllib.unquote(path_info)
+ for key, value in self.headers.items():
+ environ['HTTP_' + key.upper().replace('-', '_')] = value
+ # Hack to avoid may TCP packets
+ self.bufferon()
+ appiter=self.server.wsgiapp(environ, self.start_response)
+ for data in appiter:
+ self.write(data)
+ self.bufferoff()
+ self.bufferoff()
+ def do_GET(self):
+ self.serve('GET')
+ def do_POST(self):
+ self.serve('GET')
+class QWebWSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
+ """ QWebWSGIServer
+ qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1)
+ A WSGI HTTP server threaded or not and a function to automatically run your
+ app according to the environement (either standalone, CGI or FastCGI).
+
+ This feature is called QWeb autorun. If you want to To use it on your
+ application use the following lines at the end of the main application
+ python file:
+
+ if __name__ == '__main__':
+ qweb.qweb_wsgi_autorun(your_wsgi_app)
+
+ this function will select the approriate running mode according to the
+ calling environement (http-server, FastCGI or CGI).
+ """
+ def __init__(self, wsgiapp, ip, port, threaded=1, log=1):
+ BaseHTTPServer.HTTPServer.__init__(self, (ip, port), QWebWSGIHandler)
+ self.wsgiapp = wsgiapp
+ self.threaded = threaded
+ self.log = log
+ def process_request(self,*p):
+ if self.threaded:
+ return SocketServer.ThreadingMixIn.process_request(self,*p)
+ else:
+ return BaseHTTPServer.HTTPServer.process_request(self,*p)
+def qweb_wsgi_autorun(wsgiapp,ip='127.0.0.1',port=8080,threaded=1,log=1,callback_ready=None):
+ if sys.platform=='win32':
+ fcgi=0
+ else:
+ fcgi=1
+ sock = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ sock.getpeername()
+ except socket.error, e:
+ if e[0] == errno.ENOTSOCK:
+ fcgi=0
+ if fcgi or os.environ.has_key('REQUEST_METHOD'):
+ import fcgi
+ fcgi.WSGIServer(wsgiapp,multithreaded=False).run()
+ else:
+ if log:
+ print 'Serving on %s:%d'%(ip,port)
+ s=QWebWSGIServer(wsgiapp,ip=ip,port=port,threaded=threaded,log=log)
+ if callback_ready:
+ callback_ready()
+ try:
+ s.serve_forever()
+ except KeyboardInterrupt,e:
+ sys.excepthook(*sys.exc_info())
+
+#----------------------------------------------------------
+# Qweb Documentation
+#----------------------------------------------------------
+def qweb_doc():
+ body=__doc__
+ for i in [QWebXml ,QWebHtml ,QWebForm ,QWebURL ,qweb_control ,QWebRequest ,QWebSession ,QWebWSGIServer ,qweb_wsgi_autorun]:
+ n=i.__name__
+ d=i.__doc__
+ body+='\n\n%s\n%s\n\n%s'%(n,'-'*len(n),d)
+ return body
+
+ print qweb_doc()
+
+#
diff --git a/tools/ajaxterm/sarissa.js b/tools/ajaxterm/sarissa.js
new file mode 100644
index 0000000000..6d13aa2e23
--- /dev/null
+++ b/tools/ajaxterm/sarissa.js
@@ -0,0 +1,647 @@
+/**
+ * ====================================================================
+ * About
+ * ====================================================================
+ * Sarissa is an ECMAScript library acting as a cross-browser wrapper for native XML APIs.
+ * The library supports Gecko based browsers like Mozilla and Firefox,
+ * Internet Explorer (5.5+ with MSXML3.0+), Konqueror, Safari and a little of Opera
+ * @version 0.9.6.1
+ * @author: Manos Batsis, mailto: mbatsis at users full stop sourceforge full stop net
+ * ====================================================================
+ * Licence
+ * ====================================================================
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * the GNU Lesser General Public License version 2.1 as published by
+ * the Free Software Foundation (your choice between the two).
+ *
+ * This program 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 or GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * or GNU Lesser General Public License along with this program; if not,
+ * write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * or visit http://www.gnu.org
+ *
+ */
+/**
+ * <p>Sarissa is a utility class. Provides "static" methods for DOMDocument and
+ * XMLHTTP objects, DOM Node serializatrion to XML strings and other goodies.</p>
+ * @constructor
+ */
+function Sarissa(){};
+/** @private */
+Sarissa.PARSED_OK = "Document contains no parsing errors";
+/**
+ * Tells you whether transformNode and transformNodeToObject are available. This functionality
+ * is contained in sarissa_ieemu_xslt.js and is deprecated. If you want to control XSLT transformations
+ * use the XSLTProcessor
+ * @deprecated
+ * @type boolean
+ */
+Sarissa.IS_ENABLED_TRANSFORM_NODE = false;
+/**
+ * tells you whether XMLHttpRequest (or equivalent) is available
+ * @type boolean
+ */
+Sarissa.IS_ENABLED_XMLHTTP = false;
+/**
+ * tells you whether selectNodes/selectSingleNode is available
+ * @type boolean
+ */
+Sarissa.IS_ENABLED_SELECT_NODES = false;
+var _sarissa_iNsCounter = 0;
+var _SARISSA_IEPREFIX4XSLPARAM = "";
+var _SARISSA_HAS_DOM_IMPLEMENTATION = document.implementation && true;
+var _SARISSA_HAS_DOM_CREATE_DOCUMENT = _SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.createDocument;
+var _SARISSA_HAS_DOM_FEATURE = _SARISSA_HAS_DOM_IMPLEMENTATION && document.implementation.hasFeature;
+var _SARISSA_IS_MOZ = _SARISSA_HAS_DOM_CREATE_DOCUMENT && _SARISSA_HAS_DOM_FEATURE;
+var _SARISSA_IS_SAFARI = (navigator.userAgent && navigator.vendor && (navigator.userAgent.toLowerCase().indexOf("applewebkit") != -1 || navigator.vendor.indexOf("Apple") != -1));
+var _SARISSA_IS_IE = document.all && window.ActiveXObject && navigator.userAgent.toLowerCase().indexOf("msie") > -1 && navigator.userAgent.toLowerCase().indexOf("opera") == -1;
+if(!window.Node || !window.Node.ELEMENT_NODE){
+ var Node = {ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12};
+};
+
+// IE initialization
+if(_SARISSA_IS_IE){
+ // for XSLT parameter names, prefix needed by IE
+ _SARISSA_IEPREFIX4XSLPARAM = "xsl:";
+ // used to store the most recent ProgID available out of the above
+ var _SARISSA_DOM_PROGID = "";
+ var _SARISSA_XMLHTTP_PROGID = "";
+ /**
+ * Called when the Sarissa_xx.js file is parsed, to pick most recent
+ * ProgIDs for IE, then gets destroyed.
+ * @param idList an array of MSXML PROGIDs from which the most recent will be picked for a given object
+ * @param enabledList an array of arrays where each array has two items; the index of the PROGID for which a certain feature is enabled
+ */
+ pickRecentProgID = function (idList, enabledList){
+ // found progID flag
+ var bFound = false;
+ for(var i=0; i < idList.length && !bFound; i++){
+ try{
+ var oDoc = new ActiveXObject(idList[i]);
+ o2Store = idList[i];
+ bFound = true;
+ for(var j=0;j<enabledList.length;j++)
+ if(i <= enabledList[j][1])
+ Sarissa["IS_ENABLED_"+enabledList[j][0]] = true;
+ }catch (objException){
+ // trap; try next progID
+ };
+ };
+ if (!bFound)
+ throw "Could not retreive a valid progID of Class: " + idList[idList.length-1]+". (original exception: "+e+")";
+ idList = null;
+ return o2Store;
+ };
+ // pick best available MSXML progIDs
+ _SARISSA_DOM_PROGID = pickRecentProgID(["Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"], [["SELECT_NODES", 2],["TRANSFORM_NODE", 2]]);
+ _SARISSA_XMLHTTP_PROGID = pickRecentProgID(["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"], [["XMLHTTP", 4]]);
+ _SARISSA_THREADEDDOM_PROGID = pickRecentProgID(["Msxml2.FreeThreadedDOMDocument.5.0", "MSXML2.FreeThreadedDOMDocument.4.0", "MSXML2.FreeThreadedDOMDocument.3.0"]);
+ _SARISSA_XSLTEMPLATE_PROGID = pickRecentProgID(["Msxml2.XSLTemplate.5.0", "Msxml2.XSLTemplate.4.0", "MSXML2.XSLTemplate.3.0"], [["XSLTPROC", 2]]);
+ // we dont need this anymore
+ pickRecentProgID = null;
+ //============================================
+ // Factory methods (IE)
+ //============================================
+ // see non-IE version
+ Sarissa.getDomDocument = function(sUri, sName){
+ var oDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
+ // if a root tag name was provided, we need to load it in the DOM
+ // object
+ if (sName){
+ // if needed, create an artifical namespace prefix the way Moz
+ // does
+ if (sUri){
+ oDoc.loadXML("<a" + _sarissa_iNsCounter + ":" + sName + " xmlns:a" + _sarissa_iNsCounter + "=\"" + sUri + "\" />");
+ // don't use the same prefix again
+ ++_sarissa_iNsCounter;
+ }
+ else
+ oDoc.loadXML("<" + sName + "/>");
+ };
+ return oDoc;
+ };
+ // see non-IE version
+ Sarissa.getParseErrorText = function (oDoc) {
+ var parseErrorText = Sarissa.PARSED_OK;
+ if(oDoc.parseError != 0){
+ parseErrorText = "XML Parsing Error: " + oDoc.parseError.reason +
+ "\nLocation: " + oDoc.parseError.url +
+ "\nLine Number " + oDoc.parseError.line + ", Column " +
+ oDoc.parseError.linepos +
+ ":\n" + oDoc.parseError.srcText +
+ "\n";
+ for(var i = 0; i < oDoc.parseError.linepos;i++){
+ parseErrorText += "-";
+ };
+ parseErrorText += "^\n";
+ };
+ return parseErrorText;
+ };
+ // see non-IE version
+ Sarissa.setXpathNamespaces = function(oDoc, sNsSet) {
+ oDoc.setProperty("SelectionLanguage", "XPath");
+ oDoc.setProperty("SelectionNamespaces", sNsSet);
+ };
+ /**
+ * Basic implementation of Mozilla's XSLTProcessor for IE.
+ * Reuses the same XSLT stylesheet for multiple transforms
+ * @constructor
+ */
+ XSLTProcessor = function(){
+ this.template = new ActiveXObject(_SARISSA_XSLTEMPLATE_PROGID);
+ this.processor = null;
+ };
+ /**
+ * Impoprts the given XSLT DOM and compiles it to a reusable transform
+ * @argument xslDoc The XSLT DOMDocument to import
+ */
+ XSLTProcessor.prototype.importStylesheet = function(xslDoc){
+ // convert stylesheet to free threaded
+ var converted = new ActiveXObject(_SARISSA_THREADEDDOM_PROGID);
+ converted.loadXML(xslDoc.xml);
+ this.template.stylesheet = converted;
+ this.processor = this.template.createProcessor();
+ // (re)set default param values
+ this.paramsSet = new Array();
+ };
+ /**
+ * Transform the given XML DOM
+ * @argument sourceDoc The XML DOMDocument to transform
+ * @return The transformation result as a DOM Document
+ */
+ XSLTProcessor.prototype.transformToDocument = function(sourceDoc){
+ this.processor.input = sourceDoc;
+ var outDoc = new ActiveXObject(_SARISSA_DOM_PROGID);
+ this.processor.output = outDoc;
+ this.processor.transform();
+ return outDoc;
+ };
+ /**
+ * Set global XSLT parameter of the imported stylesheet
+ * @argument nsURI The parameter namespace URI
+ * @argument name The parameter base name
+ * @argument value The new parameter value
+ */
+ XSLTProcessor.prototype.setParameter = function(nsURI, name, value){
+ /* nsURI is optional but cannot be null */
+ if(nsURI){
+ this.processor.addParameter(name, value, nsURI);
+ }else{
+ this.processor.addParameter(name, value);
+ };
+ /* update updated params for getParameter */
+ if(!this.paramsSet[""+nsURI]){
+ this.paramsSet[""+nsURI] = new Array();
+ };
+ this.paramsSet[""+nsURI][name] = value;
+ };
+ /**
+ * Gets a parameter if previously set by setParameter. Returns null
+ * otherwise
+ * @argument name The parameter base name
+ * @argument value The new parameter value
+ * @return The parameter value if reviously set by setParameter, null otherwise
+ */
+ XSLTProcessor.prototype.getParameter = function(nsURI, name){
+ nsURI = nsURI || "";
+ if(nsURI in this.paramsSet && name in this.paramsSet[nsURI]){
+ return this.paramsSet[nsURI][name];
+ }else{
+ return null;
+ };
+ };
+}
+else{ /* end IE initialization, try to deal with real browsers now ;-) */
+ if(_SARISSA_HAS_DOM_CREATE_DOCUMENT){
+ /**
+ * <p>Ensures the document was loaded correctly, otherwise sets the
+ * parseError to -1 to indicate something went wrong. Internal use</p>
+ * @private
+ */
+ Sarissa.__handleLoad__ = function(oDoc){
+ if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror")
+ oDoc.parseError = -1;
+ Sarissa.__setReadyState__(oDoc, 4);
+ };
+ /**
+ * <p>Attached by an event handler to the load event. Internal use.</p>
+ * @private
+ */
+ _sarissa_XMLDocument_onload = function(){
+ Sarissa.__handleLoad__(this);
+ };
+ /**
+ * <p>Sets the readyState property of the given DOM Document object.
+ * Internal use.</p>
+ * @private
+ * @argument oDoc the DOM Document object to fire the
+ * readystatechange event
+ * @argument iReadyState the number to change the readystate property to
+ */
+ Sarissa.__setReadyState__ = function(oDoc, iReadyState){
+ oDoc.readyState = iReadyState;
+ if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function")
+ oDoc.onreadystatechange();
+ };
+ Sarissa.getDomDocument = function(sUri, sName){
+ var oDoc = document.implementation.createDocument(sUri?sUri:"", sName?sName:"", null);
+ oDoc.addEventListener("load", _sarissa_XMLDocument_onload, false);
+ return oDoc;
+ };
+ if(false && window.XMLDocument){
+ /**
+ * <p>Emulate IE's onreadystatechange attribute</p>
+ */
+ XMLDocument.prototype.onreadystatechange = null;
+ /**
+ * <p>Emulates IE's readyState property, which always gives an integer from 0 to 4:</p>
+ * <ul><li>1 == LOADING,</li>
+ * <li>2 == LOADED,</li>
+ * <li>3 == INTERACTIVE,</li>
+ * <li>4 == COMPLETED</li></ul>
+ */
+ XMLDocument.prototype.readyState = 0;
+ /**
+ * <p>Emulate IE's parseError attribute</p>
+ */
+ XMLDocument.prototype.parseError = 0;
+
+ // NOTE: setting async to false will only work with documents
+ // called over HTTP (meaning a server), not the local file system,
+ // unless you are using Moz 1.4+.
+ // BTW the try>catch block is for 1.4; I haven't found a way to check if
+ // the property is implemented without
+ // causing an error and I dont want to use user agent stuff for that...
+ var _SARISSA_SYNC_NON_IMPLEMENTED = false;// ("async" in XMLDocument.prototype) ? false: true;
+ /**
+ * <p>Keeps a handle to the original load() method. Internal use and only
+ * if Mozilla version is lower than 1.4</p>
+ * @private
+ */
+ XMLDocument.prototype._sarissa_load = XMLDocument.prototype.load;
+
+ /**
+ * <p>Overrides the original load method to provide synchronous loading for
+ * Mozilla versions prior to 1.4, using an XMLHttpRequest object (if
+ * async is set to false)</p>
+ * @returns the DOM Object as it was before the load() call (may be empty)
+ */
+ XMLDocument.prototype.load = function(sURI) {
+ var oDoc = document.implementation.createDocument("", "", null);
+ Sarissa.copyChildNodes(this, oDoc);
+ this.parseError = 0;
+ Sarissa.__setReadyState__(this, 1);
+ try {
+ if(this.async == false && _SARISSA_SYNC_NON_IMPLEMENTED) {
+ var tmp = new XMLHttpRequest();
+ tmp.open("GET", sURI, false);
+ tmp.send(null);
+ Sarissa.__setReadyState__(this, 2);
+ Sarissa.copyChildNodes(tmp.responseXML, this);
+ Sarissa.__setReadyState__(this, 3);
+ }
+ else {
+ this._sarissa_load(sURI);
+ };
+ }
+ catch (objException) {
+ this.parseError = -1;
+ }
+ finally {
+ if(this.async == false){
+ Sarissa.__handleLoad__(this);
+ };
+ };
+ return oDoc;
+ };
+
+
+ }//if(window.XMLDocument)
+ else if(document.implementation && document.implementation.hasFeature && document.implementation.hasFeature('LS', '3.0')){
+ Document.prototype.async = true;
+ Document.prototype.onreadystatechange = null;
+ Document.prototype.parseError = 0;
+ Document.prototype.load = function(sURI) {
+ var parser = document.implementation.createLSParser(this.async ? document.implementation.MODE_ASYNCHRONOUS : document.implementation.MODE_SYNCHRONOUS, null);
+ if(this.async){
+ var self = this;
+ parser.addEventListener("load",
+ function(e) {
+ self.readyState = 4;
+ Sarissa.copyChildNodes(e.newDocument, self.documentElement, false);
+ self.onreadystatechange.call();
+ },
+ false);
+ };
+ try {
+ var oDoc = parser.parseURI(sURI);
+ }
+ catch(e){
+ this.parseError = -1;
+ };
+ if(!this.async)
+ Sarissa.copyChildNodes(oDoc, this.documentElement, false);
+ return oDoc;
+ };
+ /**
+ * <p>Factory method to obtain a new DOM Document object</p>
+ * @argument sUri the namespace of the root node (if any)
+ * @argument sUri the local name of the root node (if any)
+ * @returns a new DOM Document
+ */
+ Sarissa.getDomDocument = function(sUri, sName){
+ return document.implementation.createDocument(sUri?sUri:"", sName?sName:"", null);
+ };
+ };
+ };//if(_SARISSA_HAS_DOM_CREATE_DOCUMENT)
+};
+//==========================================
+// Common stuff
+//==========================================
+if(!window.DOMParser){
+ /*
+ * DOMParser is a utility class, used to construct DOMDocuments from XML strings
+ * @constructor
+ */
+ DOMParser = function() {
+ };
+ if(_SARISSA_IS_SAFARI){
+ /**
+ * Construct a new DOM Document from the given XMLstring
+ * @param sXml the given XML string
+ * @param contentType the content type of the document the given string represents (one of text/xml, application/xml, application/xhtml+xml).
+ * @return a new DOM Document from the given XML string
+ */
+ DOMParser.prototype.parseFromString = function(sXml, contentType){
+ if(contentType.toLowerCase() != "application/xml"){
+ throw "Cannot handle content type: \"" + contentType + "\"";
+ };
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET", "data:text/xml;charset=utf-8," + encodeURIComponent(str), false);
+ xmlhttp.send(null);
+ return xmlhttp.responseXML;
+ };
+ }else if(Sarissa.getDomDocument && Sarissa.getDomDocument() && "loadXML" in Sarissa.getDomDocument()){
+ DOMParser.prototype.parseFromString = function(sXml, contentType){
+ var doc = Sarissa.getDomDocument();
+ doc.loadXML(sXml);
+ return doc;
+ };
+ };
+};
+
+if(window.XMLHttpRequest){
+ Sarissa.IS_ENABLED_XMLHTTP = true;
+}
+else if(_SARISSA_IS_IE){
+ /**
+ * Emulate XMLHttpRequest
+ * @constructor
+ */
+ XMLHttpRequest = function() {
+ return new ActiveXObject(_SARISSA_XMLHTTP_PROGID);
+ };
+ Sarissa.IS_ENABLED_XMLHTTP = true;
+};
+
+if(!window.document.importNode && _SARISSA_IS_IE){
+ try{
+ /**
+ * Implements importNode for the current window document in IE using innerHTML.
+ * Testing showed that DOM was multiple times slower than innerHTML for this,
+ * sorry folks. If you encounter trouble (who knows what IE does behind innerHTML)
+ * please gimme a call.
+ * @param oNode the Node to import
+ * @param bChildren whether to include the children of oNode
+ * @returns the imported node for further use
+ */
+ window.document.importNode = function(oNode, bChildren){
+ var importNode = document.createElement("div");
+ if(bChildren)
+ importNode.innerHTML = Sarissa.serialize(oNode);
+ else
+ importNode.innerHTML = Sarissa.serialize(oNode.cloneNode(false));
+ return importNode.firstChild;
+ };
+ }catch(e){};
+};
+if(!Sarissa.getParseErrorText){
+ /**
+ * <p>Returns a human readable description of the parsing error. Usefull
+ * for debugging. Tip: append the returned error string in a &lt;pre&gt;
+ * element if you want to render it.</p>
+ * <p>Many thanks to Christian Stocker for the initial patch.</p>
+ * @argument oDoc The target DOM document
+ * @returns The parsing error description of the target Document in
+ * human readable form (preformated text)
+ */
+ Sarissa.getParseErrorText = function (oDoc){
+ var parseErrorText = Sarissa.PARSED_OK;
+ if(oDoc && oDoc.parseError && oDoc.parseError != 0){
+ /*moz*/
+ if(oDoc.documentElement.tagName == "parsererror"){
+ parseErrorText = oDoc.documentElement.firstChild.data;
+ parseErrorText += "\n" + oDoc.documentElement.firstChild.nextSibling.firstChild.data;
+ }/*konq*/
+ else{
+ parseErrorText = Sarissa.getText(oDoc.documentElement);/*.getElementsByTagName("h1")[0], false) + "\n";
+ parseErrorText += Sarissa.getText(oDoc.documentElement.getElementsByTagName("body")[0], false) + "\n";
+ parseErrorText += Sarissa.getText(oDoc.documentElement.getElementsByTagName("pre")[0], false);*/
+ };
+ };
+ return parseErrorText;
+ };
+};
+Sarissa.getText = function(oNode, deep){
+ var s = "";
+ var nodes = oNode.childNodes;
+ for(var i=0; i < nodes.length; i++){
+ var node = nodes[i];
+ var nodeType = node.nodeType;
+ if(nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE){
+ s += node.data;
+ }else if(deep == true
+ && (nodeType == Node.ELEMENT_NODE
+ || nodeType == Node.DOCUMENT_NODE
+ || nodeType == Node.DOCUMENT_FRAGMENT_NODE)){
+ s += Sarissa.getText(node, true);
+ };
+ };
+ return s;
+};
+if(window.XMLSerializer){
+ /**
+ * <p>Factory method to obtain the serialization of a DOM Node</p>
+ * @returns the serialized Node as an XML string
+ */
+ Sarissa.serialize = function(oDoc){
+ var s = null;
+ if(oDoc){
+ s = oDoc.innerHTML?oDoc.innerHTML:(new XMLSerializer()).serializeToString(oDoc);
+ };
+ return s;
+ };
+}else{
+ if(Sarissa.getDomDocument && (Sarissa.getDomDocument("","foo", null)).xml){
+ // see non-IE version
+ Sarissa.serialize = function(oDoc) {
+ var s = null;
+ if(oDoc){
+ s = oDoc.innerHTML?oDoc.innerHTML:oDoc.xml;
+ };
+ return s;
+ };
+ /**
+ * Utility class to serialize DOM Node objects to XML strings
+ * @constructor
+ */
+ XMLSerializer = function(){};
+ /**
+ * Serialize the given DOM Node to an XML string
+ * @param oNode the DOM Node to serialize
+ */
+ XMLSerializer.prototype.serializeToString = function(oNode) {
+ return oNode.xml;
+ };
+ };
+};
+
+/**
+ * strips tags from a markup string
+ */
+Sarissa.stripTags = function (s) {
+ return s.replace(/<[^>]+>/g,"");
+};
+/**
+ * <p>Deletes all child nodes of the given node</p>
+ * @argument oNode the Node to empty
+ */
+Sarissa.clearChildNodes = function(oNode) {
+ // need to check for firstChild due to opera 8 bug with hasChildNodes
+ while(oNode.firstChild){
+ oNode.removeChild(oNode.firstChild);
+ };
+};
+/**
+ * <p> Copies the childNodes of nodeFrom to nodeTo</p>
+ * <p> <b>Note:</b> The second object's original content is deleted before
+ * the copy operation, unless you supply a true third parameter</p>
+ * @argument nodeFrom the Node to copy the childNodes from
+ * @argument nodeTo the Node to copy the childNodes to
+ * @argument bPreserveExisting whether to preserve the original content of nodeTo, default is false
+ */
+Sarissa.copyChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
+ if((!nodeFrom) || (!nodeTo)){
+ throw "Both source and destination nodes must be provided";
+ };
+ if(!bPreserveExisting){
+ Sarissa.clearChildNodes(nodeTo);
+ };
+ var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
+ var nodes = nodeFrom.childNodes;
+ if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
+ for(var i=0;i < nodes.length;i++) {
+ nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
+ };
+ }
+ else{
+ for(var i=0;i < nodes.length;i++) {
+ nodeTo.appendChild(nodes[i].cloneNode(true));
+ };
+ };
+};
+
+/**
+ * <p> Moves the childNodes of nodeFrom to nodeTo</p>
+ * <p> <b>Note:</b> The second object's original content is deleted before
+ * the move operation, unless you supply a true third parameter</p>
+ * @argument nodeFrom the Node to copy the childNodes from
+ * @argument nodeTo the Node to copy the childNodes to
+ * @argument bPreserveExisting whether to preserve the original content of nodeTo, default is
+ */
+Sarissa.moveChildNodes = function(nodeFrom, nodeTo, bPreserveExisting) {
+ if((!nodeFrom) || (!nodeTo)){
+ throw "Both source and destination nodes must be provided";
+ };
+ if(!bPreserveExisting){
+ Sarissa.clearChildNodes(nodeTo);
+ };
+ var nodes = nodeFrom.childNodes;
+ // if within the same doc, just move, else copy and delete
+ if(nodeFrom.ownerDocument == nodeTo.ownerDocument){
+ while(nodeFrom.firstChild){
+ nodeTo.appendChild(nodeFrom.firstChild);
+ };
+ }else{
+ var ownerDoc = nodeTo.nodeType == Node.DOCUMENT_NODE ? nodeTo : nodeTo.ownerDocument;
+ if(ownerDoc.importNode && (!_SARISSA_IS_IE)) {
+ for(var i=0;i < nodes.length;i++) {
+ nodeTo.appendChild(ownerDoc.importNode(nodes[i], true));
+ };
+ }else{
+ for(var i=0;i < nodes.length;i++) {
+ nodeTo.appendChild(nodes[i].cloneNode(true));
+ };
+ };
+ Sarissa.clearChildNodes(nodeFrom);
+ };
+};
+
+/**
+ * <p>Serialize any object to an XML string. All properties are serialized using the property name
+ * as the XML element name. Array elements are rendered as <code>array-item</code> elements,
+ * using their index/key as the value of the <code>key</code> attribute.</p>
+ * @argument anyObject the object to serialize
+ * @argument objectName a name for that object
+ * @return the XML serializationj of the given object as a string
+ */
+Sarissa.xmlize = function(anyObject, objectName, indentSpace){
+ indentSpace = indentSpace?indentSpace:'';
+ var s = indentSpace + '<' + objectName + '>';
+ var isLeaf = false;
+ if(!(anyObject instanceof Object) || anyObject instanceof Number || anyObject instanceof String
+ || anyObject instanceof Boolean || anyObject instanceof Date){
+ s += Sarissa.escape(""+anyObject);
+ isLeaf = true;
+ }else{
+ s += "\n";
+ var itemKey = '';
+ var isArrayItem = anyObject instanceof Array;
+ for(var name in anyObject){
+ s += Sarissa.xmlize(anyObject[name], (isArrayItem?"array-item key=\""+name+"\"":name), indentSpace + " ");
+ };
+ s += indentSpace;
+ };
+ return s += (objectName.indexOf(' ')!=-1?"</array-item>\n":"</" + objectName + ">\n");
+};
+
+/**
+ * Escape the given string chacters that correspond to the five predefined XML entities
+ * @param sXml the string to escape
+ */
+Sarissa.escape = function(sXml){
+ return sXml.replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&apos;");
+};
+
+/**
+ * Unescape the given string. This turns the occurences of the predefined XML
+ * entities to become the characters they represent correspond to the five predefined XML entities
+ * @param sXml the string to unescape
+ */
+Sarissa.unescape = function(sXml){
+ return sXml.replace(/&apos;/g,"'")
+ .replace(/&quot;/g,"\"")
+ .replace(/&gt;/g,">")
+ .replace(/&lt;/g,"<")
+ .replace(/&amp;/g,"&");
+};
+// EOF
diff --git a/tools/ajaxterm/sarissa_dhtml.js b/tools/ajaxterm/sarissa_dhtml.js
new file mode 100644
index 0000000000..2d85c817e3
--- /dev/null
+++ b/tools/ajaxterm/sarissa_dhtml.js
@@ -0,0 +1,105 @@
+/**
+ * ====================================================================
+ * About
+ * ====================================================================
+ * Sarissa cross browser XML library - AJAX module
+ * @version 0.9.6.1
+ * @author: Copyright Manos Batsis, mailto: mbatsis at users full stop sourceforge full stop net
+ *
+ * This module contains some convinient AJAX tricks based on Sarissa
+ *
+ * ====================================================================
+ * Licence
+ * ====================================================================
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or
+ * the GNU Lesser General Public License version 2.1 as published by
+ * the Free Software Foundation (your choice between the two).
+ *
+ * This program 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 or GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * or GNU Lesser General Public License along with this program; if not,
+ * write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * or visit http://www.gnu.org
+ *
+ */
+/**
+ * Update an element with response of a GET request on the given URL.
+ * @addon
+ * @param sFromUrl the URL to make the request to
+ * @param oTargetElement the element to update
+ * @param xsltproc (optional) the transformer to use on the returned
+ * content before updating the target element with it
+ */
+Sarissa.updateContentFromURI = function(sFromUrl, oTargetElement, xsltproc) {
+ try{
+ oTargetElement.style.cursor = "wait";
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open("GET", sFromUrl);
+ function sarissa_dhtml_loadHandler() {
+ if (xmlhttp.readyState == 4) {
+ oTargetElement.style.cursor = "auto";
+ Sarissa.updateContentFromNode(xmlhttp.responseXML, oTargetElement, xsltproc);
+ };
+ };
+ xmlhttp.onreadystatechange = sarissa_dhtml_loadHandler;
+ xmlhttp.send(null);
+ oTargetElement.style.cursor = "auto";
+ }
+ catch(e){
+ oTargetElement.style.cursor = "auto";
+ throw e;
+ };
+};
+
+/**
+ * Update an element's content with the given DOM node.
+ * @addon
+ * @param sFromUrl the URL to make the request to
+ * @param oTargetElement the element to update
+ * @param xsltproc (optional) the transformer to use on the given
+ * DOM node before updating the target element with it
+ */
+Sarissa.updateContentFromNode = function(oNode, oTargetElement, xsltproc) {
+ try {
+ oTargetElement.style.cursor = "wait";
+ Sarissa.clearChildNodes(oTargetElement);
+ // check for parsing errors
+ var ownerDoc = oNode.nodeType == Node.DOCUMENT_NODE?oNode:oNode.ownerDocument;
+ if(ownerDoc.parseError && ownerDoc.parseError != 0) {
+ var pre = document.createElement("pre");
+ pre.appendChild(document.createTextNode(Sarissa.getParseErrorText(ownerDoc)));
+ oTargetElement.appendChild(pre);
+ }
+ else {
+ // transform if appropriate
+ if(xsltproc) {
+ oNode = xsltproc.transformToDocument(oNode);
+ };
+ // be smart, maybe the user wants to display the source instead
+ if(oTargetElement.tagName.toLowerCase == "textarea" || oTargetElement.tagName.toLowerCase == "input") {
+ oTargetElement.value = Sarissa.serialize(oNode);
+ }
+ else {
+ // ok that was not smart; it was paranoid. Keep up the good work by trying to use DOM instead of innerHTML
+ if(oNode.nodeType == Node.DOCUMENT_NODE || oNode.ownerDocument.documentElement == oNode) {
+ oTargetElement.innerHTML = Sarissa.serialize(oNode);
+ }
+ else{
+ oTargetElement.appendChild(oTargetElement.ownerDocument.importNode(oNode, true));
+ };
+ };
+ };
+ }
+ catch(e) {
+ throw e;
+ }
+ finally{
+ oTargetElement.style.cursor = "auto";
+ };
+};
+
diff --git a/tools/euca-get-ajax-console b/tools/euca-get-ajax-console
new file mode 100755
index 0000000000..37060e74f3
--- /dev/null
+++ b/tools/euca-get-ajax-console
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# pylint: disable-msg=C0103
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Euca add-on to use ajax console"""
+
+import getopt
+import os
+import sys
+
+# If ../nova/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+import boto
+import nova
+from boto.ec2.connection import EC2Connection
+from euca2ools import Euca2ool, InstanceValidationError, Util, ConnectionFailed
+
+usage_string = """
+Retrieves a url to an ajax console terminal
+
+euca-get-ajax-console [-h, --help] [--version] [--debug] instance_id
+
+REQUIRED PARAMETERS
+
+instance_id: unique identifier for the instance show the console output for.
+
+OPTIONAL PARAMETERS
+
+"""
+
+
+# This class extends boto to add AjaxConsole functionality
+class NovaEC2Connection(EC2Connection):
+
+ def get_ajax_console(self, instance_id):
+ """
+ Retrieves a console connection for the specified instance.
+
+ :type instance_id: string
+ :param instance_id: The instance ID of a running instance on the cloud.
+
+ :rtype: :class:`AjaxConsole`
+ """
+
+ class AjaxConsole:
+ def __init__(self, parent=None):
+ self.parent = parent
+ self.instance_id = None
+ self.url = None
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceId':
+ self.instance_id = value
+ elif name == 'url':
+ self.url = value
+ else:
+ setattr(self, name, value)
+
+ params = {}
+ self.build_list_params(params, [instance_id], 'InstanceId')
+ return self.get_object('GetAjaxConsole', params, AjaxConsole)
+ pass
+
+
+def override_connect_ec2(aws_access_key_id=None,
+ aws_secret_access_key=None, **kwargs):
+ return NovaEC2Connection(aws_access_key_id,
+ aws_secret_access_key, **kwargs)
+
+# override boto's connect_ec2 method, so that we can use NovaEC2Connection
+boto.connect_ec2 = override_connect_ec2
+
+
+def usage(status=1):
+ print usage_string
+ Util().usage()
+ sys.exit(status)
+
+
+def version():
+ print Util().version()
+ sys.exit()
+
+
+def display_console_output(console_output):
+ print console_output.instance_id
+ print console_output.timestamp
+ print console_output.output
+
+
+def display_ajax_console_output(console_output):
+ print console_output.url
+
+
+def main():
+ try:
+ euca = Euca2ool()
+ except Exception, e:
+ print e
+ usage()
+
+ instance_id = None
+
+ for name, value in euca.opts:
+ if name in ('-h', '--help'):
+ usage(0)
+ elif name == '--version':
+ version()
+ elif name == '--debug':
+ debug = True
+
+ for arg in euca.args:
+ instance_id = arg
+ break
+
+ if instance_id:
+ try:
+ euca.validate_instance_id(instance_id)
+ except InstanceValidationError:
+ print 'Invalid instance id'
+ sys.exit(1)
+
+ try:
+ euca_conn = euca.make_connection()
+ except ConnectionFailed, e:
+ print e.message
+ sys.exit(1)
+ try:
+ console_output = euca_conn.get_ajax_console(instance_id)
+ except Exception, ex:
+ euca.display_error_and_exit('%s' % ex)
+
+ display_ajax_console_output(console_output)
+ else:
+ print 'instance_id must be specified'
+ usage()
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 32c3723526..4e3941210b 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -66,7 +66,8 @@ def check_dependencies():
# Try installing it via easy_install...
if HAS_EASY_INSTALL:
print 'Installing virtualenv via easy_install...',
- if not run_command(['which', 'easy_install']):
+ if not (run_command(['which', 'easy_install']) and
+ run_command(['easy_install', 'virtualenv'])):
die('ERROR: virtualenv not found.\n\nNova development requires virtualenv,'
' please install it using your favorite package management tool')
print 'done.'