Subversion Repositories ALCASAR

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log

/**
 *   ChilliLibrary.js
 *   V2.0
 *
 *   This Javascript library can be used to create HTML/JS browser
 *   based smart clients (BBSM) for the CoovaChilli access controller
 *   Coova Chilli rev 81 or higher is required
 *   
 *   This library creates four global objects :
 *
 *    - chilliController  Expose session/client state and 
 *                        connect()/disconnect() methods the to BBSM.
 *
 *    - chilliJSON        INTERNAL (should not be called from the BBSM).
 *                        Issues a command to the chilli daemon by adding a new <SCRIPT>
 *                        tag to the HTML DOM (this hack enables cross server requests). 
 *                        
 *    - chilliClock       Can be used by BBSMs to display a count down.
 *                        Will sync with chilliController for smooth UI display (not yet implemented)
 *
 *    - chilliLibrary     Expose API and library versions
 *
 *  For more information http://www.coova.org/CoovaChilli/JSON
 *
 *  TODO :
 *   - Fine tune level of debug messages
 *   - Define error code when invoking onError
 *   - Retry mechanism after a JSON request fails
 *   - Delay clock tick when there is already an ongoing request
 *   - Use a true JSON parser to validate what we received
 *   - Use idleTime and idleTimeout to re-schedule autofresh after
 *     a likely idle termination by chilli
 *   - check that the library can be compiled as a Flash swf library
 *     and used from Flash BBSMs with the same API.
 *
 *   Copyright (C) Y.Deltroo 2007
 *   Distributed under the BSD License
 *
 *   This file also contains third party code :
 *   - MD5, distributed under the BSD license
 *     http://pajhome.org.uk/crypt/md5
 *
 */

var chilliLibrary = { revision:'85' , apiVersion:'2.0' } ;


/**
 *   Global chilliController object
 *
 *   CONFIGUARION PROPERTIES
 *   -----------------------
 *    ident (String) 
 *      Hex encoded string (used for client side CHAP-Password calculations) 
 *                
 *    interval (Number)
 *       Poll the gateway every interval, in seconds
 *
 *    host (String)
 *       IP address of the controller (String)
 *
 *    port (Number)
 *        UAM port to direct request to on the gateway
 *
 *    ssl (Boolean)
 *       Shall we use HTTP or HTTPS to communicate with the chilli controller 
 *
 *    uamService : String
 *        !!! EXPERIMENTAL FEATURE !!!
 *        URL to external uamService script (used for external MD5 calculation when portal/chilli trust is required)
 *        This remote script runs on a SSL enable web server, and knows UAM SECRET.
 *        The chilliController javascript object will send the password over SSL (and challenge for CHAP) 
 *        UAM SERVICE should reply with a JSON response containing
 *           - CHAP logon : CHAP-Password X0Red with UAM SECRET
 *           - PAP  logon : Password XORed with UAM SECRET
 *
 *   For more information http://www.coova.org/CoovaChilli/JSON
 *
 */

if (!chilliController || !chilliController.host)
var chilliController = { interval:30 , host:"1.0.0.1" , port:false , ident:'00' , ssl:false , uamService: false };

/* Define clientState numerical code constants  */
chilliController.stateCodes = { UNKNOWN:-1 , NOT_AUTH:0 , AUTH:1 , AUTH_PENDING:2 , AUTH_SPLASH:3 } ;

/* Initializing session and accounting members, objet properties */
chilliController.session     = {} ;
chilliController.accounting  = {} ;
chilliController.redir       = {} ;

chilliController.location   = { name: '' } ;
chilliController.challenge       = '' ;
chilliController.message         = '' ;
chilliController.clientState     = chilliController.stateCodes.UNKNOWN ;
chilliController.command         = '' ;
chilliController.autorefreshTimer = 0  ;

/* This method returns the root URL for commands */
chilliController.urlRoot = function () {
        var protocol = ( chilliController.ssl ) ? "https" : "http" ;
        var urlRoot = protocol + "://" + chilliController.host + (chilliController.port ? ":" + chilliController.port.toString() : "") + "/json/" ;
        return urlRoot;
};

/* Default event handlers */
chilliController.onUpdate = function ( cmd ) {
        log('>> Default onUpdate handler. <<\n>> You should write your own. <<\n>> cmd = ' + cmd + ' <<' );
};

