GERONIMO-4121 Add support for 8-bit MIME body encoding in the SMTPTransport.
git-svn-id: https://svn.apache.org/repos/asf/geronimo/javamail/trunk@668672 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
index 14e91a4..45f8290 100644
--- a/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
+++ b/geronimo-javamail_1.4/geronimo-javamail_1.4_provider/src/main/java/org/apache/geronimo/javamail/transport/smtp/SMTPTransport.java
@@ -45,6 +45,8 @@
import javax.mail.event.TransportEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePart;
import javax.net.ssl.SSLSocket;
import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
@@ -137,6 +139,8 @@
protected static final String MAIL_SMTP_EHLO = "ehlo";
protected static final String MAIL_SMTP_ENCODE_TRACE = "encodetrace";
+
+ protected static final String MAIL_SMTP_ALLOW8BITMIME = "allow8bitmime";
protected static final int MIN_MILLIS = 1000 * 60;
@@ -242,6 +246,9 @@
// is TLS enabled on our part?
protected boolean useTLS = false;
+ // should we use 8BITMIME encoding if supported by the server?
+ protected boolean use8bit = false;
+
// do we use SSL for our initial connection?
protected boolean sslConnection = false;
@@ -306,6 +313,8 @@
reportSuccess = isProtocolPropertyTrue(MAIL_SMTP_REPORT_SUCCESS);
// and also check for TLS enablement.
useTLS = isProtocolPropertyTrue(MAIL_SMTP_STARTTLS_ENABLE);
+ // and also check for TLS enablement.
+ use8bit = isProtocolPropertyTrue(MAIL_SMTP_ALLOW8BITMIME);
// get our debug output.
debugStream = session.getDebugOut();
@@ -469,6 +478,23 @@
Address[] sent = null;
Address[] unsent = null;
Address[] invalid = null;
+
+ // If the server supports the 8BITMIME extension, we might need to change the
+ // transfer encoding for the content to allow for direct transmission of the
+ // 8-bit codes.
+ if (supportsExtension("8BITMIME")) {
+ // we only do this if the capability was enabled via a property option or
+ // by explicitly setting the property on the message object.
+ if (use8bit || (message instanceof SMTPMessage && ((SMTPMessage)message).getAllow8bitMIME())) {
+ // go check the content and see if the can convert the transfer encoding to
+ // allow direct 8-bit transmission.
+ if (convertTransferEncoding((MimeMessage)message)) {
+ // if we changed the encoding on any of the parts, then we
+ // need to save the message again
+ message.saveChanges();
+ }
+ }
+ }
try {
// send sender first. If this failed, send a failure notice of the
@@ -2362,4 +2388,119 @@
debugOut("Exception message -> " + e.getMessage());
e.printStackTrace(debugStream);
}
+
+
+ /**
+ * Check to see if a MIME body part can have its
+ * encoding changed from quoted-printable or base64
+ * encoding to 8bit encoding. In order for this
+ * to work, it must follow the rules laid out in
+ * RFC 2045. To qualify for conversion, the text
+ * must be:
+ *
+ * 1) No more than 998 bytes long
+ * 2) All lines are terminated with CRLF sequences
+ * 3) CR and LF characters only occur in properly
+ * formed line separators
+ * 4) No null characters are allowed.
+ *
+ * The conversion will only be applied to text
+ * elements, and this will recurse through the
+ * different elements of MultiPart content.
+ *
+ * @param bodyPart The bodyPart to convert. Initially, this will be
+ * the message itself.
+ *
+ * @return true if any conversion was performed, false if
+ * nothing was converted.
+ */
+ protected boolean convertTransferEncoding(MimePart bodyPart)
+ {
+ boolean converted = false;
+ try {
+ // if this is a multipart element, apply the conversion rules
+ // to each of the parts.
+ if (bodyPart.isMimeType("multipart/")) {
+ MimeMultipart parts = (MimeMultipart)bodyPart.getContent();
+ for (int i = 0; i < parts.getCount(); i++) {
+ // convert each body part, and accumulate the conversion result
+ converted = converted && convertTransferEncoding((MimePart)parts.getBodyPart(i));
+ }
+ }
+ else {
+ // we only do this if the encoding is quoted-printable or base64
+ String encoding = bodyPart.getEncoding();
+ if (encoding != null) {
+ encoding = encoding.toLowerCase();
+ if (encoding.equals("quoted-printable") || encoding.equals("base64")) {
+ // this requires encoding. Read the actual content to see if
+ // it conforms to the 8bit encoding rules.
+ if (isValid8bit(bodyPart.getInputStream())) {
+ // it's valid, so change the transfer encoding to just
+ // pass the data through.
+ bodyPart.setHeader("Content-Transfer-Encoding", "8bit");
+ converted = true; // we've changed something
+ }
+ }
+ }
+ }
+ } catch (MessagingException e) {
+ } catch (IOException e) {
+ }
+ return converted;
+ }
+
+
+ /**
+ * Read the bytes in a stream a test to see if this
+ * conforms to the RFC 2045 rules for 8bit encoding.
+ *
+ * 1) No more than 998 bytes long
+ * 2) All lines are terminated with CRLF sequences
+ * 3) CR and LF characters only occur in properly
+ * formed line separators
+ * 4) No null characters are allowed.
+ *
+ * @param inStream The source input stream.
+ *
+ * @return true if this can be transmitted successfully
+ * using 8bit encoding, false if an alternate encoding
+ * will be required.
+ */
+ protected boolean isValid8bit(InputStream inStream) {
+ try {
+ int ch;
+ int lineLength = 0;
+ while ((ch = inStream.read()) >= 0) {
+ // nulls are decidedly not allowed
+ if (ch == 0) {
+ return false;
+ }
+ // start of a CRLF sequence (potentially)
+ else if (ch == '\r') {
+ // check the next character. There must be one,
+ // and it must be a LF for this to be value
+ ch = inStream.read();
+ if (ch != '\n') {
+ return false;
+ }
+ // reset the line length
+ lineLength = 0;
+ }
+ else {
+ // a normal character
+ lineLength++;
+ // make sure the line is not too long
+ if (lineLength > 998) {
+ return false;
+ }
+ }
+
+ }
+ } catch (IOException e) {
+ return false; // can't read this, don't try passing it
+ }
+ // this converted ok
+ return true;
+ }
}