]> bicyclesonthemoon.info Git - ott/bsta/blobdiff - 2words.1.pl
use optimised HTML entity encoding
[ott/bsta] / 2words.1.pl
index c00ee4fbb98854a65575dc69ff6e730ad5762295..1bc2d74be5eef2d5c7ea48caa6b2960aaa8061d2 100644 (file)
@@ -5,7 +5,7 @@
 #
 # The wordgame interface
 #
-# Copyright (C) 2016 - 2017, 2023  Balthasar Szczepański
+# Copyright (C) 2016, 2017, 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
@@ -27,51 +27,35 @@ use Encode ('encode', 'decode');
 
 ###PERL_LIB: use lib /botm/lib/bsta
 use botm_common (
-       'read_data_file', 'write_data_file'
+       'HTTP_STATUS',
+       'http_header_status', 'http_header_allow',
+       'merge_url',
+       'read_header_env',
+       'html_entity_encode_dec',
+       'url_query_decode', 'url_query_encode',
+       'open_encoded'
 );
 use bsta_lib (
        'STATE', 'INTF_STATE',
+       'get_id',
        'fail_method', 'fail_content_type',
-       'read_header_env',
-       'url_query_decode',
-       
-       
-       
-       # to replace
-       'failpage',
-       'gethttpheader',
-       'getcgi',
-       'entityencode',
-       'readdatafile',
-       'writedatafile',
-       'urlencode',
-       'bb2ht'
+       'print_html_start', 'print_html_end',
+       'print_html_head_start', 'print_html_head_end',
+       'print_html_body_start', 'print_html_body_end',
+       'write_index',
+       'get_remote_addr', 'get_password',
+       'merge_settings',
+       'ong',
+       'read_story', 'write_story',
+       'read_settings', 'read_state'
 );
-use  File::Copy;
 
 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
 ###PERL_CGI_2WORDS_PATH:    CGI_2WORDS_PATH    = /bsta/2words
-###PERL_CGI_CSS_PATH:       CGI_CSS_PATH       = /bsta/bsta.css
-###PERL_CGI_LOGO_PATH:      CGI_LOGO_PATH      = /bsta/botmlogo.png
-###PERL_CGI_VIEWER_PATH:    CGI_VIEWER_PATH    = /bsta/v
-
-###PERL_DATA_PATH:          DATA_PATH          = /botm/data/bsta/
-###PERL_DATA_COIN_PATH:     DATA_COIN_PATH     = /botm/data/bsta/coincidence
-###PERL_DATA_DEFAULT_PATH:  DATA_DEFAULT_PATH  = /botm/data/bsta/default
-###PERL_DATA_LIST_PATH:     DATA_LIST_PATH     = /botm/data/bsta/list
-###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
-###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_INDEX_PATH:     WWW_INDEX_PATH     = /botm/www/1190/bsta/index.htm
+###PERL_DATA_STORY_PATH:    DATA_STORY_PATH    = /botm/data/bsta/story
 
-###PERL_WEBSITE:            WEBSITE            = 1190.bicyclesonthemoon.info
 ###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
-###PERL_FAVICON_PATH:       FAVICON_PATH       = /img/favicon.png
-
-###PERL_COIN_DATE:          COIN_DATE          = 13-Nov-2016 22:15
-###PERL_INTF_DATE:          INTF_DATE          = 28-Sep-2016 20:34
 
 ###PERL_STORY_LENGTH:       STORY_LENGTH       = 16
 ###PERL_PAGE_LENGTH:        PAGE_LENGTH        = 16
@@ -88,7 +72,6 @@ my %story;
 my %new_story;
 my %settings;
 my %state;
-my %goto_list;
 
 my $time = time();
 srand ($time-$$);
@@ -100,21 +83,23 @@ my $color2;
 my $last_IP;
 my $story_id;
 my $turn;
+my $status;
+my $allow;
 my $message;
 my $first_letter;
 my $second_letter;
+my $last_letter;
 my $intf_state;
 my $intf_pass;
 my $intf_pause;
 my $intf_mode;
-my $story_path;
-my $story_file;
+my $fh;
 my $story_lock;
-my @storylines;
+my @story_lines;
 my $ong_state;
 my $page;
-my $cmd_clear;
-my $cmd_clear_all;
+my $password;
+my $password_ok;
 
 
 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
@@ -123,48 +108,47 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
        $method = $1;
 }
-else{
-       exit fail_method($ENV{'REQUEST_METHOD'}, 'GET, POST, HEAD');
+else {
+       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'});
        }
 }
+
 $IP = get_remote_addr();
-if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
-       $page=int($1);
-}
-else {
-       $page=0;
-}
+$page = get_id(\%cgi);
+$password = get_password(\%cgi);
 if ($cgi{'words'} ne '') {
-       $words=$cgi{'words'};
+       $words = $cgi{'words'};
 }
 
-%settings = read_data_file(DATA_SETTINGS_PATH());
-%state    = read_data_file(DATA_STATE_PATH());
+%settings = read_settings();
+%state    = read_state();
 $ong_state = int($state{'state'});
