###PERL;

# interface.pl is generated from interface.1.pl
# 13.01.2016
#
# This is the software of the facebook interface, to access archived groups,
# threads, images, etc.
# 
#    Copyright (C) 2015-2016  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
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
#use warnings;
###PROXY_LIB;
###FACEBUG_LIB;
use proxy_lib qw(entityencode getcgi urldecode readconfigfile);
use facebug_lib qw(key readdatafile gettimenumber);

###IF_CSS_PATH;
###LOGIN_PATH;
###PASS_PATH;
###ACCESS_PATH;
###INTERFACE_PATH;
###LOGOUT_PATH;
###LIST_PATH;
###GROUP_PATH;
###THREAD_PATH;
###POST_PATH;
###IMAGE_PATH;
###KEY_BITS;
###TIMEOUT_UNLOCK;
###TIMEOUT_INACT;
###GROUPSETTINGS_PATH;
###ARCH_PATH;
###POSTS_IN_PAGE;
###THREADS_IN_PAGE;
###HTML_HEAD;
###HTML_TOP;
###HTML_BOTTOM;

my %http;
my %cgi;

my $mode;
my $method;
my $IP;
my $id;
my $skey;   # session key
my $key;    # key of accessed content
my $tkey;   # key of parent thread
my $gpage;  # page number on thread list, relevant when coming back
my $tpage;  # page number on parentthread, relevant when coming back
my $grev;   # reversed order when coming back to thread list
my $rev;    # reversed order
my $page;

my @idt;    # array of ID segments

my $time = time();
srand ($time-$$);

delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
###PATH;


if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
	$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.");
}

%http = gethttpheader (\%ENV);
%cgi = getcgi($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};
		}
	}
	# multipart not supported
	else{
		exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
	}
}

# How the URLs work:
# domain_and_path/mode/id
# 
# id:
# group:  group_id/page_number
# thread: group_id/thread_id/page_number
# post:   group_id/thread_id/post_id/page_number
# image:  group_id/image_id
#
# Any next path segments are ignored. Except when in "image" mode - then they
# are treated as part of the id.
# 
# If there is an "id" CGI parameter it's used instead of the id in the path in
# the URL. If there is a "mode" CGI parameter it's used  instead of the mode in
# the URL. If there is a "p" CGI parameter it's used instead of the page number
# inside the ID. If there is no page number 1 is default.

if ($cgi{'mode'} ne '') {
	if ($cgi{'mode'} =~ /^(list|group|thread|post|image|login|logout)$/) {
		$mode = $1;
		$id = '';
	}
	else {
		exit failpage("Status: 404 Not Found\n","404 Not Found\n","\"$cgi{'mode'}\" is not a valid mode.");
	}
}
elsif ($ENV{'PATH_INFO'} =~ /^\/(list|group|thread|post|image|login|logout)(\/((.+)\/?)?)?$/) {
	$mode = $1;
	$id = $4;
}
elsif ($ENV{'PATH_INFO'} =~ /^\/?$/) {
	$mode = 'list';
	$id = '';
}
else{
	exit failpage("Status: 404 Not Found\n","404 Not Found\n","Path \"$ENV{'PATH_INFO'}\" is not part of the interface.");
}

if($cgi{'id'} ne '') {
	$id = $cgi{'id'};
}

# Some http header checks should be added here.
# if(defined($http{'expect'})) {
	# exit failpage("Status: 406 Not Acceptable\n","406 Not Acceptable","\"Expect\" header field not supported.");
# }

# IP required for login verification
if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
	$IP=$1;
}
else{
	exit failpage("Status: 500 Internal Server Error\n","500 Internal Server Error","Can't get your IP.");
}

if ($mode !~ /^(login|logout)$/) {
	# session key required for accessing anything
	unless ($skey=access($time,$IP,$cgi{'skey'})) {
		exit loginpage ("You must log in to access this $mode.","Status: 403 Forbidden\n");
	}
	if ($mode eq 'image') {
		# image id validation
		if($id =~ /^(([0-9]+)\/([a-zA-Z]_([A-Za-z0-9\-\._\/]|(\@[A-Fa-f0-9]{2}))+))$/) {
			$id = $1;
			$idt[0]=$2;
			$idt[1]=$3;
		}
		else {
			exit failpage("Status: 404 Not Found\n","404 Not Found\n"," \"$id\" is not a valid image ID.");
		}
	}
	else {
		# first part of (non-image) id validation.
		if($id =~ /^([0-9\/\-]*)$/) {
			$id = $1;
			@idt = split('/',$id)
		}
		elsif ($mode !~ /^(list)$/) {
			exit failpage("Status: 404 Not Found\n","404 Not Found\n"," \"$id\" is not a valid $mode ID.");
		}
	}
}


if($mode eq 'login') {
	exit login();
}
if($mode eq 'logout') {
	exit logout();
}
if ($mode eq 'list') {
	exit list();
}
# first id segment required for group, thread, post, image 
if ($idt[0] !~ /^([0-9]+)$/) {
	exit failpage("Status: 404 Not Found\n","404 Not Found","No $mode ID.");
}
if ($mode eq 'group') {
	# negative page number (in path only!) causes antichronological order
	if ($idt[1] =~ /^-([0-9]+)$/) {
		$idt[1]=$1;
		$rev=1;
	}
	# cgi parameter also does this
	elsif ($cgi{'rev'} ne '') {
		$rev=1;
	}
	exit thread();
}
# second path segment required for thread, post, image
if ((($idt[1] !~ /^([0-9]+)$/) and ($mode ne 'image')) or $idt[1] eq '') {
	exit failpage("Status: 404 Not Found\n","404 Not Found","Incomplete $mode ID.");
}
# these things also require a key
if ($cgi{'key'} =~ /^([A-Fa-f0-9]+)$/) {
	$key=$1;
}
if ($mode eq 'image') {
	exit image();
}
# if order was reversed in theread list
if ($cgi{'grev'} ne '') {
	$grev=1;
}
# page number on thread list
if ($cgi{'gp'} =~ /^([0-9]+)$/) {
	$gpage=$1;
}
if ($mode eq 'thread') {
	exit thread();
}
# third page segment required by post
if ($idt[2] !~ /^([0-9]+)$/) {
	exit failpage("Status: 404 Not Found\n","404 Not Found","Incomplete $mode ID.");
}
# parent thread key
if ($cgi{'tkey'} =~ /^([A-Fa-f0-9]+)$/) {
	$tkey=$1;
}
# parent thread page number
if ($cgi{'tp'} =~ /^([0-9]+)$/) {
	$tpage=$1;
}
if ($mode eq 'post') {
	exit thread();
}
exit failpage("Status: 500 Internal Server Error\n","500 Internal Server Error","$mode: $id");

