Subversion Repositories ALCASAR

Rev

Rev 484 | Details | Compare with Previous | Last modification | View Log

Rev Author Line No. Line
484 stephane 1
/**
2
 *   ChilliLibrary.js
3
 *   V2.0
4
 *
5
 *   This Javascript library can be used to create HTML/JS browser
6
 *   based smart clients (BBSM) for the CoovaChilli access controller
7
 *   Coova Chilli rev 81 or higher is required
8
 *   
9
 *   This library creates four global objects :
10
 *
11
 *    - chilliController  Expose session/client state and 
12
 *                        connect()/disconnect() methods the to BBSM.
13
 *
14
 *    - chilliJSON        INTERNAL (should not be called from the BBSM).
15
 *                        Issues a command to the chilli daemon by adding a new <SCRIPT>
16
 *                        tag to the HTML DOM (this hack enables cross server requests). 
17
 *                        
18
 *    - chilliClock       Can be used by BBSMs to display a count down.
19
 *                        Will sync with chilliController for smooth UI display (not yet implemented)
20
 *
21
 *    - chilliLibrary     Expose API and library versions
22
 *
23
 *  For more information http://www.coova.org/CoovaChilli/JSON
24
 *
25
 *  TODO :
26
 *   - Fine tune level of debug messages
27
 *   - Define error code when invoking onError
28
 *   - Retry mechanism after a JSON request fails
29
 *   - Delay clock tick when there is already an ongoing request
30
 *   - Use a true JSON parser to validate what we received
31
 *   - Use idleTime and idleTimeout to re-schedule autofresh after
32
 *     a likely idle termination by chilli
33
 *   - check that the library can be compiled as a Flash swf library
34
 *     and used from Flash BBSMs with the same API.
35
 *
36
 *   Copyright (C) Y.Deltroo 2007
37
 *   Distributed under the BSD License
38
 *
39
 *   This file also contains third party code :
40
 *   - MD5, distributed under the BSD license
41
 *     http://pajhome.org.uk/crypt/md5
42
 *
43
 */
44
 
45
var chilliLibrary = { revision:'85' , apiVersion:'2.0' } ;
46
 
47
 
48
/**
49
 *   Global chilliController object
50
 *
51
 *   CONFIGUARION PROPERTIES
52
 *   -----------------------
53
 *    ident (String) 
54
 *      Hex encoded string (used for client side CHAP-Password calculations) 
55
 *		  
56
 *    interval (Number)
57
 *       Poll the gateway every interval, in seconds
58
 *
59
 *    host (String)
60
 *       IP address of the controller (String)
61
 *
62
 *    port (Number)
63
 *        UAM port to direct request to on the gateway
64
 *
65
 *    ssl (Boolean)
66
 *       Shall we use HTTP or HTTPS to communicate with the chilli controller 
67
 *
68
 *    uamService : String
69
 *        !!! EXPERIMENTAL FEATURE !!!
70
 *        URL to external uamService script (used for external MD5 calculation when portal/chilli trust is required)
71
 *        This remote script runs on a SSL enable web server, and knows UAM SECRET.
72
 *        The chilliController javascript object will send the password over SSL (and challenge for CHAP) 
73
 *        UAM SERVICE should reply with a JSON response containing
74
 *           - CHAP logon : CHAP-Password X0Red with UAM SECRET
75
 *           - PAP  logon : Password XORed with UAM SECRET
76
 *
77
 *   For more information http://www.coova.org/CoovaChilli/JSON
78
 *
79
 */
80
 
81
if (!chilliController || !chilliController.host)
82
var chilliController = { interval:30 , host:"1.0.0.1" , port:false , ident:'00' , ssl:false , uamService: false };
83
 
84
/* Define clientState numerical code constants  */
85
chilliController.stateCodes = { UNKNOWN:-1 , NOT_AUTH:0 , AUTH:1 , AUTH_PENDING:2 , AUTH_SPLASH:3 } ;
86
 
87
/* Initializing session and accounting members, objet properties */
88
chilliController.session     = {} ;
89
chilliController.accounting  = {} ;
90
chilliController.redir       = {} ;
91
 
