]>
git.p6c8.net - selfforum.git/blob - selfforum-cgi/user/fo_posting.pl
3 ################################################################################
5 # File: user/fo_posting.pl #
7 # Authors: André Malo <nd@o3media.de>, 2001-03-31 #
9 # Description: Accept new postings, display "Neue Nachricht" page #
11 # not ready, be patient please #
13 ################################################################################
33 use vars
qw($Bin $Shared $Script);
38 my $null = $0; $null =~ s/\\/\//g; # for win :-(
39 ($Bin) = ($null =~ /^(.*)\/.*$/)? $1 : '.';
40 $Shared = "$Bin/../shared";
41 ($Script) = ($null =~ /^.*\/(.*)$/)? $1 : $null;
45 use CGI::Carp qw(fatalsToBrowser);
50 # load script configuration and admin default conf.
52 my $conf = read_script_conf
($Bin, $Shared, $Script);
53 my $adminDefault = read_admin_conf
($conf -> {files
} -> {adminDefault
});
55 # Initialize the request
57 my $request = new Posting
::Request
($conf, $adminDefault);
59 # fetch and parse the cgi-params
61 $request -> parse_cgi
;
63 # handle errors or save the posting
65 $request -> handle_error
or $request -> save
;
73 ### main end ###################################################################
75 ################################################################################
76 ### Posting::Request ###########################################################
77 package Posting
::Request
;
80 use Encode
::Plain
; $Encode::Plain
::utf8
= 1; # generally convert from UTF-8
94 use Template
::Posting
;
98 ### sub new ####################################################################
100 # initialising the Posting::Request object
101 # check parameters and fill in object properties
104 my ($class, $conf, $adminDefault) = @_;
106 my $sp = $conf -> {show
} -> {Posting
};
111 admin
=> $adminDefault,
113 message_path
=> $conf -> {files
} -> {messagePath
},
114 forum_file_name
=> $conf -> {files
} -> {forum
},
117 assign
=> $sp -> {assign
},
118 template
=> $conf -> {template
},
119 form_must
=> $sp -> {form
} -> {must
},
120 form_data
=> $sp -> {form
} -> {data
},
121 form_action
=> $sp -> {form
} -> {action
},
124 template
=> new Template
$sp -> {templateFile
},
133 ### sub response ###############################################################
135 # print the response to STDOUT
141 my $formdata = $self -> {conf
} -> {form_data
};
142 my $formact = $self -> {conf
} -> {form_action
};
143 my $template = $self -> {template
};
144 my $assign = $self -> {conf
} -> {assign
};
145 my $q = $self -> {cgi_object
};
147 # fill out the form field names
150 for (keys %$formdata) {
151 $pars -> {$formdata -> {$_} -> {assign
} -> {name
}} = plain
($formdata -> {$_} -> {name
}) if (
152 exists($formdata -> {$_} -> {name
})
153 and exists ($formdata -> {$_} -> {assign
})
154 and exists ($formdata -> {$_} -> {assign
} -> {name
})
158 # response the 'new message' page
160 if ($self -> {response
} -> {new_thread
}) {
161 my $list = [map {{$assign -> {optval
} => plain
($_)}} @
{$formdata -> {posterCategory
} -> {values}}];
163 print $q -> header
(-type
=> 'text/html');
164 print ${$template -> scrap
(
166 { $formdata->{uniqueID
} ->{assign
}->{value
} => plain
(unique_id
),
167 $formdata->{quoteChar
} ->{assign
}->{value
} =>
168 'ÿ'.plain
($self -> {conf
} -> {admin
} -> {View
} -> {quoteChars
}),
169 $formdata->{posterCategory
}->{assign
}->{value
} => $template->list ($assign -> {option
}, $list),
170 $formact->{post
}->{assign
} => $formact->{post
}->{url
}
177 # check the response -> doc
179 unless ($self -> {response
} -> {doc
}) {
181 spec
=> 'unknown_error',
185 $self -> handle_error
;
187 unless ($self -> {response
} -> {doc
}) {
188 $self -> jerk
('While producing the HTML response an unknown error has occurred.');
193 # ok, print the response document to STDOUT
195 print $q -> header
(-type
=> 'text/html');
196 print ${$template -> scrap
(
197 $self -> {response
} -> {doc
},
199 $self -> {response
} -> {pars
}
206 ### sub handle_error ###########################################################
208 # analyze error data and create content for the response method
210 # Return: true if error detected
216 my $spec = $self -> {error
} -> {spec
};
218 return unless ($spec);
220 my $assign = $self -> {conf
} -> {assign
};
221 my $formdata = $self -> {conf
} -> {form_data
};
223 my $desc = $self -> {error
} -> {desc
} || '';
224 my $type = $self -> {error
} -> {type
};
227 if (exists ($formdata -> {$desc})
228 and exists ($formdata -> {$desc} -> {assign
} -> {$spec})) {
229 $emsg = $formdata -> {$desc} -> {assign
} -> {$spec};
232 $emsg = $assign -> {$spec} || '';
237 if ($type eq 'fatal') {
238 $self -> {response
} -> {doc
} = $assign -> {docFatal
};
239 $self -> {response
} -> {pars
} = {
240 $assign -> {errorMessage
} => $self -> {template
} -> insert
($emsg)
245 # user is able to repair his request
247 elsif ($type eq 'repeat' or $type eq 'fetch') {
248 $self -> {response
} -> {doc
} = $assign -> {docError
};
249 $self -> fillout_form
;
250 $self -> {response
} -> {pars
} -> {$assign -> {errorMessage
}} = $self -> {template
} -> insert
($emsg);
251 my $num = $spec eq 'too_long'
252 ?
$formdata -> {$desc} -> {maxlength
}
253 : ($spec eq 'too_short'
254 ?
$formdata -> {$desc} -> {minlength
}
258 $self -> {response
} -> {pars
} -> {$assign -> {charNum
}} = $num
265 ### sub fillout_form ###########################################################
267 # fill out the form using available form data
274 my $assign = $self -> {conf
} -> {assign
};
275 my $formdata = $self -> {conf
} -> {form_data
};
276 my $formact = $self -> {conf
} -> {form_action
};
277 my $q = $self -> {cgi_object
};
282 $pars -> {$formact -> {post
} -> {assign
}} = $formact -> {post
} -> {url
};
284 for (keys %$formdata) {
285 if ($_ eq 'quoteChar') {
286 $pars -> {$formdata->{$_}->{assign
}->{value
}}
287 = 'ÿ'.plain
($q -> param
($formdata -> {quoteChar
} -> {name
}) or '');
289 elsif (exists ($formdata -> {$_} -> {name
})) {
290 unless (exists ($formdata -> {$_} -> {values})) {
291 $pars -> {$formdata -> {$_} -> {assign
} -> {value
}}
292 = plain
($q -> param
($formdata -> {$_} -> {name
}));
296 $pars -> {$formdata -> {$_} -> {assign
} -> {value
}}
297 = $self -> {template
} -> list
(
300 { $assign -> {optval
} => plain
($_),
301 (( $_ eq $q -> param
($formdata -> {$_name} -> {name
}))
302 ?
($assign -> {optsel
} => 1)
306 } @
{$formdata -> {$_name} -> {values}}
313 $self -> {response
} -> {pars
} = $pars;
317 ### sub save ###################################################################
320 # check on legal reply or dupe is released here
327 # if an empty 'new message' document, there's nothing to save
329 return if ($self -> {response
} -> {new_thread
});
331 $self -> {check_success
} = 0;
333 # lock and load the forum main file
335 if ($self -> load_main_file
) {
337 # if a reply - is it legal?
340 if ($self -> check_reply_dupe
) {
342 unless ($self -> {response
} -> {reply
} or $self -> {response
} -> {new
}) {
343 # don't know, if we any time come to this branch
344 # the script is probably broken
347 spec
=> 'unknown_error',
353 my $formdata = $self -> {conf
} -> {form_data
};
354 my $q = $self -> {cgi_object
};
355 my $f = $self -> {forum
};
357 author
=> $q -> param
($formdata -> {posterName
} -> {name
}),
358 email
=> $q -> param
($formdata -> {posterEmail
} -> {name
}),
359 category
=> $q -> param
($formdata -> {posterCategory
} -> {name
}),
360 subject
=> $q -> param
($formdata -> {posterSubject
} -> {name
}),
361 body
=> $q -> param
($formdata -> {posterBody
} -> {name
}),
362 homepage
=> $q -> param
($formdata -> {posterURL
} -> {name
}),
363 image
=> $q -> param
($formdata -> {posterImage
} -> {name
}),
364 quoteChars
=> $q -> param
($formdata -> {quoteChar
} -> {name
}),
365 uniqueID
=> $q -> param
($formdata -> {uniqueID
} -> {name
}),
367 ip
=> $q -> remote_addr
,
368 forumFile
=> $self -> {conf
} -> {forum_file_name
},
369 messagePath
=> $self -> {conf
} -> {message_path
},
370 lastThread
=> $f -> {last_thread
},
371 lastMessage
=> $f -> {last_message
},
372 parsedThreads
=> $f -> {threads
},
374 messages
=> $self -> {template
} -> {messages
}
377 if ($self -> {response
} -> {reply
}) {
378 $pars -> {parentMessage
} = $self -> {fup_mid
};
379 $pars -> {thread
} = $self -> {fup_tid
};
382 my ($stat, $xml, $mid) = write_posting
($pars);
392 $self -> {check_success
} = 1;
393 my $thx = $self -> {conf
} -> {show_posting
} -> {thanx
};
395 # define special response data
397 $self -> {response
} -> {doc
} = $self -> {conf
} -> {assign
} -> {docThx
};
398 $self -> {response
} -> {pars
} = {
399 $thx -> {subject
} => plain
($q -> param
($formdata -> {posterSubject
} -> {name
})),
400 $thx -> {author
} => plain
($q -> param
($formdata -> {posterName
} -> {name
})),
401 $thx -> {email
} => plain
($q -> param
($formdata -> {posterEmail
} -> {name
})),
402 $thx -> {time} => plain
(hr_time
($time)),
403 $thx -> {body
} => message_as_HTML
(
407 assign
=> $self -> {conf
} -> {assign
},
408 quoteChars
=> $q -> param
($formdata -> {quoteChar
} -> {name
}),
409 quoting
=> $self -> {conf
} -> {admin
} -> {View
} -> {quoting
}
411 $thx -> {category
} => plain
($q -> param
($formdata -> {posterCategory
} -> {name
})),
412 $thx -> {home
} => plain
($q -> param
($formdata -> {posterURL
} -> {name
})),
413 $thx -> {image
} => plain
($q -> param
($formdata -> {posterImage
} -> {name
}))
420 # unlock forum main file
422 if ($self -> {forum
} -> {flocked
}) {
423 violent_unlock_file
($self -> {conf
} -> {forum_file_name
}) unless write_unlock_file
($self -> {conf
} -> {forum_file_name
});
424 $self -> {forum
} -> {flocked
} = 0;
427 $self -> handle_error
unless $self -> {check_success
};
432 ### sub parse_cgi ##############################################################
434 # fetch and decode cgi-parameters,
435 # find out the kind of response requested by the user (new message, reply)
442 # create the CGI object
444 $self -> {cgi_object
} = new CGI
;
448 $self -> {check_success
} = $self -> check_cgi
;
453 ### sub load_main_file #########################################################
455 # load and parse the forum main file
457 # Return: Success (true/false)
463 unless ($lock_stat = write_lock_file
($self -> {conf
} -> {forum_file_name
})) {
464 if (defined $lock_stat and $lock_stat == 0) {
465 # occupied or no w-bit set for the directory..., hmmm
467 violent_unlock_file
($self -> {conf
} -> {forum_file_name
});
478 spec
=> 'master_lock',
485 $self -> {forum
} -> {flocked
} = 1;
486 ( $self -> {forum
} -> {threads
},
487 $self -> {forum
} -> {last_thread
},
488 $self -> {forum
} -> {last_message
},
489 $self -> {forum
} -> {dtd
},
490 $self -> {forum
} -> {unids
}
491 ) = get_all_threads
($self -> {conf
} -> {forum_file_name
}, KEEP_DELETED
);
498 ### sub check_reply_dupe #######################################################
500 # check whether a reply is legal
501 # (followup posting must exists)
503 # check whether this form request is a dupe
504 # (unique id already exists)
506 # Return: Status Code (Bool)
508 sub check_reply_dupe
{
512 # return true unless it's not a reply
516 $self -> {response
} -> {reply
}
517 or $self -> {response
} -> {new
}
520 if ($self -> {response
} -> {reply
}) {
522 my ($threads, $ftid, $fmid, $i, %msg) = (
523 $self -> {forum
} -> {threads
},
528 # thread doesn't exist
530 unless (exists($threads -> {$ftid})) {
538 # build a reverse lookup hash (mid => number in array)
539 # and ignore invisible messages
540 # (users can't reply to "deleted" msg)
542 for ($i=0; $i < @
{$threads -> {$ftid}}; $i++) {
544 if ($threads -> {$ftid} -> [$i] -> {deleted
}) {
545 $i+=$threads -> {$ftid} -> [$i] -> {answers
};
548 $msg{$threads -> {$ftid} -> [$i] -> {mid
}}=$i;
552 # message doesn't exist
554 unless (exists($msg{$fmid})) {
562 # build a unique id lookup hash
563 # use the unids of parent message's kids
565 %unids = map {$_ => 1} @
{$threads -> {$ftid} -> [$msg{$fmid}] -> {unids
}};
568 # build a unique id lookup hash, too
569 # but use only the level-zero-messages
571 %unids = map {$_ => 1} @
{$self -> {forum
} -> {unids
}};
577 $self -> {cgi_object
} -> param
(
578 $self -> {conf
} -> {form_data
} -> {uniqueID
} -> {name
})})) {
590 ### sub check_cgi ##############################################################
592 # cgi params are like raw eggs...
594 # Return: Status Code (Bool)
595 # creates content for the handle_error method if anything fails
600 # count the submitted keys and get the keys themselves
602 my %got_keys = map {($_ => 1)} $self -> {cgi_object
} -> param
;
603 my $cnt_got_keys = keys %got_keys;
604 my $formdata = $self -> {conf
} -> {form_data
};
605 my $formmust = $self -> {conf
} -> {form_must
};
607 # user requested the 'new thread' page
608 # (no params but perhaps the user-ID have been submitted)
610 if ($cnt_got_keys == 0 or (
611 exists ($formdata -> {userID
})
612 and $cnt_got_keys == 1
613 and $got_keys{$formdata -> {userID
} -> {name
}}
615 $self -> {response
} -> {new_thread
} = 1;
616 $self -> {check_success
} = 1;
620 # now we know, we've got a filled out form
621 # we do the following steps to check it:
623 # 1st: create a reverse Hash (CGI-key - identifier)
624 # 2nd: did we get _all_ must-keys?
625 # check whether reply or new message request
626 # 3rd: did we get too many keys?
627 # 4th: do _all_ submitted values accord to
629 # fetch the "missing" keys
635 exists($formdata -> {$_} -> {name
})
636 ?
($formdata -> {$_} -> {name
} => $_)
642 $self -> {response
} -> {reply
} = $got_keys {$formdata -> {followUp
} -> {name
}}?
1 : 0;
643 $self -> {response
} -> {new
} = not $self -> {response
} -> {reply
};
645 # define the fetch array (values to fetch from parent message)
647 $self -> {fetch
} = [];
649 for ( @
{$formmust -> {$self -> {response
} -> {reply
}?
'reply':'new'}} ) {
651 unless ($got_keys {$formdata -> {$_} -> {name
}}) {
653 # only miss the key unless we're able to fetch it from parent posting
656 not $self -> {response
} -> {reply
}
657 or $formdata -> {$_} -> {errorType
} eq 'fetch') {
660 spec
=> 'missing_key',
667 # keep in mind to fetch the value later
669 push @
{$self -> {fetch
}} => $_;
674 # I'm lazy - I know...
675 my $q = $self -> {cgi_object
};
680 unless (exists ($name {$_})) {
682 spec
=> 'unexpected_key',
692 unless ($self -> decode_param
) {
694 spec
=> 'unknown_encoding',
700 if ($self -> {response
} -> {reply
}) {
702 # get the parent-identifiers if we got a reply request
704 my ($ftid, $fmid) = split /;/ => $q -> param
($formdata -> {followUp
} -> {name
}) => 2;
706 unless ($ftid =~ /\d+/ and $fmid =~ /\d+/) {
708 spec
=> 'unknown_followup',
713 $self -> {fup_tid
} = $ftid;
714 $self -> {fup_mid
} = $fmid;
716 # fetch the missing keys
717 # if it fails, they're too short, too... ;)
720 $got_keys{$formdata -> {$_} -> {name
}} = 1 for (@
{$self -> {fetch
}});
723 # now we can check on length, type etc.
725 for (keys %got_keys) {
727 # we are sure, we've got only one value for one key
729 my $val = $q -> param
($_);
731 $val =~ s/\302\240/ /g; # convert nbsp (UTF-8 encoded) into normal spaces
732 $val =~ s/\015\012|\015|\012/ /g # convert \n into spaces unless it's a multiline field
734 exists ($formdata -> {$name {$_}} -> {type
})
735 and $formdata -> {$name {$_}} -> {type
} eq 'multiline-text'
738 $q -> param
($_ => $val); # write it back
742 if (length $val > $formdata -> {$name {$_}} -> {maxlength
}) {
746 type
=> $formdata -> {$name {$_}} -> {errorType
}
748 $self -> kill_param
or return;
752 # (only check if there's defined a minimum length)
754 if (exists ($formdata -> {$name {$_}} -> {minlength
})) {
756 # kill the whitespaces to get only the visible characters...
758 (my $val_ww = $val) =~ s/\s+//g;
760 if (length $val_ww < $formdata -> {$name {$_}} -> {minlength
}) {
764 type
=> $formdata -> {$name {$_}} -> {errorType
}
766 $self -> kill_param
or return;
770 # check the values on expected kinds of content
771 # (email, http-url, url, option)
773 if (exists ($formdata -> {$name {$_}} -> {type
}) and length $val) {
774 if ($formdata -> {$name {$_}} -> {type
} eq 'email' and not is_email
$val) {
776 spec
=> 'wrong_mail',
778 type
=> $formdata -> {$name {$_}} -> {errorType
}
780 $self -> kill_param
or return;
783 elsif ($formdata -> {$name {$_}} -> {type
} eq 'http-url' and not is_URL
$val => 'http') {
785 spec
=> 'wrong_http_url',
787 type
=> $formdata -> {$name {$_}} -> {errorType
}
789 $self -> kill_param
or return;
792 elsif ($formdata -> {$name {$_}} -> {type
} eq 'url' and not is_URL
$val => ':ALL') {
796 type
=> $formdata -> {$name {$_}} -> {errorType
}
798 $self -> kill_param
or return;
802 if (exists ($formdata -> {$name {$_}} -> {values})
803 and not exists ({map {$_ => undef} @
{$formdata -> {$name {$_}} -> {values}}} -> {$val})) {
807 type
=> $formdata -> {$name {$_}} -> {errorType
}
809 $self -> kill_param
or return;
816 ### sub kill_param #############################################################
818 # kill the param (set it on '') if wrong and declared as 'kill' in config file
820 # Return: true if killed
826 if ($self -> {conf
} -> {form_data
} -> {$self -> {error
} -> {desc
}} -> {errorType
} eq 'kill') {
827 $self -> {cgi_object
} -> param
($self -> {conf
} -> {form_data
} -> {$self -> {error
} -> {desc
}} -> {name
} => '');
828 $self -> {error
} = {};
835 ### sub fetch ##################################################################
837 # fetch "missing" keys from parent posting
841 my $q = $self -> {cgi_object
};
842 my $formdata = $self -> {conf
} -> {form_data
};
844 if (@
{$self -> {fetch
}}) {
845 my $filename = $self -> {conf
} -> {message_path
}.'t'.$self -> {fup_tid
}.'.xml';
847 if (lock_file
($filename)) {
848 my $xml = parse_xml_file
($filename);
849 violent_unlock_file
($filename) unless unlock_file
($filename);
852 my $mnode = get_message_node
($xml, 't'.$self -> {fup_tid
}, 'm'.$self -> {fup_mid
});
854 my $header = get_message_header
($mnode);
856 $q -> param
($formdata -> {$_} -> {name
} => $header -> {$formdata -> {$_} -> {header
}})
857 for (@
{$self -> {fetch
}});
866 # fillout the values with an empty string
868 $q -> param
($formdata -> {$_} -> {name
} => '')
869 for (@
{$self -> {fetch
}});
874 ### sub decode_param ###########################################################
876 # convert submitted form data into UTF-8
877 # unless it's not encoded yet
879 # Return: Status Code (Bool)
880 # false if unknown encoding (like UTF-7 for instance)
885 my $q = $self -> {cgi_object
};
886 my $formdata = $self -> {conf
} -> {form_data
};
888 my $code = $q -> param
($formdata -> {quoteChar
} -> {name
});
891 # Latin 1 (we hope so - there's no real way to find out :-( )
892 if ($code =~ /^\377/) {
893 $q -> param
($_ => map {toUTF8
($_)} $q -> param
($_)) for ($q -> param
);
896 # UTF-8 is (probably) correct,
897 # other encodings we don't know and fail
898 return unless $code =~ /^\303\277/;
901 # remove the ÿ (encoded as UTF-8) from quotechars
902 $q -> param
($formdata -> {quoteChar
} -> {name
}
903 => substr $q -> param
($formdata -> {quoteChar
} -> {name
}),2);
905 # ok, params now should be UTF-8 encoded
910 my $text = $_[1] || 'An error has occurred.';
912 Content-type: text/plain\n\n
917 We will fix it as soon as possible. Thank you for your patience.
926 ### end of fo_posting.pl #######################################################
patrick-canterino.de