chilliController.onError = function ( str ) {
        log ( '>> Default Error Handler<<\n>> You should write your own  <<\n>> ' + str + ' <<' );
};


chilliController.formatTime = function ( t , zeroReturn ) {

    if ( typeof(t) == 'undefined' ) {
        return "Not available";
    }

    t = parseInt ( t , 10 ) ;
    if ( (typeof (zeroReturn) !='undefined') && ( t === 0 ) ) {
        return zeroReturn;
    }

    var h = Math.floor( t/3600 ) ;
    var m = Math.floor( (t - 3600*h)/60 ) ;
    var s = t % 60  ;

    var s_str = s.toString();
    if (s < 10 ) { s_str = '0' + s_str;   }

    var m_str = m.toString();
    if (m < 10 ) { m_str= '0' + m_str;    }

    var h_str = h.toString();
    if (h < 10 ) { h_str= '0' + h_str;    }


    if      ( t < 60 )   { return s_str + 's' ; }
    else if ( t < 3600 ) { return m_str + 'm' + s_str + 's' ; }
    else                 { return h_str + 'h' + m_str + 'm' + s_str + 's'; }

};

chilliController.formatBytes = function ( b , zeroReturn ) {

    if ( typeof(b) == 'undefined' ) {
        b = 0;
    } else {
        b = parseInt ( b , 10 ) ;
    }

    if ( (typeof (zeroReturn) !='undefined') && ( b === 0 ) ) {
        return zeroReturn;
    }

    var kb = Math.round(b  / 10) / 100;
    if (kb < 1) return b  + ' Bytes';

    var mb = Math.round(kb / 10) / 100;
    if (mb < 1)  return kb + ' Kilobytes';

    var gb = Math.round(mb / 10) / 100;
    if (gb < 1)  return mb + ' Megabytes';

    return gb + ' Gigabytes';
};


/**
 *   Global chilliController object
 *
 *   PUBLIC METHODS
 *   --------------
 *     logon ( username, password ) :
 *           Attempt a CHAP logon with username/password
 *           issues a /logon command to chilli daemon
 *
 *     logon2 ( username, response ) :
 *           Attempt a CHAP logon with username/response
 *           issues a /logon command to chilli daemon
 *
 *     logoff () :
 *           Disconnect the current user by issuing a
 *           /logoff command to the chilli daemon
 *
 *     refresh () :
 *           Issues a /status command to chilli daemon to refresh 
 *           the local chilliController object state/session data
 *
 */
 
chilliController.logon = function ( username , password )  {

        if ( typeof(username) !== 'string') {
                chilliController.onError( 1 , "username missing (or incorrect type)" ) ;
        }
        
        if ( typeof(password) !== 'string') {
                chilliController.onError( 2 , "password missing (or incorrect type)" ) ;
        }

        log ( 'chilliController.logon( "' + username + '" , "' + password + ' " )' );

        chilliController.temp = { 'username': username , 'password': password };
        chilliController.command = 'logon';

        log ('chilliController.logon: asking for a new challenge ' );
        chilliJSON.onError        = chilliController.onError    ;
        chilliJSON.onJSONReady    = chilliController.logonStep2 ;
        chilliController.clientState = chilliController.AUTH_PENDING ; 
        chilliJSON.get( chilliController.urlRoot() + 'status'  ) ;
};

chilliController.logon2 = function ( username , response )  {

        if ( typeof(username) !== 'string') {
                chilliController.onError( 1 , "username missing (or incorrect type)" ) ;
        }
        
        if ( typeof(response) !== 'string') {
                chilliController.onError( 2 , "response missing (or incorrect type)" ) ;
        }

        log ( 'chilliController.logon2( "' + username + '" , "' + response + ' " )' );

        chilliController.temp = { 'username': username , 'response': response };
        chilliController.command = 'logon2';

        log ('chilliController.logon2: asking for a new challenge ' );
        chilliJSON.onError        = chilliController.onError    ;
        chilliJSON.onJSONReady    = chilliController.logonStep2 ;
        chilliController.clientState = chilliController.AUTH_PENDING ; 
        chilliJSON.get( chilliController.urlRoot() + 'status'  ) ;
};


