<?php

// shouldn't have to modify anything in this file...
define('VERSION', "AeroMail 2.23");

include("config.inc");

define('FILTER', IMAP_SERVER_TYPE == "Cyrus" ? "INBOX" : PROG_DIR);
define('IMAP_STR', "{".IMAP_SERVER.":".IMAP_PORT.IMAP_OPTS."}");

function mailbox_log_in( $folder )
{
    global $user, $pass;
    error_reporting(error_reporting() - 2);

    $folder = $folder == "INBOX" ? "INBOX" : construct_folder_str($folder);
    $mbox = imap_open(IMAP_STR . "$folder", $user , $pass);

    error_reporting(error_reporting() + 2);
    return $mbox;
}

function construct_folder_str( $folder )
{
    // Cyrus style: INBOX.Junque
    // UWash style: ./aeromail/Junque

    $folder_str = IMAP_SERVER_TYPE == "Cyrus" ? "INBOX." . $folder : PROG_DIR . $folder;
    return $folder_str;
}

function deconstruct_folder_str( $folder )
{
    // Cyrus style: INBOX.Junque
    // UWash style: ./aeromail/Junque

    $srch_str = IMAP_SERVER_TYPE == "Cyrus" ? "INBOX." : PROG_DIR;

    $folder_str = substr($folder, strlen($srch_str), strlen($folder));

    return $folder_str;
}

function removecrlf($string)
{
    return strtr($string, "\015\012", "  ");
}

function decode_header_string($hed_str)
{
    $output = "";
              
    if($hed_str)
    {
        $element=imap_mime_header_decode($hed_str);
        for($i=0; $i < count($element); $i++) {
            if ($element[$i]->charset == "default")
                $output .= $element[$i]->text;
            else
            {
                $output .= make_utf8($element[$i]->text, $element[$i]->charset);
            }
        }
    }
    return $output;
}

function make_address_list($header_arr)
{
    $out = array();
    if (is_array($header_arr) && count($header_arr)) 
    {
        foreach ($header_arr as $adr)
        {
            $out[] = imap_rfc822_write_address($adr->mailbox, $adr->host, $adr->personal);
        }
    }
    return $out;
}

function make_formaddress_list($header_arr)
{
    $list = make_address_list($header_arr);
    $l = array();
    foreach ($list as $adr)
    {
        $l[] = htmlentities(decode_header_string($adr));
    }
    return implode(", ", $l);
}


function make_clickable_list($header_arr, $folder)
{    
    $list = make_address_list($header_arr);
    $l = array();
    foreach ($list as $adr)
    {
        if (DISPLAY_FULL_ADDR)
          $linkdisplay = hemlspecialchars(decode_header_string($adr));
        else
          $linkdisplay = strip_tags(str_replace("\"","",$adr));
        $l[] = "<a href=\"compose.php?folder="
            . urlencode($folder) 
            . "&amp;to=" . urlencode($adr) . "\">"
            . "$linkdisplay</a>";
    }
    return implode(", ", $l);
}

function list_folders( $mailbox )
{
    $mailboxes = imap_listmailbox($mailbox, IMAP_STR, FILTER . "*");
    if($mailboxes)
    {
        sort($mailboxes);
        $num_boxes = count($mailboxes);
        if (FILTER != "INBOX" && NO_INBOX_EXPLICIT != 1) { echo "<option>INBOX"; }
        for ($index = 0; $index < $num_boxes; $index++)
        {
            $nm = substr($mailboxes[$index], strrpos($mailboxes[$index], "}") + 1, strlen($mailboxes[$index]));
            echo "<option>";
            if ($nm != "INBOX")
            {
                echo deconstruct_folder_str($nm);
            }
            else
            {
                echo "INBOX";
            }
            echo "\n";
        }
    }
    else
    {
        echo "<option>INBOX";
    }
}

function get_mime_type($de_part)
{
    switch ($de_part->type)
    {
        case TYPETEXT:      $mime_type = "text"; break;
        case TYPEMULTIPART: $mime_type = "multipart"; break;
        case TYPEMESSAGE:   $mime_type = "message"; break;
        case TYPEAPPLICATION:   $mime_type = "application"; break;
        case TYPEAUDIO:     $mime_type = "audio"; break;
        case TYPEIMAGE:     $mime_type = "image"; break;
        case TYPEVIDEO:     $mime_type = "video"; break;
        case TYPEMODEL:     $mime_type = "model"; break;
        default:        $mime_type = "unknown";
    }
    return $mime_type;
}

