]> bicyclesonthemoon.info Git - ott/bsta/blobdiff - bsta_lib.1.pm
input validation; goto form; show version; 2 words password
[ott/bsta] / bsta_lib.1.pm
index 7b8c37d5c45dee09ec0db16061a8bf4af51fe7e1..7545a90440d9e89b3cd94603a63f1ae2c2189fcc 100644 (file)
@@ -19,7 +19,6 @@
 
 # TODO: FQ NBSP ?
 # TODO: DEBUG
-# TODO: timer JS
 # TODO: BB & INFO indent
 
 package bsta_lib;
@@ -47,6 +46,14 @@ our @EXPORT_OK   = (
        'print_html_body_start', 'print_html_body_end',
        'print_viewer_page', 'print_goto',
        'write_index', 'write_static_viewer_page', 'write_static_goto',
+       'get_frame_file', 'get_page_file',
+       'read_frame_data', 'write_frame_data', 'read_default', 'read_noaccess',
+       'read_state', 'write_state',
+       'read_words_list', 'write_words_list', 'read_words', 'write_words',
+       'read_story', 'write_story',
+       'read_goto', 'write_goto',
+       'read_chat', 'write_chat',
+       'read_settings', 'read_attachment', 'read_coincidence',
        'ong',
        'eval_bb', 'bb_to_bbcode', 'bb_to_html'
 );
