4 # chat.pl is generated from chat.1.pl.
7 # The coincidence interface
9 # Copyright (C) 2016-2017 Balthasar SzczepaĆski
11 # This program is free software: you can redistribute it and/or modify
12 # it under the terms of the GNU Affero General Public License as
13 # published by the Free Software Foundation, either version 3 of the
14 # License, or (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU Affero General Public License for more details.
21 # You should have received a copy of the GNU Affero General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode);
66 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
69 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
73 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);
76 %http = gethttpheader (\%ENV);
77 %cgi = getcgi($ENV{'QUERY_STRING'});
79 if ($method eq 'POST') {
80 if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
81 my %cgipost=getcgi( <STDIN> );
82 foreach my $ind (keys %cgipost) {
83 $cgi{$ind}=$cgipost{$ind};
86 # multipart not supported
88 exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
92 if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
99 if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
106 if ($cgi{'words'} ne '') {
107 $words=$cgi{'words'};
109 if ($cgi{'username'} ne '') {
110 $username=$cgi{'username'};
112 if ($cgi{'join'} ne '') {
115 elsif ($cgi{'leave'} ne '') {
118 elsif ($cgi{'nopost'} ne '') {
121 elsif ($cgi{'file'} ne '') {
127 if ($cgi{'p'} ne '') {
131 %coin=readdatafile(COIN_PATH);
133 if($password eq $coin{'password'}){
142 if (open ($chatfile,"+<",CHAT_PATH)){
143 if (flock($chatfile,2)) {
144 %chat=readdatafile($chatfile);
146 $chatstate=int($chat{'state'});
147 $chatid=int($chat{'id'});
150 if($action==0 && $cgi{'words'} ne '') {
151 if($chatstate < 1 && !$passwordOK) {
152 $message = 'Not connected.';
155 if ($cgi{'words'} !~ /[\r\n]/) {
156 if($username =~ /^[A-Za-z]*$/) {
157 $chat{'content'}=$chat{'content'}.$username.': '.$cgi{'words'}."\n";
162 writedatafile($chatfile,%chat);
165 $message='Invalid username.';
169 $message='Invalid text.';
174 if($chatstate > 0 && !$passwordOK) {
175 $message = 'Already connected.';
178 if($username =~ /^[A-Za-z]*$/) {
179 if ($passwordOK || $cgi{'words'} eq $coin{'server'}) {
180 $chat{'content'}=$chat{'content'}.'join@'.$username.': '.$cgi{'words'}."\n";
185 writedatafile($chatfile,%chat);
187 elsif ($cgi{'words'} eq '') {
188 $message='Server ID missing.';
190 elsif ($cgi{'words'} !~ /^[0-9]+$/) {
191 $message='Invalid server ID.';
194 $message='No active Coincidence server with this ID.';
198 $message = 'Invalid username.';
203 if($chatstate < 1 && !$passwordOK) {
204 $message = 'Already disconnected.';
207 if($username =~ /^[A-Za-z]*$/) {
208 $chat{'content'}=$chat{'content'}.'leave@'.$username.': '.$cgi{'words'}."\n";
209 if($username ne '') {
210 writedatafile($chatfile,%chat);
212 elsif ($chatstate > 1) {
213 writedatafile(CHAT_PATH.$chatid,%chat);
215 $newchat{'id'}=$chatid+1;
217 $newchat{'content'}='';
218 writedatafile($chatfile,%newchat);
222 $newchat{'id'}=$chatid;
224 $newchat{'content'}='';
225 writedatafile($chatfile,%newchat);
229 $message = 'Invalid username.';
233 elsif($action==4 && $cgi{'file'} ne '' && $cgi{'words'} ne '' && $passwordOK) {
234 if ($cgi{'words'} !~ /[\r\n]/) {
235 if($username =~ /^[A-Za-z]*$/) {
236 $chat{'content'}=$chat{'content'}.'file@'.$username.': '.$cgi{'words'}."\n";
241 writedatafile($chatfile,%chat);
244 $message='Invalid username.';
248 $message='Invalid text.';
252 @chatlines = split(/\r?\n/,$chat{'content'});
256 $message='Can\'t lock data file!';
264 $message='Can\'t open data file!';
268 # %chat=readdatafile(CHAT_PATH);
269 # $chatid=int($chat{'id'})-$page;
270 %chat=readdatafile(CHAT_PATH);
271 $lastid=int($chat{'id'});
272 %chat=readdatafile(CHAT_PATH.$page);
273 $chatid=int($chat{'id'});
274 $chatstate=int($chat{'state'});
275 @chatlines = split(/\r?\n/,$chat{'content'});
279 # print "Content-type: text/plain\n\n";
280 # print CHAT_PATH."\n";
281 # print 'state: '.$chat{'state'}."\n";
282 # print 'id: '.$chat{'id'}."\n\n";
283 # print $chat{'content'};
285 print "Content-type: text/html\n\n";
286 if($method eq 'HEAD') {
290 print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
291 print '<html lang="en"><head>'."\n";
292 print '<title>Coincidence • '.WEBSITE_NAME.'</title>'."\n";
293 print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
294 print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
295 print '<link rel="stylesheet" href="'.CSS_PATH.'">'."\n";
296 print '</head><body>'."\n";
297 print '<a href="/"><img id="botmlogo" src="'.LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
298 print '<div id="all">'."\n";
300 print '<div id="inst" class="ins">'."\n";
302 print '<div id="title">'."\n";
303 print '<H1 id="titletext">Coincidence</H1>'."\n";
306 print '<div id="storypuzzle">'."\n";
308 print 'Before: '.$chatid."\n";
310 elsif ($chatstate>0){
311 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";
314 print 'Not connected.';
317 print '<div id="command">'."\n";
318 if ($message ne '') {
319 print '<span class="br">'.entityencode($message).'</span>'."\n";
322 print '<form method="post" action="'.COINCIDENCE_PATH.'">'."\n";
324 print '<input class="intxc" type="text" name="words">'."\n";
325 print '<input class="inbt" type="submit" value="Send">'."\n";
327 print '<input class="intx" type="text" name="username" value="'.entityencode($username).'">'."\n";
328 print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
329 print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
330 print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
331 print '<input class="inbt" type="submit" name="file" value="Send file">'."\n";
332 print '<input type="hidden" name="p" value="'.entityencode($coin{'password'}).'">'."\n";
334 elsif ($chatstate>0) {
335 print '<input class="intxc" type="text" name="words">'."\n";
336 print '<input class="inbt" type="submit" value="Send">'."\n";
338 print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
339 print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
342 print '<input class="intx" type="text" name="words">'."\n";
343 print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
345 print '</form>'."\n";
349 print '</div><div id="insb" class="ins">'."\n";
351 print '<div id="chat">'."\n";
353 for (my $i = @chatlines-1; $i>=0; --$i) {
354 print chatline($chatlines[$i])."<br>\n";
358 for (my $i = 0; $i<@chatlines; ++$i) {
359 print chatline($chatlines[$i])."<br>\n";
364 print '<div id="underlinks">'."\n";
365 print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.COINCIDENCE_PATH.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Once again</a>';
367 print ' | <a href="'.COINCIDENCE_PATH.'/'.($chatid-1).($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Before</a>';
369 if ($chatid < $lastid) {
370 print ' | <a href="'.COINCIDENCE_PATH.'/'.(($chatid < $lastid-1)?($chatid +1):'').($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Unbefore</a>';
373 print ' | <a href="'.COINCIDENCE_PATH.'/0'.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Initially</a>';
375 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";
381 print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
382 print '</body></html>'."\n";
386 # print CHAT_PATH."\n";
387 # print 'state: '.$chat{'state'}."\n";
388 # print 'id: '.$chat{'id'}."\n\n";
389 # print $chat{'content'};
395 if($name !~ /^[A-Za-z]+$/) {
399 $abbr = uc(substr($name,0,1));
400 $name = substr($name,1);
401 while($name =~ m/([A-Z])/g) {
413 if ($line =~ /^([a-z]*@)?([A-Za-z]*): (.*)$/) {
419 if ($action eq 'join@') {
420 return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') joined the public chat on server '.entityencode($coin{'server'}).'.';
422 elsif ($action eq 'leave@') {
423 return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') left the public chat on server '.entityencode($coin{'server'}).'.';
425 elsif ($action eq 'file@') {
426 return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') sent the file '.entityencode($text).'.';
433 return '<span class="'.(($name ne '')?'br':'ni').'">'.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).': '.entityencode($text).'</span>';