function get_mime_encoding($de_part)
{
    switch ($de_part->encoding)
    {
        case ENCBASE64:         $mime_encoding = "base64"; break;
        case ENCQUOTEDPRINTABLE:    $mime_encoding = "qprint"; break;
        case ENCOTHER:          $mime_encoding = "other"; break;
        default:            $mime_encoding = "other";
    }
    return $mime_encoding;
}

function get_charset($de_part)
{
    $charset = "US-ASCII";
    for ($i = 0; $i < count($de_part->parameters); $i++)
    {
        $param = $de_part->parameters[$i];
        if ($param->attribute == "CHARSET")
        {
            $charset = $param->value;
        }
    }
    return $charset;
}


function make_utf8($str, $charset)
{
    /* Try, but at least return something! */
    if (function_exists('iconv')) 
    {
        if (!$charset)
            $charset="US-ASCII";
        $charset = strtoupper($charset);
        /* Do some processing to catch sloppy charsets */
        $charset = ereg_replace("^WINDOWS-", "CP", $charset);
        /* Found in the wild: "CHINESEBIG5" */
        $charset = ereg_replace("^CHINESE", "", $charset);
        $out = iconv($charset, "UTF-8", $str);
        if ($out)
            return $out;
    }
    return $str;
}


function get_att_name($de_part)
{
    $att_name = 0;
    $att_filename = "Unknown";
    for ($i = 0; $i < count($de_part->parameters); $i++)
    {
        $param = $de_part->parameters[$i];
        if ($param->attribute == "NAME")
        {
            $att_name = $param->value;
        }
    }
    for ($i = 0; $i < count($de_part->dparameters); $i++)
    {
        $param = $de_part->dparameters[$i];
        if ($param->attribute == "FILENAME")
        {
            $att_filename = $param->value;
        }
    }
    if ($att_name)
        return $att_name;
    else
        return $att_filename;
}

function attach_display($de_part, $part_no)
{
    global $mailbox, $folder, $msgnum;
    $mime_type = get_mime_type($de_part);
    $mime_encoding = get_mime_encoding($de_part);

    $att_name = get_att_name($de_part);
    $url_att_name = urlencode($att_name);

    $jnk = "<a href=\"get_attach.php?folder=$folder&amp;msgnum=$msgnum&amp;part_no=$part_no";
    $jnk .= "&amp;type=$mime_type&amp;subtype=$de_part->subtype&amp;name=$url_att_name&amp;encoding=$mime_encoding";
    $jnk .= "\">$att_name</a>";
    return $jnk;
}

/* imap_qprint doesn't handle soft returns correctly */
function my_imap_qprint($dsp)
{
    $dsp = str_replace("=\r\n", "", $dsp);
    $dsp = imap_qprint($dsp);
    return $dsp;
}