-$cmd_clear     = settings{'password'}.' clear';
-$cmd_clear_all = settings{'password'}.' clearall';
+
+$password_ok = ($password eq $settings{'password'});
+if ($password_ok) {
+       $IP .= ' OK';
+}
 
 $story_lock=0;
-if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
+if (open_encoded($fh, "+<:encoding(UTF-8)", DATA_STORY_PATH())) {
        $story_lock=1;
-       if (flock($story_file,2)) {
+       if (flock($fh,2)) {
                $story_lock=2;
        }
-       %story = $read_data_file($story_file);
+       %story = read_story($fh);
        
        if ($story{'lastip'} =~ /^.+$/) {
                $last_IP=$&;
@@ -177,26 +161,26 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
        $story_id   = int($story{'id'});
        $intf_pass  = int($story{'pass'});
        $intf_state = int($story{'state'});
-       $intf_mode  = $intf_state;
-       $intf_pause = $intf_state & 0x01;
-       if ($intf_pause) {
-               $intf_mode &= 0xFE;
-       }
+       $intf_mode  = $intf_state & INTF_STATE->{'mode'};
+       $intf_pause = $intf_state & INTF_STATE->{'||'};
        
        if ($IP ne $last_IP) {
                $turn = 1;
        }
        else {
-               $turn = 0       ;
+               $turn = 0;
        }
        
        if (
-               ($words eq $cmd_clear) ||
-               ($words eq $cmd_clear_all) ||
-               ($intf_state < 0)
+               ($intf_state < 0) || (
+                       ($method eq 'POST') && (
+                               ($cgi{'clear'} ne '') || 
+                               ($cgi{'clear_all'} ne '')
+                       )
+               )
        ) {
                if (
-                       ($words eq $cmd_clear_all) ||
+                       ($cgi{'clear_all'} ne '') ||
                        ($intf_state < -1)
                ) {
                        $story{'id'} = 0;
@@ -207,436 +191,486 @@ if (open ($story_file,"+<:encoding(UTF-8)",DATA_STORY_PATH())){
                $story{'pass'   } = 0;
                $story{'state'  } = INTF_STATE->{'X'};
                $turn = 0;
-               if($ong_state == STATE->{'inactive'}) {
-                       writeindex(WWW_INDEX_PATH,0,0,0);  # TO REPLACE
+               if ($ong_state == STATE->{'inactive'}) {
+                       write_index(
+                               \%state,
+                               \%settings,
+                               $story{'pass'},
+                               $story{'state'},
+                               0 # pause
+                       );
                }
-               write_data_file($story_file, '', '', \%story);
+               write_story($fh, \%story);
        }
        
-       if ($words ne '') {
-               if (!$turn) {
+       if (($words ne '') && ($method eq 'POST')) {
+               if (
+                       (!$turn) &&
+                       (!$password_ok)
+               ) {
+                       $status = HTTP_STATUS->{'forbidden'};
                        $message = "It's not your turn.";
                }
                # TODO: consider allowing non-ASCII letters in words.
                # (not very important in English language)
-               elsif ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
+               elsif (
+                       ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) ||
+                       ($password_ok && ($words ne ''))
+               ) {
+                       # we have 2 words
                        $first_letter  = lc(substr($2, 0, 1));
                        $second_letter = lc(substr($4, 0, 1));
                        if (
                                ($first_letter ne $last_letter) &&
-                               ($last_letter ne '')
+                               ($last_letter ne '') && 
+                               (!$password_ok)
                        ) {
+                               $status = HTTP_STATUS->{'bad_request'};
                                $message = 'The first word must start with '.uc($last_letter).'.';
                        }
-                       elsif ($first_letter eq $second_letter) {
+                       elsif (
+                               ($first_letter eq $second_letter) &&
+                               (!$password_ok)
+                       ) {
+                               $status = HTTP_STATUS->{'bad_request'};
                                $message = 'The second word can\'t also start with '.uc($first_letter).'.';
                        }
                        else {
+                               # words are valid
+                               # update state
                                $story{'content'} = $story{'content'} . $words."\n";
                                $turn = 0;
                                $story{'lastip'} = $IP;
                                $story{'letter'} = $second_letter;
                                
                                if ($cgi{'next'} ne '') {
-                                       if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
-                                               $story_path = DATA_STORY_PATH.$story_id;
-                                               write_data_file($story_path, '', '', \%story);
+                                       # start next game
+                                       if (
+                                               $password_ok ||
+                                               (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1))
+                                       ) {
+                                               # store finished game
+                                               write_story($story_id, \%story);
+                                               # init new game
                                                $new_story{'id'     } = $story_id + 1;
                                                $new_story{'letter' } = '';
                                                $new_story{'lastip' } = $IP;
                                                $new_story{'content'} = '';
                                                $new_story{'pass'   } = 0;
                                                $new_story{'state'  } = INTF_STATE->{'X'};
+                                               # reset hidden interface
                                                $intf_state = INTF_STATE->{'X'};
                                                $intf_pass = 0;
                                                $intf_mode = INTF_STATE->{'X'};
                                                $intf_pause= 0;
                                                if($ong_state == STATE->{'inactive'}) {
-                                                       writeindex(WWW_INDEX_PATH,0,0,0); # TO REPLACE
+                                                       # ONG not activated yet; reset index
+                                                       write_index(
+                                                               \%state,
+                                                               \%settings,
+                                                               $intf_pass,
+                                                               $intf_mode,
+                                                               $intf_pause
+                                                       );
                                                }
-                                               write_data_file($story_file, '', '', \%new_story);
+                                               # save new game
+                                               write_story($fh, \%new_story);
                                        }
                                        else {
                                                $message = 'To early to finish this wordgame.';
-                                               write_data_file($story_file, '', '', \%story);
+                                               write_story($fh, \%story);
                                        }
                                }
                                else {
+                                       # continue the game
                                        if ($intf_pass == 1) {
+                                               # hidden interface was already active; deactivate
                                                $intf_pass = 2;
                                                $story{'pass'} = 2;
                                                if($ong_state == STATE->{'inactive'}) {
-                                                       writeindex(WWW_INDEX_PATH,2,0,0); # TO REPLACE
+                                                       write_index(
+                                                               \%state,
+                                                               \%settings,
+                                                               $intf_pass,
+                                                               $intf_mode,
+                                                               $intf_pause
+                                                       );
                                                }
                                        }
                                        elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
+                                               # correct password for the hidden interface!
                                                if ($intf_pass != 0) {
                                                        $message = 'The password has already been used in this story.';
                                                }
                                                elsif ($ong_state != STATE->{'inactive'}) {
+                                                       # ONG already active, nothing to do here
                                                        $message = "???";
                                                }
                                                else {
-                                                       my %frame_data = read_data_file(DATA_PATH.0);
-                                                       my %default = read_data_file(DATA_DEFAULT_PATH());
-                                                       my $in_path;
-                                                       my $out_path;
-                                                       
-                                                       $frame_data{'ongtime'} = $time;
-                                                       $goto_list{'title-0'} = $frame_data{'title'};
-                                                       $goto_list{'ongtime-0'} = $frame_data{'ongtime'};
-                                                       writedatafile(DATA_PATH.0,%frame_data);
-                                                       writedatafile(DATA_LIST_PATH,%goto_list);
+                                                       # ready to activate?
+                                                       my $r;
                                                        
-                                                       foreach my $ind (keys %default) {
-                                                               unless(defined($frame_data{$ind})){
-                                                                       $frame_data{$ind}=$default{$ind};
-                                                               }
+                                                       # ONG tape interface
+                                                       $r = ong(
+                                                               'i',   # ID: tape interface
+                                                               $time, # ONG time;    not relevant
+                                                               0,     # timer;       not relevant
+                                                               0,     # update;      not relevant
+                                                               0,     # print
+                                                               \%settings,         # not relevant
+                                                               '',    # %default;    not relevant
+                                                               '',    # %frame_data; not relevant
+                                                               ''     # $goto_list;  not relevant
+                                                       );
+                                                       if ($r) {
+                                                               # ONG CFRT
+                                                               $r = ong(
+                                                                       'c',   # ID: CFRT
+                                                                       $time, # ONG time;   not relevant
+                                                                       0,     # timer;      not relevant
+                                                                       0,     # update;     not relevant
+                                                                       0,     # print
+                                                                       \%settings,
+                                                                       '',    # %default
+                                                                       '',    # %frame_data
+                                                                       ''     # $goto_list; not relevant
+                                                               );
                                                        }
-                                                       
-                                                       $inpath = DATA_PATH.sprintf($settings{'frame'},0,$frame_data{'ext'});
-                                                       $outpath = WWW_PATH.sprintf($settings{'frame'},0,$frame_data{'ext'});
-                                                       
-                                                       if(copy ($inpath, $outpath)) {
+                                                       if ($r) {
+                                                               # ONG frame 0!
+                                                               $r = ong(
+                                                                       0,     # frame ID
+                                                                       $time, # ONG time; might get overwritten later
+                                                                       0,     # timer
+                                                                       0,     # update
+                                                                       0,     # print
+                                                                       \%settings,
+                                                                       '',    # %default
+                                                                       '',    # %frame_data
+                                                                       ''     # $goto_list
+                                                               );
+                                                       }
+                                                       if($r) {
+                                                               # new state of hidden interface
                                                                $intf_pass = 1;
-                                                               $intf_state = 0;
-                                                               $intf_mode=0;
-                                                               $intf_pause=0;
-                                                               $story{'pass'} = '1';
-                                                               $story{'state'} = '0';
-                                                               writeindex(WWW_INDEX_PATH,1,0,0);
+                                                               $intf_state = INTF_STATE->{'X'};
+                                                               $intf_mode  = INTF_STATE->{'X'};
+                                                               $intf_pause = 0;
+                                                               $story{'pass'} = 1;
+                                                               $story{'state'} = INTF_STATE->{'X'};
+                                                               write_index(
+                                                                       \%state,
+                                                                       \%settings,
+                                                                       $intf_pass,
+                                                                       $intf_mode,
+                                                                       $intf_pause
+                                                               );
                                                        }
                                                }
                                        }
-                                       writedatafile($story_file,%story);
+                                       write_story($fh, \%story);
                                }
                        }
-                       else {
-                               $message = 'Please, two words, not more, not less (some punctuation is allowed).';
-                       }
-               }
-       }
-       elsif (($cgi{'s'} ne '') && ($intf_pass==1) && ($ong_state == 0)) {
-               $intf_state = int($cgi{'s'});
-               if($intf_state > 63 || $intf_state <0) {
-                       $intf_state = 0;
                }
-               $intf_mode = $intf_state;
-               $intf_pause = $intf_state & 1;
-               if ($intf_pause) {
-                       $intf_mode -= 1;
+               else {
+                       $status = HTTP_STATUS->{'bad_request'};
+                       $message = 'Please, two words, not more, not less (some punctuation is allowed).';
                }
+       }
+       elsif (
+               ($cgi{'s'} ne '') &&
+               ($intf_pass == 1) &&
+               ($ong_state == STATE->{'inactive'})
+       ) {
+               $intf_state = int($cgi{'s'}) & INTF_STATE->{'mask'};
+               $intf_mode  = $intf_state & INTF_STATE->{'mode'};
+               $intf_pause = $intf_state & INTF_STATE->{'||'};
                $story{'state'} = $intf_state;
-               writeindex(WWW_INDEX_PATH,1,$intf_mode,$intf_pause);
-               writedatafile($story_file,%story);
+               write_index(
+                       \%state,
+                       \%settings,
+                       $intf_pass,
+                       $intf_mode,
+                       $intf_pause
+               );
+               write_story($fh, \%story);
        }
-       @storylines = split(/\r?\n/,$story{'content'});
-       if(@storylines & 1) {
+       @story_lines = split(/\r?\n/, $story{'content'});
+       if(@story_lines & 1) {
                $turn = !$turn;
        }
        
-       close($story_file);
+       close($fh);
+}
+
+if ($status ne '') {
+       print http_header_status($status);
 }
+if ($allow ne '') {
+       print http_header_allow($allow);
+}
+print "Content-type: text/html; charset=UTF-8\n\n";
 
-print "Content-type: text/html\n\n";
 if($method eq 'HEAD') {
        exit;
 }
 
-print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
-print '<html lang="en"><head>'."\n";
-print '<title>Two words &bull; '.WEBSITE_NAME.'</title>'."\n";
-print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
-print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
-print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
-print '</head><body>'."\n";
-print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
-print '<div id="all">'."\n";
+my $max_page = int(($story_id + PAGE_LENGTH - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH);
+my $newer_available = ($page > 0);
+my $older_available = ($page < $max_page);
+my $show_intf = ($intf_pass == 1) && ($ong_state == STATE->{'inactive'});
+my $id_start = 
+       $story_id-1 -(
+               ($page == 0)    ? 0 : (
+                       (($page-1) * PAGE_LENGTH ) + FIRSTPAGE_LENGTH
+               )
+       );
+my $id_stop = $story_id-1 - (($page*PAGE_LENGTH) + FIRSTPAGE_LENGTH);
+if ($id_stop < 0) {
+       $id_stop = -1;
+}
 
-print '<div id="inst" class="ins">'."\n";
+my $bsta_url = CGI_PATH;
+my $twowords_url = CGI_2WORDS_PATH;
+my $newer_url;
+my $older_url;
+my $oldest_url;
+my $newest_url = merge_url(
+       {'path' => $twowords_url},
+       {'path' => 0}
+);
+if ($newer_available) {
+       $newer_url = merge_url(
+               {'path' => $twowords_url},
+               {'path' => $page-1}
+       );
+}
+if ($older_available) {
+       $older_url = merge_url(
+               {'path' => $twowords_url},
+               {'path' => $page+1}
+       );
+       $oldest_url = merge_url(
+               {'path' => $twowords_url},
+               {'path' => $max_page}
+       );
+}
+my $button_4_url = merge_url(
+       {'path' => $twowords_url},
+       {'query' => {'s' => (INTF_STATE->{'>'} | $intf_pause)}}
+);
+my $button_3_url = merge_url(
+       {'path' => $twowords_url},
+       {'query' => {'s' => (INTF_STATE->{'<<'} | $intf_pause)}}
+);
+my $button_2_url = merge_url(
+       {'path' => $twowords_url},
+       {'query' => {'s' => (INTF_STATE->{'>>'} | $intf_pause)}}
+);
+my $button_1_url = merge_url(
+       {'path' => $twowords_url},
+       {'query' => {'s' => INTF_STATE->{'X'}}}
+);
+my $button_0_url = merge_url(
+       {'path' => $twowords_url},
+       {'query' => {'s' => ($intf_pause ? $intf_mode : ($intf_mode | INTF_STATE->{'||'}))}}
+);
+my $button_5_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-20.gif'}
+);
+my $button_4_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-10'.(($intf_mode == INTF_STATE->{'>'}) ? '_' : '').'.gif'}
+);
+my $button_3_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-08'.(($intf_mode == INTF_STATE->{'<<'}) ? '_' : '').'.gif'}
+);
+my $button_2_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-04'.(($intf_mode == INTF_STATE->{'>>'}) ? '_' : '').'.gif'}
+);
+my $button_1_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-02.gif'}
+);
+my $button_0_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-01'.($intf_pause ? '_' : '').'.gif'}
+);
+my $intf_img_id = '';
+if ($intf_state == INTF_STATE->{'>'}) {
+       $intf_img_id = '_10'
+}
+elsif ($intf_mode == INTF_STATE->{'<<'}) {
+       $intf_img_id = '_08'
+}
+elsif ($intf_mode == INTF_STATE->{'>>'}) {
+       $intf_img_id = '_04'
+}
+my $intf_img = merge_url(
+       {'path' => CGI_PATH()},
+       {'path' => 'intf-00'.$intf_img_id.'.gif'}
+);
 
-print '<div id="title">'."\n";
-print '<H1 id="titletext">Two words</H1>'."\n";
-print '</div>'."\n";
+if ($password_ok) {
+       my $password_query = url_query_encode({'p', $settings{'password'}});
+       $twowords_url = merge_url($twowords_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $newest_url   = merge_url($newest_url   , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $newer_url    = merge_url($newer_url    , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $older_url    = merge_url($older_url    , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $button_4_url = merge_url($button_4_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $button_3_url = merge_url($button_3_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $button_2_url = merge_url($button_2_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $button_1_url = merge_url($button_1_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+       $button_0_url = merge_url($button_0_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
+}
+
+my $_password = $password_ok ? html_entity_encode_dec($settings{'password'}, 1): '';
+my $_bsta_url     = html_entity_encode_dec($bsta_url     , 1);
+my $_twowords_url = html_entity_encode_dec($twowords_url , 1);
+my $_newest_url   = html_entity_encode_dec($newest_url   , 1);
+my $_newer_url    = html_entity_encode_dec($newer_url    , 1);
+my $_older_url    = html_entity_encode_dec($older_url    , 1);
+my $_oldest_url   = html_entity_encode_dec($oldest_url   , 1);
+my $_button_4_url = html_entity_encode_dec($button_4_url , 1);
+my $_button_3_url = html_entity_encode_dec($button_3_url , 1);
+my $_button_2_url = html_entity_encode_dec($button_2_url , 1);
+my $_button_1_url = html_entity_encode_dec($button_1_url , 1);
+my $_button_0_url = html_entity_encode_dec($button_0_url , 1);
+my $_button_5_img = html_entity_encode_dec($button_5_img , 1);
+my $_button_4_img = html_entity_encode_dec($button_4_img , 1);
+my $_button_3_img = html_entity_encode_dec($button_3_img , 1);
+my $_button_2_img = html_entity_encode_dec($button_2_img , 1);
+my $_button_1_img = html_entity_encode_dec($button_1_img , 1);
+my $_button_0_img = html_entity_encode_dec($button_0_img , 1);
+my $_intf_img     = html_entity_encode_dec($intf_img     , 1);
+my $_message      = html_entity_encode_dec($message      , 1);
+my $_website_name = html_entity_encode_dec(WEBSITE_NAME(), 1);
+
+print_html_start(\*STDOUT);
+print_html_head_start(\*STDOUT);
+
+
+print '  <title>Two words &bull; '.$_website_name.'</title>'."\n";
+print '  <link rel="start" href="'.$_oldest_url.'">'."\n";
+if ($older_available) {
+       print '  <link rel="prev" href="'.$_older_url.'">'."\n";
+}
+if ($newer_available) {
+       print '  <link rel="next" href="'.$_newer_url.'">'."\n";
+}
+
+print_html_head_end(\*STDOUT);
+print_html_body_start(\*STDOUT);
+
+print '   <div id="inst" class="ins">'."\n";
+
+print '    <div id="title">'."\n";
+print '     <h1 id="titletext">Two words</h1>'."\n";
+print '    </div>'."\n";
 
 if ($page == 0) {
-       print '<div id="storypuzzle">'."\n";
-       for (my $i = 0; $i < @storylines; ++$i){
-               print '<span class="'.($turn?'ni':'br').'">'.entityencode($storylines[$i]).'</span>'."\n";
+       print '    <div id="storypuzzle">'."\n";
+       for (my $i = 0; $i < @story_lines; ++$i) {
+               print '     <span class="'.($turn ? 'ni':'br').'">'.html_entity_encode_dec($story_lines[$i], 1).'</span>'."\n";
                $turn = !$turn;
        }
-       print '</div>'."\n";
+       print '    </div>'."\n";
 
-       print '<div id="command">'."\n";
+       print '    <div id="command">'."\n";
        if ($message ne '') {
-               print '<span class="br">'.entityencode($message).'</span>'."\n";
+               print '     <span class="br">'.$_message.'</span>'."\n";
        }
        
-       if ($turn) {
-               print '<form method="post" action="'.CGI_2WORDS_PATH.'">'."\n";
+       if ($turn || $password_ok) {
+               print '     <form method="post" action="'.$_twowords_url.'">'."\n";
                if ($message eq '') {
                        if ($story{"content"} eq '') {
-                               print 'Two words, please:<br>'."\n";
+                               print '      Two words, please:<br>'."\n";
                        }
                        else {
-                               print 'Please continue, two words:<br>'."\n";
+                               print '      Please continue, two words:<br>'."\n";
                        }
                }
-               print '<input class="intx" type="text" name="words">'."\n";
-               print '<input class="inbt" type="submit" value="enter">'."\n";
-               if(@storylines >= (STORY_LENGTH-1)) {
-                       print '<input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
+               print '      <input class="intx" type="text" name="words">'."\n";
+               print '      <input class="inbt" type="submit" value="enter">'."\n";
+               if ((@story_lines >= (STORY_LENGTH-1)) || $password_ok ) {
+                       print '      <input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
+               }
+               if ($password_ok) {
+                       print '      <input class="inbt" type="submit" name="clear" value="clear">'."\n";
+                       print '      <input class="inbt" type="submit" name="clear_all" value="clear all">'."\n";
+                       print '      <input type="hidden" name="p" value="'.$_password.'">'."\n";
                }
-               print '</form>'."\n";
+               print '     </form>'."\n";
        }
        else {
                if ($message eq '') {
-                       print 'Wait for it.'."\n";
+                       print '     Wait for it.'."\n";
                }
        }
-       print '</div>'."\n";
+       print '    </div>'."\n";
 }
 elsif ($message ne '') {
-       print '<div id="command">'."\n";
-       print '<span class="br">'.entityencode($message).'</span>'."\n";
-       print '</div>'."\n";
+       print '    <div id="command">'."\n";
+       print '     <span class="br">'.$_message.'</span>'."\n";
+       print '    </div>'."\n";
 }
+print '   </div>'."\n";
 
-if(($intf_pass == 1) && ($ong_state == 0)) {
-       print '</div><div id="framespace">'."\n";
-       print '<table id="intftable" cellspacing="0" cellpadding="0"><tr class="intf">'."\n";
+if ($show_intf) {
+       print '   <div id="framespace">'."\n";
+       print '    <table id="intftable" cellspacing="0" cellpadding="0">'."\n";
+       print '     <tr class="intf">'."\n";
+       print '      <td colspan="6" class="intf"><img src="'.$_intf_img.'" alt="" class="intf"></td>'."\n";
+       print '     </tr>'."\n";
        
-       print '<td colspan="6" class="intf"><img src="'.CGI_PATH.'intf-00';
-       if ($intf_mode == 4) {
-               print '_04';
-       }
-       elsif ($intf_mode == 8) {
-               print '_08';
-       }
-       elsif ($intf_state == 16) {
-               print '_10';
-       }       
-       print'.gif" alt="" class="intf"></td>'."\n";
-       
-       print '</tr><tr class="intf">'."\n";
-       print '<td class="intf"><img src="'.CGI_PATH.'intf-20.gif" alt="o" class="intf"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?17:16).'"><img src="'.CGI_PATH.'intf-10'.(($intf_mode==16)?'_':'').'.gif" class="intf" alt="&gt"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?9:8).'"><img src="'.CGI_PATH.'intf-08'.(($intf_mode==8)?'_':'').'.gif" class="intf" alt="&lt;&lt;"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?5:4).'"><img src="'.CGI_PATH.'intf-04'.(($intf_mode==4)?'_':'').'.gif" class="intf" alt="&gt;&gt;"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?0:0).'"><img src="'.CGI_PATH.'intf-02.gif" class="intf" alt="^"></td>'."\n";
-       print '<td class="intf"><a href="'.CGI_PATH.'2words?s='.($intf_pause?$intf_mode:$intf_mode+1).'"><img src="'.CGI_PATH.'intf-01'.($intf_pause?'_':'').'.gif" class="intf" alt="||"></td>'."\n";
-       print '</tr></table>'."\n";
+       print '     <tr class="intf">'."\n";
+       print '      <td class="intf"><img src="'.$_button_5_img.'" alt="o" class="intf"></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_4_url.'"><img src="'.$_button_4_img.'" class="intf" alt="&gt;"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_3_url.'"><img src="'.$_button_3_img.'" class="intf" alt="&lt;&lt;"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_2_url.'"><img src="'.$_button_2_img.'" class="intf" alt="&gt;&gt;"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_1_url.'"><img src="'.$_button_1_img.'" class="intf" alt="^"></a></td>'."\n";
+       print '      <td class="intf"><a href="'.$_button_0_url.'"><img src="'.$_button_0_img.'" class="intf" alt="||"></a></td>'."\n";
+       print '     </tr>'."\n";
+       print '    </table>'."\n";
+       print '   </div>'."\n";
 }
 
-print '</div><div id="insb" class="ins">'."\n";
-print '<div id="undertext">'."\n";
-for (my $i = $story_id-1-(($page!=0)?((($page-1)*PAGE_LENGTH)+FIRSTPAGE_LENGTH):0); $i > ($story_id-1-($page*PAGE_LENGTH)- FIRSTPAGE_LENGTH) && $i >= 0; --$i) {
-       $story_path = DATA_STORY_PATH.$i;
-       %new_story = readdatafile($story_path);
-       print '<p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.entityencode($new_story{'content'}).'</p>'."\n";
+print '   <div id="insb" class="ins">'."\n";
+
+print '    <div id="undertext">'."\n";
+for (my $i = $id_start; $i > $id_stop; --$i) {
+       %new_story = read_story($i);
+       print '     <p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.html_entity_encode_dec($new_story{'content'}).'</p>'."\n";
 }
+print '    </div>'."\n";
 
-print '</div>'."\n";
-print '<div id="underlinks">'."\n";
-print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_2WORDS_PATH.'">Once again</a>';
-if(($story_id - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
-       print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page+1).'">Before</a>';
+print '    <div id="underlinks">'."\n";
+print '     <a href="'.$_bsta_url.'">BSTA</a> |'."\n";
+print '     <a href="'.$_twowords_url.'">Once again</a>';
+if ($older_available) {
+       print ' |'."\n";
+       print '     <a href="'.$_older_url.'">Before</a>';
 }
-if($page > 0) {
-       print ' | <a href="'.CGI_2WORDS_PATH.'/'.($page-1).'">Unbefore</a>';
+if ($newer_available) {
+       print ' |'."\n";
+       print '     <a href="'.$newer_url.'">Unbefore</a>';
 }
-if(($story_id - ($page*PAGE_LENGTH)) - FIRSTPAGE_LENGTH > 0) {
-       print ' | <a href="'.CGI_2WORDS_PATH.'/'.(int(($story_id - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1).'">Initially</a>';
+if ($older_available) {
+       print ' |'."\n";
+       print '<a href="'.$_oldest_url.'">Initially</a>';
 }
 if($turn) {
-       print ' | (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
+       print ' |'."\n";
+       print '     (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
 }
 print "\n";
-print '</div>'."\n";
-
-print '</div>'."\n";
-print '</div>'."\n";
-print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
-print '</body></html>'."\n";
-
-sub writeindex {
-       (my $indexpath, my $pass, my $mode, my $pause) = @_;
-       my $indexfile;
-       my $indexof;
-               
-       if(ref($indexpath)) {
-               $indexfile=$indexpath;
-               unless (seek($indexfile, 0, 0)) {
-                       return 0;
-               }
-       }
-       else {
-               unless (open ($indexfile, ">", $indexpath)) {
-                       return 0;
-               }
-       }
-       
-       $indexof = CGI_PATH;
-       $indexof =~ s/\/$//g;
-       
-       my %coin = readdatafile(DATA_COIN_PATH);
-       
-       if ($pass != 1) {
-               print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'."\n";
-               print $indexfile '<html>'."\n";
-               print $indexfile ' <head>'."\n";
-               print $indexfile '  <title>Index of '.$indexof.'</title>'."\n";
-               print $indexfile ' </head>'."\n";
-               print $indexfile ' <body>'."\n";
-               print $indexfile '<h1>Index of '.$indexof.'</h1>'."\n";
-               print $indexfile '<table><tr><th><img src="/icons/blank.gif" alt="[ICO]"></th><th><a href="?C=N;O=D">Name</a></th><th><a href="?C=M;O=A">Last modified</a></th><th><a href="?C=S;O=A">Size</a></th><th><a href="?C=D;O=A">Description</a></th></tr><tr><th colspan="5"><hr></th></tr>'."\n";
-               print $indexfile '<tr><td valign="top"><img src="/icons/back.gif" alt="[DIR]"></td><td><a href="/">Parent Directory</a></td><td>&nbsp;</td><td align="right">  - </td><td>&nbsp;</td></tr>'."\n";
-               print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="2words/">2words/</a></td><td align="right">'.INTF_DATE.'  </td><td align="right">  - </td><td>&nbsp;</td></tr>'."\n";
-               print $indexfile '<tr><td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td><td><a href="coin/">coin/</a></td><td align="right">'.COIN_DATE.'  </td><td align="right">  - </td><td> Coincidence </td></tr>'."\n";
-               print $indexfile '<tr><th colspan="5"><hr></th></tr>'."\n";
-               print $indexfile '</table>'."\n";
-               print $indexfile '<address>Apache/2.2.22 (Debian) Server at '.WEBSITE.' Port 80</address>'."\n";
-               print $indexfile '</body></html>'."\n";
-       }
-       else {
-               my %framedata;
-               my %nextframedata;
-               my %default;
-               
-               %framedata = readdatafile(DATA_PATH.0);
-               %nextframedata = readdatafile(DATA_PATH.1);
-               %default=readdatafile(DATA_DEFAULT_PATH);
-               
-               # if($mode == 16 && $pause) {
-                       # $framedata{'ongtime'} = $time;
-                       # writedatafile(DATA_PATH.0,%framedata);
-               # }
-               
-               foreach my $ind (keys %default) {
-                       unless(defined($framedata{$ind})){
-                               $framedata{$ind}=$default{$ind};
-                       }
-                       unless(defined($nextframedata{$ind})){
-                               $nextframedata{$ind}=$default{$ind};
-                       }
-               }
-               
-               
-               print $indexfile '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
-               print $indexfile '<html lang="en"><head>'."\n";
-               print $indexfile '<title>';
-               if($mode == 8 || $mode == 4) {
-                       print $indexfile 'Index of';
-               }
-               elsif($mode == 16) {
-                       print $indexfile entityencode($settings{'story'});
-                       if($pause) {
-                               print $indexfile ' &bull; '.WEBSITE_NAME;
-                       }
-               }
-               else {
-                       print $indexfile 'Index of '.$indexof;
-               }
-               print $indexfile '</title>'."\n";
-               print $indexfile '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
-               print $indexfile '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
-               print $indexfile '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
-               print $indexfile '</head><body>'."\n";
-               print $indexfile '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
-               print $indexfile '<div id="all">'."\n";
-               
-               print $indexfile '<div id="inst" class="ins">'."\n";
-               
-               print $indexfile '<div id="title">'."\n";
-               print $indexfile '<H1 id="titletext">';
-               if($mode == 8 || $mode == 4) {
-                       print $indexfile 'Index of';
-               }
-               elsif($mode == 16) {
-                       print $indexfile entityencode($settings{'story'});
-               }
-               else {
-                       print $indexfile 'Index of '.$indexof;
-               }
-               print $indexfile '</H1>'."\n";
-               print $indexfile '</div>'."\n";
-               
-               print $indexfile '</div><div id="framespace">'."\n";
-               print $indexfile '<img src="'.CGI_PATH;
-               if($mode == 8) {
-                       print $indexfile 'intf-ll.gif'
-               }
-               elsif($mode == 4) {
-                       print $indexfile 'intf-pp.gif'
-               }
-               elsif($mode == 16) {
-                       if($pause) {
-                               print $indexfile sprintf($settings{'frame'},0,$framedata{'ext'}).'" title="'.entityencode($framedata{'title'});
-                       }
-                       else {
-                               print $indexfile 'intf-tr.gif'
-                       }
-               }
-               else {
-                       print $indexfile 'intf-kw.gif'
-               }
-               print $indexfile '" alt="0" id="frame">'."\n";
-               
-               print $indexfile '</div><div id="insb" class="ins">'."\n";
-               print $indexfile '<div id="undertext">'."\n";
-               if($mode == 8 || $mode == 4) {
-                       print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br><img src="/icons/folder.gif" alt="[DIR]"> <a href="#">yyyyb/</a>'."\n";
-               }
-               elsif ($mode == 16) {
-                       if($pause) {
-                               print $indexfile bb2ht($framedata{'content'})."\n";
-                       }
-                       else {
-                               print $indexfile "...\n";
-                       }
-               }
-               else {
-                       print $indexfile '<img src="/icons/back.gif" alt="[DIR]"> <a href="/">Parent Directory</a><br>'."\n";
-                       print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="2words/">2words/</a> '.INTF_DATE.' - <br>'."\n";
-                       print $indexfile '<img src="/icons/folder.gif" alt="[DIR]"> <a href="coin/">coin/</a> '.COIN_DATE.' - '.entityencode($coin{'server'})."\n";
-               }
-               print $indexfile '</div>'."\n";
-               
-               print $indexfile '<div id="command">'."\n";
-               if ($mode == 8) {
-                       print $indexfile '[<span class="br">EE</span>:<span class="br">EE</span>:<span class="br">EE</span>]'."\n";
-               }
-               elsif ($mode == 4) {
-                       print $indexfile '[<span class="ni">EE</span>:<span class="ni">EE</span>:<span class="ni">EE</span>]'."\n";
-               }
-               elsif ($mode == 16) {
-                       if($pause) {
-                               print $indexfile '[<span class="br">00</span>:<span class="br">00</span>:<span class="br">00</span>]<br>'."\n";
-                               print $indexfile '&gt;<a href="'.CGI_VIEWER_PATH.'/1">'.entityencode($nextframedata{'title'}).'</a>'."\n";
-                       }
-                       else {
-                               print $indexfile '[<span class="ni">--</span>:<span class="ni">--</span>:<span class="ni">--</span>]<br>'."\n";
-                               print $indexfile '&gt;...<span class="inp">_</span>'."\n";
-                       }
-               }
-               else {
-               }
-               print $indexfile '</div>'."\n";
-               
-               print $indexfile '</div>'."\n";
-               
-               print $indexfile '</div>'."\n";
-               print $indexfile '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
-               
-               print $indexfile '</body></html>'."\n";
-       }
-       
-       unless (ref($indexpath)) {
-               close ($indexfile);
-       }
-       else {
-               truncate ($indexfile , tell($indexfile));
-       }
-       
-       return 1;
-}
+print '    </div>'."\n";
+
+print '   </div>'."\n";
+
+print_html_body_end(\*STDOUT, $ong_state == STATE->{'inactive'});
+print_html_end(\*STDOUT);