sub logout {
	# logging out is acomplished by removing the access file
	unless ($skey=access($time,$IP,$cgi{'skey'})) {
		exit loginpage ("You were not logged in.","Status: 403 Forbidden\n");
	}
	my $accesspath=ACCESS_PATH.$skey;
	unlink $accesspath or return failpage("Status: 500 Internal Server Error\n","500 Internal Server Error","$Couldn't remove access file.");
	
	if($method eq 'HEAD') {
		print "Status: 200 Ok\n\n";
		return;
	}
	print "Content-type: text/html\n\n";
	print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
	print '<html lang="en"><head>'."\n";
	print '<title>Logout successful - the redundant facebook copy</title>'."\n";
	print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
	print '<link rel="stylesheet" href="'.IF_CSS_PATH.'">'."\n";
	print HTML_HEAD."\n";
	print '</head><body>'."\n";
	print HTML_TOP."\n";
	print '<h1>The redundant facebook copy</h1>'."\n";
	print '<h2>Logout successful</h2>'."\n";
	print '<p><b class="ni">You have successfully logged out.</b></p>'."\n";
	print HTML_BOTTOM."\n";
	print '</body></html>'."\n";
}

# Function which displays one post/thread on a list of posts/threads
# Arguments:
# 1. LAST SEGMENT of ID.
# 2. mode: 'thread', 'post', 'posstreply'
# 3. if post is even or odd
# 4. if post is a firstpost on a thread
# Returns 1 on success and 0 on failure 
sub showpost {
	(my $postid, my $pmode, my $even, my $firstpost) = @_;
	my $post;
	my $postpath;
	my %posth;
	my $first;
	my $repliespath;
	my $postcontent;
	
	if ($pmode =~ /^(postreply|post|thread)$/){
		$pmode=$1;
	}
	else {
		return 0;
	}
	if (ref($postid)){
		$post=$postid;
	}
	else {
		if($postid =~ /^([0-9]+)$/) {
			$postid=$1;
		}
		else {
			return 0;
		}
		# path depends on mode
		$postpath=ARCH_PATH.$idt[0].'/'.$pmode.'/'.(($pmode eq 'thread')?'':($idt[1].'/'.(($pmode eq 'post')?'':($idt[2].'/')))).$postid;
		%posth=readdatafile($postpath);
		$post=\%posth;
	}
	if($$post{'id'} =~ /^([0-9]+)$/) {
		$postid=$1;
	}
	else {
		return 0;
	}
	if ($pmode eq 'thread') {
		$postcontent = \($$post{'postcontent'});
	}
	else{
		$postcontent = \($$post{'content'});
	}
	$$postcontent =~ s/&img([^;]+);/insertimg($1)/ge;
	print '<div class="post-'.($even?'0':'1').'" id="'.(($pmode eq 'thread')?'':($idt[1].'-'.(($pmode eq 'post')?'':($idt[2].'-')))).$postid.'">';
	print '<div class="post-name">'.entityencode($$post{'name'}).(($$post{'timetext'} ne '')?(' &bull; '.showtime($$post{'timetext'})):'').(($$post{'likes'} ne '')?(' &bull; likes: <b>'.$$post{'likes'}.'</b>'):'').(($$post{'replies'} ne '')?(' &bull; replies: <b>'.$$post{'replies'}.'</b>'):'').'</div>'."\n";
	print '<div class="post-content-'.($even?'0':'1').'">';
	if ($$post{'description'} ne '') {
		print '<p><b class = "br">'.$$post{'description'}.'</b></p>';
	}
	print $$postcontent.'</div>'."\n";
	
	# "shared post" (post inside a post)
	if ($$post{'shared'}) {
		if ($pmode eq 'thread') {
			$postcontent = \($$post{'shared-postcontent'});
		}
		else{
			$postcontent = \($$post{'shared-content'});
		}
		$$postcontent =~ s/&img([^;]+);/insertimg($1)/ge;
		print '<div class="post-'.($even?'1':'0').'" id="'.(($pmode eq 'thread')?'':($idt[1].'-'.(($pmode eq 'post')?'':($idt[2].'-')))).'-shared'.$postid.'">';
		print '<div class="post-name">'.entityencode($$post{'shared-name'}).(($$post{'shared-timetext'} ne '')?(' &bull; '.showtime($$post{'shared-timetext'})):'').'</div>'."\n";
		print '<div class="post-content-'.($even?'1':'0').'">';
		if ($$post{'shared-description'} ne '') {
			print '<p><b class = "br">'.$$post{'shared-description'}.'</b></p>';
		}
		print $$postcontent.'</div>'."\n";
		
		# shared posts can have attachments too
		$first=1;
		for(my $ind=1;;++$ind) {
			if($$post{'shared-link-'.$ind} ne ''){
				if($first){
					print '<div class="post-content-'.($even?'1':'0').'">'."\n";
					$first=0;
				}
				print '<div class="post-attachment">';
				if($$post{'shared-img-'.$ind} ne ''){
					print '<div class="left"><a href="'.entityencode($$post{'shared-link-'.$ind}).'"><img class="post-attachment" src="'.IMAGE_PATH.'/'.$idt[0].'/'.$$post{'shared-img-'.$ind}.'?key='.$$post{'shared-imgkey-'.$ind}.'&amp;skey='.$skey.'" alt="image '.$ind.'"></a></div>';
				}
				print '<div class="left"><a href="'.entityencode($$post{'shared-link-'.$ind}).'" class="br"><b>'.$$post{'shared-linktitle-'.$ind}.'</b></a><br><a href="'.entityencode($$post{'shared-link-'.$ind}).'" class="br">'.$$post{'shared-linktext-'.$ind}.'</a></div></div>'."\n";
			}
			elsif($$post{'shared-img-'.$ind} ne ''){
				if($first){
					print '<div class="post-content-'.($even?'1':'0').'">'."\n";
					$first=0;
				}
				print '<img class="post-attachment" src="'.IMAGE_PATH.'/'.$idt[0].'/'.$$post{'shared-img-'.$ind}.'?key='.$$post{'shared-imgkey-'.$ind}.'&amp;skey='.$skey.'" alt="image '.$ind.'">';
			}
			else{
				last;
			}
		}
		unless($first) {
			print '</div>'."\n";
		}
		print '</div>'."\n";
	}
	
	# attachments
	$first=1;
	for(my $ind=1;;++$ind) {
		if($$post{'link-'.$ind} ne ''){
			if($first){
				print '<div class="post-content-'.($even?'0':'1').'">'."\n";
				$first=0;
			}
			print '<div class="post-attachment">';
			if($$post{'img-'.$ind} ne ''){
				print '<div class="left"><a href="'.entityencode($$post{'link-'.$ind}).'"><img class="post-attachment" src="'.IMAGE_PATH.'/'.$idt[0].'/'.$$post{'img-'.$ind}.'?key='.$$post{'imgkey-'.$ind}.'&amp;skey='.$skey.'" alt="image '.$ind.'"></a></div>';
			}
			print '<div class="left"><a href="'.entityencode($$post{'link-'.$ind}).'" class="br"><b>'.$$post{'linktitle-'.$ind}.'</b></a><br><a href="'.entityencode($$post{'link-'.$ind}).'" class="br">'.$$post{'linktext-'.$ind}.'</a></div></div>'."\n";
		}
		elsif($$post{'img-'.$ind} ne ''){
			if($first){
				print '<div class="post-content-'.($even?'0':'1').'">'."\n";
				$first=0;
			}
			print '<img class="post-attachment" src="'.IMAGE_PATH.'/'.$idt[0].'/'.$$post{'img-'.$ind}.'?key='.$$post{'imgkey-'.$ind}.'&amp;skey='.$skey.'" alt="image '.$ind.'">';
		}
		else{
			last;
		}
	}
	unless($first) {
		print '</div>'."\n";
	}
	
	# if post/thread has replies show a button that takes to the (sub)thread
	if(($pmode ne 'postreply')and !$firstpost) {
		if ($pmode eq 'thread') {
			$repliespath=ARCH_PATH.$idt[0].'/post/'.$postid.'/';
		}
		else {
			$repliespath=ARCH_PATH.$idt[0].'/postreply/'.$idt[1].'/'.$postid.'/';
		}
		if (-d $repliespath) {
			print '<div class="post-content-'.($even?'0':'1').'">';
			print '<form class="inline" method="post" action="'.(($pmode eq 'thread')?THREAD_PATH:POST_PATH).'/'.$idt[0].'/'.(($pmode eq 'thread')?'':($idt[1].'/')).$postid.'">';
			print '<input type="submit" class="button" value="show '.(($pmode eq 'thread')?'':'sub').'thread">';
			print '<input type="hidden" name="skey" value="'.$skey.'">';
			if ($pmode eq 'thread') {
				print '<input type="hidden" name="gp" value="'.$page.'">';
				if ($rev ne '') {
					print '<input type="hidden" name="grev" value="'.$rev.'">';
				}
			}
			else {
				print '<input type="hidden" name="tp" value="'.$page.'">';
				if ($key ne '') {
					print '<input type="hidden" name="tkey" value="'.$key.'">';
				}
				if ($grev ne '') {
					print '<input type="hidden" name="grev" value="'.$grev.'">';
				}
				if ($gpage ne '') {
					print '<input type="hidden" name="gp" value="'.$gpage.'">';
				}
			}
			print '<input type="hidden" name="key" value="'.$$post{'key'}.'"></form></div>'."\n";
		}
	}
	print '</div>'."\n";
	
	return 1;
}

