]> bicyclesonthemoon.info Git - ott/bsta/blobdiff - chat.1.pl
don't include empty password in words links
[ott/bsta] / chat.1.pl
index 5449aad43f4b42ad7d69d38f45761bfeace36cfa..90ea637323a58d57d762aa154bc5db3e71bdadc0 100644 (file)
--- a/chat.1.pl
+++ b/chat.1.pl
@@ -5,7 +5,7 @@
 #
 # The coincidence interface
 #
-# Copyright (C) 2016, 2017, 2023  Balthasar Szczepański
+# Copyright (C) 2016, 2017, 2023, 2024  Balthasar Szczepański
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 use strict;
-#use warnings;
+use utf8;
 ###PERL_LIB: use lib /botm/lib/bsta
-use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode);
+# use Encode::Locale ('decode_argv');
+use Encode ('encode', 'decode');
+
+###PERL_LIB: use lib /botm/lib/bsta
+use botm_common (
+       'HTTP_STATUS',
+       'read_header_env',
+       'url_query_decode', 'url_query_encode',
+       'merge_url',
+       'html_entity_encode_dec',
+       'open_encoded',
+       'http_header_status'
+);
+use bsta_lib (
+       'STATE', 'CHAT_STATE', 'CHAT_ACTION',
+       'fail_method', 'fail_content_type',
+       'get_remote_addr', 'get_id', 'get_password',
+       'print_html_start', 'print_html_end',
+       'print_html_head_start', 'print_html_head_end',
+       'print_html_body_start', 'print_html_body_end',
+       'merge_settings',
+       'read_chat', 'write_chat',
+       'read_coincidence', 'read_settings', 'read_state'
+);
 use  File::Copy;
 
 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
 ###PERL_CGI_COIN_PATH:      CGI_COIN_PATH      = /bsta/coin
-###PERL_CGI_CSS_PATH:       CGI_CSS_PATH       = /bsta/bsta.css
-###PERL_CGI_LOGO_PATH:      CGI_LOGO_PATH      = /bsta/botmlogo.png
 
 ###PERL_DATA_CHAT_PATH:     DATA_CHAT_PATH     = /botm/data/bsta/chat
-###PERL_DATA_COIN_PATH:     DATA_COIN_PATH     = /botm/data/bsta/coincidence
 
-###PERL_WEBSITE:            WEBSITE            = 1190.bicyclesonthemoon.info
 ###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
-###PERL_FAVICON_PATH:       FAVICON_PATH       = /img/favicon.png
+
+binmode STDIN,  ':encoding(UTF-8)';
+binmode STDOUT, ':encoding(UTF-8)';
+binmode STDERR, ':encoding(UTF-8)';
+# decode_argv();
 
 my %http;
 my %cgi;
 my %coin;
 my %chat;
+my %settings;
+my %state;
 
 my $time = time();
 srand ($time-$$);
@@ -49,345 +74,378 @@ srand ($time-$$);
 my $method;
 my $IP;
 my $page;
-my $words;
-my $username;
-my $action;
+my $words = '';
+my $username = '';
+my $action = CHAT_ACTION->{'none'};
 my $password;
-my $chatfile;
+my $fh;
 my $state;
-my $passwordOK;
-my @chatlines;
-my $chatstate;
+my $password_ok;
+my @chat_lines;
+my $chat_state;
+my $status;
 my $message;
-my $chatid;
-my $lastid;
+my $chat_id;
+my $last_id;
 
 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
 
 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
-       $method=$1;
+       $method = $1;
 }
-else{
-       exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
+else {
+       exit fail_method($ENV{'REQUEST_METHOD'}, ['GET', 'POST', 'HEAD']);
 }
 
-%http = gethttpheader (\%ENV);
-%cgi = getcgi($ENV{'QUERY_STRING'});
+%http = read_header_env(\%ENV);
+%cgi = url_query_decode($ENV{'QUERY_STRING'});
 
 if ($method eq 'POST') {
        if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
-               my %cgipost=getcgi( <STDIN> );
-               foreach my $ind (keys %cgipost) {
-                       $cgi{$ind}=$cgipost{$ind};
-               }
+               my %cgi_post = url_query_decode( <STDIN> );
+               %cgi = merge_settings(\%cgi, \%cgi_post);
        }
        # multipart not supported
        else{
-               exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
+               exit fail_content_type($method, $http{'content-type'});
        }
 }
 