/**
 *   Second part of the logon process invoked after
 *   the just requested challenge has been received
 */
chilliController.logonStep2 = function ( resp ) {

        log('Entering logonStep 2');

        if ( typeof (resp.challenge) != 'string' ) {
                log('logonStep2: cannot find a challenge. Aborting.');
                return chilliController.onError('Cannot get challenge');
        }

        if ( resp.clientSate === chilliController.stateCodes.AUTH ) {
                log('logonStep2: Already connected. Aborting.');
                return chilliController.onError('Already connected.');
        }

        var challenge = resp.challenge;

        var username = chilliController.temp.username ; 
        var password = chilliController.temp.password ;
        var response = chilliController.temp.response ;

        log ('chilliController.logonStep2: Got challenge = ' + challenge );

        if ( chilliController.uamService ) { /* MD5 CHAP will be calculated by uamService */

                log ('chilliController.logonStep2: Logon using uamService (external MD5 CHAP)');

                var c ;
                if ( chilliController.uamService.indexOf('?') === -1 ) { 
                        c = '?' ;
                }
                else {
                        c = '&' ;
                }

                // Build command URL
                var url = chilliController.uamService + c + 'username=' + escape(username) +'&password=' + escape(password) +'&challenge=' + challenge ;

                if (chilliController.queryObj && chilliController.queryObj['userurl'] ) {
                    url += '&userurl='+chilliController.queryObj['userurl'] ;
                }

                // Make uamService request
                chilliJSON.onError     = chilliController.onError     ;
                chilliJSON.onJSONReady = chilliController.logonStep3 ;

                chilliController.clientState = chilliController.AUTH_PENDING ; 
                chilliJSON.get( url ) ;
        }
        else {
                /* TODO: Should check if challenge has expired and possibly get a new one */
                /*       OR always call status first to get a fresh challenge             */

            if (!response || response == '') {
                /* Calculate MD5 CHAP at the client side */
                var myMD5 = new ChilliMD5();
                response = myMD5.chap ( chilliController.ident , password , challenge );
                log ( 'chilliController.logonStep2: Calculating CHAP-Password = ' + response );
            }

                /* Prepare chilliJSON for logon request */
                chilliJSON.onError     = chilliController.onError     ;
                chilliJSON.onJSONReady = chilliController.processReply ;
                chilliController.clientState = chilliController.stateCodes.AUTH_PENDING ; 
        
                /* Build /logon command URL */
                var logonUrl = chilliController.urlRoot() + 'logon?username=' + escape(username) + '&response='  + response;
                if (chilliController.queryObj && chilliController.queryObj['userurl'] ) {
                    logonUrl += '&userurl='+chilliController.queryObj['userurl'] ;
                }
                chilliJSON.get ( logonUrl ) ;
        }

}; 

/**
 *   Third part of the logon process invoked after
 *   getting a uamService response
 */
chilliController.logonStep3 = function ( resp ) {
        log('Entering logonStep 3');

        var username = chilliController.temp.username ; 

        if ( typeof (resp.response) == 'string' ) {
                chilliJSON.onError     = chilliController.onError     ;
                chilliJSON.onJSONReady = chilliController.processReply ;
                chilliController.clientState = chilliController.stateCodes.AUTH_PENDING ; 
        
                /* Build /logon command URL */
                var logonUrl = chilliController.urlRoot() + 'logon?username=' + escape(username) + '&response='  + resp.response;
                if (chilliController.queryObj && chilliController.queryObj['userurl'] ) {
                    logonUrl += '&userurl='+chilliController.queryObj['userurl'] ;
                }
                chilliJSON.get ( logonUrl ) ;
        }
}

chilliController.refresh = function ( ) {

        if ( chilliController.autorefreshTimer ) {
                chilliController.command = 'autorefresh' ;
        }
        else {
                chilliController.command = 'refresh' ;
        }

        chilliJSON.onError     = chilliController.onError        ;
        chilliJSON.onJSONReady = chilliController.processReply   ;
        chilliJSON.get( chilliController.urlRoot() + 'status'  ) ;
};

chilliController.logoff = function () {

        chilliController.command  = 'logoff'                      ;
        chilliJSON.onError        = chilliController.onError      ;
        chilliJSON.onJSONReady    = chilliController.processReply ;
        chilliJSON.get( chilliController.urlRoot() + 'logoff' );
};

