Subversion Repositories cryptochat

Rev

Rev 5 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
5 daniel-mar 3
/*
4
 * ViaThinkSoft CryptoChat
5
 * Copyright 2014 - 2018 Daniel Marschall, ViaThinkSoft
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
2 daniel-mar 19
 
20
// -----------------------------------------------------------------------------------------------
21
 
22
// feature: user comes online, user comes offline, user is inactive, user is active, user is typing, get user list
23
// todo: hide password length to avoid screensaves revealing confidential information
24
// feature: split screen (frames), with united document.title ! (sum unread posts of each chat)
25
// ng: smileys
26
// ng: roomname, username encrypt
27
// ng: sha3
28
// ng: maybe it would be usefil if you could see by different colors if something was encrypted using a different password
29
// bug: some browsers cannot mark the last line, otherwise everything will be marked
30
// bug: offline detection does not work...
31
// todo: "reset chat" (counter=0, document='') while pwd change?
32
// feature: verhindern dass man etwas postet ohne ein passwort eingegeben zu haben? -> config
33
// feature: posts durchnumerieren?
34
// bug openpandora: password field on change geht net (cannot repro all the time?) ist !== null ok? (fixed now?)
35
// bug cheery laptop: froze, didn't check new messages (mehrfach. repro? Aurora 24.2.0 Gentoo). eigene kommen nicht an. nach einer weile geht es plötzlich wieder? evtl ist vts request 1 mal fehlgeschlagen. (fixed now with fail callbacks?)
36
// feature: shift+enter = new line without sending?
37
// rp feature: ((->[i] , rp notizen + cors
38
// feature: send files?
39
// feature: dereferer
40
// feature: configuration option to show a drop down box with all chat rooms
41
// todo: always renew cache
42
// todo: warn if config is not correct, e.g. admin password missing
43
// feature: prevent making screenshots? keep clipboard empty?
44
// feature: detect NoScript
45
// todo: onChatHide() does not get called when tab is active (and visible), but browser window hasn't the focus
46
// feature: loginmessage. show version of user. user soll bei jedem ajax-verkehr die version mitsenden, damit der server ihn ablehnen kann wenn die version zu alt ist!. ggf ein re-load forcing (self.reload in same room/user/pwd)?
47
// idea: undo decryption if pwd field changed? delete pwd field and save actual decryption key in VAR array. so no screenloggers can see
48
// feature?: possibility to disable hash? just try to do AES.
49
// feature?: possibility to disable user stats or to prune them.
50
// feature?: possibility to prune chat
51
// feature?: send pwd hashed with refresh() so you can see which key(s) the active users use?
52
// feature?: at "(Logging off)"-Message sending: remove DIRECTLY from stat list
53
// feature: when configuration symbols are not defined, redefine them with default settings
54
// feature: online list - instead of showing 2 same names, just write "x2"
55
 
56
// -----------------------------------------------------------------------------------------------
57
 
58
if (!file_exists(__DIR__ . '/config/config.inc.php')) {
59
        die('ERROR: File <b>config/config.inc.php</b> does not exist. Please create it using <b>config/config.original.inc.php</b>');
60
}
61
require __DIR__ . '/config/config.inc.php';
62
define('PRODUCT_NAME', 'CryptoChat');
63
define('MCC_VER',      trim(file_get_contents(__DIR__ . '/VERSION')));
64
 
65
require __DIR__ . '/' . DEP_DIR_SAJAX       . '/php/sajax.php';
66
require __DIR__ . '/' . DEP_DIR_JSONWRAPPER . '/jsonwrapper.php'; // TODO: make optional. only required if running with PHP 5.1
67
 
68
header('Content-type: text/html; charset=utf-8');
69
 
70
if ((REQUIRE_TLS) && (!is_secure())) {
71
        // TODO: redirect
72
        echo '<h1>Please connect via HTTPS or change <b>REQUIRE_TLS</b> to false.</h1>';
73
        die();
74
}
75
 
76
define('ACTION_LIST_ROOMS',  1);
77
define('ACTION_CREATE_ROOM', 2);
78
 
79
$ses_room  = (isset($_POST['ses_room']))  ? trim($_POST['ses_room'])  : '';
80
$ses_user  = (isset($_POST['ses_user']))  ? trim($_POST['ses_user'])  : '';
81
$admin_pwd = (isset($_POST['admin_pwd'])) ? trim($_POST['admin_pwd']) : '';
82
 
83
$ses_room = trim(strtolower($ses_room));
84
$ses_user = trim($ses_user);
85
// $admin_pwd will be trimmed in adminAuth()
86
 
87
$chat_exists = (!CHAT_MUST_EXIST) || (adminAuth($admin_pwd, ACTION_CREATE_ROOM)) || chat_exists($ses_room);
88
 
89
$is_logged_in = ($ses_room != '') && ($ses_user != '') && $chat_exists;
90
$show_list    = isset($_REQUEST['list_chatrooms']) && (!LIST_CHATS_REQUIRE_PWD || adminAuth($_POST['admin_pwd'], ACTION_LIST_ROOMS));
91
 
92
if ($is_logged_in) {
93
        $chatroom_file = chatroom_file($ses_room);
94
        touch($chatroom_file);
95
}
96
 
97
function adminAuth($password, $action) {
98
        if ($action == ACTION_LIST_ROOMS) {
99
                // if (trim(PWD_LIST_ROOMS) == '') return false;
100
                return (trim($password) == trim(PWD_LIST_ROOMS));
101
        } else if ($action == ACTION_CREATE_ROOM) {
102
                // if (trim(PWD_CREATE_ROOM) == '') return false;
103
                return (trim($password) == trim(PWD_CREATE_ROOM));
104
        } else {
105
                throw new Exception('Unknown action');
106
        }
107
}
108
 
109
function is_secure() {
110
        // https://stackoverflow.com/questions/1175096/how-to-find-out-if-you-are-using-https-without-serverhttps
111
        if(isset($_SERVER['HTTPS'])) {
112
                if ($_SERVER['HTTPS'] == 'on') {
113
                        return true;
114
                }
115
        }
116
        return false;
117
}
118
 
119
function chat_exists($room) {
120
        return file_exists(chatroom_file($room));
121
}
122
 
123
function chatroom_file($room) {
124
        return __DIR__ . '/chats/'.escape_filename($room).'.html';
125
}
126
 
127
function chatroom_userstat_file($room, $unique_id) {
128
        return __DIR__ . '/chats/'.escape_filename($room).'_'.escape_filename($unique_id).'.session';
129
}
130
 
131
function chatroom_userstat_files($room) {
132
        $ses_ids = array();
133
 
134
        $ary = glob(__DIR__ . '/chats/'.escape_filename($room).'_*.session');
135
        foreach ($ary as &$a) {
136
                if (!preg_match('@'.preg_quote(__DIR__ . '/chats/'.escape_filename($room).'_').'(.+)\.session@ismU', $a, $m)) {
137
                        die('Internal error at '.__LINE__);
138
                }
139
                $ses_ids[] = $m[1];
140
        }
141
 
142
        return $ses_ids;
143
}
144
 
145
function write_user_stat($room, $unique_id, $username, $ip) {
146
        $file = chatroom_userstat_file($room, $unique_id);
147
 
148
        $cont  = '';
149
#       $cont .= "ROOM:$room\n";
150
        $cont .= "USERNAME:$username\n";
151
#       $cont .= "UNIQUEID:$unique_id\n";
152
        $cont .= "IP:$ip\n";
153
#       $now=time(); $cont .= "ACTIVE:$now\n";
154
#       $key=...; $cont .= "KEY:$key\n";
155
 
156
        file_put_contents($file, $cont);
157
}
158
 
159
function file_age($file) {
160
        return time()-filemtime($file);
161
}
162
 
163
function get_active_users($room, $max_inactivity=USERSTAT_INACTIVE_SECS) {
164
        $ses_ids = array();
165
 
166
        $all_ses_ids = chatroom_userstat_files($room);
167
        foreach ($all_ses_ids as &$ses_id) {
168
                $file = chatroom_userstat_file($room, $ses_id);
169
                if (file_age($file) <= $max_inactivity) {
170
                        $ses_ids[] = $ses_id;
171
                } else {
172
                        if (DELETE_OLD_USERSTATS) {
173
                                $file = chatroom_userstat_file($room, $ses_id);
174
                                unlink($file);
175
                        }
176
                }
177
        }
178
 
179
        return $ses_ids;
180
}
181
 
182
function read_user_stat($room, $unique_id) {
183
        $res = array();
184
 
185
        $file = chatroom_userstat_file($room, $unique_id);
186
 
187
        if (!file_exists($file)) return false;
188
 
189
        $cont = file($file);
190
        foreach ($cont as &$c) {
191
                $c = trim($c);
192
                if ($c == '') continue;
193
                $nameval = explode(':', $c, 2);
194
                if (count($nameval) < 2) continue;
195
 
196
                $name = $nameval[0];
197
                $val  = $nameval[1];
198
                $res[$name] = $val;
199
        }
200
        $res['ACTIVE']   = filemtime($file);
201
        $res['ROOM']     = $room;
202
        $res['UNIQUEID'] = $unique_id;
203
 
204
        return $res;
205
}
206
 
207
function list_rooms() {
208
        $ary = glob(__DIR__ . '/chats/*.html');
209
        foreach ($ary as &$a) {
210
                $a = basename($a, '.html');
211
        }
212
        return $ary;
213
}
214
 
215
function count_messages($room) {
216
        $room_file = chatroom_file($room);
217
        return count(file($room_file));
218
}
219
 
220
function count_users($room) {
221
        $room_file = chatroom_file($room);
222
        $cont = file_get_contents($room_file);
223
        preg_match_all('@\(user:(.*)\)@ismU', $cont, $m);
6 daniel-mar 224
        $users_lookup = array();
2 daniel-mar 225
        foreach ($m[1] as &$us) {
226
                $users_lookup[$us] = true;
227
        }
228
        return count($users_lookup);
229
}
230
 
231
function escape_filename($str) {
232
        $str = str_replace('/', '_', $str);
233
        return $str;
234
}
235
 
236
/* exported via AJAX */
237
function add_line($id, $room, $user, $msg) {
238
        if (!chat_exists($room)) return;
239
        if ($user == '') return;
240
 
241
        $f = fopen(chatroom_file($room), 'a');
242
        $dt = date('Y-m-d H:i:s');
243
//      $msg = stripslashes($msg);
244
        $remote = $_SERVER['REMOTE_ADDR'];
245
 
246
        // Escape stuff
247
        $user = htmlentities($user, ENT_QUOTES, 'UTF-8');
248
        $msg  = htmlentities($msg,  ENT_QUOTES, 'UTF-8');
249
 
250
        $msgs = explode("\n", $msg);
251
        foreach ($msgs as &$msg) {
252
                fwrite($f, "(date: $dt)(user: $user)$msg<br>\n");
253
        }
254
 
255
        fclose($f);
256
 
257
        return array($id);
258
}
259
 
