diff options
author | Ewan Mellor <ewan.mellor@citrix.com> | 2011-01-12 11:43:29 +0000 |
---|---|---|
committer | Ewan Mellor <ewan.mellor@citrix.com> | 2011-01-12 11:43:29 +0000 |
commit | 6cdea8c9b024194d81ae724245b2f595d99606fe (patch) | |
tree | 8912c69eb1d8d99114441564bd40d3c7e4e3619e /tools | |
parent | 3461b9cf49201eb88ed65473eccf395382c25611 (diff) | |
parent | 3d57735caf78fd421da6e660c4d56c635706fa7d (diff) | |
download | nova-6cdea8c9b024194d81ae724245b2f595d99606fe.tar.gz |
Merged with trunk.
Diffstat (limited to 'tools')
-rw-r--r-- | tools/ajaxterm/README.txt | 120 | ||||
-rw-r--r-- | tools/ajaxterm/ajaxterm.1 | 35 | ||||
-rw-r--r-- | tools/ajaxterm/ajaxterm.css | 64 | ||||
-rw-r--r-- | tools/ajaxterm/ajaxterm.html | 25 | ||||
-rw-r--r-- | tools/ajaxterm/ajaxterm.js | 279 | ||||
-rwxr-xr-x | tools/ajaxterm/ajaxterm.py | 586 | ||||
-rwxr-xr-x | tools/ajaxterm/configure | 32 | ||||
-rw-r--r-- | tools/ajaxterm/configure.ajaxterm.bin | 2 | ||||
-rw-r--r-- | tools/ajaxterm/configure.initd.debian | 33 | ||||
-rw-r--r-- | tools/ajaxterm/configure.initd.gentoo | 27 | ||||
-rw-r--r-- | tools/ajaxterm/configure.initd.redhat | 75 | ||||
-rw-r--r-- | tools/ajaxterm/configure.makefile | 20 | ||||
-rw-r--r-- | tools/ajaxterm/qweb.py | 1356 | ||||
-rw-r--r-- | tools/ajaxterm/sarissa.js | 647 | ||||
-rw-r--r-- | tools/ajaxterm/sarissa_dhtml.js | 105 | ||||
-rwxr-xr-x | tools/euca-get-ajax-console | 164 | ||||
-rw-r--r-- | tools/install_venv.py | 3 |
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="&%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 <pre> + * 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, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +}; + +/** + * 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(/'/g,"'") + .replace(/"/g,"\"") + .replace(/>/g,">") + .replace(/</g,"<") + .replace(/&/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.' |