Explorar el Código

Initial commit of version 0.15 alpha.

Sebastien SAUVAGE hace 14 años
commit
52630374e5
Se han modificado 17 ficheros con 4969 adiciones y 0 borrados
  1. 33 0
      README
  2. 339 0
      index.php
  3. 128 0
      lib/base64.js
  4. BIN
      lib/busy.gif
  5. BIN
      lib/icon_clone.png
  6. BIN
      lib/icon_new.png
  7. BIN
      lib/icon_send.png
  8. BIN
      lib/icon_shorten.png
  9. 1 0
      lib/jquery.js
  10. 1043 0
      lib/rain.tpl.class.php
  11. 1671 0
      lib/rawdeflate.js
  12. 753 0
      lib/rawinflate.js
  13. 41 0
      lib/sjcl.js
  14. 158 0
      lib/vizhash_gd_zero.php
  15. 357 0
      lib/zerobin.css
  16. 364 0
      lib/zerobin.js
  17. 81 0
      tpl/page.html

+ 33 - 0
README

@@ -0,0 +1,33 @@
+ZeroBin 0.15 Alpha
+=== THIS IS ALPHA SOFTWARE - USE AT YOUR OWN RISKS ====
+
+ZeroBin is a minimalist, opensource online pastebin where the server 
+has zero knowledge of pasted data. Data is encrypted/decrypted in the 
+browser using 256 bits AES. 
+
+More information on the project page:
+http://sebsauvage.net/wiki/doku.php?id=php:zerobin
+
+------------------------------------------------------------------------------
+
+Copyright (c) 2012 Sébastien SAUVAGE (sebsauvage.net)
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from 
+the use of this software.
+
+Permission is granted to anyone to use this software for any purpose, 
+including commercial applications, and to alter it and redistribute it 
+freely, subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must 
+       not claim that you wrote the original software. If you use this 
+       software in a product, an acknowledgment in the product documentation
+       would be appreciated but is not required.
+
+    2. Altered source versions must be plainly marked as such, and must 
+       not be misrepresented as being the original software.
+
+    3. This notice may not be removed or altered from any source distribution.
+
+------------------------------------------------------------------------------

+ 339 - 0
index.php