260
/* exported via AJAX */
261
function refresh($room, $fromline, $username, $unique_id) {
262
        if (!chat_exists($room)) return;
263
        if ($fromline < 0) return;
264
 
265
        $unique_id = trim($unique_id);
266
        if ($unique_id == '') {
267
                # TODO: show error message
268
                return false;
269
        }
270
        write_user_stat($room, $unique_id, $username, $_SERVER['REMOTE_ADDR']);
271
 
272
        $res = '';
273
        $lines = file(chatroom_file($room));
274
        for ($i=$fromline; $i<count($lines); $i++) {
275
                $res .= $lines[$i] . "\n";
276
        }
277
 
278
        $userstats = array();
279
        $ses_ids = get_active_users($room);
280
        foreach ($ses_ids as &$ses_id) {
281
                $userstats[] = read_user_stat($room, $ses_id);
282
        }
283
 
284
        return array(count($lines), $res, $userstats);
285
}
286
 
287
// $sajax_debug_mode = true;
288
$sajax_failure_redirect = 'http://web.archive.org/web/20090915191608/http://sajax.info/sajaxfail.html';
6 daniel-mar 289
sajax_export( /** @phpstan-ignore-line */ // PHPstan thinks that sajax_export() only accepts 0 parameters?!
2 daniel-mar 290
        array('name' => 'add_line', 'method' => 'POST'),
291
        array('name' => 'refresh',  'method' => 'GET')   // TODO: post?
292
);
293
sajax_handle_client_request();
294
 
295
?>
296
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
297
        "http://www.w3.org/TR/html4/loose.dtd">
298
<html>
299
<head>
300
        <META HTTP-EQUIV="content-type" CONTENT="text/html; charset=utf-8">
301
        <title><?php echo htmlentities(PRODUCT_NAME); ?></title>
302
        <style>
303
        body {
304
                background-color:#000011;
305
                color:white;
306
                font-family:Arial;
307
                font-size:13px;
308
        }
309
        body.chatroom {
310
                margin: 0;
311
                background-color:#000011;
312
                color:white;
313
                font-family:Arial;
314
                font-size:13px;
315
        }
316
        thead td {
317
                font-weight:bold;
318
                background-color:#000044;
319
        }
320
        .encrypted {
321
                color:orange;
322
        }
323
        .error {
324
                color:#dd0000;
325
        }
326
        input, textarea {
327
                background-color:#000011;
328
                color:white;
329
        }
330
        #postarea textarea {
331
                width:80%;
332
                background-color:#000011;
333
                color:white;
334
        }
335
        #header {
336
                padding:5px;
337
                position:fixed;
338
                background-color:#000044;
339
                color:white;
340
                width:100%;
341
                height: 50px;
342
        }
343
        #status {
344
                position:fixed;
345
                right:5px;
346
                top:5px;
347
        }
348
        #chatmessages {
349
                padding-bottom:115px;
350
                padding-top: 65px;
351
                padding-left: 5px;
352
                padding-right: 5px;
353
        }
354
        #activeusers {
355
                position:fixed;
356
                top:65px;
357
                /*bottom:115px;*/
358
                right:5px;
359
                /*height:100px;*/
360
                width:150px;
361
                background-color:#222222;
362
                padding:10px;
363
        }
364
        #postarea {
365
                padding:5px;
366
                position: fixed;
367
                bottom:0;
368
                height: 100px;
369
                width: 100%;
370
                background-color:#000044;
371
                color:white;
372
        }
373
        .button {
374
                /* http://www.drweb.de/magazin/browser-cursor-hand/ */
375
                cursor: hand; cursor: pointer;
376
                font-family: Arial;
377
                color:white;
378
                background-color: #000022;
379
                border-color:#000000;
380
                border-style: inset;
381
        }
382
        .button:hover {
383
                color:yellow;
384
        }
385
        label {
386
                /* http://www.drweb.de/magazin/browser-cursor-hand/ */
387
                cursor: hand; cursor: pointer;
388
        }
389
        label:hover {
390
                color:yellow;
391
        }
392
        a:link {
393
                color:#ffdd00;
394
        }
395
        a:visited {
396
                color:darkred;
397
        }
398
        a:active, a:hover {
399
                color:white;
400
        }
401
        .logout a {
402
                color:white;
403
        }
404
        .logout a:hover {
405
                color:orange;
406
        }
407
        .logout {
408
                margin-left: 20px;
409
        }
410
        </style>
411
 