# function to send an image, supports if-modified-since. 
sub image {
	my $headerpath;
	my $imagepath;
	my $imagefile;
	my %header;
	my $buffer;
	my $notmodified=0;
	my $modifiedsince;
	
	$imagepath=ARCH_PATH.$idt[0].'/image/'.$idt[1];
	# "header" file with metadata
	$headerpath=$imagepath.'@h';
	$imagepath.='@v';
	%header = readdatafile($headerpath);
	
	if($header{'id'}!~/^[a-zA-Z]_([A-Za-z0-9\-\._\/]|(\@[A-Fa-f0-9]{2}))+$/) {
		return failpage("Status: 404 Not Found\n","404 Not Found","Image \"$id\" not found.");
	}
	
	if(($header{'key'} ne'') and ($key ne $header{'key'})) {
		exit loginpage ("You must log in to access this image.","Status: 403 Forbidden\n");
	}
	
	if(defined($http{'if-modified-since'})) {
		if(($modifiedsince=gettimenumber($http{'if-modified-since'})) and ($header{'timenumber'} ne '')) {
			if($header{'timenumber'} <= $modifiedsince) {
				$notmodified=1;
			}
		}
	}
	
	if($notmodified) {
		print "Status: 304 Not Modified\n\n";
		return;
	}
	
	open($imagefile,'<',$imagepath) or return failpage("Status: 500 Internal Server Error\n","500 Internal Server Error"," Can't open image file.");
	unless(binmode($imagefile)) {
		close($imagefile);
		return failpage("Status: 500 Internal Server Error\n","500 Internal Server Error"," Can't switch to binary mode.");
	}
	
	# some header fields from the file should be sent
	foreach my $ind (keys %header) {
		if ($ind =~ /^((content-.*)|last-modified)$/) {
			print "$ind: $header{$ind}\n";
		}
	}
	
	print "\n";
	
	if ($method eq 'HEAD'){
		close($imagefile);
		return;
	}
	
	while (read ($imagefile,$buffer,1024)) {
		print (STDOUT $buffer);
	}
	close($imagefile);
}

