# vim: set cindent expandtab ts=4 sw=4:
#
# Copyright (c) 1998-2005 Chi-Keung Ho. All rights reserved.
#
# This programe is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# Extmail - a high-performance webmail to maildir
# $Id$
package Ext::App::Message;
use strict;
use Exporter;
use vars qw(@ISA @EXPORT $MSGFILE $FOLDER $POS $t0 $t1);
@ISA = qw(Exporter Ext::App);
use vars qw($CRLF); # XXX todo
$CRLF = "\012"; # \r \015, \n \012
use Ext::App;
use Ext::MIME;
use Ext::Storage::Maildir;
use Ext::Utils;
use MIME::Base64;
use MIME::QuotedPrint;
use vars qw(%lang_readmsg $lang_charset);
use Ext::Lang;
use Ext::Unicode;
undef $MSGFILE;
undef $FOLDER;
undef $POS;
sub init {
my $self = shift;
$self->SUPER::init(@_);
return unless($self->valid||$self->permit);
$self->add_methods(readmsg_sum => \&readmsg_sum);
$self->add_methods(readmsg_rawdt => \&readmsg_rawdt);
$self->add_methods(readmsg_header => \&readmsg_header);
$self->add_methods(delete => \&delete);
$self->add_methods(download => \&download);
$self->{default_mode} = 'readmsg_sum';
Ext::Storage::Maildir::init($self->get_working_path);
Ext::MIME::init(path => $self->get_working_path, debug=>0);
$FOLDER = $self->{query}->cgi('folder');
$POS = $self->{query}->cgi('pos');
$MSGFILE = $ENV{MAILDIR}; # XXX
$MSGFILE .= '/'._name2mdir($FOLDER);
$self->_initme;
if(valid_maildir($FOLDER)) {
if(my $file = pos2file($FOLDER, $POS)) {
$MSGFILE .= '/cur/'.pos2file($FOLDER, $POS);
}else {
$self->{tpl}->assign(
# must assign sid, App::global_tpl() has not been
# called at this stage
#SID => $self->{query}->cgi('sid'),
REDIRECT => 1,
FOLDER => str2url($FOLDER)
);
$self->error($lang_readmsg{'message_err'});
}
}else {
$self->error($lang_readmsg{'folder_err'});
}
$self;
}
sub _initme {
initlang($_[0]->userconfig->{lang}, __PACKAGE__);
$_[0]->{tpl}->assign( lang_charset => $lang_charset );
$_[0]->{tpl}->assign( \%lang_readmsg );
}
sub delete {
my $self = shift;
my $tpl = $self->{tpl};
my $q = $self->{query};
# use global $POS
if(defined $POS) {
my $sort_order = get_sortorder($FOLDER);
set_bmsgs_delete($FOLDER, $POS);
set_msgs_cache($FOLDER, $sort_order);
# flush the $MSGFILE to the current pos file
$MSGFILE = $ENV{MAILDIR}; # XXX
$MSGFILE .= '/'._name2mdir($FOLDER);
my $file = "";
while(!($file = pos2file($FOLDER, $POS))) {
if($POS>0) { $POS-- }
else { last }
}
if($file) {
# still has file
$MSGFILE .= '/cur/'.pos2file($FOLDER, $POS);
}else {
# redirect to the folder message list mode, no more
# message can show, abort
$tpl->assign(
REDIRECT => 1,
FOLDER => str2url($FOLDER)
);
$self->error($lang_readmsg{'message_err'});
return;
}
$self->readmsg_sum;
}else {
$self->error($lang_readmsg{'delete_err'});
}
}
sub download {
my $self = shift;
my $tpl = $self->{tpl};
my $q = $self->{query};
my $mimeid = $q->cgi("mimeid");
if($mimeid eq '') {
$mimeid = 1;
}
$tpl->{noprint} = 1; # disable output buffer;
get_parts($MSGFILE, $mimeid, 'to_std');
}
sub readmsg_rawdt {
my $self = shift;
my $tpl = $self->{tpl};
my $q = $self->{query};
# disable template output bufffer
$tpl->{noprint} = 1;
print "Content-type: text/plain\r\n";
print "Content-Disposition: filename=\"rawdata.txt\"\r\n\r\n";
open(FD, "<$MSGFILE") or die "can't open $MSGFILE\n";
while(<FD>) {
print;
}
close FD;
}
sub readmsg_header {
my $self = shift;
my $tpl = $self->{tpl};
my $q = $self->{query};
my ($dir, $pos) = ($q->cgi('folder'), $q->cgi('pos'));
$tpl->{noprint} = 1;
my $file = pos2file($dir, $pos);
open(FD, "< "._name2mdir($dir)."/cur/$file") or
die "Can't open $file, $!\n";
local $/=$CRLF.$CRLF;
my $h = <FD>;
close FD;
print "Content-Type: text/plain\r\n";
print "Content-Disposition: filename=\"header.txt\"\r\n\r\n";
print $h;
}
sub readmsg_sum {
my $self = shift;
my $tpl = $self->{tpl};
my $q = $self->{query};
my $sid = $self->{sid};
my $usercfg = $self->userconfig();
my $utf8 = Ext::Unicode->new;
$tpl->assign(READMSG_SUM=>1);
my $parts = get_msg_info($MSGFILE);
my $hdr = $parts->{head}{hash};
my $charset = $q->cgi('charset') || hdr_get_hash('charset', %$hdr);
$utf8->set_charset($charset);
if($q->cgi("detail")) {
$tpl->assign(DETAIL=>1);
$tpl->assign(PARTS => mydumper($parts));
}
my ($from, $to, $subject, $date) = (
decode_words_utf8(hdr_get_hash('From', %$hdr)),
decode_words_utf8(hdr_get_hash('To', %$hdr)),
decode_words_utf8(hdr_get_hash('Subject', %$hdr)),
hdr_get_hash('Date', %$hdr)
);
# XXX FIXME experimental code
my @cp_maps = qw(gb2312 gbk gb18030 big5 utf-8 iso-2022-jp shift-jis euc-jp euc-kr iso-2022-kr);
my $matched = (grep(/^$charset$/i, @cp_maps) ? 1 : 0);
$tpl->assign('SEL_CHARSET_LOOP', SRC_CHARSET => 'auto', CHECKED => !$matched);
for (@cp_maps) {
$tpl->assign(
'SEL_CHARSET_LOOP',
SRC_CHARSET => $_,
CHECKED => lc $charset eq lc $_ ? 1 : 0,
);
}
# XXX FIXME - experimental detect code - urgly !
my $sjchar;
TRY: {
if ($charset) {
$sjchar = $charset; # force to CGI charset parameter
last TRY;
}
my $subject = decode_words(hdr_get_hash('Subject', %$hdr));
$subject =~ s/\s+//; # remove space
my $c = charset_detect($subject);
if ($c =~ /^(windows-1252|iso-8859-)/ && length $subject < 6) {
$sjchar = charset_detect(decode_words(hdr_get_hash('From', %$hdr)));
} else {
$sjchar = $c;
}
}
$from = iconv($from, $sjchar, 'utf-8') if charset_detect($from) ne 'utf-8';
$to = iconv($to, $sjchar, 'utf-8') if charset_detect($to) ne 'utf-8';
$subject = iconv($subject, $sjchar, 'utf-8') if charset_detect($subject) ne 'utf-8';
# XXX FIXME set charset to subject charset if it's unavailable
$charset = $sjchar unless $charset;
$tpl->assign(
SID => $sid,
FOLDER => str2url($FOLDER), # XXX should str2url
POS => $POS,
SUBJECT => html_escape($subject),
FROM => html_escape($from),
TO => html_escape($to),
DATE => $date
);
if (my $cc = decode_words_utf8(hdr_get_hash('Cc', %$hdr))) {
$tpl->assign( CC => html_escape($cc));
}
if (my $bcc = decode_words_utf8(hdr_get_hash('Bcc', %$hdr))) {
$tpl->assign( BCC => html_escape($bcc));
}
my ($ref, $nomore) = get_msgs_cache($FOLDER,1,$POS);
$tpl->assign(
HAVE_NEXT => $nomore?0:1,
NEXT => $nomore?$POS:$POS+1,
HAVE_PREV => $POS eq 0?0:1,
PREV => $POS eq 0?0:$POS-1
);
if($usercfg->{'page_size'}>=$POS) {
$tpl->assign(PAGE=>0);
}else {
# the system int() will work for us :-)
my $rv = int ($POS / $usercfg->{'page_size'});
$tpl->assign(PAGE=>$rv);
}
# hash to indicate which part should be ignore, in general
# they are email text/body or html
my %ignore = ();
if(scalar @{$parts->{body}{list}} >1) {
# Here is the most complex part that handle email text/body
# displaying, Hmm it should be redesign one day, ouch :-(
my ($cnt, %th) = (0, ()); # text + html = th
my $last_idflag;
foreach my $p ( @{get_parts_name($parts)} ) {
$last_idflag = $p->{idflag} unless($last_idflag); # init
hdr_get_hash('Content-Type', %{$p->{phead}}) =~ m#(text/.*)#i;
my $subtype = $1 || 'message/unknow';
# Infact we should check every part's charset, but for simplify
# reason, use the first part(text or html)'s charset
my $char = ${get_parts_name($parts)}[0]->{phead}{charset} || $charset;
if( $cnt < 2 && $subtype =~ /text/i &&
$last_idflag =~ /alternative/i &&
$p->{idflag} eq $last_idflag) {
# XXX experimental multi-charset handling, wait for fix
my $print = {id=>0, type=>'text'}; # default;
$th{text} = $cnt if($subtype=~/plain/i);
$th{html} = $cnt if($subtype=~/html/i);
if(exists $th{text} && exists $th{html}) {
if($usercfg->{show_html}) {
$print->{id} = $th{html};
$print->{type} = 'html';
}else {
$print->{id} = $th{text};
$print->{type} = 'text';
}
}elsif(exists $th{text}) {
$print->{id} = $th{text};
$print->{type} = 'text';
}elsif(exists $th{html}) {
$print->{id} = $th{html};
$print->{type} = 'html';
}else {
# not match anything, goto attchment handling
goto HANDLE;
}
# XXX FIXME performance degrade here, get_parts will
# call get_msg_info again, damn it, wait for fix!!
my $body = get_parts($MSGFILE, $print->{id}, 'to_string');
if($print->{type} eq 'text') {
# dirty hack on iso-2022-jp, Thanks ken lau<kenkenqd@msn.com>
# if we encouter iso-2022-jp, html_escape() will fail to convert
# and return null to caller, so we had to use <pre></pre> to
# display body, to be fix :-(
if($char=~ /iso-2022-jp/) {
$body = '<pre>'.$body.'</pre>';
}else {
# XXX FIXME convert to web link
if ($usercfg->{conv_link} && $subtype =~ /plain/i) {
$body = txt2html($body, html_escape=>1, txt2link=>1);
} else {
$body = html_escape($body);
}
}
}elsif ($print->{type} eq 'html') {
$body = htmlsanity($body);
}
$utf8->set_charset($char) if ($char);
# XXX FIXME $tpl->assign(BODY => str2ncr($char, $body));
$tpl->assign(BODY => $utf8->utf8_encode($body));
$ignore{$cnt} = 1;
$cnt++;
next;
}elsif($cnt < 1 && $subtype=~/text/i) {
# only one text/plain or text/html part, no
# alternative, compatible with some sucks MUA
my $print = { id => 0, type => 'text'};
if($subtype=~/html/i) {
$print->{type} = 'html';
}
my $body = get_parts($MSGFILE, $print->{id}, 'to_string');
if($print->{type} eq 'text') {
if($char=~ /iso-2022-jp/) {
$body = '<pre>'.$body.'</pre>';
}else {
# XXX FIXME convert to web link
if ($usercfg->{conv_link} && $subtype =~ /plain/i) {
$body = txt2html($body, html_escape=>1, txt2link=>1);
} else {
$body = html_escape($body);
}
}
} elsif ($print->{type} eq 'html') {
$body = htmlsanity($body);
}
$utf8->set_charset($char) if ($char);
# XXX FIXME =$tpl->assign(BODY => str2ncr($charset, $body));
$tpl->assign(BODY => $utf8->utf8_encode($body));
$ignore{$cnt} = 1;
$cnt++;
next;
}
HANDLE:
{
my $part_char = hdr_get_hash('charset', %{$p->{phead}}) || $char;
my $filename = decode_words_utf8($p->{name});
if (charset_detect($filename) ne 'utf-8') {
$utf8->set_charset($part_char);
$filename = $utf8->utf8_encode($filename);
}
$tpl->assign(ATTACHMENT => 1);
$tpl->assign(
'LOOP_ATTACH',
CNT => $cnt,
# NAME => str2ncr($charset, $p->{name}),
NAME => $filename,
HSIZE => human_size($p->{size})
);
$cnt ++;
} # END of HANDLE
$last_idflag = $p->{idflag}; # XXX
}
} else {
my $type = hdr_get_hash('Content-Type', %$hdr) || 'text/plain';# top type
my $phdr = $parts->{body}{list}->[0]->{phead};
my $subtype = hdr_get_hash('Content-Type', %$phdr) || 'plain';
my $charset = hdr_get_hash('charset', %$hdr) || $sjchar; # get the charset
my $print = {id => 0, type => 'text'};
# if toptype or subtype match html, display it. this mechanism
# make sense to some bad RFC compatible email, most of them are
# spam :-) But we can disply it if user want to review.
if (hdr_get_hash('filename', %$phdr) || hdr_get_hash('name', %$phdr)) {
my $char = ${get_parts_name($parts)}[0]->{phead}{charset} || $charset;
my $p = ${get_parts_name($parts)}[0];
$utf8->set_charset($char) if ($char);
$tpl->assign(ATTACHMENT => 1);
$tpl->assign(
'LOOP_ATTACH',
CNT => 0,
NAME => $utf8->utf8_encode(decode_words($p->{name})),
HSIZE => human_size($p->{size})
);
set_msg_seen($FOLDER, $POS);
return;
}
my $body = get_parts($MSGFILE, 0, 'to_string');
if (hdr_get_hash('charset', %$phdr)) {
$charset = hdr_get_hash('charset', %$phdr);
}
$utf8->set_charset($charset) if $charset;
if (($type=~/html/ or $subtype=~/html/) && $usercfg->{show_html}) {
$print->{type} = 'html';
$body = htmlsanity($body);
} else {
if($charset=~ /iso-2022-jp/) {
$body = '<pre>'.$body.'</pre>';
}else {
# $body = html_escape($body);
# XXX FIXME convert to web link
if ($usercfg->{conv_link} && $subtype =~ /plain/i) {
$body = txt2html($body, txt2link=>1, html_escape=>1);
} else {
$body = html_escape($body) if ($subtype =~ /plain/i);
}
}
}
# XXX FIXME $tpl->assign(BODY => str2ncr($charset, $body));
$tpl->assign(BODY => $utf8->utf8_encode($body));
}
# update file status
set_msg_seen($FOLDER, $POS); # XXX wait for fix
}
sub pre_run { 1 }
sub post_run {
my $template = $_[0]->{query}->cgi('screen') || 'readmsg.html';
# dirty hack, to fallback original working path, ouch :-(
reset_working_path();
$_[0]->{tpl}->process($template);
$_[0]->{tpl}->print;
}
sub DESTORY {
}
1;
syntax highlighted by Code2HTML, v. 0.9.1