) {
if ($lynx && $_ =~ /^\s*References\s*$/) {
# Start of Lynx references output
$gotrefs++;
}
elsif ($lynx && $gotrefs &&
$_ =~ /^\s*(\d+)\.\s+(http|https|ftp|mailto)/) {
# Skip this URL reference line
}
else {
$text .= $_;
}
}
close(OUT);
unlink($temp);
return $text;
}
else {
# Can we use Perl HTML formatter
# for the better conversion
eval "use HTML::TreeBuilder";
if (!$@) {
eval "use HTML::FormatText";
if (!$@) {
my $html_parser = HTML::TreeBuilder->new();
eval "use utf8";
utf8::decode($html)
if (!$@);
$html_parser->parse($html);
my $formatter = HTML::FormatText->new(leftmargin => 1, rightmargin => 79);
return $formatter->format($html_parser);
}
}
# Do conversion manually :(
$html =~ s/(<|<)(style|script).*?(>|>).*?(<|<)\/(style|script)(>|>)//gs;
$html =~ s/\s+/ /g;
$html =~ s//\n\n/gi;
$html =~ s/
/\n/gi;
$html =~ s/<[^>]+>//g;
my $useutf8 = 0;
eval "use utf8";
$useutf8 = 1 if (!$@);
utf8::decode($html)
if ($useutf8);
$html = &entities_to_ascii($html);
utf8::encode($html)
if ($useutf8);
return $html;
}
}
# folder_select(&folders, selected-folder, name, [extra-options], [by-id],
# [auto-submit])
# Returns HTML for selecting a folder
sub folder_select
{
local ($folders, $folder, $name, $extra, $byid, $auto) = @_;
local @opts;
push(@opts, @$extra) if ($extra);
foreach my $f (@$folders) {
next if ($f->{'hide'} && $f ne $_[1]);
local $umsg;
if (&should_show_unread($f)) {
local ($c, $u) = &mailbox_folder_unread($f);
if ($u) {
$umsg = " ($u)";
}
}
push(@opts, [ $byid ? &folder_name($f) : $f->{'index'},
&html_escape($f->{'name'}).$umsg ]);
}
return &ui_select($name, $byid ? &folder_name($folder) : $folder->{'index'},
\@opts, 1, 0, 0, 0, $auto ? "onChange='form.submit()'" : "");
}
# folder_size(&folder, ...)
# Sets the 'size' field of one or more folders, and returns the total
sub folder_size
{
local ($f, $total);
foreach $f (@_) {
if ($f->{'type'} == 0 || $f->{'type'} == 7) {
# Single mail file - size is easy
local @st = stat($f->{'file'});
$f->{'size'} = $st[7];
}
elsif ($f->{'type'} == 1) {
# Maildir folder size is that of all files in it, except
# sub-folders.
$f->{'size'} = 0;
foreach my $sd ("cur", "new", "tmp") {
$f->{'size'} += &recursive_disk_usage(
$f->{'file'}."/".$sd, '^\\.');
}
}
elsif ($f->{'type'} == 3) {
# MH folder size is that of all mail files
local $mf;
$f->{'size'} = 0;
opendir(MHDIR, $f->{'file'});
while($mf = readdir(MHDIR)) {
next if ($mf eq "." || $mf eq "..");
local @st = stat("$f->{'file'}/$mf");
$f->{'size'} += $st[7];
}
closedir(MHDIR);
}
elsif ($f->{'type'} == 4) {
# Get size of IMAP folder
local ($ok, $h, $count, $uidnext) = &imap_login($f);
if ($ok) {
$f->{'size'} = 0;
$f->{'lastchange'} = $uidnext;
local @rv = &imap_command($h,
"FETCH 1:$count (RFC822.SIZE)");
foreach my $r (@{$rv[1]}) {
if ($r =~ /RFC822.SIZE\s+(\d+)/) {
$f->{'size'} += $1;
}
}
}
}
elsif ($f->{'type'} == 5) {
# Size of a combined folder is the size of all sub-folders
return &folder_size(@{$f->{'subfolders'}});
}
else {
# Cannot get size of a POP3 folder
$f->{'size'} = undef;
}
$total += $f->{'size'};
}
return $total;
}
# parse_boolean(string)
# Separates a string into a series of and/or separated values. Returns a
# mode number (0=or, 1=and, 2=both) and a list of words
sub parse_boolean
{
local @rv;
local $str = $_[0];
local $mode = -1;
local $lastandor = 0;
while($str =~ /^\s*"([^"]*)"(.*)$/ ||
$str =~ /^\s*"([^"]*)"(.*)$/ ||
$str =~ /^\s*(\S+)(.*)$/) {
local $word = $1;
$str = $2;
if (lc($word) eq "and") {
if ($mode < 0) { $mode = 1; }
elsif ($mode != 1) { $mode = 2; }
$lastandor = 1;
}
elsif (lc($word) eq "or") {
if ($mode < 0) { $mode = 0; }
elsif ($mode != 0) { $mode = 2; }
$lastandor = 1;
}
else {
if (!$lastandor && @rv) {
$rv[$#rv] .= " ".$word;
}
else {
push(@rv, $word);
}
$lastandor = 0;
}
}
$mode = 0 if ($mode < 0);
return ($mode, \@rv);
}
# recursive_files(dir, treat-dirs-as-folders)
sub recursive_files
{
local ($f, @rv);
opendir(DIR, $_[0]);
local @files = readdir(DIR);
closedir(DIR);
foreach $f (@files) {
next if ($f eq "." || $f eq ".." || $f =~ /\.lock$/i ||
$f eq "cur" || $f eq "tmp" || $f eq "new" ||
$f =~ /^\.imap/i || $f eq ".customflags" ||
$f eq "dovecot-uidlist" || $f =~ /^courierimap/ ||
$f eq "maildirfolder" || $f eq "maildirsize" ||
$f eq "maildircache" || $f eq ".subscriptions" ||
$f eq ".usermin-maildircache" || $f =~ /^dovecot\.index/ ||
$f =~ /^dovecot-uidvalidity/ || $f eq "subscriptions" ||
$f =~ /\.webmintmp\.\d+$/ || $f eq "dovecot-keywords" ||
$f =~ /^dovecot\.mailbox/);
local $p = "$_[0]/$f";
local $added = 0;
if ($_[1] || !-d $p || -d "$p/cur") {
push(@rv, $p);
$added = 1;
}
# If this directory wasn't a folder (or it it in Maildir format),
# search it too.
if (-d "$p/cur" || !$added) {
push(@rv, &recursive_files($p));
}
}
return @rv;
}
# editable_mail(&mail)
# Returns 0 if some mail message should not be editable (ie. internal folder)
sub editable_mail
{
return $_[0]->{'header'}->{'subject'} !~ /DON'T DELETE THIS MESSAGE.*FOLDER INTERNAL DATA/;
}
# fix_cids(html, &attachments, url-prefix)
# Replaces HTML like img src=cid:XXX with img src=detach.cgi?whatever
sub fix_cids
{
local $rv = $_[0];
# Fix images referring to CIDs
$rv =~ s/(src="|href="|background=")cid:([^"]+)(")/$1.&fix_cid($2,$_[1],$_[2]).$3/gei;
$rv =~ s/(src='|href='|background=')cid:([^']+)(')/$1.&fix_cid($2,$_[1],$_[2]).$3/gei;
$rv =~ s/(src=|href=|background=)cid:([^\s>]+)()/$1.&fix_cid($2,$_[1],$_[2]).$3/gei;
# Fix images whose URL is actually in an attachment
$rv =~ s/(src="|href="|background=")([^"]+)(")/$1.&fix_contentlocation($2,$_[1],$_[2]).$3/gei;
$rv =~ s/(src='|href='|background=')([^']+)(')/$1.&fix_contentlocation($2,$_[1],$_[2]).$3/gei;
$rv =~ s/(src=|href=|background=)([^\s>]+)()/$1.&fix_contentlocation($2,$_[1],$_[2]).$3/gei;
return $rv;
}
# fix_cid(cid, &attachments, url-prefix)
sub fix_cid
{
local ($cont) = grep { $_->{'header'}->{'content-id'} eq $_[0] ||
$_->{'header'}->{'content-id'} eq "<$_[0]>" } @{$_[1]};
if ($cont) {
return "$_[2]&attach=$cont->{'idx'}";
}
else {
return "cid:$_[0]";
}
}
# fix_contentlocation(url, &attachments, url-prefix)
sub fix_contentlocation
{
local ($cont) = grep { $_->{'header'}->{'content-location'} eq $_[0] ||
$_->{'header'}->{'content-location'} eq "<$_[0]>" } @{$_[1]};
if ($cont) {
return "$_[2]&attach=$cont->{'idx'}";
}
else {
return $_[0];
}
}
# create_cids(html, &results-map)
# Replaces all image references in the body like
with
# cid: tags, stores in the results map pointers from the index to the CID.
sub create_cids
{
local ($html, $cidmap) = @_;
$html =~ s/(src="|href="|background=")detach.cgi\?([^"]+)(")/$1.&create_cid($2,$cidmap).$3/gei;
$html =~ s/(src='|href='|background=')detach.cgi\?([^']+)(')/$1.&create_cid($2,$cidmap).$3/gei;
$html =~ s/(src=|href=|background=)detach.cgi\?([^\s>]+)()/$1.&create_cid($2,$cidmap).$3/gei;
return $html;
}
sub create_cid
{
local ($args, $cidmap) = @_;
if ($args =~ /attach=(\d+)/) {
$create_cid_count++;
$cidmap->{$1} = time().$$.$create_cid_count;
return "cid:".$cidmap->{$1};
}
else {
# No attachment ID!
return "";
}
}
# disable_html_images(html, disable?, &urls)
# Turn off some or all images in HTML email. Mode 0=Do nothing, 1=Offsite only,
# 2=All images. Returns the URL of images found in &urls
sub disable_html_images
{
my ($html, $dis, $urls) = @_;
my $newhtml;
my $masked_img;
while($html =~ /^([\000-\377]*?)(<\s*img[^>]*src=('[^']*'|"[^"]*"|\S+)[^>]*>)([\000-\377]*)/i &&
# Inline images must be safe to skip
$3 !~ /^['"]*data:.*?\/.*?base64,/) {
my ($before, $allimg, $img, $after) = ($1, $2, $3, $4);
$img =~ s/^'(.*)'$/$1/ || $img =~ s/^"(.*)"$/$1/;
push(@$urls, $img) if ($urls);
if ($dis == 3) {
# Let server load it in async mode
if ($img !~ /^cid:/) {
my $imgcont = $allimg;
$imgcont =~ s/src=/data-presrc=/g;
$newhtml .= $before.$imgcont;
$masked_img++;
}
else {
$newhtml .= $before.$allimg;
}
}
elsif ($dis == 0) {
# Don't harm image
$newhtml .= $before.$allimg;
}
elsif ($dis == 1) {
# Don't touch unless offsite
if ($img =~ /^(http|https|ftp):/) {
my $imgcont = $allimg;
$imgcont =~ s/src=/data-nosrc=/g;
$newhtml .= $before.$imgcont;
$masked_img++;
}
else {
$newhtml .= $before.$allimg;
}
}
elsif ($dis == 2) {
# Always remove image
$newhtml .= $before;
}
$html = $after;
}
$newhtml .= $html;
if ($masked_img) {
my $masked_img_style =
"";
$masked_img_style =~ s/[\n\r\s]+/ /g;
$masked_img_style = &trim($masked_img_style);
if ($newhtml =~ /<\/body>/) {
$newhtml =~ s/<\/body>/$masked_img_style<\/body>/;
}
else {
$newhtml .= $masked_img_style;
}
}
return $newhtml;
}
# iframe_body(body)
# Returns email message in an iframe HTML element
sub iframe_body
{
my ($body) = @_;
# Do we have theme styles to embed when
# viewing an email? It can be useful for
# themes with dark palettes
my $iframe_theme_file = sub {
my $f =
"$root_directory/$current_theme/unauthenticated/css/_iframe/$_[0].min.css";
return -r $f ? &read_file_contents($f) : '';
};
my $iframe_styles_theme =
&$iframe_theme_file($ENV{'HTTP_X_COLOR_PALETTE_FILE'}) ||
&$iframe_theme_file('quote');
# Mail iframe inner styles
my $iframe_styles = <
html, body { overflow-y: hidden; }
$iframe_styles_theme
EOF
# Add inner styles to the email body
if ($body =~ /<\/body>/) {
$body =~ s/<\/body>/$iframe_styles<\/body>/;
}
else {
$body .= $iframe_styles;
}
$body = &trim("e_escape($body, '"'));
# Email iframe stuff
my $webprefix = &get_webprefix();
my $image_mode = int(defined($in{'images'}) ? $in{'images'} : $userconfig{'view_images'});
my $iframe_body = <