-if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
-       $page=int($1);
-}
-else {
-       $page=-1;
-}
+$IP       = get_remote_addr();
+$page     = get_id(\%cgi, -1);
+$password = get_password(\%cgi);
 
-if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
-       $IP=$1;
-}
-else {
-       $IP='0.0.0.0';
-}
+%coin      = read_coincidence();
+%settings  = read_settings();
+%state     = read_state();
+
+$password_ok = ($password eq $settings{'password'});
 
 if ($cgi{'words'} ne '') {
-       $words=$cgi{'words'};
+       $words = $cgi{'words'};
 }
-if ($cgi{'username'} ne '') {
-       $username=$cgi{'username'};
+if ($password_ok && ($cgi{'username'} ne '')) {
+       $username = $cgi{'username'};
 }
-if ($cgi{'join'} ne '') {
-       $action=1;
-}
-elsif ($cgi{'leave'} ne '') {
-       $action=2;
-}
-elsif ($cgi{'nopost'} ne '') {
-       $action=3;
-}
-elsif ($cgi{'file'} ne '') {
-       $action=4;
-}
-else {
-       $action=0;
-}
-if ($cgi{'p'} ne '') {
-       $password=$cgi{'p'};
-}
-
-%coin=readdatafile(DATA_COIN_PATH);
-
-if($password eq $coin{'password'}){
-       $passwordOK = 1;
-}
-else{
-       $passwordOK = 0;
-       $username = '';
+foreach my $action_id ('join', 'leave', 'nopost', 'file') {
+       if ($cgi{$action_id} ne '') {
+               $action = CHAT_ACTION->{$action_id};
+               last;
+       }
 }
 
-if($page < 0) {
-       if (open ($chatfile,"+<",DATA_CHAT_PATH)){
-               if (flock($chatfile,2)) {
-                       %chat=readdatafile($chatfile);
+# ongoing chat
+if ($page < 0) {
+       if (open_encoded($fh, "+<", DATA_CHAT_PATH())) {
+               if (flock($fh, 2)) {
+                       %chat = read_chat($fh);
+                       
+                       $chat_state = int($chat{'state'});
+                       $chat_id    = int($chat{'id'});
+                       $last_id    = $chat_id;
                        
-                       $chatstate=int($chat{'state'});
-                       $chatid=int($chat{'id'});
-                       $lastid=$chatid;
+                       if ($method ne 'POST') {
+                               #
+                       }
                        
-                       if($action==0 && $cgi{'words'} ne '') {
-                               if($chatstate < 1 && !$passwordOK) {
+                       elsif (
+                               ($action == CHAT_ACTION->{'none'}) &&
+                               ($words ne '')
+                       ) {
+                               if (($chat_state < CHAT_STATE->{'ready'}) && !$password_ok) {
+                                       $status = HTTP_STATUS->{'forbidden'};
                                        $message = 'Not connected.';
                                }
                                else {
-                                       if ($cgi{'words'} !~ /[\r\n]/) {
-                                               if($username =~ /^[A-Za-z]*$/) {
-                                                       $chat{'content'}=$chat{'content'}.$username.': '.$cgi{'words'}."\n";
-                                                       if($chatstate < 2) {
-                                                               $chat{'state'} = 2;
-                                                               $chatstate = 2;
+                                       if ($words !~ /[\r\n]/) {
+                                               if ($username =~ /^[A-Za-z]*$/) {
+                                                       $chat{'content'} .= $username.': '.$words."\n";
+                                                       if ($chat_state < CHAT_STATE->{'active'}) {
+                                                               $chat_state = CHAT_STATE->{'active'};
+                                                               $chat{'state'} = $chat_state;
                                                        }
-                                                       writedatafile($chatfile,%chat);
+                                                       write_chat($fh, \%chat);
                                                }
                                                else {
-                                                       $message='Invalid username.';
+                                                       $status = HTTP_STATUS->{'bad_request'};
+                                                       $message = 'Invalid username.';
                                                }
                                        }
                                        else {
-                                               $message='Invalid text.';
+                                               $status = HTTP_STATUS->{'bad_request'};
+                                               $message = 'Invalid text.';
                                        }
                                }
                        }
-                       elsif ($action==1) {
-                               if($chatstate > 0 && !$passwordOK) {
+                       
+                       elsif ($action == CHAT_ACTION->{'join'}) {
+                               if (($chat_state > CHAT_STATE->{'disconnected'}) && !$password_ok) {
                                        $message = 'Already connected.';
                                }
                                else {
-                                       if($username =~ /^[A-Za-z]*$/) {
-                                               if ($passwordOK || $cgi{'words'} eq $coin{'server'}) {
-                                                       $chat{'content'}=$chat{'content'}.'join@'.$username.': '.$cgi{'words'}."\n";
-                                                       if($chatstate < 1) {
-                                                               $chat{'state'} = 1;
-                                                               $chatstate = 1;
+                                       if ($username =~ /^[A-Za-z]*$/) {
+                                               if ($password_ok || $words eq $coin{'server'}) {
+                                                       $chat{'content'} .= 'join@'.$username.': '.$words."\n";
+                                                       if ($chat_state < CHAT_STATE->{'ready'}) {
+                                                               $chat_state = CHAT_STATE->{'ready'};
+                                                               $chat{'state'} = $chat_state;
                                                        }
-                                                       writedatafile($chatfile,%chat);
+                                                       write_chat($fh, \%chat);
                                                }
-                                               elsif ($cgi{'words'} eq '') {
-                                                       $message='Server ID missing.';
+                                               elsif ($words eq '') {
+                                                       $status = HTTP_STATUS->{'bad_request'};
+                                                       $message = 'Server ID missing.';
                                                }
-                                               elsif ($cgi{'words'} !~ /^[0-9]+$/) {
-                                                       $message='Invalid server ID.';
+                                               elsif ($words !~ /^[0-9]+$/) {
+                                                       $status = HTTP_STATUS->{'bad_request'};
+                                                       $message = 'Invalid server ID.';
                                                }
                                                else {
-                                                       $message='No active Coincidence server with this ID.';
+                                                       $status = HTTP_STATUS->{'not_found'};
+                                                       $message = 'No active Coincidence server with this ID.';
                                                }
                                        }
                                        else {
+                                               $status = HTTP_STATUS->{'bad_request'};
                                                $message = 'Invalid username.';
                                        }
                                }
                        }
-                       elsif ($action==2) {
-                               if($chatstate < 1 && !$passwordOK) {
+                       
+                       elsif ($action == CHAT_ACTION->{'leave'}) {
+                               if (($chat_state < CHAT_STATE->{'ready'}) && !$password_ok) {
                                        $message = 'Already disconnected.';
                                }
                                else {
-                                       if($username =~ /^[A-Za-z]*$/) {
-                                               $chat{'content'}=$chat{'content'}.'leave@'.$username.': '.$cgi{'words'}."\n";
-                                               if($username ne '') {
-                                                       writedatafile($chatfile,%chat);
-                                               }
-                                               elsif ($chatstate > 1) {
-                                                       writedatafile(DATA_CHAT_PATH.$chatid,%chat);
-                                                       my %newchat;
-                                                       $newchat{'id'}=$chatid+1;
-                                                       $newchat{'state'}=0;
-                                                       $newchat{'content'}='';
-                                                       writedatafile($chatfile,%newchat);
+                                       if ($username =~ /^[A-Za-z]*$/) {
+                                               $chat{'content'} .= 'leave@'.$username.': '.$words."\n";
+                                               if ($username ne '') {
+                                                       write_chat($fh, \%chat);
                                                }
                                                else {
-                                                       my %newchat;
-                                                       $newchat{'id'}=$chatid;
-                                                       $newchat{'state'}=0;
-                                                       $newchat{'content'}='';
-                                                       writedatafile($chatfile,%newchat);
+                                                       my %new_chat;
+                                                       if ($chat_state > 1) {
+                                                               write_chat($chat_id, \%chat);
+                                                               $new_chat{'id'} = $chat_id+1;
+                                                       }
+                                                       else {
+                                                               $new_chat{'id'} = $chat_id;
+                                                       }
+                                                       $new_chat{'state'} = CHAT_STATE->{'disconnected'};
+                                                       $new_chat{'content'} = '';
+                                                       write_chat($fh, \%new_chat);
                                                }
                                        }
                                        else {
+                                               $status = HTTP_STATUS->{'bad_request'};
                                                $message = 'Invalid username.';
                                        }
                                }
                        }
-                       elsif($action==4 && $cgi{'file'} ne '' && $cgi{'words'} ne '' &&  $passwordOK) {
-                               if ($cgi{'words'} !~ /[\r\n]/) {
-                                       if($username =~ /^[A-Za-z]*$/) {
-                                               $chat{'content'}=$chat{'content'}.'file@'.$username.': '.$cgi{'words'}."\n";
-                                               if($chatstate < 2) {
-                                                       $chat{'state'} = 2;
-                                                       $chatstate = 2;
+                       
+                       elsif (
+                               ($action == CHAT_ACTION->{'file'}) &&
+                               ($cgi{'file'} ne '') &&
+                               ($words ne '') && 
+                               $password_ok
+                       ) {
+                               if ($words !~ /[\r\n]/) {
+                                       if ($username =~ /^[A-Za-z]*$/) {
+                                               $chat{'content'} .= 'file@'.$username.': '.$words."\n";
+                                               if ($chat_state < CHAT_STATE->{'active'}) {
+                                                       $chat_state = CHAT_STATE->{'active'};
+                                                       $chat{'state'} = $chat_state;
                                                }
-                                               writedatafile($chatfile,%chat);
+                                               write_chat($fh, \%chat);
                                        }
                                        else {
-                                               $message='Invalid username.';
+                                               $status = HTTP_STATUS->{'bad_request'};
+                                               $message = 'Invalid username.';
                                        }
                                }
                                else {
-                                       $message='Invalid text.';
+                                       $status = HTTP_STATUS->{'bad_request'};
+                                       $message = 'Invalid text.';
                                }
                        }
                        
-                       @chatlines = split(/\r?\n/,$chat{'content'});
+                       @chat_lines = split(/\r?\n/, $chat{'content'});
                }
-               else{
-                       $chatstate=0;
-                       $message='Can\'t lock data file!';
+               else {
+                       $chat_state = CHAT_STATE->{'disconnected'};
+                       $status = HTTP_STATUS->{'internal_server_error'};
+                       $message = 'Can\'t lock data file!';
                }
                
-               
-               close($chatfile);
+               close($fh);
        }
        else {
-               $chatstate=0;
+               $chat_state = CHAT_STATE->{'disconnected'};
+               $status = HTTP_STATUS->{'internal_server_error'};
                $message='Can\'t open data file!';
        }
 }
+# old chat archive
 else {
-       # %chat=readdatafile(DATA_CHAT_PATH);
-       # $chatid=int($chat{'id'})-$page;
-       %chat=readdatafile(DATA_CHAT_PATH);
-       $lastid=int($chat{'id'});
-       %chat=readdatafile(DATA_CHAT_PATH.$page);
-       $chatid=int($chat{'id'});
-       $chatstate=int($chat{'state'});
-       @chatlines = split(/\r?\n/,$chat{'content'});
+       $chat_id = $page;
+       %chat = read_chat();
+       $last_id = int($chat{'id'});
+       if ($chat_id < $last_id) {
+               %chat = read_chat($page);
+               $chat_state = int($chat{'state'});
+               @chat_lines = split(/\r?\n/, $chat{'content'});
+       }
 }
 
-
-# print "Content-type: text/plain\n\n";
-# print DATA_CHAT_PATH."\n";
-# print 'state: '.$chat{'state'}."\n";
-# print 'id: '.$chat{'id'}."\n\n";
-# print $chat{'content'};
-
-print "Content-type: text/html\n\n";
+if ($status ne '') {
+       print http_header_status($status);
+}
+print "Content-type: text/html; charset=UTF-8\n\n";
 if($method eq 'HEAD') {
        exit;
 }
 
-print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
-print '<html lang="en"><head>'."\n";
-print '<title>Coincidence &bull; '.WEBSITE_NAME.'</title>'."\n";
-print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
-print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
-print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
-print '</head><body>'."\n";
-print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
-print '<div id="all">'."\n";
-
-print '<div id="inst" class="ins">'."\n";
-
-print '<div id="title">'."\n";
-print '<H1 id="titletext">Coincidence</H1>'."\n";
-print '</div>'."\n";
-
-print '<div id="storypuzzle">'."\n";
+if ($username eq '') {
+       $username = $coin{'name'};
+}
+
+my $base_url = CGI_PATH();
+my $coin_url = CGI_COIN_PATH();
+my $form_url = $coin_url;
+my $oldest_url = merge_url(
+       {'path' => $coin_url},
+       {'path' => 0}
+);
+my $older_url = merge_url(
+       {'path' => $coin_url},
+       {'path' => $chat_id -1}
+);
+my $newer_url = ($chat_id < ($last_id -1)) ?
+       merge_url(
+               {'path' => $coin_url},
+               {'path' => $chat_id +1}
+       ) : $coin_url;
+
+if ($password_ok) {
+       my $password_query = url_query_encode({'p', $settings{'password'}});
+       $coin_url   = merge_url($coin_url  , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $oldest_url = merge_url($oldest_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $older_url  = merge_url($older_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $newer_url  = merge_url($newer_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+}
+
+my $_password = $password_ok ? html_entity_encode_dec($settings{'password'}, 1): '';
+my $abbr = abbr_name($username);
+my $_website_name = html_entity_encode_dec(WEBSITE_NAME() , 1);
+my $_server       = html_entity_encode_dec($coin    {'server'}  , 1);
+my $_key          = html_entity_encode_dec($coin    {'key'}     , 1);
+my $_cgi_username = html_entity_encode_dec($cgi     {'username'}, 1);
+my $_username     = html_entity_encode_dec($username  , 1);
+my $_abbr         = html_entity_encode_dec($abbr      , 1);
+my $_message      = html_entity_encode_dec($message   , 1);
+my $_base_url     = html_entity_encode_dec($base_url  , 1);
+my $_coin_url     = html_entity_encode_dec($coin_url  , 1);
+my $_form_url     = html_entity_encode_dec($form_url  , 1);
+my $_oldest_url   = html_entity_encode_dec($oldest_url, 1);
+my $_older_url    = html_entity_encode_dec($older_url , 1);
+my $_newer_url    = html_entity_encode_dec($newer_url , 1);
+
+print_html_start(\*STDOUT);
+print_html_head_start(\*STDOUT);
+
+print '  <title>Coincidence &bull; '.$_website_name.'</title>'."\n";
+
+print_html_head_end(\*STDOUT);
+print_html_body_start(\*STDOUT);
+
+print '   <div id="inst" class="ins">'."\n";
+
+print '    <div id="title">'."\n";
+print '     <H1 id="titletext">Coincidence</H1>'."\n";
+print '    </div>'."\n";
+
+print '    <div id="storypuzzle">'."\n";
 if ($page >= 0) {
-       print 'Before: '.$chatid."\n";
+       print '     Before: '.$chat_id."\n";
 }
-elsif ($chatstate>0){
-       print 'Connected to server <span class="br">'.entityencode($coin{'server'}).'</span> as user <span class="ni">'.entityencode(($username ne '')?$username:$coin{'name'}).'</span> (<span class="ni">'.entityencode(abbrname(($username ne '')?$username:$coin{'name'})).'</span>), public key <span class="br">'.entityencode($coin{'key'}).'</span>.'."\n";
+elsif ($chat_state > CHAT_STATE->{'disconnected'}) {
+       print '     Connected to server <span class="br">'.$_server.'</span> as user <span class="ni">'.$_username.'</span> (<span class="ni">'.$_abbr.'</span>), public key <span class="br">'.$_key.'</span>.'."\n";
 }
 else{
-       print 'Not connected.';
+       print '     Not connected.'."\n";
 }
-print '</div>'."\n";
-print '<div id="command">'."\n";
+print '    </div>'."\n";
+
+print '    <div id="command">'."\n";
 if ($message ne '') {
-       print '<span class="br">'.entityencode($message).'</span>'."\n";
+       print '     <span class="br">'.$_message.'</span>'."\n";
 }
 if ($page < 0) {
-       print '<form method="post" action="'.CGI_COIN_PATH.'">'."\n";
-       if ($passwordOK) {
-               print '<input class="intxc" type="text" name="words">'."\n";
-               print '<input class="inbt" type="submit" value="Send">'."\n";
-               print "|\n";
-               print '<input class="intx" type="text" name="username" value="'.entityencode($username).'">'."\n";
-               print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
-               print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
-               print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
-               print '<input class="inbt" type="submit" name="file" value="Send file">'."\n";
-               print '<input type="hidden" name="p" value="'.entityencode($coin{'password'}).'">'."\n";
+       print '     <form method="post" action="'.$_form_url.'">'."\n";
+       if ($password_ok) {
+               print '      <input class="intxc" type="text" name="words">'."\n";
+               print '      <input class="inbt" type="submit" value="Send">'."\n";
+               print "      |\n";
+               print '      <input class="intx" type="text" name="username" value="'.$_cgi_username.'">'."\n";
+               print '      <input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
+               print '      <input class="inbt" type="submit" name="join" value="Connect">'."\n";
+               print '      <input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
+               print '      <input class="inbt" type="submit" name="file" value="Send file">'."\n";
+               print '      <input type="hidden" name="p" value="'.$_password.'">'."\n";
        }
-       elsif ($chatstate>0) {
-               print '<input class="intxc" type="text" name="words">'."\n";
-               print '<input class="inbt" type="submit" value="Send">'."\n";
-               print "|\n";
-               print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
-               print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
+       elsif ($chat_state > CHAT_STATE->{'disconnected'}) {
+               print '      <input class="intxc" type="text" name="words">'."\n";
+               print '      <input class="inbt" type="submit" value="Send">'."\n";
+               print "      |\n";
+               print '      <input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
+               print '      <input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
        }
        else {
-               print '<input class="intx" type="text" name="words">'."\n";
-               print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
+               print '      <input class="intx" type="text" name="words">'."\n";
+               print '      <input class="inbt" type="submit" name="join" value="Connect">'."\n";
        }
-       print '</form>'."\n";
+       print '     </form>'."\n";
 }
-print '</div>'."\n";
+print '    </div>'."\n";
 
-print '</div><div id="insb" class="ins">'."\n";
+print '   </div>'."\n";
+print '   <div id="insb" class="ins">'."\n";
 
-print '<div id="chat">'."\n";
+print '    <div id="chat">'."\n";
 if ($page < 0) {
-       for (my $i = @chatlines-1; $i>=0; --$i) {
-               print chatline($chatlines[$i])."<br>\n";
+       for (my $i = @chat_lines-1; $i>=0; --$i) {
+               print '     '.chat_line($chat_lines[$i])."<br>\n";
        }
 }
 else {
-       for (my $i = 0; $i<@chatlines; ++$i) {
-               print chatline($chatlines[$i])."<br>\n";
+       for (my $i = 0; $i<@chat_lines; ++$i) {
+               print '     '.chat_line($chat_lines[$i])."<br>\n";
        }
 }
-print '</div>'."\n";
+print '    </div>'."\n";
 
-print '<div id="underlinks">'."\n";
-print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_COIN_PATH.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Once again</a>';
-if ($chatid > 0) {
-       print ' | <a href="'.CGI_COIN_PATH.'/'.($chatid-1).($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Before</a>';
+print '    <div id="underlinks">'."\n";
+print '     <a href="'.$_base_url.'">BSTA</a> | <a href="'.$_coin_url.'">Once again</a>';
+if ($chat_id > 0) {
+       print ' | <a href="'.$_older_url.'">Before</a>';
 }
-if ($chatid < $lastid) {
-       print ' | <a href="'.CGI_COIN_PATH.'/'.(($chatid < $lastid-1)?($chatid +1):'').($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Unbefore</a>';
+if ($chat_id < $last_id) {
+       print ' | <a href="'.$_newer_url.'">Unbefore</a>';
 }
-if ($chatid > 0) {
-       print ' | <a href="'.CGI_COIN_PATH.'/0'.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Initially</a>';
+if ($chat_id > 0) {
+       print ' | <a href="'.$_oldest_url.'">Initially</a>';
 }
 print ' | (This interface is only a demo, a proof of concept. It is very limited. No autorefresh, no private chat, etc. For full functionality use the actual Coincidence client.)'."\n";
-print "\n";
-print '</div>'."\n";
-
-print '</div>'."\n";
-print '</div>'."\n";
-print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
-print '</body></html>'."\n";
+print '    </div>'."\n";
 
+print '   </div>'."\n";
 
+print_html_body_end(\*STDOUT, int($state{'state'}) == STATE->{'inactive'});
+print_html_end(\*STDOUT);
 
-# print DATA_CHAT_PATH."\n";
-# print 'state: '.$chat{'state'}."\n";
-# print 'id: '.$chat{'id'}."\n\n";
-# print $chat{'content'};
 
-sub abbrname {
+sub abbr_name {
        (my $name) = @_;
        my $abbr;
        
@@ -403,33 +461,44 @@ sub abbrname {
        return $abbr;
 }
 
-sub chatline {
+sub chat_line {
        (my $line) = @_;
-       my $action;
-       my $name;
-       my $text;
        
        if ($line =~ /^([a-z]*@)?([A-Za-z]*): (.*)$/) {
-               $action=$1;
-               $name=$2;
-               $text=$3;
+               my $action = $1;
+               my $name = $2;
+               my $text = $3;
+               my $color;
+               if ($name eq '') {
+                       $name = $coin{'name'};
+                       $color = 'ni';
+               }
+               else {
+                       $color = 'br';
+               }
+               $abbr = abbr_name($name);
+               
+               my $_name   = html_entity_encode_dec($name          , 1);
+               my $_abbr   = html_entity_encode_dec($abbr          , 1);
+               my $_text   = html_entity_encode_dec($text          , 1);
+               my $_server = html_entity_encode_dec($coin{'server'}, 1);
                
-               if($action ne ''){
+               if($action ne '') {
                        if ($action eq 'join@') {
-                               return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') joined the public chat on server '.entityencode($coin{'server'}).'.';
+                               return "$_name ($_abbr) joined the public chat on server $_server.";
                        }
                        elsif ($action eq 'leave@') {
-                               return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') left the public chat on server '.entityencode($coin{'server'}).'.';
+                               return "$_name ($_abbr) left the public chat on server $_server.";
                        }
                        elsif ($action eq 'file@') {
-                               return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') sent the file '.entityencode($text).'.';
+                               return "$_name ($_abbr)  sent the file $_text.";
                        }
                        else {
                                return 'E:E:E';
                        }
                }
                else {
-                       return '<span class="'.(($name ne '')?'br':'ni').'">'.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).': '.entityencode($text).'</span>';
+                       return "<span class=\"$color\">$_abbr: $_text</span>";
                }
        }
        else {