]> bicyclesonthemoon.info Git - ott/bsta/blob - viewer.1.pl
use optimised HTML entity encoding
[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 $no_cgi = (scalar (keys %cgi) == 0);
127
128 $IP = get_remote_addr();
129 $frame = get_frame(\%cgi);
130 $password = get_password(\%cgi);
131
132 %settings  = read_settings();
133 %default   = read_default();
134
135 if ($frame >= 0) {
136         %frame_data= read_frame_data($frame);
137 }
138
139 $password_ok = ($password eq $settings{'password'});
140
141 if (
142         (defined $cgi{'f'}) &&
143         ($cgi{'f'} eq '') &&
144         ($cgi{'g'} ne '')
145 ) { # GOTO with no value
146         my $goto_url = CGI_GOTO_PATH();
147         if ($password_ok) {
148                 $goto_url = merge_url(
149                         {'path' => $goto_url},
150                         {'query' => {'p' => $password}}
151                 );
152         }
153         exit redirect($method, $goto_url, HTTP_STATUS->{'see_other'});
154 }
155
156
157 # state & activation logic
158 if (open_encoded($fh, "+<:encoding(UTF-8)", DATA_STATE_PATH())) {
159         if (flock($fh, 2)) {
160                 
161                 %state = read_state($fh);
162                 
163                 if ($frame < 0) {
164                         $frame = int($state{'last'}) + $frame +1;
165                         if ($frame >= 0) {
166                                 $force_redirect = 1;
167                                 %frame_data = read_frame_data($frame);
168                         }
169                 }
170                 
171                 if (
172                         (int($state{'state'}) == STATE->{'waiting'}) &&
173                         ($frame == int($state{'last'})) &&
174                         ($method ne 'HEAD') &&
175                         (!$password_ok)
176                 ) {
177                         # register IP for progress
178                         my %new_state = %state;
179                         unless (
180                                 ($state{'ip1'} eq $IP) ||
181                                 ($state{'ip2'} eq $IP) ||
182                                 ($state{'ip3'} eq $IP)
183                         )
184                         {
185                                 if ($state{'ip1'} eq '') {
186                                         $new_state{'ip1'} = $IP;
187                                 }
188                                 elsif ($state{'ip2'} eq '') {
189                                         $new_state{'ip2'} = $IP;
190                                 }
191                                 elsif ($state{'ip3'} eq '') {
192                                         $new_state{'ip3'} = $IP;
193                                         $new_state{'state'} = STATE->{'ready'};
194                                 }
195                                 else {
196                                         $new_state{'state'} = STATE->{'ready'};
197                                 }
198                                 if ($new_state{'state'} == STATE->{'ready'}) {
199                                         write_static_goto(\%new_state, \%settings, '');
200                                         write_static_viewer_page(
201                                                 $frame-1,
202                                                 \%new_state,
203                                                 \%settings,
204                                                 \%default,
205                                                 '', # frame data
206                                                 '', # prev frame data
207                                                 \%frame_data, # next frame data,
208                                                 '' # words data
209                                         );
210                                 }
211                                 write_state($fh, \%new_state);
212                         }
213                 }
214                 elsif (
215                         (int($state{'state'}) == STATE->{'inactive'}) &&
216                         ($frame == 1) &&
217                         (!$password_ok)
218                 ) {
219                         # ready to activate?
220                         # NOTE: at this point frame 0 is already ONGed.
221                         my %story;
222                         my $ong_time = int($settings{'firstongtime'});
223                         my $r;
224                         
225                         %story     = read_story();
226                         %goto_list = read_goto();
227                         
228                         if (
229                                 (int($story{'state'}) == INTF_STATE->{'>|'} ) &&
230                                 (int($story{'pass'}) == 1)
231                         ) {
232                                 # conditions met; ACTIVATE!
233                                 
234                                 # set initial state
235                                 $state{'state'} = STATE->{'waiting'};
236                                 $state{'last'}  = 1; 
237                                 $state{'ip1'}   = '0.0.0.0';
238                                 $state{'ip2'}   = '0.0.0.0';
239                                 $state{'ip3'}   = '';
240                                 $state{'nextong'} = (int($time / 3600) + int($settings{'firstongtime'})) * 3600 ;
241                                 $state{'ongtime'} = $ong_time;
242                                 
243                                 # prepare to ONG frame 1
244                                 
245                                 $r = ong(
246                                         1,         # frame ID
247                                         $time,     # ONG time,
248                                         $ong_time, # timer
249                                         0,         # update
250                                         0,         # print
251                                         \%settings,
252                                         \%default,
253                                         \%frame_data,
254                                         \%goto_list
255                                 );
256                                 if ($r) {
257                                         $r = write_index(\%state, \%settings);
258                                 }
259                                 if ($r) {
260                                         $r = write_static_goto(\%state, \%settings, \%goto_list);
261                                 }
262                                 if ($r) {
263                                         $r = write_state($fh, \%state);
264                                 }
265                                 unless ($r) {
266                                         # FAILED ONG! Story as if it was inactive!
267                                         $state{'state'} = STATE->{'inactive'};
268                                 }
269                         }
270                 }
271         }
272         else {
273                 # FAILED GET STATE! Story as if it was inactive!
274                 $state{'state'} = STATE->{'inactive'};
275         }
276         close ($fh);
277 }
278 else {
279         $state{'state'} = STATE->{'inactive'};
280 }
281
282 $access = (
283         $password_ok || (
284                 (int($state{'state'}) >= STATE->{'waiting'}) &&
285                 ($frame <= int($state{'last'})) &&
286                 ($frame >= 0)
287         )
288 );
289
290 if ($access) {
291         if ($no_cgi) {
292                 # no CGI - static page is OK
293                 if ($frame == 0) {
294                         exit redirect($method, CGI_PATH(), HTTP_STATUS->{'see_other'});
295                 }
296                 elsif ($frame < int($state{'last'})) {
297                         my $page_file = get_page_file($frame, \%frame_data, \%settings);
298                         if (_x_encoded('-f',
299                                 join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
300                         )) {
301                                 my $static_url = merge_url(
302                                         {'path' => CGI_PATH()},
303                                         {'path' => $page_file}
304                                 );
305                                 exit redirect($method, $static_url, HTTP_STATUS->{'see_other'});
306                         }
307                 }
308         }
309         if ($force_redirect) {
310                 my $redirect_url = merge_url(
311                         {'path' => CGI_VIEWER_PATH()},
312                         {'path' => $frame}
313                 );
314                 unless ($no_cgi) {
315                         delete $cgi{'f'}; # to avoid infinite loop
316                         $redirect_url = merge_url(
317                                 {'path' => $redirect_url},
318                                 {'query' => \%cgi}
319                         );
320                 }
321                 exit redirect($method, $redirect_url, HTTP_STATUS->{'see_other'});
322         }
323         
324         if ($frame > 0) {
325                 %prev_frame_data = read_frame_data($frame-1, \%default);
326         }
327         else {
328                 %prev_frame_data = %default;
329         }
330         %next_frame_data = read_frame_data($frame+1, \%default);
331         %frame_data      = merge_settings(\%default, \%frame_data);
332 }
333 else {
334         # replace frame data with fail state replacement
335         %frame_data = read_noaccess(\%default);
336 }
337
338 $timer   = int($state{'nextong'}) - $time;
339 $ongtime = int($state{'ongtime'});
340 if($ongtime == 0) {
341         $ongtime = int($settings{'ongtime'})
342 }
343
344 $show_command = ($timer < ($ongtime*3600/3));
345 if ($state{'state'} >= STATE->{'ready'}) {
346         $timer_unlocked = 3;
347 }
348 elsif ($state{'ip3'} ne '') {
349         $timer_unlocked = 3;
350 }
351 elsif ($state{'ip2'} ne '') {
352         $timer_unlocked = 2;
353 }
354 elsif ($state{'ip1'} ne '') {
355         $timer_unlocked = 1;
356 }
357 else {
358         $timer_unlocked = 0;
359 }
360
361 $text_mode = int($cgi{'b'});
362 if($text_mode > TEXT_MODE->{'words'}) {
363         $text_mode = TEXT_MODE->{'normal'};
364 }
365 $words_page = int($cgi{'i'});
366 $goto = int($cgi{'g'});
367
368 %words_data = read_words_list(
369         $frame,
370         ($text_mode != TEXT_MODE->{'words'})
371 );
372
373 if (!$access) {
374         print http_header_status(HTTP_STATUS->{'forbidden'});
375 }
376 print "Content-type: text/html; charset=UTF-8\n\n";
377 if($method eq 'HEAD') {
378         exit;
379 }
380
381 print_viewer_page (
382         \*STDOUT,
383         {
384                 'launch'        => 0,
385                 'frame'         => $frame,
386                 'access'        => $access,
387                 'password_ok'   => $password_ok,
388                 'timer_unlocked'=> $timer_unlocked,
389                 'timer'         => $timer,
390                 'static'        => 0,
391                 'show_command'  => $show_command,
392                 'text_mode'     => $text_mode,
393                 'words_page'    => $words_page,
394                 'goto'          => $goto
395         },
396         \%state,
397         \%settings,
398         \%frame_data,
399         $access ? \%prev_frame_data : \%frame_data,
400         $access ? \%next_frame_data : \%frame_data,
401         \%words_data,
402 );