]> bicyclesonthemoon.info Git - ott/bsta/blob - viewer.1.pl
improve GOTO handling
[ott/bsta] / viewer.1.pl
1 ###RUN_PERL: #!/usr/bin/perl
2
3 # /bsta/v
4 # viewer is generated from viewer.1.pl.
5 #
6 # The viewer interface
7 #
8 # Copyright (C) 2016, 2017, 2019, 2020, 2023, 2024  Balthasar SzczepaƄski
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU Affero General Public License as
12 # published by the Free Software Foundation, either version 3 of the
13 # License, or (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU Affero General Public License for more details.
19 #
20 # You should have received a copy of the GNU Affero General Public License
21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23 use strict;
24 use utf8;
25 # use Encode::Locale ('decode_argv');
26 use Encode ('encode', 'decode');
27
28 ###PERL_LIB: use lib /botm/lib/bsta
29 use botm_common (
30         'HTTP_STATUS',
31         'read_header_env',
32         'url_query_decode',
33         'join_path',
34         'open_encoded', '_x_encoded',
35         'http_header_status',
36         'merge_url'
37 );
38 use bsta_lib (
39         'STATE', 'TEXT_MODE', 'INTF_STATE',
40         'fail_method', 'fail_content_type', 'redirect',
41         'get_remote_addr', 'get_frame', 'get_password',
42         'merge_settings',
43         'print_viewer_page',
44         'write_index', 'write_static_goto', 'write_static_viewer_page',
45         'ong',
46         'read_frame_data', 'read_default', 'read_noaccess',
47         'read_words_list', 'read_settings', 'read_story', 'read_goto',
48         'read_state', 'write_state',
49         'get_page_file'
50 );
51
52 ###PERL_PATH_SEPARATOR:     PATH_SEPARATOR     = /
53
54 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
55 ###PERL_CGI_GOTO_PATH:      CGI_GOTO_PATH      = /bsta/g
56 ###PERL_CGI_VIEWER_PATH:    CGI_VIEWER_PATH    = /bsta/v
57
58 ###PERL_DATA_STATE_PATH:    DATA_STATE_PATH    = /botm/data/bsta/state
59
60 ###PERL_WWW_PATH:           WWW_PATH           = /botm/www/
61
62 binmode STDIN,  ':encoding(UTF-8)';
63 binmode STDOUT, ':encoding(UTF-8)';
64 binmode STDERR, ':encoding(UTF-8)';
65 # decode_argv();
66
67 my $time = time();
68 srand ($time-$$);
69
70 my %http;
71 my %cgi;
72 my %frame_data;
73 my %prev_frame_data;
74 my %next_frame_data;
75 my %default;
76 my %settings;
77 my %state;
78 my %new_state;
79 my %goto_list;
80 my %words_data;
81
82 my $method;
83 my $frame;
84 my $prev_frame_data_path;
85 my $next_frame_data_path;
86 my $password;
87 my $password_ok;
88 my $IP;
89 my $access;
90 my $timer;
91 my $timer_unlocked;
92 my $fh;
93 my $show_command;
94 my $ongtime;
95 my $goto;
96 my $text_mode;
97 my $words_page;
98 my $words_data_path;
99 my $no_cgi;
100 my $force_redirect;
101
102 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
103 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
104
105 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
106         $method = $1;
107 }
108 else{
109         exit fail_method($ENV{'REQUEST_METHOD'}, ['GET', 'POST', 'HEAD']);
110 }
111
112 %http = read_header_env(\%ENV);
113 %cgi = url_query_decode($ENV{'QUERY_STRING'});
114
115 if ($method eq 'POST') {
116         if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
117                 my %cgi_post = url_query_decode( <STDIN> );
118                 %cgi = merge_settings(\%cgi, \%cgi_post);
119         }
120         # multipart not supported
121         else{
122                 exit fail_content_type($method, $http{'content-type'});
123         }
124 }
125
126 if (
127         (defined $cgi{'f'}) &&
128         ($cgi{'f'} eq '') &&
129         ($cgi{'g'} ne '')
130 ) { # GOTO with no value
131         exit redirect($method, CGI_GOTO_PATH(), HTTP_STATUS->{'see_other'});
132 }
133
134
135 $no_cgi = (scalar (keys %cgi) == 0);
136
137 $IP = get_remote_addr();
138 $frame = get_frame(\%cgi);
139 $password = get_password(\%cgi);
140
141 %settings  = read_settings();
142 %default   = read_default();
143
144 if ($frame >= 0) {
145         %frame_data= read_frame_data($frame);
146 }
147
148 $password_ok = ($password eq $settings{'password'});
149
150 # state & activation logic
151 if (open_encoded($fh, "+<:encoding(UTF-8)", DATA_STATE_PATH())) {
152         if (flock($fh, 2)) {
153                 
154                 %state = read_state($fh);
155                 
156                 if ($frame < 0) {
157                         $frame = int($state{'last'}) + $frame +1;
158                         if ($frame >= 0) {
159                                 $force_redirect = 1;
160                                 %frame_data = read_frame_data($frame);
161                         }
162                 }
163                 
164                 if (
165                         (int($state{'state'}) == STATE->{'waiting'}) &&
166                         ($frame == int($state{'last'})) &&
167                         ($method ne 'HEAD') &&
168                         (!$password_ok)
169                 ) {
170                         # register IP for progress
171                         my %new_state = %state;
172                         unless (
173                                 ($state{'ip1'} eq $IP) ||
174                                 ($state{'ip2'} eq $IP) ||
175                                 ($state{'ip3'} eq $IP)
176                         )
177                         {
178                                 if ($state{'ip1'} eq '') {
179                                         $new_state{'ip1'} = $IP;
180                                 }
181                                 elsif ($state{'ip2'} eq '') {
182                                         $new_state{'ip2'} = $IP;
183                                 }
184                                 elsif ($state{'ip3'} eq '') {
185                                         $new_state{'ip3'} = $IP;
186                                         $new_state{'state'} = STATE->{'ready'};
187                                 }
188                                 else {
189                                         $new_state{'state'} = STATE->{'ready'};
190                                 }
191                                 if ($new_state{'state'} == STATE->{'ready'}) {
192                                         write_static_goto(\%new_state, \%settings, '');
193                                         write_static_viewer_page(
194                                                 $frame-1,
195                                                 \%new_state,
196                                                 \%settings,
197                                                 \%default,
198                                                 '', # frame data
199                                                 '', # prev frame data
200                                                 \%frame_data, # next frame data,
201                                                 '' # words data
202                                         );
203                                 }
204                                 write_state($fh, \%new_state);
205                         }
206                 }
207                 elsif (
208                         (int($state{'state'}) == STATE->{'inactive'}) &&
209                         ($frame == 1) &&
210                         (!$password_ok)
211                 ) {
212                         # ready to activate?
213                         # NOTE: at this point frame 0 is already ONGed.
214                         my %story;
215                         my $ong_time = int($settings{'firstongtime'});
216                         my $r;
217                         
218                         %story     = read_story();
219                         %goto_list = read_goto();
220                         
221                         if (
222                                 (int($story{'state'}) == INTF_STATE->{'>|'} ) &&
223                                 (int($story{'pass'}) == 1)
224                         ) {
225                                 # conditions met; ACTIVATE!
226                                 
227                                 # set initial state
228                                 $state{'state'} = STATE->{'waiting'};
229                                 $state{'last'}  = 1; 
230                                 $state{'ip1'}   = '0.0.0.0';
231                                 $state{'ip2'}   = '0.0.0.0';
232                                 $state{'ip3'}   = '';
233                                 $state{'nextong'} = (int($time / 3600) + int($settings{'firstongtime'})) * 3600 ;
234                                 $state{'ongtime'} = $ong_time;
235                                 
236                                 # prepare to ONG frame 1
237                                 
238                                 $r = ong(
239                                         1,         # frame ID
240                                         $time,     # ONG time,
241                                         $ong_time, # timer
242                                         0,         # update
243                                         0,         # print
244                                         \%settings,
245                                         \%default,
246                                         \%frame_data,
247                                         \%goto_list
248                                 );
249                                 if ($r) {
250                                         $r = write_index(\%state, \%settings);
251                                 }
252                                 if ($r) {
253                                         $r = write_static_goto(\%state, \%settings, \%goto_list);
254                                 }
255                                 if ($r) {
256                                         $r = write_state($fh, \%state);
257                                 }
258                                 unless ($r) {
259                                         # FAILED ONG! Story as if it was inactive!
260                                         $state{'state'} = STATE->{'inactive'};
261                                 }
262                         }
263                 }
264         }
265         else {
266                 # FAILED GET STATE! Story as if it was inactive!
267                 $state{'state'} = STATE->{'inactive'};
268         }
269         close ($fh);
270 }
271 else {
272         $state{'state'} = STATE->{'inactive'};
273 }
274
275 $access = (
276         $password_ok || (
277                 (int($state{'state'}) >= STATE->{'waiting'}) &&
278                 ($frame <= int($state{'last'})) &&
279                 ($frame >= 0)
280         )
281 );
282
283 if ($access) {
284         if ($no_cgi) {
285                 # no CGI - static page is OK
286                 if ($frame == 0) {
287                         exit redirect($method, CGI_PATH(), HTTP_STATUS->{'see_other'});
288                 }
289                 elsif ($frame < int($state{'last'})) {
290                         my $page_file = get_page_file($frame, \%frame_data, \%settings);
291                         if (_x_encoded('-f',
292                                 join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
293                         )) {
294                                 my $static_url = merge_url(
295                                         {'path' => CGI_PATH()},
296                                         {'path' => $page_file}
297                                 );
298                                 exit redirect($method, $static_url, HTTP_STATUS->{'see_other'});
299                         }
300                 }
301         }
302         if ($force_redirect) {
303                 my $redirect_url = merge_url(
304                         {'path' => CGI_VIEWER_PATH()},
305                         {'path' => $frame}
306                 );
307                 unless ($no_cgi) {
308                         delete $cgi{'f'}; # to avoid infinite loop
309                         $redirect_url = merge_url(
310                                 {'path' => $redirect_url},
311                                 {'query' => \%cgi}
312                         );
313                 }
314                 exit redirect($method, $redirect_url, HTTP_STATUS->{'see_other'});
315         }
316         
317         if ($frame > 0) {
318                 %prev_frame_data = read_frame_data($frame-1, \%default);
319         }
320         else {
321                 %prev_frame_data = %default;
322         }
323         %next_frame_data = read_frame_data($frame+1, \%default);
324         %frame_data      = merge_settings(\%default, \%frame_data);
325 }
326 else {
327         # replace frame data with fail state replacement
328         %frame_data = read_noaccess(\%default);
329 }
330
331 $timer   = int($state{'nextong'}) - $time;
332 $ongtime = int($state{'ongtime'});
333 if($ongtime == 0) {
334         $ongtime = int($settings{'ongtime'})
335 }
336
337 $show_command = ($timer < ($ongtime*3600/3));
338 if ($state{'state'} >= STATE->{'ready'}) {
339         $timer_unlocked = 3;
340 }
341 elsif ($state{'ip3'} ne '') {
342         $timer_unlocked = 3;
343 }
344 elsif ($state{'ip2'} ne '') {
345         $timer_unlocked = 2;
346 }
347 elsif ($state{'ip1'} ne '') {
348         $timer_unlocked = 1;
349 }
350 else {
351         $timer_unlocked = 0;
352 }
353
354 $text_mode = int($cgi{'b'});
355 if($text_mode > TEXT_MODE->{'words'}) {
356         $text_mode = TEXT_MODE->{'normal'};
357 }
358 $words_page = int($cgi{'i'});
359 $goto = int($cgi{'g'});
360
361 %words_data = read_words_list(
362         $frame,
363         ($text_mode != TEXT_MODE->{'words'})
364 );
365
366 if (!$access) {
367         print http_header_status(HTTP_STATUS->{'forbidden'});
368 }
369 print "Content-type: text/html; charset=UTF-8\n\n";
370 if($method eq 'HEAD') {
371         exit;
372 }
373
374 print_viewer_page (
375         \*STDOUT,
376         {
377                 'launch'        => 0,
378                 'frame'         => $frame,
379                 'access'        => $access,
380                 'password_ok'   => $password_ok,
381                 'timer_unlocked'=> $timer_unlocked,
382                 'timer'         => $timer,
383                 'static'        => 0,
384                 'show_command'  => $show_command,
385                 'text_mode'     => $text_mode,
386                 'words_page'    => $words_page,
387                 'goto'          => $goto
388         },
389         \%state,
390         \%settings,
391         \%frame_data,
392         $access ? \%prev_frame_data : \%frame_data,
393         $access ? \%next_frame_data : \%frame_data,
394         \%words_data,
395 );