# function to send a page of a list of threads / a thread / a subthread
sub thread {
	my $dir;
	my $threadpath;
	my $threadid;
	my %thread;
	my $postspath;
	my @posts;
	my $postid;
	my $pagesq;
	my $postsq;
	my $even;
	my $noreplies;
	my $pagepath;
	my $pmode;
	my $postsinpage;
	my $idpath;
	
	if ($mode eq 'group') {
		$pmode='thread';
		$postsinpage=THREADS_IN_PAGE;
		$threadpath=GROUPSETTINGS_PATH.$idt[0];
		%thread=readconfigfile($threadpath);
	}
	else {
		if($mode eq 'thread') {
			$pmode = 'post';
		}
		else {
			$pmode = 'postreply';
		}
		$postsinpage=POSTS_IN_PAGE;
		$threadpath=ARCH_PATH.'/'.$idt[0].'/'.$mode.'/'.$idt[1].(($mode eq 'post')?('/'.$idt[2]):'');
		%thread=readdatafile($threadpath);
	}
	
	if($thread{'id'} =~ /^([0-9]+)$/) {
		$threadid=$1;
	}
	else {
		return failpage("Status: 404 Not Found\n","404 Not Found",ucfirst($mode).' "'.$idt[0].(($mode eq 'group')?'':('/'.$idt[1].(($mode eq 'thread')?'':('/'.$idt[2])))).' not found.');
	}
	
	$idpath=(($mode eq 'group')?'':($idt[0].'/'.(($mode eq 'thread')?'':($idt[1].'/')))).$threadid;
	$pagepath=$idt[($mode eq 'group')?1:(($mode eq 'thread')?2:3)];
	if($mode ne 'group') {
		if(($thread{'key'} ne'') and ($key ne $thread{'key'})) {
			exit loginpage ("You must log in to access this $mode.","Status: 403 Forbidden\n");
		}
	}
	
	if ($cgi{'p'} =~ /^([0-9]+)$/) {
		$page=int($1);
	}
	elsif ($pagepath ne '') {
		$page=int($pagepath);
	}
	else {
		$page=1;
	}
	
	# list of the posts in a thread
	$postspath=ARCH_PATH.'/'.(($mode eq 'group')?($threadid.'/thread/'):($idt[0].'/'.$pmode.'/'.(($mode eq 'thread')?'':($idt[1].'/')).$threadid.'/'));
	if (opendir ($dir, $postspath)) {
		$noreplies=0;
		while (defined ($postid = readdir $dir)) {
			if ($postid =~ /^([0-9]+)$/) {
				push @posts, $1;
			}
		}
		closedir($dir);
		if($rev){
			@posts=sort { $b <=> $a } @posts;
		}
		else {
			@posts=sort { $a <=> $b } @posts;
		}
		
		$postsq=scalar @posts;
		# in thread/post mode there is one more post (firstpost) which is not on the
		# list!
		$pagesq= int(($postsq-(($mode eq 'group')?1:0))/$postsinpage)+1;
		if($pagesq<=0) {
			$pagesq=1;
		}
		if ($page > $pagesq) {
			$page=$pagesq;
		}
		elsif ($page<=0) {
			$page=1;
		}
	}
	else {
		$noreplies=1;
		$page=1;
		$pagesq=1;
	}
	
	if ($method eq 'HEAD') {
		print "Status: 200 Ok\n\n";
		return;
	}
	print "Content-type: text/html\n\n";
	print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
	print '<html lang="en"><head>'."\n";
	print '<title>'.(($mode eq 'group')?'List of threads':('Thread '.$threadid)).(($pagesq>1)?(', page '.$page.' of '.$pagesq):'').' - the redundant facebook copy</title>'."\n";
	print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
	print '<link rel="stylesheet" href="'.IF_CSS_PATH.'">'."\n";
	print HTML_HEAD."\n";
	print '</head><body>'."\n";
	print HTML_TOP."\n";
	print '<h1>'.(($mode eq 'group')?((($thread{'name'}ne'')?(entityencode($thread{'name'})):$threadid).' - t'):'T').'he redundant facebook copy</h1>'."\n";
	print '<h2>'.(($mode eq 'group')?'List of threads':('Thread '.$threadid)).(($pagesq>1)?(', page '.$page.' of '.$pagesq):'').'</h2>'."\n";
	
	# for(){} because there are the same buttons/inputs before and after the posts.
	for (my $jnd=0; $jnd<2; ++$jnd) {
		print '<form class="inline" method="post" action="'.LOGOUT_PATH.'">';
		print '<input type="submit" value="log out" class="button"><input type="hidden" name="skey" value="'.$skey.'">';
		print '</form>'."\n";
		
		if ($mode eq 'group') {
			print '<form class="inline" method="post" action="'.LIST_PATH.'">';
			print '<input type="submit" value="back to the group list" class="button"><input type="hidden" name="skey" value="'.$skey.'">';
			print '</form>'."\n";
			
			# reverse order
			print '<form class="inline" method="post" action="'.GROUP_PATH.'/'.$threadid.'" ><input type="hidden" name="skey" value="'.$skey.'">';
			if($rev){
				print '<input type="submit" value="show oldest first" class="button">';
			}
			else {
				print '<input type="submit" value="show newest first" class="button"><input type="hidden" name="rev" value="1">';
			}
			print '</form>'."\n";
		}
		elsif($mode eq 'thread') {
			print '<form class="inline" method="post" action="'.GROUP_PATH.'/'.$idt[0].(($gpage ne '')?('/'.(($grev ne '')?'-':'').$gpage):'').'">';
			if(($grev ne '')and ($gpage eq '')) {
				print '<input type="hidden" name="rev" value="'.$grev.'">';
			}
			print '<input type="hidden" name="skey" value="'.$skey.'">';
			print '<input type="submit" value="back to the thread list" class="button">';
			print '</form>'."\n";
		}
		else {
			print '<form class="inline" method="post" action="'.THREAD_PATH.'/'.$idt[0].'/'.$idt[1].(($tpage ne '')?('/'.$tpage):'').'">';
			if($grev ne '') {
				print '<input type="hidden" name="grev" value="'.$grev.'">';
			}
			if($gpage ne '') {
				print '<input type="hidden" name="gp" value="'.$gpage.'">';
			}
			if($tkey ne '') {
				print '<input type="hidden" name="key" value="'.$tkey.'">';
			}
			print '<input type="hidden" name="skey" value="'.$skey.'">';
			print '<input type="submit" value="back to the thread" class="button">';
			print '</form>'."\n";
		}
		
		# page links
		if($pagesq > 1){
			print ' Page: ';
			if($page>1){
				pagebutton($idpath,1,$rev);
				if ($page > 6) {
					print ' ... ';
				}
			}
			for(my $ind=(($page>6)?(($page<$pagesq)?($page-3):($page-4)):2);$ind<$page;++$ind) {
				pagebutton($idpath,$ind,$rev);
			}
			pagebutton($idpath,$page,$rev,1);
			for(my $ind=$page+1;$ind<=(($page+6<=$pagesq)?(($page==1)?($page+4):($page+3)):($pagesq-1));++$ind) {
				pagebutton($idpath,$ind,$rev);
			}
			if ($page<$pagesq) {
				if ($page+6<=$pagesq){
					print ' ... ';
				}
				pagebutton($idpath,$pagesq,$rev);
			}
			if($mode eq 'group'){
				print ' Go to page: <form class="inline" method="post" action="'.GROUP_PATH.'/'.$idpath.'" >';
				if($rev) {
					print '<input type="hidden" name="rev" value="1">';
				}
			}
			else {
				if($mode eq 'thread'){
					print ' Go to page: <form class="inline" method="post" action="'.THREAD_PATH.'/'.$idpath.'" >';
				}
				else {
					print ' Go to page: <form class="inline" method="post" action="'.THREAD_PATH.'/'.$idpath.'" >';
					if($tpage ne '') {
						print '<input type="hidden" name="tp" value="'.$tpage.'">';
					}
					if($tkey ne '') {
						print '<input type="hidden" name="tkey" value="'.$tkey.'">';
					}
				}
				if($grev ne '') {
					print '<input type="hidden" name="grev" value="'.$grev.'">';
				}
				if($gpage ne '') {
					print '<input type="hidden" name="gp" value="'.$gpage.'">';
				}
				if($key ne '') {
					print '<input type="hidden" name="key" value="'.$key.'">';
				}
			}
			print '<input type="hidden" name="skey" value="'.$skey.'">';
			print '<input type="text" class="textfield" name="p" size="4"><input type="submit" value="go" class="button"></form>'."\n";
		}
		
		if($jnd>0) {
			last;
		}
		
		$even = 1;
		my $indstart;
		my $indstop;
		if ($mode eq 'group') {
			$indstart = ($page-1)*$postsinpage;
			$indstop = $page*$postsinpage;
		}
		else {
			$indstart = (($page==1)?0:(($page-1)*$postsinpage-1));
			$indstop = $page*$postsinpage-1;
			if($page==1) {
			if (showpost(\%thread,$mode,$even,1)) {
				$even = !$even;
			}
		}
		}
		for(my $ind = $indstart; ($ind < $indstop) and ($ind < $postsq); ++$ind) {
			if (showpost($posts[$ind],$pmode,$even)) {
				$even = !$even;
			}
		}
	}
	print HTML_BOTTOM."\n";
	print '</body></html>'."\n";
	
}