function enriched_to_html($dsp)
{
    // Convert to HMTL
    /* Double << is <. Using &lt; makes sure it is no longer taken
       as a command start, as & will become $amp; Later we replace 
       &amp;lt; with &lt; */ 
    $dsp = str_replace("<<", "&lt;", $dsp);
    $dsp = htmlspecialchars($dsp);
    // Simple ones
    $dsp = eregi_replace("&lt;bold&gt;", "<b>", $dsp);
    $dsp = eregi_replace("&lt;/bold&gt;", "</b>", $dsp);
    $dsp = eregi_replace("&lt;italic&gt;", "<i>", $dsp);
    $dsp = eregi_replace("&lt;/italic&gt;", "</i>", $dsp);
    $dsp = eregi_replace("&lt;underline&gt;", "<u>", $dsp);
    $dsp = eregi_replace("&lt;/underline&gt;", "</u>", $dsp);
    $dsp = eregi_replace("&lt;fixed&gt;", "<tt>", $dsp);
    $dsp = eregi_replace("&lt;/fixed&gt;", "</tt>", $dsp);
    $dsp = eregi_replace("&lt;smaller&gt;", "<small>", $dsp);
    $dsp = eregi_replace("&lt;/smaller&gt;", "</small>", $dsp);
    $dsp = eregi_replace("&lt;bigger&gt;", "<big>", $dsp);
    $dsp = eregi_replace("&lt;/bigger&gt;", "</big>", $dsp);
    $dsp = eregi_replace("&lt;center&gt;", "<div align=\"center\">", $dsp);
    $dsp = eregi_replace("&lt;/center&gt;", "</div>", $dsp);
    $dsp = eregi_replace("&lt;flushleft&gt;", "<div align=\"left\">", $dsp);
    $dsp = eregi_replace("&lt;/flushleft&gt;", "</div>", $dsp);
    $dsp = eregi_replace("&lt;flushright&gt;", "<div align=\"right\">", $dsp);
    $dsp = eregi_replace("&lt;/flushright&gt;", "</div>", $dsp);
    $dsp = eregi_replace("&lt;flushboth&gt;", "<div align=\"justify\">", $dsp);
    $dsp = eregi_replace("&lt;/flushboth&gt;", "</div>", $dsp);
    /* nofill -> pre. TODO: Within this block, no newline replacing 
       should be done. */
    $dsp = eregi_replace("&lt;nofill&gt;", "<pre>", $dsp);
    $dsp = eregi_replace("&lt;/nofill&gt;", "</pre>", $dsp);
    /* Paraindent -> blockquote
       Not entriely correct: there is a param telling what kind
       of indenting to do, which we don't support.
       Another thing: My params cannot contain an & char, which
       might be a problem.
     */
    $dsp = eregi_replace("&lt;paraindent&gt;&lt;param&gt;[^&]*&lt;/param&gt;", "<blockquote>", $dsp);
    $dsp = eregi_replace("&lt;/paraindent&gt;", "</blockquote>", $dsp);
    /* Excerpt can optionally have a param telling where excerpt 
       comes from. */
    $dsp = eregi_replace("&lt;excerpt&gt;&lt;param&gt;([^&]*)&lt;/param&gt;", "<hr><br>\\\\1<blockquote>", $dsp);
    $dsp = eregi_replace("&lt;excerpt&gt;", "<hr><blockquote>", $dsp);
    $dsp = eregi_replace("&lt;/excerpt&gt;", "</blockquote>", $dsp);
    /* Lang: Language is ignored */
    $dsp = eregi_replace("&lt;lang&gt;&lt;param&gt;[^&]*&lt;/param&gt;", "", $dsp);
    $dsp = eregi_replace("&lt;/lang&gt;", "", $dsp);
    /* FontFamily */
    $dsp = eregi_replace("&lt;fontfamily&gt;&lt;param&gt;([^&]*)&lt;/param&gt;", "<font face=\"\\\\1\">", $dsp);
    $dsp = eregi_replace("&lt;/fontfamily&gt;", "</font>", $dsp);
    /* color */
    while (eregi("&lt;color&gt;&lt;param&gt;([^&]*)&lt;/param&gt;", $dsp, $regs))
    {
        // regs[1] = xxxx,yyyy,zzzz or colorname
        $c = explode(",", $regs[1]);
        if (count($c) == 1)
            $color = $c[0];
        else
        {
            // First 2 chars of 4char hex string is string /256
            $color = "#" . 
                substr($c[0], 0, 2) . 
                substr($c[1], 0, 2) . 
                substr($c[2], 0, 2);
        }
        $dsp = eregi_replace("&lt;color&gt;&lt;param&gt;[^&]*&lt;/param&gt;", "<font color=\"$color\">", $dsp);
    }
    $dsp = eregi_replace("&lt;/color&gt;", "</font>", $dsp);
    /* Now remove any unrecognised <param>s */
    $dsp = eregi_replace("&lt;param&gt;[^&]*&lt;/param&gt;", "", $dsp);
    /* And last, remove any unknown tags */
    $dsp = eregi_replace("&lt;[^&]*&gt;", "", $dsp);
    /* Now finish the work on << */
    $dsp = eregi_replace("&amp;lt;", "&lt;", $dsp);
    /* Do the newlines */
    while (ereg("\n\n", $dsp))
        $dsp = ereg_replace("\n\n", "<br>\n", $dsp);
    while (ereg("\r\n\r\n", $dsp))
        $dsp = ereg_replace("\r\n\r\n", "<br>\r\n", $dsp);
    /* text/enriched must start with filled text according to RFC */
    $dsp = "<div align=\"justify\">$dsp</div>";
    return $dsp;
}