@@ -72,6 +79,7 @@ use botm_common (
 ###PERL_CGI_ATTACH_PATH:    CGI_ATTACH_PATH    = /bsta/a
 ###PERL_CGI_2WORDS_PATH:    CGI_2WORDS_PATH    = /bsta/2words
 ###PERL_CGI_BBCODE_PATH:    CGI_BBCODE_PATH    = /bsta/b
+###PERL_DATA_CHAT_PATH:     DATA_CHAT_PATH     = /botm/data/bsta/chat
 ###PERL_CGI_COIN_PATH:      CGI_COIN_PATH      = /bsta/coin
 ###PERL_CGI_CSS_PATH:       CGI_CSS_PATH       = /bsta/bsta.css
 ###PERL_CGI_FRAME_PATH:     CGI_FRAME_PATH     = /bsta/f
@@ -91,6 +99,7 @@ use botm_common (
 ###PERL_DATA_NOACCESS_PATH: DATA_NOACCESS_PATH = /botm/data/bsta/noaccess
 ###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_DATA_WORDS_PATH:    DATA_WORDS_PATH    = /botm/data/bsta/words/
 
 ###PERL_WWW_PATH:           WWW_PATH           = /botm/www/
@@ -949,8 +958,7 @@ sub print_goto {
                $ongtime = $goto_list->{'ongtime-'.$frame};
                $title   = $goto_list->{'title-'  .$frame};
                if (($ongtime eq '') && ($title eq '')) {
-                       my $frame_data_path = join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
-                       my %frame_data = read_data_file($frame_data_path);
+                       my %frame_data = read_frame_data($frame);
                        $ongtime = $frame_data{'ongtime'};
                        $title   = $frame_data{'title'};
                        unless (keys %frame_data) {
@@ -1034,6 +1042,7 @@ sub print_viewer_page {
        my $password_ok = $context->{'password_ok'};
        my $static      = $context->{'static'};
        
+       my $goto           = int($context->{'goto'});
        my $frame          = int($context->{'frame'});
        my $text_mode      = int($context->{'text_mode'});
        my $timer_unlocked = int($context->{'timer_unlocked'});
@@ -1137,7 +1146,6 @@ sub print_viewer_page {
        my $password_query;
        
        my $base_url   = CGI_PATH();
-       my $goto_url   = CGI_GOTO_PATH();
        my $timer_url  = CGI_TIMER_PATH();
        my $viewer_full_url = merge_url(
                {'scheme' => SCHEME(), 'host' => WEBSITE()},
@@ -1164,6 +1172,16 @@ sub print_viewer_page {
                {'path' => CGI_VIEWER_PATH()},
                {'path' => ($static ? -1 : $last_frame)}
        );
+       my $goto_url = ($goto) ?
+               CGI_GOTO_PATH() :
+               merge_url(
+                       {'path' => $viewer_url},
+                       {
+                               'query' => {'g' => 1},
+                               'fragment' => 'goto'
+                       }
+               );
+       
        unless ($password_ok) {
                my $page_file;
                $viewer_0_url = $base_url;
@@ -1171,15 +1189,7 @@ sub print_viewer_page {
                        $viewer_prev_url = $viewer_0_url;
                }
                else {
-                       if ($prev_frame_data->{'page'} ne '') {
-                               $page_file = $prev_frame_data->{'page'};
-                       }
-                       else {
-                               $page_file = sprintf(
-                                       $settings->{'frame'},
-                                       $prev_frame, 'htm'
-                               );
-                       }
+                       $page_file = get_page_file($prev_frame, $prev_frame_data, $settings);
                        if (_x_encoded('-f',
                                join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
                        )) {
@@ -1190,15 +1200,7 @@ sub print_viewer_page {
                        }
                }
                if ($next_frame < $last_frame) {
-                       if ($next_frame_data->{'page'} ne '') {
-                               $page_file = $next_frame_data->{'page'};
-                       }
-                       else {
-                               $page_file = sprintf(
-                                       $settings->{'frame'},
-                                       $next_frame, 'htm'
-                               );
-                       }
+                       $page_file = get_page_file($next_frame, $next_frame_data, $settings);
                        if (_x_encoded('-f',
                                join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
                        )) {
@@ -1208,7 +1210,10 @@ sub print_viewer_page {
                                );
                        }
                }
-               if (_x_encoded('-f',WWW_GOTO_PATH())) {
+               if (
+                       $goto &&
+                       (_x_encoded('-f',WWW_GOTO_PATH()))
+               ) {
                        $goto_url = CGI_LIST_PATH();
                }
        }
@@ -1255,15 +1260,7 @@ sub print_viewer_page {
        my $frame_next_url;
        my $frame_normal_url;
        my $frame_full_url;
-       if ($frame_data->{'frame'} ne '') {
-               $frame_file = $frame_data->{'frame'};
-       }
-       else {
-               $frame_file = sprintf(
-                       $settings->{'frame'},
-                       $frame, $frame_data->{'ext'}
-               );
-       }
+       $frame_file = get_frame_file($frame, $frame_data, $settings);
        $frame_normal_url = merge_url(
                        {'path' => CGI_PATH()},
                        {'path' => $frame_file}
@@ -1284,34 +1281,24 @@ sub print_viewer_page {
                        {'path' => $prev_frame}
                );
        }
-       elsif ($prev_frame_data->{'frame'} ne '') {
+       else {
                $frame_prev_url = merge_url(
                        {'path' => CGI_PATH()},
-                       {'path' => $prev_frame_data->{'frame'}}
+                       {'path' => get_frame_file($prev_frame, $prev_frame_data, $settings)}
                );
        }
-       else {
-               $frame_prev_url = merge_url(CGI_PATH(), sprintf(
-                       $settings->{'frame'}, $prev_frame, $prev_frame_data->{'ext'}
-               ));
-       }
        if ($nextframe_indirect) {
                $frame_next_url = merge_url(
                        {'path' => CGI_FRAME_PATH()},
                        {'path' => $next_frame}
                );
        }
-       elsif ($next_frame_data->{'frame'} ne '') {
+       else {
                $frame_next_url = merge_url(
                        {'path' => CGI_PATH()},
-                       {'path' => $next_frame_data->{'frame'}}
+                       {'path' => get_frame_file($next_frame, $next_frame_data, $settings)}
                );
        }
-       else {
-               $frame_next_url = merge_url(CGI_PATH(), sprintf(
-                       $settings->{'frame'}, $next_frame, $next_frame_data->{'ext'}
-               ));
-       }
        
        if ($password_ok) {
                $password_query = url_query_encode({'p', $settings->{'password'}});
@@ -1334,22 +1321,24 @@ sub print_viewer_page {
                        $frame_next_url= merge_url($frame_next_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
                }
        }
-       my $_base_url        = html_entity_encode_dec($base_url       , 1);
-       my $_goto_url        = html_entity_encode_dec($goto_url       , 1);
-       my $_info_url        = html_entity_encode_dec($info_url       , 1);
-       my $_words_url       = html_entity_encode_dec($words_url      , 1);
-       my $_bbcode_url      = html_entity_encode_dec($bbcode_url     , 1);
-       my $_timer_url       = html_entity_encode_dec($timer_url      , 1);
-       my $_viewer_full_url = html_entity_encode_dec($viewer_full_url, 1);
-       my $_viewer_url      = html_entity_encode_dec($viewer_url     , 1);
-       my $_viewer_0_url    = html_entity_encode_dec($viewer_0_url   , 1);
-       my $_viewer_prev_url = html_entity_encode_dec($viewer_prev_url, 1);
-       my $_viewer_next_url = html_entity_encode_dec($viewer_next_url, 1);
-       my $_viewer_last_url = html_entity_encode_dec($viewer_last_url, 1);
-       my $_frame_url       = html_entity_encode_dec($frame_url      , 1);
-       my $_frame_prev_url  = html_entity_encode_dec($frame_prev_url , 1);
-       my $_frame_next_url  = html_entity_encode_dec($frame_next_url , 1);
-       my $_frame_full_url  = html_entity_encode_dec($frame_full_url , 1);
+       my $_password = $password_ok ? html_entity_encode_dec($settings->{'password'}, 1) : '';
+       my $_action_url      = html_entity_encode_dec(CGI_VIEWER_PATH(), 1);
+       my $_base_url        = html_entity_encode_dec($base_url        , 1);
+       my $_goto_url        = html_entity_encode_dec($goto_url        , 1);
+       my $_info_url        = html_entity_encode_dec($info_url        , 1);
+       my $_words_url       = html_entity_encode_dec($words_url       , 1);
+       my $_bbcode_url      = html_entity_encode_dec($bbcode_url      , 1);
+       my $_timer_url       = html_entity_encode_dec($timer_url       , 1);
+       my $_viewer_full_url = html_entity_encode_dec($viewer_full_url , 1);
+       my $_viewer_url      = html_entity_encode_dec($viewer_url      , 1);
+       my $_viewer_0_url    = html_entity_encode_dec($viewer_0_url    , 1);
+       my $_viewer_prev_url = html_entity_encode_dec($viewer_prev_url , 1);
+       my $_viewer_next_url = html_entity_encode_dec($viewer_next_url , 1);
+       my $_viewer_last_url = html_entity_encode_dec($viewer_last_url , 1);
+       my $_frame_url       = html_entity_encode_dec($frame_url       , 1);
+       my $_frame_prev_url  = html_entity_encode_dec($frame_prev_url  , 1);
+       my $_frame_next_url  = html_entity_encode_dec($frame_next_url  , 1);
+       my $_frame_full_url  = html_entity_encode_dec($frame_full_url  , 1);
        
        my $_story      = html_entity_encode_dec($story     , 1);
        my $_title      = html_entity_encode_dec($title     , 1);
@@ -1369,14 +1358,8 @@ sub print_viewer_page {
                        unless (($access) && ($frame < $last_frame)) {
                                $frame_data->{'page'} = '';
                        }
-                       elsif ($frame == 0) {
-                               $frame_data->{'page'} = 'index.htm';
-                       }
                        else {
-                               $frame_data->{'page'} = sprintf(
-                                       $settings->{'frame'},
-                                       $frame, 'htm'
-                               );
+                               $frame_data->{'page'} = get_page_file($frame, $frame_data, $settings);
                        }
                }
        }
@@ -1514,7 +1497,10 @@ sub print_viewer_page {
        }
        print $fh '<a href="'.$_goto_url.'">GOTO</a>'."\n";
        print $fh '     <span style="float: right;">'."\n      ";
-       if ($text_mode == TEXT_MODE->{'normal'}) {
+       if (
+               ($text_mode == TEXT_MODE->{'normal'}) &&
+               (!$goto)
+       ){
                if ($show_words) {
                        print $fh '<a href="'.$_words_url.'">'.$words_link_text.'</a> | ';
                }
@@ -1527,6 +1513,25 @@ sub print_viewer_page {
        print $fh "\n     </span>\n";
        
        print $fh "    </div>\n";
+       
+       if ($goto) {
+               print $fh '    <div class="underlinks" id="goto">'."\n";
+               print $fh '     <form class="goto" method="get" action="'.$_action_url.'">'."\n";
+               print $fh '      GO TO:'."\n";
+               print $fh '      <input class="intx" type="number" size="4" name="f"'.(
+                       ($goto > 1) ?
+                               ('value="'.$frame.'"') :
+                               ''
+                       ).'>'."\n";
+               print $fh '      <input class="inbt" type="submit" value="GO">'."\n";
+               if ($password_ok) {
+                       print $fh '      <input type="hidden" name="p" value="'.$_password.'">'."\n";
+               }
+               print $fh '      <input type="hidden" name="g" value="2">'."\n";
+               print $fh '     </form>'."\n";
+               print $fh "    </div>\n";
+       }
+       
        print $fh "   </div>\n";
        
        if (($text_mode == TEXT_MODE->{'words'}) && $show_words) {
@@ -1597,8 +1602,8 @@ sub print_comments {
                $newer_url = merge_url($older_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
        }
        
+       my $_password = $password_ok ? html_entity_encode_dec($settings->{'password'}, 1) : '';
        my $_post_url = html_entity_encode_dec(CGI_WORDS_PATH(), 1);
-       my $_password = html_entity_encode_dec($settings->{'password'}, 1);
        my $_older_url = html_entity_encode_dec($older_url, 1);
        my $_newer_url = html_entity_encode_dec($newer_url, 1);
        
@@ -1634,8 +1639,7 @@ sub print_comments {
        if ($post_count > 0) {
                for (my $i=$id_start; $i<$id_stop; ++$i) {
                        my $ID = $words_data->{'content'}->[$i];
-                       my $post_path = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $ID);
-                       my %post_data = read_data_file($post_path);
+                       my %post_data = read_words($ID);
                        
                        my $post_time = int($post_data{'posttime'});
                        my $edit_time = int($post_data{'edittime'});
@@ -1786,20 +1790,14 @@ sub write_index {
        
        # normal running story
        if ($ong_state > STATE->{'inactive'}) {
-               my %frame_data     = read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 0));
-               my %next_frame_data= read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 1));
-               my %default        = read_data_file(DATA_DEFAULT_PATH());
-               my %words_data     = read_data_file(
-                       join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), 0),
-                       '', # encoding
-                       0,  # no header
+               my %default        = read_default();
+               my %frame_data     = read_frame_data(0, \%default);
+               my %next_frame_data= read_frame_data(1, \%default);
+               my %words_data     = read_words_list(
+                       0, # frame ID
                        1,  # header only
-                       1,  # as list
                );
                
-               %frame_data     = merge_settings(\%default,      \%frame_data);
-               %next_frame_data= merge_settings(\%default, \%next_frame_data);
-               
                $r = print_viewer_page(
                        $fh,
                        {
@@ -1812,7 +1810,8 @@ sub write_index {
                                'static'         => 1,
                                'show_command'   => 1,
                                'text_mode'      => TEXT_MODE->{'normal'},
-                               'words_page'     => 0 # not relevant
+                               'words_page'     => 0, # not relevant
+                               'goto'           => 0
                        },
                        $state,
                        $settings,
@@ -1874,13 +1873,10 @@ sub write_index {
        }
        # the launch index
        else {
-               my %frame_data     = read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 0));
-               my %next_frame_data= read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 1));
-               my %default        = read_data_file(DATA_DEFAULT_PATH());
-               my %coin_data      = read_data_file(DATA_COIN_PATH());
-               
-               %frame_data     = merge_settings(\%default,      \%frame_data);
-               %next_frame_data= merge_settings(\%default, \%next_frame_data);
+               my %default        = read_default();
+               my %frame_data     = read_frame_data(0, \%default);
+               my %next_frame_data= read_frame_data(1, \%default);
+               my %coin_data      = read_coincidence();
                
                if (($mode == INTF_STATE->{'>'}) && $pause) {
                        $r = print_viewer_page(
@@ -1895,7 +1891,8 @@ sub write_index {
                                        'static'         => 1,
                                        'show_command'   => 1,
                                        'text_mode'      => TEXT_MODE->{'normal'},
-                                       'words_page'     => 0 # not relevant
+                                       'words_page'     => 0, # not relevant
+                                       'goto'           => 0
                                },
                                $state,
                                $settings,
@@ -2051,7 +2048,7 @@ sub write_static_viewer_page {
        
        %state = (ref ($state_ref)) ?
                %$state_ref :
-               read_data_file(DATA_STATE_PATH());
+               read_state();
        my $ong_state = int($state{'state'});
        my $last_frame = int($state{'last'});
        
@@ -2071,55 +2068,38 @@ sub write_static_viewer_page {
        
        %settings = (ref ($settings_ref)) ?
                %$settings_ref :
-               read_data_file(DATA_SETTINGS_PATH());
+               read_settings();
        %default = (ref ($default_ref)) ?
                %$default_ref :
-               read_data_file(DATA_DEFAULT_PATH());
+               read_default();
        
        %frame_data = (ref ($frame_data_ref)) ?
                %$frame_data_ref :
-               read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), $frame));
+               read_frame_data($frame);
        
        %prev_frame_data = (ref ($prev_frame_data_ref)) ?
                %$prev_frame_data_ref : (
                        ($prev_frame >= 0) ?
-                       read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), $prev_frame)) :
+                       read_frame_data($prev_frame) :
                        %default
                );
                
        %next_frame_data = (ref ($next_frame_data_ref)) ?
-               %$next_frame_data_ref : (
-                       (($next_frame < $last_frame) || (
-                               ($next_frame <= $last_frame) &&
-                               ($next_frame >= STATE->{'end'})
-                       )) ?
-                       read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), $next_frame)) :
-                       %default
-               );
-       
-       %frame_data      = merge_settings(\%default, \%frame_data);
-       %prev_frame_data = merge_settings(\%default, \%prev_frame_data);
-       %next_frame_data = merge_settings(\%default, \%next_frame_data);
+               %$next_frame_data_ref :
+               read_frame_data($next_frame);
        
        %words_data = (ref ($words_data_ref)) ?
                %$words_data_ref :
-               read_data_file(
-                       join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $frame), # file
-                       '', # encoding
-                       0,  # no header
+               read_words_list(
+                       $frame, # frame ID
                        1,  # header only
-                       1,  # as list; not relevant
                );
        
-       if ($frame_data{'page'} ne '') {
-               $file = $frame_data{'page'}
-       }
-       else {
-               $file = sprintf(
-                       $settings{'frame'},
-                       $frame, 'htm'
-               );
-       }
+       %frame_data      = merge_settings(\%default, \%frame_data);
+       %prev_frame_data = merge_settings(\%default, \%prev_frame_data);
+       %next_frame_data = merge_settings(\%default, \%next_frame_data);
+       
+       $file = get_page_file($frame, \%frame_data, \%settings);
        $file = join_path(PATH_SEPARATOR(), WWW_PATH(), $file);
        
        return print_viewer_page(
@@ -2135,6 +2115,7 @@ sub write_static_viewer_page {
                        'show_command'  => 1,
                        'text_mode'     => TEXT_MODE->{'normal'},
                        'words_page'    => 0, # not relevant
+                       'goto'          => 0
                },
                \%state,
                \%settings,
@@ -2153,13 +2134,13 @@ sub write_static_goto {
        
        %state = (ref ($state_ref)) ?
                %$state_ref :
-               read_data_file(DATA_STATE_PATH());
+               read_state();
        %settings = (ref ($settings_ref)) ?
                %$settings_ref :
-               read_data_file(DATA_SETTINGS_PATH());
+               read_settings();
        %goto_list = (ref ($goto_ref)) ?
                %$goto_ref :
-               read_data_file(DATA_SETTINGS_PATH());
+               read_goto();
        
        return print_goto(
                WWW_GOTO_PATH(),
@@ -2232,32 +2213,22 @@ sub ong {
        else {
                %settings = (ref ($settings_ref)) ?
                        %$settings_ref :
-                       read_data_file(DATA_SETTINGS_PATH());
-               %default = (ref ($default_ref)) ?
-                       %$default_ref :
-                       read_data_file(DATA_DEFAULT_PATH());
+                       read_settings();
+               %default = (ref ($default_ref)) ? %$default_ref : read_default();
                $frame_data_path = $cfrt ?
                        DATA_NOACCESS_PATH() :
                        join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
                %frame_data = (ref ($data_ref)) ?
                        %$data_ref :
-                       read_data_file($frame_data_path);
+                       read_frame_data($frame_data_path);
                %frame_full_data = merge_settings(\%default, \%frame_data);
-               @files = (
-                       ($frame_full_data{'frame'} ne '') ?
-                               $frame_full_data{'frame'} :
-                               sprintf(
-                                       $settings{'frame'},
-                                       $frame, $frame_full_data{'ext'}
-                               )
-                       ,
-               );
+               @files = (get_frame_file($frame, \%frame_full_data, \%settings), );
                unless ($cfrt) {
                        %goto_list = (ref ($goto_ref)) ?
                                %$goto_ref :
-                               read_data_file(DATA_LIST_PATH());
+                               read_goto();
                        for (my $i=0; ;$i+=1) {
-                               my %file_data = read_data_file(DATA_ATTACH_PATH().$i);
+                               my %file_data = read_attachment($i);
                                if ($file_data{'frame'} eq '') {
                                        last;
                                }
@@ -2287,7 +2258,7 @@ sub ong {
                                $write_data = 1;
                        }
                        if ($write_data) {
-                               $r = write_data_file($frame_data_path, \%frame_data);
+                               $r = write_frame_data($frame_data_path, \%frame_data);
                                unless ($r) {
                                        print STDERR "fail writing $frame_data_path\n";
                                        if ($print) {
@@ -2298,7 +2269,7 @@ sub ong {
                        }
                        $goto_list{'title-'  .$frame} = $frame_full_data{'title'};
                        $goto_list{'ongtime-'.$frame} = $frame_full_data{'ongtime'};
-                       $r = write_data_file(DATA_LIST_PATH(), \%goto_list);
+                       $r = write_goto('', \%goto_list);
                        unless ($r) {
                                print STDERR "fail writing ".DATA_LIST_PATH()."\n";
                                if ($print) {
@@ -2327,4 +2298,637 @@ sub ong {
        return 1;
 }
 
+
+sub get_frame_file {
+       (my $frame, my $frame_data, my $settings) = @_;
+       my $file;
+       my $pattern;
+       
+       if ($frame_data->{'frame'} ne '') {
+               $file = $frame_data->{'frame'};
+       }
+       else {
+               $pattern = validate_filename($settings->{'frame'}, '%d.%ext');
+               $file = sprintf(
+                       $pattern,
+                       int($frame), $frame_data->{'ext'}
+               );
+       }
+       return validate_filename($file);
+}
+
+sub get_page_file {
+       (my $frame, my $frame_data, my $settings) = @_;
+       my $file;
+       my $pattern;
+       
+       if ($frame == 0) {
+               return 'index.htm';
+       }
+       if ($frame_data->{'page'} ne '') {
+               $file = $frame_data->{'page'};
+       }
+       else {
+               $pattern = validate_filename($settings->{'frame'}, '%d.%ext');
+               $file = sprintf(
+                       $pattern,
+                       int($frame), 'htm'
+               );
+       }
+       return validate_filename($file);
+}
+
+sub validate_filename {
+       (my $filename, my $fallback) = @_;
+       if ($fallback eq '') {
+               $fallback = '';
+       }
+       
+       # TODO: more checks
+       
+       if ($filename =~ /^\./) {
+               return $fallback;
+       }
+       if (index($filename, PATH_SEPARATOR()) >= 0) {
+               return $fallback;
+       }
+       return $filename;
+}
+
+sub validate_frame_data {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'ongtime'} ne '') {
+               $data{'ongtime'} = int($data{'ongtime'});
+       }
+       if ($data{'timer'} ne '') {
+               $data{'timer'} = int($data{'timer'});
+       }
+       if ($data{'width'} ne '') {
+               $data{'width'} = int($data{'width'});
+       }
+       if ($data{'height'} ne '') {
+               $data{'height'} = int($data{'height'});
+       }
+       if ($data{'page'} ne '') {
+               $data{'page'} = validate_filename($data{'page'});
+       }
+       if ($data{'frame'} ne '') {
+               $data{'frame'} = validate_filename($data{'frame'});
+       }
+       
+       return %data;
+}
+
+sub validate_settings {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'ongtime'} ne '') {
+               $data{'ongtime'} = int($data{'ongtime'});
+       }
+       if ($data{'dynamicongtime'} ne '') {
+               $data{'dynamicongtime'} = int($data{'dynamicongtime'});
+       }
+       if ($data{'firstongtime'} ne '') {
+               $data{'firstongtime'} = int($data{'firstongtime'});
+       }
+       if ($data{'last'} ne '') {
+               $data{'last'} = int($data{'last'});
+       }
+       $data{'frame'} = validate_filename($data{'frame'}, '%d.%s');
+       
+       return %data;
+}
+
+sub validate_state {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'state'} ne '') {
+               $data{'state'} = int($data{'state'});
+       }
+       if ($data{'last'} ne '') {
+               $data{'last'} = int($data{'last'});
+       }
+       if ($data{'nextong'} ne '') {
+               $data{'nextong'} = int($data{'nextong'});
+       }
+       
+       return %data;
+}
+
+sub validate_words_list {
+       (my $data_in, my $not_list) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'ongtime'} ne '') {
+               $data{'ongtime'} = int($data{'ongtime'});
+       }
+       
+       if ($not_list) {
+               my $id_list = '';
+               foreach my $ID (split(/\r?\n/, $data{'content'})) {
+                       $ID = validate_filename($ID);
+                       if ($ID ne '') {
+                               $id_list .= $ID."\n";
+                       }
+               }
+               $data{'content'} = $id_list;
+       }
+       else {
+               my @id_list;
+               foreach my $ID (@{$data{'content'}}) {
+                       
+                       $ID = validate_filename($ID);
+                       if ($ID ne '') {
+                               push @id_list, $ID;
+                       }
+               }
+               $data{'content'} = [@id_list];
+       }
+       
+       return %data;
+}
+
+sub validate_words {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'posttime'} ne '') {
+               $data{'posttime'} = int($data{'posttime'});
+       }
+       if ($data{'edittime'} ne '') {
+               $data{'edittime'} = int($data{'edittime'});
+       }
+       
+       return %data;
+}
+
+sub validate_story {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'id'} ne '') {
+               $data{'id'} = int($data{'id'});
+       }
+       if ($data{'pass'} ne '') {
+               $data{'pass'} = int($data{'pass'});
+       }
+       if ($data{'state'} ne '') {
+               $data{'state'} = int($data{'state'});
+       }
+       
+       return %data;
+}
+
+sub validate_goto {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       foreach my $key (keys %data) {
+               if ($key =~ /^ongtime-([0-9]+)$/) {
+                       my $new_key = 'ongtime-'.int($1);
+                       $data{$new_key} = int($data{$key});
+                       if ($new_key != $key) {
+                               delete $data{$key};
+                       }
+               }
+       }
+       
+       return %data;
+}
+
+sub validate_attachment {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'frame'} ne '') {
+               $data{'frame'} = int($data{'frame'});
+       }
+       $data{'filename'} = validate_filename($data{'filename'});
+       
+       return %data;
+}
+
+sub validate_coincidence {
+       (my $data_in) = @_;
+       my %data = %$data_in;
+       
+       if ($data{'server'} ne '') {
+               $data{'server'} = int($data{'server'});
+       }
+       
+       return %data;
+}
+
+sub read_frame_data {
+       (my $f, my $default) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # frame ID
+               $file = join_path(PATH_SEPARATOR(), DATA_PATH(), int($&));
+       }
+       elsif ($f =~ /^(c(frt)?)|(noaccess)$/) { # CFRT (no access)
+               $file = DATA_NOACCESS_PATH();
+       }
+       elsif ($f =~ /^d(efault)?$/) { # default
+               $file = DATA_DEFAULT_PATH();
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_DEFAULT_PATH();
+       }
+       
+       %data = read_data_file($file);
+       if (ref ($default)) {
+               %data = merge_settings($default, \%data);
+       }
+       elsif ($default ne '') {
+               my %default_data = read_data_file(DATA_DEFAULT_PATH());
+               %data = merge_settings(\%default_data, \%data);
+       }
+       
+       return validate_frame_data(\%data);
+}
+
+sub write_frame_data {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # frame ID
+               $file = join_path(PATH_SEPARATOR(), DATA_PATH(), int($&));
+       }
+       elsif ($f =~ /^(c(frt)?)|(noaccess)$/) { # CFRT (no access)
+               return 0; # forbidden
+       }
+       elsif ($f =~ /^d(efault)?$/) { # default
+               return 0; # forbidden
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               return 0; # forbidden
+       }
+       
+       my %_data = validate_frame_data($data);
+       
+       return write_data_file($file, \%_data);
+}
+
+sub read_default {
+       return read_frame_data('default');
+}
+
+sub read_noaccess {
+       (my $default) = @_;
+       return read_frame_data('noaccess', $default);
+}
+
+sub read_settings {
+       (my $f) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_SETTINGS_PATH();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_settings(\%data);
+}
+
+sub read_state {
+       (my $f) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_STATE_PATH();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_state(\%data);
+}
+
+sub write_state {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = PERL_DATA_STATE_PATH();
+       }
+       
+       my %_data = validate_state($data);
+       
+       return write_data_file($file, \%_data);
+}
+
+sub read_words_list {
+       (my $f, my $header_only, my $not_list) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # frame ID
+               $file = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), int($&));
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else { # which frame ???
+               return ('posts' => 0);
+       }
+       
+       %data = read_data_file(
+               $file,
+               '', # encoding
+               0,  # no header
+               $header_only,
+               not $not_list # as list
+       );
+       
+       return validate_words_list(\%data, $not_list);
+}
+
+sub write_words_list {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # frame ID
+               $file = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), int($&));
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else { # which frame ???
+               return 0;
+       }
+       
+       my %_data = validate_words_list($data);
+       
+       return write_data_file(
+               $file, # file 
+               \%_data,
+               '',  # encoding
+               0,   # no header
+               0,   # header only
+               1    # as list
+       );
+}
+
+sub read_words {
+       (my $f, my $default) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+\.[0-9\.]+$/) { # post ID
+               $file = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else { # which post ???
+               return ();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_words(\%data);
+}
+
+sub write_words {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9\.]+$/) { # post ID
+               $file = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else { # which post ???
+               return 0;
+       }
+       
+       my %_data = validate_words($data);
+       
+       return write_data_file($file, \%_data);
+}
+
+sub read_story {
+       (my $f) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # story ID
+               $file = DATA_STORY_PATH().int($&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_STORY_PATH();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_story(\%data);
+}
+
+sub write_story {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # story ID
+               $file = DATA_STORY_PATH().int($&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_STORY_PATH();
+       }
+       
+       my %_data = validate_story($data);
+       
+       return write_data_file($file, \%_data);
+}
+
+sub read_goto {
+       (my $f) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_LIST_PATH();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_goto(\%data);
+}
+
+sub write_goto {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_LIST_PATH();
+       }
+       
+       my %_data = validate_goto($data);
+       
+       return write_data_file($file, \%_data);
+}
+
+sub read_attachment {
+       (my $f, my $default) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # attachment ID
+               $file = DATA_ATTACH_PATH().int($&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               return ();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_attachment(\%data);
+}
+
+sub read_coincidence {
+       (my $f) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_COIN_PATH();
+       }
+       
+       %data = read_data_file($file);
+       
+       return validate_coincidence(\%data);
+}
+
+sub read_chat {
+       (my $f) = @_;
+       my $file;
+       my %data;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # chat ID
+               $file = DATA_CHAT_PATH().int($&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_CHAT_PATH();
+       }
+       
+       return read_data_file($file);
+       
+       # no validation
+}
+
+sub write_chat {
+       (my $f, my $data) = @_;
+       my $file;
+       
+       if (ref ($f)) { # already open file
+               $file = $f;
+       }
+       elsif ($f =~ /^[0-9]+$/) { # chat ID
+               $file = DATA_CHAT_PATH().int($&);
+       }
+       elsif ($f ne '') { # path
+               $file = $f;
+       }
+       else {
+               $file = DATA_CHAT_PATH();
+       }
+       
+       # no validation
+       
+       return write_data_file($file, $data);
+}
+
+
 1