# function to convert image IDs to image URLs in post text
sub insertimg {
	(my $imgtext) = @_;
	my $imgid;
	my $imgkey;
	
	if ($imgtext=~ /^\@i([a-zA-Z]_([A-Za-z0-9\-\._\/]|(\@[A-Fa-f0-9]{2}))+)\@k([A-Fa-f0-9]+)$/) {
		return IMAGE_PATH.'/'.$idt[0].'/'.$1.'?key='.$4.'&amp;skey='.$skey;
	}
	else {
		return $imgtext;
	}
	
}

# function to show a page button
# arguments:
# 1. FULL id.
# 2. page number
# 3. if reversed order
# 4. if links to the same page
sub pagebutton {
	(my $id, my $page, my $reversed, my $samepage) = @_;
	print '<form class="inline" method="post" action="'.(($mode eq 'post')?POST_PATH:(($mode eq 'thread')?THREAD_PATH:GROUP_PATH)).'/'.$id.'/'.($reversed?'-':'').$page.'">';
	print '<input type="hidden" name="skey" value="'.$skey.'">';
	if($key ne '') {
		print '<input type="hidden" name="key" value="'.$key.'">';
	}
	if($tkey ne '') {
		print '<input type="hidden" name="tkey" value="'.$tkey.'">';
	}
	if($gpage ne '') {
		print '<input type="hidden" name="gp" value="'.$gpage.'">';
	}
	if($tpage ne '') {
		print '<input type="hidden" name="tp" value="'.$tpage.'">';
	}
	if($grev ne '') {
		print '<input type="hidden" name="grev" value="'.$grev.'">';
	}
	# if($reversed){
		# print '<input type="hidden" name="rev" value="1">';
	# }
	print '<input type="submit" class="'.($samepage?'samepagebutton':'pagebutton').'" value="'.$page.'">';
	print '</form>'."\n";
}

