#!/usr/bin/perl -w
#------------------------------------------------------------------------------
# TeamFile server-wide browser headline CGI
#------------------------------------------------------------------------------
use strict;
use utf8;
use Encode qw(decode FB_CROAK);
use Fcntl qw(O_RDONLY O_NOFOLLOW S_ISREG);
use TF::CommonUtils;

binmode(STDOUT, ':utf8');

my $LOCATION = $ENV{'TF_LOCATION'} || '';
my $LANGPARAM = TF::CommonUtils::getLangPriority();
my $HEADLINE_FILE = $ENV{'TF_BROWSER_HEADLINE_FILE'} ||
	'/var/lib/teamfile/notice/browser-headline.txt';
my $MAX_HEADLINE_BYTES = 4096;

if (!$LOCATION) {
	TF::CommonUtils::showAccessDeny();
	die '';
}

my $headlines = read_headlines($HEADLINE_FILE);
print_headlines($headlines);

sub read_headlines {
	my ($path) = @_;
	my $fh;

	return [] if !defined($path) || $path eq '';
	return [] if !sysopen($fh, $path, O_RDONLY | O_NOFOLLOW);
	my @stat = stat($fh);
	if (!@stat || !S_ISREG($stat[2]) || $stat[7] > $MAX_HEADLINE_BYTES) {
		close($fh);
		return [];
	}

	my $bytes = '';
	my $read_size = read($fh, $bytes, $MAX_HEADLINE_BYTES + 1);
	close($fh);
	return [] if !defined($read_size) || $read_size > $MAX_HEADLINE_BYTES;

	my $text;
	eval { $text = decode('UTF-8', $bytes, FB_CROAK); };
	return [] if $@;

	my @headlines;
	foreach my $line (split(/\r\n|\n|\r/, $text)) {
		$line =~ s/[\x{0000}-\x{001f}\x{007f}]+/ /g;
		$line =~ s/\s+/ /g;
		$line =~ s/^\s+|\s+$//g;
		push(@headlines, $line) if $line ne '';
	}
	return \@headlines;
}

sub escape_html {
	my ($value) = @_;
	$value = '' if !defined($value);
	$value =~ s/&/&amp;/g;
	$value =~ s/</&lt;/g;
	$value =~ s/>/&gt;/g;
	$value =~ s/"/&quot;/g;
	$value =~ s/'/&#39;/g;
	return $value;
}

sub print_headlines {
	my ($headlines) = @_;
	my $active = (@$headlines) ? 'true' : 'false';
	my $label = ($LANGPARAM eq 'ja') ?
		'サーバ管理者からのお知らせ' : 'Notice from the server administrator';
	my $escaped_label = escape_html($label);
	my $message_html = '';
	for (my $i = 0; $i < @$headlines; $i++) {
		my $class = ($i == 0) ? 'headline-message-item active' : 'headline-message-item';
		$message_html .= '<span class="' . $class . '">' .
			escape_html($headlines->[$i]) . '</span>';
	}

	print "Content-Type: text/html; charset=UTF-8\r\n";
	print "Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n";
	print "Pragma: no-cache\r\n";
	print "X-Content-Type-Options: nosniff\r\n\r\n";
	print <<"HTML";
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
html,body{height:100%;margin:0;overflow:hidden;background:#173f5f;color:#fff;font-family:Meiryo,"Hiragino Kaku Gothic ProN",sans-serif;}
.headline{box-sizing:border-box;display:flex;align-items:center;gap:16px;height:48px;padding:0 22px;border-bottom:3px solid #f2b134;white-space:nowrap;}
.headline-label{flex:0 0 auto;font-size:12px;font-weight:bold;letter-spacing:.04em;opacity:.82;}
.headline-messages{position:relative;flex:1 1 auto;align-self:stretch;min-width:0;overflow:hidden;font-size:14px;font-weight:bold;cursor:default;}
.headline-message-item{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;opacity:0;transform:translateY(-110%);transition:transform .4s ease,opacity .25s ease;}
.headline-message-item.active{opacity:1;transform:translateY(0);}
.headline-message-item.leaving{opacity:0;transform:translateY(110%);}
\@media (prefers-reduced-motion:reduce){.headline-message-item{transition:none;}}
</style>
</head>
<body>
<div class="headline"><span class="headline-label">$escaped_label</span><span class="headline-messages">$message_html</span></div>
<script>
(function() {
	var messages = document.getElementsByClassName('headline-message-item');
	var messageArea = document.getElementsByClassName('headline-messages')[0];
	var current = 0;
	var rotationTimer = null;
	var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

	if (parent && typeof parent.TF_setServerHeadline === 'function') {
		parent.TF_setServerHeadline($active);
	}
	function rotateMessage() {
			var previous = messages[current];
			current = (current + 1) % messages.length;
			var next = messages[current];
			if (reduceMotion) {
				previous.className = 'headline-message-item';
				next.className = 'headline-message-item active';
				return;
			}
			previous.className = 'headline-message-item leaving';
			next.className = 'headline-message-item active';
			window.setTimeout(function() {
				previous.className = 'headline-message-item';
			}, 450);
	}
	function startRotation() {
		if (messages.length < 2 || rotationTimer) return;
		rotationTimer = window.setInterval(rotateMessage, 6000);
	}
	function stopRotation() {
		if (!rotationTimer) return;
		window.clearInterval(rotationTimer);
		rotationTimer = null;
	}
	if (messages.length > 1) {
		messageArea.addEventListener('mouseenter', stopRotation);
		messageArea.addEventListener('mouseleave', startRotation);
		startRotation();
	}
	window.setTimeout(function() {
		var url = window.location.href.split('?')[0];
		window.location.replace(url + '?_=' + new Date().getTime());
	}, 60000);
}());
</script>
</body>
</html>
HTML
}