92
chilliController.location   = { name: '' } ;
93
chilliController.challenge       = '' ;
94
chilliController.message         = '' ;
95
chilliController.clientState     = chilliController.stateCodes.UNKNOWN ;
96
chilliController.command         = '' ;
97
chilliController.autorefreshTimer = 0  ;
98
 
99
/* This method returns the root URL for commands */
100
chilliController.urlRoot = function () {
101
	var protocol = ( chilliController.ssl ) ? "https" : "http" ;
102
	var urlRoot = protocol + "://" + chilliController.host + (chilliController.port ? ":" + chilliController.port.toString() : "") + "/json/" ;
103
	return urlRoot;
104
};
105
 
106
/* Default event handlers */
107
chilliController.onUpdate = function ( cmd ) {
108
	log('>> Default onUpdate handler. <<\n>> You should write your own. <<\n>> cmd = ' + cmd + ' <<' );
109
};
110
 
111
chilliController.onError = function ( str ) {
112
	log ( '>> Default Error Handler<<\n>> You should write your own  <<\n>> ' + str + ' <<' );
113
};
114
 
115
 
116
chilliController.formatTime = function ( t , zeroReturn ) {
117
 
118
    if ( typeof(t) == 'undefined' ) {
119
	return "Not available";
120
    }
121
 
122
    t = parseInt ( t , 10 ) ;
123
    if ( (typeof (zeroReturn) !='undefined') && ( t === 0 ) ) {
124
	return zeroReturn;
125
    }
126
 
127
    var h = Math.floor( t/3600 ) ;
128
    var m = Math.floor( (t - 3600*h)/60 ) ;
129
    var s = t % 60  ;
130
 
131
    var s_str = s.toString();
132
    if (s < 10 ) { s_str = '0' + s_str;   }
133
 
134
    var m_str = m.toString();
135
    if (m < 10 ) { m_str= '0' + m_str;    }
136
 
137
    var h_str = h.toString();
138
    if (h < 10 ) { h_str= '0' + h_str;    }
139
 
140
 
141
    if      ( t < 60 )   { return s_str + 's' ; }
142
    else if ( t < 3600 ) { return m_str + 'm' + s_str + 's' ; }
143
    else                 { return h_str + 'h' + m_str + 'm' + s_str + 's'; }
144
 
145
};
146
 
147
chilliController.formatBytes = function ( b , zeroReturn ) {
148
 
149
    if ( typeof(b) == 'undefined' ) {
150
        b = 0;
151
    } else {
152
        b = parseInt ( b , 10 ) ;
153
    }
154
 
155
    if ( (typeof (zeroReturn) !='undefined') && ( b === 0 ) ) {
156
	return zeroReturn;
157
    }
158
 
159
    var kb = Math.round(b  / 10) / 100;
160
    if (kb < 1) return b  + ' Bytes';
161
 
162
    var mb = Math.round(kb / 10) / 100;
163
    if (mb < 1)  return kb + ' Kilobytes';
164
 
165
    var gb = Math.round(mb / 10) / 100;
166
    if (gb < 1)  return mb + ' Megabytes';
167
 
168
    return gb + ' Gigabytes';
169
};
170
 
171
 
172
/**
173
 *   Global chilliController object
174
 *
175
 *   PUBLIC METHODS
176
 *   --------------
177
 *     logon ( username, password ) :
178
 *           Attempt a CHAP logon with username/password
179
 *           issues a /logon command to chilli daemon
180
 *
181
 *     logon2 ( username, response ) :
182
 *           Attempt a CHAP logon with username/response
183
 *           issues a /logon command to chilli daemon
184
 *
185
 *     logoff () :
186
 *           Disconnect the current user by issuing a
187
 *           /logoff command to the chilli daemon
188
 *
189
 *     refresh () :
190
 *           Issues a /status command to chilli daemon to refresh 
191
 *           the local chilliController object state/session data
192
 *
193
 */
194
 