@@ -0,0 +1,339 @@
+<?php
+/*
+ZeroBin - a zero-knowledge paste bin
+Please see project page: http://sebsauvage.net/wiki/doku.php?id=php:zerobin
+*/
+$VERSION='Alpha 0.15';
+if (version_compare(PHP_VERSION, '5.2.6') < 0) die('ZeroBin requires php 5.2.6 or above to work. Sorry.');
+require_once "lib/vizhash_gd_zero.php";
+
+// In case stupid admin has left magic_quotes enabled in php.ini:
+if (get_magic_quotes_gpc())
+{
+    function stripslashes_deep($value) { $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value); return $value; }
+    $_POST = array_map('stripslashes_deep', $_POST);
+    $_GET = array_map('stripslashes_deep', $_GET);
+    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
+}
+
+// trafic_limiter : Make sure the IP address makes at most 1 request every 10 seconds.
+// Will return false if IP address made a call less than 10 seconds ago.
+function trafic_limiter_canPass($ip)
+{
+    $tfilename='./data/trafic_limiter.php';
+    if (!is_file($tfilename))
+    { 
+        file_put_contents($tfilename,"<?php\n\$GLOBALS['trafic_limiter']=array();\n?>"); 
+        chmod($tfilename,0705); 
+    }
+    require $tfilename;
+    $tl=$GLOBALS['trafic_limiter'];
+    if (!empty($tl[$ip]) && ($tl[$ip]+10>=time()))
+    {       
+        return false;
+        // FIXME: purge file of expired IPs to keep it small
+    }
+    $tl[$ip]=time();
+    file_put_contents($tfilename, "<?php\n\$GLOBALS['trafic_limiter']=".var_export($tl,true).";\n?>");
+    return true;
+}
+
+/* Convert paste id to storage path.
+   The idea is to creates subdirectories in order to limit the number of files per directory.
+   (A high number of files in a single directory can slow things down.)
+   eg. "f468483c313401e8" will be stored in "data/f4/68/f468483c313401e8"
+   High-trafic websites may want to deepen the directory structure (like Squid does).
+
+   eg. input 'e3570978f9e4aa90' --> output 'data/e3/57/'
+*/
+function dataid2path($dataid)
+{
+    return 'data/'.substr($dataid,0,2).'/'.substr($dataid,2,2).'/';
+}
+
+/* Convert paste id to discussion storage path.
+   eg. 'e3570978f9e4aa90' --> 'data/e3/57/e3570978f9e4aa90.discussion/'
+*/
+function dataid2discussionpath($dataid)
+{
+    return dataid2path($dataid).$dataid.'.discussion/'; 
+}
+
+// Checks if a json string is a proper SJCL encrypted message.
+// False if format is incorrect.
+function validSJCL($jsonstring)
+{
+    $accepted_keys=array('iv','salt','ct');
+    
+    // Make sure content is valid json
+    $decoded = json_decode($jsonstring);
+    if ($decoded==null) return false;
+    $decoded = (array)$decoded;
+    
+    // Make sure required fields are present and that they are base64 data.  
+    foreach($accepted_keys as $k)
+    {
+        if (!array_key_exists($k,$decoded))  { return false; }
+        if (base64_decode($decoded[$k],$strict=true)==null) { return false; }
+    }
+    
+    // Make sure no additionnal keys were added.
+    if (count(array_intersect(array_keys($decoded),$accepted_keys))!=3) { return false; }
+
+    // FIXME: Reject data if entropy is too low ?
+
+    // Make sure some fields have a reasonable size.
+    if (strlen($decoded['iv'])>24) return false;
+    if (strlen($decoded['salt'])>14) return false;
+    return true;
+}
+
+// Delete a paste and its discussion.
+// Input: $pasteid : the paste identifier.
+function deletePaste($pasteid)
+{
+    // Delete the paste itself
+    unlink(dataid2path($pasteid).$pasteid);
+    
+    // Delete discussion if it exists.
+    $discdir = dataid2discussionpath($pasteid);
+    if (is_dir($discdir))
+    {
+        // Delete all files in discussion directory
+        $dhandle = opendir($discdir); 
+        while (false !== ($filename = readdir($dhandle)))
+        { 
+            if (is_file($discdir.$filename))  unlink($discdir.$filename); 
+        }
+        closedir($dhandle);
+        
+        // Delete the discussion directory.
+        rmdir($discdir);
+    }
+}    
+    
+if (!empty($_POST['data'])) // Create new paste/comment
+{
+    /* POST contains:
+         data (mandatory) = json encoded SJCL encrypted text (containing keys: iv,salt,ct)
+         
+         All optional data will go to meta information:
+         expire (optional) = expiration delay (never,10min,1hour,1day,1month,1year,burn) (default:never)
+         opendiscusssion (optional) = is the discussion allowed on this paste ? (0/1) (default:0)
+         nickname (optional) = son encoded SJCL encrypted text nickname of author of comment (containing keys: iv,salt,ct)
+         parentid (optional) = in discussion, which comment this comment replies to.
+         pasteid (optional) = in discussion, which paste this comment belongs to.
+    */
+
+    header('Content-type: application/json');
+    $error = false;
+    
+    // Create storage directory if it does not exist.
+    if (!is_dir('data'))
+    { 
+        mkdir('data',0705); 
+        file_put_contents('data/.htaccess',"Allow from none\nDeny from all\n"); 
+    }  
+    
+    // Make sure last paste from the IP address was more than 10 seconds ago. 
+    if (!trafic_limiter_canPass($_SERVER['REMOTE_ADDR']))
+        { echo json_encode(array('status'=>1,'message'=>'Please wait 10 seconds between each post.')); exit; }
+
+    // Make sure content is not too big.
+    $data = $_POST['data'];
+    if (strlen($data)>2000000)
+        { echo json_encode(array('status'=>1,'message'=>'Paste is limited to 2 Mb of encrypted data.')); exit; }
+
+    // Make sure format is correct.
+    if (!validSJCL($data))
+        { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
+
+    // Read additional meta-information.
+    $meta=array();
+
+    // Read expiration date
+    if (!empty($_POST['expire']))
+    {
+        $expire=$_POST['expire'];
+        if ($expire=='10min') $meta['expire_date']=time()+10*60;
+        elseif ($expire=='1hour') $meta['expire_date']=time()+60*60;
+        elseif ($expire=='1day') $meta['expire_date']=time()+24*60*60;
+        elseif ($expire=='1month') $meta['expire_date']=time()+30*24*60*60; // Well this is not *exactly* one month, it's 30 days.
+        elseif ($expire=='1year') $meta['expire_date']=time()+365*24*60*60;
+        elseif ($expire=='burn') $meta['burnafterreading']=true;
+    }
+    
+    // Read open discussion flag
+    if (!empty($_POST['opendiscussion']))
+    {
+        $opendiscussion = $_POST['opendiscussion'];
+        if ($opendiscussion!='0' && $opendiscussion!='1') { $error=true; }
+        if ($opendiscussion!='0') { $meta['opendiscussion']=true; }
+    }
+    
+    // You can't have an open discussion on a "Burn after reading" paste:
+    if (isset($meta['burnafterreading'])) unset($meta['opendiscussion']);  
+    
+    // Optional nickname for comments
+    if (!empty($_POST['nickname']))
+    {
+        $nick = $_POST['nickname'];
+        if (!validSJCL($nick))
+        {
+            $error=true;
+        }
+        else
+        {
+            $meta['nickname']=$nick;
+            
+            // Generation of the anonymous avatar (Vizhash):
+            // If a nickname is provided, we generate a Vizhash.
+            // (We assume that if the user did not enter a nickname, he/she wants
+            // to be anonymous and we will not generate the vizhash.)
+            $vz = new vizhash16x16();
+            $pngdata = $vz->generate($_SERVER['REMOTE_ADDR']);
+            if ($pngdata!='') $meta['vizhash'] = 'data:image/png;base64,'.base64_encode($pngdata);  
+            // Once the avatar is generated, we do not keep the IP address, nor its hash.           
+        }            
+    }    
+
+    if ($error)
+    {
+        echo json_encode(array('status'=>1,'message'=>'Invalid data.'));
+        exit;    
+    }
+
+    // Add post date to meta.
+    $meta['postdate']=time();
+
+    // We just want a small hash to avoid collisions: Half-MD5 (64 bits) will do the trick
+    $dataid = substr(hash('md5',$data),0,16);
+
+    $is_comment = (!empty($_POST['parentid']) && !empty($_POST['pasteid'])); // Is this post a comment ?
+    $storage = array('data'=>$data);
+    if (count($meta)>0) $storage['meta'] = $meta;  // Add meta-information only if necessary.
+    
+    if ($is_comment) // The user posts a comment.
+    {
+        $pasteid = $_POST['pasteid'];
+        $parentid = $_POST['parentid'];
+        if (!preg_match('/[a-f\d]{16}/',$pasteid)) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
+        if (!preg_match('/[a-f\d]{16}/',$parentid)) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
+        
+        unset($storage['expire_date']); // Comment do not expire (it's the paste that expires)
+        unset($storage['opendiscussion']);
+        
+        // Make sure paste exists.
+        $storagedir = dataid2path($pasteid);
+        if (!is_file($storagedir.$pasteid)) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
+        
+        // Make sure the discussion is opened in this paste.
+        $paste=json_decode(file_get_contents($storagedir.$pasteid));
+        if (!$paste->meta->opendiscussion) { echo json_encode(array('status'=>1,'message'=>'Invalid data.')); exit; }
+        
+        $discdir = dataid2discussionpath($pasteid);
+        $filename = $pasteid.'.'.$dataid.'.'.$parentid;
+        if (!is_dir($discdir)) mkdir($discdir,$mode=0705,$recursive=true); 
+        if (is_file($discdir.$filename)) // Oups... improbable collision.
+        {
+            echo json_encode(array('status'=>1,'message'=>'You are unlucky. Try again.'));
+            exit;
+        }
+        
+        file_put_contents($discdir.$filename,json_encode($storage));
+        echo json_encode(array('status'=>0,'id'=>$dataid)); // 0 = no error
+        exit;    
+    }
+    else // a standard paste.
+    {
+        $storagedir = dataid2path($dataid);
+        if (!is_dir($storagedir)) mkdir($storagedir,$mode=0705,$recursive=true); 
+        if (is_file($storagedir.$dataid)) // Oups... improbable collision.
+        {
+            echo json_encode(array('status'=>1,'message'=>'You are unlucky. Try again.'));
+            exit;
+        }
+        // New paste
+        file_put_contents($storagedir.$dataid,json_encode($storage));
+        echo json_encode(array('status'=>0,'id'=>$dataid)); // 0 = no error
+        exit;
+    }
+    
+echo json_encode(array('status'=>1,'message'=>'Server error.'));
+exit;
+}
+
+$CIPHERDATA='';
+$ERRORMESSAGE='';
+if (!empty($_SERVER['QUERY_STRING']))  // Display an existing paste.
+{
+    $dataid = $_SERVER['QUERY_STRING'];
+    if (preg_match('/[a-f\d]{16}/',$dataid))  // Is this a valid paste identifier ?
+    {
+        $filename = dataid2path($dataid).$dataid;
+        if (is_file($filename)) // Check that paste exists.
+        {
+            // Get the paste itself.
+            $paste=json_decode(file_get_contents($filename));
+
+            // See if paste has expired.
+            if (isset($paste->meta->expire_date) && $paste->meta->expire_date<time())
+            {
+                deletePaste($dataid);  // Delete the paste
+                $ERRORMESSAGE='Paste does not exist or has expired.';                
+            }
+
+            if ($ERRORMESSAGE=='') // If no error, return the paste.
+            {
+                // We kindly provide the remaining time before expiration (in seconds)
+                if ($paste->meta->expire_date) $paste->meta->remaining_time = $paste->meta->expire_date - time();
+
+                $messages = array($paste); // The paste itself is the first in the list of encrypted messages.
+                // If it's a discussion, get all comments.
+                if ($paste->meta->opendiscussion)
+                {
+                    $comments=array();
+                    $datadir = dataid2discussionpath($dataid);
+                    if (!is_dir($datadir)) mkdir($datadir,$mode=0705,$recursive=true); 
+                    $dhandle = opendir($datadir); 
+                    while (false !== ($filename = readdir($dhandle))) 
+                    { 
+                        if (is_file($datadir.$filename))
+                        {
+                            $comment=json_decode(file_get_contents($datadir.$filename));
+                            // Filename is in the form pasteid.commentid.parentid:
+                            // - pasteid is the paste this reply belongs to.
+                            // - commentid is the comment identifier itself.
+                            // - parentid is the comment this comment replies to (It can be pasteid)
+                            $items=explode('.',$filename);
+                            $comment->meta->commentid=$items[1]; // Add some meta information not contained in file.
+                            $comment->meta->parentid=$items[2];
+                            $comments[$comment->meta->postdate]=$comment; // Store in table
+                        }
+                    }
+                    closedir($dhandle);
+                    ksort($comments); // Sort comments by date, oldest first.
+                    $messages = array_merge($messages, $comments);
+                }
+                $CIPHERDATA = json_encode($messages);
+                
+                // If the paste was meant to be read only once, delete it.
+                if ($paste->meta->burnafterreading) deletePaste($dataid);
+            }
+        }
+        else
+        {
+            $ERRORMESSAGE='Paste does not exist or has expired.';
+        }
+    }
+}
+
+
+require_once "lib/rain.tpl.class.php";
+header('Content-Type: text/html; charset=utf-8');
+$page = new RainTPL;    
+$page->assign('CIPHERDATA',htmlspecialchars($CIPHERDATA,ENT_NOQUOTES));  // We escape it here because ENT_NOQUOTES can't be used in RainTPL templates.
+$page->assign('VERSION',$VERSION);
+$page->assign('ERRORMESSAGE',$ERRORMESSAGE);
+$page->draw('page');
+?>

+ 128 - 0
lib/base64.js

@@ -0,0 +1,128 @@
+/*
+ * $Id: base64.js,v 1.2 2011/12/27 14:34:49 dankogai Exp dankogai $
+ *
+ *  Licensed under the MIT license.
+ *  http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+
+(function(global){
+
+if (global.Base64) return;
+
+var b64chars 
+    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+var b64tab = function(bin){
+    var t = {};
+    for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
+    return t;
+}(b64chars);
+
+var sub_toBase64 = function(m){
+    var n = (m.charCodeAt(0) << 16)
+          | (m.charCodeAt(1) <<  8)
+          | (m.charCodeAt(2)      );
+    return b64chars.charAt( n >>> 18)
+         + b64chars.charAt((n >>> 12) & 63)
+         + b64chars.charAt((n >>>  6) & 63)
+         + b64chars.charAt( n         & 63);
+};
+
+var toBase64 = function(bin){
+    if (bin.match(/[^\x00-\xFF]/)) throw 'unsupported character found' ;
+    var padlen = 0;
+    while(bin.length % 3) {
+        bin += '\0';
+        padlen++;
+    };
+    var b64 = bin.replace(/[\x00-\xFF]{3}/g, sub_toBase64);
+    if (!padlen) return b64;
+    b64 = b64.substr(0, b64.length - padlen);
+    while(padlen--) b64 += '=';
+    return b64;
+};
+
+var btoa = global.btoa || toBase64;
+
+var sub_fromBase64 = function(m){
+        var n = (b64tab[ m.charAt(0) ] << 18)
+            |   (b64tab[ m.charAt(1) ] << 12)
+            |   (b64tab[ m.charAt(2) ] <<  6)
+            |   (b64tab[ m.charAt(3) ]);
+    return String.fromCharCode(  n >> 16 )
+        +  String.fromCharCode( (n >>  8) & 0xff )
+        +  String.fromCharCode(  n        & 0xff );
+};
+
+var fromBase64 = function(b64){
+    b64 = b64.replace(/[^A-Za-z0-9\+\/]/g, '');
+    var padlen = 0;
+    while(b64.length % 4){
+        b64 += 'A';
+        padlen++;
+    }
+    var bin = b64.replace(/[A-Za-z0-9\+\/]{4}/g, sub_fromBase64);
+    if (padlen >= 2)
+        bin = bin.substring(0, bin.length - [0,0,2,1][padlen]);
+    return bin;
+};
+
+var atob = global.atob || fromBase64;
+
+var re_char_nonascii = /[^\x00-\x7F]/g;
+
+var sub_char_nonascii = function(m){
+    var n = m.charCodeAt(0);
+    return n < 0x800 ? String.fromCharCode(0xc0 | (n >>>  6))
+                     + String.fromCharCode(0x80 | (n & 0x3f))
+        :              String.fromCharCode(0xe0 | ((n >>> 12) & 0x0f))
+                     + String.fromCharCode(0x80 | ((n >>>  6) & 0x3f))
+                     + String.fromCharCode(0x80 |  (n         & 0x3f))
+        ;
+};
+
+var utob = function(uni){
+    return uni.replace(re_char_nonascii, sub_char_nonascii);
+};
+
+var re_bytes_nonascii
+    = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g;
+
+var sub_bytes_nonascii = function(m){
+    var c0 = m.charCodeAt(0);
+    var c1 = m.charCodeAt(1);
+    if(c0 < 0xe0){
+        return String.fromCharCode(((c0 & 0x1f) << 6) | (c1 & 0x3f));
+    }else{
+        var c2 = m.charCodeAt(2);
+        return String.fromCharCode(
+            ((c0 & 0x0f) << 12) | ((c1 & 0x3f) <<  6) | (c2 & 0x3f)
+        );
+    }
+};
+    
+var btou = function(bin){
+    return bin.replace(re_bytes_nonascii, sub_bytes_nonascii);
+};
+
+global.Base64 = {
+    fromBase64:fromBase64,
+    toBase64:toBase64,
+    atob:atob,
+    btoa:btoa,
+    utob:utob,
+    btou:btou,
+    encode:function(u){ return btoa(utob(u)) },
+    encodeURI:function(u){
+        return btoa(utob(u)).replace(/[+\/]/g, function(m0){
+            return m0 == '+' ? '-' : '_';
+        }).replace(/=+$/, '');
+    },
+    decode:function(a){ 
+        return btou(atob(a.replace(/[-_]/g, function(m0){
+            return m0 == '-' ? '+' : '/';
+        })));
+    }
+};
+
+})(this);

BIN
lib/busy.gif


BIN
lib/icon_clone.png


BIN
lib/icon_new.png


BIN
lib/icon_send.png


BIN
lib/icon_shorten.png


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
lib/jquery.js


+ 1043 - 0
lib/rain.tpl.class.php

@@ -0,0 +1,1043 @@
+<?php
+
+/**
+ *  RainTPL
+ *  -------
+ *  Realized by Federico Ulfo & maintained by the Rain Team
+ *  Distributed under GNU/LGPL 3 License
+ *
+ *  @version 2.7.2
+ */
+
+
+class RainTPL{
+
+	// -------------------------
+	// 	CONFIGURATION
+	// -------------------------
+
+		/**
+		 * Template directory
+		 *
+		 * @var string
+		 */
+		static $tpl_dir = "tpl/";
+
+
+		/**
+		 * Cache directory. Is the directory where RainTPL will compile the template and save the cache
+		 *
+		 * @var string
+		 */
+		static $cache_dir = "tmp/";
+
+
+		/**
+		 * Template base URL. RainTPL will add this URL to the relative paths of element selected in $path_replace_list.
+		 *
+		 * @var string
+		 */
+		static $base_url = null;
+
+
+		/**
+		 * Template extension.
+		 *
+		 * @var string
+		 */
+		static $tpl_ext = "html";
+
+
+		/**
+		 * Path replace is a cool features that replace all relative paths of images (<img src="...">), stylesheet (<link href="...">), script (<script src="...">) and link (<a href="...">)
+		 * Set true to enable the path replace.
+		 *
+		 * @var unknown_type
+		 */
+		static $path_replace = true;
+
+
+		/**
+		 * You can set what the path_replace method will replace.
+		 * Avaible options: a, img, link, script, input
+		 *
+		 * @var array
+		 */
+		static $path_replace_list = array( 'a', 'img', 'link', 'script', 'input' );
+
+
+		/**
+		 * You can define in the black list what string are disabled into the template tags
+		 *
+		 * @var unknown_type
+		 */
+		static $black_list = array( '\$this', 'raintpl::', 'self::', '_SESSION', '_SERVER', '_ENV',  'eval', 'exec', 'unlink', 'rmdir' );
+
+
+		/**
+		 * Check template.
+		 * true: checks template update time, if changed it compile them
+		 * false: loads the compiled template. Set false if server doesn't have write permission for cache_directory.
+		 *
+		 */
+		static $check_template_update = true;
+                
+
+		/**
+		 * PHP tags <? ?> 
+		 * True: php tags are enabled into the template
+		 * False: php tags are disabled into the template and rendered as html
+		 *
+		 * @var bool
+		 */
+		static $php_enabled = false;
+
+		
+		/**
+		 * Debug mode flag.
+		 * True: debug mode is used, syntax errors are displayed directly in template. Execution of script is not terminated.
+		 * False: exception is thrown on found error.
+		 *
+		 * @var bool
+		 */
+		static $debug = false;
+
+	// -------------------------
+
+
+	// -------------------------
+	// 	RAINTPL VARIABLES
+	// -------------------------
+
+		/**
+		 * Is the array where RainTPL keep the variables assigned
+		 *
+		 * @var array
+		 */
+		public $var = array();
+
+		protected $tpl = array(),		// variables to keep the template directories and info
+				  $cache = false,		// static cache enabled / disabled
+                  $cache_id = null;       // identify only one cache
+
+                protected static $config_name_sum = array();   // takes all the config to create the md5 of the file
+
+	// -------------------------
+
+
+
+	const CACHE_EXPIRE_TIME = 3600; // default cache expire time = hour
+
+
+
+	/**
+	 * Assign variable
+	 * eg. 	$t->assign('name','mickey');
+	 *
+	 * @param mixed $variable_name Name of template variable or associative array name/value
+	 * @param mixed $value value assigned to this variable. Not set if variable_name is an associative array
+	 */
+
+	function assign( $variable, $value = null ){
+		if( is_array( $variable ) )
+			$this->var += $variable;
+		else
+			$this->var[ $variable ] = $value;
+	}
+
+
+
+	/**
+	 * Draw the template
+	 * eg. 	$html = $tpl->draw( 'demo', TRUE ); // return template in string
+	 * or 	$tpl->draw( $tpl_name ); // echo the template
+	 *
+	 * @param string $tpl_name  template to load
+	 * @param boolean $return_string  true=return a string, false=echo the template
+	 * @return string
+	 */
+
+	function draw( $tpl_name, $return_string = false ){
+
+		try {
+			// compile the template if necessary and set the template filepath
+			$this->check_template( $tpl_name );
+		} catch (RainTpl_Exception $e) {
+			$output = $this->printDebug($e);
+			die($output);
+		}
+
+		// Cache is off and, return_string is false
+        // Rain just echo the template
+
+        if( !$this->cache && !$return_string ){
+            extract( $this->var );
+            include $this->tpl['compiled_filename'];
+            unset( $this->tpl );
+        }
+
+
+		// cache or return_string are enabled
+        // rain get the output buffer to save the output in the cache or to return it as string
+
+        else{
+
+            //----------------------
+            // get the output buffer
+            //----------------------
+                ob_start();
+                extract( $this->var );
+                include $this->tpl['compiled_filename'];
+                $raintpl_contents = ob_get_clean();
+            //----------------------
+
+
+            // save the output in the cache
+            if( $this->cache )
+                file_put_contents( $this->tpl['cache_filename'], "<?php if(!class_exists('raintpl')){exit;}?>" . $raintpl_contents );
+
+            // free memory
+            unset( $this->tpl );
+
+            // return or print the template
+            if( $return_string ) return $raintpl_contents; else echo $raintpl_contents;
+
+        }
+
+	}
+
+
+
+	/**
+	 * If exists a valid cache for this template it returns the cache
+	 *
+	 * @param string $tpl_name Name of template (set the same of draw)
+	 * @param int $expiration_time Set after how many seconds the cache expire and must be regenerated
+	 * @return string it return the HTML or null if the cache must be recreated
+	 */
+
+	function cache( $tpl_name, $expire_time = self::CACHE_EXPIRE_TIME, $cache_id = null ){
+
+        // set the cache_id
+        $this->cache_id = $cache_id;
+
+		if( !$this->check_template( $tpl_name ) && file_exists( $this->tpl['cache_filename'] ) && ( time() - filemtime( $this->tpl['cache_filename'] ) < $expire_time ) )
+			return substr( file_get_contents( $this->tpl['cache_filename'] ), 43 );
+		else{
+			//delete the cache of the selected template
+            if (file_exists($this->tpl['cache_filename']))
+            unlink($this->tpl['cache_filename'] );
+			$this->cache = true;
+		}
+	}
+
+
+
+	/**
+	 * Configure the settings of RainTPL
+	 *
+	 */
+	static function configure( $setting, $value = null ){
+		if( is_array( $setting ) )
+			foreach( $setting as $key => $value )
+				self::configure( $key, $value );
+		else if( property_exists( __CLASS__, $setting ) ){
+			self::$$setting = $value;
+            self::$config_name_sum[ $setting ] = $value; // take trace of all config
+        }
+	}
+
+
+
+	// check if has to compile the template
+	// return true if the template has changed
+	protected function check_template( $tpl_name ){
+
+		if( !isset($this->tpl['checked']) ){
+
+			$tpl_basename                       = basename( $tpl_name );														// template basename
+			$tpl_basedir                        = strpos($tpl_name,"/") ? dirname($tpl_name) . '/' : null;						// template basedirectory
+			$tpl_dir                            = self::$tpl_dir . $tpl_basedir;								// template directory
+			$this->tpl['tpl_filename']          = $tpl_dir . $tpl_basename . '.' . self::$tpl_ext;	// template filename
+			$temp_compiled_filename             = self::$cache_dir . $tpl_basename . "." . md5( $tpl_dir . serialize(self::$config_name_sum));
+			$this->tpl['compiled_filename']     = $temp_compiled_filename . '.rtpl.php';	// cache filename
+			$this->tpl['cache_filename']        = $temp_compiled_filename . '.s_' . $this->cache_id . '.rtpl.php';	// static cache filename
+
+			// if the template doesn't exsist throw an error
+			if( self::$check_template_update && !file_exists( $this->tpl['tpl_filename'] ) ){
+				$e = new RainTpl_NotFoundException( 'Template '. $tpl_basename .' not found!' );
+				throw $e->setTemplateFile($this->tpl['tpl_filename']);
+			}
+
+			// file doesn't exsist, or the template was updated, Rain will compile the template
+			if( !file_exists( $this->tpl['compiled_filename'] ) || ( self::$check_template_update && filemtime($this->tpl['compiled_filename']) < filemtime( $this->tpl['tpl_filename'] ) ) ){
+				$this->compileFile( $tpl_basename, $tpl_basedir, $this->tpl['tpl_filename'], self::$cache_dir, $this->tpl['compiled_filename'] );
+				return true;
+			}
+			$this->tpl['checked'] = true;
+		}
+	}
+
+
+	/**
+	* execute stripslaches() on the xml block. Invoqued by preg_replace_callback function below
+	* @access protected
+	*/
+	protected function xml_reSubstitution($capture) {
+    		return "<?php echo '<?xml ".stripslashes($capture[1])." ?>'; ?>";
+	} 
+
+	/**
+	 * Compile and write the compiled template file
+	 * @access protected
+	 */
+	protected function compileFile( $tpl_basename, $tpl_basedir, $tpl_filename, $cache_dir, $compiled_filename ){
+
+		//read template file
+		$this->tpl['source'] = $template_code = file_get_contents( $tpl_filename );
+
+		//xml substitution
+		$template_code = preg_replace( "/<\?xml(.*?)\?>/s", "##XML\\1XML##", $template_code );
+
+		//disable php tag
+		if( !self::$php_enabled )
+			$template_code = str_replace( array("<?","?>"), array("&lt;?","?&gt;"), $template_code );
+
+		//xml re-substitution
+		$template_code = preg_replace_callback ( "/##XML(.*?)XML##/s", array($this, 'xml_reSubstitution'), $template_code ); 
+
+		//compile template
+		$template_compiled = "<?php if(!class_exists('raintpl')){exit;}?>" . $this->compileTemplate( $template_code, $tpl_basedir );
+		
+
+		// fix the php-eating-newline-after-closing-tag-problem
+		$template_compiled = str_replace( "?>\n", "?>\n\n", $template_compiled );
+
+		// create directories
+		if( !is_dir( $cache_dir ) )
+			mkdir( $cache_dir, 0755, true );
+
+		if( !is_writable( $cache_dir ) )
+			throw new RainTpl_Exception ('Cache directory ' . $cache_dir . 'doesn\'t have write permission. Set write permission or set RAINTPL_CHECK_TEMPLATE_UPDATE to false. More details on http://www.raintpl.com/Documentation/Documentation-for-PHP-developers/Configuration/');
+
+		//write compiled file
+		file_put_contents( $compiled_filename, $template_compiled );
+	}
+
+
+
+	/**
+	 * Compile template
+	 * @access protected
+	 */
+	protected function compileTemplate( $template_code, $tpl_basedir ){
+
+		//tag list
+		$tag_regexp = array( 'loop'         => '(\{loop(?: name){0,1}="\${0,1}[^"]*"\})',
+                             'loop_close'   => '(\{\/loop\})',
+                             'if'           => '(\{if(?: condition){0,1}="[^"]*"\})',
+                             'elseif'       => '(\{elseif(?: condition){0,1}="[^"]*"\})',
+                             'else'         => '(\{else\})',
+                             'if_close'     => '(\{\/if\})',
+                             'function'     => '(\{function="[^"]*"\})',
+                             'noparse'      => '(\{noparse\})',
+                             'noparse_close'=> '(\{\/noparse\})',
+                             'ignore'       => '(\{ignore\}|\{\*)',
+                             'ignore_close'	=> '(\{\/ignore\}|\*\})',
+                             'include'      => '(\{include="[^"]*"(?: cache="[^"]*")?\})',
+                             'template_info'=> '(\{\$template_info\})',
+                             'function'		=> '(\{function="(\w*?)(?:.*?)"\})'
+							);
+
+		$tag_regexp = "/" . join( "|", $tag_regexp ) . "/";
+
+		//split the code with the tags regexp
+		$template_code = preg_split ( $tag_regexp, $template_code, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
+
+		//path replace (src of img, background and href of link)
+		$template_code = $this->path_replace( $template_code, $tpl_basedir );
+
+		//compile the code
+		$compiled_code = $this->compileCode( $template_code );
+
+		//return the compiled code
+		return $compiled_code;
+
+	}
+
+
+
+	/**
+	 * Compile the code
+	 * @access protected
+	 */
+	protected function compileCode( $parsed_code ){
+
+		//variables initialization
+		$compiled_code = $open_if = $comment_is_open = $ignore_is_open = null;
+        $loop_level = 0;
+
+	 	//read all parsed code
+	 	while( $html = array_shift( $parsed_code ) ){
+
+	 		//close ignore tag
+			if( !$comment_is_open && ( strpos( $html, '{/ignore}' ) !== FALSE || strpos( $html, '*}' ) !== FALSE ) )
+	 			$ignore_is_open = false;
+
+	 		//code between tag ignore id deleted
+	 		elseif( $ignore_is_open ){
+	 			//ignore the code
+	 		}
+
+	 		//close no parse tag
+			elseif( strpos( $html, '{/noparse}' ) !== FALSE )
+	 			$comment_is_open = false;
+
+	 		//code between tag noparse is not compiled
+	 		elseif( $comment_is_open )
+ 				$compiled_code .= $html;
+
+	 		//ignore
+			elseif( strpos( $html, '{ignore}' ) !== FALSE || strpos( $html, '{*' ) !== FALSE )
+	 			$ignore_is_open = true;
+
+	 		//noparse
+	 		elseif( strpos( $html, '{noparse}' ) !== FALSE )
+	 			$comment_is_open = true;
+
+			//include tag
+			elseif( preg_match( '/\{include="([^"]*)"(?: cache="([^"]*)"){0,1}\}/', $html, $code ) ){
+
+				//variables substitution
+				$include_var = $this->var_replace( $code[ 1 ], $left_delimiter = null, $right_delimiter = null, $php_left_delimiter = '".' , $php_right_delimiter = '."', $loop_level );
+
+				// if the cache is active
+				if( isset($code[ 2 ]) ){
+					
+					//dynamic include
+					$compiled_code .= '<?php $tpl = new '.get_class($this).';' .
+								 'if( $cache = $tpl->cache( $template = basename("'.$include_var.'") ) )' .
+								 '	echo $cache;' .
+								 'else{' .
+								 '	$tpl_dir_temp = self::$tpl_dir;' .
+								 '	$tpl->assign( $this->var );' .
+									( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
+								 '	$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
+								 '} ?>';
+				}
+				else{
+	
+					//dynamic include
+					$compiled_code .= '<?php $tpl = new '.get_class($this).';' .
+									  '$tpl_dir_temp = self::$tpl_dir;' .
+									  '$tpl->assign( $this->var );' .
+									  ( !$loop_level ? null : '$tpl->assign( "key", $key'.$loop_level.' ); $tpl->assign( "value", $value'.$loop_level.' );' ).
+									  '$tpl->draw( dirname("'.$include_var.'") . ( substr("'.$include_var.'",-1,1) != "/" ? "/" : "" ) . basename("'.$include_var.'") );'.
+									  '?>';
+					
+					
+				}
+
+			}
+
+	 		//loop
+			elseif( preg_match( '/\{loop(?: name){0,1}="\${0,1}([^"]*)"\}/', $html, $code ) ){
+
+	 			//increase the loop counter
+	 			$loop_level++;
+
+				//replace the variable in the loop
+				$var = $this->var_replace( '$' . $code[ 1 ], $tag_left_delimiter=null, $tag_right_delimiter=null, $php_left_delimiter=null, $php_right_delimiter=null, $loop_level-1 );
+
+				//loop variables
+				$counter = "\$counter$loop_level";       // count iteration
+				$key = "\$key$loop_level";               // key
+				$value = "\$value$loop_level";           // value
+
+				//loop code
+				$compiled_code .=  "<?php $counter=-1; if( isset($var) && is_array($var) && sizeof($var) ) foreach( $var as $key => $value ){ $counter++; ?>";
+
+			}
+
+			//close loop tag
+			elseif( strpos( $html, '{/loop}' ) !== FALSE ) {
+
+				//iterator
+				$counter = "\$counter$loop_level";
+
+				//decrease the loop counter
+				$loop_level--;
+
+				//close loop code
+				$compiled_code .=  "<?php } ?>";
+
+			}
+
+			//if
+			elseif( preg_match( '/\{if(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
+
+				//increase open if counter (for intendation)
+				$open_if++;
+
+				//tag
+				$tag = $code[ 0 ];
+
+				//condition attribute
+				$condition = $code[ 1 ];
+
+				// check if there's any function disabled by black_list
+				$this->function_check( $tag );
+
+				//variable substitution into condition (no delimiter into the condition)
+				$parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
+
+				//if code
+				$compiled_code .=   "<?php if( $parsed_condition ){ ?>";
+
+			}
+
+			//elseif
+			elseif( preg_match( '/\{elseif(?: condition){0,1}="([^"]*)"\}/', $html, $code ) ){
+
+				//tag
+				$tag = $code[ 0 ];
+
+				//condition attribute
+				$condition = $code[ 1 ];
+
+				//variable substitution into condition (no delimiter into the condition)
+				$parsed_condition = $this->var_replace( $condition, $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
+
+				//elseif code
+				$compiled_code .=   "<?php }elseif( $parsed_condition ){ ?>";
+			}
+
+			//else
+			elseif( strpos( $html, '{else}' ) !== FALSE ) {
+
+				//else code
+				$compiled_code .=   '<?php }else{ ?>';
+
+			}
+
+			//close if tag
+			elseif( strpos( $html, '{/if}' ) !== FALSE ) {
+
+				//decrease if counter
+				$open_if--;
+
+				// close if code
+				$compiled_code .=   '<?php } ?>';
+
+			}
+
+			//function
+			elseif( preg_match( '/\{function="(\w*)(.*?)"\}/', $html, $code ) ){
+
+				//tag
+				$tag = $code[ 0 ];
+
+				//function
+				$function = $code[ 1 ];
+
+				// check if there's any function disabled by black_list
+				$this->function_check( $tag );
+
+				if( empty( $code[ 2 ] ) )
+					$parsed_function = $function . "()";
+				else
+					// parse the function
+					$parsed_function = $function . $this->var_replace( $code[ 2 ], $tag_left_delimiter = null, $tag_right_delimiter = null, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level );
+				
+				//if code
+				$compiled_code .=   "<?php echo $parsed_function; ?>";
+			}
+
+			// show all vars
+			elseif ( strpos( $html, '{$template_info}' ) !== FALSE ) {
+
+				//tag
+				$tag  = '{$template_info}';
+
+				//if code
+				$compiled_code .=   '<?php echo "<pre>"; print_r( $this->var ); echo "</pre>"; ?>';
+			}
+
+
+			//all html code
+			else{
+
+				//variables substitution (es. {$title})
+				$html = $this->var_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
+				//const substitution (es. {#CONST#})
+				$html = $this->const_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
+				//functions substitution (es. {"string"|functions})
+				$compiled_code .= $this->func_replace( $html, $left_delimiter = '\{', $right_delimiter = '\}', $php_left_delimiter = '<?php ', $php_right_delimiter = ';?>', $loop_level, $echo = true );
+			}
+		}
+
+		if( $open_if > 0 ) {
+			$e = new RainTpl_SyntaxException('Error! You need to close an {if} tag in ' . $this->tpl['tpl_filename'] . ' template');
+			throw $e->setTemplateFile($this->tpl['tpl_filename']);
+		}
+		return $compiled_code;
+	}
+	
+	
+	/**
+	 * Reduce a path, eg. www/library/../filepath//file => www/filepath/file
+	 * @param type $path
+	 * @return type
+	 */
+	protected function reduce_path( $path ){
+		$path = str_replace( "://", "@not_replace@", $path );
+		$path = str_replace( "//", "/", $path );
+		$path = str_replace( "@not_replace@", "://", $path );
+		return preg_replace('/\w+\/\.\.\//', '', $path );
+	}
+
+
+
+	/**
+	 * replace the path of image src, link href and a href.
+	 * url => template_dir/url
+	 * url# => url
+	 * http://url => http://url
+	 *
+	 * @param string $html
+	 * @return string html sostituito
+	 */
+	protected function path_replace( $html, $tpl_basedir ){
+
+		if( self::$path_replace ){
+
+			$tpl_dir = self::$base_url . self::$tpl_dir . $tpl_basedir;
+			
+			// reduce the path
+			$path = $this->reduce_path($tpl_dir);
+
+			$exp = $sub = array();
+
+			if( in_array( "img", self::$path_replace_list ) ){
+				$exp = array( '/<img(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<img(.*?)src=(?:")([^"]+?)#(?:")/i', '/<img(.*?)src="(.*?)"/', '/<img(.*?)src=(?:\@)([^"]+?)(?:\@)/i' );
+				$sub = array( '<img$1src=@$2://$3@', '<img$1src=@$2@', '<img$1src="' . $path . '$2"', '<img$1src="$2"' );
+			}
+
+			if( in_array( "script", self::$path_replace_list ) ){
+				$exp = array_merge( $exp , array( '/<script(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<script(.*?)src=(?:")([^"]+?)#(?:")/i', '/<script(.*?)src="(.*?)"/', '/<script(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
+				$sub = array_merge( $sub , array( '<script$1src=@$2://$3@', '<script$1src=@$2@', '<script$1src="' . $path . '$2"', '<script$1src="$2"' ) );
+			}
+
+			if( in_array( "link", self::$path_replace_list ) ){
+				$exp = array_merge( $exp , array( '/<link(.*?)href=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<link(.*?)href=(?:")([^"]+?)#(?:")/i', '/<link(.*?)href="(.*?)"/', '/<link(.*?)href=(?:\@)([^"]+?)(?:\@)/i' ) );
+				$sub = array_merge( $sub , array( '<link$1href=@$2://$3@', '<link$1href=@$2@' , '<link$1href="' . $path . '$2"', '<link$1href="$2"' ) );
+			}
+
+			if( in_array( "a", self::$path_replace_list ) ){
+				$exp = array_merge( $exp , array( '/<a(.*?)href=(?:")(http\:\/\/|https\:\/\/|javascript:)([^"]+?)(?:")/i', '/<a(.*?)href="(.*?)"/', '/<a(.*?)href=(?:\@)([^"]+?)(?:\@)/i'  ) );
+				$sub = array_merge( $sub , array( '<a$1href=@$2$3@', '<a$1href="' . self::$base_url . '$2"', '<a$1href="$2"' ) );
+			}
+
+			if( in_array( "input", self::$path_replace_list ) ){
+				$exp = array_merge( $exp , array( '/<input(.*?)src=(?:")(http|https)\:\/\/([^"]+?)(?:")/i', '/<input(.*?)src=(?:")([^"]+?)#(?:")/i', '/<input(.*?)src="(.*?)"/', '/<input(.*?)src=(?:\@)([^"]+?)(?:\@)/i' ) );
+				$sub = array_merge( $sub , array( '<input$1src=@$2://$3@', '<input$1src=@$2@', '<input$1src="' . $path . '$2"', '<input$1src="$2"' ) );
+			}
+
+			return preg_replace( $exp, $sub, $html );
+
+		}
+		else
+			return $html;
+
+	}
+
+
+
+
+
+	// replace const
+	function const_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
+		// const
+		return preg_replace( '/\{\#(\w+)\#{0,1}\}/', $php_left_delimiter . ( $echo ? " echo " : null ) . '\\1' . $php_right_delimiter, $html );
+	}
+
+
+
+	// replace functions/modifiers on constants and strings
+	function func_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
+
+		preg_match_all( '/' . '\{\#{0,1}(\"{0,1}.*?\"{0,1})(\|\w.*?)\#{0,1}\}' . '/', $html, $matches );
+
+		for( $i=0, $n=count($matches[0]); $i<$n; $i++ ){
+
+			//complete tag ex: {$news.title|substr:0,100}
+			$tag = $matches[ 0 ][ $i ];
+
+			//variable name ex: news.title
+			$var = $matches[ 1 ][ $i ];
+
+			//function and parameters associate to the variable ex: substr:0,100
+			$extra_var = $matches[ 2 ][ $i ];
+
+			// check if there's any function disabled by black_list
+			$this->function_check( $tag );
+
+			$extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
+            
+
+			// check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
+			$is_init_variable = preg_match( "/^(\s*?)\=[^=](.*?)$/", $extra_var );
+
+			//function associate to variable
+			$function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
+
+			//variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
+			$temp = preg_split( "/\.|\[|\-\>/", $var );
+
+			//variable name
+			$var_name = $temp[ 0 ];
+
+			//variable path
+			$variable_path = substr( $var, strlen( $var_name ) );
+
+			//parentesis transform [ e ] in [" e in "]
+			$variable_path = str_replace( '[', '["', $variable_path );
+			$variable_path = str_replace( ']', '"]', $variable_path );
+
+			//transform .$variable in ["$variable"]
+			$variable_path = preg_replace('/\.\$(\w+)/', '["$\\1"]', $variable_path );
+
+			//transform [variable] in ["variable"]
+			$variable_path = preg_replace('/\.(\w+)/', '["\\1"]', $variable_path );
+
+			//if there's a function
+			if( $function_var ){
+                
+                // check if there's a function or a static method and separate, function by parameters
+				$function_var = str_replace("::", "@double_dot@", $function_var );
+
+                // get the position of the first :
+                if( $dot_position = strpos( $function_var, ":" ) ){
+
+                    // get the function and the parameters
+                    $function = substr( $function_var, 0, $dot_position );
+                    $params = substr( $function_var, $dot_position+1 );
+
+                }
+                else{
+
+                    //get the function
+                    $function = str_replace( "@double_dot@", "::", $function_var );
+                    $params = null;
+
+                }
+
+                // replace back the @double_dot@ with ::
+                $function = str_replace( "@double_dot@", "::", $function );
+                $params = str_replace( "@double_dot@", "::", $params );
+
+
+			}
+			else
+				$function = $params = null;
+
+			$php_var = $var_name . $variable_path;
+
+			// compile the variable for php
+			if( isset( $function ) ){
+				if( $php_var )
+					$php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
+				else
+					$php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $params ) )" : "$function()" ) . $php_right_delimiter;
+			}
+			else
+				$php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
+
+			$html = str_replace( $tag, $php_var, $html );
+
+		}
+
+		return $html;
+
+	}
+
+
+
+	function var_replace( $html, $tag_left_delimiter, $tag_right_delimiter, $php_left_delimiter = null, $php_right_delimiter = null, $loop_level = null, $echo = null ){
+
+		//all variables
+		if( preg_match_all( '/' . $tag_left_delimiter . '\$(\w+(?:\.\${0,1}[A-Za-z0-9_]+)*(?:(?:\[\${0,1}[A-Za-z0-9_]+\])|(?:\-\>\${0,1}[A-Za-z0-9_]+))*)(.*?)' . $tag_right_delimiter . '/', $html, $matches ) ){
+
+                    for( $parsed=array(), $i=0, $n=count($matches[0]); $i<$n; $i++ )
+                        $parsed[$matches[0][$i]] = array('var'=>$matches[1][$i],'extra_var'=>$matches[2][$i]);
+
+                    foreach( $parsed as $tag => $array ){
+
+                            //variable name ex: news.title
+                            $var = $array['var'];
+
+                            //function and parameters associate to the variable ex: substr:0,100
+                            $extra_var = $array['extra_var'];
+
+                            // check if there's any function disabled by black_list
+                            $this->function_check( $tag );
+
+                            $extra_var = $this->var_replace( $extra_var, null, null, null, null, $loop_level );
+
+                            // check if there's an operator = in the variable tags, if there's this is an initialization so it will not output any value
+                            $is_init_variable = preg_match( "/^[a-z_A-Z\.\[\](\-\>)]*=[^=]*$/", $extra_var );
+                            
+                            //function associate to variable
+                            $function_var = ( $extra_var and $extra_var[0] == '|') ? substr( $extra_var, 1 ) : null;
+
+                            //variable path split array (ex. $news.title o $news[title]) or object (ex. $news->title)
+                            $temp = preg_split( "/\.|\[|\-\>/", $var );
+
+                            //variable name
+                            $var_name = $temp[ 0 ];
+
+                            //variable path
+                            $variable_path = substr( $var, strlen( $var_name ) );
+
+                            //parentesis transform [ e ] in [" e in "]
+                            $variable_path = str_replace( '[', '["', $variable_path );
+                            $variable_path = str_replace( ']', '"]', $variable_path );
+
+                            //transform .$variable in ["$variable"] and .variable in ["variable"]
+                            $variable_path = preg_replace('/\.(\${0,1}\w+)/', '["\\1"]', $variable_path );
+                            
+                            // if is an assignment also assign the variable to $this->var['value']
+                            if( $is_init_variable )
+                                $extra_var = "=\$this->var['{$var_name}']{$variable_path}" . $extra_var;
+
+                                
+
+                            //if there's a function
+                            if( $function_var ){
+                                
+                                    // check if there's a function or a static method and separate, function by parameters
+                                    $function_var = str_replace("::", "@double_dot@", $function_var );
+
+
+                                    // get the position of the first :
+                                    if( $dot_position = strpos( $function_var, ":" ) ){
+
+                                        // get the function and the parameters
+                                        $function = substr( $function_var, 0, $dot_position );
+                                        $params = substr( $function_var, $dot_position+1 );
+
+                                    }
+                                    else{
+
+                                        //get the function
+                                        $function = str_replace( "@double_dot@", "::", $function_var );
+                                        $params = null;
+
+                                    }
+
+                                    // replace back the @double_dot@ with ::
+                                    $function = str_replace( "@double_dot@", "::", $function );
+                                    $params = str_replace( "@double_dot@", "::", $params );
+                            }
+                            else
+                                    $function = $params = null;
+
+                            //if it is inside a loop
+                            if( $loop_level ){
+                                    //verify the variable name
+                                    if( $var_name == 'key' )
+                                            $php_var = '$key' . $loop_level;
+                                    elseif( $var_name == 'value' )
+                                            $php_var = '$value' . $loop_level . $variable_path;
+                                    elseif( $var_name == 'counter' )
+                                            $php_var = '$counter' . $loop_level;
+                                    else
+                                            $php_var = '$' . $var_name . $variable_path;
+                            }else
+                                    $php_var = '$' . $var_name . $variable_path;
+                            
+                            // compile the variable for php
+                            if( isset( $function ) )
+                                    $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . ( $params ? "( $function( $php_var, $params ) )" : "$function( $php_var )" ) . $php_right_delimiter;
+                            else
+                                    $php_var = $php_left_delimiter . ( !$is_init_variable && $echo ? 'echo ' : null ) . $php_var . $extra_var . $php_right_delimiter;
+                            
+                            $html = str_replace( $tag, $php_var, $html );
+
+
+                    }
+                }
+
+		return $html;
+	}
+
+
+
+	/**
+	 * Check if function is in black list (sandbox)
+	 *
+	 * @param string $code
+	 * @param string $tag
+	 */
+	protected function function_check( $code ){
+
+		$preg = '#(\W|\s)' . implode( '(\W|\s)|(\W|\s)', self::$black_list ) . '(\W|\s)#';
+
+		// check if the function is in the black list (or not in white list)
+		if( count(self::$black_list) && preg_match( $preg, $code, $match ) ){
+
+			// find the line of the error
+			$line = 0;
+			$rows=explode("\n",$this->tpl['source']);
+			while( !strpos($rows[$line],$code) )
+				$line++;
+
+			// stop the execution of the script
+			$e = new RainTpl_SyntaxException('Unallowed syntax in ' . $this->tpl['tpl_filename'] . ' template');
+			throw $e->setTemplateFile($this->tpl['tpl_filename'])
+				->setTag($code)
+				->setTemplateLine($line);
+		}
+
+	}
+
+	/**
+	 * Prints debug info about exception or passes it further if debug is disabled.
+	 *
+	 * @param RainTpl_Exception $e
+	 * @return string
+	 */
+	protected function printDebug(RainTpl_Exception $e){
+		if (!self::$debug) {
+			throw $e;
+		}
+		$output = sprintf('<h2>Exception: %s</h2><h3>%s</h3><p>template: %s</p>',
+			get_class($e),
+			$e->getMessage(),
+			$e->getTemplateFile()
+		);
+		if ($e instanceof RainTpl_SyntaxException) {
+			if (null != $e->getTemplateLine()) {
+				$output .= '<p>line: ' . $e->getTemplateLine() . '</p>';
+			}
+			if (null != $e->getTag()) {
+				$output .= '<p>in tag: ' . htmlspecialchars($e->getTag()) . '</p>';
+			}
+			if (null != $e->getTemplateLine() && null != $e->getTag()) {
+				$rows=explode("\n",  htmlspecialchars($this->tpl['source']));
+				$rows[$e->getTemplateLine()] = '<font color=red>' . $rows[$e->getTemplateLine()] . '</font>';
+				$output .= '<h3>template code</h3>' . implode('<br />', $rows) . '</pre>';
+			}
+		}
+		$output .= sprintf('<h3>trace</h3><p>In %s on line %d</p><pre>%s</pre>',
+			$e->getFile(), $e->getLine(),
+			nl2br(htmlspecialchars($e->getTraceAsString()))
+		);
+		return $output;
+	}
+}
+
+
+/**
+ * Basic Rain tpl exception.
+ */
+class RainTpl_Exception extends Exception{
+	/**
+	 * Path of template file with error.
+	 */
+	protected $templateFile = '';
+
+	/**
+	 * Returns path of template file with error.
+	 *
+	 * @return string
+	 */
+	public function getTemplateFile()
+	{
+		return $this->templateFile;
+	}
+
+	/**
+	 * Sets path of template file with error.
+	 *
+	 * @param string $templateFile
+	 * @return RainTpl_Exception
+	 */
+	public function setTemplateFile($templateFile)
+	{
+		$this->templateFile = (string) $templateFile;
+		return $this;
+	}
+}
+
+/**
+ * Exception thrown when template file does not exists.
+ */
+class RainTpl_NotFoundException extends RainTpl_Exception{
+}
+
+/**
+ * Exception thrown when syntax error occurs.
+ */
+class RainTpl_SyntaxException extends RainTpl_Exception{
+	/**
+	 * Line in template file where error has occured.
+	 *
+	 * @var int | null
+	 */
+	protected $templateLine = null;
+
+	/**
+	 * Tag which caused an error.
+	 *
+	 * @var string | null
+	 */
+	protected $tag = null;
+
+	/**
+	 * Returns line in template file where error has occured
+	 * or null if line is not defined.
+	 *
+	 * @return int | null
+	 */
+	public function getTemplateLine()
+	{
+		return $this->templateLine;
+	}
+
+	/**
+	 * Sets  line in template file where error has occured.
+	 *
+	 * @param int $templateLine
+	 * @return RainTpl_SyntaxException
+	 */
+	public function setTemplateLine($templateLine)
+	{
+		$this->templateLine = (int) $templateLine;
+		return $this;
+	}
+
+	/**
+	 * Returns tag which caused an error.
+	 *
+	 * @return string
+	 */
+	public function getTag()
+	{
+		return $this->tag;
+	}
+
+	/**
+	 * Sets tag which caused an error.
+	 *
+	 * @param string $tag
+	 * @return RainTpl_SyntaxException
+	 */
+	public function setTag($tag)
+	{
+		$this->tag = (string) $tag;
+		return $this;
+	}
+}
+
+// -- end

+ 1671 - 0
lib/rawdeflate.js

@@ -0,0 +1,1671 @@
+/*
+ * $Id: rawdeflate.js,v 0.3 2009/03/01 19:05:05 dankogai Exp dankogai $
+ *
+ * Original:
+ *   http://www.onicos.com/staff/iz/amuse/javascript/expert/deflate.txt
+ */
+
+(function(){
+
+/* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
+ * Version: 1.0.1
+ * LastModified: Dec 25 1999
+ */
+
+/* Interface:
+ * data = zip_deflate(src);
+ */
+
+/* constant parameters */
+var zip_WSIZE = 32768;		// Sliding Window size
+var zip_STORED_BLOCK = 0;
+var zip_STATIC_TREES = 1;
+var zip_DYN_TREES    = 2;
+
+/* for deflate */
+var zip_DEFAULT_LEVEL = 6;
+var zip_FULL_SEARCH = true;
+var zip_INBUFSIZ = 32768;	// Input buffer size
+var zip_INBUF_EXTRA = 64;	// Extra buffer
+var zip_OUTBUFSIZ = 1024 * 8;
+var zip_window_size = 2 * zip_WSIZE;
+var zip_MIN_MATCH = 3;
+var zip_MAX_MATCH = 258;
+var zip_BITS = 16;
+// for SMALL_MEM
+var zip_LIT_BUFSIZE = 0x2000;
+var zip_HASH_BITS = 13;
+// for MEDIUM_MEM
+// var zip_LIT_BUFSIZE = 0x4000;
+// var zip_HASH_BITS = 14;
+// for BIG_MEM
+// var zip_LIT_BUFSIZE = 0x8000;
+// var zip_HASH_BITS = 15;
+if(zip_LIT_BUFSIZE > zip_INBUFSIZ)
+    alert("error: zip_INBUFSIZ is too small");
+if((zip_WSIZE<<1) > (1<<zip_BITS))
+    alert("error: zip_WSIZE is too large");
+if(zip_HASH_BITS > zip_BITS-1)
+    alert("error: zip_HASH_BITS is too large");
+if(zip_HASH_BITS < 8 || zip_MAX_MATCH != 258)
+    alert("error: Code too clever");
+var zip_DIST_BUFSIZE = zip_LIT_BUFSIZE;
+var zip_HASH_SIZE = 1 << zip_HASH_BITS;
+var zip_HASH_MASK = zip_HASH_SIZE - 1;
+var zip_WMASK = zip_WSIZE - 1;
+var zip_NIL = 0; // Tail of hash chains
+var zip_TOO_FAR = 4096;
+var zip_MIN_LOOKAHEAD = zip_MAX_MATCH + zip_MIN_MATCH + 1;
+var zip_MAX_DIST = zip_WSIZE - zip_MIN_LOOKAHEAD;
+var zip_SMALLEST = 1;
+var zip_MAX_BITS = 15;
+var zip_MAX_BL_BITS = 7;
+var zip_LENGTH_CODES = 29;
+var zip_LITERALS =256;
+var zip_END_BLOCK = 256;
+var zip_L_CODES = zip_LITERALS + 1 + zip_LENGTH_CODES;
+var zip_D_CODES = 30;
+var zip_BL_CODES = 19;
+var zip_REP_3_6 = 16;
+var zip_REPZ_3_10 = 17;
+var zip_REPZ_11_138 = 18;
+var zip_HEAP_SIZE = 2 * zip_L_CODES + 1;
+var zip_H_SHIFT = parseInt((zip_HASH_BITS + zip_MIN_MATCH - 1) /
+			   zip_MIN_MATCH);
+
+/* variables */
+var zip_free_queue;
+var zip_qhead, zip_qtail;
+var zip_initflag;
+var zip_outbuf = null;
+var zip_outcnt, zip_outoff;
+var zip_complete;
+var zip_window;
+var zip_d_buf;
+var zip_l_buf;
+var zip_prev;
+var zip_bi_buf;
+var zip_bi_valid;
+var zip_block_start;
+var zip_ins_h;
+var zip_hash_head;
+var zip_prev_match;
+var zip_match_available;
+var zip_match_length;
+var zip_prev_length;
+var zip_strstart;
+var zip_match_start;
+var zip_eofile;
+var zip_lookahead;
+var zip_max_chain_length;
+var zip_max_lazy_match;
+var zip_compr_level;
+var zip_good_match;
+var zip_nice_match;
+var zip_dyn_ltree;
+var zip_dyn_dtree;
+var zip_static_ltree;
+var zip_static_dtree;
+var zip_bl_tree;
+var zip_l_desc;
+var zip_d_desc;
+var zip_bl_desc;
+var zip_bl_count;
+var zip_heap;
+var zip_heap_len;
+var zip_heap_max;
+var zip_depth;
+var zip_length_code;
+var zip_dist_code;
+var zip_base_length;
+var zip_base_dist;
+var zip_flag_buf;
+var zip_last_lit;
+var zip_last_dist;
+var zip_last_flags;
+var zip_flags;
+var zip_flag_bit;
+var zip_opt_len;
+var zip_static_len;
+var zip_deflate_data;
+var zip_deflate_pos;
+
+/* objects (deflate) */
+
+var zip_DeflateCT = function() {
+    this.fc = 0; // frequency count or bit string
+    this.dl = 0; // father node in Huffman tree or length of bit string
+}
+
+var zip_DeflateTreeDesc = function() {
+    this.dyn_tree = null;	// the dynamic tree
+    this.static_tree = null;	// corresponding static tree or NULL
+    this.extra_bits = null;	// extra bits for each code or NULL
+    this.extra_base = 0;	// base index for extra_bits
+    this.elems = 0;		// max number of elements in the tree
+    this.max_length = 0;	// max bit length for the codes
+    this.max_code = 0;		// largest code with non zero frequency
+}
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+var zip_DeflateConfiguration = function(a, b, c, d) {
+    this.good_length = a; // reduce lazy search above this match length
+    this.max_lazy = b;    // do not perform lazy search above this match length
+    this.nice_length = c; // quit search above this match length
+    this.max_chain = d;
+}
+
+var zip_DeflateBuffer = function() {
+    this.next = null;
+    this.len = 0;
+    this.ptr = new Array(zip_OUTBUFSIZ);
+    this.off = 0;
+}
+
+/* constant tables */
+var zip_extra_lbits = new Array(
+    0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0);
+var zip_extra_dbits = new Array(
+    0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13);
+var zip_extra_blbits = new Array(
+    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7);
+var zip_bl_order = new Array(
+    16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15);
+var zip_configuration_table = new Array(
+	new zip_DeflateConfiguration(0,    0,   0,    0),
+	new zip_DeflateConfiguration(4,    4,   8,    4),
+	new zip_DeflateConfiguration(4,    5,  16,    8),
+	new zip_DeflateConfiguration(4,    6,  32,   32),
+	new zip_DeflateConfiguration(4,    4,  16,   16),
+	new zip_DeflateConfiguration(8,   16,  32,   32),
+	new zip_DeflateConfiguration(8,   16, 128,  128),
+	new zip_DeflateConfiguration(8,   32, 128,  256),
+	new zip_DeflateConfiguration(32, 128, 258, 1024),
+	new zip_DeflateConfiguration(32, 258, 258, 4096));
+
+
+/* routines (deflate) */
+
+var zip_deflate_start = function(level) {
+    var i;
+
+    if(!level)
+	level = zip_DEFAULT_LEVEL;
+    else if(level < 1)
+	level = 1;
+    else if(level > 9)
+	level = 9;
+
+    zip_compr_level = level;
+    zip_initflag = false;
+    zip_eofile = false;
+    if(zip_outbuf != null)
+	return;
+
+    zip_free_queue = zip_qhead = zip_qtail = null;
+    zip_outbuf = new Array(zip_OUTBUFSIZ);
+    zip_window = new Array(zip_window_size);
+    zip_d_buf = new Array(zip_DIST_BUFSIZE);
+    zip_l_buf = new Array(zip_INBUFSIZ + zip_INBUF_EXTRA);
+    zip_prev = new Array(1 << zip_BITS);
+    zip_dyn_ltree = new Array(zip_HEAP_SIZE);
+    for(i = 0; i < zip_HEAP_SIZE; i++)
+	zip_dyn_ltree[i] = new zip_DeflateCT();
+    zip_dyn_dtree = new Array(2*zip_D_CODES+1);
+    for(i = 0; i < 2*zip_D_CODES+1; i++)
+	zip_dyn_dtree[i] = new zip_DeflateCT();
+    zip_static_ltree = new Array(zip_L_CODES+2);
+    for(i = 0; i < zip_L_CODES+2; i++)
+	zip_static_ltree[i] = new zip_DeflateCT();
+    zip_static_dtree = new Array(zip_D_CODES);
+    for(i = 0; i < zip_D_CODES; i++)
+	zip_static_dtree[i] = new zip_DeflateCT();
+    zip_bl_tree = new Array(2*zip_BL_CODES+1);
+    for(i = 0; i < 2*zip_BL_CODES+1; i++)
+	zip_bl_tree[i] = new zip_DeflateCT();
+    zip_l_desc = new zip_DeflateTreeDesc();
+    zip_d_desc = new zip_DeflateTreeDesc();
+    zip_bl_desc = new zip_DeflateTreeDesc();
+    zip_bl_count = new Array(zip_MAX_BITS+1);
+    zip_heap = new Array(2*zip_L_CODES+1);
+    zip_depth = new Array(2*zip_L_CODES+1);
+    zip_length_code = new Array(zip_MAX_MATCH-zip_MIN_MATCH+1);
+    zip_dist_code = new Array(512);
+    zip_base_length = new Array(zip_LENGTH_CODES);
+    zip_base_dist = new Array(zip_D_CODES);
+    zip_flag_buf = new Array(parseInt(zip_LIT_BUFSIZE / 8));
+}
+
+var zip_deflate_end = function() {
+    zip_free_queue = zip_qhead = zip_qtail = null;
+    zip_outbuf = null;
+    zip_window = null;
+    zip_d_buf = null;
+    zip_l_buf = null;
+    zip_prev = null;
+    zip_dyn_ltree = null;
+    zip_dyn_dtree = null;
+    zip_static_ltree = null;
+    zip_static_dtree = null;
+    zip_bl_tree = null;
+    zip_l_desc = null;
+    zip_d_desc = null;
+    zip_bl_desc = null;
+    zip_bl_count = null;
+    zip_heap = null;
+    zip_depth = null;
+    zip_length_code = null;
+    zip_dist_code = null;
+    zip_base_length = null;
+    zip_base_dist = null;
+    zip_flag_buf = null;
+}
+
+var zip_reuse_queue = function(p) {
+    p.next = zip_free_queue;
+    zip_free_queue = p;
+}
+
+var zip_new_queue = function() {
+    var p;
+
+    if(zip_free_queue != null)
+    {
+	p = zip_free_queue;
+	zip_free_queue = zip_free_queue.next;
+    }
+    else
+	p = new zip_DeflateBuffer();
+    p.next = null;
+    p.len = p.off = 0;
+
+    return p;
+}
+
+var zip_head1 = function(i) {
+    return zip_prev[zip_WSIZE + i];
+}
+
+var zip_head2 = function(i, val) {
+    return zip_prev[zip_WSIZE + i] = val;
+}
+
+/* put_byte is used for the compressed output, put_ubyte for the
+ * uncompressed output. However unlzw() uses window for its
+ * suffix table instead of its output buffer, so it does not use put_ubyte
+ * (to be cleaned up).
+ */
+var zip_put_byte = function(c) {
+    zip_outbuf[zip_outoff + zip_outcnt++] = c;
+    if(zip_outoff + zip_outcnt == zip_OUTBUFSIZ)
+	zip_qoutbuf();
+}
+
+/* Output a 16 bit value, lsb first */
+var zip_put_short = function(w) {
+    w &= 0xffff;
+    if(zip_outoff + zip_outcnt < zip_OUTBUFSIZ - 2) {
+	zip_outbuf[zip_outoff + zip_outcnt++] = (w & 0xff);
+	zip_outbuf[zip_outoff + zip_outcnt++] = (w >>> 8);
+    } else {
+	zip_put_byte(w & 0xff);
+	zip_put_byte(w >>> 8);
+    }
+}
+
+/* ==========================================================================
+ * Insert string s in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * IN  assertion: all calls to to INSERT_STRING are made with consecutive
+ *    input characters and the first MIN_MATCH bytes of s are valid
+ *    (except for the last MIN_MATCH-1 bytes of the input file).
+ */
+var zip_INSERT_STRING = function() {
+    zip_ins_h = ((zip_ins_h << zip_H_SHIFT)
+		 ^ (zip_window[zip_strstart + zip_MIN_MATCH - 1] & 0xff))
+	& zip_HASH_MASK;
+    zip_hash_head = zip_head1(zip_ins_h);
+    zip_prev[zip_strstart & zip_WMASK] = zip_hash_head;
+    zip_head2(zip_ins_h, zip_strstart);
+}
+
+/* Send a code of the given tree. c and tree must not have side effects */
+var zip_SEND_CODE = function(c, tree) {
+    zip_send_bits(tree[c].fc, tree[c].dl);
+}
+
+/* Mapping from a distance to a distance code. dist is the distance - 1 and
+ * must not have side effects. dist_code[256] and dist_code[257] are never
+ * used.
+ */
+var zip_D_CODE = function(dist) {
+    return (dist < 256 ? zip_dist_code[dist]
+	    : zip_dist_code[256 + (dist>>7)]) & 0xff;
+}
+
+/* ==========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+var zip_SMALLER = function(tree, n, m) {
+    return tree[n].fc < tree[m].fc ||
+      (tree[n].fc == tree[m].fc && zip_depth[n] <= zip_depth[m]);
+}
+
+/* ==========================================================================
+ * read string data
+ */
+var zip_read_buff = function(buff, offset, n) {
+    var i;
+    for(i = 0; i < n && zip_deflate_pos < zip_deflate_data.length; i++)
+	buff[offset + i] =
+	    zip_deflate_data.charCodeAt(zip_deflate_pos++) & 0xff;
+    return i;
+}
+
+/* ==========================================================================
+ * Initialize the "longest match" routines for a new file
+ */
+var zip_lm_init = function() {
+    var j;
+
+    /* Initialize the hash table. */
+    for(j = 0; j < zip_HASH_SIZE; j++)
+//	zip_head2(j, zip_NIL);
+	zip_prev[zip_WSIZE + j] = 0;
+    /* prev will be initialized on the fly */
+
+    /* Set the default configuration parameters:
+     */
+    zip_max_lazy_match = zip_configuration_table[zip_compr_level].max_lazy;
+    zip_good_match     = zip_configuration_table[zip_compr_level].good_length;
+    if(!zip_FULL_SEARCH)
+	zip_nice_match = zip_configuration_table[zip_compr_level].nice_length;
+    zip_max_chain_length = zip_configuration_table[zip_compr_level].max_chain;
+
+    zip_strstart = 0;
+    zip_block_start = 0;
+
+    zip_lookahead = zip_read_buff(zip_window, 0, 2 * zip_WSIZE);
+    if(zip_lookahead <= 0) {
+	zip_eofile = true;
+	zip_lookahead = 0;
+	return;
+    }
+    zip_eofile = false;
+    /* Make sure that we always have enough lookahead. This is important
+     * if input comes from a device such as a tty.
+     */
+    while(zip_lookahead < zip_MIN_LOOKAHEAD && !zip_eofile)
+	zip_fill_window();
+
+    /* If lookahead < MIN_MATCH, ins_h is garbage, but this is
+     * not important since only literal bytes will be emitted.
+     */
+    zip_ins_h = 0;
+    for(j = 0; j < zip_MIN_MATCH - 1; j++) {
+//      UPDATE_HASH(ins_h, window[j]);
+	zip_ins_h = ((zip_ins_h << zip_H_SHIFT) ^ (zip_window[j] & 0xff)) & zip_HASH_MASK;
+    }
+}
+
+/* ==========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ */
+var zip_longest_match = function(cur_match) {
+    var chain_length = zip_max_chain_length; // max hash chain length
+    var scanp = zip_strstart; // current string
+    var matchp;		// matched string
+    var len;		// length of current match
+    var best_len = zip_prev_length;	// best match length so far
+
+    /* Stop when cur_match becomes <= limit. To simplify the code,
+     * we prevent matches with the string of window index 0.
+     */
+    var limit = (zip_strstart > zip_MAX_DIST ? zip_strstart - zip_MAX_DIST : zip_NIL);
+
+    var strendp = zip_strstart + zip_MAX_MATCH;
+    var scan_end1 = zip_window[scanp + best_len - 1];
+    var scan_end  = zip_window[scanp + best_len];
+
+    /* Do not waste too much time if we already have a good match: */
+    if(zip_prev_length >= zip_good_match)
+	chain_length >>= 2;
+
+//  Assert(encoder->strstart <= window_size-MIN_LOOKAHEAD, "insufficient lookahead");
+
+    do {
+//    Assert(cur_match < encoder->strstart, "no future");
+	matchp = cur_match;
+
+	/* Skip to next match if the match length cannot increase
+	    * or if the match length is less than 2:
+	*/
+	if(zip_window[matchp + best_len]	!= scan_end  ||
+	   zip_window[matchp + best_len - 1]	!= scan_end1 ||
+	   zip_window[matchp]			!= zip_window[scanp] ||
+	   zip_window[++matchp]			!= zip_window[scanp + 1]) {
+	    continue;
+	}
+
+	/* The check at best_len-1 can be removed because it will be made
+         * again later. (This heuristic is not always a win.)
+         * It is not necessary to compare scan[2] and match[2] since they
+         * are always equal when the other bytes match, given that
+         * the hash keys are equal and that HASH_BITS >= 8.
+         */
+	scanp += 2;
+	matchp++;
+
+	/* We check for insufficient lookahead only every 8th comparison;
+         * the 256th check will be made at strstart+258.
+         */
+	do {
+	} while(zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		zip_window[++scanp] == zip_window[++matchp] &&
+		scanp < strendp);
+
+      len = zip_MAX_MATCH - (strendp - scanp);
+      scanp = strendp - zip_MAX_MATCH;
+
+      if(len > best_len) {
+	  zip_match_start = cur_match;
+	  best_len = len;
+	  if(zip_FULL_SEARCH) {
+	      if(len >= zip_MAX_MATCH) break;
+	  } else {
+	      if(len >= zip_nice_match) break;
+	  }
+
+	  scan_end1  = zip_window[scanp + best_len-1];
+	  scan_end   = zip_window[scanp + best_len];
+      }
+    } while((cur_match = zip_prev[cur_match & zip_WMASK]) > limit
+	    && --chain_length != 0);
+
+    return best_len;
+}
+
+/* ==========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead, and sets eofile if end of input file.
+ * IN assertion: lookahead < MIN_LOOKAHEAD && strstart + lookahead > 0
+ * OUT assertions: at least one byte has been read, or eofile is set;
+ *    file reads are performed for at least two bytes (required for the
+ *    translate_eol option).
+ */
+var zip_fill_window = function() {
+    var n, m;
+
+    // Amount of free space at the end of the window.
+    var more = zip_window_size - zip_lookahead - zip_strstart;
+
+    /* If the window is almost full and there is insufficient lookahead,
+     * move the upper half to the lower one to make room in the upper half.
+     */
+    if(more == -1) {
+	/* Very unlikely, but possible on 16 bit machine if strstart == 0
+         * and lookahead == 1 (input done one byte at time)
+         */
+	more--;
+    } else if(zip_strstart >= zip_WSIZE + zip_MAX_DIST) {
+	/* By the IN assertion, the window is not empty so we can't confuse
+         * more == 0 with more == 64K on a 16 bit machine.
+         */
+//	Assert(window_size == (ulg)2*WSIZE, "no sliding with BIG_MEM");
+
+//	System.arraycopy(window, WSIZE, window, 0, WSIZE);
+	for(n = 0; n < zip_WSIZE; n++)
+	    zip_window[n] = zip_window[n + zip_WSIZE];
+      
+	zip_match_start -= zip_WSIZE;
+	zip_strstart    -= zip_WSIZE; /* we now have strstart >= MAX_DIST: */
+	zip_block_start -= zip_WSIZE;
+
+	for(n = 0; n < zip_HASH_SIZE; n++) {
+	    m = zip_head1(n);
+	    zip_head2(n, m >= zip_WSIZE ? m - zip_WSIZE : zip_NIL);
+	}
+	for(n = 0; n < zip_WSIZE; n++) {
+	    /* If n is not on any hash chain, prev[n] is garbage but
+	     * its value will never be used.
+	     */
+	    m = zip_prev[n];
+	    zip_prev[n] = (m >= zip_WSIZE ? m - zip_WSIZE : zip_NIL);
+	}
+	more += zip_WSIZE;
+    }
+    // At this point, more >= 2
+    if(!zip_eofile) {
+	n = zip_read_buff(zip_window, zip_strstart + zip_lookahead, more);
+	if(n <= 0)
+	    zip_eofile = true;
+	else
+	    zip_lookahead += n;
+    }
+}
+
+/* ==========================================================================
+ * Processes a new input file and return its compressed length. This
+ * function does not perform lazy evaluationof matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+var zip_deflate_fast = function() {
+    while(zip_lookahead != 0 && zip_qhead == null) {
+	var flush; // set if current block must be flushed
+
+	/* Insert the string window[strstart .. strstart+2] in the
+	 * dictionary, and set hash_head to the head of the hash chain:
+	 */
+	zip_INSERT_STRING();
+
+	/* Find the longest match, discarding those <= prev_length.
+	 * At this point we have always match_length < MIN_MATCH
+	 */
+	if(zip_hash_head != zip_NIL &&
+	   zip_strstart - zip_hash_head <= zip_MAX_DIST) {
+	    /* To simplify the code, we prevent matches with the string
+	     * of window index 0 (in particular we have to avoid a match
+	     * of the string with itself at the start of the input file).
+	     */
+	    zip_match_length = zip_longest_match(zip_hash_head);
+	    /* longest_match() sets match_start */
+	    if(zip_match_length > zip_lookahead)
+		zip_match_length = zip_lookahead;
+	}
+	if(zip_match_length >= zip_MIN_MATCH) {
+//	    check_match(strstart, match_start, match_length);
+
+	    flush = zip_ct_tally(zip_strstart - zip_match_start,
+				 zip_match_length - zip_MIN_MATCH);
+	    zip_lookahead -= zip_match_length;
+
+	    /* Insert new strings in the hash table only if the match length
+	     * is not too large. This saves time but degrades compression.
+	     */
+	    if(zip_match_length <= zip_max_lazy_match) {
+		zip_match_length--; // string at strstart already in hash table
+		do {
+		    zip_strstart++;
+		    zip_INSERT_STRING();
+		    /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+		     * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH
+		     * these bytes are garbage, but it does not matter since
+		     * the next lookahead bytes will be emitted as literals.
+		     */
+		} while(--zip_match_length != 0);
+		zip_strstart++;
+	    } else {
+		zip_strstart += zip_match_length;
+		zip_match_length = 0;
+		zip_ins_h = zip_window[zip_strstart] & 0xff;
+//		UPDATE_HASH(ins_h, window[strstart + 1]);
+		zip_ins_h = ((zip_ins_h<<zip_H_SHIFT) ^ (zip_window[zip_strstart + 1] & 0xff)) & zip_HASH_MASK;
+
+//#if MIN_MATCH != 3
+//		Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+
+	    }
+	} else {
+	    /* No match, output a literal byte */
+	    flush = zip_ct_tally(0, zip_window[zip_strstart] & 0xff);
+	    zip_lookahead--;
+	    zip_strstart++;
+	}
+	if(flush) {
+	    zip_flush_block(0);
+	    zip_block_start = zip_strstart;
+	}
+
+	/* Make sure that we always have enough lookahead, except
+	 * at the end of the input file. We need MAX_MATCH bytes
+	 * for the next match, plus MIN_MATCH bytes to insert the
+	 * string following the next match.
+	 */
+	while(zip_lookahead < zip_MIN_LOOKAHEAD && !zip_eofile)
+	    zip_fill_window();
+    }
+}
+
+var zip_deflate_better = function() {
+    /* Process the input block. */
+    while(zip_lookahead != 0 && zip_qhead == null) {
+	/* Insert the string window[strstart .. strstart+2] in the
+	 * dictionary, and set hash_head to the head of the hash chain:
+	 */
+	zip_INSERT_STRING();
+
+	/* Find the longest match, discarding those <= prev_length.
+	 */
+	zip_prev_length = zip_match_length;
+	zip_prev_match = zip_match_start;
+	zip_match_length = zip_MIN_MATCH - 1;
+
+	if(zip_hash_head != zip_NIL &&
+	   zip_prev_length < zip_max_lazy_match &&
+	   zip_strstart - zip_hash_head <= zip_MAX_DIST) {
+	    /* To simplify the code, we prevent matches with the string
+	     * of window index 0 (in particular we have to avoid a match
+	     * of the string with itself at the start of the input file).
+	     */
+	    zip_match_length = zip_longest_match(zip_hash_head);
+	    /* longest_match() sets match_start */
+	    if(zip_match_length > zip_lookahead)
+		zip_match_length = zip_lookahead;
+
+	    /* Ignore a length 3 match if it is too distant: */
+	    if(zip_match_length == zip_MIN_MATCH &&
+	       zip_strstart - zip_match_start > zip_TOO_FAR) {
+		/* If prev_match is also MIN_MATCH, match_start is garbage
+		 * but we will ignore the current match anyway.
+		 */
+		zip_match_length--;
+	    }
+	}
+	/* If there was a match at the previous step and the current
+	 * match is not better, output the previous match:
+	 */
+	if(zip_prev_length >= zip_MIN_MATCH &&
+	   zip_match_length <= zip_prev_length) {
+	    var flush; // set if current block must be flushed
+
+//	    check_match(strstart - 1, prev_match, prev_length);
+	    flush = zip_ct_tally(zip_strstart - 1 - zip_prev_match,
+				 zip_prev_length - zip_MIN_MATCH);
+
+	    /* Insert in hash table all strings up to the end of the match.
+	     * strstart-1 and strstart are already inserted.
+	     */
+	    zip_lookahead -= zip_prev_length - 1;
+	    zip_prev_length -= 2;
+	    do {
+		zip_strstart++;
+		zip_INSERT_STRING();
+		/* strstart never exceeds WSIZE-MAX_MATCH, so there are
+		 * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH
+		 * these bytes are garbage, but it does not matter since the
+		 * next lookahead bytes will always be emitted as literals.
+		 */
+	    } while(--zip_prev_length != 0);
+	    zip_match_available = 0;
+	    zip_match_length = zip_MIN_MATCH - 1;
+	    zip_strstart++;
+	    if(flush) {
+		zip_flush_block(0);
+		zip_block_start = zip_strstart;
+	    }
+	} else if(zip_match_available != 0) {
+	    /* If there was no match at the previous position, output a
+	     * single literal. If there was a match but the current match
+	     * is longer, truncate the previous match to a single literal.
+	     */
+	    if(zip_ct_tally(0, zip_window[zip_strstart - 1] & 0xff)) {
+		zip_flush_block(0);
+		zip_block_start = zip_strstart;
+	    }
+	    zip_strstart++;
+	    zip_lookahead--;
+	} else {
+	    /* There is no previous match to compare with, wait for
+	     * the next step to decide.
+	     */
+	    zip_match_available = 1;
+	    zip_strstart++;
+	    zip_lookahead--;
+	}
+
+	/* Make sure that we always have enough lookahead, except
+	 * at the end of the input file. We need MAX_MATCH bytes
+	 * for the next match, plus MIN_MATCH bytes to insert the
+	 * string following the next match.
+	 */
+	while(zip_lookahead < zip_MIN_LOOKAHEAD && !zip_eofile)
+	    zip_fill_window();
+    }
+}
+
+var zip_init_deflate = function() {
+    if(zip_eofile)
+	return;
+    zip_bi_buf = 0;
+    zip_bi_valid = 0;
+    zip_ct_init();
+    zip_lm_init();
+
+    zip_qhead = null;
+    zip_outcnt = 0;
+    zip_outoff = 0;
+
+    if(zip_compr_level <= 3)
+    {
+	zip_prev_length = zip_MIN_MATCH - 1;
+	zip_match_length = 0;
+    }
+    else
+    {
+	zip_match_length = zip_MIN_MATCH - 1;
+	zip_match_available = 0;
+    }
+
+    zip_complete = false;
+}
+
+/* ==========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+var zip_deflate_internal = function(buff, off, buff_size) {
+    var n;
+
+    if(!zip_initflag)
+    {
+	zip_init_deflate();
+	zip_initflag = true;
+	if(zip_lookahead == 0) { // empty
+	    zip_complete = true;
+	    return 0;
+	}
+    }
+
+    if((n = zip_qcopy(buff, off, buff_size)) == buff_size)
+	return buff_size;
+
+    if(zip_complete)
+	return n;
+
+    if(zip_compr_level <= 3) // optimized for speed
+	zip_deflate_fast();
+    else
+	zip_deflate_better();
+    if(zip_lookahead == 0) {
+	if(zip_match_available != 0)
+	    zip_ct_tally(0, zip_window[zip_strstart - 1] & 0xff);
+	zip_flush_block(1);
+	zip_complete = true;
+    }
+    return n + zip_qcopy(buff, n + off, buff_size - n);
+}
+
+var zip_qcopy = function(buff, off, buff_size) {
+    var n, i, j;
+
+    n = 0;
+    while(zip_qhead != null && n < buff_size)
+    {
+	i = buff_size - n;
+	if(i > zip_qhead.len)
+	    i = zip_qhead.len;
+//      System.arraycopy(qhead.ptr, qhead.off, buff, off + n, i);
+	for(j = 0; j < i; j++)
+	    buff[off + n + j] = zip_qhead.ptr[zip_qhead.off + j];
+	
+	zip_qhead.off += i;
+	zip_qhead.len -= i;
+	n += i;
+	if(zip_qhead.len == 0) {
+	    var p;
+	    p = zip_qhead;
+	    zip_qhead = zip_qhead.next;
+	    zip_reuse_queue(p);
+	}
+    }
+
+    if(n == buff_size)
+	return n;
+
+    if(zip_outoff < zip_outcnt) {
+	i = buff_size - n;
+	if(i > zip_outcnt - zip_outoff)
+	    i = zip_outcnt - zip_outoff;
+	// System.arraycopy(outbuf, outoff, buff, off + n, i);
+	for(j = 0; j < i; j++)
+	    buff[off + n + j] = zip_outbuf[zip_outoff + j];
+	zip_outoff += i;
+	n += i;
+	if(zip_outcnt == zip_outoff)
+	    zip_outcnt = zip_outoff = 0;
+    }
+    return n;
+}
+
+/* ==========================================================================
+ * Allocate the match buffer, initialize the various tables and save the
+ * location of the internal file attribute (ascii/binary) and method
+ * (DEFLATE/STORE).
+ */
+var zip_ct_init = function() {
+    var n;	// iterates over tree elements
+    var bits;	// bit counter
+    var length;	// length value
+    var code;	// code value
+    var dist;	// distance index
+
+    if(zip_static_dtree[0].dl != 0) return; // ct_init already called
+
+    zip_l_desc.dyn_tree		= zip_dyn_ltree;
+    zip_l_desc.static_tree	= zip_static_ltree;
+    zip_l_desc.extra_bits	= zip_extra_lbits;
+    zip_l_desc.extra_base	= zip_LITERALS + 1;
+    zip_l_desc.elems		= zip_L_CODES;
+    zip_l_desc.max_length	= zip_MAX_BITS;
+    zip_l_desc.max_code		= 0;
+
+    zip_d_desc.dyn_tree		= zip_dyn_dtree;
+    zip_d_desc.static_tree	= zip_static_dtree;
+    zip_d_desc.extra_bits	= zip_extra_dbits;
+    zip_d_desc.extra_base	= 0;
+    zip_d_desc.elems		= zip_D_CODES;
+    zip_d_desc.max_length	= zip_MAX_BITS;
+    zip_d_desc.max_code		= 0;
+
+    zip_bl_desc.dyn_tree	= zip_bl_tree;
+    zip_bl_desc.static_tree	= null;
+    zip_bl_desc.extra_bits	= zip_extra_blbits;
+    zip_bl_desc.extra_base	= 0;
+    zip_bl_desc.elems		= zip_BL_CODES;
+    zip_bl_desc.max_length	= zip_MAX_BL_BITS;
+    zip_bl_desc.max_code	= 0;
+
+    // Initialize the mapping length (0..255) -> length code (0..28)
+    length = 0;
+    for(code = 0; code < zip_LENGTH_CODES-1; code++) {
+	zip_base_length[code] = length;
+	for(n = 0; n < (1<<zip_extra_lbits[code]); n++)
+	    zip_length_code[length++] = code;
+    }
+    // Assert (length == 256, "ct_init: length != 256");
+
+    /* Note that the length 255 (match length 258) can be represented
+     * in two different ways: code 284 + 5 bits or code 285, so we
+     * overwrite length_code[255] to use the best encoding:
+     */
+    zip_length_code[length-1] = code;
+
+    /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+    dist = 0;
+    for(code = 0 ; code < 16; code++) {
+	zip_base_dist[code] = dist;
+	for(n = 0; n < (1<<zip_extra_dbits[code]); n++) {
+	    zip_dist_code[dist++] = code;
+	}
+    }
+    // Assert (dist == 256, "ct_init: dist != 256");
+    dist >>= 7; // from now on, all distances are divided by 128
+    for( ; code < zip_D_CODES; code++) {
+	zip_base_dist[code] = dist << 7;
+	for(n = 0; n < (1<<(zip_extra_dbits[code]-7)); n++)
+	    zip_dist_code[256 + dist++] = code;
+    }
+    // Assert (dist == 256, "ct_init: 256+dist != 512");
+
+    // Construct the codes of the static literal tree
+    for(bits = 0; bits <= zip_MAX_BITS; bits++)
+	zip_bl_count[bits] = 0;
+    n = 0;
+    while(n <= 143) { zip_static_ltree[n++].dl = 8; zip_bl_count[8]++; }
+    while(n <= 255) { zip_static_ltree[n++].dl = 9; zip_bl_count[9]++; }
+    while(n <= 279) { zip_static_ltree[n++].dl = 7; zip_bl_count[7]++; }
+    while(n <= 287) { zip_static_ltree[n++].dl = 8; zip_bl_count[8]++; }
+    /* Codes 286 and 287 do not exist, but we must include them in the
+     * tree construction to get a canonical Huffman tree (longest code
+     * all ones)
+     */
+    zip_gen_codes(zip_static_ltree, zip_L_CODES + 1);
+
+    /* The static distance tree is trivial: */
+    for(n = 0; n < zip_D_CODES; n++) {
+	zip_static_dtree[n].dl = 5;
+	zip_static_dtree[n].fc = zip_bi_reverse(n, 5);
+    }
+
+    // Initialize the first block of the first file:
+    zip_init_block();
+}
+
+/* ==========================================================================
+ * Initialize a new block.
+ */
+var zip_init_block = function() {
+    var n; // iterates over tree elements
+
+    // Initialize the trees.
+    for(n = 0; n < zip_L_CODES;  n++) zip_dyn_ltree[n].fc = 0;
+    for(n = 0; n < zip_D_CODES;  n++) zip_dyn_dtree[n].fc = 0;
+    for(n = 0; n < zip_BL_CODES; n++) zip_bl_tree[n].fc = 0;
+
+    zip_dyn_ltree[zip_END_BLOCK].fc = 1;
+    zip_opt_len = zip_static_len = 0;
+    zip_last_lit = zip_last_dist = zip_last_flags = 0;
+    zip_flags = 0;
+    zip_flag_bit = 1;
+}
+
+/* ==========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+var zip_pqdownheap = function(
+    tree,	// the tree to restore
+    k) {	// node to move down
+    var v = zip_heap[k];
+    var j = k << 1;	// left son of k
+
+    while(j <= zip_heap_len) {
+	// Set j to the smallest of the two sons:
+	if(j < zip_heap_len &&
+	   zip_SMALLER(tree, zip_heap[j + 1], zip_heap[j]))
+	    j++;
+
+	// Exit if v is smaller than both sons
+	if(zip_SMALLER(tree, v, zip_heap[j]))
+	    break;
+
+	// Exchange v with the smallest son
+	zip_heap[k] = zip_heap[j];
+	k = j;
+
+	// And continue down the tree, setting j to the left son of k
+	j <<= 1;
+    }
+    zip_heap[k] = v;
+}
+
+/* ==========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+var zip_gen_bitlen = function(desc) { // the tree descriptor
+    var tree		= desc.dyn_tree;
+    var extra		= desc.extra_bits;
+    var base		= desc.extra_base;
+    var max_code	= desc.max_code;
+    var max_length	= desc.max_length;
+    var stree		= desc.static_tree;
+    var h;		// heap index
+    var n, m;		// iterate over the tree elements
+    var bits;		// bit length
+    var xbits;		// extra bits
+    var f;		// frequency
+    var overflow = 0;	// number of elements with bit length too large
+
+    for(bits = 0; bits <= zip_MAX_BITS; bits++)
+	zip_bl_count[bits] = 0;
+
+    /* In a first pass, compute the optimal bit lengths (which may
+     * overflow in the case of the bit length tree).
+     */
+    tree[zip_heap[zip_heap_max]].dl = 0; // root of the heap
+
+    for(h = zip_heap_max + 1; h < zip_HEAP_SIZE; h++) {
+	n = zip_heap[h];
+	bits = tree[tree[n].dl].dl + 1;
+	if(bits > max_length) {
+	    bits = max_length;
+	    overflow++;
+	}
+	tree[n].dl = bits;
+	// We overwrite tree[n].dl which is no longer needed
+
+	if(n > max_code)
+	    continue; // not a leaf node
+
+	zip_bl_count[bits]++;
+	xbits = 0;
+	if(n >= base)
+	    xbits = extra[n - base];
+	f = tree[n].fc;
+	zip_opt_len += f * (bits + xbits);
+	if(stree != null)
+	    zip_static_len += f * (stree[n].dl + xbits);
+    }
+    if(overflow == 0)
+	return;
+
+    // This happens for example on obj2 and pic of the Calgary corpus
+
+    // Find the first bit length which could increase:
+    do {
+	bits = max_length - 1;
+	while(zip_bl_count[bits] == 0)
+	    bits--;
+	zip_bl_count[bits]--;		// move one leaf down the tree
+	zip_bl_count[bits + 1] += 2;	// move one overflow item as its brother
+	zip_bl_count[max_length]--;
+	/* The brother of the overflow item also moves one step up,
+	 * but this does not affect bl_count[max_length]
+	 */
+	overflow -= 2;
+    } while(overflow > 0);
+
+    /* Now recompute all bit lengths, scanning in increasing frequency.
+     * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+     * lengths instead of fixing only the wrong ones. This idea is taken
+     * from 'ar' written by Haruhiko Okumura.)
+     */
+    for(bits = max_length; bits != 0; bits--) {
+	n = zip_bl_count[bits];
+	while(n != 0) {
+	    m = zip_heap[--h];
+	    if(m > max_code)
+		continue;
+	    if(tree[m].dl != bits) {
+		zip_opt_len += (bits - tree[m].dl) * tree[m].fc;
+		tree[m].fc = bits;
+	    }
+	    n--;
+	}
+    }
+}
+
+  /* ==========================================================================
+   * Generate the codes for a given tree and bit counts (which need not be
+   * optimal).
+   * IN assertion: the array bl_count contains the bit length statistics for
+   * the given tree and the field len is set for all tree elements.
+   * OUT assertion: the field code is set for all tree elements of non
+   *     zero code length.
+   */
+var zip_gen_codes = function(tree,	// the tree to decorate
+		   max_code) {	// largest code with non zero frequency
+    var next_code = new Array(zip_MAX_BITS+1); // next code value for each bit length
+    var code = 0;		// running code value
+    var bits;			// bit index
+    var n;			// code index
+
+    /* The distribution counts are first used to generate the code values
+     * without bit reversal.
+     */
+    for(bits = 1; bits <= zip_MAX_BITS; bits++) {
+	code = ((code + zip_bl_count[bits-1]) << 1);
+	next_code[bits] = code;
+    }
+
+    /* Check that the bit counts in bl_count are consistent. The last code
+     * must be all ones.
+     */
+//    Assert (code + encoder->bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+//	    "inconsistent bit counts");
+//    Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+    for(n = 0; n <= max_code; n++) {
+	var len = tree[n].dl;
+	if(len == 0)
+	    continue;
+	// Now reverse the bits
+	tree[n].fc = zip_bi_reverse(next_code[len]++, len);
+
+//      Tracec(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+//	  n, (isgraph(n) ? n : ' '), len, tree[n].fc, next_code[len]-1));
+    }
+}
+
+/* ==========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+var zip_build_tree = function(desc) { // the tree descriptor
+    var tree	= desc.dyn_tree;
+    var stree	= desc.static_tree;
+    var elems	= desc.elems;
+    var n, m;		// iterate over heap elements
+    var max_code = -1;	// largest code with non zero frequency
+    var node = elems;	// next internal node of the tree
+
+    /* Construct the initial heap, with least frequent element in
+     * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+     * heap[0] is not used.
+     */
+    zip_heap_len = 0;
+    zip_heap_max = zip_HEAP_SIZE;
+
+    for(n = 0; n < elems; n++) {
+	if(tree[n].fc != 0) {
+	    zip_heap[++zip_heap_len] = max_code = n;
+	    zip_depth[n] = 0;
+	} else
+	    tree[n].dl = 0;
+    }
+
+    /* The pkzip format requires that at least one distance code exists,
+     * and that at least one bit should be sent even if there is only one
+     * possible code. So to avoid special checks later on we force at least
+     * two codes of non zero frequency.
+     */
+    while(zip_heap_len < 2) {
+	var xnew = zip_heap[++zip_heap_len] = (max_code < 2 ? ++max_code : 0);
+	tree[xnew].fc = 1;
+	zip_depth[xnew] = 0;
+	zip_opt_len--;
+	if(stree != null)
+	    zip_static_len -= stree[xnew].dl;
+	// new is 0 or 1 so it does not have extra bits
+    }
+    desc.max_code = max_code;
+
+    /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+     * establish sub-heaps of increasing lengths:
+     */
+    for(n = zip_heap_len >> 1; n >= 1; n--)
+	zip_pqdownheap(tree, n);
+
+    /* Construct the Huffman tree by repeatedly combining the least two
+     * frequent nodes.
+     */
+    do {
+	n = zip_heap[zip_SMALLEST];
+	zip_heap[zip_SMALLEST] = zip_heap[zip_heap_len--];
+	zip_pqdownheap(tree, zip_SMALLEST);
+
+	m = zip_heap[zip_SMALLEST];  // m = node of next least frequency
+
+	// keep the nodes sorted by frequency
+	zip_heap[--zip_heap_max] = n;
+	zip_heap[--zip_heap_max] = m;
+
+	// Create a new node father of n and m
+	tree[node].fc = tree[n].fc + tree[m].fc;
+//	depth[node] = (char)(MAX(depth[n], depth[m]) + 1);
+	if(zip_depth[n] > zip_depth[m] + 1)
+	    zip_depth[node] = zip_depth[n];
+	else
+	    zip_depth[node] = zip_depth[m] + 1;
+	tree[n].dl = tree[m].dl = node;
+
+	// and insert the new node in the heap
+	zip_heap[zip_SMALLEST] = node++;
+	zip_pqdownheap(tree, zip_SMALLEST);
+
+    } while(zip_heap_len >= 2);
+
+    zip_heap[--zip_heap_max] = zip_heap[zip_SMALLEST];
+
+    /* At this point, the fields freq and dad are set. We can now
+     * generate the bit lengths.
+     */
+    zip_gen_bitlen(desc);
+
+    // The field len is now set, we can generate the bit codes
+    zip_gen_codes(tree, max_code);
+}
+
+/* ==========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree. Updates opt_len to take into account the repeat
+ * counts. (The contribution of the bit length codes will be added later
+ * during the construction of bl_tree.)
+ */
+var zip_scan_tree = function(tree,// the tree to be scanned
+		       max_code) {  // and its largest code of non zero frequency
+    var n;			// iterates over all tree elements
+    var prevlen = -1;		// last emitted length
+    var curlen;			// length of current code
+    var nextlen = tree[0].dl;	// length of next code
+    var count = 0;		// repeat count of the current code
+    var max_count = 7;		// max repeat count
+    var min_count = 4;		// min repeat count
+
+    if(nextlen == 0) {
+	max_count = 138;
+	min_count = 3;
+    }
+    tree[max_code + 1].dl = 0xffff; // guard
+
+    for(n = 0; n <= max_code; n++) {
+	curlen = nextlen;
+	nextlen = tree[n + 1].dl;
+	if(++count < max_count && curlen == nextlen)
+	    continue;
+	else if(count < min_count)
+	    zip_bl_tree[curlen].fc += count;
+	else if(curlen != 0) {
+	    if(curlen != prevlen)
+		zip_bl_tree[curlen].fc++;
+	    zip_bl_tree[zip_REP_3_6].fc++;
+	} else if(count <= 10)
+	    zip_bl_tree[zip_REPZ_3_10].fc++;
+	else
+	    zip_bl_tree[zip_REPZ_11_138].fc++;
+	count = 0; prevlen = curlen;
+	if(nextlen == 0) {
+	    max_count = 138;
+	    min_count = 3;
+	} else if(curlen == nextlen) {
+	    max_count = 6;
+	    min_count = 3;
+	} else {
+	    max_count = 7;
+	    min_count = 4;
+	}
+    }
+}
+
+  /* ==========================================================================
+   * Send a literal or distance tree in compressed form, using the codes in
+   * bl_tree.
+   */
+var zip_send_tree = function(tree, // the tree to be scanned
+		   max_code) { // and its largest code of non zero frequency
+    var n;			// iterates over all tree elements
+    var prevlen = -1;		// last emitted length
+    var curlen;			// length of current code
+    var nextlen = tree[0].dl;	// length of next code
+    var count = 0;		// repeat count of the current code
+    var max_count = 7;		// max repeat count
+    var min_count = 4;		// min repeat count
+
+    /* tree[max_code+1].dl = -1; */  /* guard already set */
+    if(nextlen == 0) {
+      max_count = 138;
+      min_count = 3;
+    }
+
+    for(n = 0; n <= max_code; n++) {
+	curlen = nextlen;
+	nextlen = tree[n+1].dl;
+	if(++count < max_count && curlen == nextlen) {
+	    continue;
+	} else if(count < min_count) {
+	    do { zip_SEND_CODE(curlen, zip_bl_tree); } while(--count != 0);
+	} else if(curlen != 0) {
+	    if(curlen != prevlen) {
+		zip_SEND_CODE(curlen, zip_bl_tree);
+		count--;
+	    }
+	    // Assert(count >= 3 && count <= 6, " 3_6?");
+	    zip_SEND_CODE(zip_REP_3_6, zip_bl_tree);
+	    zip_send_bits(count - 3, 2);
+	} else if(count <= 10) {
+	    zip_SEND_CODE(zip_REPZ_3_10, zip_bl_tree);
+	    zip_send_bits(count-3, 3);
+	} else {
+	    zip_SEND_CODE(zip_REPZ_11_138, zip_bl_tree);
+	    zip_send_bits(count-11, 7);
+	}
+	count = 0;
+	prevlen = curlen;
+	if(nextlen == 0) {
+	    max_count = 138;
+	    min_count = 3;
+	} else if(curlen == nextlen) {
+	    max_count = 6;
+	    min_count = 3;
+	} else {
+	    max_count = 7;
+	    min_count = 4;
+	}
+    }
+}
+
+/* ==========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+var zip_build_bl_tree = function() {
+    var max_blindex;  // index of last bit length code of non zero freq
+
+    // Determine the bit length frequencies for literal and distance trees
+    zip_scan_tree(zip_dyn_ltree, zip_l_desc.max_code);
+    zip_scan_tree(zip_dyn_dtree, zip_d_desc.max_code);
+
+    // Build the bit length tree:
+    zip_build_tree(zip_bl_desc);
+    /* opt_len now includes the length of the tree representations, except
+     * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+     */
+
+    /* Determine the number of bit length codes to send. The pkzip format
+     * requires that at least 4 bit length codes be sent. (appnote.txt says
+     * 3 but the actual value used is 4.)
+     */
+    for(max_blindex = zip_BL_CODES-1; max_blindex >= 3; max_blindex--) {
+	if(zip_bl_tree[zip_bl_order[max_blindex]].dl != 0) break;
+    }
+    /* Update opt_len to include the bit length tree and counts */
+    zip_opt_len += 3*(max_blindex+1) + 5+5+4;
+//    Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+//	    encoder->opt_len, encoder->static_len));
+
+    return max_blindex;
+}
+
+/* ==========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+var zip_send_all_trees = function(lcodes, dcodes, blcodes) { // number of codes for each tree
+    var rank; // index in bl_order
+
+//    Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+//    Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+//	    "too many codes");
+//    Tracev((stderr, "\nbl counts: "));
+    zip_send_bits(lcodes-257, 5); // not +255 as stated in appnote.txt
+    zip_send_bits(dcodes-1,   5);
+    zip_send_bits(blcodes-4,  4); // not -3 as stated in appnote.txt
+    for(rank = 0; rank < blcodes; rank++) {
+//      Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+	zip_send_bits(zip_bl_tree[zip_bl_order[rank]].dl, 3);
+    }
+
+    // send the literal tree
+    zip_send_tree(zip_dyn_ltree,lcodes-1);
+
+    // send the distance tree
+    zip_send_tree(zip_dyn_dtree,dcodes-1);
+}
+
+/* ==========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+var zip_flush_block = function(eof) { // true if this is the last block for a file
+    var opt_lenb, static_lenb; // opt_len and static_len in bytes
+    var max_blindex;	// index of last bit length code of non zero freq
+    var stored_len;	// length of input block
+
+    stored_len = zip_strstart - zip_block_start;
+    zip_flag_buf[zip_last_flags] = zip_flags; // Save the flags for the last 8 items
+
+    // Construct the literal and distance trees
+    zip_build_tree(zip_l_desc);
+//    Tracev((stderr, "\nlit data: dyn %ld, stat %ld",
+//	    encoder->opt_len, encoder->static_len));
+
+    zip_build_tree(zip_d_desc);
+//    Tracev((stderr, "\ndist data: dyn %ld, stat %ld",
+//	    encoder->opt_len, encoder->static_len));
+    /* At this point, opt_len and static_len are the total bit lengths of
+     * the compressed block data, excluding the tree representations.
+     */
+
+    /* Build the bit length tree for the above two trees, and get the index
+     * in bl_order of the last bit length code to send.
+     */
+    max_blindex = zip_build_bl_tree();
+
+    // Determine the best encoding. Compute first the block length in bytes
+    opt_lenb	= (zip_opt_len   +3+7)>>3;
+    static_lenb = (zip_static_len+3+7)>>3;
+
+//    Trace((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u dist %u ",
+//	   opt_lenb, encoder->opt_len,
+//	   static_lenb, encoder->static_len, stored_len,
+//	   encoder->last_lit, encoder->last_dist));
+
+    if(static_lenb <= opt_lenb)
+	opt_lenb = static_lenb;
+    if(stored_len + 4 <= opt_lenb // 4: two words for the lengths
+       && zip_block_start >= 0) {
+	var i;
+
+	/* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+	 * Otherwise we can't have processed more than WSIZE input bytes since
+	 * the last block flush, because compression would have been
+	 * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+	 * transform a block into a stored block.
+	 */
+	zip_send_bits((zip_STORED_BLOCK<<1)+eof, 3);  /* send block type */
+	zip_bi_windup();		 /* align on byte boundary */
+	zip_put_short(stored_len);
+	zip_put_short(~stored_len);
+
+      // copy block
+/*
+      p = &window[block_start];
+      for(i = 0; i < stored_len; i++)
+	put_byte(p[i]);
+*/
+	for(i = 0; i < stored_len; i++)
+	    zip_put_byte(zip_window[zip_block_start + i]);
+
+    } else if(static_lenb == opt_lenb) {
+	zip_send_bits((zip_STATIC_TREES<<1)+eof, 3);
+	zip_compress_block(zip_static_ltree, zip_static_dtree);
+    } else {
+	zip_send_bits((zip_DYN_TREES<<1)+eof, 3);
+	zip_send_all_trees(zip_l_desc.max_code+1,
+			   zip_d_desc.max_code+1,
+			   max_blindex+1);
+	zip_compress_block(zip_dyn_ltree, zip_dyn_dtree);
+    }
+
+    zip_init_block();
+
+    if(eof != 0)
+	zip_bi_windup();
+}
+
+/* ==========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+var zip_ct_tally = function(
+	dist, // distance of matched string
+	lc) { // match length-MIN_MATCH or unmatched char (if dist==0)
+    zip_l_buf[zip_last_lit++] = lc;
+    if(dist == 0) {
+	// lc is the unmatched char
+	zip_dyn_ltree[lc].fc++;
+    } else {
+	// Here, lc is the match length - MIN_MATCH
+	dist--;		    // dist = match distance - 1
+//      Assert((ush)dist < (ush)MAX_DIST &&
+//	     (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+//	     (ush)D_CODE(dist) < (ush)D_CODES,  "ct_tally: bad match");
+
+	zip_dyn_ltree[zip_length_code[lc]+zip_LITERALS+1].fc++;
+	zip_dyn_dtree[zip_D_CODE(dist)].fc++;
+
+	zip_d_buf[zip_last_dist++] = dist;
+	zip_flags |= zip_flag_bit;
+    }
+    zip_flag_bit <<= 1;
+
+    // Output the flags if they fill a byte
+    if((zip_last_lit & 7) == 0) {
+	zip_flag_buf[zip_last_flags++] = zip_flags;
+	zip_flags = 0;
+	zip_flag_bit = 1;
+    }
+    // Try to guess if it is profitable to stop the current block here
+    if(zip_compr_level > 2 && (zip_last_lit & 0xfff) == 0) {
+	// Compute an upper bound for the compressed length
+	var out_length = zip_last_lit * 8;
+	var in_length = zip_strstart - zip_block_start;
+	var dcode;
+
+	for(dcode = 0; dcode < zip_D_CODES; dcode++) {
+	    out_length += zip_dyn_dtree[dcode].fc * (5 + zip_extra_dbits[dcode]);
+	}
+	out_length >>= 3;
+//      Trace((stderr,"\nlast_lit %u, last_dist %u, in %ld, out ~%ld(%ld%%) ",
+//	     encoder->last_lit, encoder->last_dist, in_length, out_length,
+//	     100L - out_length*100L/in_length));
+	if(zip_last_dist < parseInt(zip_last_lit/2) &&
+	   out_length < parseInt(in_length/2))
+	    return true;
+    }
+    return (zip_last_lit == zip_LIT_BUFSIZE-1 ||
+	    zip_last_dist == zip_DIST_BUFSIZE);
+    /* We avoid equality with LIT_BUFSIZE because of wraparound at 64K
+     * on 16 bit machines and because stored blocks are restricted to
+     * 64K-1 bytes.
+     */
+}
+
+  /* ==========================================================================
+   * Send the block data compressed using the given Huffman trees
+   */
+var zip_compress_block = function(
+	ltree,	// literal tree
+	dtree) {	// distance tree
+    var dist;		// distance of matched string
+    var lc;		// match length or unmatched char (if dist == 0)
+    var lx = 0;		// running index in l_buf
+    var dx = 0;		// running index in d_buf
+    var fx = 0;		// running index in flag_buf
+    var flag = 0;	// current flags
+    var code;		// the code to send
+    var extra;		// number of extra bits to send
+
+    if(zip_last_lit != 0) do {
+	if((lx & 7) == 0)
+	    flag = zip_flag_buf[fx++];
+	lc = zip_l_buf[lx++] & 0xff;
+	if((flag & 1) == 0) {
+	    zip_SEND_CODE(lc, ltree); /* send a literal byte */
+//	Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+	} else {
+	    // Here, lc is the match length - MIN_MATCH
+	    code = zip_length_code[lc];
+	    zip_SEND_CODE(code+zip_LITERALS+1, ltree); // send the length code
+	    extra = zip_extra_lbits[code];
+	    if(extra != 0) {
+		lc -= zip_base_length[code];
+		zip_send_bits(lc, extra); // send the extra length bits
+	    }
+	    dist = zip_d_buf[dx++];
+	    // Here, dist is the match distance - 1
+	    code = zip_D_CODE(dist);
+//	Assert (code < D_CODES, "bad d_code");
+
+	    zip_SEND_CODE(code, dtree);	  // send the distance code
+	    extra = zip_extra_dbits[code];
+	    if(extra != 0) {
+		dist -= zip_base_dist[code];
+		zip_send_bits(dist, extra);   // send the extra distance bits
+	    }
+	} // literal or match pair ?
+	flag >>= 1;
+    } while(lx < zip_last_lit);
+
+    zip_SEND_CODE(zip_END_BLOCK, ltree);
+}
+
+/* ==========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+var zip_Buf_size = 16; // bit size of bi_buf
+var zip_send_bits = function(
+	value,	// value to send
+	length) {	// number of bits
+    /* If not enough room in bi_buf, use (valid) bits from bi_buf and
+     * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+     * unused bits in value.
+     */
+    if(zip_bi_valid > zip_Buf_size - length) {
+	zip_bi_buf |= (value << zip_bi_valid);
+	zip_put_short(zip_bi_buf);
+	zip_bi_buf = (value >> (zip_Buf_size - zip_bi_valid));
+	zip_bi_valid += length - zip_Buf_size;
+    } else {
+	zip_bi_buf |= value << zip_bi_valid;
+	zip_bi_valid += length;
+    }
+}
+
+/* ==========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+var zip_bi_reverse = function(
+	code,	// the value to invert
+	len) {	// its bit length
+    var res = 0;
+    do {
+	res |= code & 1;
+	code >>= 1;
+	res <<= 1;
+    } while(--len > 0);
+    return res >> 1;
+}
+
+/* ==========================================================================
+ * Write out any remaining bits in an incomplete byte.
+ */
+var zip_bi_windup = function() {
+    if(zip_bi_valid > 8) {
+	zip_put_short(zip_bi_buf);
+    } else if(zip_bi_valid > 0) {
+	zip_put_byte(zip_bi_buf);
+    }
+    zip_bi_buf = 0;
+    zip_bi_valid = 0;
+}
+
+var zip_qoutbuf = function() {
+    if(zip_outcnt != 0) {
+	var q, i;
+	q = zip_new_queue();
+	if(zip_qhead == null)
+	    zip_qhead = zip_qtail = q;
+	else
+	    zip_qtail = zip_qtail.next = q;
+	q.len = zip_outcnt - zip_outoff;
+//      System.arraycopy(zip_outbuf, zip_outoff, q.ptr, 0, q.len);
+	for(i = 0; i < q.len; i++)
+	    q.ptr[i] = zip_outbuf[zip_outoff + i];
+	zip_outcnt = zip_outoff = 0;
+    }
+}
+
+var zip_deflate = function(str, level) {
+    var i, j;
+
+    zip_deflate_data = str;
+    zip_deflate_pos = 0;
+    if(typeof level == "undefined")
+	level = zip_DEFAULT_LEVEL;
+    zip_deflate_start(level);
+
+    var buff = new Array(1024);
+    var aout = [];
+    while((i = zip_deflate_internal(buff, 0, buff.length)) > 0) {
+	var cbuf = new Array(i);
+	for(j = 0; j < i; j++){
+	    cbuf[j] = String.fromCharCode(buff[j]);
+	}
+	aout[aout.length] = cbuf.join("");
+    }
+    zip_deflate_data = null; // G.C.
+    return aout.join("");
+}
+
+if (! window.RawDeflate) RawDeflate = {};
+RawDeflate.deflate = zip_deflate;
+
+})();

