Mantis - Quercus
Viewing Issue Advanced Details
3923 minor always 03-03-10 12:42 02-10-11 10:53
emil  
 
normal  
new 4.0.5  
open  
none    
none  
0003923: Multipart messages get stuffed into a single mime part by Javamail
(rep by michitopf)

In PHP one can easily send Multipart MIME messages. I use the code below to accomplish this. In Quercus this no longer works. It seems that the whole message is wrapped in a single large MIME part of a JavaMail MimeMessage.
How can I then send Multipart MIME messages in Quercus?

/*----------
 * $parts is an array where each entry represents a MIME part and is in turn another array with following entries:
 * $parts[$i]['content-type']
 * $parts[$i]['content-transfer-encoding'] ... 8bit (default), base64
 * $parts[$i]['filename']
 * $parts[$i]['content']
 */
function send_multipart_mail($to, $from, $subject, $parts) {
    global $mail_error;

    $boundary = '=_' . strtoupper(md5(uniqid('', true)));
    $headers = '';
    if ($from)
        $headers .= 'From: ' . $from . CRLF;
    $headers .= 'MIME-Version: 1.0' . CRLF;
    $headers .= 'Date: ' . date('r') . CRLF; // 'r' ... RFC 822
    $headers .= 'Content-Type: multipart/mixed;' . CRLF;
    $headers .= ' boundary="' . $boundary . '"';
    $headers .= 'Content-Transfer-Encoding: 8bit' . CRLF;
    
    $text = 'This is a message with multiple parts in MIME format.' . CRLF;

    foreach ($parts as $part) {
        $text .= '--' . $boundary . CRLF;
        $text .= 'Content-Type: ' . $part['content-type'] . CRLF;
        $content_transfer_encoding = $part['content-transfer-encoding'];
        if (!$content_transfer_encoding)
            $content_transfer_encoding = '8bit';
        $text .= 'Content-Transfer-Encoding: ' . $content_transfer_encoding . CRLF;
        $filename = $part['filename'];
        if ($filename)
            $text .= 'Content-Disposition: attachment; filename="' . $filename . '"' . CRLF;
        $text .= CRLF;
        $content = $part['content'];
        if ($content_transfer_encoding == 'base64')
            $content = trim(chunk_split(base64_encode($content)));
        $text .= $content . CRLF;
    }
    $text .= '--' . $boundary . '--';

    set_error_handler(mail_error_handler);
    $mail_ok = mail($to, $subject, $text, $headers);
    restore_error_handler();
    return $mail_ok;
}

The code can be used like this (also serves as a testcase):

$text = array(
        'content-type' => 'text/plain; charset="ISO-8859-1"',
        'content' => 'Dear recipient,' . CRLF . CRLF . 'attached you will find the requested information' . CRLF . CRLF . 'regards' . CRLF . 'Server' . CRLF . CRLF
    );
    $attachment = array(
        'content-type' => 'text/xml',
        'content-transfer-encoding' => 'base64',
        'filename' => 'info.xml',
        'content' => '<?xml version="1.0" encoding="ISO-8859-1" ?>' . CRLF .
        '<info>Test</info>');
send_multipart_mail('to@recipient.com', 'from@sender.org', 'Subject', array($text, $attachment));
 Main.java [^] (1,384 bytes) 02-10-11 10:53

Notes
(0004609)
Chris Graham   
05-23-10 07:18   
If you look at the example code you will see:
    $headers .= ' boundary="' . $boundary . '"';
    $headers .= 'Content-Transfer-Encoding: 8bit' . CRLF;

Notice the missing CRLF on the first line.

That's problem "1".

There are also problems "2" and "3"

2...

If you have the Content-Transfer-Encoding header after the Content-Type header you get a Java stack trace:

java.lang.ArrayIndexOutOfBoundsException: 64
    com.caucho.util.CharBuffer.deleteCharAt(CharBuffer.java:472)
    com.caucho.quercus.lib.mail.MailModule.splitHeaders(MailModule.java:386)
    com.caucho.quercus.lib.mail.MailModule.mail(MailModule.java:76)

3..

Likewise, if you put an unneeded CRLF on the end of $headers you get the same stack trace (i.e. the last header line should not have a CRLF on the end).

----

I have confirmed that generally mail works fine on Quercus, it's just some very specific corner cases that are pulled up in the code here :).
(0005042)
doogie   
02-10-11 10:53   
CharBuffer.deleteCharAt(int) will *always* fail, no matter which char you try to delete. The fix is to change the line:

int tail = _length - index + 1;

to:

int tail = _length - index - 1;

Even deleting the the first char will try to copy 1 more character than is available. When a char is deleted, you actually want to shift 1 less than the length.