/* *
 *
 * This functions does some check/type processing on the JSON resp
 * and updates the corresponding chilliController members
 *
 */
chilliController.processReply = function ( resp ) {

        if ( typeof (resp.message)  == 'string' ) {

                /* The following trick will replace HTML entities with the corresponding
                 * character. This will not work in Flash (no innerHTML) 
                 */

                var fakediv = document.createElement('div');
                fakediv.innerHTML = resp.message ;
                chilliController.message = fakediv.innerHTML  ;
        }

        if ( typeof (resp.challenge) == 'string' ) {
                chilliController.challenge = resp.challenge ;
        }

        if ( typeof ( resp.location ) == 'object' ) {
                chilliController.location =  resp.location ;
        }

        if ( typeof ( resp.accounting ) == 'object' ) {
                chilliController.accounting = resp.accounting ;
        }

        if (  (typeof ( resp.redir ) == 'object') ) {
                chilliController.redir = resp.redir ;
        }               
        
        /* Update the session member only the first time after AUTH */
        if (  (typeof ( resp.session ) == 'object') &&
              ( chilliController.session==null || (
                 ( chilliController.clientState !== chilliController.stateCodes.AUTH  )  &&
                 ( resp.clientState === chilliController.stateCodes.AUTH  )))) {

                chilliController.session = resp.session ;

                if ( resp.session.startTime ) {
                        chilliController.session.startTime = new Date();
                        chilliController.session.startTime.setTime(resp.session.startTime);
                }
        }

        /* Update clientState */
        if (  ( resp.clientState === chilliController.stateCodes.NOT_AUTH     ) ||
              ( resp.clientState === chilliController.stateCodes.AUTH         ) ||
              ( resp.clientState === chilliController.stateCodes.AUTH_SPLASH  ) ||
              ( resp.clientState === chilliController.stateCodes.AUTH_PENDING ) ) {

                chilliController.clientState = resp.clientState ;
        }
        else {
                chilliController.onError("Unknown clientState found in JSON reply");
        }


        /* Launch or stop the autorefresh timer if required */
        if ( chilliController.clientState === chilliController.stateCodes.AUTH  ) {

             if ( !chilliController.autorefreshTimer ) {
                        chilliController.autorefreshTimer = setInterval ('chilliController.refresh()' , 1000*chilliController.interval);
             }
        } 
        else if ( chilliController.clientState  === chilliController.stateCodes.NOT_AUTH ) {
                clearInterval ( chilliController.autorefreshTimer ) ;
                 chilliController.autorefreshTimer = 0 ;
        }

        /* Lastly... call the event handler  */
        log ('chilliController.processReply: Calling onUpdate. clienState = ' + chilliController.clientState);
        chilliController.onUpdate( chilliController.command );
};



/** 
 *  chilliJSON object  
 *
 *  This private objet implements the cross domain hack
 *  If no answer is received before timeout, then an error is raised.
 *
 */

var chilliJSON = { timeout:25000 , timer:0 , node:0 , timestamp:0 };

chilliJSON.expired   = function () {

                if ( chilliJSON.node.text ) {
                        log ('chilliJSON: reply content \n' + chilliJSON.node.text );
                }
                else {
                        log ('chilliJSON: request timed out (or reply is not valid JS)');
                }

                clearInterval ( chilliJSON.timer ) ;
                chilliJSON.timer = 0 ;

                /* remove the <SCRIPT> tag node that we have created */
                if ( typeof (chilliJSON.node) !== 'number' ) {
                        document.getElementsByTagName('head')[0].removeChild ( chilliJSON.node );
                }
                chilliJSON.node = 0;

                /* TODO: Implement some kind of retry mechanism here ... */

                chilliJSON.onError('JSON request timed out (or reply is not valid)');
};

