]> bicyclesonthemoon.info Git - ott/bsta/blob - viewer.1.pl
4e8cc0e2039bf027bcfa0fb1dce2926979781add
[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         'read_data_file', 'write_data_file',
33         'url_query_decode',
34         'join_path',
35         'open_encoded', '_x_encoded',
36         'http_header_status',
37         'merge_url'
38 );
39 use bsta_lib (
40         'STATE', 'TEXT_MODE', 'INTF_STATE',
41         'fail_method', 'fail_content_type', 'redirect',
42         'get_remote_addr', 'get_frame', 'get_password',
43         'merge_settings',
44         'print_viewer_page', 'write_index',
45         'ong'
46 );
47
48 ###PERL_PATH_SEPARATOR:     PATH_SEPARATOR     = /
49
50 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
51 ###PERL_CGI_VIEWER_PATH:    CGI_VIEWER_PATH    = /bsta/v
52
53 ###PERL_DATA_PATH:          DATA_PATH          = /botm/data/bsta/
54 ###PERL_DATA_DEFAULT_PATH:  DATA_DEFAULT_PATH  = /botm/data/bsta/default
55 ###PERL_DATA_NOACCESS_PATH: DATA_NOACCESS_PATH = /botm/data/bsta/noaccess
56 ###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
57 ###PERL_DATA_STATE_PATH:    DATA_STATE_PATH    = /botm/data/bsta/state
58 ###PERL_DATA_STORY_PATH:    DATA_STORY_PATH    = /botm/data/bsta/story
59 ###PERL_DATA_WORDS_PATH:    DATA_WORDS_PATH    = /botm/data/bsta/words/
60
61 ###PERL_WWW_PATH:           WWW_PATH           = /botm/www/
62
63 binmode STDIN,  ':encoding(UTF-8)';
64 binmode STDOUT, ':encoding(UTF-8)';
65 binmode STDERR, ':encoding(UTF-8)';
66 # decode_argv();
67
68 my $time = time();
69 srand ($time-$$);
70
71 my %http;
72 my %cgi;
73 my %frame_data;
74 my %prev_frame_data;
75 my %next_frame_data;
76 my %default;
77 my %settings;
78 my %state;
79 my %new_state;
80 my %goto_list;
81 my %words_data;
82
83 my $method;
84 my $frame;
85 my $frame_data_path;
86 my $prev_frame_data_path;
87 my $next_frame_data_path;
88 my $password;
89 my $password_ok;
90 my $IP;
91 my $access;
92 my $timer;
93 my $timer_unlocked;
94 my $fh;
95 my $show_command;
96 my $ongtime;
97 my $text_mode;
98 my $words_page;
99 my $words_data_path;
100 my $no_cgi;
101 my $force_redirect;
102
103 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
104 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
105
106 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
107         $method = $1;
108 }
109 else{
110         exit fail_method($ENV{'REQUEST_METHOD'}, ['GET', 'POST', 'HEAD']);
111 }
112
113 %http = read_header_env(\%ENV);
114 %cgi = url_query_decode($ENV{'QUERY_STRING'});
115
116 if ($method eq 'POST') {
117         if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
118                 my %cgi_post = url_query_decode( <STDIN> );
119                 %cgi = merge_settings(\%cgi, \%cgi_post);
120         }
121         # multipart not supported
122         else{
123                 exit fail_content_type($method, $http{'content-type'});
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_data_file(DATA_SETTINGS_PATH());
133 %default   = read_data_file(DATA_DEFAULT_PATH());
134
135 if ($frame >= 0) {
136         $frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
137         %frame_data= read_data_file($frame_data_path);
138 }
139
140 $password_ok = ($password eq $settings{'password'});
141
142 # state & activation logic
143 if (open_encoded($fh, "+<:encoding(UTF-8)", DATA_STATE_PATH())) {
144         if (flock($fh, 2)) {
145                 
146                 %state = read_data_file($fh);
147                 
148                 if ($frame < 0) {
149                         $frame = int($state{'last'}) + $frame +1;
150                         if ($frame >= 0) {
151                                 $force_redirect = 1;
152                                 $frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
153                                 %frame_data = read_data_file($frame_data_path);
154                         }
155                 }
156                 
157                 if (
158                         (int($state{'state'}) == STATE->{'waiting'}) &&
159                         ($frame == int($state{'last'})) &&
160                         ($method ne 'HEAD') &&
161                         (!$password_ok)
162                 ) {
163                         # register IP for progress
164                         my %new_state = %state;
165                         unless (
166                                 ($state{'ip1'} eq $IP) ||
167                                 ($state{'ip2'} eq $IP) ||
168                                 ($state{'ip3'} eq $IP)
169                         )
170                         {
171                                 if ($state{'ip1'} eq '') {
172                                         $new_state{'ip1'} = $IP;
173                                 }
174                                 elsif ($state{'ip2'} eq '') {
175                                         $new_state{'ip2'} = $IP;
176                                 }
177                                 elsif ($state{'ip3'} eq '') {
178                                         $new_state{'ip3'} = $IP;
179                                         $new_state{'state'} = STATE->{'ready'};
180                                 }
181                                 else {
182                                         $new_state{'state'} = STATE->{'ready'};
183                                 }
184                                 write_data_file($fh, \%new_state);
185                         }
186                 }
187                 elsif (
188                         (int($state{'state'}) == STATE->{'inactive'}) &&
189                         ($frame == 1)
190                 ) {
191                         # ready to activate?
192                         # NOTE: at this point frame 0 is already ONGed.
193                         my %story;
194                         my $ong_time = int($settings{'firstongtime'});
195                         my $r;
196                         
197                         %story     = read_data_file(DATA_STORY_PATH());
198                         
199                         if (
200                                 (int($story{'state'}) == INTF_STATE->{'>|'} ) &&
201                                 (int($story{'pass'}) == 1)
202                         ) {
203                                 # conditions met; ACTIVATE!
204                                 
205                                 # set initial state
206                                 $state{'state'} = STATE->{'waiting'};
207                                 $state{'last'}  = 1; 
208                                 $state{'ip1'}   = '0.0.0.0';
209                                 $state{'ip2'}   = '0.0.0.0';
210                                 $state{'ip3'}   = '';
211                                 $state{'nextong'} = (int($time / 3600) + int($settings{'firstongtime'})) * 3600 ;
212                                 $state{'ongtime'} = $ong_time;
213                                 
214                                 # prepare to ONG frame 1
215                                 
216                                 $r = ong(
217                                         1,         # frame ID
218                                         $time,     # ONG time,
219                                         $ong_time, # timer
220                                         0,         # update
221                                         0,         # print
222                                         \%settings,
223                                         \%default,
224                                         \%frame_data,
225                                         ''         # %goto_list
226                                 );
227                                 if ($r) {
228                                         $r = write_index(\%state, \%settings);
229                                 }
230                                 if ($r) {
231                                         $r = write_data_file($fh, \%state);
232                                 }
233                                 unless ($r) {
234                                         # FAILED ONG! Story as if it was inactive!
235                                         $state{'state'} = STATE->{'inactive'};
236                                 }
237                         }
238                 }
239         }
240         else {
241                 # FAILED GET STATE! Story as if it was inactive!
242                 $state{'state'} = STATE->{'inactive'};
243         }
244         close ($fh);
245 }
246 else {
247         $state{'state'} = STATE->{'inactive'};
248 }
249
250 $access = (
251         $password_ok || (
252                 (int($state{'state'}) >= STATE->{'waiting'}) &&
253                 ($frame <= int($state{'last'})) &&
254                 ($frame >= 0)
255         )
256 );
257
258 if ($access) {
259         if ($no_cgi) {
260                 # no CGI - static page is OK
261                 if ($frame == 0) {
262                         exit redirect($method, CGI_PATH(), HTTP_STATUS->{'see_other'});
263                 }
264                 elsif ($frame < int($state{'last'})) {
265                         my $page_file;
266                         if ($frame_data{'page'} ne '') {
267                                 $page_file = $frame_data{'page'};
268                         }
269                         else {
270                                 $page_file = sprintf(
271                                         $settings{'frame'},
272                                         $frame, 'htm'
273                                 );
274                         }
275                         if (_x_encoded('-f',
276                                 join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
277                         )) {
278                                 my $static_url = merge_url(
279                                         {'path' => CGI_PATH()},
280                                         {'path' => $page_file}
281                                 );
282                                 exit redirect($method, $static_url, HTTP_STATUS->{'see_other'});
283                         }
284                 }
285         }
286         if ($force_redirect) {
287                 my $redirect_url = merge_url(
288                         {'path' => CGI_VIEWER_PATH()},
289                         {'path' => $frame}
290                 );
291                 unless ($no_cgi) {
292                         $redirect_url = merge_url(
293                                 {'path' => $redirect_url},
294                                 {'query' => \%cgi}
295                         );
296                 }
297                 exit redirect($method, $redirect_url, HTTP_STATUS->{'see_other'});
298         }
299         
300         if ($frame > 0) {
301                 $prev_frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame-1);
302                 %prev_frame_data = read_data_file($prev_frame_data_path);
303         }
304         $next_frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame+1);
305         %next_frame_data = read_data_file($next_frame_data_path);
306         
307         %frame_data      = merge_settings(\%default,      \%frame_data);
308         %prev_frame_data = merge_settings(\%default, \%prev_frame_data);
309         %next_frame_data = merge_settings(\%default, \%next_frame_data);
310 }
311 else {
312         # replace frame data with fail state replacement
313         %frame_data = read_data_file(DATA_NOACCESS_PATH());
314         %frame_data = merge_settings(\%default, \%frame_data);
315 }
316
317 $timer   = int($state{'nextong'}) - $time;
318 $ongtime = int($state{'ongtime'});
319 if($ongtime == 0) {
320         $ongtime = int($settings{'ongtime'})
321 }
322
323 $show_command = ($timer < ($ongtime*3600/3));
324 if ($state{'state'} >= STATE->{'ready'}) {
325         $timer_unlocked = 3;
326 }
327 elsif ($state{'ip3'} ne '') {
328         $timer_unlocked = 3;
329 }
330 elsif ($state{'ip2'} ne '') {
331         $timer_unlocked = 2;
332 }
333 elsif ($state{'ip1'} ne '') {
334         $timer_unlocked = 1;
335 }
336 else {
337         $timer_unlocked = 0;
338 }
339
340 $text_mode = int($cgi{'b'});
341 if($text_mode > TEXT_MODE->{'words'}) {
342         $text_mode = TEXT_MODE->{'normal'};
343 }
344 $words_page = int($cgi{'i'});
345
346 $words_data_path = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $frame);
347 %words_data = read_data_file(
348         $words_data_path, # file
349         '', # encoding
350         0,  # no header
351         ($text_mode != TEXT_MODE->{'words'}), # header only
352         1,  # as list
353 );
354
355 if (!$access) {
356         print http_header_status(HTTP_STATUS->{'forbidden'});
357 }
358 print "Content-type: text/html; charset=UTF-8\n\n";
359 if($method eq 'HEAD') {
360         exit;
361 }
362
363 print_viewer_page (
364         \*STDOUT,
365         {
366                 'launch'        => 0,
367                 'frame'         => $frame,
368                 'access'        => $access,
369                 'password_ok'   => $password_ok,
370                 'timer_unlocked'=> $timer_unlocked,
371                 'timer'         => $timer,
372                 'static'        => 0,
373                 'show_command'  => $show_command,
374                 'text_mode'     => $text_mode,
375                 'words_page'    => $words_page
376         },
377         \%state,
378         \%settings,
379         \%frame_data,
380         $access ? \%prev_frame_data : \%frame_data,
381         $access ? \%next_frame_data : \%frame_data,
382         \%words_data,
383 );