195
chilliController.logon = function ( username , password )  {
196
 
197
	if ( typeof(username) !== 'string') {
198
		chilliController.onError( 1 , "username missing (or incorrect type)" ) ;
199
	}
200
 
201
	if ( typeof(password) !== 'string') {
202
		chilliController.onError( 2 , "password missing (or incorrect type)" ) ;
203
	}
204
 
205
	log ( 'chilliController.logon( "' + username + '" , "' + password + ' " )' );
206
 
207
	chilliController.temp = { 'username': username , 'password': password };
208
	chilliController.command = 'logon';
209
 
210
	log ('chilliController.logon: asking for a new challenge ' );
211
	chilliJSON.onError        = chilliController.onError    ;
212
	chilliJSON.onJSONReady    = chilliController.logonStep2 ;
213
	chilliController.clientState = chilliController.AUTH_PENDING ; 
214
	chilliJSON.get( chilliController.urlRoot() + 'status'  ) ;
215
};
216
 
217
chilliController.logon2 = function ( username , response )  {
218
 
219
	if ( typeof(username) !== 'string') {
220
		chilliController.onError( 1 , "username missing (or incorrect type)" ) ;
221
	}
222
 
223
	if ( typeof(response) !== 'string') {
224
		chilliController.onError( 2 , "response missing (or incorrect type)" ) ;
225
	}
226
 
227
	log ( 'chilliController.logon2( "' + username + '" , "' + response + ' " )' );
228
 
229
	chilliController.temp = { 'username': username , 'response': response };
230
	chilliController.command = 'logon2';
231
 
232
	log ('chilliController.logon2: asking for a new challenge ' );
233
	chilliJSON.onError        = chilliController.onError    ;
234
	chilliJSON.onJSONReady    = chilliController.logonStep2 ;
235
	chilliController.clientState = chilliController.AUTH_PENDING ; 
236
	chilliJSON.get( chilliController.urlRoot() + 'status'  ) ;
237
};
238
 
239
 
240
/**
241
 *   Second part of the logon process invoked after
242
 *   the just requested challenge has been received
243
 */
244
chilliController.logonStep2 = function ( resp ) {
245
 
246
	log('Entering logonStep 2');
247
 
248
	if ( typeof (resp.challenge) != 'string' ) {
249
		log('logonStep2: cannot find a challenge. Aborting.');
250
		return chilliController.onError('Cannot get challenge');
251
	}
252
 
2370 tom.houday 253
	if ( resp.clientState === chilliController.stateCodes.AUTH ) {
484 stephane 254
		log('logonStep2: Already connected. Aborting.');
255
		return chilliController.onError('Already connected.');
256
	}
257
 
258
	var challenge = resp.challenge;
259
 
260
	var username = chilliController.temp.username ; 
261
	var password = chilliController.temp.password ;
262
	var response = chilliController.temp.response ;
263
 
264
	log ('chilliController.logonStep2: Got challenge = ' + challenge );
265
 
266
	if ( chilliController.uamService ) { /* MD5 CHAP will be calculated by uamService */
267
 
268
		log ('chilliController.logonStep2: Logon using uamService (external MD5 CHAP)');
269
 
270
		var c ;
271
		if ( chilliController.uamService.indexOf('?') === -1 ) { 
272
			c = '?' ;
273
		}
274
		else {
275
			c = '&' ;
276
		}
277
 
278
		// Build command URL
279
		var url = chilliController.uamService + c + 'username=' + escape(username) +'&password=' + escape(password) +'&challenge=' + challenge ;
280
 
281
		if (chilliController.queryObj && chilliController.queryObj['userurl'] ) {
282
		    url += '&userurl='+chilliController.queryObj['userurl'] ;
283
		}
284
 
285
		// Make uamService request
286
		chilliJSON.onError     = chilliController.onError     ;
287
		chilliJSON.onJSONReady = chilliController.logonStep3 ;
288
 
289
		chilliController.clientState = chilliController.AUTH_PENDING ; 
290
		chilliJSON.get( url ) ;
291
	}
292
	else {
293
		/* TODO: Should check if challenge has expired and possibly get a new one */
294
        	/*       OR always call status first to get a fresh challenge             */
295
 
296
	    if (!response || response == '') {
297
		/* Calculate MD5 CHAP at the client side */
298
		var myMD5 = new ChilliMD5();
299
		response = myMD5.chap ( chilliController.ident , password , challenge );
300
		log ( 'chilliController.logonStep2: Calculating CHAP-Password = ' + response );
301
	    }
302
 
303
		/* Prepare chilliJSON for logon request */
304
		chilliJSON.onError     = chilliController.onError     ;
305
		chilliJSON.onJSONReady = chilliController.processReply ;
306
		chilliController.clientState = chilliController.stateCodes.AUTH_PENDING ; 
307
 
308
		/* Build /logon command URL */
309
		var logonUrl = chilliController.urlRoot() + 'logon?username=' + escape(username) + '&response='  + response;
310
		if (chilliController.queryObj && chilliController.queryObj['userurl'] ) {
311
		    logonUrl += '&userurl='+chilliController.queryObj['userurl'] ;
312
		}
313
		chilliJSON.get ( logonUrl ) ;
314
	}
315
 
316
}; 
317
 