412
<?php
413
if ($is_logged_in) {
414
?>
415
 
416
        <script type="text/javascript" src="<?php echo DEP_DIR_CRYPTOJS; ?>/rollups/sha256.js"></script>
417
        <script type="text/javascript" src="<?php echo DEP_DIR_CRYPTOJS; ?>/rollups/aes.js"></script>
418
        <script type="text/javascript" src="<?php echo DEP_DIR_CRYPTOJS; ?>/rollups/md5.js"></script>
419
 
420
        <script type="text/javascript" src="<?php echo DEP_DIR_SAJAX; ?>/php/json_stringify.js"></script>
421
        <script type="text/javascript" src="<?php echo DEP_DIR_SAJAX; ?>/php/json_parse.js"></script>
422
        <script type="text/javascript" src="<?php echo DEP_DIR_SAJAX; ?>/php/sajax.js"></script>
423
 
424
        <script type="text/javascript" src="<?php echo DEP_DIR_CRC32; ?>/crc32.js"></script>
425
 
426
        <script type="text/javascript">
427
 
428
        <?php
429
        sajax_show_javascript();
430
        ?>
431
 
432
        var product = <?php echo json_encode(PRODUCT_NAME); ?>;
433
        var version = <?php echo json_encode(MCC_VER); ?>;
434
        var room = <?php echo json_encode($ses_room); ?>;
435
        var user = <?php echo json_encode($ses_user); ?>;
436
        var curseq = 0;
437
        var roomSalt = "<?php echo sha1(X_SALT.$ses_room); ?>";
438
        var curVisible = true;
439
        var backgroundSeqCount = 0;
440
        var blink = 0;
441
        var uniqueID = "";
442
 
443
        // --- Useful functions
444
 
445
        function scrollToBottom() {
446
                // http://www.sourcetricks.com/2010/07/javascript-scroll-to-bottom-of-page.html
447
                window.scrollTo(0, document.body.scrollHeight);
448
        }
449
 
450
        function randNum(min, max) {
451
                return Math.floor((Math.random()*max)+min);
452
        }
453
 
454
        // http://stackoverflow.com/questions/1349404/generate-a-string-of-5-random-characters-in-javascript/1349426#1349426
455
        function randomString(len) {
456
                var text = "";
457
                var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
458
 
459
                for( var i=0; i < len; i++ ) {
460
                        text += possible.charAt(Math.floor(Math.random() * possible.length));
461
                }
462
 
463
                return text;
464
        }
465
 
466
        // Array Remove - By John Resig (MIT Licensed)
467
        Array.prototype.remove = function(from, to) {
468
                var rest = this.slice((to || from) + 1 || this.length);
469
                this.length = from < 0 ? this.length + from : from;
470
                return this.push.apply(this, rest);
471
        };
472
 
473
        function sleep(milliseconds) {
474
                // http://www.phpied.com/sleep-in-javascript/
475
                var start = new Date().getTime();
476
                for (var i = 0; i < 1e7; i++) {
477
                        if ((new Date().getTime() - start) > milliseconds) {
478
                                break;
479
                        }
480
                }
481
        }
482
 
483
        function get_html_translation_table(table, quote_style) {
484
                // From: http://phpjs.org/functions
485
                // +   original by: Philip Peterson
486
                // +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
487
                // +   bugfixed by: noname
488
                // +   bugfixed by: Alex
489
                // +   bugfixed by: Marco
490
                // +   bugfixed by: madipta
491
                // +   improved by: KELAN
492
                // +   improved by: Brett Zamir (http://brett-zamir.me)
493
                // +   bugfixed by: Brett Zamir (http://brett-zamir.me)
494
                // +      input by: Frank Forte
495
                // +   bugfixed by: T.Wild
496
                // +      input by: Ratheous
497
                // %          note: It has been decided that we're not going to add global
498
                // %          note: dependencies to php.js, meaning the constants are not
499
                // %          note: real constants, but strings instead. Integers are also supported if someone
500
                // %          note: chooses to create the constants themselves.
501
                // *     example 1: get_html_translation_table('HTML_SPECIALCHARS');
502
                // *     returns 1: {'"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;'}
503
                var entities = {},
504
                        hash_map = {},
505
                        decimal;
506
                var constMappingTable = {},
507
                        constMappingQuoteStyle = {};
508
                var useTable = {},
509
                        useQuoteStyle = {};
510
 
511
                // Translate arguments
512
                constMappingTable[0] = 'HTML_SPECIALCHARS';
513
                constMappingTable[1] = 'HTML_ENTITIES';
514
                constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
515
                constMappingQuoteStyle[2] = 'ENT_COMPAT';
516
                constMappingQuoteStyle[3] = 'ENT_QUOTES';
517
 
518
                useTable = !isNaN(table) ? constMappingTable[table] : table ? table.toUpperCase() : 'HTML_SPECIALCHARS';
519
                useQuoteStyle = !isNaN(quote_style) ? constMappingQuoteStyle[quote_style] : quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT';
520
 
521
                if (useTable !== 'HTML_SPECIALCHARS' && useTable !== 'HTML_ENTITIES') {
522
                        throw new Error("Table: " + useTable + ' not supported');
523
                        // return false;
524
                }
525
 
526
                entities['38'] = '&amp;';
527
                if (useTable === 'HTML_ENTITIES') {
528
                        entities['160'] = '&nbsp;';
529
                        entities['161'] = '&iexcl;';
530
                        entities['162'] = '&cent;';
531
                        entities['163'] = '&pound;';
532
                        entities['164'] = '&curren;';
533
                        entities['165'] = '&yen;';
534
                        entities['166'] = '&brvbar;';
535
                        entities['167'] = '&sect;';
536
                        entities['168'] = '&uml;';
537
                        entities['169'] = '&copy;';
538
                        entities['170'] = '&ordf;';
539
                        entities['171'] = '&laquo;';
540
                        entities['172'] = '&not;';
541
                        entities['173'] = '&shy;';
542
                        entities['174'] = '&reg;';
543
                        entities['175'] = '&macr;';
544
                        entities['176'] = '&deg;';
545
                        entities['177'] = '&plusmn;';
546
                        entities['178'] = '&sup2;';
547
                        entities['179'] = '&sup3;';
548
                        entities['180'] = '&acute;';
549
                        entities['181'] = '&micro;';
550
                        entities['182'] = '&para;';
551
                        entities['183'] = '&middot;';
552
                        entities['184'] = '&cedil;';
553
                        entities['185'] = '&sup1;';
554
                        entities['186'] = '&ordm;';
555
                        entities['187'] = '&raquo;';
556
                        entities['188'] = '&frac14;';
557
                        entities['189'] = '&frac12;';
558
                        entities['190'] = '&frac34;';
559
                        entities['191'] = '&iquest;';
560
                        entities['192'] = '&Agrave;';
561
                        entities['193'] = '&Aacute;';
562
                        entities['194'] = '&Acirc;';
563
                        entities['195'] = '&Atilde;';
564
                        entities['196'] = '&Auml;';
565
                        entities['197'] = '&Aring;';
566
                        entities['198'] = '&AElig;';
567
                        entities['199'] = '&Ccedil;';
568
                        entities['200'] = '&Egrave;';
569
                        entities['201'] = '&Eacute;';
570
                        entities['202'] = '&Ecirc;';
571
                        entities['203'] = '&Euml;';
572
                        entities['204'] = '&Igrave;';
573
                        entities['205'] = '&Iacute;';
574
                        entities['206'] = '&Icirc;';
575
                        entities['207'] = '&Iuml;';
576
                        entities['208'] = '&ETH;';
577
                        entities['209'] = '&Ntilde;';
578
                        entities['210'] = '&Ograve;';
579
                        entities['211'] = '&Oacute;';
580
                        entities['212'] = '&Ocirc;';
581
                        entities['213'] = '&Otilde;';
582
                        entities['214'] = '&Ouml;';
583
                        entities['215'] = '&times;';
584
                        entities['216'] = '&Oslash;';
585
                        entities['217'] = '&Ugrave;';
586
                        entities['218'] = '&Uacute;';
587
                        entities['219'] = '&Ucirc;';
588
                        entities['220'] = '&Uuml;';
589
                        entities['221'] = '&Yacute;';
590
                        entities['222'] = '&THORN;';
591
                        entities['223'] = '&szlig;';
592
                        entities['224'] = '&agrave;';
593
                        entities['225'] = '&aacute;';
594
                        entities['226'] = '&acirc;';
595
                        entities['227'] = '&atilde;';
596
                        entities['228'] = '&auml;';
597
                        entities['229'] = '&aring;';
598
                        entities['230'] = '&aelig;';
599
                        entities['231'] = '&ccedil;';
600
                        entities['232'] = '&egrave;';
601
                        entities['233'] = '&eacute;';
602
                        entities['234'] = '&ecirc;';
603
                        entities['235'] = '&euml;';
604
                        entities['236'] = '&igrave;';
605
                        entities['237'] = '&iacute;';
606
                        entities['238'] = '&icirc;';
607
                        entities['239'] = '&iuml;';
608
                        entities['240'] = '&eth;';
609
                        entities['241'] = '&ntilde;';
610
                        entities['242'] = '&ograve;';
611
                        entities['243'] = '&oacute;';
612
                        entities['244'] = '&ocirc;';
613
                        entities['245'] = '&otilde;';
614
                        entities['246'] = '&ouml;';
615
                        entities['247'] = '&divide;';
616
                        entities['248'] = '&oslash;';
617
                        entities['249'] = '&ugrave;';
618
                        entities['250'] = '&uacute;';
619
                        entities['251'] = '&ucirc;';
620
                        entities['252'] = '&uuml;';
621
                        entities['253'] = '&yacute;';
622
                        entities['254'] = '&thorn;';
623
                        entities['255'] = '&yuml;';
624
                }
625
 
626
                if (useQuoteStyle !== 'ENT_NOQUOTES') {
627
                        entities['34'] = '&quot;';
628
                }
629
                if (useQuoteStyle === 'ENT_QUOTES') {
630
                        entities['39'] = '&#39;';
631
                }
632
                entities['60'] = '&lt;';
633
                entities['62'] = '&gt;';
634
 
635
                // ascii decimals to real symbols
636
                for (decimal in entities) {
637
                        if (entities.hasOwnProperty(decimal)) {
638
                                hash_map[String.fromCharCode(decimal)] = entities[decimal];
639
                        }
640
                }
641
 
642
                return hash_map;
643
        }
644
 
645
        function htmlentities(string, quote_style, charset, double_encode) {
646
                // From: http://phpjs.org/functions
647
                // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
648
                // +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
649
                // +   improved by: nobbler
650
                // +    tweaked by: Jack
651
                // +   bugfixed by: Onno Marsman
652
                // +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
653
                // +    bugfixed by: Brett Zamir (http://brett-zamir.me)
654
                // +      input by: Ratheous
655
                // +   improved by: Rafa. Kukawski (http://blog.kukawski.pl)
656
                // +   improved by: Dj (http://phpjs.org/functions/htmlentities:425#comment_134018)
657
                // -    depends on: get_html_translation_table
658
                // *     example 1: htmlentities('Kevin & van Zonneveld');
659
                // *     returns 1: 'Kevin &amp; van Zonneveld'
660
                // *     example 2: htmlentities("foo'bar","ENT_QUOTES");
661
                // *     returns 2: 'foo&#039;bar'
662
                var hash_map = this.get_html_translation_table('HTML_ENTITIES', quote_style),
663
                        symbol = '';
664
                string = string == null ? '' : string + '';
665
 
666
                if (!hash_map) {
667
                        return false;
668
                }
669
 
670
                if (quote_style && quote_style === 'ENT_QUOTES') {
671
                        hash_map["'"] = '&#039;';
672
                }
673
 
674
                if (!!double_encode || double_encode == null) {         // TODO: korrekt "== null"? nicht "!= null"?
675
                        for (symbol in hash_map) {
676
                                if (hash_map.hasOwnProperty(symbol)) {
677
                                        string = string.split(symbol).join(hash_map[symbol]);
678
                                }
679
                        }
680
                } else {
681
                        string = string.replace(/([\s\S]*?)(&(?:#\d+|#x[\da-f]+|[a-zA-Z][\da-z]*);|$)/g, function (ignore, text, entity) {
682
                                for (symbol in hash_map) {
683
                                        if (hash_map.hasOwnProperty(symbol)) {
684
                                                text = text.split(symbol).join(hash_map[symbol]);
685
                                        }
686
                                }
687
 
688
                                return text + entity;
689
                        });
690
                }
691
 
692
                return string;
693
        }
694
 
695
        /* accepts parameters
696
         * h  Object = {h:x, s:y, v:z}
697
         * OR
698
         * h, s, v
699
         * This code expects 0 <= h, s, v <= 1
700
         * http://stackoverflow.com/a/17243070
701
        */
702
        function HSVtoRGB(h, s, v) {
703
                var r, g, b, i, f, p, q, t;
704
                if (h && s === undefined && v === undefined) {
705
                        s = h.s, v = h.v, h = h.h;
706
                }
707
                i = Math.floor(h * 6);
708
                f = h * 6 - i;
709
                p = v * (1 - s);
710
                q = v * (1 - f * s);
711
                t = v * (1 - (1 - f) * s);
712
                switch (i % 6) {
713
                        case 0: r = v, g = t, b = p; break;
714
                        case 1: r = q, g = v, b = p; break;
715
                        case 2: r = p, g = v, b = t; break;
716
                        case 3: r = p, g = q, b = v; break;
717
                        case 4: r = t, g = p, b = v; break;
718
                        case 5: r = v, g = p, b = q; break;
719
                }
720
                return {
721
                        r: Math.floor(r * 255),
722
                        g: Math.floor(g * 255),
723
                        b: Math.floor(b * 255)
724
                };
725
        }
726
 
727
        // Returns something between 0..255
728
        function crc8(message) {
729
                // return parseInt(CryptoJS.SHA256(message).toString().substr(0,2), 16);
730
                // return parseInt(CryptoJS.MD5(message).toString().substr(0,2), 16);
731
                return crc32(message)%256;
732
        }
733
 
734
        // http://stackoverflow.com/a/2998822
735
        function pad(num, size) {
736
                var s = num+"";
737
                while (s.length < size) s = "0" + s;
738
                return s;
739
        }
740
 
741
        function html_rgb(r, g, b) {
742
                return "#" + pad(r.toString(16), 2) + pad(g.toString(16), 2) + pad(b.toString(16), 2);
743
 
744
        }
745
 
746
        function getToken() {
747
                return new Date().getTime() + randNum(0, 999999);
748
        }
749
 
750
        function replaceURLWithHTMLLinks(text) {
751
                // TODO: deferer?
752
 
753
                // test@example.com
754
                text = text.replace(/mailto:(.+?)@/ig, "mailto:$1#");
755
                text = text.replace(/(([^>" ]+?)@([^<" ]+))/ig, "<a href=\"mailto:$1\" target=\"_blank\">$1</a>");
756
                text = text.replace(/mailto:(.+?)#/ig, "mailto:$1@");
757
 
758
                // www.example.com
759
                text = text.replace(/:\/\/www\./ig, "://###.");
760
                text = text.replace(/\b(www\.(.+?))\b/ig, "http://$1");
761
                text = text.replace(/:\/\/###\./ig, "://www.");
762
 
763
                // http://www.google.com
764
                // https://stackoverflow.com/questions/37684/how-to-replace-plain-urls-with-links
765
                var exp = /(\b(https?|ftp|file|mailto):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
766
                text = text.replace(/href="(.{1,5}?):/ig, "href=\"$1#");
767
                text = text.replace(exp, "<a href=\"$1\" target=\"_blank\">$1</a>");
768
                text = text.replace(/href="(.{1,5}?)#/ig, "href=\"$1:");
769
 
770
                return text;
771
        }
772
 
773
        function luminance_perceived(r, g, b) {
774
                // Luminance (standard, objective)
775
                // http://stackoverflow.com/a/596243
776
                // return (0.2126*r) + (0.7152*g) + (0.0722*b);
777
 
778
                // Luminance (perceived option 1)
779
                // http://stackoverflow.com/a/596243
780
                // return (0.299*r) + (0.587*g) + (0.114*b);
781
 
782
                // Luminance (perceived option 2, slower to calculate)
783
                // http://alienryderflex.com/hsp.html
784
                // return Math.sqrt(0.299*Math.pow(r,2) + 0.587*Math.pow(g,2) + 0.114*Math.pow(b,2))
785
 
786
                // TODO: Is this the correct formula?
787
                return Math.sqrt(0.241*Math.pow(r,2) + 0.691*Math.pow(g,2) + 0.068*Math.pow(b,2))
788
        }
789
 
790
        function colorizeUsername(username, salt) {
791
                if (salt === undefined) salt = "";
792
 
793
                // return "#" + CryptoJS.SHA256(username).toString().substr(0,6);
794
 
795
                // TODO: is there any method obtaining the background color in runtime? instead of using THEME_DARK
796
 
797
                var x = crc8(username+salt);
798
                var rgb = HSVtoRGB(x/255, 1, 0.75);
799
 
800
                var bg_lum = <?php if (THEME_DARK) echo 0 /* assume #000011 */; else echo 255 /* assume white */; ?>;
801
                var fg_lum = luminance_perceived(rgb.r, rgb.g, rgb.b);
802
 
803
                var lum_dif = Math.floor(Math.abs(bg_lum-fg_lum));
804
 
805
                if (lum_dif < 128) {
806
                        <?php if (THEME_DARK) { ?>
807
                        rgb.r = (rgb.r + (128-lum_dif)); if (rgb.r > 255) rgb.r = 255;
808
                        rgb.g = (rgb.g + (128-lum_dif)); if (rgb.g > 255) rgb.g = 255;
809
                        rgb.b = (rgb.b + (128-lum_dif)); if (rgb.b > 255) rgb.b = 255;
810
                        <?php } else { ?>
811
                        rgb.r = (rgb.r - (128-lum_dif)); if (rgb.r < 0) rgb.r = 0;
812
                        rgb.g = (rgb.g - (128-lum_dif)); if (rgb.g < 0) rgb.g = 0;
813
                        rgb.b = (rgb.b - (128-lum_dif)); if (rgb.b < 0) rgb.b = 0;
814
                        <?php } ?>
815
                }
816
 
817
                return html_rgb(rgb.r, rgb.g, rgb.b);
818
        }
819
 
820
        // --- Getter and setter
821
 
822
        function encryptionEnabled() {
823
                return document.getElementById("doencrypt").checked == 1;
824
        }
825
 
826
        function getPassword() {
827
                return document.getElementById("pwd").value.trim();
828
        }
829
 
830
        function autoscrollEnabled() {
831
                return document.getElementById("autoscroll").checked == 1;
832
        }
833
 
834
        // --- MD5
835
 
836
        var selftest_md5_finished = false;
837
        function selftest_md5() {
838
                if (selftest_md5_finished) return;
839
                selftest_md5_finished = true;
840
 
841
                var errors = "";
842
                cmp_a = CryptoJS.enc.Base64.stringify(CryptoJS.MD5("Message"));
843
                cmp_b = "TCqP5+ryRyHMep8BdRFb1A==";
844
                if (cmp_a !== cmp_b) {
845
                        errors += "MD5 self test failed!\n";
846
                        console.error("MD5 self test failed: '" + cmp_a + "' vs. '" + cmp_b + "'");
847
                }
848
 
849
                if (errors) {
850
                        alert(errors+"\nYour browser seems to be buggy. Decryption of particular messages might fail.");
851
                } else {
852
                        console.info("MD5 self test passed");
853
                }
854
        }
855
 
856
        function md5(message) {
857
                selftest_md5();
858
                var hash = CryptoJS.MD5(message);
859
                hash = CryptoJS.enc.Base64.stringify(hash);
860
                return hash;
861
        }
862
 
863
        // --- SHA256
864
 
865
        var selftest_sha256_finished = false;
866
        function selftest_sha256() {
867
                if (selftest_sha256_finished) return;
868
                selftest_sha256_finished = true;
869
 
870
                var errors = "";
871
                cmp_a = CryptoJS.enc.Base64.stringify(CryptoJS.SHA256("Message"));
872
                cmp_b = "L3dmip37+NWEi57rSnFFypTG7ZI25Kdz9tyvpRMrL5E=";
873
                if (cmp_a !== cmp_b) {
874
                        errors += "SHA256 self test failed!\n";
875
                        console.error("SHA256 self test failed: '" + cmp_a + "' vs. '" + cmp_b + "'");
876
                }
877
 
878
                if (errors) {
879
                        alert(errors+"\nYour browser seems to be buggy. Decryption of particular messages might fail.");
880
                } else {
881
                        console.info("SHA256 self test passed");
882
                }
883
        }
884
 
885
        function sha256(message) {
886
                selftest_sha256();
887
                var hash = CryptoJS.SHA256(message);
888
                hash = CryptoJS.enc.Base64.stringify(hash);
889
                return hash;
890
        }
891
 
892
        // --- AES
893
 
894
        var selftest_aes_finished = false;
895
        function selftest_aes() {
896
                if (selftest_aes_finished) return;
897
                selftest_aes_finished = true;
898
 
899
                var errors = "";
900
                var cmp_a = CryptoJS.AES.decrypt("U2FsdGVkX19kJJkA0NL7WJRdXKrdqDcf6A2yDODaL2g=", "Secret Passphrase").toString(CryptoJS.enc.Utf8);
901
                var cmp_b = "Message";
902
                if ((cmp_a !== cmp_b) || (aes_dec(aes_enc("Message")) !== "Message")) {
903
                        errors += "AES self test failed!\n";
904
                        console.error("AES self test failed: '" + cmp_a + "' vs. '" + cmp_b + "'");
905
                }
906
 
907
                if (errors) {
908
                        alert(errors+"\nYour browser seems to be buggy. Decryption of particular messages might fail.");
909
                } else {
910
                        console.info("AES self test passed");
911
                }
912
        }
913
 
914
        function aes_enc(msg, ver) {
915
                // ver is currently not used
916
                selftest_aes();
917
                var passwd = getPassword();
918
                return CryptoJS.AES.encrypt(msg, roomSalt+passwd);
919
        }
920
 
921
        function aes_dec(msg, ver) {
922
                // ver is currently not used
923
                selftest_aes();
924
                var passwd = getPassword();
925
                try {
926
                        return CryptoJS.AES.decrypt(msg, roomSalt+passwd).toString(CryptoJS.enc.Utf8);
927
                } catch (e) {
928
                        return null;
929
                }
930
        }
931
 
932
        // --- Decrypting stuff
933
 
934
        // Alternative hash digest for compatibility issues
935
        // Some browsers like Jumanji have problems using SHA1 or SHA2, but work with MD5, e.g.
936
        // Jumanji 1.1.2.4 (versionsangabe nicht sicher) ist inkompatibel mit CryptoJS 3.1.2
937
        // UA = "Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/534.26+ (KHTML, like Gecko) Version/5.0 Safari/534.26+ jumanji/0.0"
938
        // CryptoJS.MD5("Message"):
939
        // - Normal:  4c2a8fe7eaf24721cc7a9f0175115bd4
940
        // - Jumanji: 4c2a8fe7eaf24721cc7a9f0175115bd4 (OK)
941
        // CryptoJS.SHA1("Message"):
942
        // - Normal:  68f4145fee7dde76afceb910165924ad14cf0d00
943
        // - Jumanji: 5a5aa74ecae1d696900b034d5f1b71497c170ea0 (Error)
944
        // CryptoJS.SHA256("Message"):
945
        // - Normal:  2f77668a9dfbf8d5848b9eeb4a7145ca94c6ed9236e4a773f6dcafa5132b2f91
946
        // - Jumanji: 5a28f8b8778c15a166f7f17ebb89ce8e8381fbb5e39ddc2511239793119a649e (Error)
947
        // CryptoJS.AES.encrypt("Message", "Secret Passphrase"):
948
        // - Normal:  U2FsdGVkX19zlzNcfljComkcU0A7XfZ+gzZbI+GyFm0=
949
        // - Jumanji: U2FsdGVkX19kJJkA0NL7WJRdXKrdqDcf6A2yDODaL2g= (OK)
950
        // This is a fast version (4x MD5) necessary for slow computers with ARM architecture
951
        function specialMD5fast4(message) {
952
                var a = md5("IuErOmVyPeL2ek6e16vkjTWjssgLmd" + message);
953
                var b = md5("8wxdm3mVi8UQXdboJvCctYwm8ZxTyX" + message);
954
                return md5(a+b) + md5(b+a);
955
        }
956
 
957
        function specialHash(val, entrySalt, version) {
958
                var hash = null;
959
                if (version == 1) {
960
                        hash = sha256(roomSalt+entrySalt+val);
961
                } else if (version == 2) {
962
                        hash = specialMD5fast4(roomSalt+entrySalt+val);
963
                } else {
964
                        console.error("Version " + version + " is unknown at specialHash()");
965
                        return null;
966
                }
967
                return entrySalt + "_" + hash + "_ver" + version;
968
        }
969
 
970
        function cbDecrypt(str, pEntrySalt, pHash, pVer, pMesg, offset, s) {
971
                var passwd = getPassword();
972
 
973
                var hash_client = specialHash(passwd, pEntrySalt, pVer);
974
                var hash_server = pEntrySalt + "_" + pHash + "_ver" + pVer;
975
                if (hash_client == hash_server) {
976
                        var msg = aes_dec(pMesg, pVer);
977
                        msg = htmlentities(msg, "ENT_NOQUOTES", "UTF-8");
978
                        return "<span class=\"encrypted\">" + msg + "</span>";
979
                } else {
980
                        // TODO: maybe we can still make the string invisible or something, so it becomes more user comfortable?
981
                        // TODO: maybe we can still save the decode-information so we can revert decryption as soon as the password is changed again?
982
                        return str; // can be encrypted later
983
                }
984
        }
985
 
986
        function cbUsername(str, pUsername, offset, s) {
987
                var color = colorizeUsername(pUsername);
988
 
989
                return "<font color=\"" + color + "\"><b>[" + pUsername + "]</b></font>: ";
990
        }
991
 
992
        function cbDate(str, pDateTime, offset, s) {
993
                return pDateTime + " - ";
994
        }
995
 
996
        var formatAndDecryptIsRunning = false; // to prevent that two instances of formatAndDecrypt() run simultaneously
997
        function formatAndDecrypt() {
998
                // Set the mutex and write a "Decrypting..." message
999
                //while (formatAndDecryptIsRunning);
1000
                if (formatAndDecryptIsRunning) return; // If there is already a decrypt process, we will exit instead of waiting (because we don't need 2 subsequent decryptions)
1001
                formatAndDecryptIsRunning = true;
1002
                changeStatusLabel(); // Add "Decrypting..." message
1003
                try {
1004
                        var message = document.getElementById("chatmessages").innerHTML;
1005
 
1006
                        // TODO: man darf kein ")" im benutzernamen haben!!!!
1007
 
1008
                        // first decode
1009
                        message = message.replace(/\(dec_(.*?)_(.+?)_ver(.+?): (.+?)\)/g, cbDecrypt);
1010
 
1011
                        // then do formating
1012
                        message = message.replace(/\(date: (.+?)\)/g, cbDate);
1013
                        message = message.replace(/\(user: (.+?)\)/g, cbUsername);
1014
 
1015
                        // make links clickable
1016
                        message = replaceURLWithHTMLLinks(message);
1017
 
1018
                        // Only refresh if something has been changed. otherwise we could not copy-paste when it is permanently refreshing
1019
                        if (document.getElementById("chatmessages").innerHTML != message) {
1020
                                document.getElementById("chatmessages").innerHTML = message;
1021
                        }
1022
                } finally {
1023
                        // Release the mutex and remove the "Decrypting..." message
1024
                        formatAndDecryptIsRunning = false;
1025
                        changeStatusLabel(); // Remove "Decrypting..." message
1026
                }
1027
        }
1028
 
1029
        // --- Status label function
1030
 
1031
        function alertUser() {
1032
                // TODO: play sound?
1033
        }
1034
 
1035
        function changeStatusLabel() {
1036
                var status = "";
1037
 
1038
                if (addMessagePendingQueue.length > 0) {
1039
                        status = "Sending (" + addMessagePendingQueue.length + ")...";
1040
                } else if (formatAndDecryptIsRunning) {
1041
                        status = "Decrypting...";
1042
                } else if (refreshFailedTimerSet) {
1043
                        status = "Checking...";
1044
                } else {
1045
                        status = curseq + " posts";
1046
                }
1047
 
1048
                document.getElementById("status").innerHTML = status;
1049
 
1050
                var title = ""; // document.title;
1051
 
1052
                // If tab is not in focus, we might want to alert the user when new posts arrived
1053
                if ((curseq > 0) && (!curVisible) && (backgroundSeqCount != curseq)) {
1054
                        blink = 1 - blink;
1055
                        title = "(" + (curseq-backgroundSeqCount) + ") " + htmlentities(room);
1056
                        if (blink == 1) {
1057
                                title = title.toUpperCase();
1058
                        } else {
1059
                                title = title.toLowerCase();
1060
                        }
1061
                } else {
1062
                        title = htmlentities(room);
1063
                }
1064
                title += " - " + htmlentities(product);
1065
 
1066
                if (document.title != title) document.title = title;
1067
        }
1068
 
1069
        // --- Refresh
1070
 
1071
        var refreshTimer = null;
1072
        var refreshTimerSet = false;
1073
        var refreshFailedTimer = null;
1074
        var refreshFailedTimerSet = false;
1075
 
1076
        function refresh_failed(id) {
1077
                sajax_cancel(id);
1078
 
1079
                // var refreshFailedTimerSet = (typeof refreshFailedTimer !== 'undefined');
1080
                // var refreshFailedTimerSet = (refreshFailedTimer !== null);
1081
                if (refreshFailedTimerSet) {
1082
                        clearTimeout(refreshFailedTimer);
1083
                        refreshFailedTimer = null;
1084
                        refreshFailedTimerSet = false;
1085
                }
1086
 
1087
                // Remove "Checking..." message
1088
                // changeStatusLabel();
1089
 
1090
                // TODO: die meldung kommt viel zu oft, auch nachdem alles wieder geht. warum?
1091
//              alert("Refresh failed. Will try again.");
1092
                refresh();
1093
        }
1094
 
1095
        function refresh_cb(data) {
1096
                // var refreshFailedTimerSet = (typeof refreshFailedTimer !== 'undefined');
1097
                // var refreshFailedTimerSet = (refreshFailedTimer !== null);
1098
                if (refreshFailedTimerSet) {
1099
                        clearTimeout(refreshFailedTimer);
1100
                        refreshFailedTimer = null;
1101
                        refreshFailedTimerSet = false;
1102
                }
1103
 
1104
                var newcurseq = data[0];
1105
                var new_data  = data[1];
1106
                var userstats = data[2];
1107
 
1108
                // User list
1109
                document.getElementById("activeusers").innerHTML = '<b>ACTIVE USERS:</b><br><br>';
1110
                for (var i = 0; i < userstats.length; ++i ) {
1111
                        var username = userstats[i]['USERNAME'];
1112
                        var color    = colorizeUsername(username);
1113
                        var postfix  = (userstats[i]['UNIQUEID'] == uniqueID) ? ' (you)' : '';
1114
                        document.getElementById("activeusers").innerHTML += '<font color="'+color+'"><b>'+htmlentities(username)+'</b></font>'+postfix+'<br>';
1115
                }
1116
 
1117
                var timeout = <?php echo TIMER_1; ?>; // default timeout
1118
                if (newcurseq != curseq) {
1119
                        curseq = newcurseq;
1120
 
1121
                        document.getElementById("chatmessages").innerHTML += new_data;
1122
 
1123
                        formatAndDecrypt();
1124
 
1125
                        if (autoscrollEnabled()) {
1126
                                scrollToBottom();
1127
                        }
1128
 
1129
                        if (!curVisible) {
1130
                                // blink = 0;
1131
                                alertUser();
1132
                        }
1133
 
1134
                        timeout = <?php echo TIMER_2; ?>; // shorter timeout when a new message has just arrived
1135
                }
1136
 
1137
                // Remove "Checking..." message
1138
                changeStatusLabel();
1139
 
1140
                if (!refreshTimerSet) {
1141
                        refreshTimer = setTimeout("refresh()", timeout);
1142
                        refreshTimerSet = true;
1143
                }
1144
        }
1145
 
1146
        function refresh() {
1147
                // Only run the timer once (since we also call refresh() when a new post is added)
1148
                // var refreshTimerSet = (typeof refreshTimer !== 'undefined');
1149
                // var refreshTimerSet = (refreshTimer !== null);
1150
                if (refreshTimerSet) {
1151
                        clearTimeout(refreshTimer);
1152
                        refreshTimer = null;
1153
                        refreshTimerSet = false;
1154
                }
1155
 
1156
                var sajax_id = x_refresh(room, curseq, user, uniqueID, refresh_cb);
1157
 
1158
                // TODO: gibt es eine bessere möglichkeit festzustellen, ob der request gestorben ist? ein negativer callback z.B.?
1159
                if (!refreshFailedTimerSet) {
1160
                        refreshFailedTimer = setTimeout(function() { refresh_failed(sajax_id); }, <?php echo TIMER_DEAD; ?>);
1161
                        refreshFailedTimerSet = true;
1162
                }
1163
 
1164
                // Add "Checking..." message
1165
                changeStatusLabel();
1166
        }
1167
 
1168
        // --- Add
1169
 
1170
        var addMessagePendingQueue = [];
1171
 
1172
        function do_encrypt(msg) {      // NG: "Encrypting" status message
1173
                var msgToServer = "";
1174
 
1175
                var lines = msg.split("\n");
1176
                for (var i = 0; i < lines.length; ++i ) {
1177
                        var line = lines[i];
1178
 
1179
                        // encrypt message
1180
                        if (encryptionEnabled()) {
1181
                                var version   = <?php echo CFG_CIPHERSUITE; ?>;
1182
                                var passwd    = getPassword();
1183
                                var entrySalt = randomString(20);
1184
                                var hash      = specialHash(passwd, entrySalt, version);
1185
                                line = "(dec_" + hash + ": " + aes_enc(line, version) + ")";
1186
                        }
1187
 
1188
                        msgToServer += line + "\n";
1189
                }
1190
 
1191
                return msgToServer.trim();
1192
        }
1193
 
1194
        function add_get_index_of(token) {
1195
                for (var i = 0; i < addMessagePendingQueue.length; ++i ) {
1196
                        if (addMessagePendingQueue[i].token == token) return i;
1197
                }
1198
                return -1;
1199
        }
1200
 
1201
        function add_failed(token) {
1202
                var i = add_get_index_of(token);
1203
                if (i == -1) return;
1204
 
1205
                sajax_cancel(addMessagePendingQueue[i].sajax_id);
1206
 
1207
                var failedMsg = addMessagePendingQueue[i].message;
1208
                addMessagePendingQueue.remove(i);
1209
                changeStatusLabel(); // Remove "Sending..." message
1210
 
1211
                // Nachricht ausgeben.
1212
                // QUE TODO: automatisch neu versuchen?
1213
                // TODO: reihenfolge der nachrichten stimmt nicht.
1214
                alert("Following message could not be sent. Please try again:" + failedMsg);
1215
 
1216
                // Add message back to the input box, so the user can send it again
1217
                if (document.getElementById("line").value.trim() == initMsg.trim()) {
1218
                        // If login message fails, then clear box first before append
1219
                        document.getElementById("line").value = "";
1220
                }
1221
                var newMsgCont = document.getElementById("line").value.trim() + "\n" + failedMsg.trim();
1222
                document.getElementById("line").value = newMsgCont.trim();
1223
        }
1224
 
1225
        function add_cb(data) {
1226
                var token = data[0];
1227
 
1228
                var i = add_get_index_of(token);
1229
                if (i == -1) return;
1230
 
1231
                clearTimeout(addMessagePendingQueue[i].failTimer);
1232
                addMessagePendingQueue.remove(i);
1233
                changeStatusLabel(); // Remove "Sending..." message
1234
 
1235
                // Refresh now, so that the new post quickly appears!
1236
                refresh();
1237
        }
1238
 
1239
        var addIsRunning = false; // to prevent that two instances of add() run simultaneously
1240
        function add() {
1241
                // Set the mutex
1242
                while (addIsRunning);
1243
                addIsRunning = true;
1244
                try {
1245
                        var msg = document.getElementById("line").value.trim();
1246
                        if ((msg == "") || (msg == initMsg.trim())) {
1247
                                return;
1248
                        } else {
1249
                                document.getElementById("line").value = "";
1250
                        }
1251
 
1252
                        send_msg(msg, 1);
1253
 
1254
                        sleep(100); // damit es zu keiner racecondition (= posts in falscher reihenfolge) kommen kann
1255
                } finally {
1256
                        // Release the mutex
1257
                        addIsRunning = false;
1258
                }
1259
        }
1260
 
1261
        function send_msg(msg, encrypt) {
1262
                var msgToServer = msg;
1263
 
1264
                if (encrypt) {
1265
                        msgToServer = do_encrypt(msgToServer);
1266
                }
1267
 
1268
                // We need a token which is sent back by the server, so our succeed-callback can find and remove the entry from the queue.
1269
                // We cannot use the sajax_id generated by SAJAX, because we need to send it to the server.
1270
                var token = getToken();
1271
                var sajax_id = x_add_line(token, room, user, msgToServer, add_cb);
1272
 
1273
                // Backup the text, so we can back it up if it fails
1274
                // TODO: gibt es eine bessere möglichkeit festzustellen, ob der request gestorben ist? ein negativer callback z.B.?
1275
                var t = setTimeout(function() { add_failed(token); }, <?php echo TIMER_DEAD; ?>);
1276
                addMessagePendingQueue.push({
1277
                        "token": token,
1278
                        "sajax_id": sajax_id,
1279
                        "message": msg,
1280
                        "failTimer": t
1281
                } );
1282
                changeStatusLabel(); // Add "Sending..." message
1283
 
1284
                return sajax_id;
1285
        }
1286
 
1287
        // --- Logoff
1288
 
1289
        var logoffEventFired = 0;
1290
        function send_logoff() {
1291
                if (logoffEventFired == 1) return;
1292
                logoffEventFired = 1;
1293
 
1294
                send_msg("(Logging off)", 0);
1295
 
1296
                document.getElementById("pwd").value = "";
1297
 
1298
                // TODO: go back to login form
1299
        }
1300
 
1301
        var logonEventFired = 0;
1302
        function send_logon() {
1303
                if (logonEventFired == 1) return;
1304
                logonEventFired = 1;
1305
 
1306
                send_msg("(Logging in)", 0);
1307
        }
1308
 
1309
        // --- Initialization stuff
1310
 
1311
        var initMsg = "(enter your message here)"; /* const */
1312
 
1313
        function initShowHideHandlers() {
1314
                // https://stackoverflow.com/questions/1060008/is-there-a-way-to-detect-if-a-browser-window-is-not-currently-active
1315
 
1316
                var hidden = "hidden";
1317
 
1318
                // Standards:
1319
                if (hidden in document)
1320
                        document.addEventListener("visibilitychange", onchange);
1321
                else if ((hidden = "mozHidden") in document)
1322
                        document.addEventListener("mozvisibilitychange", onchange);
1323
                else if ((hidden = "webkitHidden") in document)
1324
                        document.addEventListener("webkitvisibilitychange", onchange);
1325
                else if ((hidden = "msHidden") in document)
1326
                        document.addEventListener("msvisibilitychange", onchange);
1327
                // IE 9 and lower:
1328
                else if ('onfocusin' in document)
1329
                        document.onfocusin = document.onfocusout = onchange;
1330
                // All others:
1331
                else
1332
                        window.onpageshow = window.onpagehide = window.onfocus = window.onblur = onchange;
1333
 
1334
                function onchange(evt) {
1335
                        var v = true, h = false,
1336
                        evtMap = {
1337
                                focus:v, focusin:v, pageshow:v, blur:h, focusout:h, pagehide:h
1338
                        };
1339
 
1340
                        evt = evt || window.event;
1341
                        var res;
1342
                        if (evt.type in evtMap) {
1343
                                res = evtMap[evt.type];
1344
                        } else {
1345
                                res = !this[hidden];
1346
                        }
1347
 
1348
                        if (res) {
1349
                                onChatShow();
1350
                        } else {
1351
                                onChatHide();
1352
                        }
1353
                }
1354
        }
1355
 
1356
        function initReturnKeyHandler() {
1357
                var wage = document.getElementById("line");
1358
                wage.addEventListener("keydown", function (e) {
1359
                        if (e.which == 13 || e.keyCode === 13) {  //checks whether the pressed key is "Enter"
1360
                                add();
1361
                        }
1362
                });
1363
                // important for <textarea> otherwise we'll have a #13 in the box after it
1364
                wage.addEventListener("keyup", function (e) {
1365
                        if (e.which == 13 || e.keyCode === 13) {  //checks whether the pressed key is "Enter"
1366
                                document.getElementById("line").value = "";
1367
                        }
1368
                });
1369
        }
1370
 
1371
        function initUnloadHandler() {
1372
                // TODO: does not work when following a link or when clicking "back button"
1373
                window.onbeforeunload = function() {
1374
                        send_logoff();
1375
                }
1376
                window.addEventListener("beforeunload", function(e) {
1377
                        send_logoff();
1378
                }, false);
1379
        }
1380
 
1381
        // Delayed (single run) onkeydown event for the password field
1382
        var delayedKeyChangeTimer = null;
1383
        var delayedKeyChangeTimerSet = false;
1384
        function initKeyChangeHandler() {
1385
                // We use onkeydown instead of onkeypress, otherwise "del" or "ctrl+v" won't work on Chrome
1386
                // See http://help.dottoro.com/ljlwfxum.php
1387
 
1388
                document.getElementById("pwd").onkeydown = function(evt) {
1389
                        var enterPressed = (evt.which == 13 || evt.keyCode === 13);
1390
 
1391
                        // var delayedKeyChangeTimerSet = (typeof delayedKeyChangeTimer !== 'undefined');
1392
                        // var delayedKeyChangeTimerSet = (delayedKeyChangeTimer !== null);
1393
                        if (delayedKeyChangeTimerSet) {
1394
                                clearTimeout(delayedKeyChangeTimer);
1395
                                delayedKeyChangeTimer = null;
1396
                                delayedKeyChangeTimerSet = false;
1397
                        }
1398
 
1399
                        var timeout = enterPressed ? 0 : <?php echo TIMER_KEYCHANGE; ?>;
1400
                        if (!delayedKeyChangeTimerSet) {
1401
                                delayedKeyChangeTimer = setTimeout("formatAndDecrypt()", <?php echo TIMER_KEYCHANGE; ?>);
1402
                                delayedKeyChangeTimerSet = true;
1403
                        }
1404
                };
1405
        }
1406
 
1407
        function initPage() {
1408
                uniqueID=randomString(20);
1409
                // document.getElementById("pwd").value  = "";
1410
                document.getElementById("line").value = initMsg;
1411
                changeStatusLabel();
1412
                initUnloadHandler();
1413
                initReturnKeyHandler();
1414
                initShowHideHandlers();
1415
                initKeyChangeHandler();
1416
                refresh();
1417
                // We do it at last, otherwise we will have the problem that the chat will not be scrolled to the very bottom in the beginning
1418
                send_logon();
1419
        }
1420
 
1421
        // --- Misc event handlers
1422
 
1423
        function onChatShow() {
1424
                curVisible = true;
1425
                changeStatusLabel();
1426
        }
1427
 
1428
        function onChatHide() {
1429
                curVisible = false;
1430
                blink = 1;
1431
                backgroundSeqCount = curseq;
1432
                // changeStatusLabel();
1433
        }
1434
 
1435
        function onFocusChatField() {
1436
                if (document.getElementById("line").value == initMsg) {
1437
                        document.getElementById("line").value = "";
1438
                }
1439
        }
1440
 
1441
        </script>
1442
 
1443
</head>
1444
 
1445
<body class="chatroom" onload="initPage();">
1446
 
1447
<div id="header">
1448
        <font size="+2"><font color="orange"><?php echo htmlentities(PRODUCT_NAME); ?> <font size="-1"><?php
1449
                echo htmlentities(MCC_VER);
1450
        ?></font></font> <span class="userid">[<?php
1451
                echo htmlentities("$ses_user@$ses_room");
1452
        ?>]</span></font><br>
1453
        <span class="pwdenter">Password for client-side encryption of messages: <input type="password" name="pwd" id="pwd" value=""></span>
1454
        <span class="logout"><a href="<?php echo $_SERVER['SCRIPT_NAME']; ?>" onClick="send_logoff();return true;">Back to login form</a></span>
1455
</div>
1456
<div id="status"><em>Loading...</em></div>
1457
<div id="activeusers"></div>
1458
<div id="chatmessages"></div>
1459
<form name="f" action="#" onsubmit="add();return false;">
1460
        <div id="postarea">
1461
                <textarea cols="70" rows="3" name="line" id="line" value="" onfocus="onFocusChatField()"></textarea>
1462
                <input type="button" name="check" value="Post message" onclick="add();return false;" class="button">
1463
                <table width="100%" border="0" cellspacing="0" cellpadding="0">
1464
                        <tr>
1465
                                <td align="left" width="50%"><label><input type="checkbox" id="autoscroll" name="autoscroll" value="yes" checked> Scroll down when new message arrives</label><br></td>
1466
                                <td align="left" width="50%"><label><input type="checkbox" id="doencrypt"  name="doencrypt"  value="yes" checked> Encrypt all messages</label><br></td>
1467
                        </tr>
1468
                </table>
1469
        </div>
1470
</form>
1471
 
1472
</body>
1473
 
1474
<?php
1475
 
1476
} else if ($show_list) {
1477
 
1478
?>
1479
 
1480
</head>
1481
 
1482
<body>
1483
 
1484
<h1><?php echo htmlentities(PRODUCT_NAME); ?> <?php echo htmlentities(MCC_VER); ?></h1>
1485
 
1486
<h2>List chat rooms</h2>
1487
 
1488
<table border="1" cellpadding="4" cellspacing="0">
1489
<thead><tr>
1490
        <td>Chat room</td>
1491
        <td>Messages</td>
1492
        <td>Users</td>
1493
        <td>Last activity</td>
1494
</tr></thead>
1495
<tbody>
1496
        <?php
1497
 
1498
        $rooms = list_rooms();
1499
        foreach ($rooms as &$room) {
1500
                $room_file = chatroom_file($room);
1501
                $messages = count_messages($room);
1502
                $users = count_users($room);
1503
                $last_activity = date('Y-m-d H:i:s o', filemtime($room_file));
1504
                echo '<tr>';
1505
                echo '<td>'.htmlentities($room).'</td>';
1506
                echo '<td>'.htmlentities($messages).'</td>';
1507
                echo '<td>'.htmlentities($users).'</td>';
1508
                echo '<td>'.htmlentities($last_activity).'</td>';
1509
                echo '</tr>';
1510
        }
1511
 
1512
        ?>
1513
</tbody>
1514
</table>
1515
 
1516
<p><a href="<?php echo $_SERVER['SCRIPT_NAME']; ?>">Back to login</a></p>
1517
 
1518
<?php
1519
 
1520
} else {
1521
 
1522
?>
1523
 
1524
</head>
1525
 
1526
<body>
1527
 
1528
<h1><?php echo htmlentities(PRODUCT_NAME); ?> <?php echo htmlentities(MCC_VER); ?></h1>
1529
 
1530
<h2>Login</h2>
1531
 
1532
<form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="POST">
1533
<input type="hidden" name="sent" value="1">
1534
 
1535
<?php
1536
 
1537
if (isset($_POST['sent'])) {
1538
        if ($ses_room == '') {
1539
                echo "<p><span class=\"error\"><b>Error:</b> You must enter a chat room name.</span></p>";
1540
        } elseif (!$chat_exists) {
1541
                echo "<p><span class=\"error\"><b>Error:</b> Chat room <b>".htmlentities($ses_room)."</b> does not exist. Please ask an administrator to create one.</span></p>";
1542
        }
1543
}
1544
 
1545
?>
1546
 
1547
Room<?php if (CHAT_MUST_EXIST) echo " (chat room must exist)"; ?>:<br>
1548
<input type="text" name="ses_room" value="<?php echo $ses_room; ?>"><br><br>
1549
 
1550
<?php
1551
 
1552
if (isset($_POST['sent'])) {
1553
        if ($ses_user == '') {
1554
                echo "<p><span class=\"error\"><b>Error:</b> You must enter an username.</span></p>";
1555
        }
1556
}
1557
 
1558
?>
1559
 
1560
Username (you can freely choose it yourself):<br>
1561
<input type="text" name="ses_user" value="<?php echo $ses_user; ?>"><br><br>
1562
 
1563
<input type="submit" value="Login" class="button">
1564
 
1565
<?php
1566
if (CHAT_MUST_EXIST) {
1567
        if (isset($_POST['sent'])) {
1568
                if (($admin_pwd != '') && (!adminAuth($admin_pwd, ACTION_CREATE_ROOM))) {
1569
                        echo "<p><span class=\"error\"><b>Error:</b> This is not the correct administrator password.</span></p>";
1570
                }
1571
        }
1572
        ?>
1573
        <br><br><br>Optional: Admin-Password for creating a new chat:<br>
1574
        <input type="password" name="admin_pwd" value=""><br><br>
1575
        <?php
1576
}
1577
?>
1578
 
1579
</form>
1580
 
1581
<hr>
1582
 
1583
<h2>List chat rooms</h2>
1584
 
1585
<form action="<?php echo $_SERVER['SCRIPT_NAME']; ?>" method="POST">
1586
<input type="hidden" name="list_chatrooms" value="1">
1587
 
1588
<?php
1589
if (LIST_CHATS_REQUIRE_PWD) {
1590
        if (isset($_POST['list_chatrooms'])) {
1591
                echo "<p><span class=\"error\"><b>Error:</b> Wrong password.</span></p>";
1592
        }
1593
        ?>
1594
        Admin-Password for listing chats:<br>
1595
        <input type="password" name="admin_pwd" value=""><br><br>
1596
        <?php
1597
}
1598
?>
1599
 
1600
<input type="submit" value="List chat rooms" class="button"><br><br>
1601
 
1602
</form>
1603
 
1604
<hr>
1605
 
1606
<p><a href="http://www.viathinksoft.com/redir.php?id=324897" target="_blank"><?php echo htmlentities(PRODUCT_NAME); ?> <?php echo htmlentities(MCC_VER); ?></a> &copy; 2014-2018 <a href="http://www.viathinksoft.com/" target="_blank">ViaThinkSoft</a>.</a></p>
1607
 
1608
</body>
1609
 
1610
<?php
1611
}
1612
?>
1613
 
1614
</html>