# function to send a list of archived facebook groups
sub list {
	my $dir;
	my $groupfile;
	my %group;
	my $even;
	my $groupid;
	
	if ($method eq 'HEAD') {
		print "Status: 200 Ok\n\n";
		return;
	}
	
	opendir ($dir, GROUPSETTINGS_PATH) or return failpage("Status: 500 Internal Server Error\n","500 Internal Server Error","Can't access group list.");
	
	print "Content-type: text/html\n\n";
	print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
	print '<html lang="en"><head>'."\n";
	print '<title>List of groups - the redundant facebook copy</title>'."\n";
	print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
	print '<link rel="stylesheet" href="'.IF_CSS_PATH.'">'."\n";
	print HTML_HEAD."\n";
	print '</head><body>'."\n";
	print HTML_TOP."\n";
	print '<h1>The redundant facebook copy</h1>'."\n";
	print '<h2>List of groups</h2>'."\n";
	print '<table class="list"><tr class="list-name-b"><td colspan="2">Available groups</td></tr>'."\n";
	print '<tr class="list-name"><td class="list-cell">name</td><td class="list-cell">action</td></tr>'."\n";

	$even=0;
	
	# the list is assumed to be short. no pagination.
	
	while (defined ($groupid = readdir $dir)) {
		if ($groupid =~ /^([0-9]+)$/) {
			%group = readconfigfile(GROUPSETTINGS_PATH.$1);
		}
		else {
			next;
		}
		if ($group{'id'} =~ /^([0-9]+)$/) {
			$groupid=$1;
		}
		else {
			next;
		}
		$even = !$even;
		print '<tr class="'.($even?'list-entry-0':'list-entry-1').'"><td class="list-cell">'.(($group{'name'}ne'')?(entityencode($group{'name'})):$groupid).'</td><td class="list-cell"><form method="post" action="'.GROUP_PATH.'/'.$groupid.'" class="inline"><input type="hidden" name="skey" value="'.$skey.'"><input type="submit" value="show oldest first" class="button"></form> <form method="post" action="'.GROUP_PATH.'/'.$groupid.'" class="inline"><input type="hidden" name="skey" value="'.$skey.'"><input type="hidden" name="rev" value="1"><input type="submit" value="show newest first" class="button"></form></td></tr>'."\n";
	}
	
	print '</table>'."\n";
	print '<form class="inline" method="post" action="'.LOGOUT_PATH.'">';
	print '<input type="submit" value="log out" class="button"><input type="hidden" name="skey" value="'.$skey.'">';
	print '</form>'."\n";
	print HTML_BOTTOM."\n";
	print '</body></html>'."\n";
	closedir ($dir);
}

# Function to check if logged in
# Arguments:
# 1. timestamp (current time)
# 2. IP address
# 3. session key
# Returns session key on success and 0 on failure
sub access {
	(my $time, my $ip, my $key) = @_;
	my $timeout_unlock = TIMEOUT_UNLOCK*60;
	my $timeout_inact = TIMEOUT_INACT*60;
	my $accesspath='';
	my $accessfile;
	my $lasttime;
	my $unlocktime;
	my $lastip;
	my $username;
	
	if($key =~ /^([0-9A-Fa-f]+)$/){
		$key=$1;
	}
	else{
		return 0;
	}
	if ($ip =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
		$ip = $1;
	}
	else {
		return 0;
	}
	if ($time =~ /^([0-9]+)$/) {
		$time=int($1);
	}
	else{
		return 0;
	}
	
	# the session key is the filename
	$accesspath=ACCESS_PATH.$key;
	
	if (! (-e $accesspath)) {
		return 0;
	}
	
	elsif (open ($accessfile,"+<",$accesspath)) {
		unless (flock ($accessfile, 2)) {
			close ($accessfile);
			return 0;
		}
		$unlocktime=<$accessfile>;
		$lasttime=<$accessfile>;
		$lastip=<$accessfile>;
		$username=<$accessfile>;
		
		$unlocktime =~ s/[\r\n]//g;
		$lasttime =~ s/[\r\n]//g;
		$lastip =~ s/[\r\n]//g;
		$username =~ s/[\r\n]//g;
		
		if ($unlocktime =~ /^([0-9]+)$/) {
			$unlocktime=int($1);
		}
		else {
			close ($accessfile);
			return 0;
		}
		if ($lasttime =~ /^([0-9]+)$/) {
			$lasttime=int($1);
		}
		else {
			close ($accessfile);
			return 0;
		}
		if ($lastip =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
			$lastip = $1;
		}
		else {
			close ($accessfile);
			return 0;
		}
		if ($lastip ne $ip) {
			close ($accessfile);
			return 0;
		}
		
		if ((abs($time-$unlocktime)>$timeout_unlock) or (abs($time-$lasttime)>$timeout_inact) or ($ip ne $lastip)){
			close ($accessfile);
			unlink $accesspath;
			return 0;
		}
		else {
			if (seek($accessfile, 0, 0)) {
				print $accessfile "$unlocktime\n$time\n$ip\n$username\n";
				truncate ($accessfile , tell($accessfile));
			}
			close ($accessfile);
			return $key;
		}
	}
	else {
		return 0;
	}
}

