# 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::Compose; use strict; use Exporter; use vars qw($VERSION); use vars qw(@ISA @EXPORT $usercfg $tmp_draft); @ISA = qw(Exporter Ext::App); $usercfg = undef; $tmp_draft = undef; use Fcntl qw(:flock); use Ext::App; use Ext::MIME; use Ext::Storage::Maildir; use Ext::CGI; # import $CRLF use Ext::Utils; use Ext::Abook; use Ext::RFC822; # import rfc822_* func use MIME::Base64; # XXX use XS if possible, perl version sucks use MIME::QuotedPrint; use vars qw(%lang_compose $lang_charset); use Ext::Lang; use Ext::Unicode; use Encode::PPUniDetector; # import from Ext::App $VERSION = $Ext::App::VERSION; sub init { my $self = shift; $self->SUPER::init(@_); return unless($self->valid||$self->permit); $self->add_methods(edit_compose => \&edit_compose); # no draft $self->add_methods(edit_drafts => \&edit_drafts); # has draft $self->add_methods(attach_mgr => \&attach_mgr); # alias $self->add_methods(edit_reply => \&edit_reply); # reply $self->add_methods(edit_forward => \&edit_forward); # forward $self->add_methods(edit_import => \&edit_import); # import attaches! $self->{default_mode} = 'edit_compose'; Ext::Storage::Maildir::init($self->get_working_path); Ext::MIME::init(path => $self->get_working_path); # load usercfg from App.pm userconfig(), it will initialize everything. $usercfg = $self->userconfig; $self->_initme; $self; } sub _initme { initlang($_[0]->userconfig->{lang}, __PACKAGE__); $_[0]->{tpl}->assign( lang_charset => $lang_charset ); $_[0]->{tpl}->assign( \%lang_compose ); } # edit_compose - write a new email message, dummy function sub edit_compose { my $self = shift; my $q = $self->{query}; my $tpl = $self->{tpl}; #$tpl->assign(SID => $q->cgi('sid')); $tpl->assign(FOLDER => $q->cgi('folder')); $tpl->assign( TO => html_fmt($q->cgi('to')), CC => html_fmt($q->cgi('cc')), BCC => html_fmt($q->cgi('bcc')), SUBJECT => html_fmt($q->cgi('subject')), CCSENT => $usercfg->{'ccsent'}, BODY => "\n\n". $self->get_signature, RTE_ON => $usercfg->{compose_html} ? 1 : 0, BODY_IS_HTML => $usercfg->{compose_html} ? 1 : 0, ); 1; } # CORE function for message compose/rewrite XXX sub edit_drafts { my $self = shift; my $tpl = $self->{tpl}; my $q = $self->{query}; my $sid = $self->{sid}; my $draft = $q->cgi('draft') ? $q->cgi('draft') : undef; $tpl->assign( SID => $sid, CCSENT => $usercfg->{'ccsent'}, RTE_ON => $usercfg->{'compose_html'} ? 1 : 0, ); if($self->submited) { if($q->cgi('dosave')) { $self->rebuild_attach('update'); $tpl->assign(FOLDER => 'Drafts'); # XXX call attach_mgr() now $self->attach_mgr(); # show attach now # on the first time no new draft create, we should # use $tmp_draft instead of draft $draft = $tmp_draft if ($tmp_draft); $self->show_attach('.Drafts/cur/'.$draft); reset_working_path(); $tpl->{template} = 'saveok.html'; }elsif($q->cgi('dosend')) { $self->rebuild_attach('update'); my $newdraft = $self->attach_mgr(); $tpl->assign(FOLDER => 'Drafts'); if ($newdraft) { $self->sendmail($newdraft) unless ($self->{_errmsg}); } else { $self->error('SendMail Error: Disk full or filesystem error'); return; } reset_working_path(); $tpl->{template} = 'sendok.html'; }elsif($q->cgi('attachmgr')) { # it's a hidden field if($q->cgi('return')) { # back to compose $self->show_draft($draft); return; } if($draft) { $tpl->assign(HAVE_DRAFT => 1, DRAFT => $draft); } $self->rebuild_attach('update'); reset_working_path(); $tpl->{template} = 'attachmgr.html'; }else { $self->error('Sucks, no valid __mode specify'); return; } }else { if($draft) { $self->show_draft($draft); }else { $self->error('No draft specify or no such draft'); } } } sub edit_forward { my $self = shift; my $tpl = $self->{tpl}; my $q = $self->{query}; my $folder = $q->cgi('folder') || 'Inbox'; my $pos = $q->cgi('pos') || 0; my $file = _name2mdir($folder).'/cur/'.pos2file($folder, $pos); my $parts = get_msg_info($file); my $hdr = $parts->{head}{hash}; # UTF-8 handling my $utf8 = Ext::Unicode->new; my $charset = hdr_get_hash('Charset', %$hdr); $utf8->set_charset($charset); my $subj = decode_words_utf8(hdr_get_hash('Subject', %$hdr)); my $from = decode_words_utf8(hdr_get_hash('From', %$hdr)); my $oldto = decode_words_utf8(hdr_get_hash('To', %$hdr)); my $as_attach = $q->cgi('asattach') || 0; # default to 0 my $has_attach = 0; # XXX default to false my $boundary = undef; my $tmpdraft = _gen_maildir_filename(); my $nick = $usercfg->{'nick_name'} || rfc822_addr_parse($ENV{USERNAME})->{name}; my $pftype = $usercfg->{compose_html} ? 'text/html' : 'text/plain'; my $bodyref = _getbody($parts, $file, $pftype); my $part0; if ($pftype =~ /$bodyref->{type}/i) { # yes we found the prefered type if ($bodyref->{id} >=0) { $part0 = ${$parts->{body}{list}}[$bodyref->{id}]; } else { $part0 = {}; } } else { # oops, we fallback to the first part $part0 = ${$parts->{body}{list}}[0]; } my $obody_type = hdr_get_hash('Content-Type', %{$part0->{phead}}) || 'text/plain'; my $sjchar; 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; } } $subj = iconv($subj, $sjchar, 'utf-8') if charset_detect($subj) ne 'utf-8'; $from = iconv($from, $sjchar, 'utf-8') if charset_detect($from) ne 'utf-8'; # XXX FIXME terrible bad perl code to do encoding detect, wait for rewrite :( my $chst; { my $tmp_body = ''; # doing urgly checks :( if ($bodyref->{id}>=0) { $tmp_body = get_parts($file, $bodyref->{id}, 'to_string'); if ($bodyref->{charset}) { $tmp_body = iconv($tmp_body, $bodyref->{charset}, 'utf-8'); } else { $tmp_body = iconv($tmp_body, $charset||$sjchar, 'utf-8'); } } $chst = $self->myiconv_setup($subj.$from.$oldto.$tmp_body.$nick); } $utf8->set_charset($sjchar) unless ($charset); $tpl->assign( CCSENT => $usercfg->{'ccsent'}, RTE_ON => $usercfg->{'compose_html'} ? 1 : 0, BODY_IS_HTML => ($obody_type =~ /html/i?1:0), ); my $type = ($usercfg->{'compose_html'} && $obody_type=~/html/i) ? 'text/html' : 'text/plain'; my $crlf = $type eq 'text/html' ? "\n" : "\n"; open(my $NEW, "> .Drafts/tmp/$tmpdraft") or die "Can't write to $tmpdraft"; print $NEW 'From: "'.rfc822_encode_str($chst,$self->myiconv($nick)).'"'; print $NEW " <".$ENV{USERNAME}.">\n"; print $NEW 'To: '.rfc822_encode_addr($chst, $self->myiconv($q->cgi('to')))."\n"; print $NEW 'Subject: '.rfc822_encode_str($chst,$self->myiconv('Fwd: '.$subj))."\n"; print $NEW 'Date: '.rfc822_date($self->userconfig->{'timezone'})."\n"; print $NEW "Mime-version: 1.0\n"; print $NEW "X-Originating-Ip: [$ENV{REMOTE_ADDR}]\n"; if(my $ver = $VERSION ? "ExtMail $VERSION" : '') { print $NEW "X-Mailer: $ver\n"; } $has_attach = 1 if (has_attach($file) || hdr_get_hash('Content-Disposition', %{$parts->{head}{hash}}) =~ /(attach|name)/i); if($as_attach or defined $parts->{head}{hash}->{boundary} or $has_attach or scalar @{$parts->{body}{list}} >1 ) { # XXX buggy, alternative part # will have more parts! $type = 'multipart/mixed'; } print $NEW 'Content-Type: '.$type.'; '; print $NEW "charset=\"$chst\";\n"; # XXX bug? if($as_attach) { $boundary = _gen_boundary(); # must new a boundary; print $NEW " boundary=\"$boundary\"\n"; # more white space }else { if (hdr_get_hash('boundary', %{$parts->{head}{hash}})) { # on normal forward mode, can use old boundary:) $boundary = hdr_get_hash('boundary', %{$parts->{head}{hash}}); print $NEW " boundary=\"$boundary\"\n"; } elsif($has_attach) { # XXX FIXME new state $boundary = _gen_boundary(); print $NEW " boundary=\"$boundary\"\n"; } else { print $NEW 'Content-Transfer-Encoding: 8bit'."\n"; } } print $NEW "\n"; # terminate header if($as_attach) { # Null body part XXX my $type = $usercfg->{'compose_html'} ? 'text/html' : 'text/plain'; print $NEW "--$boundary\n"; print $NEW "Content-Type: $type; charset=\"$chst\";\n"; print $NEW "Content-Transfer-Encoding: 8bit\n\n"; print $NEW "\n"; # here wait for signature? print $NEW $self->get_signature($type),"\n"; # must has newline open(my $OLD, "< $file") or die "Can't open $file, $!\n"; print $NEW "--$boundary\n"; print $NEW "Content-Type: message/rfc822\n\n"; while(<$OLD>) { print $NEW $_; } close $OLD; print $NEW "\n"; print $NEW "--$boundary--\n"; }else { if($boundary) { # has attach? open(my $OLD, "< $file") or die "Can't open $file, $!\n"; # build body print $NEW "--$boundary\n"; # my $part0 = ${$parts->{body}{list}}[0]; # XXX my $bodytype = ($usercfg->{'compose_html'} && $obody_type=~/html/i) ? 'text/html' : 'text/plain'; # convert encoding from old to new , must 8bit, currently only # support text/plain, so 8bit is suitable print $NEW "Content-Type: $bodytype; charset=\"$chst\";\n"; print $NEW "Content-Transfer-Encoding: 8bit\n\n"; print $NEW "\n"; my $body = ''; my $qtext = ''; print $NEW "$crlf-------- Forwarded Messages --------$crlf"; if ($obody_type =~ /html/i && $usercfg->{compose_html}) { # the body is html and we enable compose_html $from =~ s![<>]+!!g; $oldto =~ s![<>]+!!g; $from = txt2html($from, html_escape=>1); $oldto = txt2html($oldto, html_escape=>1); } $qtext .= "From: $from $crlf"; $qtext .= "To: $oldto $crlf$crlf"; if ($obody_type =~ /html/i && !$usercfg->{compose_html}) { $body = html2txt($body); } if ($bodyref->{id}>=0) { $body = get_parts($file, $bodyref->{id}, 'to_string'); } if ($bodyref->{charset}) { # body has specific charset, we use it $utf8->set_charset($bodyref->{charset}); } $body = $utf8->utf8_encode($body); $body = "$qtext\n$body"; $body = $self->html_quote($body) if ($obody_type =~ /html/i && $usercfg->{compose_html}); $body .= "\n".$self->get_signature($obody_type); # it's time to do myiconv :) $body = $self->myiconv($body); print $NEW $body."\n"; # build attach part my $cnt = 0; foreach my $p (@{$parts->{body}{list}}) { # XXX will ignore non-attachment part if((!$p->{phead}->{filename} && !$p->{phead}->{name} && !$p->{phead}->{'Content-Disposition'} && $p->{phead}->{'Content-Disposition'} !~/attach/i) && $cnt<2) { $cnt ++; # increase next; } my ($a,$b) = ($p->{pos_start}, $p->{pos_end}); seek($OLD, $a, 0); # seek to the begin of part print $NEW "--$boundary\n"; while(<$OLD>) { print $NEW $_; last if(tell $OLD>= $b); } $cnt++; } print $NEW "\n"; print $NEW "--$boundary--\n"; }else { # no attach, normal body, we just want the normal body, part0 # my $part0 = ${$parts->{body}{list}}[0]; # XXX open(my $OLD, "< $file") or die "Can't open $file, $!\n"; my $body = ''; my $qtext = ''; if ($obody_type =~ /html/i && $usercfg->{compose_html}) { # the body is html and we enable compose_html $from =~ s![<>]+!!g; $oldto =~ s![<>]+!!g; $from = txt2html($from, html_escape=>1); $oldto = txt2html($oldto, html_escape=>1); } print $NEW "$crlf-------- Forwarded Messages --------$crlf"; $qtext .= "From: $from $crlf"; $qtext .= "To: $oldto $crlf$crlf"; if ($bodyref->{id}>=0) { $body = get_parts($file, $bodyref->{id}, 'to_string'); } if ($obody_type =~ /html/i && !$usercfg->{compose_html}) { $body = html2txt($body); } if ($bodyref->{charset}) { # body has specific charset, we use it $utf8->set_charset($bodyref->{charset}); } $body = $utf8->utf8_encode($body); $body = "$qtext\n$body"; $body = $self->html_quote($body) if ($obody_type =~ /html/i && $usercfg->{compose_html}); $body .= "\n".$self->get_signature($obody_type); # it's time to do myiconv $body = $self->myiconv($body); print $NEW $body."\n"; } } close $NEW; $self->myiconv_close; my $newdraft = _gen_maildir_filename(".Drafts/tmp/$tmpdraft", '1'); rename(".Drafts/tmp/$tmpdraft", ".Drafts/tmp/$newdraft"); my $size = (stat '.Drafts/tmp/'.$newdraft)[7]; my $distname = ""; my ($is_oversize, $is_overquota); my $errbuf = ''; if ($size > 0) { if (my $sz = $self->is_oversize($size)) { $is_oversize = 1; $errbuf = sprintf($lang_compose{oversize}, human_size($sz)); } elsif (is_overquota($size, 1)>1) { $is_overquota = 1; $errbuf = $lang_compose{overquota}; } } if ($is_overquota || $is_oversize) { $self->{_errmsg} = $errbuf; $tpl->assign(OPMSG => $errbuf, ERRMSG => $errbuf); unlink untaint(".Drafts/tmp/$newdraft"); return 0; # return on failure }else { $distname=$newdraft.",S=$size:2,S"; # XXX has performance problem FIXME if (has_attach(".Drafts/tmp/$newdraft")) { $distname .= 'A'; # if multipart, set flag to Attach } rename('.Drafts/tmp/'.$newdraft, '.Drafts/cur/'.$distname); my @a = parse_curcnt('.Drafts'); $a[0] += $size; # if nagetive, perl will handle it:) $a[1] ++; open(FD, "> .Drafts/extmail-curcnt") or die "Can't write to extmail-curcnt, $!\n"; flock(FD, LOCK_EX); print FD "$a[0] $a[1] $a[2]\n"; flock(FD, LOCK_UN); close FD; my %quota = (); $quota{a} = "$size 1"; update_quota_s(\%quota); } $self->show_draft($distname); } sub edit_reply { my $self = shift; my $q = $self->{query}; my $tpl = $self->{tpl}; my $folder = $q->cgi('folder') || 'Inbox'; my $pos = $q->cgi('pos') || 0; my $file = _name2mdir($folder).'/cur/'.pos2file($folder, $pos); my $parts = get_msg_info($file); my $hdr = $parts->{head}{hash}; my $utf8 = Ext::Unicode->new; my $charset = hdr_get_hash('Charset', %$hdr); my $date = hdr_get_hash('Date', %$hdr); my $from = decode_words_utf8(hdr_get_hash('From', %$hdr)); my $replyto = decode_words_utf8(hdr_get_hash('Reply-To', %$hdr)); my $return = decode_words_utf8(hdr_get_hash('Return-Path', %$hdr)); my ($to, $cc, $bcc) = ( decode_words_utf8(hdr_get_hash('To', %$hdr)), decode_words_utf8(hdr_get_hash('Cc', %$hdr)), decode_words_utf8(hdr_get_hash('Bcc', %$hdr)), ); my $subj = decode_words_utf8(hdr_get_hash('Subject', %$hdr)); # XXX FIXME charset auto detect part my $sjchar; 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'; $cc = iconv($cc, $sjchar, 'utf-8') if charset_detect($cc) ne 'utf-8'; $bcc = iconv($bcc, $sjchar, 'utf-8') if charset_detect($bcc) ne 'utf-8'; $replyto = iconv($replyto, $sjchar, 'utf-8') if charset_detect($replyto) ne 'utf-8'; $return = iconv($replyto, $sjchar, 'utf-8') if charset_detect($return) ne 'utf-8'; $subj = iconv($subj, $sjchar, 'utf-8') if charset_detect($subj) ne 'utf-8'; # XXX FIXME set charset to subject charset if it's unavailable $charset = $sjchar unless $charset; my $pftype = $usercfg->{compose_html} ? 'text/html' : 'text/plain'; my $bodyref = _getbody($parts, $file, $pftype); my $part0; if ($pftype =~ /$bodyref->{type}/i) { # yes we found the prefered type $part0 = ${$parts->{body}{list}}[$bodyref->{id}]; # XXX } else { # oops, we fallback to the first part $part0 = ${$parts->{body}{list}}[0]; } $utf8->set_charset($bodyref->{charset} || $charset); my $ctype = hdr_get_hash('Content-Type', %{$part0->{phead}}) || 'text/plain'; my $text = $utf8->utf8_encode($bodyref->{body});# the body text! my $body_is_html = 0; $date =~ s/\s*[-+]\d+\s*.*//; my $quotetpl = $lang_compose{'quotetpl'} || '%s at %s wrote:'; if ($ctype =~ /html/i) { $body_is_html = 1; # format body text to reply style XXX to be fix if (!$usercfg->{'compose_html'}) { $text = html2txt($text); $text =~ s#^(.{1})#>$1#; $text =~ s#\n#\n>#g; $text = sprintf("\n$quotetpl\n\%s", $from, $date, $text); } else { #$text =~ s!\r*\n!!gsi; #$text =~ s!<\s*/?\s*br\s*/?\s*>!\n!gsi; #$text =~ s!!\n!gi; #$text =~ s!
!\r\n\r\n!gi; #$text =~ s!!\r\n!gi; #$text =~ s!^(.{1})!>$1!; #$text =~ s!\n!\n>!gsi; # the following is waiting for fix, can't input under IE # FIXME XXX the following code need to do subject auto focus() # when everything loaded, but i don't know why, need help! $text = $self->html_quote($text); $text = sprintf("\n$quotetpl\n\%s", $from, $date, $text); } } else { $text =~ s#^(.{1})#>$1#; $text =~ s#\n#\n>#g; $text = sprintf("\n$quotetpl\n\%s", $from, $date, $text); } $tpl->assign(BODY_IS_HTML => $body_is_html); # strip out RFC822 email addr, to be fix XXX FIXME $from=~m{<*([^<> ]+@[^<> ]+)>*}; $from=$1; $cc = $to . ($cc ? ", $cc" : '') if ($to); if($q->cgi('replyall')) { $tpl->assign( CC => html_fmt($cc), BCC => html_fmt($bcc) ); } $tpl->assign( ORG_CHARSET => $charset, CCSENT => $usercfg->{'ccsent'}, TO => html_fmt($replyto || $from || $return), SUBJECT => html_fmt('Re: '.$subj), BODY=>$text."\n\n". $self->get_signature($ctype), RTE_ON => $usercfg->{'compose_html'}, ); } sub edit_import { my $self = shift; my $q = $self->{query}; my $tpl = $self->{tpl}; my $itype = $q->cgi('type'); # import type my %files; my $nick = $usercfg->{'nick_name'} || rfc822_addr_parse($ENV{USERNAME})->{name}; if ($itype eq 'netdisk') { my $base = url2str($q->cgi('base')); my $a = $q->cgi_full_names; my @arr = grep { /^FILE-/ } @$a; if (@arr) { for (@arr) { my $file = url2str($q->cgi($_)); my $path = "./fileman/$base/$file"; my $size = (stat $path)[7]; $files{$file} = $path; # store it } } } return unless keys %files; # abort unless there is files my $type = $usercfg->{compose_html} ? 'text/html' : 'text/plain'; # add the signature part my $sig = $self->get_signature($type); my $chst = $self->myiconv_setup($sig.$nick); $nick = $self->myiconv($nick); # XXX initialize $tpl->assign( CCSENT => $usercfg->{'ccsent'}, BODY => "\n\n". $sig, RTE_ON => $self->userconfig->{compose_html} ? 1 : 0, BODY_IS_HTML => $usercfg->{compose_html} ? 1: 0, ); # XXX the following code derive from rebuild_attach() my $tmpdraft = _gen_maildir_filename(); my $boundary = _gen_boundary(); open(my $FD, "> .Drafts/tmp/$tmpdraft") or die "Can't write to $tmpdraft, $!\n"; select((select(FD), $| = 1)[0]); print $FD 'From: "'.rfc822_encode_str($chst, $nick).'"'; print $FD " <".$ENV{USERNAME}.">\n"; print $FD 'To: '.rfc822_encode_addr($chst, $self->myiconv($q->cgi('to')))."\n"; print $FD 'Subject: '.rfc822_encode_str($chst, $self->myiconv($q->cgi('subject')))."\n"; print $FD 'Date: '.rfc822_date($self->userconfig->{'timezone'})."\n"; print $FD "Mime-version: 1.0\n"; print $FD "X-Originating-Ip: [$ENV{REMOTE_ADDR}]\n"; if(my $ver = $VERSION ? "ExtMail $VERSION" : '') { print $FD "X-Mailer: $ver\n"; } print $FD 'Content-Type: multipart/mixed; '; print $FD "boundary=$boundary; charset=\"$chst\"\n"; print $FD 'Content-Transfer-Encoding: 8bit'."\n\n"; print $FD "This is a MIME-formatted message. If you see this text it means that your\n"; print $FD "mail software cannot handle MIME-formatted messages.\n\n"; print $FD "--$boundary\n"; print $FD "Content-Type: $type; charset=\"$chst\";\n"; print $FD "Content-Transfer-Encoding: 8bit\n\n"; $sig = $self->myiconv($sig); print $FD "$sig\n"; # must prepend newline or I/O will fail print $FD "\n"; # build the attachment parts foreach my $f (keys %files) { $chst = $self->myiconv_setup($f); my $lf = $self->myiconv($f); print $FD "--$boundary\n"; print $FD "Content-Disposition: attachment; filename=\"$lf\"\n"; print $FD "Content-Type: application/octet-stream; charset=\"$chst\"; name=\"$lf\"\n"; print $FD "Content-Transfer-Encoding: base64\n\n"; open (my $ATT, "< $files{$f}") or die "open $files{$f} fail, $!\n"; while(read($ATT, my $buf, 60*57)) { print $FD encode_base64($buf); } close $ATT; print $FD "\n"; # need? } print $FD "--$boundary--\n"; close $FD; $self->myiconv_close; # get the standard maildir name according to the official standard # see http://cr.yp.to/maildir.html my $newdraft = _gen_maildir_filename(".Drafts/tmp/$tmpdraft", '1'); rename(".Drafts/tmp/$tmpdraft", ".Drafts/tmp/$newdraft"); my $newsize = (stat '.Drafts/tmp/'.$newdraft)[7]; my $distname = $newdraft.",S=$newsize:2,SA"; # Attach flag my $oldsize = 0; # overquota or oversize (message) checks my ($is_oversize, $is_overquota); my $errbuf = ''; if ($newsize > 0) { if (my $sz = $self->is_oversize($newsize)) { $is_oversize = 1; $errbuf = sprintf($lang_compose{oversize}, human_size($sz)); } elsif (is_overquota($newsize, 1)>1) { $is_overquota = 1; $errbuf = $lang_compose{overquota}; } } if ($is_overquota || $is_oversize) { $self->{_errmsg} = $errbuf; $tpl->assign(OPMSG => $errbuf, ERRMSG => $errbuf); unlink untaint(".Drafts/tmp/$newdraft"); return 0; # return on failure } else { rename('.Drafts/tmp/'.$newdraft, '.Drafts/cur/'.$distname); my @a = parse_curcnt('.Drafts'); # calculate the delta size $a[0] += $newsize; $a[1] ++; # new add situation open(FD, "> .Drafts/extmail-curcnt") or die "Can't write to extmail-curcnt, $!\n"; flock(FD, LOCK_EX); print FD "$a[0] $a[1] $a[2]\n"; flock(FD, LOCK_UN); close FD; # Update system maildirsize file now update_quota($newsize, 1); } my $newparts = get_msg_info('.Drafts/cur/'.$distname); $self->show_attach('.Drafts/cur/'.$distname); $tpl->assign( 'HAVE_DRAFT' => 1, DRAFT => $distname, OPMSG => $self->{opmsg}, ); 1; } # attach_mgr() - manage attachment sub attach_mgr { my $self = shift; my $q = $self->{query}; my $tpl = $self->{tpl}; my $draft = $q->cgi('draft') ? $q->cgi('draft') : ($tmp_draft ? $tmp_draft : undef); $tpl->assign( CCSENT => $usercfg->{'ccsent'}, RTE_ON => $usercfg->{'compose_html'}, ); if($q->cgi('doattach')) { # XXX yes, upload now - by nick - FIXME, why append then remove? $self->rebuild_attach('append'); return $self->rebuild_attach('remove'); }elsif($q->cgi('delete')) { # XXX delete some attch return $self->rebuild_attach('remove'); }elsif($q->cgi('return')) { # XXX return to compose page $self->edit_drafts; return; }elsif ($q->cgi('dosend') || $q->cgi('dosave')) { return; }else { $self->error('No valid action taken'); } reset_working_path(); $tpl->{template} = 'attachmgr.html'; } sub show_attach { my $self = shift; $_[0]=~ m#([^\/]+)$#; # only name part, must match! my $filename = maildir_find('.Drafts', $1); # try to find it my $draft = '.Drafts/cur/'.$filename; my $utf8 = Ext::Unicode->new; my $tpl = $self->{tpl}; return unless($draft); my $parts = get_msg_info($draft); if(scalar @{$parts->{body}{list}} >1) { $tpl->assign(LIST_ATTACH => 1); my $cnt = 0; my $files = get_parts_name($parts); my $part; foreach (1... scalar @$files-1) { # omit body my $part = $files->[$_]; my $hdr = $part->{phead}; my $charset = hdr_get_hash('charset', %$hdr); my $filename = decode_words_utf8($part->{name}); $utf8->set_charset($charset); $tpl->assign( 'LOOP_ATTACH', CNT => $cnt, NAME => $charset ? $utf8->utf8_encode(decode_words($part->{name})) : $filename, HSIZE => human_size($files->[$_]->{size}) ); $cnt ++; } } } sub show_draft { my $self = shift; my $draft = $_[0]; my $q = $self->{query}; my $tpl = $self->{tpl}; $draft = maildir_find('.Drafts', $draft); # try to find it my($from,$to,$cc,$bcc,$subject,$body); my $parts = get_msg_info('.Drafts/cur/'.$draft); my $body_is_html = 0; my $utf8 = Ext::Unicode->new; $tpl->assign( HAVE_DRAFT => 1, DRAFT => $draft ); my $part0 = ${$parts->{body}{list}}[0]; if($self->submited && !$q->cgi('return')) { # XXX submited action, may be has attach or do/send etc. $from = $q->cgi('from'); $to = $q->cgi('to'); $cc = $q->cgi('cc'); $bcc = $q->cgi('bcc'); $subject = $q->cgi('subject'); $body = $q->cgi('body'); }else { # XXX call from Drafts folder messages list # my $parts = get_msg_info('.Drafts/cur/'.$draft); XXX meaningless my $hdr = $parts->{head}{hash}; my $pftype = $usercfg->{compose_html} ? 'text/html' : 'text/plain'; my $bodyref = _getbody($parts, ".Drafts/cur/$draft", $pftype); my $charset = hdr_get_hash('charset', %$hdr); $utf8->set_charset($bodyref->{charset} || $charset); $from = $utf8->utf8_encode(decode_words(hdr_get_hash('From', %$hdr))); $to = $utf8->utf8_encode(decode_words(hdr_get_hash('To', %$hdr))); $cc = $utf8->utf8_encode(decode_words(hdr_get_hash('Cc', %$hdr))); $bcc = $utf8->utf8_encode(decode_words(hdr_get_hash('Bcc', %$hdr))); $subject = $utf8->utf8_encode(decode_words(hdr_get_hash('Subject', %$hdr))); if ($pftype =~ /$bodyref->{type}/i) { # yes we found the prefered type $part0 = ${$parts->{body}{list}}[$bodyref->{id}]; # XXX } # if not match, use the default setting above $body = $utf8->utf8_encode($bodyref->{body});# the body text! chomp $body; # XXX remove last \n added in rebuild } if ($part0->{phead}->{'Content-Type'} =~ /html/i or $q->cgi('html')) { $body_is_html = 1; # convert body to text if we are not in RTF mode $body = html2txt($body) if !$usercfg->{compose_html}; } $tpl->assign( BODY_IS_HTML => $body_is_html ); $tpl->assign( FROM => $from, TO => html_fmt($to), CC => html_fmt($cc), BCC => html_fmt($bcc), SUBJECT => html_fmt($subject), BODY => $body, ); $self->show_attach('.Drafts/cur/'.$draft); } # rebuild_attach() - rebuild attachment parts, not include header sub rebuild_attach { my $self = shift; my $mode = $_[0]; my $q = $self->{query}; my $tpl = $self->{tpl}; my $draft = $q->cgi('draft') ? $q->cgi('draft') : ($tmp_draft ? $tmp_draft : undef); my $type = $q->cgi('html') ? 'text/html' : 'text/plain'; $draft = maildir_find('.Drafts', $draft); # try to find it $self->{opmsg} = "Default information"; # operation message my($newdraft) = ""; # function field varible my($delta_size) = 0; # data size change(add/remove) #$tpl->assign(SID => $q->cgi('sid')); # XXX must exists if($draft) {# XXX already save or build draft $tpl->assign( 'HAVE_DRAFT' => 1, DRAFT => $draft ); $newdraft = untaint(_gen_maildir_filename($draft)); # sucks, pass FD via object will cause more cleanup work, # may be pass newdraft name is better? tobe fix # # remove $newdraft from tmp/ must be done whether we hit # the bottom of rebuild_attach, so it's urgly implement:( open($self->{newfd}, "> .Drafts/tmp/$newdraft") or die "Can't write to $newdraft, $!\n"; if($mode eq 'append') { unless($q->cgi('UPLOAD_FILES')) { close $self->{newfd}; delete $self->{newfd}; unlink untaint(".Drafts/tmp/$newdraft"); return $draft; # $self->show_attach('.Drafts/cur/'.$draft); # return 1; } $self->rebuild_append_attach($draft); }elsif($mode eq 'remove') { my @a = grep { s/^REMOVE-// } @{$q->cgi_full_names}; unless(@a) { close $self->{newfd}; delete $self->{newfd}; unlink untaint(".Drafts/tmp/$newdraft"); # $self->show_attach('.Drafts/cur/'.$draft); return $draft; } $self->rebuild_remove_attach($draft); }elsif($mode eq 'update') { $self->rebuild_update_message($draft); }else { close $self->{newfd}; delete $self->{newfd}; unlink untaint(".Drafts/tmp/$newdraft"); $self->error('Unknow method: '. $mode); return 0; } my $TMP = $self->{newfd}; if($self->{boundary}) { print $TMP '--'.$self->{boundary}."--\n"; } close $TMP; }else {# XXX the first time, no $draft $type = $q->cgi('html') ? 'text/html' : 'text/plain'; # The following code is deprecated, not need to gen newdraft # here, from compose to attachmgr, newdraft will be create # automatically # #if($q->cgi('doattach') or $q->cgi('dosave') or !$draft) { # upload attach # $newdraft = _gen_maildir_filename(); #} #unless($draft) { # new create if no draft #} my $tmpdraft = _gen_maildir_filename(); my $chst = $self->myiconv_setup;# $self->userconfig->{'charset'} || 'UTF-8'; my $nick = $self->myiconv($usercfg->{'nick_name'} || rfc822_addr_parse($ENV{USERNAME})->{name}); open(FD, "> .Drafts/tmp/$tmpdraft") or die "Can't write to $tmpdraft, $!\n"; select((select(FD), $| = 1)[0]); print FD 'From: "'.rfc822_encode_str($chst, $nick).'"'; print FD " <".$ENV{USERNAME}.">\n"; print FD 'To: '.rfc822_encode_addr($chst, $self->myiconv($q->cgi('to')))."\n"; if($q->cgi('cc')) { print FD 'Cc: '.rfc822_encode_addr($chst, $self->myiconv($q->cgi('cc')))."\n"; } if($q->cgi('bcc')) { print FD 'Bcc: '.rfc822_encode_addr($chst, $self->myiconv($q->cgi('bcc')))."\n"; } print FD 'Subject: '.rfc822_encode_str($chst, $self->myiconv($q->cgi('subject')))."\n"; print FD 'Date: '.rfc822_date($self->userconfig->{'timezone'})."\n"; print FD "Mime-version: 1.0\n"; print FD "X-Originating-Ip: [$ENV{REMOTE_ADDR}]\n"; if(my $ver = $VERSION ? "ExtMail $VERSION" : '') { print FD "X-Mailer: $ver\n"; } print FD 'Content-Type: '.$type.'; '; print FD "charset=\"$chst\"\n"; print FD 'Content-Transfer-Encoding: 8bit'."\n"; print FD "\n"; print FD $self->myiconv(($q->cgi('html') ? $q->cgi('body') : $q->cgi('plaintext')||$q->cgi('body'))),"\n\n"; close FD; # get the standard maildir name according to the official standard # see http://cr.yp.to/maildir.html $newdraft = _gen_maildir_filename(".Drafts/tmp/$tmpdraft", '1'); rename(".Drafts/tmp/$tmpdraft", ".Drafts/tmp/$newdraft"); $self->{opmsg} = $lang_compose{msgsaved} || 'Message Saved'; # XXX add by nick This var is for load attach_mgr() in draft_edit() # Now we can add attach and save or send mail in one cgi request! $tmp_draft = $newdraft.",S=".(stat '.Drafts/tmp/'.$newdraft)[7].":2,S"; } # is_overquota part here should be redesign, the schema not clear! my $newsize = (stat '.Drafts/tmp/'.$newdraft)[7]; my $distname = $newdraft.",S=$newsize:2,S"; my $oldsize = ( $draft ? (stat '.Drafts/cur/'.$draft)[7] : 0 ); $delta_size = ($newsize - $oldsize); # XXX performance problem FIXME if (has_attach(".Drafts/tmp/$newdraft")) { $distname .= 'A'; # set Attach flag } # new mechanism, check delta whether it's >0 or not, if gt 0, we will # check is_overquota, if lt 0, doest not need to check my ($is_oversize, $is_overquota); my $errbuf = ''; if ($delta_size > 0) { if (my $sz = $self->is_oversize($newsize)) { $is_oversize = 1; $errbuf = sprintf($lang_compose{oversize}, human_size($sz)); } elsif (is_overquota($delta_size, $oldsize ? 0 : 1)>1) { $is_overquota = 1; $errbuf = $lang_compose{overquota}; } } if ($is_overquota || $is_oversize) { $self->{_errmsg} = $errbuf; $tpl->assign(OPMSG => $errbuf, ERRMSG => $errbuf); unlink untaint(".Drafts/tmp/$newdraft"); return 0; # return on failure } else { # unlink '.Drafts/cur/'.$distname if (-r '.Drafts/cur/'.$distname); rename(untaint(".Drafts/tmp/$newdraft"), untaint(".Drafts/cur/$distname")); my @a = parse_curcnt('.Drafts'); # calculate the delta size $a[0] += $delta_size; # if nagetive, perl will handle it:) $a[1] ++ unless($draft); # add if new create open(FD, "> .Drafts/extmail-curcnt") or die "Can't write to extmail-curcnt, $!\n"; flock(FD, LOCK_EX); print FD "$a[0] $a[1] $a[2]\n"; flock(FD, LOCK_UN); close FD; # Update system maildirsize file now my %quota = (); $quota{a} = "-$oldsize -1" if($draft); $quota{b} = "$newsize 1"; update_quota_s(\%quota); } my $newparts = get_msg_info('.Drafts/cur/'.$distname); # print Dumper($newparts); # final clean up.., then curcnt timestamp newer than cache, so # cache will be rebuild after return to the Drafts folder # messages list :-( a bad trick? unlink untaint(".Drafts/cur/$draft") if($draft ne $distname); # XXX FIXME $self->show_attach('.Drafts/cur/'.$distname); $tpl->assign( 'HAVE_DRAFT' => 1, DRAFT => $distname, OPMSG => $self->{opmsg} ); delete $self->{opmsg}; # cleanup after usage delete $self->{boundary}; delete $self->{newfd}; $distname; } sub rebuild_append_attach { my $self = shift; my $q = $self->{query}; my $draft = $_[0]; # already maildir_find() ? my $type = 'multipart/mixed'; my $chst = $self->userconfig->{'charset'} || 'UTF-8'; unless($q->cgi('UPLOAD_FILES')) { $self->{opmsg} = $lang_compose{noattupload} || 'No attch upload!'; return; # return if no attach, ouch } my $parts = get_msg_info('.Drafts/cur/'.$draft); open(FD, "< .Drafts/cur/$draft") or die "Can't open $draft\n"; my $old = $/; local $/ = "\n\n"; my $header ='; my $quote_end = $lang_compose{'div_quote_end'} || '