summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordangofactory (ketsuban) <ketsuban@dangofactory.net>2026-10-03 16:51:23 +0900
committerdangofactory (ketsuban) <ketsuban@dangofactory.net>2026-10-03 16:51:23 +0900
commitaa7d8ed5b4e715131b526aa7bd4401d791405964 (patch)
tree8b5bc2f3d0932f7a285fb3d3a49eaf232ef90a5b
parent4c86fb88fe9ec07e863ed657b46786dea3ff6b67 (diff)
downloadfml8-aa7d8ed5b4e715131b526aa7bd4401d791405964.tar.gz
fml8-aa7d8ed5b4e715131b526aa7bd4401d791405964.tar.bz2
fml8-aa7d8ed5b4e715131b526aa7bd4401d791405964.zip
Keep the stray editor backups and the stale IM::Iso2022jp.pm patch file found on the hostHEADmaster
ToHTML.pm.save{,.1,.2} are three successive editor backups, none of which matches upstream (md5 f06b2ba0 / 48f80ed8 / 64d605b9 vs upstream 3333975a). They are kept verbatim under host-artifacts/ rather than deleted. patch is the IM::Iso2022jp.pm patch from fml-help@ffs.fml.org message 00607 (OGAWA Kunihiko, 2001). It is already applied upstream -- img/lib/IM/Iso2022jp.pm and the installed copy share md5 406f8955 -- so it is documentation, not a pending change.
-rw-r--r--host-artifacts/Mail/Message/ToHTML.pm.save2557
-rw-r--r--host-artifacts/Mail/Message/ToHTML.pm.save.12609
-rw-r--r--host-artifacts/Mail/Message/ToHTML.pm.save.22608
-rw-r--r--host-artifacts/patch18
4 files changed, 7792 insertions, 0 deletions
diff --git a/host-artifacts/Mail/Message/ToHTML.pm.save b/host-artifacts/Mail/Message/ToHTML.pm.save
new file mode 100644
index 00000000..ad0f7406
--- /dev/null
+++ b/host-artifacts/Mail/Message/ToHTML.pm.save
@@ -0,0 +1,2557 @@
+#-*- perl -*-
+#
+# Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi
+# All rights reserved. This program is free software; you can
+# redistribute it and/or modify it under the same terms as Perl itself.
+#
+# $FML: ToHTML.pm,v 1.85 2006/06/15 10:35:24 tmu Exp $
+#
+
+package Mail::Message::ToHTML;
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD $hints);
+use Carp;
+
+my $is_strict_warn = 0;
+my $debug = 0;
+my $URL =
+ "<A HREF=\"http://www.fml.org/software/\">Mail::Message::ToHTML</A>";
+
+my $version = q$FML: ToHTML.pm,v 1.85 2006/06/15 10:35:24 tmu Exp $;
+my $versionid = 0;
+if ($version =~ /,v\s+([\d\.]+)\s+/) {
+ $versionid = "$1";
+ $version = "$URL $versionid";
+}
+
+=head1 NAME
+
+Mail::Message::ToHTML - convert text format mail to HTML format
+
+=head1 SYNOPSIS
+
+ ... lock by something ...
+
+ use Mail::Message::ToHTML;
+ my $obj = new Mail::Message::ToHTML {
+ charset => "euc-jp",
+ directory => "/var/www/htdocs/ml/elena",
+ };
+
+ $obj->htmlify_rfc822_message({
+ id => 1,
+ src => "/var/spool/ml/elena/spool/1",
+ });
+
+ ... unlock by something ...
+
+This module itself provides no lock function.
+please use flock() built in perl or CPAN lock modules for it.
+
+=head1 DESCRIPTION
+
+=head2 Message structure created as HTML
+
+HTML-fied message has following structure.
+something() below is method name.
+
+ for example
+ -------------------------------------------------------------------
+ html_start() <HTML><HEAD> ... </HEAD><BODY>
+ mhl_preamble() <!-- comment used by this module -->
+ mhl_separator() <HR>
+
+ message header
+ From: ...
+ Subject: ...
+
+ mhl_separator() <HR>
+
+ message body
+
+ mhl_separator() <HR>
+ mhl_footer() <!-- comment used by this module -->
+ html_end() </BODY></HTML>
+
+=head1 METHODS
+
+=head2 new($args)
+
+ $args = {
+ directory => $directory,
+ };
+
+C<$directory> is top level directory where html-fied articles are
+stored.
+
+=cut
+
+
+# Descriptions: constructor.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: OBJ
+sub new
+{
+ my ($self, $args) = @_;
+ my ($type) = ref($self) || $self;
+ my $me = {};
+
+ $me->{ _charset } = $args->{ charset } || 'us-ascii';
+ $me->{ _html_base_directory } = $args->{ output_dir };
+ $me->{ _db_type } = $args->{ db_type };
+ $me->{ _db_name } = $args->{ db_name };
+ $me->{ _db_base_dir } = $args->{ db_base_dir };
+ $me->{ _is_attachment } = defined($args->{ attachment }) ? 1 : 0;
+ $me->{ _args } = $args;
+ $me->{ _num_attachments } = 0; # for child process
+ $me->{ _use_subdir } = 'yes';
+ $me->{ _subdir_style } = 'yyyymm';
+ $me->{ _html_id_order } = $args->{ index_order } || 'normal';
+ $me->{ _use_address_mask } = $args->{ use_address_mask } || 'yes';
+ $me->{ _address_mask_type } = $args->{ address_mask_type } || 'all';
+
+ # global hints
+ $hints = $args->{ hints } || {};
+
+ use Mail::Message::Thread;
+ my $t = new Mail::Message::Thread $args;
+ $me->{ _thread_object } = $t;
+
+ return bless $me, $type;
+}
+
+
+# Descriptions: destructor.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: none
+sub DESTROY
+{
+ my ($self) = @_;
+
+ _PRINT_DEBUG("ToHTML::DESTROY");
+ 1;
+}
+
+
+=head2 htmlify_rfc822_message($args)
+
+convert mail to html.
+
+ $args = {
+ id => $id,
+ path => $path,
+ };
+
+where C<$path> is file path.
+
+=cut
+
+
+# Descriptions: top level entrance to convert mail to html
+# Arguments: OBJ($self) HASH_REF($args)
+# $args = { id => $id, path => $path };
+# $id identifier (e.g. "1" (article id))
+# $path file path (e.g. "/some/where/1");
+# Side Effects: none
+# Return Value: none
+sub htmlify_rfc822_message
+{
+ my ($self, $args) = @_;
+
+ # prepare source
+ use Mail::Message;
+ use FileHandle;
+ my $rh = new FileHandle $args->{ src };
+ my $msg = Mail::Message->parse( { fd => $rh } );
+ my $hdr = $msg->whole_message_header;
+ my $body = $msg->whole_message_body;
+ $self->{ _current_msg } = $msg;
+ $self->{ _current_hdr } = $hdr;
+ $self->{ _current_body } = $body;
+
+ # initialize basic information
+ # $id = article id
+ # $src = source file
+ # $dst = destination file (target html)
+ my ($id, $src, $dst) = $self->_init_htmlify_rfc822_message($args);
+ $self->{ _debug_id } = $id;
+
+ # save information for index.html and thread.html
+ $self->cache_message_info($msg, { id => $id,
+ src => $src,
+ dst => $dst,
+ } );
+
+ # target html not define
+ unless ($dst) {
+ $self->{ _ignore_list }->{ $id } = 1; # ignore flag
+ warn("html file for $id not define") if $debug;
+ return undef;
+ }
+
+ # target html exists already.
+ if (-f $dst) {
+ $self->{ _ignore_list }->{ $id } = 1; # ignore flag
+ warn("html file for $id already exists") if $debug;
+ return undef;
+ }
+
+ # prepare output channel
+ my $wh = $self->_set_output_channel( { dst => $dst } );
+ unless (defined $wh) {
+ croak("cannot open output file $dst\n");
+ }
+
+ # before main message
+ $self->html_start($wh, { message => $msg });
+ $self->mhl_preamble($wh);
+
+ # analyze $msg, chain of Mail::Message objects.
+ # See Mail::Message class for more detail.
+ # XXX we use $m->{ next } here, but we should avoid this style and
+ # XXX prepare access method for it in Mail::Message class.
+ my ($m, $type, $attach);
+ CHAIN:
+ for ($m = $msg; defined($m) ; $m = $m->{ 'next' }) {
+ $type = $m->data_type;
+
+ last CHAIN if $type eq 'multipart.close-delimiter'; # last of multipart
+ next CHAIN if $type =~ /^multipart/; # multipart type is special.
+
+ unless ($type =~ /^\w+\/[-\w\d\.]+$/) {
+ warn("invalid type={$type}");
+ next CHAIN;
+ }
+
+ # header (Mail::Message object uses this special type)
+ if ($type eq 'text/rfc822-headers') {
+ $self->mhl_separator($wh);
+ my $charset = $self->{ _charset };
+ my $header = $self->_format_safe_header($msg);
+ _print_raw_str($wh, $header, $charset);
+ $self->mhl_separator($wh);
+ }
+ # message/rfc822 case (attached rfc822 message)
+ elsif ($type eq 'message/rfc822') {
+ $attach++;
+
+ my $tmpf = $self->_create_temporary_file_in_raw_mode($m);
+ if (defined $tmpf && -f $tmpf) {
+ # write attachement into a separete file
+ my $outf = _gen_attachment_filename($dst, $attach, 'html');
+ my $args = $self->{ _args };
+ $args->{ attachment } = 1; # clarify not top level content.
+ my $text = new Mail::Message::ToHTML $args;
+ $text->htmlify_rfc822_message({
+ parent_id => $id,
+ src => $tmpf,
+ dst => $outf,
+ });
+
+ # show inline <HREF> link,
+ # which appears in parent html ( == $wh channel ).
+ $self->_print_inline_object_link({
+ fh => $wh, # file descriptor
+ type => $type, # XXX derived from input message
+ num => $attach, # number
+ file => $outf, # temporary file name
+ });
+
+ unlink $tmpf;
+ }
+ }
+ # text/plain case.
+ # XXX inline expansion.
+ elsif ($type eq 'text/plain') {
+ if ($self->{ _use_address_mask } eq 'yes') {
+ $self->_text_plain_part_safe_print($wh, $m);
+ }
+ else { # original
+ $self->_text_safe_print({
+ fh => $wh, # parent html
+ data => $m->message_text(),
+ charset => $m->charset(),
+ encoding => $m->encoding_mechanism(),
+ });
+ }
+ }
+ # create a separete file for attachment
+ else {
+ $attach++;
+
+ # write attachement into a separete file
+ my $outf = _gen_attachment_filename($dst, $attach, $type);
+ my $enc = $m->encoding_mechanism;
+ my $msginfo = { message => $m };
+
+ # e.g. text/xxx case (e.g. text/html case)
+ if ($type =~ /^text/) {
+ # 1. firstly saved to temporary file $tmpf in "raw" mode
+ my $tmpf = $self->_create_temporary_filename();
+ $msginfo->{ file } = $tmpf;
+
+ # once create temporary file
+ _PRINT_DEBUG("attachment: type=$type attach=$attach enc=$enc");
+ if ($enc) {
+ $self->_binary_print($msginfo); # XXX raw mode
+ }
+ else {
+ $self->_text_raw_print($msginfo); # XXX raw mode
+ }
+
+ # 2. secondary convert $tmpf to real target $outf with
+ # some modification e.g. metachars escaping, ...
+ # disable html tag in file saved in raw mode.
+ if (-f $tmpf) {
+ $msginfo->{ description } = "(HTML TAGs are disabled)";
+ _disable_html_tag_in_file($tmpf, $outf);
+ unlink $tmpf;
+ }
+ }
+ # e.g. image/gif not text/* nor message/*
+ else {
+ $msginfo->{ file } = $outf;
+ $self->_binary_print($msginfo);
+ }
+
+ # show inline <HREF> link appeared in parent html.
+ $self->_print_inline_object_link({
+ inline => 1,
+ fh => $wh,
+ type => $type,
+ num => $attach,
+ file => $outf,
+ info => $msginfo,
+ });
+ }
+ }
+
+ # show navigation bar et.al. after message itself
+ $self->mhl_separator($wh);
+ $self->mhl_footer($wh);
+ $self->html_end($wh);
+ return 1;
+}
+
+
+# Descriptions: copy $inf file to $outf file with disabling HTML tag
+# by _print_safe_buf().
+# Arguments: STR($inf) STR($outf)
+# Side Effects: create $outf file
+# Return Value: none
+sub _disable_html_tag_in_file
+{
+ my ($inf, $outf) = @_;
+ my $mask = umask();
+
+ umask(022);
+
+ use FileHandle;
+ my $rh = new FileHandle $inf;
+ my $wh = new FileHandle "> $outf";
+ if (defined $rh) {
+ my $buf = '';
+
+ my $b;
+ while ($b = <$rh>) { $buf .= $b;}
+
+ _print_safe_buf($wh, $buf);
+ $wh->close;
+ $rh->close;
+ }
+
+ umask($mask);
+}
+
+
+# Descriptions: return HTML filename
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR or UNDEF
+sub html_filename
+{
+ my ($self, $id) = @_;
+ my $use_subdir = $self->{ _use_subdir };
+
+ # relative path under html_base_dir
+ if (defined($id) && ($id > 0)) {
+ if ($use_subdir eq 'yes') {
+ return $self->_html_file_subdir_name($id);
+ }
+ else {
+ return "msg${id}.html";
+ }
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: return HTML sub directory string
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR
+sub _html_file_subdir_name
+{
+ my ($self, $id) = @_;
+ my $ndb = $self->ndb();
+ my $subdir = '';
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $subdir_style = $self->{ _subdir_style };
+ my $dir_mode = $self->{ _dir_mode } || 0755;
+
+ if ($subdir_style eq 'yyyymm') {
+ my $hdr = $self->{ _current_hdr };
+ use Mail::Message::Utils;
+ $subdir = Mail::Message::Utils::get_time_from_header($hdr, 'yyyymm');
+
+ use File::Spec;
+ my $xsubdir = File::Spec->catfile($html_base_dir, $subdir);
+ unless (-d $xsubdir) {
+ my $mask = umask();
+ umask(022);
+ mkdir($xsubdir, $dir_mode);
+ umask($mask);
+ }
+ }
+ else {
+ croak("unknown \$subdir_style");
+ }
+
+ if ($subdir) {
+ use File::Spec;
+ return File::Spec->catfile($subdir, "msg$id.html");
+ }
+ else {
+ warn("not create msg$id.html");
+ return undef;
+ }
+}
+
+
+# Descriptions: return HTML file path
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR
+sub html_filepath
+{
+ my ($self, $id) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+
+ if (defined($id) && ($id > 0)) {
+ my $filename = $self->html_filename($id);
+ return undef unless($filename);
+
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, $filename);
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: parse $args and return file id, name, path.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: ARRAY(NUM, STR, STR)
+sub _init_htmlify_rfc822_message
+{
+ my ($self, $args) = @_;
+ my ($id, $src, $dst);
+
+ if (defined $args->{ src }) {
+ $src = $args->{ src };
+ }
+ else {
+ croak("htmlify_rfc822_message: \$src is mandatory\n");
+ }
+
+ if (defined $args->{ id }) {
+ my $html_base_dir = $self->{ _html_base_directory };
+ $id = $args->{ id };
+ $dst = $self->html_filepath($id);
+ }
+ # this object is an attachment if parent_id is specified.
+ elsif (defined $args->{ parent_id }) {
+ $self->{ _num_attachments }++;
+ $id = $args->{ parent_id } .'.'. $self->{ _num_attachments };
+ $dst = $args->{ dst };
+ }
+ # last resort: give unique identifier
+ elsif (defined $args->{ dst }) {
+ $id = sprintf("%s.%s", time, $$);
+ $dst = $args->{ dst };
+ }
+ # oops ;) wrong call of this function
+ else {
+ croak("htmlify_rfc822_message: specify \$id or \$dst\n");
+ }
+
+ return ($id, $src, $dst);
+}
+
+
+# Descriptions: show html header + file title in <BODY>
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub html_start
+{
+ my ($self, $wh, $args) = @_;
+ my ($msg, $hdr, $title);
+
+ if (defined $args->{ title }) {
+ $title = $args->{ title };
+ }
+ elsif (defined $args->{ message }) {
+ $msg = $args->{ message };
+ $hdr = $msg->whole_message_header;
+ $title = $self->_decode_mime_string( $hdr->get('article_subject') ||
+ $hdr->get('subject') );
+ }
+
+ print $wh "<!-- X-FML 8 ToHTML $versionid -->\n";
+ print $wh
+ q{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">};
+ print $wh "\n";
+ print $wh "<HTML>\n";
+ print $wh "<HEAD>\n";
+
+ if (defined $self->{ _charset }) {
+ my $charset = $self->{ _charset };
+ print $wh "<META http-equiv=\"Content-Type\"\n";
+ print $wh " content=\"text/html; charset=${charset}\">\n";
+ }
+
+ if (defined $self->{ _stylsheet }) {
+ my $css = $self->{ _stylsheet };
+ print $wh "<LINK rel=\"stylesheet\"\n";
+ print $wh " type=\"text/css\" href=\"fml.css\">\n";
+ }
+
+ if (defined $title) {
+ print $wh "<title>";
+ _print_safe_str($wh, $title);
+ print $wh "</title>\n";
+ }
+
+ print $wh "</HEAD>\n";
+ print $wh "<BODY>\n";
+ print $wh "<CENTER>";
+ _print_safe_str($wh, $title);
+ print $wh "</CENTER>\n";
+}
+
+
+# Descriptions: show html closing
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub html_end
+{
+ my ($self, $wh) = @_;
+ print $wh "</BODY>";
+ print $wh "</HTML>\n";
+}
+
+
+# Descriptions: show html separetor, we use <HR> now.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_separator
+{
+ my ($self, $wh) = @_;
+ print $wh "<HR>\n";
+}
+
+
+my $preamble_begin = "<!-- __PREAMBLE_BEGIN__ by Mail::Message::ToHTML -->";
+my $preamble_end = "<!-- __PREAMBLE_END__ by Mail::Message::ToHTML -->";
+my $footer_begin = "<!-- __FOOTER_BEGIN__ by Mail::Message::ToHTML -->";
+my $footer_end = "<!-- __FOOTER_END__ by Mail::Message::ToHTML -->";
+
+
+# Descriptions: prepare information area before main message appears.
+# Later, this area is replaced with useful information
+# e.g. thread link.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_preamble
+{
+ my ($self, $wh) = @_;
+ print $wh $preamble_begin, "\n";
+ print $wh $preamble_end, "\n";
+}
+
+
+# Descriptions: prepare information area after main message appears.
+# Later, this area is replaced with useful information
+# e.g. thread link.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_footer
+{
+ my ($self, $wh) = @_;
+ print $wh $footer_begin, "\n";
+ print $wh $footer_end, "\n";
+}
+
+
+# Descriptions: prepare write handle
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $args->{ dst } file if needed
+# Return Value: HANDLE
+sub _set_output_channel
+{
+ my ($self, $args) = @_;
+ my $dst = $args->{ dst };
+ my $wh = undef;
+
+ my $mask = umask();
+ umask(022);
+
+ if (defined $dst) {
+ use FileHandle;
+ $wh = new FileHandle "> $dst";
+ }
+ else {
+ $wh = \*STDOUT;
+ }
+
+ umask($mask);
+
+ return $wh;
+}
+
+
+# Descriptions: return temporary file path.
+# XXX temporary file is created under $db_dir not public space
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: STR
+sub _create_temporary_filename
+{
+ my ($self) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, "tmp.$$");
+}
+
+
+# Descriptions: create a temporary file with the content $msg
+# Arguments: OBJ($self) OBJ($msg)
+# Side Effects: create $tmpf file
+# Return Value: STR
+sub _create_temporary_file_in_raw_mode
+{
+ my ($self, $msg) = @_;
+ my $tmpf = $self->_create_temporary_filename();
+ my $mask = umask();
+ umask(022);
+
+ use FileHandle;
+ my $wh = new FileHandle "> $tmpf";
+ if (defined $wh) {
+ $wh->autoflush(1);
+
+ my $buf = $msg->message_text();
+ $wh->print($buf);
+ $wh->close;
+
+ umask($mask);
+ return ($tmpf);
+ }
+
+ umask($mask);
+ return undef;
+}
+
+
+# Descriptions: convert $file filepath to relative path
+# XXX UNIX specific ???
+# Arguments: OBJ($self) STR($file)
+# Side Effects: none
+# Todo: UNIX specific
+# Return Value: STR
+sub _relative_path
+{
+ my ($self, $file) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ $file =~ s/$html_base_dir//;
+ $file =~ s@^/@@;
+ return $file;
+}
+
+
+# Descriptions: print inline link as html for attachments e.g.
+# images, files et. al.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _print_inline_object_link
+{
+ my ($self, $args) = @_;
+ my $wh = $args->{ fh };
+ my $type = $args->{ type };
+ my $num = $args->{ num };
+ my $file = $self->_relative_path($args->{ file });
+ my $desc = '';
+ my $inline = defined( $args->{ inline } ) ? 1 : 0;
+
+ if (defined $args->{ info }->{ description }) {
+ $desc = $args->{ info }->{ description };
+ }
+
+ if ($inline && $type =~ /image/) {
+ print $wh "<BR><IMG SRC=\"../$file\">$desc\n";
+ }
+ else {
+ my $t = $file;
+ print $wh "<BR><A HREF=\"../$file\" TARGET=\"$t\"> $type $num </A>";
+ print $wh "$desc<BR>\n";
+ }
+}
+
+
+# Descriptions: return attachment filename
+# Arguments: STR($dst) STR($attach) STR($suffix)
+# Side Effects: none
+# Todo: UNIX specific
+# Return Value: STR
+sub _gen_attachment_filename
+{
+ my ($dst, $attach, $suffix) = @_;
+ my $outf = $dst;
+ if ($suffix =~ m@/@) { $suffix =~ s@.*/@@;}
+
+ $outf =~s/\.html$//;
+ $outf = "$outf.$attach.$suffix";
+ return $outf;
+}
+
+
+# default header to show
+my @header_field = qw(From To Cc Subject Date Message-Id X-Sequence);
+
+
+# Descriptions: format header of $msg with escaping HTML metachars
+# and disabling special HTML tags.
+# See _sprintf_safe_str() for how to escape.
+# Arguments: OBJ($self) OBJ($msg)
+# Side Effects: none
+# XXX $buf is printed out later in raw mode.
+# Return Value: STR
+sub _format_safe_header
+{
+ my ($self, $msg) = @_;
+ my ($buf);
+ my $hdr = $msg->whole_message_header;
+ my $header_field = \@header_field;
+
+ my $mimeopt = $main::opt_mimedecodequoted;
+ $main::opt_mimedecodequoted = 1;
+
+ # header
+ $buf .= "<SPAN CLASS=mailheaders>\n";
+ for my $field (@$header_field) {
+ if (defined($hdr->get($field))) {
+ $buf .= "<SPAN CLASS=${field}>\n";
+ $buf .= "${field}: ";
+ $buf .= "</SPAN>\n";
+
+ my $xbuf = $hdr->get($field);
+
+ # mask the raw address against address collector (e.g. spammer).
+ if ($self->{ _use_address_mask } eq 'yes') {
+ if ($self->_is_mask_address($field)) {
+ $xbuf = $self->_address_to_gecos($xbuf);
+ }
+ }
+
+ $xbuf = $self->_decode_mime_string($xbuf) if $xbuf =~ /=\?/i;
+ $buf .= "<SPAN CLASS=${field}-value>\n";
+ $buf .= _sprintf_safe_str($xbuf);
+ $buf .= "</SPAN>\n";
+ $buf .= "<BR>\n";
+ }
+ }
+ $buf .= "</SPAN>\n";
+
+ $main::opt_mimedecodequoted = $mimeopt;
+ return($buf);
+}
+
+
+# create index.html type (all thread month month_thread top)
+my @indexs = qw(month month_thread top);
+
+# Descriptions: show link to indexes as navigation
+# Arguments: HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _format_index_navigator
+{
+ my ($args) = @_;
+ my $use_subdir = defined $args->{use_subdir} ? $args->{use_subdir} : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $str;
+ my $indexs = \@indexs;
+
+ for my $index (@$indexs) {
+ $str .= qq{<A HREF=\"${prefix}index_all.html\">[ID Index]</A>\n} if ($index eq "all");
+ $str .= qq{<A HREF=\"${prefix}thread.html\">[Thread Index]</A>\n} if ($index eq "thread");
+ $str .= qq{<A HREF=\"${prefix}monthly_index.html\">[Monthly ID Index]</A>\n} if ($index eq "month");
+ $str .= qq{<A HREF=\"${prefix}monthly_thread.html\">[Monthly Thread Index]</A>\n} if ($index eq "month_thread");
+ $str .= qq{<A HREF=\"${prefix}index.html\">[Top Index]</A>\n} if ($index eq "top");
+ };
+
+return $str;
+}
+
+
+# Descriptions: print text/plain part by printing each paragraph.
+# mask raw mail addresses in the signature if could.
+# Arguments: OBJ($self) HANDLE($wh) OBJ($m)
+# Side Effects: none
+# Return Value: STR
+sub _text_plain_part_safe_print
+{
+ my ($self, $wh, $m) = @_;
+ my $total = $m->paragraph_total();
+
+ # print each paragraph.
+ for (my $i = 1; $i <= $total ; $i++) {
+ my $buf = $m->nth_paragraph($i);
+
+ # try to hide domain since the last paragraph must be signature.
+ if ($self->{ _use_address_mask } eq 'yes') {
+ if ($i == $total) {
+ $buf =~ s/(\w+\@[\w\.]+)/$self->_address_to_gecos($1)/ge;
+ }
+ }
+
+ $self->_text_safe_print({
+ fh => $wh, # parent html
+ data => $buf,
+ charset => $m->charset(),
+ encoding => $m->encoding_mechanism(),
+ });
+ }
+}
+
+
+# Descriptions: print out text data with escaping by _print_safe_buf()
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub _text_safe_print
+{
+ my ($self, $args) = @_;
+ my $buf = $args->{ data };
+ my $fh = $args->{ fh } || \*STDOUT;
+ my $in_code = $args->{ charset } || undef;
+ my $encoding = $args->{ encoding } || '7bit';
+
+ if ($encoding eq 'base64') {
+ use MIME::Base64;
+ $buf = MIME::Base64::decode_base64($buf);
+ }
+ elsif ($encoding eq 'quoted-printable') {
+ use MIME::QuotedPrint;
+ $buf = MIME::QuotedPrint::decode_qp($buf);
+ }
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined $buf && $buf) {
+ $buf = $self->_convert($buf, 'euc');
+ }
+
+ _print_safe_buf($fh, $buf);
+}
+
+
+# Descriptions: print out message without escaping
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $outf
+# Return Value: none
+sub _text_raw_print
+{
+ my ($self, $args) = @_;
+ my $msg = $args->{ message }; # Mail::Message object
+ my $type = $msg->data_type;
+ my $enc = $msg->encoding_mechanism;
+ my $buf = $msg->message_text();
+
+ if (defined( $args->{ file } )) {
+ my $mask = umask();
+ umask(022);
+
+ my $outf = $args->{ file };
+ use FileHandle;
+ my $fh = new FileHandle "> $outf";
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined $buf && $buf) {
+ $buf = $self->_convert($buf, 'euc');
+ }
+ print $fh $buf, "\n";
+ $fh->close();
+
+ umask($mask);
+ }
+}
+
+
+# Descriptions: print out binary with MIME encoding or
+# text with escaping
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $outf file
+# Return Value: none
+sub _binary_print
+{
+ my ($self, $args) = @_;
+ my $msg = $args->{ message }; # Mail::Message object
+ my $type = $msg->data_type;
+ my $enc = $msg->encoding_mechanism || '';
+ my $mask = umask();
+
+ umask(022);
+
+ if (defined( $args->{ file } )) {
+ my $outf = $args->{ file };
+ use FileHandle;
+ my $fh = new FileHandle "> $outf";
+
+ if (defined $fh) {
+ $fh->autoflush(1);
+ binmode($fh);
+
+ if ($enc eq 'base64') {
+ print $fh MIME::Base64::decode_base64( $msg->message_text() );
+ }
+ elsif ($enc eq 'quoted-printable') {
+ print $fh MIME::QuotedPrint::decode_qp( $msg->message_text() );
+ }
+ elsif ($enc eq '7bit') {
+ _print_safe_str($fh, $msg->message_text());
+ }
+ else {
+ my $r = "*** unknown MIME encoding enc='$enc' ***\n";
+ _print_safe_str($fh, $r);
+ _print_safe_str($fh, $msg->message_text());
+ }
+
+ $fh->close();
+ }
+ }
+
+ umask($mask);
+}
+
+
+=head2 is_ignore($id)
+
+we should not process this C<$id>
+
+=cut
+
+
+# Descriptions: check whether article $id is ignored
+# Arguments: OBJ($self) STR($id)
+# Side Effects: none
+# Return Value: 1 or 0
+sub is_ignore
+{
+ my ($self, $id) = @_;
+
+ return defined($self->{ _ignore_list }->{ $id }) ? 1 : 0;
+}
+
+
+
+=head1 METHODS for index and thread
+
+=head2 cache_message_info($msg, $args)
+
+save information into DB.
+See section C<Internal Data Presentation> for more detail.
+
+=cut
+
+
+# Descriptions: update database on message header, thread relation
+# et. al.
+# Arguments: OBJ($self) OBJ($msg) HASH_REF($args)
+# Side Effects: update database
+# Return Value: none
+sub cache_message_info
+{
+ my ($self, $msg, $args) = @_;
+ my $ndb = $self->ndb();
+ my $id = $args->{ id };
+ my $src = $args->{ src };
+ my $dst = $args->{ dst };
+
+ $ndb->set_key($id);
+
+ $ndb->set('html_filename', $id, $self->html_filename($id));
+ $ndb->set('html_filepath', $id, $dst);
+
+ unless ($ndb->get('message_id', $id)) {
+ # analyze $msg only if not yet analyzed.
+ print STDERR "debug: analyze $id.\n" if $debug;
+ $ndb->add($msg);
+ }
+ else {
+ print STDERR "debug: already analyzed!\n" if $debug;
+ }
+}
+
+
+# Descriptions: return Mail::Message::DB object.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: OBJ
+sub ndb
+{
+ my ($self) = @_;
+ my $t = $self->{ _thread_object };
+
+ return $t->db();
+}
+
+
+=head2 update_msg_html_links($id)
+
+update link relation around C<$id>.
+
+=cut
+
+
+# Descriptions: top level dispatcher to update database.
+# _msg_file_rewrite_links() has real function for updating.
+# Arguments: OBJ($self) STR($id)
+# Side Effects: update databse
+# Return Value: none
+sub update_msg_html_links
+{
+ my ($self, $id) = @_;
+ my $info = $self->evaluate_links_relation($id);
+ my $list = $self->{ _affected_idlist } = [];
+
+ if ($self->is_ignore($id)) {
+ warn("not update relation around $id") if $debug;
+ return undef;
+ }
+
+ # sanity
+ return unless defined $id;
+ return unless $id;
+
+ # update target itself, of course
+ $self->_msg_file_rewrite_links($id);
+ push(@$list, $id);
+
+ # no rewriting for myself
+ my %uniq = ( $id => 1 );
+
+ KEY:
+ for my $_link (qw(prev_id next_id prev_thread_id next_thread_id)) {
+ if (defined $info->{ $_link }) {
+ my $_id = $info->{ $_link };
+
+ next KEY if $uniq{ $_id };
+ $uniq{ $_id } = 1;
+
+ _PRINT_DEBUG("try: rewrite $_link links in msg $_id");
+
+ if (defined $_id && $_id) {
+ $self->_msg_file_rewrite_links($_id);
+ push(@$list, $_id);
+ }
+ }
+ else {
+ _PRINT_DEBUG("error: fail to rewrite msg $_link");
+ }
+ }
+
+ # hint cached on memory, provided by _print_thread().
+ if (defined $self->{ _hint_ref_key_list }->{ $id }) {
+ my $thread_list = $self->{ _hint_ref_key_list }->{ $id } || [];
+
+ # update link relation for all articles in this thread.
+ KEY:
+ for my $id (@$thread_list) {
+ next KEY if $uniq{ $id};
+ $uniq{ $id } = 1;
+
+ $self->_msg_file_rewrite_links( $id );
+ push(@$list, $id);
+ }
+ }
+}
+
+
+# Descriptions: update link at preamble and footer of HTML-ified message.
+# Arguments: OBJ($self) STR($id)
+# Side Effects: rewrite index file
+# Return Value: none
+sub _msg_file_rewrite_links
+{
+ my ($self, $id) = @_;
+ my $info = $self->evaluate_links_relation($id);
+ my $preamble = $self->evaluate_safe_preamble($info);
+ my $footer = $self->evaluate_safe_footer($info);
+ my $code = _charset_to_code($self->{ _charset });
+
+ my $pat_preamble_begin = quotemeta($preamble_begin);
+ my $pat_preamble_end = quotemeta($preamble_end);
+ my $pat_footer_begin = quotemeta($footer_begin);
+ my $pat_footer_end = quotemeta($footer_end);
+
+ my $mask = umask();
+
+ umask(022);
+
+ _PRINT_DEBUG("try _msg_file_rewrite_links($id)");
+
+ use FileHandle;
+ my $file = $info->{ filepath };
+ if (defined $file && $file && -f $file) {
+ my ($old, $new) = ($file, "$file.new.$$");
+ my $rh = new FileHandle $old;
+ my $wh = new FileHandle "> $new";
+
+ if (defined $rh && defined $wh) {
+ my $buf;
+
+ _PRINT_DEBUG("rewrite: open msg $id");
+
+ LINE:
+ while ($buf = <$rh>) {
+ if ($buf =~ /^$pat_preamble_begin/
+ ..
+ $buf =~ /^$pat_preamble_end/) {
+ if ($buf =~ /^$pat_preamble_end/) {
+ _print_raw_str($wh, $preamble, $code);
+ }
+ next LINE;
+ }
+
+ if ($buf =~ /^$pat_footer_begin/
+ ..
+ $buf =~ /^$pat_footer_end/) {
+ if ($buf =~ /^$pat_footer_end/) {
+ _print_raw_str($wh, $footer, $code);
+ }
+ next LINE;
+ }
+
+ # just copy (rewrite only $preamble and $footer not message)
+ _print_raw_str($wh, $buf, $code);
+ }
+ $rh->close;
+ $wh->close;
+
+ unless (rename($new, $old)) {
+ croak("rename($new, $old) fail (id=$id)\n");
+ }
+ else {
+ _PRINT_DEBUG("done: rewritten links in msg $id");
+ }
+ }
+ else {
+ unless (defined $file) {
+ $new = $old = '(null string)';
+ }
+ warn("cannot open $old (id=$id)\n") unless defined $rh;
+ warn("cannot create $new (id=$id)\n") unless defined $wh;
+ }
+ }
+ else {
+ warn("undefined file for $id\n") if $is_strict_warn;
+ }
+
+ umask($mask);
+}
+
+
+# Descriptions: return thread link relation info et.al. for $id
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: HASH_REF
+sub evaluate_links_relation
+{
+ my ($self, $id) = @_;
+ my $ndb = $self->ndb();
+
+ return $ndb->get_tohtml_thread_summary($id);
+}
+
+
+# Descriptions: return preamble without metachars
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub evaluate_safe_preamble
+{
+ my ($self, $args) = @_;
+ my $link_prev_id = $args->{ link_prev_id };
+ my $link_next_id = $args->{ link_next_id };
+ my $link_prev_thread_id = $args->{ link_prev_thread_id };
+ my $link_next_thread_id = $args->{ link_next_thread_id };
+
+ my $use_subdir = $self->{ _use_subdir } eq 'yes' ? 1 : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $preamble = $preamble_begin. "\n";
+
+ my $mask = umask();
+
+ umask(022);
+
+ # for debug
+ $preamble .= "<!-- rewritten for id=$self->{ _debug_id } -->\n";
+
+ if (defined($link_prev_id) && $link_prev_id) {
+ $preamble .= "<A HREF=\"${prefix}$link_prev_id\">[Prev by ID]</A>\n";
+ }
+ else {
+ $preamble .= "[No Prev ID]\n";
+ }
+
+ if (defined($link_next_id) && $link_next_id) {
+ $preamble .= "<A HREF=\"${prefix}$link_next_id\">[Next by ID]</A>\n";
+ }
+ else {
+ $preamble .= "[No Next ID]\n";
+ }
+
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_prev_thread_id\">[Prev by Thread]</A>\n";
+ }
+ else {
+ if (defined $link_prev_id && $link_prev_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_prev_id\">[Prev by Thread]</A>\n";
+ }
+ else {
+ $preamble .= "[No Prev Thread]\n";
+ }
+ }
+
+ if (defined $link_next_thread_id && $link_next_thread_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_next_thread_id\">[Next by Thread]</A>\n";
+ }
+ else {
+ if (defined $link_next_id && $link_next_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_next_id\">[Next by Thread]</A>\n";
+ }
+ else {
+ $preamble .= "[No Next Thread]\n";
+ }
+ }
+
+ $preamble .= _format_index_navigator( { use_subdir => $use_subdir } );
+ $preamble .= $preamble_end. "\n";;
+
+ umask($mask);
+
+ return $preamble;
+}
+
+
+# Descriptions: return footer without metachars
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub evaluate_safe_footer
+{
+ my ($self, $args) = @_;
+ my $link_prev_id = $args->{ link_prev_id };
+ my $link_next_id = $args->{ link_next_id };
+ my $link_prev_thread_id = $args->{ link_prev_thread_id };
+ my $link_next_thread_id = $args->{ link_next_thread_id };
+ my $subject = $args->{ subject };
+
+ my $use_subdir = $self->{ _use_subdir } eq 'yes' ? 1 : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $footer = $footer_begin. "\n";;
+
+ if (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .= "<A HREF=\"${prefix}$link_prev_id\">Prev by ID: ";
+ if (defined $subject->{ prev_id } ) {
+ $footer .= _sprintf_safe_str( $subject->{ prev_id } );
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .= "<A HREF=\"${prefix}$link_next_id\">Next by ID: ";
+ if (defined $subject->{ next_id } ) {
+ $footer .= _sprintf_safe_str( $subject->{ next_id } );
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_thread_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_thread_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_thread_id });
+ }
+ $footer .= "</A>\n";
+ }
+ elsif (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_id });
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined $link_next_thread_id && $link_next_thread_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_thread_id\">Next by Thread: ";
+ if (defined $subject->{ next_thread_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_thread_id });
+ }
+ $footer .= "</A>\n";
+ }
+ elsif (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_id\">Next by Thread: ";
+ if (defined $subject->{ next_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_id });
+ }
+ $footer .= "</A>\n";
+ }
+
+ $footer .= qq{<BR>\n};
+ $footer .= _format_index_navigator( { use_subdir => $use_subdir } );
+ $footer .= $footer_end. "\n";;
+
+ return $footer;
+}
+
+
+=head2 update_id_index($args)
+
+update index.html.
+
+=cut
+
+
+# Descriptions: print navigation bar et.al. at upper half of indexes
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $new html
+# Return Value: none
+sub _print_index_begin
+{
+ my ($self, $args) = @_;
+ my $old = $args->{ old };
+ my $new = $args->{ new };
+ my $title = $args->{ title };
+ my $code = _charset_to_code($self->{ _charset });
+
+ my $mask = umask();
+
+ umask(022);
+
+ use FileHandle;
+ my $wh = new FileHandle "> $new";
+ $args->{ wh } = $wh;
+
+ $self->html_start($wh, { title => $title });
+
+ _print_raw_str($wh, _format_index_navigator(), $code);
+ $self->mhl_separator($wh);
+
+ umask($mask);
+}
+
+
+# Descriptions: print navigation bar et.al. at the end of indexes.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $new html
+# Return Value: none
+sub _print_index_end
+{
+ my ($self, $args) = @_;
+ my $wh = $args->{ wh };
+ my $old = $args->{ old };
+ my $new = $args->{ new };
+ my $title = $args->{ title };
+ my $code = $args->{ code };
+
+ $self->mhl_separator($wh);
+ _print_raw_str($wh, _format_index_navigator(), $code);
+
+ # append version information
+ _print_raw_str($wh, "<BR>Genereated by $version\n", $code);
+
+ $self->html_end($wh);
+
+ unless (rename($new, $old)) {
+ croak("rename($new, $old) fail\n");
+ }
+}
+
+# Descriptions: create Top index.html if no index.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create Top index.html
+# Return Value: none
+sub create_top_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Top Index",
+ old => "$html_base_dir/index.html",
+ new => "$html_base_dir/index.html.new.$$",
+ code => $code,
+ };
+
+ return if ( -f $htmlinfo-> { old } );
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: update index_all.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite index_all.html
+# Return Value: none
+sub update_id_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "ID Index",
+ old => "$html_base_dir/index_all.html",
+ new => "$html_base_dir/index_all.html.new.$$",
+ code => $code,
+ };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update index_all.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+
+ $self->_print_ul($wh, $db, $code);
+ if ($order eq 'reverse') {
+ for my $id (reverse (1 .. $max_id)) {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ else {
+ for my $id (1 .. $max_id) {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+=head2 update_monthly_id_index($args)
+
+=cut
+
+
+# Descriptions: update monthly index
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly index
+# Return Value: none
+sub update_monthly_id_index
+{
+ my ($self, $args) = @_;
+ my $affected_list = $self->{ _affected_idlist };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update index.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ # open databaes
+ my $db = $self->ndb();
+ my %month_update = ();
+
+ IDLIST:
+ for my $id (@$affected_list) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
+ $month_update{ $month } = 1;
+ }
+ }
+
+ # todo list
+ for my $month (sort keys %month_update) {
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
+
+ $self->_update_monthly_id_index($args, {
+ this_month => $this_month,
+ suffix => $suffix,
+ });
+ }
+
+ # update monthly_index.html
+ $self->_update_id_montly_index_master($args);
+}
+
+
+# Descriptions: update monthly index master
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly_index.html
+# Return Value: none
+sub _update_id_montly_index_master
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_index.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_index.html.new.$$");
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "ID Index",
+ old => $old,
+ new => $new,
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
+ my (@list) = sort __sort_yyyymm keys %$mlist;
+ my ($years) = _yyyy_range(\@list);
+
+ _print_raw_str($wh, "<table border='1'>", $code);
+
+ for my $year (sort {$b <=> $a} @$years) {
+ _print_raw_str($wh, "<tr>", $code);
+ _print_raw_str($wh, "<th> $year </th>", $code);
+
+ for my $month (1 .. 12) {
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $fn = "month.$xx.html";
+
+ use File::Spec;
+ my $file = File::Spec->catfile($html_base_dir, $fn);
+ if (-f $file) {
+ _print_raw_str($wh, "<td><a href=\"$fn\"> $month </a>", $code);
+ }
+ else {
+ _print_raw_str($wh, "<td>", $code);
+ }
+ _print_raw_str($wh, "</td>\n", $code);
+ }
+ _print_raw_str($wh, "</tr>\n", $code);
+ }
+ _print_raw_str($wh, "</table>", $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: return list of YYYY/MM format
+# Arguments: ARRAY_REF($list)
+# Side Effects: none
+# Return Value: ARRAY_REF
+sub _yyyy_range
+{
+ my ($list) = @_;
+ my ($yyyy) = {};
+
+ for my $y (@$list) {
+ if ($y =~ /^(\d{4})\/(\d{2})/o) {
+ $yyyy->{ $1 } = $1;
+ }
+ }
+
+ my (@yyyy) = keys %$yyyy;
+ return( \@yyyy );
+}
+
+
+# Descriptions: sort YYYY/MM formt strings
+# Arguments: none
+# Side Effects: none
+# Return Value: NUM
+sub __sort_yyyymm
+{
+ my ($xa, $xb) = ($a, $b);
+ $xa =~ s@/@@;
+ $xb =~ s@/@@;
+ if ($xa eq '') { $xa = 0;}
+ if ($xb eq '') { $xb = 0;}
+
+ $xa <=> $xb;
+}
+
+
+# Descriptions: update month.YYYYMM.html
+# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
+# Side Effects: update month.YYYYMM.html
+# Return Value: none
+sub _update_monthly_id_index
+{
+ my ($self, $args, $monthlyinfo) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
+ my $suffix = $monthlyinfo->{ suffix }; # yyyymm
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => "ID Monthly Index $this_month",
+ old => "$html_base_dir/month.${suffix}.html",
+ new => "$html_base_dir/month.${suffix}.html.new.$$",
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
+
+ $self->_print_ul($wh, $db, $code);
+ if ($order eq 'reverse') {
+ ID:
+ for my $id (reverse sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ else {
+ ID:
+ for my $id (sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+=head2 update_thread_index($args)
+
+update thread.html.
+
+=cut
+
+
+# Descriptions: update thread.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite thread.html
+# Return Value: none
+sub update_thread_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $code = _charset_to_code($self->{ _charset });
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
+ old => "$html_base_dir/thread.html",
+ new => "$html_base_dir/thread.html.new.$$",
+ code => $code,
+ };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update thread.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+
+ # initialize negagtive cache to ensure uniquness
+ delete $self->{ _uniq };
+
+ $self->_print_ul($wh, $db, $code);
+ for my $id ( 1 .. $max_id ) {
+ # head of the thread (not referenced yet)
+ unless (defined $self->{ _uniq }->{ $id }) {
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: check whether $id has next or previous link.
+# Arguments: OBJ($self) HASH_REF($db) NUM($id)
+# Side Effects: none
+# Return Value: 1 or 0
+sub _has_link
+{
+ my ($self, $db, $id) = @_;
+
+ if ($db->get('next_key', $id) || $db->get('prev_key', $id)) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+
+# Descriptions: print thread array of (head_id id2 id3 ...)
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($head_id) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_thread
+{
+ my ($self, $wh, $db, $head_id, $code) = @_;
+ my $saved_stack_level = $self->{ _stack };
+ my $uniq = $self->{ _uniq };
+
+ # get id list: @idlist = ( $head_id id2 id3 ... )
+ my $ndb = $self->ndb();
+ my $buf = $ndb->get('ref_key_list', $head_id);
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- thread head=$head_id ($buf) -->\n", $code);
+
+ my $idlist = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ if (@$idlist) {
+ IDLIST:
+ for my $id (@$idlist) {
+ # save $head_id => "id1 id2 id3 ..." on memory for further use.
+ # "> 1" implies idlist contains others than myself.
+ if ($#$idlist > 1) {
+ my $ra = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ $self->{ _hint_ref_key_list }->{ $id } = $ra;
+ }
+
+ # @$idlist = (number's)
+ _print_raw_str($wh, "<!-- thread (@$idlist) -->\n", $code);
+
+ next IDLIST if $uniq->{ $id };
+ $uniq->{ $id } = 1;
+
+ $self->_print_ul($wh, $db, $code);
+
+ # oops, we should ignore head of the thread ( myself ;-)
+ if (($id != $head_id) && $self->_has_link($db, $id)) {
+ _print_raw_str($wh, "<!-- thread $id has link -->\n", $code);
+ $self->_print_li_filename($wh, $db, $id, $code);
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ else {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ }
+
+ while ($self->{ _stack } > $saved_stack_level) {
+ $self->_print_end_of_ul($wh, $db, $code);
+ }
+}
+
+=head2 update_monthly_thread_index($args)
+
+=cut
+
+# Descriptions: update monthly thread index
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly thread index
+# Return Value: none
+sub update_monthly_thread_index
+{
+ my ($self, $args) = @_;
+ my $affected_list = $self->{ _affected_idlist };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update monthly_thread.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ # open databaes
+ my $db = $self->ndb();
+ my %month_update = ();
+
+ IDLIST:
+ for my $id (@$affected_list) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
+ $month_update{ $month } = 1;
+ }
+ }
+
+ # todo list
+ for my $month (sort keys %month_update) {
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
+
+ $self->_update_monthly_thread_index($args, {
+ this_month => $this_month,
+ suffix => $suffix,
+ });
+ }
+
+ # update monthly_index.html
+ $self->_update_montly_thread_index_master($args);
+}
+
+
+# Descriptions: update monthly thread index master
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly_thread.html
+# Return Value: none
+sub _update_montly_thread_index_master
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_thread.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_thread.html.new.$$");
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
+ old => $old,
+ new => $new,
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
+ my (@list) = sort __sort_yyyymm keys %$mlist;
+ my ($years) = _yyyy_range(\@list);
+
+ _print_raw_str($wh, "<table border='1'>", $code);
+
+ for my $year (sort {$b <=> $a} @$years) {
+ _print_raw_str($wh, "<TR>", $code);
+ _print_raw_str($wh, "<th> $year </th>", $code);
+
+ for my $month (1 .. 12) {
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $fn = "thread.$xx.html";
+
+ use File::Spec;
+ my $file = File::Spec->catfile($html_base_dir, $fn);
+ if (-f $file) {
+ _print_raw_str($wh, "<td><a href=\"$fn\"> $month </A>", $code);
+ }
+ else {
+ _print_raw_str($wh, "<td>", $code);
+ }
+ _print_raw_str($wh, "</td>\n", $code);
+ }
+ _print_raw_str($wh, "</tr>\n", $code);
+ }
+ _print_raw_str($wh, "</table>", $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: update thread.YYYYMM.html
+# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
+# Side Effects: update thread.YYYYMM.html
+# Return Value: none
+sub _update_monthly_thread_index
+{
+ my ($self, $args, $monthlyinfo) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
+ my $suffix = $monthlyinfo->{ suffix }; # yyyymm
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => "Monthly Thread Index $this_month",
+ old => "$html_base_dir/thread.${suffix}.html",
+ new => "$html_base_dir/thread.${suffix}.html.new.$$",
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
+
+ # initialize negagtive cache to ensure uniquness
+ delete $self->{ _uniq };
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
+
+ $self->_print_ul($wh, $db, $code);
+ for my $id (sort {$a <=> $b} @$list) {
+ # head of the thread (not referenced yet)
+ unless (defined $self->{ _uniq }->{ $id }) {
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+=head2 internal utility functions for IO
+
+=cut
+
+
+# Descriptions: cnvert charset to code e.g. iso-2022-jp => jis
+# Arguments: STR($charset)
+# Side Effects: none
+# Return Value: STR
+sub _charset_to_code
+{
+ my ($charset) = @_;
+
+ if (defined $charset) {
+ $charset =~ tr/A-Z/a-z/;
+ if ($charset eq 'euc-jp') {
+ return 'euc';
+ }
+ elsif ($charset eq 'iso-2022-jp') {
+ return 'jis';
+ }
+ else {
+ return $charset; # may be wrong, but I hope it works well:-)
+ }
+ }
+ else {
+ return 'euc'; # euc-jp by default
+ }
+}
+
+
+# Descriptions: print raw $str to $wh channel
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_raw_str
+{
+ my ($wh, $str, $code) = @_;
+ $code = defined($code) ? $code : 'euc'; # euc-jp by default
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined($str) && $str) {
+ $str = __nc_convert($str, $code || 'euc');
+ }
+
+ print $wh $str;
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# XXX text2html($str, urls => 1, pre => 0)
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_safe_str
+{
+ my ($wh, $str, $code) = @_;
+ __print_safe_str(0, $wh, $str, $code);
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# XXX text2html($str, urls => 1, pre => 1)
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_safe_buf
+{
+ my ($wh, $str, $code) = @_;
+ __print_safe_str(1, $wh, $str, $code);
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub __print_safe_str
+{
+ my ($attr_pre, $wh, $str, $code) = @_;
+ my $p = __sprintf_safe_str($attr_pre, $wh, $str, $code);
+ print $wh $p if defined $p;
+ print $wh "\n";
+}
+
+
+# Descriptions: return safe $str
+# Arguments: STR($str) STR($code)
+# Side Effects: none
+# Return Value: STR
+sub _sprintf_safe_str
+{
+ my ($str, $code) = @_;
+ return __sprintf_safe_str(0, undef, $str, $code);
+}
+
+
+# Descriptions: return safe $str modified by text2html().
+# $str language code is modified by Mail::Message::Encode.
+# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: STR or UNDEF
+sub __sprintf_safe_str
+{
+ my ($attr_pre, $wh, $str, $code) = @_;
+ my $regexp = $hints->{ subject_tag_regexp } || '';
+ my $rbuf = '';
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined($str) && $str) {
+ $str = __nc_convert($str, $code || 'euc');
+ }
+
+ if (defined $str) {
+ # $url$trailor => $url $trailor for text2html() incomplete regexp
+ $str =~ s#(http://[^\s\<\>\'\"]+[\w\d/])#_separete_url($1)#ge;
+
+ use HTML::FromText;
+ # NOT CONVERT subject tag (see fml-devel:726).
+ if ($str =~ /^\s*($regexp)(.*)/) {
+ my ($tag, $post) = ($1, $2);
+ my $tag_s = text2html($tag, urls => 0, pre => $attr_pre);
+ my $post_s = text2html($post, urls => 1, pre => $attr_pre);
+ return sprintf("%s%s", $tag_s, $post_s);
+ }
+ else {
+ return text2html($str, urls => 1, pre => $attr_pre);
+ }
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: extract URL syntax in $url string.
+# $url$trailor => $url $trailor for text2html()
+# XXX incomplete regexp, we should correct it.
+# Arguments: STR($url)
+# Side Effects: none
+# History: based on fml 4.0-current (2001/10/28)
+# Return Value: STR
+sub _separete_url
+{
+ my ($url) = @_;
+ my ($re_euc_c) = '[\241-\376][\241-\376]';
+ my ($re_euc_s) = "($re_euc_c)+";
+ my $trailor = '';
+
+ # remove prepended/appended EUC strings
+ if ($url =~ /($re_euc_s)+$/) {
+ $trailor = $1;
+ $url =~ s/$trailor//;
+ }
+
+ # incomplete but may be effective ?
+ # RFC2068 says these special char's are not used.
+ # we should not include these char's in URL.
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
+ # unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"
+ if ($url =~ /(\&\w{2}\;|\;|\?|\:|\@|\&|\=|\+|\#|\%|\<|\>|\")+$/) {
+ my $pat = $1;
+ $trailor = $pat . $trailor;
+ $url =~ s/${pat}$//;
+ }
+
+ return "$url $trailor";
+}
+
+
+# Descriptions: debug
+# Arguments: STR($str)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG
+{
+ my ($str) = @_;
+ print STDERR "(debug) $str\n" if $debug;
+}
+
+
+# Descriptions: debug, print out hash
+# Arguments: HASH_REF($hash)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG_DUMP_HASH
+{
+ my ($hash) = @_;
+ my ($k,$v);
+
+ if ($debug) {
+ while (($k, $v) = each %$hash) {
+ printf STDERR "%-30s => %s\n", $k, $v;
+ }
+ }
+}
+
+
+=head2 internal utility functions for HTML TAGS
+
+C<_print_something()> internal function provides wrapper to print HTML
+tags et.al.
+
+=cut
+
+
+# Descriptions: print <UL> with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_ul
+{
+ my ($self, $wh, $db, $code) = @_;
+
+ $self->{ _stack }++;
+
+ my $padding = " " x $self->{ _stack };
+ _print_raw_str($wh, "${padding}<UL>\n", $code);
+}
+
+
+# Descriptions: print </UL> with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_end_of_ul
+{
+ my ($self, $wh, $db, $code) = @_;
+
+ return unless $self->{ _stack } > 0;
+
+ my $padding = " " x $self->{ _stack };
+ _print_raw_str($wh, "${padding}</UL>\n", $code);
+
+ $self->{ _stack }--;
+}
+
+
+# Descriptions: print <LI> filename ... with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) NUM($id) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_li_filename
+{
+ my ($self, $wh, $db, $id, $code) = @_;
+ my $filename = $db->get('html_filename', $id);
+ my $subject = $db->get('article_subject', $id) ||
+ $db->get('subject', $id) || "no subject";
+ my $who = $db->get('who', $id) || "no sender";
+ if ($self->{ _use_address_mask } ne 'yes' &&
+ $db->get('from', $id) ne '') {
+ $who = "" if ($who =~ /\@xxx/);
+ $who .= " " . $db->get('from', $id);
+ }
+
+ my $mimeopt = $main::opt_mimedecodequoted;
+ $main::opt_mimedecodequoted = 1;
+ $subject = $self->_decode_mime_string($subject) if $subject =~ /=\?/i;
+ $who = $self->_decode_mime_string($who) if $who =~ /=\?/i;
+ $main::opt_mimedecodequoted = $mimeopt;
+
+ _PRINT_DEBUG("-- print_li_filename id=$id file=$filename");
+
+ if (defined $filename && $filename) {
+ _print_raw_str($wh, "<!-- LI id=$id -->\n", $code);
+
+ _print_raw_str($wh, "<LI>\n", $code);
+ _print_raw_str($wh, "<A HREF=\"$filename\">\n", $code);
+ _print_safe_str($wh, $subject, $code);
+ _print_raw_str($wh, ",\n", $code);
+ _print_safe_str($wh, "$who\n", $code);
+ _print_raw_str($wh, "</A>\n", $code);
+ }
+}
+
+
+# Descriptions: extrace gecos field in $address
+# Arguments: OBJ($self) STR($address)
+# Side Effects: none
+# Return Value: STR
+sub _address_to_gecos
+{
+ my ($self, $address) = @_;
+
+ use Mail::Message::Utils;
+ return Mail::Message::Utils::from_address_to_name($address);
+}
+
+
+# Descriptions: mask the detail of address
+# Arguments: OBJ($self) STR($field)
+# Side Effects: none
+# Return Value: NUM
+sub _is_mask_address
+{
+ my ($self, $field) = @_;
+ my $type = $self->{ _address_mask_type } || 'all';
+
+ if ($type eq 'all') {
+ if ($field =~ /^(From|To|Cc)$/i) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+ else {
+ return 0;
+ }
+}
+
+
+# Descriptions: decode MIME-encoded $str
+# Arguments: OBJ($self) STR($str) HASH_REF($options)
+# Side Effects: none
+# Return Value: STR
+sub _decode_mime_string
+{
+ my ($self, $str, $options) = @_;
+ my $charset = $options->{ 'charset' } || $self->{ _charset };
+ my $code = _charset_to_code($charset) || 'euc';
+
+ if (defined($str) && $str) {
+ use Mail::Message::Subject;
+ my $sbj = new Mail::Message::Subject $str;
+ $sbj->mime_header_decode();
+ return $sbj->as_str();
+ }
+ else {
+ return '';
+ }
+}
+
+
+# Descriptions: convert $str to $out_code code
+# Arguments: OBJ($self) STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+sub _convert
+{
+ my ($self, $str, $out_code, $in_code) = @_;
+
+ return '' unless defined $str && $str ne '';
+}
+
+# Descriptions: convert $str to $out_code code (non method version)
+# XXX you should remove this function.
+# Arguments: STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+sub __nc_convert
+{
+ my ($str, $out_code, $in_code) = @_;
+
+ use Mail::Message::Encode;
+ my $encode = new Mail::Message::Encode;
+ return $encode->convert($str, $out_code, $in_code);
+}
+
+
+=head1 useful functions as entrance
+
+=head2 htmlify_file($file, $args)
+
+try to convert rfc822 message C<$file> to HTML.
+
+ $args = {
+ directory => "destination directory",
+ };
+
+=head2 htmlify_dir($dir, $args)
+
+try to convert all rfc822 messages to HTML in C<$dir> directory.
+
+ $args = {
+ directory => "destination directory",
+ };
+
+=cut
+
+
+# Descriptions: convert $file to HTML
+# Arguments: OBJ($self) STR($file) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub htmlify_file
+{
+ my ($self, $file, $args) = @_;
+ my $dst_dir = $args->{ output_dir };
+ my $indexs = \@indexs;
+
+ unless (-f $file) {
+ print STDERR "no such file: $file\n" if $debug;
+ return;
+ }
+
+ unless (-s $file) {
+ print STDERR "empty file: $file\n" if $debug;
+ return;
+ }
+
+ use File::Basename;
+ my $id = basename($file);
+ my $html = new Mail::Message::ToHTML $args;
+
+ if ($debug) {
+ printf STDERR "htmlify_file( id=%-6s src=%s )\n", $id, $file;
+ }
+
+ _PRINT_DEBUG("htmlify_rfc822_message begin");
+ unless ($html->htmlify_rfc822_message({
+ id => $id,
+ src => $file,
+ }) ) {
+ _PRINT_DEBUG("htmlify_rfc822_message end no change");
+ return;
+ }
+ _PRINT_DEBUG("htmlify_rfc822_message end");
+
+ if ($debug) {
+ printf STDERR "htmlify_file( id=%-6s ) update relation\n", $id;
+ }
+
+ _PRINT_DEBUG("-- msg_html_links");
+ $html->update_msg_html_links( $id );
+
+ for my $index (@$indexs) {
+ if ($index eq "month") {
+ _PRINT_DEBUG("-- monthly id index");
+ $html->update_monthly_id_index({ id => $id });
+ }
+
+ if ($index eq "all") {
+ _PRINT_DEBUG("-- id index");
+ $html->update_id_index({ id => $id });
+ }
+
+ if ($index eq "thread") {
+ _PRINT_DEBUG("-- thread index");
+ $html->update_thread_index({ id => $id });
+ }
+
+ if ($index eq "month_thread") {
+ _PRINT_DEBUG("-- month thread index");
+ $html->update_monthly_thread_index({ id => $id });
+ }
+
+ if ($index eq "top") {
+ _PRINT_DEBUG("-- top index");
+ $html->create_top_index();
+ }
+ }
+
+ # no more action for old files
+ if ($html->is_ignore($id)) {
+ warn("not process $id (already exists)") if $debug;
+ }
+ else {
+ if ($debug) {
+ printf STDERR " converted( id=%-6s src=%s )\n", $id, $file;
+ }
+ }
+}
+
+
+# Descriptions: convert all articles in specified directory
+# Arguments: OBJ($self) STR($src_dir) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub htmlify_dir
+{
+ my ($self, $src_dir, $args) = @_;
+ my $dst_dir = $args->{ output_dir };
+ my $min = 0;
+ my $max = 0;
+ my $has_fork = 1; # ok on unix and perl>5.6 on wine32.
+
+ print STDERR "src = $src_dir\ndst = $dst_dir\n" if $debug;
+
+ use DirHandle;
+ my $dh = new DirHandle $src_dir;
+ if (defined $dh) {
+ FILE:
+ for my $file ( $dh->read() ) {
+ next FILE unless $file =~ /^\d+$/;
+
+ # initialize $min
+ unless ($min) { $min = $file;}
+
+ $max = $max < $file ? $file : $max;
+ $min = $min > $file ? $file : $min;
+ }
+ }
+
+ # overwride
+ $has_fork = $args->{ has_fork } if defined $args->{ has_fork };
+ $max = $args->{ max } if defined $args->{ max };
+
+ print STDERR " scan ( $min .. $max ) for $src_dir\n" if $debug;
+ for my $id ( $min .. $max ) {
+ use File::Spec;
+ my $file = File::Spec->catfile($src_dir, $id);
+
+ unless ( $has_fork ) {
+ $self->htmlify_file($file, $args);
+ }
+ else {
+ my $pid = fork();
+ if ($pid < 0) {
+ croak("cannot fork");
+ }
+ elsif ($pid == 0) {
+ $self->htmlify_file($file, $args);
+ exit 0;
+ }
+
+ # parent
+ my $dying;
+ while (($dying = wait()) != -1 && ($dying != $pid) ){
+ ;
+ }
+ }
+ }
+}
+
+
+#
+# debug
+#
+if ($0 eq __FILE__) {
+ my $dir = "/tmp/htdocs";
+ my $has_fork = defined $ENV{'HAS_FORK'} ? 1 : 0;
+ my $max = defined $ENV{'MAX'} ? $ENV{'MAX'} : 1000;
+ my $charset = 'euc-jp';
+ my $opts = {
+ output_dir => "/tmp/htdocs",
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ };
+
+ eval q{
+ my ($t, $time_b, $time_e);
+ for my $x (@ARGV) {
+ $time_b = time;
+ print STDERR "debug.main processing $x ...";
+
+ if (-f $x) {
+ eval q{
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_file($x, {
+ output_dir => "/tmp/htdocs",
+ directory => $dir,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ });
+ };
+ print STDERR $@ if $@;
+ }
+ elsif (-d $x) {
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_dir($x, {
+ output_dir => "/tmp/htdocs",
+ directory => $dir,
+ has_fork => $has_fork,
+ max => $max,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ });
+ }
+
+ $t = time - $time_b;
+ print STDERR "\t$t sec.\n";
+ }
+ print STDERR "done.\n";
+ };
+
+ if ($@) { croak($@);}
+}
+
+
+=head1 TODO
+
+ expiration
+
+ sub directory?
+
+=head1 CODING STYLE
+
+See C<http://www.fml.org/software/FNF/> on fml coding style guide.
+
+=head1 AUTHOR
+
+Ken'ichi Fukamachi
+
+=head1 COPYRIGHT
+
+Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi
+
+All rights reserved. This program is free software; you can
+redistribute it and/or modify it under the same terms as Perl itself.
+
+=head1 HISTORY
+
+Mail::Message::ToHTML first appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+This class is renamed from C<Mail::HTML::Lite> 1.40 (2001-2002).
+
+=cut
+
+
+1;
diff --git a/host-artifacts/Mail/Message/ToHTML.pm.save.1 b/host-artifacts/Mail/Message/ToHTML.pm.save.1
new file mode 100644
index 00000000..58f96d3b
--- /dev/null
+++ b/host-artifacts/Mail/Message/ToHTML.pm.save.1
@@ -0,0 +1,2609 @@
+#-*- perl -*-
+#
+# Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi
+# All rights reserved. This program is free software; you can
+# redistribute it and/or modify it under the same terms as Perl itself.
+#
+# $FML: ToHTML.pm,v 1.85 2006/06/15 10:35:24 tmu Exp $
+#
+
+package Mail::Message::ToHTML;
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD $hints);
+use Carp;
+
+my $is_strict_warn = 0;
+my $debug = 0;
+my $URL =
+ "<A HREF=\"http://www.fml.org/software/\">Mail::Message::ToHTML</A>";
+
+my $version = q$FML: ToHTML.pm,v 1.85 2006/06/15 10:35:24 tmu Exp $;
+my $versionid = 0;
+if ($version =~ /,v\s+([\d\.]+)\s+/) {
+ $versionid = "$1";
+ $version = "$URL $versionid";
+}
+
+=head1 NAME
+
+Mail::Message::ToHTML - convert text format mail to HTML format
+
+=head1 SYNOPSIS
+
+ ... lock by something ...
+
+ use Mail::Message::ToHTML;
+ my $obj = new Mail::Message::ToHTML {
+ charset => "euc-jp",
+ directory => "/var/www/htdocs/ml/elena",
+ };
+
+ $obj->htmlify_rfc822_message({
+ id => 1,
+ src => "/var/spool/ml/elena/spool/1",
+ });
+
+ ... unlock by something ...
+
+This module itself provides no lock function.
+please use flock() built in perl or CPAN lock modules for it.
+
+=head1 DESCRIPTION
+
+=head2 Message structure created as HTML
+
+HTML-fied message has following structure.
+something() below is method name.
+
+ for example
+ -------------------------------------------------------------------
+ html_start() <HTML><HEAD> ... </HEAD><BODY>
+ mhl_preamble() <!-- comment used by this module -->
+ mhl_separator() <HR>
+
+ message header
+ From: ...
+ Subject: ...
+
+ mhl_separator() <HR>
+
+ message body
+
+ mhl_separator() <HR>
+ mhl_footer() <!-- comment used by this module -->
+ html_end() </BODY></HTML>
+
+=head1 METHODS
+
+=head2 new($args)
+
+ $args = {
+ directory => $directory,
+ };
+
+C<$directory> is top level directory where html-fied articles are
+stored.
+
+=cut
+
+
+# Descriptions: constructor.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: OBJ
+sub new
+{
+ my ($self, $args) = @_;
+ my ($type) = ref($self) || $self;
+ my $me = {};
+
+ $me->{ _charset } = $args->{ charset } || 'us-ascii';
+ $me->{ _html_base_directory } = $args->{ output_dir };
+ $me->{ _db_type } = $args->{ db_type };
+ $me->{ _db_name } = $args->{ db_name };
+ $me->{ _db_base_dir } = $args->{ db_base_dir };
+ $me->{ _is_attachment } = defined($args->{ attachment }) ? 1 : 0;
+ $me->{ _args } = $args;
+ $me->{ _num_attachments } = 0; # for child process
+ $me->{ _use_subdir } = 'yes';
+ $me->{ _subdir_style } = 'yyyymm';
+ $me->{ _html_id_order } = $args->{ index_order } || 'normal';
+ $me->{ _use_address_mask } = $args->{ use_address_mask } || 'yes';
+ $me->{ _address_mask_type } = $args->{ address_mask_type } || 'all';
+
+ # global hints
+ $hints = $args->{ hints } || {};
+
+ use Mail::Message::Thread;
+ my $t = new Mail::Message::Thread $args;
+ $me->{ _thread_object } = $t;
+
+ return bless $me, $type;
+}
+
+
+# Descriptions: destructor.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: none
+sub DESTROY
+{
+ my ($self) = @_;
+
+ _PRINT_DEBUG("ToHTML::DESTROY");
+ 1;
+}
+
+
+=head2 htmlify_rfc822_message($args)
+
+convert mail to html.
+
+ $args = {
+ id => $id,
+ path => $path,
+ };
+
+where C<$path> is file path.
+
+=cut
+
+
+# Descriptions: top level entrance to convert mail to html
+# Arguments: OBJ($self) HASH_REF($args)
+# $args = { id => $id, path => $path };
+# $id identifier (e.g. "1" (article id))
+# $path file path (e.g. "/some/where/1");
+# Side Effects: none
+# Return Value: none
+sub htmlify_rfc822_message
+{
+ my ($self, $args) = @_;
+
+ # prepare source
+ use Mail::Message;
+ use FileHandle;
+ my $rh = new FileHandle $args->{ src };
+ my $msg = Mail::Message->parse( { fd => $rh } );
+ my $hdr = $msg->whole_message_header;
+ my $body = $msg->whole_message_body;
+ $self->{ _current_msg } = $msg;
+ $self->{ _current_hdr } = $hdr;
+ $self->{ _current_body } = $body;
+
+ # initialize basic information
+ # $id = article id
+ # $src = source file
+ # $dst = destination file (target html)
+ my ($id, $src, $dst) = $self->_init_htmlify_rfc822_message($args);
+ $self->{ _debug_id } = $id;
+
+ # save information for index.html and thread.html
+ $self->cache_message_info($msg, { id => $id,
+ src => $src,
+ dst => $dst,
+ } );
+
+ # target html not define
+ unless ($dst) {
+ $self->{ _ignore_list }->{ $id } = 1; # ignore flag
+ warn("html file for $id not define") if $debug;
+ return undef;
+ }
+
+ # target html exists already.
+ if (-f $dst) {
+ $self->{ _ignore_list }->{ $id } = 1; # ignore flag
+ warn("html file for $id already exists") if $debug;
+ return undef;
+ }
+
+ # prepare output channel
+ my $wh = $self->_set_output_channel( { dst => $dst } );
+ unless (defined $wh) {
+ croak("cannot open output file $dst\n");
+ }
+
+ # before main message
+ $self->html_start($wh, { message => $msg });
+ $self->mhl_preamble($wh);
+
+ # analyze $msg, chain of Mail::Message objects.
+ # See Mail::Message class for more detail.
+ # XXX we use $m->{ next } here, but we should avoid this style and
+ # XXX prepare access method for it in Mail::Message class.
+ my ($m, $type, $attach);
+ CHAIN:
+ for ($m = $msg; defined($m) ; $m = $m->{ 'next' }) {
+ $type = $m->data_type;
+
+ last CHAIN if $type eq 'multipart.close-delimiter'; # last of multipart
+ next CHAIN if $type =~ /^multipart/; # multipart type is special.
+
+ unless ($type =~ /^\w+\/[-\w\d\.]+$/) {
+ warn("invalid type={$type}");
+ next CHAIN;
+ }
+
+ # header (Mail::Message object uses this special type)
+ if ($type eq 'text/rfc822-headers') {
+ $self->mhl_separator($wh);
+ my $charset = $self->{ _charset };
+ my $header = $self->_format_safe_header($msg);
+ _print_raw_str($wh, $header, $charset);
+ $self->mhl_separator($wh);
+ }
+ # message/rfc822 case (attached rfc822 message)
+ elsif ($type eq 'message/rfc822') {
+ $attach++;
+
+ my $tmpf = $self->_create_temporary_file_in_raw_mode($m);
+ if (defined $tmpf && -f $tmpf) {
+ # write attachement into a separete file
+ my $outf = _gen_attachment_filename($dst, $attach, 'html');
+ my $args = $self->{ _args };
+ $args->{ attachment } = 1; # clarify not top level content.
+ my $text = new Mail::Message::ToHTML $args;
+ $text->htmlify_rfc822_message({
+ parent_id => $id,
+ src => $tmpf,
+ dst => $outf,
+ });
+
+ # show inline <HREF> link,
+ # which appears in parent html ( == $wh channel ).
+ $self->_print_inline_object_link({
+ fh => $wh, # file descriptor
+ type => $type, # XXX derived from input message
+ num => $attach, # number
+ file => $outf, # temporary file name
+ });
+
+ unlink $tmpf;
+ }
+ }
+ # text/plain case.
+ # XXX inline expansion.
+ elsif ($type eq 'text/plain') {
+ if ($self->{ _use_address_mask } eq 'yes') {
+ $self->_text_plain_part_safe_print($wh, $m);
+ }
+ else { # original
+ $self->_text_safe_print({
+ fh => $wh, # parent html
+ data => $m->message_text(),
+ charset => $m->charset(),
+ encoding => $m->encoding_mechanism(),
+ });
+ }
+ }
+ # create a separete file for attachment
+ else {
+ $attach++;
+
+ # write attachement into a separete file
+ my $outf = _gen_attachment_filename($dst, $attach, $type);
+ my $enc = $m->encoding_mechanism;
+ my $msginfo = { message => $m };
+
+ # e.g. text/xxx case (e.g. text/html case)
+ if ($type =~ /^text/) {
+ # 1. firstly saved to temporary file $tmpf in "raw" mode
+ my $tmpf = $self->_create_temporary_filename();
+ $msginfo->{ file } = $tmpf;
+
+ # once create temporary file
+ _PRINT_DEBUG("attachment: type=$type attach=$attach enc=$enc");
+ if ($enc) {
+ $self->_binary_print($msginfo); # XXX raw mode
+ }
+ else {
+ $self->_text_raw_print($msginfo); # XXX raw mode
+ }
+
+ # 2. secondary convert $tmpf to real target $outf with
+ # some modification e.g. metachars escaping, ...
+ # disable html tag in file saved in raw mode.
+ if (-f $tmpf) {
+ $msginfo->{ description } = "(HTML TAGs are disabled)";
+ _disable_html_tag_in_file($tmpf, $outf);
+ unlink $tmpf;
+ }
+ }
+ # e.g. image/gif not text/* nor message/*
+ else {
+ $msginfo->{ file } = $outf;
+ $self->_binary_print($msginfo);
+ }
+
+ # show inline <HREF> link appeared in parent html.
+ $self->_print_inline_object_link({
+ inline => 1,
+ fh => $wh,
+ type => $type,
+ num => $attach,
+ file => $outf,
+ info => $msginfo,
+ });
+ }
+ }
+
+ # show navigation bar et.al. after message itself
+ $self->mhl_separator($wh);
+ $self->mhl_footer($wh);
+ $self->html_end($wh);
+ return 1;
+}
+
+
+# Descriptions: copy $inf file to $outf file with disabling HTML tag
+# by _print_safe_buf().
+# Arguments: STR($inf) STR($outf)
+# Side Effects: create $outf file
+# Return Value: none
+sub _disable_html_tag_in_file
+{
+ my ($inf, $outf) = @_;
+ my $mask = umask();
+
+ umask(022);
+
+ use FileHandle;
+ my $rh = new FileHandle $inf;
+ my $wh = new FileHandle "> $outf";
+ if (defined $rh) {
+ my $buf = '';
+
+ my $b;
+ while ($b = <$rh>) { $buf .= $b;}
+
+ _print_safe_buf($wh, $buf);
+ $wh->close;
+ $rh->close;
+ }
+
+ umask($mask);
+}
+
+
+# Descriptions: return HTML filename
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR or UNDEF
+sub html_filename
+{
+ my ($self, $id) = @_;
+ my $use_subdir = $self->{ _use_subdir };
+
+ # relative path under html_base_dir
+ if (defined($id) && ($id > 0)) {
+ if ($use_subdir eq 'yes') {
+ return $self->_html_file_subdir_name($id);
+ }
+ else {
+ return "msg${id}.html";
+ }
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: return HTML sub directory string
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR
+sub _html_file_subdir_name
+{
+ my ($self, $id) = @_;
+ my $ndb = $self->ndb();
+ my $subdir = '';
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $subdir_style = $self->{ _subdir_style };
+ my $dir_mode = $self->{ _dir_mode } || 0755;
+
+ if ($subdir_style eq 'yyyymm') {
+ my $hdr = $self->{ _current_hdr };
+ use Mail::Message::Utils;
+ $subdir = Mail::Message::Utils::get_time_from_header($hdr, 'yyyymm');
+
+ use File::Spec;
+ my $xsubdir = File::Spec->catfile($html_base_dir, $subdir);
+ unless (-d $xsubdir) {
+ my $mask = umask();
+ umask(022);
+ mkdir($xsubdir, $dir_mode);
+ umask($mask);
+ }
+ }
+ else {
+ croak("unknown \$subdir_style");
+ }
+
+ if ($subdir) {
+ use File::Spec;
+ return File::Spec->catfile($subdir, "msg$id.html");
+ }
+ else {
+ warn("not create msg$id.html");
+ return undef;
+ }
+}
+
+
+# Descriptions: return HTML file path
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR
+sub html_filepath
+{
+ my ($self, $id) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+
+ if (defined($id) && ($id > 0)) {
+ my $filename = $self->html_filename($id);
+ return undef unless($filename);
+
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, $filename);
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: parse $args and return file id, name, path.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: ARRAY(NUM, STR, STR)
+sub _init_htmlify_rfc822_message
+{
+ my ($self, $args) = @_;
+ my ($id, $src, $dst);
+
+ if (defined $args->{ src }) {
+ $src = $args->{ src };
+ }
+ else {
+ croak("htmlify_rfc822_message: \$src is mandatory\n");
+ }
+
+ if (defined $args->{ id }) {
+ my $html_base_dir = $self->{ _html_base_directory };
+ $id = $args->{ id };
+ $dst = $self->html_filepath($id);
+ }
+ # this object is an attachment if parent_id is specified.
+ elsif (defined $args->{ parent_id }) {
+ $self->{ _num_attachments }++;
+ $id = $args->{ parent_id } .'.'. $self->{ _num_attachments };
+ $dst = $args->{ dst };
+ }
+ # last resort: give unique identifier
+ elsif (defined $args->{ dst }) {
+ $id = sprintf("%s.%s", time, $$);
+ $dst = $args->{ dst };
+ }
+ # oops ;) wrong call of this function
+ else {
+ croak("htmlify_rfc822_message: specify \$id or \$dst\n");
+ }
+
+ return ($id, $src, $dst);
+}
+
+
+# Descriptions: show html header + file title in <BODY>
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub html_start
+{
+ my ($self, $wh, $args) = @_;
+ my ($msg, $hdr, $title);
+
+ if (defined $args->{ title }) {
+ $title = $args->{ title };
+ }
+ elsif (defined $args->{ message }) {
+ $msg = $args->{ message };
+ $hdr = $msg->whole_message_header;
+ $title = $self->_decode_mime_string( $hdr->get('article_subject') ||
+ $hdr->get('subject') );
+ }
+
+ print $wh "<!-- X-FML 8 ToHTML $versionid -->\n";
+ print $wh
+ q{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">};
+ print $wh "\n";
+ print $wh "<HTML>\n";
+ print $wh "<HEAD>\n";
+
+ if (defined $self->{ _charset }) {
+ my $charset = $self->{ _charset };
+ print $wh "<META http-equiv=\"Content-Type\"\n";
+ print $wh " content=\"text/html; charset=${charset}\">\n";
+ }
+
+ if (defined $self->{ _stylsheet }) {
+ my $css = $self->{ _stylsheet };
+ print $wh "<LINK rel=\"stylesheet\"\n";
+ print $wh " type=\"text/css\" href=\"fml.css\">\n";
+ }
+
+ if (defined $title) {
+ print $wh "<title>";
+ _print_safe_str($wh, $title);
+ print $wh "</title>\n";
+ }
+
+ print $wh "</HEAD>\n";
+ print $wh "<BODY>\n";
+ print $wh "<CENTER>";
+ _print_safe_str($wh, $title);
+ print $wh "</CENTER>\n";
+}
+
+
+# Descriptions: show html closing
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub html_end
+{
+ my ($self, $wh) = @_;
+ print $wh "</BODY>";
+ print $wh "</HTML>\n";
+}
+
+
+# Descriptions: show html separetor, we use <HR> now.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_separator
+{
+ my ($self, $wh) = @_;
+ print $wh "<HR>\n";
+}
+
+
+my $preamble_begin = "<!-- __PREAMBLE_BEGIN__ by Mail::Message::ToHTML -->";
+my $preamble_end = "<!-- __PREAMBLE_END__ by Mail::Message::ToHTML -->";
+my $footer_begin = "<!-- __FOOTER_BEGIN__ by Mail::Message::ToHTML -->";
+my $footer_end = "<!-- __FOOTER_END__ by Mail::Message::ToHTML -->";
+
+
+# Descriptions: prepare information area before main message appears.
+# Later, this area is replaced with useful information
+# e.g. thread link.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_preamble
+{
+ my ($self, $wh) = @_;
+ print $wh $preamble_begin, "\n";
+ print $wh $preamble_end, "\n";
+}
+
+
+# Descriptions: prepare information area after main message appears.
+# Later, this area is replaced with useful information
+# e.g. thread link.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_footer
+{
+ my ($self, $wh) = @_;
+ print $wh $footer_begin, "\n";
+ print $wh $footer_end, "\n";
+}
+
+
+# Descriptions: prepare write handle
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $args->{ dst } file if needed
+# Return Value: HANDLE
+sub _set_output_channel
+{
+ my ($self, $args) = @_;
+ my $dst = $args->{ dst };
+ my $wh = undef;
+
+ my $mask = umask();
+ umask(022);
+
+ if (defined $dst) {
+ use FileHandle;
+ $wh = new FileHandle "> $dst";
+ }
+ else {
+ $wh = \*STDOUT;
+ }
+
+ umask($mask);
+
+ return $wh;
+}
+
+
+# Descriptions: return temporary file path.
+# XXX temporary file is created under $db_dir not public space
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: STR
+sub _create_temporary_filename
+{
+ my ($self) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, "tmp.$$");
+}
+
+
+# Descriptions: create a temporary file with the content $msg
+# Arguments: OBJ($self) OBJ($msg)
+# Side Effects: create $tmpf file
+# Return Value: STR
+sub _create_temporary_file_in_raw_mode
+{
+ my ($self, $msg) = @_;
+ my $tmpf = $self->_create_temporary_filename();
+ my $mask = umask();
+ umask(022);
+
+ use FileHandle;
+ my $wh = new FileHandle "> $tmpf";
+ if (defined $wh) {
+ $wh->autoflush(1);
+
+ my $buf = $msg->message_text();
+ $wh->print($buf);
+ $wh->close;
+
+ umask($mask);
+ return ($tmpf);
+ }
+
+ umask($mask);
+ return undef;
+}
+
+
+# Descriptions: convert $file filepath to relative path
+# XXX UNIX specific ???
+# Arguments: OBJ($self) STR($file)
+# Side Effects: none
+# Todo: UNIX specific
+# Return Value: STR
+sub _relative_path
+{
+ my ($self, $file) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ $file =~ s/$html_base_dir//;
+ $file =~ s@^/@@;
+ return $file;
+}
+
+
+# Descriptions: print inline link as html for attachments e.g.
+# images, files et. al.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _print_inline_object_link
+{
+ my ($self, $args) = @_;
+ my $wh = $args->{ fh };
+ my $type = $args->{ type };
+ my $num = $args->{ num };
+ my $file = $self->_relative_path($args->{ file });
+ my $desc = '';
+ my $inline = defined( $args->{ inline } ) ? 1 : 0;
+
+ if (defined $args->{ info }->{ description }) {
+ $desc = $args->{ info }->{ description };
+ }
+
+ if ($inline && $type =~ /image/) {
+ print $wh "<BR><IMG SRC=\"../$file\">$desc\n";
+ }
+ else {
+ my $t = $file;
+ print $wh "<BR><A HREF=\"../$file\" TARGET=\"$t\"> $type $num </A>";
+ print $wh "$desc<BR>\n";
+ }
+}
+
+
+# Descriptions: return attachment filename
+# Arguments: STR($dst) STR($attach) STR($suffix)
+# Side Effects: none
+# Todo: UNIX specific
+# Return Value: STR
+sub _gen_attachment_filename
+{
+ my ($dst, $attach, $suffix) = @_;
+ my $outf = $dst;
+ if ($suffix =~ m@/@) { $suffix =~ s@.*/@@;}
+
+ $outf =~s/\.html$//;
+ $outf = "$outf.$attach.$suffix";
+ return $outf;
+}
+
+
+# default header to show
+my @header_field = qw(From To Cc Subject Date Message-Id X-Sequence);
+
+
+# Descriptions: format header of $msg with escaping HTML metachars
+# and disabling special HTML tags.
+# See _sprintf_safe_str() for how to escape.
+# Arguments: OBJ($self) OBJ($msg)
+# Side Effects: none
+# XXX $buf is printed out later in raw mode.
+# Return Value: STR
+sub _format_safe_header
+{
+ my ($self, $msg) = @_;
+ my ($buf);
+ my $hdr = $msg->whole_message_header;
+ my $header_field = \@header_field;
+
+ my $mimeopt = $main::opt_mimedecodequoted;
+ $main::opt_mimedecodequoted = 1;
+
+ # header
+ $buf .= "<SPAN CLASS=mailheaders>\n";
+ for my $field (@$header_field) {
+ if (defined($hdr->get($field))) {
+ $buf .= "<SPAN CLASS=${field}>\n";
+ $buf .= "${field}: ";
+ $buf .= "</SPAN>\n";
+
+ my $xbuf = $hdr->get($field);
+
+ # mask the raw address against address collector (e.g. spammer).
+ if ($self->{ _use_address_mask } eq 'yes') {
+ if ($self->_is_mask_address($field)) {
+ $xbuf = $self->_address_to_gecos($xbuf);
+ }
+ }
+
+ $xbuf = $self->_decode_mime_string($xbuf) if $xbuf =~ /=\?/i;
+ $buf .= "<SPAN CLASS=${field}-value>\n";
+ $buf .= _sprintf_safe_str($xbuf);
+ $buf .= "</SPAN>\n";
+ $buf .= "<BR>\n";
+ }
+ }
+ $buf .= "</SPAN>\n";
+
+ $main::opt_mimedecodequoted = $mimeopt;
+ return($buf);
+}
+
+
+# create index.html type (all thread month month_thread top)
+my @indexs = qw(month month_thread top);
+
+# Descriptions: show link to indexes as navigation
+# Arguments: HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _format_index_navigator
+{
+ my ($args) = @_;
+ my $use_subdir = defined $args->{use_subdir} ? $args->{use_subdir} : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $str;
+ my $indexs = \@indexs;
+
+ for my $index (@$indexs) {
+ $str .= qq{<A HREF=\"${prefix}index_all.html\">[ID Index]</A>\n} if ($index eq "all");
+ $str .= qq{<A HREF=\"${prefix}thread.html\">[Thread Index]</A>\n} if ($index eq "thread");
+ $str .= qq{<A HREF=\"${prefix}monthly_index.html\">[Monthly ID Index]</A>\n} if ($index eq "month");
+ $str .= qq{<A HREF=\"${prefix}monthly_thread.html\">[Monthly Thread Index]</A>\n} if ($index eq "month_thread");
+ $str .= qq{<A HREF=\"${prefix}index.html\">[Top Index]</A>\n} if ($index eq "top");
+ };
+
+return $str;
+}
+
+
+# Descriptions: print text/plain part by printing each paragraph.
+# mask raw mail addresses in the signature if could.
+# Arguments: OBJ($self) HANDLE($wh) OBJ($m)
+# Side Effects: none
+# Return Value: STR
+sub _text_plain_part_safe_print
+{
+ my ($self, $wh, $m) = @_;
+ my $total = $m->paragraph_total();
+
+ # print each paragraph.
+ for (my $i = 1; $i <= $total ; $i++) {
+ my $buf = $m->nth_paragraph($i);
+
+ # try to hide domain since the last paragraph must be signature.
+ if ($self->{ _use_address_mask } eq 'yes') {
+ if ($i == $total) {
+ $buf =~ s/(\w+\@[\w\.]+)/$self->_address_to_gecos($1)/ge;
+ }
+ }
+
+ $self->_text_safe_print({
+ fh => $wh, # parent html
+ data => $buf,
+ charset => $m->charset(),
+ encoding => $m->encoding_mechanism(),
+ });
+ }
+}
+
+
+# Descriptions: print out text data with escaping by _print_safe_buf()
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+use Encode qw(decode);
+use MIME::Base64 qw(decode_base64);
+use MIME::QuotedPrint qw(decode_qp);
+
+sub _text_safe_print
+{
+ my ($self, $args) = @_;
+ my $buf = $args->{ data };
+ my $fh = $args->{ fh } || \*STDOUT;
+ my $in_code = $args->{ charset } || undef;
+ my $encoding = $args->{ encoding } || '7bit';
+
+ if ($encoding eq 'base64') {
+ $buf = decode_base64($buf);
+ }
+ elsif ($encoding eq 'quoted-printable') {
+ $buf = decode_qp($buf);
+ }
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined $buf && $buf ne '') {
+ $buf = $self->_convert($buf, 'euc');
+ }
+
+ _print_safe_buf($fh, $buf);
+}
+
+# Descriptions: print out message without escaping
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $outf
+# Return Value: none
+sub _text_raw_print
+{
+ my ($self, $args) = @_;
+ my $msg = $args->{ message }; # Mail::Message object
+ my $type = $msg->data_type;
+ my $enc = $msg->encoding_mechanism;
+ my $buf = $msg->message_text();
+
+ if (defined( $args->{ file } )) {
+ my $mask = umask();
+ umask(022);
+
+ my $outf = $args->{ file };
+ use FileHandle;
+ my $fh = new FileHandle "> $outf";
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined $buf && $buf) {
+ $buf = $self->_convert($buf, 'euc');
+ }
+ print $fh $buf, "\n";
+ $fh->close();
+
+ umask($mask);
+ }
+}
+
+
+# Descriptions: print out binary with MIME encoding or
+# text with escaping
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $outf file
+# Return Value: none
+use FileHandle;
+use MIME::Base64 qw(decode_base64);
+use MIME::QuotedPrint qw(decode_qp);
+
+sub _binary_print
+{
+ my ($self, $args) = @_;
+ my $msg = $args->{ message }; # Mail::Message object
+ my $type = $msg->data_type;
+ my $enc = $msg->encoding_mechanism || '';
+ my $mask = umask();
+
+ umask(022);
+
+ if (defined( $args->{ file } )) {
+ my $outf = $args->{ file };
+ my $fh = FileHandle->new("> $outf");
+
+ if (defined $fh) {
+ $fh->autoflush(1);
+ binmode($fh);
+
+ my $data = $msg->message_text();
+
+ if ($enc eq 'base64') {
+ print $fh decode_base64($data);
+ }
+ elsif ($enc eq 'quoted-printable') {
+ print $fh decode_qp($data);
+ }
+ elsif ($enc eq '7bit') {
+ _print_safe_str($fh, $data);
+ }
+ else {
+ my $r = "*** unknown MIME encoding enc='$enc' ***\n";
+ _print_safe_str($fh, $r);
+ _print_safe_str($fh, $data);
+ }
+
+ $fh->close();
+ }
+ }
+
+ umask($mask);
+}
+
+=head2 is_ignore($id)
+
+we should not process this C<$id>
+
+=cut
+
+
+# Descriptions: check whether article $id is ignored
+# Arguments: OBJ($self) STR($id)
+# Side Effects: none
+# Return Value: 1 or 0
+sub is_ignore
+{
+ my ($self, $id) = @_;
+
+ return defined($self->{ _ignore_list }->{ $id }) ? 1 : 0;
+}
+
+
+
+=head1 METHODS for index and thread
+
+=head2 cache_message_info($msg, $args)
+
+save information into DB.
+See section C<Internal Data Presentation> for more detail.
+
+=cut
+
+
+# Descriptions: update database on message header, thread relation
+# et. al.
+# Arguments: OBJ($self) OBJ($msg) HASH_REF($args)
+# Side Effects: update database
+# Return Value: none
+sub cache_message_info
+{
+ my ($self, $msg, $args) = @_;
+ my $ndb = $self->ndb();
+ my $id = $args->{ id };
+ my $src = $args->{ src };
+ my $dst = $args->{ dst };
+
+ $ndb->set_key($id);
+
+ $ndb->set('html_filename', $id, $self->html_filename($id));
+ $ndb->set('html_filepath', $id, $dst);
+
+ unless ($ndb->get('message_id', $id)) {
+ # analyze $msg only if not yet analyzed.
+ print STDERR "debug: analyze $id.\n" if $debug;
+ $ndb->add($msg);
+ }
+ else {
+ print STDERR "debug: already analyzed!\n" if $debug;
+ }
+}
+
+
+# Descriptions: return Mail::Message::DB object.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: OBJ
+sub ndb
+{
+ my ($self) = @_;
+ my $t = $self->{ _thread_object };
+
+ return $t->db();
+}
+
+
+=head2 update_msg_html_links($id)
+
+update link relation around C<$id>.
+
+=cut
+
+
+# Descriptions: top level dispatcher to update database.
+# _msg_file_rewrite_links() has real function for updating.
+# Arguments: OBJ($self) STR($id)
+# Side Effects: update databse
+# Return Value: none
+sub update_msg_html_links
+{
+ my ($self, $id) = @_;
+ my $info = $self->evaluate_links_relation($id);
+ my $list = $self->{ _affected_idlist } = [];
+
+ if ($self->is_ignore($id)) {
+ warn("not update relation around $id") if $debug;
+ return undef;
+ }
+
+ # sanity
+ return unless defined $id;
+ return unless $id;
+
+ # update target itself, of course
+ $self->_msg_file_rewrite_links($id);
+ push(@$list, $id);
+
+ # no rewriting for myself
+ my %uniq = ( $id => 1 );
+
+ KEY:
+ for my $_link (qw(prev_id next_id prev_thread_id next_thread_id)) {
+ if (defined $info->{ $_link }) {
+ my $_id = $info->{ $_link };
+
+ next KEY if $uniq{ $_id };
+ $uniq{ $_id } = 1;
+
+ _PRINT_DEBUG("try: rewrite $_link links in msg $_id");
+
+ if (defined $_id && $_id) {
+ $self->_msg_file_rewrite_links($_id);
+ push(@$list, $_id);
+ }
+ }
+ else {
+ _PRINT_DEBUG("error: fail to rewrite msg $_link");
+ }
+ }
+
+ # hint cached on memory, provided by _print_thread().
+ if (defined $self->{ _hint_ref_key_list }->{ $id }) {
+ my $thread_list = $self->{ _hint_ref_key_list }->{ $id } || [];
+
+ # update link relation for all articles in this thread.
+ KEY:
+ for my $id (@$thread_list) {
+ next KEY if $uniq{ $id};
+ $uniq{ $id } = 1;
+
+ $self->_msg_file_rewrite_links( $id );
+ push(@$list, $id);
+ }
+ }
+}
+
+
+# Descriptions: update link at preamble and footer of HTML-ified message.
+# Arguments: OBJ($self) STR($id)
+# Side Effects: rewrite index file
+# Return Value: none
+sub _msg_file_rewrite_links
+{
+ my ($self, $id) = @_;
+ my $info = $self->evaluate_links_relation($id);
+ my $preamble = $self->evaluate_safe_preamble($info);
+ my $footer = $self->evaluate_safe_footer($info);
+ my $code = _charset_to_code($self->{ _charset });
+
+ my $pat_preamble_begin = quotemeta($preamble_begin);
+ my $pat_preamble_end = quotemeta($preamble_end);
+ my $pat_footer_begin = quotemeta($footer_begin);
+ my $pat_footer_end = quotemeta($footer_end);
+
+ my $mask = umask();
+
+ umask(022);
+
+ _PRINT_DEBUG("try _msg_file_rewrite_links($id)");
+
+ use FileHandle;
+ my $file = $info->{ filepath };
+ if (defined $file && $file && -f $file) {
+ my ($old, $new) = ($file, "$file.new.$$");
+ my $rh = new FileHandle $old;
+ my $wh = new FileHandle "> $new";
+
+ if (defined $rh && defined $wh) {
+ my $buf;
+
+ _PRINT_DEBUG("rewrite: open msg $id");
+
+ LINE:
+ while ($buf = <$rh>) {
+ if ($buf =~ /^$pat_preamble_begin/
+ ..
+ $buf =~ /^$pat_preamble_end/) {
+ if ($buf =~ /^$pat_preamble_end/) {
+ _print_raw_str($wh, $preamble, $code);
+ }
+ next LINE;
+ }
+
+ if ($buf =~ /^$pat_footer_begin/
+ ..
+ $buf =~ /^$pat_footer_end/) {
+ if ($buf =~ /^$pat_footer_end/) {
+ _print_raw_str($wh, $footer, $code);
+ }
+ next LINE;
+ }
+
+ # just copy (rewrite only $preamble and $footer not message)
+ _print_raw_str($wh, $buf, $code);
+ }
+ $rh->close;
+ $wh->close;
+
+ unless (rename($new, $old)) {
+ croak("rename($new, $old) fail (id=$id)\n");
+ }
+ else {
+ _PRINT_DEBUG("done: rewritten links in msg $id");
+ }
+ }
+ else {
+ unless (defined $file) {
+ $new = $old = '(null string)';
+ }
+ warn("cannot open $old (id=$id)\n") unless defined $rh;
+ warn("cannot create $new (id=$id)\n") unless defined $wh;
+ }
+ }
+ else {
+ warn("undefined file for $id\n") if $is_strict_warn;
+ }
+
+ umask($mask);
+}
+
+
+# Descriptions: return thread link relation info et.al. for $id
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: HASH_REF
+sub evaluate_links_relation
+{
+ my ($self, $id) = @_;
+ my $ndb = $self->ndb();
+
+ return $ndb->get_tohtml_thread_summary($id);
+}
+
+
+# Descriptions: return preamble without metachars
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub evaluate_safe_preamble
+{
+ my ($self, $args) = @_;
+ my $link_prev_id = $args->{ link_prev_id };
+ my $link_next_id = $args->{ link_next_id };
+ my $link_prev_thread_id = $args->{ link_prev_thread_id };
+ my $link_next_thread_id = $args->{ link_next_thread_id };
+
+ my $use_subdir = $self->{ _use_subdir } eq 'yes' ? 1 : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $preamble = $preamble_begin. "\n";
+
+ my $mask = umask();
+
+ umask(022);
+
+ # for debug
+ $preamble .= "<!-- rewritten for id=$self->{ _debug_id } -->\n";
+
+ if (defined($link_prev_id) && $link_prev_id) {
+ $preamble .= "<A HREF=\"${prefix}$link_prev_id\">[Prev by ID]</A>\n";
+ }
+ else {
+ $preamble .= "[No Prev ID]\n";
+ }
+
+ if (defined($link_next_id) && $link_next_id) {
+ $preamble .= "<A HREF=\"${prefix}$link_next_id\">[Next by ID]</A>\n";
+ }
+ else {
+ $preamble .= "[No Next ID]\n";
+ }
+
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_prev_thread_id\">[Prev by Thread]</A>\n";
+ }
+ else {
+ if (defined $link_prev_id && $link_prev_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_prev_id\">[Prev by Thread]</A>\n";
+ }
+ else {
+ $preamble .= "[No Prev Thread]\n";
+ }
+ }
+
+ if (defined $link_next_thread_id && $link_next_thread_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_next_thread_id\">[Next by Thread]</A>\n";
+ }
+ else {
+ if (defined $link_next_id && $link_next_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_next_id\">[Next by Thread]</A>\n";
+ }
+ else {
+ $preamble .= "[No Next Thread]\n";
+ }
+ }
+
+ $preamble .= _format_index_navigator( { use_subdir => $use_subdir } );
+ $preamble .= $preamble_end. "\n";;
+
+ umask($mask);
+
+ return $preamble;
+}
+
+
+# Descriptions: return footer without metachars
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub evaluate_safe_footer
+{
+ my ($self, $args) = @_;
+ my $link_prev_id = $args->{ link_prev_id };
+ my $link_next_id = $args->{ link_next_id };
+ my $link_prev_thread_id = $args->{ link_prev_thread_id };
+ my $link_next_thread_id = $args->{ link_next_thread_id };
+ my $subject = $args->{ subject };
+
+ my $use_subdir = $self->{ _use_subdir } eq 'yes' ? 1 : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $footer = $footer_begin. "\n";;
+
+ if (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .= "<A HREF=\"${prefix}$link_prev_id\">Prev by ID: ";
+ if (defined $subject->{ prev_id } ) {
+ $footer .= _sprintf_safe_str( $subject->{ prev_id } );
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .= "<A HREF=\"${prefix}$link_next_id\">Next by ID: ";
+ if (defined $subject->{ next_id } ) {
+ $footer .= _sprintf_safe_str( $subject->{ next_id } );
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_thread_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_thread_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_thread_id });
+ }
+ $footer .= "</A>\n";
+ }
+ elsif (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_id });
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined $link_next_thread_id && $link_next_thread_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_thread_id\">Next by Thread: ";
+ if (defined $subject->{ next_thread_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_thread_id });
+ }
+ $footer .= "</A>\n";
+ }
+ elsif (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_id\">Next by Thread: ";
+ if (defined $subject->{ next_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_id });
+ }
+ $footer .= "</A>\n";
+ }
+
+ $footer .= qq{<BR>\n};
+ $footer .= _format_index_navigator( { use_subdir => $use_subdir } );
+ $footer .= $footer_end. "\n";;
+
+ return $footer;
+}
+
+
+=head2 update_id_index($args)
+
+update index.html.
+
+=cut
+
+
+# Descriptions: print navigation bar et.al. at upper half of indexes
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $new html
+# Return Value: none
+sub _print_index_begin
+{
+ my ($self, $args) = @_;
+ my $old = $args->{ old };
+ my $new = $args->{ new };
+ my $title = $args->{ title };
+ my $code = _charset_to_code($self->{ _charset });
+
+ my $mask = umask();
+
+ umask(022);
+
+ use FileHandle;
+ my $wh = new FileHandle "> $new";
+ $args->{ wh } = $wh;
+
+ $self->html_start($wh, { title => $title });
+
+ _print_raw_str($wh, _format_index_navigator(), $code);
+ $self->mhl_separator($wh);
+
+ umask($mask);
+}
+
+
+# Descriptions: print navigation bar et.al. at the end of indexes.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $new html
+# Return Value: none
+sub _print_index_end
+{
+ my ($self, $args) = @_;
+ my $wh = $args->{ wh };
+ my $old = $args->{ old };
+ my $new = $args->{ new };
+ my $title = $args->{ title };
+ my $code = $args->{ code };
+
+ $self->mhl_separator($wh);
+ _print_raw_str($wh, _format_index_navigator(), $code);
+
+ # append version information
+ _print_raw_str($wh, "<BR>Genereated by $version\n", $code);
+
+ $self->html_end($wh);
+
+ unless (rename($new, $old)) {
+ croak("rename($new, $old) fail\n");
+ }
+}
+
+# Descriptions: create Top index.html if no index.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create Top index.html
+# Return Value: none
+sub create_top_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Top Index",
+ old => "$html_base_dir/index.html",
+ new => "$html_base_dir/index.html.new.$$",
+ code => $code,
+ };
+
+ return if ( -f $htmlinfo-> { old } );
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: update index_all.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite index_all.html
+# Return Value: none
+sub update_id_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "ID Index",
+ old => "$html_base_dir/index_all.html",
+ new => "$html_base_dir/index_all.html.new.$$",
+ code => $code,
+ };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update index_all.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+
+ $self->_print_ul($wh, $db, $code);
+ if ($order eq 'reverse') {
+ for my $id (reverse (1 .. $max_id)) {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ else {
+ for my $id (1 .. $max_id) {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+=head2 update_monthly_id_index($args)
+
+=cut
+
+
+# Descriptions: update monthly index
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly index
+# Return Value: none
+sub update_monthly_id_index
+{
+ my ($self, $args) = @_;
+ my $affected_list = $self->{ _affected_idlist };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update index.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ # open databaes
+ my $db = $self->ndb();
+ my %month_update = ();
+
+ IDLIST:
+ for my $id (@$affected_list) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
+ $month_update{ $month } = 1;
+ }
+ }
+
+ # todo list
+ for my $month (sort keys %month_update) {
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
+
+ $self->_update_monthly_id_index($args, {
+ this_month => $this_month,
+ suffix => $suffix,
+ });
+ }
+
+ # update monthly_index.html
+ $self->_update_id_montly_index_master($args);
+}
+
+
+# Descriptions: update monthly index master
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly_index.html
+# Return Value: none
+sub _update_id_montly_index_master
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_index.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_index.html.new.$$");
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "ID Index",
+ old => $old,
+ new => $new,
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
+ my (@list) = sort __sort_yyyymm keys %$mlist;
+ my ($years) = _yyyy_range(\@list);
+
+ _print_raw_str($wh, "<table border='1'>", $code);
+
+ for my $year (sort {$b <=> $a} @$years) {
+ _print_raw_str($wh, "<tr>", $code);
+ _print_raw_str($wh, "<th> $year </th>", $code);
+
+ for my $month (1 .. 12) {
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $fn = "month.$xx.html";
+
+ use File::Spec;
+ my $file = File::Spec->catfile($html_base_dir, $fn);
+ if (-f $file) {
+ _print_raw_str($wh, "<td><a href=\"$fn\"> $month </a>", $code);
+ }
+ else {
+ _print_raw_str($wh, "<td>", $code);
+ }
+ _print_raw_str($wh, "</td>\n", $code);
+ }
+ _print_raw_str($wh, "</tr>\n", $code);
+ }
+ _print_raw_str($wh, "</table>", $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: return list of YYYY/MM format
+# Arguments: ARRAY_REF($list)
+# Side Effects: none
+# Return Value: ARRAY_REF
+sub _yyyy_range
+{
+ my ($list) = @_;
+ my ($yyyy) = {};
+
+ for my $y (@$list) {
+ if ($y =~ /^(\d{4})\/(\d{2})/o) {
+ $yyyy->{ $1 } = $1;
+ }
+ }
+
+ my (@yyyy) = keys %$yyyy;
+ return( \@yyyy );
+}
+
+
+# Descriptions: sort YYYY/MM formt strings
+# Arguments: none
+# Side Effects: none
+# Return Value: NUM
+sub __sort_yyyymm
+{
+ my ($xa, $xb) = ($a, $b);
+ $xa =~ s@/@@;
+ $xb =~ s@/@@;
+ if ($xa eq '') { $xa = 0;}
+ if ($xb eq '') { $xb = 0;}
+
+ $xa <=> $xb;
+}
+
+
+# Descriptions: update month.YYYYMM.html
+# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
+# Side Effects: update month.YYYYMM.html
+# Return Value: none
+sub _update_monthly_id_index
+{
+ my ($self, $args, $monthlyinfo) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
+ my $suffix = $monthlyinfo->{ suffix }; # yyyymm
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => "ID Monthly Index $this_month",
+ old => "$html_base_dir/month.${suffix}.html",
+ new => "$html_base_dir/month.${suffix}.html.new.$$",
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
+
+ $self->_print_ul($wh, $db, $code);
+ if ($order eq 'reverse') {
+ ID:
+ for my $id (reverse sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ else {
+ ID:
+ for my $id (sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+=head2 update_thread_index($args)
+
+update thread.html.
+
+=cut
+
+
+# Descriptions: update thread.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite thread.html
+# Return Value: none
+sub update_thread_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $code = _charset_to_code($self->{ _charset });
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
+ old => "$html_base_dir/thread.html",
+ new => "$html_base_dir/thread.html.new.$$",
+ code => $code,
+ };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update thread.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+
+ # initialize negagtive cache to ensure uniquness
+ delete $self->{ _uniq };
+
+ $self->_print_ul($wh, $db, $code);
+ for my $id ( 1 .. $max_id ) {
+ # head of the thread (not referenced yet)
+ unless (defined $self->{ _uniq }->{ $id }) {
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: check whether $id has next or previous link.
+# Arguments: OBJ($self) HASH_REF($db) NUM($id)
+# Side Effects: none
+# Return Value: 1 or 0
+sub _has_link
+{
+ my ($self, $db, $id) = @_;
+
+ if ($db->get('next_key', $id) || $db->get('prev_key', $id)) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+
+# Descriptions: print thread array of (head_id id2 id3 ...)
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($head_id) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_thread
+{
+ my ($self, $wh, $db, $head_id, $code) = @_;
+ my $saved_stack_level = $self->{ _stack };
+ my $uniq = $self->{ _uniq };
+
+ # get id list: @idlist = ( $head_id id2 id3 ... )
+ my $ndb = $self->ndb();
+ my $buf = $ndb->get('ref_key_list', $head_id);
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- thread head=$head_id ($buf) -->\n", $code);
+
+ my $idlist = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ if (@$idlist) {
+ IDLIST:
+ for my $id (@$idlist) {
+ # save $head_id => "id1 id2 id3 ..." on memory for further use.
+ # "> 1" implies idlist contains others than myself.
+ if ($#$idlist > 1) {
+ my $ra = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ $self->{ _hint_ref_key_list }->{ $id } = $ra;
+ }
+
+ # @$idlist = (number's)
+ _print_raw_str($wh, "<!-- thread (@$idlist) -->\n", $code);
+
+ next IDLIST if $uniq->{ $id };
+ $uniq->{ $id } = 1;
+
+ $self->_print_ul($wh, $db, $code);
+
+ # oops, we should ignore head of the thread ( myself ;-)
+ if (($id != $head_id) && $self->_has_link($db, $id)) {
+ _print_raw_str($wh, "<!-- thread $id has link -->\n", $code);
+ $self->_print_li_filename($wh, $db, $id, $code);
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ else {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ }
+
+ while ($self->{ _stack } > $saved_stack_level) {
+ $self->_print_end_of_ul($wh, $db, $code);
+ }
+}
+
+=head2 update_monthly_thread_index($args)
+
+=cut
+
+# Descriptions: update monthly thread index
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly thread index
+# Return Value: none
+sub update_monthly_thread_index
+{
+ my ($self, $args) = @_;
+ my $affected_list = $self->{ _affected_idlist };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update monthly_thread.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ # open databaes
+ my $db = $self->ndb();
+ my %month_update = ();
+
+ IDLIST:
+ for my $id (@$affected_list) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
+ $month_update{ $month } = 1;
+ }
+ }
+
+ # todo list
+ for my $month (sort keys %month_update) {
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
+
+ $self->_update_monthly_thread_index($args, {
+ this_month => $this_month,
+ suffix => $suffix,
+ });
+ }
+
+ # update monthly_index.html
+ $self->_update_montly_thread_index_master($args);
+}
+
+
+# Descriptions: update monthly thread index master
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly_thread.html
+# Return Value: none
+sub _update_montly_thread_index_master
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_thread.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_thread.html.new.$$");
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
+ old => $old,
+ new => $new,
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
+ my (@list) = sort __sort_yyyymm keys %$mlist;
+ my ($years) = _yyyy_range(\@list);
+
+ _print_raw_str($wh, "<table border='1'>", $code);
+
+ for my $year (sort {$b <=> $a} @$years) {
+ _print_raw_str($wh, "<TR>", $code);
+ _print_raw_str($wh, "<th> $year </th>", $code);
+
+ for my $month (1 .. 12) {
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $fn = "thread.$xx.html";
+
+ use File::Spec;
+ my $file = File::Spec->catfile($html_base_dir, $fn);
+ if (-f $file) {
+ _print_raw_str($wh, "<td><a href=\"$fn\"> $month </A>", $code);
+ }
+ else {
+ _print_raw_str($wh, "<td>", $code);
+ }
+ _print_raw_str($wh, "</td>\n", $code);
+ }
+ _print_raw_str($wh, "</tr>\n", $code);
+ }
+ _print_raw_str($wh, "</table>", $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: update thread.YYYYMM.html
+# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
+# Side Effects: update thread.YYYYMM.html
+# Return Value: none
+sub _update_monthly_thread_index
+{
+ my ($self, $args, $monthlyinfo) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
+ my $suffix = $monthlyinfo->{ suffix }; # yyyymm
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => "Monthly Thread Index $this_month",
+ old => "$html_base_dir/thread.${suffix}.html",
+ new => "$html_base_dir/thread.${suffix}.html.new.$$",
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
+
+ # initialize negagtive cache to ensure uniquness
+ delete $self->{ _uniq };
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
+
+ $self->_print_ul($wh, $db, $code);
+ for my $id (sort {$a <=> $b} @$list) {
+ # head of the thread (not referenced yet)
+ unless (defined $self->{ _uniq }->{ $id }) {
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+=head2 internal utility functions for IO
+
+=cut
+
+
+# Descriptions: cnvert charset to code e.g. iso-2022-jp => jis
+# Arguments: STR($charset)
+# Side Effects: none
+# Return Value: STR
+sub _charset_to_code
+{
+ my ($charset) = @_;
+
+ if (defined $charset) {
+ $charset =~ tr/A-Z/a-z/;
+ if ($charset eq 'euc-jp') {
+ return 'euc';
+ }
+ elsif ($charset eq 'iso-2022-jp') {
+ return 'jis';
+ }
+ else {
+ return $charset; # may be wrong, but I hope it works well:-)
+ }
+ }
+ else {
+ return 'euc'; # euc-jp by default
+ }
+}
+
+
+# Descriptions: print raw $str to $wh channel
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_raw_str
+{
+ my ($wh, $str, $code) = @_;
+ $code = defined($code) ? $code : 'euc'; # euc-jp by default
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined($str) && $str) {
+ $str = __nc_convert($str, $code || 'euc');
+ }
+
+ print $wh $str;
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# XXX text2html($str, urls => 1, pre => 0)
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_safe_str
+{
+ my ($wh, $str, $code) = @_;
+ __print_safe_str(0, $wh, $str, $code);
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# XXX text2html($str, urls => 1, pre => 1)
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_safe_buf
+{
+ my ($wh, $str, $code) = @_;
+ __print_safe_str(1, $wh, $str, $code);
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub __print_safe_str
+{
+ my ($attr_pre, $wh, $str, $code) = @_;
+ my $p = __sprintf_safe_str($attr_pre, $wh, $str, $code);
+ print $wh $p if defined $p;
+ print $wh "\n";
+}
+
+
+# Descriptions: return safe $str
+# Arguments: STR($str) STR($code)
+# Side Effects: none
+# Return Value: STR
+sub _sprintf_safe_str
+{
+ my ($str, $code) = @_;
+ return __sprintf_safe_str(0, undef, $str, $code);
+}
+
+
+# Descriptions: return safe $str modified by text2html().
+# $str language code is modified by Mail::Message::Encode.
+# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: STR or UNDEF
+sub __sprintf_safe_str
+{
+ my ($attr_pre, $wh, $str, $code) = @_;
+ my $regexp = $hints->{ subject_tag_regexp } || '';
+ my $rbuf = '';
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined($str) && $str) {
+ $str = __nc_convert($str, $code || 'euc');
+ }
+
+ if (defined $str) {
+ # $url$trailor => $url $trailor for text2html() incomplete regexp
+ $str =~ s#(http://[^\s\<\>\'\"]+[\w\d/])#_separete_url($1)#ge;
+
+ use HTML::FromText;
+ # NOT CONVERT subject tag (see fml-devel:726).
+ if ($str =~ /^\s*($regexp)(.*)/) {
+ my ($tag, $post) = ($1, $2);
+ my $tag_s = text2html($tag, urls => 0, pre => $attr_pre);
+ my $post_s = text2html($post, urls => 1, pre => $attr_pre);
+ return sprintf("%s%s", $tag_s, $post_s);
+ }
+ else {
+ return text2html($str, urls => 1, pre => $attr_pre);
+ }
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: extract URL syntax in $url string.
+# $url$trailor => $url $trailor for text2html()
+# XXX incomplete regexp, we should correct it.
+# Arguments: STR($url)
+# Side Effects: none
+# History: based on fml 4.0-current (2001/10/28)
+# Return Value: STR
+sub _separete_url
+{
+ my ($url) = @_;
+ my ($re_euc_c) = '[\241-\376][\241-\376]';
+ my ($re_euc_s) = "($re_euc_c)+";
+ my $trailor = '';
+
+ # remove prepended/appended EUC strings
+ if ($url =~ /($re_euc_s)+$/) {
+ $trailor = $1;
+ $url =~ s/$trailor//;
+ }
+
+ # incomplete but may be effective ?
+ # RFC2068 says these special char's are not used.
+ # we should not include these char's in URL.
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
+ # unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"
+ if ($url =~ /(\&\w{2}\;|\;|\?|\:|\@|\&|\=|\+|\#|\%|\<|\>|\")+$/) {
+ my $pat = $1;
+ $trailor = $pat . $trailor;
+ $url =~ s/${pat}$//;
+ }
+
+ return "$url $trailor";
+}
+
+
+# Descriptions: debug
+# Arguments: STR($str)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG
+{
+ my ($str) = @_;
+ print STDERR "(debug) $str\n" if $debug;
+}
+
+
+# Descriptions: debug, print out hash
+# Arguments: HASH_REF($hash)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG_DUMP_HASH
+{
+ my ($hash) = @_;
+ my ($k,$v);
+
+ if ($debug) {
+ while (($k, $v) = each %$hash) {
+ printf STDERR "%-30s => %s\n", $k, $v;
+ }
+ }
+}
+
+
+=head2 internal utility functions for HTML TAGS
+
+C<_print_something()> internal function provides wrapper to print HTML
+tags et.al.
+
+=cut
+
+
+# Descriptions: print <UL> with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_ul
+{
+ my ($self, $wh, $db, $code) = @_;
+
+ $self->{ _stack }++;
+
+ my $padding = " " x $self->{ _stack };
+ _print_raw_str($wh, "${padding}<UL>\n", $code);
+}
+
+
+# Descriptions: print </UL> with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_end_of_ul
+{
+ my ($self, $wh, $db, $code) = @_;
+
+ return unless $self->{ _stack } > 0;
+
+ my $padding = " " x $self->{ _stack };
+ _print_raw_str($wh, "${padding}</UL>\n", $code);
+
+ $self->{ _stack }--;
+}
+
+
+# Descriptions: print <LI> filename ... with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) NUM($id) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_li_filename
+{
+ my ($self, $wh, $db, $id, $code) = @_;
+ my $filename = $db->get('html_filename', $id);
+ my $subject = $db->get('article_subject', $id) ||
+ $db->get('subject', $id) || "no subject";
+ my $who = $db->get('who', $id) || "no sender";
+ if ($self->{ _use_address_mask } ne 'yes' &&
+ $db->get('from', $id) ne '') {
+ $who = "" if ($who =~ /\@xxx/);
+ $who .= " " . $db->get('from', $id);
+ }
+
+ my $mimeopt = $main::opt_mimedecodequoted;
+ $main::opt_mimedecodequoted = 1;
+ $subject = $self->_decode_mime_string($subject) if $subject =~ /=\?/i;
+ $who = $self->_decode_mime_string($who) if $who =~ /=\?/i;
+ $main::opt_mimedecodequoted = $mimeopt;
+
+ _PRINT_DEBUG("-- print_li_filename id=$id file=$filename");
+
+ if (defined $filename && $filename) {
+ _print_raw_str($wh, "<!-- LI id=$id -->\n", $code);
+
+ _print_raw_str($wh, "<LI>\n", $code);
+ _print_raw_str($wh, "<A HREF=\"$filename\">\n", $code);
+ _print_safe_str($wh, $subject, $code);
+ _print_raw_str($wh, ",\n", $code);
+ _print_safe_str($wh, "$who\n", $code);
+ _print_raw_str($wh, "</A>\n", $code);
+ }
+}
+
+
+# Descriptions: extrace gecos field in $address
+# Arguments: OBJ($self) STR($address)
+# Side Effects: none
+# Return Value: STR
+sub _address_to_gecos
+{
+ my ($self, $address) = @_;
+
+ use Mail::Message::Utils;
+ return Mail::Message::Utils::from_address_to_name($address);
+}
+
+
+# Descriptions: mask the detail of address
+# Arguments: OBJ($self) STR($field)
+# Side Effects: none
+# Return Value: NUM
+sub _is_mask_address
+{
+ my ($self, $field) = @_;
+ my $type = $self->{ _address_mask_type } || 'all';
+
+ if ($type eq 'all') {
+ if ($field =~ /^(From|To|Cc)$/i) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+ else {
+ return 0;
+ }
+}
+
+
+# Descriptions: decode MIME-encoded $str
+# Arguments: OBJ($self) STR($str) HASH_REF($options)
+# Side Effects: none
+# Return Value: STR
+sub _decode_mime_string
+{
+ my ($self, $str, $options) = @_;
+ my $charset = $options->{ 'charset' } || $self->{ _charset };
+ my $code = _charset_to_code($charset) || 'euc';
+
+ if (defined($str) && $str) {
+ use Mail::Message::Subject;
+ my $sbj = new Mail::Message::Subject $str;
+ $sbj->mime_header_decode();
+ return $sbj->as_str();
+ }
+ else {
+ return '';
+ }
+}
+
+
+# Descriptions: convert $str to $out_code code
+# Arguments: OBJ($self) STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+use Encode qw(decode encode);
+
+sub _convert
+{
+ my ($self, $str, $out_code, $in_code) = @_;
+
+ return '' unless defined $str && $str ne '';
+
+ $in_code ||= 'euc-jp';
+ $out_code ||= 'euc-jp';
+
+ $out_code = lc $out_code;
+
+ if ($out_code =~ /^(jis|iso2022jp|iso-2022-jp)$/) {
+ $out_code = 'iso-2022-jp';
+ }
+ elsif ($out_code =~ /^(sjis|shiftjis)$/) {
+ $out_code = 'shiftjis';
+ }
+ else {
+ $out_code = 'euc-jp';
+ }
+
+ my $decoded = eval {
+ decode($in_code, $str, Encode::FB_DEFAULT);
+ };
+ return $decoded;
+}
+
+# Descriptions: convert $str to $out_code code (non method version)
+# XXX you should remove this function.
+# Arguments: STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+sub __nc_convert
+{
+ my ($str, $out_code, $in_code) = @_;
+
+ return '' unless defined $str && $str ne '';
+
+ $in_code ||= 'euc-jp';
+ $out_code ||= 'euc-jp';
+
+ $out_code = lc $out_code;
+
+ if ($out_code =~ /^(jis|iso2022jp|iso-2022-jp)$/) {
+ $out_code = 'iso-2022-jp';
+ }
+ elsif ($out_code =~ /^(sjis|shiftjis)$/) {
+ $out_code = 'shiftjis';
+ }
+ else {
+ $out_code = 'euc-jp';
+ }
+
+ my $decoded;
+
+ eval {
+ $decoded = decode($in_code, $str);
+ 1;
+ } or do {
+ # fallback: 元データを返す
+ $decoded = $str;
+ };
+
+ return defined $decoded ? $decoded : $str;
+}
+
+
+=head1 useful functions as entrance
+
+=head2 htmlify_file($file, $args)
+
+try to convert rfc822 message C<$file> to HTML.
+
+ $args = {
+ directory => "destination directory",
+ };
+
+=head2 htmlify_dir($dir, $args)
+
+try to convert all rfc822 messages to HTML in C<$dir> directory.
+
+ $args = {
+ directory => "destination directory",
+ };
+
+=cut
+
+
+# Descriptions: convert $file to HTML
+# Arguments: OBJ($self) STR($file) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub htmlify_file
+{
+ my ($self, $file, $args) = @_;
+ my $dst_dir = $args->{ output_dir };
+ my $indexs = \@indexs;
+
+ unless (-f $file) {
+ print STDERR "no such file: $file\n" if $debug;
+ return;
+ }
+
+ unless (-s $file) {
+ print STDERR "empty file: $file\n" if $debug;
+ return;
+ }
+
+ use File::Basename;
+ my $id = basename($file);
+ my $html = new Mail::Message::ToHTML $args;
+
+ if ($debug) {
+ printf STDERR "htmlify_file( id=%-6s src=%s )\n", $id, $file;
+ }
+
+ _PRINT_DEBUG("htmlify_rfc822_message begin");
+ unless ($html->htmlify_rfc822_message({
+ id => $id,
+ src => $file,
+ }) ) {
+ _PRINT_DEBUG("htmlify_rfc822_message end no change");
+ return;
+ }
+ _PRINT_DEBUG("htmlify_rfc822_message end");
+
+ if ($debug) {
+ printf STDERR "htmlify_file( id=%-6s ) update relation\n", $id;
+ }
+
+ _PRINT_DEBUG("-- msg_html_links");
+ $html->update_msg_html_links( $id );
+
+ for my $index (@$indexs) {
+ if ($index eq "month") {
+ _PRINT_DEBUG("-- monthly id index");
+ $html->update_monthly_id_index({ id => $id });
+ }
+
+ if ($index eq "all") {
+ _PRINT_DEBUG("-- id index");
+ $html->update_id_index({ id => $id });
+ }
+
+ if ($index eq "thread") {
+ _PRINT_DEBUG("-- thread index");
+ $html->update_thread_index({ id => $id });
+ }
+
+ if ($index eq "month_thread") {
+ _PRINT_DEBUG("-- month thread index");
+ $html->update_monthly_thread_index({ id => $id });
+ }
+
+ if ($index eq "top") {
+ _PRINT_DEBUG("-- top index");
+ $html->create_top_index();
+ }
+ }
+
+ # no more action for old files
+ if ($html->is_ignore($id)) {
+ warn("not process $id (already exists)") if $debug;
+ }
+ else {
+ if ($debug) {
+ printf STDERR " converted( id=%-6s src=%s )\n", $id, $file;
+ }
+ }
+}
+
+
+# Descriptions: convert all articles in specified directory
+# Arguments: OBJ($self) STR($src_dir) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub htmlify_dir
+{
+ my ($self, $src_dir, $args) = @_;
+ my $dst_dir = $args->{ output_dir };
+ my $min = 0;
+ my $max = 0;
+ my $has_fork = 1; # ok on unix and perl>5.6 on wine32.
+
+ print STDERR "src = $src_dir\ndst = $dst_dir\n" if $debug;
+
+ use DirHandle;
+ my $dh = new DirHandle $src_dir;
+ if (defined $dh) {
+ FILE:
+ for my $file ( $dh->read() ) {
+ next FILE unless $file =~ /^\d+$/;
+
+ # initialize $min
+ unless ($min) { $min = $file;}
+
+ $max = $max < $file ? $file : $max;
+ $min = $min > $file ? $file : $min;
+ }
+ }
+
+ # overwride
+ $has_fork = $args->{ has_fork } if defined $args->{ has_fork };
+ $max = $args->{ max } if defined $args->{ max };
+
+ print STDERR " scan ( $min .. $max ) for $src_dir\n" if $debug;
+ for my $id ( $min .. $max ) {
+ use File::Spec;
+ my $file = File::Spec->catfile($src_dir, $id);
+
+ unless ( $has_fork ) {
+ $self->htmlify_file($file, $args);
+ }
+ else {
+ my $pid = fork();
+ if ($pid < 0) {
+ croak("cannot fork");
+ }
+ elsif ($pid == 0) {
+ $self->htmlify_file($file, $args);
+ exit 0;
+ }
+
+ # parent
+ my $dying;
+ while (($dying = wait()) != -1 && ($dying != $pid) ){
+ ;
+ }
+ }
+ }
+}
+
+
+#
+# debug
+#
+if ($0 eq __FILE__) {
+ my $dir = "/tmp/htdocs";
+ my $has_fork = defined $ENV{'HAS_FORK'} ? 1 : 0;
+ my $max = defined $ENV{'MAX'} ? $ENV{'MAX'} : 1000;
+ my $charset = 'euc-jp';
+ my $opts = {
+ output_dir => "/tmp/htdocs",
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ };
+
+ eval q{
+ my ($t, $time_b, $time_e);
+ for my $x (@ARGV) {
+ $time_b = time;
+ print STDERR "debug.main processing $x ...";
+
+ if (-f $x) {
+ eval q{
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_file($x, {
+ output_dir => "/tmp/htdocs",
+ directory => $dir,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ });
+ };
+ print STDERR $@ if $@;
+ }
+ elsif (-d $x) {
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_dir($x, {
+ output_dir => "/tmp/htdocs",
+ directory => $dir,
+ has_fork => $has_fork,
+ max => $max,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ });
+ }
+
+ $t = time - $time_b;
+ print STDERR "\t$t sec.\n";
+ }
+ print STDERR "done.\n";
+ };
+
+ if ($@) { croak($@);}
+}
+
+
+=head1 TODO
+
+ expiration
+
+ sub directory?
+
+=head1 CODING STYLE
+
+See C<http://www.fml.org/software/FNF/> on fml coding style guide.
+
+=head1 AUTHOR
+
+Ken'ichi Fukamachi
+
+=head1 COPYRIGHT
+
+Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi
+
+All rights reserved. This program is free software; you can
+redistribute it and/or modify it under the same terms as Perl itself.
+
+=head1 HISTORY
+
+Mail::Message::ToHTML first appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+This class is renamed from C<Mail::HTML::Lite> 1.40 (2001-2002).
+
+=cut
+
+
+1;
diff --git a/host-artifacts/Mail/Message/ToHTML.pm.save.2 b/host-artifacts/Mail/Message/ToHTML.pm.save.2
new file mode 100644
index 00000000..b3548f8e
--- /dev/null
+++ b/host-artifacts/Mail/Message/ToHTML.pm.save.2
@@ -0,0 +1,2608 @@
+#-*- perl -*-
+#
+# Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi All rights reserved. This program is
+# free software; you can redistribute it and/or modify it under the same terms as Perl itself.
+#
+# $FML: ToHTML.pm,v 1.85 2006/06/15 10:35:24 tmu Exp $
+#
+
+package Mail::Message::ToHTML;
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD $hints);
+use Carp;
+
+my $is_strict_warn = 0;
+my $debug = 0;
+my $URL =
+ "<A HREF=\"http://www.fml.org/software/\">Mail::Message::ToHTML</A>";
+
+my $version = q$FML: ToHTML.pm,v 1.85 2006/06/15 10:35:24 tmu Exp $;
+my $versionid = 0;
+if ($version =~ /,v\s+([\d\.]+)\s+/) {
+ $versionid = "$1";
+ $version = "$URL $versionid";
+}
+
+=head1 NAME
+
+Mail::Message::ToHTML - convert text format mail to HTML format
+
+=head1 SYNOPSIS
+
+ ... lock by something ...
+
+ use Mail::Message::ToHTML;
+ my $obj = new Mail::Message::ToHTML {
+ charset => "euc-jp",
+ directory => "/var/www/htdocs/ml/elena",
+ };
+
+ $obj->htmlify_rfc822_message({
+ id => 1,
+ src => "/var/spool/ml/elena/spool/1",
+ });
+
+ ... unlock by something ...
+
+This module itself provides no lock function.
+please use flock() built in perl or CPAN lock modules for it.
+
+=head1 DESCRIPTION
+
+=head2 Message structure created as HTML
+
+HTML-fied message has following structure.
+something() below is method name.
+
+ for example
+ -------------------------------------------------------------------
+ html_start() <HTML><HEAD> ... </HEAD><BODY>
+ mhl_preamble() <!-- comment used by this module -->
+ mhl_separator() <HR>
+
+ message header
+ From: ...
+ Subject: ...
+
+ mhl_separator() <HR>
+
+ message body
+
+ mhl_separator() <HR>
+ mhl_footer() <!-- comment used by this module -->
+ html_end() </BODY></HTML>
+
+=head1 METHODS
+
+=head2 new($args)
+
+ $args = {
+ directory => $directory,
+ };
+
+C<$directory> is top level directory where html-fied articles are
+stored.
+
+=cut
+
+
+# Descriptions: constructor.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: OBJ
+sub new
+{
+ my ($self, $args) = @_;
+ my ($type) = ref($self) || $self;
+ my $me = {};
+
+ $me->{ _charset } = $args->{ charset } || 'us-ascii';
+ $me->{ _html_base_directory } = $args->{ output_dir };
+ $me->{ _db_type } = $args->{ db_type };
+ $me->{ _db_name } = $args->{ db_name };
+ $me->{ _db_base_dir } = $args->{ db_base_dir };
+ $me->{ _is_attachment } = defined($args->{ attachment }) ? 1 : 0;
+ $me->{ _args } = $args;
+ $me->{ _num_attachments } = 0; # for child process
+ $me->{ _use_subdir } = 'yes';
+ $me->{ _subdir_style } = 'yyyymm';
+ $me->{ _html_id_order } = $args->{ index_order } || 'normal';
+ $me->{ _use_address_mask } = $args->{ use_address_mask } || 'yes';
+ $me->{ _address_mask_type } = $args->{ address_mask_type } || 'all';
+
+ # global hints
+ $hints = $args->{ hints } || {};
+
+ use Mail::Message::Thread;
+ my $t = new Mail::Message::Thread $args;
+ $me->{ _thread_object } = $t;
+
+ return bless $me, $type;
+}
+
+
+# Descriptions: destructor.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: none
+sub DESTROY
+{
+ my ($self) = @_;
+
+ _PRINT_DEBUG("ToHTML::DESTROY");
+ 1;
+}
+
+
+=head2 htmlify_rfc822_message($args)
+
+convert mail to html.
+
+ $args = {
+ id => $id,
+ path => $path,
+ };
+
+where C<$path> is file path.
+
+=cut
+
+
+# Descriptions: top level entrance to convert mail to html
+# Arguments: OBJ($self) HASH_REF($args)
+# $args = { id => $id, path => $path };
+# $id identifier (e.g. "1" (article id))
+# $path file path (e.g. "/some/where/1");
+# Side Effects: none
+# Return Value: none
+sub htmlify_rfc822_message
+{
+ my ($self, $args) = @_;
+
+ # prepare source
+ use Mail::Message;
+ use FileHandle;
+ my $rh = new FileHandle $args->{ src };
+ my $msg = Mail::Message->parse( { fd => $rh } );
+ my $hdr = $msg->whole_message_header;
+ my $body = $msg->whole_message_body;
+ $self->{ _current_msg } = $msg;
+ $self->{ _current_hdr } = $hdr;
+ $self->{ _current_body } = $body;
+
+ # initialize basic information
+ # $id = article id
+ # $src = source file
+ # $dst = destination file (target html)
+ my ($id, $src, $dst) = $self->_init_htmlify_rfc822_message($args);
+ $self->{ _debug_id } = $id;
+
+ # save information for index.html and thread.html
+ $self->cache_message_info($msg, { id => $id,
+ src => $src,
+ dst => $dst,
+ } );
+
+ # target html not define
+ unless ($dst) {
+ $self->{ _ignore_list }->{ $id } = 1; # ignore flag
+ warn("html file for $id not define") if $debug;
+ return undef;
+ }
+
+ # target html exists already.
+ if (-f $dst) {
+ $self->{ _ignore_list }->{ $id } = 1; # ignore flag
+ warn("html file for $id already exists") if $debug;
+ return undef;
+ }
+
+ # prepare output channel
+ my $wh = $self->_set_output_channel( { dst => $dst } );
+ unless (defined $wh) {
+ croak("cannot open output file $dst\n");
+ }
+
+ # before main message
+ $self->html_start($wh, { message => $msg });
+ $self->mhl_preamble($wh);
+
+ # analyze $msg, chain of Mail::Message objects.
+ # See Mail::Message class for more detail.
+ # XXX we use $m->{ next } here, but we should avoid this style and
+ # XXX prepare access method for it in Mail::Message class.
+ my ($m, $type, $attach);
+ CHAIN:
+ for ($m = $msg; defined($m) ; $m = $m->{ 'next' }) {
+ $type = $m->data_type;
+
+ last CHAIN if $type eq 'multipart.close-delimiter'; # last of multipart
+ next CHAIN if $type =~ /^multipart/; # multipart type is special.
+
+ unless ($type =~ /^\w+\/[-\w\d\.]+$/) {
+ warn("invalid type={$type}");
+ next CHAIN;
+ }
+
+ # header (Mail::Message object uses this special type)
+ if ($type eq 'text/rfc822-headers') {
+ $self->mhl_separator($wh);
+ my $charset = $self->{ _charset };
+ my $header = $self->_format_safe_header($msg);
+ _print_raw_str($wh, $header, $charset);
+ $self->mhl_separator($wh);
+ }
+ # message/rfc822 case (attached rfc822 message)
+ elsif ($type eq 'message/rfc822') {
+ $attach++;
+
+ my $tmpf = $self->_create_temporary_file_in_raw_mode($m);
+ if (defined $tmpf && -f $tmpf) {
+ # write attachement into a separete file
+ my $outf = _gen_attachment_filename($dst, $attach, 'html');
+ my $args = $self->{ _args };
+ $args->{ attachment } = 1; # clarify not top level content.
+ my $text = new Mail::Message::ToHTML $args;
+ $text->htmlify_rfc822_message({
+ parent_id => $id,
+ src => $tmpf,
+ dst => $outf,
+ });
+
+ # show inline <HREF> link,
+ # which appears in parent html ( == $wh channel ).
+ $self->_print_inline_object_link({
+ fh => $wh, # file descriptor
+ type => $type, # XXX derived from input message
+ num => $attach, # number
+ file => $outf, # temporary file name
+ });
+
+ unlink $tmpf;
+ }
+ }
+ # text/plain case.
+ # XXX inline expansion.
+ elsif ($type eq 'text/plain') {
+ if ($self->{ _use_address_mask } eq 'yes') {
+ $self->_text_plain_part_safe_print($wh, $m);
+ }
+ else { # original
+ $self->_text_safe_print({
+ fh => $wh, # parent html
+ data => $m->message_text(),
+ charset => $m->charset(),
+ encoding => $m->encoding_mechanism(),
+ });
+ }
+ }
+ # create a separete file for attachment
+ else {
+ $attach++;
+
+ # write attachement into a separete file
+ my $outf = _gen_attachment_filename($dst, $attach, $type);
+ my $enc = $m->encoding_mechanism;
+ my $msginfo = { message => $m };
+
+ # e.g. text/xxx case (e.g. text/html case)
+ if ($type =~ /^text/) {
+ # 1. firstly saved to temporary file $tmpf in "raw" mode
+ my $tmpf = $self->_create_temporary_filename();
+ $msginfo->{ file } = $tmpf;
+
+ # once create temporary file
+ _PRINT_DEBUG("attachment: type=$type attach=$attach enc=$enc");
+ if ($enc) {
+ $self->_binary_print($msginfo); # XXX raw mode
+ }
+ else {
+ $self->_text_raw_print($msginfo); # XXX raw mode
+ }
+
+ # 2. secondary convert $tmpf to real target $outf with
+ # some modification e.g. metachars escaping, ...
+ # disable html tag in file saved in raw mode.
+ if (-f $tmpf) {
+ $msginfo->{ description } = "(HTML TAGs are disabled)";
+ _disable_html_tag_in_file($tmpf, $outf);
+ unlink $tmpf;
+ }
+ }
+ # e.g. image/gif not text/* nor message/*
+ else {
+ $msginfo->{ file } = $outf;
+ $self->_binary_print($msginfo);
+ }
+
+ # show inline <HREF> link appeared in parent html.
+ $self->_print_inline_object_link({
+ inline => 1,
+ fh => $wh,
+ type => $type,
+ num => $attach,
+ file => $outf,
+ info => $msginfo,
+ });
+ }
+ }
+
+ # show navigation bar et.al. after message itself
+ $self->mhl_separator($wh);
+ $self->mhl_footer($wh);
+ $self->html_end($wh);
+ return 1;
+}
+
+
+# Descriptions: copy $inf file to $outf file with disabling HTML tag
+# by _print_safe_buf().
+# Arguments: STR($inf) STR($outf)
+# Side Effects: create $outf file
+# Return Value: none
+sub _disable_html_tag_in_file
+{
+ my ($inf, $outf) = @_;
+ my $mask = umask();
+
+ umask(022);
+
+ use FileHandle;
+ my $rh = new FileHandle $inf;
+ my $wh = new FileHandle "> $outf";
+ if (defined $rh) {
+ my $buf = '';
+
+ my $b;
+ while ($b = <$rh>) { $buf .= $b;}
+
+ _print_safe_buf($wh, $buf);
+ $wh->close;
+ $rh->close;
+ }
+
+ umask($mask);
+}
+
+
+# Descriptions: return HTML filename
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR or UNDEF
+sub html_filename
+{
+ my ($self, $id) = @_;
+ my $use_subdir = $self->{ _use_subdir };
+
+ # relative path under html_base_dir
+ if (defined($id) && ($id > 0)) {
+ if ($use_subdir eq 'yes') {
+ return $self->_html_file_subdir_name($id);
+ }
+ else {
+ return "msg${id}.html";
+ }
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: return HTML sub directory string
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR
+sub _html_file_subdir_name
+{
+ my ($self, $id) = @_;
+ my $ndb = $self->ndb();
+ my $subdir = '';
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $subdir_style = $self->{ _subdir_style };
+ my $dir_mode = $self->{ _dir_mode } || 0755;
+
+ if ($subdir_style eq 'yyyymm') {
+ my $hdr = $self->{ _current_hdr };
+ use Mail::Message::Utils;
+ $subdir = Mail::Message::Utils::get_time_from_header($hdr, 'yyyymm');
+
+ use File::Spec;
+ my $xsubdir = File::Spec->catfile($html_base_dir, $subdir);
+ unless (-d $xsubdir) {
+ my $mask = umask();
+ umask(022);
+ mkdir($xsubdir, $dir_mode);
+ umask($mask);
+ }
+ }
+ else {
+ croak("unknown \$subdir_style");
+ }
+
+ if ($subdir) {
+ use File::Spec;
+ return File::Spec->catfile($subdir, "msg$id.html");
+ }
+ else {
+ warn("not create msg$id.html");
+ return undef;
+ }
+}
+
+
+# Descriptions: return HTML file path
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: STR
+sub html_filepath
+{
+ my ($self, $id) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+
+ if (defined($id) && ($id > 0)) {
+ my $filename = $self->html_filename($id);
+ return undef unless($filename);
+
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, $filename);
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: parse $args and return file id, name, path.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: ARRAY(NUM, STR, STR)
+sub _init_htmlify_rfc822_message
+{
+ my ($self, $args) = @_;
+ my ($id, $src, $dst);
+
+ if (defined $args->{ src }) {
+ $src = $args->{ src };
+ }
+ else {
+ croak("htmlify_rfc822_message: \$src is mandatory\n");
+ }
+
+ if (defined $args->{ id }) {
+ my $html_base_dir = $self->{ _html_base_directory };
+ $id = $args->{ id };
+ $dst = $self->html_filepath($id);
+ }
+ # this object is an attachment if parent_id is specified.
+ elsif (defined $args->{ parent_id }) {
+ $self->{ _num_attachments }++;
+ $id = $args->{ parent_id } .'.'. $self->{ _num_attachments };
+ $dst = $args->{ dst };
+ }
+ # last resort: give unique identifier
+ elsif (defined $args->{ dst }) {
+ $id = sprintf("%s.%s", time, $$);
+ $dst = $args->{ dst };
+ }
+ # oops ;) wrong call of this function
+ else {
+ croak("htmlify_rfc822_message: specify \$id or \$dst\n");
+ }
+
+ return ($id, $src, $dst);
+}
+
+
+# Descriptions: show html header + file title in <BODY>
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub html_start
+{
+ my ($self, $wh, $args) = @_;
+ my ($msg, $hdr, $title);
+
+ if (defined $args->{ title }) {
+ $title = $args->{ title };
+ }
+ elsif (defined $args->{ message }) {
+ $msg = $args->{ message };
+ $hdr = $msg->whole_message_header;
+ $title = $self->_decode_mime_string( $hdr->get('article_subject') ||
+ $hdr->get('subject') );
+ }
+
+ print $wh "<!-- X-FML 8 ToHTML $versionid -->\n";
+ print $wh
+ q{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">};
+ print $wh "\n";
+ print $wh "<HTML>\n";
+ print $wh "<HEAD>\n";
+
+ if (defined $self->{ _charset }) {
+ my $charset = $self->{ _charset };
+ print $wh "<META http-equiv=\"Content-Type\"\n";
+ print $wh " content=\"text/html; charset=${charset}\">\n";
+ }
+
+ if (defined $self->{ _stylsheet }) {
+ my $css = $self->{ _stylsheet };
+ print $wh "<LINK rel=\"stylesheet\"\n";
+ print $wh " type=\"text/css\" href=\"fml.css\">\n";
+ }
+
+ if (defined $title) {
+ print $wh "<title>";
+ _print_safe_str($wh, $title);
+ print $wh "</title>\n";
+ }
+
+ print $wh "</HEAD>\n";
+ print $wh "<BODY>\n";
+ print $wh "<CENTER>";
+ _print_safe_str($wh, $title);
+ print $wh "</CENTER>\n";
+}
+
+
+# Descriptions: show html closing
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub html_end
+{
+ my ($self, $wh) = @_;
+ print $wh "</BODY>";
+ print $wh "</HTML>\n";
+}
+
+
+# Descriptions: show html separetor, we use <HR> now.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_separator
+{
+ my ($self, $wh) = @_;
+ print $wh "<HR>\n";
+}
+
+
+my $preamble_begin = "<!-- __PREAMBLE_BEGIN__ by Mail::Message::ToHTML -->";
+my $preamble_end = "<!-- __PREAMBLE_END__ by Mail::Message::ToHTML -->";
+my $footer_begin = "<!-- __FOOTER_BEGIN__ by Mail::Message::ToHTML -->";
+my $footer_end = "<!-- __FOOTER_END__ by Mail::Message::ToHTML -->";
+
+
+# Descriptions: prepare information area before main message appears.
+# Later, this area is replaced with useful information
+# e.g. thread link.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_preamble
+{
+ my ($self, $wh) = @_;
+ print $wh $preamble_begin, "\n";
+ print $wh $preamble_end, "\n";
+}
+
+
+# Descriptions: prepare information area after main message appears.
+# Later, this area is replaced with useful information
+# e.g. thread link.
+# Arguments: OBJ($self) HANDLE($wh)
+# Side Effects: none
+# Return Value: none
+sub mhl_footer
+{
+ my ($self, $wh) = @_;
+ print $wh $footer_begin, "\n";
+ print $wh $footer_end, "\n";
+}
+
+
+# Descriptions: prepare write handle
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $args->{ dst } file if needed
+# Return Value: HANDLE
+sub _set_output_channel
+{
+ my ($self, $args) = @_;
+ my $dst = $args->{ dst };
+ my $wh = undef;
+
+ my $mask = umask();
+ umask(022);
+
+ if (defined $dst) {
+ use FileHandle;
+ $wh = new FileHandle "> $dst";
+ }
+ else {
+ $wh = \*STDOUT;
+ }
+
+ umask($mask);
+
+ return $wh;
+}
+
+
+# Descriptions: return temporary file path.
+# XXX temporary file is created under $db_dir not public space
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: STR
+sub _create_temporary_filename
+{
+ my ($self) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+
+ use File::Spec;
+ return File::Spec->catfile($html_base_dir, "tmp.$$");
+}
+
+
+# Descriptions: create a temporary file with the content $msg
+# Arguments: OBJ($self) OBJ($msg)
+# Side Effects: create $tmpf file
+# Return Value: STR
+sub _create_temporary_file_in_raw_mode
+{
+ my ($self, $msg) = @_;
+ my $tmpf = $self->_create_temporary_filename();
+ my $mask = umask();
+ umask(022);
+
+ use FileHandle;
+ my $wh = new FileHandle "> $tmpf";
+ if (defined $wh) {
+ $wh->autoflush(1);
+
+ my $buf = $msg->message_text();
+ $wh->print($buf);
+ $wh->close;
+
+ umask($mask);
+ return ($tmpf);
+ }
+
+ umask($mask);
+ return undef;
+}
+
+
+# Descriptions: convert $file filepath to relative path
+# XXX UNIX specific ???
+# Arguments: OBJ($self) STR($file)
+# Side Effects: none
+# Todo: UNIX specific
+# Return Value: STR
+sub _relative_path
+{
+ my ($self, $file) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ $file =~ s/$html_base_dir//;
+ $file =~ s@^/@@;
+ return $file;
+}
+
+
+# Descriptions: print inline link as html for attachments e.g.
+# images, files et. al.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _print_inline_object_link
+{
+ my ($self, $args) = @_;
+ my $wh = $args->{ fh };
+ my $type = $args->{ type };
+ my $num = $args->{ num };
+ my $file = $self->_relative_path($args->{ file });
+ my $desc = '';
+ my $inline = defined( $args->{ inline } ) ? 1 : 0;
+
+ if (defined $args->{ info }->{ description }) {
+ $desc = $args->{ info }->{ description };
+ }
+
+ if ($inline && $type =~ /image/) {
+ print $wh "<BR><IMG SRC=\"../$file\">$desc\n";
+ }
+ else {
+ my $t = $file;
+ print $wh "<BR><A HREF=\"../$file\" TARGET=\"$t\"> $type $num </A>";
+ print $wh "$desc<BR>\n";
+ }
+}
+
+
+# Descriptions: return attachment filename
+# Arguments: STR($dst) STR($attach) STR($suffix)
+# Side Effects: none
+# Todo: UNIX specific
+# Return Value: STR
+sub _gen_attachment_filename
+{
+ my ($dst, $attach, $suffix) = @_;
+ my $outf = $dst;
+ if ($suffix =~ m@/@) { $suffix =~ s@.*/@@;}
+
+ $outf =~s/\.html$//;
+ $outf = "$outf.$attach.$suffix";
+ return $outf;
+}
+
+
+# default header to show
+my @header_field = qw(From To Cc Subject Date Message-Id X-Sequence);
+
+
+# Descriptions: format header of $msg with escaping HTML metachars
+# and disabling special HTML tags.
+# See _sprintf_safe_str() for how to escape.
+# Arguments: OBJ($self) OBJ($msg)
+# Side Effects: none
+# XXX $buf is printed out later in raw mode.
+# Return Value: STR
+sub _format_safe_header
+{
+ my ($self, $msg) = @_;
+ my ($buf);
+ my $hdr = $msg->whole_message_header;
+ my $header_field = \@header_field;
+
+ my $mimeopt = $main::opt_mimedecodequoted;
+ $main::opt_mimedecodequoted = 1;
+
+ # header
+ $buf .= "<SPAN CLASS=mailheaders>\n";
+ for my $field (@$header_field) {
+ if (defined($hdr->get($field))) {
+ $buf .= "<SPAN CLASS=${field}>\n";
+ $buf .= "${field}: ";
+ $buf .= "</SPAN>\n";
+
+ my $xbuf = $hdr->get($field);
+
+ # mask the raw address against address collector (e.g. spammer).
+ if ($self->{ _use_address_mask } eq 'yes') {
+ if ($self->_is_mask_address($field)) {
+ $xbuf = $self->_address_to_gecos($xbuf);
+ }
+ }
+
+ $xbuf = $self->_decode_mime_string($xbuf) if $xbuf =~ /=\?/i;
+ $buf .= "<SPAN CLASS=${field}-value>\n";
+ $buf .= _sprintf_safe_str($xbuf);
+ $buf .= "</SPAN>\n";
+ $buf .= "<BR>\n";
+ }
+ }
+ $buf .= "</SPAN>\n";
+
+ $main::opt_mimedecodequoted = $mimeopt;
+ return($buf);
+}
+
+
+# create index.html type (all thread month month_thread top)
+my @indexs = qw(month month_thread top);
+
+# Descriptions: show link to indexes as navigation
+# Arguments: HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub _format_index_navigator
+{
+ my ($args) = @_;
+ my $use_subdir = defined $args->{use_subdir} ? $args->{use_subdir} : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $str;
+ my $indexs = \@indexs;
+
+ for my $index (@$indexs) {
+ $str .= qq{<A HREF=\"${prefix}index_all.html\">[ID Index]</A>\n} if ($index eq "all");
+ $str .= qq{<A HREF=\"${prefix}thread.html\">[Thread Index]</A>\n} if ($index eq "thread");
+ $str .= qq{<A HREF=\"${prefix}monthly_index.html\">[Monthly ID Index]</A>\n} if ($index eq "month");
+ $str .= qq{<A HREF=\"${prefix}monthly_thread.html\">[Monthly Thread Index]</A>\n} if ($index eq "month_thread");
+ $str .= qq{<A HREF=\"${prefix}index.html\">[Top Index]</A>\n} if ($index eq "top");
+ };
+
+return $str;
+}
+
+
+# Descriptions: print text/plain part by printing each paragraph.
+# mask raw mail addresses in the signature if could.
+# Arguments: OBJ($self) HANDLE($wh) OBJ($m)
+# Side Effects: none
+# Return Value: STR
+sub _text_plain_part_safe_print
+{
+ my ($self, $wh, $m) = @_;
+ my $total = $m->paragraph_total();
+
+ # print each paragraph.
+ for (my $i = 1; $i <= $total ; $i++) {
+ my $buf = $m->nth_paragraph($i);
+
+ # try to hide domain since the last paragraph must be signature.
+ if ($self->{ _use_address_mask } eq 'yes') {
+ if ($i == $total) {
+ $buf =~ s/(\w+\@[\w\.]+)/$self->_address_to_gecos($1)/ge;
+ }
+ }
+
+ $self->_text_safe_print({
+ fh => $wh, # parent html
+ data => $buf,
+ charset => $m->charset(),
+ encoding => $m->encoding_mechanism(),
+ });
+ }
+}
+
+
+# Descriptions: print out text data with escaping by _print_safe_buf()
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+use Encode qw(decode);
+use MIME::Base64 qw(decode_base64);
+use MIME::QuotedPrint qw(decode_qp);
+
+sub _text_safe_print
+{
+ my ($self, $args) = @_;
+ my $buf = $args->{ data };
+ my $fh = $args->{ fh } || \*STDOUT;
+ my $in_code = $args->{ charset } || undef;
+ my $encoding = $args->{ encoding } || '7bit';
+
+ if ($encoding eq 'base64') {
+ $buf = decode_base64($buf);
+ }
+ elsif ($encoding eq 'quoted-printable') {
+ $buf = decode_qp($buf);
+ }
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined $buf && $buf ne '') {
+ $buf = $self->_convert($buf, 'euc');
+ }
+
+ _print_safe_buf($fh, $buf);
+}
+
+# Descriptions: print out message without escaping
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $outf
+# Return Value: none
+sub _text_raw_print
+{
+ my ($self, $args) = @_;
+ my $msg = $args->{ message }; # Mail::Message object
+ my $type = $msg->data_type;
+ my $enc = $msg->encoding_mechanism;
+ my $buf = $msg->message_text();
+
+ if (defined( $args->{ file } )) {
+ my $mask = umask();
+ umask(022);
+
+ my $outf = $args->{ file };
+ use FileHandle;
+ my $fh = new FileHandle "> $outf";
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined $buf && $buf) {
+ $buf = $self->_convert($buf, 'euc');
+ }
+ print $fh $buf, "\n";
+ $fh->close();
+
+ umask($mask);
+ }
+}
+
+
+# Descriptions: print out binary with MIME encoding or
+# text with escaping
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $outf file
+# Return Value: none
+use FileHandle;
+use MIME::Base64 qw(decode_base64);
+use MIME::QuotedPrint qw(decode_qp);
+
+sub _binary_print
+{
+ my ($self, $args) = @_;
+ my $msg = $args->{ message }; # Mail::Message object
+ my $type = $msg->data_type;
+ my $enc = $msg->encoding_mechanism || '';
+ my $mask = umask();
+
+ umask(022);
+
+ if (defined( $args->{ file } )) {
+ my $outf = $args->{ file };
+ my $fh = FileHandle->new("> $outf");
+
+ if (defined $fh) {
+ $fh->autoflush(1);
+ binmode($fh);
+
+ my $data = $msg->message_text();
+
+ if ($enc eq 'base64') {
+ print $fh decode_base64($data);
+ }
+ elsif ($enc eq 'quoted-printable') {
+ print $fh decode_qp($data);
+ }
+ elsif ($enc eq '7bit') {
+ _print_safe_str($fh, $data);
+ }
+ else {
+ my $r = "*** unknown MIME encoding enc='$enc' ***\n";
+ _print_safe_str($fh, $r);
+ _print_safe_str($fh, $data);
+ }
+
+ $fh->close();
+ }
+ }
+
+ umask($mask);
+}
+
+=head2 is_ignore($id)
+
+we should not process this C<$id>
+
+=cut
+
+
+# Descriptions: check whether article $id is ignored
+# Arguments: OBJ($self) STR($id)
+# Side Effects: none
+# Return Value: 1 or 0
+sub is_ignore
+{
+ my ($self, $id) = @_;
+
+ return defined($self->{ _ignore_list }->{ $id }) ? 1 : 0;
+}
+
+
+
+=head1 METHODS for index and thread
+
+=head2 cache_message_info($msg, $args)
+
+save information into DB.
+See section C<Internal Data Presentation> for more detail.
+
+=cut
+
+
+# Descriptions: update database on message header, thread relation
+# et. al.
+# Arguments: OBJ($self) OBJ($msg) HASH_REF($args)
+# Side Effects: update database
+# Return Value: none
+sub cache_message_info
+{
+ my ($self, $msg, $args) = @_;
+ my $ndb = $self->ndb();
+ my $id = $args->{ id };
+ my $src = $args->{ src };
+ my $dst = $args->{ dst };
+
+ $ndb->set_key($id);
+
+ $ndb->set('html_filename', $id, $self->html_filename($id));
+ $ndb->set('html_filepath', $id, $dst);
+
+ unless ($ndb->get('message_id', $id)) {
+ # analyze $msg only if not yet analyzed.
+ print STDERR "debug: analyze $id.\n" if $debug;
+ $ndb->add($msg);
+ }
+ else {
+ print STDERR "debug: already analyzed!\n" if $debug;
+ }
+}
+
+
+# Descriptions: return Mail::Message::DB object.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: OBJ
+sub ndb
+{
+ my ($self) = @_;
+ my $t = $self->{ _thread_object };
+
+ return $t->db();
+}
+
+
+=head2 update_msg_html_links($id)
+
+update link relation around C<$id>.
+
+=cut
+
+
+# Descriptions: top level dispatcher to update database.
+# _msg_file_rewrite_links() has real function for updating.
+# Arguments: OBJ($self) STR($id)
+# Side Effects: update databse
+# Return Value: none
+sub update_msg_html_links
+{
+ my ($self, $id) = @_;
+ my $info = $self->evaluate_links_relation($id);
+ my $list = $self->{ _affected_idlist } = [];
+
+ if ($self->is_ignore($id)) {
+ warn("not update relation around $id") if $debug;
+ return undef;
+ }
+
+ # sanity
+ return unless defined $id;
+ return unless $id;
+
+ # update target itself, of course
+ $self->_msg_file_rewrite_links($id);
+ push(@$list, $id);
+
+ # no rewriting for myself
+ my %uniq = ( $id => 1 );
+
+ KEY:
+ for my $_link (qw(prev_id next_id prev_thread_id next_thread_id)) {
+ if (defined $info->{ $_link }) {
+ my $_id = $info->{ $_link };
+
+ next KEY if $uniq{ $_id };
+ $uniq{ $_id } = 1;
+
+ _PRINT_DEBUG("try: rewrite $_link links in msg $_id");
+
+ if (defined $_id && $_id) {
+ $self->_msg_file_rewrite_links($_id);
+ push(@$list, $_id);
+ }
+ }
+ else {
+ _PRINT_DEBUG("error: fail to rewrite msg $_link");
+ }
+ }
+
+ # hint cached on memory, provided by _print_thread().
+ if (defined $self->{ _hint_ref_key_list }->{ $id }) {
+ my $thread_list = $self->{ _hint_ref_key_list }->{ $id } || [];
+
+ # update link relation for all articles in this thread.
+ KEY:
+ for my $id (@$thread_list) {
+ next KEY if $uniq{ $id};
+ $uniq{ $id } = 1;
+
+ $self->_msg_file_rewrite_links( $id );
+ push(@$list, $id);
+ }
+ }
+}
+
+
+# Descriptions: update link at preamble and footer of HTML-ified message.
+# Arguments: OBJ($self) STR($id)
+# Side Effects: rewrite index file
+# Return Value: none
+sub _msg_file_rewrite_links
+{
+ my ($self, $id) = @_;
+ my $info = $self->evaluate_links_relation($id);
+ my $preamble = $self->evaluate_safe_preamble($info);
+ my $footer = $self->evaluate_safe_footer($info);
+ my $code = _charset_to_code($self->{ _charset });
+
+ my $pat_preamble_begin = quotemeta($preamble_begin);
+ my $pat_preamble_end = quotemeta($preamble_end);
+ my $pat_footer_begin = quotemeta($footer_begin);
+ my $pat_footer_end = quotemeta($footer_end);
+
+ my $mask = umask();
+
+ umask(022);
+
+ _PRINT_DEBUG("try _msg_file_rewrite_links($id)");
+
+ use FileHandle;
+ my $file = $info->{ filepath };
+ if (defined $file && $file && -f $file) {
+ my ($old, $new) = ($file, "$file.new.$$");
+ my $rh = new FileHandle $old;
+ my $wh = new FileHandle "> $new";
+
+ if (defined $rh && defined $wh) {
+ my $buf;
+
+ _PRINT_DEBUG("rewrite: open msg $id");
+
+ LINE:
+ while ($buf = <$rh>) {
+ if ($buf =~ /^$pat_preamble_begin/
+ ..
+ $buf =~ /^$pat_preamble_end/) {
+ if ($buf =~ /^$pat_preamble_end/) {
+ _print_raw_str($wh, $preamble, $code);
+ }
+ next LINE;
+ }
+
+ if ($buf =~ /^$pat_footer_begin/
+ ..
+ $buf =~ /^$pat_footer_end/) {
+ if ($buf =~ /^$pat_footer_end/) {
+ _print_raw_str($wh, $footer, $code);
+ }
+ next LINE;
+ }
+
+ # just copy (rewrite only $preamble and $footer not message)
+ _print_raw_str($wh, $buf, $code);
+ }
+ $rh->close;
+ $wh->close;
+
+ unless (rename($new, $old)) {
+ croak("rename($new, $old) fail (id=$id)\n");
+ }
+ else {
+ _PRINT_DEBUG("done: rewritten links in msg $id");
+ }
+ }
+ else {
+ unless (defined $file) {
+ $new = $old = '(null string)';
+ }
+ warn("cannot open $old (id=$id)\n") unless defined $rh;
+ warn("cannot create $new (id=$id)\n") unless defined $wh;
+ }
+ }
+ else {
+ warn("undefined file for $id\n") if $is_strict_warn;
+ }
+
+ umask($mask);
+}
+
+
+# Descriptions: return thread link relation info et.al. for $id
+# Arguments: OBJ($self) NUM($id)
+# Side Effects: none
+# Return Value: HASH_REF
+sub evaluate_links_relation
+{
+ my ($self, $id) = @_;
+ my $ndb = $self->ndb();
+
+ return $ndb->get_tohtml_thread_summary($id);
+}
+
+
+# Descriptions: return preamble without metachars
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub evaluate_safe_preamble
+{
+ my ($self, $args) = @_;
+ my $link_prev_id = $args->{ link_prev_id };
+ my $link_next_id = $args->{ link_next_id };
+ my $link_prev_thread_id = $args->{ link_prev_thread_id };
+ my $link_next_thread_id = $args->{ link_next_thread_id };
+
+ my $use_subdir = $self->{ _use_subdir } eq 'yes' ? 1 : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $preamble = $preamble_begin. "\n";
+
+ my $mask = umask();
+
+ umask(022);
+
+ # for debug
+ $preamble .= "<!-- rewritten for id=$self->{ _debug_id } -->\n";
+
+ if (defined($link_prev_id) && $link_prev_id) {
+ $preamble .= "<A HREF=\"${prefix}$link_prev_id\">[Prev by ID]</A>\n";
+ }
+ else {
+ $preamble .= "[No Prev ID]\n";
+ }
+
+ if (defined($link_next_id) && $link_next_id) {
+ $preamble .= "<A HREF=\"${prefix}$link_next_id\">[Next by ID]</A>\n";
+ }
+ else {
+ $preamble .= "[No Next ID]\n";
+ }
+
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_prev_thread_id\">[Prev by Thread]</A>\n";
+ }
+ else {
+ if (defined $link_prev_id && $link_prev_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_prev_id\">[Prev by Thread]</A>\n";
+ }
+ else {
+ $preamble .= "[No Prev Thread]\n";
+ }
+ }
+
+ if (defined $link_next_thread_id && $link_next_thread_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_next_thread_id\">[Next by Thread]</A>\n";
+ }
+ else {
+ if (defined $link_next_id && $link_next_id) {
+ $preamble .=
+ "<A HREF=\"${prefix}$link_next_id\">[Next by Thread]</A>\n";
+ }
+ else {
+ $preamble .= "[No Next Thread]\n";
+ }
+ }
+
+ $preamble .= _format_index_navigator( { use_subdir => $use_subdir } );
+ $preamble .= $preamble_end. "\n";;
+
+ umask($mask);
+
+ return $preamble;
+}
+
+
+# Descriptions: return footer without metachars
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: none
+# Return Value: STR
+sub evaluate_safe_footer
+{
+ my ($self, $args) = @_;
+ my $link_prev_id = $args->{ link_prev_id };
+ my $link_next_id = $args->{ link_next_id };
+ my $link_prev_thread_id = $args->{ link_prev_thread_id };
+ my $link_next_thread_id = $args->{ link_next_thread_id };
+ my $subject = $args->{ subject };
+
+ my $use_subdir = $self->{ _use_subdir } eq 'yes' ? 1 : 0;
+ my $prefix = $use_subdir ? '../' : '';
+ my $footer = $footer_begin. "\n";;
+
+ if (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .= "<A HREF=\"${prefix}$link_prev_id\">Prev by ID: ";
+ if (defined $subject->{ prev_id } ) {
+ $footer .= _sprintf_safe_str( $subject->{ prev_id } );
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .= "<A HREF=\"${prefix}$link_next_id\">Next by ID: ";
+ if (defined $subject->{ next_id } ) {
+ $footer .= _sprintf_safe_str( $subject->{ next_id } );
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined $link_prev_thread_id && $link_prev_thread_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_thread_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_thread_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_thread_id });
+ }
+ $footer .= "</A>\n";
+ }
+ elsif (defined($link_prev_id) && $link_prev_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_prev_id\">Prev by Thread: ";
+ if (defined $subject->{ prev_id }) {
+ $footer .= _sprintf_safe_str($subject->{ prev_id });
+ }
+ $footer .= "</A>\n";
+ }
+
+ if (defined $link_next_thread_id && $link_next_thread_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_thread_id\">Next by Thread: ";
+ if (defined $subject->{ next_thread_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_thread_id });
+ }
+ $footer .= "</A>\n";
+ }
+ elsif (defined($link_next_id) && $link_next_id) {
+ $footer .= "<BR>\n";
+ $footer .=
+ "<A HREF=\"${prefix}$link_next_id\">Next by Thread: ";
+ if (defined $subject->{ next_id }) {
+ $footer .= _sprintf_safe_str($subject->{ next_id });
+ }
+ $footer .= "</A>\n";
+ }
+
+ $footer .= qq{<BR>\n};
+ $footer .= _format_index_navigator( { use_subdir => $use_subdir } );
+ $footer .= $footer_end. "\n";;
+
+ return $footer;
+}
+
+
+=head2 update_id_index($args)
+
+update index.html.
+
+=cut
+
+
+# Descriptions: print navigation bar et.al. at upper half of indexes
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $new html
+# Return Value: none
+sub _print_index_begin
+{
+ my ($self, $args) = @_;
+ my $old = $args->{ old };
+ my $new = $args->{ new };
+ my $title = $args->{ title };
+ my $code = _charset_to_code($self->{ _charset });
+
+ my $mask = umask();
+
+ umask(022);
+
+ use FileHandle;
+ my $wh = new FileHandle "> $new";
+ $args->{ wh } = $wh;
+
+ $self->html_start($wh, { title => $title });
+
+ _print_raw_str($wh, _format_index_navigator(), $code);
+ $self->mhl_separator($wh);
+
+ umask($mask);
+}
+
+
+# Descriptions: print navigation bar et.al. at the end of indexes.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create $new html
+# Return Value: none
+sub _print_index_end
+{
+ my ($self, $args) = @_;
+ my $wh = $args->{ wh };
+ my $old = $args->{ old };
+ my $new = $args->{ new };
+ my $title = $args->{ title };
+ my $code = $args->{ code };
+
+ $self->mhl_separator($wh);
+ _print_raw_str($wh, _format_index_navigator(), $code);
+
+ # append version information
+ _print_raw_str($wh, "<BR>Genereated by $version\n", $code);
+
+ $self->html_end($wh);
+
+ unless (rename($new, $old)) {
+ croak("rename($new, $old) fail\n");
+ }
+}
+
+# Descriptions: create Top index.html if no index.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: create Top index.html
+# Return Value: none
+sub create_top_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Top Index",
+ old => "$html_base_dir/index.html",
+ new => "$html_base_dir/index.html.new.$$",
+ code => $code,
+ };
+
+ return if ( -f $htmlinfo-> { old } );
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: update index_all.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite index_all.html
+# Return Value: none
+sub update_id_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "ID Index",
+ old => "$html_base_dir/index_all.html",
+ new => "$html_base_dir/index_all.html.new.$$",
+ code => $code,
+ };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update index_all.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+
+ $self->_print_ul($wh, $db, $code);
+ if ($order eq 'reverse') {
+ for my $id (reverse (1 .. $max_id)) {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ else {
+ for my $id (1 .. $max_id) {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+=head2 update_monthly_id_index($args)
+
+=cut
+
+
+# Descriptions: update monthly index
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly index
+# Return Value: none
+sub update_monthly_id_index
+{
+ my ($self, $args) = @_;
+ my $affected_list = $self->{ _affected_idlist };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update index.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ # open databaes
+ my $db = $self->ndb();
+ my %month_update = ();
+
+ IDLIST:
+ for my $id (@$affected_list) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
+ $month_update{ $month } = 1;
+ }
+ }
+
+ # todo list
+ for my $month (sort keys %month_update) {
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
+
+ $self->_update_monthly_id_index($args, {
+ this_month => $this_month,
+ suffix => $suffix,
+ });
+ }
+
+ # update monthly_index.html
+ $self->_update_id_montly_index_master($args);
+}
+
+
+# Descriptions: update monthly index master
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly_index.html
+# Return Value: none
+sub _update_id_montly_index_master
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_index.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_index.html.new.$$");
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "ID Index",
+ old => $old,
+ new => $new,
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
+ my (@list) = sort __sort_yyyymm keys %$mlist;
+ my ($years) = _yyyy_range(\@list);
+
+ _print_raw_str($wh, "<table border='1'>", $code);
+
+ for my $year (sort {$b <=> $a} @$years) {
+ _print_raw_str($wh, "<tr>", $code);
+ _print_raw_str($wh, "<th> $year </th>", $code);
+
+ for my $month (1 .. 12) {
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $fn = "month.$xx.html";
+
+ use File::Spec;
+ my $file = File::Spec->catfile($html_base_dir, $fn);
+ if (-f $file) {
+ _print_raw_str($wh, "<td><a href=\"$fn\"> $month </a>", $code);
+ }
+ else {
+ _print_raw_str($wh, "<td>", $code);
+ }
+ _print_raw_str($wh, "</td>\n", $code);
+ }
+ _print_raw_str($wh, "</tr>\n", $code);
+ }
+ _print_raw_str($wh, "</table>", $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: return list of YYYY/MM format
+# Arguments: ARRAY_REF($list)
+# Side Effects: none
+# Return Value: ARRAY_REF
+sub _yyyy_range
+{
+ my ($list) = @_;
+ my ($yyyy) = {};
+
+ for my $y (@$list) {
+ if ($y =~ /^(\d{4})\/(\d{2})/o) {
+ $yyyy->{ $1 } = $1;
+ }
+ }
+
+ my (@yyyy) = keys %$yyyy;
+ return( \@yyyy );
+}
+
+
+# Descriptions: sort YYYY/MM formt strings
+# Arguments: none
+# Side Effects: none
+# Return Value: NUM
+sub __sort_yyyymm
+{
+ my ($xa, $xb) = ($a, $b);
+ $xa =~ s@/@@;
+ $xb =~ s@/@@;
+ if ($xa eq '') { $xa = 0;}
+ if ($xb eq '') { $xb = 0;}
+
+ $xa <=> $xb;
+}
+
+
+# Descriptions: update month.YYYYMM.html
+# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
+# Side Effects: update month.YYYYMM.html
+# Return Value: none
+sub _update_monthly_id_index
+{
+ my ($self, $args, $monthlyinfo) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
+ my $suffix = $monthlyinfo->{ suffix }; # yyyymm
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => "ID Monthly Index $this_month",
+ old => "$html_base_dir/month.${suffix}.html",
+ new => "$html_base_dir/month.${suffix}.html.new.$$",
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
+
+ $self->_print_ul($wh, $db, $code);
+ if ($order eq 'reverse') {
+ ID:
+ for my $id (reverse sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ else {
+ ID:
+ for my $id (sort {$a <=> $b} @$list) {
+ next ID unless $id =~ /^\d+$/o;
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+=head2 update_thread_index($args)
+
+update thread.html.
+
+=cut
+
+
+# Descriptions: update thread.html
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite thread.html
+# Return Value: none
+sub update_thread_index
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $code = _charset_to_code($self->{ _charset });
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
+ old => "$html_base_dir/thread.html",
+ new => "$html_base_dir/thread.html.new.$$",
+ code => $code,
+ };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update thread.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+
+ # initialize negagtive cache to ensure uniquness
+ delete $self->{ _uniq };
+
+ $self->_print_ul($wh, $db, $code);
+ for my $id ( 1 .. $max_id ) {
+ # head of the thread (not referenced yet)
+ unless (defined $self->{ _uniq }->{ $id }) {
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: check whether $id has next or previous link.
+# Arguments: OBJ($self) HASH_REF($db) NUM($id)
+# Side Effects: none
+# Return Value: 1 or 0
+sub _has_link
+{
+ my ($self, $db, $id) = @_;
+
+ if ($db->get('next_key', $id) || $db->get('prev_key', $id)) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+}
+
+
+# Descriptions: print thread array of (head_id id2 id3 ...)
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($head_id) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_thread
+{
+ my ($self, $wh, $db, $head_id, $code) = @_;
+ my $saved_stack_level = $self->{ _stack };
+ my $uniq = $self->{ _uniq };
+
+ # get id list: @idlist = ( $head_id id2 id3 ... )
+ my $ndb = $self->ndb();
+ my $buf = $ndb->get('ref_key_list', $head_id);
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- thread head=$head_id ($buf) -->\n", $code);
+
+ my $idlist = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ if (@$idlist) {
+ IDLIST:
+ for my $id (@$idlist) {
+ # save $head_id => "id1 id2 id3 ..." on memory for further use.
+ # "> 1" implies idlist contains others than myself.
+ if ($#$idlist > 1) {
+ my $ra = $ndb->get_as_array_ref('ref_key_list', $head_id);
+ $self->{ _hint_ref_key_list }->{ $id } = $ra;
+ }
+
+ # @$idlist = (number's)
+ _print_raw_str($wh, "<!-- thread (@$idlist) -->\n", $code);
+
+ next IDLIST if $uniq->{ $id };
+ $uniq->{ $id } = 1;
+
+ $self->_print_ul($wh, $db, $code);
+
+ # oops, we should ignore head of the thread ( myself ;-)
+ if (($id != $head_id) && $self->_has_link($db, $id)) {
+ _print_raw_str($wh, "<!-- thread $id has link -->\n", $code);
+ $self->_print_li_filename($wh, $db, $id, $code);
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ else {
+ $self->_print_li_filename($wh, $db, $id, $code);
+ }
+ }
+ }
+
+ while ($self->{ _stack } > $saved_stack_level) {
+ $self->_print_end_of_ul($wh, $db, $code);
+ }
+}
+
+=head2 update_monthly_thread_index($args)
+
+=cut
+
+# Descriptions: update monthly thread index
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly thread index
+# Return Value: none
+sub update_monthly_thread_index
+{
+ my ($self, $args) = @_;
+ my $affected_list = $self->{ _affected_idlist };
+
+ if ($self->is_ignore($args->{id})) {
+ warn("not update monthly_thread.html around $args->{id}") if $debug;
+ return undef;
+ }
+
+ # open databaes
+ my $db = $self->ndb();
+ my %month_update = ();
+
+ IDLIST:
+ for my $id (@$affected_list) {
+ next IDLIST unless $id =~ /^\d+$/o;
+ next IDLIST if $id =~ /^\s*$/o;
+
+ my $month = $db->get('month', $id);
+ if (defined $month && $month !~ /^\s*$/o) {
+ $month_update{ $month } = 1;
+ }
+ }
+
+ # todo list
+ for my $month (sort keys %month_update) {
+ my $this_month = $month; # yyyy/mm
+ my $suffix = $month; $suffix =~ s@/@@go; # yyyymm
+
+ $self->_update_monthly_thread_index($args, {
+ this_month => $this_month,
+ suffix => $suffix,
+ });
+ }
+
+ # update monthly_index.html
+ $self->_update_montly_thread_index_master($args);
+}
+
+
+# Descriptions: update monthly thread index master
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: rewrite monthly_thread.html
+# Return Value: none
+sub _update_montly_thread_index_master
+{
+ my ($self, $args) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+
+ use File::Spec;
+ my $old = File::Spec->catfile($html_base_dir, "monthly_thread.html");
+ my $new = File::Spec->catfile($html_base_dir, "monthly_thread.html.new.$$");
+ my $htmlinfo = {
+ title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
+ old => $old,
+ new => $new,
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $mlist = $db->get_table_as_hash_ref('inv_month'); # month => (id ...)
+ my (@list) = sort __sort_yyyymm keys %$mlist;
+ my ($years) = _yyyy_range(\@list);
+
+ _print_raw_str($wh, "<table border='1'>", $code);
+
+ for my $year (sort {$b <=> $a} @$years) {
+ _print_raw_str($wh, "<TR>", $code);
+ _print_raw_str($wh, "<th> $year </th>", $code);
+
+ for my $month (1 .. 12) {
+ my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
+ my $fn = "thread.$xx.html";
+
+ use File::Spec;
+ my $file = File::Spec->catfile($html_base_dir, $fn);
+ if (-f $file) {
+ _print_raw_str($wh, "<td><a href=\"$fn\"> $month </A>", $code);
+ }
+ else {
+ _print_raw_str($wh, "<td>", $code);
+ }
+ _print_raw_str($wh, "</td>\n", $code);
+ }
+ _print_raw_str($wh, "</tr>\n", $code);
+ }
+ _print_raw_str($wh, "</table>", $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+
+# Descriptions: update thread.YYYYMM.html
+# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
+# Side Effects: update thread.YYYYMM.html
+# Return Value: none
+sub _update_monthly_thread_index
+{
+ my ($self, $args, $monthlyinfo) = @_;
+ my $html_base_dir = $self->{ _html_base_directory };
+ my $code = _charset_to_code($self->{ _charset });
+ my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
+ my $suffix = $monthlyinfo->{ suffix }; # yyyymm
+ my $order = $self->{ _html_id_order } || 'normal';
+ my $htmlinfo = {
+ title => "Monthly Thread Index $this_month",
+ old => "$html_base_dir/thread.${suffix}.html",
+ new => "$html_base_dir/thread.${suffix}.html.new.$$",
+ code => $code,
+ };
+
+ $self->_print_index_begin( $htmlinfo );
+ my $wh = $htmlinfo->{ wh };
+ my $db = $self->ndb();
+ my $max_id = $db->get('hint', 'max_id');
+ my $list = $db->get_as_array_ref('inv_month', $this_month);
+
+ # initialize negagtive cache to ensure uniquness
+ delete $self->{ _uniq };
+
+ # debug information (it is useful not to remove this ?)
+ _print_raw_str($wh, "<!-- this month ids=(@$list) -->\n", $code);
+
+ $self->_print_ul($wh, $db, $code);
+ for my $id (sort {$a <=> $b} @$list) {
+ # head of the thread (not referenced yet)
+ unless (defined $self->{ _uniq }->{ $id }) {
+ $self->_print_thread($wh, $db, $id, $code);
+ }
+ }
+ $self->_print_end_of_ul($wh, $db, $code);
+
+ $self->_print_index_end( $htmlinfo );
+}
+
+=head2 internal utility functions for IO
+
+=cut
+
+
+# Descriptions: cnvert charset to code e.g. iso-2022-jp => jis
+# Arguments: STR($charset)
+# Side Effects: none
+# Return Value: STR
+sub _charset_to_code
+{
+ my ($charset) = @_;
+
+ if (defined $charset) {
+ $charset =~ tr/A-Z/a-z/;
+ if ($charset eq 'euc-jp') {
+ return 'euc';
+ }
+ elsif ($charset eq 'iso-2022-jp') {
+ return 'jis';
+ }
+ else {
+ return $charset; # may be wrong, but I hope it works well:-)
+ }
+ }
+ else {
+ return 'euc'; # euc-jp by default
+ }
+}
+
+
+# Descriptions: print raw $str to $wh channel
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_raw_str
+{
+ my ($wh, $str, $code) = @_;
+ $code = defined($code) ? $code : 'euc'; # euc-jp by default
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined($str) && $str) {
+ $str = __nc_convert($str, $code || 'euc');
+ }
+
+ print $wh $str;
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# XXX text2html($str, urls => 1, pre => 0)
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_safe_str
+{
+ my ($wh, $str, $code) = @_;
+ __print_safe_str(0, $wh, $str, $code);
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# XXX text2html($str, urls => 1, pre => 1)
+# Arguments: HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_safe_buf
+{
+ my ($wh, $str, $code) = @_;
+ __print_safe_str(1, $wh, $str, $code);
+}
+
+
+# Descriptions: print safe $str to $wh channel
+# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: none
+sub __print_safe_str
+{
+ my ($attr_pre, $wh, $str, $code) = @_;
+ my $p = __sprintf_safe_str($attr_pre, $wh, $str, $code);
+ print $wh $p if defined $p;
+ print $wh "\n";
+}
+
+
+# Descriptions: return safe $str
+# Arguments: STR($str) STR($code)
+# Side Effects: none
+# Return Value: STR
+sub _sprintf_safe_str
+{
+ my ($str, $code) = @_;
+ return __sprintf_safe_str(0, undef, $str, $code);
+}
+
+
+# Descriptions: return safe $str modified by text2html().
+# $str language code is modified by Mail::Message::Encode.
+# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
+# Side Effects: none
+# Return Value: STR or UNDEF
+sub __sprintf_safe_str
+{
+ my ($attr_pre, $wh, $str, $code) = @_;
+ my $regexp = $hints->{ subject_tag_regexp } || '';
+ my $rbuf = '';
+
+ # XXX-TODO: euc-jp is hard-coded.
+ if (defined($str) && $str) {
+ $str = __nc_convert($str, $code || 'euc');
+ }
+
+ if (defined $str) {
+ # $url$trailor => $url $trailor for text2html() incomplete regexp
+ $str =~ s#(http://[^\s\<\>\'\"]+[\w\d/])#_separete_url($1)#ge;
+
+ use HTML::FromText;
+ # NOT CONVERT subject tag (see fml-devel:726).
+ if ($str =~ /^\s*($regexp)(.*)/) {
+ my ($tag, $post) = ($1, $2);
+ my $tag_s = text2html($tag, urls => 0, pre => $attr_pre);
+ my $post_s = text2html($post, urls => 1, pre => $attr_pre);
+ return sprintf("%s%s", $tag_s, $post_s);
+ }
+ else {
+ return text2html($str, urls => 1, pre => $attr_pre);
+ }
+ }
+ else {
+ return undef;
+ }
+}
+
+
+# Descriptions: extract URL syntax in $url string.
+# $url$trailor => $url $trailor for text2html()
+# XXX incomplete regexp, we should correct it.
+# Arguments: STR($url)
+# Side Effects: none
+# History: based on fml 4.0-current (2001/10/28)
+# Return Value: STR
+sub _separete_url
+{
+ my ($url) = @_;
+ my ($re_euc_c) = '[\241-\376][\241-\376]';
+ my ($re_euc_s) = "($re_euc_c)+";
+ my $trailor = '';
+
+ # remove prepended/appended EUC strings
+ if ($url =~ /($re_euc_s)+$/) {
+ $trailor = $1;
+ $url =~ s/$trailor//;
+ }
+
+ # incomplete but may be effective ?
+ # RFC2068 says these special char's are not used.
+ # we should not include these char's in URL.
+ # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
+ # unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"
+ if ($url =~ /(\&\w{2}\;|\;|\?|\:|\@|\&|\=|\+|\#|\%|\<|\>|\")+$/) {
+ my $pat = $1;
+ $trailor = $pat . $trailor;
+ $url =~ s/${pat}$//;
+ }
+
+ return "$url $trailor";
+}
+
+
+# Descriptions: debug
+# Arguments: STR($str)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG
+{
+ my ($str) = @_;
+ print STDERR "(debug) $str\n" if $debug;
+}
+
+
+# Descriptions: debug, print out hash
+# Arguments: HASH_REF($hash)
+# Side Effects: none
+# Return Value: none
+sub _PRINT_DEBUG_DUMP_HASH
+{
+ my ($hash) = @_;
+ my ($k,$v);
+
+ if ($debug) {
+ while (($k, $v) = each %$hash) {
+ printf STDERR "%-30s => %s\n", $k, $v;
+ }
+ }
+}
+
+
+=head2 internal utility functions for HTML TAGS
+
+C<_print_something()> internal function provides wrapper to print HTML
+tags et.al.
+
+=cut
+
+
+# Descriptions: print <UL> with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_ul
+{
+ my ($self, $wh, $db, $code) = @_;
+
+ $self->{ _stack }++;
+
+ my $padding = " " x $self->{ _stack };
+ _print_raw_str($wh, "${padding}<UL>\n", $code);
+}
+
+
+# Descriptions: print </UL> with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_end_of_ul
+{
+ my ($self, $wh, $db, $code) = @_;
+
+ return unless $self->{ _stack } > 0;
+
+ my $padding = " " x $self->{ _stack };
+ _print_raw_str($wh, "${padding}</UL>\n", $code);
+
+ $self->{ _stack }--;
+}
+
+
+# Descriptions: print <LI> filename ... with proper indentation
+# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) NUM($id) STR($code)
+# Side Effects: none
+# Return Value: none
+sub _print_li_filename
+{
+ my ($self, $wh, $db, $id, $code) = @_;
+ my $filename = $db->get('html_filename', $id);
+ my $subject = $db->get('article_subject', $id) ||
+ $db->get('subject', $id) || "no subject";
+ my $who = $db->get('who', $id) || "no sender";
+ if ($self->{ _use_address_mask } ne 'yes' &&
+ $db->get('from', $id) ne '') {
+ $who = "" if ($who =~ /\@xxx/);
+ $who .= " " . $db->get('from', $id);
+ }
+
+ my $mimeopt = $main::opt_mimedecodequoted;
+ $main::opt_mimedecodequoted = 1;
+ $subject = $self->_decode_mime_string($subject) if $subject =~ /=\?/i;
+ $who = $self->_decode_mime_string($who) if $who =~ /=\?/i;
+ $main::opt_mimedecodequoted = $mimeopt;
+
+ _PRINT_DEBUG("-- print_li_filename id=$id file=$filename");
+
+ if (defined $filename && $filename) {
+ _print_raw_str($wh, "<!-- LI id=$id -->\n", $code);
+
+ _print_raw_str($wh, "<LI>\n", $code);
+ _print_raw_str($wh, "<A HREF=\"$filename\">\n", $code);
+ _print_safe_str($wh, $subject, $code);
+ _print_raw_str($wh, ",\n", $code);
+ _print_safe_str($wh, "$who\n", $code);
+ _print_raw_str($wh, "</A>\n", $code);
+ }
+}
+
+
+# Descriptions: extrace gecos field in $address
+# Arguments: OBJ($self) STR($address)
+# Side Effects: none
+# Return Value: STR
+sub _address_to_gecos
+{
+ my ($self, $address) = @_;
+
+ use Mail::Message::Utils;
+ return Mail::Message::Utils::from_address_to_name($address);
+}
+
+
+# Descriptions: mask the detail of address
+# Arguments: OBJ($self) STR($field)
+# Side Effects: none
+# Return Value: NUM
+sub _is_mask_address
+{
+ my ($self, $field) = @_;
+ my $type = $self->{ _address_mask_type } || 'all';
+
+ if ($type eq 'all') {
+ if ($field =~ /^(From|To|Cc)$/i) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ }
+ else {
+ return 0;
+ }
+}
+
+
+# Descriptions: decode MIME-encoded $str
+# Arguments: OBJ($self) STR($str) HASH_REF($options)
+# Side Effects: none
+# Return Value: STR
+sub _decode_mime_string
+{
+ my ($self, $str, $options) = @_;
+ my $charset = $options->{ 'charset' } || $self->{ _charset };
+ my $code = _charset_to_code($charset) || 'euc';
+
+ if (defined($str) && $str) {
+ use Mail::Message::Subject;
+ my $sbj = new Mail::Message::Subject $str;
+ $sbj->mime_header_decode();
+ return $sbj->as_str();
+ }
+ else {
+ return '';
+ }
+}
+
+
+# Descriptions: convert $str to $out_code code
+# Arguments: OBJ($self) STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+use Encode qw(decode encode);
+
+sub _convert
+{
+ my ($self, $str, $out_code, $in_code) = @_;
+
+ return '' unless defined $str && $str ne '';
+
+ $in_code ||= 'euc-jp';
+ $out_code ||= 'euc-jp';
+
+ $out_code = lc $out_code;
+
+ if ($out_code =~ /^(jis|iso2022jp|iso-2022-jp)$/) {
+ $out_code = 'iso-2022-jp';
+ }
+ elsif ($out_code =~ /^(sjis|shiftjis)$/) {
+ $out_code = 'shiftjis';
+ }
+ else {
+ $out_code = 'euc-jp';
+ }
+
+ my $decoded = eval {
+ decode($in_code, $str, Encode::FB_DEFAULT);
+ };
+ return $decoded;
+}
+
+# Descriptions: convert $str to $out_code code (non method version)
+# XXX you should remove this function.
+# Arguments: STR($str) STR($out_code) STR($in_code)
+# Side Effects: none
+# Return Value: STR
+sub __nc_convert
+{
+ my ($str, $out_code, $in_code) = @_;
+
+ return '' unless defined $str && $str ne '';
+
+ $in_code ||= 'euc-jp';
+ $out_code ||= 'euc-jp';
+
+ $out_code = lc $out_code;
+
+ if ($out_code =~ /^(jis|iso2022jp|iso-2022-jp)$/) {
+ $out_code = 'iso-2022-jp';
+ }
+ elsif ($out_code =~ /^(sjis|shiftjis)$/) {
+ $out_code = 'shiftjis';
+ }
+ else {
+ $out_code = 'euc-jp';
+ }
+
+ my $decoded;
+
+ eval {
+ $decoded = decode($in_code, $str);
+ 1;
+ } or do {
+ # fallback: 元データを返す
+ $decoded = $str;
+ };
+
+ return defined $decoded ? $decoded : $str;
+}
+
+
+=head1 useful functions as entrance
+
+=head2 htmlify_file($file, $args)
+
+try to convert rfc822 message C<$file> to HTML.
+
+ $args = {
+ directory => "destination directory",
+ };
+
+=head2 htmlify_dir($dir, $args)
+
+try to convert all rfc822 messages to HTML in C<$dir> directory.
+
+ $args = {
+ directory => "destination directory",
+ };
+
+=cut
+
+
+# Descriptions: convert $file to HTML
+# Arguments: OBJ($self) STR($file) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub htmlify_file
+{
+ my ($self, $file, $args) = @_;
+ my $dst_dir = $args->{ output_dir };
+ my $indexs = \@indexs;
+
+ unless (-f $file) {
+ print STDERR "no such file: $file\n" if $debug;
+ return;
+ }
+
+ unless (-s $file) {
+ print STDERR "empty file: $file\n" if $debug;
+ return;
+ }
+
+ use File::Basename;
+ my $id = basename($file);
+ my $html = new Mail::Message::ToHTML $args;
+
+ if ($debug) {
+ printf STDERR "htmlify_file( id=%-6s src=%s )\n", $id, $file;
+ }
+
+ _PRINT_DEBUG("htmlify_rfc822_message begin");
+ unless ($html->htmlify_rfc822_message({
+ id => $id,
+ src => $file,
+ }) ) {
+ _PRINT_DEBUG("htmlify_rfc822_message end no change");
+ return;
+ }
+ _PRINT_DEBUG("htmlify_rfc822_message end");
+
+ if ($debug) {
+ printf STDERR "htmlify_file( id=%-6s ) update relation\n", $id;
+ }
+
+ _PRINT_DEBUG("-- msg_html_links");
+ $html->update_msg_html_links( $id );
+
+ for my $index (@$indexs) {
+ if ($index eq "month") {
+ _PRINT_DEBUG("-- monthly id index");
+ $html->update_monthly_id_index({ id => $id });
+ }
+
+ if ($index eq "all") {
+ _PRINT_DEBUG("-- id index");
+ $html->update_id_index({ id => $id });
+ }
+
+ if ($index eq "thread") {
+ _PRINT_DEBUG("-- thread index");
+ $html->update_thread_index({ id => $id });
+ }
+
+ if ($index eq "month_thread") {
+ _PRINT_DEBUG("-- month thread index");
+ $html->update_monthly_thread_index({ id => $id });
+ }
+
+ if ($index eq "top") {
+ _PRINT_DEBUG("-- top index");
+ $html->create_top_index();
+ }
+ }
+
+ # no more action for old files
+ if ($html->is_ignore($id)) {
+ warn("not process $id (already exists)") if $debug;
+ }
+ else {
+ if ($debug) {
+ printf STDERR " converted( id=%-6s src=%s )\n", $id, $file;
+ }
+ }
+}
+
+
+# Descriptions: convert all articles in specified directory
+# Arguments: OBJ($self) STR($src_dir) HASH_REF($args)
+# Side Effects: none
+# Return Value: none
+sub htmlify_dir
+{
+ my ($self, $src_dir, $args) = @_;
+ my $dst_dir = $args->{ output_dir };
+ my $min = 0;
+ my $max = 0;
+ my $has_fork = 1; # ok on unix and perl>5.6 on wine32.
+
+ print STDERR "src = $src_dir\ndst = $dst_dir\n" if $debug;
+
+ use DirHandle;
+ my $dh = new DirHandle $src_dir;
+ if (defined $dh) {
+ FILE:
+ for my $file ( $dh->read() ) {
+ next FILE unless $file =~ /^\d+$/;
+
+ # initialize $min
+ unless ($min) { $min = $file;}
+
+ $max = $max < $file ? $file : $max;
+ $min = $min > $file ? $file : $min;
+ }
+ }
+
+ # overwride
+ $has_fork = $args->{ has_fork } if defined $args->{ has_fork };
+ $max = $args->{ max } if defined $args->{ max };
+
+ print STDERR " scan ( $min .. $max ) for $src_dir\n" if $debug;
+ for my $id ( $min .. $max ) {
+ use File::Spec;
+ my $file = File::Spec->catfile($src_dir, $id);
+
+ unless ( $has_fork ) {
+ $self->htmlify_file($file, $args);
+ }
+ else {
+ my $pid = fork();
+ if ($pid < 0) {
+ croak("cannot fork");
+ }
+ elsif ($pid == 0) {
+ $self->htmlify_file($file, $args);
+ exit 0;
+ }
+
+ # parent
+ my $dying;
+ while (($dying = wait()) != -1 && ($dying != $pid) ){
+ ;
+ }
+ }
+ }
+}
+
+
+#
+# debug
+#
+if ($0 eq __FILE__) {
+ my $dir = "/tmp/htdocs";
+ my $has_fork = defined $ENV{'HAS_FORK'} ? 1 : 0;
+ my $max = defined $ENV{'MAX'} ? $ENV{'MAX'} : 1000;
+ my $charset = 'euc-jp';
+ my $opts = {
+ output_dir => "/tmp/htdocs",
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ };
+
+ eval q{
+ my ($t, $time_b, $time_e);
+ for my $x (@ARGV) {
+ $time_b = time;
+ print STDERR "debug.main processing $x ...";
+
+ if (-f $x) {
+ eval q{
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_file($x, {
+ output_dir => "/tmp/htdocs",
+ directory => $dir,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ });
+ };
+ print STDERR $@ if $@;
+ }
+ elsif (-d $x) {
+ my $obj = new Mail::Message::ToHTML $opts;
+ $obj->htmlify_dir($x, {
+ output_dir => "/tmp/htdocs",
+ directory => $dir,
+ has_fork => $has_fork,
+ max => $max,
+ charset => $charset,
+ db_base_dir => "/tmp/",
+ db_name => "elena",
+ });
+ }
+
+ $t = time - $time_b;
+ print STDERR "\t$t sec.\n";
+ }
+ print STDERR "done.\n";
+ };
+
+ if ($@) { croak($@);}
+}
+
+
+=head1 TODO
+
+ expiration
+
+ sub directory?
+
+=head1 CODING STYLE
+
+See C<http://www.fml.org/software/FNF/> on fml coding style guide.
+
+=head1 AUTHOR
+
+Ken'ichi Fukamachi
+
+=head1 COPYRIGHT
+
+Copyright (C) 2002,2003,2004,2005,2006 Ken'ichi Fukamachi
+
+All rights reserved. This program is free software; you can
+redistribute it and/or modify it under the same terms as Perl itself.
+
+=head1 HISTORY
+
+Mail::Message::ToHTML first appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+This class is renamed from C<Mail::HTML::Lite> 1.40 (2001-2002).
+
+=cut
+
+
+1;
diff --git a/host-artifacts/patch b/host-artifacts/patch
new file mode 100644
index 00000000..82f927f6
--- /dev/null
+++ b/host-artifacts/patch
@@ -0,0 +1,18 @@
+IM:: LOCAL PATCH
+
+Reference:
+ fml-help@ffs.fml.org: 00607
+ patch by OGAWA Kunihiko <kuni@edit.ne.jp>
+
+
+--- IM/Iso2022jp.pm Mon Feb 28 12:54:21 2000
++++ IM/Iso2022jp.pm Sun Aug 19 11:39:50 2001
+@@ -432,7 +432,7 @@
+ }
+ $word_out .= $Jp_out;
+ }
+- if ($word_in) {
++ if ($word_in ne '') {
+ $word_out .= "\n\t";
+ }
+ $size = $main::Folding_length;