# The function to log in
sub login {
	my $passpath;
	my $passfile;
	my $accesspath;
	my $accessfile;
	my $pass;
	my $key;
	my $username;
	
	if($cgi{'username'} eq '') {
		return loginpage ("Username missing.","Status: 403 Forbidden\n");
	}
	if($cgi{'password'} eq '') {
		return loginpage ("Password missing.","Status: 403 Forbidden\n");
	}
	if ($cgi{'username'} =~ /^([A-Za-z0-9_]+)$/){
		$username = $1;
	}
	else {
		return loginpage('Wrong username or password.',"Status: 403 Forbidden\n");
	}
	
	$passpath = PASS_PATH.$username;
	open($passfile, "<", $passpath) or return loginpage('Wrong username or password.',"Status: 403 Forbidden\n");
	$pass = <$passfile>;
	close($passfile);
	$pass =~ s/[\r\n]//g;
	$pass = urldecode($pass);
	
	if ($pass ne $cgi{'password'}){
		return loginpage('Wrong username or password.',"Status: 403 Forbidden\n");
	}
	
	# if password confirmed create the key and the access file
	$key=key(KEY_BITS);
	$accesspath=ACCESS_PATH.$key;
	
	open ($accessfile,">",$accesspath) or return loginpage("Couldn't create temporary file $accesspath.","Status: 500 Internal Server Error\n");
	print $accessfile "$time\n$time\n$IP\n$username\n";
	close ($accessfile);
	
	if($method eq 'HEAD') {
		print "Status: 200 Ok\n\n";
		return;
	}
	print "Content-type: text/html\n\n";
	print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
	print '<html lang="en"><head>'."\n";
	print '<title>Login successful - the redundant facebook copy</title>'."\n";
	print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
	print '<link rel="stylesheet" href="'.IF_CSS_PATH.'">'."\n";
	print HTML_HEAD."\n";
	print '</head><body>'."\n";
	print HTML_TOP."\n";
	print '<h1>The redundant facebook copy</h1>'."\n";
	print '<h2>Login successful</h2>'."\n";
	print '<p><b class="ni">You have successfully logged in.</b></p>'."\n";
	print '<form class ="inline" method="post" action="'.LIST_PATH.'">'."\n";
	print '<input type="submit" value="show the list" class="button">'."\n";
	print '<input type="hidden" name="skey" value="'.entityencode($key).'">'."\n";
	print '</form>'."\n";
	print '<form class="inline" method="post" action="'.LOGOUT_PATH.'">';
	print '<input type="submit" value="log out" class="button"><input type="hidden" name="skey" value="'.entityencode($key).'">';
	print '</form>'."\n";
	
	if ($cgi{'rmode'} =~ /^(group|thread|post|image)$/){
		# if this is a login to access some content after getting a 403 before
		# there should be a link to it after successfully logging in.
		# also some values (keys, page numbers) have to be determined for this.
		my %info;
		my $path;
		my $ind;
		my @list;
		my $dir;
	
		print '<form class="inline" method="post" action="'.INTERFACE_PATH.'">';
		print '<input type="submit" value="continue to the previously requested page" class="button">';
		print '<input type="hidden" name="skey" value="'.entityencode($key).'">';
		print '<input type="hidden" name="mode" value="'.entityencode($cgi{'rmode'}).'">';
		if ($cgi{'rid'} ne '') {
			print '<input type="hidden" name="id" value="'.entityencode($cgi{'rid'}).'">';
		}
		if ($cgi{'rmode'} =~ /^(thread|post)$/) {
			if($cgi{'rid'} =~ /^([0-9]+)\/([0-9]+)(\/([0-9]+))?(\/.*)?/) {
				if (($cgi{'rmode'} eq 'post') and ($4 ne '')) {
					$path = ARCH_PATH.$1.'/post/'.$2.'/'.$4;
					%info = readdatafile($path);
					if ($info{'key'} ne '') {
						print '<input type="hidden" name="key" value="'.entityencode($info{'key'}).'">';
					}
					$path = ARCH_PATH.$1.'/post/'.$2.'/';
					if (opendir ($dir, $path)) {
						while (defined ($ind = readdir $dir)) {
							if ($ind =~ /^([0-9]+)$/) {
								push @list, $1;
							}
						}
						closedir($dir);
						@list=sort { $a <=> $b } @list;
						$ind = getposition(\@list,$4);
						print '<input type="hidden" name="tp" value="'.(int(($ind+1)/POSTS_IN_PAGE)+1).'">';
					}
				}
				$path = ARCH_PATH.$1.'/thread/'.$2;
				%info = readdatafile($path);
				if ($info{'key'} ne '') {
					if($cgi{'rmode'} eq 'post') {
						print '<input type="hidden" name="tkey" value="'.entityencode($info{'key'}).'">';
					}
					else {
						print '<input type="hidden" name="key" value="'.entityencode($info{'key'}).'">';
					}
				}
				$path = ARCH_PATH.$1.'/thread/';
				if (opendir ($dir, $path)) {
					while (defined ($ind = readdir $dir)) {
						if ($ind =~ /^([0-9]+)$/) {
							push @list, $1;
						}
					}
					closedir($dir);
					@list=sort { $a <=> $b } @list;
					$ind = getposition(\@list,$2);
					print '<input type="hidden" name="gp" value="'.(int($ind/THREADS_IN_PAGE)+1).'">';
				}
			}
		}
		elsif ($cgi{'rmode'} eq 'image') {
			if($cgi{'rid'} =~ /^(([0-9]+)\/([a-zA-Z]_([A-Za-z0-9\-\._\/]|(\@[A-Fa-f0-9]{2}))+))$/) {
				$path=ARCH_PATH.$1.'/image/'.$2.'@h';
				%info = readdatafile($path);
				if ($info{'key'} ne '') {
					print '<input type="hidden" name="key" value="'.entityencode($info{'key'}).'">';
				}
			}
		}
		print '</form>'."\n";
	}
	print HTML_BOTTOM."\n";
	print '</body></html>'."\n";
}