+ 753 - 0
lib/rawinflate.js

@@ -0,0 +1,753 @@
+/*
+ * $Id: rawinflate.js,v 0.2 2009/03/01 18:32:24 dankogai Exp $
+ *
+ * original:
+ * http://www.onicos.com/staff/iz/amuse/javascript/expert/inflate.txt
+ */
+
+(function(){
+
+/* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
+ * Version: 1.0.0.1
+ * LastModified: Dec 25 1999
+ */
+
+/* Interface:
+ * data = zip_inflate(src);
+ */
+
+/* constant parameters */
+var zip_WSIZE = 32768;		// Sliding Window size
+var zip_STORED_BLOCK = 0;
+var zip_STATIC_TREES = 1;
+var zip_DYN_TREES    = 2;
+
+/* for inflate */
+var zip_lbits = 9; 		// bits in base literal/length lookup table
+var zip_dbits = 6; 		// bits in base distance lookup table
+var zip_INBUFSIZ = 32768;	// Input buffer size
+var zip_INBUF_EXTRA = 64;	// Extra buffer
+
+/* variables (inflate) */
+var zip_slide;
+var zip_wp;			// current position in slide
+var zip_fixed_tl = null;	// inflate static
+var zip_fixed_td;		// inflate static
+var zip_fixed_bl, fixed_bd;	// inflate static
+var zip_bit_buf;		// bit buffer
+var zip_bit_len;		// bits in bit buffer
+var zip_method;
+var zip_eof;
+var zip_copy_leng;
+var zip_copy_dist;
+var zip_tl, zip_td;	// literal/length and distance decoder tables
+var zip_bl, zip_bd;	// number of bits decoded by tl and td
+
+var zip_inflate_data;
+var zip_inflate_pos;
+
+
+/* constant tables (inflate) */
+var zip_MASK_BITS = new Array(
+    0x0000,
+    0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff,
+    0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff);
+// Tables for deflate from PKZIP's appnote.txt.
+var zip_cplens = new Array( // Copy lengths for literal codes 257..285
+    3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+    35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0);
+/* note: see note #13 above about the 258 in this list. */
+var zip_cplext = new Array( // Extra bits for literal codes 257..285
+    0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+    3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99); // 99==invalid
+var zip_cpdist = new Array( // Copy offsets for distance codes 0..29
+    1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+    257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+    8193, 12289, 16385, 24577);
+var zip_cpdext = new Array( // Extra bits for distance codes
+    0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
+    7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+    12, 12, 13, 13);
+var zip_border = new Array(  // Order of the bit length code lengths
+    16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15);
+/* objects (inflate) */
+
+var zip_HuftList = function() {
+    this.next = null;
+    this.list = null;
+}
+
+var zip_HuftNode = function() {
+    this.e = 0; // number of extra bits or operation
+    this.b = 0; // number of bits in this code or subcode
+
+    // union
+    this.n = 0; // literal, length base, or distance base
+    this.t = null; // (zip_HuftNode) pointer to next level of table
+}
+
+var zip_HuftBuild = function(b,	// code lengths in bits (all assumed <= BMAX)
+		       n,	// number of codes (assumed <= N_MAX)
+		       s,	// number of simple-valued codes (0..s-1)
+		       d,	// list of base values for non-simple codes
+		       e,	// list of extra bits for non-simple codes
+		       mm	// maximum lookup bits
+		   ) {
+    this.BMAX = 16;   // maximum bit length of any code
+    this.N_MAX = 288; // maximum number of codes in any set
+    this.status = 0;	// 0: success, 1: incomplete table, 2: bad input
+    this.root = null;	// (zip_HuftList) starting table
+    this.m = 0;		// maximum lookup bits, returns actual
+
+/* Given a list of code lengths and a maximum table size, make a set of
+   tables to decode that set of codes.	Return zero on success, one if
+   the given code set is incomplete (the tables are still built in this
+   case), two if the input is invalid (all zero length codes or an
+   oversubscribed set of lengths), and three if not enough memory.
+   The code with value 256 is special, and the tables are constructed
+   so that no bits beyond that code are fetched when that code is
+   decoded. */
+    {
+	var a;			// counter for codes of length k
+	var c = new Array(this.BMAX+1);	// bit length count table
+	var el;			// length of EOB code (value 256)
+	var f;			// i repeats in table every f entries
+	var g;			// maximum code length
+	var h;			// table level
+	var i;			// counter, current code
+	var j;			// counter
+	var k;			// number of bits in current code
+	var lx = new Array(this.BMAX+1);	// stack of bits per table
+	var p;			// pointer into c[], b[], or v[]
+	var pidx;		// index of p
+	var q;			// (zip_HuftNode) points to current table
+	var r = new zip_HuftNode(); // table entry for structure assignment
+	var u = new Array(this.BMAX); // zip_HuftNode[BMAX][]  table stack
+	var v = new Array(this.N_MAX); // values in order of bit length
+	var w;
+	var x = new Array(this.BMAX+1);// bit offsets, then code stack
+	var xp;			// pointer into x or c
+	var y;			// number of dummy codes added
+	var z;			// number of entries in current table
+	var o;
+	var tail;		// (zip_HuftList)
+
+	tail = this.root = null;
+	for(i = 0; i < c.length; i++)
+	    c[i] = 0;
+	for(i = 0; i < lx.length; i++)
+	    lx[i] = 0;
+	for(i = 0; i < u.length; i++)
+	    u[i] = null;
+	for(i = 0; i < v.length; i++)
+	    v[i] = 0;
+	for(i = 0; i < x.length; i++)
+	    x[i] = 0;
+
+	// Generate counts for each bit length
+	el = n > 256 ? b[256] : this.BMAX; // set length of EOB code, if any
+	p = b; pidx = 0;
+	i = n;
+	do {
+	    c[p[pidx]]++;	// assume all entries <= BMAX
+	    pidx++;
+	} while(--i > 0);
+	if(c[0] == n) {	// null input--all zero length codes
+	    this.root = null;
+	    this.m = 0;
+	    this.status = 0;
+	    return;
+	}
+
+	// Find minimum and maximum length, bound *m by those
+	for(j = 1; j <= this.BMAX; j++)
+	    if(c[j] != 0)
+		break;
+	k = j;			// minimum code length
+	if(mm < j)
+	    mm = j;
+	for(i = this.BMAX; i != 0; i--)
+	    if(c[i] != 0)
+		break;
+	g = i;			// maximum code length
+	if(mm > i)
+	    mm = i;
+
+	// Adjust last length count to fill out codes, if needed
+	for(y = 1 << j; j < i; j++, y <<= 1)
+	    if((y -= c[j]) < 0) {
+		this.status = 2;	// bad input: more codes than bits
+		this.m = mm;
+		return;
+	    }
+	if((y -= c[i]) < 0) {
+	    this.status = 2;
+	    this.m = mm;
+	    return;
+	}
+	c[i] += y;
+
+	// Generate starting offsets into the value table for each length
+	x[1] = j = 0;
+	p = c;
+	pidx = 1;
+	xp = 2;
+	while(--i > 0)		// note that i == g from above
+	    x[xp++] = (j += p[pidx++]);
+
+	// Make a table of values in order of bit lengths
+	p = b; pidx = 0;
+	i = 0;
+	do {
+	    if((j = p[pidx++]) != 0)
+		v[x[j]++] = i;
+	} while(++i < n);
+	n = x[g];			// set n to length of v
+
+	// Generate the Huffman codes and for each, make the table entries
+	x[0] = i = 0;		// first Huffman code is zero
+	p = v; pidx = 0;		// grab values in bit order
+	h = -1;			// no tables yet--level -1
+	w = lx[0] = 0;		// no bits decoded yet
+	q = null;			// ditto
+	z = 0;			// ditto
+
+	// go through the bit lengths (k already is bits in shortest code)
+	for(; k <= g; k++) {
+	    a = c[k];
+	    while(a-- > 0) {
+		// here i is the Huffman code of length k bits for value p[pidx]
+		// make tables up to required level
+		while(k > w + lx[1 + h]) {
+		    w += lx[1 + h]; // add bits already decoded
+		    h++;
+
+		    // compute minimum size table less than or equal to *m bits
+		    z = (z = g - w) > mm ? mm : z; // upper limit
+		    if((f = 1 << (j = k - w)) > a + 1) { // try a k-w bit table
+			// too few codes for k-w bit table
+			f -= a + 1;	// deduct codes from patterns left
+			xp = k;
+			while(++j < z) { // try smaller tables up to z bits
+			    if((f <<= 1) <= c[++xp])
+				break;	// enough codes to use up j bits
+			    f -= c[xp];	// else deduct codes from patterns
+			}
+		    }
+		    if(w + j > el && w < el)
+			j = el - w;	// make EOB code end at table
+		    z = 1 << j;	// table entries for j-bit table
+		    lx[1 + h] = j; // set table size in stack
+
+		    // allocate and link in new table
+		    q = new Array(z);
+		    for(o = 0; o < z; o++) {
+			q[o] = new zip_HuftNode();
+		    }
+
+		    if(tail == null)
+			tail = this.root = new zip_HuftList();
+		    else
+			tail = tail.next = new zip_HuftList();
+		    tail.next = null;
+		    tail.list = q;
+		    u[h] = q;	// table starts after link
+
+		    /* connect to last table, if there is one */
+		    if(h > 0) {
+			x[h] = i;		// save pattern for backing up
+			r.b = lx[h];	// bits to dump before this table
+			r.e = 16 + j;	// bits in this table
+			r.t = q;		// pointer to this table
+			j = (i & ((1 << w) - 1)) >> (w - lx[h]);
+			u[h-1][j].e = r.e;
+			u[h-1][j].b = r.b;
+			u[h-1][j].n = r.n;
+			u[h-1][j].t = r.t;
+		    }
+		}
+
+		// set up table entry in r
+		r.b = k - w;
+		if(pidx >= n)
+		    r.e = 99;		// out of values--invalid code
+		else if(p[pidx] < s) {
+		    r.e = (p[pidx] < 256 ? 16 : 15); // 256 is end-of-block code
+		    r.n = p[pidx++];	// simple code is just the value
+		} else {
+		    r.e = e[p[pidx] - s];	// non-simple--look up in lists
+		    r.n = d[p[pidx++] - s];
+		}
+
+		// fill code-like entries with r //
+		f = 1 << (k - w);
+		for(j = i >> w; j < z; j += f) {
+		    q[j].e = r.e;
+		    q[j].b = r.b;
+		    q[j].n = r.n;
+		    q[j].t = r.t;
+		}
+
+		// backwards increment the k-bit code i
+		for(j = 1 << (k - 1); (i & j) != 0; j >>= 1)
+		    i ^= j;
+		i ^= j;
+
+		// backup over finished tables
+		while((i & ((1 << w) - 1)) != x[h]) {
+		    w -= lx[h];		// don't need to update q
+		    h--;
+		}
+	    }
+	}
+
+	/* return actual size of base table */
+	this.m = lx[1];
+
+	/* Return true (1) if we were given an incomplete table */
+	this.status = ((y != 0 && g != 1) ? 1 : 0);
+    } /* end of constructor */
+}
+
+
+/* routines (inflate) */
+
+var zip_GET_BYTE = function() {
+    if(zip_inflate_data.length == zip_inflate_pos)
+	return -1;
+    return zip_inflate_data.charCodeAt(zip_inflate_pos++) & 0xff;
+}
+
+var zip_NEEDBITS = function(n) {
+    while(zip_bit_len < n) {
+	zip_bit_buf |= zip_GET_BYTE() << zip_bit_len;
+	zip_bit_len += 8;
+    }
+}
+
+var zip_GETBITS = function(n) {
+    return zip_bit_buf & zip_MASK_BITS[n];
+}
+
+var zip_DUMPBITS = function(n) {
+    zip_bit_buf >>= n;
+    zip_bit_len -= n;
+}
+
+var zip_inflate_codes = function(buff, off, size) {
+    /* inflate (decompress) the codes in a deflated (compressed) block.
+       Return an error code or zero if it all goes ok. */
+    var e;		// table entry flag/number of extra bits
+    var t;		// (zip_HuftNode) pointer to table entry
+    var n;
+
+    if(size == 0)
+      return 0;
+
+    // inflate the coded data
+    n = 0;
+    for(;;) {			// do until end of block
+	zip_NEEDBITS(zip_bl);
+	t = zip_tl.list[zip_GETBITS(zip_bl)];
+	e = t.e;
+	while(e > 16) {
+	    if(e == 99)
+		return -1;
+	    zip_DUMPBITS(t.b);
+	    e -= 16;
+	    zip_NEEDBITS(e);
+	    t = t.t[zip_GETBITS(e)];
+	    e = t.e;
+	}
+	zip_DUMPBITS(t.b);
+
+	if(e == 16) {		// then it's a literal
+	    zip_wp &= zip_WSIZE - 1;
+	    buff[off + n++] = zip_slide[zip_wp++] = t.n;
+	    if(n == size)
+		return size;
+	    continue;
+	}
+
+	// exit if end of block
+	if(e == 15)
+	    break;
+
+	// it's an EOB or a length
+
+	// get length of block to copy
+	zip_NEEDBITS(e);
+	zip_copy_leng = t.n + zip_GETBITS(e);
+	zip_DUMPBITS(e);
+
+	// decode distance of block to copy
+	zip_NEEDBITS(zip_bd);
+	t = zip_td.list[zip_GETBITS(zip_bd)];
+	e = t.e;
+
+	while(e > 16) {
+	    if(e == 99)
+		return -1;
+	    zip_DUMPBITS(t.b);
+	    e -= 16;
+	    zip_NEEDBITS(e);
+	    t = t.t[zip_GETBITS(e)];
+	    e = t.e;
+	}
+	zip_DUMPBITS(t.b);
+	zip_NEEDBITS(e);
+	zip_copy_dist = zip_wp - t.n - zip_GETBITS(e);
+	zip_DUMPBITS(e);
+
+	// do the copy
+	while(zip_copy_leng > 0 && n < size) {
+	    zip_copy_leng--;
+	    zip_copy_dist &= zip_WSIZE - 1;
+	    zip_wp &= zip_WSIZE - 1;
+	    buff[off + n++] = zip_slide[zip_wp++]
+		= zip_slide[zip_copy_dist++];
+	}
+
+	if(n == size)
+	    return size;
+    }
+
+    zip_method = -1; // done
+    return n;
+}
+
+var zip_inflate_stored = function(buff, off, size) {
+    /* "decompress" an inflated type 0 (stored) block. */
+    var n;
+
+    // go to byte boundary
+    n = zip_bit_len & 7;
+    zip_DUMPBITS(n);
+
+    // get the length and its complement
+    zip_NEEDBITS(16);
+    n = zip_GETBITS(16);
+    zip_DUMPBITS(16);
+    zip_NEEDBITS(16);
+    if(n != ((~zip_bit_buf) & 0xffff))
+	return -1;			// error in compressed data
+    zip_DUMPBITS(16);
+
+    // read and output the compressed data
+    zip_copy_leng = n;
+
+    n = 0;
+    while(zip_copy_leng > 0 && n < size) {
+	zip_copy_leng--;
+	zip_wp &= zip_WSIZE - 1;
+	zip_NEEDBITS(8);
+	buff[off + n++] = zip_slide[zip_wp++] =
+	    zip_GETBITS(8);
+	zip_DUMPBITS(8);
+    }
+
+    if(zip_copy_leng == 0)
+      zip_method = -1; // done
+    return n;
+}
+
+var zip_inflate_fixed = function(buff, off, size) {
+    /* decompress an inflated type 1 (fixed Huffman codes) block.  We should
+       either replace this with a custom decoder, or at least precompute the
+       Huffman tables. */
+
+    // if first time, set up tables for fixed blocks
+    if(zip_fixed_tl == null) {
+	var i;			// temporary variable
+	var l = new Array(288);	// length list for huft_build
+	var h;	// zip_HuftBuild
+
+	// literal table
+	for(i = 0; i < 144; i++)
+	    l[i] = 8;
+	for(; i < 256; i++)
+	    l[i] = 9;
+	for(; i < 280; i++)
+	    l[i] = 7;
+	for(; i < 288; i++)	// make a complete, but wrong code set
+	    l[i] = 8;
+	zip_fixed_bl = 7;
+
+	h = new zip_HuftBuild(l, 288, 257, zip_cplens, zip_cplext,
+			      zip_fixed_bl);
+	if(h.status != 0) {
+	    alert("HufBuild error: "+h.status);
+	    return -1;
+	}
+	zip_fixed_tl = h.root;
+	zip_fixed_bl = h.m;
+
+	// distance table
+	for(i = 0; i < 30; i++)	// make an incomplete code set
+	    l[i] = 5;
+	zip_fixed_bd = 5;
+
+	h = new zip_HuftBuild(l, 30, 0, zip_cpdist, zip_cpdext, zip_fixed_bd);
+	if(h.status > 1) {
+	    zip_fixed_tl = null;
+	    alert("HufBuild error: "+h.status);
+	    return -1;
+	}
+	zip_fixed_td = h.root;
+	zip_fixed_bd = h.m;
+    }
+
+    zip_tl = zip_fixed_tl;
+    zip_td = zip_fixed_td;
+    zip_bl = zip_fixed_bl;
+    zip_bd = zip_fixed_bd;
+    return zip_inflate_codes(buff, off, size);
+}
+
+var zip_inflate_dynamic = function(buff, off, size) {
+    // decompress an inflated type 2 (dynamic Huffman codes) block.
+    var i;		// temporary variables
+    var j;
+    var l;		// last length
+    var n;		// number of lengths to get
+    var t;		// (zip_HuftNode) literal/length code table
+    var nb;		// number of bit length codes
+    var nl;		// number of literal/length codes
+    var nd;		// number of distance codes
+    var ll = new Array(286+30); // literal/length and distance code lengths
+    var h;		// (zip_HuftBuild)
+
+    for(i = 0; i < ll.length; i++)
+	ll[i] = 0;
+
+    // read in table lengths
+    zip_NEEDBITS(5);
+    nl = 257 + zip_GETBITS(5);	// number of literal/length codes
+    zip_DUMPBITS(5);
+    zip_NEEDBITS(5);
+    nd = 1 + zip_GETBITS(5);	// number of distance codes
+    zip_DUMPBITS(5);
+    zip_NEEDBITS(4);
+    nb = 4 + zip_GETBITS(4);	// number of bit length codes
+    zip_DUMPBITS(4);
+    if(nl > 286 || nd > 30)
+      return -1;		// bad lengths
+
+    // read in bit-length-code lengths
+    for(j = 0; j < nb; j++)
+    {
+	zip_NEEDBITS(3);
+	ll[zip_border[j]] = zip_GETBITS(3);
+	zip_DUMPBITS(3);
+    }
+    for(; j < 19; j++)
+	ll[zip_border[j]] = 0;
+
+    // build decoding table for trees--single level, 7 bit lookup
+    zip_bl = 7;
+    h = new zip_HuftBuild(ll, 19, 19, null, null, zip_bl);
+    if(h.status != 0)
+	return -1;	// incomplete code set
+
+    zip_tl = h.root;
+    zip_bl = h.m;
+
+    // read in literal and distance code lengths
+    n = nl + nd;
+    i = l = 0;
+    while(i < n) {
+	zip_NEEDBITS(zip_bl);
+	t = zip_tl.list[zip_GETBITS(zip_bl)];
+	j = t.b;
+	zip_DUMPBITS(j);
+	j = t.n;
+	if(j < 16)		// length of code in bits (0..15)
+	    ll[i++] = l = j;	// save last length in l
+	else if(j == 16) {	// repeat last length 3 to 6 times
+	    zip_NEEDBITS(2);
+	    j = 3 + zip_GETBITS(2);
+	    zip_DUMPBITS(2);
+	    if(i + j > n)
+		return -1;
+	    while(j-- > 0)
+		ll[i++] = l;
+	} else if(j == 17) {	// 3 to 10 zero length codes
+	    zip_NEEDBITS(3);
+	    j = 3 + zip_GETBITS(3);
+	    zip_DUMPBITS(3);
+	    if(i + j > n)
+		return -1;
+	    while(j-- > 0)
+		ll[i++] = 0;
+	    l = 0;
+	} else {		// j == 18: 11 to 138 zero length codes
+	    zip_NEEDBITS(7);
+	    j = 11 + zip_GETBITS(7);
+	    zip_DUMPBITS(7);
+	    if(i + j > n)
+		return -1;
+	    while(j-- > 0)
+		ll[i++] = 0;
+	    l = 0;
+	}
+    }
+
+    // build the decoding tables for literal/length and distance codes
+    zip_bl = zip_lbits;
+    h = new zip_HuftBuild(ll, nl, 257, zip_cplens, zip_cplext, zip_bl);
+    if(zip_bl == 0)	// no literals or lengths
+	h.status = 1;
+    if(h.status != 0) {
+	if(h.status == 1)
+	    ;// **incomplete literal tree**
+	return -1;		// incomplete code set
+    }
+    zip_tl = h.root;
+    zip_bl = h.m;
+
+    for(i = 0; i < nd; i++)
+	ll[i] = ll[i + nl];
+    zip_bd = zip_dbits;
+    h = new zip_HuftBuild(ll, nd, 0, zip_cpdist, zip_cpdext, zip_bd);
+    zip_td = h.root;
+    zip_bd = h.m;
+
+    if(zip_bd == 0 && nl > 257) {   // lengths but no distances
+	// **incomplete distance tree**
+	return -1;
+    }
+
+    if(h.status == 1) {
+	;// **incomplete distance tree**
+    }
+    if(h.status != 0)
+	return -1;
+
+    // decompress until an end-of-block code
+    return zip_inflate_codes(buff, off, size);
+}
+
+var zip_inflate_start = function() {
+    var i;
+
+    if(zip_slide == null)
+	zip_slide = new Array(2 * zip_WSIZE);
+    zip_wp = 0;
+    zip_bit_buf = 0;
+    zip_bit_len = 0;
+    zip_method = -1;
+    zip_eof = false;
+    zip_copy_leng = zip_copy_dist = 0;
+    zip_tl = null;
+}
+
+var zip_inflate_internal = function(buff, off, size) {
+    // decompress an inflated entry
+    var n, i;
+
+    n = 0;
+    while(n < size) {
+	if(zip_eof && zip_method == -1)
+	    return n;
+
+	if(zip_copy_leng > 0) {
+	    if(zip_method != zip_STORED_BLOCK) {
+		// STATIC_TREES or DYN_TREES
+		while(zip_copy_leng > 0 && n < size) {
+		    zip_copy_leng--;
+		    zip_copy_dist &= zip_WSIZE - 1;
+		    zip_wp &= zip_WSIZE - 1;
+		    buff[off + n++] = zip_slide[zip_wp++] =
+			zip_slide[zip_copy_dist++];
+		}
+	    } else {
+		while(zip_copy_leng > 0 && n < size) {
+		    zip_copy_leng--;
+		    zip_wp &= zip_WSIZE - 1;
+		    zip_NEEDBITS(8);
+		    buff[off + n++] = zip_slide[zip_wp++] = zip_GETBITS(8);
+		    zip_DUMPBITS(8);
+		}
+		if(zip_copy_leng == 0)
+		    zip_method = -1; // done
+	    }
+	    if(n == size)
+		return n;
+	}
+
+	if(zip_method == -1) {
+	    if(zip_eof)
+		break;
+
+	    // read in last block bit
+	    zip_NEEDBITS(1);
+	    if(zip_GETBITS(1) != 0)
+		zip_eof = true;
+	    zip_DUMPBITS(1);
+
+	    // read in block type
+	    zip_NEEDBITS(2);
+	    zip_method = zip_GETBITS(2);
+	    zip_DUMPBITS(2);
+	    zip_tl = null;
+	    zip_copy_leng = 0;
+	}
+
+	switch(zip_method) {
+	  case 0: // zip_STORED_BLOCK
+	    i = zip_inflate_stored(buff, off + n, size - n);
+	    break;
+
+	  case 1: // zip_STATIC_TREES
+	    if(zip_tl != null)
+		i = zip_inflate_codes(buff, off + n, size - n);
+	    else
+		i = zip_inflate_fixed(buff, off + n, size - n);
+	    break;
+
+	  case 2: // zip_DYN_TREES
+	    if(zip_tl != null)
+		i = zip_inflate_codes(buff, off + n, size - n);
+	    else
+		i = zip_inflate_dynamic(buff, off + n, size - n);
+	    break;
+
+	  default: // error
+	    i = -1;
+	    break;
+	}
+
+	if(i == -1) {
+	    if(zip_eof)
+		return 0;
+	    return -1;
+	}
+	n += i;
+    }
+    return n;
+}
+
+var zip_inflate = function(str) {
+    var i, j;
+
+    zip_inflate_start();
+    zip_inflate_data = str;
+    zip_inflate_pos = 0;
+
+    var buff = new Array(1024);
+    var aout = [];
+    while((i = zip_inflate_internal(buff, 0, buff.length)) > 0) {
+	var cbuf = new Array(i);
+	for(j = 0; j < i; j++){
+	    cbuf[j] = String.fromCharCode(buff[j]);
+	}
+	aout[aout.length] = cbuf.join("");
+    }
+    zip_inflate_data = null; // G.C.
+    return aout.join("");
+}
+
+if (! window.RawDeflate) RawDeflate = {};
+RawDeflate.inflate = zip_inflate;
+
+})();

