hotspotlogin 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,844 @@
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
+
253
+ if ( resp.clientSate === chilliController.stateCodes.AUTH ) {
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
+ }
803
+ return bin;
804
+ }
805
+
806
+ function binl2hex(binarray) {
807
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
808
+ var str = "";
809
+ for (var i = 0; i < binarray.length * 4; i++) {
810
+ str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
811
+ hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF);
812
+ }
813
+ return str;
814
+ }
815
+
816
+ function str2hex ( str ) {
817
+ var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
818
+ var hex = '';
819
+ var val ;
820
+ for ( var i=0 ; i<str.length ; i++) {
821
+ /* TODO: adapt this if chrz=16 */
822
+ val = str.charCodeAt(i);
823
+ hex = hex + hex_tab.charAt( val/16 );
824
+ hex = hex + hex_tab.charAt( val%16 );
825
+ }
826
+ return hex;
827
+ }
828
+
829
+ function hex2binl ( hex ) {
830
+ /* Clean-up hex encoded input string */
831
+ hex = hex.toLowerCase() ;
832
+ hex = hex.replace( / /g , "");
833
+
834
+ var bin =[] ;
835
+
836
+ /* Transfrom to array of integers (binary representation) */
837
+ for ( i=0 ; i < hex.length*4 ; i=i+8 ) {
838
+ octet = parseInt( hex.substr( i/4 , 2) , 16) ;
839
+ bin[i>>5] |= ( octet & 255 ) << (i%32);
840
+ }
841
+ return bin;
842
+ }
843
+
844
+ } // end of ChilliMD5 constructor