318
/**
319
 *   Third part of the logon process invoked after
320
 *   getting a uamService response
321
 */
322
chilliController.logonStep3 = function ( resp ) {
323
	log('Entering logonStep 3');
324
 
325
	var username = chilliController.temp.username ; 
326
 
327
	if ( typeof (resp.response) == 'string' ) {
328
		chilliJSON.onError     = chilliController.onError     ;
329
		chilliJSON.onJSONReady = chilliController.processReply ;
330
		chilliController.clientState = chilliController.stateCodes.AUTH_PENDING ; 
331
 
332
		/* Build /logon command URL */
333
		var logonUrl = chilliController.urlRoot() + 'logon?username=' + escape(username) + '&response='  + resp.response;
334
		if (chilliController.queryObj && chilliController.queryObj['userurl'] ) {
335
		    logonUrl += '&userurl='+chilliController.queryObj['userurl'] ;
336
		}
337
		chilliJSON.get ( logonUrl ) ;
338
	}
339
}
340
 
341
chilliController.refresh = function ( ) {
342
 
343
	if ( chilliController.autorefreshTimer ) {
344
		chilliController.command = 'autorefresh' ;
345
	}
346
	else {
347
		chilliController.command = 'refresh' ;
348
	}
349
 
350
	chilliJSON.onError     = chilliController.onError        ;
351
	chilliJSON.onJSONReady = chilliController.processReply   ;
352
	chilliJSON.get( chilliController.urlRoot() + 'status'  ) ;
353
};
354
 
355
chilliController.logoff = function () {
356
 
357
	chilliController.command  = 'logoff'                      ;
358
	chilliJSON.onError        = chilliController.onError      ;
359
	chilliJSON.onJSONReady    = chilliController.processReply ;
360
	chilliJSON.get( chilliController.urlRoot() + 'logoff' );
361
};
362
 
363
/* *
364
 *
365
 * This functions does some check/type processing on the JSON resp
366
 * and updates the corresponding chilliController members
367
 *
368
 */