# function to get values of http header fields. Returns a hash. names of header
# fields are lowercase
sub gethttpheader {
	(my $env) = @_;
	
	my %http;
	
	foreach my $ind (keys %$env) {
		my $name = '';
		my $value= '';
		
		if ($ind =~ /^HTTP_([A-Z0-9_]+)$/) {
			$name=$1;
		}
		elsif ($ind =~ /^(CONTENT_[A-Z0-9_]+)$/) {
			$name=$1;
		}
		else{
			next;
		}
		$name =~ s/_/-/g;
		$name = lc($name);
		if ($$env{$ind} =~ /^([\x20-\x7e]*)$/) {
			$value=$1;
		}
		else {
			next;
		}
		$http{$name}=$value;
	}
	return %http;
}


# Function for showing the login form page.
# arguments: 1 - additional message , 2 - additional header fields
sub loginpage {
	(my $message, my $header)=@_;
	if($header ne ''){
		print $header;
	}
	if($method eq 'HEAD') {
		print "\n";
		return;
	}
	print "Content-type: text/html\n\n";
	print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
	print '<html lang="en"><head>'."\n";
	print '<title>Log in - the redundant facebook copy</title>'."\n";
	print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
	print '<link rel="stylesheet" href="'.IF_CSS_PATH.'">'."\n";
	print HTML_HEAD."\n";
	print '</head><body>'."\n";
	print HTML_TOP."\n";
	print '<h1>The redundant facebook copy</h1>'."\n";
	print '<h2>Log in</h2>'."\n";
	if($message ne ''){
		print '<p><b class="br">'.entityencode($message).'</b></p>'."\n";
	}
	print '<form method="post" action="'.LOGIN_PATH.'"><table>'."\n";
	print '<tr><td><b>Username: </b></td><td><input type="text" name="username" class="textfield"></td></tr>'."\n";
	print '<tr><td><b>Password: </b></td><td><input type="password" name="password" class="textfield"></td></tr>'."\n";
	print '</table><input type="submit" value="log in" class="button">'."\n";
	if ($mode eq 'login') {
		if ($cgi{'rmode'} ne '') {
			print '<input type="hidden" name="rmode" value="'.entityencode($cgi{'rmode'}).'">';
		}
		if ($cgi{'rid'} ne '') {
			print '<input type="hidden" name="rid" value="'.entityencode($cgi{'rid'}).'">';
		}
	}
	else {
		print '<input type="hidden" name="rmode" value="'.$mode.'">';
		print '<input type="hidden" name="rid" value="'.entityencode($id).'">';
	}
	print '</form>'."\n";
	print HTML_BOTTOM."\n";
	print '</body></html>'."\n";
}

# Function to show an error page
# arguments: 1 - header fields, 2 - page title, 3 - error message 
sub failpage {
	(my $header, my $title, my $message)=@_;
	if($header ne ''){
		print $header;
	}
	if($method eq 'HEAD') {
		print "\n";
		return;
	}
	print "Content-type: text/html\n\n";
	print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
	print '<html lang="en"><head>'."\n";
	if($title ne ''){
		print '<title>'.entityencode($title).'</title>'."\n";
	}
	print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
	print '<link rel="stylesheet" href="'.IF_CSS_PATH.'">'."\n";
	print HTML_HEAD."\n";
	print '</head><body>'."\n";
	print HTML_TOP."\n";
	if($title ne ''){
		print "<h1>$title - the redundant facebook copy</h1>"."\n";
	}
	if($message ne ''){
		print '<p><b class="br">'.entityencode($message).'</b></p>'."\n";
	}
	print '<form class="inline" method="post" action="'.INTERFACE_PATH.'">';
	if($cgi{'skey'} =~ /^([A-Fa-f0-9]+)$/){
		print '<input type="hidden" name="skey" value="'.$1.'">';
	}
	print '<input type="submit" value="back to the interface" class="button"></form>'."\n";
	print '<form class="inline" method="post" action="'.LOGOUT_PATH.'">';
	print '<input type="submit" value="log out" class="button"><input type="hidden" name="skey" value="'.$skey.'">';
	print '</form>'."\n";
	print HTML_BOTTOM."\n";
	print '</body></html>'."\n";
}

# example from perlmonks.org
sub getposition {
	my ($array, $word) = @_;
	my $low = 0;
	my $high = @$array - 1;
	
	while ( $low <= $high ) {
		my $try = int( ($low+$high) / 2 );
		$low  = $try+1, next if $array->[$try] < $word;
		$high = $try-1, next if $array->[$try] > $word;
		return $try;
	}
	return;
}

sub showtime {
	(my $timetext) = @_;
	my $returnvalue;
	
	if ($timetext =~ /^[0-9]+$/) {
		$returnvalue = int(substr($timetext,6,2)).'.'.substr($timetext,4,2).'.'.int(substr($timetext,0,4));
		if (length($timetext) >= 10) {
			$returnvalue.= ', '.int(substr($timetext,8,2));
			if (length($timetext) >= 12) {
				$returnvalue.= ':'.substr($timetext,10,2);
			}
		}
	}
	else {
		$returnvalue = $timetext;
	}
	return $returnvalue;
}