chilliJSON.reply = function  ( raw ) {

                clearInterval ( chilliJSON.timer ) ;
                chilliJSON.timer = 0 ;

                var now = new Date()    ;
                var end = now.getTime() ;
                 
                if ( chilliJSON.timestamp ) {
                        log ( 'chilliJSON: JSON reply received in ' + ( end - chilliJSON.timestamp ) + ' ms\n' + dumpObject(raw) );
                }

                if ( typeof (chilliJSON.node) !== 'number' ) {
                        document.getElementsByTagName('head')[0].removeChild ( chilliJSON.node );
                }
                chilliJSON.node = 0;

                /* TODO: We should parse raw JSON as an extra security measure */
                                
                chilliJSON.onJSONReady( raw ) ;
} ;

chilliJSON.get = function ( gUrl ) {

                if ( typeof(gUrl) == "string" ) {
                        chilliJSON.url = gUrl ;
                }
                else {
                        log ( "chilliJSON:error:Incorrect url passed to chilliJSON.get():" + gUrl );
                        chilliJSON.onError ( "Incorrect url passed to chilliJSON.get() " );
                        return ;
                }

                if ( chilliJSON.timer ) {
                        log('logon:   There is already a request running. Return without launching a new request.');
                        return ;
                }

        
                var scriptElement  = document.createElement('script');
                scriptElement.type = 'text/javascript';

                var c ;
                if ( this.url.indexOf('?') === -1 ) { 
                        c = '?' ;
                }
                else {
                        c = '&' ;
                }

                scriptElement.src = chilliJSON.url + c + 'callback=chilliJSON.reply' ;
                scriptElement.src += '&'+Math.random(); // prevent caching in Safari
                
                /* Adding the node that will trigger the HTTP request to the DOM tree */
                chilliJSON.node = document.getElementsByTagName('head')[0].appendChild(scriptElement);

                /* Using interval instead of timeout to support Flash 5,6,7 */
                chilliJSON.timer     = setInterval ( 'chilliJSON.expired()' , chilliJSON.timeout ) ; 
                var now              = new Date();
                chilliJSON.timestamp = now.getTime() ;

                log ('chilliJSON: getting ' + chilliJSON.url + ' . Waiting for reply ...');

}; // end chilliJSON.get = function ( url )


/** 
 *  chilliClock object  
 *
 *  Can be used by BBSMs to display a count down.
 *
 *  Will sync with chilliController and modulate the delay to call onTick
 *  This will avoid ugly sequence of short updates in the IO
 *  (not yet implemented)
 *
 */

var chilliClock = { isStarted : 0 };

chilliClock.onTick = function () {
        log ("You should define your own onTick() handler on this clock object. Clock value = " + this.value );
};
 
chilliClock.increment = function () {

        chilliClock.value  =  chilliClock.value + 1 ;
        chilliClock.onTick( chilliClock.value ) ;
};

chilliClock.resync = function ( newval ) {
        clearInterval ( chilliClock.isStarted )    ;
        chilliClock.value     = parseInt( newval , 10 ) ;
        chilliClock.isStarted = setInterval ( 'chilliClock.increment()' , 1000 );
};

chilliClock.start = function ( newval ) {

        if ( typeof (newval) !== 'Number' ) {
                chilliClock.resync ( 0 ) ;
        }
        else {
                chilliClock.resync ( newval ) ;
        }
};

chilliClock.stop = function () {
        clearInterval ( chilliClock.isStarted )  ;
        chilliClock.isStarted = 0 ;
};


function getel(e) {
        if (document.getElementById) {
                return document.getElementById(e);
        } else if (document.all){
                return document.all[e];
        }
}

function log( msg , messageLevel ) {
        if (!chilliController.debug) return;
        if ( typeof(trace)=="function") {
                // ActionScript trace
                trace ( msg );
        }
        else if ( typeof(console)=="object") {
                // FireBug console
                console.debug ( msg );
        }

        if ( getel('debugarea') ) {
                var e = getel('debugarea') ;
                e.value = e.value + '\n' + msg;
                e.scrollTop = e.scrollHeight - e.clientHeight;
        }
}

/* Transform an object to a text representation */
function dumpObject ( obj ) {

        var str = '' ;

        for (var key in obj ) {
                str = str + "    " + key + " = " + obj[key] + "\n" ;
                if ( typeof ( obj[key] ) == "object" ) {
                        for ( var key2 in obj[key] ) {
                                str = str + "      " + key2 + " = "  + obj[key][key2] + "\n" ;
                        }
                }
        }

        return str;
}

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 *
 * added by Y.DELTROO
 *   - new functions: chap(), hex2binl() and str2hex() 
 *   - modifications to comply with the jslint test, http://www.jslint.com/
 *
 * Copyright (c) 2007
 * Distributed under the BSD License
 *
 */