369
chilliController.processReply = function ( resp ) {
370
 
371
	if ( typeof (resp.message)  == 'string' ) {
372
 
373
		/* The following trick will replace HTML entities with the corresponding
374
                 * character. This will not work in Flash (no innerHTML) 
375
                 */
376
 
377
		var fakediv = document.createElement('div');
378
		fakediv.innerHTML = resp.message ;
379
		chilliController.message = fakediv.innerHTML  ;
380
	}
381
 
382
	if ( typeof (resp.challenge) == 'string' ) {
383
		chilliController.challenge = resp.challenge ;
384
	}
385
 
386
	if ( typeof ( resp.location ) == 'object' ) {
387
		chilliController.location =  resp.location ;
388
	}
389
 
390
	if ( typeof ( resp.accounting ) == 'object' ) {
391
		chilliController.accounting = resp.accounting ;
392
	}
393
 
394
	if (  (typeof ( resp.redir ) == 'object') ) {
395
		chilliController.redir = resp.redir ;
396
	}		
397
 
398
	/* Update the session member only the first time after AUTH */
399
	if (  (typeof ( resp.session ) == 'object') &&
400
	      ( chilliController.session==null || (
401
	         ( chilliController.clientState !== chilliController.stateCodes.AUTH  )  &&
402
	         ( resp.clientState === chilliController.stateCodes.AUTH  )))) {
403
 
404
		chilliController.session = resp.session ;
405
 
406
		if ( resp.session.startTime ) {
407
			chilliController.session.startTime = new Date();
408
			chilliController.session.startTime.setTime(resp.session.startTime);
409
		}
410
	}
411
 
412
	/* Update clientState */
413
	if (  ( resp.clientState === chilliController.stateCodes.NOT_AUTH     ) ||
414
              ( resp.clientState === chilliController.stateCodes.AUTH         ) ||
415
              ( resp.clientState === chilliController.stateCodes.AUTH_SPLASH  ) ||
416
	      ( resp.clientState === chilliController.stateCodes.AUTH_PENDING ) ) {
417
 
418
		chilliController.clientState = resp.clientState ;
419
	}
420
	else {
421
		chilliController.onError("Unknown clientState found in JSON reply");
422
	}
423
 
424
 
425
	/* Launch or stop the autorefresh timer if required */
426
	if ( chilliController.clientState === chilliController.stateCodes.AUTH  ) {
427
 
428
             if ( !chilliController.autorefreshTimer ) {
429
			chilliController.autorefreshTimer = setInterval ('chilliController.refresh()' , 1000*chilliController.interval);
430
	     }
431
	} 
432
	else if ( chilliController.clientState  === chilliController.stateCodes.NOT_AUTH ) {
433
		clearInterval ( chilliController.autorefreshTimer ) ;
434
		 chilliController.autorefreshTimer = 0 ;
435
	}
436
 
437
	/* Lastly... call the event handler  */
438
	log ('chilliController.processReply: Calling onUpdate. clienState = ' + chilliController.clientState);
439
	chilliController.onUpdate( chilliController.command );
440
};
441
 
442
 
443
 
444
/** 
445
 *  chilliJSON object  
446
 *
447
 *  This private objet implements the cross domain hack
448
 *  If no answer is received before timeout, then an error is raised.
449
 *
450
 */
451
 
452
var chilliJSON = { timeout:25000 , timer:0 , node:0 , timestamp:0 };
453
 
454
chilliJSON.expired   = function () {
455
 
456
		if ( chilliJSON.node.text ) {
457
			log ('chilliJSON: reply content \n' + chilliJSON.node.text );
458
		}
459
		else {
460
			log ('chilliJSON: request timed out (or reply is not valid JS)');
461
		}
462
 
463
		clearInterval ( chilliJSON.timer ) ;
464
		chilliJSON.timer = 0 ;
465
 
466
		/* remove the <SCRIPT> tag node that we have created */
467
		if ( typeof (chilliJSON.node) !== 'number' ) {
468
			document.getElementsByTagName('head')[0].removeChild ( chilliJSON.node );
469
		}
470
		chilliJSON.node = 0;
471
 
472
		/* TODO: Implement some kind of retry mechanism here ... */
473
 
474
		chilliJSON.onError('JSON request timed out (or reply is not valid)');
475
};
476
 
477
chilliJSON.reply = function  ( raw ) {
478
 
479
		clearInterval ( chilliJSON.timer ) ;
480
		chilliJSON.timer = 0 ;
481
 
482
		var now = new Date()    ;
483
		var end = now.getTime() ;
484
 
485
		if ( chilliJSON.timestamp ) {
486
			log ( 'chilliJSON: JSON reply received in ' + ( end - chilliJSON.timestamp ) + ' ms\n' + dumpObject(raw) );
487
		}
488
 
489
		if ( typeof (chilliJSON.node) !== 'number' ) {
490
			document.getElementsByTagName('head')[0].removeChild ( chilliJSON.node );
491
		}
492
		chilliJSON.node = 0;
493
 
494
         	/* TODO: We should parse raw JSON as an extra security measure */
495
 
496
		chilliJSON.onJSONReady( raw ) ;
497
} ;
498
 