+ 41 - 0
lib/sjcl.js

@@ -0,0 +1,41 @@
+"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};
+if(typeof module!="undefined"&&module.exports)module.exports=sjcl;
+sjcl.cipher.aes=function(a){this.h[0][0][0]||this.w();var b,c,d,e,f=this.h[0][4],g=this.h[1];b=a.length;var h=1;if(b!==4&&b!==6&&b!==8)throw new sjcl.exception.invalid("invalid aes key size");this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(a%b===0||b===8&&a%b===4){c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255];if(a%b===0){c=c<<8^c>>>24^h<<24;h=h<<1^(h>>7)*283}}d[a]=d[a-b]^c}for(b=0;a;b++,a--){c=d[b&3?a:a-4];e[b]=a<=4||b<4?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^
+g[3][f[c&255]]}};
+sjcl.cipher.aes.prototype={encrypt:function(a){return this.H(a,0)},decrypt:function(a){return this.H(a,1)},h:[[[],[],[],[],[]],[[],[],[],[],[]]],w:function(){var a=this.h[0],b=this.h[1],c=a[4],d=b[4],e,f,g,h=[],i=[],k,j,l,m;for(e=0;e<0x100;e++)i[(h[e]=e<<1^(e>>7)*283)^e]=e;for(f=g=0;!c[f];f^=k||1,g=i[g]||1){l=g^g<<1^g<<2^g<<3^g<<4;l=l>>8^l&255^99;c[f]=l;d[l]=f;j=h[e=h[k=h[f]]];m=j*0x1010101^e*0x10001^k*0x101^f*0x1010100;j=h[l]*0x101^l*0x1010100;for(e=0;e<4;e++){a[e][f]=j=j<<24^j>>>8;b[e][l]=m=m<<24^m>>>8}}for(e=
+0;e<5;e++){a[e]=a[e].slice(0);b[e]=b[e].slice(0)}},H:function(a,b){if(a.length!==4)throw new sjcl.exception.invalid("invalid aes block size");var c=this.a[b],d=a[0]^c[0],e=a[b?3:1]^c[1],f=a[2]^c[2];a=a[b?1:3]^c[3];var g,h,i,k=c.length/4-2,j,l=4,m=[0,0,0,0];g=this.h[b];var n=g[0],o=g[1],p=g[2],q=g[3],r=g[4];for(j=0;j<k;j++){g=n[d>>>24]^o[e>>16&255]^p[f>>8&255]^q[a&255]^c[l];h=n[e>>>24]^o[f>>16&255]^p[a>>8&255]^q[d&255]^c[l+1];i=n[f>>>24]^o[a>>16&255]^p[d>>8&255]^q[e&255]^c[l+2];a=n[a>>>24]^o[d>>16&
+255]^p[e>>8&255]^q[f&255]^c[l+3];l+=4;d=g;e=h;f=i}for(j=0;j<4;j++){m[b?3&-j:j]=r[d>>>24]<<24^r[e>>16&255]<<16^r[f>>8&255]<<8^r[a&255]^c[l++];g=d;d=e;e=f;f=a;a=g}return m}};
+sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===undefined?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<<c)-1},concat:function(a,b){if(a.length===0||b.length===0)return a.concat(b);var c=a[a.length-1],d=sjcl.bitArray.getPartial(c);return d===32?a.concat(b):sjcl.bitArray.P(b,d,c|0,a.slice(0,a.length-1))},bitLength:function(a){var b=a.length;
+if(b===0)return 0;return(b-1)*32+sjcl.bitArray.getPartial(a[b-1])},clamp:function(a,b){if(a.length*32<b)return a;a=a.slice(0,Math.ceil(b/32));var c=a.length;b&=31;if(c>0&&b)a[c-1]=sjcl.bitArray.partial(b,a[c-1]&2147483648>>b-1,1);return a},partial:function(a,b,c){if(a===32)return b;return(c?b|0:b<<32-a)+a*0x10000000000},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return false;var c=0,d;for(d=0;d<a.length;d++)c|=
+a[d]^b[d];return c===0},P:function(a,b,c,d){var e;e=0;if(d===undefined)d=[];for(;b>=32;b-=32){d.push(c);c=0}if(b===0)return d.concat(a);for(e=0;e<a.length;e++){d.push(c|a[e]>>>b);c=a[e]<<32-b}e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,b+a>32?c:d.pop(),1));return d},k:function(a,b){return[a[0]^b[0],a[1]^b[1],a[2]^b[2],a[3]^b[3]]}};
+sjcl.codec.utf8String={fromBits:function(a){var b="",c=sjcl.bitArray.bitLength(a),d,e;for(d=0;d<c/8;d++){if((d&3)===0)e=a[d/4];b+=String.fromCharCode(e>>>24);e<<=8}return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c<a.length;c++){d=d<<8|a.charCodeAt(c);if((c&3)===3){b.push(d);d=0}}c&3&&b.push(sjcl.bitArray.partial(8*(c&3),d));return b}};
+sjcl.codec.hex={fromBits:function(a){var b="",c;for(c=0;c<a.length;c++)b+=((a[c]|0)+0xf00000000000).toString(16).substr(4);return b.substr(0,sjcl.bitArray.bitLength(a)/4)},toBits:function(a){var b,c=[],d;a=a.replace(/\s|0x/g,"");d=a.length;a+="00000000";for(b=0;b<a.length;b+=8)c.push(parseInt(a.substr(b,8),16)^0);return sjcl.bitArray.clamp(c,d*4)}};
+sjcl.codec.base64={D:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fromBits:function(a,b,c){var d="",e=0,f=sjcl.codec.base64.D,g=0,h=sjcl.bitArray.bitLength(a);if(c)f=f.substr(0,62)+"-_";for(c=0;d.length*6<h;){d+=f.charAt((g^a[c]>>>e)>>>26);if(e<6){g=a[c]<<6-e;e+=26;c++}else{g<<=6;e-=6}}for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d=0,e=sjcl.codec.base64.D,f=0,g;if(b)e=e.substr(0,62)+"-_";for(b=0;b<a.length;b++){g=e.indexOf(a.charAt(b));
+if(g<0)throw new sjcl.exception.invalid("this isn't base64!");if(d>26){d-=26;c.push(f^g>>>d);f=g<<32-d}else{d+=6;f^=g<<32-d}}d&56&&c.push(sjcl.bitArray.partial(d&56,f,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.a[0]||this.w();if(a){this.n=a.n.slice(0);this.i=a.i.slice(0);this.e=a.e}else this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()};
+sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.n=this.N.slice(0);this.i=[];this.e=0;return this},update:function(a){if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);var b,c=this.i=sjcl.bitArray.concat(this.i,a);b=this.e;a=this.e=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)this.C(c.splice(0,16));return this},finalize:function(){var a,b=this.i,c=this.n;b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.e/
+4294967296));for(b.push(this.e|0);b.length;)this.C(b.splice(0,16));this.reset();return c},N:[],a:[],w:function(){function a(e){return(e-Math.floor(e))*0x100000000|0}var b=0,c=2,d;a:for(;b<64;c++){for(d=2;d*d<=c;d++)if(c%d===0)continue a;if(b<8)this.N[b]=a(Math.pow(c,0.5));this.a[b]=a(Math.pow(c,1/3));b++}},C:function(a){var b,c,d=a.slice(0),e=this.n,f=this.a,g=e[0],h=e[1],i=e[2],k=e[3],j=e[4],l=e[5],m=e[6],n=e[7];for(a=0;a<64;a++){if(a<16)b=d[a];else{b=d[a+1&15];c=d[a+14&15];b=d[a&15]=(b>>>7^b>>>18^
+b>>>3^b<<25^b<<14)+(c>>>17^c>>>19^c>>>10^c<<15^c<<13)+d[a&15]+d[a+9&15]|0}b=b+n+(j>>>6^j>>>11^j>>>25^j<<26^j<<21^j<<7)+(m^j&(l^m))+f[a];n=m;m=l;l=j;j=k+b|0;k=i;i=h;h=g;g=b+(h&i^k&(h^i))+(h>>>2^h>>>13^h>>>22^h<<30^h<<19^h<<10)|0}e[0]=e[0]+g|0;e[1]=e[1]+h|0;e[2]=e[2]+i|0;e[3]=e[3]+k|0;e[4]=e[4]+j|0;e[5]=e[5]+l|0;e[6]=e[6]+m|0;e[7]=e[7]+n|0}};
+sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,i=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];if(i<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(f=2;f<4&&k>>>8*f;f++);if(f<15-i)f=15-i;c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.G(a,b,c,d,e,f);g=sjcl.mode.ccm.I(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),i=f.clamp(b,h-e),k=f.bitSlice(b,
+h-e);h=(h-e)/8;if(g<7)throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes");for(b=2;b<4&&h>>>8*b;b++);if(b<15-g)b=15-g;c=f.clamp(c,8*(15-b));i=sjcl.mode.ccm.I(a,i,c,k,e,b);a=sjcl.mode.ccm.G(a,i.data,c,d,e,b);if(!f.equal(i.tag,a))throw new sjcl.exception.corrupt("ccm: tag doesn't match");return i.data},G:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,i=h.k;e/=8;if(e%2||e<4||e>16)throw new sjcl.exception.invalid("ccm: invalid tag length");if(d.length>0xffffffff||b.length>0xffffffff)throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data");
+f=[h.partial(8,(d.length?64:0)|e-2<<2|f-1)];f=h.concat(f,c);f[3]|=h.bitLength(b)/8;f=a.encrypt(f);if(d.length){c=h.bitLength(d)/8;if(c<=65279)g=[h.partial(16,c)];else if(c<=0xffffffff)g=h.concat([h.partial(16,65534)],[c]);g=h.concat(g,d);for(d=0;d<g.length;d+=4)f=a.encrypt(i(f,g.slice(d,d+4).concat([0,0,0])))}for(d=0;d<b.length;d+=4)f=a.encrypt(i(f,b.slice(d,d+4).concat([0,0,0])));return h.clamp(f,e*8)},I:function(a,b,c,d,e,f){var g,h=sjcl.bitArray;g=h.k;var i=b.length,k=h.bitLength(b);c=h.concat([h.partial(8,
+f-1)],c).concat([0,0,0]).slice(0,4);d=h.bitSlice(g(d,a.encrypt(c)),0,e);if(!i)return{tag:d,data:[]};for(g=0;g<i;g+=4){c[3]++;e=a.encrypt(c);b[g]^=e[0];b[g+1]^=e[1];b[g+2]^=e[2];b[g+3]^=e[3]}return{tag:d,data:h.clamp(b,k)}}};
+sjcl.mode.ocb2={name:"ocb2",encrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");var g,h=sjcl.mode.ocb2.A,i=sjcl.bitArray,k=i.k,j=[0,0,0,0];c=h(a.encrypt(c));var l,m=[];d=d||[];e=e||64;for(g=0;g+4<b.length;g+=4){l=b.slice(g,g+4);j=k(j,l);m=m.concat(k(c,a.encrypt(k(c,l))));c=h(c)}l=b.slice(g);b=i.bitLength(l);g=a.encrypt(k(c,[0,0,0,b]));l=i.clamp(k(l.concat([0,0,0]),g),b);j=k(j,k(l.concat([0,0,0]),g));j=a.encrypt(k(j,k(c,h(c))));
+if(d.length)j=k(j,f?d:sjcl.mode.ocb2.pmac(a,d));return m.concat(i.concat(l,i.clamp(j,e)))},decrypt:function(a,b,c,d,e,f){if(sjcl.bitArray.bitLength(c)!==128)throw new sjcl.exception.invalid("ocb iv must be 128 bits");e=e||64;var g=sjcl.mode.ocb2.A,h=sjcl.bitArray,i=h.k,k=[0,0,0,0],j=g(a.encrypt(c)),l,m,n=sjcl.bitArray.bitLength(b)-e,o=[];d=d||[];for(c=0;c+4<n/32;c+=4){l=i(j,a.decrypt(i(j,b.slice(c,c+4))));k=i(k,l);o=o.concat(l);j=g(j)}m=n-c*32;l=a.encrypt(i(j,[0,0,0,m]));l=i(l,h.clamp(b.slice(c),
+m).concat([0,0,0]));k=i(k,l);k=a.encrypt(i(k,i(j,g(j))));if(d.length)k=i(k,f?d:sjcl.mode.ocb2.pmac(a,d));if(!h.equal(h.clamp(k,e),h.bitSlice(b,n)))throw new sjcl.exception.corrupt("ocb: tag doesn't match");return o.concat(h.clamp(l,m))},pmac:function(a,b){var c,d=sjcl.mode.ocb2.A,e=sjcl.bitArray,f=e.k,g=[0,0,0,0],h=a.encrypt([0,0,0,0]);h=f(h,d(d(h)));for(c=0;c+4<b.length;c+=4){h=d(h);g=f(g,a.encrypt(f(h,b.slice(c,c+4))))}b=b.slice(c);if(e.bitLength(b)<128){h=f(h,d(h));b=e.concat(b,[2147483648|0,0,
+0,0])}g=f(g,b);return a.encrypt(f(d(f(h,d(h))),g))},A:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^(a[0]>>>31)*135]}};sjcl.misc.hmac=function(a,b){this.M=b=b||sjcl.hash.sha256;var c=[[],[]],d=b.prototype.blockSize/32;this.l=[new b,new b];if(a.length>d)a=b.hash(a);for(b=0;b<d;b++){c[0][b]=a[b]^909522486;c[1][b]=a[b]^1549556828}this.l[0].update(c[0]);this.l[1].update(c[1])};
+sjcl.misc.hmac.prototype.encrypt=sjcl.misc.hmac.prototype.mac=function(a,b){a=(new this.M(this.l[0])).update(a,b).finalize();return(new this.M(this.l[1])).update(a).finalize()};
+sjcl.misc.pbkdf2=function(a,b,c,d,e){c=c||1E3;if(d<0||c<0)throw sjcl.exception.invalid("invalid params to pbkdf2");if(typeof a==="string")a=sjcl.codec.utf8String.toBits(a);e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,i,k=[],j=sjcl.bitArray;for(i=1;32*k.length<(d||1);i++){e=f=a.encrypt(j.concat(b,[i]));for(g=1;g<c;g++){f=a.encrypt(f);for(h=0;h<f.length;h++)e[h]^=f[h]}k=k.concat(e)}if(d)k=j.clamp(k,d);return k};
+sjcl.random={randomWords:function(a,b){var c=[];b=this.isReady(b);var d;if(b===0)throw new sjcl.exception.notReady("generator isn't seeded");else b&2&&this.U(!(b&1));for(b=0;b<a;b+=4){(b+1)%0x10000===0&&this.L();d=this.u();c.push(d[0],d[1],d[2],d[3])}this.L();return c.slice(0,a)},setDefaultParanoia:function(a){this.t=a},addEntropy:function(a,b,c){c=c||"user";var d,e,f=(new Date).valueOf(),g=this.q[c],h=this.isReady();d=this.F[c];if(d===undefined)d=this.F[c]=this.R++;if(g===undefined)g=this.q[c]=0;this.q[c]=
+(this.q[c]+1)%this.b.length;switch(typeof a){case "number":break;case "object":if(b===undefined)for(c=b=0;c<a.length;c++)for(e=a[c];e>0;){b++;e>>>=1}this.b[g].update([d,this.J++,2,b,f,a.length].concat(a));break;case "string":if(b===undefined)b=a.length;this.b[g].update([d,this.J++,3,b,f,a.length]);this.b[g].update(a);break;default:throw new sjcl.exception.bug("random: addEntropy only supports number, array or string");}this.j[g]+=b;this.f+=b;if(h===0){this.isReady()!==0&&this.K("seeded",Math.max(this.g,
+this.f));this.K("progress",this.getProgress())}},isReady:function(a){a=this.B[a!==undefined?a:this.t];return this.g&&this.g>=a?this.j[0]>80&&(new Date).valueOf()>this.O?3:1:this.f>=a?2:0},getProgress:function(a){a=this.B[a?a:this.t];return this.g>=a?1["0"]:this.f>a?1["0"]:this.f/a},startCollectors:function(){if(!this.m){if(window.addEventListener){window.addEventListener("load",this.o,false);window.addEventListener("mousemove",this.p,false)}else if(document.attachEvent){document.attachEvent("onload",
+this.o);document.attachEvent("onmousemove",this.p)}else throw new sjcl.exception.bug("can't attach event");this.m=true}},stopCollectors:function(){if(this.m){if(window.removeEventListener){window.removeEventListener("load",this.o,false);window.removeEventListener("mousemove",this.p,false)}else if(window.detachEvent){window.detachEvent("onload",this.o);window.detachEvent("onmousemove",this.p)}this.m=false}},addEventListener:function(a,b){this.r[a][this.Q++]=b},removeEventListener:function(a,b){var c;
+a=this.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&a[c]===b&&d.push(c);for(b=0;b<d.length;b++){c=d[b];delete a[c]}},b:[new sjcl.hash.sha256],j:[0],z:0,q:{},J:0,F:{},R:0,g:0,f:0,O:0,a:[0,0,0,0,0,0,0,0],d:[0,0,0,0],s:undefined,t:6,m:false,r:{progress:{},seeded:{}},Q:0,B:[0,48,64,96,128,192,0x100,384,512,768,1024],u:function(){for(var a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}return this.s.encrypt(this.d)},L:function(){this.a=this.u().concat(this.u());this.s=new sjcl.cipher.aes(this.a)},
+T:function(a){this.a=sjcl.hash.sha256.hash(this.a.concat(a));this.s=new sjcl.cipher.aes(this.a);for(a=0;a<4;a++){this.d[a]=this.d[a]+1|0;if(this.d[a])break}},U:function(a){var b=[],c=0,d;this.O=b[0]=(new Date).valueOf()+3E4;for(d=0;d<16;d++)b.push(Math.random()*0x100000000|0);for(d=0;d<this.b.length;d++){b=b.concat(this.b[d].finalize());c+=this.j[d];this.j[d]=0;if(!a&&this.z&1<<d)break}if(this.z>=1<<this.b.length){this.b.push(new sjcl.hash.sha256);this.j.push(0)}this.f-=c;if(c>this.g)this.g=c;this.z++;
+this.T(b)},p:function(a){sjcl.random.addEntropy([a.x||a.clientX||a.offsetX,a.y||a.clientY||a.offsetY],2,"mouse")},o:function(){sjcl.random.addEntropy(new Date,2,"loadtime")},K:function(a,b){var c;a=sjcl.random.r[a];var d=[];for(c in a)a.hasOwnProperty(c)&&d.push(a[c]);for(c=0;c<d.length;c++)d[c](b)}};try{var s=new Uint32Array(32);crypto.getRandomValues(s);sjcl.random.addEntropy(s,1024,"crypto['getRandomValues']")}catch(t){}
+sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},encrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.c({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.c(f,c);c=f.adata;if(typeof f.salt==="string")f.salt=sjcl.codec.base64.toBits(f.salt);if(typeof f.iv==="string")f.iv=sjcl.codec.base64.toBits(f.iv);if(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||typeof a==="string"&&f.iter<=100||f.ts!==64&&f.ts!==96&&f.ts!==128||f.ks!==128&&f.ks!==192&&f.ks!==0x100||f.iv.length<
+2||f.iv.length>4)throw new sjcl.exception.invalid("json encrypt: invalid parameters");if(typeof a==="string"){g=sjcl.misc.cachedPbkdf2(a,f);a=g.key.slice(0,f.ks/32);f.salt=g.salt}if(typeof b==="string")b=sjcl.codec.utf8String.toBits(b);if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);g=new sjcl.cipher[f.cipher](a);e.c(d,f);d.key=a;f.ct=sjcl.mode[f.mode].encrypt(g,b,f.iv,c,f.ts);return e.encode(e.V(f,e.defaults))},decrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json;b=e.c(e.c(e.c({},e.defaults),
+e.decode(b)),c,true);var f;c=b.adata;if(typeof b.salt==="string")b.salt=sjcl.codec.base64.toBits(b.salt);if(typeof b.iv==="string")b.iv=sjcl.codec.base64.toBits(b.iv);if(!sjcl.mode[b.mode]||!sjcl.cipher[b.cipher]||typeof a==="string"&&b.iter<=100||b.ts!==64&&b.ts!==96&&b.ts!==128||b.ks!==128&&b.ks!==192&&b.ks!==0x100||!b.iv||b.iv.length<2||b.iv.length>4)throw new sjcl.exception.invalid("json decrypt: invalid parameters");if(typeof a==="string"){f=sjcl.misc.cachedPbkdf2(a,b);a=f.key.slice(0,b.ks/32);
+b.salt=f.salt}if(typeof c==="string")c=sjcl.codec.utf8String.toBits(c);f=new sjcl.cipher[b.cipher](a);c=sjcl.mode[b.mode].decrypt(f,b.ct,b.iv,c,b.ts);e.c(d,b);d.key=a;return sjcl.codec.utf8String.fromBits(c)},encode:function(a){var b,c="{",d="";for(b in a)if(a.hasOwnProperty(b)){if(!b.match(/^[a-z0-9]+$/i))throw new sjcl.exception.invalid("json encode: invalid property name");c+=d+'"'+b+'":';d=",";switch(typeof a[b]){case "number":case "boolean":c+=a[b];break;case "string":c+='"'+escape(a[b])+'"';
+break;case "object":c+='"'+sjcl.codec.base64.fromBits(a[b],1)+'"';break;default:throw new sjcl.exception.bug("json encode: unsupported type");}}return c+"}"},decode:function(a){a=a.replace(/\s/g,"");if(!a.match(/^\{.*\}$/))throw new sjcl.exception.invalid("json decode: this isn't json!");a=a.replace(/^\{|\}$/g,"").split(/,/);var b={},c,d;for(c=0;c<a.length;c++){if(!(d=a[c].match(/^(?:(["']?)([a-z][a-z0-9]*)\1):(?:(\d+)|"([a-z0-9+\/%*_.@=\-]*)")$/i)))throw new sjcl.exception.invalid("json decode: this isn't json!");
+b[d[2]]=d[3]?parseInt(d[3],10):d[2].match(/^(ct|salt|iv)$/)?sjcl.codec.base64.toBits(d[4]):unescape(d[4])}return b},c:function(a,b,c){if(a===undefined)a={};if(b===undefined)return a;var d;for(d in b)if(b.hasOwnProperty(d)){if(c&&a[d]!==undefined&&a[d]!==b[d])throw new sjcl.exception.invalid("required parameter overridden");a[d]=b[d]}return a},V:function(a,b){var c={},d;for(d in a)if(a.hasOwnProperty(d)&&a[d]!==b[d])c[d]=a[d];return c},W:function(a,b){var c={},d;for(d=0;d<b.length;d++)if(a[b[d]]!==
+undefined)c[b[d]]=a[b[d]];return c}};sjcl.encrypt=sjcl.json.encrypt;sjcl.decrypt=sjcl.json.decrypt;sjcl.misc.S={};sjcl.misc.cachedPbkdf2=function(a,b){var c=sjcl.misc.S,d;b=b||{};d=b.iter||1E3;c=c[a]=c[a]||{};d=c[d]=c[d]||{firstSalt:b.salt&&b.salt.length?b.salt.slice(0):sjcl.random.randomWords(2,0)};c=b.salt===undefined?d.firstSalt:b.salt;d[c]=d[c]||sjcl.misc.pbkdf2(a,c,b.iter);return{key:d[c].slice(0),salt:c.slice(0)}};

+ 158 - 0
lib/vizhash_gd_zero.php

@@ -0,0 +1,158 @@
+<?php
+// VizHash_GD 0.0.4 beta ZeroBin 0.15
+// Visual Hash implementation in php4+GD, stripped down and modified version for ZeroBin
+// See: http://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
+// This is free software under the zlib/libpng licence
+// http://www.opensource.org/licenses/zlib-license.php
+/* Example:
+    $vz = new vizhash16x16();
+    $data = $vz->generate('hello');
+    header('Content-type: image/png');
+    echo $data;
+    exit;
+*/
+class vizhash16x16
+{
+    private $VALUES;
+    private $VALUES_INDEX;
+    private $width;
+    private $height;
+    private $salt;
+    function __construct()
+    {
+        $this->width=16;
+        $this->height=16;
+        
+        // Read salt from file (and create it if does not exist).
+        // The salt will make vizhash avatar unique on each ZeroBin installation
+        // to prevent IP checking.
+        $saltfile = 'data/salt.php';
+        if (!is_file($saltfile))
+            file_put_contents($saltfile,'<?php /* |'.$this->randomSalt().'| */ ?>');
+        $items=explode('|',file_get_contents($saltfile));
+        $this->salt = $items[1];
+    }  
+    
+    // Generate a 16x16 png corresponding to $text.
+    // Input: $text (string)
+    // Output: PNG data. Or empty string if GD is not available.
+    function generate($text)
+    {
+        if (!function_exists('gd_info')) return '';
+
+        // We hash the input string.
+        $hash=hash('sha1',$text.$this->salt).hash('md5',$text.$this->salt);
+        $hash=$hash.strrev($hash);  # more data to make graphics
+
+        // We convert the hash into an array of integers.
+        $this->VALUES=array();
+        for($i=0; $i<strlen($hash); $i=$i+2){ array_push($this->VALUES,hexdec(substr($hash,$i,2))); }
+        $this->VALUES_INDEX=0; // to walk the array.
+
+        // Then use these integers to drive the creation of an image.
+        $image = imagecreatetruecolor($this->width,$this->height);
+
+        $r0 = $this->getInt();$r=$r0;
+        $g0 = $this->getInt();$g=$g0;
+        $b0 = $this->getInt();$b=$b0;
+
+        // First, create an image with a specific gradient background.
+        $op='v'; if (($this->getInt()%2)==0) { $op='h'; };
+        $image = $this->degrade($image,$op,array($r0,$g0,$b0),array(0,0,0));
+
+        for($i=0; $i<7; $i=$i+1)
+        {     
+            $action=$this->getInt();
+            $color = imagecolorallocate($image, $r,$g,$b);
+            $r = ($r0 + $this->getInt()/25)%256;
+            $g = ($g0 + $this->getInt()/25)%256;
+            $b = ($b0 + $this->getInt()/25)%256;
+            $r0=$r; $g0=$g; $b0=$b;
+            $this->drawshape($image,$action,$color);   
+        }
+
+        $color = imagecolorallocate($image,$this->getInt(),$this->getInt(),$this->getInt());
+        $this->drawshape($image,$this->getInt(),$color);
+        ob_start();
+        imagepng($image);
+        $imagedata = ob_get_contents();
+        ob_end_clean();
+        imagedestroy($image);
+        
+        return $imagedata;
+    } 
+    
+    // Generate a large random hexadecimal salt.
+    private function randomSalt()
+    {
+        $randomSalt='';
+        for($i=0;$i<6;$i++) { $randomSalt.=base_convert(mt_rand(),10,16); }
+        return $randomSalt;
+    }
+   
+    
+    private function getInt() // Returns a single integer from the $VALUES array (0...255)
+    {
+        $v= $this->VALUES[$this->VALUES_INDEX]; 
+        $this->VALUES_INDEX++;
+        $this->VALUES_INDEX %= count($this->VALUES); // Warp around the array
+        return $v;
+    }
+    private function getX() // Returns a single integer from the array (roughly mapped to image width) 
+    {
+        return $this->width*$this->getInt()/256;
+    }
+
+    private function getY() // Returns a single integer from the array (roughly mapped to image height) 
+    { 
+        return $this->height*$this->getInt()/256;
+    }  
+    
+    # Gradient function taken from:
+    # http://www.supportduweb.com/scripts_tutoriaux-code-source-41-gd-faire-un-degrade-en-php-gd-fonction-degrade-imagerie.html
+    private function degrade($img,$direction,$color1,$color2)
+    {
+            if($direction=='h') { $size = imagesx($img); $sizeinv = imagesy($img); }
+            else { $size = imagesy($img); $sizeinv = imagesx($img);}
+            $diffs = array(
+                    (($color2[0]-$color1[0])/$size),
+                    (($color2[1]-$color1[1])/$size),
+                    (($color2[2]-$color1[2])/$size)
+            );
+            for($i=0;$i<$size;$i++)
+            {
+                    $r = $color1[0]+($diffs[0]*$i);
+                    $g = $color1[1]+($diffs[1]*$i);
+                    $b = $color1[2]+($diffs[2]*$i);
+                    if($direction=='h') { imageline($img,$i,0,$i,$sizeinv,imagecolorallocate($img,$r,$g,$b)); }
+                    else { imageline($img,0,$i,$sizeinv,$i,imagecolorallocate($img,$r,$g,$b)); }
+            }
+            return $img;
+    }
+    
+    private function drawshape($image,$action,$color)
+    {
+        switch($action%7)
+        {
+            case 0:
+                ImageFilledRectangle ($image,$this->getX(),$this->getY(),$this->getX(),$this->getY(),$color);  
+                break;
+            case 1:
+            case 2:
+                ImageFilledEllipse ($image, $this->getX(), $this->getY(), $this->getX(), $this->getY(), $color);  
+                break;
+            case 3:
+                $points = array($this->getX(), $this->getY(), $this->getX(), $this->getY(), $this->getX(), $this->getY(),$this->getX(), $this->getY());
+                ImageFilledPolygon ($image, $points, 4, $color);
+                break;
+            case 4:
+            case 5:
+            case 6:
+                $start=$this->getInt()*360/256; $end=$start+$this->getInt()*180/256;
+                ImageFilledArc ($image, $this->getX(), $this->getY(), $this->getX(), $this->getY(),$start,$end,$color,IMG_ARC_PIE);
+                break;     
+        }
+    }    
+}    
+
+?>

+ 357 - 0
lib/zerobin.css

@@ -0,0 +1,357 @@
+/* ZeroBin 0.15 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
+
+/*   Hé, t'as vu Idleman, j'ai fait un effort sur les CSS !   ;-)      */
+
+/*  CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.
+Licensed under the BSD License. - http://yuilibrary.com/license/    */
+html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}
+
+html  {
+background-color:#455463;
+color:white;
+min-height:100%;
+background-image: linear-gradient(bottom, #0F1823 0%, #455463 100%);
+background-image: -o-linear-gradient(bottom, #0F1823 0%, #455463 100%);
+background-image: -moz-linear-gradient(bottom, #0F1823 0%, #455463 100%);
+background-image: -webkit-linear-gradient(bottom, #0F1823 0%, #455463 100%);
+background-image: -ms-linear-gradient(bottom, #0F1823 0%, #455463 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0F1823), color-stop(1, #455463));
+}
+
+body {
+font-family: Arial, Helvetica, sans-serif;
+font-size: 0.8em;
+margin-bottom:15px;
+padding-left:60px; padding-right:60px;
+}
+ 
+a  { color:#0F388F; }
+
+h1 {
+font-size:3.5em;
+font-weight:700;
+color:#000;
+position:relative;
+display:inline;
+cursor:pointer;
+}
+
+h1:before {
+content:attr(title);
+position:absolute;
+color:rgba(255,255,255,0.15);
+top:1px;
+left:1px;
+cursor:pointer;
+}
+
+h2 { 
+color:#000; 
+font-size:1em; 
+display:inline; 
+font-style:italic; 
+font-weight:bold; 
+position:relative; 
+bottom:8px;} 
+  
+h3 { 
+color:#94a3b4; 
+font-size:0.7em; 
+display:inline; 
+position:relative; 
+bottom:8px;}   
+
+#aboutbox { 
+font-size:0.85em;
+color: #94a3b4; 
+padding: 4px 8px 4px 16px;
+position:relative; 
+top:10px;
+border-left: 2px solid #94a3b4;
+float:right;  
+width:60%;
+}
+
+div#aboutbox a { color: #94a3b4; }
+
+textarea#message,div#cleartext,.replymessage {
+clear:both;
+color:black;
+background-color:#fff;
+white-space:pre-wrap;
+font-family:Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace;
+font-size:9pt;
+border: 1px solid #28343F;
+padding:5px;
+box-sizing:border-box;
+-webkit-box-sizing:border-box;
+-moz-box-sizing:border-box;
+-ms-box-sizing:border-box;
+-o-box-sizing:border-box;
+width:100%;
+}
+
+div#status { 
+clear:both; 
+padding:5px 10px; 
+}
+
+
+div#pastelink { 
+background-color:#1F2833;
+color:white;
+padding:4px 12px; 
+clear:both;
+-moz-box-shadow: inset 0px 2px 2px #000;
+-webkit-box-shadow: inset 0px 2px 2px #000;
+box-shadow: inset 0px 2px 5px #000;
+}
+div#pastelink a { color:white; }
+div#pastelink button { margin-left:11px }
+div#toolbar, div#status { margin-bottom:5px; }
+
+button,.button,div#expiration,div#language {
+color:#fff;
+background-color:#323B47;
+background-repeat:no-repeat;
+background-position:center left;
+padding:4px 8px;
+font-size:1em;
+margin-right:5px;
+display:inline;
+background-image: linear-gradient(bottom, #323B47 0%, #51606E 100%);
+background-image: -o-linear-gradient(bottom, #323B47 0%, #51606E 100%);
+background-image: -moz-linear-gradient(bottom, #323B47 0%, #51606E 100%);
+background-image: -webkit-linear-gradient(bottom, #323B47 0%, #51606E 100%);
+background-image: -ms-linear-gradient(bottom, #323B47 0%, #51606E 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #323B47), color-stop(1, #51606E));
+border: 1px solid #28343F;
+-moz-box-shadow: inset 0px 1px 2px #647384;
+-webkit-box-shadow: inset 0px 1px 2px #647384;
+box-shadow: inset 0px 1px 2px #647384;
+-webkit-border-radius: 3px;
+-moz-border-radius: 3px;
+border-radius: 3px;
+-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
+}
+button:hover  { 
+background-image: linear-gradient(bottom, #424B57 0%, #61707E 100%);
+background-image: -o-linear-gradient(bottom, #424B57 0%, #61707E 100%);
+background-image: -moz-linear-gradient(bottom, #424B57 0%, #61707E 100%);
+background-image: -webkit-linear-gradient(bottom, #424B57 0%, #61707E 100%);
+background-image: -ms-linear-gradient(bottom, #424B57 0%, #61707E 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #424B57), color-stop(1, #61707E));
+}
+button:active {
+background-image: linear-gradient(bottom, #51606E 0%, #323B47 100%);
+background-image: -o-linear-gradient(bottom, #51606E 0%, #323B47 100%);
+background-image: -moz-linear-gradient(bottom, #51606E 0%, #323B47 100%);
+background-image: -webkit-linear-gradient(bottom, #51606E 0%, #323B47 100%);
+background-image: -ms-linear-gradient(bottom, #51606E 0%, #323B47 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #51606E), color-stop(1, #323B47));
+position:relative;
+top:1px;
+}
+button:disabled, .buttondisabled {
+background:#ccc;
+color:#888;
+top:0px;
+}
+button img {
+margin-right:8px;
+position:relative;
+top:2px;
+}
+
+div#expiration, div#language, div#opendisc { 
+background-color:#414D5A; 
+padding:6px 8px;
+margin:0px 5px 0px 0px;;
+position: relative;
+bottom:1px; /* WTF ? Why is this shifted by 1 pixel ? */
+}
+div#expiration select, div#language select {
+color:#eee;
+background: transparent;
+border: none;
+}
+
+
+div#expiration select option, div#language select option {
+color:#eee;
+background: #414D5A;
+background-color:#414D5A;
+}
+
+div#remainingtime {
+color: #94a3b4; 
+display:inline;
+font-size:0.85em;
+}
+
+.foryoureyesonly {
+color: yellow !important;
+font-size: 1em !important;
+font-weight:bold !important;
+}
+
+button#newbutton { float:right; margin-right:0px;margin-bottom:5px; display:inline; }
+input { color:#777; font-size:1em; padding:6px; border: 1px solid #28343F; }
+
+.nonworking {
+background-color:#fff;
+color:#000;
+width:100%;
+text-align:center;
+font-weight:bold;
+font-size:10pt;
+-webkit-border-radius:4px;
+-moz-border-radius:4px;
+border-radius:4px;
+padding:5px;
+}
+
+div#ienotice {
+background-color:#7E98AF;
+color:#000;
+font-size:0.85em;
+padding:3px 5px;
+text-align:center;
+-webkit-border-radius:4px;
+-moz-border-radius:4px;
+border-radius:4px;
+display:none;
+}
+
+div#ienotice a {
+color:black;
+}
+
+div#oldienotice {
+display:none;
+}
+
+.errorMessage {
+background-color:#FF7979 !important;
+color:#FF0;
+}
+
+
+/* --- discussion related CSS ------- */
+
+
+div#discussion {  /* Discussion container */
+margin-top:20px;
+width:100%;
+margin-left:-30px;
+min-width:200px;
+}
+
+h4 {
+font-size:1.2em;
+color: #94A3B4;
+font-style:italic;
+font-weight:bold; 
+position:relative;
+margin-left:30px;
+}
+
+
+div.comment /* One single reply */
+{
+background-color:#CECED6;
+color:#000;
+white-space:pre-wrap;
+font-family:Consolas,"Lucida Console","DejaVu Sans Mono",Monaco,monospace;
+font-size:9pt;
+border-left: 1px solid #859AAE;
+border-top: 1px solid #859AAE;
+padding:5px 0px 5px 5px;
+margin-left:30px;
+-moz-box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
+-webkit-box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
+box-shadow: -3px -3px 5px rgba(0,0,0,0.15);
+min-width:200px;
+overflow:auto;
+}
+/* FIXME: Add min-width */
+
+div.reply {
+margin: 5px 0px 0px 30px;
+}
+
+div#replystatus { 
+display:inline;
+padding:1px 7px; 
+font-family: Arial, Helvetica, sans-serif;
+}
+
+div.comment button  {
+color:#446;
+background-color:#aab;
+background-repeat:no-repeat;
+background-position:center left;
+padding:0px 2px;
+font-size:0.73em;
+margin: 3px 5px 3px 0px;
+display:inline;
+background-image: linear-gradient(bottom, #aab 0%, #ccc 100%);
+background-image: -o-linear-gradient(bottom, #aab 0%, #ccc 100%);
+background-image: -moz-linear-gradient(bottom, #aab 0%, #ccc 100%);
+background-image: -webkit-linear-gradient(bottom, #aab 0%, #ccc 100%);
+background-image: -ms-linear-gradient(bottom, #aab 0%, #ccc 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #aab), color-stop(1, #ccc));
+border: 1px solid #ccd;
+-moz-box-shadow: inset 0px 1px 2px #ddd;
+-webkit-box-shadow: inset 0px 1px 2px #fff;
+box-shadow: inset 0px 1px 2px #eee;
+-webkit-border-radius: 3px;
+-moz-border-radius: 3px;
+border-radius: 3px;
+-moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box;
+}
+div.comment button:hover  { 
+background-image: linear-gradient(bottom, #ccd 0%, #fff 100%);
+background-image: -o-linear-gradient(bottom, #ccd 0%, #fff 100%);
+background-image: -moz-linear-gradient(bottom, #ccd 0%, #fff 100%);
+background-image: -webkit-linear-gradient(bottom, #ccd 0%, #fff 100%);
+background-image: -ms-linear-gradient(bottom, #ccd 0%, #fff 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccd), color-stop(1, #fff));
+}
+div.comment button:active {
+background-image: linear-gradient(bottom, #fff 0%, #889 100%);
+background-image: -o-linear-gradient(bottom, #fff 0%, #889 100%);
+background-image: -moz-linear-gradient(bottom, #fff 0%, #889 100%);
+background-image: -webkit-linear-gradient(bottom, #fff 0%, #889 100%);
+background-image: -ms-linear-gradient(bottom, #fff 0%, #889 100%);
+background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(1, #889));
+position:relative;
+top:1px;
+}
+
+div.comment input  {
+padding:2px;
+}
+
+textarea#replymessage {
+margin-top:5px;
+}
+
+div.commentmeta {
+color: #fff;
+background-color:#8EA0B2;
+margin-bottom:3px;
+padding:0px 0px 0px 3px;
+}
+
+span.commentdate {
+color: #BFCEDE;
+}
+
+img.vizhash {
+width:16px;
+height:16px;
+position:relative;
+top:2px;
+left:-3px;
+}

+ 364 - 0
lib/zerobin.js

@@ -0,0 +1,364 @@
+/* ZeroBin 0.15 - http://sebsauvage.net/wiki/doku.php?id=php:zerobin */
+
+// Immediately start random number generator collector.
+sjcl.random.startCollectors();
+
+// Converts a duration (in seconds) into human readable format.
+function secondsToHuman(seconds)
+{
+    if (seconds<60) { var v=Math.floor(seconds); return v+' second'+((v>1)?'s':''); }
+    if (seconds<60*60) { var v=Math.floor(seconds/60); return v+' minute'+((v>1)?'s':''); }
+    if (seconds<60*60*24) { var v=Math.floor(seconds/(60*60)); return v+' hour'+((v>1)?'s':''); }
+    // If less than 2 months, display in days:
+    if (seconds<60*60*24*60) { var v=Math.floor(seconds/(60*60*24)); return v+' day'+((v>1)?'s':''); }
+    var v=Math.floor(seconds/(60*60*24*30)); return v+' month'+((v>1)?'s':'');
+}
+// Compress a message (deflate compression). Returns base64 encoded data.
+function compress(message) { return Base64.toBase64(RawDeflate.deflate(Base64.utob(message))); }
+
+// Decompress a message compressed with compress().
+function decompress(data) { return Base64.btou(RawDeflate.inflate(Base64.fromBase64(data))) }
+
+// Compress, then encrypt message with key.
+function zeroCipher(key,message)
+{
+    return sjcl.encrypt(key,compress(message));
+}
+// Decrypt message with key, then decompress.
+function zeroDecipher(key,data)
+{
+    return decompress(sjcl.decrypt(key,data));
+}
+
+// Returns the current script location (without search or hash part of the URL).
+// eg. http://server.com/zero/?aaaa#bbbb --> http://server.com/zero/
+function scriptLocation()
+{
+    return window.location.href.substring(0,window.location.href.length
+               -window.location.search.length -window.location.hash.length);
+}
+
+// Returns the paste unique identifier from the URL
+// eg. 'c05354954c49a487'
+function pasteID()
+{
+    return window.location.search.substring(1);
+}
+
+// Set text of a DOM element (required for IE)
+// This is equivalent to element.text(text)
+// Input: element : a DOM element.
+//        text : the text to enter.
+function setElementText(element,text)
+{
+    if ($('div#oldienotice').is(":visible"))  // For IE<10.
+    {
+        // IE<10 do not support white-space:pre-wrap; so we have to do this BIG UGLY STINKING THING.
+        element.text(text.replace(/\n/ig,'{BIG_UGLY_STINKING_THING__OH_GOD_I_HATE_IE}'));
+        element.html(element.text().replace(/{BIG_UGLY_STINKING_THING__OH_GOD_I_HATE_IE}/ig,"\r\n<br>"));
+    }
+    else // for other (sane) browsers:
+    {
+        element.text(text);
+    }
+}
+
+// Show decrypted text in the display area, including discussion (if open)
+// Input: messages (Array) : Array of messages to display (items = array with keys ('data','meta')
+//        key (string): decryption key
+function displayMessages(key,comments)
+{     
+    try { // Try to decrypt the paste.
+        var cleartext = zeroDecipher(key,comments[0].data); 
+    } catch(err) { 
+        $('div#cleartext').hide();
+        $('button#clonebutton').hide();
+        showError('Could not decrypt data (Wrong key ?)'); 
+        return;
+    }
+    setElementText($('div#cleartext'),cleartext);
+    urls2links($('div#cleartext')); // Convert URLs to clickable links.
+    
+    // Display paste expiration.
+    if (comments[0].meta.expire_date) $('div#remainingtime').removeClass('foryoureyesonly').text('This document will expire in '+secondsToHuman(comments[0].meta.remaining_time)+'.').show();
+    if (comments[0].meta.burnafterreading)
+    {
+        $('div#remainingtime').addClass('foryoureyesonly').text('FOR YOUR EYES ONLY.  Don\'t close this window, this message can\'t be displayed again.').show();
+        $('button#clonebutton').hide(); // Discourage cloning (as it can't really be prevented).
+    }
+    
+    // If the discussion is opened on this paste, display it.      
+    if (comments[0].meta.opendiscussion)
+    {
+        $('div#comments').html('');
+        for (var i = 1; i < comments.length; i++) // For each comment.
+        {
+            var comment=comments[i]; 
+            var cleartext="[Could not decrypt comment ; Wrong key ?]";            
+            try { cleartext = zeroDecipher(key,comment.data); } catch(err) { }                
+            var place = $('div#comments');
+            // If parent comment exists, display below (CSS will automatically shift it right.)
+            var cname = 'div#comment_'+comment.meta.parentid
+            if ($(cname).length)  place = $(cname); // If the element exists in page
+            var divComment = $('<div class="comment" id="comment_'+comment.meta.commentid+'">'
+                               +'<div class="commentmeta"><span class="nickname"></span><span class="commentdate"></span></div><div class="commentdata"></div>'
+                               +'<button onclick="open_reply($(this),\''+comment.meta.commentid+'\');return false;">Reply</button>'
+                               +'</div>');
+            setElementText(divComment.find('div.commentdata'),cleartext);
+            urls2links(divComment.find('div.commentdata')); // Convert URLs to clickable links in comment.
+            divComment.find('span.nickname').html('<i>(Anonymous)</i>');
+            // Try to get optional nickname:
+            try { divComment.find('span.nickname').text(zeroDecipher(key,comment.meta.nickname));} catch(err) { }  
+            divComment.find('span.commentdate').text('  ('+(new Date(comment.meta.postdate*1000).toUTCString())+')').attr('title','CommentID: '+comment.meta.commentid);
+            // If an avatar is available, display it.
+            if (comment.meta.vizhash) 
+                divComment.find('span.nickname').before('<img src="'+comment.meta.vizhash+'" class="vizhash" title="Anonymous avatar (Vizhash of the IP address)" />');
+            place.append(divComment);
+        }
+        $('div#comments').append('<div class="comment"><button onclick="open_reply($(this),\''+pasteID()+'\');return false;">Add comment</button></div>');
+        $('div#discussion').show();
+    }
+}
+
+// Open the comment entry when clicking the "Reply" button of a comment.
+// source = element which emitted the event.
+// commentid = identifier of the comment we want to reply to.
+function open_reply(source,commentid)
+{
+    $('div.reply').remove(); // Remove any other reply area.
+    source.after('<div class="reply">'
+                +'<input type="text" id="nickname" title="Optional nickname..." value="Optional nickname..." />'
+                +'<textarea id="replymessage" class="replymessage" cols="80" rows="7"></textarea>'
+                +'<br><button id="replybutton" onclick="send_comment(\''+commentid+'\');return false;">Post comment</button>'
+                +'<div id="replystatus">&nbsp;</div>'
+                +'</div>');
+    $('input#nickname').focus(function() {
+                             $(this).css('color', '#000');
+                             if($(this).val()==$(this).attr('title')) $(this).val('');
+                        });    
+    $('textarea#replymessage').focus();
+}
+
+// Send a reply in a discussion.
+// Input: parentid : the comment identifier we want to send a reply to.
+function send_comment(parentid)
+{
+    if ($('textarea#replymessage').val().length==0) return; // Do not send if no data.
+    showStatus('Sending comment...',spin=true); 
+    var cipherdata=zeroCipher(pageKey(), $('textarea#replymessage').val());
+    var ciphernickname='';
+    var nick=$('input#nickname').val();
+    if (nick!='' && nick!='Optional nickname...') ciphernickname=ciphernickname=zeroCipher(pageKey(),nick);
+    var data_to_send =  { data:cipherdata,
+                          parentid: parentid,
+                          pasteid: pasteID(),
+                          nickname: ciphernickname
+                        }; 
+    $.post(scriptLocation(), data_to_send ,'json' )
+    .error( function() { showError('Comment could not be sent (serveur error or not responding).'); } )
+    .success(function(data)
+             {
+                if (data.status==0) 
+                {
+                    showStatus('Comment posted.');
+                    location.reload();
+                }
+                else if (data.status==1) 
+                { 
+                    showError('Could not post comment: '+data.message); 
+                }
+                else
+                { 
+                    showError('Could not post comment.'); 
+                }
+             }
+    );
+}
+
+// Send a new paste to server
+function send_data()
+{
+    if ($('textarea#message').val().length==0) return; // Do not send if no data.
+    showStatus('Sending paste...',spin=true); 
+    var randomkey = sjcl.codec.base64.fromBits(sjcl.random.randomWords(8,0),0);
+    var cipherdata = zeroCipher(randomkey,$('textarea#message').val());
+    var data_to_send =  { data:cipherdata,
+                          expire:$('select#pasteExpiration').val(),
+                          opendiscussion:$('input#opendiscussion').is(':checked')?1:0
+                        }; 
+    $.post(scriptLocation(), data_to_send ,'json' )
+    .error( function() { showError('Data could not be sent (serveur error or not responding).'); } )
+    .success(function(data)
+             {
+                if (data.status==0) 
+                {
+                    stateExistingPaste();
+                    var url=scriptLocation()+"?"+data.id+'#'+randomkey; 
+                    showStatus('');
+                    $('div#pastelink').html('Your paste is <a href="'+url+'">'+url+'</a>');
+                    $('div#pastelink').append('&nbsp;&nbsp;<button id="shortenbutton" onclick="document.location=\''+shortenUrl(url)+'\'"><img src="lib/icon_shorten.png" width="13" height="15" />Shorten URL</button>').show();
+                    setElementText($('div#cleartext'),$('textarea#message').val());                    
+                    urls2links($('div#cleartext')); 
+                    showStatus('');
+                }
+                else if (data.status==1) 
+                { 
+                    showError('Could not create paste: '+data.message); 
+                }
+                else
+                { 
+                    showError('Could not create paste.'); 
+                }
+             }
+    );
+}
+
+// Put the screen in "New paste" mode.
+function stateNewPaste()
+{
+    $('button#sendbutton').show();
+    $('button#clonebutton').hide();
+    $('div#expiration').show();
+    $('div#remainingtime').hide();
+    $('div#language').hide(); // $('#language').show();
+    $('input#password').hide(); //$('#password').show();
+    $('div#opendisc').show();
+    $('button#newbutton').show();
+    $('div#pastelink').hide();
+    $('textarea#message').text('');
+    $('textarea#message').show();
+    $('div#cleartext').hide();
+    $('div#message').focus();
+    $('div#discussion').hide();
+}
+
+// Put the screen in "Existing paste" mode.
+function stateExistingPaste()
+{
+    $('button#sendbutton').hide();
+    if (!$('div#oldienotice').is(":visible")) $('button#clonebutton').show(); // No "clone" for IE<10.
+    $('button#clonebutton').show();// FIXME
+    $('div#expiration').hide();
+    $('div#language').hide();
+    $('input#password').hide();
+    $('div#opendisc').hide();
+    $('button#newbutton').show();
+    $('div#pastelink').hide();
+    $('textarea#message').hide();
+    $('div#cleartext').show();
+}
+
+// Clone the current paste.
+function clonePaste()
+{
+    stateNewPaste();
+    showStatus('');
+    $('textarea#message').text($('div#cleartext').text());
+}
+
+// Create a new paste.
+function newPaste()
+{
+    stateNewPaste();
+    showStatus('');
+    $('textarea#message').text('');
+}
+
+// Display an error message
+// (We use the same function for paste and reply to comments)
+function showError(message)
+{
+    $('div#status').addClass('errorMessage').text(message);
+    $('div#replystatus').addClass('errorMessage').text(message);
+}
+
+// Display status
+// (We use the same function for paste and reply to comments)
+// message (string) = text to display
+// spin (boolean, optional) = tell if the "spinning" animation should be displayed.
+function showStatus(message,spin)
+{
+    $('div#replystatus').removeClass('errorMessage');
+    $('div#replystatus').text(message);
+    if (!message) { $('div#status').html('&nbsp'); return; }
+    if (message=='') { $('div#status').html('&nbsp'); return; }
+    $('div#status').removeClass('errorMessage');
+    $('div#status').text(message);
+    if (spin)
+    {
+        var img = '<img src="lib/busy.gif" style="width:16px;height:9px;margin:0px 4px 0px 0px;" />';
+        $('div#status').prepend(img);
+        $('div#replystatus').prepend(img);
+    }
+}
+
+// Generate link to URL shortener.
+function shortenUrl(url)
+{
+    return 'http://snipurl.com/site/snip?link='+encodeURIComponent(url);
+}
+
+// Convert URLs to clickable links.
+// Input: element : a jQuery DOM element.
+// Example URLs to handle:
+//   magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7
+//   http://localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
+//   http://user:password@localhost:8800/zero/?6f09182b8ea51997#WtLEUO5Epj9UHAV9JFs+6pUQZp13TuspAUjnF+iM+dM=
+// FIXME: add ppa & apt links.
+function urls2links(element)
+{
+    var re = /((http|https|ftp):\/\/[\w?=&.\/-;#@~%+-]+(?![\w\s?&.\/;#~%"=-]*>))/ig;
+    element.html(element.html().replace(re,'<a href="$1" rel="nofollow">$1</a>'));
+    var re = /((magnet):[\w?=&.\/-;#@~%+-]+)/ig;
+    element.html(element.html().replace(re,'<a href="$1">$1</a>'));
+}
+
+// Return the deciphering key stored in anchor part of the URL
+function pageKey()
+{
+    var key = window.location.hash.substring(1);  // Get key
+    
+    // Some stupid web 2.0 services and redirectors add data AFTER the anchor
+    // (such as &utm_source=...).
+    // We will strip any additional data.
+    
+    // First, strip everything after the equal sign (=) which signals end of base64 string.
+    i = key.indexOf('='); if (i>-1) { key = key.substring(0,i+1); }
+    
+    // If the equal sign was not present, some parameters may remain:
+    i = key.indexOf('&'); if (i>-1) { key = key.substring(0,i); }
+    
+    // Then add trailing equal sign if it's missing
+    if (key.charAt(key.length-1)!=='=') key+='=';
+
+    return key;
+}
+
+$(document).ready(function() {
+
+    $('select#pasteExpiration').change(function() {
+        if ($(this).val()=='burn') { $('div#opendisc').addClass('buttondisabled'); $('input#opendiscussion').attr('disabled',true); }
+        else { $('div#opendisc').removeClass('buttondisabled'); $('input#opendiscussion').removeAttr('disabled'); }
+    });
+
+
+    if ($('div#cipherdata').text().length>1) // Display an existing paste
+    {
+        if (window.location.hash.length==0) // Missing decryption key in URL ?
+        {
+            showError('Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL ?)');
+            return;
+        }
+        var messages = jQuery.parseJSON($('div#cipherdata').text()); // List of messages to display
+        stateExistingPaste();  // Show proper elements on screen. 
+        displayMessages(pageKey(),messages);
+    }
+    else if ($('div#errormessage').text().length>1) // Display error message from php code.
+    {
+        showError($('div#errormessage').text());
+    }
+    else // Create a new paste.
+    {
+        newPaste();
+    }
+});

+ 81 - 0
tpl/page.html

@@ -0,0 +1,81 @@
+<html>
+<head>
+<title>ZeroBin</title>
+<link type="text/css" rel="stylesheet" href="lib/zerobin.css?{$VERSION|rawurlencode}#" /> 
+<script src="lib/jquery.js#"></script>
+<script src="lib/sjcl.js#"></script>
+<script src="lib/base64.js#"></script>
+<script src="lib/rawdeflate.js#"></script>
+<script src="lib/rawinflate.js#"></script>
+<script src="lib/zerobin.js?{$VERSION|rawurlencode}#"></script>
+
+<!--[if lt IE 10]>
+<style> body {padding-left:60px;padding-right:60px;} div#ienotice {display:block;} </style>
+<![endif]-->
+
+<!--[if lt IE 10]>
+<style> div#ienotice {display:block; }  div#oldienotice {display:block; } </style>
+<![endif]-->
+
+</head>
+  <body>
+      <div id="aboutbox">
+          ZeroBin is a minimalist, opensource online pastebin where the server has zero knowledge of pasted data.
+          Data is encrypted/decrypted <i>in the browser</i> using 256 bits AES. 
+          More information on the <a href="http://sebsauvage.net/wiki/doku.php?id=php:zerobin">project page</a>.<br />
+          <span style="text-decoration:blink;font-size:10pt;color:#a4b3c4;">&#9654;</span> Note: This is a test service: 
+          Data may be deleted anytime. Kittens will die if you abuse this service.
+</div>
+    <h1 title="ZeroBin" onclick="window.location.href=scriptLocation();return false;">ZeroBin</h1><br>
+    <h2>Because ignorance is bliss</h2><br>
+    <h3>{$VERSION}</h3>
+    <noscript><div class="nonworking">Javascript is required for ZeroBin to work.<br>Sorry for the inconvenience.</div></noscript>
+    <div id="oldienotice" class="nonworking">ZeroBin requires a modern browser to work.</div>
+    <div id="ienotice">Still using Internet Explorer ? &nbsp;Do yourself a favor, switch to a modern browser: 
+        <a href="http://www.mozilla.org/firefox/">Firefox</a>, 
+        <a href="http://www.opera.com/">Opera</a>, 
+        <a href="http://www.google.com/chrome">Chrome</a>, 
+        <a href="http://www.apple.com/safari">Safari</a>...
+    </div>
+    <div id="status">&nbsp;</div>
+    <div id="errormessage" style="display:none">{$ERRORMESSAGE|htmlspecialchars}</div>
+    <div id="toolbar">
+    <button id="newbutton" onclick="window.location.href=scriptLocation();return false;" style="display:none;"><img src="lib/icon_new.png#" width="11" height="15" />New</button>
+    <button id="sendbutton" onclick="send_data();return false;" style="display:none;"><img src="lib/icon_send.png#" width="18" height="15" />Send</button>
+    <button id="clonebutton" onclick="clonePaste();return false;" style="display:none;"><img src="lib/icon_clone.png#" width="15" height="17" />Clone</button>
+      <div id="expiration" style="display:none;">Expire: 
+      <select id="pasteExpiration" name="pasteExpiration">
+        <option value="burn">Burn after reading</option>
+        <option value="10min">10 minutes</option>
+        <option value="1hour">1 hour</option>
+        <option value="1day">1 day</option>
+        <option value="1month" selected="selected">1 month</option>
+        <option value="1year">1 year</option>
+        <option value="never">Never</option>
+      </select>
+      </div>
+      <div id="remainingtime" style="display:none;"></div>
+      <div id="language" style="display:none;">
+      <select name="language">
+        <option value="language" selected="selected">Language</option>
+        <option value="C/C++">C/C++</option>
+        <option value="php">php</option>
+        <option value="python">Python</option>
+      </select>
+      </div>
+        <input id="password" value="Optional password..." style="display:none;" />
+        <div id="opendisc" class="button" style="display:none;">
+           <input type="checkbox" id="opendiscussion" name="opendiscussion" />
+           <label for="opendiscussion">Open discussion</label>
+        </div>
+    </div>
+    <div id="pastelink" style="display:none;"></div>
+    <div id="cleartext" style="display:none;"></div>
+    <textarea id="message" name="message" cols="80" rows="25" style="display:none;"></textarea>
+    <div id="discussion" style="display:none;">
+        <h4>Discussion</h4>
+        <div id="comments"></div>
+    </div>
+    <div id="cipherdata" style="display:none;">{$CIPHERDATA}</div>
+  </body>
+</html>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio