#
# The viewer interface
#
-# Copyright (C) 2016-2017, 2019-2020, 2023 Balthasar Szczepański
+# Copyright (C) 2016, 2017, 2019, 2020, 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
###PERL_LIB: use lib /botm/lib/bsta
use botm_common (
+ 'HTTP_STATUS',
'read_header_env',
- 'read_data_file', 'write_data_file',
'url_query_decode',
- 'join_path'
+ 'join_path',
+ 'open_encoded', '_x_encoded',
+ 'http_header_status',
+ 'merge_url'
);
use bsta_lib (
- 'STATE', 'TEXT_MODE',
- 'fail_method', 'fail_content_type',
+ 'STATE', 'TEXT_MODE', 'INTF_STATE',
+ 'fail_method', 'fail_content_type', 'redirect',
'get_remote_addr', 'get_frame', 'get_password',
'merge_settings',
- 'print_viewer_page', 'write_index'
+ 'print_viewer_page',
+ 'write_index', 'write_static_goto', 'write_static_viewer_page',
+ 'ong',
+ 'read_frame_data', 'read_default', 'read_noaccess',
+ 'read_words_list', 'read_settings', 'read_story', 'read_goto',
+ 'read_state', 'write_state',
+ 'get_page_file'
);
-use File::Copy;
###PERL_PATH_SEPARATOR: PATH_SEPARATOR = /
-###PERL_DATA_PATH: DATA_PATH = /botm/data/bsta/
-###PERL_DATA_DEFAULT_PATH: DATA_DEFAULT_PATH = /botm/data/bsta/default
-###PERL_DATA_LIST_PATH: DATA_LIST_PATH = /botm/data/bsta/list
-###PERL_DATA_NOACCESS_PATH: DATA_NOACCESS_PATH = /botm/data/bsta/noaccess
-###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
+###PERL_CGI_PATH: CGI_PATH = /bsta/
+###PERL_CGI_GOTO_PATH: CGI_GOTO_PATH = /bsta/g
+###PERL_CGI_VIEWER_PATH: CGI_VIEWER_PATH = /bsta/v
+
###PERL_DATA_STATE_PATH: DATA_STATE_PATH = /botm/data/bsta/state
-###PERL_DATA_STORY_PATH: DATA_STORY_PATH = /botm/data/bsta/story
-###PERL_WWW_PATH: WWW_PATH = /botm/www/1190/bsta/
+###PERL_WWW_PATH: WWW_PATH = /botm/www/
binmode STDIN, ':encoding(UTF-8)';
binmode STDOUT, ':encoding(UTF-8)';
my %http;
my %cgi;
my %frame_data;
+my %prev_frame_data;
my %next_frame_data;
my %default;
my %settings;
my %state;
my %new_state;
my %goto_list;
+my %words_data;
my $method;
my $frame;
-my $frame_data_path;
+my $prev_frame_data_path;
my $next_frame_data_path;
my $password;
my $password_ok;
my $access;
my $timer;
my $timer_unlocked;
-my $state_file;
+my $fh;
my $show_command;
my $ongtime;
+my $goto;
my $text_mode;
+my $words_page;
+my $words_data_path;
+my $no_cgi;
+my $force_redirect;
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
$method = $1;
}
else{
- exit fail_method($ENV{'REQUEST_METHOD'}, 'GET, POST, HEAD');
+ exit fail_method($ENV{'REQUEST_METHOD'}, ['GET', 'POST', 'HEAD']);
}
%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 %cgi_post=url_query_decode( <STDIN> );
- foreach my $ind (keys %cgi_post) {
- $cgi{$ind} = $cgi_post{$ind};
- }
+ my %cgi_post = url_query_decode( <STDIN> );
+ %cgi = merge_settings(\%cgi, \%cgi_post);
}
# multipart not supported
else{
- exit fail_content_type($http{'content-type'}, $method);
+ exit fail_content_type($method, $http{'content-type'});
}
}
+
+$no_cgi = (scalar (keys %cgi) == 0);
+
$IP = get_remote_addr();
$frame = get_frame(\%cgi);
$password = get_password(\%cgi);
-%settings = read_data_file(DATA_SETTINGS_PATH());
-%default = read_data_file(DATA_DEFAULT_PATH());
+%settings = read_settings();
+%default = read_default();
if ($frame >= 0) {
- $frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
- %frame_data= read_data_file($frame_data_path);
+ %frame_data= read_frame_data($frame);
}
$password_ok = ($password eq $settings{'password'});
+if (
+ (defined $cgi{'f'}) &&
+ ($cgi{'f'} eq '') &&
+ ($cgi{'g'} ne '')
+) { # GOTO with no value
+ my $goto_url = CGI_GOTO_PATH();
+ if ($password_ok) {
+ $goto_url = merge_url(
+ {'path' => $goto_url},
+ {'query' => {'p' => $password}}
+ );
+ }
+ exit redirect($method, $goto_url, HTTP_STATUS->{'see_other'});
+}
+
+
# state & activation logic
-if (open ($state_file, "+<:encoding(UTF-8)", DATA_STATE_PATH())) {
- if (flock($state_file, 2)) {
+if (open_encoded($fh, "+<:encoding(UTF-8)", DATA_STATE_PATH())) {
+ if (flock($fh, 2)) {
- %state = read_data_file($state_file);
+ %state = read_state($fh);
if ($frame < 0) {
$frame = int($state{'last'}) + $frame +1;
- $frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
- %frame_data = read_data_file($frame_data_path);
+ if ($frame >= 0) {
+ $force_redirect = 1;
+ %frame_data = read_frame_data($frame);
+ }
}
if (
}
elsif ($state{'ip3'} eq '') {
$new_state{'ip3'} = $IP;
+ $new_state{'state'} = STATE->{'ready'};
}
- write_data_file($state_file, '', '', \%new_state);
+ else {
+ $new_state{'state'} = STATE->{'ready'};
+ }
+ if ($new_state{'state'} == STATE->{'ready'}) {
+ write_static_goto(\%new_state, \%settings, '');
+ write_static_viewer_page(
+ $frame-1,
+ \%new_state,
+ \%settings,
+ \%default,
+ '', # frame data
+ '', # prev frame data
+ \%frame_data, # next frame data,
+ '' # words data
+ );
+ }
+ write_state($fh, \%new_state);
}
}
elsif (
(int($state{'state'}) == STATE->{'inactive'}) &&
- ($frame == 1)
+ ($frame == 1) &&
+ (!$password_ok)
) {
# ready to activate?
# NOTE: at this point frame 0 is already ONGed.
my %story;
- my $frame_file;
- my $in_path;
- my $out_path;
+ my $ong_time = int($settings{'firstongtime'});
+ my $r;
- %story = read_data_file(DATA_STORY_PATH());
- %goto_list = read_data_file(DATA_LIST_PATH);
+ %story = read_story();
+ %goto_list = read_goto();
if (
- (int($story{'state'}) == 0x11) && # buttons 0 + 4
+ (int($story{'state'}) == INTF_STATE->{'>|'} ) &&
(int($story{'pass'}) == 1)
) {
# conditions met; ACTIVATE!
- # update ONG time of frame 1
- $frame_data{'ongtime'} = $time;
- write_data_file($frame_data_path, '', '', \%frame_data);
-
# set initial state
$state{'state'} = STATE->{'waiting'};
$state{'last'} = 1;
$state{'ip2'} = '0.0.0.0';
$state{'ip3'} = '';
$state{'nextong'} = (int($time / 3600) + int($settings{'firstongtime'})) * 3600 ;
- $state{'ongtime'} = int($settings{'firstongtime'});
+ $state{'ongtime'} = $ong_time;
# prepare to ONG frame 1
- # determine frame file & paths
- unless (defined($frame_data{'ext'})){
- $frame_data{'ext'} = $default{'ext'};
+ $r = ong(
+ 1, # frame ID
+ $time, # ONG time,
+ $ong_time, # timer
+ 0, # update
+ 0, # print
+ \%settings,
+ \%default,
+ \%frame_data,
+ \%goto_list
+ );
+ if ($r) {
+ $r = write_index(\%state, \%settings);
}
- $frame_file = sprintf($settings{'frame'}, $frame, $frame_data{'ext'});
- $in_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame_file);
- $out_path = join_path(PATH_SEPARATOR(), WWW_PATH(), $frame_file);
-
- # update the GOTO list with frame 1
- $goto_list{'title-1'} = $frame_data{'title'};
- $goto_list{'ongtime-1'} = $frame_data{'ongtime'};
-
- if (copy ($in_path, $out_path)) {
- write_index('viewer', \%state, \%settings);
- write_data_file($state_file, '','', \%state);
- write_data_file(DATA_LIST_PATH(),'','', \%goto_list);
+ if ($r) {
+ $r = write_static_goto(\%state, \%settings, \%goto_list);
}
- else {
+ if ($r) {
+ $r = write_state($fh, \%state);
+ }
+ unless ($r) {
# FAILED ONG! Story as if it was inactive!
$state{'state'} = STATE->{'inactive'};
}
# FAILED GET STATE! Story as if it was inactive!
$state{'state'} = STATE->{'inactive'};
}
- close ($state_file);
+ close ($fh);
}
else {
$state{'state'} = STATE->{'inactive'};
}
-$next_frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame+1);
-%next_frame_data = read_data_file($next_frame_data_path);
+$access = (
+ $password_ok || (
+ (int($state{'state'}) >= STATE->{'waiting'}) &&
+ ($frame <= int($state{'last'})) &&
+ ($frame >= 0)
+ )
+);
-# apply defaults
-%frame_data = merge_settings(\%default, \%frame_data);
-%next_frame_data = merge_settings(\%default, \%next_frame_data);
+if ($access) {
+ if ($no_cgi) {
+ # no CGI - static page is OK
+ if ($frame == 0) {
+ exit redirect($method, CGI_PATH(), HTTP_STATUS->{'see_other'});
+ }
+ elsif ($frame < int($state{'last'})) {
+ my $page_file = get_page_file($frame, \%frame_data, \%settings);
+ if (_x_encoded('-f',
+ join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
+ )) {
+ my $static_url = merge_url(
+ {'path' => CGI_PATH()},
+ {'path' => $page_file}
+ );
+ exit redirect($method, $static_url, HTTP_STATUS->{'see_other'});
+ }
+ }
+ }
+ if ($force_redirect) {
+ my $redirect_url = merge_url(
+ {'path' => CGI_VIEWER_PATH()},
+ {'path' => $frame}
+ );
+ unless ($no_cgi) {
+ delete $cgi{'f'}; # to avoid infinite loop
+ $redirect_url = merge_url(
+ {'path' => $redirect_url},
+ {'query' => \%cgi}
+ );
+ }
+ exit redirect($method, $redirect_url, HTTP_STATUS->{'see_other'});
+ }
+
+ if ($frame > 0) {
+ %prev_frame_data = read_frame_data($frame-1, \%default);
+ }
+ else {
+ %prev_frame_data = %default;
+ }
+ %next_frame_data = read_frame_data($frame+1, \%default);
+ %frame_data = merge_settings(\%default, \%frame_data);
+}
+else {
+ # replace frame data with fail state replacement
+ %frame_data = read_noaccess(\%default);
+}
$timer = int($state{'nextong'}) - $time;
$ongtime = int($state{'ongtime'});
$timer_unlocked = 0;
}
-if (
- $password_ok || (
- (int($state{'state'}) >= STATE->{'waiting'}) &&
- ($frame <= int($state{'last'})) &&
- ($frame >= 0)
- )
- ) {
- $access = 1;
-}
-else {
- $access = 0;
- # replace frame data with fail state replacement
- %frame_data = read_data_file(DATA_NOACCESS_PATH());
- %frame_data = merge_settings(\%default, \%frame_data);
-}
-
$text_mode = int($cgi{'b'});
-if($text_mode > 2) {
+if($text_mode > TEXT_MODE->{'words'}) {
$text_mode = TEXT_MODE->{'normal'};
}
+$words_page = int($cgi{'i'});
+$goto = int($cgi{'g'});
+
+%words_data = read_words_list(
+ $frame,
+ ($text_mode != TEXT_MODE->{'words'})
+);
-print "Content-type: text/html\n";
-if(!$access) {
- print "Status: 403 Forbidden\n";
+if (!$access) {
+ print http_header_status(HTTP_STATUS->{'forbidden'});
}
-print "\n";
+print "Content-type: text/html; charset=UTF-8\n\n";
if($method eq 'HEAD') {
exit;
}
print_viewer_page (
\*STDOUT,
{
+ 'launch' => 0,
'frame' => $frame,
'access' => $access,
'password_ok' => $password_ok,
'timer' => $timer,
'static' => 0,
'show_command' => $show_command,
+ 'text_mode' => $text_mode,
+ 'words_page' => $words_page,
+ 'goto' => $goto
},
\%state,
\%settings,
\%frame_data,
- \%next_frame_data
+ $access ? \%prev_frame_data : \%frame_data,
+ $access ? \%next_frame_data : \%frame_data,
+ \%words_data,
);