function ChilliMD5() {

        var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
        var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
        var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

        this.hex_md5 = function (s){
                return binl2hex(core_md5(str2binl(s), s.length * chrsz));
        };

        this.chap = function ( hex_ident , str_password , hex_chal ) {

                //  Convert everything to hex encoded strings
                var hex_password =  str2hex ( str_password );

                // concatenate hex encoded strings
                var hex   = hex_ident + hex_password + hex_chal;

                // Convert concatenated hex encoded string to its binary representation
                var bin   = hex2binl ( hex ) ;

                // Calculate MD5 on binary representation
                var md5 = core_md5( bin , hex.length * 4 ) ; 

                return binl2hex( md5 );
        };

        function core_md5(x, len) {
          x[len >> 5] |= 0x80 << ((len) % 32);
          x[(((len + 64) >>> 9) << 4) + 14] = len;

          var a =  1732584193;
          var b = -271733879;
          var c = -1732584194;
          var d =  271733878;

          for(var i = 0; i < x.length; i += 16) {
                var olda = a;
                var oldb = b;
                var oldc = c;
                var oldd = d;

                a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
                d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
                c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
                b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
                a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
                d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
                c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
                b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
                a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
                d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
                c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
                b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
                a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
                d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
                c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
                b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

                a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
                d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
                c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
                b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
                a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
                d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
                c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
                b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
                a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
                d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
                c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
                b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
                a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
                d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
                c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
                b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

                a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
                d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
                c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
                b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
                a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
                d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
                c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
                b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
                a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
                d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
                c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
                b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
                a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
                d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
                c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
                b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

                a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
                d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
                c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
                b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
                a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
                d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
                c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
                b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
                a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
                d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
                c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
                b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
                a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
                d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
                c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
                b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

                a = safe_add(a, olda);
                b = safe_add(b, oldb);
                c = safe_add(c, oldc);
                d = safe_add(d, oldd);
          }
          return [ a, b, c, d ];

        }

        function md5_cmn(q, a, b, x, s, t) {
          return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
        }

        function md5_ff(a, b, c, d, x, s, t) {
          return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
        }

        function md5_gg(a, b, c, d, x, s, t) {
          return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
        }

        function md5_hh(a, b, c, d, x, s, t) {
          return md5_cmn(b ^ c ^ d, a, b, x, s, t);
        }

        function md5_ii(a, b, c, d, x, s, t) {
          return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
        }

        function safe_add(x, y) {
          var lsw = (x & 0xFFFF) + (y & 0xFFFF);
          var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
          return (msw << 16) | (lsw & 0xFFFF);
        }
        function bit_rol(num, cnt) {
          return (num << cnt) | (num >>> (32 - cnt));
        }

        function str2binl(str) {
          var bin = [] ;
          var mask = (1 << chrsz) - 1;
          for (var i = 0; i < str.length * chrsz; i += chrsz) {
                bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
          }
          return bin;
        }

        function binl2hex(binarray) {
          var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
          var str = "";
          for (var i = 0; i < binarray.length * 4; i++) {
                str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
                           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
          }
          return str;
        }

        function str2hex ( str ) {
                var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
                var hex = '';
                var val ;
                for ( var i=0 ; i                        /* TODO: adapt this if chrz=16   */
                        val = str.charCodeAt(i);
                        hex = hex + hex_tab.charAt( val/16 );
                        hex = hex + hex_tab.charAt( val%16 );
                }
                return hex;
        }

        function hex2binl ( hex ) {
                /*  Clean-up hex encoded input string */
                hex = hex.toLowerCase() ;
                hex = hex.replace( / /g , "");

                var bin =[] ;

                /* Transfrom to array of integers (binary representation) */ 
                for ( i=0 ; i < hex.length*4   ; i=i+8 )  {
                        octet =  parseInt( hex.substr( i/4 , 2) , 16) ;
                        bin[i>>5] |= ( octet & 255 ) << (i%32);
                }
                return bin;
        }
        
} // end of ChilliMD5 constructor

Generated by GNU Enscript 1.6.6.