function make_safe_html($dsp)
{
    $hidetag = array (
        '!doctype',
        'html',
        'head',
        'meta',
        'body'
    );
    foreach ($hidetag as $tag)
    {
        $dsp = eregi_replace("<\\s*(/?)\\s*$tag([^>]*)>",
                             "<!-- \\1$tag\\2 -->", $dsp);
    }
    $hideelt = array (
        'title',
        'style'
    );
    foreach ($hideelt as $tag)
    {
        $dsp = eregi_replace("<\\s*$tag([^>]*)>",
                             "<!-- $tag\\1>", $dsp);
        $dsp = eregi_replace("<\\s*/\\s*$tag([^>]*)>",
                             "</$tag\\1 -->", $dsp);
    }
    return $dsp;
}

function do_inline_display($msgnum, $part_no, $encoding, $charset, $type, $safe = 0)
{
    global $mailbox, $msgnum;
    global $folder;

    /* $safe means: Safe to display in an HTML form. 
       not safe is largely unchanged. */

    $dsp = imap_fetchbody($mailbox, $msgnum, $part_no);
    if ($encoding == "base64") {
        $dsp = imap_base64($dsp);
    }
    if ($encoding == "qprint") {
        $dsp = my_imap_qprint($dsp);
    }
    if (!$safe)
    {
        /* Done, no further processing. */
        echo $dsp;
    }
    else
    {
        $dsp = make_utf8($dsp, $charset);
        if (strtolower($type) == "text/plain")
        {
            /* For text/plain we exhibit the line break behavior defined
               in the config.inc
             */
            if (WRAP_AT_COLUMN)
              $dsp = forcelinebreaks($dsp,WRAP_AT_COLUMN);
            $dsp = htmlspecialchars($dsp);
            $dsp = make_clickable($dsp);
            echo "<pre>$dsp</pre>";
        }
        else if (strtolower($type) == "text/enriched")
        {
            $dsp = enriched_to_html($dsp);
            echo $dsp;
        }
        else if (strtolower($type) == "text/html")
        {
            $struct = imap_fetchstructure($mailbox, $msgnum);
            while (ereg("cid:([^\"]*)", $dsp, $regs))
            {
               /* WARNING: MAKE SURE you quotemeta() on $regs[1]. They can
                  (and often do) contain $ characters.
               */
                $dsp = ereg_replace("cid:" . quotemeta($regs[1]), cid_name($struct, $regs[1]), $dsp);
            }
            $dsp = make_safe_html($dsp);
            echo $dsp;
        }
        else
        {
            /* For unknown types, we just display exactly as sent, so here
               we do use <pre>. */
            $dsp = htmlspecialchars($dsp);
            echo "<pre>\n$dsp</pre>\n";
        }
    }
}

function inline_display($de_part, $part_no)
{
    global $mailbox, $msgnum, $folder;
    
    
    $mime_type = get_mime_type($de_part);
    $type = $mime_type . "/" . $de_part->subtype;
    $mime_encoding = get_mime_encoding($de_part);
    $charset = get_charset($de_part);

    /* Default, simple mail */
    if (strtolower($type) == "text/plain")
    {
        $ret = 1;
    }
    /* Smart types we know of */
    elseif ((strtolower($type) == "text/enriched") or
            (strtolower($type) == "text/html"))
    {
        output_bound(L_MIME_SECTION . ":" , "$mime_type/$de_part->subtype");
        $ret = 1;
    }
    /* All other types: return 0 as we don't know whether we displayed 
       anything useful for the user. */
    else
    {
        output_bound(L_MIME_SECTION .  ":" , "$mime_type/$de_part->subtype");
        $ret = 0;
    }
    /* This is not really useful
    print "<p><a href=\"view_inline.php?folder=" . urlencode($folder) 
                . "&amp;msgnum=$msgnum"
                . "&amp;part_no=$part_no"
                . "&amp;encoding=" . urlencode($mime_encoding)
                . "&amp;charset=" . urlencode($charset)
                . "&amp;type=" . urlencode($type)
                . "\">" . L_MESSAGE . "</a></p>\n";
    */                
    
    /* Embedded objects have a fixed height, which means embedded scrolbars.
       That's not worth it.
    print "<object "
                . "data=\"view_inline.php?folder=" . urlencode($folder) 
                . "&amp;msgnum=$msgnum"
                . "&amp;part_no=$part_no"
                . "&amp;encoding=" . urlencode($mime_encoding)
                . "&amp;charset=" . urlencode($charset)
                . "&amp;type=" . urlencode($type)
                . "\" width=\"100%\">\n";
    */                

    do_inline_display($msgnum, $part_no, $mime_encoding,
                      $charset, $type, 1);
    /*
    echo "</object>\n";
    */
    return $ret;
}