499
chilliJSON.get = function ( gUrl ) {
500
 
501
		if ( typeof(gUrl) == "string" ) {
502
			chilliJSON.url = gUrl ;
503
		}
504
		else {
505
			log ( "chilliJSON:error:Incorrect url passed to chilliJSON.get():" + gUrl );
506
			chilliJSON.onError ( "Incorrect url passed to chilliJSON.get() " );
507
			return ;
508
		}
509
 
510
		if ( chilliJSON.timer ) {
511
			log('logon:   There is already a request running. Return without launching a new request.');
512
			return ;
513
		}
514
 
515
 
516
		var scriptElement  = document.createElement('script');
517
		scriptElement.type = 'text/javascript';
518
 
519
		var c ;
520
		if ( this.url.indexOf('?') === -1 ) { 
521
			c = '?' ;
522
		}
523
		else {
524
			c = '&' ;
525
		}
526
 
527
		scriptElement.src = chilliJSON.url + c + 'callback=chilliJSON.reply' ;
528
		scriptElement.src += '&'+Math.random(); // prevent caching in Safari
529
 
530
		/* Adding the node that will trigger the HTTP request to the DOM tree */
531
		chilliJSON.node = document.getElementsByTagName('head')[0].appendChild(scriptElement);
532
 
533
		/* Using interval instead of timeout to support Flash 5,6,7 */
534
		chilliJSON.timer     = setInterval ( 'chilliJSON.expired()' , chilliJSON.timeout ) ; 
535
		var now              = new Date();
536
		chilliJSON.timestamp = now.getTime() ;
537
 
538
		log ('chilliJSON: getting ' + chilliJSON.url + ' . Waiting for reply ...');
539
 
540
}; // end chilliJSON.get = function ( url )
541
 
542
 
543
/** 
544
 *  chilliClock object  
545
 *
546
 *  Can be used by BBSMs to display a count down.
547
 *
548
 *  Will sync with chilliController and modulate the delay to call onTick
549
 *  This will avoid ugly sequence of short updates in the IO
550
 *  (not yet implemented)
551
 *
552
 */
553
 
554
var chilliClock = { isStarted : 0 };
555
 
556
chilliClock.onTick = function () {
557
	log ("You should define your own onTick() handler on this clock object. Clock value = " + this.value );
558
};
559
 
560
chilliClock.increment = function () {
561
 
562
	chilliClock.value  =  chilliClock.value + 1 ;
563
	chilliClock.onTick( chilliClock.value ) ;
564
};
565
 
566
chilliClock.resync = function ( newval ) {
567
	clearInterval ( chilliClock.isStarted )    ;
568
	chilliClock.value     = parseInt( newval , 10 ) ;
569
	chilliClock.isStarted = setInterval ( 'chilliClock.increment()' , 1000 );
570
};
571
 
572
chilliClock.start = function ( newval ) {
573
 
574
	if ( typeof (newval) !== 'Number' ) {
575
		chilliClock.resync ( 0 ) ;
576
	}
577
	else {
578
		chilliClock.resync ( newval ) ;
579
	}
580
};
581
 
582
chilliClock.stop = function () {
583
	clearInterval ( chilliClock.isStarted )  ;
584
	chilliClock.isStarted = 0 ;
585
};
586
 
587
 
588
function getel(e) {
589
	if (document.getElementById) {
590
		return document.getElementById(e);
591
	} else if (document.all){
592
		return document.all[e];
593
	}
594
}
595
 
596
function log( msg , messageLevel ) {
597
	if (!chilliController.debug) return;
598
	if ( typeof(trace)=="function") {
599
		// ActionScript trace
600
		trace ( msg );
601
	}
602
	else if ( typeof(console)=="object") {
603
		// FireBug console
604
		console.debug ( msg );
605
	}
606
 
607
	if ( getel('debugarea') ) {
608
		var e = getel('debugarea') ;
609
		e.value = e.value + '\n' + msg;
610
		e.scrollTop = e.scrollHeight - e.clientHeight;
611
	}
612
}
613
 