function output_bound($title, $str)
{
    body_left_end();
    body_row_end();

    title_start();
    title_left();
    echo $title;
    title_left_end();
    title_data();
    echo $str;
    title_data_end();
    body_row('class="rowon"');
    body_left('colspan="2" class="body"');
}


function cid_name($struct, $cid_to_find, $numprefix="")
{
    global $folder, $msgnum;
    global $cidsdone;
    $name = "";
    $numparts = !$struct->parts ? "1" : count($struct->parts);
    for($i = 0; $i < $numparts; $i++)
    {
        $part = !$struct->parts[$i] ? $part = $struct : $part = $struct->parts[$i];

        if ($part->parts)
        {
            $subpartnum = $i + 1;
            $name .= cid_name($part, $cid_to_find, $numprefix . $subpartnum . ".");
            if ($name)
            {
                return $name;
            }
        }
        else
        {
            $cid = $part->ifid ? $part->id : "";
            if ($cid == $cid_to_find || $cid == "<$cid_to_find>")
            {
                $subpartnum = $i + 1;
                $att_name = get_att_name($part);
                $name .= "view_image.php?folder=" . $folder 
                    . "&amp;msgnum=$msgnum&amp;part_no=" . $numprefix
                    . $subpartnum
                    . "&amp;type=$part->subtype&amp;name=$att_name";
                $cidsdone[$cid] = 1;
                return $name;
            }
        }
    }
    // Not found, leave it as it is
    return $cid_to_find;
}

/* ciddone is set for an inline image that is processed. If the image data 
   appears later in the message, it doesn't have to be displayed again.
   This is not entirely correct, because when the image data appears first, 
   it is displayed, and again later as inline.
*/
function ciddone($part)
{
    global $cidsdone;
    $cid = $part->ifid ? $part->id : "";
    if ($cid == "")
        return 0;
    return $cidsdone[$cid];
}

function image_display($folder, $msgnum, $de_part, $part_no, $att_name)
{
    output_bound(L_IMAGE . ":" , $att_name);
    echo "\n<img src=\""; 
    echo "view_image.php?folder=" . urlencode($folder) . "&amp;msgnum=$msgnum&amp;part_no=$part_no";
    echo "&amp;type=$de_part->subtype&amp;name=$att_name\">\n<br>\n";
}

// function make_clickable rewritten to follow
// RFC 1738 (URLs)
// RFC 2822 (Internet Message formats)
function make_clickable($text)
{
    global $folder;
    
    // schemes in URLs that we will recognise. These are schemes commonly
    // supported by browsers, others are not necessary.
    $schemes = array (
        // official
        "ftp",
        "http",
        "gopher",
        "news",
        "nntp",
        "telnet",
        "wais",
        "file",
        "https",
        "tn3270",
        // common?
        "aim",
        "irc"
        // DON'T include script types for security
    );

    // Turn &amp; into st that does not contain an &, and is 
    // not appearing in text. Then the URL matcher can exclude &s; so &gt; and
    // &lt; are not matched.
    do {
        $rep = "!=!" . rand() . "!=!";
    } while (eregi($rep, $text));
    $ret = eregi_replace("&amp;", $rep, $text);
    // RFC 1738, but restrict to common schemes
    $schemere = implode("|", $schemes);
    $ret = eregi_replace(
        '(' . "($schemere)" . ':[-$_.+!*\'(),;/?:@=~[:alnum:]#]+)', 
        "<a href=\"\\1\" target\"_new\">\\1</a>", $ret);
    // One more as it is used a lot: something without a scheme, which is 
    // always http://.
    // Try not to match too much, so demand . and /
    // make sure it is surrounded by blanks
    $ret = eregi_replace(
        '(^|[[:space:]])([[:alnum:]]+\.[[:alnum:]\.]+/[-$_.+!*\'(),;/?:@=~[:alnum:]#]+)([[:space:]]|$)',
        "\\1<a href=\"http://\\2\" target\"_new\">\\2</a>\\3", $ret);
    $ret = eregi_replace($rep, "&amp;", $ret);
    $ret = eregi_replace(
        '([-!#$%&\'*+/=?^_`{|}~[:alnum:]]+@[-a-zA-Z0-9.]+)',
        "<a href=\"compose.php?folder=" . urlencode($folder) . "&amp;to=\\1\">\\1</a>", $ret);
    return($ret);
}


function self_with_param($paramname, $value)
{
    global $HTTP_SERVER_VARS;
    $url = $HTTP_SERVER_VARS["REQUEST_URI"];
    /* first strip out the old value of $paramname */
    $url = ereg_replace("\&$paramname=[^&]*", "", $url);
    $url = ereg_replace("$paramname=[^&]*\&", "", $url);
    $url = ereg_replace("\?$paramname=[^&]*", "", $url);
    /* Then insert the new one */
    /* Note: $value != "" is FALSE when $value is the string "0". You gotta 
       love PHP... */
    if ($value != "" or $value == "0")
    {
        if (strpos($url, "?"))
            $url .= "&";
        else
            $url .= "?";
        $url .= "$paramname=$value";
    }
    return htmlspecialchars($url);
}

function forcelinebreaks($dsp,$charcount) {
  /* lastLB = lask-known line break (absolute position)
     nextSP = next-known space, if any (relative to lastLB)
     nextLB = next-known line break, if any (relative to lastLB)
     walkback = the last space prior to the $charcount position
  */
    $lastLB = 0;
    $dsptemp = "";
    for ($n = 0; $n < strlen($dsp); $n++) {
      if (strcmp(substr($dsp,$n,1), "\n") == 0)
        $lastLB = $n;
      $nextSP = strpos(substr($dsp,$lastLB), " ");
      $nextLB = strpos(substr($dsp,$lastLB), "\n");
      if (($nextSP > $charcount) && ($nextLB > $charcount)) {
        // Jump ahead to the next space or newline.
        if ($nextSP > $nextLB) {
          # closest is newline, go there.
          $dsptemp .= substr($dsp,$n,$nextLB + 1);
          $lastLB = $nextLB + $n;
          $n = $lastLB + 1;
        } else {
          # closest is space, go there.
          $dsptemp .= substr($dsp,$n,$nextSP) . "\n";
          $lastLB = $nextSP + $n;
          $n = $lastLB;
        } // end if nextSP > nextLB
      } else if ($nextSP < 1) {
        // do nothing (don't walk back!). just keep walking the string.
      } else if ($n - $lastLB > $charcount) {
        // walk backwards on the string to the last space
        $walkback = strrpos(substr($dsp,$lastLB,$charcount)," ");
        $curpos = $lastLB + $walkback;
        $dsptemp = substr($dsptemp,0,$curpos) . "\n";
        $lastLB = $curpos + 1;
        $n = $lastLB;
      } // end if nextSP > charcount
      $dsptemp .= substr($dsp,$n,1);
    } // end for n < strlen($dsp)
    return $dsptemp;
} //End function forcelinebreaks

function login_prompt()
{
    Header("WWW-Authenticate: Basic realm=\"" . PROG_NAME . "\"");
    Header("HTTP/1.0 401 Unauthorized");
    echo "<meta HTTP-EQUIV=\"Refresh\" CONTENT=\"0; URL= " . SERVER_REDIR . "\">";
    exit;
}

include('layout.inc');

if ((!$PHP_AUTH_USER) or (!$PHP_AUTH_PW) or ($time > time()))
{
    login_prompt();
} 
else
{ 
    if($PHP_AUTH_USER && $PHP_AUTH_PW)
    {
        $user = $PHP_AUTH_USER;
        $pass = $PHP_AUTH_PW;
        $folder = !$folder ? "INBOX" : $folder;
        $mailbox = mailbox_log_in($folder);

        if(!$mailbox)
            login_prompt();
    }
} 

?>