614
/* Transform an object to a text representation */
615
function dumpObject ( obj ) {
616
 
617
	var str = '' ;
618
 
619
	for (var key in obj ) {
620
		str = str + "    " + key + " = " + obj[key] + "\n" ;
621
		if ( typeof ( obj[key] ) == "object" ) {
622
			for ( var key2 in obj[key] ) {
623
				str = str + "      " + key2 + " = "  + obj[key][key2] + "\n" ;
624
			}
625
		}
626
	}
627
 
628
	return str;
629
}
630
 
631
/*
632
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
633
 * Digest Algorithm, as defined in RFC 1321.
634
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
635
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
636
 * Distributed under the BSD License
637
 * See http://pajhome.org.uk/crypt/md5 for more info.
638
 *
639
 * added by Y.DELTROO
640
 *   - new functions: chap(), hex2binl() and str2hex() 
641
 *   - modifications to comply with the jslint test, http://www.jslint.com/
642
 *
643
 * Copyright (c) 2007
644
 * Distributed under the BSD License
645
 *
646
 */
647
 
648
 
649
function ChilliMD5() {
650
 
651
	var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
652
	var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
653
	var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */
654
 
655
	this.hex_md5 = function (s){
656
		return binl2hex(core_md5(str2binl(s), s.length * chrsz));
657
	};
658
 
659
	this.chap = function ( hex_ident , str_password , hex_chal ) {
660
 
661
		//  Convert everything to hex encoded strings
662
		var hex_password =  str2hex ( str_password );
663
 
664
		// concatenate hex encoded strings
665
		var hex   = hex_ident + hex_password + hex_chal;
666
 
667
		// Convert concatenated hex encoded string to its binary representation
668
		var bin   = hex2binl ( hex ) ;
669
 
670
		// Calculate MD5 on binary representation
671
		var md5 = core_md5( bin , hex.length * 4 ) ; 
672
 
673
		return binl2hex( md5 );
674
	};
675
 
676
	function core_md5(x, len) {
677
	  x[len >> 5] |= 0x80 << ((len) % 32);
678
	  x[(((len + 64) >>> 9) << 4) + 14] = len;
679
 
680
	  var a =  1732584193;
681
	  var b = -271733879;
682
	  var c = -1732584194;
683
	  var d =  271733878;
684
 
685
	  for(var i = 0; i < x.length; i += 16) {
686
		var olda = a;
687
		var oldb = b;
688
		var oldc = c;
689
		var oldd = d;
690
 
691
		a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
692
		d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
693
		c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
694
		b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
695
		a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
696
		d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
697
		c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
698
		b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
699
		a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
700
		d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
701
		c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
702
		b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
703
		a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
704
		d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
705
		c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
706
		b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
707
 
708
		a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
709
		d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
710
		c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
711
		b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
712
		a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
713
		d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
714
		c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
715
		b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
716
		a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
717
		d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
718
		c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
719
		b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
720
		a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
721
		d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
722
		c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
723
		b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
724
 
725
		a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
726
		d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
727
		c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
728
		b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
729
		a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
730
		d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
731
		c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
732
		b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
733
		a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
734
		d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
735
		c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
736
		b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
737
		a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
738
		d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
739
		c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
740
		b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
741
 
742
		a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
743
		d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
744
		c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
745
		b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
746
		a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
747
		d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
748
		c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
749
		b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
750
		a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
751
		d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
752
		c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
753
		b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
754
		a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
755
		d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
756
		c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
757
		b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
758
 
759
		a = safe_add(a, olda);
760
		b = safe_add(b, oldb);
761
		c = safe_add(c, oldc);
762
		d = safe_add(d, oldd);
763
	  }
764
	  return [ a, b, c, d ];
765
 
766
	}
767
 
768
	function md5_cmn(q, a, b, x, s, t) {
769
	  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
770
	}
771
 
772
	function md5_ff(a, b, c, d, x, s, t) {
773
	  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
774
	}
775
 
776
	function md5_gg(a, b, c, d, x, s, t) {
777
	  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
778
	}
779
 
780
	function md5_hh(a, b, c, d, x, s, t) {
781
	  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
782
	}
783
 
784
	function md5_ii(a, b, c, d, x, s, t) {
785
	  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
786
	}
787
 
788
	function safe_add(x, y) {
789
	  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
790
	  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
791
	  return (msw << 16) | (lsw & 0xFFFF);
792
	}
793
	function bit_rol(num, cnt) {
794
	  return (num << cnt) | (num >>> (32 - cnt));
795
	}
796
 
797
	function str2binl(str) {
798
	  var bin = [] ;
799
	  var mask = (1 << chrsz) - 1;
800
	  for (var i = 0; i < str.length * chrsz; i += chrsz) {
801
		bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
802
<< (i%32);< (i%32);	  }
803
<< (i%32);< (i%32);	  return bin;
804
<< (i%32);< (i%32);	}
805
 
806
<< (i%32);< (i%32);	function binl2hex(binarray) {
807
<< (i%32);< (i%32);	  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
808
<< (i%32);< (i%32);	  var str = "";
809
<< (i%32);< (i%32);	  for (var i = 0; i < binarray.length * 4; i++) {
810
<< (i%32);< (i%32);< binarray.length * 4; i++) {		str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
811
<< (i%32);< (i%32);< binarray.length * 4; i++) {			   hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
812
<< (i%32);< (i%32);< binarray.length * 4; i++) {	  }
813
<< (i%32);< (i%32);< binarray.length * 4; i++) {	  return str;
814
<< (i%32);< (i%32);< binarray.length * 4; i++) {	}
815
 
816
<< (i%32);< (i%32);< binarray.length * 4; i++) {	function str2hex ( str ) {
817
<< (i%32);< (i%32);< binarray.length * 4; i++) {		var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
818
<< (i%32);< (i%32);< binarray.length * 4; i++) {		var hex = '';
819
<< (i%32);< (i%32);< binarray.length * 4; i++) {		var val ;
820
<< (i%32);< (i%32);< binarray.length * 4; i++) {		for ( var i=0 ; i
821
<< (i%32);< (i%32);< binarray.length * 4; i++) {* TODO: adapt this if chrz=16   */
822
<< (i%32);< (i%32);< binarray.length * 4; i++) {			val = str.charCodeAt(i);
823
<< (i%32);< (i%32);< binarray.length * 4; i++) {			hex = hex + hex_tab.charAt( val/16 );
824
<< (i%32);< (i%32);< binarray.length * 4; i++) {
825
<< (i%32);< (i%32);< binarray.length * 4; i++) {
826
<< (i%32);< (i%32);< binarray.length * 4; i++) {return hex;
827
<< (i%32);< (i%32);< binarray.length * 4; i++) {
828
 
829
<< (i%32);< (i%32);< binarray.length * 4; i++) {function hex2binl ( hex ) {
830
<< (i%32);< (i%32);< binarray.length * 4; i++) {/*  Clean-up hex encoded input string */
831
<< (i%32);< (i%32);< binarray.length * 4; i++) {
832
<< (i%32);< (i%32);< binarray.length * 4; i++) {/ /g , "");
833
 
834
<< (i%32);< (i%32);< binarray.length * 4; i++) {var bin =[] ;
835
 
836
<< (i%32);< (i%32);< binarray.length * 4; i++) {/* Transfrom to array of integers (binary representation) */ 
837
<< (i%32);< (i%32);< binarray.length * 4; i++) {for ( i=0 ; i < hex.length*4   ; i=i+8 )  {
838
<< (i%32);< (i%32);< binarray.length * 4; i++) {parseInt( hex.substr( i/4 , 2) , 16) ;
839
<< (i%32);< (i%32);< binarray.length * 4; i++) {			bin[i>>5] |= ( octet & 255 ) << (i%32);
840
<< (i%32);< (i%32);< binarray.length * 4; i++) {<< (i%32);< (i%32);		}
841
<< (i%32);< (i%32);< binarray.length * 4; i++) {<< (i%32);< (i%32);		return bin;
842
<< (i%32);< (i%32);< binarray.length * 4; i++) {<< (i%32);< (i%32);	}
843
 
844
<< (i%32);< (i%32);< binarray.length * 4; i++) {<< (i%32);< (i%32);} // end of ChilliMD5 constructor